STM32 HAL + FreeRTOS(CMSIS-RTOS V1)完整开发攻略

本文基于 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 配置

  1. 选择对应 STM32 芯片,配置外部高速晶振 HSE;
  2. SYS->Debug:选择 Serial Wire(下载调试);
  3. 关键:时基源分离(重中之重)
    • 进入 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:

  1. 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 开启线程信号标志
  2. 任务默认栈:普通 IO 任务 512,浮点运算 / 串口打印 1024,复杂算法 2048 以上;

  3. 生成工程设置:生成独立.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 内核控制函数

  1. osStatus osKernelStart (void)
  • 功能:启动 RTOS 内核调度,所有就绪线程开始运行;
  • 返回值:osOK 正常;内核启动后函数不返回。
  1. int32_t osKernelRunning(void)
  • 返回 1:内核已启动;0:未启动;-1:该功能被配置关闭;
  • 适用场景:区分初始化阶段与任务运行阶段。
  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 核心接口
  1. 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);
    }
    }

  2. osThreadGetId(void):获取当前运行任务 ID;

  3. osThreadTerminate(osThreadId thread_id):销毁指定任务;

  4. osThreadSuspend / osThreadResume:挂起 / 恢复任务;

  5. osThreadSuspendAll / osThreadResumeAll:全局暂停所有线程(临界保护慎用);

  6. osThreadSetPriority / osThreadGetPriority:动态修改任务优先级;

3.2.3 延时函数区别
  1. osDelay(uint32_t millisec) 相对延时:阻塞当前任务指定毫秒,任务让出 CPU;适用于普通闪烁、轮询。

  2. osDelayUntil(uint32_t *PreviousWakeTime, uint32_t millisec) 绝对周期延时,不受任务运行耗时影响,适合精准周期采集、定时上报:

    uint32_t tick = osKernelSysTick();
    for(;;)
    {
    AD_Read();
    osDelayUntil(&tick, 10); // 严格10ms周期执行一次采集
    }

  3. osAbortDelay(osThreadId thread_id):强制终止某个任务的延时阻塞。

3.3 软件定时器(osTimer)

  1. 两种类型:
  • osTimerOnce:单次定时器,触发一次后停止;
  • osTimerPeriodic:周期定时器,循环触发回调;
  1. 关键函数: 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、全局变量等独占资源,自动解决优先级反转。

  1. osMutexCreate 创建互斥;
  2. osMutexWait(mutex_id, timeout) 获取互斥,超时等待;
  3. osMutexRelease 释放互斥;
  4. 递归互斥osRecursiveMutexCreate:同一任务可多次获取,配套 RecursiveWait/Release;

禁止中断内使用互斥量。

4.2 信号量 Semaphore(任务同步、资源计数)

  1. 二值信号量:中断唤醒任务(串口接收、外部中断);
  2. 计数信号量:管理有限硬件资源(多个串口、缓冲通道); 接口: 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(大块结构体数据)

结合内存池,传递完整结构体(传感器数据、协议帧),自动管理内存分配释放:

  1. osMailCreate 创建邮件队列;
  2. osMailAlloc / osMailCAlloc 申请内存块(CAlloc 自动清零);
  3. osMailPut 将内存块送入队列;
  4. osMailGet 阻塞读取邮件;
  5. 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 适配(高频踩坑点)

  1. 中断优先级规则: FreeRTOS 配置configMAX_SYSCALL_INTERRUPT_PRIORITY高于该优先级的中断禁止调用任何 CMSIS-RTOS API;仅低优先级中断可使用信号量置位、信号置位等无阻塞函数。

  2. 中断内允许 API: osSignalSet、osSemaphoreRelease、osMessagePut 无阻塞版本;

  3. 中断禁止:osDelay、osMutexWait、osMailGet 等阻塞函数;

  4. 示例:外部中断唤醒采集任务

    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 功能规划

  1. LedTask:500ms 翻转 LED(osDelay 相对延时);
  2. AdcTask:10ms 精准周期 ADC 采集(osDelayUntil);
  3. KeyTask:等待外部中断信号,按键后发送消息到显示队列;
  4. 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 常见死机问题排查

  1. 栈溢出:CubeMX 开启栈水位监测,osThreadList打印栈高水位;现象:HardFault、随机死机;解决:加大 stacksize;
  2. SysTick 冲突:时基未分离,HAL_Delay 卡死;
  3. 中断调用阻塞 API:直接 HardFault;
  4. 内存泄漏:邮件 / 内存池分配后未 Free,堆耗尽创建任务失败;
  5. 死锁:多任务多互斥获取顺序不一致;

8.2 调试工具 API

  1. osThreadList(uint8_t *buffer):输出所有任务名称、状态、栈使用;
  2. 栈溢出检测:FreeRTOS 开启栈涂鸦,运行后查看剩余栈空间;
  3. 系统状态打印:通过串口周期打印任务列表,定位卡死任务。

8.3 系统优化方案

  1. 静态分配:configSUPPORT_STATIC_ALLOCATION=1,减少堆碎片;
  2. 合理划分优先级:采集 > 通讯 > 显示 > LED 低优先级;
  3. 禁止任务内 while (1) 空轮询,全部使用阻塞 API 让出 CPU;
  4. 大数据传输优先邮件队列,避免全局缓冲区拷贝;
  5. 低功耗:空闲任务开启 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

十、开发规范总结

  1. 所有全局资源访问必须加互斥量;
  2. 中断只做标记唤醒,业务逻辑放任务;
  3. 周期采集优先 osDelayUntil 保证精度;
  4. 结构体大数据传输使用 Mail,简单数值用 Message;
  5. 任务栈宁大勿小,上线前核查栈高水位;
  6. 动态内存分配场景尽量开启静态分配,减少碎片;
  7. 定时器回调、中断回调禁止阻塞、长耗时操作。
相关推荐
不悔哥6 小时前
开源OV-Watch:怎么做一个智能手表
单片机·开源·嵌入式
XiHongShi20166 小时前
STM32F407 RTC定时器例程,建议保存
stm32·单片机·学习
优信电子7 小时前
AIT1001 非接触式红外测温模块 STM32 驱动开发实战:从引脚到代码全解析
stm32·嵌入式·温度传感器·红外测温·非接触式测温·ait1001
深圳老胡8 小时前
STM32F407 控制 L6470 步进电机驱动 —— 控制过程简介
笔记·stm32·单片机·嵌入式硬件·代码规范
wuyk5558 小时前
《WiFi 嵌入式物联网开发全套实战》| 第 16 章 ESP32 AP+STA 双模共存原理与工程坑点
网络·stm32·物联网
女神下凡9 小时前
嵌入式设计的各种存储芯片的硬件/软件设计规范,非常全面。
arm开发·单片机·嵌入式硬件
梅梅96610 小时前
一款mipi转lvds的lcd调试(LT8912B)
单片机·计算机外设·电脑·显示器·pd芯片
开发笔记-阿牛10 小时前
蓝牙音乐灯拆解,又一次拆到蓝牙音乐灯芯片CK6865L
人工智能·单片机·嵌入式硬件·音视频·音频
从零开始的嵌入式之旅11 小时前
day47
arm开发·经验分享·笔记·嵌入式硬件
恒锐丰科技林技术员11 小时前
EG1164 高压大电流升压同步整流芯片解析
经验分享·嵌入式硬件·硬件工程