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¶
配置步骤¶
- 打开 CubeMX 工程
- Middleware and Software Packs → FREERTOS
- Interface 选择 CMSIS_V2(推荐,比 V1 更现代)
- 在 Tasks and Queues 标签页中添加任务
- 配置堆大小(Config parameters →
TOTAL_HEAP_SIZE,默认 15360 字节) - 生成代码
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 中:
- 配置 PC13 和 PA5 为 GPIO_Output
- 启用 FreeRTOS(CMSIS_V2)
- 添加两个任务:
LED1_Task(优先级 Normal)和LED2_Task(优先级 Normal) - 生成代码
在 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() 等函数。