Skip to content

Debug via SWD & JTAG

sommermorgentraum edited this page Apr 23, 2024 · 18 revisions

If you just wanna do general simple debugging (just printing var/vec/label/symbol etc instead of setting break point, checking register etc), you may also use Debug with serial guide, which is maybe easier for beginners. ref.

(1) Debug via SWD

Coding without the possibility of debugging can be quite time consuming because you are mostly blind. The portapack is an embedded system and requires some work for the comfort.

Introduction to the Arm Debug Interface (ADI)

The Arm Debug Interface (ADI) is a specification of both the hardware interface and the logical interface for debugging between a host and one or more devices. Currently, most processors are implementing ADIv5. The ADI defines a debug access port (DAP), made up of a debug port (DP) and access ports (APs). The DAP is the primary “unit” of the debug interface. A given device will have one debug port, which handles the physical connection with a debugger, as well as a number of access ports that provide access to system resources such as debug registers, trace port registers, a ROM table, or system memory. Thus the DP includes the physical connection (JTAG, SWD) as well as some built-in registers, and each AP has its connections to the system, and a number of its own registers.

image

image

NXP ARM Cortex-M4/M0 dual-core microcontroller LPC 43xx Debug Configuration

Our specific LPC 4320 dual-core microcontroller supports JTAG and Serial Wire Debug (SWD), serial trace, eight breakpoints, and four watch points.

From NXP LPC 43xx product datasheet :

image

image

Following other additional documents from NXP Community

3A011E7D-4A5F-4062-9AF7-3244E47C159B

image

2A6D5C59-E021-41EB-9565-7212B0A63D1B

Hackrf debugging references

Based on latest Hackrf User's guide There are various debugger options for the LPC43xx. It introduces the following ones in chapter 19 (also good source to check),we just attach here the index picture,

image

Talking about Black Magic Probe , the author mentioned ,

"It is possible to attach to the M0 instead of the M4 if you use jtag_scan instead of swdp_scan , but the Black Magic Probe had some bugs when trying to work with the M0 the last time I tried it."

What you need

  • Black Magic Probe
    • Or something similar (bluepill (STM32F103C8T6) works as well, just add BLUEPILL=1 when you compiling the blackmagic). HackRF has a list of hardware that should work. In this example we will be using the Jeff Probe with the portapack H2+ in an aluminium case.

The “Black Magic Probe” or the derivative cost down version “Jeff Probe” are both a combined hardware & software projects.

(1) At the hardware level, they implement both, JTAG and SWD interfaces for ARM Cortex A- series and M-series microcontrollers.

(2) At the software level, they provide a “gdb- server” implementation and Flash programmer support for ranges of micro- controllers of various brands.

The embedded software of the “Black Magic Probe” and “Jeff Probe” are both an open-source project, and both has its own GitHub project development.

grafik

  • Tools. Lots of tools. I think I used all tools there are:
    • Solder Iron
    • Tweezers
    • Screwdrivers
    • A drill
    • Vise
    • Multimeter
    • A file
    • Good eyes
    • Steady hands
    • exacto knife
  • Your portapack
  • Pin header 2x5 1.27mm

grafik

The things arrived. What now?

First we need to disassemble the portapack. Lots of screws. You probably already know how to do it. Then locate the debug port LPC_JTAG.

grafik

Note: The arrow points to PIN 1. Always ensure that you connect your cable correctly, typically by matching PIN 1 marked on the board to the red stripe on the cable.

If you have a Portapack without battery, you can solder the 10 x pin header JTAG connector in the designed A-side , and leave the flat FFC 10 x pin cable inside.

image image image

But if your device has integrated battery , there is no space, and you now have two options:

  1. Solder the pin header and never be able to close the portapack again.

grafik

  1. Solder it to the backside (B-side).

grafik grafik grafik

The pins are now backwards. What now?

Note: Now that the pin header is on the back side it is never possible to correctly connect your cable, because the pins are reversed. Instead of 1,2,3,4,5,6,7,8,9,10 it is now 2,1,4,3,6,5,8,7,10,9.

grafik grafik

Now we disassemble the ribbon cable to correct the back-'wardity'.

