Avez-vous déjà remarqué les options de l’IDE Arduino permettant d’activer la PSRAM ou de sélectionner un schéma de partitionnement pour un ESP32 ? Savez-vous à quoi elles servent ?
Dans ce tutoriel, consacré à l’ESP32-C5, je vous expliquerai leur signification et comment les utiliser efficacement. Je présenterai d’abord les principaux types de mémoire (FLASH, SRAM, PSRAM et EEPROM), puis les différents schémas de partitionnement.
Le partitionnement est crucial car une configuration bien pensée permet d’exploiter pleinement les fonctionnalités de l’ESP32, comme le stockage de fichiers (par exemple, des pages web pour serveurs embarqués), les mises à jour OTA du firmware et la persistance des paramètres. Une mauvaise configuration peut entraîner des problèmes d’espace disque insuffisant, la perte de fonctionnalités ou une corruption de la mémoire, comme on peut le constater lors de débordements de tampon dans des programmes.
Avant d’explorer les fonctionnalités avancées de gestion de la mémoire de l’ESP32, nous reviendrons sur les bases à l’aide de l’exemple Arduino Uno afin de comprendre où sont stockées les données et comment optimiser l’espace disponible pour chaque type de mémoire.
Comprendre les mémoires d’un microcontrôleur avec l’exemple de l’Arduino Uno
Les microcontrôleurs utilisent différents types de mémoires à des fins différentes :
- EEPROM : mémoire non volatile qui conserve les données même sans alimentation. Ce type de mémoire de petite capacité est optimisée pour des écritures fréquentes (100.000 cycles).
- FLASH : comme la mémoire EEPROM, la mémoire FLASH est non volatile et conserve les données même sans alimentation. Ce type de mémoire a généralement une plus grande capacité, mais permet moins de cycles d’écriture (10.000 cycles).
- SRAM : mémoire volatile ultra-rapide qui s’efface dès coupure alimentation.

L’arduino UNO R3 est basé sur le microcontrôleur ATMEGA 328P, équipé des mémoires suivantes selon la documentation de ce microcontrôleur :
- 32 ko de mémoire FLASH
- 1 ko de mémoire EEPROM
- 2 ko de mémoire SRAM

Prenons par exemple le programme suivant, et expliquons l’utilisation de la mémoire :
// Exemple illustrant l'utilisation des differentes memoires
// d'un Arduino UNO R3
//
// https://tutoduino.fr/
char* pointeur = NULL; // le compilateur reserve 2 octets en SRAM pour stocker ce pointeur
void setup() {
Serial.begin(115200);
pointeur = (char*)malloc(200 * sizeof(char)); // allocation dynamique de 200 octets en SRAM
if (pointeur == NULL) {
Serial.println("Erreur d'allocation mémoire!");
} else {
free(pointeur);
pointeur = NULL;
}
}
void loop() {
delay(1000);
}Une fois compilé, le programme occupe 2264 octets de Flash. Les variables globales utilisent 228 octets de SRAM, laissant 1820 octets de disponibles sur les 2 ko de mémoire SRAM totale.
Sketch uses 2264 bytes (7%) of program storage space. Maximum is 32256 bytes.
Global variables use 228 bytes (11%) of dynamic memory, leaving 1820 bytes for local variables. Maximum is 2048 bytes.Note : Sur les 32 768 octets (32 ko) de Flash, 512 octets sont réservés au bootloader Arduino, laissant 32256 octets maximum pour le programme utilisateur.
Si le programme essaie d’allouer plus de mémoire que disponible en SRAM, l’allocation va échouer. Exemple ici avec une tentative d’allocation de 1900 octets :

Il n’y a pas de schéma de partition configurable dans l’IDE Arduino pour l’Arduino UNO R3. En effet, son microcontrôleur utilise une mémoire flash fixe de 32 Ko, sans système de fichiers ni support OTA natif. Mais commençons par comprendre les différents types de mémoire utilisées par les microcontrôleurs :
Mémoires du XIAO ESP32-C5
Le module XIAO ESP32-C5 est basé sur le micro-contrôleur ESP32-C5 équipé d’une SRAM externe de 8 Mo ainsi que d’une FLASH externe de 8 Mo.
Le micro-contrôleur ESP32-C5 possède la mémoire interne suivante :
- 384 ko de SRAM haute performance (HP), utilisée par le cœur haute performance (HP core), qui fonctionne à une fréquence élevée (240 MHz).
- 16 ko de SRAM basse consommation (LP), associée au cœur basse consommation (LP core), qui fonctionne à une fréquence beaucoup plus basse (40 MHz).
- 320 ko de ROM, mémoire immuable en lecture seule, dédiée aux fonctions de base et au démarrage du système.

