Debugging on STM32 with ChibiStudio: the ultimate guide


A debugger is a powerful tool that gives us the chance to interact with the microcontroller at run-time. It allows to dynamically change the content of CPU registers, to read\write the RAM memory and to flash\erase the NVM memory of the microcontroller. The main purpose of a debugger is to discover and fix anomalies in the functional behavior of our firmware.

Anyway, there is no silver bullet: it requires some time to master the debugger learning some tricks. As usual, the time spent learning how to debug could make your day: it will reduce the effort required to develop and test your firmware.


In this article, we are going to explore in details all the debugging features offered by ChibiOS and ChibiStudio for the firmware development with the STM32 Development Boards. For those who are reading for the first time about ChibiStudio, it is an Eclipse-based IDE for the development with embedded systems(for more info see the article Developing on STM32: introducing ChibiStudio). If you are confused about the difference between a bootloader and a debugger, this article worth a read.

To comprehend some of the topics of this article it is necessary to have a grip on development on the STM32 platform with ChibiStudio. Our area Getting started with STM32 and ChibiOS offers many articles that explain how to tinker with these microcontrollers.

Background notions

The debugger

When we talk of debugger we usually refer to a blurry mix of things. Let us dispel some doubt to get a better perception of what is moving behind the scenes while we are performing daily activities with our debugger. At glance, here a list of functionalities offered by a debugger.

It can interact with the flash memory of a microcontroller allowing to:

  • Flash\erase a firmware into it without using a bootloader and in a more efficient way

It can interact directly with the CPU of a microcontroller and its registers allowing to:

  • Break the execution of the code at any moment (Stop\Resume)
  • Break the execution of the code when the Program Counter reaches a certain address (Breakpoint)
  • Execute the code one instruction at a time (Step by step execution)
  • Change the content of CPU registers for debugging purpose

It can interact directly with the RAM of a microcontroller allowing to:

  • Break the execution of the code when a certain memory address changes its value (Watchpoint)
  • Read\write static and automatic variables
  • Read\write memory-mapped registers like, for example, MCU’s peripherals (TIMs, SPIs, ADCs, UARTs, and so on)

For the sake of knowledge, I would like to define all the parts that are involved to call it a debugger. In the following figure, there is a block diagram of it:

Debugger chain block diagram
MCU’s hardware debug extensions

Let us start from the MCU that in this specific context will be called Target. the interaction at a deep level with the CPU and the memories can be ensured only by a specifically designed hardware. Indeed, ARM-Cortex architecture provides a properly designed ARM Debug Interface which contains information about hardware extensions for advanced debugging features. This hardware is implemented inside every STM32 as they are actually ARM Cortex-M cores. The debug extensions allow the core to be stopped either on a given instruction fetch (Breakpoint) or data access (Watchpoint). When stopped, the core’s internal state and the system’s external state may be examined. Once the examination is complete, the core and the system may be restored and program execution resumed.

The JTAG/SWD transport method
ARM 20-pin connector
Pin map of the ARM 20-pin connector

The interaction between the MCU and the external world should be ensured through a sort of communication channel. The transport method is specified in the ARM Debug Interface and is the JTAG. This transport method borns from the homonym standard born in the late 1980s to unify the testing procedure of hardware PCBs. The JTAG transport protocol is actually a sort of serial communication protocol and it comes with some specific JTAG connector: The ARM JTAG 20-pin connector (shown in the figure).

Actually, many pins of this connector are only ground connectors and except the power supply, only 5 lines are used by the JTAG protocol. Even more, some of these lines are optional. The JTAG connector is actually suitable for another transport method known as Serial Wire Debug or SWD which requires only three wires one of which is optional. This method is specified as an alternative to the JTAG in the ARM Debug Interface even if it does not support some advanced features the JTAG does.

In the figure, it is possible to notice which are the JTAG lines as they are marked in Azure. The alternative in yellow instead represents the SWD lines.

The Debugger Probe

On top of the JTAG/SWD transport, there is a communication protocol and a hardware infrastructure that aims to put in communication the Target with the PC (from here and out the Host). This bridge is often known as Debugger Probe and it is the ensemble of hardware and firmware connected to the Target through an SWD/JTAG channel and to the PC through a USB port (or in some cases through an Ethernet port).