grafik grafik grafik grafik grafik

Keep pairs of wires together. Insert them back but twist every pair.

grafik grafik

Now press it back together with a vice, but careful.

grafik

Do a continuity test with a multimeter to check if they are all connected. You can use the 'not yet? soldered' pin header to help reach inside. The ends of the wire are easy reachable.

grafik

First test

With the pin header in place and the ribbon cable ready you can run a first test. Attach the hackrf and the battery to the portapack and power on the portapack. The Jeff Probe will also be powered on with some LEDs.

grafik grafik

Now you can put your portapack back together with the exception of the back plate.

grafik

Finishing touch

You now have two options:

  1. Leave the backplate off while you are debugging. The backplate will fit, but you should place something non-conductive like insulating tape between the plate and the pins.

  2. Make the debug port always accessible by drilling a hole in the backplate.

grafik grafik

Congratulation. The value of your portapack has now increased by whatever you spend on tools and hardware and of course by the time you spend ;)

Use gdb (client)

image

Now that everything is set up you can connect gdb to the LPC4320.

  1. Start arm-none-eabi-gdb

    • NOTE: you should now start the ADS-B RX app for this to work

  2. depending on your system connect to the probe by running the command:

    • (gdb) target extended-remote \.\COM4

      • (on windows check device manager for the right COM port)
    • (gdb) target extended-remote /dev/ttyACM0

      • (on linux dmesg or lsusb will help) (example dmesg in linux, when attached Jeff Probe to the USB)

      image

    • (After connecting the Black Magic or Jeff Probe to a USB port, two virtual serial ports appear. One of these is for gdbserver and the other for the generic TTL‐level UART. Since the Black Magic/ Jeff Probe implements the CDC class, and Linux has drivers for CDC class devices built-in, no drivers need to be set up.

    • The device paths for the serial ports are /dev/ttyACM* where the “*” stands for a sequence number. For example, if the Black Magic or Jeff Probe is the only virtual serial port connected to the workstation, the assigned device names will be /dev/ttyACM0 and /dev/ttyACM1.)

  3. now check the fw version of the probe

    • (gdb) monitor version (example of Jeff Probe answer)

      image

  4. scan for devices

    • (gdb) monitor swdp_scan
  5. attach to the device

    • (gdb) attach 1
  6. load the symbols

    • (gdb) file build/firmware/baseband/baseband_adsbrx.elf
  7. look around

    • (gdb) info threads
    • (gdb) info registers
    • (gdb) tui enable
    • (gdb) continue

Limitations and Issues

  • M0 seems inaccessible through SWD , (as it is indicated on top , to debug M0 only can be done using JTAG, pending to check).
  • VSCode integration is rather buggy
  • When you stop debugging you have to power down your jeff probe for it to work again.

Integration into VSCode

NOTE: Using WSL on Windows has some issues forwarding the usb device to the linux host. I used vscode on Windows with 'Remote - SSH' extension to an old laptop running ubuntu where i could attach the jeff probe directly.

First create your .vscode/launch.json

At the end , it will appear here , (/opt/portapack-mayhem/.vscode/launch.json)

image

But if you never set up that debug probe before, it will be empty , as that picture,

image

Therefore we need to create it ,selecting in left side "Debug" ,and then "create a launch.json file

image

It will generate a default template

image

that we need to replace by below example contents ,

{
    "version": "0.2.0",
    "configurations": [
    {
        "name": "(gdb) JTAG probe",
        "type": "cppdbg",
        "request": "launch",
        "miDebuggerPath": "arm-none-eabi-gdb",
        "targetArchitecture": "arm",
        "program": "${workspaceRoot}/build/firmware/baseband/baseband_adsbrx.elf",
        "cwd": "${workspaceRoot}",
        "setupCommands": [
            // use logging for troubleshooting
            //{"text": "set logging file ${workspaceRoot}/build/arm-none-eabi-gdb.log"},
            //{"text": "set logging on"},
            {"text": "file '${workspaceRoot}/build/firmware/baseband/baseband_adsbrx.elf'"},
            {"text": "target extended-remote /dev/ttyACM0"},
            {"text": "monitor swdp_scan"},
            {"text": "attach 1"},
        ],
        "launchCompleteCommand": "None",
        "externalConsole": false,
    }
    ]
}

