|
|
""" |
|
|
MicroPython driver for SD cards using SPI bus. |
|
|
|
|
|
Requires an SPI bus and a CS pin. Provides readblocks and writeblocks |
|
|
methods so the device can be mounted as a filesystem. |
|
|
|
|
|
Example usage on pyboard: |
|
|
|
|
|
import pyb, sdcard, os |
|
|
sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5) |
|
|
pyb.mount(sd, '/sd2') |
|
|
os.listdir('/') |
|
|
|
|
|
Example usage on ESP8266: |
|
|
|
|
|
import machine, sdcard, os |
|
|
sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15)) |
|
|
os.mount(sd, '/sd') |
|
|
os.listdir('/') |
|
|
|
|
|
Copied from source: https://raw.githubusercontent.com/micropython/micropython-lib/master/micropython/drivers/storage/sdcard/sdcard.py |
|
|
|
|
|
""" |
|
|
|
|
|
import time |
|
|
|
|
|
from micropython import const |
|
|
|
|
|
_CMD_TIMEOUT = const(100) |
|
|
|
|
|
_R1_IDLE_STATE = const(1 << 0) |
|
|
|
|
|
_R1_ILLEGAL_COMMAND = const(1 << 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_TOKEN_CMD25 = const(0xFC) |
|
|
_TOKEN_STOP_TRAN = const(0xFD) |
|
|
_TOKEN_DATA = const(0xFE) |
|
|
|
|
|
|
|
|
class SDCard: |
|
|
def __init__(self, spi, cs, baudrate=1320000): |
|
|
self.spi = spi |
|
|
self.cs = cs |
|
|
|
|
|
self.cmdbuf = bytearray(6) |
|
|
self.dummybuf = bytearray(512) |
|
|
self.tokenbuf = bytearray(1) |
|
|
for i in range(512): |
|
|
self.dummybuf[i] = 0xFF |
|
|
self.dummybuf_memoryview = memoryview(self.dummybuf) |
|
|
|
|
|
|
|
|
self.init_card(baudrate) |
|
|
|
|
|
def init_spi(self, baudrate): |
|
|
try: |
|
|
master = self.spi.MASTER |
|
|
except AttributeError: |
|
|
|
|
|
self.spi.init(baudrate=baudrate, phase=0, polarity=0) |
|
|
else: |
|
|
|
|
|
self.spi.init(master, baudrate=baudrate, phase=0, polarity=0) |
|
|
|
|
|
def init_card(self, baudrate): |
|
|
|
|
|
self.cs.init(self.cs.OUT, value=1) |
|
|
|
|
|
|
|
|
self.init_spi(100000) |
|
|
|
|
|
|
|
|
for i in range(16): |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
|
|
|
for _ in range(5): |
|
|
if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE: |
|
|
break |
|
|
else: |
|
|
raise OSError("no SD card") |
|
|
|
|
|
|
|
|
r = self.cmd(8, 0x01AA, 0x87, 4) |
|
|
if r == _R1_IDLE_STATE: |
|
|
self.init_card_v2() |
|
|
elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND): |
|
|
self.init_card_v1() |
|
|
else: |
|
|
raise OSError("couldn't determine SD card version") |
|
|
|
|
|
|
|
|
|
|
|
if self.cmd(9, 0, 0, 0, False) != 0: |
|
|
raise OSError("no response from SD card") |
|
|
csd = bytearray(16) |
|
|
self.readinto(csd) |
|
|
if csd[0] & 0xC0 == 0x40: |
|
|
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024 |
|
|
elif csd[0] & 0xC0 == 0x00: |
|
|
c_size = (csd[6] & 0b11) << 10 | csd[7] << 2 | csd[8] >> 6 |
|
|
c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7 |
|
|
read_bl_len = csd[5] & 0b1111 |
|
|
capacity = (c_size + 1) * (2 ** (c_size_mult + 2)) * (2**read_bl_len) |
|
|
self.sectors = capacity // 512 |
|
|
else: |
|
|
raise OSError("SD card CSD format not supported") |
|
|
|
|
|
|
|
|
|
|
|
if self.cmd(16, 512, 0) != 0: |
|
|
raise OSError("can't set 512 block size") |
|
|
|
|
|
|
|
|
self.init_spi(baudrate) |
|
|
|
|
|
def init_card_v1(self): |
|
|
for i in range(_CMD_TIMEOUT): |
|
|
time.sleep_ms(50) |
|
|
self.cmd(55, 0, 0) |
|
|
if self.cmd(41, 0, 0) == 0: |
|
|
|
|
|
self.cdv = 512 |
|
|
|
|
|
return |
|
|
raise OSError("timeout waiting for v1 card") |
|
|
|
|
|
def init_card_v2(self): |
|
|
for i in range(_CMD_TIMEOUT): |
|
|
time.sleep_ms(50) |
|
|
self.cmd(58, 0, 0, 4) |
|
|
self.cmd(55, 0, 0) |
|
|
if self.cmd(41, 0x40000000, 0) == 0: |
|
|
self.cmd( |
|
|
58, 0, 0, -4 |
|
|
) |
|
|
ocr = self.tokenbuf[0] |
|
|
if not ocr & 0x40: |
|
|
|
|
|
self.cdv = 512 |
|
|
else: |
|
|
|
|
|
self.cdv = 1 |
|
|
|
|
|
return |
|
|
raise OSError("timeout waiting for v2 card") |
|
|
|
|
|
def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False): |
|
|
self.cs(0) |
|
|
|
|
|
|
|
|
buf = self.cmdbuf |
|
|
buf[0] = 0x40 | cmd |
|
|
buf[1] = arg >> 24 |
|
|
buf[2] = arg >> 16 |
|
|
buf[3] = arg >> 8 |
|
|
buf[4] = arg |
|
|
buf[5] = crc |
|
|
self.spi.write(buf) |
|
|
|
|
|
if skip1: |
|
|
self.spi.readinto(self.tokenbuf, 0xFF) |
|
|
|
|
|
|
|
|
for i in range(_CMD_TIMEOUT): |
|
|
self.spi.readinto(self.tokenbuf, 0xFF) |
|
|
response = self.tokenbuf[0] |
|
|
if not (response & 0x80): |
|
|
|
|
|
|
|
|
if final < 0: |
|
|
self.spi.readinto(self.tokenbuf, 0xFF) |
|
|
final = -1 - final |
|
|
for j in range(final): |
|
|
self.spi.write(b"\xff") |
|
|
if release: |
|
|
self.cs(1) |
|
|
self.spi.write(b"\xff") |
|
|
return response |
|
|
|
|
|
|
|
|
self.cs(1) |
|
|
self.spi.write(b"\xff") |
|
|
return -1 |
|
|
|
|
|
def readinto(self, buf): |
|
|
self.cs(0) |
|
|
|
|
|
|
|
|
for i in range(_CMD_TIMEOUT): |
|
|
self.spi.readinto(self.tokenbuf, 0xFF) |
|
|
if self.tokenbuf[0] == _TOKEN_DATA: |
|
|
break |
|
|
time.sleep_ms(1) |
|
|
else: |
|
|
self.cs(1) |
|
|
raise OSError("timeout waiting for response") |
|
|
|
|
|
|
|
|
mv = self.dummybuf_memoryview |
|
|
if len(buf) != len(mv): |
|
|
mv = mv[: len(buf)] |
|
|
self.spi.write_readinto(mv, buf) |
|
|
|
|
|
|
|
|
self.spi.write(b"\xff") |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
self.cs(1) |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
def write(self, token, buf): |
|
|
self.cs(0) |
|
|
|
|
|
|
|
|
self.spi.read(1, token) |
|
|
self.spi.write(buf) |
|
|
self.spi.write(b"\xff") |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
|
|
|
if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05: |
|
|
self.cs(1) |
|
|
self.spi.write(b"\xff") |
|
|
return |
|
|
|
|
|
|
|
|
while self.spi.read(1, 0xFF)[0] == 0: |
|
|
pass |
|
|
|
|
|
self.cs(1) |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
def write_token(self, token): |
|
|
self.cs(0) |
|
|
self.spi.read(1, token) |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
while self.spi.read(1, 0xFF)[0] == 0x00: |
|
|
pass |
|
|
|
|
|
self.cs(1) |
|
|
self.spi.write(b"\xff") |
|
|
|
|
|
def readblocks(self, block_num, buf): |
|
|
nblocks = len(buf) // 512 |
|
|
assert nblocks and not len(buf) % 512, "Buffer length is invalid" |
|
|
if nblocks == 1: |
|
|
|
|
|
if self.cmd(17, block_num * self.cdv, 0, release=False) != 0: |
|
|
|
|
|
self.cs(1) |
|
|
raise OSError(5) |
|
|
|
|
|
self.readinto(buf) |
|
|
else: |
|
|
|
|
|
if self.cmd(18, block_num * self.cdv, 0, release=False) != 0: |
|
|
|
|
|
self.cs(1) |
|
|
raise OSError(5) |
|
|
offset = 0 |
|
|
mv = memoryview(buf) |
|
|
while nblocks: |
|
|
|
|
|
self.readinto(mv[offset : offset + 512]) |
|
|
offset += 512 |
|
|
nblocks -= 1 |
|
|
if self.cmd(12, 0, 0xFF, skip1=True): |
|
|
raise OSError(5) |
|
|
|
|
|
def writeblocks(self, block_num, buf): |
|
|
nblocks, err = divmod(len(buf), 512) |
|
|
assert nblocks and not err, "Buffer length is invalid" |
|
|
if nblocks == 1: |
|
|
|
|
|
if self.cmd(24, block_num * self.cdv, 0) != 0: |
|
|
raise OSError(5) |
|
|
|
|
|
|
|
|
self.write(_TOKEN_DATA, buf) |
|
|
else: |
|
|
|
|
|
if self.cmd(25, block_num * self.cdv, 0) != 0: |
|
|
raise OSError(5) |
|
|
|
|
|
offset = 0 |
|
|
mv = memoryview(buf) |
|
|
while nblocks: |
|
|
self.write(_TOKEN_CMD25, mv[offset : offset + 512]) |
|
|
offset += 512 |
|
|
nblocks -= 1 |
|
|
self.write_token(_TOKEN_STOP_TRAN) |
|
|
|
|
|
def ioctl(self, op, arg): |
|
|
if op == 4: |
|
|
return self.sectors |
|
|
if op == 5: |
|
|
return 512 |
|
|
|