【ESP32 C++教程】Unit6-3 FreeRTOS任务间通信

本节主要讲解FreeRTOS任务间如何使用消息队列和事件组进行通信。

消息队列

在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行通信, 这个通信的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递,由于队列用来传递消息的,所以也称为消息队列。

【ESP32 C++教程】Unit6-3 FreeRTOS任务间通信

创建队列

创建消息队列时FreeRTOS会先给消息队列分配一块内存空间,这块内存的大小等于消息队列控制块大小加上(单个消息空间大小与消息队列长度的乘积),接着再初始化消息队列,此时消息队列为空。

函数原型

QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength, //队列长度 
    UBaseType_t uxItemSize //队列项目大小
);

消息发送

在发送消息操作的时候,为了保护数据,当且仅当队列允许入队的时候,发送者才能成功发送消息

函数原型

BaseType_t xQueueSend(
    QueueHandle_t xQueue, //队列句柄
    const void * pvItemToQueue, //队列项目指针
    TickType_t xTicksToWait //阻塞等待时间 
);
  • 在消息队列未满时,消息将放入队列尾,使用xQueueSendToFront函数可将消息放入队列首;
  • 不消息队列已满时,系统会根据用户指定的阻塞超时时间xTicksToWait将任务阻塞,在超时时间内如果还不能完成入队操作,发送消息的任务或者中断服务程序会收到一个错误码errQUEUE_FULL,然后解除阻塞状态;
  • 只有在任务中发送消息才允许进行阻塞状态,而在中断中发送消息不允许带有阻塞机制的,需要调用在中断中发送消息的API函数接口,因为发送消息的上下文环境是在中断中,不允许有阻塞的情况。

接收消息

在接收消息操作的时候,若队列中有消息,则立即读取消息,并将消息从队列中移除,若无消息,则根据给定的等待时间决定是结束读取消息还是阻塞等待

函数原型

BaseType_t xQueueReceive(
    QueueHandle_t xQueue, //队列句柄
    void *pvBuffer, //队列项目指针
    TickType_t xTicksToWait //阻塞等待时间 
);
  • xTicksToWait为0时,结束读取,不进入阻塞态,继续后面的操作;
  • xTicksToWait为n时,进入阻塞态,在阻塞期间(n个tick),若队列有消息,则进入就绪态,读取消息,继续后面的操作;若超过n个tick还无消息,从阻塞状中唤醒,返回一个超时的错误码;
  • xTicksToWait为portMAX_DELAY时,会一直阻塞到队列有消息;

示例:消息队列应用

本示例通过向消息队列发生0或1来控制LED的关闭或打开

从 https://gitee.com/billyzh/esp32-cpp-lesson 下载本教程的源码到本地硬盘文件夹,如d:\esp32-cpp-lesson
在VSCode中,选择【文件】->【打开文件夹...】选择上一步保存的文件夹打开

打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON63_A 1

打开unit6-lesson63a/board_config.h文件,设置LED使用的引脚,
#define BUILTIN_LED_PIN GPIO_NUM_4
配置启用GpioLed
#define CONFIG_USE_LED_GPIO 1

创建LED实例,代码如下(unit6-lesson63a/my_board.cpp):

MyBoard::MyBoard() : Board() {
    Log::Info(TAG, "===== Create Board ...... =====");

    Log::Info(TAG, "initial led.");
    led_ = new GpioLed(BUILTIN_LED_PIN, false);

    Log::Info( TAG, "===== Board config completed. =====");
}

程序很简单,就是创建一个GpioLed实例。

消息队列应用

代码如下(unit6-lesson63a/my_application.cpp):

void MyApplication::OnInit() {
    queue_ = xQueueCreate(10, sizeof(int));

    task1_ = new Task("Task1");
    task1_->OnLoop([this](){
        Task1Loop();
    });
    task1_->Start( 4096, tskIDLE_PRIORITY+1);
    
    task2_ = new Task("Task2");
    task2_->OnLoop([this](){
        Task2Loop();
    });
    task2_->Start( 4096, tskIDLE_PRIORITY+1);
}

