Skip to content

Commit c934022

Browse files
committed
add sd card library from micropython, will use it for writing telemetry
1 parent 6188f9e commit c934022

File tree

1 file changed

+307
-0
lines changed

1 file changed

+307
-0
lines changed

telemetry/sdcard.py

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

0 commit comments

Comments
 (0)