STLink V2 Debugger Probe

For example, the STLink shown in the figure on the right is a Debug Probe. Now, some of these debuggers are standalone: it means they are usually a piece of hardware in a plastic case with both a JTAG/SWD connector and a USB connector. Some other debuggers are integrated on the development kit PCB like it the case of the STM32 Development Board where the STLink is actually named Embed STLink debugger. In this case, the SWD/JTAG communication is provided by some PCB tracks while the PCB host a USB connector which is used both to connect the STLink to the PC and power-up the development kit.

The GDB server

On the host side, it is needed an application that configures the Debugger Probe allowing the user to exploit the debugging features through a set of commands. This is the case of the GNU Debug Server also known as GDB Server which is an application that runs on the PC. This application offers a command-line interface to the user usually over Telnet accepting some standard commands to address the probe.

For example, the OpenOCD is a GDB Server which supports many probes included the STLink and able to target many MCUs including almost every STM32 sub-family.

Interactions with the IDE

The IDE usually could be configured to interact with the GDB server through its interface. In the case of ChibiStudio, which basically is an Eclipse IDE which includes some plug-ins, external tools and pre-configured to be ready to use, this is done through the GDB Hardware Debugging plugin.

Debug windows + Resume
The resume button in ChibiStudio. In the debug window is possible to see that OpenOCD is running.

Thanks to these configurations it is possible to exploit all the debugging features interacting with the Graphical User Interface (GUI) of Eclipse acting on specific buttons, input areas or with its windows.

For example, during the procedure of flash and run, when we push the resume button actually the IDE is communicating with the GDB server issuing a target’s execution resume command. This simple action actually triggers the whole chain from the IDE to the MCU debug hardware cell.

The build procedure

The build procedure is something we did on a daily basis when developing. Anyway, the mechanisms behind are often ignored by many. Here we want to introduce some concepts every embedded developer should know, mostly because this impacts directly on how a debugger works.

The result of the build procedure is a file that contains the code as opcode instructions encoded as Binary: such file usually has the extension of .bin and it used called as firmware or executable file. Often the same file is even encoded as hexadecimal and saved with the extension .hex.

Build folder
the build folder: the result of a successfully make in ChibiStudio

Speaking in general, a CPU is able to load, fetch and execute instructions stored as binary code into its RAM memory. Anyway, the RAM is volatile thus to preserve the code at power cycle it is usually flashed inside a non-volatile memory such a Flash and loaded in RAM in pieces to be executed. Saying that we are flashing the code into an MCU we are actually stating that the code is stored inside the flash memory of the microcontroller.

To be precise, the code shall be loaded in RAM only when the fetch/execution time is shorter than the load time. MCUs are quite slower in term of instruction per seconds in comparison to a CPU. In this case, the load time is comparable to the fetch and execution time and the code could be executed directly from the flash memory.

Back to the firmware, in a .bin file, there is no clue between the opcode and the C-related symbolism: in other terms, there is no clear association between variables, functions, types declared in the C code and the related machine-level code. So, this file is not enough to exploit the debugging features. Indeed, OpenOCD, as many debuggers, uses a special file which contains both the executable code and the linked symbols: this file is known as Executable Linked File and it has the .elf extension. In ChibiStudio, the result of a build is a folder which contains the firmware as binary, hexadecimal and executable linked file as shown in the figure above.

Note that an executable linked file could be from 5 to 6 time larger than a binary and it contains information that could be used to do reverse engineering on your firmware (if you are sharing your application as a closed source).

Recapping we can state that:

To debug a firmware we need it as an Executable Linked File, a file which contains both the code and the linked symbols. When the debug session is over the symbols are useless thus the firmware could be shared a Binary. ChibiOS builds both a ch.bin and a ch.elf file.

The code execution and the code optimization

An ARM Cortex-M fires an IRQ at startup known as ResetHandler. The related ISR is a routine that configures the MCU, the memory areas and the clock tree before to jump in the main function: this is usually known as start-up code.

