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()
ESP32 machine.SDCard() Instantiation throws error on soft reboot: SPI bus already initialized; ESP_ERR_INVALID_STATE
We're having trouble with microSD card access with MicroPython versions 1.16 through 1.19 on two different revisions of a single-board PCB based on the ESP32-WROVER-B (SPI RAM) with direct connections from the ESP32 pins to the pins of a MEM2075 microSD card connector (PDF). On first power-up, or after a hard reset, the card can be mounted on the REPL with the commands
from machine import SDCard
# PCB Revision A pin configuration:
sd = SDCard(slot=2, sck=13, miso=15, mosi=23, cs=12)
# PCB Revision B pin configuration:
sd = SDCard(slot=2, sck=13, miso=34, mosi=23, cs=12)
The cards can be accessed normally. Once a soft reboot is executed, the code to instantiate the SDCard() class results in an error. An example from a pristine MicroPython 1.19.1 (esp32spiram-20220618-v1.19.1.bin from https://micropython.org/download/esp32spiram/) on a Revision A board:
MicroPython v1.19.1 on 2022-06-18; ESP32 module (spiram) with ESP32
Type "help()" for more information.
>>> from machine import SDCard
>>> sd = SDCard(slot=2, sck=13, miso=15, mosi=23, cs=12)
>>>
MPY: soft reboot
MicroPython v1.19.1 on 2022-06-18; ESP32 module (spiram) with ESP32
Type "help()" for more information.
>>> from machine import SDCard
>>> sd = SDCard(slot=2, sck=13, miso=15, mosi=23, cs=12)
E (xxxxx) spi: spi_bus_initialize(462): SPI bus already initialized.
E (xxxxx) sdspi_host: spi_bus_initialize failed with rc=0x103
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: (-259, 'ESP_ERR_INVALID_STATE')
where we are not using sd.deinit() because the MicroPython documentation says sd.deinit() is only implemented on the cc3200 port.
This problem does not appear to affect re-instantiating SDCard() after wake from deep sleep, only soft reboot. That said, access to the microSD card is an integral part of remote operation of our device and we feel this problem blocks us from migrating to MicroPython 1.19.1, keeping us on version 1.14. This is, needless to say, a pretty serious problem for us.
We're aware that we're not using the slot 2 pins recommended in class SDCard - ESP32 but we're uncertain whether changing the pin connections on the PCB will resolve the problem. We're using every pin on the ESP32, so changing the microSD card connections will be a non-trivial task. We'll do it if we know it will resolve the SPI bus already initialized error.
We were using MicroPython 1.14 (esp32spiram-idf4-20210202-v1.14.bin) when the boards were developed and did not, and do not, see this error with microSD card access that we see with later versions. The issue causing the errors above appears to have been introduced in version 1.16 of the firmware. Hopefully this provides a clue to the change(s) causing the problem and the potential resolution?
Previous GitHub Issues and MicroPython Forum posts of similar errors on instantiating SDCard() after soft reboot coincide in time with the release of version 1.16:
- SD card not accessible from rshell. SD card not mounted after soft_reset https://forum.micropython.org/viewtopic.php?f=18&t=10882&p=59841
- Best practice for remounting sdcard and checking availability https://forum.micropython.org/viewtopic.php?f=2&t=11205&p=61967
- Oddities with SD https://forum.micropython.org/viewtopic.php?f=16&t=12622&p=68456
- #7414
- https://github.com/micropython/micropython/issues/7352#issuecomment-863908895
- #4722