Typical situation. The logging and UART component was not initialized correctly, or the device hung somewhere in the initialization after the reset. Or the device endlessly reboots after power up. How to understand on which line the run-time error occurred? The classic solution is step-by-step debugging via JTAG or SWD.
A little about iron. I will work with this board
Here on the board (e) both the U2 programmer and the U1 (Target) microcontroller itself. Both 94pin (new) nrf5340 in aQFN94 package. They are connected by an SWD interface. Each has 2 Arm Cortex-M33 (Armv8-M) cores. Outside also goes 2 UART(s) for the command line interface. They are forwarded via USB (J2) to DeskTop.
What do you need from the software?
GDB Client for ARM
C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin
C:\Program Files (x86)\SEGGER\JLink\
microcontroller flash driver
C:\Program Files (x86)\Nordic Semiconductor\nrf-command-line-tools\bin
COM port terminal
C:\Program Files (x86)\teraterm
This should be enough.
Phase 1 Upload firmware to Target
This can be done with this script
IF "%1"=="" (
echo set dflt SDK ver
) ELSE (
echo set dflt SDK from argument
echo Artefact hex: [%ARTEFACT_HEX%]
echo Project Dir:%project_dir%
set FlashTool="C:\Program Files (x86)\Nordic Semiconductor\nrf-command-line-tools\bin\nrfjprog.exe"
set options=--iface USB --family NRF53 --coprocessor CP_APPLICATION --program %ARTEFACT_HEX% --log --chiperase --verify --reset
call %FlashTool% %options%
call %tools_dir%\launch_terminal.bat 9 %baudrate% %project_name%
call %tools_dir%\launch_terminal.bat 10 %baudrate% %project_name%
After running this script, it will download the firmware to the nrf5340 microcontroller.
Phase 2 Starting the GDB Server (Back-End)
GDB server is a utility that interrogates the debugger programmer. It is she who communicates with the device. You need to start the GDB server. Here is the launch script for this utility.
set GDBServerOpt = -select USB -device nRF5340_xxAA_APP -endian little -if SWD -speed 400 -ir -LocalhostOnly -logtofile -log "C:\projects\code_base_workspace\code_base_firmware\tool\GdbServerLog.txt"
set GDBServerDir="C:\Program Files (x86)\SEGGER\JLink\"
call %GDBServerPath% %GDBServerOpt%
We need to install the communication interface of the programmer and the microcontroller that we want to debug. In this case, it is the SWD interface.
J-link will ask you to select a microcontroller
the list of supported chips is very large
In my case, the nrf5340 chip
GDB server J-Link launched locally. Programmer (S/N: 1050009032) connected via USB. Target is connected to the programmer by SWD.
here is the full log of the waiting gdb server
SEGGER J-Link GDB Server V7.66a GUI Version
JLinkARM.dll V7.66a (DLL compiled May 19 2022 15:13:27)
—–GDB Server start settings—–
GDBInit file: none
GDB Server Listening port: 2331
SWO raw output listening port: 2332
Terminal I/O port: 2333
Accept remote connection: localhost only
Generate logfile: off
Verify download: off
Init regs on start: off
Silent mode: off
Single run mode: off
Target connection timeout: 5000ms
——J-Link related settings——
J-Link Host interface: USB
J-Link script: none
J-Link settings file: none
——Target related settings——
Target device: Unspecified
Target interface: SWD
Target interface speed: 4000kHz
Target endian: little
Connecting to J-Link…
J-Link is connected.
Firmware: J-Link OB-nRF5340-NordicSemi compiled Dec 3 2021 15:46:49
Checking target voltage…
Target voltage: 3.30V
Listening on TCP/IP port 2331
Connecting to target…
connected to target
Waiting for GDB connection…
Now the GDB server just waits for a connection on a TCP port: 2331
On the nRF5340-DK debug board, there is built-in the J-Link programmer and the green LED is constantly on there, from the debugger.
Phase 3 Launching the GDB Client (Front-End )
If the GDB server can be metaphorically considered the Back-End (th), then the Front-End part for step-by-step debugging is the GDB client. Here is the script to run it.
"C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe" --help
"C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe" C:/ncs/v2.1.0/nrf/applications/nrf5340_dk_audio_w/build/headset_app/zephyr/zephyr.elf
GBD client was able to pick up *.elf file with debug symbols
It’s time to connect to the GBD server. This is done by the team.
target remote localhost:2331
Connected. Now the execution of the firmware is paused. HeartBeat LED is not blinking, UART-Shell is not responding.
To start the firmware, you need to type the command continue
Now the HeartBeat LED is blinking. The CLI is responding. The LED on the programmer also blinks only somehow aperiodically.
In order to stop the program execution again, press Ctrl+C in the client’s GDB terminal.
Now you can set a breakpoint. I’m interested in the function bool hw_init(void)
(gdb) break hw_init
You can view the list of set breakpoints with the command
(gdb) info b
This is what is displayed now
Restarts the program for execution. Team c
But here’s the problem. The breakpoint is set inside the initialization function, which has already worked out a long time ago. You need to somehow reload trarget so that the initialization works again.
This is where another terminal will help us. This is a command line terminal over UART implemented right inside the firmware on the Target (e). I will just ask the device to reboot with a command in the UART CLI
-> kernel reboot cold
and the device actually reboots
At the same time, I didn’t even take my hands off the keyboard. And here the debugger hooked the bool function hw_init(void). This is called the synergy of GDB and UART-CLI. Success.
You can execute one line of code with the command n. You can view the contents of a local variable with the command info locals
You can get inside the function with the command s.
Of course, there is a history of commands in the GBD client console and you can use the up and down arrows on the keyboard to type commands that have already been used in the past.
To exit the GDB client, type the q command in its console. Target is still suspended. Therefore, it is also necessary to close the GDB server. Only after that Target will become resumed and continue to execute its code.
Subsequently, you can start the GDB server and the GDB client with a single script.
set GDB_CLIENT="C:\Program Files (x86)\GNU Arm Embedded Toolchain\10 2021.10\bin\arm-none-eabi-gdb.exe"
Summarizing the above, the whole process can be explained with this simple diagram
Also a small cheat sheet on the most common commands of the console GDB client
Show current function arguments
List all breakpoints
show function call stack
Step over functions
Execute the rest of the current function. Step out of the current function.
Step into functions
Print the value of a variable count
Continue execution up to the next breakpoint or until termination if no breakpoints are encountered
Delete breakpoint number n
show local variables
target remote localhost:2331
Connect to J-Link
Set a breakpoint at the beginning of function
I will also update the GDB client command registry here
As you can see, debugging via the console in GDB is not difficult at all. Therefore, all sorts of cyclopean and expensive IDEs for step-by-step debugging are not really needed, as it were. The console is even better in the sense that attention is focused on the essence (code), and not on the straziks from the design of the IDE. You can generally debug without code, simply by receiving only *.elf file by mail.
At the same time, the overhead costs for installing the Toolchain (a) for step-by-step debugging are minimal and everything is absolutely free.
How does the JTAG breakpoint setting mechanism work under the hood?