In an ARM Cortex M, the code is executed directly from flash, and all the code is stored in there. As you may know, the current instruction address is stored in the Program Counter, also abbreviated as PC.

Of course, the machine is able to fetch and execute only the machine code and this code could be translated one to one with Assembly code. Each C instruction is often composed of a set of Assembly instructions. Consequently, we can state that each C instruction is composed of one or more machine code instructions. Normally each step executes the whole C instruction.

When the compiler optimizations are enabled the machine code is reordered mixing pieces of the C instructions. This impacts directly on many aspects of the debugging.

To make the debugging more effective it is suggested to disabled code optimization.

In ChibiOS, this could be done acting on the makefile and changing the optimization level from O2 to O0. In the following figure, we could spot the differences in the assembly code derivated from the same application. For example, looking at the O2 box, we can notice how some instruction related to the test suite are moved in the middle of the code related to Thread 1 creation.

Comparison between optimization levels

Disabling the optimizations will directly impact on performances and firmware footprint. Restore the optimizations before to deploy the final firmware.

A big warning: in certain cases, it could happen that a software issue occurs when optimizations are enabled and disappears when optimizations are disabled. This kind of issues is the most difficult to debug because are often related to timing and every change in code flow could impact the test conditions. Fortunately, such bugs are quite rare when you are dealing with simple and common application scenarios.

A good way to proceed is to disable optimization mechanisms in the development phase, enable them before to deploy the final firmware, perform some final tests on the binary candidate to be released.

Exploiting the debugger

To access to the debugger features we need to launch the debugger and start the flash and run procedure. This is a very common task in ChibiStudio and it has been explained in Developing on STM32: introducing ChibiStudio, the introductory article about ChibiStudio.

As quick remind this is the task we are talking about

Since this procedure stores the firmware inside the flash memory, the firmware will survive to a power cycle restarting on the next power-on. Because of that, the procedure is often used to merely load the firmware into the MCU.

Actually, there is more than that behind this procedure because this is indeed the way to access the debugger features. When we launch the procedure, in the console windows, we will see some text in red: this is the log, from OpenOCD, of the erase and the flash of the MCU’s internal memory. When the flash is over, the MCU will run executing the start-up code and break the execution when at the beginning of main().

Eclipse automatically switches to the Debug perspective where the user has many additional windows that could be used to exploit debugging features

Step by step execution

The debugger is able to execute one instruction at a time breaking the execution at every step. Such operation is widely known as step by step execution and is one of the best ways to analyze the code workflow. In ChibiStudio such operations can be performed from the Debug perspective interacting with some buttons:

  • Skip All Breakpoints: when enabled the breakpoints are ignored.
  • Resume: resumes the execution continuously. This button is disabled and greyed out when the code is already running or the debugger is stopped.
  • Suspend: suspends the execution of the code. The code will break on the current instruction.
  • Terminate: terminates the current debugging session.
  • Step Into: executes the next instruction, if the next instruction is a function call enters inside it then breaks.
  • Step Over: executes the next instruction, if the next instruction is a function call executes all the function then breaks.
  • Step Return: executes the next instruction, if the execution is inside a function executes all the code until the last instruction before returning to the caller.
  • Instruction Stepping Mode: allows changing step by step execution between C and Assembly mode. In C mode each step is a C instruction, in assembly mode, each step is an assembly instruction.
The debugging buttons in ChibiStudio

The code could be executed step by step in C or in Assembly toggling the button Instruction Stepping Mode. In Assembly mode, it is very useful to rely on the Disassembly Window that contains the disassembly of the original code currently flashed in the MCU.

Note that the step by step execution is not so straightforward when compiler optimizations are enabled because, executing step by step, we will notice that the program counter will bounce up and down. This is due to the code reordering performed by the compiler and shown in figure 6.

Step by step execution is impacted by code optimization. Remember to disable code optimization while debugging as explained in chapter 2.3

Program counter and stack backtrace

When the execution is suspended there are few things you would take advantage of. First of all, as you may know, the current instruction address is stored in the Program Counter, also abbreviated as PC. In Eclipse it is possible to see the current instruction in the GUI because it is reported as an Arrow in the Editor window.

