SOC-ESP32S3部分:12-1、任务通信-队列
飞书文档https://x509p6c8to.feishu.cn/wiki/L7GFwnpnhiO8ESkIHYBcbg0EnKe
以往我们大循环中两个模块进行通信,或者中断和大循环通信,很多时候都用一个全局变量标志位的方式进行,在学习单片机部分串口通信时,我们也使用过队列的方式进行,但是都是需要我们自己编写代码实现,
而IDF集成了FreeRTOS后,我们就可以直接使用FreeRTOS的队列功能,来实现任务与任务,中断与任务的通信。
下面我们来看看如何使用FreeRTOS的队列功能。
头文件
#include "freertos/queue.h"QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize
);
功能: xQueueCreate 函数用于创建一个队列,用于在任务之间传递数据。队列可以用于同步和通信,确保数据在任务之间的安全传递。
参数:
uxQueueLength: 队列中可以容纳的最大项数。
uxItemSize: 队列中每一项的大小(以字节为单位)。
返回值:
QueueHandle_t: 队列句柄。如果队列创建失败,返回 NULL。BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait
);
功能: xQueueSend 函数用于将数据项发送到队列中。如果队列已满,任务可以选择等待队列中有空间可用,或者立即返回。
参数:
xQueue: 队列句柄,由 xQueueCreate 函数返回。
pvItemToQueue: 指向要发送的数据项的指针。
xTicksToWait: 任务在队列满时等待的时间。可以使用 portMAX_DELAY 表示无限等待,直到队列中有空间可用。
返回值:
pdPASS: 数据项成功发送到队列。
errQUEUE_FULL: 队列已满,任务未在指定时间内发送数据项。BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait
);
功能: xQueueReceive 函数用于从队列中接收数据项。如果队列为空,任务可以选择等待队列中有数据可用,或者立即返回。
参数:
xQueue: 队列句柄,由 xQueueCreate 函数返回。
pvBuffer: 指向存储接收到的数据项的缓冲区的指针。
xTicksToWait: 任务在队列空时等待的时间。可以使用 portMAX_DELAY 表示无限等待,直到队列中有数据可用。
返回值:
pdPASS: 数据项成功从队列接收。
errQUEUE_EMPTY: 队列为空,任务未在指定时间内接收数据项。
例如,创建一个队列,然后在任务1中间隔1s发送消息,任务2中接收消息
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"static const char* TAG = "MyModule";static QueueHandle_t queue = NULL;static void task1(void* arg)
{uint32_t num = 0;for (;;) {vTaskDelay(1000 / portTICK_PERIOD_MS);num ++;ESP_LOGI(TAG, "send num = %ld\n",num);xQueueSend(queue, &num, portMAX_DELAY);}
}static void task2(void* arg)
{uint32_t num;for (;;) {if (xQueueReceive(queue, &num, portMAX_DELAY)) {ESP_LOGI(TAG, "receive num = %ld\n",num);}}
}void app_main(void)
{//创建队列queue = xQueueCreate(10, sizeof(uint32_t));//创建任务xTaskCreate(task1, "task1", 2048, NULL, 10, NULL);xTaskCreate(task2, "task2", 2048, NULL, 10, NULL);while (1) {vTaskDelay(1000 / portTICK_PERIOD_MS);}
}
像我们前面说到,按键中断触发后,不应该在中断内部打印,需要把触发的事件发出来,然后在任务中进行处理,这里我们就可以使用队列进行通信。
GPIO中断优化
这段代码的主要功能是初始化一个 GPIO 输入引脚(编号为 5),设置其为上升沿触发中断,并为该引脚注册一个中断服务函数。当该引脚的电平从低电平变为高电平时,会触发中断并执行 gpio_isr_handler 函数。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"static const char* TAG = "MyModule";#define GPIO_INPUT_IO 42
#define GPIO_INPUT_PIN_SEL (1ULL<<GPIO_INPUT_IO)static QueueHandle_t gpio_evt_queue = NULL;// 定义一个静态的中断服务函数 gpio_isr_handler,用于处理 GPIO 引脚的中断事件
// IRAM_ATTR 表示该函数将被放置在内部高速 RAM(IRAM)中执行,以提高中断处理的速度
// 参数 arg 是传递给中断服务函数的参数
static void IRAM_ATTR gpio_isr_handler(void* arg)
{// 这个变量表示触发中断的 GPIO 引脚编号uint32_t gpio_num = (uint32_t) arg;// gpio_evt_queue 是一个事先定义好的队列句柄,xQueueSendFromISR 是 FreeRTOS 用于在中断服务函数中发送数据到队列的函数xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}static void gpio_task_example(void* arg)
{uint32_t io_num;for (;;) {if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {ESP_LOGI(TAG, "GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));}}
}// 定义一个静态函数 init_isr,用于初始化 GPIO 引脚的中断功能
static void init_isr()
{gpio_config_t io_conf = {.intr_type = GPIO_INTR_POSEDGE, //中断触发类型为上升沿触发.pin_bit_mask = GPIO_INPUT_PIN_SEL, //GPIO掩码.mode = GPIO_MODE_INPUT, //输入模式.pull_up_en = GPIO_PULLUP_ENABLE, //启用 GPIO 引脚的上拉电阻};gpio_config(&io_conf);// 安装 GPIO 中断服务// 参数 0 表示使用默认的中断分配标志,该函数会初始化 GPIO 中断服务所需的资源gpio_install_isr_service(0);// 为指定的 GPIO 引脚注册中断服务函数// 当该引脚触发中断时,会调用 gpio_isr_handler 函数进行处理// 最后一个参数 (void*) GPIO_INPUT_IO_0 是传递给中断服务函数的参数gpio_isr_handler_add(GPIO_INPUT_IO, gpio_isr_handler, (void*)GPIO_INPUT_IO);
}void app_main(void)
{//创建队列gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));//创建任务xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);init_isr();int cnt = 0;while (1) {ESP_LOGI(TAG, "cnt: %d\n", cnt++);vTaskDelay(1000 / portTICK_PERIOD_MS);}
}