carlk3 / FreeRTOS-FAT-CLI-for-RPi-Pico

Project to add SD cards as peripherals on Raspberry Pi Pico.
Apache License 2.0
43 stars 12 forks source link

FreeRTOS-FAT-CLI-for-RPi-Pico

v2.13.0

=============================

C/C++ Library for SD Cards on the Pico

This project is essentially a FreeRTOS+FAT Media Driver for the Raspberry Pi Pico, using Serial Peripheral Interface (SPI), based on SDBlockDevice from Mbed OS 5, and/or a 4-bit Secure Digital Input Output (SDIO) driver derived from ZuluSCSI-firmware. It is wrapped up in a complete runnable project, with a little command line interface, some self tests, and an example data logging application.

What's new

v2.13.0

Ported to SDK 2 and Pico 2.

v2.12.0

Add support for running without Chip Select (CS) (formerly Slave Select [SS]).

v2.11.0

For required migration actions, see Appendix A: Migration actions.

Note: Release 1 remains available on the v1.0.0 branch.

Features

Limitations

Resources Used

SPI and SDIO can share the same DMA IRQ.

For the complete examples/command_line application, configured for oneSDIO-attached card, MinSizeRel build, as reported by link flag -Wl,--print-memory-usage:

[build] Memory region         Used Size  Region Size  %age Used
[build]            FLASH:      160400 B         2 MB      7.65%
[build]              RAM:      221584 B       256 KB     84.53%

The high RAM consumption is because I chose to devote 192 kB to the FreeRTOS Heap4:

  #define configTOTAL_HEAP_SIZE                   192 * 1024

in FreeRTOSConfig.h on the theory that if you're running FreeRTOS, you're more likely to use pvPortMalloc() than malloc(). mounting the SD card takes 2504 bytes of heap. After running the cvef (Create and Verify Example Files) test:

> heap-stats
Configured total heap size:     196608
Free bytes in the heap now:     193480
Minimum number of unallocated bytes that have ever existed in the heap: 192424

so the maximum heap utilization was 4184 bytes, or about 1.6 % of the Pico's RAM.

Performance

Writing and reading a file of 200 MiB of psuedorandom data on the same Silicon Power 3D NAND U1 32GB microSD card inserted into a Pico Stackable, Plug & Play SD Card Expansion Module at the default Pico system clock frequency (clk_sys) of 125 MHz, MinSizeRel build, using the command big_file_test bf 200 x. once on SPI and one on SDIO.

Results from a port of SdFat's bench:

For example, using the command mtbft 80 /sd0/bf to write a 80 MiB file to a single SDIO-attached SD card, I got a transfer rate of 6.46 MiB/s.

Using the command mtbft 40 /sd0/bf /sd3/bf to write 40 MiB files on two SDIO-attached SD cards, I got a transfer rate of 12.4 MiB/s.

(This test includes the time to fill or check the buffer in the transfer rate calculation, so the actual write or read performance is higher.)

This gives a speedup of about 1.9 X for two cards vs a single card.

Choosing the Interface Type(s)

The main reason to use SDIO is for the much greater speed that the 4-bit wide interface gets you. However, you pay for that in pins. SPI can get by with four GPIOs for the first card and one more for each additional card. SDIO needs at least six GPIOs, and the 4 bits of the data bus have to be on consecutive GPIOs. It is possible to put more than one card on an SDIO bus (each card has an address in the protocol), but at the higher speeds (higher than this implementation can do) the tight timing requirements don't allow it. I haven't tried it. Running multiple SD cards on multiple SDIO buses works, but it does require a lot of pins and PIO resources.

You can mix and match the attachment types. One strategy: use SDIO for cache and SPI for backing store. A similar strategy that I have used: SDIO for fast, interactive use, and SPI to offload data.

Hardware

My boards

Prewired boards with SD card sockets

There are a variety of RP2040 boards on the market that provide an integrated µSD socket. As far as I know, most are useable with this library.

Rolling your own

Prerequisites:

image

Please see here for an example wiring table for an SPI attached card and an SDIO attached card on the same Pico. SPI and SDIO at 31.5 MHz are pretty demanding electrically. You need good, solid wiring, especially for grounds. A printed circuit board with a ground plane would be nice!