The program counter in ChibiStudio

When the execution is suspended the Debug windows reports the Stack Backtrace. This is a log of all the currently active stack frames and basically is a list of all the function calls that lead the program counter to the current instruction

The stack backtrace


The breakpoints are a very useful tool that allows breaking the code execution when the PC reaches a certain instruction. This mechanism relies on specific hardware that compares the current value of the PC with some special registers representing the breakpoints. These registers are limited in number and thus we have a limited number of breakpoints: the number of breakpoints is logged out by OpenOCD on startup.

In ChibiStudio a breakpoint could be set\removed clicking on the left border of a code line in the Editor Window.

A breakpoint in ChibiStudio

All the configured breakpoints are reported in the window Breakpoints that provides a quick interface to deal with them. It is also possible to temporarily disable all the breakpoints without removing them acting on the button Toggle Breakpoints.

Breakpoints are impacted by code optimization. Remember to disable code optimization while debugging as explained in chapter 2.3

Debugging with multithreading

One of the most common problems debugging with ChibiOS is the multithreading. Indeed when we stop the execution of the code using the button Suspend, we will break inside a certain thread (typically the Idle thread) and executing the code step by step (Step Over) we will be stuck in that thread with apparently no chance to move to other threads. The breakpoints help to work around the issue:

To jump from a thread to the other while debugging we could place a breakpoint inside the thread we want to jump in and resume the execution.

Pay attention to place the breakpoint on a piece of code that will be executed after the resume. Let us consider the following code as an example

Launching the debugger, the execution will break on halInit() because of a temporary breakpoint on the function main(). Executing step by step (step over) we will be able to enter into the while of the main but we will never reach Thread1.

To be sure to break in the Thread1 we should place the breakpoint inside its while. Indeed, let us assume we resume the application and after that, we place a breakpoint on chRegSetThreadName(): the execution will never break because the function has been already executed and there is no way to go back at that instruction unless we restart the application.

Attach without re-flash

It is possible to restart the debugging session without re-flash the firmware. This trick is very useful when we need to debug the same piece of firmware without changing the code. To do that we have to place a breakpoint at the beginning of our main (usually on the halInit call) and press the MCU reset button. The code execution will restart and the debugger will re-attach to the target breaking, the execution will be automatically stopped when the PC reaches the breakpoint.

Dealing with memory

The debugger is able to inspect RAM memory performing read/write operations on it when the code execution is suspended. Through this capability, the debugger offers many useful features.

The Memory, the Variables and the Expressions windows

It is possible to explore the raw memory through the Memory window. As all the peripherals registers and the flash memory are memory-mapped, through this window we can access and edit peripheral registers, RAM and, flash memory. Nonetheless, we are bonded by hardware constraints, for example, we cannot write read-only bit fields in peripheral registers or we cannot write ‘1’ inside the flash memory.

The problem with the Memory view is that data is organized as raw data and is up to the user to understand the meaning of each value. Anyway, there are some simpler ways to deal with memory depending on what we are trying to achieve.

If we want to deal with variables stored in RAM, then the Variables window is what we are looking for: it allows accessing all the variable available in the current scope. When the execution is stopped, the windows reports all the parameters passed to the current function and all the function’s automatic variables. It is possible to edit the value of the variables as well as monitor global variables, cast their type and perform common operations on them right-clicking inside the window area.

Another interesting tool is the Expressions window that normally is not visible. To show it select in the menu Windows->Show view. In this window is possible to digit the name of a variable we want to inspect but most interesting composing expressions with these variables. Note that the Expressions window is bonded to some obvious mechanism: for example, it is not to inspect the value of an automatic variable of a function when we are out of its scope. This because the automatic variable is created and destroyed within the function stack frame.

To deepen this topic, let us do an example. I wrote a dummy firmware for a Nucleo-64 in which I declared a 32-bit static unsigned integer variable with the name dummy_var and I have assigned to it the value 0xD425E4CF. Then I have placed a breakpoint in a thread after that the variable has been allocated and its value assigned.

Let us thus consider the following figure:

Inspecting Memory in ChibiStudio
The dummy_var seen from the Memory window

