跳转至

FreeRTOS

FreeRTOS 是目前最流行的嵌入式实时操作系统(RTOS)之一,轻量、开源、免费,广泛运行在 STM32 等 MCU 上。它让单片机从"一个人干所有活"变成"多个任务并行协作",是嵌入式开发进阶的必经之路。


为什么需要 RTOS?

裸机开发的困境

在裸机(无操作系统)程序中,所有逻辑都在一个大的 while(1) 循环里:

while (1)
{
    读取传感器();     // 花 10ms
    PID计算();        // 花 2ms
    电机控制();       // 花 1ms
    屏幕刷新();       // 花 50ms ← 这里阻塞了!
    串口通信();       // 花 5ms
}

问题显而易见:

  • 屏幕刷新 50ms 期间,其他任务全部停滞
  • 任务之间互相阻塞,无法做到真正的"并行"
  • 高优先级任务(如电机控制)无法及时响应
  • 延时函数(HAL_Delay)会阻塞整个程序

RTOS 的解决方案

RTOS 把程序拆分成多个独立的 任务(Task),每个任务有自己的"时间片",由调度器自动切换:

gantt
    title RTOS 多任务调度(时间片轮转示意)
    dateFormat X
    axisFormat %s

    section 传感器任务
    运行 :a1, 0, 10
    运行 :a2, 68, 78

    section PID 任务
    运行 :b1, 10, 12
    运行 :b2, 78, 80

    section 电机任务
    运行 :c1, 12, 13
    运行 :c2, 80, 81

    section 屏幕任务
    运行 :d1, 13, 63

    section 串口任务
    运行 :e1, 63, 68

RTOS 的核心价值

  • 并发执行:多个任务"看起来同时运行"(实际是快速切换)
  • 优先级调度:重要任务优先执行,不会被低优先级任务阻塞
  • 延时不阻塞:任务等待时 CPU 去执行其他任务,不浪费算力
  • 模块化设计:每个功能独立成任务,代码结构清晰

FreeRTOS 是什么?

特性 说明
全称 Free Real-Time Operating System
许可证 MIT 开源协议,商用免费
内核大小 约 4~9KB Flash,极其轻量
支持架构 ARM Cortex-M、RISC-V、AVR、x86 等 40+ 架构
当前维护 由 Amazon(AWS)维护
STM32 集成 CubeMX 内置,一键配置

核心概念一览

1. 任务(Task)

任务是 FreeRTOS 最基本的执行单元,类似 PC 上的"线程"。

  • 每个任务有独立的栈空间优先级
  • 任务函数是一个永远不会 return 的无限循环
  • 高优先级任务可以抢占低优先级任务
void LED_Task(void *pvParameters)
{
    while (1)  // 任务永远不退出
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        osDelay(500);  // 不阻塞,让出 CPU 给其他任务
    }
}

2. 调度器(Scheduler)

调度器是 RTOS 的"大脑",决定哪个任务获得 CPU:

调度策略 说明
抢占式调度 高优先级任务就绪时,立即打断低优先级任务(默认)
时间片轮转 同优先级任务按固定时间片轮流执行
协作式调度 任务主动让出 CPU 才切换(较少使用)
graph TD
    A[任务 A<br>优先级 3] -->|"优先级最高<br>优先运行"| SCH[调度器]
    B[任务 B<br>优先级 2] --> SCH
    C[任务 C<br>优先级 1] --> SCH
    SCH --> CPU[CPU 执行]

3. 任务状态

stateDiagram-v2
    [*] --> Ready : 创建任务
    Ready --> Running : 调度器选中
    Running --> Ready : 被更高优先级抢占 / 时间片耗尽
    Running --> Blocked : 等待事件(delay/队列/信号量)
    Running --> Suspended : 被挂起(vTaskSuspend)
    Blocked --> Ready : 等待的事件到来
    Suspended --> Ready : 被恢复(vTaskResume)
状态 含义 触发条件
Running 正在执行 调度器选中此任务
Ready 就绪,等待调度 条件满足但 CPU 被更高优先级占用
Blocked 阻塞,等待事件 调用 osDelay()、等待队列/信号量
Suspended 挂起,不参与调度 调用 vTaskSuspend()

4. 任务间通信与同步

多个任务之间需要交换数据或协调执行顺序,FreeRTOS 提供了多种机制:

机制 用途 典型场景
队列(Queue) 任务间传递数据 传感器数据 → 处理任务
信号量(Semaphore) 同步 / 资源计数 ISR 通知任务、限制并发数
互斥量(Mutex) 互斥访问共享资源 保护串口、I2C 总线
事件组(Event Group) 多条件等待 等待多个任务都完成
任务通知(Notification) 轻量级通信 替代信号量,效率更高
软件定时器(Timer) 定时执行回调 周期性检查、超时处理

5. 中断管理

FreeRTOS 对中断有严格规则:

  • 中断中只能调用以 FromISR 结尾的API
  • 优先级 ≤ configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断才能调用 FreeRTOS API
  • SysTick 中断用于驱动调度器的时间片

6. 内存管理

FreeRTOS 提供 5 种内存分配方案(heap_1 ~ heap_5),CubeMX 默认使用 heap_4:

方案 特点 CubeMX 默认
heap_1 只分配不释放
heap_2 可释放但不合并碎片
heap_3 封装标准 malloc/free
heap_4 可释放,自动合并相邻碎片
heap_5 heap_4 + 支持不连续内存区域

CubeMX 中配置 FreeRTOS

