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. 定时器回调、中断回调禁止阻塞、长耗时操作。
相关推荐
wuyk55512 小时前
第2章:六步换相原理全解+STM32工程实战
c语言·开发语言·stm32·单片机·嵌入式硬件
小僧景贤16 小时前
单片机全方位调试实战指南:串口/RTT/逻辑分析仪/示波器/内核排错分层避坑教程
单片机·嵌入式硬件·物联网
2401_8275012820 小时前
【无标题】
arm开发·嵌入式硬件
LeoZY_20 小时前
LinkScope 使用笔记:基于 OpenOCD 的通用硬件芯片调试助手
笔记·单片机·嵌入式硬件·开源软件
星空真迷人1 天前
嵌入式鸿蒙并非精简版,核心究竟是什么?
stm32·单片机·嵌入式硬件·物联网·华为·harmonyos·iot
iCxhust1 天前
8088单板机VScode集成开发环境使用方法二(汇编版)
汇编·单片机·微机原理·8088单板机
hongmai6668881 天前
GK7205V300芯片深度解析:H.265低功耗视觉主控,助力安防监控高效选型
arm开发·嵌入式硬件·物联网·智能家居·h.265·risc-v
m0_466607701 天前
只用三步使用MCSDK6生成电机控制工程
stm32·电机控制
2301_801434981 天前
STM32F407移植TinyUSB
stm32·单片机·嵌入式硬件