本文基于 STM32 HAL 库 + CubeMX + FreeRTOS,采用CMSIS-RTOS V1 标准 API开发,适配绝大多数 STM32F1/F4/F7/H7/L4 系列单片机。 CMSIS-RTOS 对 FreeRTOS 进行上层封装,屏蔽原生 xTaskCreate/xSemaphoreTake 等底层函数,一套 API 可兼容 RTX、FreeRTOS 等实时内核,降低跨 RTOS 迁移成本。 配套文档:CMSISIS_OS_V1 主要函数完整 API 手册(文中附录完整收录所有接口)。
二、CubeMX 工程搭建(核心避坑)
2.1 基础芯片与 RCC 配置
- 选择对应 STM32 芯片,配置外部高速晶振 HSE;
- SYS->Debug:选择 Serial Wire(下载调试);
- 关键:时基源分离(重中之重)
- 进入 SYS->Timebase Source:不要选择 SysTick,改用 TIM1/TIM2/TIM4 等普通定时器;
- 原因:FreeRTOS 占用 SysTick 做系统节拍,HAL 库延时(HAL_Delay、HAL_UART 超时)共用 SysTick 会造成调度冲突、死机、延时不准。
2.2 FreeRTOS 内核参数配置
进入 Middleware and Software Packs -> FREERTOS,模式选择 CMSIS-RTOS V1:
-
Config parameters 基础配置
表格
参数 推荐值 说明 configUSE_PREEMPTION 1 抢占式调度(实时系统必开) configCPU_CLOCK_HZ 芯片主频 自动填充,核对即可 configTICK_RATE_HZ 1000 1ms 系统节拍,osDelay 单位毫秒匹配 configMAX_PRIORITIES 16 任务优先级 0~15,数值越大优先级越高 configMINIMAL_STACK_SIZE 128 空闲任务最小栈 configTOTAL_HEAP_SIZE 8192/16384 动态内存总堆,外设多、消息队列大则加大 configSUPPORT_STATIC_ALLOCATION 1 开启静态内存分配,避免堆碎片 configUSE_MUTEXES 1 开启互斥量 configUSE_RECURSIVE_MUTEXES 1 开启递归互斥量 configUSE_SEMAPHORES 1 开启信号量 configUSE_TIMERS 1 开启软件定时器 configUSE_SIGNALS 1 开启线程信号标志 -
任务默认栈:普通 IO 任务 512,浮点运算 / 串口打印 1024,复杂算法 2048 以上;
-
生成工程设置:生成独立.c/.h 文件,勾选 Generate initialization as .c/.h pair。
2.3 工程代码基础初始化
CubeMX 自动生成freertos.c,内核启动流程固定:
// main.c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_FREERTOS_Init(); // CubeMX自动生成,创建所有线程/信号量/队列
osKernelStart(); // 启动RTOS内核,永不返回
while(1);
}
osKernelStart()内部初始化调度器,创建空闲任务、定时器服务任务,调用后所有线程进入调度。
三、CMSIS-RTOS V1 内核基础 API 详解
3.1 内核控制函数
osStatus osKernelStart (void)
- 功能:启动 RTOS 内核调度,所有就绪线程开始运行;
- 返回值:osOK 正常;内核启动后函数不返回。
int32_t osKernelRunning(void)
- 返回 1:内核已启动;0:未启动;-1:该功能被配置关闭;
- 适用场景:区分初始化阶段与任务运行阶段。
uint32_t osKernelSysTick(void)
- 获取系统节拍计数器,单位 ms;
- 配合
osDelayUntil实现精准周期任务。
3.2 线程(任务)全套 API(最常用)
3.2.1 线程结构体定义
typedef struct os_thread_def {
char *name; // 线程名称,调试用
os_pthread pthread; // 任务函数入口
osPriority tpriority; // 优先级osPriorityLow~osPriorityHigh
uint32_t instances; // 任务最大实例数,普通任务填1
uint32_t stacksize; // 栈大小字节
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
uint32_t *buffer; // 静态栈缓冲区,动态分配填NULL
osStaticThreadDef_t *controlblock;// 静态任务控制块,动态分配填NULL
#endif
} osThreadDef_t;
3.2.2 核心接口
-
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)创建任务,参数 argument 传递入口参数;返回线程 ID,NULL 创建失败。 示例(CubeMX 标准写法):// 定义任务
osThreadDef(TaskLed, LedTask, osPriorityNormal, 1, 512);
osThreadId TaskLedHandle;
// freertos.c初始化
void MX_FREERTOS_Init(void) {
TaskLedHandle = osThreadCreate(osThread(TaskLed), NULL);
}
// 任务函数
void LedTask(void const * argument)
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5);
osDelay(500);
}
} -
osThreadGetId(void):获取当前运行任务 ID; -
osThreadTerminate(osThreadId thread_id):销毁指定任务; -
osThreadSuspend / osThreadResume:挂起 / 恢复任务; -
osThreadSuspendAll / osThreadResumeAll:全局暂停所有线程(临界保护慎用); -
osThreadSetPriority / osThreadGetPriority:动态修改任务优先级;
3.2.3 延时函数区别
-
osDelay(uint32_t millisec)相对延时:阻塞当前任务指定毫秒,任务让出 CPU;适用于普通闪烁、轮询。 -
osDelayUntil(uint32_t *PreviousWakeTime, uint32_t millisec)绝对周期延时,不受任务运行耗时影响,适合精准周期采集、定时上报:uint32_t tick = osKernelSysTick();
for(;;)
{
AD_Read();
osDelayUntil(&tick, 10); // 严格10ms周期执行一次采集
} -
osAbortDelay(osThreadId thread_id):强制终止某个任务的延时阻塞。
3.3 软件定时器(osTimer)
- 两种类型:
- osTimerOnce:单次定时器,触发一次后停止;
- osTimerPeriodic:周期定时器,循环触发回调;
- 关键函数:
osTimerCreate创建、osTimerStart(timer_id, ms)启动、osTimerStop停止、osTimerDelete销毁;
注意:定时器回调运行在定时器服务任务,栈很小,禁止复杂运算、长延时、阻塞 API。
3.4 线程信号标志(Signal,单任务多事件唤醒)
一套 32bit 标志位,用于同一任务内多路事件等待(按键、串口、定时):
osSignalSet(thread_id, signal):置位指定 bit;中断中可用来唤醒任务;osSignalClear:清除标志位;osSignalWait(signals, timeout):阻塞等待指定信号,支持等待任意 / 全部标志。
四、同步互斥机制(资源保护 & 任务同步)
4.1 互斥量 Mutex(独占资源保护)
用于串口、LCD、全局变量等独占资源,自动解决优先级反转。
osMutexCreate创建互斥;osMutexWait(mutex_id, timeout)获取互斥,超时等待;osMutexRelease释放互斥;- 递归互斥
osRecursiveMutexCreate:同一任务可多次获取,配套 RecursiveWait/Release;
禁止中断内使用互斥量。
4.2 信号量 Semaphore(任务同步、资源计数)
- 二值信号量:中断唤醒任务(串口接收、外部中断);
- 计数信号量:管理有限硬件资源(多个串口、缓冲通道); 接口:
osSemaphoreCreate(def, count)创建;osSemaphoreWait获取(count-1);osSemaphoreRelease释放(count+1);osSemaphoreGetCount获取当前剩余资源数。
五、任务间通信(数据传递)
5.1 消息队列 osMessageQ(32 位简单数据)
每条消息仅存储 uint32_t,适合传递状态码、标志、指针地址;
osMessagePut发送消息,可设置阻塞超时;osMessageGet阻塞读取消息,返回 osEvent 结构体;osMessagePeek查看队首消息不弹出;osMessageWaiting查询队列当前消息数量;osMessageAvailableSpace查询剩余空位。
5.2 邮件队列 osMailQ(大块结构体数据)
结合内存池,传递完整结构体(传感器数据、协议帧),自动管理内存分配释放:
osMailCreate创建邮件队列;osMailAlloc / osMailCAlloc申请内存块(CAlloc 自动清零);osMailPut将内存块送入队列;osMailGet阻塞读取邮件;osMailFree使用完成释放内存块(必须释放,否则内存泄漏)。
5.3 内存池 osPool(固定大小内存块,防碎片)
相比 malloc 动态分配,内存池预分配固定大小块,无堆碎片,实时性更高; osPoolAlloc 分配、osPoolFree归还。
5.4 osEvent 通用返回结构体
所有等待类 API(消息、邮件、信号)统一返回 osEvent:
typedef struct {
osStatus status; // 状态码:正常/超时/错误
union {
uint32_t v; // 32位消息数值
void *p; // 邮件/内存指针
int32_t signals; // 信号标志
} value;
union {
osMailQId mail_id;
osMessageQId message_id;
} def;
} osEvent;
六、中断服务程序与 FreeRTOS 适配(高频踩坑点)
-
中断优先级规则: FreeRTOS 配置
configMAX_SYSCALL_INTERRUPT_PRIORITY,高于该优先级的中断禁止调用任何 CMSIS-RTOS API;仅低优先级中断可使用信号量置位、信号置位等无阻塞函数。 -
中断内允许 API: osSignalSet、osSemaphoreRelease、osMessagePut 无阻塞版本;
-
中断禁止:osDelay、osMutexWait、osMailGet 等阻塞函数;
-
示例:外部中断唤醒采集任务
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0)
{
osSignalSet(TaskCollectHandle, 0x01); // 置位信号唤醒采集任务
}
}
七、工程实战完整示例(多任务综合 Demo)
7.1 功能规划
- LedTask:500ms 翻转 LED(osDelay 相对延时);
- AdcTask:10ms 精准周期 ADC 采集(osDelayUntil);
- KeyTask:等待外部中断信号,按键后发送消息到显示队列;
- DispTask:读取消息队列,用互斥量保护串口打印。
7.2 关键代码片段
// 全局句柄
osThreadId LedHandle,AdcHandle,KeyHandle,DispHandle;
osMutexId UartMutex;
osMessageQId MsgQueue;
// LED任务
void LedTask(void const * arg)
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5);
osDelay(500);
}
}
// ADC精准周期采集
void AdcTask(void const * arg)
{
uint32_t tick = osKernelSysTick();
uint16_t adc_val;
for(;;)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1,10);
adc_val = HAL_ADC_GetValue(&hadc1);
osMessagePut(MsgQueue, adc_val, 0);
osDelayUntil(&tick,10);
}
}
// 按键等待信号
void KeyTask(void const * arg)
{
osEvent evt;
for(;;)
{
evt = osSignalWait(0x01, osWaitForever);
if(evt.status == osEventSignal)
{
osMessagePut(MsgQueue, 0xFF00, 0);
}
}
}
// 串口显示任务,互斥保护
void DispTask(void const * arg)
{
osEvent msg;
for(;;)
{
msg = osMessageGet(MsgQueue, osWaitForever);
if(msg.status == osEventMessage)
{
osMutexWait(UartMutex, osWaitForever);
printf("DATA:%d\r\n",msg.value.v);
osMutexRelease(UartMutex);
}
}
}
八、调试、排错与性能优化
8.1 常见死机问题排查
- 栈溢出:CubeMX 开启栈水位监测,
osThreadList打印栈高水位;现象:HardFault、随机死机;解决:加大 stacksize; - SysTick 冲突:时基未分离,HAL_Delay 卡死;
- 中断调用阻塞 API:直接 HardFault;
- 内存泄漏:邮件 / 内存池分配后未 Free,堆耗尽创建任务失败;
- 死锁:多任务多互斥获取顺序不一致;
8.2 调试工具 API
osThreadList(uint8_t *buffer):输出所有任务名称、状态、栈使用;- 栈溢出检测:FreeRTOS 开启栈涂鸦,运行后查看剩余栈空间;
- 系统状态打印:通过串口周期打印任务列表,定位卡死任务。
8.3 系统优化方案
- 静态分配:configSUPPORT_STATIC_ALLOCATION=1,减少堆碎片;
- 合理划分优先级:采集 > 通讯 > 显示 > LED 低优先级;
- 禁止任务内 while (1) 空轮询,全部使用阻塞 API 让出 CPU;
- 大数据传输优先邮件队列,避免全局缓冲区拷贝;
- 低功耗:空闲任务开启 MCU STOP 模式,任务阻塞时进入低功耗。
九、CMSIS-RTOS V1 全 API 附录(完整收录)
9.1 内核管理
osKernelStart osKernelRunning osKernelSysTick
9.2 线程管理
osThreadCreate osThreadGetId osThreadTerminate osThreadYield osThreadSetPriority osThreadGetPriority osDelay osDelayUntil osAbortDelay osThreadSuspend osThreadResume osThreadSuspendAll osThreadResumeAll osThreadGetState osThreadIsSuspended osThreadList
9.3 软件定时器
osTimerCreate osTimerStart osTimerStop osTimerDelete
9.4 线程信号
osSignalSet osSignalClear osSignalWait
9.5 互斥量(含递归)
osMutexCreate osMutexWait osMutexRelease osMutexDelete osRecursiveMutexCreate osRecursiveMutexWait osRecursiveMutexRelease
9.6 信号量
osSemaphoreCreate osSemaphoreWait osSemaphoreRelease osSemaphoreDelete osSemaphoreGetCount
9.7 内存池
osPoolCreate osPoolAlloc osPoolCAlloc osPoolFree
9.8 消息队列
osMessageCreate osMessagePut osMessageGet osMessagePeek osMessageWaiting osMessageAvailableSpace osMessageDelete
9.9 邮件队列
osMailCreate osMailAlloc osMailCAlloc osMailPut osMailGet osMailFree
9.10 底层滴答
osSystickHandler
十、开发规范总结
- 所有全局资源访问必须加互斥量;
- 中断只做标记唤醒,业务逻辑放任务;
- 周期采集优先 osDelayUntil 保证精度;
- 结构体大数据传输使用 Mail,简单数值用 Message;
- 任务栈宁大勿小,上线前核查栈高水位;
- 动态内存分配场景尽量开启静态分配,减少碎片;
- 定时器回调、中断回调禁止阻塞、长耗时操作。