Gérer la mémoire sur Arduino, c’est composer avec une ressource rare : un microcontrôleur a très peu de mémoire vive (2 Ko de SRAM sur une Uno). Comprendre quelle mémoire stocke quoi — la Flash le programme, la SRAM les variables, l’EEPROM les données persistantes — et savoir déporter les constantes vers la Flash (macro F(), PROGMEM) évite les plantages silencieux par saturation de RAM, l’un des bugs les plus déroutants du débutant.
À quoi ça sert ?
Sur un PC, la mémoire semble infinie. Sur un microcontrôleur, elle est comptée. Un sketch qui multiplie les variables, accumule des chaînes de caractères ou embarque de gros tableaux peut épuiser la SRAM, et le symptôme est traître : pas de message d’erreur, juste un programme qui redémarre tout seul, se fige ou se comporte n’importe comment. Savoir où va la mémoire et comment l’économiser est donc une compétence de survie dès qu’un projet grossit. L’enjeu se pose en preuve de concept, quand le code dépasse le sketch d’exemple et commence à manquer de place.
Procédure pas à pas
Quatre étapes : distinguer les trois mémoires, comprendre où vivent les variables, économiser la RAM, et la mesurer.
1. Distinguer les trois mémoires
Un AVR (ATmega328P de l’Uno) sépare trois espaces aux rôles distincts :
- Flash (32 Ko) — la mémoire de programme : elle contient le code téléversé. Vaste, mais en lecture seule à l’exécution. On peut y ranger des constantes.
- SRAM (2 Ko) — la mémoire vive : elle contient les variables manipulées pendant l’exécution. Petite et précieuse. C’est elle qui sature.
- EEPROM (1 Ko) — la mémoire persistante : elle conserve quelques données après extinction (réglages, calibration), via son tuto dédié.
La règle de fond : la SRAM est la ressource critique, et tout ce qui peut en sortir doit en sortir.
Ces chiffres (et donc la pression sur la RAM) sont propres à l’AVR. Une Uno R4 (32 Ko de SRAM) ou un ESP32 (~520 Ko) desserrent énormément la contrainte : F() et PROGMEM y restent valides mais quasi sans gain, et la chasse aux octets n’a plus le même sens. Les bonnes habitudes (mesurer, éviter de fragmenter le tas) restent utiles partout (voir ESP32).
2. Comprendre où vivent les variables en SRAM
La SRAM se partage en trois zones :
- les variables globales et statiques, placées en bas, occupent une part fixe dès le démarrage ;
- la pile (stack), qui grandit vers le bas, stocke les variables locales et les appels de fonctions ;
- le tas (heap), qui grandit vers le haut, sert à l’allocation dynamique (les chaînes
String, lenew).
Pile et tas grandissent l’un vers l’autre dans le même petit espace : s’ils se rejoignent, c’est le plantage. Les détails du langage (pointeurs, allocation) relèvent des bases du C++. Ici, l’essentiel est de savoir que chaque variable a un coût et que le tas est le plus risqué.
3. Économiser la RAM : F() et PROGMEM
Le gaspillage le plus courant : les chaînes de caractères littérales. Par défaut, Serial.println("texte") copie le texte en SRAM au démarrage. La macro F() le laisse en Flash :
Serial.println(F("Ce texte reste en Flash, il ne mange pas la SRAM"));Pour des données constantes volumineuses (tables de correspondance, polices, sons), PROGMEM les range en Flash, lues à la demande. Et côté texte dynamique, préférer les tableaux de caractères de taille fixe (char[]) aux String : les String allouent sur le tas et le fragmentent (voir pièges).
4. Mesurer la RAM libre
On ne devine pas la saturation, on la mesure. Une fonction freeMemory() (ou la bibliothèque MemoryFree) renvoie l’espace restant entre la pile et le tas. L’imprimer permet de surveiller la marge et de repérer une fuite.
// (freeMemory() fourni par la bibliothèque MemoryFree)
Serial.print(F("RAM libre : "));
Serial.println(freeMemory());Le moniteur affiche alors la marge restante, en octets. On relève trois fois — au démarrage, après les premières allocations, puis en régime établi :
RAM libre : XXXX
RAM libre : XXXX
RAM libre : XXXX
Ce qui compte n’est pas le chiffre mais sa tendance : une marge qui se stabilise est saine, une marge qui décroît tour après tour signale une fuite. Les valeurs sont à relever sur votre montage. Elles dépendent du sketch et des bibliothèques chargées.
Exemple — Diagnostiquer un plantage par saturation
Un sketch affiche des messages et concatène des String. Au bout d’un moment, il redémarre sans raison apparente. Le coupable : la SRAM épuisée par les chaînes.
// AVANT — gaspille et fragmente la SRAM
void afficher(int n) {
String message = "Mesure numero "; // alloue sur le tas
message += n; // réalloue, fragmente
message += " : OK"; // encore
Serial.println(message);
}Réécrit pour garder les textes en Flash et éviter le tas :
// APRES — textes en Flash, pas d'allocation dynamique
void afficher(int n) {
Serial.print(F("Mesure numero ")); // en Flash
Serial.print(n);
Serial.println(F(" : OK")); // en Flash
}La seconde version ne touche pas au tas : aucune fragmentation, aucune copie de texte en SRAM. Sur un sketch qui affiche souvent, l’écart de RAM libre est spectaculaire, et le redémarrage mystérieux disparaît. La démarche générale : mesurer la RAM libre, repérer les String et les longs textes, les remplacer par F() et des char[].
Pièges
La classe String qui fragmente le tas. Concaténer des String alloue et réalloue sans cesse, laissant des « trous » dans le tas (fragmentation). Avec si peu de SRAM, c’est la cause n°1 de plantages erratiques. Préférer les char[] de taille fixe et les fonctions de chaîne C, ou réserver les String à un usage très limité.
Oublier F() sur les textes. Chaque chaîne littérale sans F() mange de la SRAM inutilement. Sur un programme bavard, des centaines d’octets partent en fumée pour rien.
De gros tableaux en variables globales. Un tableau de quelques centaines d’entiers en SRAM, et la marge fond. S’il est constant, le ranger en PROGMEM (Flash) ; sinon, repenser le besoin.
La récursion ou les grosses variables locales. La pile est minuscule : une fonction récursive profonde ou un gros tableau local peut la faire mordre sur le tas. Éviter la récursion non bornée sur AVR.
Variable de temps en int au lieu d’unsigned long. Au-delà de la mémoire, un mauvais type provoque des débordements (voir temporisation) : le bon dimensionnement des types fait aussi partie de la gestion mémoire.
Confondre Flash pleine et SRAM pleine. Le compilateur annonce deux chiffres : l’espace programme (Flash) et la mémoire dynamique (SRAM). Un programme peut tenir en Flash mais manquer de SRAM à l’exécution. C’est ce second chiffre qu’il faut surveiller.
Cas particulier — Lire le rapport de compilation
Après chaque téléversement, l’IDE affiche deux lignes : combien de Flash le programme occupe, et combien de SRAM les variables globales réservent (avec la RAM restante pour la pile et le tas). Ce rapport est le premier réflexe de diagnostic : si la SRAM annoncée frôle déjà la limite avant même l’allocation dynamique, le plantage est quasi assuré dès que le programme tourne. Le surveiller à chaque build évite de découvrir le problème trop tard.
Raccrochage projet
- Phase de preuve de concept — dès que le code dépasse l’exemple (plusieurs capteurs, affichage, journalisation), surveiller la RAM et adopter
F()/PROGMEMavant de heurter le mur de la SRAM. - Phase d’intégration et tests — l’intégration de plusieurs fonctions cumule leurs besoins mémoire. Un budget RAM tenu fonction par fonction évite les plantages qui n’apparaissent qu’une fois tout assemblé.
Anticiper la mémoire évite la situation la plus pénible en embarqué : un programme qui marchait, qui plante après l’ajout d’une fonctionnalité, sans message, et qu’on traque pendant des heures faute d’avoir regardé la RAM libre.
Voir aussi
- Mémoire — la notion : les types de mémoire d’un microcontrôleur
- Stockage EEPROM — conserver des données après extinction
- Le langage C++ — pointeurs, types et allocation, à la racine de la gestion mémoire (transverse)
- Arduino — hub des tutoriels Arduino
- Processeur — le cœur qui accède à ces mémoires
- ESP32 — beaucoup plus de SRAM (~520 Ko) : la contrainte mémoire de l’AVR s’y desserre largement