Pour les projets d’objets connectés qui nécessitent le stockage d’une importante quantité de données, il peut s’avérer nécessaire d’avoir recours à un stockage sur carte micro SD. En effet, la plupart des cartes de développement Arduino ne disposent pas de mémoire Flash contrairement à l’ESP32 ou ESP8266.
Les cartes de développement ESP32 et ESP8266 embarquent un module de mémoire flash d’au moins 4Mo dont on peut utiliser au moins 3Mo pour stocker des données. Attention car la mémoire flash peut être écrite 10000 fois ce qui est faible pour un système d’acquisition de données.
Par contre, le stockage sur la mémoire flash embarquée est très bien adapté pour enregistrer le code source de l’interface HTML, feuille de style, fichiers de paramètres… Pour en savoir plus, voici quelques articles qui traitent du sujet
Sommaire
- 1 Repérage des broches du bus SPI des cartes de développement les plus courantes
- 2 Breakout (carte d’extension) lecteur de carte micro SD sur bus SPI
- 3 Cartes de développement avec lecteur de carte SD intégré
- 4 Connecter directement la carte micro-SD au micro-contrôleur !
- 5 Formater la carte SD en FAT16 ou FAT32
- 6 Fonctions proposées par la librairie SD.h, classes SD et File
- 7 Disponibilité des fonctions sur Arduino, ESP32 et ESP8266
- 8 Monter la carte SD. Code Arduino compatible ESP32, ESP8266
- 9 ESP32 uniquement, type de carte SD, espace occupé et espace disponible
- 10 Ecrire dans un fichier, version ESP32
- 11 Ecrire dans un fichier, version ESP8266 ou Arduino
- 12 Mises à jour
Repérage des broches du bus SPI des cartes de développement les plus courantes
Tous les modules lecteur de carte micro-SD utilisent le bus SPI pour communiquer avec le micro-contrôleur. Impossible de lister toutes les cartes de développement du marché.
Voici toutefois un tableau récapitulatif des cartes les plus courantes. Certaines cartes de développement disposent de plusieurs bus SPI.
Breakout (carte d’extension) lecteur de carte micro SD sur bus SPI
Les modules lecteur de carte micro SD utilisent le bus SPI pour communiquer avec le micro-contrôleur, Le bus SPI nécessitera de réserver 4 broches à votre projet. Cela ne pose pas de problème pour la plupart des cartes de développement à l’exception de l’ESP8266 plus limité.
Dans ce cas, vous pouvez opter pour un ESP32 dont la programmation est très similaire. Vous pouvez également ajouter une carte d’extension I2C qui permettra d’augmenter le nombre d’I/O ou d’entrées analogiques. Voici quelques solutions courantes.
Cartes de développement avec lecteur de carte SD intégré
Certaines cartes de développement embarquent un lecteur de carte. micro-SD. Voici quelques cartes de développement très courantes dans le grand public.
Malheureusement aucun carte Arduino (sauf erreur de ma part) n’est équipée d’un lecteur de carte SD, y compris les nouveaux modèles Pro. Idem pour l’ESP8266. Il faudra obligatoirement passer par un module ou un shield à empiler (au format d1 mini).
Connecter directement la carte micro-SD au micro-contrôleur !
Chaque carte micro SD embarque un contrôleur SPI, donc on peut très bien se passer d’un lecteur de carte micro-SD !
C’est beaucoup moins élégant mais vous pouvez vous dépanner avec quelques résistances en cas de panne ou en attendant de recevoir votre matériel. Pour en savoir plus, lisez ce tutoriel proposé par Renzo Mischianti.
Source : mischianti.org
Formater la carte SD en FAT16 ou FAT32
La librairie SD ne supporte que le formatage de type FAT16 ou FAT32. Les systèmes de fichier FAT16 et FAT32 sont supportés par tous les systèmes d’exploitation. Windows, macOS et Linux (y compris les versions ARM). Vous n’aurez aucun problème pour lire vos données.
Pour éviter tout problème de compatibilité, il est préférable d’utiliser l’utilitaire gratuit SD Card Formatter développé par l’association SD. SD Card Formatter est disponible pour toutes les plateformes ici.
Aucun réglage à faire, tout est automatique !
Fonctions proposées par la librairie SD.h, classes SD et File
La documentation officielle est disponible en ligne ici.
Classe SD, initialisation, opérations sur les dossiers
La classe SD fournit des fonctions pour accéder à la carte SD et manipuler les fichiers et répertoires quelle contient.
begin(broche_CS)
Initialise la bibliothèque SD et la carte. Renvoie true en cas de succès, false en cas d’échec.
Si la broche n’est pas précisée, la librairie utilise la broche SS par défaut de la plateforme cible
exists(nom_de_fichier)
Teste si un fichier ou un répertoire existe sur la carte SD. Renvoie true si le fichier ou le répertoire existe, false dans le cas contraire.
end()
Ferme la communication avec le lecteur de carte avant éjection
mkdir(nom_de_dossier)
Créez un répertoire sur la carte SD, y compris l’arborescence si elle n’existe pas. Renvoie true si la création du répertoire a réussi, false dans le cas contraire.
Par exemple la commande mkdir(“data/today”) créera le dossier data puis le sous-dossier today.
open(chemin_fichier, mode)
Ouvre un fichier sur la carte SD.
Les méthodes pour réaliser des opérations sur les fichiers sont listés au prochain paragraphe.
Mode (paramètre optionnel), le mode dans lequel ouvrir le fichier
- FILE_READ (mode par défaut) ouvre le fichier en lecture. Le pointeur est placé au début du fichier
- FILE_WRITE ouvre le fichier en lecture et en écriture. Le pointeur est placé à la fin du fichier. Si le fichier n’existe pas, il est créé automatiquement.
remove(nom_de_fichier)
Supprime un fichier de la carte SD. Renvoie true si la suppression du fichier a réussi, false en cas d’échec. Aucune valeur de retour si le fichier n’existait pas sur la carte SD.
rmdir(nom_de_dossier)
Supprime un répertoire de la carte SD. Renvoie true si la suppression du répertoire a réussi, false sinon. Aucune valeur de retour si le répertoire n’existait pas sur la carte SD.
Attention, le répertoire doit être vide pour pouvoir être supprimé
Classe File, opérations sur les fichiers
La classe File permet de réaliser toutes les opérations de lecture et l’écriture de fichiers individuels sur la carte SD.
name()
Renvoie le nom du fichier
available()
Vérifie si le fichier n’est pas vide. Renvoie le nombre d’octets utilisés par le fichier.
close()
Ferme le fichier ouvert précédemment avec la fonction open()
flush()
Garantit que tous les octets écrits dans le fichier sont physiquement enregistrés sur la carte SD. Cela se fait automatiquement lorsque le fichier est fermé.
peek()
Lit un octet du fichier sans passer au suivant. La pointeur n’est pas déplacé. Utilisé la méthode seek() pour déplacer le pointeur dans le fichier.
position()
Récupère la position actuelle du pointeur dans le fichier. Octet non signé.
fprint(data, base)
Imprime (enregistre) les données dans le fichier, qui doit avoir été ouvert au préalable à l’aide de la fonction open() dans le mode FILE_WRITE.
data les données à imprimer. Types supportés : char, byte, int, long ou string.
base (optionnel) la base dans laquelle imprimer les nombres: BIN pour binaire (base 2), DEC pour décimal (base 10), OCT pour octal (base 8), HEX pour hexadécimal (base 16).
println(data, base)
Identique à la méthode print() mais ajoute un retour chariot (CR) et nouvelle ligne (LF). Pratique pour un enregistreur de données.
seek(pos)
Positionne le pointeur de fichier à la position indiquée (type unsigned long). La position doit être comprise entre 0 et la taille du fichier (inclus). Renvoie true en cas de succès, false en cas d’échec
size()
Récupère la taille du fichier en octets (non signé).
read() ou read(buf, len)
Lire dans le fichier du fichier à la position du pointeur.
Utiliser la fonction open() avant de pouvoir lire dans un fichier
En cas de doute, utiliser la méthode isDirectory() pour savoir si c’est bien un fichier.
Utiliser la fonction seek() pour placer le pointeur à l’endroit désiré. Renvoie l’octet (ou caractère) suivant, ou -1 si aucun n’est disponible.
Utiliser la méthode available() pour tester si le fichier n’est pas vide
N’oubliez pas de fermer le fichier avec close() dès qu’il n’y a plus rien à lire.
write(data) ou write(buf, len)
Ecrit des data dans le fichier. Renvoie le nombre d’octets écrits.
Utiliser la fonction open() avant de pouvoir lire dans un fichier
Utiliser la fonction seek() pour placer le pointeur à l’endroit désiré. Renvoie l’octet (ou caractère) suivant, ou -1 si aucun n’est disponible.
N’oubliez pas de fermer le fichier avec close() dès qu’il n’y a plus rien à lire.
isDirectory()
Renvoie true si c’est un répertoire.
openNextFile()
Renvoie le fichier ou dossier suivant dans le chemin.
rewindDirectory()
Ramène au premier fichier du répertoire, utiliser conjointement avec openNextFile().
Disponibilité des fonctions sur Arduino, ESP32 et ESP8266
Les librairies SD.h et File.h sont disponibles sur les 3 plateformes. On pourrait croire que les fonctionnalités sont identiques quelque soit la plateforme mais il n’en est rien ce qui rend les développements un peu pénibles. C’est le choix d’Espressif qui a préféré utiliser les mêmes noms pour ces librairies.
Voici donc un tableau récapitulatif pour vous aider à mieux vous y retrouver. Espressif est revenu en arrière pour l’ESP32 en proposant les mêmes fonctions que sur Arduino. On dispose juste de 3 fonctions supplémentaires pour déterminer le type, l’espace occupé et l’espace disponible sur la carte micro SD.
Monter la carte SD. Code Arduino compatible ESP32, ESP8266
La première chose à faire avant de pouvoir lire ou enregistrer des fichiers sur la carte SD est de créer un objet SD à l’aide de la méthode begin().
Créer un nouveau croquis ou projet PlatformIO puis téléverser le code ci-dessous. Vous pouvez utiliser le fichier de configuration platformio.ini pour tester sur Arduino Uno, ESP32 ou ESP8266.
Code Arduino multi-plateforme | Fichier Platformio.ini |
|
[env:lolin_d32] platform = espressif32 board = lolin_d32 framework = arduino monitor_speed = 115200 [env:d1_mini_lite] platform = espressif8266 board = d1_mini_lite framework = arduino monitor_speed = 115200 [env:uno] platform = atmelavr board = uno framework = arduino monitor_speed = 115200 lib_deps = 161 |
Explication du code
La librairie SPI est nécessaire pour communiquer avec le lecteur carte SD. La librairie SD met à disposition des classes SD et File permettant de lire , écrire et manipuler les fichiers sur la cartes SD.
#include
#include
On utilise la directive #define pour spécifier au compilateur le code à inclure en fonction de la plateforme cible, ici la broche CS nécessaire pour la fonction begin().
#if defined(__AVR__)
#define SD_CS 10
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define SD_CS 53
#elif defined(ESP8266)
#define SD_CS D8
#elif ESP32
#define SD_CS 5 //SS
#endif
La méthode SD.begin(SD_CS) renvoi un booléen qui indique si l’objet SD a pu être initialisé, c’est à dire si une carte SD valide se trouve dans le lecteur de carte.
ESP32 uniquement, type de carte SD, espace occupé et espace disponible
Voici un petit programme qui permet de connaître l’espace occupé, l’espace disponible ainsi que le type de carte SD insérée dans le lecteur.
#include
#include
#include
#if defined(__AVR__)
#define SD_CS 10
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define SD_CS 53
#elif defined(ESP8266)
#define SD_CS D8
#elif ESP32
#define SD_CS 5 //SS
#endif
void setup(){
Serial.begin(115200);
if(!SD.begin(SD_CS)){
Serial.println("Card Mount Failed");
return;
} else {
Serial.println("SD Card mounted with success");
#if ESP32
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize();
int cardSizeInMB = cardSize/(1024 * 1024);
Serial.printf("Card size: %u MB \n", cardSizeInMB);
uint64_t bytesAvailable = SD.totalBytes();
int spaceAvailableInMB = bytesAvailable/(1024 * 1024);
Serial.printf("Space available: %u MB \n", spaceAvailableInMB);
uint64_t spaceUsed = SD.usedBytes();
Serial.printf("Space used: %u bytes", spaceUsed);
#endif
}
}
void loop(){
}
Ecrire dans un fichier, version ESP32
Voici enfin un dernier exemple qui explique comment écrire des données dans un fichier. Nous allons récupérer la température et la pression atmosphérique sur un BMP180 connecté sur le bus I2C.
Avant de téléverser le projet, il faudra modifier les variables pour que cela convienne à votre carte ESP32 :
- SD_CS broche CS
- PIN_SDA broche SDA du bus I2C, par défaut 21
- PIN_SCL broche SCL du bus I2C, par défaut 22
Par défaut, une mesure est faite chaque minute (TIME_TO_SLEEP) avant de mettre en veille profonde l’ESP32.
#include
#include
#include
#include
#include
#include
#define SD_CS 5
#define PIN_SDA 22
#define PIN_SCL 17 // la broche 21 n'est pas exposee sur le GPIO de la LoLin D32
// Conversion factor for micro seconds to seconds
uint64_t uS_TO_S_FACTOR = 1000000;
// Sleep for 1 minutes = 60 seconds
uint64_t TIME_TO_SLEEP = 60;
Adafruit_BMP085 bmp;
int BME_EXIST = false;
float temp = 0;
float pressure = 0;
void recordNewData();
void storeDataToSDCard(fs::FS &fs, const char * path, const char * message);
void setup(){
Serial.begin(115200);
Wire.begin(PIN_SDA, PIN_SCL);
if (!bmp.begin()) {
Serial.println("Could not find BMP180 or BMP280 sensor");
} else {
BME_EXIST = true;
}
if(!SD.begin(SD_CS)){
Serial.println("Card Mount Failed");
return;
} else {
Serial.println("SD Card mounted with success");
}
}
void loop(){
if ( BME_EXIST ) {
recordNewData();
}
// Enable Timer wake_up
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void recordNewData(){
temp = bmp.readTemperature();
pressure = bmp.readSealevelPressure();
String message = String(temp) + "," + String(pressure);
storeDataToSDCard(SD, "/data.txt", message.c_str());
}
void storeDataToSDCard(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Appending data to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file for appending");
return;
}
if(file.println(message)) {
Serial.println("Data appended");
} else {
Serial.println("Append failed");
}
file.close();
}
Explication du code
En fonction de la carte de développement, les broches I2C peuvent ne pas être exposées sur le GPIO. La librairie Wire.h pour ESP32 et ESP8266 permet d’attribuer d’autres broches en exécutant la fonction.
Wire.begin(PIN_SDA, PIN_SCL);
Pour en savoir plus sur la librairie Wire.h, lisez cet article
La librairie Adafruit_BMP085 a la fâcheuse tendance à faire planter l’ESP32 si le BMP180 n’a pas été correctement initialisé. IL est facile de contourner le problème en mettant un flag à True si le BMP180 a été correctement initialisé.
if (!bmp.begin()) {
Serial.println("Could not find BMP180 or BMP280 sensor");
} else {
BME_EXIST = true;
}
On l’ESP32 en sommeil entre deux enregistrements
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
Pour en savoir plus sur la mise en veille de l’ESP32, lisez ce tutoriel détaillé
On prépare l’enregistrement sous la forme d’une String. On prendra soin de convertir en chaîne les données numériques.
On va utiliser la méthode println() pour ajouter un enregistrement au fichier de données. Pour renvoyer à la ligne entre chaque enregistrement avec la fonction print(), vous devez ajouter la chaine “\r\n”.
Les méthodes print() et print() acceptent uniquement des chaînes au format C++. On devra donc convertir les strings dans ce format à l’aide de la méthode c_str().
String message = String(temp) + "," + String(pressure);
storeDataToSDCard(SD, "/data.txt", message.c_str());
Toutes les fonctions sur les chaînes sont expliquées dans ce tutoriel
Pour enregistrer des données à un fichier existant, la méthode open() pour ESP32 dispose de l’option FILE_APPEND. Sur Arduino ou ESP8266, on ouvrira simplement avec l’option FILE_WRITE.
Si le fichier est correctement ouvert, on ajoute l’enregistrement. Comme le pointeur de fichier est placé automatiquement à la fin du fichier, les données sont ajoutées. Ici on utilise la méthode println() ce qui permet de passer automatiquement à la ligne.
Pour éviter de détériorer la carte SD et libérer celle-ci pour une autre fonction, on ferme le fichier avec la méthode close().
File file = fs.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file for appending");
return;
}
if(file.println(message)) {
Serial.println("Data appended");
} else {
Serial.println("Append failed");
}
file.close();
Exemple du fichier de données créé
21.60,98569.00
21.60,98578.00
21.60,98570.00
21.60,98576.00
21.60,98570.00
21.60,98572.00
21.60,98572.00
21.60,98571.00
21.60,98572.00
21.60,98567.00
21.60,98569.00
21.60,98568.00
21.60,98571.00
Ecrire dans un fichier, version ESP8266 ou Arduino
Le code adapté à l’Arduino ou l’ESP8266
#include
#include
#include
#include
#include
#include
#define TIME_TO_WAIT 5000
#define PATH_DATA_FILE "/data.txt"
Adafruit_BMP085 bmp;
int BME_EXIST = false;
float temp = 0;
float pressure = 0;
#if defined(__AVR__)
#define SD_CS 10
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define SD_CS 53
#elif defined(ESP8266)
#define SD_CS D8
#endif
void setup(){
Serial.begin(115200);
if (!bmp.begin()) {
Serial.println("Could not find BMP180 or BMP085 sensor at 0x77");
} else {
BME_EXIST = true;
}
if(!SD.begin(SD_CS)){
Serial.println("Card Mount Failed");
return;
} else {
Serial.println("SD Card mounted with success");
}
}
void loop(){
if ( BME_EXIST ) {
temp = bmp.readTemperature();
pressure = bmp.readSealevelPressure();
String message = String(temp) + "," + String(pressure) ;
File file = SD.open(PATH_DATA_FILE, FILE_WRITE);
if(!file) {
Serial.println("Failed to open file for appending");
return;
}
if(file.println(message)) {
Serial.println("Data appended");
} else {
Serial.println("Append failed");
}
file.close();
}
delay(TIME_TO_WAIT);
}
Mises à jour
12/10/2020 Publication de l’article
Merci pour votre lecture.
- ESP32, broches GPIO et fonctions associées. I/O, PWM, RTC, I2C, SPI, ADC, DAC
- ESP32-CAM. Broches et équipements ESP-EYE, AI Thinker, TTGO T-Camera, M5Stack Timer Camera…
- ESP32-CAM. Quel modèle choisir ? ESP-EYE, AI Thinker, TTGO T-Camera, M5Stack Timer Camera…
- M5Stack Atomic GPS. Tracker ESP32 TinyGPS++, export GPX sur carte SD, visualisation sur Google Maps ou VSCode
- Home Assistant. Installer le snap sur NAS Synology sur une machine virtuelle Ubuntu
Avez-vous aimé cet article ?