Through the Expressions window, I evaluated the expressions:

  • &dummy_var, to get the address of my static variable,
  • dummy_var, to get the value of the variable.

Now, in the Memory window, I added a monitor (just clicking on the green plus symbol) to the address of dummy_var to see how it appears in memory. I want to point out some facts that could be obvious for an expert user but complicated for a beginner:

  • The ARM Cortex-M is a 32-bit architecture: each memory address is a 32-bit value (e.g. 0x20001308)
  • Each memory location refers to a cell having a size of 1 Byte (8 bits)
  • A 32-bit variable will occupy 4 cells (e.g. our variable starts at the address 0x20001308 but occupies even the cells 0x20001309, 0x2000130A and 0x2000130B)
  • The STM32 is little-endian. The endianness refers to the order fo bytes in memory: little means that less significant bytes are stored at the lower address.

In the figure on the right, a diagram of how the variable is organized in the memory. As you can see the variable is swapped byte per byte.

The EmbSys Registers plugin

Finally, there is the EmbSys Registers window. ChibiStudio comes with a plugin know as EmbSys Registers View that is able to access the memory through the debugger in order to read\write the peripheral registers. So far nothing new, but the interesting difference is that this plugin is able to show the organization of the register including their bit-fields. The plugin relies on some XML files that contain the registers map and the description of bitfields.

The EmbSys Registers in ChibiStudio

It is possible to select the proper XML through the button in the top left corner (Settings) and here select the MCU by architecture, vendor and chip. For example, looking at the figure above, the Architecture is ARM_Cortex-M4, the vendor is STMicroelectronics, and the Chip is STM32F303. In the view, we are monitoring the register CR2 of USART1. You can notice that a monitored register is marked in green while an unmonitored register in simply black: it is possible to enable the monitor on a certain register double-clicking on it.

When a bitfield is reported in red (like ABREN in the figure) it means we have changed the value of the register through the plugin but the change is still not in place (the changes are put in place on the resume).

Additional debugging features offered by ChibiOS

In addition to standard features offered by the debugger, ChibiOS offers some additional debugging features. These features require some additional code which is embedded in the final firmware. As these features are resources consuming, they can be enabled\disabled through some preprocessor switches in chconf.h that could be easily identified because they are all suffixed as CH_DBG_

Considering the outcome of these debugging features, they could be organized into two macro groups:

  1. Those who prevent unexpected behaviors and stops the code execution in a controlled way allowing to perform debugging or mitigation actions.
  2. Those who collect additional data that could be used to evaluate system performances or optimize the application

Preventing unexpected behaviors

ChibiOS is designed following very formal design constraints. The RTOS aims to achieve high performances without wasting excessive resources, the HAL takes inspiration from Autosar, a software architecture oriented to the automotive market where safety and quality are not an option.

This kind of formalisms allows introducing some run-time checking mechanisms that help to ensure software consistency. Such kind of checks are organized in 4 groups and they could be enabled/disabled independently. When enables, if any of these checks fail, the scheduling is interrupted and the system enters in an infinite loop known as System Halt.

This controlled loop stops the kernel in a controlled way avoiding that the misbehavior corrupts data useful for debugging purpose. These mechanisms are very useful to address issues caused by the wrong usage of ChbiOS features and catch them during the development phase.

The system state check

The system state check relies on the concept of contexts implemented in ChibiOS/RT. Indeed, RT implements a Finite State Machine that is summarized in the next figure

ChibiOS\RT finite state machine

Quoting “The states have a strong meaning and must be understood in order to utilize the RTOS correctly:

  • Init. This state represents the execution of code before the RTOS is initialized using chSysInit(). The only kind of OS functions that can be called in the “Init” state objects initializers.
  • Thread. This state represents the RTOS executing one of its threads. Normal APIs can be called in this state.
  • Suspended. In this state all the OS-IRQs are disabled but Fast-IRQs are still served.
  • Disabled. In this state all interrupt sources are disabled.
  • S-Locked. This is the state of critical zones in thread context.
  • I-Locked. This is the state of critical zones in ISR context.
  • IRQ WAIT. When the system has no threads ready for execution it enters a state where it just waits for interrupts. This state can be mapped on a low power state of the host architecture.
  • ISR. This state represents the execution of the ISR code.

