Skip to content

sd #7932

@saul530

Description

@saul530

Having issue's re-initializing SD card:
#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()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions