]> vault307.fbx.one Git - PicoTamachibi.git/blob - lib/sdcard.py
one new line for demonstarion purposes
[PicoTamachibi.git] / lib / sdcard.py
1 """
2 MicroPython driver for SD cards using SPI bus.
3
4 Requires an SPI bus and a CS pin. Provides readblocks and writeblocks
5 methods so the device can be mounted as a filesystem.
6
7 Example usage on pyboard:
8
9 import pyb, sdcard, os
10 sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
11 pyb.mount(sd, '/sd2')
12 os.listdir('/')
13
14 Example usage on ESP8266:
15
16 import machine, sdcard, os
17 sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
18 os.mount(sd, '/sd')
19 os.listdir('/')
20
21 """
22
23 from micropython import const
24 import time
25
26
27 _CMD_TIMEOUT = const(100)
28
29 _R1_IDLE_STATE = const(1 << 0)
30 # R1_ERASE_RESET = const(1 << 1)
31 _R1_ILLEGAL_COMMAND = const(1 << 2)
32 # R1_COM_CRC_ERROR = const(1 << 3)
33 # R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
34 # R1_ADDRESS_ERROR = const(1 << 5)
35 # R1_PARAMETER_ERROR = const(1 << 6)
36 _TOKEN_CMD25 = const(0xFC)
37 _TOKEN_STOP_TRAN = const(0xFD)
38 _TOKEN_DATA = const(0xFE)
39
40
41 class SDCard:
42 def __init__(self, spi, cs, baudrate=1320000):
43 self.spi = spi
44 self.cs = cs
45
46 self.cmdbuf = bytearray(6)
47 self.dummybuf = bytearray(512)
48 self.tokenbuf = bytearray(1)
49 for i in range(512):
50 self.dummybuf[i] = 0xFF
51 self.dummybuf_memoryview = memoryview(self.dummybuf)
52
53 # initialise the card
54 self.init_card(baudrate)
55
56 def init_spi(self, baudrate):
57 try:
58 master = self.spi.MASTER
59 except AttributeError:
60 # on ESP8266
61 self.spi.init(baudrate=baudrate, phase=0, polarity=0)
62 else:
63 # on pyboard
64 self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)
65
66 def init_card(self, baudrate):
67 # init CS pin
68 self.cs.init(self.cs.OUT, value=1)
69
70 # init SPI bus; use low data rate for initialisation
71 self.init_spi(100000)
72
73 # clock card at least 100 cycles with cs high
74 for i in range(16):
75 self.spi.write(b"\xff")
76
77 # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)
78 for _ in range(5):
79 if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
80 break
81 else:
82 raise OSError("no SD card")
83
84 # CMD8: determine card version
85 r = self.cmd(8, 0x01AA, 0x87, 4)
86 if r == _R1_IDLE_STATE:
87 self.init_card_v2()
88 elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
89 self.init_card_v1()
90 else:
91 raise OSError("couldn't determine SD card version")
92
93 # get the number of sectors
94 # CMD9: response R2 (R1 byte + 16-byte block read)
95 if self.cmd(9, 0, 0, 0, False) != 0:
96 raise OSError("no response from SD card")
97 csd = bytearray(16)
98 self.readinto(csd)
99 if csd[0] & 0xC0 == 0x40: # CSD version 2.0
100 self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
101 elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
102 c_size = (csd[6] & 0b11) << 10 | csd[7] << 2 | csd[8] >> 6
103 c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7
104 read_bl_len = csd[5] & 0b1111
105 capacity = (c_size + 1) * (2 ** (c_size_mult + 2)) * (2**read_bl_len)
106 self.sectors = capacity // 512
107 else:
108 raise OSError("SD card CSD format not supported")
109 # print('sectors', self.sectors)
110
111 # CMD16: set block length to 512 bytes
112 if self.cmd(16, 512, 0) != 0:
113 raise OSError("can't set 512 block size")
114
115 # set to high data rate now that it's initialised
116 self.init_spi(baudrate)
117
118 def init_card_v1(self):
119 for i in range(_CMD_TIMEOUT):
120 time.sleep_ms(50)
121 self.cmd(55, 0, 0)
122 if self.cmd(41, 0, 0) == 0:
123 # SDSC card, uses byte addressing in read/write/erase commands
124 self.cdv = 512
125 # print("[SDCard] v1 card")
126 return
127 raise OSError("timeout waiting for v1 card")
128
129 def init_card_v2(self):
130 for i in range(_CMD_TIMEOUT):
131 time.sleep_ms(50)
132 self.cmd(58, 0, 0, 4)
133 self.cmd(55, 0, 0)
134 if self.cmd(41, 0x40000000, 0) == 0:
135 self.cmd(58, 0, 0, -4) # 4-byte response, negative means keep the first byte
136 ocr = self.tokenbuf[0] # get first byte of response, which is OCR
137 if not ocr & 0x40:
138 # SDSC card, uses byte addressing in read/write/erase commands
139 self.cdv = 512
140 else:
141 # SDHC/SDXC card, uses block addressing in read/write/erase commands
142 self.cdv = 1
143 # print("[SDCard] v2 card")
144 return
145 raise OSError("timeout waiting for v2 card")
146
147 def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
148 self.cs(0)
149
150 # create and send the command
151 buf = self.cmdbuf
152 buf[0] = 0x40 | cmd
153 buf[1] = arg >> 24
154 buf[2] = arg >> 16
155 buf[3] = arg >> 8
156 buf[4] = arg
157 buf[5] = crc
158 self.spi.write(buf)
159
160 if skip1:
161 self.spi.readinto(self.tokenbuf, 0xFF)
162
163 # wait for the response (response[7] == 0)
164 for i in range(_CMD_TIMEOUT):
165 self.spi.readinto(self.tokenbuf, 0xFF)
166 response = self.tokenbuf[0]
167 if not (response & 0x80):
168 # this could be a big-endian integer that we are getting here
169 # if final<0 then store the first byte to tokenbuf and discard the rest
170 if final < 0:
171 self.spi.readinto(self.tokenbuf, 0xFF)
172 final = -1 - final
173 for j in range(final):
174 self.spi.write(b"\xff")
175 if release:
176 self.cs(1)
177 self.spi.write(b"\xff")
178 return response
179
180 # timeout
181 self.cs(1)
182 self.spi.write(b"\xff")
183 return -1
184
185 def readinto(self, buf):
186 self.cs(0)
187
188 # read until start byte (0xff)
189 for i in range(_CMD_TIMEOUT):
190 self.spi.readinto(self.tokenbuf, 0xFF)
191 if self.tokenbuf[0] == _TOKEN_DATA:
192 break
193 time.sleep_ms(1)
194 else:
195 self.cs(1)
196 raise OSError("timeout waiting for response")
197
198 # read data
199 mv = self.dummybuf_memoryview
200 if len(buf) != len(mv):
201 mv = mv[: len(buf)]
202 self.spi.write_readinto(mv, buf)
203
204 # read checksum
205 self.spi.write(b"\xff")
206 self.spi.write(b"\xff")
207
208 self.cs(1)
209 self.spi.write(b"\xff")
210
211 def write(self, token, buf):
212 self.cs(0)
213
214 # send: start of block, data, checksum
215 self.spi.read(1, token)
216 self.spi.write(buf)
217 self.spi.write(b"\xff")
218 self.spi.write(b"\xff")
219
220 # check the response
221 if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
222 self.cs(1)
223 self.spi.write(b"\xff")
224 return
225
226 # wait for write to finish
227 while self.spi.read(1, 0xFF)[0] == 0:
228 pass
229
230 self.cs(1)
231 self.spi.write(b"\xff")
232
233 def write_token(self, token):
234 self.cs(0)
235 self.spi.read(1, token)
236 self.spi.write(b"\xff")
237 # wait for write to finish
238 while self.spi.read(1, 0xFF)[0] == 0x00:
239 pass
240
241 self.cs(1)
242 self.spi.write(b"\xff")
243
244 def readblocks(self, block_num, buf):
245 # workaround for shared bus, required for (at least) some Kingston
246 # devices, ensure MOSI is high before starting transaction
247 self.spi.write(b"\xff")
248
249 nblocks = len(buf) // 512
250 assert nblocks and not len(buf) % 512, "Buffer length is invalid"
251 if nblocks == 1:
252 # CMD17: set read address for single block
253 if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
254 # release the card
255 self.cs(1)
256 raise OSError(5) # EIO
257 # receive the data and release card
258 self.readinto(buf)
259 else:
260 # CMD18: set read address for multiple blocks
261 if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
262 # release the card
263 self.cs(1)
264 raise OSError(5) # EIO
265 offset = 0
266 mv = memoryview(buf)
267 while nblocks:
268 # receive the data and release card
269 self.readinto(mv[offset : offset + 512])
270 offset += 512
271 nblocks -= 1
272 if self.cmd(12, 0, 0xFF, skip1=True):
273 raise OSError(5) # EIO
274
275 def writeblocks(self, block_num, buf):
276 # workaround for shared bus, required for (at least) some Kingston
277 # devices, ensure MOSI is high before starting transaction
278 self.spi.write(b"\xff")
279
280 nblocks, err = divmod(len(buf), 512)
281 assert nblocks and not err, "Buffer length is invalid"
282 if nblocks == 1:
283 # CMD24: set write address for single block
284 if self.cmd(24, block_num * self.cdv, 0) != 0:
285 raise OSError(5) # EIO
286
287 # send the data
288 self.write(_TOKEN_DATA, buf)
289 else:
290 # CMD25: set write address for first block
291 if self.cmd(25, block_num * self.cdv, 0) != 0:
292 raise OSError(5) # EIO
293 # send the data
294 offset = 0
295 mv = memoryview(buf)
296 while nblocks:
297 self.write(_TOKEN_CMD25, mv[offset : offset + 512])
298 offset += 512
299 nblocks -= 1
300 self.write_token(_TOKEN_STOP_TRAN)
301
302 def ioctl(self, op, arg):
303 if op == 4: # get number of blocks
304 return self.sectors
305 if op == 5: # get block size in bytes
306 return 512
307
308
309 __version__ = '0.1.0'