Come usare la scheda SD con esp8266 e Arduino – 1
Quando si utilizza un microcontrollore, una funzionalità importante è l’archiviazione dei dati, per il logging o salvare impostazioni, o magari per gestire un server Web oppure per mostrare un’immagine.
La soluzione migliore è una scheda SD, perché è un dispositivo semplice, piccolo e di bassa potenza.
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 ed il microcontrollore ha un modulo controller separato e per utilizzarlo richiede una licenza. Poiché SPI è un protocollo ampiamente utilizzato ed è disponibile nella maggior parte dei microcontrollori a basso costo, la modalità SPI è un’interfaccia ampiamente utilizzata nei sistemi embedded a basso costo. La tensione di lavoro della famiglia SD va da 2,7 V a 3,6 V e questo è indicato nel registro delle condizioni operative (OCR). Esiste una scheda SD a bassa potenza che funziona a 1,8 V, ma non è così utilizzata.
Pinout
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 | 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 |
Per prima cosa interfacciamo la scheda SD.
Cablaggio
La tensione operativa ci obbliga a creare 2 distinti schemi di connessione in base al tipo di microcontrollore. Per interfacciare la scheda SD uso un adattatore SD per micro SD, e il risultato è questo.
Arduino UNO
Come sapete, Arduino UNO funziona a 5 V, quindi è necessario aggiungere nella linea di ingresso un partitore di tensione per evitare di danneggiare la scheda SD.
Per maggiori informazioni sul partitore di tensione potete vedere qui “Partitore di tensione (voltage divider): calcolatore e applicazioni“.
Uso 2 resistenze di 1K e 2K per ottenere 3,3v da 5v e metto una resistenza di pull-up il pin MISO per prevenire disturbi.
Nello schema è più semplice identificare il partitore di tensione e le connessioni.
Uso i pins esempi standard di Arduino, quindi in seguito useremo del codice che puoi trovare anche nell’Arduino IDE.
esp8266
Qui la tensione è la stessa, quindi tutto è più semplice.
ed ecco lo schema.
Usiamo il pin D2 che identifica il pin 4 come negli esempi SD di Arduino.
RICORDA!! puoi usare solo i file con pattern 8.3, ad esempio un file come config.txt
è accettato ma configuration.text
no, perché la lunghezza massima del file è di 8 caratteri e l’estensione 3.
Ma ricorda che la connessione standard SPI prevede il pin D8 come CS, come nello schema seguente.
Moduli
Esistono vari moduli per interfacciare il tuo microcontrollore con il tuo dispositivo ed in pratica sono fatti esattamente come lo schema di connessione Arduino per adattatore 5v e come lo schema di connessione esp8266 per 3.3v. Quando ne acquisti uno, devi prestare attenzione alla tensione di lavoro.
Esistono alcune varianti che supportano 3.3v e 5v, come quella qui collegata.
Puoi trovare il modulo per SD e MicroSD qui su AliExpress
Comandi
Classe SD
sd.begin() sd.begin(cspin)
Inizializza la libreria e la scheda SD. Questo inizializza l’uso del bus SPI e del pin di selezione del chip, il cui valore predefinito è il pin SS dell’hardware. Restituisce vero in caso di successo; falso in caso di fallimento.
sd.exists(filename)
Verifica se esiste un file o una directory sulla scheda SD. Restituisce vero se esiste il file o la directory, falso in caso contrario.
sd.mkdir(filename)
Crea una directory sulla scheda SD. Questo creerà anche qualsiasi directory intermedia se non esiste già; per esempio. SD.mkdir("a/b/c")
creerà a, b e c. Restituisce vero se la creazione della directory ha avuto successo, falso in caso contrario.
sd.open(filepath) sd.open(filepath, mode)
Apre un file sulla scheda SD. Se il file viene aperto per la scrittura, verrà creato se non esiste già (ma la directory che lo contiene deve già esistere). Parametro mode
(opzionale): la modalità in cui aprire il file, il valore predefinito è FILE_READ – byte. uno di: FILE_READ: apre il file per la lettura, a partire dall’inizio del file. FILE_WRITE: apre il file per la lettura e la scrittura, a partire dalla fine del file. Restituisce un oggetto File che fa riferimento al file aperto; se non è possibile aprire il file, questo oggetto verrà valutato come falso in un contesto booleano, ovvero è possibile verificare il valore restituito con “if (f)
“.
sd.remove(filename)
Rimuovere un file dalla scheda SD. Restituisce vero se la rimozione del file ha esito positivo, falso in caso contrario. (se il file non esisteva, il valore restituito non è specificato)
rs.rmdir(filename)
Rimuove una directory dalla scheda SD. La directory deve essere vuota. Restituisce vero se la rimozione della directory è riuscita, falso in caso contrario. (se la directory non esisteva, il valore restituito non è specificato)
File class
file.name()
Restituisce il nome del file
file.available()
Controlla se ci sono byte disponibili per la lettura dal file. Restituisce il numero di byte.
file.close()
Chiudere il file e assicurarsi che tutti i dati scritti su di esso vengano salvati fisicamente sulla scheda SD.
file.flush()
Assicura che tutti i byte scritti nel file vengano fisicamente salvati sulla scheda SD. Questo viene fatto automaticamente alla chiusura del file.
file.peek()
Leggi un byte dal file senza passare a quello successivo. Cioè, le chiamate successive a peek() restituiranno lo stesso valore, così come la chiamata successiva a read().
file.position()
Ottieni la posizione corrente all’interno del file (ovvero la posizione in cui il prossimo byte verrà letto o scritto). Restituisce la posizione all’interno del file (unsigned long).
file.print(data) file.print(data, base)
Stampa i dati sul file (doveva essere aperto per la scrittura). Stampa i numeri come una sequenza di cifre, ognuna un carattere ASCII (ad es. Il numero 123 viene inviato come i tre caratteri ‘1’, ‘2’, ‘3’).
data: i dati da stampare (char, byte, int, long o string),
base (opzionale): la base in cui stampare i numeri: BIN per binario (base 2), DEC per decimale (base 10), PTOM per ottale (base 8), HEX per esadecimale (base 16). Restituisce il numero di byte scritti, sebbene la lettura di quel numero sia facoltativa.
file.println() file.println(data) file.println(data, base)
Come print
ma con il return finale.
file.seek(pos)
Cerca una nuova posizione nel file, che deve essere compresa tra 0 e le dimensioni del file (incluso).
Parametri:
pos: la posizione in cui cercare (unsigned long). Restituisce vero per il successo, falso per il fallimento (booleano)
file.size()
Ottieni le dimensioni del file. Restituisce la dimensione del file in byte (unsigned long).
file.read() file.read(buf, len)
Leggi dal file. Restituisce il byte (o il carattere) successivo o -1 se nessuno è disponibile.
file.write(data) file.write(buf, len)
Scrivi i dati nel file. Restituisce il numero di byte scritti, la lettura di quel numero è facoltativa
file.isDirectory()
Le directory (o cartelle) sono tipi speciali di file, questa funzione segnala se il file corrente è una directory o meno. Restituisce vero se è directory.
file.openNextFile()
Riporta il file o la cartella successivi in una directory. Restituisce il file o la cartella successivi nel percorso.
file.rewindDirectory()
Riporta al primo file nella directory, utilizzato insieme a openNextFile().
Esempi Arduino e esp8266 with core <= 2.4.2
Nell’IDE di Arduino puoi trovare alcuni esempi molto utili e ben commentati.
Qui uno schetck che estrae tutte le informazioni sulla scheda SD utilizzata.
/*
SD card test
This example shows how use the utility libraries on which the'
SD library is based in order to get info about your SD card.
Very useful for testing a card when you're not sure whether its working or not.
The circuit:
SD card attached to SPI bus as follows:
** MOSI - pin 11 on Arduino Uno/Duemilanove/Diecimila
** MISO - pin 12 on Arduino Uno/Duemilanove/Diecimila
** CLK - pin 13 on Arduino Uno/Duemilanove/Diecimila
** CS - depends on your SD card shield or module.
Pin 4 used here for consistency with other Arduino examples
created 28 Mar 2011
by Limor Fried
modified 9 Apr 2012
by Tom Igoe
*/
// include the SD library:
#include <SPI.h>
#include <SD.h>
// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;
// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// WeMos D1 esp8266: pin 4 (D2) as the first schema or D8 as standard
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
// MKRZero SD: SDCARD_SS_PIN
const int chipSelect = 4;
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 (!card.init(SPI_HALF_SPEED, chipSelect)) {
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.");
}
// print the type of card
Serial.println();
Serial.print("Card type: ");
switch (card.type()) {
case SD_CARD_TYPE_SD1:
Serial.println("SD1");
break;
case SD_CARD_TYPE_SD2:
Serial.println("SD2");
break;
case SD_CARD_TYPE_SDHC:
Serial.println("SDHC");
break;
default:
Serial.println("Unknown");
}
// Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
if (!volume.init(card)) {
Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
while (1);
}
Serial.print("Clusters: ");
Serial.println(volume.clusterCount());
Serial.print("Blocks x Cluster: ");
Serial.println(volume.blocksPerCluster());
Serial.print("Total Blocks: ");
Serial.println(volume.blocksPerCluster() * volume.clusterCount());
Serial.println();
// print the type and size of the first FAT-type volume
uint32_t volumesize;
Serial.print("Volume type is: FAT");
Serial.println(volume.fatType(), DEC);
volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
volumesize *= volume.clusterCount(); // we'll have a lot of clusters
volumesize /= 2; // SD card blocks are always 512 bytes (2 blocks are 1KB)
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.println("\nFiles found on the card (name, date and size in bytes): ");
root.openRoot(volume);
// list all files in the card with date and size
root.ls(LS_R | LS_DATE | LS_SIZE);
}
void loop(void) {
}
Ecco un esempio di lettura e scrittura di una scheda SD.
/*
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:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN) in WeMos D1 esp8266: pin 4 (D2) as the first schema or D8 as standard
created Nov 2010
by David A. Mellis
modified 9 Apr 2012
by Tom Igoe
This example code is in the public domain.
*/
#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(4)) {
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
}
Esempi esp8266 con versione core > 2.4.2
Gli ultimi esp8266 core hanno sostituito la libreria SD standard con un’implementazione che prevede l’uso dell’SdFat, aggiungendo di fatto il supporto ai nomi file senza limitazioni, gestione dei tempi di salvataggio ecc.
Comandi aggiuntivi tramite SDFS
SDFS.format ()
Formatta il file system. Restituisce vero se la formattazione ha avuto esito positivo.
SDFS.open (percorso, modalità)
Apre un file. il percorso dovrebbe essere un percorso assoluto che inizia con una barra (ad es. /dir/filename.txt). modalità
è una stringa che specifica la modalità di accesso. Può essere uno di “r”, “w”, “a”, “r +”, “w +”, “a +”. Il significato di queste modalità è lo stesso della funzione C fopen.
Restituisce l’oggetto File. Per verificare se il file è stato aperto correttamente, utilizzare l’operatore booleano.
SDFS.exists (percorso)
Restituisce vero se esiste un file con un determinato percorso
, altrimenti falso.
SDFS.openDir (percorso)
Apre una directory dato il suo percorso
assoluto. Restituisce un oggetto Dir.
SDFS.remove (percorso)
elimina un file dato il suo percorso
assoluto. Restituisce vero se il file è stato eliminato correttamente.
SDFS.rename (pathFrom, pathTo)
Rinomina il file da pathFrom
a pathTo
. I percorsi devono essere assoluti. Restituisce vero se il file è stato rinominato correttamente.
SDFS.info (fs_info)
Riempie la struttura di FSInfo
con informazioni sul file system. Restituisce vero ha esito positivo, altrimenti falso.
file.getCreationTime()
Ritorna la data di creazione in epoch time.
file.getLastWrite()
Ritorna la data dell’ultima scrittura/variazione in epoch time.
Codice
Perciò andrete ad aggiungere le librerie
- ESP8266SdFat
- SD
- SDFS
- SPI
/*
SD card test for esp8266
This example shows how use the utility libraries
The circuit:
SD card attached to SPI bus as follows:
PIN_SPI_SS (15)
PIN_SPI_MOSI (13)
PIN_SPI_MISO (12)
PIN_SPI_SCK (14)
by Mischianti Renzo <https://mischianti.org>
https://www.mischianti.org
*/
// include the SD library:
#include <SPI.h>
#include <SD.h>
// WeMos D1 esp8266: D8 as standard
const int chipSelect = 4;
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.");
}
// 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 2:
Serial.println("SDHC");
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();
}
}
Ed il risultato sarà
Initializing SD card...Wiring is correct and a card is present.
Card type: SDHC
Cluster size: 32768
Blocks x Cluster: 64
Blocks size: 512
Total Blocks: 940
Total Cluster: 60184
Volume type is: FAT16
Volume size (Kb): 1972109
Volume size (Mb): 1925
Volume size (Gb): 1.88
Card size: 1972109.25
Total bytes: 1972109312
Used bytes: 1376256
System Volume Information/
SPLAS.H 49354 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
desktop.ini 130 CREATION: 2021-03-23 14:40:24 LAST WRITE: 2021-01-29 12:02:42
PROVA/
SOTTO/
1109.PDF 148872 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
WPSETT~1.DAT 12 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
PAGe.PHP 122 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
LOGO.JPG 0 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
barbie_singer_step_11.png 55829 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
PROVA2/
404.PHP 121 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
LOGO.JPG 0 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
bomb.png 44236 CREATION: 2021-03-23 14:40:14 LAST WRITE: 2019-01-02 16:34:28
SPLASk.H 24677 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
TESTe.TXT 32 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
logoBN1264.bmp 9338 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
1109.pdf 148872 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
barbie_singer_step_11.png 55829 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
ciao/
logoEncod.txt 6500 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
logo.jpg 46136 CREATION: 2021-03-23 14:40:18 LAST WRITE: 2019-10-23 13:15:28
logoSchermo.xcf 6473 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
nome.svg 8546 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
nome_vector_text.svg 20513 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
logoBN128x64.png 2213 CREATION: 2021-01-01 00:00:00 LAST WRITE: 2021-01-01 00:00:00
LOGOs.JPG 71112 CREATION: 2000-01-01 01:00:00 LAST WRITE: 2000-01-01 01:00:00
Il secondo sketch funziona anche su questa versione del core.
Buonasera complimenti per l’articolo veramente ben fatto. Ho una domanda : in un mio sketch nello scrivere il file questo me lo fa scrivere solo una volta e non le successive anche se restituisce che l’ha scritto. Secondo te quale potrebbe essere il problema? Buona serata.
Ciao Valerio,
che microcontrollore e che versione di core usi?
Se apri un topic e metti anche il codice gli diamo un occhio.
Ciao Renzo
Salve
Ho seguito il tuo tutorial per salvare dati su scheda sd.
la prima parte quella che salva e legge dati su sd usando arduino mega 2560 è tutto ok
la seconda parte quella che chiama in causa esp82266-12e modello vendor montato su scudo Moer Shield Version 1.0
non riesco proprio a farla funzionare
apro il tuo file sd card test for esp8266 sia su ide arduino che su sdcode
inposto scheda su nodeMCU (esp8266 12e module)
uso GPIO 15 > SS GPIO 12 > MISO GPIO 13 > MOSI GPIO 13 > SCK
inserisco le varie librerie SPI SD SDFS esp8266SDFAT ma non riesco ad ottenere nulla non riconosce la scheda
il comando sd.begin(15) o sd.begin(ss) da sempre errore ho anche provato a inpostare il pin 15 a LOW ma non ottengo nessun risultato
Ho anche provato tutti gli esempi su le varie librerie elencate sopra ma niente non riesco a capire dove sbaglio
ho cercato documentazioni in linea ma niente lo stesso succede anche con esempi publicati su random nerd tutorials e su esp8266-italiano software arduino forum
sicuramente sbaglio qualche impostazione ma non n riesco a venire a capo
scusa per la lunghezza del commento
grazie
Ciao Ivan,
ti direi che i problemi possono essere innumerevoli, ma ti consiglierei di verificare se i cavi sono danneggiati e se passi il giusto voltaggio al tuo adattatore SD.
Ciao Renzo