事件标志组简介(了解)基本概念
当在嵌入式系统中运行多个任务时,这些任务可能需要相互通信,协调其操作。FreeRTOS中的事件标志组(Event Flags Group)提供了一种轻量级的机制,用于在任务之间传递信息和同步操作。
事件标志组就像是一个共享的标志牌集合,每个标志位都代表一种特定的状态或事件。任务可以等待或设置这些标志位,从而实现任务之间的协同工作。
事件位(事件标志)
事件位用于指示事件是否发生。 事件位通常称为事件标志。例如,应用程序可以:
- 定义一个位(或标志), 设置为 1 时表示“已收到消息并准备好处理”, 设置为 0 时表示“没有消息等待处理”。
- 定义一个位(或标志), 设置为 1 时表示“应用程序已将准备发送到网络的消息排队”, 设置为 0 时表示 “没有消息需要排队准备发送到网络”。
- 定义一个位(或标志), 设置为 1 时表示“需要向网络发送心跳消息”, 设置为 0 时表示“不需要向网络发送心跳消息”。
事件组
事件组就是一组事件位。 事件组中的事件位通过位编号来引用。 同样,以上面列出的三个例子为例:
- 事件标志组位编号为 0 表示“已收到消息并准备好处理”。
- 事件标志组位编号为 1 表示“应用程序已将准备发送到网络的消息排队”。
- 事件标志组位编号为 2 表示“需要向网络发送心跳消息”。
事件组和事件位数据类型
事件组由 EventGroupHandle_t 类型的变量引用。
在事件组中实现的位数(或标志数)取决于是使用 configUSE_16_BIT_TICKS 还是 configTICK_TYPE_WIDTH_IN_BITS 来控制 TickType_t 的类型:
- 如果 configUSE_16_BIT_TICKS 设置为 1,则事件组内实现的位数(或标志数)为 8; 如果 configUSE_16_BIT_TICKS 设置为 0,则为 24。
- 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_16_BITS,则事件组内实现的位数(或标志数)为 8。
- 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_32_BITS,则为 24 。
- 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_64_BITS,则为 56。
对configUSE_16_BIT_TICKS或configTICK_TYPE_WIDTH_IN_BITS 的依赖源于 RTOS 任务内部实现中用于线程本地存储的数据类型。我们当前的版本不支持configTICK_TYPE_WIDTH_IN_BITS配置,只有configUSE_16_BIT_TICKS配置。
事件组中的所有事件位都 存储在 EventBits_t 类型的单个无符号整数变量中。 事件位 0 存储在位 0 中,事件位 1 存储在位1 中,依此类推。
下图表示一个 24 位事件组,使用 3 个位来保存前面描述的 3 个示例事件。 在图片中,仅设置了事件位2。

