How to use SD card with stm32 and SdFat library

Spread the love

It’s fundamental for data logging the interfacing with SD cards. STM32 doesn’t support well native SD library, so we will use the SdFat library, which has an Adafruit fork also that we’ll use for other purposes.

Ho to use SD Card Adapter STM32 Arduino IDE
Ho to use SD Card Adapter STM32 Arduino IDE

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 it needs a separate license to use it. Since the SPI is a widely used protocol and is available in most low-cost microcontrollers, the SPI mode is the widely used interface in low-cost embedded systems. The operating 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 at 1.8V but isn’t so used.

SD 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 SD card for first.

SD pinout

Modules

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

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

You can find sd card module on AliExpress

Library

We are going to use the SdFat library for this test, the original one from greiman,

but exists a fork from Adafruit, and for a lot of my projects, I use that.

You can install the library from the Arduino libraries manager.

Wiring

Pinout STM32 STM32F1 STM32F103 STM32F103C8 low resolution
Pinout STM32 STM32F1 STM32F103 STM32F103C8 low resolution

I want to show some kind of wiring, raw and with an adapter. I think the raw wiring can be used to understand better the flow of the protocol.

STM32 STM32F401 STM32F401CCU6 pinout low resolution
STM32 STM32F401 STM32F401CCU6 pinout low resolution

Wiring primary SPI

The most common wiring is to use primary SPI, but It isn’t always the better choice, especially when we have SPI Flash or Ethernet on the same SPI interface.

STM32F1 (blue-pill)

Here is the raw wiring schema on primary SPI.

STM32F1 SD Card on SPI
STM32F1 SD Card on SPI

We probably use an adapter for the primary usage like so.

STM32F1 SD Card adapter wiring on primary SPI
STM32F1 SD Card adapter wiring on primary SPI

STM32F4 (black-pill)

STM32F4 SD Card adapter wiring on primary SPI
STM32F4 SD Card adapter wiring on primary SPI

Wiring secondary SPI

Here is the solution with the secondary SPI.

STM32F1 (blue-pill)

STM32F1 SD Card on secondary SPI
STM32F1 SD Card on secondary SPI

And here with the relative adapter.

STM32F1 SD Card adapter wiring on secondary SPI
STM32F1 SD Card adapter wiring on secondary SPI

STM32F4 (black-pill)

STM32F4 SD Card adapter wiring on secondary SPI
STM32F4 SD Card adapter wiring on secondary SPI

Commands

I won’t explain all the commands, now here is the standard FS system list.

SdFat Class

bool begin (SdCsPin_t csPin, uint32_t maxSck)

[in]csPinSD card chip select pin.
[in]maxSckMaximum SCK frequency.

true for success or false for failure.

bool begin (SdCsPin_t csPin=SS)

[in]csPinSD card chip select pin.

true for success or false for failure.

bool begin (SdioConfig sdioConfig)

[in]sdioConfigSDIO configuration.

true for success or false for failure.

bool begin (SdSpiConfig spiConfig)

[in]spiConfigSPI configuration.

true for success or false for failure.

FsVolume Class

uint32_t bytesPerCluster () const
bool chdir ()
bool chdir (const char *path)
bool chdir (const String &path)
void chvol ()
uint32_t clusterCount () const
uint32_t dataStartSector () const
uint8_t * end ()
bool exists (const char *path)
bool exists (const String &path)
uint32_t fatStartSector () const
uint8_t fatType () const
uint32_t freeClusterCount () const
bool isBusy ()
bool ls ()
bool ls (const char *path, uint8_t flags=0)
bool ls (print_t *pr)
bool ls (print_t *pr, const char *path, uint8_t flags)
bool ls (print_t *pr, uint8_t flags)
bool ls (uint8_t flags)
bool mkdir (const char *path, bool pFlag=true)
bool mkdir (const String &path, bool pFlag=true)
FsFile open (const char *path, oflag_t oflag=0X00)
FsFile open (const String &path, oflag_t oflag=0X00)
bool remove (const char *path)
bool remove (const String &path)
bool rename (const char *oldPath, const char *newPath)
bool rename (const String &oldPath, const String &newPath)
bool rmdir (const char *path)
bool rmdir (const String &path)
uint32_t sectorsPerCluster () const

SdFile Class

