Skip to Content

Module de carte SD avec ESP32

Module de carte SD avec ESP32

Dans ce tutoriel, vous apprendrez comment connecter un module Micro SD à l’ESP32 et effectuer des opérations de fichiers essentielles. Nous couvrirons la liste des répertoires, la création et la suppression de fichiers et dossiers, la lecture et l’écriture de données, et plus encore. À la fin, vous aurez une base solide pour intégrer le stockage sur carte SD dans vos propres projets.

Pièces requises

Voici les composants nécessaires pour ce tutoriel. J’ai utilisé une ancienne carte ESP32, qui est obsolète mais que vous pouvez encore trouver à très bas prix. Cependant, n’importe quelle autre ESP32 convient également.

Nous avons également besoin d’un module Micro SD. Tout module avec une interface SPI et une logique 3,3 V fonctionnera. Si vous souhaitez connecter le module SD à un Arduino UNO, vous aurez besoin d’un module avec une logique 5 V ou d’un convertisseur de niveau logique. J’ai choisi un module très simple et compact sans convertisseur, car nous le connecterons à un ESP32 avec une logique 3,3 V.

Module Micro SD

Carte Micro SD 8 Go

ESP32 lite Lolin32

ESP32 lite

USB data cable

Câble USB de données

Dupont wire set

Jeu de fils Dupont

Half_breadboard56a

Plaque d’essai (breadboard)

Bases des modules lecteur de carte SD

Les modules MicroSD servent d’interface entre un microcontrôleur, comme l’ESP32, et une carte MicroSD. Ils permettent au microcontrôleur de lire et d’écrire des données sur la carte, augmentant ainsi la capacité de stockage pour la journalisation, les médias ou les fichiers de configuration.

Fonctionnement des modules MicroSD

Au cœur, les cartes MicroSD communiquent via le protocole SPI (Serial Peripheral Interface). Le module contient un socket MicroSD et un circuit de conversion de niveau si nécessaire, pour adapter les niveaux de tension entre la carte et le microcontrôleur. Connecté, l’ESP32 agit en maître SPI, envoyant des commandes et recevant des données de la carte MicroSD, qui agit en esclave SPI.

Le bus SPI utilise des lignes séparées pour l’entrée de données (MOSI), la sortie de données (MISO), l’horloge (SCK) et la sélection de puce (CS). Cette communication full-duplex permet un transfert rapide et fiable, essentiel pour les opérations sur le système de fichiers.

Pourquoi SPI et pas I2C ?

SPI est préféré à I2C pour la communication avec les cartes MicroSD en raison de ses vitesses de transfert plus élevées et de son protocole plus simple. I2C est un bus à deux fils conçu pour des communications courtes et lentes, généralement jusqu’à 400 kHz ou 1 MHz en modes rapides. En revanche, SPI peut facilement fonctionner à plusieurs MHz, ce qui est crucial pour lire et écrire efficacement de gros fichiers.

De plus, SPI supporte la communication full-duplex, permettant l’envoi et la réception simultanés de données, alors que I2C est half-duplex. La spécification MicroSD supporte explicitement le mode SPI, ce qui en fait le choix naturel.

Broches SPI

Le tableau suivant montre les broches nécessaires pour SPI :

Broche SPIFonctionDescription
MOSIMaster Out Slave InTransporte les données de l’ESP32 vers la carte MicroSD
MISOMaster In Slave OutTransporte les données de la carte MicroSD vers l’ESP32
SCKHorloge sérieSynchronise la transmission des données
CSChip Select (sélection esclave)Sélectionne la carte MicroSD pour la communication

L’ESP32 permet généralement une assignation flexible de ces broches via sa matrice GPIO, mais les valeurs par défaut courantes sont GPIO 23 pour MOSI, GPIO 19 pour MISO, GPIO 18 pour SCK, et GPIO 5 pour CS.

Types de modules MicroSD : 5V vs 3,3V

