FreeRTOS快速入门

一、FreeRTOS概述与体验

1. FreeRTOS目录架构

在这里插入图片描述
source根目录是核心文件,这些文件是通用的。portable目录下是移植时需要实现的文件。
FreeRTOS的最核心只有两个文件:FreeRTOS/Source/tasks.c与FreeRTOS/Source/list.c
在这里插入图片描述
在FreeRTOS中,任务就是一个函数,该函数不能返回。
任务优先级的取值范围是:0-{configMAX_PRIORITIES-1},数值越大表示优先级越高。

四个任务状态

在这里插入图片描述
就绪态的任务,可以被调度器挑选出来切换为运行态,调度器永远都是挑选最高优先级的就绪态任务并使其进入运行状态。

PS:用口诀理解C语言:
变量变量,能变,就是能读能写,必定在内存里;
指针指针,保存的是地址,32位处理器中地址都是32位的,无论是什么类型的指针变量,都是4字节。

二、任务间通信

消息队列

消息队列(Message Queue) 是 FreeRTOS 中实现任务间通信的核心机制之一,它允许任务以 FIFO(先进先出)的方式发送和接收数据。

1. 消息队列的基本概念
  • 队列项(Queue Item):队列中存储的基本单位,可以是任意类型的数据
  • 队列长度(Queue Length):队列能够存储的最大项数
  • 队列项大小(Item Size):每个队列项占用的字节数
  • 阻塞时间(Block Time):任务在队列操作时的最大等待时间
2. 消息队列的创建
// 创建消息队列
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, 
                            UBaseType_t uxItemSize );

// 示例:创建一个能存储10个整数的队列
QueueHandle_t xIntegerQueue;
xIntegerQueue = xQueueCreate(10, sizeof(int32_t));

// 示例:创建一个能存储5个结构体的队列
typedef struct {
    uint8_t command;
    uint32_t parameter;
} Message_t;

QueueHandle_t xMessageQueue;
xMessageQueue = xQueueCreate(5, sizeof(Message_t));
3. 消息队列的操作函数
  • 发送消息到队列尾xQueueSend()xQueueSendToBack()
  • 发送消息到队列头xQueueSendToFront()
  • 从队列接收消息xQueueReceive()
  • 查看队列消息(不取出)xQueuePeek()
  • 重置队列xQueueReset()
  • 删除队列vQueueDelete()
4. 使用示例
// 发送消息示例
void vSenderTask(void *pvParameters) {
    int32_t lValueToSend = 100;
    BaseType_t xStatus;
    
    while(1) {
        // 发送消息到队列,等待10个tick
        xStatus = xQueueSend(xIntegerQueue, &lValueToSend, 10);
        
        if(xStatus == pdPASS) {
            // 发送成功
            lValueToSend++;
        } else {
            // 发送失败(超时)
        }
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

// 接收消息示例
void vReceiverTask(void *pvParameters) {
    int32_t lReceivedValue;
    BaseType_t xStatus;
    
    while(1) {
        // 从队列接收消息,无限等待
        xStatus = xQueueReceive(xIntegerQueue, &lReceivedValue, portMAX_DELAY);
        
        if(xStatus == pdPASS) {
            // 处理接收到的消息
            printf("Received: %d\n", lReceivedValue);
        }
    }
}
5. 消息队列的应用场景
  1. 任务间数据传递:生产者任务产生数据,消费者任务处理数据
  2. 事件通知:一个任务通知另一个任务某个事件已发生
  3. 缓冲数据:在数据处理速度不匹配的任务间提供缓冲
  4. 命令传递:发送命令和控制信息给其他任务
6. 重要注意事项
  • 队列操作是线程安全的,多个任务可以同时访问同一个队列
  • 队列满时发送操作会阻塞,队列空时接收操作会阻塞
  • 可以设置阻塞时间为0实现非阻塞操作
  • 使用 portMAX_DELAY 可以实现无限等待
  • 队列创建后需要检查返回值是否为NULL
7. 队列与任务通知的比较
特性 消息队列 任务通知
存储容量 可配置多个项 单个值
数据类型 任意类型 32位值
阻塞机制 支持 支持
内存占用 较多 极少
性能 较慢 极快
适用场景 复杂数据传递 简单事件通知

消息队列是 FreeRTOS 中最灵活的任务间通信机制,适用于需要传递复杂数据或需要缓冲的场景。

信号量

简介:信号量是一种实现任务间通信的机制,可以实现任务间同步与互斥访问,常用于协助一组相互竞争的任务访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。

1. 信号量的基本概念与类型

信号量是一种用于任务间同步和互斥的机制,本质上是一个计数器,用于控制对共享资源的访问。FreeRTOS 提供了三种类型的信号量:

  1. 二值信号量(Binary Semaphore)

    • 只有 0 和 1 两种状态
    • 常用于任务同步和互斥
    • 创建后初始值为 0
  2. 计数信号量(Counting Semaphore)

    • 值可以在 0 到创建时指定的最大值之间变化
    • 用于管理多个相同的资源
    • 例如:停车场有 10 个车位,信号量初始值为 10,每进一辆车减 1,每出一辆车加 1
  3. 互斥信号量(Mutex Semaphore)

    • 特殊的二值信号量,具有优先级继承机制
    • 用于解决优先级反转问题
    • 确保高优先级任务能够及时获取资源
2. 创建与使用信号量的核心 API 函数

创建信号量:

// 创建二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary(void);

// 创建计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(
    UBaseType_t uxMaxCount,      // 最大计数值
    UBaseType_t uxInitialCount   // 初始计数值
);

// 创建互斥信号量
SemaphoreHandle_t xSemaphoreCreateMutex(void);

获取信号量(Take):

BaseType_t xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,  // 信号量句柄
    TickType_t xTicksToWait        // 等待时间(portMAX_DELAY 表示无限等待)
);