int available () const
uint32_t available32 () const
void clearError ()
void clearWriteError ()
bool close ()
bool contiguousRange (uint32_t *bgnSector, uint32_t *endSector)
bool createContiguous (const char *path, uint32_t size)
bool createContiguous (FatFile *dirFile, const char *path, uint32_t size)
uint32_t curCluster () const
uint32_t curPosition () const
bool dirEntry (DirFat_t *dir)
uint16_t dirIndex () const
uint32_t dirSize ()
void dmpFile (print_t *pr, uint32_t pos, size_t n)
bool exists (const char *path)
void fgetpos (fspos_t *pos) const
int fgets (char *str, int num, char *delim=NULL)
uint32_t fileSize () const
uint32_t firstBlock () const
uint32_t firstSector () const
void flush ()
void fsetpos (const fspos_t *pos)
bool getAccessDate (uint16_t *pdate)
bool getAccessDateTime (uint16_t *pdate, uint16_t *ptime)
bool getCreateDateTime (uint16_t *pdate, uint16_t *ptime)
uint8_t getError () const
bool getModifyDateTime (uint16_t *pdate, uint16_t *ptime)
size_t getName (char *name, size_t size)
size_t getName7 (char *name, size_t size)
size_t getName8 (char *name, size_t size)
size_t getSFN (char *name, size_t size)
bool getWriteError () const
bool isBusy ()
bool isContiguous () const
bool isDir () const
bool isFile () const
bool isHidden () const
bool isLFN () const
bool isOpen () const
bool isReadable () const
bool isReadOnly () const
bool isRoot () const
bool isRoot32 () const
bool isRootFixed () const
bool isSubDir () const
bool isSystem () const
bool isWritable () const
bool ls (print_t *pr, uint8_t flags=0, uint8_t indent=0)
bool ls (uint8_t flags=0)
bool mkdir (FatFile *dir, const char *path, bool pFlag=true)
bool open (const char *path, oflag_t oflag=0X00)
bool open (FatFile *dirFile, const char *path, oflag_t oflag)
bool open (FatFile *dirFile, uint16_t index, oflag_t oflag)
bool open (FatVolume *vol, const char *path, oflag_t oflag)
bool openExistingSFN (const char *path)
bool openNext (FatFile *dirFile, oflag_t oflag=0X00)
bool openRoot (FatVolume *vol)
int peek ()
bool preAllocate (uint32_t length)
size_t printAccessDate (print_t *pr)
size_t printAccessDateTime (print_t *pr)
size_t printCreateDateTime (print_t *pr)
size_t printField (double value, char term, uint8_t prec=2)
size_t printField (float value, char term, uint8_t prec=2)
size_t printField (Type value, char term)
size_t printFileSize (print_t *pr)
size_t printModifyDateTime (print_t *pr)
size_t printName ()
size_t printName (print_t *pr)
size_t printName7 (print_t *pr)
size_t printName8 (print_t *pr)
size_t printSFN (print_t *pr)
int read ()
int read (void *buf, size_t count)
int8_t readDir (DirFat_t *dir)
bool remove ()
bool remove (const char *path)
bool rename (const char *newPath)
bool rename (FatFile *dirFile, const char *newPath)
void rewind ()
bool rmdir ()
bool rmRfStar ()
SdFile (const char *path, oflag_t oflag)
bool seekCur (int32_t offset)
bool seekEnd (int32_t offset=0)
bool seekSet (uint32_t pos)
bool sync ()
bool timestamp (uint8_t flags, uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
bool truncate ()
bool truncate (uint32_t length)
size_t write (const char *str)
size_t write (const void *buf, size_t count)
size_t write (uint8_t b)

Examples

STM32F1 SDCard adapter on primary SPI
STM32F1 SDCard adapter on primary SPI

SD information and directory list

Here is a simple sketch that extracts the basic information of the SD and gets the list of files in the directory

/*
  SD card test for stm32 and SdFat library

  This example shows how use the utility libraries

    SD card attached to the primary SPI as follows:
		SS    = PA4;
		MOSI  = PA7;
		MISO  = PA8;
		SCK   = PA5;

    SD card attached to the secondary SPI as follows:
		SS    = PB12;
		MOSI  = PB14;
		MISO  = PB15;
		SCK   = PB13;

   by Mischianti Renzo <https://mischianti.org>
 
   https://www.mischianti.org
*/
#include <SPI.h>
//#include <SD.h>
#include "SdFat.h"

// #define SD_CS_PIN PA4

#define SD_CS_PIN PB12
static SPIClass mySPI2(PB15, PB14, PB13, SD_CS_PIN);
#define SD2_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(18), &mySPI2)

SdFat SD;

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(SD2_CONFIG)) {
  // if (!SD.begin(SD_CS_PIN)) {
    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.");
  }

  uint32_t cardSize = SD.card()->sectorCount();

  // print the type of card
  Serial.println();
  Serial.print("Card type:         ");
  switch (SD.card()->type()) {
  case SD_CARD_TYPE_SD1:
    Serial.println(F("SD1"));
    break;
  case SD_CARD_TYPE_SD2:
    Serial.println(F("SD2"));
    break;

  case SD_CARD_TYPE_SDHC:
    if (cardSize < 70000000) {
      Serial.println(F("SDHC"));
    } else {
      Serial.println(F("SDXC"));
    }
    break;

  default:
    Serial.println(F("Unknown"));
  }


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

  Serial.print("Card size:  ");
  Serial.println((float) 0.000512*cardSize);

  Serial.print("Total bytes: ");
  Serial.println(0.000512*SD.vol()->clusterCount()*SD.sectorsPerCluster());

  Serial.print("Free bytes: ");
  Serial.println(0.000512*SD.vol()->freeClusterCount()*SD.sectorsPerCluster());

  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');
    }
    entry.printName(&Serial);

    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);
      uint16_t pdate;
      uint16_t ptime;
      entry.getModifyDateTime(&pdate, &ptime);

      Serial.printf("\tLAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n", FS_YEAR(pdate), FS_MONTH(pdate), FS_DAY(pdate), FS_HOUR(ptime), FS_MINUTE(ptime), FS_SECOND(ptime));
    }
    entry.close();
  }
}

