Raspberry Pi Pico and rp2040 boards: how to use SD card – 5

Spread the love

We have already talked about the internal flash memories for rp2040 boards and other microcontrollers, which are very useful for storing pages or static files. However, they are not suitable for logging or similar applications, so we will learn how to connect and use an SD card which can have a lot of space, a very small form factor, and low power consumption.

Raspberry Pi Pico W rp2040 how to use SD card
Raspberry Pi Pico W rp2040 how to use SD card

Protocol

SD card has a native host interface apart from the SPI mode for communicating with master devices. The native interface uses four lines for data transfer, where the microcontroller has an SD card controller module and needs a separate license to use it. Since the SPI is a widely used protocol and it is available in most low-cost microcontrollers, the SPI mode is the widely used interface in low-cost embedded systems. The working voltage range of the SD family is 2.7V to 3.6V, and this is indicated in the operation condition register (OCR). Exist a low-power SD Card that operates al 1.8V but isn’t so used.

Pinout

Exists various form factors, but the base pinout is the same.

MMC SD miniSD microSD pins and size factor
Pin NumberPin NameIn SD ModeIn SPI Mode
1DAT2/XConnector Data line 2No use
2DAT3/CSConnector Data line 3Chip Select
3CMD/DICommand / Response LineData Input
4VDD/VDDPower supply (+3.3V)Power supply (+3.3V)
5CLK/SCLKClockSerial Clock
6VSS/VSSGroundGround
7DAT0/D0Connector Data line 0Data Out
8DAT1/XConnector Data line 1No use

Now we are going to interface the SD card first.

SD pinout

Wiring

Raspberry Pi Pico rp2040 pinout low resolution
Raspberry Pi Pico rp2040 pinout low resolution

The operating voltage forces us to make 2 distinct connection schema based on the nm type of microcontroller. To interface the SD card, I use an SD adapter with micro SD, which results.

Vista frontale dei pins dell’adattatore SD
SD adapter pins back

Raspberry Pi Pico and raw SD card

Here the simple wiring schema on primary SPI.

Raspberry Pi Pico rp2040 SD Card SPI
Raspberry Pi Pico rp2040 SD Card SPI
SD CardRaspberry Pi Pico (SPI)
CSGPIO17
SCKGPIO18
MISOGPIO16
MOSIGPIO19
GNDGND
3.3v3.3v

And here with secondary SPI interface SPI1

Raspberry Pi Pico rp2040 SD Card on secondary SPI1
Raspberry Pi Pico rp2040 SD Card on secondary SPI1
SD CardRaspberry Pi Pico (SPI1)
CSGPIO13
SCKGPIO14
MISOGPIO12
MOSIGPIO15
GNDGND
3.3v3.3v

Modules

Various modules interface your microcontroller with your device, and It works exactly like the Arduino connection schema for the 5v adapter and the esp8266 connection schema for 3.3v. When you buy one, you must pay attention to the working voltage.

Exists some variant that supports 3.3v and 5v, like the one linked here.

You can find sd card module on AliExpress

Raspberry Pi Pico and SD card module

Here the wiring with standard SPI

Raspberry Pi Pico rp2040 SD Card module SPI
Raspberry Pi Pico rp2040 SD Card module SPI
SD CardRaspberry Pi Pico (SPI)
CSGPIO17
SCKGPIO18
MISOGPIO16
MOSIGPIO19
GNDGND
3.3v3.3v

and here with the secondary SPI (SPI1) interface.

Raspberry Pi Pico rp2040 SD Card module on secondary SPI1
Raspberry Pi Pico rp2040 SD Card module on secondary SPI1
SD CardRaspberry Pi Pico (SPI1)
CSGPIO13
SCKGPIO14
MISOGPIO12
MOSIGPIO15
GNDGND
3.3v3.3v

Commands

sd.begin() sd.begin(cspin)
Initializes the SD library and card. This begins the use of the SPI bus and the chip select pin, which defaults to the hardware SS pin. Returns true on success; false on failure.

sd.exists(filename)
Test whether a file or directory exists on the SD card. Returns true if the file or directory exists, false if not.

sd.mkdir(filename)
Create a directory on the SD card. This will also create any intermediate directories that don’t already exist; e.g. SD.mkdir(“a/b/c”) will create a, b, and c. Returns true if the creation of the directory succeeded, false if not.

sd.open(filepath) sd.open(filepath, mode)
Opens a file on the SD card. If the file is opened for writing, it will be created if it doesn’t already exist (but the directory containing it must already exist). Parameter mode (optional): the mode in which to open the file defaults to FILE_READ – byte. one of FILE_READ: open the file for reading, starting at the beginning of the file. FILE_WRITE: open the file for reading and writing, starting at the end of the file. Returns a File object referring to the opened file; if the file can’t be opened, this object will evaluate to false in a boolean context, i.e. you can test the return value with “if (f)”.

