L’Arduino-core pour ESP32 est la couche logicielle qui apporte l’API Arduino (setup(), loop(), digitalWrite…) sur les puces ESP32. C’est le chemin recommandé pour débuter : on réutilise les mêmes réflexes que sur un Arduino, mais avec la puissance, la mémoire et les radios de l’ESP32. Sous le capot, cette couche repose sur ESP-IDF (l’environnement natif d’Espressif) et son système temps réel FreeRTOS, ce qui change discrètement quelques règles du jeu. La façon de structurer un firmware, quel que soit le chemin, relève de firmware.
À quoi ça sert ?
L’Arduino-core remplit un rôle de pont :
Réutiliser ce qu’on sait. Tout le vocabulaire Arduino (pinMode, analogRead, Serial, bibliothèques Wire/SPI…) fonctionne tel quel. La migration d’un projet Arduino vers ESP32 est souvent immédiate.
Accéder aux capacités ESP32 simplement. Wi-Fi, BLE, deep sleep, PWM par LEDC : autant de fonctions exposées via des API simples, sans plonger dans l’environnement natif.
Garder une porte vers le natif. Depuis un sketch Arduino, on peut appeler directement des fonctions ESP-IDF (esp_*) quand on a besoin de finesse — sans tout réécrire.
C’est le bon point d’entrée. Le passage à l’environnement natif complet (ESP-IDF) ne se justifie que sur des besoins précis.
Le même code qu’Arduino, presque
Un sketch ESP32 a la forme d’un sketch Arduino :
const int LED = 16;void setup() { Serial.begin(115200); pinMode(LED, OUTPUT);}void loop() { digitalWrite(LED, !digitalRead(LED)); delay(500);}
Câblage : la LED sur GPIO16 avec sa résistance de 220 Ω (voir le montage de la prise en main).
setup() une fois, loop() en boucle : le modèle est identique. Les différences sont des détails de plateforme, pas de structure : un débit série usuel de 115200 (celui du journal de démarrage de la puce, quand les exemples AVR sont à 9600), analogRead sur 12 bits, PWM par ledcAttach au lieu d’analogWrite, broches en 3,3 V (tous traités dans configurer les GPIO et le moniteur série).
Ce qui change sous le capot
L’Arduino-core ESP32 n’est pas du « bare metal » comme l’Arduino AVR : il tourne au-dessus de FreeRTOS. Concrètement :
loop() est une tâche FreeRTOS. Le cœur crée une tâche (loopTask) qui appelle setup() puis répète loop(). Le code partage donc le processeur avec les tâches système (pile Wi-Fi/BLE, etc.).
Deux cœurs. L’ESP32 d’origine a deux cœurs, et loop() s’exécute par défaut sur l’un d’eux. On peut créer ses propres tâches et les répartir (voir FreeRTOS). Attention aux variantes : les C3, C6 et H2 sont mono-cœur (RISC-V), et xPortGetCoreID() y répond toujours 0.
Beaucoup plus de mémoire. Des centaines de kilo-octets de RAM (contre quelques-uns sur un Uno) : les String, les buffers, les bibliothèques lourdes passent plus facilement.
L’API native est accessible.esp_*, les fonctions FreeRTOS (xTaskCreate, vTaskDelay) sont utilisables directement dans un sketch.
Tip
loop() doit rendre la main. Comme loop() est une tâche partageant le CPU, une boucle qui ne « souffle » jamais (calcul intensif sans delay ni vTaskDelay) peut affamer les tâches système et déclencher le task watchdog. Un delay() (qui, sur ESP32, cède le processeur) ou une architecture non bloquante évitent le souci.
Exemple — Un sketch qui mêle Arduino et ESP-IDF
Pour rendre tangible que l’Arduino-core est posé sur l’environnement natif, ce sketch utilise l’API Arduino et deux fonctions natives : le cœur sur lequel tourne loop(), et la mémoire libre.
void setup() { Serial.begin(115200); delay(200);}void loop() { // API native ESP-IDF / FreeRTOS, appelées depuis un sketch Arduino Serial.print("loop() tourne sur le coeur "); Serial.println(xPortGetCoreID()); // 0 ou 1 (toujours 0 sur les puces mono-coeur) Serial.print("Memoire libre : "); Serial.print(esp_get_free_heap_size()); // octets Serial.println(" octets"); Serial.print("... la meme valeur, vue par la facade Arduino : "); Serial.print(ESP.getFreeHeap()); // equivalent Arduino Serial.println(" octets"); delay(2000);}
Au moniteur série, on lit le numéro de cœur et la mémoire libre, deux informations qui n’existent pas sur un Arduino AVR, obtenues sans quitter le confort du sketch. Les deux lignes de mémoire affichent la même valeur par deux chemins : esp_get_free_heap_size() est la fonction native, ESP.getFreeHeap() la façade Arduino posée dessus. C’est l’illustration concrète du pont : on programme « en Arduino » tout en ayant l’ESP-IDF sous la main.
Au moniteur, un bloc de trois lignes toutes les 2 secondes :
loop() tourne sur le coeur 1
Memoire libre : XXXXXX octets
... la meme valeur, vue par la facade Arduino : XXXXXX octets
Les deux chiffres de mémoire sont identiques. C’est le point de la démonstration. Leur valeur, elle, dépend de la carte et des bibliothèques chargées : à relever sur votre montage.
Pièges
Supposer les réflexes AVR. Manipuler des registres AVR, compter sur des timings au cycle près, ou utiliser <avr/...> ne fonctionne pas : l’architecture est différente (Xtensa ou RISC-V). Passer par les API du cœur.
Boucle qui ne rend jamais la main. Un loop() (ou une tâche) en calcul permanent sans delay/vTaskDelay déclenche le task watchdog (Task watchdog got triggered). Céder le processeur régulièrement.
Bibliothèque Arduino incompatible ESP32. Toutes les bibliothèques Arduino ne supportent pas l’ESP32 (certaines tapent dans des registres AVR). Vérifier la compatibilité ESP32 avant de dépendre d’une bibliothèque.
Croire au « bare metal ». L’Arduino-core ESP32 partage le CPU avec des tâches système (Wi-Fi/BLE). Le déterminisme temporel fin n’est pas garanti comme sur un AVR nu. Pour des échéances strictes, structurer en tâches (FreeRTOS) ou passer en natif.
Exercices
Exercice 1 — Sur quel cœur ?
Faites afficher, une seule fois au démarrage, le numéro de cœur sur lequel s’exécute setup(). Est-ce le même que loop() ?
Corrigé
void setup() { Serial.begin(115200); delay(200); Serial.print("setup() sur le coeur "); Serial.println(xPortGetCoreID());}void loop() {}
setup() et loop() s’exécutent dans la même tâche (loopTask), donc sur le même cœur (le cœur 1 par défaut sur l’ESP32 d’origine). Pour faire travailler l’autre cœur, il faut créer une tâche et l’y épingler (voir FreeRTOS).
Exercice 2 — Mémoire avant/après
Affichez la mémoire libre, puis allouez une grande String (par exemple 5000 caractères) et réaffichez-la. Que constate-t-on ?
La mémoire libre baisse d’environ la taille allouée (un peu plus, avec le surcoût de gestion). Sur ESP32, ces milliers d’octets passent sans souci. Sur un Uno (2 ko de RAM), la même allocation planterait. C’est l’un des apports concrets de la plateforme.
Cas particulier — PlatformIO et Arduino dans ESP-IDF
Deux configurations dépassent l’IDE Arduino :
PlatformIO (extension VS Code) gère l’Arduino-core et l’ESP-IDF dans un même projet, avec gestion fine des bibliothèques et du versionnage, pratique dès que le projet grossit.
Arduino comme composant d’ESP-IDF : on peut utiliser les bibliothèques Arduino à l’intérieur d’un projet ESP-IDF natif. On garde le confort Arduino pour certaines parties tout en bénéficiant de l’environnement natif — un pont dans l’autre sens.
Raccrochage projet
Étape 4 de la phase de preuve de concept. Choisir l’Arduino-core comme environnement de la PoC logicielle est le défaut raisonnable : on avance vite, on garde la porte du natif ouverte. Le réserver permet de ne basculer en ESP-IDF que si un verrou précis l’exige.
Réutilisation d’un prototype Arduino. Un montage validé sur Arduino se reporte souvent tel quel sur ESP32 via le core, en gagnant connectivité et mémoire.
Comprendre que l’Arduino-core repose sur FreeRTOS (donc que loop() n’est pas seul au monde) évite les pièges de watchdog et prépare le passage au multitâche quand le projet le demande.
Aller plus loin
FreeRTOS — créer et coordonner plusieurs tâches, exploiter les deux cœurs.
Découvrir ESP-IDF — quand et pourquoi passer à l’environnement natif.
Firmware — structurer le code embarqué, du super-loop au RTOS (transverse).