micropython / micropython/micropython

sd

Open
#7,932 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
22.1k
Forks
9k
Avg merge
6d 4h
Merged PRs (30d)
16

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading issue 7414 and drivers/sdcard/sdcard.py, then compare them with the proposed esp_sdcard.py implementation. Reproduce the SD re-initialization problem on the reported hardware and verify whether optional initialization and support for 1G SD cards are needed; done should address those behaviors in the shared driver.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
embedded-iot
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.