PlatformIO Community

Debugging FreeRTOS on STM32 Bluepill

Hello PlatformIO Experts,

I am working on a project based on a Bluepill Board (STM31F1) using libopencm3. I have an initial version of my software working. I have been using an ST-Link v2 Clone to upload and debug my software. To support the next set of features, I have extended my project with FreeRTOS 10.4.1. After some more tinkering, I have my software roughly running with occasional crashes. This is sort of expected, as I believe that I haven’t gotten the configuration of FreeRTOS just right yet. To find these problems, I want to use the Debugger. However, my software does not run under the Debugger when using FreeRTOS.

The Debugger does break at the start of main(). Afterwards, no breakpoints are hit and I can’t observe any action of the software. If I pause the debugger, the CPU is in the blocking_handler of FreeRTOS. If I start the identical software without the debugger, the software starts to run until I drive it into the crash I’m trying to debug.

I have tried this on two projects. There is a branch on my own project: https://github.com/deltaphi/c6021light/tree/freertos
To ensure that the debugger problem is not caused by the bugs in my project itself, I have also tried this with Bastiaan Schaap’s Reference Project for Bluepill/libopencm3/FreeRTOS/PlatformIO: https://github.com/bjwschaap/platformio-libopencm3-freertos/
On Bastiaan’s project, I observed the same problem.

I also tried to add http://openocd.zylin.com/#/c/2347/4/contrib/rtos-helpers/FreeRTOS-openocd.c to my project - no success.

How can I make PlatformIO successfully debug my Project?

Thanks!
Damian

When debugging the reference project, does it stop at the task1 function if a breakpoint is set there or inside (e.g. at gpio_toggle())

It is normal to be unable to go beyond the main() function because after calling vTaskStartScheduler(); in main(), the code under it should never be reached (unless the task scheduler stops due to a fatal error). Instead if should starte executing the tasks functions given to it and switch between them.

Hi @maxgerhardt,

I can step through main() until vTaskStartScheduler() begins. I am aware that the code after this should never be reached.

Breakpoints in the task are never reached. Similarly, other Breakpoints in configASSERT or the sys_tick_handler() are also not reached.

EDIT: I just did a test run again. A slight correction to the above: The breakpoint in vPortSVCHandler does get hit. After that, there is nothing happening anymore.

Regards
Damian

Hi,

even more tinkering reveals that I actually end up in the hard_fault_handler - for an unknown reason.

Also, I previously believed that having the debugger attached or not was the main difference between “works” and “broken”. However, there is another factor: The build process. I observed that a release build reliably runs whereas a debug build will reliably not run, even when the software is not running under a debugger (e.g., Start Debugging, Stop Debugging, Press the reset button on the board without reflashing).

I got verbose logs of pio run -v and pio debug -v and ran a diff on them, but no difference sprung to my eye.

Regards
Damian

Can you set build_type = debug (docs) in the platformio.ini and upload normally to the microcontroller (no debugging)? Does it crash now too?

Hi @maxgerhardt,

good idea! I tried it and the result is as expected:

  • build_type = debug breaks
  • build_type = release works

You can go back and forth between the two. I would conclude that it really is about the build, not about the debugger.

Regards
Damian

Hi,

so now that we know that this is about the result of a debug build and not about attaching the debugger, I tried to dig deeper. After all, my main problem is that my custom project is buggy and I’m trying to debug that - which I still can’t do.

I used https://interrupt.memfault.com/blog/cortex-m-fault-debug as a guide to progress further. Looking at the Fault Status Registers of the CPU tells me that my hard_fault is a usage fault where NOCP is set - apparently indicating that a coprocessor instruction is supposed to be executed. The HardFault Status Register tells me that it is a FORCED hard fault - which isn’t telling much. I’m guessing that this is due to the fact that there is no dedicated handler for Usage Faults.

Further using the debugger to examine the program counters, I see the following:

p/x $lr
$1 = 0xfffffff9

This indicates that the call stack behind $msp is responsible for the fault. That stack reads:

p/a *(uint32_t[8] *)$msp
$3 = {0x20005000
, 0x8c9f
, 0x3
, 0x20000000 <uxCriticalNesting>
, 0x200000
, 0x8000603 <xPortStartScheduler+126>
, 0x8000444 <prvPortStartFirstTask+28>
, 0x1000000
}

Hopefully this tells someone something. I also checked out $psp:

p/a *(uint32_t[8] *)$psp
$2 = {0x0
, 0x0
, 0x0
, 0x0
, 0x0
, 0x80003f9 <prvTaskExitError>
, 0x8000c20 <task1>
, 0x1000000
}

To the untrained eye, this looks like task1 tries to exit. However, it is currently beyond me how you exit from the implemented infinite loop.

Any ideas on what may be going wrong here are much appreciated!

Also: Has anyone else experienced the problem in the Context of PlatformIO that debug builds with FreeRTOS don’t work?

Thanks!
Damian

I’ll try and have a look at why no task breakpoint is being hit in debug build type mode, I have a bluepill and STLink here.

Acctually can you change that from unused to used? When reading the docs, unused just makes GCC not issue a warning if the variable is unsed, but used will force the compiler to emit this symbol in all circumstances.

Hi @maxgerhardt,

i tried:

static void task1(void *args __attribute__((used))) {

The result was the same. The release build works, the debug build breaks. I end up with the same error bits set and the same stacks as in my previous post.

Regards
Damian

Hi,

I believe I have found the problem and a solution - although I don’t understand why my solution solved anything.

In an earlier post, I stated that I had compared the compiler flags bing used and that no obvious differences sprang to my eye. However, while toying around with the build settings for the debug build yesterday I noticed that the example project uses quite a lot of build_flags and that PlatformIO uses dedicated debug_build_flags for the debug build - the regular build_flagsare completely ignored. Grasping at straws at this point, I just copied all the build_flags over to the debug_build_flags - and suddenly the debug build worked!

Armed with this knowledge and now a somewhat bigger straw I decided to systematically analyze whether I could point to any one of the build flags as the culprit (or savior, rather). I disabled all build flags and enabled them one by one, uploading the project each time to test it out. And it turns out: As soon as I set -Os, the project immediately started to work. Going back to the build logs I saw that the rehease build would always use -Os, whereas the debug build would use -Og. And sure enough: Using -Ogin the release build breaks the project as well.

Now, debugging a size-optimized executable makes for some funky debugging experience with the program counter going all over the place or breakpoints being hit only half of the time - but at least, the breakpoint in task1() was hit occasionally. So setting -Osfor the debugging usecase does not really help me with my actual proejct.

Fortunately, there are two ways to integrate libopencm3 and FreeRTOS. One is to use the forwarder-file opencm3.c proposed by Warren and used by Bastiaan. The other is to #define the names of the FreeRTOS function implementations to the names that libopencm3 expects:

#define vPortSVCHandler sv_call_handler
#define xPortPendSVHandler pend_sv_handler
#define xPortSysTickHandler sys_tick_handler

I verified that I hadn’t made a typo by patching libopencm3 on my disk to temporarily un-weak these symbols. Without the defines you get a linker error, with the defines the project links. And even better: with this approach, the project works reliably in both release build and debug build and the debugger hits the breakpoints just as you would expect. I recreated the experiment with an unmodified libopencm3 as currently provided by PlatformIO as well as with my actual project. They all work now. I created both an Issue and a Pull Request on Bastiaans’ repo.

However, I don’t understand why this optimization option causes this issue. While I would be ready to believe that size optimization causes the linker to replace the forwarding functions with the call to the actual function right away, why does having the forwarder then possibly present in the debug code break the project?

Regards
Damian