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, and a 4-bit wide 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.
Fix initialization problem when multiple SD cards share an SPI bus. The fix puts all cards into SPI mode at driver initialization time (rather than deferring until media initialization time).
Faster way to read trailing CRC bytes
Substantial performance improvement for writing large contiguous blocks of data. This is accomplished by avoiding sending "stop transmission" for as long as possible. The improved throughput is especially noticeable on SDIO-attached cards, since the 4 bit wide SD interface is less of a bottleneck than the 1 bit wide SPI.
Added ability to statically assign DMA channels for SPI. (See SPI Controller Configuration.)
Fix miscalculation in get_num_sectors
.
pcName
member of sd_card_t
has been removed.
Rationale: FatFs provides ways to customize the volume ID strings (or "drive prefixes") used
to designate logical drives (see
FF_STR_VOLUME_ID).
pcName
was redundant and confusing.examples/unix_like
. This example illustrates the use of Unix style drive prefixes. (See Path Names on the FatFs API). It also demonstrates customization of the FatFs configuration file, ffconf.h
.For required migration actions, see Appendix A: Migration actions.
Note: v1.2.4 and v1.1.2 remain available on the v1.2.4 branch and the v1.1.2 branch, repectively.
dma_claim_unused_channel
.irq_add_shared_handler
or irq_set_exclusive_handler
(configurable) and enabled.dma_claim_unused_channel
irq_add_shared_handler
or irq_set_exclusive_handler
(configurable) and enabled.SPI and SDIO can share the same DMA IRQ.
For the complete
examples/command_line application,
configured for one SPI attached card and one SDIO-attached card, release build,
as reported by link flag -Wl,--print-memory-usage
:
Memory region Used Size Region Size %age Used
FLASH: 167056 B 2 MB 7.97%
RAM: 17524 B 256 KB 6.68%
A MinSizeRel
build for a single SPI-attached card:
Memory region Used Size Region Size %age Used
FLASH: 124568 B 2 MB 5.94%
RAM: 12084 B 256 KB 4.61%
A MinSizeRel
build configured for three SPI-attached and one SDIO-attached SD cards:
Memory region Used Size Region Size %age Used
FLASH: 133704 B 2 MB 6.38%
RAM: 21172 B 256 KB 8.08%
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, Release
build, using the command
big_file_test bf 200
x,
once on SPI and one on SDIO.
SDIO:
SPI:
Results from a port of SdFat's bench:
...
write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
11372.8,7731,5540,5719
9532.5,19400,5515,6835
...
read speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
13173.1,5221,4940,4978
13173.1,5220,4940,4972
...
...
write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
2887.0,23936,22497,22663
2904.6,23923,22496,22546
...
read speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
2778.4,24017,23569,23592
2778.4,24018,23569,23587
...
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.
This library is not specifically designed for use with FreeRTOS. For use with FreeRTOS, I suggest you consider FreeRTOS-FAT-CLI-for-RPi-Pico instead. That implementation is designed from the ground up for FreeRTOS.
While FatFs has some support for “re-entrancy” or thread safety (where "thread" == "task", in FreeRTOS terminology), it is limited to operations such as:
There does not appear to be sufficient FAT and directory locking in FatFs to make operations like f_mkdir, f_chdir and f_getcwd thread safe. If your application has a static directory tree, it should be OK in a multi-tasking application with FatFs. Otherwise, you probably need to add some additional locking.
Then, there are the facilities used for mutual exclusion and various ways of waiting (delay, wait for interrupt, etc.). This library uses the Pico SDK facilities (which are oriented towards multi-processing on the two cores), but FreeRTOS-FAT-CLI-for-RPi-Pico uses the FreeRTOS facilities, which put the waiting task into a Blocked state, allowing other, Ready tasks to run.
FreeRTOS-FAT-CLI-for-RPi-Pico is designed to maximize parallelism. So, if you have two cores and multiple SD card buses (SPI or SDIO), multiple FreeRTOS tasks can keep them all busy simultaneously.
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.
Prerequisites:
Raspberry Pi Pico or some other kind of RP2040 board
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.
Breadboard and wires
Raspberry Pi Pico C/C++ SDK
(Optional) A couple of ~10 kΩ - 50 kΩ resistors for pull-ups
(Optional) 100 nF, 1 µF, and 10 µF capacitors for decoupling
(Optional) 22 µH inductor for decoupling
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!
gpio_pull_up
is weak: around 56uA or 60kΩ.
If a pull up is needed, it's best to add an external pull up resistor of around 5-50 kΩ to 3.3v.
The internal gpio_pull_up
can be disabled in the hardware configuration by setting the no_miso_gpio_pull_up
attribute of the spi_t
object.git clone --recurse-submodules git@github.com:carlk3/no-OS-FatFS-SD-SDIO-SPI-RPi-Pico.git no-OS-FatFs
pico_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/examples/command_line
mkdir build
cd build
cmake ..
make
command_line
example for operation.
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
),
sd_spi_if_t
, sd_sdio_if_t
, and sd_card_t
(see sd_driver/sd_card.h
).
sd_card_t
describe the configuration of SD card sockets.sd_card_t
is associated (one to one) with an sd_spi_if_t
or sd_sdio_if_t
interface object,
and points to it with spi_if_p
or sdio_if_p
[^5].sdio_if_p
specify the configuration of an SDIO/PIO interface.sd_spi_if_t
is assocated (many to one) with an instance of spi_t
and points to it with spi_t *spi
. (It is a many to one relationship because multiple SD cards can share a single SPI bus, as long as each has a unique slave (or "chip") select (SS, or "CS") line.) It describes the configuration of a specific SD card's interface to a specific SPI hardware component.spi_t
describe the configuration of the RP2040 SPI hardware components used.
There can be multiple objects (or "instances") of all three types.
Attributes (or "fields", or "members") of these objects specify which pins to use for what, baud rates, features like Card Detect, etc.0
or false
. (This is the user's responsibility if using Dynamic Configuration, but in a Static Configuration
[see Static vs. Dynamic Configuration],
the C runtime initializes static memory to 0.)Illustration of the configuration dev_brd.hw_config.c
sd_card_t
describes the configuration of one SD card socketstruct sd_card_t {
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;
//...
}
type
Type of interface: either SD_IF_SPI
or SD_IF_SDIO
spi_if_p
or sdio_if_p
Pointer to the instance sd_spi_if_t
or sd_sdio_if_t
that drives this SD carduse_card_detect
Whether or not to use Card Detect, meaning the hardware switch featured on some SD card sockets. This requires a GPIO pin.card_detect_gpio
Ignored if not use_card_detect
. GPIO number of the Card Detect, connected to the SD card socket's Card Detect switch (sometimes marked DET)card_detected_true
Ignored if not use_card_detect
. What the GPIO read returns when a card is present (Some sockets use active high, some low)card_detect_use_pull
Ignored if not use_card_detect
. If true, use the card_detect_gpio
's pad's Pull Up / Pull Down resistors;
if false, no pull resistor is applied.
Often, a Card Detect Switch is just a switch to GND or Vdd,
and you need a resistor to pull it one way or the other to make logic levels.card_detect_pull_hi
Ignored if not use_card_detect
. Ignored if not card_detect_use_pull
. Otherwise, if true, pull up; if false, pull down.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.
CLK_gpio
RP2040 GPIO to use for Clock (CLK).
Implicitly set to (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32
where SDIO_CLK_PIN_D0_OFFSET
is defined in sd_driver/SDIO/rp2040_sdio.pio
.
As of this writing, SDIO_CLK_PIN_D0_OFFSET
is 30, which is -2 in mod32 arithmetic, so:
CMD_gpio
RP2040 GPIO to use for Command/Response (CMD)
D0_gpio
RP2040 GPIO to use for Data Line [Bit 0]. The PIO code requires D0 - D3 to be on consecutive GPIOs, with D0 being the lowest numbered GPIO.
D1_gpio
RP2040 GPIO to use for Data Line [Bit 1]. Implicitly set to D0_gpio + 1.
D2_gpio
RP2040 GPIO to use for Data Line [Bit 2]. Implicitly set to D0_gpio + 2.
D3_gpio
RP2040 GPIO to use for Card Detect/Data Line [Bit 3]. Implicitly set to D0_gpio + 3.
SDIO_PIO
Which PIO block to use. Defaults to pio0
. Can be changed to avoid conflicts.
If you try to use multiple SDIO-attached SD cards simultaneously on the same PIO block,
contention might lead to timeouts.
DMA_IRQ_num
Which IRQ to use for DMA. Defaults to DMA_IRQ_0. Set this to avoid conflicts with any exclusive DMA IRQ handlers that might be elsewhere in the system.
use_exclusive_DMA_IRQ_handler
If true, the IRQ handler is added with the SDK's irq_set_exclusive_handler
. The default is to add the handler with irq_add_shared_handler
, so it's not exclusive.
baud_rate
The frequency of the SDIO clock in Hertz. This may be no higher than the system clock frequency divided by CLKDIV
in sd_driver\SDIO\rp2040_sdio.pio
, which is currently four. For example, if the system clock frequency is 125 MHz, baud_rate
cannot exceed 31250000 (31.25 MHz). The default is 10 MHz.
This is used to divide the system clock frequency (clk_sys
) to get a ratio to pass to the SDK's sm_config_set_clkdiv. As it says there, "An integer clock divisor of n will cause the state machine to run 1 cycle in every n. Note that for small n, the jitter introduced by a fractional divider (e.g. 2.5) may be unacceptable although it will depend on the use case."
In this case, n can be as little as four (which I would consider small).
The fractional divider essentially causes the frequency to vary in a range,
with the average being the requested frequency.
If the hardware is capable of running at the high end of the range,
you might as well run at that frequency all the time.
Therefore, I recommend choosing a baud rate that is some factor of the system clock frequency.
For example, if the system clock frequency is the default 125 MHz:
.baud_rate = 125 * 1000 * 1000 / 10, // 12500000 Hz
or
.baud_rate = 125 * 1000 * 1000 / 4 // 31250000 Hz
The higher the baud rate, the faster the data transfer. However, the hardware might limit the usable baud rate. See Pull Up Resistors and other electrical considerations.
set_drive_strength
If true, enable explicit specification of output drive strengths on CLK_gpio
, CMD_gpio
, and D0_gpio
- D3_gpio
.
The GPIOs on RP2040 have four different output drive strengths, which are nominally 2, 4, 8 and 12mA modes.
If set_drive_strength
is false, all will be implicitly set to 4 mA.
If set_drive_strength
is true, each GPIO's drive strength can be set individually. Note that if it is not explicitly set, it will default to 0, which equates to GPIO_DRIVE_STRENGTH_2MA
(2 mA nominal drive strength).
CLK_gpio_drive_strength
CMD_gpio_drive_strength
D0_gpio_drive_strength
D1_gpio_drive_strength
D2_gpio_drive_strength
D3_gpio_drive_strength
Ignored if set_drive_strength
is false. Otherwise, these can be set to one of the following:
GPIO_DRIVE_STRENGTH_2MA
GPIO_DRIVE_STRENGTH_4MA
GPIO_DRIVE_STRENGTH_8MA
GPIO_DRIVE_STRENGTH_12MA
You might want to do this for electrical tuning. A low drive strength can give a cleaner signal, with less overshoot and undershoot. In some cases, this allows operation at higher baud rates. In other cases, the signal lines might have a lot of capacitance to overcome. Then, a higher drive strength might allow operation at higher baud rates. A low drive strength generates less noise. This might be important in, say, audio applications.
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;
spi
Points to the instance of spi_t
that is to be used as the SPI to drive this interfacess_gpio
Slave Select (SS) (or "Chip Select [CS]") GPIO for the SD card socket associated with this interfaceset_drive_strength
Enable explicit specification of output drive strength of ss_gpio_drive_strength
.
If false, the GPIO's drive strength will be implicitly set to 4 mA.ss_gpio_drive_strength
Drive strength for the SS (or CS).
Ignored if set_drive_strength
is false. Otherwise, it can be set to one of the following:
GPIO_DRIVE_STRENGTH_2MA
GPIO_DRIVE_STRENGTH_4MA
GPIO_DRIVE_STRENGTH_8MA
GPIO_DRIVE_STRENGTH_12MA
An instance of spi_t
describes the configuration of one RP2040 SPI controller.
typedef struct spi_t {
spi_inst_t *hw_inst; // SPI HW
uint miso_gpio; // SPI MISO GPIO number (not pin number)
uint mosi_gpio;
uint sck_gpio;
uint baud_rate;
uint DMA_IRQ_num; // DMA_IRQ_0 or DMA_IRQ_1
bool use_exclusive_DMA_IRQ_handler;
bool no_miso_gpio_pull_up;
/* 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 mosi_gpio_drive_strength;
enum gpio_drive_strength sck_gpio_drive_strength;
bool use_static_dma_channels;
uint tx_dma;
uint rx_dma;
// State variables:
// ...
} spi_t;
hw_inst
Identifier for the hardware SPI instance (for use in SPI functions). e.g. spi0
, spi1
, declared in pico-sdk\src\rp2_common\hardware_spi\include\hardware\spi.h
miso_gpio
SPI Master In, Slave Out (MISO) (also called "CIPO" or "Peripheral's SDO") GPIO number. This is connected to the SD card's Data Out (DO).mosi_gpio
SPI Master Out, Slave In (MOSI) (also called "COPI", or "Peripheral's SDI") GPIO number. This is connected to the SD card's Data In (DI).sck_gpio
SPI Serial Clock GPIO number. This is connected to the SD card's Serial Clock (SCK).baud_rate
Frequency of the SPI Serial Clock, in Hertz. The default is 10 MHz.
This is ultimately passed to the SDK's spi_set_baudrate. This applies a hardware prescale and a post-divide to the Peripheral clock (clk_peri
) (see section 4.4.2.3. Clock prescaler in RP2040 Datasheet).
The Peripheral clock typically,
but not necessarily, runs from clk_sys
.
Practically, the hardware limits the choices for the SPI frequency to clk_peri
divided by an even number.
For example, if clk_peri
is clk_sys
and clk_sys
is running at the default 125 MHz,
.baud_rate = 125 * 1000 * 1000 / 10, // 12500000 Hz
or
.baud_rate = 125 * 1000 * 1000 / 4 // 31250000 Hz
If you ask for 14000000, you'll actually get 12500000 Hz.
The actual baud rate will be printed out if USE_DBG_PRINTF
(see Messages from the SD card driver) is defined at compile time.
The higher the baud rate, the faster the data transfer.
However, the hardware might limit the usable baud rate.
See Pull Up Resistors and other electrical considerations.
DMA_IRQ_num
Which IRQ to use for DMA. Defaults to DMA_IRQ_0. Set this to avoid conflicts with any exclusive DMA IRQ handlers that might be elsewhere in the system.use_exclusive_DMA_IRQ_handler
If true, the IRQ handler is added with the SDK's irq_set_exclusive_handler
. The default is to add the handler with irq_add_shared_handler
, so it's not exclusive. no_miso_gpio_pull_up
According to the standard, an SD card's DO MUST be pulled up (at least for the old MMC cards).
However, it might be done externally. If no_miso_gpio_pull_up
is false, the library will set the RP2040 GPIO internal pull up.set_drive_strength
Specifies whether or not to set the RP2040 GPIO pin drive strength.
If set_drive_strength
is false, all will be implicitly set to 4 mA.
If set_drive_strength
is true, each GPIO's drive strength can be set individually. Note that if it is not explicitly set, it will default to 0, which equates to GPIO_DRIVE_STRENGTH_2MA
(2 mA nominal drive strength).mosi_gpio_drive_strength
SPI Master Out, Slave In (MOSI) drive strength, sck_gpio_drive_strength
SPI Serial Clock (SCK) drive strength:
Ignored if set_drive_strength
is false. Otherwise, these can be set to one of the following:
GPIO_DRIVE_STRENGTH_2MA
GPIO_DRIVE_STRENGTH_4MA
GPIO_DRIVE_STRENGTH_8MA
GPIO_DRIVE_STRENGTH_12MA
You might want to do this for electrical tuning. A low drive strength can give a cleaner signal, with less overshoot and undershoot. In some cases, this allows operation at higher baud rates. In other cases, the signal lines might have a lot of capacitance to overcome. Then, a higher drive strength might allow operation at higher baud rates. A low drive strength generates less noise. This might be important in, say, audio applications.
use_static_dma_channels
If true, the DMA channels provided in tx_dma
and rx_dma
will be claimed with dma_channel_claim
and used. If false, two DMA channels will be claimed with dma_claim_unused_channel
.tx_dma
The DMA channel to use for SPI TX. Ignored if dma_claim_unused_channel
is falserx_dma
The DMA channel to use for SPI RX. Ignored if dma_claim_unused_channel
is false
sd_driver/hw_config.h
: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
physical drive number.
In a static, hw_config.c
kind of configuration, that could be the (zero origin indexed) position in the sd_cards[]
array.
It may return NULL if the drive is not available; e.g., if it is disabled. num
must be less than sd_get_num()
.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.There are many options to configure the features of FatFs for various requirements of each project.
The configuration options are defined in ffconf.h
.
See Configuration Options.
In order to allow applications to customize ffconf.h
without modifying this library,
I have renamed src\ff15\source\ffconf.h
in the FatFs distribution files included in this library.
(This is necessary because GCC always looks in the directory of the current file as the first search directory for #include "file".)
There is a somewhat customized version of ffconf.h
in include\ffconf.h
that is normally picked up by the library's src\CMakeLists.txt
.
An application may provide its own tailored copy of ffconf.h
by putting it in the include path for the library compilation.
For example, for CMake, if the customized ffconf.h
file is in subdirectory include/
(relative to the application's CMakeLists.txt
file):
target_include_directories(no-OS-FatFS-SD-SDIO-SPI-RPi-Pico BEFORE INTERFACE
${CMAKE_CURRENT_LIST_DIR}/include
)
For an example, see examples/unix_like
.
After stdio_init_all()
, time_init()
, and whatever other Pico SDK initialization is required,
you may call sd_init_driver()
to initialize the block device driver. sd_init_driver()
is now[^2] called implicitly by disk_initialize
,
which is called by FatFs's f_mount,
but you might want to call it sooner so that the GPIOs get configured, e.g., if you want to set up a Card Detect interrupt.
You need to call sd_init_driver()
before you can use sd_get_drive_prefix(sd_card_t *sd_card_p)
,
so you might need to add an explicit call to sd_init_driver
if you want to use sd_get_drive_prefix
to get the drive prefix to use for the path
parameter in a call to f_mount
.
There is no harm in calling sd_init_driver()
repeatedly.
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.)Sometimes problems arise when attempting to use SD cards. At the FatFs Application Interface level, it can be difficult to diagnose problems. You get a return code, but it might just tell you FR_NOT_READY
("The physical drive cannot work"),
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.
Two compile definitions control how these are handled:
USE_PRINTF
If this is defined and not zero,
these message output functions will use the Pico SDK's Standard Output (stdout
).USE_DBG_PRINTF
If this is not defined or is zero or NDEBUG
is defined,
DBG_PRINTF
statements will be effectively stripped from the code.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.
At heart, this is a C library, but I have made a (thin) C++ wrapper for it: include\FatFsSd.h
.
For details on most of the functions, refer to FatFs - Generic FAT Filesystem Module, "Application Interface".
See examples\PlatformIO\one_SPI.C++\src\main.cpp
for an example of the use of this API.
The C++ API is in namespace FatFsNs
, to avoid name clashes with other packages (e.g.: SdFat).
This is a pure static class that represents the global file system as a whole.
It stores the hardware configuration objects internally,
which is useful in Arduino-like environments where you have a setup
and a loop
.
The objects can be constructed in the setup
and added to FatFs
and the copies won't go out of scope
when control leaves setup
and goes to loop
.
It automatically provides these functions required internally by the library:
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 Public Member Functions:
static SdCard* add_sd_card(SdCard& SdCard)
Use this to add an instance of SdCard
or sd_card_t
to the configuration. Returns a pointer that can be used to access the newly created SDCard object.static size_t SdCard_get_num ()
Get the number of SD cards in the configutationstatic SdCard *SdCard_get_by_num (size_t num)
Get pointer to an SdCard
by (zero-origin) indexstatic SdCard *SdCard_get_by_name (const char *const name)
Get pointer to an SdCard
by logical drive identifierstatic FRESULT chdrive (const TCHAR *path)
Change current drive to given logical drivestatic FRESULT setcp (WORD cp)
Set current code pagestatic bool begin ()
Initialize driver and all SD cards in configurationRepresents an SD card socket. It is generalized: the SD card can be either SPI or SDIO attached.
Public Member Functions:
const char * get_name ()
Get the the FatFs logical drive identifier.FRESULT mount ()
Mount SD cardFRESULT unmount ()
Unmount SD cardFRESULT format ()
Create a FAT volume with defaultsFATFS * fatfs ()
Get filesystem object structure (FATFS)uint64_t get_num_sectors ()
Get number of blocks on the drivevoid cidDmp(printer_t printer)
Print information from Card IDendtification registervoid csdDmp(printer_t printer)
Print information from Card-Specific Data registerStatic Public Member Functions
static FRESULT fdisk (BYTE pdrv, const LBA_t ptbl[], void* work)
Divide a physical drive into some partitionsstatic FRESULT getfree (const TCHAR *path, DWORD *nclst, FATFS **fatfs)
Get number of free blocks on the drivestatic FRESULT getlabel (const TCHAR *path, TCHAR *label, DWORD *vsn)
Get volume labelstatic FRESULT setlabel (const TCHAR *label)
Set volume labelFRESULT open (const TCHAR *path, BYTE mode)
Open or create a file FRESULT close ()
Close an open file objectFRESULT read (void *buff, UINT btr, UINT *br)
Read data from the fileFRESULT write (const void *buff, UINT btw, UINT *bw)
Write data to the file FRESULT lseek (FSIZE_t ofs)
Move file pointer of the file objectFRESULT expand (uint64_t file_size)
Prepare or allocate a contiguous data area to the file with default optionFRESULT truncate ()
Truncate the file FRESULT sync ()
Flush cached data of the writing fileint putc (TCHAR c)
Put a character to the file int puts (const TCHAR *str)
Put a string to the fileint printf (const TCHAR *str,...)
Put a formatted string to the fileTCHAR * gets (TCHAR *buff, int len)
Get a string from the filebool eof ()
Test for end-of-fileBYTE error ()
Test for an errorFSIZE_t tell ()
Get current read/write pointerFSIZE_t size ()
Get size in bytesFRESULT rewind ()
Move the file read/write pointer to 0 (beginning of file)FRESULT forward (UINT(*func)(const BYTE *, UINT), UINT btf, UINT *bf)
Forward data to the streamFRESULT expand (FSIZE_t fsz, BYTE opt)
Prepare or allocate a contiguous data area to the filePublic Member Functions:
FRESULT rewinddir ()
Rewind the read index of the directory object FRESULT rmdir (const TCHAR *path)
Remove a sub-directoryFRESULT opendir (const TCHAR *path)
Open a directoryFRESULT closedir ()
Close an open directoryFRESULT readdir (FILINFO *fno)
Read a directory itemFRESULT findfirst (FILINFO *fno, const TCHAR *path, const TCHAR *pattern)
Open a directory and read the first item matchedFRESULT findnext (FILINFO *fno)
Read a next item matched
Static Public Member Functions:static FRESULT mkdir (const TCHAR *path)
Create a sub-directorystatic FRESULT unlink (const TCHAR *path)
Remove a file or sub-directorystatic FRESULT rename (const TCHAR *path_old, const TCHAR *path_new)
Rename/Move a file or sub-directorystatic FRESULT stat (const TCHAR *path, FILINFO *fno)
Check existance of a file or sub-directorystatic FRESULT chmod (const TCHAR *path, BYTE attr, BYTE mask)
Change attribute of a file or sub-directorystatic FRESULT utime (const TCHAR *path, const FILINFO *fno)
Change timestamp of a file or sub-directorystatic FRESULT chdir (const TCHAR *path)
Change current directorystatic FRESULT chdrive (const TCHAR *path)
Change current drivestatic FRESULT getcwd (TCHAR *buff, UINT len)
Retrieve the current directory and driveThis library is available at https://registry.platformio.org/libraries/carlk3/no-OS-FatFS-SD-SDIO-SPI-RPi-Pico. It is currently running with
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board_build.core = earlephilhower
There is a example data logging application in data_log_demo.c
.
It can be launched from the examples/command_line
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 no-OS-FatFS-SD-SDIO-SPI-RPi-Pico as a library embedded in another project, use something like:
git submodule add git@github.com:carlk3/no-OS-FatFS-SD-SDIO-SPI-RPi-Pico.git
or
git submodule add https://github.com/carlk3/no-OS-FatFS-SD-SDIO-SPI-RPi-Pico.git
You might also need to pick up the library in CMakeLists.txt:
add_subdirectory(no-OS-FatFS-SD-SDIO-SPI-RPi-Pico/src build)
target_link_libaries(${PROGRAM_NAME} no-OS-FatFS-SD-SDIO-SPI-RPi-Pico)
(where ${PROGRAM_NAME}
is your target project
)
and #include "ff.h"
.
Happy hacking!
You are welcome to contribute to this project! Just submit a Pull Request in GitHub. Here are some ideas for future enhancements:
Any references to the pcName
member must be removed from each instance of sd_card_t
in the hardware configuration.
For example, if you were using a hw_config.c
containing
static sd_card_t sd_cards[] = { // One for each SD card
{ // sd_cards[0]: Socket sd0
.pcName = "0:",
.type = SD_IF_SPI,
// ...
change it to
static sd_card_t sd_cards[] = { // One for each SD card
{ // sd_cards[0]: Socket sd0
.type = SD_IF_SPI,
// ...
See Customizing for the Hardware Configuration.
If you had any references to pcName
in your code,
you can replace them with calls to char const *sd_get_drive_prefix(sd_card_t *sd_card_p);
(sd_card.h
).
Note: sd_init_driver()
must be called before sd_get_drive_prefix
.
The object model for hardware configuration has changed.
If you are migrating a project from no-OS-FatFS-SD-SPI-RPi-Pico, you will have to change the hardware configuration customization. The sd_card_t
now points to new object that specifies the configuration of either an SPI interface or an SDIO interface,
and a new type
member that identifies which type of interface.
Also, the pcName
member of sd_card_t
has been removed.
See Customizing for the Hardware Configuration.
For example, if you were using a hw_config.c
containing
static sd_card_t sd_cards[] = { // One for each SD card
{
.pcName = "0:", // Name used to mount device
.spi = &spis[0], // Pointer to the SPI driving this card
.ss_gpio = 17, // The SPI slave select GPIO for this SD card
// ...
that would now become
static sd_spi_if_t spi_ifs[] = {
{
.spi = &spis[0], // Pointer to the SPI driving this card
.ss_gpio = 17, // The SPI slave select GPIO for this SD card
//...
static sd_card_t sd_cards[] = { // One for each SD card
{
.type = SD_IF_SPI,
.spi_if_p = &spi_ifs[0], // Pointer to the SPI interface driving this card
//...
Instances of the interface classes sd_spi_t
and sd_sdio_t
are no longer embedded in sd_card_t
as spi_if
and sdio_if
. They are moved to the top level as instances of sd_spi_if_t
and sd_sdio_if_t
and pointed to by instances of sd_card_t
.
Also, the pcName
member of sd_card_t
in the hardware configuration has been removed.
For example, if you were using a hw_config.c
containing:
static sd_card_t sd_cards[] = { // One for each SD card
{
.pcName = "0:", // Name used to mount device
.type = SD_IF_SPI,
.spi_if.spi = &spis[0], // Pointer to the SPI driving this card
.spi_if.ss_gpio = 7, // The SPI slave select GPIO for this SD card
//...
that would become:
static sd_spi_if_t spi_ifs[] = {
{
.spi = &spis[0], // Pointer to the SPI driving this card
.ss_gpio = 7, // The SPI slave select GPIO for this SD card
//...
static sd_card_t sd_cards[] = { // One for each SD card
{
.type = SD_IF_SPI,
.spi_if_p = &spi_ifs[0], // Pointer to the SPI interface driving this card
//...
For details, see Customizing for the Hardware Configuration.
command_line
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
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.