FreeRTOS快速入门


前言

本文适合对FreeRTOS完全不了解的零基础小白入门。

一、FreeRTOS介绍

1.FreeRTOS概念

FreeRTOS 是一款开源、轻量级的实时操作系统(RTOS),专为资源受限的嵌入式设备设计。
抢占式任务调度:支持多任务并发执行,按优先级动态分配CPU时间。(并发执行就是很多事情同时做)
​低内存占用:最小内存 footprint 可低至 ​几KB​(适用于8位/16位MCU)。
​跨平台支持:兼容ARM、AVR、PIC、RISC-V等多种架构。(同一段代码可以重复应用在不同芯片上,无需重复编写底层驱动)

2.FreeRTOS 核心功能

​任务管理:(给控制器分配任务的,)
创建/删除任务(Task),设置优先级和堆栈大小。
支持任务阻塞(如 vTaskDelay())和唤醒(如事件触发)。
​作用:通过创建/删除任务(xTaskCreate())、设置优先级(数值越小优先级越高)、动态调整堆栈大小,实现多任务并发执行。
​意义:
​抢占式调度确保高优先级任务(如实时控制)优先执行,满足实时性要求。
​资源分配灵活:适应不同复杂度的任务(简单传感器采集 vs 复杂算法处理)。

​ 同步机制:
任务间通信:队列(Queue)、信号量(Semaphore)、互斥锁(Mutex)。
事件通知(Event)和二值信号量(Binary Semaphore)。
作用:通过队列(Queue)、信号量(Semaphore)、互斥锁(Mutex)等工具,协调任务间通信与资源访问。
​意义:
​避免竞争条件:防止多个任务同时操作共享资源(如传感器数据或通信接口)。
​事件驱动编程:通过信号量触发任务唤醒(如按键按下触发UI更新)。

​时间管理:
软件定时器(Software Timer)和硬件定时器中断。
作用:提供软定时器(xTimerCreate())和硬件定时器中断,支持任务延时(vTaskDelay())和周期性事件。
​意义:
​精确控制时序:确保周期性任务(如PWM调速)按时执行。
​超时检测:监测任务响应时间(如网络请求超时重试)。

​内存管理:
动态内存分配(pvPortMalloc() / vPortFree())。
作用:动态分配内存(pvPortMalloc())并回收(vPortFree()),优化内存利用率。
​意义:
​减少内存碎片:在资源受限设备(如8位MCU)中避免内存耗尽。
​适应动态需求:根据任务负载灵活调整内存分配(如临时缓存数据)。

二、核心功能示例代码

1.基础任务创建与调度

#include <FreeRTOS.h>
#include <task.h>

// 任务句柄声明
TaskHandle_t Task1_Handle = NULL;
TaskHandle_t Task2_Handle = NULL;

// 任务1函数
void Task1(void *pvParameters) {
  while (1) {
    printf("Task 1 running...\n");
    vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1秒
  }
}

// 任务2函数
void Task2(void *pvParameters) {
  while (1) {
    printf("Task 2 running...\n");
    vTaskDelay(pdMS_TO_TICKS(500)); // 延迟0.5秒
  }
}

int main() {
  FreeRTOS.begin(); // 初始化FreeRTOS

  // 创建任务
  xTaskCreate(Task1, "Task1", 256, NULL, 1, &Task1_Handle); // 优先级1
  xTaskCreate(Task2, "Task2", 256, NULL, 2, &Task2_Handle); // 优先级2

  vTaskStartScheduler(); // 启动任务调度器
  return 0;
}

展示多任务并发执行:任务1每秒打印一次,任务2每0.5秒打印一次。
​优先级机制:优先级1的任务优先级高于优先级2,因此任务1的打印间隔更规律。
输出:
Task 1 running…
Task 2 running…
Task 1 running…
Task 2 running…

2.队列实现任务间通信

#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>

QueueHandle_t xQueue = NULL;