sd.remove(filename)
Remove a file from the SD card. Returns true if the removal of the file succeeded, false if not. (if the file didn’t exist, the return value is unspecified)

rs.rmdir(filename)
Remove a directory from the SD card. The directory must be empty. Returns true if the removal of the directory succeeded, false if not. (if the directory didn’t exist, the return value is unspecified)

file.name()
Returns the file name

file.available()
Check if there are any bytes available for reading from the file. Returns the number of bytes.

file.close()
Close the file, and ensure that any data written to it is physically saved to the SD card.

file.flush()
Ensures that any bytes written to the file are physically saved to the SD card. This is done automatically when the file is closed.

file.peek()
Read a byte from the file without advancing to the next one. That is, successive calls to peek() will return the same value, as will the next call to read().

file.position()
Get the current position within the file (i.e. the location to which the next byte will be read from or written to). Returns the position within the file (unsigned long).

file.print(data) file.print(data, base)
Print data to the file, which must have been opened for writing. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters ‘1’, ‘2’, ‘3’). Parameter data: the data to print (char, byte, int, long, or string), BASE (optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16). Returns the number of bytes written, though reading that number is optional.

file.println() file.println(data) file.println(data, base)
As print but with final return

file.seek(pos)
Seek to a new position in the file, which must be between 0 and the size of the file (inclusive). Parameters: pos: the position to which to seek (unsigned long). Returns true for success, false for failure (boolean)

file.size()
Get the size of the file. Returns the size of the file in bytes (unsigned long).

file.read()  file.read(buf, len)
Read from the file. Returns the next byte (or character), or -1 if none is available.

file.write(data) file.write(buf, len)
Write data to the file. Returns the number of bytes written, though reading that number is optional

file.isDirectory()
Directories (or folders) are special kinds of files, this function reports if the current file is a directory or not. Returns true if is directory.

file.openNextFile()
Reports the next file or folder in a directory. Returns the next file or folder in the path.

file.rewindDirectory()
Will bring you back to the first file in the directory, used in conjunction with openNextFile().

SD.begin(uint8_t csPin, SPISettings cfg = SPI_HALF_SPEED)
SDFS.setConfig(SDFSConfig(csPin, cfg))

You can set more information and parameter that are Inherited from the SDFS class.

SDFS.format()
Formats the file system. Returns true if the formatting was successful.

SDFS.open(path, mode)
Opens a file. path should be an absolute path starting with a slash (e.g. /dir/filename.txt). mode is a string specifying access mode. It can be one of “r”, “w”, “a”, “r+”, “w+”, “a+”. Meaning of these modes is the same as for fopen C function.
Returns File object. To check whether the file was opened successfully, use the boolean operator.

SDFS.exists(path)
Returns true if a file with given path exists, false otherwise.

SDFS.openDir(path)
Opens a directory given its absolute path. Returns a Dir object.

SDFS.remove(path): Deletes the file given its absolute path. Returns true if the file was deleted successfully.

SDFS.rename(pathFrom, pathTo)
Renames file from pathFrom to pathTo. Paths must be absolute. Returns true if the file was renamed successfully.

SDFS.info(fs_info)
Fills FSInfo structure with information about the file system. Returns true if successful, false otherwise.

file.getCreationTime()
Returns the creation date in epoch time.

file.getLastWrite()
Returns the data of the last write/change in epoch time.

Code

Raspberry Pi Pico, W and rp2040: SD Card module SPI
Raspberry Pi Pico, W and rp2040: SD Card module SPI

Now some examples with the default wiring (SPI).

Get all information and files list of SD Card

/*
  SD card test for Raspberry Pi Pico or rp2040 boards

  This example shows how use the utility libraries

  The circuit:
    SD card attached to SPI bus as follows:
	#define PIN_SPI0_MISO  (16u)
	#define PIN_SPI0_MOSI  (19u)
	#define PIN_SPI0_SCK   (18u)
	#define PIN_SPI0_SS    (17u)
*/
// include the SD library:
#include <SPI.h>
#include <SD.h>

