Come utilizzare la scheda SD con l’stm32 e la libreria SdFat
Fondamentale per la registrazione dei dati è l’interfacciamento con le schede SD. STM32 non supporta bene la libreria SD nativa, quindi useremo la libreria SdFat, che ha anche un fork Adafruit che useremo per altri scopi.
Protocollo
La scheda SD ha un’interfaccia host nativa oltre alla modalità SPI per la comunicazione con i dispositivi master. L’interfaccia nativa utilizza quattro linee per il trasferimento dei dati se il microcontrollore ha un modulo specifico per il controllo della scheda SD ha bisogno di una licenza separata per usarlo. Poiché SPI è un protocollo ampiamente utilizzato ed è disponibile nella maggior parte dei microcontrollori a basso costo, la modalità SPI è l’interfaccia ampiamente utilizzata nei sistemi embedded a basso costo. L’intervallo di tensione operativa dellea SD è compreso tra 2,7 V e 3,6 V e questo è indicato nel registro delle condizioni di funzionamento (OCR). Esiste una scheda SD a bassa potenza che funziona a 1,8 V ma non è così utilizzata.
Piedinatura SD
Esistono vari fattori di forma, ma la piedinatura di base è la stessa.
Pin Number | Pin Name | In SD Mode | In SPI Mode |
1 | DAT2/X | Connettore Linea dati 2 | No use |
2 | DAT3/CS | Connettore Linea dati 3 | Chip Select |
3 | CMD/DI | Riga di comando/risposta | Data Input |
4 | VDD/VDD | Alimentazione (+3.3V) | Alimentazione (+3.3V) |
5 | CLK/SCLK | Clock | Serial Clock |
6 | VSS/VSS | Ground | Ground |
7 | DAT0/D0 | Connettore Linea dati 0 | Data Out |
8 | DAT1/X | Connettore Linea dati 1 | No use |
Ora per prima cosa interfacceremo la scheda SD.
Moduli
Esistono vari moduli per interfacciare il tuo microcontrollore con il dispositivo e funziona esattamente con lo schema di connessione Arduino per l’adattatore 5v e come lo schema di connessione esp8266 per 3.3v. Quando ne acquisti uno, devi prestare attenzione alla tensione di esercizio.
Esiste qualche variante che supporta 3.3v e 5v, come quella qui linkata.
Puoi trovare il modulo della scheda SD su AliExpress
Libreria
Utilizzeremo la libreria SdFat per questo test, quella originale di greiman,
ma esiste un fork di Adafruit e per molti dei miei progetti lo uso.
Puoi installare la libreria dal gestore delle librerie di Arduino.
Cablaggio
Voglio mostrare una specie di cablaggio, grezzo e con un adattatore. Penso che il cablaggio grezzo possa essere utilizzato per comprendere meglio il flusso del protocollo.
Cablaggio della SPI primaria
Il cablaggio più comune consiste nell’utilizzare l’SPI primario, ma non è sempre la scelta migliore, soprattutto quando abbiamo SPI Flash o Ethernet sulla stessa interfaccia SPI.
STM32F1 (blue-pill)
Ecco lo schema di cablaggio grezzo sulla SPI primaria.
Probabilmente utilizziamo un adattatore per l’uso principale in questo modo.
STM32F4 (black-pill)
Cablaggio su SPI secondario
Ecco la soluzione con la SPI secondaria.
STM32F1 (blue-pill)
E qui con il relativo adattatore.
STM32F4 (black-pill)
Comandi
Non spiegherò tutti i comandi, ora ecco l’elenco standard del sistema FS.
Classe SdFat
bool begin (SdCsPin_t csPin, uint32_t maxSck)
[in] | csPin | SD card chip select pin. |
[in] | maxSck | Maximum SCK frequency. |
true per il successo o false per il fallimento.
bool begin (SdCsPin_t csPin=SS)
[in] | csPin | SD card chip select pin. |
true per il successo o false per il fallimento.
bool begin (SdioConfig sdioConfig)
[in] | sdioConfig | SDIO configuration. |
true per il successo o false per il fallimento.
bool begin (SdSpiConfig spiConfig)
[in] | spiConfig | SPI configuration. |
true per il successo o false per il fallimento.
Classe FsVolume
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
Classe SdFile
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)
Esempi
Informazioni SD ed elenco di una directory
Ecco un semplice schizzo che estrae le informazioni di base della SD e ottiene l’elenco dei file nella 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();
}
}
L’uscita seriale diventa così.
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
Configurazione dell’interfaccia SPI primaria e secondaria
Ma fai attenzione alla configurazione SPI. Se desideri utilizzare l’interfaccia SPI primaria, puoi configurarla in questo modo:
#define SD_CS_PIN PA4
SdFat SD;
[...]
if (!SD.begin(SD_CS_PIN)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
ma se intendi utilizzare l’interfaccia SPI secondaria, devi dichiarare una nuova 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.");
Ho impostato la velocità massima dell’interfaccia SPI per STM32, ma puoi usare la variabile SPI_FULL_SPEED
o l’altra variabile standard.
Specifico anche che l’interfaccia SPI viene utilizzata solo per la scheda SD con la variabile DEDICATED_SPI
invece di SHARED_SPI
.
Esempio di lettura/scrittura
E ora il classico esempio di scrittura/lettura.
/*
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.
Ed ecco l’uscita seriale.
Grazie
- 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: piedinatura, specifiche e configurazione IDE Arduino (STM32duino e STMicroelectronics)
- STM32: programmazione (STM32F1) via USB con bootloader STM32duino
- STM32: programmazione (STM32F1 STM32F4) tramite USB con bootloader HID
- STM32F4 Black Pill: pinout, specifiche e configurazione IDE Arduino
- STM32: ethernet w5500 standard (HTTP) e SSL (HTTPS)
- STM32: ethernet enc28j60 standard (HTTP) e SSL (HTTPS)
- STM32: WiFiNINA con un ESP32 come WiFi Co-Processor
- Come utilizzare la scheda SD con l’stm32 e la libreria SdFat
- STM32: memoria flash SPI FAT FS
- STM32: RTC interno, sistema orario e backup batteria (VBAT)
- STM32 LoRa
- STM32 Risparmio energetico
- STM32F1 Blue-Pill gestione clock e frequenza
- STM32F4 Black-Pill gestione clock e frequenza
- Introduzione e framework Arduino vs STM
- Libreria LowPower, cablaggio e Idle (STM Sleep).
- Sleep, deep sleep, shutdown e consumo energetico
- Sveglia da allarme RTC e Seriale
- Sveglia da sorgente esterna
- Introduzione al dominio di backup e conservazione delle variabili durante il RESET
- Registro di backup RTC e conservazione della SRAM
- STM32 invia email con allegati e SSL (come Gmail): w5500, enc28j60, SD e SPI Flash
- Server FTP su STM32 con W5500, ENC28J60, scheda SD e memoria flash SPI
- Collegamento dell’EByte E70 ai dispositivi STM32 (black/blue pill) e un semplice sketch di esempio