配置步骤

  1. 打开 CubeMX 工程
  2. Middleware and Software Packs → FREERTOS
  3. Interface 选择 CMSIS_V2(推荐,比 V1 更现代)
  4. Tasks and Queues 标签页中添加任务
  5. 配置堆大小(Config parameters → TOTAL_HEAP_SIZE,默认 15360 字节)
  6. 生成代码

CMSIS_V1 vs CMSIS_V2

CubeMX 提供两种 API 封装层:

  • CMSIS-RTOS V1:旧版接口,函数名以 os 开头,如 osThreadCreate()
  • CMSIS-RTOS V2:新版接口,函数名以 os 开头但参数更规范,如 osThreadNew()

推荐使用 V2,它是 ARM 官方推荐的标准封装,API 更清晰。本笔记所有示例均基于 CMSIS_V2。

CubeMX 生成的代码结构

Core/
├── Src/
│   ├── main.c                 # 主函数,调用 osKernelStart()
│   ├── freertos.c             # FreeRTOS 任务创建和用户代码
│   └── stm32f1xx_it.c         # 中断处理(SysTick 等)
Middlewares/Third_Party/FreeRTOS/
├── Source/
│   ├── tasks.c                # 任务管理
│   ├── queue.c                # 队列
│   ├── timers.c               # 软件定时器
│   ├── semphr.c               # 信号量
│   ├── event_groups.c         # 事件组
│   └── portable/MemMang/
│       └── heap_4.c           # 内存管理(默认 heap_4)

最小示例:两个任务交替闪烁 LED

在 CubeMX 中:

  1. 配置 PC13PA5 为 GPIO_Output
  2. 启用 FreeRTOS(CMSIS_V2)
  3. 添加两个任务:LED1_Task(优先级 Normal)和 LED2_Task(优先级 Normal)
  4. 生成代码

freertos.c 中编写任务函数:

/* USER CODE BEGIN Header_LED1_Task */
/**
 * @brief LED1 任务:PC13 每 500ms 翻转
 */
/* USER CODE END Header_LED1_Task */
void LED1_Task(void *argument)
{
    /* USER CODE BEGIN LED1_Task */
    for (;;)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        osDelay(500);  // 延时 500ms,期间 CPU 执行其他任务
    }
    /* USER CODE END LED1_Task */
}

/* USER CODE BEGIN Header_LED2_Task */
/**
 * @brief LED2 任务:PA5 每 200ms 翻转
 */
/* USER CODE END Header_LED2_Task */
void LED2_Task(void *argument)
{
    /* USER CODE BEGIN LED2_Task */
    for (;;)
    {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        osDelay(200);  // 延时 200ms
    }
    /* USER CODE END LED2_Task */
}

osDelay vs HAL_Delay

  • HAL_Delay():死等,CPU 不做任何事(裸机用)
  • osDelay():让出 CPU,去执行其他就绪任务(RTOS 中必须用这个)

在 FreeRTOS 中禁止使用 HAL_Delay(),否则会阻塞所有低优先级任务。


学习路线与本笔记结构

掌握 FreeRTOS 建议按以下顺序学习,每个主题对应一页详细笔记:

graph TD
    T[任务管理] --> Q[队列与通信]
    Q --> S[信号量与互斥量]
    S --> TM[软件定时器]
    TM --> I[中断管理]
    I --> M[内存管理]
章节 核心内容 你将学会
任务管理 创建/删除/挂起任务、优先级、调度策略 设计多任务程序架构
队列与通信 队列收发数据、邮箱、任务通知 任务间安全传递数据
信号量与互斥量 二值/计数信号量、互斥量、优先级反转 同步任务、保护共享资源
软件定时器 单次/周期定时器、定时器回调 实现超时、周期性操作
中断管理 FromISR API、中断优先级配置、延迟处理 中断中安全操作 RTOS
内存管理 heap 方案、栈溢出检测、内存调优 合理分配系统资源

FreeRTOS 常用 API 速查(CMSIS_V2)

API 功能
osKernelStart() 启动调度器
osThreadNew() 创建任务
osThreadTerminate() 删除任务
osDelay() 任务延时(让出 CPU)
osDelayUntil() 绝对延时(周期精确)
osMessageQueueNew() 创建队列
osMessageQueuePut() 向队列发送数据
osMessageQueueGet() 从队列接收数据
osSemaphoreNew() 创建信号量
osSemaphoreAcquire() 获取信号量
osSemaphoreRelease() 释放信号量
osMutexNew() 创建互斥量
osMutexAcquire() 获取互斥量
osMutexRelease() 释放互斥量
osTimerNew() 创建软件定时器
osTimerStart() 启动定时器

常见问题

FreeRTOS 占多少资源?

内核代码约 4~9KB Flash,每个任务需要独立的栈空间(通常 128~512 字,即 512B~2KB)。对于 STM32F103C8T6(64KB Flash / 20KB SRAM),运行 4~6 个任务完全没问题。

裸机和 RTOS 怎么选?

  • 裸机:逻辑简单、任务少、实时性要求不高(如只读传感器 + 点灯)
  • RTOS:多任务并行、有通信/同步需求、需要响应多种事件
  • 一般原则:超过 3 个独立功能模块时,考虑上 RTOS

CMSIS-RTOS API 和原生 FreeRTOS API 什么区别?

CMSIS-RTOS 是 ARM 官方定义的 RTOS 抽象层,底层可以对接 FreeRTOS、RTX 等不同 RTOS。好处是代码可跨 RTOS 移植。CubeMX 默认生成 CMSIS-RTOS API,但你也可以直接混用原生 xTaskCreate() 等函数。