STM32-RTOS-库

FreeRTOS 官网
CMSIS RTOS V2 Library

介绍

STM 社区有一些可用的 RTOS (Real-Time Operate System) 库, 这里介绍两个:

  • Free RTOS, 支持多任务, 速度快
  • CMSIS RTOS, 是 CMSIS (Common Microcontroller Software Interface Standard) 标准的一部分, 提供了基于 RTOS 开发的接口

相关概念

Tasks and Task Priorities

在 Real time system 中, 常需要同时完成多个任务, 此时需要给每个任务设置优先级, 以更早运行以及获得更多运行时间:

  • Task1 和 Task2 优先级更高, 因此先运行, 由于他们优先级相同, 因此交替运行

Task Scheduler

Task scheduler 负责任务间的切换, 一般在 SysTick 中断发生时进行.

Memory Management

在 RTOS 中, 创建的 Tasks 是在 Heap 中分配的内存, 其分为两部分:

  • TCB, Task Control Block, 存储 Task 在 Heap 中的位置信息
  • Stack

(可以在 这里查看 FreeRTOS 的 Memory Management 细节.)

对于 FreeRTOS 而言:

  • pvPortMalloc() 分配内存, 而非 malloc()
  • vPortFree() 释放内存, 而非 free()

FreeRTOS 实现了 5 个 heap (原话是 five sample memory allocation implementations, 就是 5 种不同的实现, 可能是不同的 API, 也可能是不同效果), 其源码位于 Middlewares/Third_Party/FreeRTOS/portable/MemMang/ 目录下, 分别是 heap_1.c, heap_2.c, heap_3.c, heap_4.c, heap_5.c, 不同实现的区别如下:

  • heap_1, 最简单, 但不允许释放内存 (可能会导致内存泄漏)
  • heap_2, 允许释放内存, 但不会合并相临的空闲块, 意味着可能会导致内存碎片化
  • heap_3, 简单包装了标准的 malloc()free() 函数, 且确保了线程安全, 即多个线程可以安全地同时分配和释放内存, 而不会相互干扰
  • heap_4, 会合并相临空闲块, 以避免内存碎片化, 还包括一个绝对地址放置选项, 意味着程序可以指定一个特定的内存地址来分配内存, 这对于某些需要特定内存位置的特殊用途 (如图形或硬件相关的操作) 可能是必要的
  • heap_5, 与 heap_4 类似, 但它能够跨越多个不连续的内存区域来扩展堆. 这在物理内存有限或需要将数据分布到不同内存区域以优化性能的情况下非常有用

默认情况下似乎是启用 heap_4:

下面是 heap_4 的部分示例和说明,

  • configTOTAL_HEAP_SIZE 用于设置 heap 的大小, 可以在 Core/Inc/FreeRTOSConfig.h 中设置:
  • configAPPLICATION_ALLOCATED_HEAP 用于让 heap 放置在指定的内存位置, 同样在 Core/Inc/FreeRTOSConfig.h 中设置 (但似乎没看到这个常量)
  • xPortGetFreeHeapSize() 函数返回还未分配的 heap 大小
  • xPortGetMinimumEverFreeHeapSize() 函数返回自 FreeRTOS 程序启动以来, 系统中曾经存在的最小空闲堆空间字节数, 用于了解系统运行过程中堆空间的最低可用情况
  • vPortGetHeapStats() 函数返回 HeapStats_t 结构体, 包含 heap 的使用统计数据
  • pvPortCalloc()calloc 函数一样, 为一个对象数组分配内存. 它计算总的所需内存大小 (num * size), 然后从堆中分配相应大小的内存块, 且会把分配的内存块中的所有字节初始化为零

Queues

Queues, 队列, 在这里主要是让数据以 FIFO 的方式输入和输出.

Queues 一般对所有 Tasks 都是全局可见的, 且 Queues 中存储的数据具有原子性, 即:

  • 在所有数据都存储进 queues 之前, 其他 task 都不能中断其操作

    (比如这里 DATA 1TASK 1 填充, DATA 2TASK 2 填充, 最后 TASK 3 来读取)

Queues 中存储的数据也不区分类型, 任何数据都能复制进去.

在 STM32CubeIDE 中的设置如下:

这里的 Queue 命名为 Queue01, 会生成一个名为 Queue01HandleosMessageQueueId_t 类型 handler.

Mutex

Mutex, 互斥锁,

启用 FreeRTOS

先用 STM32CubeIDE 创建一个项目, 在 .ioc 界面, 选中 Middleware and software packs:

之后选中 FREERTOS 以及 CMSIS_V2:

默认情况下, 会启用 preemption 选项, 让 scheduler 能够停止一个 task 并运行另一个优先级更高的 task:

任务和优先级也可直接在 .ioc 界面设置, 选中 Tasks and Queues 部分, 双击默认的任务 (default 那个) 可以在弹窗设置:

Entry Function 就是 Task 的入口函数, 包含 Task 的具体内容 (要执行的代码), 会生成到 main.c 文件里.


(这里是之后保存了再看的)