L’adresse virtuelle est mappée à l’espace d’adressage physique de la mémoire externe via l’unité de gestion de la mémoire (MMU). Le tableau suivant présente les adresses virtuelles des différentes mémoires de l’ESP32-C5.

Note : Sur le XIAO ESP32-C5, la PSRAM externe et la Flash partagent l’espace d’adresses virtuelles 0x42000000–0x43FFFFFF car le SoC ESP32-C5 utilise un sous-système cache/MMU pour mapper les mémoires externes dans un espace d’adresses virtuelles commun. La Flash et la PSRAM sont mappées dynamiquement dans cette plage via les mappings de pages MMU plutôt que d’occuper des régions d’adresses virtuelles distinctes.
Mémoire SRAM interne et PSRAM externe du XIAO ESP32-C5
Pour revenir au début de ce tutoriel, rappelons que l’allocation dynamique de mémoire s’effectue dans la SRAM. Or, la SRAM interne du microcontrôleur ESP32-C5 est limitée à 384 Ko.
Si votre programme nécessite une allocation dynamique de mémoire plus importante, la PSRAM de 8 Mo du module XIAO ESP32-C5 entre en jeu. La PSRAM est une mémoire externe, connectée via SPI, conçue pour émuler la SRAM mais utilisant la technologie DRAM (Dynamic RAM).
Cependant, la PSRAM n’est pas activée par défaut dans la configuration ESP32 de l’IDE Arduino ; vous devez l’activer explicitement dans les paramètres de l’IDE.

