Using openocd and a ST-Link V2 to debug betaflight on a STM32F3

Disclosure: This post may contain affiliate links, meaning I get a commission if you decide to make a purchase through my links, at no cost to you.

By | December 27, 2016

During development of my tinyfish FC i used the st-util gdb bridge for debugging. I had some issues using it, mainly unstable usb communication etc. Therefor i switched to openocd. Recently i have read a lot about the very nice blackmagic probe, the best part is that the firmware can be flashed into the cheap st-link devices (~$3 only!) from aliexpress. I will try this out once my second device arrives. But for now i will stick with the stock st-link v2 firmware and use openocd for debugging. First of all compile your betaflight firmware with debugging symbols. For my tinyfish target i simply call:

fishpepper:~/src/betaflight\$ make DEBUG=GDB TARGET=TINYFISH

In order to run Openocd with the st-link hardware we will use the following configuration file (save it as tinyfish.cfg):

# This is a TINYFISH board with a single STM32F303 chip
source [find interface/stlink-v2.cfg]
set WORKAREASIZE 0x4000
source [find target/stm32f3x.cfg]
# use hardware reset, connect under reset
# reset_config srst_only srst_nogate
reset_config none separate

When you start openocd with this configuration by calling

fishpepper:~/src/betaflight\$ openocd -f tinyfish.cfg

you should see something like

Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v17 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.250895
Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints

If you do not connect the reset pin (as i did) you should make sure the loaded firmware does NOT use the swd I/O pins… My tinyFISH firmware does so (SBUS), i had to change the target config not to use these I/Os during my debug session.

Once openocd is running you can fire up your remote gdb session:

fishpepper:~/src/betaflight\$ ./tools/gcc-arm-none-eabi-5_4-2016q3/bin/arm-none-eabi-gdb --eval-command="target remote localhost:3333" obj/main/betaflight_TINYFISH.elf

Now you can start debugging. For this session i will debug my issue with the blackbox readout feature. Therefore will will set two breakpoints, one at the msp call to the flash readout routine, and the other one at the flash readout function:

(gdb) mon reset
(gdb) b serializeDataflashReadReply
(gdb) b flashfsReadAbs
(gdb) c

Now fire up betaflight configurator and start to read out the blackbox log. This will trigger the breakpoint, let’s have a short look:

Breakpoint 1, serializeDataflashReadReply (dst=0x10001f64, address=0, size=4096, useLegacyFormat=false) at ./src/main/fc/fc_msp.c:534
 534 uint16_t readLen = size;

(gdb) bt
#0 serializeDataflashReadReply (dst=0x10001f64, address=0, size=4096, useLegacyFormat=false) at ./src/main/fc/fc_msp.c:534
#1 0x0801702a in mspFcDataFlashReadCommand (dst=0x10001f64, src=0x10001f58) at ./src/main/fc/fc_msp.c:1231
#2 0x08018446 in mspFcProcessCommand (cmd=0x10001f58, reply=0x10001f64, mspPostProcessFn=0x10001f54) at ./src/main/fc/fc_msp.c:1888
#3 0x08026378 in mspSerialProcessReceivedCommand (msp=0x200039c4 <mspPorts>, mspProcessCommandFn=0x80183e1 <mspFcProcessCommand>) at ./src/main/msp/msp_serial.c:165
#4 0x0802642e in mspSerialProcess (evaluateNonMspData=MSP_EVALUATE_NON_MSP_DATA, mspProcessCommandFn=0x80183e1 <mspFcProcessCommand>) at ./src/main/msp/msp_serial.c:199
#5 0x08014e2c in taskHandleSerial (currentTimeUs=47137577) at ./src/main/fc/fc_tasks.c:107
#6 0x08029bca in scheduler () at ./src/main/scheduler/scheduler.c:281
#7 0x0800ed68 in main_step () at ./src/main/main.c:581
#8 0x0800ed78 in main () at ./src/main/main.c:590

Fine, we are exactly where we wanted to be. Let’s analyze what happens here: The fc tries to read out size=4096 bytes of data at address=0 and will store the results at the destination pointer 0x10001f64 in ram. Fine. Let’s continue:

(gdb) c

This will trigger the second breakpoint, let’s have a look again:

Breakpoint 2, flashfsReadAbs (address=0, buffer=0x20003b63 <outBuf.7936+7> "", len=4096) at ./src/main/io/flashfs.c:471
 471 if (address + len > flashfsGetSize()) {

(gdb) bt

#0 flashfsReadAbs (address=0, buffer=0x20003b63 <outBuf.7936+7> "", len=4096) at ./src/main/io/flashfs.c:471
#1 0x080157ee in serializeDataflashReadReply (dst=0x10001f64, address=0, size=4096, useLegacyFormat=false) at ./src/main/fc/fc_msp.c:552
#2 0x0801702a in mspFcDataFlashReadCommand (dst=0x10001f64, src=0x10001f58) at ./src/main/fc/fc_msp.c:1231
#3 0x08018446 in mspFcProcessCommand (cmd=0x10001f58, reply=0x10001f64, mspPostProcessFn=0x10001f54) at ./src/main/fc/fc_msp.c:1888
#4 0x08026378 in mspSerialProcessReceivedCommand (msp=0x200039c4 <mspPorts>, mspProcessCommandFn=0x80183e1 <mspFcProcessCommand>) at ./src/main/msp/msp_serial.c:165
#5 0x0802642e in mspSerialProcess (evaluateNonMspData=MSP_EVALUATE_NON_MSP_DATA, mspProcessCommandFn=0x80183e1 <mspFcProcessCommand>) at ./src/main/msp/msp_serial.c:199
#6 0x08014e2c in taskHandleSerial (currentTimeUs=47137577) at ./src/main/fc/fc_tasks.c:107
#7 0x08029bca in scheduler () at ./src/main/scheduler/scheduler.c:281
#8 0x0800ed68 in main_step () at ./src/main/main.c:581
#9 0x0800ed78 in main () at ./src/main/main.c:590

So the flashfsReadAbs function is called. Let’s step through this function:

(gdb) n
477 flashfsFlushSync();
(gdb) n
479 bytesRead = m25p16_readBytes(address, buffer, len);
(gdb) n
481 return bytesRead;
(gdb) p bytesRead
$7 = 4096

Fine, seems like the spi function returned successfully. Let’s have a peek into the data:

(gdb) x/32b buffer
0x20003b63 <outBuf.7936+7>: 0x48 0x20 0x50 0x72 0x6f 0x64 0x75 0x63
0x20003b6b <outBuf.7936+15>: 0x74 0x3a 0x42 0x6c 0x61 0x63 0x6b 0x62
0x20003b73 <outBuf.7936+23>: 0x6f 0x78 0x20 0x66 0x6c 0x69 0x67 0x68
0x20003b7b <outBuf.7936+31>: 0x74 0x20 0x64 0x61 0x74 0x61 0x20 0x72

Those bytes are the first 32 bytes stored inside the buffer pointer. If we continue we will see that serializeDataflashReadReply is called again and again for the same address. This is exactly what i saw on the scope: Reading address 0x000000 again and again…

So the problem is somewhere else. After further investigation it turned out that the betaflight code called CDC_Send(…, uint8_t size) with a size value that was no uint8_t. Therefor a value like 4096 gets squashed to ZERO -> endless loop.

See https://github.com/betaflight/betaflight/issues/1899 for more details.

Happy debugging 😉

Leave a Reply

Your email address will not be published. Required fields are marked *