# FreeRTOS 学习笔记(CMSIS-RTOS2 API 版)
> 本笔记面向 STM32 + FreeRTOS + CMSIS-RTOS2 的常见项目。概念来自 FreeRTOS,示例统一使用 `cmsis_os2.h` 中的 `os...` API,例如 `osThreadNew`、`osMessageQueuePut`、`osSemaphoreAcquire`。
>
> 这里关注“怎么选、怎么写、怎么排错”,不过度展开内核源码。代码中的 `HAL_...`、`BSP_...`、`Sensor_...` 代表芯片或板级驱动函数,不属于 RTOS API。
## 目录
1. [先建立整体认识](#1-先建立整体认识)
2. [CMSIS-RTOS2 常用约定](#2-cmsis-rtos2-常用约定)
3. [线程(任务)与调度](#3-线程任务与调度)
4. [延时、周期任务与线程控制](#4-延时周期任务与线程控制)
5. [任务之间如何通信:选型表](#5-任务之间如何通信选型表)
6. [消息队列:传递具体数据](#6-消息队列传递具体数据)
7. [信号量:同步事件或管理数量](#7-信号量同步事件或管理数量)
8. [互斥量:保护共享资源](#8-互斥量保护共享资源)
9. [事件标志:等待多个条件](#9-事件标志等待多个条件)
10. [线程标志:快速通知一个指定线程](#10-线程标志快速通知一个指定线程)
11. [内存池:固定大小的安全内存块](#11-内存池固定大小的安全内存块)
12. [软件定时器](#12-软件定时器)
13. [中断与 RTOS](#13-中断与-rtos)
14. [任务栈、动态内存和配置](#14-任务栈动态内存和配置)
15. [一个小型项目骨架](#15-一个小型项目骨架)
16. [高频错误与排错顺序](#16-高频错误与排错顺序)
17. [学习路线和速查表](#17-学习路线和速查表)
18. [参考资料](#18-参考资料)
---
## 1. 先建立整体认识
### 1.1 为什么需要 RTOS
裸机程序通常是一个大循环:
```c
while (1) {
Key_Scan();
Motor_Control();
Uart_Process();
}
```
功能少时很直观,但会逐渐出现这些问题:某个函数执行时间太长会拖住其他功能;全局变量容易被覆盖;很难表达“等待 100 ms”“等待串口收到一帧”“等待两个传感器都完成”;优先级、超时和资源保护散落在各处。
RTOS 把程序拆成多个线程(FreeRTOS 中通常称为 task),由调度器选择当前运行的线程;线程之间通过消息队列、信号量、互斥量、事件标志等内核对象协作。
### 1.2 并发不等于并行
单核 MCU 同一时刻只能执行一个线程。调度器通过抢占和切换,让多个线程在时间上交替运行,这叫并发;多核芯片才可能真正并行。实时的重点也不是“绝对最快”,而是在规定时间内得到可预测的响应。
### 1.3 FreeRTOS 与 CMSIS-RTOS2 的关系
CMSIS-RTOS2 是 ARM 定义的一套统一 RTOS 接口;FreeRTOS 提供了 CMSIS-RTOS2 适配层。应用层只包含:
```c
#include "cmsis_os2.h"
```
本文不直接调用 FreeRTOS 原生函数,应用层统一使用 `os...` API。
---
## 2. CMSIS-RTOS2 常用约定
### 2.1 内核启动顺序
```c
#include "cmsis_os2.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
Board_PeripheralInit();
osKernelInitialize();
App_ObjectsInit();
osKernelStart();
for (;;) {
/* 一般不会执行到这里 */
}
}
```
对象可以在 `osKernelStart()` 前创建,也可以在内核运行后创建(只要当前上下文允许)。所有创建函数都要检查返回值,返回 `NULL` 代表失败。
### 2.2 时间单位、超时和状态码
超时参数通常是 **tick 数**,不是毫秒:
```c
osDelay(100U); // 100 个 tick
osDelay(osKernelGetTickFreq()); // 大约 1 秒
```
建议统一做毫秒转换:
```c
static uint32_t MsToTicks(uint32_t ms)
{
uint64_t ticks = ((uint64_t)ms * osKernelGetTickFreq()) / 1000U;
return (ticks == 0U && ms != 0U) ? 1U : (uint32_t)ticks;
}
```
| 写法 | 含义 |
| --- | --- |
| `0U` | 不等待,立即返回 |
| `osWaitForever` | 一直等待 |
| `MsToTicks(20U)` | 最多等待约 20 ms |
常见 `osStatus_t`:`osOK`(成功)、`osErrorTimeout`(超时)、`osErrorResource`(对象暂不可用且未等待)、`osErrorParameter`(参数或上下文错误)。
### 2.3 线程属性和优先级
```c
static const osThreadAttr_t sensor_attr = {
.name = "sensor",
.priority = osPriorityAboveNormal,
.stack_size = 512U // CMSIS-RTOS2 中通常按字节填写
};
```
常用优先级从低到高是 `osPriorityIdle`、`osPriorityLow`、`osPriorityBelowNormal`、`osPriorityNormal`、`osPriorityAboveNormal`、`osPriorityHigh`。具体范围以适配层为准。
---
## 3. 线程(任务)与调度
### 3.1 创建线程
```c
static void LedThread(void *argument)
{
(void)argument;
for (;;) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
osDelay(MsToTicks(500U));
}
}
static const osThreadAttr_t led_attr = {
.name = "led",
.priority = osPriorityLow,
.stack_size = 256U
};
osThreadId_t led_id = osThreadNew(LedThread, NULL, &led_attr);
if (led_id == NULL) {
Error_Handler();
}
```
线程函数通常是无限循环;确实需要结束时,可以从线程内部 `return`,或调用 `osThreadTerminate(thread_id)`。
### 3.2 调度器规则
抢占式调度下,READY 状态中优先级最高的线程获得 CPU;同优先级线程通常按时间片轮转(受 FreeRTOS 配置影响)。高优先级线程不能无限循环,完成一小段工作后应调用 `osDelay`、`osThreadYield` 或等待队列/信号量。
### 3.3 线程状态与控制
| 状态 | 说明 |
| --- | --- |
| Running | 当前正在执行 |
| Ready | 已就绪,等待调度 |
| Blocked | 等待延时或对象,不占 CPU |
| Suspended | 被显式挂起,不会自动恢复 |
| Terminated | 已结束 |
```c
osThreadState_t state = osThreadGetState(led_id);
osPriority_t priority = osThreadGetPriority(led_id);
osThreadSetPriority(led_id, osPriorityNormal);
osThreadSuspend(led_id);
osThreadResume(led_id);
osThreadYield();
```
不要用挂起/恢复代替“数据到了”“转换完成了”这类事件通信;这类需求优先使用队列、信号量或线程标志。
---
## 4. 延时、周期任务与线程控制
### 4.1 `osDelay`:相对延时
```c
for (;;) {
Sample_Once();
osDelay(MsToTicks(10U));
}
```
它表示“从现在开始至少等待 10 ms”,所以工作耗时变化会造成周期变化。
### 4.2 `osDelayUntil`:固定周期
```c
static void ControlThread(void *argument)
{
uint32_t next_tick = osKernelGetTickCount();
const uint32_t period = MsToTicks(10U);
(void)argument;
for (;;) {
Motor_ControlStep();
next_tick += period;
osDelayUntil(next_tick);
}
}
```
如果一次计算超过周期,线程会尽快进入下一次循环,应该降低计算量或重新评估实时性。
---
## 5. 任务之间如何通信:选型表
| 需求 | 首选对象 | 是否携带数据 | 典型场景 |
| --- | --- | --- | --- |
| 生产者把结构体交给消费者 | 消息队列 | 是,按值拷贝 | 串口帧、按键、传感器数据 |
| 通知“一次事件发生” | 二值信号量 | 否 | ISR 通知线程、DMA 完成 |
| 管理有限个相同资源 | 计数信号量 | 否 | 缓冲区、连接数、空闲设备 |
| 保护一次只能一个线程访问的资源 | 互斥量 | 否 | UART、I2C、SPI、共享配置 |
| 等待多个条件中的一个或全部 | 事件标志 | 位集合 | 网络已连接、传感器就绪 |
| 快速通知一个指定线程 | 线程标志 | 位集合 | ISR 唤醒唯一处理线程 |
| 固定大小内存块所有权转移 | 内存池 + 消息队列 | 指针/块 | 大报文、零拷贝风格缓冲 |
| 周期或单次延后执行 | 软件定时器 | 回调参数 | 超时、重试、LED 闪烁 |
简单判断:要传数据用消息队列;只表达事件用信号量或标志;保护资源用互斥量;多个任务都要知道用事件标志;只有一个固定接收者且追求轻量用线程标志。
---
## 6. 消息队列:传递具体数据
### 6.1 创建和收发
消息队列复制消息内容,因此发送方的局部变量在函数返回后仍然安全。
```c
typedef struct {
uint8_t type;
uint8_t length;
uint16_t value;
} AppMessage_t;
static osMessageQueueId_t app_queue_id;
app_queue_id = osMessageQueueNew(16U, sizeof(AppMessage_t), NULL);
if (app_queue_id == NULL) {
Error_Handler();
}
```
发送:
```c
static void KeyThread(void *argument)
{
AppMessage_t msg = {0};
(void)argument;
for (;;) {
if (Key_IsPressed()) {
msg.type = 1U;
msg.length = 1U;
msg.value = KEY_OK;
(void)osMessageQueuePut(app_queue_id, &msg, 0U,
MsToTicks(20U));
}
osDelay(MsToTicks(5U));
}
}
```
接收:
```c
static void AppThread(void *argument)
{
AppMessage_t msg;
(void)argument;
for (;;) {
if (osMessageQueueGet(app_queue_id, &msg, NULL,
osWaitForever) == osOK) {
App_HandleMessage(&msg);
}
}
}
```
### 6.2 关键点
- `msg_count` 是最多保存多少条消息,`msg_size` 是每条消息的字节数;
- 队列保存数据副本,不是发送方变量的引用;
- 队列满时可以立即失败、有限等待或永久等待;
- 大数组直接放入队列会占用较多 RAM,可考虑“内存池分配块 + 队列发送指针”;
- 多生产者时要明确消息顺序、优先级和拥塞策略。
```c
uint32_t count = osMessageQueueGetCount(app_queue_id);
uint32_t space = osMessageQueueGetSpace(app_queue_id);
uint32_t capacity = osMessageQueueGetCapacity(app_queue_id);
```
---
## 7. 信号量:同步事件或管理数量
### 7.1 二值信号量:ISR 通知线程
```c
static osSemaphoreId_t adc_done_id;
adc_done_id = osSemaphoreNew(1U, 0U, NULL);
```
处理线程:
```c
static void AdcProcessThread(void *argument)
{
(void)argument;
for (;;) {
if (osSemaphoreAcquire(adc_done_id, osWaitForever) == osOK) {
Adc_ProcessBuffer();
}
}
}
```
ADC/DMA 完成中断:
```c
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
(void)hadc;
(void)osSemaphoreRelease(adc_done_id);
}
```
二值信号量不携带数据。如果事件可能连续到来且不能合并,改用计数信号量或消息队列。
### 7.2 计数信号量:记录次数或管理资源
```c
static osSemaphoreId_t buffer_slots_id;
buffer_slots_id = osSemaphoreNew(4U, 4U, NULL);
if (osSemaphoreAcquire(buffer_slots_id, MsToTicks(10U)) == osOK) {
Buffer_UseOne();
(void)osSemaphoreRelease(buffer_slots_id);
}
```
计数信号量的 token 数量代表资源数量;它没有“拥有者”,不适合保护需要严格成对加锁/解锁的共享资源。
---
## 8. 互斥量:保护共享资源
```c
static osMutexId_t uart_mutex_id;
uart_mutex_id = osMutexNew(NULL);
static void UartPrint(const char *text)
{
if (osMutexAcquire(uart_mutex_id, MsToTicks(50U)) == osOK) {
HAL_UART_Transmit(&huart1, (uint8_t *)text,
(uint16_t)strlen(text), 100U);
(void)osMutexRelease(uart_mutex_id);
} else {
Log_DropMessage();
}
}
```
互斥量同一时刻只能被一个线程持有,必须由持有者释放;适配层通常支持优先级继承,可缓解优先级反转;不能在 ISR 中获取。锁内不要做长时间计算或无界阻塞。
优先级反转的典型情况是:低优先级 L 持锁,高优先级 H 等锁,中优先级 M 抢占 L。互斥量的优先级继承会临时提高 L 的优先级,让它尽快释放锁。二值信号量没有这个语义。
如果线程需要多个锁,所有线程必须按固定顺序获取,例如总是先 `spi_mutex` 再 `flash_mutex`,并设置有限超时,避免死锁。
---
## 9. 事件标志:等待多个条件
事件标志对象是一组 bit,每个 bit 表示一个条件,适合广播给多个线程。
```c
#define EVT_NET_READY (1U << 0)
#define EVT_TIME_READY (1U << 1)
#define EVT_SENSOR_OK (1U << 2)
static osEventFlagsId_t system_events_id;
system_events_id = osEventFlagsNew(NULL);
(void)osEventFlagsSet(system_events_id, EVT_NET_READY);
uint32_t flags = osEventFlagsWait(system_events_id,
EVT_NET_READY | EVT_TIME_READY,
osFlagsWaitAll | osFlagsNoClear,
osWaitForever);
if ((flags & (EVT_NET_READY | EVT_TIME_READY)) ==
(EVT_NET_READY | EVT_TIME_READY)) {
Business_Start();
}
```
`osFlagsWaitAny` 表示任意一个 bit,`osFlagsWaitAll` 表示全部 bit;不加 `osFlagsNoClear` 时满足条件后会自动清除等待的 bit,加上后只观察不清除。事件标志表达状态/条件,不携带复杂数据。
---
## 10. 线程标志:快速通知一个指定线程
线程标志只能发给一个指定线程,不需要单独创建对象,适合轻量唤醒。
```c
#define THREAD_FLAG_RX (1U << 0)
static osThreadId_t rx_thread_id;
static void RxThread(void *argument)
{
(void)argument;
for (;;) {
uint32_t flags = osThreadFlagsWait(THREAD_FLAG_RX,
osFlagsWaitAny,
osWaitForever);
if ((flags & THREAD_FLAG_RX) != 0U) {
Uart_ParsePendingBytes();
}
}
}
void USART1_IRQHandler(void)
{
uint32_t status = UART_ReadStatus();
UART_ClearInterrupt(status);
if ((status & UART_RX_FLAG) != 0U) {
(void)osThreadFlagsSet(rx_thread_id, THREAD_FLAG_RX);
}
}
```
线程标志不能代替必须逐条处理的数据。常见组合是:接收数据放队列,线程标志只负责唤醒。
---
## 11. 内存池:固定大小的安全内存块
内存池预先划分固定大小的块,分配和释放时间稳定,适合报文缓冲。
```c
typedef struct {
uint16_t length;
uint8_t payload[128];
} PacketBlock_t;
static osMemoryPoolId_t packet_pool_id;
static osMessageQueueId_t packet_queue_id;
packet_pool_id = osMemoryPoolNew(8U, sizeof(PacketBlock_t), NULL);
packet_queue_id = osMessageQueueNew(8U, sizeof(PacketBlock_t *), NULL);
```
生产者:
```c
PacketBlock_t *packet = osMemoryPoolAlloc(packet_pool_id, MsToTicks(5U));
if (packet != NULL) {
packet->length = Packet_Read(packet->payload, sizeof(packet->payload));
if (osMessageQueuePut(packet_queue_id, &packet, 0U, 0U) != osOK) {
(void)osMemoryPoolFree(packet_pool_id, packet);
}
}
```
消费者:
```c
PacketBlock_t *packet = NULL;
if (osMessageQueueGet(packet_queue_id, &packet, NULL, osWaitForever) == osOK) {
Packet_Process(packet->payload, packet->length);
(void)osMemoryPoolFree(packet_pool_id, packet);
}
```
必须明确内存块最终由谁释放;队列失败时要立即归还,否则内存池会逐渐耗尽。
---
## 12. 软件定时器
软件定时器由 RTOS 定时器线程在到期时调用回调,适合短小、非阻塞的动作。
```c
static osTimerId_t led_timer_id;
static void LedTimerCallback(void *argument)
{
(void)argument;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
led_timer_id = osTimerNew(LedTimerCallback, osTimerPeriodic, NULL, NULL);
if (led_timer_id != NULL) {
(void)osTimerStart(led_timer_id, MsToTicks(500U));
}
```
单次定时器使用 `osTimerOnce`。回调中不要等待队列或互斥量;可只设置事件标志或向队列发送命令,把业务交给普通线程。
---
## 13. 中断与 RTOS
### 13.1 ISR 的职责
中断只读取硬件状态、清除标志、保存必要数据、通知线程,然后退出。复杂计算、协议解析和日志输出放在线程中。
### 13.2 ISR 中的 CMSIS-RTOS2 调用
ISR 中必须使用不等待的调用,timeout 为 `0U`:
```c
static osMessageQueueId_t rx_queue_id;
void DMA1_Channel1_IRQHandler(void)
{
DMA_ClearFlag();
(void)osSemaphoreRelease(adc_done_id);
}
void USART2_IRQHandler(void)
{
uint8_t byte = UART_ReadByte();
(void)osMessageQueuePut(rx_queue_id, &byte, 0U, 0U);
}
```
常见适合 ISR 的接口是 `osSemaphoreRelease`、`osMessageQueuePut/Get`(timeout 为 0)、`osThreadFlagsSet`、`osEventFlagsSet`。不要在 ISR 中调用 `osDelay`、`osThreadNew`、`osMutexAcquire`,也不要调用任何可能阻塞的接口。
### 13.3 中断优先级陷阱
Cortex-M NVIC 通常是数值越小优先级越高;RTOS 线程优先级通常是数值越大越高,两者不是一套数字。能够调用 RTOS ISR API 的中断优先级必须在 `FreeRTOSConfig.h` 的 `configMAX_SYSCALL_INTERRUPT_PRIORITY` 等设置允许的范围内。
---
## 14. 任务栈、动态内存和配置
### 14.1 栈监控
大数组、深层调用和格式化输出会消耗大量栈。可以查询当前线程的栈余量:
```c
uint32_t free_stack = osThreadGetStackSpace(osThreadGetId());
```
应在峰值路径下观察余量,而不是只看正常运行时的数值。
### 14.2 重要 FreeRTOS 配置
虽然应用调用的是 `os...`,底层仍由 `FreeRTOSConfig.h` 决定资源:
- `configTOTAL_HEAP_SIZE`:动态分配的堆总量;
- `configSUPPORT_DYNAMIC_ALLOCATION`、`configSUPPORT_STATIC_ALLOCATION`:动态/静态分配开关;
- `configUSE_PREEMPTION`:抢占开关;
- `configTICK_RATE_HZ`:tick 频率;
- `configMAX_PRIORITIES`:优先级数量;
- `configUSE_MUTEXES`、`configUSE_COUNTING_SEMAPHORES`:对象功能开关;
- `configCHECK_FOR_STACK_OVERFLOW`:栈溢出检查;
- `configUSE_TIMERS`:软件定时器支持。
启用静态分配时,可以通过 `osThreadAttr_t` 的 `cb_mem/cb_size` 和 `stack_mem/stack_size` 提供内存,但具体支持程度以当前 CMSIS-RTOS2 适配层为准。
---
## 15. 一个小型项目骨架
下面组合了消息队列、二值信号量、事件标志和周期线程:
```c
#include "cmsis_os2.h"
#define EVT_ADC_READY (1U << 0)
#define EVT_APP_READY (1U << 1)
typedef struct {
uint8_t type;
uint16_t value;
} AppMessage_t;
static osMessageQueueId_t app_queue_id;
static osSemaphoreId_t adc_done_id;
static osEventFlagsId_t system_events_id;
static const osThreadAttr_t control_attr = {
.name = "control", .priority = osPriorityAboveNormal, .stack_size = 512U
};
static const osThreadAttr_t key_attr = {
.name = "key", .priority = osPriorityNormal, .stack_size = 384U
};
static const osThreadAttr_t periodic_attr = {
.name = "periodic", .priority = osPriorityLow, .stack_size = 256U
};
static void ObjectsInit(void)
{
app_queue_id = osMessageQueueNew(8U, sizeof(AppMessage_t), NULL);
adc_done_id = osSemaphoreNew(1U, 0U, NULL);
system_events_id = osEventFlagsNew(NULL);
if ((app_queue_id == NULL) || (adc_done_id == NULL) ||
(system_events_id == NULL)) {
Error_Handler();
}
}
static void KeyThread(void *argument)
{
AppMessage_t message;
(void)argument;
for (;;) {
if (Key_GetEvent(&message.value)) {
message.type = 1U;
(void)osMessageQueuePut(app_queue_id, &message, 0U,
MsToTicks(10U));
}
osDelay(MsToTicks(5U));
}
}
static void ControlThread(void *argument)
{
AppMessage_t message;
(void)argument;
(void)osEventFlagsSet(system_events_id, EVT_APP_READY);
for (;;) {
if (osMessageQueueGet(app_queue_id, &message, NULL,
osWaitForever) == osOK) {
Control_HandleKey(message.value);
}
if (osSemaphoreAcquire(adc_done_id, 0U) == osOK) {
Control_HandleAdc();
}
}
}
static void PeriodicThread(void *argument)
{
uint32_t next_tick = osKernelGetTickCount();
const uint32_t period = MsToTicks(20U);
(void)argument;
for (;;) {
Control_20msStep();
next_tick += period;
osDelayUntil(next_tick);
}
}
void App_Init(void)
{
ObjectsInit();
(void)osThreadNew(ControlThread, NULL, &control_attr);
(void)osThreadNew(KeyThread, NULL, &key_attr);
(void)osThreadNew(PeriodicThread, NULL, &periodic_attr);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
(void)hadc;
(void)osSemaphoreRelease(adc_done_id);
(void)osEventFlagsSet(system_events_id, EVT_ADC_READY);
}
```
实际项目中不建议让一个线程承担太多工作;更常见的结构是 ISR 通知专用外设线程,外设线程把整理好的消息交给业务线程。
---
## 16. 高频错误与排错顺序
### 16.1 线程像死机一样不动
检查:是否调用 `osKernelInitialize`/`osKernelStart`;`osThreadNew` 是否返回 `NULL`;是否存在更高优先级无限循环;是否等待一个不会释放的对象;线程栈是否溢出;tick、SysTick、PendSV 是否配置正确。
### 16.2 队列总是满
这表示生产速度长期大于消费速度,或者消费者没运行。检查消费者优先级、是否应该合并重复消息、是否应发送内存池指针,以及发送失败是否被静默忽略。
### 16.3 信号量和互斥量混用
“某件事完成”用二值信号量;“有 N 个资源”用计数信号量;“谁能访问 UART”用互斥量。不要用二值信号量代替互斥量,因为它没有所有权和优先级继承语义。
### 16.4 延时单位写错
`osDelay(100U)` 是 100 tick,不一定是 100 ms。统一使用 `MsToTicks()` 并确认 `osKernelGetTickFreq()`。
### 16.5 在中断中调用阻塞 API
ISR 不能等待队列、互斥量或延时。采用“中断记录少量信息 + 非阻塞通知 + 线程处理”的结构。
### 16.6 共享变量的可见性
ISR 和线程共同访问的简单状态变量通常需要 `volatile`,但 `volatile` 不能保证复合操作原子,也不能代替队列、锁或临界区。多个字节的结构体更新应使用消息队列或互斥量。
---
## 17. 学习路线和速查表
### 17.1 推荐练习顺序
1. 创建两个 `osThreadNew` 线程并用 `osDelay` 闪烁不同 LED;
2. 用 `osMessageQueueNew/Put/Get` 完成按键到控制线程的数据传递;
3. 用 `osSemaphoreNew/Acquire/Release` 实现 DMA 完成通知;
4. 用 `osMutexNew/Acquire/Release` 保护 UART;
5. 用 `osEventFlagsNew/Set/Wait` 等待多个初始化条件;
6. 用 `osThreadFlagsSet/Wait` 完成串口接收线程唤醒;
7. 加入软件定时器、内存池、栈余量监控;
8. 故意制造队列满、超时和优先级反转,再观察日志。
### 17.2 API 速查
| 功能 | 高频 API |
| --- | --- |
| 内核 | `osKernelInitialize`、`osKernelStart`、`osKernelGetTickCount`、`osKernelGetTickFreq` |
| 线程 | `osThreadNew`、`osThreadGetId`、`osThreadSetPriority`、`osThreadSuspend`、`osThreadResume`、`osThreadTerminate`、`osThreadYield` |
| 延时 | `osDelay`、`osDelayUntil` |
| 消息队列 | `osMessageQueueNew`、`osMessageQueuePut`、`osMessageQueueGet`、`osMessageQueueGetCount`、`osMessageQueueGetSpace` |
| 信号量 | `osSemaphoreNew`、`osSemaphoreAcquire`、`osSemaphoreRelease`、`osSemaphoreGetCount` |
| 互斥量 | `osMutexNew`、`osMutexAcquire`、`osMutexRelease`、`osMutexGetOwner` |
| 事件标志 | `osEventFlagsNew`、`osEventFlagsSet`、`osEventFlagsClear`、`osEventFlagsWait` |
| 线程标志 | `osThreadFlagsSet`、`osThreadFlagsClear`、`osThreadFlagsWait` |
| 内存池 | `osMemoryPoolNew`、`osMemoryPoolAlloc`、`osMemoryPoolFree` |
| 定时器 | `osTimerNew`、`osTimerStart`、`osTimerStop`、`osTimerDelete` |
### 17.3 最重要的规则
1. 数据传递用消息队列,事件同步用信号量或标志;
2. 保护共享资源用互斥量,并尽快释放;
3. 高优先级线程不能无限占用 CPU;
4. 周期任务优先考虑 `osDelayUntil`;
5. ISR 只做短、快、非阻塞的工作;
6. 所有对象创建都检查 `NULL`,所有超时操作都检查 `osStatus_t`;
7. 定期检查栈余量、队列峰值和堆空间;
8. 先画出数据流和所有权,再决定使用哪个 RTOS 对象。
---
## 18. 参考资料
以下资料用于交叉核对概念、API 参数和常见实践;示例已按 CMSIS-RTOS2 API 重新整理:
- [ARM CMSIS-RTOS2 API 总览](https://arm-software.github.io/CMSIS_5/RTOS2/html/rtos_api2.html)
- [ARM CMSIS-RTOS2 线程管理](https://arm-software.github.io/CMSIS_5/RTOS2/html/group__CMSIS__RTOS__ThreadMgmt.html)
- [ARM CMSIS-RTOS2 消息队列](https://arm-software.github.io/CMSIS_6/main/RTOS2/group__CMSIS__RTOS__Message.html)
- [ARM CMSIS-RTOS2 信号量](https://arm-software.github.io/CMSIS_6/main/RTOS2/group__CMSIS__RTOS__SemaphoreMgmt.html)
- [CSDN:FreeRTOS 事件组详解](https://blog.csdn.net/freestep96/article/details/130227935)
- [CSDN:CMSIS-RTOS2 功能概述与 API 清单](https://blog.csdn.net/u012325601/article/details/80031669)
- [CSDN:FreeRTOS 学习与优先级反转、事件组](https://blog.csdn.net/2301_76908811/article/details/134913691)
- [CSDN:STM32CubeMX 创建 CMSIS-RTOS2 任务](https://blog.csdn.net/qq_63922192/article/details/134111549)
> 不同芯片包、FreeRTOS 版本和 CMSIS-RTOS2 适配层可能对静态内存属性、ISR 可调用范围、优先级数量有差异。编译前以工程中的 `cmsis_os2.h`、适配层实现和 `FreeRTOSConfig.h` 为最终依据。