void MyApplication::Task1Loop() {
    state_ = (state_==0 ? 1 : 0);

    if (xQueueSend(queue_, &state_, 0) != pdPASS) {
        Log::Warn(TAG, "发送数据到队列失败。");
    }

    delay(500);
}

void MyApplication::Task2Loop() {
    int receive = 0;
    if (xQueueReceive(queue_, &receive, portMAX_DELAY) != pdPASS) {
        Log::Warn(TAG, "从队列接收数据失败。");
        return;
    }

    Led *led = Board::GetInstance().GetLed();
    if (receive==1) 
    {
        led->TurnOn();
    }
    else 
    {
        led->TurnOff();
    }
}

程序解读
1. 在OnInit方法内,创建一个消息队列和两个Task实例,然后启动任务;
2. Task1Loop方法是任务1的循环体方法,具体为向队列发生数据,每500ms发生一次数据0或1;
3. Task2Loop方法是任务2的循环体方法,具体为从队列阻塞式接收数据,当有数据时,为1点亮LED,为0熄灭LED;

编译项目并上传开发板检验

事件组

事件组(Event Group)是FreeRTOS中用于任务同步的一种机制。它允许任务等待多个事件的发生,事件的含义完全由开发者定义。事件组中的事件使用位(bit)来表示,每个位可以表示一个事件。事件组主要用于任务之间的协作,可以等待多个事件中的一个或多个事件的发生。

事件只与任务相关联,事件相互独立,一个 32 位的事件集合(高8位保留,实际可有 24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生)。

【ESP32 C++教程】Unit6-3 FreeRTOS任务间通信

任务之间可以设置、清除、等待这些位,来完成类似“谁完成了什么事”、“等谁先完成”这种协调。

设置事件组某位

将事件组中你指定的某几位设置为 1,如果有任务正在等待这些位,就会立刻唤醒它们。
多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。

函数原型

EventBits_t xEventGroupSetBits(
	EventGroupHandle_t xEventGroup,  //事件组句柄
	const EventBits_t uxBitsToSet          //要设置的事件bit位
);

等待事件组某位(或全部)

用于等待事件组中某些事件位被设置,支持事件等待超时机制。例如,一个任务需要等待多个传感器的信号,只要其中一个传感器触发,任务就可以继续执行。

函数原型

EventBits_t xEventGroupWaitBits(
	const EventGroupHandle_t xEventGroup, //事件组句柄
	const EventBits_t uxBitsToWaitFor,          //等待的事件bit位
	const BaseType_t xClearOnExit,               //是否清除事件
	const BaseType_t xWaitForAllBits,
	TickType_t xTicksToWait 
);

清除指事件位

用于清除事件组中的某些位(即将它们置为0)。它和 xEventGroupSetBits() 相反,它不会触发阻塞在事件位上的任务,只是静默清除某些位。

函数原型

EventBits_t xEventGroupClearBits(
	EventGroupHandle_t xEventGroup,   //事件组句柄
	EventBits_t uxBitsToClear             //要设置的事件bit位
);

示例:事件组应用

本示例通过事件组的事件来触发LED的关闭或打开

从 https://gitee.com/billyzh/esp32-cpp-lesson 下载本教程的源码到本地硬盘文件夹,如d:\esp32-cpp-lesson
在VSCode中,选择【文件】->【打开文件夹...】选择上一步保存的文件夹打开

打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON63_B 1

打开unit6-lesson63b/board_config.h文件,设置LED使用的引脚,
#define BUILTIN_LED_PIN GPIO_NUM_4
配置启用GpioLed
#define CONFIG_USE_LED_GPIO 1

创建LED实例,代码如下(unit6-lesson63b/my_board.cpp):

MyBoard::MyBoard() : Board() {
    Log::Info(TAG, "===== Create Board ...... =====");

    Log::Info(TAG, "initial led.");
    led_ = new GpioLed(BUILTIN_LED_PIN, false);

    Log::Info( TAG, "===== Board config completed. =====");
}