The states transitions are regulated by physical events like IRQs and call of system APIs. The states marked as double circles are the states where the RTOS APIs can be invoked.”

When the state check is enabled, if the system state changes when proper conditions are not met, the system is halted reporting an error condition.

The system state checks introduce some additional code in the kernel code reducing overall performances of the application. It is suggested to disable them when the development phase is over.

State checks could be enabled through the following switch in chconf.h

Reported panic conditions are:

  • SV#1, if the chSysDisable() is misplaced and called from an ISR or from a critical zone.
  • SV#2, if the chSysSuspend() is misplaced and called from an ISR or from a critical zone.
  • SV#3, if the chSysEnable() is misplaced and called from an ISR or from a critical zone.
  • SV#4, if the chSysLock() is misplaced and called from an ISR or from a critical zone.
  • SV#5, if the chSysUnlock() is misplaced and called from an ISR or from a critical zone.
  • SV#6, if the chSysLockFromISR() is misplaced and called from an ISR or from a critical zone.
  • SV#7, if the chSysUnlockFromISR() is misplaced and called from an ISR or from a critical zone.
  • SV#8, if the CH_IRQ_PROLOGUE() is misplaced and at ISR begin or called from a critical zone.
  • SV#9, if the CH_IRQ_EPILOGUE() is misplaced without a CH_IRQ_PROLOGUE() or not called at ISR end or called from a critical zone.
  • SV#10, misplaced I-class function not called from within a critical zone.
  • SV#11, misplaced S-class function not called from within a critical zone or called from an ISR.

ChibiOS is designed following an object-oriented approach: every driver in ChibiOS is an object that implements a well defined Finite State Machine. The state of the object changes on specific events like the completion of an operation or a driver function call. Now, a certain driver’s function could be called only if the driver lays in a specific state. The formal design makes possible to introduce the assertions.

For example, let us consider the following FSM from the SPI driver

The SPI Driver state machine

we can notice that the spiStart() could be invoked only from two states: SPI_STOP and SPI_READY. Thus, calling the spiStart() we can assert that the driver state should be SPI_STOP or SPI_READY. If this condition is not met we are violating the FSM and this could lead to misbehavior or a system crash.

Now looking a the code of the function we could notice that there is an assertion in line with the expectation

The code is full of calls like this. Such a call is actually a macro completely skipped if the related switch in chconf.h is off

The user could introduce assertions in its own application using the same macro

Note that the condition shall be verified to avoid the system halt. Thus it should be intended as: I can assert that this condition is true when the execution reaches that point otherwise something is wrong and the system should be halt.

To remove extra code branches, it is suggested to disable assertions when the development phase is over.


Another debugging mechanism implemented in ChibiOS is the checks. The working principle is similar to the assertion but the idea behind is conceptually different: while assertions verify that a condition surely true is actually confirmed, a check is usually adopted to verify those input parameters of a function are in line with the expectations.

For example, let us consider the spiStart(): the functions receives two parameters, two pointers to two objects that are the SPIDriver and the SPIConfiguration. In this case, we could check that these pointers are not null.

The code is full of calls like this. Such a call is actually a macro completely skipped if the related switch in chconf.h is off

The user could introduce checks in its own functions using the same macro

Note that the condition shall be verified to avoid the system halt. Thus it should be intended as: the function parameters shall match the condition otherwise something is wrong and the system should be halt.

To remove extra code branches, it is suggested to disable checks when the development phase is over.

Stack checks

In ChibiOS\RT a thread is a function that runs independently using its own memory area which is usually is allocated statically. This area is organized as a stack and grows from the top to the bottom. At the beginning of the stack, there is an area dedicated to the CPU registers storing which happens when the thread is suspended. The remaining area is filled with the stack frames of the functions called by the thread.

If the area is too small it could happen a common condition known as stack overflow: when this condition occurs a stack trespasses into the contiguous area which with high probability is another stack of another thread destroying its context. If this happens the system is able to work without any issue until the thread with damaged context is resumed: at this point, the behavior of the system will probably lead to an unhandled exception but in the worst case will execute random code provoking unexpected behaviors.

