2014-12-27 15:23:14 -05:00
|
|
|
"""
|
2017-06-30 03:22:17 -04:00
|
|
|
MicroPython driver for SD cards using SPI bus.
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
Requires an SPI bus and a CS pin. Provides readblocks and writeblocks
|
|
|
|
methods so the device can be mounted as a filesystem.
|
|
|
|
|
2016-08-25 04:54:59 -04:00
|
|
|
Example usage on pyboard:
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
import pyb, sdcard, os
|
|
|
|
sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
|
|
|
|
pyb.mount(sd, '/sd2')
|
|
|
|
os.listdir('/')
|
|
|
|
|
2016-08-25 04:54:59 -04:00
|
|
|
Example usage on ESP8266:
|
|
|
|
|
|
|
|
import machine, sdcard, os
|
2018-01-16 15:16:25 -05:00
|
|
|
sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
|
2018-02-18 07:40:54 -05:00
|
|
|
os.mount(sd, '/sd')
|
|
|
|
os.listdir('/')
|
2016-08-25 04:54:59 -04:00
|
|
|
|
2014-12-27 15:23:14 -05:00
|
|
|
"""
|
|
|
|
|
2016-11-02 21:41:11 -04:00
|
|
|
from micropython import const
|
2016-08-25 04:54:59 -04:00
|
|
|
import time
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
|
2016-08-25 04:54:59 -04:00
|
|
|
_CMD_TIMEOUT = const(100)
|
|
|
|
|
|
|
|
_R1_IDLE_STATE = const(1 << 0)
|
2020-02-26 23:36:53 -05:00
|
|
|
# R1_ERASE_RESET = const(1 << 1)
|
2016-08-25 04:54:59 -04:00
|
|
|
_R1_ILLEGAL_COMMAND = const(1 << 2)
|
2020-02-26 23:36:53 -05:00
|
|
|
# R1_COM_CRC_ERROR = const(1 << 3)
|
|
|
|
# R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
|
|
|
|
# R1_ADDRESS_ERROR = const(1 << 5)
|
|
|
|
# R1_PARAMETER_ERROR = const(1 << 6)
|
|
|
|
_TOKEN_CMD25 = const(0xFC)
|
|
|
|
_TOKEN_STOP_TRAN = const(0xFD)
|
|
|
|
_TOKEN_DATA = const(0xFE)
|
2016-08-25 04:54:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
class SDCard:
|
2014-12-27 15:23:14 -05:00
|
|
|
def __init__(self, spi, cs):
|
|
|
|
self.spi = spi
|
|
|
|
self.cs = cs
|
|
|
|
|
|
|
|
self.cmdbuf = bytearray(6)
|
|
|
|
self.dummybuf = bytearray(512)
|
2017-12-19 17:54:24 -05:00
|
|
|
self.tokenbuf = bytearray(1)
|
2014-12-27 15:23:14 -05:00
|
|
|
for i in range(512):
|
2020-02-26 23:36:53 -05:00
|
|
|
self.dummybuf[i] = 0xFF
|
2014-12-27 15:23:14 -05:00
|
|
|
self.dummybuf_memoryview = memoryview(self.dummybuf)
|
|
|
|
|
|
|
|
# initialise the card
|
|
|
|
self.init_card()
|
|
|
|
|
2016-08-25 04:54:59 -04:00
|
|
|
def init_spi(self, baudrate):
|
|
|
|
try:
|
|
|
|
master = self.spi.MASTER
|
|
|
|
except AttributeError:
|
|
|
|
# on ESP8266
|
|
|
|
self.spi.init(baudrate=baudrate, phase=0, polarity=0)
|
|
|
|
else:
|
|
|
|
# on pyboard
|
|
|
|
self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)
|
|
|
|
|
2014-12-27 15:23:14 -05:00
|
|
|
def init_card(self):
|
|
|
|
# init CS pin
|
2016-08-25 04:54:59 -04:00
|
|
|
self.cs.init(self.cs.OUT, value=1)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# init SPI bus; use low data rate for initialisation
|
2016-08-25 04:54:59 -04:00
|
|
|
self.init_spi(100000)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# clock card at least 100 cycles with cs high
|
|
|
|
for i in range(16):
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
2016-08-25 04:54:59 -04:00
|
|
|
# CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)
|
2015-10-09 19:07:40 -04:00
|
|
|
for _ in range(5):
|
2016-08-25 04:54:59 -04:00
|
|
|
if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
|
2015-10-09 19:07:40 -04:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise OSError("no SD card")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# CMD8: determine card version
|
2020-02-26 23:36:53 -05:00
|
|
|
r = self.cmd(8, 0x01AA, 0x87, 4)
|
2016-08-25 04:54:59 -04:00
|
|
|
if r == _R1_IDLE_STATE:
|
2014-12-27 15:23:14 -05:00
|
|
|
self.init_card_v2()
|
2016-08-25 04:54:59 -04:00
|
|
|
elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
|
2014-12-27 15:23:14 -05:00
|
|
|
self.init_card_v1()
|
|
|
|
else:
|
|
|
|
raise OSError("couldn't determine SD card version")
|
|
|
|
|
|
|
|
# get the number of sectors
|
|
|
|
# CMD9: response R2 (R1 byte + 16-byte block read)
|
|
|
|
if self.cmd(9, 0, 0, 0, False) != 0:
|
|
|
|
raise OSError("no response from SD card")
|
|
|
|
csd = bytearray(16)
|
|
|
|
self.readinto(csd)
|
2020-02-26 23:36:53 -05:00
|
|
|
if csd[0] & 0xC0 == 0x40: # CSD version 2.0
|
2018-06-15 04:02:40 -04:00
|
|
|
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
|
2020-02-26 23:36:53 -05:00
|
|
|
elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
|
2017-12-19 19:15:26 -05:00
|
|
|
c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4
|
|
|
|
c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7
|
|
|
|
self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2))
|
|
|
|
else:
|
2014-12-27 15:23:14 -05:00
|
|
|
raise OSError("SD card CSD format not supported")
|
2020-02-26 23:36:53 -05:00
|
|
|
# print('sectors', self.sectors)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# CMD16: set block length to 512 bytes
|
|
|
|
if self.cmd(16, 512, 0) != 0:
|
|
|
|
raise OSError("can't set 512 block size")
|
|
|
|
|
|
|
|
# set to high data rate now that it's initialised
|
2016-08-25 04:54:59 -04:00
|
|
|
self.init_spi(1320000)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
def init_card_v1(self):
|
2016-08-25 04:54:59 -04:00
|
|
|
for i in range(_CMD_TIMEOUT):
|
2014-12-27 15:23:14 -05:00
|
|
|
self.cmd(55, 0, 0)
|
|
|
|
if self.cmd(41, 0, 0) == 0:
|
|
|
|
self.cdv = 512
|
2020-02-26 23:36:53 -05:00
|
|
|
# print("[SDCard] v1 card")
|
2014-12-27 15:23:14 -05:00
|
|
|
return
|
|
|
|
raise OSError("timeout waiting for v1 card")
|
|
|
|
|
|
|
|
def init_card_v2(self):
|
2016-08-25 04:54:59 -04:00
|
|
|
for i in range(_CMD_TIMEOUT):
|
|
|
|
time.sleep_ms(50)
|
2014-12-27 15:23:14 -05:00
|
|
|
self.cmd(58, 0, 0, 4)
|
|
|
|
self.cmd(55, 0, 0)
|
|
|
|
if self.cmd(41, 0x40000000, 0) == 0:
|
|
|
|
self.cmd(58, 0, 0, 4)
|
|
|
|
self.cdv = 1
|
2020-02-26 23:36:53 -05:00
|
|
|
# print("[SDCard] v2 card")
|
2014-12-27 15:23:14 -05:00
|
|
|
return
|
|
|
|
raise OSError("timeout waiting for v2 card")
|
|
|
|
|
2017-12-19 17:54:24 -05:00
|
|
|
def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(0)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# create and send the command
|
|
|
|
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
|
2016-08-25 04:54:59 -04:00
|
|
|
self.spi.write(buf)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
2017-12-19 17:54:24 -05:00
|
|
|
if skip1:
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.readinto(self.tokenbuf, 0xFF)
|
2017-12-19 17:54:24 -05:00
|
|
|
|
2016-12-20 21:48:44 -05:00
|
|
|
# wait for the response (response[7] == 0)
|
2016-08-25 04:54:59 -04:00
|
|
|
for i in range(_CMD_TIMEOUT):
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.readinto(self.tokenbuf, 0xFF)
|
2017-12-19 17:54:24 -05:00
|
|
|
response = self.tokenbuf[0]
|
2014-12-27 15:23:14 -05:00
|
|
|
if not (response & 0x80):
|
|
|
|
# this could be a big-endian integer that we are getting here
|
|
|
|
for j in range(final):
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
if release:
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
return response
|
|
|
|
|
|
|
|
# timeout
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
return -1
|
|
|
|
|
|
|
|
def readinto(self, buf):
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(0)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# read until start byte (0xff)
|
2019-11-04 04:25:16 -05:00
|
|
|
for i in range(_CMD_TIMEOUT):
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.readinto(self.tokenbuf, 0xFF)
|
2018-06-28 19:32:02 -04:00
|
|
|
if self.tokenbuf[0] == _TOKEN_DATA:
|
2017-12-19 17:54:24 -05:00
|
|
|
break
|
2021-04-23 09:44:37 -04:00
|
|
|
time.sleep_ms(1)
|
2019-11-04 04:25:16 -05:00
|
|
|
else:
|
|
|
|
self.cs(1)
|
|
|
|
raise OSError("timeout waiting for response")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# read data
|
2017-12-19 17:54:24 -05:00
|
|
|
mv = self.dummybuf_memoryview
|
|
|
|
if len(buf) != len(mv):
|
2020-02-26 23:36:53 -05:00
|
|
|
mv = mv[: len(buf)]
|
2016-08-25 04:54:59 -04:00
|
|
|
self.spi.write_readinto(mv, buf)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# read checksum
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
2016-01-30 02:21:43 -05:00
|
|
|
def write(self, token, buf):
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(0)
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# send: start of block, data, checksum
|
2016-08-25 04:54:59 -04:00
|
|
|
self.spi.read(1, token)
|
|
|
|
self.spi.write(buf)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
# check the response
|
2020-02-26 23:36:53 -05:00
|
|
|
if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
# wait for write to finish
|
2020-02-26 23:36:53 -05:00
|
|
|
while self.spi.read(1, 0xFF)[0] == 0:
|
2014-12-27 15:23:14 -05:00
|
|
|
pass
|
|
|
|
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2014-12-27 15:23:14 -05:00
|
|
|
|
2016-01-30 02:21:43 -05:00
|
|
|
def write_token(self, token):
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(0)
|
2016-08-25 04:54:59 -04:00
|
|
|
self.spi.read(1, token)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2016-01-30 02:21:43 -05:00
|
|
|
# wait for write to finish
|
2020-02-26 23:36:53 -05:00
|
|
|
while self.spi.read(1, 0xFF)[0] == 0x00:
|
2016-01-30 02:21:43 -05:00
|
|
|
pass
|
|
|
|
|
2017-04-23 04:35:34 -04:00
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
self.spi.write(b"\xff")
|
2016-01-30 02:21:43 -05:00
|
|
|
|
2014-12-27 15:23:14 -05:00
|
|
|
def readblocks(self, block_num, buf):
|
2017-12-19 17:54:24 -05:00
|
|
|
nblocks = len(buf) // 512
|
2020-02-26 23:36:53 -05:00
|
|
|
assert nblocks and not len(buf) % 512, "Buffer length is invalid"
|
2016-01-30 02:21:43 -05:00
|
|
|
if nblocks == 1:
|
|
|
|
# CMD17: set read address for single block
|
2018-06-28 19:32:02 -04:00
|
|
|
if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
|
|
|
|
# release the card
|
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
raise OSError(5) # EIO
|
2018-06-28 19:32:02 -04:00
|
|
|
# receive the data and release card
|
2016-01-30 02:21:43 -05:00
|
|
|
self.readinto(buf)
|
|
|
|
else:
|
|
|
|
# CMD18: set read address for multiple blocks
|
2018-06-28 19:32:02 -04:00
|
|
|
if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
|
|
|
|
# release the card
|
|
|
|
self.cs(1)
|
2020-02-26 23:36:53 -05:00
|
|
|
raise OSError(5) # EIO
|
2016-01-30 02:21:43 -05:00
|
|
|
offset = 0
|
|
|
|
mv = memoryview(buf)
|
|
|
|
while nblocks:
|
2018-06-28 19:32:02 -04:00
|
|
|
# receive the data and release card
|
2016-01-30 02:21:43 -05:00
|
|
|
self.readinto(mv[offset : offset + 512])
|
|
|
|
offset += 512
|
|
|
|
nblocks -= 1
|
2020-02-26 23:36:53 -05:00
|
|
|
if self.cmd(12, 0, 0xFF, skip1=True):
|
|
|
|
raise OSError(5) # EIO
|
2014-12-27 15:23:14 -05:00
|
|
|
|
|
|
|
def writeblocks(self, block_num, buf):
|
2016-01-30 02:21:43 -05:00
|
|
|
nblocks, err = divmod(len(buf), 512)
|
2020-02-26 23:36:53 -05:00
|
|
|
assert nblocks and not err, "Buffer length is invalid"
|
2016-01-30 02:21:43 -05:00
|
|
|
if nblocks == 1:
|
|
|
|
# CMD24: set write address for single block
|
|
|
|
if self.cmd(24, block_num * self.cdv, 0) != 0:
|
2020-02-26 23:36:53 -05:00
|
|
|
raise OSError(5) # EIO
|
2016-01-30 02:21:43 -05:00
|
|
|
|
|
|
|
# send the data
|
2016-08-25 04:54:59 -04:00
|
|
|
self.write(_TOKEN_DATA, buf)
|
2016-01-30 02:21:43 -05:00
|
|
|
else:
|
|
|
|
# CMD25: set write address for first block
|
|
|
|
if self.cmd(25, block_num * self.cdv, 0) != 0:
|
2020-02-26 23:36:53 -05:00
|
|
|
raise OSError(5) # EIO
|
2016-01-30 02:21:43 -05:00
|
|
|
# send the data
|
|
|
|
offset = 0
|
|
|
|
mv = memoryview(buf)
|
|
|
|
while nblocks:
|
2016-08-25 04:54:59 -04:00
|
|
|
self.write(_TOKEN_CMD25, mv[offset : offset + 512])
|
2016-01-30 02:21:43 -05:00
|
|
|
offset += 512
|
|
|
|
nblocks -= 1
|
2016-08-25 04:54:59 -04:00
|
|
|
self.write_token(_TOKEN_STOP_TRAN)
|
2018-06-15 04:01:36 -04:00
|
|
|
|
|
|
|
def ioctl(self, op, arg):
|
2020-02-26 23:36:53 -05:00
|
|
|
if op == 4: # get number of blocks
|
2018-06-15 04:01:36 -04:00
|
|
|
return self.sectors
|