The Serial output becomes like so.

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

Card type:         SDHC
Volume type is:    FAT32
Card size:  31267.49
Total bytes: 31254.90
Free bytes: 31249.83
test.txt                72      LAST WRITE: 2022-01-01 00:00:00
Bench.dat               5000000 LAST WRITE: 2022-01-01 00:00:00

Primary and secondary SPI interface configuration

STM32F1 SD Card adapter on SPI2
STM32F1 SD Card adapter on SPI2

But pay attention to the SPI configuration. If you want to use the primary SPI interface, you can configure it like so:

#define SD_CS_PIN PA4
SdFat SD;

[...]

  if (!SD.begin(SD_CS_PIN)) {
      Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

but if you’re going to use the secondary SPI interface, you must declare a new SPIClass.

#define SD_CS_PIN PB12
static SPIClass mySPI2(PB15, PB14, PB13, SD_CS_PIN);
#define SD2_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(18), &mySPI2)

SdFat SD;

[...]

  if (!SD.begin(SD2_CONFIG)) {
      Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

I set the maximum speed of the SPI interface for STM32, but you can use the variable SPI_FULL_SPEED or the other standard variable.

I also specify the SPI interface are used only for SD card with the variable DEDICATED_SPI instead of SHARED_SPI.

Read/write example

And now the classic write/read example.

/*
  SD card read/write for stm32 and SdFat library

  This example shows how use the utility libraries

    SD card attached to the primary SPI as follows:
		SS    = PA4;
		MOSI  = PA7;
		MISO  = PA8;
		SCK   = PA5;

    SD card attached to the secondary SPI as follows:
		SS    = PB12;
		MOSI  = PB14;
		MISO  = PB15;
		SCK   = PB13;

   by Mischianti Renzo <https://mischianti.org>
 
   https://www.mischianti.org
*/

#include <SPI.h>
//#include <SD.h>
#include "SdFat.h"

#define SD_CS_PIN PA4
// #define SD_CS_PIN PB12
// static SPIClass mySPI2(PB15, PB14, PB13, SD_CS_PIN);
// #define SD2_CONFIG SdSpiConfig(PB12, DEDICATED_SPI, SPI_FULL_SPEED, &mySPI2)

SdFat SD;


File myFile;

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


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

  // if (!SD.begin(SD2_CONFIG)) {
  if (!SD.begin(SD_CS_PIN)) {
      Serial.println("initialization failed!");
    return;
  }
  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
}

And here is the Serial output.

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

Thanks

  1. How to use SD card with esp8266 and Arduino
  2. How to use SD card with esp32
  3. How to use SD card with stm32 and SdFat library

  1. STM32F1 Blue-Pill: pinout, specs, and Arduino IDE configuration (STM32duino and STMicroelectronics)
  2. STM32: program (STM32F1) via USB with STM32duino bootloader
  3. STM32: programming (STM32F1 STM32F4) via USB with HID boot-loader
  4. STM32F4 Black-Pill: pinout, specs, and Arduino IDE configuration
  5. STM32: ethernet w5500 with plain HTTP and SSL (HTTPS)
  6. STM32: ethernet enc28j60 with plain HTTP and SSL (HTTPS)
  7. STM32: WiFiNINA with ESP32 WiFi Co-Processor
    1. STM32F1 Blue-pill: WiFi shield (WiFiNINA)
    2. STM32F4 Black-pill: WiFi shield (WiFiNINA)
  8. How to use SD card with stm32 and SdFat library
  9. \STM32: SPI flash memory FAT FS
  10. STM32: internal RTC, clock, and battery backup (VBAT)
  11. STM32 LoRa
    1. Unleashing IoT Potential: Integrating STM32F1 Blue-Pill with EByte LoRa E32, E22, and E220 Shields
    2. Unleashing IoT Potential: Integrating STM32F4 Black-Pill with EByte LoRa E32, E22, and E220 Shields
  1. STM32 Power saving
    1. STM32F1 Blue-Pill clock and frequency management
    2. STM32F4 Black-Pill clock and frequency management
    3. Intro and Arduino vs STM framework
    4. Library LowPower, wiring, and Idle (STM Sleep) mode
    5. Sleep, deep sleep, shutdown, and power consumption
    6. Wake up from RTC alarm and Serial
    7. Wake up from the external source
    8. Backup domain intro and variable preservation across reset
    9. RTC backup register and SRAM preservation
  1. STM32 send emails with attachments and SSL (like Gmail): w5500, enc28j60, SD, and SPI Fash
  2. FTP server on STM32 with w5500, enc28j60, SD Card, and SPI Flash
  3. Connecting the EByte E70 to STM32 (black/blue pill) devices and a simple sketch example


Spread the love

Leave a Reply

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