Pico Wireless - how to access SD card?

Trying to figure out how to access my Pico Wireless Micro SD card.

The closest I have got is picowireless.is_sdcard_detected() which returns a value of True.

Does anyone know how to read/write the card?

I’ll caveat this response with a) I don’t have a Pico Wireless pack, so this answer may be complete nonsense and b) It looks like you’re using MicroPython (I’m assuming that from the is_sdcard_detected() call) and this answer relates to C++ …

With that out of the way, there’s an example in the pimoroni GitHub repo that shows a simple web server that returns a directory of an SD card over HTTP that looks to be fairly straightforward - maybe that will help?

Also - the product page on Pimoroni’s website does say

Please note that SD card support in the C++ SDK is still quite experimental - if you’re planning on doing things with data you might have an easier time of it if you use CircuitPython!

There’s a page on adafruit’s website that shows some SD Card examples in CircuitPython - https://learn.adafruit.com/micropython-hardware-sd-cards?view=all - not sure if that’s useful/relevant but may be helpful?

D

I’ve not used the SD card on the wireless board but I have got the SD card working with MicroPython.

You do not even need an SD card reader. I soldered some pins onto the pad of the micro SD card adapter and connected directly to the Pico pins. In my example I used the other SPI set. You can find the full method here:
Raspberry Pi Pico - Review | element14 | RoadTests & Reviews

This is a long review but the section you need is about 80% through and headed
Pico, MicroPython and SD card storage

Here is the code, just adjust the pins to the wireless board connections:

import sdcard  
import machine  
import uos  
sd_spi = machine.SPI(1, sck = machine.Pin(10, machine.Pin.OUT), mosi = machine.Pin(11, machine.Pin.OUT), miso = machine.Pin(12, machine.Pin.OUT))  
sd = sdcard.SDCard(sd_spi, machine.Pin(9))  
  
uos.mount(sd, "/sd")  
  
print("Size: {} MB".format(sd.sectors/2048)) # to display card's capacity in MB  
print(uos.listdir("/sd"))  
print("\n=======================\n")  
print("Basic SDcard Test \n")  
  
with open("/sd/test2.txt", "w") as f: # Write - new file  
    f.write("First Message\r\n")  
  
with open("/sd/test2.txt", "a") as f: # Append  
    f.write("Tony Goodhew\r\n")  
  
with open("/sd/test2.txt", "a  ") as f:  
    f.write("Leicester City Cup Winners!\r\n")  
      
with open("/sd/test2.txt", "a  ") as f:  
    for i in range(10):  
        f.write(str(i) + ", " + str(i*i*i) + ", " + str(i*i*i*i) + "\r\n")  
  
  
with open("/sd/test2.txt", "a  ") as f:  
    f.write("Looping all done!\r\n")  
          
with open("/sd/test2.txt", "r") as f:  
    print("Printing lines in file: Method #1\n")  
    line = f.readline()  
    while line != '':   # NOT EOF  
        print(line)  
        line = f.readline()  
  
  
with open("/sd/test2.txt", "r") as f:  
    lines = f.readlines()  
    print("Printing lines in file: Method #2")  
    for line in lines:  
        print(line)  
  
uos.umount("/sd")

You can get the SD card library here:


"""
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.
Example usage on pyboard:
    import pyb, sdcard, os
    sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
    pyb.mount(sd, '/sd2')
    os.listdir('/')
Example usage on ESP8266:
    import machine, sdcard, os
    sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
    os.mount(sd, '/sd')
    os.listdir('/')
"""

from micropython import const
import time


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


