拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

stm32freertos教程(快速入门)

stm32freertos教程(快速入门)

# 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` 为最终依据。

返回列表