Loading... Please wait...

JTAG Debugger

JTAG Debugger

IOT-Bus ESP32 JTAG

Buy it here.

This IoT-Bus module provides JTAG debugging for the ESP32 Io and Proteus boards. The board uses the FT232H to provide a USB controller with JTAG support. Both debugging and flashing is possible using this port. To use JTAG debugging you must either be using esp-idf with any text editor, many use Eclipse or you must use Visual GDB with Arduino development in Visual Studio. JTAG debugging is not currently supported in the standard Arduino IDE.

Using JTAG for Debugging Applications Built with esp-idf

Espressif has ported OpenOCD to support the ESP32 processor and the multicore FreeRTOS, which will be the foundation of most ESP32 apps, and has written some tools to help with features OpenOCD does not support natively. You will use a combination of OpenOCD and GDB to using JATG debugging with the IoT-Bus Io and Proteus boards. The required connections for JTAG are automatically made. It is also possible to us the JTAG board with any other ESP32 module that makes the JTAG pins available. (The minimal signals to get a working JTAG connection are TDI, TDO, TCK, TMS and GND - see the pinout below.)

pinout-01.jpg

How JTAG Works with the ESP32

The key software and hardware to perform debugging of ESP32 with OpenOCD over JTAG (Joint Test Action Group) interface is presented below and includes xtensa-esp32-elf-gdb debuggerOpenOCD on chip debugger and JTAG adapter connected to ESP32 target.

JTAG debugging - overview diagram

JTAG debugging - overview diagram

Setup of OpenOCD

This step covers installation of OpenOCD binaries. If you like to build OpenOCS from sources then refer to section Building OpenOCD from Sources. All OpenOCD files will be placed in ~/esp/openocd-esp32 directory. You may choose any other directory, but need to adjust respective paths used in examples.

Pick your OS below and follow the instructions to setup OpenOCD.

After installation is complete, get familiar with two key directories inside openocd-esp32 installation folder:

  • bin containing OpenOCD executable
  • share\openocd\scripts containing configuration files invoked together with OpenOCD as command line parameters

 

Insert the JTAG module into the Io making sure GND pins are aligned. Connect a micro USB cable to the JTAG board.

Run OpenOCD

Once target is configured and connected to computer, you are ready to launch OpenOCD.

Open terminal, go to directory where OpenOCD is installed and start it up:

bin/openocd -s share/openocd/scripts -f /Users/ian/esp/esp-idf/examples/openOCD/esp32.cfg

You should see output similar to this:

 

user-name@computer-name:~/esp/openocd-esp32$ bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
Open On-Chip Debugger 0.10.0-dev-ged7b1a9 (2017-07-10-07:16)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
none separate
adapter speed: 20000 kHz
force hard breakpoints
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: Debug controller was reset (pwrstat=0x5F, after clear 0x0F).
Info : esp32: Core was reset (pwrstat=0x5F, after clear 0x0F).

Note that on Mac if the JTAG device is not found, you may need to unload the standard FTDI driver as follows prior to launching OpenOCD:

  sudo kextunload -p -b com.FTDI.driver.FTDIUSBSerialDriver

Flash with OpenOCD 

You can avoid using two USB cables and flash directly from the JTAG board by using openocd. This example flashes the blink example.

bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg -c "program_esp32 /Users/ian/esp/esp-idf/examples/get-started/blink/build/blink.bin 0x10000 verify exit" 

The GDB Debugger

The toolchain for ESP32 features GNU Debugger, in short GDB. It is available with other toolchain programs under filename xtensa-esp32-elf-gdb. GDB can be called and operated directly from command line in a terminal. Another option is to call it from within IDE (like Eclipse, Visual Studio Code, etc.) and operate indirectly with help of GUI instead of typing commands in a terminal. The examples below use GDB in a terminal window.

Create gdbinit

Create this file [where]

target remote :3333

mon reset halt

thb app_main

x $a1=0

c

Launching the GDB Debugger 

xtensa-esp32-elf-gdb -x gdbinit get-started/blink/build/blink.elf

Stop debug –

CTRL-C / quit

Command Line

Verify if your target is ready and loaded with get-started/blink example. Configure and start debugger following steps in section Command Line. Pick up where target was left by debugger, i.e. having the application halted at breakpoint established at app_main():

Temporary breakpoint 1, app_main () at /home/user-name/esp/blink/main/./blink.c:43
43          xTaskCreate(&blink_task, "blink_task", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
(gdb)

Navigating though the code, call stack and threads

When you see the (gdb) prompt, the application is halted. LED should not be blinking.

To find out where exactly the code is halted, enter l or list, and debugger will show couple of lines of code around the halt point (line 43 of code in file blink.c)

(gdb) l
38          }
39      }
40
41      void app_main()
42      {
43          xTaskCreate(&blink_task, "blink_task", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
44      }
(gdb)

Check how code listing works by entering, e.g. l 30, 40 to see particular range of lines of code.

You can use bt or backtrace to see what function calls lead up to this code:

(gdb) bt
#0  app_main () at /home/user-name/esp/blink/main/./blink.c:43
#1  0x400d057e in main_task (args=0x0) at /home/user-name/esp/esp-idf/components/esp32/./cpu_start.c:339
(gdb)

Line #0 of output provides the last function call before the application halted, i.e. app_main () we have listed previously. The app_main () was in turn called by function main_task from line 339 of code located in file cpu_start.c.

To get to the context of main_task in file cpu_start.c, enter frame  N, where N = 1, because the main_task is listed under #1):

(gdb) frame 1
#1  0x400d057e in main_task (args=0x0) at /home/user-name/esp/esp-idf/components/esp32/./cpu_start.c:339
339         app_main();
(gdb)

Enter l and this will reveal the piece of code that called app_main() (in line 339):

(gdb) l
334             ;
335         }
336     #endif
337         //Enable allocation in region where the startup stacks were located.
338         heap_caps_enable_nonos_stack_heaps();
339         app_main();
340         vTaskDelete(NULL);
341     }
342
(gdb)

By listing some lines before, you will see the function name main_task we have been looking for:

(gdb) l 326, 341
326     static void main_task(void* args)
327     {
328         // Now that the application is about to start, disable boot watchdogs
329         REG_CLR_BIT(TIMG_WDTCONFIG0_REG(0), TIMG_WDT_FLASHBOOT_MOD_EN_S);
330         REG_CLR_BIT(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN);
331     #if !CONFIG_FREERTOS_UNICORE
332         // Wait for FreeRTOS initialization to finish on APP CPU, before replacing its startup stack
333         while (port_xSchedulerRunning[1] == 0) {
334             ;
335         }
336     #endif
337         //Enable allocation in region where the startup stacks were located.
338         heap_caps_enable_nonos_stack_heaps();
339         app_main();
340         vTaskDelete(NULL);
341     }
(gdb)

To see the other code, enter i threads. This will show the list of threads running on target:

(gdb) i threads
  Id   Target Id         Frame
  8    Thread 1073411336 (dport) 0x400d0848 in dport_access_init_core (arg=)
    at /home/user-name/esp/esp-idf/components/esp32/./dport_access.c:170
  7    Thread 1073408744 (ipc0) xQueueGenericReceive (xQueue=0x3ffae694, pvBuffer=0x0, xTicksToWait=1644638200,
    xJustPeeking=0) at /home/user-name/esp/esp-idf/components/freertos/./queue.c:1452
  6    Thread 1073431096 (Tmr Svc) prvTimerTask (pvParameters=0x0)
    at /home/user-name/esp/esp-idf/components/freertos/./timers.c:445
  5    Thread 1073410208 (ipc1 : Running) 0x4000bfea in ?? ()
  4    Thread 1073432224 (dport) dport_access_init_core (arg=0x0)
    at /home/user-name/esp/esp-idf/components/esp32/./dport_access.c:150
  3    Thread 1073413156 (IDLE) prvIdleTask (pvParameters=0x0)
    at /home/user-name/esp/esp-idf/components/freertos/./tasks.c:3282
  2    Thread 1073413512 (IDLE) prvIdleTask (pvParameters=0x0)
    at /home/user-name/esp/esp-idf/components/freertos/./tasks.c:3282