Il existe deux principaux types de modules MicroSD selon la compatibilité de tension. La plupart des cartes Arduino fonctionnent en logique 5V, donc les modules conçus pour Arduino incluent des convertisseurs de niveau et des régulateurs de tension pour interfacer en toute sécurité les cartes MicroSD 3,3V avec des signaux et une alimentation 5V.

L’image ci-dessous montre le recto et le verso d’un module MicroSD 5V. Vous pouvez voir le régulateur de tension et le circuit de conversion de niveau logique :

5V MicroSD card module
Module MicroSD 5V

Cependant, si votre microcontrôleur fonctionne déjà en logique 3,3V (ESP32), un régulateur de tension et un convertisseur de niveau ne sont pas nécessaires. Vous pouvez alors utiliser un module lecteur SD plus simple et moins cher. L’image suivante montre le recto et le verso d’un module MicroSD 3,3V typique :

3.3V MicroSD card module
Module MicroSD 3,3V

En regardant de près la carte 3,3V, vous verrez qu’il n’y a pas de circuits intégrés pour la régulation de tension ou la conversion de niveau. Mais il doit y avoir des résistances de tirage (pull-up) de 10K pour l’interface SPI :

Pull-up resistors on MicroSD card module
Résistances de tirage sur le module MicroSD

Évitez les cartes qui n’ont pas ces résistances de tirage et ne connectez pas un module MicroSD 3,3V à un Arduino 5V !

Tailles et types de cartes MicroSD

Les cartes MicroSD existent en différentes capacités de stockage et classes de vitesse. Les tailles les plus courantes vont de 2 Go à 1 To, avec les classifications suivantes :

TypeNomTaille
SDSC Capacité standardJusqu’à 2 Go
SDHC Haute capacité4 Go à 32 Go
SDXC Capacité étendue64 Go à 2 To

La bibliothèque SD de l’ESP32 supporte les cartes formatées en FAT32.

Classes de vitesse des cartes MicroSD

Les classes de vitesse définissent la vitesse d’écriture minimale soutenue de la carte, ce qui est crucial pour des applications comme l’enregistrement vidéo ou la journalisation de données.

Classe de vitesseVitesse d’écriture minimaleDescription
Classe 22 Mo/sAdaptée à la vidéo définition standard
Classe 44 Mo/sAdaptée à la vidéo haute définition
Classe 66 Mo/sAdaptée à l’enregistrement vidéo HD
Classe 1010 Mo/sAdaptée à la vidéo Full HD
UHS-I U110 Mo/sUltra haute vitesse, adaptée à la vidéo HD
UHS-I U330 Mo/sUltra haute vitesse, adaptée à la vidéo 4K

Pour la plupart des projets ESP32, une carte Classe 10 ou UHS-I U1 offre un bon compromis entre vitesse et coût. Les cartes plus rapides ne garantissent pas toujours de meilleures performances à cause des limites du microcontrôleur et du bus SPI.

Connexion du module SD à l’ESP32

Connecter votre module SD à l’ESP32 via SPI est simple. L’image suivante montre le câblage entre un ESP32 Lite et un module MicroSD 3,3V :

Connecting MicroSD Card Module to ESP32 Lite
Connexion du module MicroSD à l’ESP32 Lite

Pour plus de commodité, voici le mapping des broches entre le module SD et l’ESP32 :

Broche module SDBroche ESP32
VCC3,3V
GNDGND
MISOGPIO 19
MOSIGPIO 23
SCK/CLKGPIO 18
CS (Chip Select)GPIO 5

Assurez-vous d’utiliser la broche 3,3V de l’ESP32 pour alimenter le module SD. La carte SD fonctionne en 3,3V, et appliquer 5V peut l’endommager.

Formatage de la carte SD

Avant d’utiliser votre carte SD avec l’ESP32, il est important de s’assurer qu’elle est correctement formatée. La plupart des cartes SD sont préformatées en FAT32, compatible avec la bibliothèque SD de l’ESP32.

Cependant, si votre carte est neuve ou a été utilisée avec d’autres appareils, un formatage peut être nécessaire. Vous pouvez formater la carte SD avec votre ordinateur :

