After Flashing firmware to a microcontroller we normally test & debug the behavior of the application using either an oscilloscope, logic analyzer, or seeing and probing everything manually. Another popular mechanism is to connect a USB UART module to a dedicated UART peripheral and see everything on the PC side using Serial Terminal Software.
But ...
- What if we need to deep dive and see what is happening inside the processor?
- What if we want to debug through breakpoints set at source code and navigate through the code just like a Regular Software Engineer Does?
If your answer is yes then this post might be helpful.
Microcontrollers are resource-constrained small computers and debugging at the CPU level is not so common . over the years' semiconductor manufacturers have jointly developed a protocol named JTAG to establish a common standard that we all can follow to debug these tiny computers. Not all the mcus in the market have the JTAG stack embedded inside the silicon but nowadays it has become more mainstream. We will first discuss the common concepts behind JTAG and then look into how we can set up everything. Our Target platform will be ESP32.
JTAG stands for Joint Test Action Group
The CPU inside an MCU is equipped with an On-Chip Debug Module. This on-chip debug module understands JTAG protocol and can communicate with the CPU under the rules set by JTAG specification. For example, our target MCU ESP32 has an on-chip debug module.
Then this on-chip debug module is routed to JTAG pins which are exposed at the external side of an MCU. The naming convention and behavior of these pins are as per the JTAG standard.
JTAG specification allows us to
- Program the microcontroller
- Halt the CPU
- inspect Microcontroller Registers and memory
- Define breakpoints
- Allows single stepping through the code
- Allows sending debug string messages [ e.g. ARM CPU has ITM (instrumentation trace macro-cell) ]
- Tracing
Client and Server Model
From a high-level point of view, there are 2 entities: At one end we have the microcontroller and at the other end we have our development machine ready to debug the target MCU. We need to visualize The Host side PC as a client and the microcontroller as a server.
Required Tools
By this time we have well understood the system-level topologies, now let's look into what kind of software we need. We will be using GDB ( GNU Debugger ) as our debugger software.
GDB has a GDBClient program for almost Every kind of CPU architecture. During the installation of ESP-IDF, it got automatically installed in our system.
At the microcontroller end, The on-chip-debug module itself will not act as a server, we would need 2 more components in between. one is a hardware component named JTAG-Adapter and another one is a software component named OpenOCD. Have a look at the following diagram to understand the whole idea
The OpenOCD got also installed during esp-idf setup. you can look into my previous blog post on setting up ESP-IDF SDK setup from here
ESP-IDF setup in Ubuntu
J-Link by Segger Embedded is a very common & popular JTAG adapter. we will use it in our setup.
JLink & ESP32 JTAG connection
A JTAG Port has a fixed set of PIN allotments. There are a couple of variants, of them we will follow the 20-pin option
Connect your ESP32 to the JLINK as per the following table
JLink | ESP32 |
VTref | 3.3V |
nTRST | EN |
TDI | GPIO12 |
TMS | GPIO14 |
TCK | GPIO13 |
RTCK | GND |
TDO | GPIO15 |
Pin No 4 [GND] | GND |
My Connection Setup Looked like this
I followed the following link
Now Plugin JLink to your USB port
JLink Driver Installation
I assume that you have not installed JLink Before so let's install the driver for our ubuntu host
it will get downloaded to your Downloads folder, so CD into it and use dpkg to install
~/Downloads$ sudo dpkg -i JLink_Linux_V650b_x86_64.deb |
(Reading database ... 206817 files and directories currently installed.) Preparing to unpack JLink_Linux_V650b_x86_64.deb ... Removing /opt/SEGGER/JLink ... Unpacking jlink (6.502) over (6.502) ... Setting up jlink (6.502) ... |
Check JLink Rules
60-vboxdrv.rules 70-snap.electronic-wechat.rules 70-snap.canonical-livepatch.rules 99-jlink.rules 70-snap.core.rules 99-USBAsp.rules |
If Jlink rules are automatically added like above then JLink is installed properly.
Now connect your ESP32 devkit to host over USB
Program ESP32 over JTAG & OpenOCD
The focus of this post is to debug ESP32 via JTAG but we can also Program the microcontroller via JTAG port. When we setup ESP-IDF first we got many examples as part of the SDK. we will program the usual Blink example application.
CD into the project and load esp-idf environment
~/esp/esp-idf/examples/get-started/blink$ . ~/esp/esp-idf/export.sh |
This will load esp-idf compiler toolchains and openocd executables in our shell environment
Check JTAG connection with ESP32 via OpenOCD
Then run the following command to test jLink Connection to ESP32
~/esp/esp-idf/examples/get-started/blink$ openocd -f interface/jlink.cfg -f board/esp-wroom-32.cfg |
Open On-Chip Debugger v0.10.0-esp32-20190708 (2019-07-08-11:03) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html adapter speed: 20000 kHz Info : Configured 2 cores esp32 interrupt mask on Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : J-Link V9 compiled Jun 2 2222 22:22:22 Info : Hardware version: 9.40 Info : VTarget = 3.291 V Info : Reduced speed from 20000 kHz to 15000 kHz (maximum). Info : Reduced speed from 20000 kHz to 15000 kHz (maximum). 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: Debug controller 0 was reset (pwrstat=0x5F, after clear 0x0F). Info : esp32: Core 0 was reset (pwrstat=0x5F, after clear 0x0F). Info : esp32: Debug controller 1 was reset (pwrstat=0x5F, after clear 0x0F). Info : esp32: Core 1 was reset (pwrstat=0x5F, after clear 0x0F). Info : Listening on port 3333 for gdb connections |
If you get an output like the above then connections are okay and openocd has been able to communicate with the on-chip debug module of esp32
Program ESP32 using JLink
You can also program the application binary over JTAG connection, we will build and program the simple “blink” example application.
Build the program first using idf.py
~/esp/esp-idf/examples/get-started/blink$ idf.py build |
Now program it
~/esp/esp-idf/examples/get-started/blink$ openocd -f interface/jlink.cfg -f board/esp-wroom-32.cfg -c "program_esp32 build/blink.bin 0x10000 verify reset exit" |
Here the ideal 0x10000 address was set
If the setup is okay then you will see output like this
** Programming Started ** auto erase enabled Info : Target halted. PRO_CPU: PC=0x400916EE (active) APP_CPU: PC=0x40000400 Info : Flash mapping 0: 0x10020 -> 0x3f400020, 24 KB Info : Flash mapping 1: 0x20018 -> 0x400d0018, 74 KB Info : Target halted. PRO_CPU: PC=0x400916EE (active) APP_CPU: PC=0x40000400 Info : Auto-detected flash size 4096 KB Info : Using flash size 4096 KB Info : Target halted. PRO_CPU: PC=0x400916EE (active) APP_CPU: PC=0x40000400 Info : Target halted. PRO_CPU: PC=0x400916EE (active) APP_CPU: PC=0x40000400 wrote 151552 bytes from file build/blink.bin in 5.383243s (27.493 KiB/s) ** Programming Finished ** ** Verify Started ** Info : Target halted. PRO_CPU: PC=0x400916EE (active) APP_CPU: PC=0x40000400 read 148384 bytes from file build/blink.bin and flash bank 0 at offset 0x00010000 in 2.188784s (66.204 KiB/s) contents match ** Verified OK ** ** Resetting Target ** |
Now you will see that the board’s default LED(connected to GPIO2) is blinking
JTAG Debugging
Now that we have programmed esp32 via JTAG , we can now move into the actual debugging process that we have been craving for since the start of the process
you have already seen that ESP-IDF has GDB client executable xtensa-esp32-elf-gdb in place. In this post, we will go through a simple usage of GDB. We will Halt the processor at a breakpoint.
Let's First Create a file named "gdbinit"
~/esp/esp-idf/examples/get-started/blink$ cat > gdbinit |
put the following content
target remote :3333 set remote hardware-watchpoint-limit 2 mon reset halt flushregs thb app_main c |
Here thb app_main line sets a breakpoint at app_main() function
Now you have to open 2 terminals
Both the terminal should have esp-idf environment exported,
Let’s open a new terminal [CTRL + T]
~/esp/esp-idf/examples/get-started/blink$ . /home/ayx/esp/esp-idf/export.sh |
Now in this terminal we will connect to ESP32 over JTAG and start the ESP32’s GDB server .
~/esp/esp-idf/examples/get-started/blink$ openocd -f interface/jlink.cfg -f board/esp-wroom-32.cfg |
Open On-Chip Debugger v0.10.0-esp32-20190708 (2019-07-08-11:03) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html adapter speed: 20000 kHz Info : Configured 2 cores esp32 interrupt mask on Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : J-Link V9 compiled Jun 2 2222 22:22:22 Info : Hardware version: 9.40 Info : VTarget = 3.280 V Info : Reduced speed from 20000 kHz to 15000 kHz (maximum). Info : Reduced speed from 20000 kHz to 15000 kHz (maximum). 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 : Target halted. PRO_CPU: PC=0x400D399C (active) APP_CPU: PC=0x400E2922 Info : Detected debug stubs @ 3ffb28f0 on core0 of target 'esp32' Info : Listening on port 3333 for gdb connections |
Here you can see that ESP32’s GDB server is listening on port 3333 for GDB connection
This is the same command that we used for checking JTAG connection
Now keep this terminal alive and open another terminal [CTRL +T]
Again export the environment
~/esp/esp-idf/examples/get-started/blink$ . /home/ayx/esp/esp-idf/export.sh |
Now we will use xtensa-esp32-elf-gdb use to connect to the ESP32 GDB server & debug our blink example code
~/esp/esp-idf/example/get-started/blink$ xtensa-esp32-elf-gdb -x gdbinit build/blink.elf |
You will see both the terminal’s output getting changed
As seen in the above picture you can see that The bottom terminal CPU is halted at a breakpoint
As the processor is halted the blink should also stop.
There are gdb commands to step through the code, for example
" To execute one line of code, type "step" or "s". If the line to be executed is a function call, gdb will step into that function and start executing its code one line at a time. If you want to execute the entire function with one keypress, type "next" or "n" "
reference: gdb-quickstart
This concludes our JTAG Openocd and GDB setup. in a later post, I will show how we can make use of JTAG tracing and get us free from allocating a UART peripheral just for Debug purposes. Keeping debug log only available via JTAG makes the application more production friendly as one then does not need to disable uart based debugging before product release and face untested timing scenarios
Thank you for your post. This is excellent information.
ReplyDeleteJTAG