(Note : if you are using linux, you may start to get that permission error, If that is the case, you will need to find out the user and group of that file :

"ls -la /dev/ttyA*" , and later add your user into the group of that file , by "sudo gpasswd -a your_user_name dialout" )

image

Setting breakpoints and attaching without breakpoints does not work very well. So we have to do it by hand. In code.

Add this macro somewhere in firmware/baseband/proc_adsbrx.cpp

#define HALT_IF_DEBUGGING()                              \
  do {                                                   \
    if ((*(volatile uint32_t *)0xE000EDF0) & (1 << 0)) { \
      __asm("bkpt 1");                                   \
    }                                                    \
} while (0)

And call it inside a loop.

grafik

Now:

  • make firmware
  • flash firmware to portapack
  • start the ADS-B RX app
  • attach the jeff probe to usb and portapack
  • start the debugging profile

grafik

You can control the debug steps by the following top button debug control GUI .

image

It should stop at the breakpoint now. Press F10 to step through the code.

Some notes about unified JTAG / Serial Wire Debug (SWD) interface.

ARM provides SWJ-DP (serial wire/jtag debug port) via its CoreSight technology which maps SWD pins onto JTAG's clock and reset lines. SWJ-DP therefore allows using both protocols on the same physical connection though not necessarily at the same time or with the same programmers as JTAG and SWD would have to be multiplexed in time.

The Serial Wire Debug protocol (SWD) is designed as an alternative to the JTAG protocol, for microcontrollers with a low pin count. It is part of the ARM Debug Interface specification version 5, abbreviated as ADI5. At the physical layer, it needs two lines at the minimum (plus ground), as opposed to five for JTAG. These two lines are the clock (SWCLK, driven by the debug probe) and a bidirectional data line (SWDIO). Tracing output goes over a third (optional) line: TRACESWO, but using an unrelated protocol (independent of SWCLK)

D3A45F32-884D-485D-99CF-018FDCEFEEC3

JTAG requires 4 signal lines

image

Following this Serial Wire Debug (SWD) link While the JTAG-DP is a common and familiar example of a debugging interface, most relevant to our discussion is the JTAG alternative defined for Arm devices, the Arm Serial Wire Debug (SWD). SWD was developed as a two-wire interface for Arm-core devices with limited pin counts. As microcontrollers tend to be quite dense in peripherals, most Cortex-M devices will implement SWD in place of full JTAG to save pin real-estate. So how does this protocol work?

SWD is specified in the ADIv5 specification (chapter B4). The TDI and TDO pins from JTAG are replaced by a single bidirectional pin called SWDIO; the test mode select (TMS) pin is removed entirely; and the clock (TCK) remains the same (relabeled CLK or SWCLK). Thus SWD uses only two pins compared to the four pins used in JTAG. To make this work, SWD relies on the repetitive nature of JTAG operations: the state machine is manipulated, data is shifted in or out, and the process repeats. The difference with SWD is there is no state machine. Instead, commands are issued serially over SWDIO, and then that same pin is used for reading or writing data.

There are three phases to SWD communication: packet request, acknowledgment, and data transfer. During packet request, the host platform issues a request to the DP, and this must be followed by an acknowledge response. If the packet request was a data read or data write request, and there was a valid acknowledgement, the system enters the data transfer phase, where data is clocked in (write) or clocked out (read) through SWDIO. After a data transfer, the host is responsible for either starting a packet request, or driving the SWD interface with idle cycles (clocking SWDIO LOW). A parity check is applied to packet requests and data transfer phases.

The particulars of SWD are best understood by seeing a timing diagram, shown in Figure 2.

image Figure 2. Timing diagrams showing read and write operations for Serial Wire Debug.

Before a microcontroller’s SWD port is serviceable, an initialization sequence must be performed, part of which is to switch the protocol from JTAG to SWD. Some ARM Cortex microcontrollers do not support JTAG, but the protocol requires that the JTAG-to-SWD switch is still performed.

