CircuitPython SPI & SD Card

CircuitPython boards with at least 4 pins can take advantage of a full SPI interface to talk to complex devices like SD cards and color TFT displays.

The Gemma M0 only has 3 pads available which means that you can't have a full 4-pin SPI interface. You can still do a 2-wire SPI interface (say, clock and data out for talking to DotStar LEDs) or a 3-wire SPI interface where you have a chip select, clock and then one data line, the MAX31855 for example only needs 3 pins.

But other devices like the Trinket M0 have plenty of pins! You can easily wire up an SD card that lets you log or read data without being restricted to the small internal filesystem on the Trinket.

The Trinket M0 has only one hardware SPI port:

  • SCLK on D3
  • MOSI on D4
  • MISO on D2

SD cards also need a chip select line, but that can be any pin, we'll use D1

You'll need to install the CircuitPython adafruit_sdcard library file from our driver bundle. Visit the CircuitPython Libraries page for information on how to install it. You'll also need the adafruit_bus_device library folder.

List Files

Once you're ready, load this into main.py

import adafruit_sdcard
import busio
import digitalio
import board
import storage
import os

# Use any pin that is not taken by SPI
SD_CS = board.D0

# Connect to the card and mount the filesystem.
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = digitalio.DigitalInOut(SD_CS)
sdcard = adafruit_sdcard.SDCard(spi, cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

# Use the filesystem as normal! Our files are under /sd

# This helper function will print the contents of the SD
def print_directory(path, tabs = 0):
    for file in os.listdir(path):
        stats = os.stat(path+"/"+file)
        filesize = stats[6]
        isdir = stats[0] & 0x4000
    
        if filesize < 1000:
            sizestr = str(filesize) + " by"
        elif filesize < 1000000:
            sizestr = "%0.1f KB" % (filesize/1000)
        else:
            sizestr = "%0.1f MB" % (filesize/1000000)
    
        prettyprintname = ""
        for i in range(tabs):
            prettyprintname += "   "
        prettyprintname += file
        if isdir:
            prettyprintname += "/"
        print('{0:<40} Size: {1:>10}'.format(prettyprintname, sizestr))
        
        # recursively print directory contents
        if isdir:
            print_directory(path+"/"+file, tabs+1)


print("Files on filesystem:")
print("====================")
print_directory("/sd")

Once it's loaded up, open up the REPL (and restart it with ^D if necessary) to get a printout of all the files included. We recursively print out all files and also the filesize. This is a good demo to start with because you can at least tell if your files exist!

But you probably want to do a little more, lets log the temperature from the chip to a file.

Here's the new script

import adafruit_sdcard
import microcontroller
import busio
import digitalio
import board
import storage
import os
import time

# Use any pin that is not taken by SPI
SD_CS = board.D0

led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

# Connect to the card and mount the filesystem.
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = digitalio.DigitalInOut(SD_CS)
sdcard = adafruit_sdcard.SDCard(spi, cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

# Use the filesystem as normal! Our files are under /sd

print("Logging temperature to filesystem")
# append to the file!
while True:
    # open file for append
    with open("/sd/temperature.txt", "a") as f:
        led.value = True   # turn on LED to indicate we're writing to the file
        t = microcontroller.cpu.temperature
        print("Temperature = %0.1f" % t)
        f.write("%0.1f\n" % t)
        led.value = False   # turn off LED to indicate we're done
    # file is saved
    time.sleep(1)

When saved, the Trinket will start saving the temperature once per second to the SD card under the file temperature.txt

The key part of this demo is in these lines:

print("Logging temperature to filesystem")
# append to the file!
while True:
    # open file for append
    with open("/sd/temperature.txt", "a") as f:
        led.value = True   # turn on LED to indicate we're writing to the file
        t = microcontroller.cpu.temperature
        print("Temperature = %0.1f" % t)
        f.write("%0.1f\n" % t)
        led.value = False   # turn off LED to indicate we're done
    # file is saved
    time.sleep(1)

This is a slightly complex demo but it's for a good reason. We use with (a 'context') to open the file for appending, that way the file is only opened for the very short time its written to. This is safer because then if the SD card is removed or the board turned off, all the data will be safe(r).

We use the LED to let the person using this know that the temperature is being written, it turns on just before the write and then off right after.

After the LED is turned off the with ends and the context closes, the file is safely stored.

Last updated on 2017-12-01 at 04.45.54 PM Published on 2017-08-23 at 05.57.02 PM