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
Table of Content
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.
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.
-
Create a new project without using STM32CubeMX
-
Add CMSIS files, refer to Integrate CMSIS Pack
-
Copy FreeRTOS files into the projects
-
Add new paths into the Project Paths and Symbols settings
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 Windows → FreeRTOS → FreeRTOS 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 Middeware → FreeRTOS then configure the FreeRTOS as below:
-
Interface:
CMSIS V2
(version 1 is quite old) -
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
-
Library settings
- Newlib:
USE_NEWLIB_REENTRANT
: should beEnabled
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.
- Newlib:
-
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.
When generating a new project from STM32CubeMX, there will be a popup saying that:
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:
To understand more about Reentrant, please read Reentrant example.
Add Task#
Generated code
/* 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
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 toTaskHandle_t
within FreeRTOS API) - Queues:
osMessageQId
(mapped toQueueHandle_t
within FreeRTOS API) - Semaphores:
osSemaphoreId
(mapped toSemaphoreHandle_t
within FreeRTOS API) - Mutexes:
osMutexId
(mapped toSemaphoreHandle_t
within FreeRTOS API) - SW timers:
osTimerId
(mapped toTimerHandle_t
within FreeRTOS API)
Delays and timeouts are given in ms:
0
— no delay>0
— delay in ms0xFFFFFFFF
— wait forever (defined inosWaitForever
withincmsis_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 Windows → FreeRTOS → FreeRTOS Task List.
You now see one more extra task TmrSvc
which is the Software Timer Service.