px-fwlib 0.10.0
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers generated with Doxygen 1.9.2
FreeRTOS Blinking LED example

File(s):

Reference(s):

1. Introduction

px-fwlib is a bare metal embedded C library and does not have the RTOS mechanisms to manage access to a shared resource like a UART or signal a task when an event has occurred, for example when a character has been received, but that does not prevent us from trying a basic example to get a taste of what FreeRTOS has to offer.

2. Description

This elaborate example to change the blinking rate of an LED demonstrates a few key RTOS concepts. It creates two tasks called LED and BTN.

The LED task toggles an LED every 250 ms initially. The LED task checks if there is a command on the queue to increase or decrease the blinking rate.

The BTN task enables a falling edge interrupt for 3/UP (Port C pin 13) and 4/DN (Port C pin 12). A binary semaphore is given in each interrupt handler to signal to the BTN task that a button has been pressed. The BTN task will block on the semaphore and when the semaphore is given in the interrupt handler, it will post a command to the LED task command queue.

This example contains a subtle bug on purpose! See if you can spot it... hint at the end.

3. Segger SystemView

Segger SystemView is a wonderful visualisation tool to record timestamped events and inspect them in a GUI. It requires a Segger J-Link debug probe to collect the data from the processor while it is executing your code. An inexpensive J-Link EDU Mini is available for non-commercial educational purposes. Segger also offers firmware to upgrade the on-board ST-LINK on Nucleo and Discovery boards (see HERE).

If you don't have a J-Link probe then you can still examine the saved log file in the SystemView GUI. Click HERE to download the *.SVDat file saved from this session.

The underlying high speed communication transport mechanism is Segger Real-Time Transfer (RTT).

3.1 Patch FreeRTOS

There are a few steps required to add the Segger SystemView source code to your FreeRTOS project. The FreeRTOS source files must be patched to instrument it with more trace macros. The (Windows) default patch file location is here:

c:\Program Files\SEGGER\SystemView\Src\Sample\FreeRTOSV10\Patch\FreeRTOSV10_Core.patch

Unfortunately the patch does not always keep up with the latest release of FreeRTOS (currently 10.3.1) and I had to examine the patch and apply it manually.

FreeRTOSConfig.h must be edited and the following line must be added at the end:

#include "SEGGER_SYSVIEW_FreeRTOS.h"

3.2 Timestamps

All SystemView events must be timestamped correctly and the usual solution is to piggy-back on the SysTick peripheral interrupt. Unfortunately the SysTick peripheral is only initialized when the FreeRTOS scheduler is started with a call to vTaskStartScheduler(). All SystemView events before this will be timestamped with a zero value and it confuses the Segger SystemView GUI.

The timestamping mechanism must be initialised and running correctly before logging any SystemView events. Timestamps must have sequential incrementing values otherwise the GUI will be get confused.

My solution to this catch-22 situation is to use the 16-bit TIM7 counter peripheral as a dedicated timestamping mechanism. The 32 MHz clock frequency is divided by 32 to provide 1 us resolution. The 16-bit counter overflows every 65.536 ms and results in a low overhead interrupt rate of only 15.259 Hz. It is also much easier to combine the two 16-bit values to provide a 32-bit timestamp compared to the example 1 ms SysTick implementation. The custom implementation is in SEGGER_SYSVIEW_Config_FreeRTOS.c:

static U16 SEGGER_SYSVIEW_TickCnt;
void TIM7_IRQHandler(void)
{
// Interrupt flag set?
if(LL_TIM_IsActiveFlag_UPDATE(TIM7))
{
// Clear flag
LL_TIM_ClearFlag_UPDATE(TIM7);
// Increment counter
SEGGER_SYSVIEW_TickCnt++;
}
}
static void SEGGER_SYSVIEW_TimeStampInit(void)
{
#warning "TIM7 peripheral is used for Segger SysView timestamping"
// Enable TIM7 peripheral clock
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM7);
// Set prescaler to match timestamp frequency
LL_TIM_SetPrescaler(TIM7, __LL_TIM_CALC_PSC(SYSVIEW_CPU_FREQ, SYSVIEW_TIMESTAMP_FREQ));
// Enable timer interrupt
NVIC_EnableIRQ(TIM7_IRQn);
LL_TIM_EnableIT_UPDATE(TIM7);
// Enable counter
LL_TIM_EnableCounter(TIM7);
}
U32 SEGGER_SYSVIEW_X_GetTimestamp(void)
{
U16 Counter;
U16 TickCount;
U32 TimeStamp;
// If a timer interrupt is pending, adjust overflow counter
if (LL_TIM_IsActiveFlag_UPDATE(TIM7))
{
LL_TIM_ClearFlag_UPDATE(TIM7);
SEGGER_SYSVIEW_TickCnt++;
}
// Read overflow counter
TickCount = SEGGER_SYSVIEW_TickCnt;
// Read timer counter
Counter = LL_TIM_GetCounter(TIM7);
// Create combined 32-bit timestamp
TimeStamp = (((U32)TickCount) << 16) | Counter;
return TimeStamp;
}

3.3 GUI

The GUI has three important windows. The Terminal window shows debug output text created with calls to SEGGER_SYSVIEW_Print(), SEGGER_SYSVIEW_Warn() or SEGGER_SYSVIEW_Error(). Click on an event in the Terminal window to navigate to an important event for inspection. Each timestamped event is listed seqentially on the left. The Timeline window displays each interrupt and task graphically so that it is easy to see how much time is spent.

When the 3/UP button is pressed it triggers an external rising edge interrupt and the EXTI[15:4] interrupt handler is called. The interrupt handler gives a semaphore and the scheduler is executed to switch to the BTN task that has been waiting for the semaphore. The BTN task then posts an event to the LED task's queue. Finally it blocks waiting for the next semaphore signal and releases it's execution to the scheduler.

The timing of events are different when the 4/DN button is pressed... why? The BTN task is pending for a long time and only gets a chance to run after the SysTick interrupt:

This is the "bug" hinted to at the start. Compare exti_12_handler() with exti_13_handler() and you will spot a subtle difference.

What an amazing, insightful tool!! I would not have spotted the problem, because the code appears to run just fine but could have become a problem in a larger system that requires hard real time behaviour.