Windows

  1. Insérez la carte SD dans votre PC.
  2. Ouvrez l’Explorateur de fichiers et faites un clic droit sur le lecteur de la carte SD.
  3. Sélectionnez Format.
  4. Choisissez FAT32 comme système de fichiers.
  5. Cliquez sur Démarrer pour formater.

macOS

  1. Insérez la carte SD.
  2. Ouvrez Utilitaire de disque.
  3. Sélectionnez la carte SD dans la barre latérale.
  4. Cliquez sur Effacer.
  5. Choisissez MS-DOS (FAT) comme format.
  6. Cliquez sur Effacer pour formater.

Linux

Utilisez la commande mkfs dans le terminal. Remplacez /dev/sdX1 par l’identifiant de votre carte SD :

  sudo mkfs.vfat -F 32 /dev/sdX1

Conseils importants

  • Sauvegardez toutes les données importantes avant de formater.
  • N’utilisez pas exFAT ou NTFS, car ils ne sont pas supportés par la bibliothèque SD de l’ESP32.
  • Après le formatage, éjectez la carte en toute sécurité avant de l’insérer dans le module SD.

Taille maximale de la carte SD

Sous Windows, la taille maximale que vous pouvez formater via l’explorateur de fichiers est de 32 Go. Il est possible de formater des cartes SD de plus grande capacité en FAT32 et de les utiliser avec un ESP32. Cependant, vous devrez utiliser d’autres outils logiciels. Par exemple, il existe FAT32Format ou guiformat-x64.

Classe SD

La plupart des fonctions liées aux cartes SD se trouvent dans la bibliothèque SD et plus précisément dans la SDClass. Le code ci-dessous montre la SDClass avec ses déclarations de fonctions. Jetez-y un œil rapide, et dans les sections suivantes vous apprendrez à utiliser les différentes fonctions de SDClass.

class SDClass {
    private:
      Sd2Card card;
      SdVolume volume;
      SdFile root;

      SdFile getParentDir(const char *filepath, int *indx);
	  
    public:
      // This needs to be called to set up the connection to the SD card
      // before other methods are used.
      bool begin(uint8_t csPin = SD_CHIP_SELECT_PIN);
      bool begin(uint32_t clock, uint8_t csPin);

      //call this when a card is removed. It will allow you to insert and initialise a new card.
      void end();

      // Note that currently only one file can be open at a time.
      File open(const char *filename, uint8_t mode = FILE_READ);
      File open(const String &filename, uint8_t mode = FILE_READ) {
        return open(filename.c_str(), mode);
      }

      bool exists(const char *filepath);
      bool exists(const String &filepath) {
        return exists(filepath.c_str());
      }

      bool mkdir(const char *filepath);
      bool mkdir(const String &filepath) {
        return mkdir(filepath.c_str());
      }

      bool remove(const char *filepath);
      bool remove(const String &filepath) {
        return remove(filepath.c_str());
      }

      bool rmdir(const char *filepath);
      bool rmdir(const String &filepath) {
        return rmdir(filepath.c_str());
      }

    private:
      int fileOpenMode;

      friend class File;
      friend bool callback_openPath(SdFile&, const char *, bool, void *);
  };

  extern SDClass SD;
};

Initialisation du module SD

Avant de pouvoir travailler avec des fichiers sur votre carte SD, vous devez initialiser le module SD avec votre ESP32. L’initialisation établit la communication entre l’ESP32 et la carte SD, garantissant que la carte est prête pour les opérations sur fichiers. Le code suivant montre comment faire :

#include "FS.h"
#include "SD.h"
#include "SPI.h"

void setup() {
  Serial.begin(115200);
  if(!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }
  Serial.println("SD Card Initialized");
}

void loop() { }

La fonction SD.begin() retourne true si la carte est initialisée avec succès. Si elle retourne false, la carte pourrait ne pas être correctement connectée ou mal formatée. Vérifiez aussi le câblage du module SD avec l’ESP32.