void TaskProducer(void *pvParameters) {
  int data = 0;
  while (1) {
    xQueueSend(xQueue, &data, portMAX_DELAY); // 发送数据到队列
    data++;
    vTaskDelay(pdMS_TO_TICKS(500));
  }
}

void TaskConsumer(void *pvParameters) {
  int receivedData;
  while (1) {
    xQueueReceive(xQueue, portMAX_DELAY); // 从队列接收数据
    printf("Received: %d\n", receivedData);
  }
}

int main() {
  FreeRTOS.begin();

  xQueue = xQueueCreate(10, sizeof(int)); // 创建容量为10的队列

  xTaskCreate(TaskProducer, "Producer", 256, NULL, 1, NULL);
  xTaskCreate(TaskConsumer, "Consumer", 256, NULL, 2, NULL);

  vTaskStartScheduler();
  return 0;
}

(1)逐行解析

QueueHandle_t xQueue = NULL;
​作用:声明一个队列句柄(指针),用于后续操作队列。
​初始化:NULL表示队列尚未创建。

任务 TaskProducer(生产者)
​​功能:向队列发送递增的整数(0,1,2,…)。

任务 TaskConsumer(消费者)​
功能:从队列中接收数据并打印。

xTaskCreate(TaskProducer, “Producer”, 256, NULL, 1, NULL);
xTaskCreate(TaskConsumer, “Consumer”, 256, NULL, 2, NULL);
​生产者任务优先级为1,消费者为2(数字越小优先级越高)。
​堆栈大小:256字节(足够简单任务使用)。

代码运行流程
​初始化队列:队列最多存储10个整数。
​创建任务:
​生产者任务​(优先级1):每0.5秒发送一个递增的整数到队列。
​消费者任务​(优先级2):每收到一个整数就打印它。
​任务调度:
​生产者任务先执行,发送数据到队列。
​消费者任务随后执行,从队列中读取数据并打印。
​队列满时:若队列已满(10个元素),生产者任务会被阻塞,直到队列有空闲空间。
​队列空时:消费者任务会被阻塞,直到生产者发送新数据。

(2)总结

1.队列(Queue)的作用
​任务间通信:允许不同任务共享数据,无需直接访问全局变量。
​同步机制:生产者/消费者通过队列协调操作节奏(如“生产-消费”模式)。
​FIFO特性:先发送的数据先被接收,保证数据顺序。
2.阻塞函数(portMAX_DELAY)​
​定义:任务在等待资源(如队列空间或数据)时进入阻塞状态,释放CPU使用权。
​优势:
避免忙等待(Polling),节省能源。
提高系统响应性(其他任务可在此期间运行)。
​3.任务优先级
​生产者优先级1 < ​消费者优先级2:
如果队列未满且消费者正在等待数据,消费者任务的优先级更高,会抢占生产者任务吗?
​不会!​ 因为消费者任务只有在队列非空时才会执行 xQueueReceive,而生产者任务持续发送数据。
​实际运行中:生产者任务每隔0.5秒发送一次数据,消费者几乎立即接收并打印,因此两个任务交替执行。

3.信号量实现资源保护

#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>

SemaphoreHandle_t xSemaphore = NULL;

void Task1(void *pvParameters) {
  while (1) {
    if (xSemaphoreTake(xSemaphore, portMAX_DELAY)) { // 获取信号量
      printf("Task 1 acquired the semaphore!\n");
      vTaskDelay(pdMS_TO_TICKS(1000)); // 延迟1秒
      xSemaphoreGive(xSemaphore); // 释放信号量
    }
  }
}

void Task2(void *pvParameters) {
  while (1) {
    if (xSemaphoreTake(xSemaphore, portMAX_DELAY)) {
      printf("Task 2 acquired the semaphore!\n");
      vTaskDelay(pdMS_TO_TICKS(1000));
      xSemaphoreGive(xSemaphore);
    }
  }
}

