← index #7932Issue #16526
Related · high · value 0.396
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-S3 SDCard(width=4) uses undocumented hard-coded pin assignments

closedby specternecteropened 2025-01-05updated 2025-03-13
bugport-esp32

Port, board and/or hardware

esp32 port - ESP32-S3

MicroPython version

MicroPython v1.25.0-preview.114.gbdda91fe7 on 2025-01-05

Reproduction

`
import machine
import os
import esp
esp.osdebug(True, esp.LOG_VERBOSE)

try:
sd = machine.SDCard(slot=1, width=4)
os.mount(sd, "/sd")
print("SD card mounted successfully.")

print("Contents of the SD card:")
for filename in os.listdir("/sd"):
    print(filename)

except Exception as e:
print("Error initializing SD card:", e)
`

Expected behaviour

Print a list of files on the SD card.

Observed behaviour

import sd_test
I (809804) gpio: GPIO[14]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (809854) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (809854) gpio: GPIO[2]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (809864) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (809874) gpio: GPIO[12]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (809884) gpio: GPIO[13]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
E (809924) sdmmc_common: sdmmc_init_ocr: send_op_cond (1) returned 0x107
Error initializing SD card: 16

Additional Information

I have tried everything to get an SD card to work like it does in sdmmc example for esp-idf (https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card/sdmmc). If I run this example in esp-idf, everything works perfectly fine right out of the box. It literally took 3 seconds to set up. After 7 hours of work and research, I simply can't get the sd card to work in micropython. Not SPI, not MMC, nothing. Micropython is supposed to be built on top of esp-idf so I would assume it should be just as easy. I looked at the SDCard class (https://docs.micropython.org/en/latest/library/machine.SDCard.html) and found that I should be able to configure the pins in mpconfigboard.h, to which I tried #define USDHC_DUMMY_PIN NULL , 0 #define MICROPY_USDHC1 \ { \ .cmd = { 35}, \ .clk = { 36 }, \ .cd_b = { USDHC_DUMMY_PIN },\ .data0 = { 37 },\ .data1 = { 38 },\ .data2 = { 33 },\ .data3 = { 34 },\ }
like it says, but it still tries to use ESP32 pins when I call the machine.SDCard(), like it also says to do. If I try to add the pins inside machine.SDCard() it says I have too many arguments. At one point I found someone with the same complaint but it had literally no information aside from "modify machine_sdcard.c" with no information on how to modify it and honestly I find that an absolutely terrible idea. Why modify the entire port to make a single board work, that should work from the beginning by default? I have no problem modifying a board to make it work but to me, modifying the port makes no sense unless it gets modified for everybody. I have spent 7 hours trying to get an sd card to work in micropython when it took 3 seconds to get to work in esp-idf. The only information I find are complaints about the same thing but no answers. This is insane.

Code of Conduct

Yes, I agree

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