image image

Construction

Pull Up Resistors and other electrical considerations

Notes about Card Detect

Running without Chip Select (CS) (formerly Slave Select [SS])

If you have only one SD card, and you are short on GPIOs, you may be able to run without CS/SS. I know of no guarantee that this will work for all SD cards. The Physical Layer Simplified Specification says

Every command or data block is built of 8-bit bytes and is byte aligned with the CS signal... The card starts to count SPI bus clock cycle at the assertion of the CS signal... The host starts every bus transaction by asserting the CS signal low.

It doesn't say what happens if the CS signal is always asserted. However, it worked for me with:

You will need to pull down the CS/SS line on the SD card with hardware. (I.e., connect CS to GND. CS is active low.)

In the hardware configuration definition, set ss_gpio to -1. See An instance of sd_spi_if_t describes the configuration of one SPI to SD card interface..

Firmware

Dependencies

Procedure

Customizing for the Hardware Configuration

This library can support many different hardware configurations. Therefore, the hardware configuration is not defined in the library. Instead, the application must provide it. The configuration is defined in "objects" of type spi_t (see sd_driver/spi.h), sd_spi_if_t, sd_sdio_if_t, and sd_card_t (see sd_driver/sd_card.h).

Illustration of the configuration dev_brd.hw_config.c

Illustration of the configuration dev_brd.hw_config.c

An instance of sd_card_t describes the configuration of one SD card socket

struct sd_card_t {
    const char *device_name;
    const char *mount_point; // Must be a directory off the file system's root directory and must be an absolute path that starts with a forward slash (/)
    sd_if_t type;
    union {
        sd_spi_if_t *spi_if_p;
        sd_sdio_if_t *sdio_if_p;
    };
    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
    bool card_detect_use_pull;
    bool card_detect_pull_hi;
//...
}

An instance of sd_sdio_if_t describes the configuration of one SDIO to SD card interface.

typedef struct sd_sdio_if_t {
    // See sd_driver\SDIO\rp2040_sdio.pio for SDIO_CLK_PIN_D0_OFFSET
    uint CLK_gpio;  // Must be (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32
    uint CMD_gpio;
    uint D0_gpio;      // D0
    uint D1_gpio;      // Must be D0 + 1
    uint D2_gpio;      // Must be D0 + 2
    uint D3_gpio;      // Must be D0 + 3
    PIO SDIO_PIO;      // either pio0 or pio1
    uint DMA_IRQ_num;  // DMA_IRQ_0 or DMA_IRQ_1
    bool use_exclusive_DMA_IRQ_handler;
    uint baud_rate;
    // Drive strength levels for GPIO outputs:
    // GPIO_DRIVE_STRENGTH_2MA 
    // GPIO_DRIVE_STRENGTH_4MA
    // GPIO_DRIVE_STRENGTH_8MA 
    // GPIO_DRIVE_STRENGTH_12MA
    bool set_drive_strength;
    enum gpio_drive_strength CLK_gpio_drive_strength;
    enum gpio_drive_strength CMD_gpio_drive_strength;
    enum gpio_drive_strength D0_gpio_drive_strength;
    enum gpio_drive_strength D1_gpio_drive_strength;
    enum gpio_drive_strength D2_gpio_drive_strength;
    enum gpio_drive_strength D3_gpio_drive_strength;
//...
} sd_sdio_t;

Specify D0_gpio, but pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio and are set implicitly. The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio. As of this writing, SDIO_CLK_PIN_D0_OFFSET is 30, which is -2 in mod32 arithmetic, so:

These pin assignments are set implicitly and must not be set explicitly.

An instance of sd_spi_if_t describes the configuration of one SPI to SD card interface.

typedef struct sd_spi_if_t {
    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
    // Drive strength levels for GPIO outputs:
    // GPIO_DRIVE_STRENGTH_2MA 
    // GPIO_DRIVE_STRENGTH_4MA
    // GPIO_DRIVE_STRENGTH_8MA 
    // GPIO_DRIVE_STRENGTH_12MA
    bool set_drive_strength;
    enum gpio_drive_strength ss_gpio_drive_strength;
} sd_spi_if_t;

