updating scrip to include SD card initialization by default but a flag in case your not expecting user to have the SD card at the time of assigning pins in your program.
Code: Select all
"""
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.
# Examples of how to use
# ESP32 - mount sd card
import uos
import esp_sdcard
sd = esp_sdcard.Esp_sdcard(slot=2)
uos.mount(sd, '/sd')
Notes:
Format SD FAT32 not FAT or other
When sd_init=True sd bus/slot will not get initialized if SD card is not inserted/available
# If card was removed you can remount
uos.umount('/sd')
sd.init()
uos.mount(sd, '/sd')
# List file on sd card
uos.listdir('/sd')
# Try to creat and write to a file
f = open('/sd/data.txt', 'w')
f.write('some data')
f.close()
# Try to read from a file
f = open('/sd/data.txt')
f.read()
f.close()
"""
from micropython import const
from machine import SPI, Pin
class Esp_sdcard:
# Class constants
_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)
_TOKEN_CMD25 = const(0xFC)
_TOKEN_STOP_TRAN = const(0xFD)
_TOKEN_DATA = const(0xFE)
def __init__(self, slot=2, baudrate=1000000, polarity=0, phase=0, bits=8, firstbit=0, sck=None, mosi=None, miso=None, cs=None, sd_init=True):
# Instance variables
self.spi = None
self.baudrate = baudrate # just storing value to initialize with slower baud
self.cs = None
self.cdv = 1
self.sectors = None
# SPI Type
if slot == 1:
# Use default pins if not entered
if sck is None:
sck = 14
if mosi is None:
mosi = 13
if miso is None:
miso = 12
if cs is None:
cs = 15
elif slot == 2:
# Use default pins if not entered
if sck is None:
sck = 18
if mosi is None:
mosi = 23
if miso is None:
miso = 19
if cs is None:
cs = 5
else:
raise OSError("Invalid SPI slot")
# Set baud floor value? (should probably remove)
if self.baudrate < 100000:
self.baudrate = 100000
# Initialize chip select pin
try:
self.cs = Pin(cs)
self.cs.init(self.cs.OUT, value=1)
except:
raise OSError("Failed to initialize CS")
# Initialize SPI bus
try:
self.spi = SPI(slot)
self.spi.init(baudrate=self.baudrate, polarity=polarity, phase=phase, bits=bits, firstbit=firstbit, sck=Pin(sck), mosi=Pin(mosi), miso=Pin(miso))
except:
raise OSError("Failed to initialize SPI bus")
# SD read buffers
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)
# Initialise SD card
if sd_init:
self.init() # May want to put in try block so atleast
# De-initialize SPI bus
def deinit(self):
self.spi.deinit()
# Initialize SD card
def init(self):
# Initialise the SPI bus with slow baudrate by default
self.spi.init(baudrate=100000)
# Set CS pin high
self.cs(1)
# clock card at least 100 cycles with cs high
for i in range(_CMD_TIMEOUT):#16):
self.spi.write(b"\xFF")
# CMD0: Reset card; should return _R1_IDLE_STATE
# Response: 0xFF 0x01
for i in range(_CMD_TIMEOUT):#5):
self.init_cmd(0, 0, 0x95, 2) # Read Two bytes first being garbage
#check sd card is in idle mode
if (self.dummybuf_memoryview[1] == _R1_IDLE_STATE): # only care about the second byte
break
if i== (_CMD_TIMEOUT-1):
print(bytes(self.dummybuf_memoryview[0:2])) # all bytes read
raise OSError("no SD card")
# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
# CMD8: determine card version
# Response: 0xFF 0x01 0x00 0x00 0x01 0xAA
self.init_cmd(8, 0x01AA, 0x87, 6)
if self.dummybuf_memoryview[1] == _R1_IDLE_STATE: # Only care about the second byte
for i in range(_CMD_TIMEOUT):
# CMD55 Indicates to the card that the next command is an application specific command
# Response: 0xFF 0x01 (Don't care)
#self.init_cmd(55, 0, 0, 2)
self.init_cmd(55, 0, 0x87, 2)
# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
#ACM41 Sends HCS, asks OCR content in the response
# Response: 0xFF 0x00
self.init_cmd(41, 0x40000000, 0x87, 2)
if (self.dummybuf_memoryview[1] == 0): # Only care about the second byte
#if the response is 0x00 your good
# and set cdv = 1
#self.cdv = 1
break
if i == (_CMD_TIMEOUT - 1):
print(bytes(self.dummybuf_memoryview[0:2]))
raise OSError("timeout waiting for v2 card")
# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
self.dummybuf_memoryview[2] = 0xFF
self.dummybuf_memoryview[3] = 0xFF
self.dummybuf_memoryview[4] = 0xFF
self.dummybuf_memoryview[5] = 0xFF
#ACM58 Check card version
# Response V2 HC: 0xFF 0x00 0xC0 0xFF 0x80 0x00
# Response V1 SC: 0xFF 0x00 0x80 0xFF 0x80 0x00
self.init_cmd(58, 0, 0xFF, 6)
if self.dummybuf_memoryview[2] == 0x80:
# CMD55 Indicates to the card that the next command is an application specific command
# Response: 0xFF 0x01 (Don't care)
self.init_cmd(55, 0, 0x87, 2)
# CMD42: ...
# Response: ... (Don't care)
self.init_cmd(42, 0, 0x87, 2)
self.cdv = 512
else:
self.cdv = 1
# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
self.dummybuf_memoryview[2] = 0xFF
self.dummybuf_memoryview[3] = 0xFF
self.dummybuf_memoryview[4] = 0xFF
self.dummybuf_memoryview[5] = 0xFF
# CMD16: set block length to 512 bytes
# Response: 0xFF 0x00
self.init_cmd(16, 0x00000200, 0x87, 2)
if self.dummybuf_memoryview[1] != 0: # Only care about the second byte
print(bytes(self.dummybuf_memoryview[0:2]))
raise OSError("Can't set 512 block size")
# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
else:
print(bytes(self.dummybuf_memoryview[0:6])) # all bytes read
raise OSError("Couldn't determine SD card version")
# CMD9: response R2 (R1 byte + 16-byte block read)
# Response: 0xFF 0x01 0x... 0xFE
if self.cmd(9, 0, 0xAF, 0, False) != 0:
raise OSError("no response from SD card")
# Get sector size
csd = bytearray(16)
self.readinto(csd)
if csd[0] & 0xC0 == 0x40: # CSD version 2.0
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
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:
raise OSError("SD card CSD format not supported")
# Clean whole buffer
for i in range(512):
self.dummybuf[i] = 0xFF
self.dummybuf_memoryview = memoryview(self.dummybuf)
# Set back to requested baud rate
self.spi.init(baudrate=self.baudrate)
# Send dummy byte for clock sync
self.cs(0)
self.spi.write(b"\xFF")
self.cs(1)
def init_cmd(self, cmd=None, arg=0, crc=0, resp_size=2):
# send dummy byte before every command
self.cs(0)
self.spi.write(b"\xFF")
self.cs(1)
self.cs(0)
# 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.write(buf)
# Get response from SD and save to buffer
self.spi.readinto(self.dummybuf_memoryview[0:resp_size], 0xFF)
# For debugging sd card init
#print(bytes(buf)) # Command
#print(bytes(self.dummybuf_memoryview[0:resp_size])) # Response
self.cs(1)
def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
# send dummy byte before every command
self.cs(0)
self.spi.write(b"\xFF")
self.cs(1)
self.cs(0)
# 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.write(buf)
if skip1:
self.spi.readinto(self.tokenbuf, 0xFF)
# wait for the response (response[7] == 0)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
response = self.tokenbuf[0]
# For debugging sd card commands
#print(bytes(buf)) # Command
#print(bytes(response)) # Response
if not (response & 0x80):
# this could be a big-endian integer that we are getting here
for j in range(final):
self.spi.write(b"\xff")
if release:
self.cs(1)
self.spi.write(b"\xff")
#print(bytes(response)) # Debug Response
return response
# timeout
self.cs(1)
self.spi.write(b"\xff")
return -1
def readinto(self, buf):
self.cs(0)
# read until start byte (0xff)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
if self.tokenbuf[0] == _TOKEN_DATA:
break
else:
self.cs(1)
raise OSError("timeout waiting for response")
# read data
mv = self.dummybuf_memoryview
if len(buf) != len(mv):
mv = mv[: len(buf)]
self.spi.write_readinto(mv, buf)
# read checksum
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)
# send: start of block, data, checksum
self.spi.read(1, token)
self.spi.write(buf)
self.spi.write(b"\xff")
self.spi.write(b"\xff")
# check the response
if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
self.cs(1)
self.spi.write(b"\xff")
return
# wait for write to finish
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")
# wait for write to finish
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:
# CMD17: set read address for single block
if self.cmd(17, block_num * self.cdv, 1, release=False) != 0:
# release the card
self.cs(1)
raise OSError(5) # EIO
# receive the data and release card
self.readinto(buf)
else:
# CMD18: set read address for multiple blocks
if self.cmd(18, block_num * self.cdv, 1, release=False) != 0:
# release the card
self.cs(1)
raise OSError(5) # EIO
offset = 0
mv = memoryview(buf)
while nblocks:
# receive the data and release card
self.readinto(mv[offset : offset + 512])
offset += 512
nblocks -= 1
if self.cmd(12, 0, 0xFF, skip1=True):
raise OSError(5) # EIO
def writeblocks(self, block_num, buf):
nblocks, err = divmod(len(buf), 512)
assert nblocks and not err, "Buffer length is invalid"
if nblocks == 1:
# CMD24: set write address for single block
if self.cmd(24, block_num * self.cdv, 1) != 0:
raise OSError(5) # EIO
# send the data
self.write(_TOKEN_DATA, buf)
else:
# CMD25: set write address for first block
if self.cmd(25, block_num * self.cdv, 1) != 0:
raise OSError(5) # EIO
# send the data
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: # get number of blocks
return self.sectors