释放信号量(Give):

BaseType_t xSemaphoreGive(
    SemaphoreHandle_t xSemaphore   // 信号量句柄
);

代码示例:使用二值信号量进行任务同步

// 全局信号量句柄
SemaphoreHandle_t xBinarySemaphore;

// 任务1:发送信号
void vTaskSender(void *pvParameters) {
    while(1) {
        // 执行一些操作
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // 释放信号量,通知任务2
        xSemaphoreGive(xBinarySemaphore);
        printf("信号已发送\n");
    }
}

// 任务2:等待信号
void vTaskReceiver(void *pvParameters) {
    while(1) {
        // 等待信号量,无限等待
        if(xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {
            printf("收到信号,开始处理\n");
            // 执行处理操作
        }
    }
}

// 主函数中创建信号量和任务
int main(void) {
    // 创建二值信号量
    xBinarySemaphore = xSemaphoreCreateBinary();
    
    // 创建任务
    xTaskCreate(vTaskSender, "Sender", 128, NULL, 2, NULL);
    xTaskCreate(vTaskReceiver, "Receiver", 128, NULL, 1, NULL);
    
    // 启动调度器
    vTaskStartScheduler();
    
    return 0;
}
3. 信号量的典型应用场景
  1. 任务同步

    • 一个任务等待另一个任务完成某项操作
    • 例如:任务A完成数据采集后,通知任务B开始数据处理
    • 使用二值信号量实现
  2. 资源互斥访问

    • 保护共享资源,防止多个任务同时访问
    • 例如:多个任务需要访问同一个串口或共享内存
    • 使用互斥信号量实现,避免优先级反转
  3. 资源计数管理

    • 管理有限数量的相同资源
    • 例如:缓冲池管理、连接池管理
    • 使用计数信号量实现
  4. 事件通知

    • 通知其他任务某个事件已发生
    • 例如:按键按下、定时器超时、数据到达
    • 使用二值信号量实现

使用注意事项:

  • 获取和释放信号量必须成对出现
  • 互斥信号量只能由获取它的任务释放
  • 在中断服务程序中使用 xSemaphoreGiveFromISR()xSemaphoreTakeFromISR()
  • 避免信号量嵌套,防止死锁
  • 合理设置等待超时时间,避免任务永久阻塞

信号量是 FreeRTOS 中非常重要的同步机制,合理使用信号量可以大大提高多任务系统的可靠性和效率。

PS:进程与线程的区别:

附录

进程与线程的区别

在操作系统中,进程(Process)和线程(Thread)是两个核心的并发执行概念,理解它们的区别对于掌握操作系统原理至关重要。

1. 基本定义
  • 进程:是操作系统进行资源分配和调度的基本单位。每个进程都有独立的地址空间、数据栈和其他系统资源。
  • 线程:是进程中的一个执行单元,是CPU调度和分派的基本单位。同一进程内的多个线程共享进程的资源。
2. 主要区别
对比维度 进程 线程
资源拥有 拥有独立的地址空间和系统资源 共享所属进程的资源
创建开销 较大(需要分配独立资源) 较小(共享进程资源)
通信方式 进程间通信(IPC)较复杂 线程间通信较简单(共享内存)
切换开销 上下文切换开销大 上下文切换开销小
独立性 相互独立,一个进程崩溃不影响其他进程 一个线程崩溃可能导致整个进程崩溃
并发性 进程间并发执行 线程间并发执行(更轻量)
3. 在FreeRTOS中的体现

FreeRTOS作为一个实时操作系统,主要使用**任务(Task)**的概念,这更接近于线程而非进程:

  • FreeRTOS任务共享同一地址空间(类似于线程)
  • 任务间通过队列、信号量等机制通信(类似于线程间通信)
  • 没有传统意义上的进程隔离机制
4. 实际应用场景
  • 使用进程的场景:需要高隔离性、安全性的应用(如浏览器标签页、虚拟机)
  • 使用线程的场景:需要高效并发、共享数据的应用(如Web服务器、GUI应用)
5. 总结

进程提供了更好的隔离性和安全性,但开销较大;线程提供了更高的并发效率和资源共享能力,但需要更谨慎的同步管理。在嵌入式实时系统中,由于资源限制,通常采用类似线程的任务模型。


  1. https://blog.csdn.net/qq_43212092/article/details/104845158
    FreeRTOS说明文档精心整理【适合新手+入门】
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