嵌入式|RTOS教学——FreeRTOS基础1:准备工作
目录
一、学习前提与工具准备
1. 必备基础
2. 工具准备(以 STM32 为例)
二、 FreeRTOS 核心概念(理论)
1. 核心概念拆解
2. 关键原则
三、第一个 FreeRTOS 程序(点亮两个 LED 任务)
步骤 1:用 STM32CubeMX 配置工程
步骤 2:编写任务函数(核心逻辑)
步骤 3:编译、下载与验证
四、内核与问题排查
1. 内核核心机制(简要理解)
2. 常见问题与排查
五、扩展学习资源
学习操作系统之前要先了解裸机和操作系统是什么。
裸机开发就是指程序直接运行在硬件上,比如STM32中的main()函数+while(1)无限循环结构。这种方式的优点是方便快捷,并且占用的资源很少。但也有很多缺点,比如只能顺序执行一个任务,当需要处理复杂任务时所有功能堆叠在一个任务流中,结构臃肿,扩展困难。实时性不强,且程序容易占用CPU造成“空等”。
而操作系统则是一套建立在硬件层上的底层软件,它通过内核来实现一些功能,可以克服裸机开发中的缺点。RTOS是一种满足实时应用程序需求的操作系统,它将我们需要实现的复杂功能拆分成多个任务,并通过任务调度器来管理任务的执行。通过这种方式可以实现多线程操作,大大提高了系统的实时性,并且提高了CPU的利用率。
简单来说就是:裸机只能同时做一件事,但是操作系统可以通过任务调度来同时做好几件事,也就是多线程。但这里的多线程也并不是真的在同时处理,而是通过时间片轮转让CPU在不同的任务之间快速切换,看起来就像是真的在同时处理一样。
一、学习前提与工具准备
学习 FreeRTOS(实时操作系统)需要结合理论概念和实际编程实践,尤其要理解 “实时” 的核心逻辑(任务调度、资源管理)。
在开始前,需具备基础能力并搭建好开发环境,避免后续因工具问题卡壳。
1. 必备基础
- C 语言基础:掌握变量、指针、结构体、函数、动态内存(
malloc/free
),理解指针操作(FreeRTOS 内核大量用指针访问任务控制块等)。 - 嵌入式基础:了解 MCU(如 STM32)的基本架构(GPIO、时钟、中断),能独立用 HAL 库 / 标准库写简单程序(如点亮 LED、串口打印)。
- 实时系统概念:知道 “实时” 不是 “快”,而是 “可预测”(任务在规定时间内必须完成),区分 “抢占式” 和 “协作式” 调度(FreeRTOS 是抢占式)。
2. 工具准备(以 STM32 为例)
工具类型 | 推荐选择 | 用途说明 |
---|---|---|
硬件开发板 | STM32F103C8T6(“蓝桥杯” 常用)、STM32F407 | 资源充足,资料丰富,适合入门;需带下载器(如 ST-Link V2)。 |
编译环境 | Keil MDK-ARM(V5/V6)、STM32CubeIDE | Keil 兼容性好,CubeIDE 免费且集成 STM32CubeMX(自动生成底层代码)。 |
配置工具 | STM32CubeMX | 可视化配置 MCU 时钟、外设,一键集成 FreeRTOS 内核(无需手动移植)。 |
调试工具 | ST-Link V2(下载 + 调试)、串口助手(打印) | 调试任务状态、查看变量,用串口打印输出任务运行信息(如任务执行次数)。 |
二、 FreeRTOS 核心概念(理论)
FreeRTOS 的本质是 “管理任务的内核”,所有操作都围绕 “任务” 展开。先吃透以下核心概念,再写代码会更清晰。
1. 核心概念拆解
概念 | 通俗理解 | 关键细节 |
---|---|---|
任务(Task) | 最小的执行单元(类似 “线程”) | - 每个任务有独立的栈空间和任务控制块(TCB,记录任务状态、优先级等); - 任务状态:就绪(Ready)、运行(Running)、阻塞(Blocked)、挂起(Suspended)。 |
任务优先级 | 任务的 “紧急程度” | - 数值越大,优先级越高(FreeRTOS 默认支持 0~31 级,可配置); - 抢占式调度:高优先级任务能打断低优先级任务的运行。 |
调度器(Scheduler) | 任务的 “管理者” | - 负责从 “就绪列表” 中选最高优先级任务执行; - 必须调用 vTaskStartScheduler() 启动调度器,之后内核接管任务切换。 |
队列(Queue) | 任务间通信的 “管道” | - 用于传递数据(如串口接收的数据传给处理任务); - 支持 “先进先出”(FIFO)或 “后进先出”(LIFO),可设置超时时间。 |
信号量(Semaphore) | 资源竞争的 “锁” 或 “通知器” | - 二进制信号量:用于 “同步”(如任务 A 完成后通知任务 B); - 计数信号量:用于 “资源管理”(如控制最多 3 个任务访问串口)。 |
栈(Stack) | 任务的 “临时内存” | - 每个任务独立栈,栈大小需根据任务中局部变量、函数调用深度配置(太小会栈溢出,太大浪费内存)。 |
2. 关键原则
- 禁止在中断中调用非 “中断安全” API:FreeRTOS 区分 “任务级 API”(如
xQueueSend()
)和 “中断级 API”(如xQueueSendFromISR()
),中断中必须用带FromISR
后缀的 API。 - 任务不能返回:任务函数是无限循环(
for(;;)
或while(1)
),若返回会导致栈溢出(任务栈被释放后无合法地址)。 - 动态内存 vs 静态内存:FreeRTOS 支持两种创建方式(如
xTaskCreate()
动态创建,xTaskCreateStatic()
静态创建),动态依赖malloc
,静态需手动分配栈和 TCB 内存。
三、第一个 FreeRTOS 程序(点亮两个 LED 任务)
用 STM32CubeMX 快速生成带 FreeRTOS 的工程,实现 “两个任务分别控制 LED 闪烁”(最经典的入门案例),理解任务创建和调度的核心流程。
步骤 1:用 STM32CubeMX 配置工程
- 新建工程:
- 打开 STM32CubeMX,选择对应芯片(如
STM32F103C8T6
),点击 “Start Project”。
- 打开 STM32CubeMX,选择对应芯片(如
- 配置基础外设:
- 配置时钟:RCC → 选择 “HSE”(外部高速时钟),配置 PLL 使系统时钟为 72MHz(STM32F1 最大时钟)。
- 配置 LED 引脚:假设 LED1 接 PB12,LED2 接 PB13 → 引脚模式设为 “GPIO_Output”,默认电平设为 “High”(假设 LED 低电平点亮)。
- 配置串口(可选,用于打印调试):USART1 → 模式设为 “Asynchronous”(异步),波特率 115200,引脚 PA9(TX)、PA10(RX)。
- 集成 FreeRTOS:
- 左侧菜单栏选择 “Middleware and Software Packs” → “FreeRTOS” → 勾选 “Enabled”。
- 选择 FreeRTOS 版本:推荐 “V10.3.1”(稳定版),接口选择 “CMSIS_V2”(最新标准,兼容性好)。
- 创建任务:
- 点击 “Tasks and Queues” → “Add”,创建第一个任务:
- Task Name:
Task_LED1
(任务名,自定义) - Priority:
1
(优先级,数值越大越优先) - Stack Size:
128
(栈大小,单位:字,1 字 = 4 字节,即 512 字节) - Function Name:
Task_LED1_Func
(任务函数名,自动生成)
- Task Name:
- 同理创建第二个任务
Task_LED2
,优先级设为1
(和 Task_LED1 同优先级),栈大小128
,函数名Task_LED2_Func
。
- 点击 “Tasks and Queues” → “Add”,创建第一个任务:
- 生成代码:
- 点击右上角 “GENERATE CODE”,选择编译环境(如 Keil MDK-ARM),生成工程后打开。
步骤 2:编写任务函数(核心逻辑)
在生成的代码中,找到 Src/main.c
文件,在 “USER CODE BEGIN 2” 和 “USER CODE END 2” 之间,补充两个任务的实现:
/* USER CODE BEGIN 2 */
// 任务1:控制LED1每隔500ms闪烁一次
void Task_LED1_Func(void *argument)
{for(;;) // 任务必须是无限循环{HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_12); // 翻转LED1引脚电平vTaskDelay(500); // 延时500ms(FreeRTOS的延时函数,单位:ms)// 延时期间,调度器会切换到其他就绪任务(如Task_LED2)}
}// 任务2:控制LED2每隔1000ms闪烁一次
void Task_LED2_Func(void *argument)
{for(;;){HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_13); // 翻转LED2引脚电平vTaskDelay(1000); // 延时1000ms}
}
/* USER CODE END 2 */
步骤 3:编译、下载与验证
- 编译工程:在 Keil 中点击 “Build”,确保无错误(0 Errors)。
- 下载程序:连接 ST-Link 到开发板,点击 “Download”,将程序烧录到 MCU。
- 观察现象:
- LED1 每隔 500ms 闪烁一次,LED2 每隔 1000ms 闪烁一次。
- 原理:两个任务优先级相同,调度器会采用 “时间片轮转” 调度(每个任务执行一个时间片后切换),
vTaskDelay()
会让当前任务进入 “阻塞态”,调度器自动切换到另一个任务。
四、内核与问题排查
当能熟练使用 API 后,需理解底层逻辑,避免踩坑(如栈溢出、死锁)。
1. 内核核心机制(简要理解)
- 任务切换原理:通过 “PendSV 中断” 实现(低优先级中断,确保任务切换不打断高优先级中断),切换时保存当前任务的寄存器到栈,恢复下一个任务的寄存器。
- 延时实现:依赖 “SysTick 定时器”(系统滴答定时器),每 1ms 产生一次中断,更新系统时基,检查是否有任务延时到期(到期后将任务从阻塞态转为就绪态)。
2. 常见问题与排查
问题现象 | 可能原因 | 排查方法 |
---|---|---|
程序卡死 | 1. 任务无延时(高优先级任务占满 CPU); 2. 栈溢出; 3. 死锁(两个任务互相等信号量)。 | 1. 检查任务是否有 vTaskDelay() 或 xQueueReceive() 等阻塞操作;2. 增大栈大小,或用 FreeRTOS 栈溢出检测功能(配置 configCHECK_FOR_STACK_OVERFLOW );3. 检查信号量获取顺序,避免循环等待。 |
中断中调用非中断安全 API | 如在中断中用 xQueueSend() 而非 xQueueSendFromISR() | 搜索代码中所有中断回调函数,确保只调用带 FromISR 后缀的 API。 |
队列接收不到数据 | 1. 队列未创建成功; 2. 发送方未发送数据; 3. 超时时间太短。 | 1. 检查队列句柄是否为 NULL ;2. 在发送处加打印,确认数据已发送; 3. 增大 xQueueReceive() 的超时时间(如 portMAX_DELAY )。 |
五、扩展学习资源
- 官方文档:FreeRTOS 官网(www.freertos.org)的 “API Reference” 和 “FreeRTOS User Guide”(最权威,建议常备)。
- 书籍:
- 《FreeRTOS 实时内核使用指南》(官方指南翻译版,适合入门)。
- 《STM32Cube 开发实战指南:FreeRTOS 篇》(结合 STM32 实战,案例丰富)。
- 视频教程:
- 正点原子 / 野火的 STM32 FreeRTOS 教程(B 站免费,配套开发板,适合零基础)。
- 安富莱电子的 FreeRTOS 内核解析(深入底层,适合进阶)。
FreeRTOS 的学习关键是 “边练边查”,遇到 API 用法不确定时,优先查官方文档,避免依赖碎片化资料。