You must provide a definition for the functions declared in sd_driver/hw_config.h

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.

Other Application-Specific Customization

Two other files contain definitions that should be adjusted for your particular hardware and application requirements:

For examples of these files, see examples/commmand_line/include.

Timeouts

Indefinite timeouts are normally bad practice, because they make it difficult to recover from an error. Therefore, we have timeouts all over the place. To make these configurable, they are collected in sd_timeouts_t sd_timeouts in sd_timeouts.c. The definition has the weak attribute, so it can be overridden by user code. For example, in hw_config.c you could have:

sd_timeouts_t sd_timeouts = {
    .sd_command = 2000, // Timeout in ms for response
    .sd_command_retries = 3, // Times SPI cmd is retried when there is no response
//...
    .sd_sdio_begin = 1000, // Timeout in ms for response
    .sd_sdio_stopTransmission = 200, // Timeout in ms for response
};

Messages

Sometimes problems arise when attempting to use SD cards. At the FreeRTOS-Plus-FAT API level, it can be difficult to diagnose problems. You get an error number, but it might just tell you pdFREERTOS_ERRNO_EIO ("I/O error"), for example, without telling you what you need to know in order to fix the problem. The library generates messages that might help. These are classed into Error, Informational, and Debug messages.

Messages from the SD card driver

Two compile definitions control how these are handled in the SD card driver (or " media driver "):

Messages are sent using EMSG_PRINTF, IMSG_PRINTF, and DBG_PRINTF macros, which can be redefined (see my_debug.h). By default, these call error_message_printf, info_message_printf, and debug_message_printf, which are implemented as weak functions, meaning that they can be overridden by strongly implementing them in user code. If USE_PRINTF is defined and not zero, the weak implementations will write to the Pico SDK's stdout. Otherwise, they will format the messages into strings and forward to put_out_error_message, put_out_info_message, and put_out_debug_message. These are implemented as weak functions that do nothing. You can override these to send the output somewhere.

Messages from FreeRTOS-Plus-FAT

FreeRTOS-Plus-FAT uses a macro called FF_PRINTF, which is defined in the FreeRTOS-Plus-FAT Configuration file. See Other Application-Specific Customization.

Using the Application Programming Interface

In general, you use the FreeRTOS-Plus-FAT APIs in your application. One function that is not documented as part of the standard API but is conventional in FreeRTOS-Plus-FAT:

FF_Disk_t *FF_SDDiskInit( const char *pcName ) Initializes the "disk" (SD card) and returns a pointer to an FF_Disk_t structure. This can then be passed to other functions in the FreeRTOS-Plus-FAT Native API such as FF_Mount and FF_FS_Add. The parameter pcName is the Device Name; device_name in struct sd_card_t.

A typical sequence would be:

See FreeRTOS-FAT-CLI-for-RPi-Pico/examples/simple_sdio/ for an example.

You may call sd_init_driver() to explicitly initialize the block device driver. It is called implicitly by FF_SDDiskInit, but you might want to call it sooner. For example, you might want to get the GPIOs configured before setting up a card detect interrupt handler. (See examples/command_line/src/unmounter.c.) You might want to call it to get the SD cards into SPI mode so that they can share an SPI bus with other devices. (See Cosideration on Multi-slave Configuration.) sd_init_driver() must be called from a FreeRTOS task.

Next Steps

If you want to use FreeRTOS+FAT+CLI as a library embedded in another project, use something like:

  git submodule add git@github.com:carlk3/FreeRTOS-FAT-CLI-for-RPi-Pico.git

or

  git submodule add https://github.com/carlk3/FreeRTOS-FAT-CLI-for-RPi-Pico.git

You will need to pick up the library in CMakeLists.txt:

add_subdirectory(FreeRTOS-FAT-CLI-for-RPi-Pico/FreeRTOS+FAT+CLI build)
target_link_libraries(_my_app_ FreeRTOS+FAT+CLI)

Happy hacking!

Future Directions

