Particle Debugging with Eclipse

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:

  1. Set breakpoints in code and view source.
  2. See the call stack.
  3. View variables.
  4. Single-step through code.

Features

Install local gcc-arm toolchain

  • 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.

Connect the debugger device

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.

Particle Programmer Shield

The Particle Programmer Shield is designed to fit the Photon. You can also use it with an Electron, with the extra pins hanging over the edge. It is available on the Particle store.

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.

Particle Programmer Shield

Select computer operating system:

Inexpensive mini SWD device

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.

SWD Color Photon Pin
VCC Orange No Connection
SWCKD Yellow D6
GND Green GND
SWDIO Blue D7

Mini SWD device

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 -
3 TRST TRSTN D3 GND -
4 GND GND GND GND GND
5 TDU JTDI D5 GND -
6 GND GND - GND -
7 TMS_SWDIO JTMS D7 SWDIO D7
8 GND GND - GND -
9 TCK_SWCLK JTCK D6 SWCLK D6
10 GND GND - GND -
11 NC NC - NC -
12 GND GND - GND -
13 TDO_SWO JTDO D4 TRACESWO -
14 GND GND - GND -
15 NRST NRST - NRST -
16 GND GND - GND -
17 NC NC - NC -
18 GND GND - GND -
19 VDD NC - NC -
20 GND GND - GND -

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.

2 4 6 8 10 12 14 16 18 20
1 3 5 7 9 11 13 15 17 19
notch

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.

ST-LINK SWD Color Pin
1 VCC Orange 3V3
4 GND Green GND
7 SWDIO Blue D7
9 SWSCK Yellow D6

ST-LINK/V2

Building Debug Builds

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.

src directory layout

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

Restoring modular firmware

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:

particle update

Eclipse Install

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.

Select computer operating system:

Eclipse Install GNU ARM C/C++

These steps are required for Windows, Mac and Linux to use the gcc-arm compiler and GDB with the Photon and Electron.

Add Software

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.

Unsigned Content Warning

Create an Eclipse project - All Operating Systems

  • From the File menu select New then Makefile Project with Existing Code.

Eclipse New Project

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.

Eclipse New Project

  • 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.

Project Properties

  • Select C/C++ Build.

You should not need to change any setting on this page, but here's what it should look like:

Project Properties

  • Select Build Variables (1) in the C/C++ Build preferences.
  • Use the Add button (2) to add two new build variables:
Name Type Value
cross_make String make
cross_prefix String arm-none-eabi-

It should look like this when done:

Project Properties

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:

target/${ProjName}.bin 

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).

Error Configuration

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).

Build Project

After successfully building and flashing, you'll see a message in the CDT Build Console pane at the bottom of the window.

Successful Make

Some errors are pretty self-explanatory:

No DFU capable USB device available
make[1]: *** [../build/module.mk:113: program-dfu] Error 74
make: *** [Makefile:16: target/tinkerbreak.bin] Error 2
make[1]: 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.

Set up OpenOCD Debugging

From the Run menu select Debug Configurations.

Debug Configuration

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.

New Debug Configuration

In the Main tab, in the C/C++ Application: box, enter:

target/tinkerbreak.elf

If you've named your project differently, the pattern is still target/project_name.elf.

Debug Main Configuration

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.

Debug Target Configuration

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.

Debug History

  • Click Yes to switch to a Debug perspective.

Debug History

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.

Debug View

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.

Debug View

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.

Viewing the various consoles

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.

Debug Consoles

Changing the gcc optimization level

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.

Debugging Examples

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).

Debug Break

You can click the Resume button (3) to continue executing code on the Photon. It should reconnect to the cloud and resume breathing cyan.

Setting breakpoints

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.

Setting breakpoint

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.

Hit breakpoint

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.

Hit breakpoint

Returning to C/C++ Perspective

To switch between the Debug and C/C++ Perspective, use the perspective icons in the toolbar in the upper right corner of the screen.

Perspective Selector

Switching between platforms

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.

Electron platform

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.

Debugging a P1 using SWD

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.

Settings for P1

  • 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.