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
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 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.
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
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
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
We probably use an adapter for the primary usage like so.
STM32F1 SD Card adapter wiring on primary SPI
STM32F4 (black-pill)
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
And here with the relative adapter.
STM32F1 SD Card adapter wiring on secondary SPI
STM32F4 (black-pill)
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] 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
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include
<SPI.h>
#include
"SdFat.h"
#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
() {
Serial.begin
(
9600
);
while
(
!
Serial) {
;
}
Serial.print
(
"\nInitializing SD card..."
);
if
(
!
SD.begin(SD2_CONFIG)) {
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();
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"
));
}
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) {
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
{
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.
1
2
3
4
5
6
7
8
9
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
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
#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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include
<SPI.h>
#include
"SdFat.h"
#define
SD_CS_PIN PA4
SdFat SD;
File myFile;
void
setup
() {
Serial.begin
(
115200
);
while
(
!
Serial) {
;
}
Serial.print
(
"Initializing SD card..."
);
if
(
!
SD.begin(SD_CS_PIN)) {
Serial.println
(
"initialization failed!"
);
return
;
}
Serial.println
(
"initialization done."
);
myFile
=
SD.open(
"test.txt"
, FILE_WRITE);
if
(myFile) {
Serial.print
(
"Writing to test.txt..."
);
myFile.println(
"testing 1, 2, 3."
);
myFile.close();
Serial.println
(
"done."
);
}
else
{
Serial.println
(
"error opening test.txt"
);
}
myFile
=
SD.open(
"test.txt"
);
if
(myFile) {
Serial.println
(
"test.txt:"
);
while
(myFile.available()) {
Serial.write
(myFile.read());
}
myFile.close();
}
else
{
Serial.println
(
"error opening test.txt"
);
}
}
void
loop
() {
}
And here is the Serial output.
1
2
3
4
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
STM32F1 Blue-pill: WiFi shield (WiFiNINA)
STM32F4 Black-pill: WiFi shield (WiFiNINA)
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
Unleashing IoT Potential: Integrating STM32F1 Blue-Pill with EByte LoRa E32, E22, and E220 Shields
Unleashing IoT Potential: Integrating STM32F4 Black-Pill with EByte LoRa E32, E22, and E220 Shields
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