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?

1 Like

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

3 Likes

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

1 Like

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

FYI I struggled with this on a 2.8 LCD with a built in SD reader. I was getting the same type of messages while fussing around with it. I am using a 2040 Feather but for all intents its just a smaller Pico.

Granted this is circuit python it should give you an idea of how to get it up and running, I think in MP you would replace the adafruit_sdcard library with MPā€™s sdcard.py. this is a great library for SD cards in MP peterhinchMP.

Hereā€™s my CP code (version 8.2.4 latest as of August 2023). I probably have some extra libraries I need to remove that Iā€™m not using, this was just a test to see if I could read images from the LCD from SD. You would ignore after sleep(3) as thatā€™s really more pure CP. But you can see what its doing and port it to the MP equivalent.

Adafruit also has detailed images on how to connect and SD card reader to a Pico. Just search for it in the ā€œlearnā€ section.

Last ditch is some of the waveshare LCDā€™s have pure C code (not Arduino) that work but they are clunky, when I contacted their support they were lost and building code for me directly to try to get it work. I did get the onboard SDcard to read images from the LCD but it was ugly and I had to hack it a bit myself.
If you arenā€™t against Circuitpython (not a huge fan myself I use MP mostly or C/C++ pure in a pinch) the code below works great. Iā€™m going to try it on a waveshare LCD that uses the same ili93xx driver. I think I have one. SD cards are a PITA. It should be a simple SPI setup but I noticed its not explained well and full examples are sparse/wrong or dont work on Pico. Iā€™m using a 64GB SanDisk Extreme micro SD card formatted fat32 from my Mac OS using the built in disk util. I just named the sdcard ā€œtestā€.

