Debugging ESP32 with JTAG Adapter on Linux

Although Espressif’s website provides detailed instructions, this post offers a quick overview on steps required to setup a JTAG debugger adapter for ESP32 NodeMCU development board on Linux platform.

Why JTAG

JTAG (Joint Test Action Group) is a useful interface for debugging and programming electronic devices such as microcontrollers and digital signal processors for several reasons:

  1. Non-Intrusive: JTAG is a non-intrusive interface, meaning it can be used to test and debug a device without affecting its normal operation. It provides a way to access the internal circuitry of a device, which is typically not accessible through other interfaces.
  2. Boundary Scan: JTAG includes a feature called boundary scan that allows designers to test and debug connections on printed circuit boards (PCBs). The boundary scan feature allows to test and diagnose problems with individual components or connection.
  3. In-System Programming: JTAG can be used to program a device while it is still mounted on a PCB or other system, which is known as in-system programming. This can be useful for updating firmware or debug device without needing to remove it from the system.
  4. Standardized Protocol: JTAG uses a standardized protocol defined by the IEEE 1149.1 standard, which ensures compatibility between devices and tools from different manufacturers.
  5. Semi-hosting: Provides a access to Host Resources, redirect “printf” or access to files on the host system as if they were on the local device, making it easier to test and debug software that relies on file input/output.
  6. Low-Cost: JTAG is a low-cost interface that is widely available on many microcontrollers and other electronic devices. This makes it a popular choice for developers and engineers who need to test and debug electronic systems.

Prerequisites

  1. Linux host machine, in my case Linux Mint 21.1 distribution is adopted, but any modern Linux distribution should work.
  2. OpenOCD compatible JTAG debugger hadware, in my case FT2232D that was left over from a previous plug computer project. https://globalscaletechnologies.com/product/gti-jtag-probe/
    Reference design for this JTAG interface and connection schematics: https://www.netquote.it/nqmain/wp-content/uploads/SheevaPlug.pdf
  3. Esp32 NodeMCU development board: https://wiki.geekworm.com/NodeMCU-32S
    https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf

Setup OpenOCD

Because the version of OpenOCD available in this Linux release is outdated and lacks Xtensa support, install the latest release from the official repository: https://openocd.org/pages/getting-openocd.html

For detailed instruction on howto build and install OpenOCD: https://openocd.org/doc-release/README

~/esp32$ git clone https://git.code.sf.net/p/openocd/code openocd-code
Cloning into 'openocd-code'...
remote: Enumerating objects: 77625, done.
...
Resolving deltas: 100% (64682/64682), done. 

Run “./bootstrap” when building from the git repository

~/esp32$ cd openocd-code/
~/esp32/openocd-code$ ./bootstrap
+ aclocal --warnings=all
+ libtoolize --automake --copy
+ autoconf --warnings=all
...
libjaylink/Makefile.am: installing 'build-aux/depcomp'
Bootstrap complete. Quick build instructions:
./configure ....

Now we can set some build options like hardware interface support, verbosity, installation directories and disable/enable other features.

~/esp32/openocd-code$ ./configure --enable-ftdi
checking for makeinfo... no
configure: WARNING: Info documentation will not be built.
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
...
OpenOCD configuration summary
--------------------------------------------------
MPSSE mode of FTDI based devices        yes
ST-Link Programmer                      yes (auto)
TI ICDI JTAG Programmer                 yes (auto)
Keil ULINK JTAG Programmer              yes (auto)
Altera USB-Blaster II Compatible        yes (auto)
Bitbang mode of FT232R based devices    yes (auto)
Versaloon-Link JTAG Programmer          yes (auto)
TI XDS110 Debug Probe                   yes (auto)
CMSIS-DAP v2 Compliant Debugger         yes (auto)
OSBDM (JTAG only) Programmer            yes (auto)
eStick/opendous JTAG Programmer         yes (auto)
Olimex ARM-JTAG-EW Programmer           yes (auto)
Raisonance RLink JTAG Programmer        yes (auto)
USBProg JTAG Programmer                 yes (auto)
Espressif JTAG Programmer               yes (auto)
CMSIS-DAP Compliant Debugger            no
Nu-Link Programmer                      no
Cypress KitProg Programmer              no
Altera USB-Blaster Compatible           yes (auto)
ASIX Presto Adapter                     yes (auto)
OpenJTAG Adapter                        yes (auto)
Linux GPIO bitbang through libgpiod     no
SEGGER J-Link Programmer                no
Bus Pirate                              yes (auto)
Use Capstone disassembly framework      yes (auto)

Then make and install as usual, be careful about conflicts with existing OpenOCD installation, remove or add prefix/suffix to the new OpenOCD executable.

~/esp32/openocd-code$ make -j16
...
~/esp32/openocd-code$ sudo make install
...

Add udev rules:

