FreeRTOS学习记录(1):任务创建及任务间如何通信
在嵌入式开发中,随着功能复杂度提升,传统裸机开发的 “顺序执行 + 延时阻塞” 模式逐渐暴露瓶颈:实时性差、资源利用率低、功能扩展困难。FreeRTOS 作为一款轻量级实时操作系统,通过多任务调度机制完美解决这些问题,成为嵌入式开发的主流选择。FreeRTOS 是一款开源的实时操作系统内核(RTOS),核心功能是任务调度—— 通过优先级机制让多个任务 “并发” 运行(逻辑上的并行,物理上由 CPU
所用环境:stm32CubeMX + cmake + vscode
前言:
在嵌入式开发中,随着功能复杂度提升,传统裸机开发的 “顺序执行 + 延时阻塞” 模式逐渐暴露瓶颈:实时性差、资源利用率低、功能扩展困难。FreeRTOS 作为一款轻量级实时操作系统,通过多任务调度机制完美解决这些问题,成为嵌入式开发的主流选择。
一、FreeRTOS 核心概念:从 “裸机思维” 到 “多任务思维”
1. 什么是 FreeRTOS?
FreeRTOS 是一款开源的实时操作系统内核(RTOS),核心功能是任务调度—— 通过优先级机制让多个任务 “并发” 运行(逻辑上的并行,物理上由 CPU 分时切换实现)。其优势在于:
- 实时性:高优先级任务可抢占低优先级任务,确保紧急事件优先处理;
- 资源高效:任务阻塞时(如延时、等待资源),CPU 自动切换到其他任务,避免资源浪费;
- 模块化:功能按任务拆分,代码结构清晰,便于维护和扩展。
2. 核心术语解析
- 任务(Task):最小的执行单元,每个任务是一个独立的
while(1)循环,包含专属的栈空间和运行状态(就绪、运行、阻塞、挂起)。 - 调度器(Scheduler):FreeRTOS 的核心,负责根据任务优先级和状态切换任务,无需开发者手动干预。
- 优先级(Priority):任务的重要程度(0~configMAX_PRIORITIES-1,数值越大优先级越高),高优先级任务可抢占低优先级任务。
- 阻塞(Blocking):任务因等待资源(如延时、信号量)暂时停止运行,释放 CPU 给其他任务。
- 任务间通信:多任务协作的基础,包括全局变量、消息队列、信号量等机制。
3. 从裸机到多任务的转变
最初我未能跳出裸机开发的固有思维,仍执着于在主函数中手动调度各个功能函数;但当vTaskStartScheduler()被调用后,任务的调度权便完全交由 FreeRTOS 内核接管 —— 此时我们的核心工作,是在拆分好的各个任务函数中,妥善处理任务间的通信协作与各自的业务逻辑实现。
| 场景 | 裸机开发 | FreeRTOS 多任务 |
|---|---|---|
| 周期性任务 | 顺序执行 +delay,周期相互干扰 |
每个任务独立vTaskDelay,周期精准 |
| 资源利用 | delay期间 CPU 空闲,浪费资源 |
阻塞时自动切换任务,CPU 利用率 100% |
| 实时响应 | 低优先级任务可能阻塞高优先级事件 | 高优先级任务可抢占,响应及时 |
| 代码结构 | 单函数内堆砌逻辑,耦合度高 | 按功能拆分任务,模块化清晰 |
二、FreeRTOS 基础使用:任务的创建与运行
1. 任务的基本结构
每个任务是一个void (*)(void*)类型的函数,包含初始化逻辑和无限循环,示例:
// 温度采集任务
void temp_task(void *pvParameters) {
// 1. 初始化(仅执行一次)
temp_sensor_init();
// 2. 任务主循环(无限执行)
while(1) {
// 业务逻辑:采集温度
float temp = temp_sensor_read();
// 延时3秒(阻塞期间CPU切换到其他任务)
vTaskDelay(pdMS_TO_TICKS(3000));
}
}
2. 任务的创建与启动
通过xTaskCreate创建任务,最后调用vTaskStartScheduler启动调度器:
int main(void) {
// 1. 硬件初始化(时钟、外设等)
hardware_init();
// 2. 创建任务
xTaskCreate(
temp_task, // 任务函数
"TempTask", // 任务名称(调试用)
128, // 栈大小(单位:字,如STM32中1字=4字节)
NULL, // 传递给任务的参数
1, // 优先级(1级,较低)
NULL // 任务句柄(无需记录时设为NULL)
);
xTaskCreate(
humi_task, // 湿度采集任务
"HumiTask",
128,
NULL,
1, // 与温度任务同优先级
NULL
);
// 3. 启动调度器,任务开始运行
vTaskStartScheduler();
// 调度器启动后不会执行到此处(除非内存不足)
while(1) {}
}
三、任务间通信:多任务协作的核心
多任务并行时,不可避免需要交互(如按键任务通知 OLED 任务更新显示)。FreeRTOS 提供多种通信机制,核心原则是避免直接调用任务函数,通过间接方式实现协作。
1. 全局变量(状态机)
适用于传递简单状态(如 “显示模式”“运行状态”),需配合互斥锁(Mutex)避免数据竞争。
示例:按键触发 OLED 显示切换
// 1. 定义共享状态和互斥锁
typedef enum {
SHOW_TEMP, // 显示温度
SHOW_HUMI, // 显示湿度
SHOW_KEY // 显示按键信息
} OledMode;
OledMode g_oled_mode = SHOW_TEMP; // 全局状态变量
osMutexId_t g_mode_mutex; // 保护状态的互斥锁
// 2. 按键任务:修改状态
void key_task(void *arg) {
while(1) {
if (key_pressed()) { // 检测按键
// 申请互斥锁,安全修改状态
osMutexWait(g_mode_mutex, osWaitForever);
g_oled_mode = SHOW_KEY; // 切换到按键显示模式
osMutexRelease(g_mode_mutex);
}
vTaskDelay(pdMS_TO_TICKS(50)); // 50ms检测一次
}
}
// 3. OLED任务:根据状态更新显示
void oled_task(void *arg) {
while(1) {
// 读取当前状态(加锁保护)
osMutexWait(g_mode_mutex, osWaitForever);
OledMode current_mode = g_oled_mode;
osMutexRelease(g_mode_mutex);
// 根据状态执行对应逻辑
switch(current_mode) {
case SHOW_TEMP: oled_show_temp(); break;
case SHOW_HUMI: oled_show_humi(); break;
case SHOW_KEY: oled_show_key(); break;
}
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms刷新一次
}
}
2. 消息队列(Queue)
适用于需要传递具体信息(如按键值、传感器数据)的场景,任务间通过 “发送 / 接收消息” 异步通信,无需共享变量。
示例:按键任务发送指令给 OLED 任务
// 1. 定义消息结构体和队列
typedef struct {
uint8_t type; // 消息类型:0=温度,1=按键
char data[20]; // 消息内容
} OledMsg;
osMessageQueueId_t g_oled_queue; // 消息队列句柄
// 2. 初始化队列(main中)
g_oled_queue = osMessageQueueNew(5, sizeof(OledMsg), NULL); // 容量5条消息
// 3. 按键任务:发送消息
void key_task(void *arg) {
OledMsg msg;
while(1) {
if (key_pressed()) {
msg.type = 1;
sprintf(msg.data, "Key: %d pressed", get_key_value());
// 发送消息(无限期等待队列空闲)
osMessageQueuePut(g_oled_queue, &msg, 0, osWaitForever);
}
vTaskDelay(50);
}
}
// 4. OLED任务:接收消息并处理
void oled_task(void *arg) {
OledMsg msg;
while(1) {
// 接收消息(超时100ms,超时则显示默认内容)
if (osMessageQueueGet(g_oled_queue, &msg, NULL, 100) == osOK) {
if (msg.type == 1) {
oled_show_string(msg.data); // 显示按键信息
}
} else {
oled_show_temp(); // 超时显示温度
}
}
}
3. 信号量(Semaphore)
适用于单纯的 “事件触发”(如 “按键按下” 需要通知 OLED 刷新),不传递具体数据,仅标记 “事件发生”。
示例:按键触发 OLED 刷新
// 1. 定义信号量
osSemaphoreId_t g_refresh_sem;
// 2. 初始化信号量(main中)
g_refresh_sem = osSemaphoreNew(1, 0, NULL); // 初始值0(无信号)
// 3. 按键任务:释放信号量
void key_task(void *arg) {
while(1) {
if (key_pressed()) {
osSemaphoreRelease(g_refresh_sem); // 发送“刷新”信号
}
vTaskDelay(50);
}
}
// 4. OLED任务:等待信号量
void oled_task(void *arg) {
while(1) {
// 等待信号量(无限期等待)
osSemaphoreAcquire(g_refresh_sem, osWaitForever);
oled_show_key_info(); // 收到信号后显示按键信息
vTaskDelay(2000); // 显示2秒后恢复
oled_show_temp();
}
}
4. 事件标志组(Event Group)
适用于需要 “多个事件中任意一个或全部发生” 才触发动作的场景(如 “按键按下” 或 “传感器更新” 都需刷新 OLED)。
示例:多事件触发 OLED 刷新
// 1. 定义事件标志组
osEventFlagsId_t g_oled_events;
// 2. 初始化事件组(main中)
g_oled_events = osEventFlagsNew(NULL);
// 3. 按键任务:设置“按键事件”位
void key_task(void *arg) {
while(1) {
if (key_pressed()) {
osEventFlagsSet(g_oled_events, 1 << 0); // 设置bit0
}
vTaskDelay(50);
}
}
// 4. 传感器任务:设置“数据更新事件”位
void sensor_task(void *arg) {
while(1) {
sensor_update();
osEventFlagsSet(g_oled_events, 1 << 1); // 设置bit1
vTaskDelay(1000);
}
}
// 5. OLED任务:等待任意事件
void oled_task(void *arg) {
while(1) {
// 等待bit0或bit1被设置
uint32_t flags = osEventFlagsWait(
g_oled_events,
(1<<0)|(1<<1), // 关注的事件位
osFlagsWaitAny, // 任意事件触发
osWaitForever // 无限期等待
);
if (flags & (1<<0)) oled_show_key(); // 处理按键事件
if (flags & (1<<1)) oled_show_sensor(); // 处理传感器事件
}
}
四、总结:FreeRTOS 开发的核心原则
- 任务拆分:按功能模块拆分任务(如采集、显示、交互),每个任务专注单一职责;
- 优先级设计:紧急任务(如按键响应)设高优先级,周期性任务(如采集)设低优先级;
- 通信选择:简单状态用全局变量 + 互斥锁,复杂数据用消息队列,事件通知用信号量 / 事件组;
- 避免直接调用:任务间通过通信机制交互,禁止直接调用其他任务的函数;
- 合理延时:通过
vTaskDelay释放 CPU,避免任务长期占用资源。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)