void printDirectory(File dir, int numTabs);

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.print("\nInitializing SD card...");

  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  if (!SD.begin(SS)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card inserted?");
    Serial.println("* is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    while (1);
  } else {
    Serial.println("Wiring is correct and a card is present.");
  }
  // 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC
  // print the type of card
  Serial.println();
  Serial.print("Card type:         ");
  switch (SD.type()) {
    case 0:
      Serial.println("SD1");
      break;
    case 1:
      Serial.println("SD2");
      break;
    case 3:
      Serial.println("SDHC/SDXC");
      break;
    default:
      Serial.println("Unknown");
  }

  Serial.print("Cluster size:          ");
  Serial.println(SD.clusterSize());
  Serial.print("Blocks x Cluster:  ");
  Serial.println(SD.blocksPerCluster());
  Serial.print("Blocks size:  ");
  Serial.println(SD.blockSize());

  Serial.print("Total Blocks:      ");
  Serial.println(SD.totalBlocks());
  Serial.println();

  Serial.print("Total Cluster:      ");
  Serial.println(SD.totalClusters());
  Serial.println();

  // print the type and size of the first FAT-type volume
  uint32_t volumesize;
  Serial.print("Volume type is:    FAT");
  Serial.println(SD.fatType(), DEC);

  volumesize = SD.totalClusters();
  volumesize *= SD.clusterSize();
  volumesize /= 1000;
  Serial.print("Volume size (Kb):  ");
  Serial.println(volumesize);
  Serial.print("Volume size (Mb):  ");
  volumesize /= 1024;
  Serial.println(volumesize);
  Serial.print("Volume size (Gb):  ");
  Serial.println((float)volumesize / 1024.0);

  Serial.print("Card size:  ");
  Serial.println((float)SD.size()/1000);

  FSInfo fs_info;
  SDFS.info(fs_info);

  Serial.print("Total bytes: ");
  Serial.println(fs_info.totalBytes);

  Serial.print("Used bytes: ");
  Serial.println(fs_info.usedBytes);

  File dir =  SD.open("/");
  printDirectory(dir, 0);

}

void loop(void) {
}

void printDirectory(File dir, int numTabs) {
  while (true) {

    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.print(entry.size(), DEC);
      time_t cr = entry.getCreationTime();
      time_t lw = entry.getLastWrite();
      struct tm * tmstruct = localtime(&cr);
      Serial.printf("\tCREATION: %d-%02d-%02d %02d:%02d:%02d", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
      tmstruct = localtime(&lw);
      Serial.printf("\tLAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct->tm_year) + 1900, (tmstruct->tm_mon) + 1, tmstruct->tm_mday, tmstruct->tm_hour, tmstruct->tm_min, tmstruct->tm_sec);
    }
    entry.close();
  }
}

And the result will be

Initializing SD card...Wiring is correct and a card is present.

Card type:         SDHC/SDXC
Cluster size:          32768
Blocks x Cluster:  64
Blocks size:  512
Total Blocks:      7451

Total Cluster:      476864

Volume type is:    FAT32
Volume size (Kb):  2740977
Volume size (Mb):  2676
Volume size (Gb):  2.61
Card size:  2740977.75
Total bytes: 2740977664
Used bytes: 163840
System Volume Information/
	WPSettings.dat		12	CREATION: 2022-07-07 16:19:38	LAST WRITE: 2022-07-07 16:19:40
	IndexerVolumeGuid		76	CREATION: 2022-07-07 16:19:40	LAST WRITE: 2022-07-07 16:19:42
testSD.txt		40	CREATION: 1980-01-01 00:00:02	LAST WRITE: 1980-01-01 00:00:02

Write and read a file on the SD Card

Here is a simple sketch that writes and reads a file.

/*
  SD card read/write

  This example shows how to read and write data to and from an SD card file
  The circuit:
   SD card attached to SPI bus as follows:
	#define PIN_SPI0_MISO  (16u)
	#define PIN_SPI0_MOSI  (19u)
	#define PIN_SPI0_SCK   (18u)
	#define PIN_SPI0_SS    (17u)

*/

#include <SPI.h>
#include <SD.h>

File myFile;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.print("Initializing SD card...");

  if (!SD.begin(SS)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

  // re-open the file for reading:
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");

    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      Serial.write(myFile.read());
    }
    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}

void loop() {
  // nothing happens after setup
}

Here is the serial output.

Initializing SD card...initialization done.
Writing to test.txt...done.
test.txt:
testing 1, 2, 3. 
testing 1, 2, 3.

Thanks

  1. Raspberry Pi Pico and rp2040 boards: pinout, specs, and Arduino IDE configuration
  2. Raspberry Pi Pico and rp2040 boards: integrated LittleFS filesystem
  3. Raspberry Pi Pico and rp2040 board: ethernet w5500 with plain (HTTP) and SSL (HTTPS) requests
  4. Raspberry Pi Pico and rp2040 boards: WiFiNINA with ESP32 WiFi Co-Processor
  5. Raspberry Pi Pico and rp2040 boards: how to use SD card

Spread the love

2 Responses

  1. Heinz A. says:

    Really great article! But I have one question left: How do I tell the RP2040 if I have the SD-Card connected to SPI1?
    From the code it seems like it always uses SPI0.

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *