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.
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.
Pin Number | Pin Name | In SD Mode | In SPI Mode |
1 | DAT2/X | Connector Data line 2 | No use |
2 | DAT3/CS | Connector Data line 3 | Chip Select |
3 | CMD/DI | Command / Response Line | Data Input |
4 | VDD/VDD | Power supply (+3.3V) | Power supply (+3.3V) |
5 | CLK/SCLK | Clock | Serial Clock |
6 | VSS/VSS | Ground | Ground |
7 | DAT0/D0 | Connector Data line 0 | Data Out |
8 | DAT1/X | Connector Data line 1 | No use |
Now we are going to interface SD card for first.
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
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.
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.
We probably use an adapter for the primary usage like so.
STM32F4 (black-pill)
Wiring secondary SPI
Here is the solution with the secondary SPI.
STM32F1 (blue-pill)
And here with the relative adapter.
STM32F4 (black-pill)
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] | csPin | SD card chip select pin. |
[in] | maxSck | Maximum SCK frequency. |
true for success or false for failure.
bool begin (SdCsPin_t csPin=SS)
[in] | csPin | SD card chip select pin. |
true for success or false for failure.
bool begin (SdioConfig sdioConfig)
[in] | sdioConfig | SDIO configuration. |
true for success or false for failure.
bool begin (SdSpiConfig spiConfig)
[in] | spiConfig | SPI 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
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
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
- How to use SD card with esp8266 and Arduino
- How to use SD card with esp32
- How to use SD card with stm32 and SdFat library
- STM32F1 Blue-Pill: pinout, specs, and Arduino IDE configuration (STM32duino and STMicroelectronics)
- STM32: program (STM32F1) via USB with STM32duino bootloader
- STM32: programming (STM32F1 STM32F4) via USB with HID boot-loader
- STM32F4 Black-Pill: pinout, specs, and Arduino IDE configuration
- STM32: ethernet w5500 with plain HTTP and SSL (HTTPS)
- STM32: ethernet enc28j60 with plain HTTP and SSL (HTTPS)
- STM32: WiFiNINA with ESP32 WiFi Co-Processor
- How to use SD card with stm32 and SdFat library
- \STM32: SPI flash memory FAT FS
- STM32: internal RTC, clock, and battery backup (VBAT)
- STM32 LoRa
- STM32 Power saving
- STM32F1 Blue-Pill clock and frequency management
- STM32F4 Black-Pill clock and frequency management
- Intro and Arduino vs STM framework
- Library LowPower, wiring, and Idle (STM Sleep) mode
- Sleep, deep sleep, shutdown, and power consumption
- Wake up from RTC alarm and Serial
- Wake up from the external source
- Backup domain intro and variable preservation across reset
- RTC backup register and SRAM preservation
- STM32 send emails with attachments and SSL (like Gmail): w5500, enc28j60, SD, and SPI Fash
- FTP server on STM32 with w5500, enc28j60, SD Card, and SPI Flash
- Connecting the EByte E70 to STM32 (black/blue pill) devices and a simple sketch example