Skip to content

Commit cf5ed97

Browse files
committed
micropython/drivers: Move "sdcard" driver from main repo.
Signed-off-by: Jim Mussared <[email protected]>
1 parent 33b5132 commit cf5ed97

File tree

3 files changed

+361
-0
lines changed

3 files changed

+361
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module("sdcard.py", opt=3)
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
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+
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[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+
nblocks = len(buf) // 512
247+
assert nblocks and not len(buf) % 512, "Buffer length is invalid"
248+
if nblocks == 1:
249+
# CMD17: set read address for single block
250+
if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
251+
# release the card
252+
self.cs(1)
253+
raise OSError(5) # EIO
254+
# receive the data and release card
255+
self.readinto(buf)
256+
else:
257+
# CMD18: set read address for multiple blocks
258+
if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
259+
# release the card
260+
self.cs(1)
261+
raise OSError(5) # EIO
262+
offset = 0
263+
mv = memoryview(buf)
264+
while nblocks:
265+
# receive the data and release card
266+
self.readinto(mv[offset : offset + 512])
267+
offset += 512
268+
nblocks -= 1
269+
if self.cmd(12, 0, 0xFF, skip1=True):
270+
raise OSError(5) # EIO
271+
272+
def writeblocks(self, block_num, buf):
273+
nblocks, err = divmod(len(buf), 512)
274+
assert nblocks and not err, "Buffer length is invalid"
275+
if nblocks == 1:
276+
# CMD24: set write address for single block
277+
if self.cmd(24, block_num * self.cdv, 0) != 0:
278+
raise OSError(5) # EIO
279+
280+
# send the data
281+
self.write(_TOKEN_DATA, buf)
282+
else:
283+
# CMD25: set write address for first block
284+
if self.cmd(25, block_num * self.cdv, 0) != 0:
285+
raise OSError(5) # EIO
286+
# send the data
287+
offset = 0
288+
mv = memoryview(buf)
289+
while nblocks:
290+
self.write(_TOKEN_CMD25, mv[offset : offset + 512])
291+
offset += 512
292+
nblocks -= 1
293+
self.write_token(_TOKEN_STOP_TRAN)
294+
295+
def ioctl(self, op, arg):
296+
if op == 4: # get number of blocks
297+
return self.sectors
298+
if op == 5: # get block size in bytes
299+
return 512
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Test for sdcard block protocol
2+
# Peter hinch 30th Jan 2016
3+
import os, sdcard, machine
4+
5+
6+
def sdtest():
7+
spi = machine.SPI(1)
8+
spi.init() # Ensure right baudrate
9+
sd = sdcard.SDCard(spi, machine.Pin.board.X21) # Compatible with PCB
10+
vfs = os.VfsFat(sd)
11+
os.mount(vfs, "/fc")
12+
print("Filesystem check")
13+
print(os.listdir("/fc"))
14+
15+
line = "abcdefghijklmnopqrstuvwxyz\n"
16+
lines = line * 200 # 5400 chars
17+
short = "1234567890\n"
18+
19+
fn = "/fc/rats.txt"
20+
print()
21+
print("Multiple block read/write")
22+
with open(fn, "w") as f:
23+
n = f.write(lines)
24+
print(n, "bytes written")
25+
n = f.write(short)
26+
print(n, "bytes written")
27+
n = f.write(lines)
28+
print(n, "bytes written")
29+
30+
with open(fn, "r") as f:
31+
result1 = f.read()
32+
print(len(result1), "bytes read")
33+
34+
fn = "/fc/rats1.txt"
35+
print()
36+
print("Single block read/write")
37+
with open(fn, "w") as f:
38+
n = f.write(short) # one block
39+
print(n, "bytes written")
40+
41+
with open(fn, "r") as f:
42+
result2 = f.read()
43+
print(len(result2), "bytes read")
44+
45+
os.umount("/fc")
46+
47+
print()
48+
print("Verifying data read back")
49+
success = True
50+
if result1 == "".join((lines, short, lines)):
51+
print("Large file Pass")
52+
else:
53+
print("Large file Fail")
54+
success = False
55+
if result2 == short:
56+
print("Small file Pass")
57+
else:
58+
print("Small file Fail")
59+
success = False
60+
print()
61+
print("Tests", "passed" if success else "failed")

0 commit comments

Comments
 (0)