sd
Having issue's re-initializing SD card:
https://github.com/micropython/micropython/issues/7414
So I have made changes to sdcard.py:
https://github.com/micropython/micropython/blob/master/drivers/sdcard/sdcard.py
To help with the following:
- Made initializing SD card when initializing SPI bus optional so SD card does not have to be inserted at the time of assigning pins in my program.
- small changes to SD initialization commands to help with some of my 1G SD SC cards
Not sure if there is anything here that would be helpful to anyone.
esp_sdcard.py
`
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
`
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()
PyBoard`s SD card dies with SPI device attached and initialized
When SD card is being inserted and code (that initializes SPI and sends some init data to device) launched from it in REPL and then Ctrl+D is being pressed - BOOM, SD card error occurs.
Here is the sample initialization: https://github.com/kamikaze/pyboard-examples/blob/master/display/init.py#L18
It doesn't die if CS pin init is commented out... but SPI device is not working too then. I've tried to use X5, Y5 (SS) pins for appropriate SPI buses and even random Y4 pin. Same behaviour.
I think same behaviour may occur with SSD1306 (because my init process is the same) or with any other device (but not sure).
Here is my long and painful investigation: http://forum.micropython.org/viewtopic.php?f=6&t=2657