STM32 进阶封神之路(三十八)
FreeRTOS 从零到一完全吃透|裸机对比、内核本质、任务创建与调度(超详细图文版)
前言
在嵌入式系统开发从 “裸机” 走向 “多任务” 的过程中,FreeRTOS 占据着不可替代的地位。它轻量、开源、稳定、覆盖芯片平台极广,是每一位嵌入式开发者必须掌握的实时操作系统。
本篇将从零开始,完整、细致、深入地讲解 FreeRTOS 核心基础内容。从最经典的裸机前后台架构讲起,逐步深入 RTOS 概念、可剥夺内核、任务状态、任务创建、调度机制、延时函数本质区别。全文逻辑清晰、语言通俗、内容系统,帮你真正理解 FreeRTOS 是什么、为什么要用、如何正确使用。
无论你是刚刚接触 RTOS 的单片机开发者,还是即将面对面试、笔试的嵌入式岗位求职者,这一篇都能帮你彻底打通 FreeRTOS 基础关卡。
一、什么是嵌入式系统?裸机与操作系统的本质区别
在正式进入 FreeRTOS 之前,我们必须先理清一个最核心、最基础的问题:裸机系统是什么?RTOS 又是什么?
1.1 裸机系统(前后台系统)
绝大多数开发者学习单片机的起点,都是裸机程序。
裸机系统也被称为前后台系统:
- 后台:main 函数中的 while (1) 死循环,依次轮询执行各个模块逻辑。
- 前台:中断服务函数,处理异步紧急事件,如串口接收、外部中断、定时器触发等。
典型裸机结构如下:
c
运行
while(1)
{
LED_Process();
Key_Process();
UART_Process();
LCD_Process();
}
裸机系统结构简单、易于理解,但它存在无法回避的致命缺陷:
- 实时性极差高优先级事件必须等待当前函数执行完毕才能响应。
- 程序结构混乱功能越多,while (1) 越长,耦合严重、难以维护。
- 无法抢占一旦某个模块出现长时间延时或阻塞,整个系统都会卡住。
- CPU 利用率极低大量时间浪费在空循环、软件延时中。
这就是为什么稍微复杂的项目、工业级项目,都必须使用 RTOS 实时操作系统。
1.2 什么是 RTOS(Real Time Operating System)
RTOS 即实时操作系统。
它最核心的特点是:能够在规定的时间内响应外部事件,保证高优先级任务优先执行。
常见的 RTOS:
- FreeRTOS(开源免费、最主流)
- UCOS II / UCOS III
- RT-Thread
- RTX
FreeRTOS 凭借开源、轻量、生态完善、芯片厂商原生支持,成为学习与工业产品的首选方案。
二、操作系统分类:分时、实时、半实时
2.1 分时操作系统
代表:Linux、Windows
- 按时间片轮流执行任务
- 不区分任务优先级
- 不保证实时响应
2.2 硬实时操作系统
代表:UCOS
- 永远运行优先级最高的任务
- 高优先级任务可以抢占低优先级
- 不允许相同优先级存在
2.3 半实时操作系统
代表:FreeRTOS
- 支持优先级抢占
- 允许相同优先级任务
- 相同优先级任务按时间片轮流调度
FreeRTOS 属于可剥夺型实时内核,这是它实时性强、效率高的根本原因。
三、可剥夺内核 vs 不可剥夺内核(面试必考)
这是 FreeRTOS 最核心的原理,必须彻底理解。
3.1 不可剥夺内核(合作式)
- 任务必须主动放弃 CPU 才能切换
- 中断可以唤醒高优先级任务,但不能直接抢占
- 实时性差,不适合工业控制
3.2 可剥夺内核(抢占式)—— FreeRTOS 使用
规则只有一句话:CPU 永远运行当前就绪队列中优先级最高的任务!
运行流程:
- 低优先级任务正在运行
- 高优先级任务变为就绪态
- 立即抢占 CPU,低优先级任务暂停
- 高优先级任务执行完毕
- 低优先级任务恢复运行
这就是 FreeRTOS 能够保证强实时性的核心机制。
四、FreeRTOS 基础术语(必须背会)
4.1 任务(Task)
- 一个独立的无限循环函数
- 拥有独立栈空间
- 可独立被调度、切换
4.2 任务调度器(Scheduler)
内核的核心,负责决定当前哪个任务可以运行。
4.3 优先级(Priority)
数字越大,优先级越高。优先级范围:0 ~ configMAX_PRIORITIES-1
4.4 任务堆栈(Stack)
用于保存任务运行现场(寄存器值)。创建任务时指定大小,单位:字(32 位 / 4 字节) 。
4.5 共享资源
可被多个任务同时访问的资源,如串口、Flash、IIC、SPI、全局变量。
4.6 临界区
执行过程中不希望被打断的代码段,如时序操作、打印、数据赋值。
五、FreeRTOS 任务的四种状态(核心)
任何一个任务,在任意时刻一定处于以下四种状态之一:
1. 就绪态(Ready)
- 任务已创建
- 等待被调度运行
2. 运行态(Running)
- 正在占用 CPU
- 单核 CPU 同一时间只能有一个任务运行
3. 阻塞态(Blocked)
- 任务在等待延时、信号量、队列、事件等
- 主动让出 CPU
4. 挂起态(Suspended)
- 任务被 “冻结”
- 不参与调度
- 只能通过 vTaskResume () 唤醒
状态转换重点:
- 调用延时 → 进入阻塞态
- 被更高优先级抢占 → 回到就绪态
- 调用 vTaskSuspend → 进入挂起态
- 恢复执行 → 回到就绪态
六、FreeRTOS 任务创建完整步骤(超详细)
任务是 FreeRTOS 最基本的调度单元,我们从最底层完整讲解创建流程。
6.1 包含头文件
c
运行
#include "FreeRTOS.h"
#include "task.h"
6.2 定义任务句柄
相当于任务的 “唯一标识符”。
c
运行
TaskHandle_t xHandle_LED1 = NULL;
6.3 编写任务函数(无限循环)
任务函数规则:
- 不能 return
- 必须包含让出 CPU 的操作(vTaskDelay 或阻塞)
c
运行
void vTaskLED1Code( void * pvParameters )
{
for( ;; )
{
LED1_Toggle();
vTaskDelay(1000);
}
}
6.4 使用 xTaskCreate 创建任务
c
运行
xTaskCreate(
vTaskLED1Code, // 任务函数
"vTaskLED1Code", // 任务名称(调试用)
512, // 堆栈大小(单位:字)
NULL, // 入口参数
1, // 任务优先级
&xHandle_LED1 // 任务句柄
);
6.5 启动调度器
c
运行
vTaskStartScheduler();
该函数永不返回!启动后,整个系统完全由 FreeRTOS 接管。
七、vTaskDelay 与裸机 delay_ms 的本质区别(面试必问)
这是初学者最容易踩坑、面试最高频的问题。
7.1 裸机延时 delay_ms
c
运行
void delay_ms(uint32_t ms)
{
for(i=0;i<ms*7200;i++);
}
特点:
- 死循环空转
- 不释放 CPU
- 高优先级任务使用 → 系统卡死
- 低优先级任务完全无法运行
7.2 FreeRTOS 延时 vTaskDelay
c
运行
vTaskDelay(1000);
特点:
- 任务进入阻塞态
- 主动释放 CPU
- 调度器切换到其他任务
- 系统整体运行不受影响
一句话总结:delay_ms = 霸占 CPU****vTaskDelay = 交出 CPU
八、任务堆栈大小如何确定?
创建任务时,堆栈大小单位是 字(4 字节) 。
通用判断规则:
- 简单任务:128~256
- 涉及 LCD、printf、浮点运算:512+
- 协议栈、网络任务:1024+
建议:
- 初期适当开大
- 利用调试工具查看剩余堆栈
- 最终缩到合适大小,节约内存
九、高优先级任务如何抢占低优先级?(实验现象)
实验示例:
- LED1 任务:优先级 1,vTaskDelay (1000)
- LED2 任务:优先级 2,vTaskDelay (500)
实验结果:
- LED2 优先运行
- LED2 可以随时打断 LED1
- 高优先级任务完全掌控系统执行权
如果高优先级任务使用裸机 delay_ms:
- LED2 独占 CPU
- LED1 完全无法运行
- 系统失去响应
这就是可剥夺内核最直观、最真实的表现。
十、本篇博客总结
本篇我们完整、系统地学习了以下内容:
- 裸机系统与 RTOS 的本质区别
- 实时操作系统与可剥夺内核
- FreeRTOS 基础专业术语
- 任务的四种状态及转换
- 任务创建完整流程
- vTaskDelay 与裸机延时的本质区别
- 任务堆栈设置规则
- 优先级抢占机制与实验现象
下一篇博客,我们将继续深入:临界区保护、任务挂起 / 恢复、堆栈溢出钩子、空闲任务、调度器底层运行原理。
对于嵌入式学习者而言,本篇是 FreeRTOS 最核心、最基础、面试最高频的内容,建议收藏反复理解。