When the stack check is enabled, the system checks the stack consistency before to execute the context switch and in case of overflow, it halts the system in a controlled way. The feature could be enabled/disabled through chconf.h

The ChibiOS/RT Debug View

In addition to run-time code debugging, ChibiOS offers additional features that aim to collect information about the behavior of the operating system. These features are organized per scope and could be enabled/disabled. Once enabled, all the information gathered is exposed to the user through a dedicated plugin known as ChibiOS/RT Debug View that comes with ChibiStudio and offers an additional Windows in the Debug perspective.

The information inside the window shall be updated using the refresh button in the top left corner when the code execution is suspended. The window is organized in 5 tabs and the information in here depends on three additional switches of chconf.h

Thread’s unused stack

One of the most common questions is:

How may I decide the size of a thread working area?

First of all the thread working area is also known as the thread stack. Said that the solution is:

  • choose a large area,
  • enable the switch CH_DBG_FILL_THREADS in chconf.h
  • flash and run the firmware executing the application for a while trying to cover all the application scenario
  • pause the execution and check the unused stack in the thread tab
  • we could choose a new stack size based on this new data

For example, let us consider the case in which I choose 1024 bytes as working area size and after the test, the unused stack is 900 bytes. This means I am actually using 124 bytes. I use to choose the size of the working area as a power of two: at this point, I could choose the working are size as 128 bytes, but with this choice, the unused stack would be quite small and potentially this could lead to a stack overflow. To avoid risks I would choose 256 bytes (saving 768 bytes anyway).

The ChibiOS trace buffer

Another interesting feature offered by ChibiOS is the Trace Buffer. The trace buffer collects information about noticeable events of the operating system collecting them in a data structure. The user can browse the data through the TraceBuffer tab of the ChibiOS debug view.

There are many events that could be traced

To trace events user could act on chconf.h changing the CH_DBG_TRACE_MASK and the size of the trace buffer

This feature is quite useful to understand the time flow of events related to the Operating System: using them it could be possible to understand the sequence that led to a failure.

Advanced statistics

Last but not least, ChibiOS offers some advanced statistics that provide information about threads in the Thread tab like how many time a thread has been executed (switches), the worst execution path of that thread and the cumulative execution time (both expressed as system ticks). Additionally, it collects information about the number of IRQs, the number of context switches, the duration of threads and ISRs critical zones in the Statistics tab.

The feature could be enabled through the following switch in chconf.h


ChibiStudio offers many tools to debug the user application and optimize the performances of the firmware. These features should be exploited during the development phase by enabling them and disabling the optimization. The statistic module should be used to measure effective performances of the application and reduce jitter.

Replies to Debugging on STM32 with ChibiStudio: the ultimate guide

  • Hi,

    I’ve stm32f767zi nucleo board.
    I followed instructions as shown in video but somehow i’m not able to debug application. My openOCD launches as expected, shows voltage and cpu type. There are no changes in application and it’s same as provided in chibistudio demo:

    Following is the log:

    symbol-file C:\\ChibiStudio\\chibios_trunk\\demos\\STM32\\RT-STM32F767ZI-NUCLEO144\\build\\ch.elf
    Reading symbols from C:\ChibiStudio\chibios_trunk\demos\STM32\RT-STM32F767ZI-NUCLEO144\build\ch.elf…done.
    monitor reset run
    “monitor” command not supported by this target.
    monitor delay 1000
    “monitor” command not supported by this target.
    monitor halt
    “monitor” command not supported by this target.
    set remotetimeout 20
    monitor reset init
    “monitor” command not supported by this target.
    monitor sleep 50
    “monitor” command not supported by this target.
    load C:\\ChibiStudio\\chibios_trunk\\demos\\STM32\\RT-STM32F767ZI-NUCLEO144\\build\\ch.elf
    You can’t do that when your target is `None’
    tbreak main
    Temporary breakpoint 1 at 0x202ed0: file ../../../os/hal/ports/STM32/STM32F7xx/hal_lld.c, line 120.
    The program is not being run.

    Any help would be appreciated.


Leave a Reply