Skip to content



FreeRTOS »

Blink - say Hello to the World

Blink is a very simple programming on embedded system, as it is just toggle an GPIO which drives an LED to notify user that it is running. This program is useful for checking project setup, compilation, programming as well as the hardware condition.

Last update: 2022-06-04


STM32-Tutorials

Requirements#

Use FreeRTOS to do:

  • Create the first task to Blink a LED at 10 Hz
  • Create the second task to print out a counter value to SWV
  • Use RTOS delay function to not consume CPU

You can use any STM32 board because this is just a very simple project. Refer to different types listed in Development boards and choose one suit for you.

I choose to use a Nucleo-64 board with STM32F411RE.

The Green LED is connected to the pin PA5 of the MCU.

Schematic of the Green LED on STM32 Nucleo-64 board

FreeRTOS source files#

Download FreeRTOS from the official page. At the writing time, it is at the version 202112.00 which includes the FreeRTOS Kernel version 10.4.6.

Extract the source code, then you should understand the source code structure to select files and folder to include into your project.

The FreeRTOS download includes source code for every processor port, and every demo application. From the top, the download is split into two sub directories; FreeRTOS, and FreeRTOS-Plus. These are shown below:

+-FreeRTOS      // Contains the FreeRTOS real time kernel source files and demo projects
|   + Source    // Contains the real time kernel source code.
|   |   |       // Select files from here to include to your project
|   |   + include           // Kernel header files
|   |   + portable          // Target specific immplementation
|   |       + MemMang       // Memory Management methods
|   |       + GCC           // Compiler
|   |          + ARM-CM4F   // Target MCU
|   + Demo      // Contains the demo application projects
|               // Use demo config files as a base for your project
+-FreeRTOS-Plus // Contains FreeRTOS+ components and demo projects.

The FreeRTOS/Include directory contains the kernel header files.

The core RTOS code is contained in three files, which are called tasks.c, queue.c and list.c. These three files are in the FreeRTOS/Source directory. The same directory contains two optional files called timers.c and croutine.c which implement software timer and co-routine functionality respectively.

Each supported processor architecture requires a small amount of architecture specific RTOS code. This is the RTOS portable layer, and it is located in the FreeRTOS/Source/Portable/[compiler]/[architecture] subdirectories, where [compiler] and [architecture] are the compiler used to create the port, and the architecture on which the port runs, respectively.

For the reasons stated on the memory management page, the sample heap allocation schemes are also located in the portable layer. The various sample heap_x.c files are located in the FreeRTOS/Source/portable/MemMang directory.

The FreeRTOS download also contains a demo application for every processor architecture and compiler port. The FreeRTOS/Demo subdirectories contain pre-configured projects used to build individual demo applications. The directories are named to indicate the port to which they relate. Each RTOS port also has its own web page that details the directory in which the demo application for that port can be found, such as Demos targeting ST Microelectronics products.


Select source files for STM32F411RE

Here are the files that are necessary for integrate FreeRTOS:

FreeRTOS
   croutine.c
   event_groups.c
   list.c
   queue.c
   stream_buffer.c
   tasks.c
   timers.c

├───include
       atomic.h
       croutine.h
       deprecated_definitions.h
       event_groups.h
       FreeRTOS.h
       list.h
       message_buffer.h
       mpu_prototypes.h
       mpu_wrappers.h
       portable.h
       projdefs.h
       queue.h
       semphr.h
       stack_macros.h
       stream_buffer.h
       task.h
       timers.h

└───portable
    ├───Common
           mpu_wrappers.c
    ├───GCC
       └───ARM_CM4F
               port.c
               portmacro.h
    └───MemMang
            heap_4.c

FreeRTOS APIs#

The FreeRTOS API Reference are listed in the below categories:

  • Configuration
  • Task Creation
  • Task Control
  • Kernel Control
  • Task Notifications
  • Task Utilities
  • FreeRTOS-MPU Specific
  • Queue Management
  • Queue Sets
  • Semaphores
  • Software Timers
  • Event Groups
  • Stream Buffers
  • Message Buffers
  • Co-routine specific

We will cover all above categories in other guides later.

Configuration#

FreeRTOS is customised using a configuration file called FreeRTOSConfig.h. Every FreeRTOS application must have a FreeRTOSConfig.h header file in its pre-processor include path. FreeRTOSConfig.h tailors the RTOS kernel to the application being built. It is therefore specific to the application, not the RTOS, and should be located in an application directory, not in one of the RTOS kernel source code directories.

Here is a typical FreeRTOSConfig.h definition, followed by an explanation of each parameter:

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#include <stdint.h>
#include "stm32f4xx.h"

extern uint32_t SystemCoreClock;

/* Scheduler settings */
#define configUSE_PREEMPTION    ( 1 )
#define configCPU_CLOCK_HZ      ( SystemCoreClock )
#define configTICK_RATE_HZ      ( ( TickType_t ) 1000 )
#define configUSE_16_BIT_TICKS  ( 0 )   /* use 32-bit */

/* Task settings */
#define configMAX_PRIORITIES        ( 5 )
#define configMINIMAL_STACK_SIZE    ( ( unsigned short ) 130 )
#define configTOTAL_HEAP_SIZE       ( ( size_t ) ( 75 * 1024 ) )
#define configMAX_TASK_NAME_LEN     ( 10 )

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES             ( 0 )
#define configMAX_CO_ROUTINE_PRIORITIES   ( 2 )

/* Hook function related definitions. */
#define configUSE_IDLE_HOOK                 ( 0 )
#define configUSE_TICK_HOOK                 ( 0 )
#define configCHECK_FOR_STACK_OVERFLOW      ( 0 )
#define configUSE_MALLOC_FAILED_HOOK        ( 0 )
#define configUSE_DAEMON_TASK_STARTUP_HOOK  ( 0 )

/* Optional functions */
#define INCLUDE_vTaskDelay  ( 1 )

/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
  /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
  #define configPRIO_BITS   __NVIC_PRIO_BITS
#else
  #define configPRIO_BITS   ( 4 )   /* 15 priority levels */
#endif

/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         ( 0xf )

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    ( 5 )

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY         \
    ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    \
    ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT(x)     \
    if((x) == 0) { taskDISABLE_INTERRUPTS(); for( ;; ); }

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler         SVC_Handler
#define xPortPendSVHandler      PendSV_Handler
#define xPortSysTickHandler     SysTick_Handler

#endif /* FREERTOS_CONFIG_H */

Task Creation#

Each task requires RAM that is used to hold the task state, and used by the task as its stack. If a task is created using xTaskCreate() then the required RAM is automatically allocated from the FreeRTOS heap. If a task is created using xTaskCreateStatic() then the RAM is provided by the application writer, so it can be statically allocated at compile time.


Dynamic Allocation: configSUPPORT_DYNAMIC_ALLOCATION = 1

Create a new task and add it to the list of tasks that are ready to run. configSUPPORT_DYNAMIC_ALLOCATION must be set to 1 in FreeRTOSConfig.h, or left undefined (in which case it will default to 1), for this RTOS API function to be available.

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                        const char * const pcName,
                        configSTACK_DEPTH_TYPE usStackDepth,
                        void *pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t *pxCreatedTask
                      );
/* Task to be created. */
void vTaskCode( void * pvParameters )
{
    /* The parameter value is expected to be 1 as 1 is passed in the
    pvParameters value in the call to xTaskCreate() below. 
    configASSERT( ( ( uint32_t ) pvParameters ) == 1 );

    for( ;; )
    {
        /* Task code goes here. */
    }
}

/* Function that creates a task. */
void vOtherFunction( void )
{
BaseType_t xReturned;
TaskHandle_t xHandle = NULL;

    /* Create the task, storing the handle. */
    xReturned = xTaskCreate(
                    vTaskCode,       /* Function that implements the task. */
                    "NAME",          /* Text name for the task. */
                    STACK_SIZE,      /* Stack size in words, not bytes. */
                    ( void * ) 1,    /* Parameter passed into the task. */
                    tskIDLE_PRIORITY,/* Priority at which the task is created. */
                    &xHandle );      /* Used to pass out the created task's handle. */

    if( xReturned == pdPASS )
    {
        /* The task was created.  Use the task's handle to delete the task. */
        vTaskDelete( xHandle );
    }
}

Task Control#


Task Delay

Delay a task for a given number of ticks. The actual time that the task remains blocked depends on the tick rate. The constant portTICK_PERIOD_MS can be used to calculate real time from the tick rate - with the resolution of one tick period. INCLUDE_vTaskDelay must be defined as 1 for this function to be available.

vTaskDelay() specifies a time at which the task wishes to unblock relative to the time at which vTaskDelay() is called.

void vTaskDelay( const TickType_t xTicksToDelay );
void vTaskFunction( void * pvParameters )
{
/* Block for 500ms. */
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;

    for( ;; )
    {
        /* Simply toggle the LED every 500ms, blocking between each toggle. */
        vToggleLED();
        vTaskDelay( xDelay );
    }
}

Kernel Control#

The scheduler does not run by default, it must be triggered by the function:

void vTaskStartScheduler( void );

After calling that function, the RTOS kernel has control over registered tasks.

The idle task and optionally the timer daemon task are created automatically when the RTOS scheduler is started. vTaskStartScheduler() will only return if there is insufficient RTOS heap available to create the idle or timer daemon tasks.

The Idle Task#

The idle task is created automatically when the RTOS scheduler is started to ensure there is always at least one task that is able to run. You may know this in the guide A simple implementation of a Task Scheduler.

It is created at the lowest possible priority to ensure it does not use any CPU time if there are higher priority application tasks in the ready state. The idle task is responsible for freeing memory allocated by the RTOS to tasks that have since been deleted. It is therefore important in applications that make use of the vTaskDelete() function to ensure the idle task is not starved of processing time. The idle task has no other active functions so can legitimately be starved of microcontroller time under all other conditions.

It is possible for application tasks to share the idle task priority (tskIDLE_PRIORITY). See the configIDLE_SHOULD_YIELD configuration parameter for information on how this behavior can be configured.

Software Timers#

A software timer (or just a ‘timer’) allows a function to be executed at a set time in the future. Timer functionality is optional, and not part of the core FreeRTOS kernel. It is instead provided by a timer service (or daemon) task.

Note, a software timer must be explicitly created before it can be used, which is controlled by definitions in FreeRTOSConfig.h:

/* Software timer related definitions. */
#define configUSE_TIMERS                        1
#define configTIMER_TASK_PRIORITY               3
#define configTIMER_QUEUE_LENGTH                10
#define configTIMER_TASK_STACK_DEPTH            configMINIMAL_STACK_SIZE

The FreeRTOS implementation does not execute timer callback functions from an interrupt context, does not consume any processing time unless a timer has actually expired, does not add any processing overhead to the tick interrupt, and does not walk any link list structures while interrupts are disabled.

Timer callback functions execute in the context of the timer service task. It is therefore essential that timer callback functions never attempt to block. For example, a timer callback function must not call vTaskDelay(), vTaskDelayUntil(), or specify a non zero block time when accessing a queue or a semaphore.

Use FreeRTOS in Bare-metal project#

F411RE_FreeRTOS_Blink_CMSIS.zip

We will learn how to integrate FreeRTOS into a bare-metal project which does NOT use any high-level APIs. You can review the different levels of libraries in the Blink example on STM32.

  1. Create a new project without using STM32CubeMX

  2. Add CMSIS files, refer to Integrate CMSIS Pack

  3. Copy FreeRTOS files into the projects

  4. Add new paths into the Project Paths and Symbols settings

Add Paths and Symbols of CMSIS and FreeRTOS

Implement project code#

Now, this is the step to implement the requirements in our code:

Note that we are using CMSIS package, so we can do access to GPIO and SWV through the CMSIS macros:


Create the first task to toggle PA5 at 10 Hz

void Blink_TaskFunction(void *pvParameters) {
  while(1) {
    /* set HIGH value on pin PA5 */
    GPIOA->BSRR |= GPIO_BSRR_BS_5;
    vTaskDelay(250);

    /* set LOW value on pin PA5 */
    GPIOA->BSRR |= GPIO_BSRR_BR_5;
    vTaskDelay(250);
  }
}

Create the second task to print out a counter value

The parameter is the delay.

void Log_TaskFunction(void *pvParameters) {
  uint8_t counter = 0;
  while(1) {
    printf("counter = %d\n", counter);
    counter++;
    vTaskDelay((TickType_t)pvParameters);
  }
}

Create tasks and register them

xTaskCreate(
      Blink_TaskFunction,
      "Blink",
      configMINIMAL_STACK_SIZE,
      NULL,
      1,
      &blinkTaskHandler);

  xTaskCreate(
        Log_TaskFunction,
        "Log",
        configMINIMAL_STACK_SIZE,
        (void *)500, /* delay in 500 ticks */
        1,
        &logTaskHandler);

Start the scheduler

vTaskStartScheduler();

You have to write some extra code to:

  • Redirect Standard IO function to SWV
  • Initialize the PA5 pin as output mode


Full source code:

#include <stdint.h>
#include <stdio.h>
#include <stm32f4xx.h>
#include "FreeRTOS.h"
#include "task.h"

/* Override low-level _write system call */
int _write(int file, char *ptr, int len) {
    int DataIdx;
    for (DataIdx = 0; DataIdx < len; DataIdx++) {
        ITM_SendChar(*ptr++);
    }
    return len;
}

TaskHandle_t blinkTaskHandler;
TaskHandle_t logTaskHandler;

void Blink_TaskFunction(void *pvParameters);
void Log_TaskFunction(void *pvParameters);

int main(void)
{
  SystemInit();

  /* turn on clock on GPIOA */
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

  /* set PA5 to output mode */
  GPIOA->MODER &= ~GPIO_MODER_MODE5_1;
  GPIOA->MODER |=  GPIO_MODER_MODE5_0;

  xTaskCreate(
      Blink_TaskFunction,
      "Blink",
      configMINIMAL_STACK_SIZE,
      NULL,
      1,
      &blinkTaskHandler);

  xTaskCreate(
        Log_TaskFunction,
        "Log",
        configMINIMAL_STACK_SIZE,
        (void *)500,
        1,
        &logTaskHandler);

  vTaskStartScheduler();

  while(1);
}

void Blink_TaskFunction(void *pvParameters) {
  while(1) {
    /* set HIGH value on pin PA5 */
    GPIOA->BSRR |= GPIO_BSRR_BS_5;
    vTaskDelay(250);

    /* set LOW value on pin PA5 */
    GPIOA->BSRR |= GPIO_BSRR_BR_5;
    vTaskDelay(250);
  }
}

void Log_TaskFunction(void *pvParameters) {
  uint8_t counter;
  while(1) {
    printf("counter = %d\n", counter);
    counter++;
    vTaskDelay((TickType_t)pvParameters);
  }
}

Debug#

Compile and run the project in Debug mode.

Note to enable SWV in debugger, and open the SWV Data Console for channel 0.

For FreeRTOS, the is also some special windows to debug FreeRTOS, go to WindowsFreeRTOSFreeRTOS Task List.

Show SWV data and Task List

You should notice that, the IDLE task is automatically created and run. The LED on PA5 should be blinking also.

Use FreeRTOS with STM32CubeMX project#

F411RE_FreeRTOS_Blink_Cube_HAL.zip

STM32CubeMX can generate source code with FreeRTOS integration.

Configure FreeRTOS#

In the Pinout & Configuration tab, select MiddewareFreeRTOS then configure the FreeRTOS as below:

  1. Interface: CMSIS V2 (version 1 is quite old)

  2. Config parameters:

    • Version: 10.3.1 (you can not change it, and the version usually is not the latest version)
    • MPU/FPU: set the core functions
    • The configuration in FreeRTOSConfig.h header:
      • USE_PREEMPTION: Enabled
      • CPU_CLOCK_HZ: SystemCoreClock
      • TICK_RATE_HZ: 1000
      • Hooks functions
      • Software Timer service: force to Enabled in CMSIS V2
  3. Library settings

    • Newlib:
      • USE_NEWLIB_REENTRANT: should be Enabled then a newlib reent structure will be allocated for each created task. Newlib support has been included by popular demand, but is not used by the FreeRTOS maintainers themselves.
  4. HAL Timers

    The HAL library uses its own delay function which also needs a hardware timer to keep tracking time.

    Under the System Core, select SYS and choose a General Timer for Timebase Source, do not use SysTick as it is used by FreeRTOS.

Configure FreeRTOS in STM32CubeMX

Generated and copied files for FreeRTOS

When generating a new project from STM32CubeMX, there will be a popup saying that:

Prompt asking about set another time base source

What does this mean?

SysTick

SysTick is apart of the ARM Core, that counts down from the reload value to zero, and fire an interrupt to make a periodical event. SysTick is mainly used for delay function in non-RTOS firmware, and is used as the interrupt for RTOS scheduler.

In case STM32 HAL code also uses SysTick as its time base, RTOS will be generated to use HAL’s Handler.
If STM32 HAL utilizes another timer as its time base, RTOS has its own right to initialize and handler SysTick.

It is recommended to use SysTick for RTOS only, and set a basic timer as the time base for HAL.”

By default, STM32 projects generated by STM32CubeIDE use Newlib-nano. Whenever FreeRTOS is enabled, IDE will prompt to enable Newlib Reentrant attribute:

A prompt asking to enable Newlib reentrant

To understand more about Reentrant, please read Reentrant example.

Add Task#

Add tasks information

Generated code

main.c
/* Definitions for blinkTask */
osThreadId_t blinkTaskHandle;
const osThreadAttr_t blinkTask_attributes = {
  .name = "blinkTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for logTask */
osThreadId_t logTaskHandle;
const osThreadAttr_t logTask_attributes = {
  .name = "logTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityLow,
};

/* Task functions */
void blinkTaskFunction(void *argument);
void logTaskFunction(void *argument);

int main(void) {
    ...
    /* Init scheduler */
    osKernelInitialize();

    /* creation of blinkTask */
    blinkTaskHandle = osThreadNew(blinkTaskFunction, NULL, &blinkTask_attributes);

    /* creation of logTask */
    logTaskHandle = osThreadNew(logTaskFunction, (void*) 500, &logTask_attributes);

    /* Start scheduler */
    osKernelStart();
}

Implement Task functions

main.c
void blinkTaskFunction(void *argument)
{
  for(;;)
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    osDelay(50);
  }
}

void logTaskFunction(void *argument)
{
  uint8_t counter = 0;
  for(;;)
  {
    printf("log: counter = %d\n", counter);
    counter++;
    osDelay((uint32_t)argument);
  }
}

You have to write some extra code to redirect Standard IO function to SWV in order to see printf() data.

CMSIS Interface

You should notice that the functions to work with FreeRTOS are wrappted in CMSIS calls.

API category CMSIS_RTOS API FreeRTOS API
Kernel control osKernelStart vTaskStartScheduler
Thread management osThreadCreate xTaskCreate
Semaphore osSemaphoreCreate vSemaphoreCreateBinary, xSemaphoreCreateCounting
Mutex osMutexWait xSemaphoreTake
Message queue osMessagePut xQueueSend, xQueueSendFromISR
Timer osTimerCreate xTimerCreate

Most of the functions returns osStatus value, which allows checking whether the function is completed or there was some issue (defined in the cmsis_os.h file).

Each OS component has its own ID:

  • Tasks: osThreadId (mapped to TaskHandle_t within FreeRTOS API)
  • Queues: osMessageQId (mapped to QueueHandle_t within FreeRTOS API)
  • Semaphores: osSemaphoreId (mapped to SemaphoreHandle_t within FreeRTOS API)
  • Mutexes: osMutexId (mapped to SemaphoreHandle_t within FreeRTOS API)
  • SW timers: osTimerId (mapped to TimerHandle_t within FreeRTOS API)

Delays and timeouts are given in ms:

  • 0 — no delay
  • >0 — delay in ms
  • 0xFFFFFFFF — wait forever (defined in osWaitForever within cmsis_os.h file)

Delay

The function osDelay() is part of CMSIS Library and uses vTaskDelay() internally to introduce delay with the difference that input argument of osDelay is delay time in milliseconds while the input argument of _vTaskDelay() is a number of Ticks to be delayed. Using osDelay() function, OS will be notified about the delay and OS will change the status of task to blocked for that particular time period. You may know this in the guide A simple implementation of a Task Scheduler.

The function HAL_Delay() is part of the hardware abstraction layer. It basically uses polling to introduce delay. Using HAL_Delay() function, OS won’t be notified about the delay, and the code is in polling mode.

FreeRTOS delay functions: vTaskDelay() or vTaskDelayUntil() only take effect after the scheduler has started.

Debug#

Compile and run the project in Debug mode.

Note to enable SWV in debugger, and open the SWV Data Console for channel 0.

For FreeRTOS, the is also some special windows to debug FreeRTOS, go to WindowsFreeRTOSFreeRTOS Task List.

Show SWV data and Task List

You now see one more extra task TmrSvc which is the Software Timer Service.

Comments