carlk3 / no-OS-FatFS-SD-SPI-RPi-Pico

A FAT filesystem with SPI driver for SD card on Raspberry Pi Pico
Apache License 2.0
273 stars 47 forks source link

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.


no-OS-FatFS-SD-SPI-RPi-Pico

Simple library for SD Cards on the Pico

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.

image

Features:

Resources Used

Performance

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

Prerequisites:

Some people have made micro SD card sockets by soldering pins onto old SD Card adapters.

image

image

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

Construction:

Pull Up Resistors and other electrical considerations

Notes about Card Detect

Notes about prewired boards with SD card sockets:

Firmware:

Customizing for the Hardware Configuration

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.

An instance of 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;
//...
};

An instance of 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;

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

Static vs. Dynamic Configuration

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.

image

Using the Application Programming Interface

After stdio_init_all();, time_init();, and whatever other Pico SDK initialization is required, call sd_init_driver(); to initialize the SPI block device driver. [sd_init_driver() is now[^2] called implicitly.]

Next Steps

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! image

Appendix A: Adding Additional Cards

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

Wiring:

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.

Firmware:

Appendix B: Operation of no-OS-FatFS/example:

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.

Specifies the directory to be set as current directory. e.g.: chdrive 1: getfree []: Print the free space on drive cd : Changes the current directory of the logical drive. Specifies the directory to be set as current directory. e.g.: cd 1:/dir1 mkdir : Make a new directory. Specifies the name of the directory to be created. e.g.: mkdir /dir1 ls: List directory cat : Type file contents simple: Run simple FS tests big_file_test : Writes random data to file . must be multiple of 512. e.g.: big_file_test bf 1048576 1 or: big_file_test big3G-3 0xC0000000 3 cdef: Create Disk and Example Files Expects card to be already formatted and mounted start_logger: Start Data Log Demo stop_logger: Stop Data Log Demo ``` ## Appendix C: Troubleshooting * The first thing to try is lowering the SPI baud rate (see hw_config.c). This will also make it easier to use things like logic analyzers. * Make sure the SD card(s) are getting enough power. Try an external supply. Try adding a decoupling capacitor between Vcc and GND. * Hint: check voltage while formatting card. It must be 2.7 to 3.6 volts. * Hint: If you are powering a Pico with a PicoProbe, try adding a USB cable to a wall charger to the Pico under test. * Try another brand of SD card. Some handle the SPI interface better than others. (Most consumer devices like cameras or PCs use the SDIO interface.) I have had good luck with SanDisk. * Tracing: Most of the source files have a couple of lines near the top of the file like: ``` #define TRACE_PRINTF(fmt, args...) // Disable tracing //#define TRACE_PRINTF printf // Trace with printf ``` You can swap the commenting to enable tracing of what's happening in that file. * Logic analyzer: for less than ten bucks, something like this [Comidox 1Set USB Logic Analyzer Device Set USB Cable 24MHz 8CH 24MHz 8 Channel UART IIC SPI Debug for Arduino ARM FPGA M100 Hot](https://smile.amazon.com/gp/product/B07KW445DJ/) and [PulseView - sigrok](https://sigrok.org/) make a nice combination for looking at SPI, as long as you don't run the baud rate too high. * Get yourself a protoboard and solder everything. So much more reliable than solderless breadboard! ![image](https://github.com/carlk3/FreeRTOS-FAT-CLI-for-RPi-Pico/blob/master/images/PXL_20211214_165648888.MP.jpg) [^1]: as of [Pull Request #12 Dynamic configuration](https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico/pull/12) (in response to [Issue #11 Configurable GPIO pins](https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico/issues/11)), Sep 11, 2021 [^2]: as of [Pull Request #5 Bug in ff_getcwd when FF_VOLUMES < 2](https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico/pull/5), Aug 13, 2021 [^3]: In my experience, the Card Detect switch on these doesn't work worth a damn. This might not be such a big deal, because according to [Physical Layer Simplified Specification](https://www.sdcard.org/downloads/pls/) the Chip Select (CS) line can be used for Card Detection: "At power up this line has a 50KOhm pull up enabled in the card... For Card detection, the host detects that the line is pulled high." However, the Adafruit card has it's own 47 kΩ pull up on CS, rendering it useless for Card Detection.