You are welcome to contribute to this project! Just submit a Pull Request in GitHub. Here are some ideas for future enhancements:

Migrating from Release 1.0.0

Appendix B: Operation of command_line example

date: Print current date and time

format : Creates an FAT/exFAT volume on the device name. e.g.: format sd0

mount [device_name...]: Makes the specified device available at its mount point in the directory tree. e.g.: mount sd0

unmount : Unregister the work area of the volume

info : Print information about an SD card

cd : Changes the current directory of the device name.

Specifies the directory to be set as current directory. e.g.: cd /dir1 mkdir : Make a new directory. Specifies the name of the directory to be created. e.g.: mkdir /dir1 rm [options] : Removes (deletes) a file or directory Specifies the path to the file or directory to be removed Options: -d Remove an empty directory -r Recursively remove a directory and its contents cp : Copies to mv : Moves (renames) to pwd: Print Working Directory ls [pathname]: List directory cat : Type file contents simple: Run simple FS tests lliot !DESTRUCTIVE! Low Level I/O Driver Test The SD card will need to be reformatted after this test. e.g.: lliot sd0 bench : A simple binary write/read benchmark big_file_test : Writes random data to file . Specify in units of mebibytes (2^20, or 1024*1024 bytes) e.g.: big_file_test /sd0/bf 1 1 or: big_file_test /sd1/big3G-3 3072 3 Alias for big_file_test mtbft [pathname 1...] Multi Task Big File Test pathname: Absolute path to a file (must begin with '/' and end with file name) cvef: Create and Verify Example Files Expects card to be already formatted and mounted swcwdt: Stdio With CWD Test Expects card to be already formatted and mounted. Note: run cvef first! loop_swcwdt: Run Create Disk and Example Files and Stdio With CWD Test in a loop. Expects card to be already formatted and mounted. Note: Stop with "die". mtswcwdt: MultiTask Stdio With CWD Test e.g.: mtswcwdt start_logger: Start Data Log Demo die: Kill background tasks undie: Allow background tasks to live again task-stats: Show task statistics heap-stats: Show heap statistics run-time-stats: Displays a table showing how much processing time each FreeRTOS task has used help: Shows this command help. ``` ![image](https://github.com/carlk3/no-OS-FatFS-SD-SDIO-SPI-RPi-Pico/assets/50121841/8a28782e-84c4-40c8-8757-a063a4b83292) ## Appendix C: 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 * [The hardware configuration](#customizing-for-the-hardware-configuration) must be edited to add a new instance of [sd_card_t](#an-instance-of-sd_card_t-describes-the-configuration-of-one-sd-card-socket) and its interface [sd_sdio_if_t](#an-instance-of-sd_sdio_if_t-describes-the-configuration-of-one-sdio-to-sd-card-interface) or [sd_spi_if_t](#an-instance-of-sd_spi_if_t-describes-the-configuration-of-one-spi-to-sd-card-interface). ## Appendix D: Performance Tuning Tips Obviously, if possible, use 4-bit SDIO instead of 1-bit SPI. (See [Choosing the Interface Type(s)](#choosing-the-interface-types).) Obviously, set the baud rate as high as you can. (See [Customizing for the Hardware Configuration](#customizing-for-the-hardware-configuration)). If you are using SPI, try SPI mode 3 (CPOL=1, CPHA=1) instead of 0 (CPOL=0, CPHA=0). (See [SPI Controller Configuration](#spi-controller-configuration).) This could buy a 15% speed boost. TL;DR: In general, it is much faster to transfer a given number of bytes in one large write (or read) than to transfer the same number of bytes in multiple smaller writes (or reads). One quick and easy way to speed up many applications is to take advantage of the buffering built into the C library for standard I/O streams. (See [fopencookie—open a stream with custom callbacks](https://sourceware.org/newlib/libc.html#fopencookie) and [setvbuf—specify file or stream buffering](https://sourceware.org/newlib/libc.html#setvbuf)). The application would use [fprintf](https://sourceware.org/newlib/libc.html#sprintf) instead of [ff_fprintf](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_FAT/stdio_API/ff_fprintf.html), or [fwrite](https://sourceware.org/newlib/libc.html#fwrite) instead of [ff_fwrite](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_FAT/stdio_API/ff_fwrite.html), for example. If you are using SDIO, it is critically important for performance to use `setvbuf` to set the buffer to an `aligned` buffer. Also, the buffer should be a multiple of the SD block size, 512 bytes, in size. For example: ```C static char vbuf[1024] __attribute__((aligned)); int err = setvbuf(file_p, vbuf, _IOFBF, sizeof vbuf); ``` If you have a record-oriented application, and the records are multiples of 512 bytes in size, you might not see a significant speedup. However, if, for example, you are writing text files with no fixed record length, the speedup can be great. See [examples/stdio_buffering/](https://github.com/carlk3/FreeRTOS-FAT-CLI-for-RPi-Pico/tree/master/examples/stdio_buffering). Now, for the details: The modern SD card is a block device, meaning that the smallest addressable unit is a a block (or "sector") of 512 bytes. So, it helps performance if your write size is a multiple of 512. If it isn't, partial block writes involve reading the existing block, modifying it in memory, and writing it back out. With all the space in SD cards these days, it can be well worth it to pad a record length to a multiple of 512. Generally, flash memory has to be erased before it can be written, and the minimum erase size is the "allocation unit" or "segment": > AU (Allocation Unit): is a physical boundary of the card and consists of one or more blocks and its size depends on each card. The maximum AU size is defined for memory capacity. Furthermore AU is the minimal unit in which the card guarantees its performance for devices which complies with Speed Class Specification. The information about the size and the Speed Class are stored in the SD Status. > -- SD Card Association; Physical Layer Specification Version 3.01 There is a controller in each SD card running all kinds of internal processes. When an amount of data to be written is smaller than a segment, the segment is read, modified in memory, and then written again. SD cards use various strategies to speed this up. Most implement a "translation layer". For any I/O operation, a translation from virtual to physical address is carried out by the controller. If data inside a segment is to be overwritten, the translation layer remaps the virtual address of the segment to another erased physical address. The old physical segment is marked dirty and queued for an erase. Later, when it is erased, it can be reused. Usually, SD cards have a cache of one or more segments for increasing the performance of read and write operations. The SD card is a "black box": much of this is invisible to the user, except as revealed in the Card-Specific Data register (CSD), SD_STATUS, and the observable performance characteristics. So, the write times are far from deterministic. The Allocation Unit is typically 4 MiB for a 16 or 32 GB card, for example. Of course, nobody is going to be using 4 MiB write buffers on a Pico, but the AU is still important. For good performance and wear tolerance, it is recommended that the "disk partition" be aligned to an AU boundary. [SD Memory Card Formatter](https://www.sdcard.org/downloads/formatter/) makes this happen. For my 16 GB card, it set "Partition Starting Offset 4,194,304 bytes". This accomplished by inserting "hidden sectors" between the actual start of the physical media and the start of the volume. Also, it might be helpful to have your write size be some factor of the segment size. There are more variables at the file system level. The FAT "allocation unit" (not to be confused with the SD card "allocation unit"), also known as "cluster", is a unit of "disk" space allocation for files. These are identically sized small blocks of contiguous space that are indexed by the File Allocation Table. When the size of the allocation unit is 32768 bytes, a file with 100 bytes in size occupies 32768 bytes of disk space. The space efficiency of disk usage gets worse with increasing size of allocation unit, but, on the other hand, the read/write performance increases. Therefore the size of allocation unit is a trade-off between space efficiency and performance. This is something you can change by formatting the SD card. See [FF_Format](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_FAT/native_API/FF_Format.html) and [Description of Default Cluster Sizes for FAT32 File System](https://support.microsoft.com/en-us/topic/description-of-default-cluster-sizes-for-fat32-file-system-905ea1b1-5c4e-a03f-3863-e4846a878d31). Again, there might be some advantage to making your write size be some factor or multiple of the FAT allocation unit. The `info` command in [examples/command_line](https://github.com/carlk3/FreeRTOS-FAT-CLI-for-RPi-Pico/tree/master/examples/command_line) reports the allocation unit. [File fragmentation](https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Fragmentation) can lead to long access times. Fragmented files can result from multiple files being incrementally extended in an interleaved fashion. One strategy to avoid fragmentation is to pre-allocate files to their maximum expected size, then reuse these files at run time. Since a flash memory erase block is typically filled with 0xFF after an erase (although some cards use 0x00), you could write a file full of 0xFF bytes (chosen to avoid flash memory "wear") ahead of time. (Also, see [FAT Volume Image Creator](http://elm-chan.org/fsw/ff/res/mkfatimg.zip) (Pre-creating built-in FAT volume).) Then [ff_fopen](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_FAT/stdio_API/ff_fopen.html) it in mode "r+" at run time. Obviously, you will need some way to keep track of how much valid data is in the file. You could use a file header. Alternatively, if the file contains text, you could write an End-Of-File (EOF) character. In DOS, this is the character 26, which is the Control-Z character. Alternatively, if the file contains records, each record could contain a magic number or checksum, so you can easily tell when you've reached the end of the valid records. (This might be an obvious choice if you're padding the record length to a multiple of 512 bytes.) For SDIO-attached cards, alignment of the read or write buffer is quite important for performance. This library uses DMA with `DMA_SIZE_32`, and the read and write addresses must always be aligned to the current transfer size, i.e., four bytes. (For example, you could specify that the buffer has [\_\_attribute\_\_ ((aligned (4))](https://gcc.gnu.org/onlinedocs/gcc-3.1.1/gcc/Type-Attributes.html).) If the buffer address is not aligned, the library copies each block into a temporary buffer that is aligned and then writes it out, one block at a time. (The SPI driver uses `DMA_SIZE_8` so the alignment isn't important.) For a logging type of application, opening and closing a file for each update is hugely inefficient, but if you can afford the time it can be a good way to minimize data loss in the event of an unexpected power loss or that kind of thing. You can also try to find a middle ground by periodically closing and reopening a file, or switching to a new file. A well designed directory structure can act as a sort of hierarchical database for rapid retrieval of records distributed across many small files. ## Appendix E: Troubleshooting * **Check your grounds!** Maybe add some more if you were skimpy with them. The Pico has six of them. * Turn on `DBG_PRINTF`. (See #messages-from-the-sd-card-driver.) For example, in `CMakeLists.txt`, ```CMake add_compile_definitions(USE_PRINTF USE_DBG_PRINTF) ``` You might see a clue in the messages. * Power cycle the SD card. Once an SD card is in SPI mode, the only way to get it back to SD mode is to power cycle it. At power up, an SD card's CS/DAT3 line has a 50 kΩ pull up enabled in the card, but it will be disabled if the card is initialized, and it won't be enabled again until the card is power cycled. * Try lowering the SPI or SDIO baud rate (e.g., in `hw_config.c`). This will also make it easier to use things like logic analyzers. * For SPI, this is in the [spi_t](#an-instance-of-spi_t-describes-the-configuration-of-one-rp2040-spi-controller) instance. * For SDIO, this is in the [sd_sdio_if_t](#an-instance-of-sd_sdio_if_t-describes-the-configuration-of-one-sdio-to-sd-card-interface) instance. * 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, PNY, and Silicon Power. * Tracing: Most of the source files have a couple of lines near the top of the file like: ```C #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! * Better yet, go to somwhere like [JLCPCB](https://jlcpcb.com/) and get a printed circuit board! [^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 - Card Detect / Data Line [Bit 3], rendering it useless for Card Detection. [^4]: [Physical Layer Simplified Specification](https://www.sdcard.org/downloads/pls/) [^5]: Rationale: Instances of `sd_spi_if_t` or `sd_sdio_if_t` are separate objects instead of being embedded in `sd_card_t` objects because `sd_sdio_if_t` carries a lot of state information with it (including things like data buffers). The union of the two types has the size of the largest type, which would result in a lot of wasted space in instances of `sd_spi_if_t`. I had another solution using `malloc`, but some people are frightened of `malloc` in embedded systems. [^6]: *SPI Mode* in [How to Use MMC/SDC](http://elm-chan.org/docs/mmc/mmc_e.html#spimode)