11. FreeRTOS的事件标志组
一、FreeRTOS的事件标志简介
事件标志 是一个用于指示事件是否发生的比特位,因为一个事件是否发生只有两种情况,分别为事件发生和事件未发生,因此只需一个比特位就能够表示事件是否发生(1 表示事件发生,用 0 表示事件未发生)。FreeRTOS 将多个事件标志储存在一个变量类型为 EventBits_t 变量中,这个变量就是 事件组。
#define configUSE_16_BIT_TICKS 0 // 1: 定义系统时钟节拍计数器的数据类型为16位无符号数,无默认需定义
#ifndef configTICK_TYPE_WIDTH_IN_BITS
#if ( configUSE_16_BIT_TICKS == 1 )
#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_16_BITS
#else
#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS
#endif
#endif
#if ( configTICK_TYPE_WIDTH_IN_BITS == TICK_TYPE_WIDTH_16_BITS )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#elif ( configTICK_TYPE_WIDTH_IN_BITS == TICK_TYPE_WIDTH_32_BITS )
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
typedef TickType_t EventBits_t;
从上面可以看出,EventBits_t 实际上是一个 16 位或 32 位无符号的数据类型。当 configUSE_16_BIT_TICKS
配置为 0 时,EventBits_t 是一个 32 位无符号的数据类型;当 configUSE_16_BIT_TICKS
配置为 1 时,EventBits_t 是一个 16 为无符号的数据类型。
虽然说使用了 32 位无符号的数据类型变量来存储事件标志,但这并不意味着,一个 EventBits_t 数据类型的变量能够存储 32 个事件标志,FreeRTOS 将这个 EventBits_t 数据类型的变量拆分成两部分,其中低 24 位 [23:0](configUSE_16_BIT_TICKS 配置位 1 时,是低 8 位 [7:0])用于存储事件标志,而高 8 位 [31:24](configUSE_16_BIT_TICKS 配置位 1 时,依然是高 8 位 [15:8])用作存储事件标志组的一些控制信息,也就是说一个事件组最多可以存储 24 个事件标志。EventBits_t 数据类型变量的位使用情况如下图所示:
从上图中可以看到,变量中低 24 位中的每一位都是一个事件标志,当某一位被置一时,就表示这一位对应的事件发生了。
队列、信号量当事件发生时,只会唤醒一个任务,并且队列、信号量是消耗型的资源,队列的数据被读走就没了,信号量被获取后就减少了。
事件标志组当事件发生时,会唤醒所有符合条件的任务,可以理解为“广播”的作用,并且被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件。
二、事件标志相关API函数
2.1、创建事件标志组
FreeRTOS 提供了两种创建事件标志组的方式,分别为动态方式创建事件标志组和静态方式创建事件标志组,两者的区别在于静态方式创建事件标志组时,需要用户提供创建事件标志组所需的内存空间,而使用动态方式创建事件标志组时,FreeRTOS 会自动从 FreeRTOS 管理的堆中分配创建事件标志组所需的内存空间。
2.1.1、动态方式创建事件标志组
动态方式创建事件标志组 API 函数的函数原型如下所示:
// 返回值: NULL: 事件标志组创建失败; 其它值: 事件标志组创建成功,返回其句柄
EventGroupHandle_t xEventGroupCreate( void );
2.1.2、静态方式创建事件标志组
静态方式创建事件标志组 API 函数的函数原型如下所示:
// 返回值: NULL: 事件标志组创建失败; 其它值: 事件标志组创建成功,返回其句柄
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer ); // pxEventGroupBuffer位创建事件标志组所需的内存空间
2.2、删除事件标志组
FreeRTOS 提供了用于删除事件标志组的 API 函数,函数原型如下所示:
void vEventGroupDelete( EventGroupHandle_t xEventGroup ); // xEventGroup为待删除的事件标志组句柄
2.3、等待事件标志位
等待事件标志位使用的是函数 xEventGroupWaitBits()
,其函数原型如下所示:
// 返回值: 等待的事件标志位值: 等待事件标志位成功,返回等待到的事件标志位; 其它值: 等待事件标志位失败,返回事件组中的事件标志位;
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, // 等待的事件标志组
const EventBits_t uxBitsToWaitFor, // 等待的事件标志位,可以用逻辑或等待多个事件标志位
const BaseType_t xClearOnExit, // 成功等待到事件标志位后,清除事件组中对应的事件标志位,pdTRUE: 清除uxBitsToWaitFor指定位; pdFALSE: 不清除;
const BaseType_t xWaitForAllBits, // 等待 uxBitsToWaitFor 中的所有事件标志位(逻辑与),pdTRUE:等待的位,全部为1; pdFALSE: 等待的位,某个为1;
TickType_t xTicksToWait ); // 获取等待的阻塞时间
2.4、设置事件标志位
FreeRTOS 提供了两个用于设置事件标志位的 API 函数,这个两个函数分别用于在任务和在中断中设置事件标志位。
2.4.1、在任务中设置事件标志位
在任务中设置事件标志位 API 函数的函数原型如下所示:
// 返回值:任务获取到的事件标志
OS_FLAGS OSFlagPendGetFlagsRdy (OS_ERR *p_err); // p_err 指向接收错误代码变量的指针
2.4.2、在中断中设置事件标志位
在中断中设置事件标志位 API 函数的函数原型如下所示:
// 返回值:pdPASS: 事件标志位设置成功; pdFAIL: 事件标志位设置失败;
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, // 待操作的事件标志组
const EventBits_t uxBitsToSet, // 带设置的事件标志位
BaseType_t * pxHigherPriorityTaskWoken ); // 用于标记函数退出后是否需要进行任务切换
2.5、清零设置标志位
FreeRTOS 提供了两个用于清零事件标志位的 API 函数,这个两个函数分别用于在任务和在中断中清零事件标志位。
2.5.1、在任务中清零事件标志位
在任务中清零事件标志位 API 函数的函数原型如下所示:
// 返回值: 整数: 清零事件标志位之前事件组中事件标志位的值
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, // 待操作的事件标志组
const EventBits_t uxBitsToClear ); // 待清零的事件标志位
2.5.2、在中断中清零事件标志位
在中断中清零事件标志位 API 函数的函数原型如下所示:
// 返回值: pdPASS: 事件标志位清零成功; pdFAIL: 事件标志位清零失败
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, // 待操作的事件标志组
const EventBits_t uxBitsToClear ); // 带清零的事件标志位
2.6、获取事件组中各事件标志位的值
FreeRTOS 提供了两个用于获取事件组中事件标志位值的 API 函数,这个两个函数分别用于在任务和在中断中获取事件组中事件标志位的值。
2.6.1、在任务中获取事件组中各事件标志位的值
在任务中获取事件组中事件标志位值 API 函数的函数原型如下所示:
// xEventGroup为待获取事件标志位值的事件组
// 返回一个整数代表事件组的事件标志位的值
#define xEventGroupGetBits( xEventGroup ) xEventGroupClearBits( ( xEventGroup ), 0 )
2.6.2、在中断中获取事件组中各事件标志位的值
在中断中获取事件标志位 API 函数的函数原型如下所示:
// 返回值: 整数: 事件组的事件标志位的值
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup ); // xEventGroup为待获取事件标志位值的事件组
2.7、设置并等待事件标志位
此函数一般用于多任务同步,其中每个任务都必须等待其他任务达到同步点,然后才能继续执行。函数 xEventGroupSync()
的函数原型如下所示:
// 返回值: 等待的事件标志位值: 等待事件标志位成功,返回等待到的事件标志位; 其他值: 等待事件标志位失败,返回事件组中的事件标志位
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, // 等待事件标志所在事件组
const EventBits_t uxBitsToSet, // 达到同步点后,要设置的事件标志
const EventBits_t uxBitsToWaitFor, // 等待的事件标志
TickType_t xTicksToWait ); // 等待的阻塞时间
三、实验例程
main() 函数内容如下:
int main(void)
{
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
UART_Init(&g_usart1_handle, USART1, 115200);
Key_Init();
freertos_demo();
return 0;
}
FreeRTOS 例程入口函数:
/**
* @brief FreeRTOS的入口函数
*
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t ) start_task, // 任务函数
(char * ) "start_task", // 任务名
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE, // 任务栈大小
(void * ) NULL, // 入口参数
(UBaseType_t ) START_TASK_PRIORITY, // 任务优先级
(TaskHandle_t * ) start_task_handle); // 任务句柄
vTaskStartScheduler(); // 开启任务调度器
}
START_TASK 任务配置:
EventGroupHandle_t event_group_handle;
/**
* START_TASK 任务配置
* 包括: 任务优先级 任务栈大小 任务句柄 任务函数
*/
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handle;
void start_task(void *pvParameters );
/**
* @brief 开始任务的任务函数
*
* @param pvParameters 任务函数的入口参数
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); // 进入临界区,关闭中断
event_group_handle = xEventGroupCreate(); // 创建事件标志组
if (event_group_handle != NULL)
{
printf("事件标志组创建成功\r\n");
}
xTaskCreate((TaskFunction_t ) task1, // 任务函数
(char * ) "task1", // 任务名
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE, // 任务栈大小
(void * ) NULL, // 入口参数
(UBaseType_t ) TASK1_PRIORITY, // 任务优先级
(TaskHandle_t * ) &task1_handle); // 任务句柄
xTaskCreate((TaskFunction_t ) task2, // 任务函数
(char * ) "task2", // 任务名
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE, // 任务栈大小
(void * ) NULL, // 入口参数
(UBaseType_t ) TASK2_PRIORITY, // 任务优先级
(TaskHandle_t * ) &task2_handle); // 任务句柄
vTaskDelete(NULL); // 删除任务自身
taskEXIT_CRITICAL(); // 退出临界区,重新开启中断
}
TASK1 任务配置:
/**
* TASK1 任务配置
* 包括: 任务优先级 任务栈大小 任务句柄 任务函数
*/
#define TASK1_PRIORITY 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handle;
void task1(void *pvParameters);
/**
* @brief 任务1的任务函数
*
* @param pvParameters 任务函数的入口参数
*/
void task1(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = Key_Scan(0);
if (key == KEY1_PRESS || key == KEY2_PRESS)
{
// UK_UP置位第0位,KEY1置位第1位,KEY2置位第2位,KEY3置位第3位,
xEventGroupSetBits(event_group_handle, 0x01 << (key - 1));
printf("事件%d置位\r\n", key - 1);
}
vTaskDelay(10);
}
}
TASK2 任务配置:
#define EVENT_BIT1 (0x01 << 1)
#define EVENT_BIT2 (0x01 << 2)
/**
* TASK2 任务配置
* 包括: 任务优先级 任务栈大小 任务句柄 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIORITY 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handle;
void task2(void *pvParameters);
/**
* @brief 任务2的任务函数
*
* @param pvParameters 任务函数的入口参数
*/
void task2(void *pvParameters )
{
EventBits_t event_bit = 0;
while (1)
{
event_bit = xEventGroupWaitBits(event_group_handle, // 事件标志组句柄
EVENT_BIT1 | EVENT_BIT2, // 需要等待的标志位
pdTRUE, // 是否清除标志位
pdTRUE, // 是否等待所有标志位
portMAX_DELAY); // 等待时间
printf("等待的事件标志位值为: %#lx\r\n", event_bit);
}
}