int main() {
  FreeRTOS.begin();
  xSemaphore = xSemaphoreCreateBinary(); // 创建二值信号量

  xTaskCreate(Task1, "Task1", 256, NULL, 1, NULL);
  xTaskCreate(Task2, "Task2", 256, NULL, 2, NULL);

  vTaskStartScheduler();
  return 0;
}

(1)逐行解析

​全局变量 xSemaphore
作用:存储二值信号量的句柄,初始为 NULL(未创建)。

任务 Task1 和 Task2
核心逻辑:
任务循环中尝试获取信号量(xSemaphoreTake)。
若成功获取(!= pdFAIL),打印消息并延迟1秒后释放信号量(xSemaphoreGive)。
​关键点:
​互斥访问:同一时间只有一个任务能获取信号量。
​阻塞等待:若信号量不可用,任务会被阻塞直到超时(portMAX_DELAY 表示无限等待)。

运行流程与输出
​初始化:
创建信号量 xSemaphore(初始值为1)。
创建Task1(优先级1)和Task2(优先级2)。
​任务执行:
​第一次循环:
Task1成功获取信号量,打印消息并延迟1秒。
Task2尝试获取信号量,但信号量已被Task1占用,被阻塞。
​第二次循环:
Task1释放信号量,Task2立即获取并打印消息。
Task1再次尝试获取信号量,成功后重复上述过程。

(2)总结

信号量的作用:
​互斥控制:保护共享资源(如硬件引脚、通信接口),防止多个任务同时访问。
​同步事件:任务A通过信号量通知任务B某个事件已发生。

二值信号量 vs 计数信号量:
​二值信号量:只能取0或1,适用于互斥场景。
​计数信号量:可取大于1的值,允许多次获取(需对应多次释放)。

​阻塞与唤醒:
任务在信号量不可用时自动进入阻塞状态,释放CPU资源供其他任务使用。
信号量释放后,优先级最高的等待任务会被唤醒。

4.互斥锁保护共享变量

#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>

SemaphoreHandle_t xMutex = NULL;
int shared_counter = 0;

void Task1(void *pvParameters) {
  while (1) {
    xSemaphoreTake(xMutex, portMAX_DELAY); // 加锁
    shared_counter++;
    printf("Task 1: Counter = %d\n", shared_counter);
    vTaskDelay(pdMS_TO_TICKS(500));
    xSemaphoreGive(xMutex); // 解锁
  }
}

void Task2(void *pvParameters) {
  while (1) {
    xSemaphoreTake(xMutex, portMAX_DELAY);
    shared_counter--;
    printf("Task 2: Counter = %d\n", shared_counter);
    vTaskDelay(pdMS_TO_TICKS(500));
    xSemaphoreGive(xMutex);
  }
}

int main() {
  FreeRTOS.begin();
  xMutex = xMutexCreate(); // 创建互斥锁

  xTaskCreate(Task1, "Task1", 256, NULL, 1, NULL);
  xTaskCreate(Task2, "Task2", 256, NULL, 2, NULL);

  vTaskStartScheduler();
  return 0;
}

(1)逐行解析

​全局变量
SemaphoreHandle_t xMutex = NULL;
int shared_counter = 0;
xMutex:互斥锁句柄。
shared_counter:需保护的共享变量(初始值为0)。

​任务逻辑

void Task1(void *pvParameters) {
  while (1) {
    xSemaphoreTake(xMutex, portMAX_DELAY); // 加锁
    shared_counter++;
    printf("Task 1: Counter = %d\n", shared_counter);
    vTaskDelay(pdMS_TO_TICKS(500));
    xSemaphoreGive(xMutex); // 解锁
  }
}

​互斥锁操作:
xSemaphoreTake(xMutex):获取互斥锁(若被占用,任务阻塞)。
xSemaphoreGive(xMutex):释放互斥锁,允许其他任务获取。
​共享变量操作:
递增 shared_counter 并打印结果。

​互斥锁创建
xMutex = xMutexCreate();
​xMutexCreate():创建一个互斥锁(初始为可用状态)。

