FreeRTOS操作系统(详细速通篇)——— 第十四章

        本专栏将对FreeRTOS进行快速讲解,带你了解并使用FreeRTOS的各部分内容。适用于快速了解FreeRTOS并进行开发、突击面试、对新手小白非常友好。期待您的后续关注和订阅!

目录

任务通知

1 任务通知定义

1.1 定义

1.2 通知值

1.3 通知状态

1.4 举例示范

2 任务通知优缺点

2.1 优势

2.2 劣势

3 特点及更新方式

 3.1 特点

3.2 更新方式

4 相关API函数

4.1 发送通知API函数

4.2 接受通知API函数

5 代码实例

5.1 任务通知模拟队列

 5.2 任务通知模拟信号量

 5.3 任务通知模拟事件标志组


任务通知是什么?

        在FreeRTOS中,任务通知是一种任务间的通信机制,用于通知一个任务某个事件发生了。为了更好地理解任务通知,我们可以将其比喻成日常生活中的一些场景。

1 任务通知定义

1.1 定义

        任务通知是用来通知任务的。任务控制块(Task Control Block, TCB)中的结构体成员变量 ulNotifiedValue 就是这个通知值。当任务需要接收通知时,它会等待这个通知值的变化。与使用队列、信号量、事件标志组不同,使用任务通知不需要额外创建一个结构体,通过任务控制块内置的成员变量即可实现直接通信。

1.2 通知值

        任务都有一个控制块(Task Control Block, TCB),其中包含一个通知值ulNotifiedValue。这个通知值是一个32位无符号整数,用于任务间通信时传递数据或状态。

1.3 通知状态

        每个任务都有一个通知状态ucNotifyState,用于表示当前通知的状态。通知状态有三种:

  • taskNOT_WAITING_NOTIFICATION:任务未等待通知。
  • taskWAITING_NOTIFICATION:任务在等待通知。
  • taskNOTIFICATION_RECEIVED:任务已接收到通知。
#define taskNOT_WAITING_NOTIFICATION   ( ( uint8_t ) 0 )  // 任务未等待通知
#define taskWAITING_NOTIFICATION       ( ( uint8_t ) 1 )  // 任务在等待通知
#define taskNOTIFICATION_RECEIVED      ( ( uint8_t ) 2 )  // 任务已接收到通知
1.4 举例示范

        假设有一个办公室,里面有一个老板和一个员工。老板需要通知员工去做某件事。老板可以通过多种方式通知员工,比如发电子邮件、发短信或者打电话。在FreeRTOS中,任务通知就像老板发电子邮件、短信或者打电话来通知员工去做事情一样。

  • 任务通知:老板通过发通知(比如电子邮件)告诉员工去做一件事情。
  • 任务接收通知:员工收到通知后去执行任务。

2 任务通知优缺点

2.1 优势
优点描述
高效性

速度快:任务通知是FreeRTOS中最快的任务间通信机制,直接在任务控制块(TCB)中操作,无需复杂的数据结构管理。

内存开销小:任务通知不需要额外的内存分配和管理,只使用任务控制块中的成员变量,极大地减少了内存开销。

简单性

API简单易用:任务通知API函数设计简单,易于理解和使用。包括发送通知和接收通知两类函数,使用起来非常方便。

无需额外结构:与队列、信号量、事件标志组不同,任务通知不需要额外创建和管理数据结构,简化了开发过程。

灵活性

多种更新方式:任务通知值可以通过多种方式更新,如覆盖、不覆盖、设置某个位或增加通知值。开发者可以根据具体需求选择合适的通知方式。

多用途:任务通知可以模拟信号量、消息邮箱和事件标志组,灵活应用于不同的任务同步和通信场景。

实时系统低延迟:由于任务通知的高效性和简单性,使用任务通知进行任务间通信和同步可以实现低延迟,有助于提高系统的实时性能。
2.2 劣势
缺点描述
通信单一性单一接收者:任务通知只能发送给一个特定的任务,无法广播给多个任务。这限制了其在需要一对多通信场景中的应用。
数据传递有限

无法缓存多个数据:任务通知只能保存一个通知值,无法像队列那样缓存多个数据。这意味着最新的通知值会覆盖之前的通知值。

无法发送复杂数据:任务通知主要用于发送简单的数值或标志,无法传递复杂的数据结构。

中断限制无法发送数据给ISR:任务通知无法直接用于中断服务例程(ISR)之间的通信,但ISR可以使用任务通知发送数据给任务。ISR没有任务控制块,所以无法接收任务通知。
阻塞限制发送方无法阻塞等待:在任务通知机制中,发送方无法进入阻塞状态等待接收方处理通知。这在某些需要发送方等待接收方处理完毕的场景中是一个限制。