class SDCard:
    def __init__(self, spi, cs):
        self.spi = spi
        self.cs = cs

        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 the card
        self.init_card()

    def init_spi(self, baudrate):
        try:
            master = self.spi.MASTER
        except AttributeError:
            # on ESP8266
            self.spi.init(baudrate=baudrate, phase=0, polarity=0)
        else:
            # on pyboard
            self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)

    def init_card(self):
        # init CS pin
        self.cs.init(self.cs.OUT, value=1)

        # init SPI bus; use low data rate for initialisation
        self.init_spi(100000)

        # clock card at least 100 cycles with cs high
        for i in range(16):
            self.spi.write(b"\xff")

        # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)
        for _ in range(5):
            if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
                break
        else:
            raise OSError("no SD card")

        # CMD8: determine card version
        r = self.cmd(8, 0x01AA, 0x87, 4)
        if r == _R1_IDLE_STATE:
            self.init_card_v2()
        elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
            self.init_card_v1()
        else:
            raise OSError("couldn't determine SD card version")

        # get the number of sectors
        # CMD9: response R2 (R1 byte + 16-byte block read)
        if self.cmd(9, 0, 0, 0, False) != 0:
            raise OSError("no response from SD card")
        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")
        # print('sectors', self.sectors)

        # CMD16: set block length to 512 bytes
        if self.cmd(16, 512, 0) != 0:
            raise OSError("can't set 512 block size")

        # set to high data rate now that it's initialised
        self.init_spi(1320000)

    def init_card_v1(self):
        for i in range(_CMD_TIMEOUT):
            self.cmd(55, 0, 0)
            if self.cmd(41, 0, 0) == 0:
                self.cdv = 512
                # print("[SDCard] v1 card")
                return
        raise OSError("timeout waiting for v1 card")

    def init_card_v2(self):
        for i in range(_CMD_TIMEOUT):
            time.sleep_ms(50)
            self.cmd(58, 0, 0, 4)
            self.cmd(55, 0, 0)
            if self.cmd(41, 0x40000000, 0) == 0:
                self.cmd(58, 0, 0, 4)
                self.cdv = 1
                # print("[SDCard] v2 card")
                return
        raise OSError("timeout waiting for v2 card")

    def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
        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]
            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")
                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
            time.sleep_ms(1)
        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

Call it sdcard.py

There is another on here:
micropython-infineon/sdcard.py at master · micropython/micropython-infineon · GitHub

(I’m not sure which chip (Pi Pico or ESP) is driving the SDcard reader on the Wireless board. The method above should work if it is the Pico.)

I hope this helps
Have fun

1 Like

Thanks for the reply. I should have been more specific - yes, I’m using MicroPython. I have a look at the link you provided.

Thanks very much for your reply. I’ll have a good look at the info you provided and give it a try.

I can confirm that the sdcard on the pico wireless is driven by the pico on SPI0 so I have made the following adjustments to the code:
sd_spi = machine.SPI(0, sck = machine.Pin(18, machine.Pin.OUT), mosi = machine.Pin(19, machine.Pin.OUT), miso = machine.Pin(16, machine.Pin.OUT))
sd = sdcard.SDCard(sd_spi, machine.Pin(22))

I get the following error:
File “”, line 7, in
File “/lib/sdcard.py”, line 238, in readblocks
OSError: [Errno 5] EIO

I think this would indicate that I have made an error with the pins but I can’t see one. The documentation is a bit confusing using SDIO labels, it seems the board can be driven in either that or SPI modes. I am using known good cards between 2 and 16 Gig, all formatted FAT32 with 512 block size.

Has anybody got the card to work with micropython on the Wireless pack using sdpycard.py?
Has any worked example been published, I can’t find any equivalent to the ESP32 examples?

Grateful for any input,

RytonMike

I’ve found this to be the most frustrating of all my Pico add-on boards. Lack of clear documentation about the correct pins is not helping. It has been out long enough for this to have been sorted by now. The ‘home-made’ SDcard adapter method works perfectly in MicroPython - build your own.

SD-Card

All we need is a simple example program, in MicroPython, which initialises the board, opens a write-file, writes a couple of records, closes the file, opens it for reading and gets the stored information back.

1 Like

I’m glad it’s not just me! Thanks for the comment Tonygo2.

By the way I found your workout for waveshare 1.3" IPS display very useful.
So come on Pimeroni, give us an example!

RytonMike

1 Like

Just gave it a go and @Tonygo2 's SD card example seems to work fine for me on Wireless Pack with

sd_spi = machine.SPI(0, sck = machine.Pin(18, machine.Pin.OUT), mosi = machine.Pin(19, machine.Pin.OUT), miso = machine.Pin(16, machine.Pin.OUT))
sd = sdcard.SDCard(sd_spi, machine.Pin(22))

I saved the MicroPython driver as sdcard.py using Thonny.

Could be the SD cards causing problems, perhaps - I think @gadgetoid said he found Pico was fairly fussy with SD cards that it liked? Also worth checking that the soldering on your Pico’s header is up to scratch - OSError: [Errno 5] EIO can happen when there’s a dodgy connection somewhere (similar to an IOError in standard Python).

Yep, just tried a few SD cards with varying results - the ones on the left worked fine and the ones on the right didn’t (the 1GB Sandisk did the [Errno 5] EIO thing)

Hel Gibbons, Thanks
I just tried it with a SanDisk Ultra 8GB (10) SDHC I (Red/Grey) in the Pico Wireless SD slot and it worked perfectly.

Another thing fixed!

I get my SD cards from Amazon or Ebuyer.com - there are some very iffy cards on Ebay.

1 Like

Thanks for the response, Hel. I’m on the air now as well!

1 Like