​运行流程与输出
​初始化:
创建互斥锁 xMutex。
创建Task1(优先级1)和Task2(优先级2)。
​任务执行:
​Task1 先执行,获取互斥锁后递增 shared_counter 并打印。
​Task2 尝试获取互斥锁,但被Task1占用,进入阻塞。
​Task1 释放互斥锁后,Task2 获取锁并递减 shared_counter,打印结果。
循环执行,输出交替递增/递减的值。

(3)总结

​互斥锁的作用:
​原子操作:确保对共享变量的修改(如 shared_counter++)不可分割,避免竞态条件。
​严格互斥:同一时间仅允许一个任务持有互斥锁。

​与信号量的区别:
​信号量:允许多次获取和释放(计数信号量),适用于资源池管理。
​互斥锁:仅允许一次获取和释放,强制排他访问。

​优先级反转:
若高优先级任务等待低优先级任务释放互斥锁,会发生优先级反转。
​解决方案:使用 ​优先级继承​(FreeRTOS默认开启)临时提升低优先级任务的优先级。

三.工程实际运用

1. 任务创建与删除 → 多传感器数据采集系统

场景:工业物联网设备(如环境监测仪)需同时采集温度、湿度、气压数据。
实现:
​Task1:温度传感器采样(每秒一次)。
​Task2:湿度传感器采样(每秒一次)。
​Task3:气压传感器采样(每秒一次)。
​动态删除:若某传感器故障(如温度超限),自动删除对应任务并报警。、
意义:
​资源优化:故障时释放内存,避免无效任务占用CPU。
​扩展性:新增传感器只需创建新任务,无需修改现有逻辑。

2.队列 → 工业PLC与执行器通信

场景:PLC(可编程逻辑控制器)通过队列向多个执行器(如电机、阀门)发送控制指令。
实现:
​生产者任务(PLC)​:将指令(如“打开阀门1”)发送到队列。
​消费者任务(执行器)​:从队列接收指令并执行。

// 执行器任务
void Task_Executor(void *pvParameters) {
  Instruction_t instruction;
  while (1) {
    xQueueReceive(xQueue, &instruction, portMAX_DELAY);
    actuator_execute(instruction.type, instruction.value);
  }
}

意义:
​解耦设计:PLC与执行器无需直接通信,通过队列隔离。
​实时性保障:指令按顺序处理,避免执行器竞争。

3.信号量 → 电机控制中的方向保护

场景:步进电机需要通过两个任务(方向控制与速度调节)协同工作,但方向不能冲突。
实现:

​信号量 xDirSemaphore:确保同一时间只允许一个任务修改方向。
​Task1:根据用户输入设置方向(如“顺时针”)。
​Task2:根据PID算法调整转速。
代码关联:

void Task_SetDirection(int direction) {
  if (xSemaphoreTake(xDirSemaphore, portMAX_DELAY)) {
    set_motor_direction(direction);
    xSemaphoreGive(xDirSemaphore);
  }
}

意义:
​防冲突:避免方向指令与速度调节指令同时修改电机状态。
​可靠性:适用于工业机械臂、无人机航向控制等场景。

4. 互斥锁 → 共享显示屏的数据更新

场景:物联网网关需要同时显示温度、湿度、网络状态,且数据更新频繁。
实现:
​共享变量 display_data:存储当前显示内容。
​Task1:每秒更新温度数据。
​Task2:每5秒更新网络状态。
​互斥锁保护:确保一次只更新一个任务的数据。
代码关联:

void Task_UpdateDisplay(int data_type, int value) {
  xSemaphoreTake(xMutex, portMAX_DELAY);
  display_data[data_type] = value;
  update_LCD(display_data);
  xSemaphoreGive(xMutex);
}

意义:
​数据一致性:防止屏幕闪烁或显示错误(如温度和湿度数值交替跳动)。
​扩展性:新增显示类型只需修改锁保护的变量,无需重构代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值