Notez que SD.begin() a des paramètres que vous pouvez régler si le défaut ne fonctionne pas pour vous :

SD.begin(uint8_t ssPin=SS, 
         SPIClass &spi=SPI, 
         uint32_t frequency=4000000, 
         const char * mountpoint="/sd", 
         uint8_t max_files=5, 
         bool format_if_empty=false);

Surtout, vous pouvez changer les broches de l’interface SPI ici. La bibliothèque SD utilise par défaut les broches VSPI (23, 19, 18, 5).

Pour changer les broches, redéfinissez les constantes de broches, créez une instance de SPIClass et passez cette instance à la méthode SD.begin(). Voici un exemple :

#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCK  17
#define MISO 19
#define MOSI 23
#define CS   5

SPIClass spi = SPIClass(VSPI);

void setup() {
  spi.begin(SCK, MISO, MOSI, CS);
  if (!SD.begin(CS, spi)) {
    Serial.println("Card Mount Failed");
  }
}

void loop() {}

Une fois initialisée, la carte SD est prête pour les opérations sur fichiers comme la lecture, l’écriture et la gestion des répertoires.

Lecture du type de carte SD

Après avoir initialisé le module SD, vous pouvez identifier programmatiquement le type de carte SD connectée. La fonction sd.cardType() retourne un entier représentant le type de carte. Voici un exemple de code qui affiche le type de carte dans le moniteur série :

#include "FS.h"
#include "SD.h"
#include "SPI.h"

void setup() {
  Serial.begin(115200);

  if (!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }

  uint8_t cardType = SD.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
  } else if (cardType == CARD_MMC) {
    Serial.println("MMC Card");
  } else if (cardType == CARD_SD) {
    Serial.println("Standard SD Card");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC Card");
  } else {
    Serial.println("Unknown Card Type");
  }
}

void loop() { }

On commence par initialiser la carte SD avec SD.begin(). Ensuite, on appelle SD.cardType() pour obtenir le type de carte et enfin, on affiche ce type dans le moniteur série.

Obtenir des informations sur la carte SD

La bibliothèque SD propose aussi des fonctions pour récupérer la taille d’une carte SD, l’espace total disponible et l’espace utilisé. Voir le code suivant :

#include "FS.h"
#include "SD.h"
#include "SPI.h"

void setup() {
  Serial.begin(115200);
  SD.begin();

  uint64_t MB = 1024 * 1024;

  uint64_t cardSize = SD.cardSize();
  Serial.printf("SD Card Size: %lluMB\n", cardSize / MB);

  uint64_t totalBytes = SD.cardSize();
  Serial.printf("Total space: %lluMB\n", totalBytes / MB);

  uint64_t usedBytes = SD.usedBytes();
  Serial.printf("Used space: %lluMB\n", usedBytes / MB);
}

void loop() {}

Lister un répertoire

La bibliothèque SD n’a malheureusement pas de fonction pour lister récursivement tous les fichiers et dossiers d’une carte SD. Vous devrez implémenter cette fonction vous-même. Voici le code d’une fonction printDir() qui affiche la structure des dossiers et fichiers dans le moniteur série :

#include "FS.h"
#include "SD.h"
#include "SPI.h"

void printDir(const char *dirname, uint8_t levels = 0) {
  File root = SD.open(dirname);
  if (!root || !root.isDirectory()) return;

  File file = root.openNextFile();
  while (file) {
    for (int i = 0; i < levels; i++) Serial.print("  ");

    if (file.isDirectory()) {
      Serial.printf("[%s]\n", file.name());
      printDir(file.path(), levels + 1);
    } else {
      Serial.printf("%s (%d)\n", file.name(), file.size());
    }
    file = root.openNextFile();
  }
}

void setup() {
  Serial.begin(115200);
  SD.begin();
  printDir("/");
}

void loop() {}

Si vous avez juste besoin des fichiers dans un dossier spécifique, vous pouvez utiliser le code suivant :

