drivers: Add SD card driver, controlled via SPI bus.
This commit is contained in:
parent
7690b13953
commit
1570eaf0e3
|
@ -0,0 +1,202 @@
|
|||
"""
|
||||
Micro Python 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:
|
||||
|
||||
import pyb, sdcard, os
|
||||
sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
|
||||
pyb.mount(sd, '/sd2')
|
||||
os.listdir('/')
|
||||
|
||||
"""
|
||||
|
||||
import pyb
|
||||
|
||||
class SDCard:
|
||||
CMD_TIMEOUT = const(100)
|
||||
|
||||
R1_IDLE_STATE = const(1 << 0)
|
||||
#R1_ERASE_RESET = const(1 << 1)
|
||||
R1_ILLEGAL_COMMAND = const(1 << 2)
|
||||
#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)
|
||||
|
||||
def __init__(self, spi, cs):
|
||||
self.spi = spi
|
||||
self.cs = cs
|
||||
|
||||
self.cmdbuf = bytearray(6)
|
||||
self.dummybuf = bytearray(512)
|
||||
for i in range(512):
|
||||
self.dummybuf[i] = 0xff
|
||||
self.dummybuf_memoryview = memoryview(self.dummybuf)
|
||||
|
||||
# initialise the card
|
||||
self.init_card()
|
||||
|
||||
def init_card(self):
|
||||
# init CS pin
|
||||
self.cs.high()
|
||||
self.cs.init(self.cs.OUT_PP)
|
||||
|
||||
# init SPI bus; use low data rate for initialisation
|
||||
self.spi.init(self.spi.MASTER, baudrate=100000, phase=0, polarity=0)
|
||||
|
||||
# clock card at least 100 cycles with cs high
|
||||
for i in range(16):
|
||||
self.spi.send(0xff)
|
||||
|
||||
# CMD0: init card; should return R1_IDLE_STATE (allow 2 attempts)
|
||||
if self.cmd(0, 0, 0x95) != R1_IDLE_STATE:
|
||||
if self.cmd(0, 0, 0x95) != R1_IDLE_STATE:
|
||||
raise OSError("no SD card")
|
||||
|
||||
# CMD8: determine card version
|
||||
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")
|
||||
|
||||
# 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)
|
||||
if csd[0] & 0xc0 != 0x40:
|
||||
raise OSError("SD card CSD format not supported")
|
||||
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 2014
|
||||
#print('sectors', self.sectors)
|
||||
|
||||
# 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
|
||||
self.spi.init(self.spi.MASTER, baudrate=1320000, phase=0, polarity=0)
|
||||
|
||||
def init_card_v1(self):
|
||||
for i in range(CMD_TIMEOUT):
|
||||
self.cmd(55, 0, 0)
|
||||
if self.cmd(41, 0, 0) == 0:
|
||||
self.cdv = 512
|
||||
#print("[SDCard] v1 card")
|
||||
return
|
||||
raise OSError("timeout waiting for v1 card")
|
||||
|
||||
def init_card_v2(self):
|
||||
for i in range(CMD_TIMEOUT):
|
||||
pyb.delay(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)
|
||||
self.cdv = 1
|
||||
#print("[SDCard] v2 card")
|
||||
return
|
||||
raise OSError("timeout waiting for v2 card")
|
||||
|
||||
def cmd(self, cmd, arg, crc, final=0, release=True):
|
||||
self.cs.low()
|
||||
|
||||
# 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
|
||||
self.spi.send(buf)
|
||||
|
||||
# wait for the repsonse (response[7] == 0)
|
||||
for i in range(CMD_TIMEOUT):
|
||||
response = self.spi.send_recv(0xff)[0]
|
||||
if not (response & 0x80):
|
||||
# this could be a big-endian integer that we are getting here
|
||||
for j in range(final):
|
||||
self.spi.send(0xff)
|
||||
if release:
|
||||
self.cs.high()
|
||||
self.spi.send(0xff)
|
||||
return response
|
||||
|
||||
# timeout
|
||||
self.cs.high()
|
||||
self.spi.send(0xff)
|
||||
return -1
|
||||
|
||||
def readinto(self, buf):
|
||||
self.cs.low()
|
||||
|
||||
# read until start byte (0xff)
|
||||
while self.spi.send_recv(0xff)[0] != 0xfe:
|
||||
pass
|
||||
|
||||
# read data
|
||||
mv = self.dummybuf_memoryview[:len(buf)]
|
||||
self.spi.send_recv(mv, recv=buf)
|
||||
|
||||
# read checksum
|
||||
self.spi.send(0xff)
|
||||
self.spi.send(0xff)
|
||||
|
||||
self.cs.high()
|
||||
self.spi.send(0xff)
|
||||
|
||||
def write(self, buf):
|
||||
self.cs.low()
|
||||
|
||||
# send: start of block, data, checksum
|
||||
self.spi.send(0xfe)
|
||||
self.spi.send(buf)
|
||||
self.spi.send(0xff)
|
||||
self.spi.send(0xff)
|
||||
|
||||
# check the response
|
||||
if (self.spi.send_recv(0xff)[0] & 0x1f) != 0x05:
|
||||
self.cs.high()
|
||||
self.spi.send(0xff)
|
||||
return
|
||||
|
||||
# wait for write to finish
|
||||
while self.spi.send_recv(0xff)[0] == 0:
|
||||
pass
|
||||
|
||||
self.cs.high()
|
||||
self.spi.send(0xff)
|
||||
|
||||
def count(self):
|
||||
return self.sectors
|
||||
|
||||
def readblocks(self, block_num, buf):
|
||||
# TODO support multiple block reads
|
||||
assert len(buf) == 512
|
||||
|
||||
# CMD17: set read address for single block
|
||||
if self.cmd(17, block_num * self.cdv, 0) != 0:
|
||||
return 1
|
||||
|
||||
# receive the data
|
||||
self.readinto(buf)
|
||||
return 0
|
||||
|
||||
def writeblocks(self, block_num, buf):
|
||||
# TODO support multiple block writes
|
||||
assert len(buf) == 512
|
||||
|
||||
# CMD24: set write address for single block
|
||||
if self.cmd(24, block_num * self.cdv, 0) != 0:
|
||||
return 1
|
||||
|
||||
# send the data
|
||||
self.write(buf)
|
||||
return 0
|
Loading…
Reference in New Issue