事件组
FreeRTOS 中的事件组(Event Groups)是用于任务间同步的一种机制。在 ESP32 的 FreeRTOS 中,事件组可以用来实现任务间的事件通知。下面是关于如何应用事件组的详细解析和示例:
事件组的创建和应用
1. 创建事件组
使用 xEventGroupCreate
函数创建一个事件组。这个函数返回一个句柄,用于后续的事件组操作。
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
// 创建事件组句柄
EventGroupHandle_t xEventGroup;
void app_main() {
// 创建事件组
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
// 处理创建失败的情况
printf("Event Group creation failed\n");
} else {
printf("Event Group created successfully\n");
}
}
2. 设置和清除事件标志
使用 xEventGroupSetBits
和 xEventGroupClearBits
函数设置或清除事件组中的某些事件标志位。
// 任务1:设置事件标志位
void vTask1(void *pvParameters) {
// 设置事件标志位0x01
xEventGroupSetBits(xEventGroup, BIT0);
vTaskDelete(NULL);
}
// 任务2:清除事件标志位
void vTask2(void *pvParameters) {
// 清除事件标志位0x01
xEventGroupClearBits(xEventGroup, BIT0);
vTaskDelete(NULL);
}
3. 等待事件标志
使用 xEventGroupWaitBits
函数等待特定的事件标志位被设置。可以指定等待条件和超时时间。
#define BIT0 (1 << 0)
#define BIT1 (1 << 1)
void vTask3(void *pvParameters) {
// 等待事件标志位BIT0或BIT1
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
BIT0 | BIT1, // 等待的事件位
pdTRUE, // 清除已设置的事件位
pdFALSE, // 等待任一事件位
portMAX_DELAY // 无限等待
);
if (uxBits & BIT0) {
// 处理事件BIT0被设置
printf("Event BIT0 was set\n");
}
if (uxBits & BIT1) {
// 处理事件BIT1被设置
printf("Event BIT1 was set\n");
}
vTaskDelete(NULL);
}
综合示例
下面是一个完整的示例,演示如何在 ESP32 的 FreeRTOS 中创建和使用事件组。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// 定义事件标志位
#define BIT0 (1 << 0)
#define BIT1 (1 << 1)
// 创建事件组句柄
EventGroupHandle_t xEventGroup;
// 任务1:设置事件标志位BIT0
void vTask1(void *pvParameters) {
// 设置事件标志位0x01
xEventGroupSetBits(xEventGroup, BIT0);
vTaskDelete(NULL);
}
// 任务2:设置事件标志位BIT1
void vTask2(void *pvParameters) {
// 设置事件标志位0x02
xEventGroupSetBits(xEventGroup, BIT1);
vTaskDelete(NULL);
}
// 任务3:等待事件标志位BIT0或BIT1
void vTask3(void *pvParameters) {
// 等待事件标志位BIT0或BIT1
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
BIT0 | BIT1, // 等待的事件位
pdTRUE, // 清除已设置的事件位
pdFALSE, // 等待任一事件位
portMAX_DELAY // 无限等待
);
if (uxBits & BIT0) {
// 处理事件BIT0被设置
printf("Event BIT0 was set\n");
}
if (uxBits & BIT1) {
// 处理事件BIT1被设置
printf("Event BIT1 was set\n");
}
vTaskDelete(NULL);
}
void app_main() {
// 创建事件组
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
// 处理创建失败的情况
printf("Event Group creation failed\n");
return;
}
// 创建任务
xTaskCreate(vTask1, "Task 1", 2048, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 2048, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 2048, NULL, 1, NULL);
}
关键点总结
- 事件组创建:使用
xEventGroupCreate
创建事件组。 - 设置事件标志:使用
xEventGroupSetBits
设置特定位。 - 清除事件标志:使用
xEventGroupClearBits
清除特定位。 - 等待事件标志:使用
xEventGroupWaitBits
等待特定位被设置,支持多种等待条件。
事件组在任务同步和事件通知方面非常有用,通过灵活设置和等待不同的事件标志,可以实现复杂的任务间协作和通信。
总结
我说一下我对esp32-freeRTOS 事件组的理解,就是我们如果想要实现任务间的同步,比如1个任务完成了,通知多个任务,就会用到事件组,具体操作就是:1、创建事件组 2、设置,清除某些事件组中的bit, 3、在任务重等待实践组被设置或者清除,然后通知该事件,执行相应的回调函数。
ESP32-FreeRTOS中的事件组确实是用来实现任务间同步的一种机制,特别适用于一个任务完成后通知多个任务的情况。以下是对您理解的具体步骤的详细解释和确认:
1. 创建事件组
这是事件组应用的第一步,使用 xEventGroupCreate
函数来创建一个事件组。
2. 设置和清除事件组中的 bit
任务可以使用 xEventGroupSetBits
来设置事件组中的特定位,表示某个事件发生。同样,可以使用 xEventGroupClearBits
来清除特定位,表示某个事件结束或状态恢复。
3. 等待事件组中的 bit 被设置或清除
任务可以使用 xEventGroupWaitBits
函数来等待特定位被设置或清除。等待条件满足后,任务会继续执行,通常会在这个点执行相应的回调函数或处理逻辑。
示例总结
您描述的步骤可以通过以下示例代码进一步说明:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
// 定义事件标志位
#define BIT0 (1 << 0)
#define BIT1 (1 << 1)
// 创建事件组句柄
EventGroupHandle_t xEventGroup;
// 任务1:设置事件标志位BIT0
void vTask1(void *pvParameters) {
// 模拟任务1完成了一些工作
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟延迟
// 设置事件标志位0x01
xEventGroupSetBits(xEventGroup, BIT0);
vTaskDelete(NULL);
}
// 任务2:等待事件标志位BIT0
void vTask2(void *pvParameters) {
// 等待事件标志位BIT0
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
BIT0, // 等待的事件位
pdTRUE, // 清除已设置的事件位
pdFALSE, // 等待任一事件位
portMAX_DELAY // 无限等待
);
if (uxBits & BIT0) {
// 处理事件BIT0被设置
printf("Task 2: Event BIT0 was set\n");
}
vTaskDelete(NULL);
}
// 任务3:等待事件标志位BIT0
void vTask3(void *pvParameters) {
// 等待事件标志位BIT0
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
BIT0, // 等待的事件位
pdTRUE, // 清除已设置的事件位
pdFALSE, // 等待任一事件位
portMAX_DELAY // 无限等待
);
if (uxBits & BIT0) {
// 处理事件BIT0被设置
printf("Task 3: Event BIT0 was set\n");
}
vTaskDelete(NULL);
}
void app_main() {
// 创建事件组
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
// 处理创建失败的情况
printf("Event Group creation failed\n");
return;
}
// 创建任务
xTaskCreate(vTask1, "Task 1", 2048, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 2048, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 2048, NULL, 1, NULL);
}
关键点总结
- 创建事件组:用
xEventGroupCreate
创建一个事件组。 - 设置事件标志:任务完成某些操作后,用
xEventGroupSetBits
设置事件组中的特定位。 - 等待事件标志:其他任务用
xEventGroupWaitBits
等待特定位被设置,接收到事件后执行相应的操作。 - 清除事件标志:可以通过等待函数中的参数或显式调用
xEventGroupClearBits
来清除特定位。
您对ESP32-FreeRTOS事件组的理解非常准确,以上示例代码进一步说明了事件组的使用方法。通过这些步骤,可以有效实现任务间的同步和事件通知。
互斥量
城哥,xSemaphoreCreateMutex
是 FreeRTOS 中用于创建互斥量(Mutex)的函数,互斥量是一种用于任务间同步和资源保护的工具。下面是对 xSemaphoreCreateMutex
及其在 ESP32-FreeRTOS 中应用的详细解析:
函数原型
SemaphoreHandle_t xSemaphoreCreateMutex( void );
功能描述
- 创建互斥量:
xSemaphoreCreateMutex
用于创建一个互斥量。互斥量是一种特殊的二值信号量,设计用于保护共享资源,以确保同一时刻只有一个任务能够访问该资源。 - 返回值:该函数返回一个
SemaphoreHandle_t
类型的句柄,如果创建成功,返回互斥量的句柄;如果创建失败,返回 NULL。
互斥量的特点
- 任务间独占访问:互斥量确保同一时间只有一个任务能够“持有”互斥量,从而独占访问共享资源。
- 优先级继承机制:FreeRTOS 的互斥量实现了优先级继承机制,可以防止优先级反转问题。在一个低优先级任务持有互斥量而高优先级任务等待该互斥量时,低优先级任务会临时提升到高优先级,以减少高优先级任务的等待时间。
使用步骤
1. 创建互斥量
SemaphoreHandle_t xMutex;
void app_main() {
xMutex = xSemaphoreCreateMutex();
if (xMutex == NULL) {
// 处理创建失败的情况
printf("Mutex creation failed\n");
} else {
printf("Mutex created successfully\n");
}
}
2. 获取互斥量
任务在访问共享资源前需要先获取互斥量,可以使用 xSemaphoreTake
函数。
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 成功获取互斥量,安全访问共享资源
printf("Mutex acquired\n");
// ... 访问共享资源
}
3. 释放互斥量
任务在访问完共享资源后需要释放互斥量,可以使用 xSemaphoreGive
函数。
// 访问完共享资源,释放互斥量
xSemaphoreGive(xMutex);
printf("Mutex released\n");
综合示例
下面是一个完整的示例,演示如何在 ESP32 的 FreeRTOS 中创建和使用互斥量来保护共享资源。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 定义互斥量句柄
SemaphoreHandle_t xMutex;
// 共享资源
int sharedResource = 0;
// 任务1:访问共享资源
void vTask1(void *pvParameters) {
while (1) {
// 获取互斥量
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 访问共享资源
sharedResource++;
printf("Task 1: Shared resource value: %d\n", sharedResource);
// 释放互斥量
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟任务周期
}
}
// 任务2:访问共享资源
void vTask2(void *pvParameters) {
while (1) {
// 获取互斥量
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 访问共享资源
sharedResource++;
printf("Task 2: Shared resource value: %d\n", sharedResource);
// 释放互斥量
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟任务周期
}
}
void app_main() {
// 创建互斥量
xMutex = xSemaphoreCreateMutex();
if (xMutex == NULL) {
// 处理创建失败的情况
printf("Mutex creation failed\n");
return;
}
// 创建任务
xTaskCreate(vTask1, "Task 1", 2048, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 2048, NULL, 1, NULL);
}
关键点总结
- 互斥量创建:使用
xSemaphoreCreateMutex
创建一个互斥量,确保共享资源的独占访问。 - 获取互斥量:任务在访问共享资源前使用
xSemaphoreTake
获取互斥量,成功获取后才能安全访问资源。 - 释放互斥量:任务在访问完共享资源后使用
xSemaphoreGive
释放互斥量,使其他任务能够访问资源。 - 优先级继承:互斥量具备优先级继承机制,防止优先级反转,提高系统的实时性。
通过这些步骤,您可以在 ESP32-FreeRTOS 中有效地使用互斥量来保护共享资源,确保任务间的同步和资源的独占访问。
队列
城哥,以下是对 xQueueSendFromISR
函数的详细解析和应用示例。这个函数是FreeRTOS中的一个重要函数,用于在中断服务例程(ISR)中向队列发送数据。
函数定义
BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
参数解析
-
xQueue:
- 队列句柄,指定要向其中发送数据的队列。
-
pvItemToQueue:
- 指向要发送到队列中的数据的指针。数据的大小应与创建队列时指定的项目大小相同。
-
pxHigherPriorityTaskWoken:
- 一个指向BaseType_t类型变量的指针。如果在中断退出时需要执行一个优先级更高的任务,这个变量将被设置为
pdTRUE
。
- 一个指向BaseType_t类型变量的指针。如果在中断退出时需要执行一个优先级更高的任务,这个变量将被设置为
返回值
-
pdPASS:
- 如果数据成功发送到队列,返回
pdPASS
。
- 如果数据成功发送到队列,返回
-
errQUEUE_FULL:
- 如果队列已满,数据无法发送时,返回
errQUEUE_FULL
。
- 如果队列已满,数据无法发送时,返回
示例代码
以下是一个应用示例,展示如何在中断服务例程中使用 xQueueSendFromISR
函数。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#define BTN_PIN GPIO_NUM_0 // 按键引脚
static QueueHandle_t key_evt_queue;
// 中断服务例程
static void IRAM_ATTR btn_isr_handler(void *arg)
{
uint32_t gpio_num = (uint32_t) arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 将GPIO引脚号发送到队列
xQueueSendFromISR(key_evt_queue, &gpio_num, &xHigherPriorityTaskWoken);
// 如果从ISR中唤醒了一个优先级更高的任务,进行任务切换
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// 处理按键事件的任务
static void key_read_task(void *arg)
{
uint32_t io_num;
while (1) {
// 等待队列中的按键事件
if (xQueueReceive(key_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}
// 初始化按键和队列
void btn_init()
{
gpio_config_t io_conf;
// 配置GPIO中断
io_conf.intr_type = GPIO_INTR_NEGEDGE; // 下降沿触发中断
io_conf.pin_bit_mask = (1ULL << BTN_PIN);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = 1;
gpio_config(&io_conf);
// 创建队列
key_evt_queue = xQueueCreate(10, sizeof(uint32_t));
// 安装GPIO中断服务
gpio_install_isr_service(0);
gpio_isr_handler_add(BTN_PIN, btn_isr_handler, (void*) BTN_PIN);
// 创建按键事件处理任务
xTaskCreate(key_read_task, "key_read_task", 2048, NULL, 10, NULL);
}
void app_main()
{
btn_init();
}
解析
-
中断服务例程(ISR):
btn_isr_handler
是一个GPIO中断处理函数,当按键引脚发生中断时触发。- 使用
xQueueSendFromISR
将中断事件(按键引脚号)发送到队列key_evt_queue
。 portYIELD_FROM_ISR
用于在需要时进行任务切换。
-
按键事件处理任务:
key_read_task
是一个FreeRTOS任务,从队列中接收按键事件并处理。- 使用
xQueueReceive
从队列中读取数据(按键引脚号),并打印中断引脚及其电平值。
-
按键和队列初始化:
btn_init
函数配置按键引脚,创建队列,安装GPIO中断服务,并创建按键事件处理任务。
结论
xQueueSendFromISR
函数允许在中断服务例程中向队列发送数据,从而在中断和任务之间传递信息。- 通过使用
xQueueSendFromISR
可以实现高效的中断处理,确保中断处理程序快速返回,并将更多的处理工作交给任务来完成。 - 需要特别注意在ISR中使用合适的FreeRTOS函数,以避免违反实时操作系统的限制和规则。
如果有更多的问题或需要进一步的解释,请告诉我。