Best practice for remounting sdcard and checking availability

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Saul
Posts: 10
Joined: Wed Jun 10, 2020 10:20 pm

Best practice for remounting sdcard and checking availability

Post by Saul » Sun Oct 03, 2021 6:25 am

I was wondering if anyone knew the best way to check if an SD card is inserted and also how to remount the SD card if it was removed and re-inserted?

At one point I was able to mount an SD card again after de-initializing the SPI bus.
Now I get the following error:

Code: Select all

MicroPython v1.15-835-g1fcd6e97f-dirty on 2021-08-21; ESP32 module (spiram) with ESP32
Type "help()" for more information.
>>> import uos
>>> from machine import SDCard
>>> sd = SDCard(slot=2)
>>> uos.mount(sd, '/sd')
>>> uos.listdir('/')
['sd', 'boot.py']
>>> 
>>> uos.umount('/sd')
>>> sd.deinit()
>>> sd = SDCard(slot=2)
E (145181) spi: spi_bus_initialize(462): SPI bus already initialized.
E (145181) 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')
>>> 
I would just like to handle the use case where some one removes an SD card then re-inserts it again.

Also it seems like even after an SD card is removed some files are still readable. For now the best way I know how to check if the SD card is inserted (no card detect pin) is to create file on mount that that I can write to, to validate the SD card is accessible.

Code: Select all

        available=True
        try:
            f = open('/sd/.avail', 'w')
            f.write('1')
            f.close()
        except:
           available=False
Any help would be greatly appreciated :slight_smile:

Saul
Posts: 10
Joined: Wed Jun 10, 2020 10:20 pm

Re: Best practice for remounting sdcard and checking availability

Post by Saul » Thu Oct 07, 2021 5:51 am

After doing some googling seems like the deinit issue got fixed:
https://github.com/micropython/micropython/issues/7352

But the "reinit" is still ongoing:
https://github.com/micropython/micropython/issues/7414

Just compiled the latest lv_micropyhon repo and I'm still seeing the issue on 1.17:

Code: Select all

MicroPython v1.17-571-gec6a9fca1-dirty on 2021-10-06; ESP32 module (spiram) with ESP32
Type "help()" for more information.
>>> 
>>> import uos
>>> from machine import SDCard
>>> sd = SDCard(slot=2)
>>> uos.mount(sd, '/sd')
>>> uos.listdir('/')
['sd', 'boot.py']
>>> uos.umount('/sd')
>>> sd.deinit()
>>> sd = SDCard(slot=2) # removed and re-inserted sd card before trying to initialize it
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: (-259, 'ESP_ERR_INVALID_STATE')
>>> 
We will just have to keep an eye on the bug.

Saul
Posts: 10
Joined: Wed Jun 10, 2020 10:20 pm

Re: Best practice for remounting sdcard and checking availability

Post by Saul » Sun Oct 24, 2021 5:43 am

For any one who is interested in remounting an SD card I have made some changes to the original script:
https://github.com/micropython/micropyt ... /sdcard.py

This script seems to work for my 1G SC, 4G HC, and 32G HC U1 SD cards.

esp_sdcard.py

Code: Select all

"""
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)
sd.init()
uos.mount(sd, '/sd')
Note: Format SD FAT32 not FAT or other

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

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

        # 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
        #self.init()

    # 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, 0, 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, 0, 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, 0) != 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, 0) != 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


Note: separated card initialization from class initialization routine so I can set SD bus without failing if there is no card inserted.

Will be using this script as a wrapper.
sd_card.py

Code: Select all

# Import libraries
import uos
import esp_sdcard

class SD_card: 
    def __init__(self, sd_slot=2, sd_loc='/sd'):
        # Load class variables
        
        # change slot if using different spi or mmc
        self.sd_slot = sd_slot
        self.sd_loc = sd_loc
        self.sd = None
        try:
            self.sd = esp_sdcard.Esp_sdcard(slot=self.sd_slot) # SD gets initialized
        except:
            raise OSError("Failed to set SD bus")
    
    # Initialize SD card
    def initialize(self):
        initialized = True
        
        # Attempt to initialize SD card
        try:
            self.sd.init()
        except:
            initialized = False
        
        return initialized
        
    # Mount SD card
    def mount(self):
        mounted = True
        file = self.sd_loc + '/.avail'
        
        # Maybe should check if its already available before remounting?
        # First try to unmount prevous sd mount
        try:
            uos.umount(self.sd_loc)
        except:
            pass
        
        
        #try to mount
        try:
            uos.mount(self.sd, self.sd_loc)
            
            ## Create file to check SD is writable
            f = open(file, 'w')
            f.write('1')
            f.close()
            
        except:
            mounted = False
        
        return mounted
    
    # Check if SD card is available
    def available(self):
        available = True
        file = self.sd_loc + '/.avail'
            
        # Try to write to a file
        try:
            f = open(file, 'w')
            f.write('1')
            f.close()
            
        # If not accessible to to remount
        except:
            # Re-initialize card
            self.initialize()
            
            # Remount SD card 
            if self.mount():
                
                # # Try to write to a file
                try:
                    f = open(file, 'w')
                    f.write('1')
                    f.close()
                    
                except:
                    available = False
            else:
                available = False
        
        return available

Saul
Posts: 10
Joined: Wed Jun 10, 2020 10:20 pm

Re: Best practice for remounting sdcard and checking availability

Post by Saul » Mon Oct 25, 2021 7:47 pm

updating scrip to include SD card initialization by default but a flag in case your not expecting user to have the SD card at the time of assigning pins in your program.

esp_sdcard.py

Code: Select all

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

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
sd_card.py

Code: Select all

# Import libraries
import uos
import esp_sdcard

class SD_card: 
    def __init__(self, sd_slot=2, sd_loc='/sd'):
        # Load class variables
        
        # change slot if using different spi or mmc
        self.sd_slot = sd_slot
        self.sd_loc = sd_loc
        self.sd = None
        try:
            self.sd = esp_sdcard.Esp_sdcard(slot=self.sd_slot, sd_init=False) # SD gets initialized
        except:
            raise OSError("Failed to set SD bus")
    
    # Initialize SD card
    def initialize(self):
        initialized = True
        
        # Attempt to initialize SD card
        try:
            self.sd.init()
        except:
            initialized = False
        
        return initialized
        
    # Mount SD card
    def mount(self):
        mounted = True
        file = self.sd_loc + '/.avail'
        
        # Maybe should check if its already available before remounting?
        # First try to unmount prevous sd mount
        try:
            uos.umount(self.sd_loc)
        except:
            pass
        
        
        #try to mount
        try:
            uos.mount(self.sd, self.sd_loc)
            
            ## Create file to check SD is writable
            f = open(file, 'w')
            f.write('1')
            f.close()
            
        except:
            mounted = False
        
        return mounted
    
    # Check if SD card is available
    def available(self):
        available = True
        file = self.sd_loc + '/.avail'
            
        # Try to write to a file
        try:
            f = open(file, 'w')
            f.write('1')
            f.close()
            
        # If not accessible to to remount
        except:
            # Re-initialize card
            self.initialize()
            
            # Remount SD card 
            if self.mount():
                
                # # Try to write to a file
                try:
                    f = open(file, 'w')
                    f.write('1')
                    f.close()
                    
                except:
                    available = False
            else:
                available = False
        
        return available

Post Reply