void printDir(const char *dirname) {
  File dir = SD.open(dirname);
  File file = dir.openNextFile();
  while (file) {
    if (!file.isDirectory()) {
      Serial.printf("%s (%d)\n", file.name(), file.size());
    }
    file = dir.openNextFile();
  }
}

Créer ou supprimer des répertoires

Pour créer ou supprimer des répertoires, la bibliothèque SD offre les fonctions mkdir() et rmdir(). Voici un exemple de code qui crée d’abord un répertoire puis le supprime :

#include "FS.h"
#include "SD.h"
#include "SPI.h"

void setup() {
  Serial.begin(115200);
  SD.begin();

  const char *path = "/data";
  if (!SD.mkdir(path)) {
    Serial.println("mkdir failed");
  }

  if (!SD.rmdir(path)) {
    Serial.println("rmdir failed");
  }
}

void loop() {}

Notez que vous devez créer et supprimer les dossiers imbriqués un par un et que les dossiers doivent être vides avant de pouvoir être supprimés. Voici un exemple pour créer et supprimer une structure de dossiers imbriqués :

void setup() {
  SD.begin(); 

  SD.mkdir("/data");
  SD.mkdir("/data/temp");
  SD.mkdir("/data/temp/kitchen");


  SD.rmdir("/data/temp/kitchen");
  SD.rmdir("/data/temp");
  SD.rmdir("/data");
}

Fonctions sur les fichiers

Vous trouverez toutes les fonctions liées aux fichiers dans le fichier File.cpp de la bibliothèque SD. Voici un aperçu rapide des fonctions les plus importantes :

File file = SD.open(path, FILE_WRITE);
File file = SD.open(path, FILE_APPEND);

file.write(buf, size); 
file.print(text);

file.read(buf, nbyte); 
file.read();
file.available();

uint32_t pos = file.position();
file.seek(pos);

file.size();

file.flush();
file.close();

SD.exists(path);
SD.remove(path);
SD.rename(path1, path2);

Dans les sections suivantes, nous détaillerons ces fonctions.

Écrire des données dans un fichier

Pour écrire des données dans un fichier, vous devez d’abord ouvrir le fichier, écrire les données, puis fermer le fichier. Voici un exemple sans gestion d’erreur :

File file = SD.open("/readme.txt", FILE_WRITE);
file.print("This is the readme");
file.close();

J’ai constaté que le nom de fichier doit commencer par une barre oblique (‘/’) ou le chemin depuis la racine, par exemple « /text/readme.txt ». Sinon, je n’ai pas pu créer de fichier.

Notez qu’il existe aussi une méthode printf() qui permet d’écrire du texte formaté dans un fichier, par exemple :

File file = SD.open("/log.txt", FILE_WRITE);
file.printf("Temperature =  %.2f\n", 21.3);
file.close();

Pour simplifier, vous voudrez généralement encapsuler le code d’écriture de texte dans une fonction :

void writeFile(const char *path, const char *text) {
  File file = SD.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  file.print(text);
  file.close();
}

Notez aussi que vous pouvez écrire des données binaires au lieu de texte dans un fichier :

File file = SD.open("/data.bin", FILE_WRITE);
uint8_t buffer[4] = { 0xDE, 0xAD, 0xBE, 0xEF };

size_t bytesWritten = file.write(buffer, sizeof(buffer));

if (bytesWritten != sizeof(buffer)) {
  Serial.println("Write error!");
} 

file.close();

Un cas d’usage courant est l’écriture de données de capteurs. Vous pouvez créer une struct avec vos données de capteur puis écrire ou ajouter cette struct comme bloc de données dans un fichier binaire :

struct SensorData {
  uint32_t timestamp;
  int16_t temperature;
  int16_t humidity;
};

SensorData data = { millis(), 234, 81 };
File file = SD.open("/log.bin", FILE_WRITE);
file.write((uint8_t *)&data, sizeof(data));
file.close();

Une fois écrit, vous pouvez lire les données binaires du capteur comme suit :

SensorData data;