(2) DEBUG VIA JTAG M0 / M4 USING MULTI FT232H USB TO UART, (TO SERIAL / PARALLEL PORTS).

(1) We need to buy a MULTI PURPOSE USB TO UART FT232H MODULE (RS232, RS422 o RS485), USB a FIFO, USB a FT1248, USB a JTAG, USB a SPI, USB a I2C))

image

Features Chip: FT232H

High-Speed to UART/FIFO IC: 480Mb/s

Full Speed: 12Mbits/Second

UART Transfer Data Rate: 12Mbaud

USB to Asynchronous 245 FIFO Mode for Transfer Data Rate: 8 Mbyte/Sec USB to Synchronous 245 Parallel FIFO Mode for Transfers: 40 Mbytes/Sec8. Supports a proprietary half duplex FT1248 interface with a configurable width, bi-directional data bus (1, 2, 4 or 8 bits wide). CPU-Style FIFO Interface Mode Simplifies CPU Interface Design. Fast Serial Interface Option. FTDI’s Royalty-Free Virtual Com Port (VCP) and Direct (D2XX) drivers eliminate the requirement for USB driver development in most cases. Adjustable Receive Buffer Timeout. Interface: USB, JTAG, UART, FIFO, SPI, I2C Pin Number: 24 Pins

(2) And prepare the proper interface cables to connect 4 wires + GND from the Hackrf connector .

image

From this 10 pin Hackrf JTAG connector , we need to connect (4 wires + GND) to the FT232H module. (the yellow pins, in below JTAG table pinout)

5 x Pinout:

  • AD0 - TCK

  • AD1 - TDI

  • AD2 - TDO

  • AD3 - TMS

  • GND - GND

image

I used a bridge Hackrf female cable connector PCB , to make those easy connections .

image

image

image

image

image

image

image

(3) Make sure to have installed the OpenOCD package.

At this point , if you want to confirm that the FT232H has the correct 5 wires connection to the Hackrf cable JTAG and that you have a correct OpenOCD installation , please power up the Hackrf and connect the FT232H module to the USB , and send the following linux command from terminal : openocd -f /usr/share/openocd/scripts/interface/ftdi/um232h.cfg -f target/lpc4350.cfg

And you should get that similar answer , where it detects M4 and M0 ,

"Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
cortex_m reset_config vectreset
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 500 kHz
Info : JTAG tap: lpc4350.m4 tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x4)
Info : JTAG tap: lpc4350.m0 tap/device found: 0x0ba01477 (mfg: 0x23b (ARM Ltd), part: 0xba01, ver: 0x0)
Info : [lpc4350.m4] Cortex-M4 r0p1 processor detected
Info : [lpc4350.m4] target has 6 breakpoints, 4 watchpoints
Info : [lpc4350.m0] Cortex-M0 r0p0 processor detected
Info : [lpc4350.m0] target has 2 breakpoints, 1 watchpoints
Info : starting gdb server for lpc4350.m4 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for lpc4350.m0 on 3334
Info : Listening on port 3334 for gdb connections"

It is matching with the expected M0, M4 JTAG ID,

image

then , now you are ready to proceed with vscode debug integration,

(4) Modify your vscode debug launch.json (/opt/portapack-mayhem/.vscode/launch.json) adding those two below Bernd’s module config ini blocks (to config the M4 or M0 FT232H JTAG debug) .

Note , it can be added to the previous SWD Jeff debug module block , so in this example , my launch.json file , will have 3 config blocks , and every time before debugging , you will need to connect Jeff SWD or FT232H JTAG , and select the proper one, and click play

image

When you select M4 debug → you will need to update in the launch.json file the proper .elf file , according to the selected proc_m4_file.ccp to be debug. (example : "program": "${workspaceRoot}/build/firmware/baseband/baseband_weather.elf",)

But when you select M0 debug , → no need to update the application.elf file, because there is only a common one to all mayhem application.

Then you just need to set up for m0 a max. 2 x breakdown points and 1 watch point , compile and debug it .(for m4 target, it has 6 breakpoints, 4 watchpoints)