Cet exemple démontre comment l’ESP32-C5 sélectionne automatiquement la SRAM pour les petites allocations (1 octet) et la PSRAM pour les grandes (1 Mo), les grandes allocations échouant lorsque la PSRAM est désactivée.
// Example illustrating the use of SRAM and PSRAM
// on a XIAO ESP32C5
// https://tutoduino.fr/en/partition-esp32-arduino-en/
#include <Arduino.h>
#include "esp_heap_caps.h"
#include "esp_system.h"
/**
* Function to display the memory address of any variable
* @param variableName Name of the variable for display
* @param variable Pointer to the variable to check
*/
void printAddress(const char* variableName, void* variable) {
Serial.print("Memory address of '");
Serial.print(variableName);
Serial.print("' is: 0x");
Serial.println((uintptr_t)variable, HEX);
}
void setup() {
int* one_byte_pointer; // Pointer for small allocation test (1 byte)
int* one_mbyte_pointer; // Pointer for large allocation test (1 MB)
Serial.begin(115200);
while (!Serial)
; // Wait for Serial Monitor to connect
Serial.println("\n=== SRAM vs PSRAM usage on ESP32C5 ===");
// CHECK PSRAM AVAILABILITY AND STATUS
size_t psram_total = ESP.getPsramSize();
size_t psram_free = ESP.getFreePsram();
if (psram_total == 0) {
Serial.println("❌ PSRAM not enabled or not detected");
Serial.println(" Enable PSRAM in Arduino IDE Tools → PSRAM: 'Enabled'");
} else {
Serial.println("✅ PSRAM enabled and detected");
Serial.printf(" PSRAM Total: %d bytes (%.1f MB)\n", psram_total, psram_total / 1024.0 / 1024.0);
Serial.printf(" PSRAM Free: %d bytes (%.1f MB)\n", psram_free, psram_free / 1024.0 / 1024.0);
}
Serial.println("\n--- Testing small allocation (1 byte) ---");
// TRY TO ALLOCATE 1 BYTE - Should use internal SRAM (384KB on ESP32-C5)
one_byte_pointer = (int*)malloc(0x1); // Allocate 1 byte
if (one_byte_pointer != NULL) {
Serial.println("✅ Small allocation (1 byte) succeeded - Uses SRAM");
printAddress("one_byte_pointer", (void*)one_byte_pointer);
psram_free = ESP.getFreePsram();
Serial.printf("\PSRAM Free: %d bytes (%.1f MB)\n", psram_free, psram_free / 1024.0 / 1024.0);
} else {
Serial.println("❌ Memory allocation failed for one_byte_pointer");
}
Serial.println("\n--- Testing large allocation (1 MB) ---");
// TRY TO ALLOCATE 1MB - Should use PSRAM (8MB on XIAO ESP32C5)
one_mbyte_pointer = (int*)malloc(0x100000); // Allocate 1 megabyte (1048576 bytes)
if (one_mbyte_pointer != NULL) {
Serial.println("✅ Large allocation (1 MB) succeeded - Uses PSRAM");
printAddress("one_mbyte_pointer", (void*)one_mbyte_pointer);
psram_free = ESP.getFreePsram();
Serial.printf("\PSRAM Free: %d bytes (%.1f MB)\n", psram_free, psram_free / 1024.0 / 1024.0);
// Free the large allocation to avoid memory leaks
free(one_mbyte_pointer);
one_mbyte_pointer = NULL;
} else {
Serial.println("❌ Memory allocation failed for one_mbyte_pointer");
Serial.println(" This happens if PSRAM is not enabled or allocation exceeds available space");
}
}
void loop() {
// Empty loop - all tests run once in setup()
delay(1000);
}Lorsque la PSRAM est désactivée, l’allocation d’1 octet réussit dans la SRAM interne, mais l’allocation d’1 Mo échoue :
=== SRAM vs PSRAM usage on ESP32C5 ===
❌ PSRAM not enabled or not detected
Enable PSRAM in Arduino IDE Tools → PSRAM: 'Enabled'
--- Testing small allocation (1 byte) ---
✅ Small allocation (1 byte) succeeded - Uses SRAM
Memory address of 'one_byte_pointer' is: 0x4085C834 (internal SRAM memory)
PSRAM Free: 0 bytes (0.0 MB)
--- Testing large allocation (1 MB) ---
❌ Memory allocation failed for one_mbyte_pointer
This happens if PSRAM is not enabled or allocation exceeds available spaceLorsque la PSRAM est activée, l’allocation de 1 octet s’effectue dans la SRAM interne tandis que l’allocation de 1 Mo s’effectue dans la PSRAM externe :
=== SRAM vs PSRAM usage on ESP32C5 ===
✅ PSRAM enabled and detected
PSRAM Total: 8388608 bytes (8.0 MB)
PSRAM Free: 8386296 bytes (8.0 MB)
--- Testing small allocation (1 byte) ---
✅ Small allocation (1 byte) succeeded - Uses SRAM
Memory address of 'one_byte_pointer' is: 0x4085C868 (internal SRAM memory)
PSRAM Free: 8386296 bytes (8.0 MB)
--- Testing large allocation (1 MB) ---
✅ Large allocation (1 MB) succeeded - Uses PSRAM
Memory address of 'one_mbyte_pointer' is: 0x42050908 (external PSRAM memory)
PSRAM Free: 7337704 bytes (7.0 MB)L’allocation de 1 Mo utilise bien de la PSRAM externe, et c’est bien confirmé par sa plage d’adresses (0x42000000-0x43FFFFFF).
Le schéma de partition de la mémoire Flash dans l’IDE Arduino
Le menu Partition Scheme de l’IDE Arduino permet de définir comment doit être utilisée la mémoire FLASH externe des microcontrôleurs ESP32.

