In this blog, you'll learn how to set up a Raspberry Pi as a JTAG debugger for the ESP32. I'll then show you how to use GDB and even Visual Studio Code to debug your ESP32 programs using this setup.

I tested this setup on Raspberry Pi Model 3B+, but I believe it'll work with other hardware versions as well.

The first step would be to set up OpenOCD, a software chip debugger. It implements common debugging protocols, such as JTAG used by the ESP32.

Compiling OpenOCD on the Raspberry Pi

Run the following commands to make sure you have all the necessary tools on your Raspberry Pi:

sudo apt-get update
sudo apt-get install -y git make pkg-config autoconf libtool libusb-1.0-0 libusb-1.0-0-dev

Great! Now, let's get the source code for OpenOCD with ESP32 support and build it:

git clone https://github.com/espressif/openocd-esp32 ~/openocd-esp32
cd ~/openocd-esp32
./bootstrap
./configure --enable-sysfsgpio --enable-bcm2835gpio
make

This is going to take a while, so it's a great opportunity to get familiar with GDB. Or educate yourself about the internals of AVR.

Note the extra flags for the configure command: --enable-sysfsgpio
--enable-bcm2835gpio.
They let OpenOCD use the Raspberry Pi's GPIO for talking JTAG with the ESP32.

Connecting the Raspberry Pi to the ESP32

You'll need 5 wires:

Raspberry Pi GPIO ESP32 Pin Signal Name
GND GND Ground
25 GPIO14 TMS
10 GPIO12 TDI
9 GPIO15 TDO
11 GPIO13 TCK

Or, if you prefer a diagram:

Note that you can change the Raspberry Pi pin assignments by editing ~/openocd-esp32/tcl/interface/raspberrypi2-native.cfg. Specifically, this is the line where the pin numbers are defined (TCK, TMS, TDI, and lastly TDO):

bcm2835gpio_jtag_nums 11 25 10 9

The ESP32 drawing was taken from TD-er, licensed under the MIT license.

Running OpenOCD

You have to options here: either install OpenOCD globally on your system, or run it locally from the build folder.

Installing OpenOCD Globally

Run the following command:

sudo make install

Then start OpenOCD:

openocd -f interface/raspberrypi2-native.cfg -f target/esp32.cfg -c "adapter_khz 1000"

Running OpenOCD from the build folder

To run OpenOCD without installing it on your system:

cd ~/openocd-esp32
sudo OPENOCD_SCRIPTS=$PWD/tcl src/openocd -f interface/raspberrypi2-native.cfg -f target/esp32.cfg -c "adapter_khz 1000"

We run OpenOCD as root to give it direct access to the Pi's GPIO pins.

Did it work?

If everything went well, you should see output similar to the following:

Open On-Chip Debugger  v0.10.0-esp32-20210401 (2021-04-08-23:12)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
BCM2835 GPIO nums: swclk = 11, swdio = 25

adapter speed: 1000 kHz

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : BCM2835 GPIO JTAG/SWD bitbang driver
Info : JTAG and SWD modes enabled
Info : clock speed 1001 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: Debug controller was reset.
Info : esp32.cpu0: Core was reset.
Info : esp32.cpu1: Debug controller was reset.
Info : esp32.cpu1: Core was reset.
Info : Listening on port 3333 for gdb connections

If you see an error, check your connections. You can also try setting a lower adapter_khz value in the openocd command. 1000 works well for me, but your mileage may vary.

GDB Debugging with ESP-IDF

ESP-IDF doesn't support remote debugging out of the box. The easiest workaround is setting up an SSH tunnel, so that local post 3333 is forwarded to the Raspberry Pi:

Run the following command on your development machine:

ssh -N -L 3333:raspberrypi:3333 pi@raspberrypi

Replace raspberrypi with the IP address of your Raspberry Pi. You mean need to enter a password (the default one is "raspberry", but I truly hope you changed it!)

Then, start GDB by running idf.py gdb in any ESP32 project directory. GDB should connect to the OpenOCD running on your Raspberry Pi, and stop in the first line of your app_main() function.

The output show look like:

uri@JONI:~/esp-idf/examples/get-started/hello_world$ idf.py gdb
Executing action: gdb
GNU gdb (crosstool-NG esp-2020r3) 8.1.0.20180627-git
...
esp32.cpu1: Core was reset.
esp32.cpu0: Core was reset.
esp32.cpu0: Target halted, PC=0x40000400, debug_reason=00000000
esp32.cpu1: Core was reset.
esp32.cpu1: Target halted, PC=0x40000400, debug_reason=00000000
Hardware assisted breakpoint 1 at 0x400d4984: file ../main/hello_world_main.c, line 17.
esp32.cpu0: Target halted, PC=0x400D4984, debug_reason=00000001
Set GDB target to 'esp32.cpu0'
esp32.cpu1: Target halted, PC=0x400E30DE, debug_reason=00000000
...
Thread 9 hit Temporary breakpoint 1, app_main () at ../main/hello_world_main.c:17
17      {
(gdb)

Congratulations! You have got a working setup for debugging ESP32 code!

You can also run the text user interface (TUI) of GDB, by running idf.py gdbtui, or start a web-based interface by running idf.py gdbgui.

GDB Text User Interface (TUI) debugging over JTAG
gdbgui debugging ESP32 over JTAG

Patching ESP-IDF (Alternative)

Alternatively, you can patch ESP-IDF and tell it to connect to the Raspberry Pi instead of the local machine.

Go to the ESP-IDF directory and open tools/idf_py_actions/debug_ext.py in a text editor. Then search for target remote :3333 and change it to include your Raspberry Pi's IP address, e.g. target remote 192.168.1.101:3333 .

OpenOCD will not accept remote connections by default. To change this, add -c "bindto 0.0.0.0" to the OpenOCD command line.

Now proceed to run idf.py gdb (or gdbtui / gdbgui as described above)

GDB Debugging with VSCode (Visual Studio Code)

This is my favorite setup!

First, make sure you have the C/C++ Extension for VSCode.

Next, open your project (or the ESP-IDF directory) in VSCode, and create a launch.json file:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug hello_world",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/examples/get-started/hello_world/build/hello-world.elf",
      "cwd": "${workspaceFolder}",
      "MIMode": "gdb",
      "miDebuggerServerAddress": "raspberrypi:3333",
      "miDebuggerPath": "/home/uri/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gdb"
    }
  ]
}

You'll need to customize some of the configuration options:

  1. Set program to the path of your ELF file. The ESP-IDF usually writes the ELF file inside the build directory of your project.
  2. Change the miDebuggerServerAddress to include the IP address of your Raspberry Pi (e.g. 192.168.1.101:3333)
  3. Change the miDebuggerPath to point the xtensa-esp32-elf-gdb binary. The ESP-IDF includes a copy of this GDB version, but you can also download it separately from their GitHub repo.

Press "F5" to test your configuration. You can set breakpoints (ESP32 supports 2 hardware breakpoints), and then restart your program by going to the Debug Console and typing:

`monitor reset halt
`continue

Voila! Now you can use Visual Studio code to debug your ESP32 applications!

Visual Studio Code debugging ESP32 app

Also, don't forget to take a look at our GDB Cheatsheet:

Arduino/AVR GDB Cheat Sheet
List of common GDB commands for debugging AVR code.