程序很简单,就是创建一个GpioLed实例。

事件组应用

代码如下(unit6-lesson63b/my_application.cpp):

void MyApplication::OnInit() {
    event_group_ = xEventGroupCreate();

    // 任务一
    task1_ = new Task("Task1");
    task1_->OnInit([this](){
        // 一些处理
        delay(500);
        xEventGroupSetBits(event_group_, 0b00000001);
    });
    task1_->Start(4096, tskIDLE_PRIORITY+1);

    // 任务二
    task2_ = new Task("Task2");
    task2_->OnInit([this](){
        // 一些处理
        delay(1000);
        xEventGroupSetBits(event_group_, 0b00000010);
    });
    task2_->Start(4096, tskIDLE_PRIORITY+1);
}

void MyApplication::OnLoop() {
    // 等待事件位被设置
    auto bits = xEventGroupWaitBits(event_group_, 
        0b00000011,
        pdTRUE, /* 自动清除,避免重复响应 */
        pdTRUE, /* 所有事件位被设置就返回 */
        portMAX_DELAY /* 无限期等待,也可使用pdMS_TO_TICKS指定等待时长 */
    );

    Led *led = Board::GetInstance().GetLed();
    led->TurnOn();
    
    // 有事件设置就触发(事件位OR)
    /*
    auto bits = xEventGroupWaitBits(event_group_, 
        0b00000011,
        pdTRUE, // 自动清除,避免重复响应 
        pdFALSE, // 所有事件位被设置就返回 
        portMAX_DELAY // 无限期等待,也可使用pdMS_TO_TICKS指定等待时长 
    );

    if (bits & 0b01 = 0b01) {
        Led *led = Board::GetInstance().GetLed();
        led->TurnOn();
    } else (bits & 0b10 = 0b10) {
        
    }
    */

    delay(1);
}

程序解读
1. 在OnInit方法内,创建一个事件组;创建任务一每500ms设置一次bit位0为1,创建任务二每1000ms设置一次bit位1为1;
2. 在OnLoop方法内,无限期等待事件组被设置,若bit位0和1均被设置,则点亮Led;

编译项目并上传开发板检验

- 本文由用户 老张 发布,文中观点仅代表作者本人,不代表本站立场。
- 如需转载,请联系作者;如有侵权,请联系本站处理。

02-22   阅读(1)   评论(0)
 标签: 创客 ESP32 FreeRTOS

涨知识
四位数码管

四位数码管是一种常见的LED显示器件,主要用于显示数字信息。

评论:
相关文章
【ESP32 C++教程】Unit6-2 FreeRTOS多任务

本节主要讲解Task类,FreeRTOS多任务的使用。


【ESP32 C++教程】Unit6-1 定时器

本节主要讲解Timer类,FreeRTOS定时器的使用。


【ESP32 C++教程】Unit5-2 执行器件之舵机

本节主要讲解舵机驱动类和用按键控制舵机。


【ESP32 C++教程】Unit5-1 执行器件之继电器

本节主要讲解执行器件类型和用按键控制继电器。


【ESP32 C++教程】Unit4-3 红外接收和遥控

本小节主要讲解红外接收和遥控器件,以及遥控操作LED。


【ESP32 C++教程】Unit4-2 模拟量传感器

本小节讲解模拟量传感器使用,旋转电位器,DHT11温湿度传感器和实现自定义传感器类。


【ESP32 C++教程】Unit4-1 数字量传感器

本小节讲解Sensor类及派生类、数字量传感器使用和传感器的推荐交互流程。


【ESP32 C++教程】Unit3-2 触摸输入

本小节讲解ESP32内置触摸引脚的用法,


【ESP32 C++教程】Unit3-1 按键输入

本小节主要介绍按键信号转换、Button类及派生类、和Button交互推荐流程。


【ESP32 C++教程】Unit2-2 Ws2812灯珠

本小节主要介绍Ws2812灯珠的使用、对父类进行扩展实现自定义功能,和指针向下强制转换的使用。