5 steps to setup and use a debugger with the Particle Photon
Bring out the big guns when you need to get to the root cause of a crash
So you are developing on the excellent Photon from Particle and you’ve run into a dead end: your code crashes and print statements don’t help you solve the issue. To see what’s going on inside your program while it’s running you need to bring out the debugger (also known as the Programmer Shield).
Here’s what I did to set up and use the debugger on my project.
Note: I use Linux. If you use Mac OSX or Windows and these instructions don’t work, reach out to jvanier on the Particle community forum and I’ll update this article.
1. Buy the hardware
You need special hardware to debug the Photon. It’s for sale as the Programmer Shield on the Particle store.
2. Install the software
You first need to be able to compile the Particle firmware locally, so clone the firmware repository on GitHub and follow the Getting Started guide there.
Next install OpenOCD (a free and open On-Chip Debugger). It will be a bridge between the Photon and GDB, the GNU debugger.
- Download the latest OpenOCD source code.
- Download the Particle debugger config file particle-ftdi.cfg and copy it to tcl/interface/ftdi/ in the OpenOCD source code folder.
- On Linux, install the libusb library
sudo apt-get install libusb-1.0.0-dev
- Open a terminal to the OpenOCD source folder and run
(on Linux you need sudo make install)
- On Linux, copy UDEV rules files to avoid having to run openocd with sudo.
sudo cp contrib/99-openocd.rules /etc/udev/rules.d/
- On Mac OSX, you need to make changes to the USB programmer driver bundled with OSX. See the Programmer Shield repository on GitHub for more information.
3. Compile the firmware
Don’t use pins D3 to D7
Since JTAG uses the pins D3 to D7, your firmware must not use these pins, including the blue user LED (D7) otherwise OpenOCD will not connect. (There is an alternate debug mode called SWD that uses only D6 and D7)
In order to enable the debugger, you must compile the firmware with USE_SWD_JTAG=y
In the firmware folder, check out the develop branch if you want to debug with the latest and greatest features.
git checkout develop
Connect the USB cable to the Photon (not to the Programmer Shield), put the Photon in DFU mode, compile and flash the firmware.
make clean all program-dfu PARTICLE_DEVELOP=1 PLATFORM=photon USE_SWD_JTAG=y MODULAR=n
Why use MODULAR=n? With this option, only one big executable will be created instead of 2 system parts and 1 user part. It’s a lot easier to debug. Just remember to reflash your device with the modular firmware after you are done debugging.
4. Start the debugger
Connect the USB cable to the Programmer Shield.
Pro tip: you can connect both the device and the Programmer Shield to your computer at the same time to avoid unplugging cables all the time!
Start OpenOCD for the target. The command below will listen for GDB connections on port 3333. The last bit is to help with threads (type info threads in GDB).
openocd -f interface/ftdi/particle-ftdi.cfg -f target/stm32f2x.cfg -c "gdb_port 3333" -c "\$_TARGETNAME configure -rtos FreeRTOS"
If OpenOCD can’t connect, make sure again that your code doesn’t use pin D7, the blue LED.
You can send OpenOCD commands by running telnet localhost 4444. A good command to try is reset. Try help or search online for more commands.
Start GDB (GNU Project debugger) with an ELF file to load the symbols (mapping of memory addresses to names)
arm-none-eabi-gdb -ex "target remote localhost:3333" ../build/target/main/platform-6-m/main.elf
Using the debugger
Now comes the hard part: figuring out why your program crashes.
A lot has been written about debugging with GDB, so here’s my little summary.
- Set breakpoints in your code with break <function>
- List breakpoints with info breakpoints and remove them with delete
- Resume execution with c (continue) and interrupt execution with Ctrl-C (this can be very useful to find an infinite loop).
- Go the next line with s (step) or n (next). Next skips over function calls. Run until the current function returns with fin (finish).
- Show the value of a variable with p <variable> and all locals with info locals
- Print a stack trace with bt
- Send OpenOCD commands using monitor <command>
- Reset the Photon and pause immediately at start with monitor reset halt. You can then set breakpoints and start the program with continue
I also find it useful to use the GDB split screen mode using layout split since it shows the source code, the assembly and the command line at the same time. Change which window gets arrow keys with Ctrl-X O
It’s possible to flash through the Programmer Shield. In fact, it’s the only way to flash the bootloader. However since the goal of this article was to give a step-by-step goal to be able to trace through a program I didn’t focus on flashing with the Programmer Shield. One important note is that to flash with the Programmer Shield the JTAG debugging protocol must be enabled in the microcontroller. JTAG is enabled when the firmware is compiled with USE_SWD_JTAG=y or when the device is in DFU mode.
In order to debug a problem that only happens in the modular firmware, you can either load user-part.elf, system-part1.elf or system-part2.elf. What if you need to load all 3 in the debugger?
Here’s a method to be able to debug all parts of the modular firmware together.
Load all symbols in the same GDB session:
- Extract the address of each module .text section
- Load each ELF into GDB with add-symbol-file, passing the address above (for some reason GDB doesn’t use the address in the ELF file as a default and forces you to find that address on your own)
When setting breakpoints, multiple symbols will often match because of the dynalib structure. Set a breakpoint as normal then disable the ones you don’t want.
For example, break setup
(gdb) break setup
Breakpoint 1 at 0x806ed2c: setup. (3 locations)
Find out the one you need with info break
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y <MULTIPLE>
1.1 y 0x0806ed2c in system_part2_init at src/module_system_part2.c:96
1.2 y 0x0806ed2d <setup+4294967295>
1.3 y 0x080a02f0 in setup() at src/application.cpp:40
Disable all except the one in application.cpp
(gdb) disable 1.1 1.2
Program received signal SIGINT, Interrupt.
setup () at src/application.cpp:40
38 /* This function is called once at start up ----------------------------------*/