summaryrefslogtreecommitdiffstatshomepage
path: root/drivers/sdcard
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sdcard')
-rw-r--r--drivers/sdcard/sdcard.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/drivers/sdcard/sdcard.py b/drivers/sdcard/sdcard.py
new file mode 100644
index 0000000000..e51a594d8e
--- /dev/null
+++ b/drivers/sdcard/sdcard.py
@@ -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