Debugging ESP32 code with OpenOCD
Stop debugging code with printf(3)
. Working with gdb(1)
for ESP32 is integrated in eps-idf
, and is far easier than you think.
If you are using Arduino, instead of esp-idf
, as a framework, look elsewhere. I know nothing about Arduino.
If your machine is Windows, look elsewhere. I know nothing about Windows.
If you have one of the official development boards from espressif, read the official documentation, JTAG Debugging, instead of this article because it is well-written. This article is written for those without.
Summary
To debug your code, you need:
esp-idf
- OpenOCD for ESP32
gdb
for ESP32 (included inesp-idf
toolchain)- A 3.3 V USB JTAG adapter and a device driver for the USB JTAG adapter
esp-idf
Install esp-idf
. Follow instructions in the official documentation, Get Started.
OpenOCD for ESP32
OpenOCD is an On-Chip-Debugger. OpenOCD accepts debug commands from a debugger, and controls the MCU.
OpenOCD for ESP32 is installed during esp-idf
install process. This version is a fork: OpenOCD package from your distribution will not work unless the maintainer of the package apply patches to the source (usually, not).
espressif maintains a fork of OpenOCD at espressif/openocd-esp32.
For FreeBSD users, ports are available at trombik/freebsd-ports-openocd-esp32.
openocd
must be in $PATH
environment variable. If you have another openocd
installed on your machine, the openocd
for ESP32 must come first.
openocd --version
Open On-Chip Debugger -snapshot (2022-01-06-22:27)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
gdb
debugger for ESP32
gdb
is GNU debugger. A gdb
for ESP32 is included in the official toolchain. You need a specific gdb
for your target; when the target is ESP32, you need gdb
for ESP32. If the target is ESP32S2, you need gdb
for ESP32S2.
espressif maintains a fork of binutils-gdb
at espressif/binutils-gdb. If no official toolchain is available for your machine, you can build gdb
from the source.
For FreeBSD users, ports are available at trombik/xtensa-esp32-elf. Install devel/xtensa-esp32-elf.
The file name of gdb
for ESP32 is not gdb
because it is a gdb
for a specific target, and it cannot debug binaries for other targets. The file name is in the form of $TARGET-gdb
, for example, xtensa-esp32-elf-gdb
for ESP32, and xtensa-esp32s2-elf-gdb
for ESP32S2. They must be in your $PATH
environment variable. If you are not sure, run the gdb
in your shell without full path to the file. The result should be like the following instead of command not found
.
> xtensa-esp32-elf-gdb --version
GNU gdb (crosstool-NG esp-2021r2) 9.2.90.20200913-git
Copyright (C) 2020 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.
JTAG Adapter
A JTAG adapter is an interface to the device under test. Some development boards, such as ESP-WROVER-KIT, has a JTAG debugger on board. If your board is one of the official espressif development boards, read the official documentation, (JTAG Debugging) instead.
Here, it is assumed that your development board is one of Chinese clones without JTAG adapter.
Buy a JTAG adapter supported by OpenOCD. The OpenOCD project has a list of supported hardware at Debug Adapter Hardware. Note that some JTAG adapters are designed for specific MCUs. For example, ST-LINK works only with STM32 MCUs. My recommendation is a generic FT232H USB board (usually USD 10 at AliExpress). Another one is FT2232H USB board (USD 12). FT2232H has two channels; you can debug code with JTAG interface, and see the UART output from the MCU at the same time. With FT232H, you need two USB cables; one for FT232H, and another for UART. Both of them support 3.3 V.
The datasheet for FT2232H is available at FTDI website (FT2232H Datasheet).
FT2232H series supports Multi-Protocol Synchronous Serial Engine, or MPSSE
. In short, you can talk multiple protocols, not only JTAG but also SPI and I2C, over USB. This is valuable because you can control sensors or chips from your machine.
In this article, FT232H is interchangeable with FT2232H.
FT2232H is supported by most of Unix variants by default. You do not need to install a driver for FT232H. However, MPSSE
support may vary.
Make sure the device file is accessible. On my FreeBSD machine, the device file of the JTAG adapter is /dev/usb/0.7.0
. By default, generic USB device files are not accessible by average users. dmesg(8)
shows you the device name.
> dmesg | tail
ugen0.7: <FTDI Single RS232-HS> at usbus0
uftdi0 on uhub1
uftdi0: <Single RS232-HS> on usbus0
> ls -al /dev/usb/0.7.0
crw------- 1 root operator 0x19a Jan 7 08:22 /dev/usb/0.7.0
Change the permission of the file so that you have read and write permission on the file.
On FreeBSD, you can see details of USB devices by usbconfig(8)
.
> sudo usbconfig
ugen1.1: <0x8086 XHCI root HUB> at usbus1, cfg=0 md=HOST spd=SUPER (5.0Gbps) pwr=SAVE (0mA)
ugen0.1: <0x8086 XHCI root HUB> at usbus0, cfg=0 md=HOST spd=SUPER (5.0Gbps) pwr=SAVE (0mA)
ugen0.2: <vendor 0x8087 product 0x0a2b> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (100mA)
ugen0.3: <Chicony Electronics Co.,Ltd. Integrated Camera> at usbus0, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=ON (500mA)
ugen0.4: <vendor 0x06cb product 0x009a> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (100mA)
ugen0.5: <Generic USB3.0-CRW> at usbus0, cfg=0 md=HOST spd=SUPER (5.0Gbps) pwr=ON (200mA)
ugen0.6: <Silicon Labs CP2102 USB to UART Bridge Controller> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (100mA)
ugen0.7: <FTDI Single RS232-HS> at usbus0, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=ON (500mA)
> sudo usbconfig -d ugen0.7 dump_device_desc
ugen0.7: <FTDI Single RS232-HS> at usbus0, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=ON (500mA)
bLength = 0x0012
bDescriptorType = 0x0001
bcdUSB = 0x0200
bDeviceClass = 0x0000 <Probed by interface class>
bDeviceSubClass = 0x0000
bDeviceProtocol = 0x0000
bMaxPacketSize0 = 0x0040
idVendor = 0x0403
idProduct = 0x6014
bcdDevice = 0x0900
iManufacturer = 0x0001 <FTDI>
iProduct = 0x0002 <Single RS232-HS>
iSerialNumber = 0x0000 <no string>
bNumConfigurations = 0x0001
Important bits in this information are idVendor
, or vid
, and idProduct
, or pid
. In my case, vid
is 0x0403
, and pid
is 0x6014
. You need them when you configure OpenOCD
later.
Wiring
JTAG uses four wires for communication, and one for common GND.
ESP32 | FT2232H |
---|---|
GPIO13 (MTCK) | AD0 (TCK) |
GPIO12 (MTDI) | AD1 (TDI) |
GPIO15 (MTDO) | AD2 (TDO) |
GPIO14 (MTMS) | AD3 (TMS) |
GND | GND |
See “3.1.2 Pin Descriptions” in the FT2232H datasheet for more details.
Even when your JTAG board is not FT2232H, the rule is same: connect TCK
, TDI
, TDO
, and TMS
to corresponding pins on ESP32.
FT2232H boards usually have 3.3 V output for MCUs, but it is strongly recommended to provide power to the MCU from an external power source.
Unstable power is one of major causes of instabilities, or unexpected behaviours. Always ensure the power rail has stable 3.3 V (use an oscilloscope) . Do not omit decoupling capacitors. Use a better LDO (AMS1117 that often comes with cheap development boards is not very good). USB port is not a good source of power. Use an external power source if in doubt.
Configuring openocd
for ESP32
TBW
Running openocd
> idf.py openocd --openocd-commands '-f esp32-wrover-kit-3.3v.cfg' gdbgui
Executing action: openocd
OpenOCD started as a background task 67085
Executing action: gdbgui
gdbgui started as a background task 67092
Executing action: post_debug
Open On-Chip Debugger -snapshot (2022-01-06-22:27)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Warn : libusb_detach_kernel_driver() failed with LIBUSB_ERROR_OTHER, trying to continue anyway
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
Info : clock speed 20000 kHz
Info : JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : JTAG tap: esp32.cpu1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : esp32.cpu0: Target halted, PC=0x400D63C4, debug_reason=00000001
Info : esp32.cpu1: Target halted, PC=0x4014232E, debug_reason=00000000
Info : starting gdb server for esp32.cpu0 on 3333
Info : Listening on port 3333 for gdb connections
Info : accepting 'gdb' connection on tcp/3333
Warn : No symbols for FreeRTOS!
Info : esp32.cpu0: Target halted, PC=0x40092AEE, debug_reason=00000001
Info : Set GDB target to 'esp32.cpu0'
Info : Flash mapping 0: 0x10020 -> 0x3f400020, 88 KB
Info : Flash mapping 1: 0x30020 -> 0x400d0020, 474 KB
Info : esp32.cpu0: Target halted, PC=0x40092AEE, debug_reason=00000001
Info : Auto-detected flash bank 'esp32.cpu0.flash' size 4096 KB
Info : Using flash bank 'esp32.cpu0.flash' size 4096 KB
Info : esp32.cpu0: Target halted, PC=0x40092AEE, debug_reason=00000001
Info : Flash mapping 0: 0x10020 -> 0x3f400020, 88 KB
Info : Flash mapping 1: 0x30020 -> 0x400d0020, 474 KB
Info : Using flash bank 'esp32.cpu0.irom' size 476 KB
Info : esp32.cpu0: Target halted, PC=0x40092AEE, debug_reason=00000001
Info : Flash mapping 0: 0x10020 -> 0x3f400020, 88 KB
Info : Flash mapping 1: 0x30020 -> 0x400d0020, 474 KB
Info : Using flash bank 'esp32.cpu0.drom' size 92 KB
Info : New GDB Connection: 1, Target esp32.cpu0, state: halted
Warn : Prefer GDB command "target extended-remote 3333" instead of "target remote 3333"
Info : JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : JTAG tap: esp32.cpu1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : esp32.cpu0: Debug controller was reset.
Info : esp32.cpu0: Core was reset.
Info : esp32.cpu0: Target halted, PC=0x500000CF, debug_reason=00000000
Info : esp32.cpu0: Core was reset.
Info : esp32.cpu0: Target halted, PC=0x40000400, debug_reason=00000000
Info : esp32.cpu1: Debug controller was reset.
Info : esp32.cpu1: Core was reset.
Info : esp32.cpu1: Target halted, PC=0x40000400, debug_reason=00000000
Info : esp32.cpu0: Target halted, PC=0x400D63C4, debug_reason=00000001
Info : Set GDB target to 'esp32.cpu0'
Info : esp32.cpu1: Target halted, PC=0x4014232E, debug_reason=00000000
The default URL is http://127.0.0.1:5000/.
If you prefer CLI, run gdbtui
instead of gdbgui
.
idf.py openocd --openocd-commands '-f esp32-wrover-kit-3.3v.cfg' gdbtui
env OPENOCD_SCRIPTS=/usr/local/share/openocd-esp32/scripts idf.py openocd --openocd-commands '-f ../../esp32-wrover-kit-3.3v.cfg' gdbtui