之后还需要设置一下 timer (原因还不是很懂), 在 System Core 部分选中 SYS, 将 TimeBase Source 改为 TIM4:

之后保存即可.

设置 Task 的 Stack size

注意事项

在 Task Function 中, 若要 delay 需用 osDelay() 而非 HAL_Delay():

  • osDelay() 是一个 RTOS 函数, 当 delay 的时候也允许其他 task 允许
  • HAL_Delay() 会让整个系统 halt

在 Task Function 最后最好加上 osThreadTerminate(NULL) 来显示终止任务.

一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Start_Task_1(void *argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
for (;;)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
osDelay(800); //delay 800ms
}

/* USER CODE END 5 */
/* terminate thread in case something happends */
osThreadTerminate(NULL);
}

在 Task 中, 似乎一定要 osDelay 不然所有任务不会一起进行.

Queues 示例

先创建两个 Tasks 以及一个 Queue, Tasks 的 Entry Function 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* USER CODE END Header_start_sender_task */
void start_sender_task(void *argument)
{
/* USER CODE BEGIN 5 */
uint16_t num = 0; // incrementing number
char msg[] = "Queue full \n"; // message
/* Infinite loop */
for (;;)
{
osStatus_t status = osMessageQueuePut(Queue01Handle, &num, 0, 0);
if (status != osOK) // if queue status is full / not okay
{
HAL_UART_Transmit(&huart2, (char*)msg, strlen(msg), HAL_MAX_DELAY);
}
num = num + 1; // increment by 1
osDelay(1000); // delay of a second
}
/* USER CODE END 5 */
}

/* USER CODE BEGIN Header _start_receiver_task */
/* USER CODE END Header _start_receiver_task */
void start_receiver_task(void *argument)
{
/* USER CODE BEGIN start_receiver_task */
uint16_t num; // variable to store the number from queue
char num_char[5]; // String to print
char msg[] = "Queue Empty \n"; // message
/* Infinite loop */
for(;;)
{
// receive the number the queue and get_queue_status
osStatus_t status = osMessageQueueGet(Queue01Handle, &num, 0, 0);
if (status != osOK) // if queue status is empty / not okay
{
HAL_UART_Transmit(&huart2, (char*)msg, strlen(msg), HAL_MAX_DELAY);
}
sprintf(num_char, "%d\r\n", num);
HAL_UART_Transmit(&huart2, (char*)num_char, strlen(num_char), HAL_MAX_DELAY);
osDelay(1000); // delay of a second
}

/* USER CODE END start_receiver_task */
}
  • osMessageQueuePut(Queue_Handle, address of the data, priority, wait time), 将指定地址的数据放入 Queue 中, wait time 指, 当 Queue 为 Full 时, 需要等待多长时间再尝试放入数据
  • osMessageQueueGet(Queue_Handle, address of the data, priority, wait time), 将 Queue 中的数据取出放到指定地址的变量中, wait time 指, 当 Queue 为 Empty 时, 需要等待多长时间再尝试读取数据

Task Notification

FreeRTOS 的 Task Notification 是一种 Tasks 间通信的机制, 允许 Tasks 之间发送简单的信号或数据. 其相比于信号量和消息队列, 它更简单且开销更小.

发送通知

用下面两个函数来发送通知:

  • xTaskNotify(): 可以发送特定的值
  • xTaskNotifyGive(): 用于简单的信号, 通常用于唤醒等待的任务
1
xTaskNotify(xTaskHandle, value, eSetValueWithOverwrite);

接收通知

用下面两个函数来接收通知:

  • ulTaskNotifyTake(): 阻塞直到接收到通知, 适合用于任务的状态变化
  • xTaskNotifyWait(): 可以在接收通知的同时检查是否有新的通知值
1
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "FreeRTOS.h"
#include "task.h"

TaskHandle_t xTaskHandle;

void TaskA(void *pvParameters) {
while (1) {
// 做一些工作
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟工作延迟

// 通知 TaskB
xTaskNotifyGive(xTaskHandle);
}
}

void TaskB(void *pvParameters) {
while (1) {
// 等待通知
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

// 执行响应操作
}
}

int main(void) {
xTaskCreate(TaskA, "TaskA", 100, NULL, 1, NULL);
xTaskCreate(TaskB, "TaskB", 100, NULL, 1, &xTaskHandle);
vTaskStartScheduler();

while (1);
}

(没实机不晓得对不对)

解决 freertos_mpool.h 文件缺失问题

在使用 FreeRTOS 的 CMSIS_V2 接口时, 编译发现文件确实:

.ioc 设置页面, 把 Project Manager 下的 Firmware Package Name and Version 改为 STM32Cube FW_F1 V1.8.5 则能解决.

main 函数中的 while 循环不再运行

在启用 FreeRTOS 的 Task 后, 会发现 main 函数中的 while 循环不再其作用, 可以看这一行注释:

此时调度器由 FreeRTOS 接管了.


STM32-RTOS-库
http://example.com/2024/11/06/STM32-RTOS-库/
作者
Jie
发布于
2024年11月6日
许可协议