I have an micro sd card module from Adafruit laying around. Iā€™m going to wire that up to a plain ā€œrealā€ Pico and load MP on it and see if I can get it working. After all I bought the reader for Pico with their tiny 2MB of space. I do own many Pimoroni Picoā€™s with 16MB which is nice for storing images and reading them from that,but I understand people may want to read/write other data constantly (logger) that would breach even the 16MB on the Pimoroniā€™s Picos. I read on hackster that some guy figured out how to take some of on that on board flash and use it as SRAM! (solves the buffer RAM size problem for displaying true color images on large (3.5" and above) LCDā€™s. He did not port the code and its left ā€œup to youā€ to figure it out. Iā€™m hoping someone runs with it and builds into a module that would be game changer, imagine having 8MB of flash storage and 8MB of SRAM on the Pico?! Amazing. I cant wait until we get there. Sorry side rant. Iā€™m going to wire up a pico with the SDcard module and get it working in MicroPython. Iā€™ll share the code if and when I get it working! :) Cheers.

import busio  
import sdcardio
import storage
import board  <-----this would be machine in MP
import os
import displayio
from adafruit_display_text import label  <---Need MP equivalents.
import adafruit_ili9341 <---Need MP equivalents.
import adafruit_sdcard <---Here is where I think you would use sdcard.py in MP
import digitalio
import time
displayio.release_displays()
spi = board.SPI()  <--in CP this is all you need to setup SPI assuming the pins are plugged in correctly(mosi,miso,cs)
tft_cs = board.D9
tft_dc = board.D10
sd_cs = digitalio.DigitalInOut(board.D5)

spi = board.SPI()
sd= adafruit_sdcard.SDCard(spi, sd_cs)   <------I think here you would use the sdcard.py implementation to do the equivalent.
sd= storage.VfsFat(sd)  <---I read rumors that VfsFat doesnt work on Pico, maybe that's true in MP but in CP it works just fine. I doubt that's the case in current MP builds.
storage.mount(sd,'/sd')
time.sleep(3)
#######below is display setup in CP irrelevant SD card is functional at this point####
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.D6)
display = adafruit_ili9341.ILI9341(display_bus, width=320, height=240)
filename = "/sd/fed.bmp"
....more code..

Note that a SD-card on SPI and another component (e.g. display) on the same bus tend to produce problems. This is documented in the CP sd-library suggesting that you need to initialize the sd card before the display. The reason is unclear, it probably has nothing to do with the python-layer but with the pico-sdk so you could also encounter this problem with MP or C++.

In one of my projects I either have the sd-card, or the display working, but not both. I solved the problem by downgrading CP, but the funny thing is: none of the related code has changed in the sources (and most of it is in libraries anyhow). There are also a number of open issues regarding sd-cards in the CP-repo.

So in the end, if the sd-card does not work, it might not even be the card itself.

Yeah @bablokb I had to use the same SPI bus because the micro SD slot was on the actual LCD and share the bus per the documentation. Like I said I figured it out with the above.

I also grabbed a genuine PicoW and loaded the latest stable MP version 1.20.0 and installed an Adafruit SD card reader module. I used SPI 1 in my testing using pins GP10 - GP13 for SPI 1. I grabbed a 64GB Sandisk Extreme and first formatted it blank with the SD tool from the vendor. I tried my code and it kept failing. I decided to take a look again at the micro SD card on my Mac.

Sure enough it was formatted as EXT-FAT. So I used the native tools in Mac OS X (latest version) to format the drive and I did a 2 pass erase on it and I selected FAT32 (note before doing this I was going to try to use disk util in Windows 10 virtual machine but I didnā€™t even get an option for FAT32 it went straight to EXT-FAT. I didnā€™t bother). Once the format on Mac laptop completed I had a blank 64GB FAT32 disk. I was getting optimistic at this point.

Installed the card and it didnā€™t work! Now I was getting No SD Card Found! So I remember my skills of I.T. Sys Admin Daysā€¦tried the very first thing, just give the card a jiggle made sure it was fully inserted and all the cables/pins are plugged in snug. Ran my code boom now it works like a champ I can read/write/append/delete to my hearts content!

My code looks very similar to @Tonygo2 as this is the basic framework I found all over the web for examples.

The only difference is Iā€™m using ā€œDriver.pyā€ instead of sdcard.py as my library, only because I am using the following sdcard.py code from one of the core people who is a wizard at MP. This could very well be what is in the standard sdcard.py but I was sticking with what worked for me on that combo LCD/SD card and didnā€™t bother to check if they were different.
Driver that works like a champ at least on Sandisk cards:
sdcard.py - works on Pico/PicoW/rp2040

I have a 128GB Samsung EVO Iā€™m going to format and give that a shot for giggles and see if it works.

Hereā€™s my code and I stole @Tonygo2 's print out in MB (neat!) and added it in to my code. Feel free to edit, distribute, whatever!

from machine import Pin, SPI
import driver
from driver import SDCard
import uos
import utime
from uos import VfsFat

cs = machine.Pin(13, machine.Pin.OUT,value = 1)

#Using SPI1 from Raspberry Pico 2040 Pinouts:
# SCK = or better known as SPI1_SCK 10
# MOSI or bettern known as SPI1_TX = 11
# MISO or better known as SPI1_RX = 12
# SPI chip select pin known as SPI1_CSn

# Initialize the SD card
spi=machine.SPI(1,baudrate=40000000,sck=Pin(10),mosi=Pin(11),miso=Pin(12))
sd=SDCard(spi,Pin(13))

utime.sleep(2)

# Create a instance of MicroPython Unix-like Virtual File System (VFS),
vfs = uos.VfsFat(sd)

# Mount the SD card
uos.mount(vfs, "/sd")

# Debug print SD card directory and files
print(uos.listdir('/sd'))

#Print size in MB
print("Size: {} MB".format(sd.sectors/1024)) # to display card's capacity in MB.  
# Create / Open a file in write mode.
# Write mode creates a new file.
# If  already file exists. Then, it overwrites the file.
file = open("/sd/sample.txt","w")

# Write sample text
for i in range(20):
    file.write("Sample text = %s\r\n" % i)
    
# Close the file
file.close()

# Again, open the file in "append mode" for appending a line
file = open("/sd/sample.txt","a")
file.write("Appended Sample Text at the END \n")
file.close()

# Open the file in "read mode". 
# Read the file and print the text on debug port.
file = open("/sd/sample.txt", "r")
if file != 0:
    print("Reading from SD card")
    read_data = file.read()
    print (read_data)
file.close()

Cheers,
Anglerfish27


IMG_0538

IMG_0541

I honestly didnā€™t think the Samsung 128GB card was going to work but it did! I saw about 34 MB used when the format completed so I worried it would jammed up. Nope it worked! See pics of the cards and output. The measurement in the print statement is off by a bit. Iā€™m not concerned with it I can mess with the sector size (even though its 512 at format) and get it to report more accurate sizes. Now on to tinker on an ESP32 devkit that just came in the mail :)