恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
stm32freertos教程(快速入门)
首页
资讯中心
/
stm32freertos教程(快速入门)
stm32freertos教程(快速入门)
发布时间:2026/9/30 17:31:47
# 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裸机程序通常是一个大循环cwhile (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.hint main(void){HAL_Init();SystemClock_Config();Board_PeripheralInit();osKernelInitialize();App_ObjectsInit();osKernelStart();for (;;) {/* 一般不会执行到这里 */}}对象可以在 osKernelStart() 前创建也可以在内核运行后创建只要当前上下文允许。所有创建函数都要检查返回值返回 NULL 代表失败。### 2.2 时间单位、超时和状态码超时参数通常是 **tick 数**不是毫秒cosDelay(100U); // 100 个 tickosDelay(osKernelGetTickFreq()); // 大约 1 秒建议统一做毫秒转换cstatic 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_tosOK成功、osErrorTimeout超时、osErrorResource对象暂不可用且未等待、osErrorParameter参数或上下文错误。### 2.3 线程属性和优先级cstatic const osThreadAttr_t sensor_attr {.name sensor,.priority osPriorityAboveNormal,.stack_size 512U // CMSIS-RTOS2 中通常按字节填写};常用优先级从低到高是 osPriorityIdle、osPriorityLow、osPriorityBelowNormal、osPriorityNormal、osPriorityAboveNormal、osPriorityHigh。具体范围以适配层为准。---## 3. 线程任务与调度### 3.1 创建线程cstatic 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 | 已结束 |cosThreadState_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相对延时cfor (;;) {Sample_Once();osDelay(MsToTicks(10U));}它表示“从现在开始至少等待 10 ms”所以工作耗时变化会造成周期变化。### 4.2 osDelayUntil固定周期cstatic 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 创建和收发消息队列复制消息内容因此发送方的局部变量在函数返回后仍然安全。ctypedef 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();}发送cstatic 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));}}接收cstatic 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可考虑“内存池分配块 队列发送指针”- 多生产者时要明确消息顺序、优先级和拥塞策略。cuint32_t count osMessageQueueGetCount(app_queue_id);uint32_t space osMessageQueueGetSpace(app_queue_id);uint32_t capacity osMessageQueueGetCapacity(app_queue_id);---## 7. 信号量同步事件或管理数量### 7.1 二值信号量ISR 通知线程cstatic osSemaphoreId_t adc_done_id;adc_done_id osSemaphoreNew(1U, 0U, NULL);处理线程cstatic void AdcProcessThread(void *argument){(void)argument;for (;;) {if (osSemaphoreAcquire(adc_done_id, osWaitForever) osOK) {Adc_ProcessBuffer();}}}ADC/DMA 完成中断cvoid HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){(void)hadc;(void)osSemaphoreRelease(adc_done_id);}二值信号量不携带数据。如果事件可能连续到来且不能合并改用计数信号量或消息队列。### 7.2 计数信号量记录次数或管理资源cstatic 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. 互斥量保护共享资源cstatic 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 表示任意一个 bitosFlagsWaitAll 表示全部 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. 内存池固定大小的安全内存块内存池预先划分固定大小的块分配和释放时间稳定适合报文缓冲。ctypedef 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);生产者cPacketBlock_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);}}消费者cPacketBlock_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 定时器线程在到期时调用回调适合短小、非阻塞的动作。cstatic 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 为 0Ucstatic 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/Gettimeout 为 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 栈监控大数组、深层调用和格式化输出会消耗大量栈。可以查询当前线程的栈余量cuint32_t free_stack osThreadGetStackSpace(osThreadGetId());应在峰值路径下观察余量而不是只看正常运行时的数值。### 14.2 重要 FreeRTOS 配置虽然应用调用的是 os...底层仍由 FreeRTOSConfig.h 决定资源- configTOTAL_HEAP_SIZE动态分配的堆总量- configSUPPORT_DYNAMIC_ALLOCATION、configSUPPORT_STATIC_ALLOCATION动态/静态分配开关- configUSE_PREEMPTION抢占开关- configTICK_RATE_HZtick 频率- 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/osKernelStartosThreadNew 是否返回 NULL是否存在更高优先级无限循环是否等待一个不会释放的对象线程栈是否溢出tick、SysTick、PendSV 是否配置正确。### 16.2 队列总是满这表示生产速度长期大于消费速度或者消费者没运行。检查消费者优先级、是否应该合并重复消息、是否应发送内存池指针以及发送失败是否被静默忽略。### 16.3 信号量和互斥量混用“某件事完成”用二值信号量“有 N 个资源”用计数信号量“谁能访问 UART”用互斥量。不要用二值信号量代替互斥量因为它没有所有权和优先级继承语义。### 16.4 延时单位写错osDelay(100U) 是 100 tick不一定是 100 ms。统一使用 MsToTicks() 并确认 osKernelGetTickFreq()。### 16.5 在中断中调用阻塞 APIISR 不能等待队列、互斥量或延时。采用“中断记录少量信息 非阻塞通知 线程处理”的结构。### 16.6 共享变量的可见性ISR 和线程共同访问的简单状态变量通常需要 volatile但 volatile 不能保证复合操作原子也不能代替队列、锁或临界区。多个字节的结构体更新应使用消息队列或互斥量。---## 17. 学习路线和速查表### 17.1 推荐练习顺序1. 创建两个 osThreadNew 线程并用 osDelay 闪烁不同 LED2. 用 osMessageQueueNew/Put/Get 完成按键到控制线程的数据传递3. 用 osSemaphoreNew/Acquire/Release 实现 DMA 完成通知4. 用 osMutexNew/Acquire/Release 保护 UART5. 用 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. 高优先级线程不能无限占用 CPU4. 周期任务优先考虑 osDelayUntil5. ISR 只做短、快、非阻塞的工作6. 所有对象创建都检查 NULL所有超时操作都检查 osStatus_t7. 定期检查栈余量、队列峰值和堆空间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)- [CSDNFreeRTOS 事件组详解](https://blog.csdn.net/freestep96/article/details/130227935)- [CSDNCMSIS-RTOS2 功能概述与 API 清单](https://blog.csdn.net/u012325601/article/details/80031669)- [CSDNFreeRTOS 学习与优先级反转、事件组](https://blog.csdn.net/2301_76908811/article/details/134913691)- [CSDNSTM32CubeMX 创建 CMSIS-RTOS2 任务](https://blog.csdn.net/qq_63922192/article/details/134111549) 不同芯片包、FreeRTOS 版本和 CMSIS-RTOS2 适配层可能对静态内存属性、ISR 可调用范围、优先级数量有差异。编译前以工程中的 cmsis_os2.h、适配层实现和 FreeRTOSConfig.h 为最终依据。