SD Card Data Logging¶
Written by Grace Lo & Alina Wang
The SD_Card_Methane
library was developed and tested on the Adafruit #254 MicroSD card breakout board. It logs sensor data and the associated date/time at a user-defined frequency into a .csv file format. The interface to the card is handled by a FatFs library adapted for SD cards from carlk3. The basic card functionality, including mounting the drive, opening/closing files, and read/write data is borrowed from Bruce Land. The command line interface is borrowed from tinyUSB examples to test the SD card functions. The SD card is completely handled by FatFs.
Pin Connections¶
Breadboarding best practices should be followed. Use the shortest possible wires for maximum clock speed, which when using solderless protoboards is capped at ~20MHz due to pin capacitance.

Pin connections from the microSD breakout → Pico are as follows:
- Vin (5V) → VBUS (pin 40)
- GND → GND
- CLK → SPI0_SCK (gpio 2)
- DO → SPI0_RX (gpio 4)
- DI → SPI0_TX (gpio 3)
- CS → SPI0_CSn (gpio 5)
- CD → gpio 22
hw_config.c
provides the ability to remap the SPI channel and the gpio pins connected to the SPI channel.
API¶
For the SD card library, the card must be formatted as FAT32. The initial date/time, data logging frequency, and file name are user-defined in the serial terminal interface.
The Adafruit #954 USB to TTL Serial cable can be used to connect to a PC.
- RX (white) → UART0_TX (gpio 0)
- TX (green) → UART0_RX (gpio 1)
- GND (black) → GND
When the system resets, the driver prints SD card information to the serial terminal. Then you can issue the following commands:
setdate <yyyy-mm-dd> <hh:mm:ss>
-- set the RTC intialization of date/timesetfreq <frequency>
-- set the data logging frequency in ms (default set to 5 seconds)write <filename>
-- write data logging to a fileprint <filename>
-- print the content of the filenamerm <filename>
-- delinks (deletes) a filels <directory>
-- lists directory contents; default is current directorycd <directory>
-- changes current directorymkdir <directory>
-- new directoryhelp
-- prints all valid commands
Code¶
All SD_Card_Methane
library code is in [this git repository]. The code for the serial terminal user interface is in sd_card_serial.c
and is organized in protothreads for modularity. Protothreads are extremely lightweight, stackless threads designed for memory-constrained systems; the library is written entirely as C macros by Adam Dunkels.
Includes¶
The first lines of code in the C source file include header files. Don't forget to link these in the CMakeLists.txt file!
The first set of header files are standard C headers and the Raspberry Pi Pico standard library for general use. pico/stdlib.h
is what the SDK calls a "High-Level API." These high-level API's "provide higher level functionality that isn’t hardware related or provides a richer set of functionality above the basic hardware interfaces." The architecture of this SDK is described at length in the SDK manual. All libraries within the SDK are INTERFACE libraries. pico/stdlib.h
in particular pulls in a number of lower-level hardware libraries, listed on page 196 of the C SDK guide.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
The remaining header files pull in additional API's for special functionality, the first of which is the FatFs library adapted for SD cards.
#include "sd_card.h"
#include "ff.h"
The next set of header files are for protothread utilities. There is a custom header file included in the provided git repository that defines the protothread functions.
#include "hardware/sync.h"
#include "hardware/timer.h"
#include "hardware/uart.h"
#include "pico/multicore.h"
#include "pt_cornell_rp2040_v1_3.h"
In addition, libraries are included to give access to API's associated with the Pico unique ID, datetime, and Real Time Clock on the RP2040. These libraries will be used when logging data to the file.
#include "pico/unique_id.h"
#include "pico/util/datetime.h"
#include "hardware/rtc.h"
main()
¶
The first line in main()
is a call to stdio_init_all()
. This function initializes stdio to communicate through either UART or USB, depending on the configurations in the CMakeLists.txt
file.
Then the protothreads are each assigned to a core. The RP2040 chip has 2 cores that can run independently of each other, sharing peripherals and memory with each other. Core 0 is automatically launched in main, while core 1 needs to be explicitly launched with the calls:
multicore_reset_core1();
multicore_launch_core1(&core1_main);
A protothread that toggles the on-board LED is added to core 0 with scheduling priority SCHED_PRIORITY
. If there are additional protothreads assigned to core 0, their scheduling priority can be defined when adding the thread to the core to allow higher priority threads more CPU time. This LED protothread is not essential to the SD card functionality and can be removed.
The file protothread that controls the SD card writing and user interface is assigned to core 1. In particular, it is added to the core 1 function core1_main
with scheduling priority SCHED_ROUND_ROBIN
. If there are additional protothreads assigned to core 1, they will each be allocated equal CPU time in the order they are initialized.
File Protothread¶
Initializations¶
The protothread begins with variable declarations for processing serial input (cmd
, arg1
, arg2
, arg3
, token
), date/time (datetime_buf
, datetime_str
), and frequency (logging_frequency_ms
). In addition, the datetime_t
struct is initialized to a default Sunday 01 January 00:00:00 2000 and the logging frequency is initialized to a default 5000 ms (or 5 seconds).
Then the SD card is initialized and the filesystem is mounted.
// Initialize SD card
if (!sd_init_driver())
{
printf("ERROR: Could not initialize SD card\r\n");
while (true);
}
// Mount drive
fr = f_mount(&fs, "0:", 1);
if (fr != FR_OK)
{
printf("ERROR: Could not mount filesystem (%d)\r\n", fr);
while (true);
}
Serial Terminal¶
The serial terminal runs indefinitely when the RP2040 is powered on. At the start of each loop, it gets the current date/time and converts it to a string. The wait time allots sufficient time for rtc_get_datetime(&t)
to finish executing. Otherwise, a subsequent time-dependent command can exhibit weird behavior.
rtc_get_datetime(&t);
datetime_to_str(datetime_str, sizeof(datetime_buf), &t);
sleep_ms(100);
Next, the serial terminal is read for user input and tokenized for cmd arg1 arg2 arg3
. Each of the commands are checked in sequential if-statements. New commands not implemented in Bruce Land's version of the SD card setup are described below:
The setdate
command uses arg1
and arg2
to set the date and time, respectively. The date is tokenized by delimiter -
and the time is tokenized by delimiter :
. The fields for the datetime_t
struct are updated accordingly and reinitialized in the real time clock.
The setfreq
command uses arg1
to set the frequency (in ms) for data logging. When the write
command is issued, the frequency is used to put the system to sleep in between data logging entries.
The write
command uses arg1
as the filename to write to. If the file does not exist, a new one will be created. If the file already exists, new data will be appended to the file. The contents of the file are divided into two sections - header and data. The header includes the Pico ID so that each system can be identified uniquely when many flux chambers are deployed. It also defines each of the columns. At present, the columns are DATETIME,CH4,CO2,TEMP,HUM
.
Then the data is continuously written in a loop. As mentioned previously, the frequency is used to specify the sleep time between data logging entries.
An example of setdate
, setfreq
, write
, and print
used on the serial terminal and the corresponding CSV file is provided below:


Debugging Tips¶
Power cycling! For any errors initializing the SD card and filesystem, first try power cycling to reset the system. In particular when starting up the system after it has been unused for a while, initialization and mounting errors seem more prevalent, but they usually resolve on their own with multiple power cycles. Other general debug tips include checking all circuit connections are secure and reflashing the RP2040. If the error persists, some other solutions are outlined below:
ERROR: Could not initialize SD card
May need to reformat the SD card (to FAT32) in the case that it has been corrupted.
ERROR: Could not mount filesystem
Interestingly enough, connecting UART0_TX (gpio 0) and GND to the oscilloscope solves the problem.
Read timeout
or Single Block Write failed
Before power cycling, try reentering the command multiple times. There seems to be occasional delays during file read/write that resolve on their own after a bit of time.
Cannot create '<filename>.csv'
Remove the existing file under the same name. If the error persists, it is indicative of a corrupted file. Corrupted files usually resolve themselves after power cycling or giving a bit of time, so for the time being, it may be easier to create a new file (under a new filename).