← index #7932Issue #8949
Related · medium · value 1.211
QUERY · ISSUE

sd

openby saul530opened 2021-10-25updated 2024-09-08

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()

CANDIDATE · ISSUE

ESP32 machine.SDCard() Instantiation throws error on soft reboot: SPI bus already initialized; ESP_ERR_INVALID_STATE

closedby jamesquiltyopened 2022-07-23updated 2022-09-13
bugport-esp32

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:

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied