Difference between revisions of "STM32 FreeRTOS"
(31 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | [[Category:STM32]][[Category:STM32 Development]][[Category:FreeRTOS]]{{metadesc|FreeRTOS on STM32}} | + | [[Category:STM32]][[Category:STM32 Development]][[Category:C]][[Category:FreeRTOS]]{{metadesc|FreeRTOS on STM32}} |
[[File:FreeRTOS Middleware.png|thumb]] | [[File:FreeRTOS Middleware.png|thumb]] | ||
− | [[FreeRTOS]] is a real-time operating system for embedded systems. On [[MCU]]s based on [[ARM]] [[Cortex-M]] cores a standardised API exists which is known as [[CMSIS RTOS]]. | + | [[FreeRTOS]] is a real-time operating system for embedded systems. On [[MCU]]s based on [[ARM]] [[Cortex-M]] cores a standardised API exists which is known as [[CMSIS RTOS]]. This API is built on top of [[FreeRTOS]]. Two different versions of [[CMSIS RTOS]] exists: v1 and v2. Except from the queue handling they are almost identical. |
[[STM32CubeMX]] includes an option to use [[FreeRTOS]]. In an earlier life, I did quite a lot of development on [[ESP32]], and that, due to it's dual-core design, is very much centred around [[FreeRTOS]]. Back the, I grew to hate [[FreeRTOS]]. | [[STM32CubeMX]] includes an option to use [[FreeRTOS]]. In an earlier life, I did quite a lot of development on [[ESP32]], and that, due to it's dual-core design, is very much centred around [[FreeRTOS]]. Back the, I grew to hate [[FreeRTOS]]. | ||
The examples on this page is using a [[STM32F411]] based [[Black Pill]] development board. | The examples on this page is using a [[STM32F411]] based [[Black Pill]] development board. | ||
+ | |||
+ | == Tutorial Videos == | ||
+ | |||
+ | We have created an introduction and tutorial video about FreeRTOS on [[STM32]] [[MCU]]s: | ||
+ | |||
+ | Watch on Youtune: [https://www.youtube.com/watch?v=3Kevk3l6vPs https://www.youtube.com/watch?v=3Kevk3l6vPs] | ||
+ | |||
+ | {{#ev:youtube|3Kevk3l6vPs}} | ||
+ | |||
+ | Watch on Youtube: [https://www.youtube.com/watch?v=zY_I6GZffos https://www.youtube.com/watch?v=zY_I6GZffos] | ||
+ | |||
+ | {{#ev:youtube|zY_I6GZffos}} | ||
+ | |||
+ | {{clear}} | ||
== Concepts == | == Concepts == | ||
+ | |||
+ | While [[FreeRTOS]] call itself a "real-time operating system" it is essentially merely a task manager and scheduler. | ||
+ | |||
+ | Multiple tasks can be created and each task will have it's own reserved stack and heap space. | ||
=== Tasks === | === Tasks === | ||
− | + | Tasks are essentially an endless loop and the task can operate in one of four different states: | |
+ | |||
+ | * Running | ||
+ | * Blocked | ||
+ | * Suspended | ||
+ | * Ready | ||
+ | |||
+ | ==== STM32CubeIDE ==== | ||
+ | |||
+ | Queues can be added and modified through STM32CubeMX. Here is a list of queues: | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:STM32CubeIDE FreeRTOS Queues.png|600px]] | ||
+ | </div> | ||
+ | |||
+ | Clicking "Add" or double clicking on any of the existing tasks will pop up an edit window: | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:STM32CubeIDE FreeRTOS Edit Task.png|400px]] | ||
+ | </div> | ||
+ | |||
+ | ==== The code ==== | ||
+ | |||
+ | The above definition result in the following code. First a couple of variable declarations: | ||
+ | |||
+ | <pre> | ||
+ | /* Definitions for ledTask */ | ||
+ | osThreadId_t ledTaskHandle; | ||
+ | const osThreadAttr_t ledTask_attributes = { | ||
+ | .name = "ledTask", | ||
+ | .stack_size = 128 * 4, | ||
+ | .priority = (osPriority_t) osPriorityNormal, | ||
+ | }; | ||
+ | </pre> | ||
+ | |||
+ | Based on these variables, the task can be started: | ||
+ | |||
+ | <pre> | ||
+ | /* creation of ledTask */ | ||
+ | ledTaskHandle = osThreadNew(StartLedTask, NULL, &ledTask_attributes); | ||
+ | </pre> | ||
+ | |||
+ | The task itself is merely an endless loop: | ||
+ | |||
+ | <pre> | ||
+ | /* USER CODE BEGIN Header_StartLedTask */ | ||
+ | /** | ||
+ | * @brief Function implementing the ledTask thread. | ||
+ | * @param argument: Not used | ||
+ | * @retval None | ||
+ | */ | ||
+ | /* USER CODE END Header_StartLedTask */ | ||
+ | void StartLedTask(void *argument) | ||
+ | { | ||
+ | /* USER CODE BEGIN StartLedTask */ | ||
+ | /* Infinite loop */ | ||
+ | for (;;) { | ||
+ | |||
+ | osDelay(500); | ||
+ | |||
+ | HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); | ||
+ | |||
+ | } | ||
+ | /* USER CODE END StartLedTask */ | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | === Semaphores === | ||
+ | |||
+ | Semaphores are used to synchronise tasks. A task can acquire a semaphore (without any performance penalty) and then the task will continue execution once the semaphore is released from another task. | ||
+ | |||
+ | ==== STM32CubeMX ==== | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:Edit Semaphore.png|400px]] | ||
+ | </div> | ||
+ | |||
+ | ==== The Code ==== | ||
+ | |||
+ | First a couple of variables need to be defined: | ||
+ | |||
+ | <pre> | ||
+ | /* Definitions for ledSemaphore */ | ||
+ | osSemaphoreId_t ledSemaphoreHandle; | ||
+ | const osSemaphoreAttr_t ledSemaphore_attributes = { | ||
+ | .name = "ledSemaphore" | ||
+ | }; | ||
+ | </pre> | ||
+ | |||
+ | The Semaphore can now be created: | ||
+ | |||
+ | <pre> | ||
+ | /* creation of ledSemaphore */ | ||
+ | ledSemaphoreHandle = osSemaphoreNew(1, 1, &ledSemaphore_attributes); | ||
+ | </pre> | ||
+ | |||
+ | ==== Usage ==== | ||
+ | |||
+ | In the receiving task we acquire the semaphore: | ||
+ | |||
+ | <pre> | ||
+ | /* USER CODE END Header_StartLedTask */ | ||
+ | void StartLedTask(void *argument) | ||
+ | { | ||
+ | /* USER CODE BEGIN StartLedTask */ | ||
+ | |||
+ | osStatus_t ret; | ||
+ | |||
+ | /* Infinite loop */ | ||
+ | for (;;) { | ||
+ | |||
+ | ret = osSemaphoreAcquire(ledSemaphoreHandle, osWaitForever); | ||
+ | |||
+ | if (!ret) { | ||
+ | HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | /* USER CODE END StartLedTask */ | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | This task will use zero cpu cycles while waiting. We can now toggle the led from another task thus: | ||
+ | |||
+ | <pre> | ||
+ | osSemaphoreRelease(ledSemaphoreHandle); | ||
+ | </pre> | ||
+ | |||
+ | === Mutexes === | ||
+ | |||
+ | Mutexes are used to grant or wait for exclusive access to a resource which can not be used simultaneously from several tasks. | ||
+ | |||
+ | ==== STM32CubeMX ==== | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:Edit Mutex.png|400px]] | ||
+ | </div> | ||
+ | |||
+ | ==== The Code ==== | ||
+ | |||
+ | The Mutex variables are defined: | ||
+ | |||
+ | <pre> | ||
+ | /* Definitions for ledMutex */ | ||
+ | osMutexId_t ledMutexHandle; | ||
+ | const osMutexAttr_t ledMutex_attributes = { | ||
+ | .name = "ledMutex" | ||
+ | }; | ||
+ | </pre> | ||
+ | |||
+ | The Mutex can now be created: | ||
+ | |||
+ | <pre> | ||
+ | /* creation of printMutex */ | ||
+ | printMutexHandle = osMutexNew(&printMutex_attributes); | ||
+ | </pre> | ||
+ | |||
+ | The mutex can now be used to gain exclusive access to a resource, in this case printf: | ||
+ | |||
+ | <pre> | ||
+ | osMutexWait(printMutexHandle, osWaitForever); | ||
− | === | + | printf("Tick %lu (c1 = %lu, c1h = %lu ch2 = %lu, c2h = %lu s = %lu)\n", tick / 1000, conv_ch1, conv_half_ch1, conv_ch2, conv_half_ch2, sine_task); |
− | + | osMutexRelease(printMutexHandle); | |
+ | </pre> | ||
+ | |||
+ | If multiple tasks try to print at the same time only one task (the first) will be granted the mutex immediately while the other will have to wait until the mutex is released. | ||
=== Queues === | === Queues === | ||
− | + | Queues are used for communication between tasks. The recipient will wait for a message and respond immediately or it can check non-blocking for new messages. | |
+ | |||
+ | ==== STM32CubeMX ==== | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:Edit Queue.png|400px]] | ||
+ | </div> | ||
+ | |||
+ | ==== The Code ==== | ||
+ | |||
+ | First declare the variables: | ||
+ | |||
+ | <pre> | ||
+ | /* Definitions for tickQueue */ | ||
+ | osMessageQueueId_t tickQueueHandle; | ||
+ | const osMessageQueueAttr_t tickQueue_attributes = { | ||
+ | .name = "tickQueue" | ||
+ | }; | ||
+ | </pre> | ||
+ | |||
+ | The queue can now be initialised thus: | ||
+ | |||
+ | <pre> | ||
+ | /* creation of tickQueue */ | ||
+ | tickQueueHandle = osMessageQueueNew (16, sizeof(uint32_t), &tickQueue_attributes); | ||
+ | </pre> | ||
+ | |||
+ | Notice the size of the data transmitted in the queue. Queues in CMSIS RTOS V2 is passed by value - NOT by reference (in V1 it was fixed at 32 bit). Any variable or struct can be passed. | ||
+ | |||
+ | ==== Usage ==== | ||
+ | |||
+ | We have already seen how queue messages are received: | ||
+ | |||
+ | <pre> | ||
+ | ret = osMessageQueueGet(tickQueueHandle, &tick, NULL, osWaitForever); | ||
+ | |||
+ | if (ret == osOK) { | ||
+ | |||
+ | osMutexWait(printMutexHandle, osWaitForever); | ||
+ | |||
+ | printf("Tick %lu (c1 = %lu, c1h = %lu ch2 = %lu, c2h = %lu s = %lu)\n", tick / 1000, conv_ch1, conv_half_ch1, conv_ch2, conv_half_ch2, sine_task); | ||
+ | |||
+ | osMutexRelease(printMutexHandle); | ||
+ | |||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | The messages can be send like this: | ||
+ | |||
+ | <pre> | ||
+ | uint32_t tick = osKernelGetTickCount(); | ||
+ | osMessageQueuePut(tickQueueHandle, &tick, 0, osWaitForever); | ||
+ | </pre> | ||
+ | |||
+ | == Pre-allocated FreeRTOS Memory == | ||
+ | |||
+ | FreeRTOS allocates stack and heap for each task (and queues, semaphores and mutexes). By default, FreeRTOS will allocate a chunk of memory on the heap for this purpose, but it is possible to statically allocate this memory. First put a define in FreeRTOSConfig.h: | ||
+ | |||
+ | <pre> | ||
+ | /* USER CODE BEGIN Defines */ | ||
+ | /* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */ | ||
+ | #define configAPPLICATION_ALLOCATED_HEAP 1 | ||
+ | /* USER CODE END Defines */ | ||
+ | </pre> | ||
+ | |||
+ | You can now pre-allocate the heap in any memory segment: | ||
− | + | <pre> | |
+ | #ifdef configAPPLICATION_ALLOCATED_HEAP | ||
+ | uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((section(".ccmram"))); | ||
+ | #endif | ||
+ | </pre> | ||
+ | This example will allocate the FreeRTOS heap in the [[CCMRAM]] on devices which include that (for example STM32F405). | ||
+ | <div class="res-img"> | ||
+ | [[File:FreeRTOS in CCMRAM.png|600px]] | ||
+ | </div> | ||
== Miscellaneous Links == | == Miscellaneous Links == | ||
− | * [https:// | + | * [https://stm32world.com/images/3/30/FreeRTOS_v1.7_-_CMSIS_OS_API.pdf FreeRTOS on STM32] |
* [https://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html CMSIS-RTOS Documentation] | * [https://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html CMSIS-RTOS Documentation] | ||
* [https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html CMSIS-RTOS2 Documentation] | * [https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html CMSIS-RTOS2 Documentation] | ||
+ | * [https://arm-software.github.io/CMSIS_5/RTOS2/html/os2MigrationFunctions.html Migrate from CMSIS RTOS V1 to V2] | ||
* [https://drive.google.com/drive/folders/1NSiTWwBcCHPt-Hzc67JQWQKK4aPNZ-yx ST Training] | * [https://drive.google.com/drive/folders/1NSiTWwBcCHPt-Hzc67JQWQKK4aPNZ-yx ST Training] |
Latest revision as of 04:54, 9 November 2024
FreeRTOS is a real-time operating system for embedded systems. On MCUs based on ARM Cortex-M cores a standardised API exists which is known as CMSIS RTOS. This API is built on top of FreeRTOS. Two different versions of CMSIS RTOS exists: v1 and v2. Except from the queue handling they are almost identical.
STM32CubeMX includes an option to use FreeRTOS. In an earlier life, I did quite a lot of development on ESP32, and that, due to it's dual-core design, is very much centred around FreeRTOS. Back the, I grew to hate FreeRTOS.
The examples on this page is using a STM32F411 based Black Pill development board.
Tutorial Videos
We have created an introduction and tutorial video about FreeRTOS on STM32 MCUs:
Watch on Youtune: https://www.youtube.com/watch?v=3Kevk3l6vPs
Watch on Youtube: https://www.youtube.com/watch?v=zY_I6GZffos
Concepts
While FreeRTOS call itself a "real-time operating system" it is essentially merely a task manager and scheduler.
Multiple tasks can be created and each task will have it's own reserved stack and heap space.
Tasks
Tasks are essentially an endless loop and the task can operate in one of four different states:
- Running
- Blocked
- Suspended
- Ready
STM32CubeIDE
Queues can be added and modified through STM32CubeMX. Here is a list of queues:
Clicking "Add" or double clicking on any of the existing tasks will pop up an edit window:
The code
The above definition result in the following code. First a couple of variable declarations:
/* Definitions for ledTask */ osThreadId_t ledTaskHandle; const osThreadAttr_t ledTask_attributes = { .name = "ledTask", .stack_size = 128 * 4, .priority = (osPriority_t) osPriorityNormal, };
Based on these variables, the task can be started:
/* creation of ledTask */ ledTaskHandle = osThreadNew(StartLedTask, NULL, &ledTask_attributes);
The task itself is merely an endless loop:
/* USER CODE BEGIN Header_StartLedTask */ /** * @brief Function implementing the ledTask thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartLedTask */ void StartLedTask(void *argument) { /* USER CODE BEGIN StartLedTask */ /* Infinite loop */ for (;;) { osDelay(500); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } /* USER CODE END StartLedTask */ }
Semaphores
Semaphores are used to synchronise tasks. A task can acquire a semaphore (without any performance penalty) and then the task will continue execution once the semaphore is released from another task.
STM32CubeMX
The Code
First a couple of variables need to be defined:
/* Definitions for ledSemaphore */ osSemaphoreId_t ledSemaphoreHandle; const osSemaphoreAttr_t ledSemaphore_attributes = { .name = "ledSemaphore" };
The Semaphore can now be created:
/* creation of ledSemaphore */ ledSemaphoreHandle = osSemaphoreNew(1, 1, &ledSemaphore_attributes);
Usage
In the receiving task we acquire the semaphore:
/* USER CODE END Header_StartLedTask */ void StartLedTask(void *argument) { /* USER CODE BEGIN StartLedTask */ osStatus_t ret; /* Infinite loop */ for (;;) { ret = osSemaphoreAcquire(ledSemaphoreHandle, osWaitForever); if (!ret) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } /* USER CODE END StartLedTask */ }
This task will use zero cpu cycles while waiting. We can now toggle the led from another task thus:
osSemaphoreRelease(ledSemaphoreHandle);
Mutexes
Mutexes are used to grant or wait for exclusive access to a resource which can not be used simultaneously from several tasks.
STM32CubeMX
The Code
The Mutex variables are defined:
/* Definitions for ledMutex */ osMutexId_t ledMutexHandle; const osMutexAttr_t ledMutex_attributes = { .name = "ledMutex" };
The Mutex can now be created:
/* creation of printMutex */ printMutexHandle = osMutexNew(&printMutex_attributes);
The mutex can now be used to gain exclusive access to a resource, in this case printf:
osMutexWait(printMutexHandle, osWaitForever); printf("Tick %lu (c1 = %lu, c1h = %lu ch2 = %lu, c2h = %lu s = %lu)\n", tick / 1000, conv_ch1, conv_half_ch1, conv_ch2, conv_half_ch2, sine_task); osMutexRelease(printMutexHandle);
If multiple tasks try to print at the same time only one task (the first) will be granted the mutex immediately while the other will have to wait until the mutex is released.
Queues
Queues are used for communication between tasks. The recipient will wait for a message and respond immediately or it can check non-blocking for new messages.
STM32CubeMX
The Code
First declare the variables:
/* Definitions for tickQueue */ osMessageQueueId_t tickQueueHandle; const osMessageQueueAttr_t tickQueue_attributes = { .name = "tickQueue" };
The queue can now be initialised thus:
/* creation of tickQueue */ tickQueueHandle = osMessageQueueNew (16, sizeof(uint32_t), &tickQueue_attributes);
Notice the size of the data transmitted in the queue. Queues in CMSIS RTOS V2 is passed by value - NOT by reference (in V1 it was fixed at 32 bit). Any variable or struct can be passed.
Usage
We have already seen how queue messages are received:
ret = osMessageQueueGet(tickQueueHandle, &tick, NULL, osWaitForever); if (ret == osOK) { osMutexWait(printMutexHandle, osWaitForever); printf("Tick %lu (c1 = %lu, c1h = %lu ch2 = %lu, c2h = %lu s = %lu)\n", tick / 1000, conv_ch1, conv_half_ch1, conv_ch2, conv_half_ch2, sine_task); osMutexRelease(printMutexHandle); }
The messages can be send like this:
uint32_t tick = osKernelGetTickCount(); osMessageQueuePut(tickQueueHandle, &tick, 0, osWaitForever);
Pre-allocated FreeRTOS Memory
FreeRTOS allocates stack and heap for each task (and queues, semaphores and mutexes). By default, FreeRTOS will allocate a chunk of memory on the heap for this purpose, but it is possible to statically allocate this memory. First put a define in FreeRTOSConfig.h:
/* USER CODE BEGIN Defines */ /* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */ #define configAPPLICATION_ALLOCATED_HEAP 1 /* USER CODE END Defines */
You can now pre-allocate the heap in any memory segment:
#ifdef configAPPLICATION_ALLOCATED_HEAP uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((section(".ccmram"))); #endif
This example will allocate the FreeRTOS heap in the CCMRAM on devices which include that (for example STM32F405).