3 特点及更新方式

 3.1 特点

        任务通知是FreeRTOS中用于任务间通信和同步的一种高效机制,具有以下特点:

特点描述类似机制
不覆盖接收任务的通知值任务通知值可以像队列一样,不覆盖接收任务的现有通知值。类似队列
覆盖接收任务的通知值任务通知值可以像队列一样,覆盖接收任务的现有通知值,确保只有最新的通知值保留。类似队列
更新接收任务通知值的一个或多个bit任务通知值的特定位可以被设置或清除,类似于事件标志组的操作,方便处理多种状态。类似事件标志组
增加接收任务的通知值任务通知值可以累加,类似于信号量的计数功能,方便进行计数操作。类似信号量

        因此只要合理、灵活地利用任务通知的特点,可以在某些场合中替代队列、信号量和事件标志组,实现高效的任务间通信和同步。通过灵活利用任务通知,可以在一些场合中替代队列、信号量和事件标志组,以简化代码,提高运行效率。

3.2 更新方式
  • 计数值(数值累加,类似信号量)

    • 描述:通知值在原基础上进行累加,适用于计数型信号量的实现。
    • 应用场景:用于计数、累加操作,如计数信号量。
  • 相应位置一(类似事件标志组)

    • 描述:更新通知值的特定位,将相应位置一。类似于事件标志组,用于标识多个状态。
    • 应用场景:用于事件标志管理和多状态标识。
  • 任意数值(支持覆盖和不覆盖,类似队列)

    • 描述:通知值可以被新的值覆盖,也可以选择不覆盖。类似于队列的消息传递机制。
    • 应用场景:用于简单的消息传递和数据更新。

4 相关API函数

4.1 发送通知API函数

       以下为发送通知API函数,简单去前三个函数进行介绍。

函数

描述

xTaskNotify()

发送通知,带有通知值

xTaskNotifyAndQuery()

发送通知,带有通知值并且保留接收任务的原通知值

xTaskNotifyGive()

发送通知,不带通知值

xTaskNotifyFromISR()

在中断中发送任务通知

xTaskNotifyAndQueryFromISR()

vTaskNotifyGiveFromISR()

(1)xTaskNotify()

函数作用:发送通知,带有通知值

函数原型

BaseType_t xTaskNotify(
    TaskHandle_t xTaskToNotify,  // 要通知的任务句柄
    uint32_t ulValue,            // 要发送的通知值
    eNotifyAction eAction        // 通知值的更新方式
);

参数解析

参数描述
xTaskToNotify要通知的任务句柄
ulValue要发送的通知值
eAction通知值的更新方式(eSetBitseIncrement等)

返回值
返回 pdPASS 表示发送通知成功,pdFAIL 表示发送通知失败。

函数举例

xTaskNotify(TaskB_Handle, 0x01, eSetBits); // 发送通知,更新位0

(2)xTaskNotifyAndQuery()

函数作用:发送通知,带有通知值并且保留接收任务的原通知值。

函数原型

BaseType_t xTaskNotifyAndQuery(
    TaskHandle_t xTaskToNotify,   // 要通知的任务句柄
    uint32_t ulValue,             // 要发送的通知值
    eNotifyAction eAction,        // 通知值的更新方式
    uint32_t *pulPreviousNotifyValue  // 指向接收任务的原通知值的指针
);

参数解析

参数描述
xTaskToNotify要通知的任务句柄
ulValue要发送的通知值
eAction通知值的更新方式(eSetBitseIncrement等)
pulPreviousNotifyValue用于保存接收任务的原通知值的指针

返回值
返回 pdPASS 表示发送通知成功,pdFAIL 表示发送通知失败。

函数举例

// 示例:发送通知并查询之前的通知值
uint32_t previousValue;
xTaskNotifyAndQuery(TaskB_Handle, 0x01, eSetBits, &previousValue); 

(3)xTaskNotifyGive()

函数作用:发送通知,不带通知值。

函数原型

BaseType_t xTaskNotifyGive(
    TaskHandle_t xTaskToNotify   // 要通知的任务句柄
);

参数解析

参数描述
xTaskToNotify要通知的任务句柄

返回值
返回 pdPASS 表示发送通知成功,pdFAIL 表示发送通知失败。

函数举例

// 示例:发送通知,不带通知值
xTaskNotifyGive(TaskB_Handle); 
4.2 接受通知API函数