~/esp32/openocd-code$ nano /etc/udev/rules.d/10-ft2322D_jtag.rules
UBSYSTEMS=="usb", ATTRS{idVendor}=="9e88", ATTRS{idProduct}=="9e8f", MODE="0666"

Add user to dialout group:

~/esp32/openocd-code$ sudo usermod -aG dialout $USER

JTAG connections:

ESP32 pinJTAG pin#J6
3V3VTRef (3.3V)1
ENJT_SRSTn3
GPIO12JT_TDI5
GPIO14JT_TMS_CPU7
GPIO13JT_CLK9
GPIO15JT_TDO13
GNDGND20
Esp32 to JTAG connections
Sheevaplug JTAG pinout

Detailed description for JTAG configuration can be found on this page: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/jtag-debugging/configure-other-jtag.html

For ESP32 pinout refer to this page: https://www.studiopieters.nl/esp32-pinout/

OpenOCD Configuration and Testing

Now if we run openocd binary we will se somethink like this:

~/esp32/bare_v1$ openocd 
Open On-Chip Debugger v0.11.0-esp32-20221026 (2022-10-26-14:47)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
embedded:startup.tcl:28: Error: Can't find openocd.cfg
in procedure 'script' 
at file "embedded:startup.tcl", line 28
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Error: Debug Adapter has to be specified, see "adapter driver" command
embedded:startup.tcl:28: Error: 
in procedure 'script' 
at file "embedded:startup.tcl", line 28

This error, because of unknown adapter. We need to instruct OpenOCD about our hardware interface and target. This can be done by creating a configuration file named ‘esp32_jtag.cfg’ with next settings:

# FT2232D JTAG Adapter Configuration 
adapter driver ftdi
ftdi device_desc "SheevaPlug JTAGKey FT2232D B"
ftdi vid_pid 0x9e88 0x9e8f
ftdi channel 0
ftdi layout_init 0x0608 0x0f1b
ftdi layout_signal nTRST -data 0x0200 -noe 0x0100
ftdi layout_signal nSRST -data 0x0800 -noe 0x0400

# The speed of the JTAG interface, in kHz. If you get DSR/DIR errors (and they
# do not relate to OpenOCD trying to read from a memory range without physical
# memory being present there), you can try lowering this.
#
# On DevKit-J, this can go as high as 20MHz if CPU frequency is 80MHz, or 26MHz
# if CPU frequency is 160MHz or 240MHz.
adapter speed 1000

# flash 3.3 V for ESP32-WROOM-32 
set ESP32_FLASH_VOLTAGE 3.3

# Load esp32 target configuration
source [find target/esp32.cfg]

You can find a list of available interfaces and targets in the ‘openocd-code/tcl’ folder.

Running JTAG debugger

After launching the debugger, we should see something like this:

~/esp32$ openocd -f esp32_jtag.cfg
Open On-Chip Debugger 0.12.0+dev-00078-gfc30feb51 (2023-03-11-15:26)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
force hard breakpoints
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 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 : starting gdb server for esp32.cpu0 on 3333
Info : Listening on port 3333 for gdb connections
Info : Set GDB target to 'esp32.cpu0'

Open your favorite telnet console on localhost at port 4444

~$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> help
...

Brief command list:

CommandDescription
help [command_name]Show full command help; command can be multiple tokens. (command valid any time)
versionshow program version (command valid any time)
reset [run|halt|init]Reset all targets into the specified mode. Default reset mode is run, if not given.
halt [milliseconds]request target to halt, then wait up to the specified number of milliseconds (default 5000) for it to complete
targets [target]change current default target (one parameter) or prints table of all targets (no parameters) (command valid any time)
flash banksDisplay table with information about flash banks. (command valid any time)
reg [(number|name)]
reg [(value|’force’)]
display (reread from target with “force”) or set a register; with no arguments, displays all registers and their values
resume [address]resume target execution from current PC or address
scan_chainprint current scan chain configuration (command valid any time) script
shutdownClose the OpenOCD server, disconnecting all clients (GDB, telnet, other). If option
error is used, OpenOCD will return a non-zero exit code to the parent process.
If user types CTRL-C or kills OpenOCD, the command shutdown will be automati-
cally executed to cause OpenOCD to exit.
arm semihosting [‘enable’|’disable’]Set the base directory for semihosting I/O.DEPRECATED! use arm activate support for semihosting operations
arm semihosting_basedir [dir]set the base directory for semihosting I/O operations
arm semihosting_fileio [‘enable’|’disable’]activate support for semihosting fileio operations
arm semihosting_redirect (disable | tcp [‘debug’|’stdio’|’all’])redirect semihosting IO
initInitializes configured targets and servers. Changes command mode from CONFIG to EXEC. Unless ‘noinit’ is called, this command is called automatically at the end of startup. (command valid any time)