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 1
由TASK 1
填充,DATA 2
由TASK 2
填充, 最后TASK 3
来读取)
Queues 中存储的数据也不区分类型, 任何数据都能复制进去.
在 STM32CubeIDE 中的设置如下:
这里的 Queue 命名为 Queue01
, 会生成一个名为 Queue01Handle
的 osMessageQueueId_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 |
|
在 Task 中, 似乎一定要 osDelay
不然所有任务不会一起进行.
Queues 示例
先创建两个 Tasks 以及一个 Queue, Tasks 的 Entry Function 如下:
1 |
|
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 |
|
接收通知
用下面两个函数来接收通知:
ulTaskNotifyTake()
: 阻塞直到接收到通知, 适合用于任务的状态变化xTaskNotifyWait()
: 可以在接收通知的同时检查是否有新的通知值
1 |
|
示例
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 接管了.