Note:
This repository is more or less frozen. Exceptions will be made for bug fixes and compatibility with new releases of the Pico SDK and FatFS.
Any new development will take place in the new repository: no-OS-FatFS-SD-SDIO-SPI-RPi-Pico.
This repository supports SPI-attached SD cards only. The new repository supports SPI- and/or SDIO-attached SD cards.
At the heart of this library is ChaN's FatFs - Generic FAT Filesystem Module. It also contains a Serial Peripheral Interface (SPI) SD Card block driver for the Raspberry Pi Pico, derived from SDBlockDevice from Mbed OS 5. It is wrapped up in a complete runnable project, with a little command line interface, some self tests, and an example data logging application.
dma_claim_unused_channel
.irq_add_shared_handler
and enabled.Using a Debug build: Writing and reading a file of 0xC0000000 (3,221,225,472) random bytes (3 GiB) on a SanDisk 32GB card with SPI baud rate 12,500,000:
On a SanDisk Class 4 16 GB card, I have been able to push the SPI baud rate as far as 20,833,333 which increases the transfer speed proportionately (but SDIO would be faster!).
Something like the Adafruit Micro SD SPI or SDIO Card Breakout Board[^3] or SparkFun microSD Transflash Breakout
Warning: Avoid Aduino breakout boards like these: Micro SD Storage Board Micro SD Card Modules. They are designed for 5 V Arduino signals. Many use simple resistor dividers to drop the signal voltage, and will not work properly with the 3.3 V Raspberry Pi Pico. However, see The 5V Arduino SD modules might work with a simple trick.
Some people have made micro SD card sockets by soldering pins onto old SD Card adapters.
SPI0 | GPIO | Pin | SPI | MicroSD 0 | Description | |
---|---|---|---|---|---|---|
MISO | RX | 16 | 21 | DO | DO | Master In, Slave Out |
CS0 | CSn | 17 | 22 | SS or CS | CS | Slave (or Chip) Select |
SCK | SCK | 18 | 24 | SCLK | CLK | SPI clock |
MOSI | TX | 19 | 25 | DI | DI | Master Out, Slave In |
CD | 22 | 29 | CD | Card Detect | ||
GND | 18,23 | GND | Ground | |||
3v3 | 36 | 3v3 | 3.3 volt power |
I don't think the Pimoroni Pico VGA Demo Base can work with a built in RP2040 SPI controller. It looks like RP20040 SPI0 SCK needs to be on GPIO 2, 6, or 18 (pin 4, 9, or 24, respectively), but Pimoroni wired it to GPIO 5 (pin 7).
The SparkFun RP2040 Thing Plus works well, on SPI1. The only downside to this board is that it's difficult to access the signal lines if you want to look at them with, say, a logic analyzer or an oscilloscope.
SPI0 | GPIO | Description | |
---|---|---|---|
MISO | RX | 12 | Master In, Slave Out |
CS0 | CSn | 09 | Slave (or Chip) Select |
SCK | SCK | 14 | SPI clock |
MOSI | TX | 15 | Master Out, Slave In |
CD | Card Detect |
Maker Pi Pico looks like it could work on SPI1. It has CS on GPIO 15, which is not a pin that the RP2040 built in SPI1 controller would drive as CS, but this driver controls CS explicitly with gpio_put
, so it doesn't matter.
git clone --recurse-submodules git@github.com:carlk3/no-OS-FatFS-SD-SPI-RPi-Pico.git no-OS-FatFS
ff14a/source/ffconf.h
as desiredpico_enable_stdio_uart
and pico_enable_stdio_usb
in CMakeLists.txt as you prefer.
(See 4.1. Serial input and output on Raspberry Pi Pico in Getting started with Raspberry Pi Pico and 2.7.1. Standard Input/Output (stdio) Support in Raspberry Pi Pico C/C++ SDK.) cd no-OS-FatFS/example
mkdir build
cd build
cmake ..
make
This library can support many different hardware configurations.
Therefore, the hardware configuration is not defined in the library[^1].
Instead, the application must provide it.
The configuration is defined in "objects" of type spi_t
(see sd_driver/spi.h
) and sd_card_t
(see sd_driver/sd_card.h
).
There can be one or more objects of both types.
These objects specify which pins to use for what, SPI baud rate, features like Card Detect, etc.
sd_card_t
describes the configuration of one SD card socket.// "Class" representing SD Cards
struct sd_card_t {
const char *pcName;
spi_t *spi;
// Slave select is here instead of in spi_t because multiple SDs can share an SPI.
uint ss_gpio; // Slave select for this SD card
bool use_card_detect;
uint card_detect_gpio; // Card detect; ignored if !use_card_detect
uint card_detected_true; // Varies with card socket; ignored if !use_card_detect
// Drive strength levels for GPIO outputs.
// enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2,
// GPIO_DRIVE_STRENGTH_12MA = 3 }
bool set_drive_strength;
enum gpio_drive_strength ss_gpio_drive_strength;
//...
};
pcName
FatFs Logical Drive name (or "number")ss_gpio
Slave Select (or Chip Select [CS]) for this SD carduse_card_detect
Whether or not to use Card Detectcard_detect_gpio
GPIO number of the Card Detect, connected to the SD card socket's Card Detect switch (sometimes marked DET)card_detected_true
What the GPIO read returns when a card is present (Some sockets use active high, some low)set_drive_strength
Whether or not to set the drive strengthss_gpio_drive_strength
Drive strength for the SS (or CS)spi_t
describes the configuration of one RP2040 SPI controller.// "Class" representing SPIs
typedef struct {
// SPI HW
spi_inst_t *hw_inst;
uint miso_gpio; // SPI MISO GPIO number (not pin number)
uint mosi_gpio;
uint sck_gpio;
uint baud_rate;
// Drive strength levels for GPIO outputs.
// enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2,
// GPIO_DRIVE_STRENGTH_12MA = 3 }
bool set_drive_strength;
enum gpio_drive_strength mosi_gpio_drive_strength;
enum gpio_drive_strength sck_gpio_drive_strength;
//...
} spi_t;
hw_inst
Identifier for the hardware SPI instance (for use in SPI functions). e.g. spi0
, spi1
miso_gpio
SPI Master In, Slave Out (MISO) GPIO number (not Pico pin number). This is connected to the card's Data In (DI).mosi_gpio
SPI Master Out, Slave In (MOSI) GPIO number. This is connected to the card's Data Out (DO).sck_gpio
SPI Serial Clock GPIO number. This is connected to the card's Serial Clock (SCK).baud_rate
Frequency of the SPI Serial Clockset_drive_strength
Specifies whether or not to set the RP2040 GPIO drive strengthmosi_gpio_drive_strength
SPI Master Out, Slave In (MOSI) drive strengthsck_gpio_drive_strength
SPI Serial Clock (SCK) drive strengthYou must provide a definition for the functions declared in sd_driver/hw_config.h
:
size_t spi_get_num()
Returns the number of SPIs to use
spi_t *spi_get_by_num(size_t num)
Returns a pointer to the SPI "object" at the given (zero origin) index
size_t sd_get_num()
Returns the number of SD cards
sd_card_t *sd_get_by_num(size_t num)
Returns a pointer to the SD card "object" at the given (zero origin) index.
The definition of the hardware configuration can either be built in at build time, which I'm calling "static configuration", or supplied at run time, which I call "dynamic configuration".
In either case, the application simply provides an implementation of the functions declared in sd_driver/hw_config.h
.
simple_example.dir/hw_config.c
or example/hw_config.c
for examples of static configuration.dynamic_config_example/hw_config.cpp
for an example of dynamic configuration.After [sd_init_driver() is now[^2] called implicitly.]stdio_init_all();
, time_init();
, and whatever other Pico SDK initialization is required, call sd_init_driver();
to initialize the SPI block device driver.
simple_example
subdirectory.ff_stdio.h
and ff_stdio.c
, written for compatibility with FreeRTOS+FAT API (mainly so that I could reuse some tests from that environment.)There is a example data logging application in data_log_demo.c
.
It can be launched from the no-OS-FatFS/example
CLI with the start_logger
command.
(Stop it with the stop_logger
command.)
It records the temperature as reported by the RP2040 internal Temperature Sensor once per second
in files named something like /data/2021-03-21/11.csv
.
Use this as a starting point for your own data logging application!
If you want to use FatFs_SPI as a library embedded in another project, use something like:
git submodule add git@github.com:carlk3/no-OS-FatFS-SD-SPI-RPi-Pico.git
or
git submodule add https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico.git
You will need to pick up the library in CMakeLists.txt:
add_subdirectory(no-OS-FatFS-SD-SPI-RPi-Pico/FatFs_SPI build)
target_link_libraries(_my_app_ FatFs_SPI)
and #include "ff.h"
.
Happy hacking!
When you're dealing with information storage, it's always nice to have redundancy. There are many possible combinations of SPIs and SD cards. One of these is putting multiple SD cards on the same SPI bus, at a cost of one (or two) additional Pico I/O pins (depending on whether or you care about Card Detect). I will illustrate that example here.
To add a second SD card on the same SPI, connect it in parallel, except that it will need a unique GPIO for the Card Select/Slave Select (CSn) and another for Card Detect (CD) (optional).
Name | SPI0 | GPIO | Pin | SPI | SDIO | MicroSD 0 | MicroSD 1 |
---|---|---|---|---|---|---|---|
CD1 | 14 | 19 | CD | ||||
CS1 | 15 | 20 | SS or CS | DAT3 | CS | ||
MISO | RX | 16 | 21 | DO | DAT0 | DO | DO |
CS0 | 17 | 22 | SS or CS | DAT3 | CS | ||
SCK | SCK | 18 | 24 | SCLK | CLK | SCK | SCK |
MOSI | TX | 19 | 25 | DI | CMD | DI | DI |
CD0 | 22 | 29 | CD | ||||
GND | 18, 23 | GND | GND | ||||
3v3 | 36 | 3v3 | 3v3 |
As you can see from the table above, the only new signals are CD1 and CS1. Otherwise, the new card is wired in parallel with the first card.
hw_config.c
(or equivalent) must be edited to add a new instance to static sd_card_t sd_cards[]
ff14a/source/ffconf.h
. In particular, FF_VOLUMES
:
#define FF_VOLUMES 2
no-OS-FatFS/example
:tio
work OK. For example:
tio -m ODELBS /dev/ttyACM0
>
help
command describes the available commands:
setrtc <DD> <MM> <YY> <hh> <mm> <ss>:
Set Real Time Clock
Parameters: new date (DD MM YY) new time in 24-hour format (hh mm ss)
e.g.:setrtc 16 3 21 0 4 0
date: Print current date and time
lliot <drive#>: !DESTRUCTIVE! Low Level I/O Driver Test e.g.: lliot 1
format [<drive#:>]: Creates an FAT/exFAT volume on the logical drive. e.g.: format 0:
mount [<drive#:>]: Register the work area of the volume e.g.: mount 0:
unmount <drive#:>: Unregister the work area of the volume
chdrive <drive#:>: Changes the current directory of the logical drive.