File file = SD.open("/log.bin");
file.read((uint8_t *)&data, sizeof(data));
file.close();

Serial.printf("timestamp:    %d\n", data.timestamp);
Serial.printf("temperature:  %d\n", data.temperature);
Serial.printf("humidity:     %d\n", data.humidity);

Si vous avez ajouté plusieurs blocs de données de capteur dans un fichier et souhaitez lire un bloc spécifique, vous pouvez utiliser la fonction seek() :

SensorData data;

int n = 4; // Read 5ths sample (0...4)
File file = SD.open("/log.bin");
file.seek(n * sizeof(data));
file.read((uint8_t *)&data, sizeof(data));
file.close();

Si vous avez besoin de connaître la position actuelle du pointeur de fichier lors de l’écriture, utilisez la fonction position().

Ajouter des données à un fichier

Si vous souhaitez ajouter des données à un fichier au lieu d’écraser son contenu, utilisez le drapeau FILE_APPEND :

File file = SD.open("/readme.txt", FILE_APPEND);
file.print("This is more to read");
file.close();

Voici encore une fonction pratique avec gestion d’erreur pour ajouter du texte à un fichier :

void appendFile(const char* path, const char* text) {
  File file = SD.open(path, FILE_APPEND);
  if (!file) {
    Serial.println("Failed to open file for appending");
    return;
  }
  if (!file.print(text)) {
    Serial.println("Append failed");
  }
  file.close();
}

Lire le contenu d’un fichier

Vous pouvez lire le contenu d’un fichier en utilisant la méthode read() :

File file = SD.open("/readme.txt");
while (file.available()) {
  Serial.write(file.read());
}
file.close();

La méthode read() retourne un entier et vous devez l’appeler plusieurs fois pour lire le fichier complet. Typiquement, vous faites cela dans une boucle while et utilisez la fonction available() pour savoir quand arrêter. Mais vous pouvez aussi utiliser une boucle for pour lire les premiers n caractères d’un fichier, par exemple.

Voici une fonction pratique qui affiche le contenu d’un fichier dans le moniteur série :

void printFile(const char* path) {
  File file = SD.open(path);
  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  while (file.available()) {
    Serial.write(file.read());
  }
  file.close();
}

Vider le fichier (flush)

Si vous gardez un fichier ouvert longtemps, par exemple pour ajouter périodiquement des données de capteur à un fichier journal, assurez-vous de vider fréquemment le tampon du fichier. En cas de coupure de courant, cela réduit le risque de perte ou de corruption des données.

File file = SD.open("/log.txt", FILE_WRITE);
for(int i=0; i<10; i++) {
  file.print("temp = %d", i);
  file.flush();
  delay(1000);
}
file.close();

Supprimer un fichier

Pour supprimer un fichier, appelez la fonction remove() avec le chemin du fichier. Vous pouvez aussi vérifier au préalable si le fichier existe :

if (SD.exists("/log.txt")) {
  SD.remove("/log.txt");
}

Renommer un fichier

Renommer un fichier est simple. Il suffit d’appeler la fonction rename() :

if (!SD.rename("/logs/old_log.txt", "/logs/new_log.txt")) { 
  Serial.println("Could not rename file");
}

Et voilà. Vous connaissez maintenant toutes les fonctions nécessaires pour lire et écrire des données sur une carte SD.

Conclusion

Dans ce guide, vous avez appris à connecter un module Micro SD à l’ESP32, formater la carte et initialiser la communication. Vous avez aussi exploré comment lister les répertoires, créer et supprimer des dossiers, et gérer les fichiers en les lisant, écrivant, ajoutant, renommant et supprimant. Pour plus d’applications, voyez aussi le dossier examples de la bibliothèque SD.

N’oubliez pas de fermer et de vider vos fichiers pour éviter la corruption des données. C’est particulièrement important pour les enregistreurs de données, qui peuvent garder un fichier ouvert longtemps et sont sensibles aux coupures de courant.

Si vous avez des questions, n’hésitez pas à les poser dans la section commentaires.

Bon bricolage !