(1)ulTaskNotifyTake()

函数作用:获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。

函数原型

uint32_t ulTaskNotifyTake(
    BaseType_t xClearCountOnExit, // 指定在成功接收通知后,将通知值清零或减一
    TickType_t xTicksToWait       // 阻塞等待任务通知值的最大时间
);

参数解析

参数描述
xClearCountOnExit指定在成功接收通知后,将通知值清零(pdTRUE)或减一(pdFALSE
xTicksToWait阻塞等待任务通知值的最大时间(以Tick为单位)

返回值
返回接收成功时的任务通知值,如果接收失败则返回0。

函数举例

// 示例:接收任务通知,成功接收后将通知值清零
uint32_t notificationValue;
notificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

// 检查是否成功接收到通知
if (notificationValue > 0) {
    // 成功接收到通知,执行相应操作
    printf("Notification received with value: %u\n", notificationValue);
} else {
    // 未接收到通知,处理错误情况
    printf("Failed to receive notification.\n");
}

(2)xTaskNotifyWait()

函数作用:获取任务通知,比ulTaskNotifyTake()更为复杂,可获取通知值和清除通知值的指定位。当任务通知用作事件标志组或队列时,使用此函数来获取通知。

函数原型

BaseType_t xTaskNotifyWait(
    uint32_t ulBitsToClearOnEntry, // 等待前清零指定任务通知值的比特位
    uint32_t ulBitsToClearOnExit,  // 成功等待后清零指定的任务通知值比特位
    uint32_t *pulNotificationValue,// 用来取出通知值的指针
    TickType_t xTicksToWait        // 阻塞等待任务通知值的最大时间
);

参数解析

参数描述
ulBitsToClearOnEntry等待前清零指定任务通知值的比特位(旧值对应位清0)
ulBitsToClearOnExit成功等待后清零指定的任务通知值比特位(新值对应位清0)
pulNotificationValue用来取出通知值的指针(如果不需要取出通知值,可设为NULL)
xTicksToWait阻塞等待任务通知值的最大时间(以Tick为单位)

返回值
返回 pdTRUE 表示等待任务通知成功,pdFALSE 表示等待任务通知失败。

函数举例

// 示例:接收任务通知并清除相应位,成功接收后将通知值存储在notificationValue中
uint32_t notificationValue;
BaseType_t result;
result = xTaskNotifyWait(0x00, 0xFF, &notificationValue, portMAX_DELAY);

// 检查是否成功接收到通知
if (result == pdTRUE) {
    // 成功接收到通知,执行相应操作
    printf("Notification received with value: %u\n", notificationValue);
} else {
    // 未接收到通知,处理错误情况
    printf("Failed to receive notification.\n");
}

5 代码实例

5.1 任务通知模拟队列

实验设计: 使用任务通知来模拟队列,将数据从一个任务发送到另一个任务。发送任务将数据放入通知值中,接收任务读取通知值并处理数据。

任务功能

  • 任务1(发送任务):模拟数据发送,将数据发送到任务通知值中。
  • 任务2(接收任务):接收任务通知值中的数据并处理。

代码示例

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

// 任务句柄
TaskHandle_t SendTask_Handle;
TaskHandle_t ReceiveTask_Handle;

// 任务函数声明
void SendTask(void *pvParameters);
void ReceiveTask(void *pvParameters);

int main(void)
{
    // 创建发送任务
    xTaskCreate((TaskFunction_t)SendTask, "SendTask", 128, NULL, 2, &SendTask_Handle);
    // 创建接收任务
    xTaskCreate((TaskFunction_t)ReceiveTask, "ReceiveTask", 128, NULL, 2, &ReceiveTask_Handle);

    // 启动调度器
    vTaskStartScheduler();

    // 主函数不会再执行到这里
    while(1);
}

// 发送任务:将数据发送到任务通知值中
void SendTask(void *pvParameters)
{
    uint32_t data = 0;
    while(1)
    {
        // 发送通知,带有数据
        xTaskNotify(ReceiveTask_Handle, data, eSetValueWithOverwrite);
        printf("SendTask: Sent data %u\n", data);
        data++;
        
        // 模拟发送间隔
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 接收任务:接收任务通知值中的数据并处理
void ReceiveTask(void *pvParameters)
{
    uint32_t receivedData;
    while(1)
    {
        // 等待接收任务通知值中的数据
        xTaskNotifyWait(0x00, 0x00, &receivedData, portMAX_DELAY);
        printf("ReceiveTask: Received data %u\n", receivedData);
    }
}
 5.2 任务通知模拟信号量

实验设计: 使用任务通知来模拟信号量,任务1产生信号量并发送通知,任务2接收通知并使用信号量执行相应操作。

任务功能

  • 任务1(信号量产生任务):模拟信号量产生并发送通知。
  • 任务2(信号量使用任务):接收通知并使用信号量。

代码示例

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

// 任务句柄
TaskHandle_t ProduceTask_Handle;
TaskHandle_t ConsumeTask_Handle;

// 任务函数声明
void ProduceTask(void *pvParameters);
void ConsumeTask(void *pvParameters);

int main(void)
{
    // 创建信号量产生任务
    xTaskCreate((TaskFunction_t)ProduceTask, "ProduceTask", 128, NULL, 2, &ProduceTask_Handle);
    // 创建信号量使用任务
    xTaskCreate((TaskFunction_t)ConsumeTask, "ConsumeTask", 128, NULL, 2, &ConsumeTask_Handle);

    // 启动调度器
    vTaskStartScheduler();

    // 主函数不会再执行到这里
    while(1);
}

// 信号量产生任务:产生信号量并发送通知
void ProduceTask(void *pvParameters)
{
    while(1)
    {
        // 发送通知,产生信号量
        xTaskNotifyGive(ConsumeTask_Handle);
        printf("ProduceTask: Produced signal\n");
        
        // 模拟信号量产生间隔
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 信号量使用任务:接收通知并使用信号量
void ConsumeTask(void *pvParameters)
{
    while(1)
    {
        // 等待接收信号量通知
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        printf("ConsumeTask: Consumed signal\n");
    }
}
 5.3 任务通知模拟事件标志组

实验设计: 使用任务通知来模拟事件标志组,任务1和任务2设置事件标志,任务3等待所有事件标志被设置后执行操作。

任务功能

  • 任务1(事件标志1任务):设置事件标志1。
  • 任务2(事件标志2任务):设置事件标志2。
  • 任务3(事件处理任务):等待所有事件标志被设置后执行操作。

代码示例

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

// 定义事件标志位
#define EVENT_BIT_1 (1 << 0)
#define EVENT_BIT_2 (1 << 1)

// 任务句柄
TaskHandle_t EventTask1_Handle;
TaskHandle_t EventTask2_Handle;
TaskHandle_t EventTask3_Handle;

// 任务函数声明
void EventTask1(void *pvParameters);
void EventTask2(void *pvParameters);
void EventTask3(void *pvParameters);

int main(void)
{
    // 创建事件标志1任务
    xTaskCreate((TaskFunction_t)EventTask1, "EventTask1", 128, NULL, 2, &EventTask1_Handle);
    // 创建事件标志2任务
    xTaskCreate((TaskFunction_t)EventTask2, "EventTask2", 128, NULL, 2, &EventTask2_Handle);
    // 创建事件处理任务
    xTaskCreate((TaskFunction_t)EventTask3, "EventTask3", 128, NULL, 2, &EventTask3_Handle);

    // 启动调度器
    vTaskStartScheduler();

    // 主函数不会再执行到这里
    while(1);
}

// 事件标志1任务:设置事件标志1
void EventTask1(void *pvParameters)
{
    while(1)
    {
        // 发送通知,设置事件标志1
        xTaskNotify(EventTask3_Handle, EVENT_BIT_1, eSetBits);
        printf("EventTask1: Set event bit 1\n");
        
        // 模拟事件产生间隔
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

// 事件标志2任务:设置事件标志2
void EventTask2(void *pvParameters)
{
    while(1)
    {
        // 发送通知,设置事件标志2
        xTaskNotify(EventTask3_Handle, EVENT_BIT_2, eSetBits);
        printf("EventTask2: Set event bit 2\n");
        
        // 模拟事件产生间隔
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

// 事件处理任务:等待所有事件标志被设置后执行操作
void EventTask3(void *pvParameters)
{
    uint32_t eventBits;
    while(1)
    {
        // 等待所有事件标志被设置
        xTaskNotifyWait(0x00, 0x00, &eventBits, portMAX_DELAY);

        // 检查是否所有事件标志都被设置
        if ((eventBits & (EVENT_BIT_1 | EVENT_BIT_2)) == (EVENT_BIT_1 | EVENT_BIT_2))
        {
            printf("EventTask3: All event bits set, processing events\n");
            
            // 清除所有事件标志
            xTaskNotifyStateClear(EventTask3_Handle);
        }
    }
}

       本专栏将对FreeRTOS进行快速讲解,带你了解并使用FreeRTOS的各部分内容。期待诸君的关注和订阅!

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值