Pour bien choisir un schéma de partition sur un ESP32, il est essentiel de comprendre les acronymes et les rôles de chaque section de la mémoire flash :
- SPIFFS (Serial Peripheral Interface Flash File System) est un système de fichiers qui permet de créer, lire, modifier et supprimer des fichiers directement dans la mémoire flash. Ce système de fichiers gère l’usure, la fragmentation et la persistance des données, spécifique à la technologie flash. Ce système ne permet pas d’organiser les fichiers en dossiers (tous les fichiers sont à la racine).
- FATFS est un système de fichiers est une implémentation légère du système de fichiers FAT (File Allocation Table) conçue pour les systèmes embarqués. Permet d’organiser les fichiers en dossiers et sous-dossiers et permet de gérer des fichiers et des partitions bien plus grandes que SPIFFS.
- APP (Application) est la partition dédiée au stockage du firmware (programme compilé) de l’ESP32. La partition APP peut être divisée en plusieurs partitions (ex : app0, app1) pour permettre des mises à jour du firmware à distance (OTA). La partition app0 contient le firmware principal (version actuelle ou précédente) alors que la partition app1 contient une seconde copie du firmware. Pendant qu’une nouvelle version est téléchargée dans app1, app0 continue de fonctionner. Après la mise à jour, l’ESP32 redémarre sur app1.
- OTA (Over-The-Air) est la partition qui stocke les métadonnées pour les mises à jour OTA, comme l’état de la dernière mise à jour ou la partition active (app0 ou app1).
Les schémas de partition sont stockés dans le répertoire d’installation du core ESP32 pour Arduino. Par exemple sous Linux :
~/.arduino15/packages/esp32/hardware/esp32/3.3.6/tools/partitionsVoici un exemple des schémas de partitions disponibles pour la version 3.3.6 du core ESP32.

Ces fichiers au format CSV contiennent la définition des partitions :
- Name : Identifiant de la partition (ex : app0, spiffs).
- Type : Type de partition (app, data).
- SybType : Sous-type (ota_0, ota_1, spiffs, fatfs, etc.).
- Offset : Offset en hexadécimal dans la mémoire flash.
- Size : Taille de la partition en hexadécimal.
- Flags : Options supplémentaires (ex : encrypted).
Voici par exemple le contenu du fichier default_8MB.csv

L’IDE Arduino utilise le fichier de partition (.csv) sélectionné pour générer une table de partition binaire, intégrée au firmware. Cette table est écrite dans la mémoire flash de l’ESP32, à l’adresse 0x8000, lors du téléversement. Le bootloader de l’ESP32 lit cette table lors de son exécution pour savoir où se trouvent les partitions (APP, SPIFFS, OTA, etc.).
Je vous recommande la lecture de la documentation dédiée au tables de partition sur le site Espressif : https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html
Exemple de problème lié aux schémas de partition de la Flash
Vous souhaitez développer un projet de lampe connectée basée sur un ESP32-C5, vous allez exploiter les capacités de ce microcontrôleur pour créer un appareil compatible avec Home Assistant, en utilisant le protocole Matter sur Thread. Ce choix vous permet de concevoir un objet connecté sécurisé, interopérable et facilement intégrable dans un écosystème domotique existant. Je vous invite à lire mon tutoriel Intégrer un appareil Matter (Wi-Fi et Thread) dans Home Assistant.
Pour ce projet, vous partez de l’exemple MatterOnOffLight, fourni par Espressif. Cet exemple est spécialement conçu pour les applications de type “lampe connectée” et intègre la prise en charge du protocole Matter sur Thread, ce qui simplifie grandement l’intégration avec Home Assistant.
Essayons d’utiliser le schéma de partition Minimal pour ce programme :

Ce schéma de partition minimal.csv réserve une partition app0 de type app et de taille 0x140000 (1310720 octets) :
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
spiffs, data, spiffs, 0x150000, 0xA0000,
coredump, data, coredump,0x1F0000, 0x10000,Une erreur apparaît lors de la compilation du programme. En effet, le programme compilé occupe 2193968 octets, ce qui est supérieur à la taille de la partition app configurée par le schéma de partition Minimal sélectionné (1310720 octets) :
Le croquis utilise 2193968 octets (167%) de l'espace de stockage de programmes. Le maximum est de 1310720 octets.Le fichier README de cet exemple indique bien qu’il faut sélectionner le schéma de partition Huge APP (3MB No OTA/1MB SPIFFS).


La partition app0 de type app est en effet bien configurée avec une taille 0x300000 (3145728 octets) dans le schéma de partition huge_app.csv :
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x300000,
spiffs, data, spiffs, 0x310000,0xE0000,
coredump, data, coredump,0x3F0000,0x10000,Une fois compilé, le firmware occupe 2193976 octets de la mémoire flash. Ce qui est bien inférieur au maximum de 3145728 octets (3 Mo) réservé pour notre partition APP.
Le croquis utilise 2193976 octets (69%) de l'espace de stockage de programmes. Le maximum est de 3145728 octets.
Les variables globales utilisent 100740 octets (30%) de mémoire dynamique, ce qui laisse 226940 octets pour les variables locales. Le maximum est de 327680 octets.
