大家好!在前 16 篇中,我们用 “裸机编程”(前后台系统)实现了各类功能,但当系统需要同时处理多个任务(如 “每秒采样传感器”“实时显示数据”“定时存储日志”)时,裸机编程会暴露明显缺陷:高优先级任务可能被低优先级任务阻塞,任务响应延迟不可控。而FreeRTOS(Real-Time Operating System) 作为轻量级 RTOS 的代表,能通过 “任务调度器” 实现 “多任务并发执行”,确保高优先级任务优先响应。这一篇我们将从 FreeRTOS 的核心概念讲起,详解其在 STM32F103 上的移植与配置,通过 “三任务并发” 实操,让你掌握实时操作系统的基础应用。
一、为什么需要 FreeRTOS?用 “餐厅管理” 理解核心逻辑
裸机编程就像 “只有 1 个服务员的餐厅”:服务员(CPU)必须完成当前客人的订单(任务 A),才能去接待下一个客人(任务 B),若任务 A 耗时久,任务 B 会一直等待。而 FreeRTOS 就像 “有调度员的餐厅”:
调度员(任务调度器):根据客人优先级(任务优先级)安排服务员接待顺序,VIP 客人(高优先级任务)优先被服务;服务员(CPU):在调度员指挥下,“分时” 服务多个客人(快速切换任务),从宏观上看多个任务在 “同时执行”;任务(Task):每个客人的需求(如 “采样传感器”“显示数据”),每个任务有独立的栈空间和运行状态;资源锁(Mutex):若多个客人需要共用一个设备(如打印机),调度员会发放 “使用许可”,避免冲突。
FreeRTOS 的核心优势:
实时性:高优先级任务可抢占低优先级任务,响应延迟≤1 个调度周期(STM32F103 下通常 < 1ms);模块化:任务、队列、信号量等功能可按需裁剪,最小内存占用仅 6KB;易用性:提供标准化 API(如创建任务、
xTaskCreate延时),无需手动管理任务切换。
vTaskDelay
二、FreeRTOS 的核心概念:任务、调度与同步
在使用 FreeRTOS 前,需先理解 4 个核心概念,这是后续实操的基础:
1. 任务(Task):FreeRTOS 的最小执行单元
属性:每个任务有独立的栈空间(存储局部变量、寄存器值)和优先级(0~31,数值越大优先级越高);状态:任务有 5 种状态,调度器负责状态切换:
运行态(Running):CPU 正在执行该任务;就绪态(Ready):任务已就绪,等待 CPU 调度;阻塞态(Blocked):任务因延时(如)或等待资源(如信号量)暂停运行;挂起态(Suspended):任务被手动暂停(需
vTaskDelay恢复);删除态(Deleted):任务被销毁,资源被回收。
vTaskResume
创建方式:支持动态创建(从堆内存分配栈空间)和静态创建(使用指定数组作为栈),新手推荐动态创建。
2. 任务调度器(Scheduler):任务的 “指挥中心”
调度算法:FreeRTOS 默认使用 “抢占式调度”—— 只要有高优先级任务进入就绪态,立即暂停当前低优先级任务,切换到高优先级任务;调度周期:由 “系统滴答定时器(SysTick)” 决定,默认每 1ms 触发一次调度(可修改);启动条件:调用后,调度器开始工作,CPU 从此由调度器分配。
vTaskStartScheduler()
3. 任务同步与通信:解决多任务资源冲突
当多个任务需要协作(如 “任务 A 采样数据后通知任务 B 处理”)或共用资源(如串口)时,需用到同步机制:
队列(Queue):用于任务间传递数据(如传感器采样值),支持 FIFO(先进先出),可实现异步通信;信号量(Semaphore):用于 “资源计数”(如 2 个串口,信号量初始值 = 2,占用减 1,释放加 1);互斥锁(Mutex):用于 “独占资源保护”(如避免 2 个任务同时写串口,导致数据混乱)。
4. 系统资源:堆与栈
任务栈:每个任务的独立栈,大小需根据任务复杂度设置(如简单任务设 512 字节,复杂任务设 1024 字节),栈溢出会导致系统崩溃;系统堆:用于动态创建任务、队列等,堆大小在中配置(如
FreeRTOSConfig.h,即 10KB)。
configTOTAL_HEAP_SIZE = (size_t)(10 * 1024)
三、实操:FreeRTOS 移植与三任务并发
我们实现 3 个并发任务,模拟 “环境监测系统” 的核心功能:
任务 1(高优先级):每 100ms 采样温湿度传感器(模拟),将数据发送到队列;任务 2(中优先级):从队列接收采样数据,每 500ms 通过串口打印;任务 3(低优先级):每 1 秒翻转 LED(PA5),指示系统运行状态。
1. 硬件准备与连接
STM32F103C8T6 核心板;LED+1kΩ 电阻(PA5→电阻→LED 正极→GND);USB 转 TTL 模块(PA9-TX→模块 RX,PA10-RX→模块 TX,GND 共地)。
2. FreeRTOS 移植步骤(基于 STM32CubeIDE)
STM32CubeIDE 内置 FreeRTOS 组件,无需手动下载源码,直接配置即可:
步骤 1:新建工程并启用 FreeRTOS
打开 STM32CubeIDE,新建工程,选择 “STM32F103C8T6”;进入 “Pinout & Configuration” 界面,左侧 “Middleware & Software Packs”→“FreeRTOS”;点击 “Add”,选择 “FreeRTOS V10.3.1”(或最新版本),点击 “OK”;在 “Mode” 选项卡中,“CMSIS_Version” 选择 “CMSIS-RTOS V2”(推荐,API 更统一),其他默认。
步骤 2:配置系统时钟(确保 SysTick 正常)
FreeRTOS 依赖 SysTick 定时器作为调度时钟,需配置正确的系统时钟:
进入 “Clock Configuration” 界面,配置 HSE→PLL→72MHz 系统时钟(同第 3 篇);确认 SysTick 时钟为 72MHz(默认由系统时钟分频得到,1ms 触发一次)。
步骤 3:配置 GPIO 与 UART
LED 引脚(PA5):设为 “GPIO_Output”(推挽输出,初始低电平);UART1(PA9-TX,PA10-RX):配置为异步模式,波特率 115200,8N1(同第 7 篇)。
步骤 4:创建任务(通过图形化界面)
进入 FreeRTOS 的 “Tasks and Queues” 选项卡,点击 “Add” 创建 3 个任务:
| 任务名称 | 优先级 | 栈大小 | 入口函数名 | 功能描述 |
|---|---|---|---|---|
| Task_Sensor | 3 | 512 | vTaskSensor | 采样温湿度,发送到队列 |
| Task_UARTPrint | 2 | 512 | vTaskUARTPrint | 从队列读数据,串口打印 |
| Task_LEDToggle | 1 | 256 | vTaskLEDToggle | 翻转 LED |
创建队列(用于任务 1 和任务 2 通信):
点击 “Queues”→“Add”,队列名称设为 “Queue_SensorData”,数据大小设为 “4 字节”(存储 float 类型的温度值),队列长度设为 “5”(最多缓存 5 个数据)。
步骤 5:生成代码
点击 “Generate Code”,CubeIDE 会自动生成 FreeRTOS 的初始化代码(任务创建、队列创建)和底层驱动。
3. 编写任务逻辑代码(main.c)
生成的代码中,任务入口函数(如)已自动声明,只需在
vTaskSensor的 “USER CODE” 区域实现函数逻辑:
main.c
(1)全局变量声明(队列句柄,自动生成,无需手动定义)
CubeIDE 会在中自动生成队列句柄:
main.c
/* USER CODE BEGIN PV */
// 温湿度数据(模拟)
float temp = 25.0f;
float humi = 60.0f;
/* USER CODE END PV */
// 队列句柄(自动生成,在freertos.c中定义,需声明为外部变量)
extern QueueHandle_t Queue_SensorDataHandle;
(2)任务 1:传感器采样并发送到队列(vTaskSensor)
/* USER CODE BEGIN Header_vTaskSensor */
void vTaskSensor(void *argument);
/* USER CODE END Header_vTaskSensor */
void vTaskSensor(void *argument)
{
for(;;)
{
// 1. 模拟传感器采样(温度每次+0.1℃,湿度固定)
temp += 0.1f;
if (temp > 30.0f) temp = 25.0f;
// 2. 将温度数据发送到队列(等待时间0,立即返回)
if (xQueueSend(Queue_SensorDataHandle, &temp, 0) == pdPASS)
{
// 发送成功(可选打印调试信息)
}
else
{
// 队列满,发送失败(可选处理)
}
// 3. 任务延时100ms(阻塞态,释放CPU给其他任务)
vTaskDelay(pdMS_TO_TICKS(100));
}
}
(3)任务 2:从队列接收数据并串口打印(vTaskUARTPrint)
/* USER CODE BEGIN Header_vTaskUARTPrint */
void vTaskUARTPrint(void *argument);
/* USER CODE END Header_vTaskUARTPrint */
void vTaskUARTPrint(void *argument)
{
float recv_temp;
char uart_buf[64];
for(;;)
{
// 1. 从队列接收数据(等待时间portMAX_DELAY,无限等待直到有数据)
if (xQueueReceive(Queue_SensorDataHandle, &recv_temp, portMAX_DELAY) == pdPASS)
{
// 2. 格式化并发送到串口
sprintf(uart_buf, "传感器数据:温度=%.1f℃,湿度=%.1f%%
", recv_temp, humi);
HAL_UART_Transmit(&huart1, (uint8_t*)uart_buf, strlen(uart_buf), 100);
}
// 3. 任务延时500ms
vTaskDelay(pdMS_TO_TICKS(500));
}
}
(4)任务 3:翻转 LED(vTaskLEDToggle)
/* USER CODE BEGIN Header_vTaskLEDToggle */
void vTaskLEDToggle(void *argument);
/* USER CODE END Header_vTaskLEDToggle */
void vTaskLEDToggle(void *argument)
{
for(;;)
{
// 翻转LED电平
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
// 任务延时1000ms
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
(5)启动调度器(自动生成,无需修改)
CubeIDE 会在的
main.c函数中自动添加调度器启动代码:
main
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_Init();
MX_FreeRTOS_Init(); // 初始化FreeRTOS(创建任务、队列)
/* Start scheduler */
osKernelStart(); // 启动任务调度器,从此CPU由调度器管理
/* We should never get here as control is now taken by the scheduler */
for(;;)
{
}
}
4. 烧录验证与现象
烧录程序,打开串口助手(波特率 115200);观察现象:
LED:每 1 秒翻转一次(任务 3 正常运行);串口输出:每 500ms 显示一条数据,温度从 25.0℃开始,每次 + 0.1℃,到 30.0℃后重置(任务 1 采样、任务 2 打印正常);实时性验证:即使任务 2 在串口打印(耗时操作),任务 1 仍能每 100ms 采样(高优先级任务未被阻塞)。
四、FreeRTOS 常见问题与调试技巧
调度器启动后任务不运行?
任务优先级设置错误:确保任务优先级 > 0(FreeRTOS 默认最低优先级为 0,通常设为 1 及以上);栈大小不足:若任务栈太小(如 256 字节),执行复杂操作(如)会导致栈溢出,需增大栈大小(如 512 字节);未调用
sprintf:调度器需手动启动,CubeIDE 会自动生成,但手动修改时需确认。
osKernelStart()
队列发送 / 接收失败?
队列未创建:确认不为 NULL(CubeIDE 自动创建,若手动创建需调用
Queue_SensorDataHandle);等待时间过短:
xQueueCreate的等待时间设为
xQueueSend,避免队列满时立即失败;数据类型不匹配:队列数据大小需与发送的数据类型一致(如 float 类型对应 4 字节)。
pdMS_TO_TICKS(100)
任务切换导致串口数据乱码?
未使用互斥锁:若多个任务同时调用,会导致数据冲突,需添加互斥锁保护:
HAL_UART_Transmit
// 创建互斥锁(CubeIDE图形化界面添加)
extern SemaphoreHandle_t Mutex_UARTHandle;
// 串口打印前获取互斥锁
if (xSemaphoreTake(Mutex_UARTHandle, pdMS_TO_TICKS(100)) == pdPASS)
{
HAL_UART_Transmit(&huart1, ...);
xSemaphoreGive(Mutex_UARTHandle); // 释放互斥锁
}
五、第 17 篇总结与下一篇预告
总结
这一篇我们掌握了 FreeRTOS 的核心:
FreeRTOS 通过任务调度器实现多任务并发,高优先级任务可抢占低优先级任务,确保实时性;基于 STM32CubeIDE 的移植无需手动下载源码,通过图形化界面可快速创建任务、队列;实操中实现了 “传感器采样→数据通信→串口打印→LED 指示” 的三任务并发,验证了 FreeRTOS 的优势。
下一篇预告
FreeRTOS 的任务间同步与通信是复杂系统的核心,仅用队列还不够灵活。第 18 篇我们将深入学习 FreeRTOS 的信号量、互斥锁与事件标志组,通过 “多任务资源共享” 和 “任务间事件通知” 的实操,解决多任务协作中的冲突与同步问题,让你掌握更健壮的 RTOS 编程技巧。


