Dans ce tutoriel, j’explique comme déboguer un programme sur un microcontrôleur ESP32-C3. Le principe est simple, le débogueur GDB (GNU Debugger) communique avec le microcontrôleur ESP32 en utilisant les services d’OpenOCD (Open On-Chip Debugger). OpenOCD est un serveur qui permet à GDB de s’interfacer à l’ESP32-C3 au travers de son interface JTAG.
Installation de ESP-IDF
ESP-IDF (IoT Development Framework) est l’environnement de développement d’Espressif pour l’ESP32-C3.
Vous devez suivre la procédure d’installation de ESP-IDF pour le microcontrôleur ESP32 détaillée sur cette page : https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/get-started/linux-macos-setup.html
Si l’installation se déroule normalement, vous êtes en mesure de compiler et téléverser le programme d’exemple “hello world” sur l’ESP32-C3.
. $HOME/esp/esp-idf/export.sh
cd ~/esp
cp -r $IDF_PATH/examples/get-started/hello_world .
cd ~/esp/hello_world
idf.py set-target esp32c3
idf.py build
idf.py -p /dev/ttyACM0 flash
/dev/ttyACMO est le port USB sur lequel est connecté mon ESP32C3, sous Linux.
Vous pouvez ensuite suivre le bon déroulement de l’exécution du programme en ouvrant un moniteur série :
idf.py -p /dev/ttyACM0 monitor
Vous verrez alors s’afficher les traces du programme hello_world :
Hello world!
This is esp32c3 chip with 1 CPU core(s), WiFi/BLE, silicon revision v0.4, 2MB external flash
Minimum free heap size: 330376 bytes
Restarting in 10 seconds...
Restarting in 9 seconds...
Restarting in 8 seconds...
Installation de OpenOCD
Je recommande d’installer OpenOCD en compilant ses sources.
Suivez la procédure d’installation : https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-guides/jtag-debugging/building-openocd-linux.html
Configuration de l’interface JTAG intégrée ESP32-C3
Un des intérêts du microcontrôleur ESP32-C3 est qu’il intègre un contrôleur USB Serial/JTAG. Il n’est donc plus nécessaire d’utiliser un adaptateur externe pour le déboguer via l’interface JTAG. Ce contrôleur USB Serial/JTAG permet l’utilisation simultanée des opérations série (moniteur série pour afficher les traces par exemple) et le débogage JTAG en utilisant un seul port USB.
Il faut suivre la procédure détaillée sur la page : https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/configure-builtin-jtag.html
Sur Linux il suffit d’installer les règles udev en entrant les commandes suivantes dans un terminal :
curl -fsSL https://raw.githubusercontent.com/espressif/openocd-esp32/master/contrib/60-openocd.rules | sudo tee /etc/udev/rules.d/60-openocd.rules
sudo service udev restart
Executer OpenOCD
Veuillez lire attentivement le chapitre "Éviter l'erreur Failed to get flash maps", sinon OpenOCD générera une erreur lors du démarrage de GDB.
Il faut lancer le serveur OpenOCD en lui indiquant que la cible est un ESP32-C3.
cd ~/esp/openocd-esp32
openocd -c "set ESP_FLASH_SIZE 0" -f board/esp32c3-builtin.cfg
Si tout se déroule bien, OpenOCD doit être en écoute de connection gdb sur le port 3333
tutoduino@my-computer:~/esp/openocd-esp32$ openocd -f board/esp32c3-builtin.cfg -c "set ESP_FLASH_SIZE 0"
Open On-Chip Debugger v0.12.0-esp32-20230419-191-g4da79b3e (2023-08-11-13:46)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : only one transport option; autoselecting 'jtag'
Info : esp_usb_jtag: VID set to 0x303a and PID to 0x1001
Info : esp_usb_jtag: capabilities descriptor set to 0x2000
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : esp_usb_jtag: serial (D4:F9:8D:03:EF:80)
Info : esp_usb_jtag: Device found. Base speed 40000KHz, div range 1 to 255
Info : clock speed 40000 kHz
Info : JTAG tap: esp32c3.cpu tap/device found: 0x00005c25 (mfg: 0x612 (Espressif Systems), part: 0x0005, ver: 0x0)
Info : [esp32c3] datacount=2 progbufsize=16
Info : [esp32c3] Examined RISC-V core; found 1 harts
Info : [esp32c3] XLEN=32, misa=0x40101104
Info : starting gdb server for esp32c3 on 3333
Info : Listening on port 3333 for gdb connections
Déboguer un programme avec GDB
Nous allons compiler, flasher et déboguer un programme qui fait clignoter une LED connectée sur la PIN D10 de l’ESP32C3.
Voici le programme blink_led.c que nous créons dans le répertoire ~/esp/blink_led
// Clignotement d'une LED sur la PIN D10 de l'ESP32C3
// https://tutoduino.fr/
// Copyleft 2023
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define BLINK_GPIO GPIO_NUM_10
static uint8_t s_led_state = 0;
static void configure_led(void)
{
gpio_reset_pin(BLINK_GPIO);
/* Set the GPIO as a push/pull output */
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
}
static void blink_led(void)
{
/* Toggle state of the LED */
s_led_state = !s_led_state;
/* Set the GPIO level according to the state (LOW or HIGH)*/
gpio_set_level(BLINK_GPIO, s_led_state);
}
void app_main(void)
{
printf("Hello world!\n");
configure_led();
while (1) {
blink_led();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
Pour compiler et flasher ce programme, copiez le fichier sdkconfig et CMakeLists.txt depuis le répertoire d’exemple hello_world précédemment utilisé :
cp ~/esp/hello_world/CMakeLists.txt ~/esp/blink_led/
cp ~/esp/hello_world/sdkconfig.txt ~/esp/blink_led/
Puis modifier CMakeLists.txt pour y modifier le nom du projet :
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(blink_led)
Vous pouvez ensuite compiler et flasher le programme sur l’ESP32C3 avec la commande suivante :
cd ~/esp/blink_test
idf.py set-target esp32c3
idf.py build
idf.py -p /dev/ttyACM0 flash
Il faut ensuite créer un fichier de configuration minimal pour gdb nommé gdbinit dans le répertoire ~/esp/blink_led avec le contenu suivant :
target remote :3333
set remote hardware-watchpoint-limit 2
mon reset halt
maintenance flush register-cache
thb app_main
c
Si vous utilisez un nouveau terminal, vous devrez peut-être exécuter le shell d’exportation ESP-IDF avant d’exécuter GDB :
. $HOME/esp/esp-idf/export.sh
Et ensuite il suffit de lancer gdb avec le nom du fichier exécutable au format elf :
riscv32-esp-elf-gdb -x gdbinit build/blink_led.elf
GDB se lance et le programme s’arrête dans la fonction app_main(), nous avons en effet configuré ce point d’arrêt dans le fichier de configuration de gdb avec la commande thb app_main. La commande thb (temporary hardware breakpoint) positionne un point d’arrêt matériel temporaire qui sera supprimé automatiquement une fois atteint.
tutoduino@my-computer:~/esp/blink_led$ riscv32-esp-elf-gdb -x gdbinit build/blink_led.elf
GNU gdb (esp-gdb) 12.1_20221002
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=riscv32-esp-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from build/blink_led.elf...
warning: multi-threaded target stopped without sending a thread-id, using first non-exited thread
[Switching to Thread 1070137376]
app_main () at /home/steph/esp/blink_led/main/blink_led.c:31
31 printf("Hello world!\n");
JTAG tap: esp32c3.cpu tap/device found: 0x00005c25 (mfg: 0x612 (Espressif Systems), part: 0x0005, ver: 0x0)
Reset cause (3) - (Software core reset)
Hardware assisted breakpoint 1 at 0x420073d4: file /home/steph/esp/blink_led/main/blink_led.c, line 31.
[Switching to Thread 1070135492]
Thread 2 "main" hit Temporary breakpoint 1, app_main () at /home/steph/esp/blink_led/main/blink_led.c:31
31 printf("Hello world!\n");
(gdb)
Nous pouvons maintenant exécuter le programme pas à pas et inspecter les variables.
Positionnons un point d’arrêt matériel temporaire dans la fonction blink_led(). Nous continuons ensuite l’exécution du programme et affichons la valeur de la variable s_led_state lorsqu’il s’arrête sur le point d’arrêt :
(gdb) thb blink_led
Hardware assisted breakpoint 7 at 0x420073b4: file /home/tutoduino/esp/blink_led/main/blink_led.c, line 23.
(gdb) c
Continuing.
[Switching to Thread 1070135492]
Thread 3 "main" hit Temporary breakpoint 7, blink_led () at /home/tutoduino/esp/blink_led/main/blink_led.c:23
23 s_led_state = !s_led_state;
(gdb) l
18 gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
19 }
20
21 static void blink_led(void)
22 {
23 s_led_state = !s_led_state;
24 /* Set the GPIO level according to the state (LOW or HIGH)*/
25 gpio_set_level(BLINK_GPIO, s_led_state);
26 }
27
(gdb) print s_led_state
$2 = 1 '\001'
(gdb)
Éviter l’erreur “Failed to get flash maps”
Vous remarquerez qu’OpenOCD génère les erreurs suivantes une fois que vous démarrez GDB :
Info : [esp32c3] Found 8 triggers
Info : [esp32c3] Halt cause (3) - (Hardware Breakpoint/Watchpoint or EBREAK)
Info : accepting 'gdb' connection on tcp/3333
Info : [esp32c3] Halt cause (3) - (Hardware Breakpoint/Watchpoint or EBREAK)
Memory protection is enabled. Reset target to disable it...
Info : JTAG tap: esp32c3.cpu tap/device found: 0x00005c25 (mfg: 0x612 (Espressif Systems), part: 0x0005, ver: 0x0)
Info : Reset cause (3) - (Software core reset)
Warn : No symbols for FreeRTOS!
<strong>Error: Failed to get flash maps (-1)!</strong>
Warn : Failed to get flash mappings (-4)!
Error: Failed to get flash size!
Error: Failed to get flash size!
Error: Failed to probe flash, size 0 KB
Error: auto_probe failed
Error: Connect failed. Consider setting up a gdb-attach event for the target to prepare target for GDB connect, or use 'gdb_memory_map disable'.
Error: attempted 'gdb' connection rejected
Pour éviter ce problème, plusieurs options sont possibles mais je recommande fortement la troisième (mettre la broche D8 sur HIGH au démarrage) :
1/ Utiliser la commande “gdb_memory_map disable”
Vous pouvez démarrer OpenOCD en ajoutant cette commande, mais j’ai rencontré de graves problèmes lors de la définition du point d’arrêt et de l’étape par étape dans GDB à l’aide de cette option.
openocd -f board/esp32c3-builtin.cfg -c "gdb_memory_map disable"
2/ Utiliser la commande “set ESP_FLASH_SIZE 0”
Vous pouvez démarrer OpenOCD en ajoutant cette commande, mais je n’ai pas pu faire fonctionner cette commande lors du débogage avec PlatformIO.
openocd -f board/esp32c3-builtin.cfg -c "set ESP_FLASH_SIZE 0"
2/ Mettre la broche D8 sur HIGH pendant le démarrage
Comme expliqué au chapitre 7 du manuel de référence technique ESP32-C3, l’ESP32-C3 a trois broches de “strapping” GPIO2 GPIO8 et GPIO9. Ces broches sont utilisées pour contrôler le mode de démarrage du microcontrôleur lors de sa mise sous tension ou de sa réinitialisation. La documentation n’est pas claire à ce sujet, mais j’ai observé que le fait de mettre la broche GPIO8 sur HIGH (3,3 V) lors du démarrage de l’ESP32C3 résout parfaitement ce problème !
Déboguer avec Visual Studio Code et PlatformIO
Le débogage avec Visual Studio Code et PlatformIO est assez simple. Vous n’avez qu’à installer l’extension PlatformIO sur Visual Studio Code et tout est prêt !
Installer Visual Studio Code et PlatformIO
La procédure d’installation de Visual Studio Code et de PlatformIO est décrite dans mon tutoriel Programmer un Raspberry Pi Pico avec Visual Studio Code et PlatformIO.
Pour l’instant le composant GDB de la toolchain fourni par Espressif nécessite l’installation de libpython2.7.so.1.0, pour résoudre ce problème il suffit d’installer le package python2.7-dev.
sudo apt install python2.7-dev
Fichier de configuration de PlatformIO
Il faut modifier le fichier de configuration platformio.ini qui est généré par défaut.
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
build_type = debug
framework = arduino
debug_tool = esp-builtin
debug_init_break = break setup
debug_server =
$PLATFORMIO_CORE_DIR/packages/tool-openocd-esp32/bin/openocd
-f
$PLATFORMIO_CORE_DIR/packages/tool-openocd-esp32/share/openocd/scripts/board/esp32c3-builtin.cfg
Vous devez compiler le programme et le téléverser sur l’ESP32C3. Réinitialisez votre ESP32C3 une fois le téléchargement terminé. Ensuite, vous pouvez commencer à déboguer le programme en cliquant sur l’icône du débogueur sur le côté gauche et en cliquant sur PIO Debug (without uploading).
Le programme s’exécute jusqu’à l’appel de la fonction setup(), nous avons en effet configuré ce point d’arrêt initial du débogueur dans le fichier de configuration platformio.init avec debug_init_break = break setup.
Vous pouvez ensuite exécuter le programme pas à pas, définir d’autres points d’arrêt, poursuivre l’exécution jusqu’au prochain point d’arrêt, inspecter la mémoire et les variables…
Cette courte vidéo montre comment créer le projet, téléverser le programme sur l’ESP32C3 et commencer le débogage :
Je vous recommande de lire la page des Tips and Quirks concernant le débogage via JTAG sur le site Espressif, et bien entendu l’aide de GDB.
Si vous souhaitez savoir comment utiliser l’adaptateur JTAG externe, ce tutoriel pourrait vous intéresser : https://tutoduino.fr/tutoriels/debug-esp32/debug-esp32-platformio-jtag/
Ce tutoriel est terminé, n’hésitez pas à donner votre avis et à laisser un commentaire, cela m’aidera à l’améliorer.