* 1    Thread 1073411772 (main : Running) app_main () at /home/user-name/esp/blink/main/./blink.c:43
(gdb)

The thread list shows the last function calls per each thread together with the name of C source file if available.

You can navigate to specific thread by entering thread N, where N is the thread Id. To see how it works go to thread thread 5:

(gdb) thread 5
[Switching to thread 5 (Thread 1073410208)]
#0  0x4000bfea in ?? ()
(gdb)

Then check the backtrace:

(gdb) bt
#0  0x4000bfea in ?? ()
#1  0x40083a85 in vPortCPUReleaseMutex (mux=) at /home/user-name/esp/esp-idf/components/freertos/./port.c:415
#2  0x40083fc8 in vTaskSwitchContext () at /home/user-name/esp/esp-idf/components/freertos/./tasks.c:2846
#3  0x4008532b in _frxt_dispatch ()
#4  0x4008395c in xPortStartScheduler () at /home/user-name/esp/esp-idf/components/freertos/./port.c:222
#5  0x4000000c in ?? ()
#6  0x4000000c in ?? ()
#7  0x4000000c in ?? ()
#8  0x4000000c in ?? ()
(gdb)

As you see, the backtrace may contain several entries. This will let you check what exact sequence of function calls lead to the code where the target halted. Question marks ?? instead of a function name indicate that application is available only in binary format, without any source file in C language. The value like 0x4000bfea is the memory address of the function call.

Using bti threadsthread N and list commands we are now able to navigate through the code of entire application. This comes handy when stepping though the code and working with breakpoints and will be discussed below.

Setting and clearing breakpoints

When debugging, we would like to be able to stop the application at critical lines of code and then examine the state of specific variables, memory and registers / peripherals. To do so we are using breakpoints. They provide a convenient way to quickly get to and halt the application at specific line.

Let’s establish two breakpoints when the state of LED changes. Basing on code listing above this happens at lines 33 and 36. Breakpoints may be established using command break M where M is the code line number:

(gdb) break 33
Breakpoint 2 at 0x400db6f6: file /home/user-name/esp/blink/main/./blink.c, line 33.
(gdb) break 36
Breakpoint 3 at 0x400db704: file /home/user-name/esp/blink/main/./blink.c, line 36.

If you new enter c, the processor will run and halt at a breakpoint. Entering c another time will make it run again, halt on second breakpoint, and so on:

(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB6F6 (active)    APP_CPU: PC=0x400D10D8

Breakpoint 2, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:33
33          gpio_set_level(BLINK_GPIO, 0);
(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB6F8 (active)    APP_CPU: PC=0x400D10D8
Target halted. PRO_CPU: PC=0x400DB704 (active)    APP_CPU: PC=0x400D10D8

Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:36
36          gpio_set_level(BLINK_GPIO, 1);
(gdb)

You will be also able to see that LED is changing the state only if you resume program execution by entering c.

To examine how many breakpoints are set and where, use command info break:

(gdb) info break
Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x400db6f6 in blink_task at /home/user-name/esp/blink/main/./blink.c:33
    breakpoint already hit 1 time
3       breakpoint     keep y   0x400db704 in blink_task at /home/user-name/esp/blink/main/./blink.c:36
    breakpoint already hit 1 time
(gdb)

Please note that breakpoint numbers (listed under Num) start with 2. This is because first breakpoint has been already established at function app_main() by running command thb app_main on debugger launch. As it was a temporary breakpoint, it has been automatically deleted and now is not listed anymore.

To remove breakpoints enter delete N command (in short d N), where N is the breakpoint number:

(gdb) delete 1
No breakpoint number 1.
(gdb) delete 2
(gdb)

Read more about breakpoints under Breakpoints and watchpoints available and What else should I know about breakpoints?

Halting and resuming the application

When debugging, you may resume application and enter code waiting for some event or staying in infinite loop without any break points defined. In such case, to go back to debugging mode, you can break program execution manually by entering Ctrl+C.

To check it delete all breakpoints and enter c to resume application. Then enter Ctrl+C. Application will be halted at some random point and LED will stop blinking. Debugger will print the following:

(gdb) c
Continuing.
^CTarget halted. PRO_CPU: PC=0x400D0C00             APP_CPU: PC=0x400D0C00 (active)
[New Thread 1073433352]

Program received signal SIGINT, Interrupt.
[Switching to Thread 1073413512]
0x400d0c00 in esp_vApplicationIdleHook () at /home/user-name/esp/esp-idf/components/esp32/./freertos_hooks.c:52
52              asm("waiti 0");
(gdb)

In particular case above, the application has been halted in line 52 of code in file freertos_hooks.c. Now you can resume it again by enter c or do some debugging as discussed below.

Note

In MSYS2 shell Ctrl+C does not halt the target but exists debugger. To resolve this issue consider debugging with Eclipse or check a workaround under http://www.mingw.org/wiki/Workaround_for_GDB_Ctrl_C_Interrupt.

Stepping through the code

It is also possible to step through the code using step and next commands (in short s and n). The difference is that step is entering inside subroutines calls, while next steps over the call, treating it as a single source line.

To demonstrate this functionality, using command break and delete discussed in previous paragraph, make sure that you have only one breakpoint defined at line 36 of blink.c:

(gdb) info break
Num     Type           Disp Enb Address    What
3       breakpoint     keep y   0x400db704 in blink_task at /home/user-name/esp/blink/main/./blink.c:36
    breakpoint already hit 1 time
(gdb)

Resume program by entering c and let it halt:

(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB754 (active)    APP_CPU: PC=0x400D1128

Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:36
36          gpio_set_level(BLINK_GPIO, 1);
(gdb)

Then enter n couple of times to see how debugger is stepping one program line at a time:

(gdb) n
Target halted. PRO_CPU: PC=0x400DB756 (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB758 (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DC04C (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB75B (active)    APP_CPU: PC=0x400D1128
37          vTaskDelay(1000 / portTICK_PERIOD_MS);
(gdb) n
Target halted. PRO_CPU: PC=0x400DB75E (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400846FC (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB761 (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB746 (active)    APP_CPU: PC=0x400D1128
33          gpio_set_level(BLINK_GPIO, 0);
(gdb)

If you enter s instead, then debugger will step inside subroutine calls:

(gdb) s
Target halted. PRO_CPU: PC=0x400DB748 (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB74B (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DC04C (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DC04F (active)    APP_CPU: PC=0x400D1128
gpio_set_level (gpio_num=GPIO_NUM_4, level=0) at /home/user-name/esp/esp-idf/components/driver/./gpio.c:183
183     GPIO_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), "GPIO output gpio_num error", ESP_ERR_INVALID_ARG);
(gdb)

In this particular case debugger stepped inside gpio_set_level(BLINK_GPIO, 0) and effectively moved to gpio.c driver code.

See Why stepping with “next” does not bypass subroutine calls? for potential limitation of using next command.

Checking and setting memory

Displaying the contents of memory is done with command x. With additional parameters you may vary the format and count of memory locations displayed. Run help x to see more details. Companion command to x is set that let you write values to the memory.

We will demonstrate how x and set work by reading from and writing to the memory location 0x3FF44004 labeled as GPIO_OUT_REG used to set and clear individual GPIO’s. For more information please refer to ESP32 Technical Reference Manual, chapter IO_MUX and GPIO Matrix.

Being in the same blink.c project as before, set two breakpoints right after gpio_set_levelinstruction. Enter two times c to get to the break point followed by x /1wx 0x3FF44004 to display contents of GPIO_OUT_REG memory location:

(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB75E (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB74E (active)    APP_CPU: PC=0x400D1128

Breakpoint 2, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:34
34          vTaskDelay(1000 / portTICK_PERIOD_MS);
(gdb) x /1wx 0x3FF44004
0x3ff44004: 0x00000000
(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB751 (active)    APP_CPU: PC=0x400D1128
Target halted. PRO_CPU: PC=0x400DB75B (active)    APP_CPU: PC=0x400D1128

Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:37
37          vTaskDelay(1000 / portTICK_PERIOD_MS);
(gdb) x /1wx 0x3FF44004
0x3ff44004: 0x00000010
(gdb)

If your are blinking LED connected to GPIO4, then you should see fourth bit being flipped each time the LED changes the state:

0x3ff44004: 0x00000000
...
0x3ff44004: 0x00000010

Now, when the LED is off, that corresponds to 0x3ff44004: 0x00000000 being displayed, try using set command to set this bit by writting 0x00000010 to the same memory location:

(gdb) x /1wx 0x3FF44004
0x3ff44004: 0x00000000
(gdb) set {unsigned int}0x3FF44004=0x000010

You should see the LED to turn on immediately after entering set {unsigned int}0x3FF44004=0x000010 command.

Watching and setting program variables

A common debugging tasks is checking the value of a program variable as the program runs. To be able to demonstrate this functionality, update file blink.c by adding a declaration of a global variable int i above definition of function blink_task. Then add i++ inside loop(1) of this function to get i incremented on each blink.

Exit debugger, so it is not confused with new code, build and flash the code to the ESP and restart debugger. There is no need to restart OpenOCD.

Once application is halted, enter the command watch i:

(gdb) watch i
Hardware watchpoint 2: i
(gdb)

This will insert so called “watchpoint” in each place of code where variable i is being modified. Now enter continue to resume the application and observe it being halted:

(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB751 (active)    APP_CPU: PC=0x400D0811
[New Thread 1073432196]

Program received signal SIGTRAP, Trace/breakpoint trap.
[Switching to Thread 1073432196]
0x400db751 in blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:33
33          i++;
(gdb)

Resume application couple more times so i gets incremented. Now you can enter print i (in short p i) to check the current value of i:

(gdb) p i
$1 = 3
(gdb)

To modify the value of i use set command as below (you can then print it out to check if it has been indeed changed):

(gdb) set var i = 0
(gdb) p i
$3 = 0
(gdb)

You may have up to two watchpoints, see Breakpoints and watchpoints available.

Setting conditional breakpoints

Here comes more interesting part. You may set a breakpoint to halt the program execution, if certain condition is satisfied. Delete existing breakpoints and try this:

(gdb) break blink.c:34 if (i == 2)
Breakpoint 3 at 0x400db753: file /home/user-name/esp/blink/main/./blink.c, line 34.
(gdb)

Above command sets conditional breakpoint to halt program execution in line 34 of blink.c if i == 2.

If current value of i is less than 2 and program is resumed, it will blink LED in a loop until condition i == 2 gets true and then finally halt:

(gdb) set var i = 0
(gdb) c
Continuing.
Target halted. PRO_CPU: PC=0x400DB755 (active)    APP_CPU: PC=0x400D112C
Target halted. PRO_CPU: PC=0x400DB753 (active)    APP_CPU: PC=0x400D112C
Target halted. PRO_CPU: PC=0x400DB755 (active)    APP_CPU: PC=0x400D112C
Target halted. PRO_CPU: PC=0x400DB753 (active)    APP_CPU: PC=0x400D112C

Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:34
34          gpio_set_level(BLINK_GPIO, 0);
(gdb)

Obtaining help on commands

Commands presented so for should provide are very basis and intended to let you quickly get started with JTAG debugging. Check help what are the other commands at you disposal. To obtain help on syntax and functionality of particular command, being at (gdb) prompt type help and command name:

(gdb) help next
Step program, proceeding through subroutine calls.
Usage: next [N]
Unlike "step", if the current source line calls a subroutine,
this command does not enter the subroutine, but instead steps over
the call, in effect treating it as a single source line.
(gdb)

By typing just help, you will get top level list of command classes, to aid you drilling down to more details. Optionally refer to available GDB cheat sheets, for instance http://darkdust.net/files/GDB%20Cheat%20Sheet.pdf. Good to have as a reference (even if not all commands are applicable in an embedded environment).

Ending debugger session

To quit debugger enter q:

(gdb) q
A debugging session is active.

    Inferior 1 [Remote target] will be detached.

Quit anyway? (y or n) y
Detaching from program: /home/user-name/esp/blink/build/blink.elf, Remote target
Ending remote debugging.
user-name@computer-name:~/esp/blink$

 

 

What's News

newsletter

Copyright 2018 oddWires. All Rights Reserved.
 Sitemap | oddWires