Debugging Particle Photon/P1/Electron code using JTAG/SWD and Eclipse
This FAQ describes how to use Eclipse, a free IDE for Windows, Mac, and Linux, along with OpenOCD, to do source-level debugging of code running on a Particle Photon, P1, or Electron.
Some of the things you can do:
- Set breakpoints in code and view source.
- See the call stack.
- View variables.
- Single-step through code.
While you can still use these instructions, a much easier way to install Particle Workbench. It provides an automated way to install local development tools and do source-level debugging without the extreme hassle of the method described below.
- Install a gcc-arm toolchain for local building for Windows, Mac or Linux
The instructions are in the local build FAQ. This is necessary because the cloud compilers are not configured to generate debugging builds.
In this tutorial, we'll use three different debugger devices, but they all work in the same way so you can choose whatever device you prefer.
Some debuggers support SWD (Single Wire Debug) mode, JTAG mode or both to talk to the STM32 microcontroller.
If you need your device to be in a breadboard while you debug you can look at using one of the other interfaces.
You will need to use two separate micro USB cables. One connects to the Programmer Shield to access the JTAG features, and the other connects directly to the Photon.
The Photon USB cable isn't entirely necessary because the shield can power the Photon, but since the Eclipse debug process below by default uses DFU to load the debugging code onto the Photon, it's easier if you use a direct USB connection to the Photon for that.
You can find inexpensive USB stick that support SWD (Serial Wire Debug) at places like Amazon and eBay for US$12 or less. They work great with the Photon, P1, and Electron, requiring only two data lines and ground.
They are compatible with the official ST-LINK/V2, however these devices generally only support SWD.
Here's an example on how to connect one brand of such an interface; verify that yours is the same before wiring.
- USB connector side away from you
- Debug header facing you
- Chips on top
|VCC||SWIM||GND||RESET||Top Row (SWIM)|
|VCC||SWCKD||GND||SWDIO||Bottom Row (SWD)|
You will only use the bottom row (SWD). The SWIM connections are for the STM8 processors, not the STM32 processors used in the Particle devices.
In this picture, it's wired as follows. Note that the color code is not official, it's just what it happened to be with the female-to-male jumper wires that I used.
It's a little unclear whether the VCC pin (orange) should be connected for the mini devices. I generally leave it unconnected for the mini devices but connected for the actual ST-LINK/V2 real device.
You can also use an official ST-LINK/V2 device. It supports both JTAG and SWD mode.
If you have a full 20-pin JTAG connector (0.1"x0.1" pitch, in 2x10 layout) on your board, that's the easiest way to connect the device. The connector is keyed so you can usually only insert it one way.
You can also connect the ST-LINK/V2 using jumper wires; this is more practical for SWD as it only requires 3 or 4 pins.
|Pin||Function||JTAG||JTAG Pin||SWD||SWD Pin|
|1||VAPP||MCU VDD||3V3||MCU VDD||3V3|
|2||VAPP||MCU VDD||-||MCU VDD||-|
You only need to connect one GND line, though it's best to connect all of them if possible to reduce noise on the ribbon cable if you are making a board with a 20-pin JTAG connector.
If you're just using SWD like you would with the mini SWD device, you should connect it as follows:
The pins on the connector are numbered this way when you have the programmer positioned so the logo is upright and the notch is on the bottom of the 20-pin connector.
As above, the color code is not official, it's just what I happened to have on the female-to-male jumper wires that I used.
Important: Make sure your software doesn't use the debugger pins: D6 and D7 for SWD, or D4, D5, D6, and D7 for JTAG. This means you can't use the D7 user LED while using the debugger.
The firmware compiled through the Web IDE, Desktop IDE or CLI are don't contain debug information. You'll need a local gcc-arm toolchain to make builds that are compatible with the debugger.
For debugging using JTAG/SWD you create special monolithic debug builds. They're monolithic in that they are a single file that contains the system and user firmware instead of the usual modular binaries (system parts and user part). The disadvantage of the monolithic binaries is that they are large, maybe 480 Kbytes for a simple program that might only be 6 Kbytes normally!
Whenever you switch between debug and non-debug builds, or monolithic to non-monolithic, you must add the clean option to make.
In this example I created a new directory in src called "tinkerbreak" that contains tinkerbreak.cpp, a modified version of tinker that responds to the Particle function "break" to break into the debugger under software control.
To build manually (not using Eclipse), you'd use a command line like this, based on the directory layout in the gcc-arm installation instructions linked to above.
cd firmware/main make clean all program-dfu PLATFORM=photon MODULAR=n DEBUG_BUILD=y USE_SWD_JTAG=y APPDIR=../../tinkerbreak
In the tinkerbreak/target directory are a few files that you may need:
- tinkerbreak.bin - monolithic firmware build (can flash with the Particle CLI or dfu-util)
- tinkerbreak.elf - The debugging binary needed by gdb
To restore normal modular firmware, to allow for user flashing independent of Device OS firmware and normal OTA flashing, put the device in DFU mode and:
particle flash --usb tinker
After it reboots, immediately put it back in DFU mode, then:
This is the abbreviated method that will allow you to use the source debugger in Eclipse. Setting up a project that has full code analysis and auto-completion is much more complicated, and will be the topic of a separate FAQ. For this version, we just turn off displaying some errors.
These steps are required for Windows, Mac and Linux to use the gcc-arm compiler and GDB with the Photon and Electron.
From the Help menu select Install New Software...
Click Add then Archive and select the zip file of GNU ARM Eclipse.
- Select the packages
You will need the following:
- GNU ARM C/C++ Cross Compiler
- GNU ARM C/C++ OpenOCD Debugging
- GNU ARM C/C++ STM32Fx Project Templates
If you're using a different debugging hardware like the Segger J-Link, be sure to select that as well. There are additional instructions at the official download site.
- Proceed through the rest of the installation steps
You may get a warning for unsigned content. You should select OK and proceed with the installation.
- From the File menu select New then Makefile Project with Existing Code.
In this example, we'll create a project for the tinkerbreak code we used in the command line example. Select that and make sure Cross ARM GCC is selected.
- Download Makefile and save it into your project directory.
It will be the same for all projects. You will need to right click on the project name (tinkerbreak) in the Project Explorer and select Refresh for it to display in Eclipse.
# Assumption: These variables will be set in the environment # $(APPDIR) # $(FIRMWARE) # $(PATH) # $(PLATFORM) # $(TARGETBIN) # Optionally set $(MAKEOPTS) for things like MODULAR=n DEBUG_BUILD=y USE_SWD_JTAG=y all : $(TARGETBIN) # Use the wildcard function explicitly, because just using a dependency on .h files will # fail if the project doesn't contain any .h files. This will work either way. source := $(wildcard *.cpp) $(wildcard *.h) $(TARGETBIN) : $(source) cd "$(FIRMWARE)/main" && make all PLATFORM=$(PLATFORM) APPDIR="$(APPDIR)" $(MAKEOPTS) clean : cd "$(FIRMWARE)/modules" && make clean all PLATFORM=$(PLATFORM) APPDIR="$(APPDIR)" $(MAKEOPTS) .PHONY: all clean
There must be a single tab before each of the two indented cd commands. The make will fail if there are spaces, which may happen if you copy and paste from the web page.
- Right click on tinkerbreak in the Project Explorer and select Properties. Most of the settings that you need to modify are located here.
- Select C/C++ Build.
You should not need to change any setting on this page, but here's what it should look like:
- Select Build Variables (1) in the C/C++ Build preferences.
- Use the Add button (2) to add two new build variables:
It should look like this when done:
After making a change in the preferences, it's a good idea to click Apply (3) to make sure the changes are saved.
- Select Environment (1) in the C/C++ Build preferences.
APPDIR is the path to your source directory, the one you previously created a Makefile in. For Windows, make sure you use forward slashes (/) as the directory separator, not backward slashes commonly used in Windows. The reason is that the path is passed to GNU make, which handles the directory separators in a more Unix-like manner.
FIRMWARE is the path to the firmware release you downloaded earlier as part of the installation of the local gcc-arm build chain. For Windows, make sure you use forward slashes (/) as the directory separator.
MAKEOPTS are the options passed to make. When using the debugger with a monolithic debugging build, you typically use:
MODULAR=n DEBUG_BUILD=y USE_SWD_JTAG=y program-dfu
For the simplest non-debug modular build, the MAKEOPTS can be an empty string.
PATH specifies the PATH to pass to the shell running the Make. This may vary from computer to computer and operating system.
For Windows, insert at the beginning, C:/cygwin64/bin;C:/cygwin64/usr/local/bin or where you have your Cygwin directory. This is necessary because Cygwin tools are not added to the Windows System PATH because they can cause odd problems when Linux-style tools conflict with DOS-style tools (like find), but Eclipse needs to be able to call tools like make.
For Linux and Mac, you should make sure that the gcc-arm local build chain is in the path. For example, /usr/local/bin/gcc-arm/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin.
PLATFORM is the platform you're building for, typically photon, p1, or electron.
TARGETBIN is the relative path to the built firmware binary. Setting it to:
will automatically change it to match your project name. In this example, it would be target/tinkerbreak.bin.
- Click Apply (3) when done.
- Change error settings
This is necessary because the Device OS firmware is not fully integrated into Eclipse in this FAQ, so the lack of definitions causes many lines to be flagged as errors.
Select C/C++ General then Code Analysis (1). Select Use project settings (2). Scroll down and uncheck the box for Syntax and Semantic Errors (3).
Click Apply then OK to close the Properties window.
- Put the Photon in DFU mode (blinking yellow)
- Try a build. Right click on the project name (1) and select Build Project (2).
After successfully building and flashing, you'll see a message in the CDT Build Console pane at the bottom of the window.
Some errors are pretty self-explanatory:
No DFU capable USB device available make: *** [../build/module.mk:113: program-dfu] Error 74 make: *** [Makefile:16: target/tinkerbreak.bin] Error 2 make: Leaving directory '/cygdrive/c/Users/IEUser/Documents/src/firmware/main'
The firmware binary (target/tinkerbreak.bin) in this case will still be fine, but the Photon won't have that firmware automatically loaded on it, of course. If you don't want to flash the device automatically, remove the
program-dfu option from C/C++ Build then Environment in the variable MAKEOPTS.
From the Run menu select Debug Configurations.
If the Run menu is nearly empty, from the Window menu select Perspective then Open Perspective then C/C++ and the Debug Configurations menu should appear.
Select GDB OpenOCD Debugging and click the New button.
In the Main tab, in the C/C++ Application: box, enter:
If you've named your project differently, the pattern is still target/project_name.elf.
In the Debugger tab, in the Config options enter:
-f interface/stlink-v2.cfg -f target/stm32f2x.cfg
If you're using a different debugger, specify that interface instead. For the Particle Programmer Shield, enter:
-f interface/ftdi/particle-ftdi.cfg -f target/stm32f2x.cfg
Then click Debug.
You only need to set the Debug configuration once; the next time you want to run using the debugger, from the Run menu select Debug History then tinkerbreak Default.
Make sure you select the project name Default option; selecting project name.elf will try (and fail) to launch the local debugger, not the SWD debugger. Likewise, clicking on the debug toolbar icon may try to launch the local debugger, as well.
- Click Yes to switch to a Debug perspective.
You should see a screen similar to the one below after a few seconds. A ST-LINK/V2 Mini SWD stick should be alternating red and green, and the Photon will probably have a solid white status LED.
Of importance is that the execution is stopped at 0x0 (1) and you need to click the Resume button (2) to continue.
This is what the screen looks like when firmware is running. The Photon should be breathing cyan and the a ST-LINK/V2 Mini SWD stick should be still be alternating red and green.
If you see errors in the debugging console like these make sure your code is not using pins D6 and D7 (SWD), or D4, D5, D6 and D7 (JTAG).
Examination failed, GDB will be halted. Polling again in 1500ms Warn : Invalid ACK 0x7 in JTAG-DP transaction Polling target stm32f2x.cpu failed, trying to reexamine Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Warn : Invalid ACK 0x7 in JTAG-DP transaction Examination failed, GDB will be halted. Polling again in 3100m
Some more techniques are described below in the Debugging Examples section.
Eclipse keeps multiple console views, one for each process. So there's one for the build, one for running openocd, one for running gdb, and so on.
By default, the standard Makefile optimizes for size, which makes complete sense. When debugging code, however, this can be annoying because it will make it impossible to set breakpoints at certain locations in the code, you won't be able to step some individual lines of code, and some variables won't be able to be inspected by the debugger.
- Edit firmware/build/arm-tools.mk
The file typically will have something like this near the top:
CFLAGS += -g3 -gdwarf-2 -Os -mcpu=cortex-m3 -mthumb
Simply remove the -Os (optimize for size). This is probably best done only for monolithic debug builds, since you may run out of space for some system parts in a modular build without optimization.
Assuming you've set things up as described above, the tinkerbreak application will respond to a Particle function break. In a separate command prompt window, run a command like this, replacing test4 with the name or device ID of your Photon.
particle call test4 break
The screen will change to show the call stack (1) and the line where the breakpoint occurred (2).
You can click the Resume button (3) to continue executing code on the Photon. It should reconnect to the cloud and resume breathing cyan.
To set a breakpoint in the code, double-click in the blue bar to the left of the line number. A small icon with a check mark and a tiny blue circle will appear.
In this example, I set a breakpoint on line 51.
Then, in a separate command prompt window, issue a command like:
particle call test4 div 100
The Eclipse window should change to something like the following.
Execution is suspended because of a breakpoint at line 51 (1).
The code at that line is highlighted (2).
The value of div is 100. The value of result is random because it hasn't been set yet.
Finally, click the Step Over toolbar icon (4) to proceed to to the next line of code.
Now you can see that that the next line of code is selected (1) and the value of result has been set appropriately (2).
Click the Resume button (3) to continue execution.
To switch between the Debug and C/C++ Perspective, use the perspective icons in the toolbar in the upper right corner of the screen.
To switch between Photon and Electron, for example, right click on the project name, select Properties then C/C++ Build then Environment and edit the PLATFORM setting. In this case, I changed it to electron.
You should always delete the target directory as well to make sure all of the necessary files are rebuilt. It's not necessary to do a Clean, which takes a very long time to run, as long as you delete the target directory.
If you've built a board with a P1, it's highly recommended that you include as many debugging ports as possible. However, as long as you include D6, D7, and GND for SWD, you can use OpenOCD and Eclipse for debugging.
- Right click on the project name, select Properties then C/C++ Build then Environment and edit the PLATFORM variable, setting it to p1 (lowercase). Also remove program-dfu from the MAKEOPTS variable, as you can't use the program-dfu option if you don't have USB.
Delete the target directory if you've changed the platform setting.
Right click on the project name and select Build Project.
Open two command prompt windows. In one, run the OpenOCD server in telnet mode. The directory may vary depending on version and operating system; other examples are shown above.
cd "/Applications/GNU ARM Eclipse/OpenOCD/0.10.0-201510281129-dev/scripts" ../bin/openocd -f interface/stlink-v2.cfg -f target/stm32f2x.cfg -c "telnet_port 4444"
- In the other, telnet to the OpenOCD server and issue command such as these to program the binary.
telnet localhost 4444 > reset halt > flash protect 0 5 8 off > program /Users/rickk/Documents/src/tinkerbreak/target/tinkerbreak.bin verify 0x08020000
Close both windows to stop this instance of OpenOCD.
From the Run menu in Eclipse, select Debug History then tinkerbreak Default. You should have a working debug session as above.