And following other key Bernd’s recommendations, when you encounter problems with the optimizer optimizing everything away what you need for troubleshooting, you can disable it for one function:

void attribute((optimize("O0"))) foo(unsigned char data) { // unmodifiable compiler code }

launch.json example for openocd with ft232h

{
    "version": "0.2.0",
    "configurations": [
    {
        "name": "(gdb) OpenOCD m4 baseband",
        "type": "cppdbg",
        "request": "launch",
        "program": "${workspaceRoot}/build/firmware/baseband/baseband_sd_over_usb.elf",
        "args": [],
        "cwd": "${workspaceRoot}",
        // "environment": [
        //     {
        //         "name": "PATH",
        //         "value": "${env:PATH}"
        //     }
        // ],
        "externalConsole": false,
        "MIMode": "gdb",
        "miDebuggerPath": "arm-none-eabi-gdb",
        "targetArchitecture": "arm",
        "debugServerPath": "openocd",
        "debugServerArgs": "-f interface/ftdi/um232h.cfg  -f target/lpc4350.cfg -c \"gdb_breakpoint_override hard\"",
        "serverStarted": "Listening on port [0-9]+ for gdb connections",
        "filterStderr": true,
        "filterStdout": false,
        "launchCompleteCommand": "None",
        "postRemoteConnectCommands": [
            {
                "description": "Target Remote Device on Port 3333",
                "text": "target extended-remote :3333",
                "ignoreFailures": false
            },
            {
                "description": "Respect Hardware Limitations",
                "text": "set remote hardware-watchpoint-limit 2",
                "ignoreFailures": false
            },
            {
                "description": "Respect Hardware Limitations",
                "text": "set remote hardware-breakpoint-limit 4",
                "ignoreFailures": false
            },
            {
                "description": "Shutdown GDB Server on GDB Detach",
                "text": "monitor [target current] configure -event gdb-detach { shutdown }",
                "ignoreFailures": false
            },
        ],
        "stopAtConnect": false,
        "logging": {
            "exceptions": true,
            "engineLogging": false,
            "moduleLoad": true,
            "programOutput": true,
            "trace": false,
            "traceResponse": false
        },
        "useExtendedRemote": true
    },
    {
        "name": "(gdb) OpenOCD m0 application",
        "type": "cppdbg",
        "request": "launch",
        "program": "${workspaceRoot}/build/firmware/application/application.elf",
        "args": [],
        "cwd": "${workspaceRoot}",
        // "environment": [
        //     {
        //         "name": "PATH",
        //         "value": "${env:PATH}"
        //     }
        // ],
        "externalConsole": false,
        "MIMode": "gdb",
        "miDebuggerPath": "arm-none-eabi-gdb",
        "targetArchitecture": "arm",
        "debugServerPath": "openocd",
        "debugServerArgs": "-f interface/ftdi/um232h.cfg  -f target/lpc4350.cfg -c \"gdb_breakpoint_override hard\"",
        "serverStarted": "Listening on port [0-9]+ for gdb connections",
        "filterStderr": true,
        "filterStdout": false,
        "launchCompleteCommand": "None",
        "postRemoteConnectCommands": [
            {
                "description": "Target Remote Device on Port 3334",
                "text": "target extended-remote :3334",
                "ignoreFailures": false
            },
            {
                "description": "Respect Hardware Limitations",
                "text": "set remote hardware-watchpoint-limit 1",
                "ignoreFailures": false
            },
            {
                "description": "Respect Hardware Limitations",
                "text": "set remote hardware-breakpoint-limit 2",
                "ignoreFailures": false
            },
            {
                "description": "Shutdown GDB Server on GDB Detach",
                "text": "monitor [target current] configure -event gdb-detach { shutdown }",
                "ignoreFailures": false
            },
        ],
        "stopAtConnect": false,
        "logging": {
            "exceptions": true,
            "engineLogging": false,
            "moduleLoad": true,
            "programOutput": true,
            "trace": false,
            "traceResponse": false
        },
        "useExtendedRemote": true
    },
    ]
}

Start here

How to collaborate

User manual

Developer Manual

Clone this wiki locally