事件标志组和信号量的区别
事件标志组(Event Flags Group)和信号量(Semaphore)都是FreeRTOS中用于任务同步和通信的机制,但它们在用途和行为上有一些关键的区别。
| 事件标志组 | 信号量 |
| 主要用于任务之间的事件通知和同步。每个标志位通常代表一个特定的状态或事件,任务可以等待某些标志的发生或者设置标志来通知其他任务。 | 用于任务之间的资源控制和同步。信号量通常用来保护共享资源,控制对共享资源的访问,以及在任务之间提供同步。 |
| 每个标志位通常代表一个不同的事件,每个标志位只有两个状态,即已设置或未设置。 | 信号量是一个计数器,可以具有大于1的值,表示可用的资源数量。信号量的计数可以动态增减,而且可以用于实现互斥、同步等场景。 |
| 适用于需要向其他任务通知事件发生或等待特定事件的场景,例如数据准备就绪、某个条件满足等。 | 适用于需要对共享资源进行控制,限制同时访问某个资源的任务数量,以及确保任务按顺序访问共享资源的场景。 |
| 任务可以等待多个特定的标志位同时发生,或者等待任意一个标志位发生。 | 任务等待信号量的发放,当信号量的计数大于零时,任务可以继续执行。 |
总体来说,事件标志组更侧重于任务间的事件通知和同步,而信号量更侧重于资源的控制和同步。在设计中,根据具体需求选择合适的机制会更有利于系统的设计和性能。
事件标志组相关API函数介绍(熟悉)
事件标志组相关函数:
| 函数 | 描述 |
| xEventGroupCreate() | 使用动态方式创建事件标志组 |
| xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
| xEventGroupClearBits() | 清零事件标志位 |
| xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
| xEventGroupSetBits() | 设置事件标志位 |
| xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
| xEventGroupWaitBits() | 等待事件标志位 |
| xEventGroupSync() | 设置事件标志位,并等待事件标志位 |
事件标志组实验(掌握)实验目标
学习使用 FreeRTOS 的事件标志组相关函数:
- start_task:用来创建其他2个任务,并创建事件标志组。
- task1:读取按键按下键值,根据不同键值将事件标志组相应事件位置一,模拟事件发生。
- task2:同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理。
freertos_demo.c代码清单
#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h" //事件标志组的头文件
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"
/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);
/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);
/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);
/**
* @description: 启动FreeRTOS
* @return {*}
*/
EventGroupHandle_t event_group_handle;
void freertos_start(void)
{
/* 创建事件标志组 */
event_group_handle = xEventGroupCreate();
if (event_group_handle != NULL)
{
printf("创建事件标志组成功...\r\n");
}
/* 1.创建一个启动任务 */
xTaskCreate((TaskFunction_t)start_task, // 任务函数的地址
(char *)"start_task", // 任务名字符串
(configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
(void *)NULL, // 传递给任务的参数
(UBaseType_t)START_TASK_PRIORITY, // 任务的优先级
(TaskHandle_t *)&start_task_handle); // 任务句柄的地址
/* 2.启动调度器:会自动创建空闲任务 */
vTaskStartScheduler();
}
/**
* @description: 启动任务:用来创建其他Task
* @param {void} *pvParameters
* @return {*}
*/
void start_task(void *pvParameters)
{
/* 进入临界区:保护临界区里的代码不会被打断 */
taskENTER_CRITICAL();
/* 创建3个任务 */
xTaskCreate((TaskFunction_t)task1,
(char *)"task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK,
(void *)NULL,
(UBaseType_t)TASK1_PRIORITY,
(TaskHandle_t *)&task1_handle);
xTaskCreate((TaskFunction_t)task2,
(char *)"task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK,
(void *)NULL,
(UBaseType_t)TASK2_PRIORITY,
(TaskHandle_t *)&task2_handle);
/* 启动任务只需要执行一次即可,用完就删除自己 */
vTaskDelete(NULL);
/* 退出临界区 */
taskEXIT_CRITICAL();
}
/**
* @description: 任务一:读取按键按下键值,根据不同键值将事件标志组相应事件位置一,模拟事件发生
* @param {void} *pvParameters
* @return {*}
*/
#define EVENTBIT_0 (1 << 0)
#define EVENTBIT_1 (1 << 1)
void task1(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
/* key1按下,对bit0置1 */
xEventGroupSetBits(event_group_handle, EVENTBIT_0);
printf("按下KEY1,对BIT0置1...\r\n");
}
else if (key == KEY2_PRESS)
{
/* key2按下,对bit1置1 */
xEventGroupSetBits(event_group_handle, EVENTBIT_1);
printf("按下KEY2,对BIT1置1...\r\n");
}
vTaskDelay(500);
}
}
/**
* @description: 任务二:同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理
* @param {void} *pvParameters
* @return {*}
*/
void task2(void *pvParameters)
{
EventBits_t event_bits = 0;
while (1)
{
event_bits = xEventGroupWaitBits(
event_group_handle, // 事件标志组的句柄
EVENTBIT_0 | EVENTBIT_1, // 要等待的标志位,可以写多个,用|拼接
pdTRUE, // 满足等待的条件后,是否将对应的bit位清零。pdTRUE要清零
pdTRUE, // 所有bit都是1,还是部分是1;pdTRUE要求所有都是1,pdFALSE其中有1就行
portMAX_DELAY);
printf("task2接收到的事件标志组= %#x.....\r\n",event_bits);
}
}

Comments NOTHING