FreeRTOS:事件标志组
FreeRTOS中的事件标志组(Event Groups)是一种同步机制,用于多个任务间的事件通信和同步。它允许任务等待多个事件的发生,事件标志组中的每个位代表一个独立的事件。下面让我们共同学习事件标志组。事件标志组
1.事件标志位
用一个位,来表示事件是否发生,比如之前定义的标志位全局变量 uint8_t flag,一共8位。值为1代表事件发生,值为0代表事件未发生。
2.事件标志组
事件标志组是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数。
3.事件标志组的特点
1.它的每一个位表示一个事件(高8位不算)每一位事件的含义,由用户自己决定,如:bit0表示按键是否按下,bit1表示是否接受到消息 。
2.这些位的值为1:表示事件发生了;值为0:表示事件未发生。
3.任意任务或中断都可以读写这些位。
4.每个任务可以等待某一位成立,或者等待多位同时成立再作出响应(也就是可以等待一个事件发生或者等待多个事件发生)。
一个事件组就包含了一个 EventBits_t 数据类型的变量,变量类型 EventBits_t 的定义如下所示:
EventBits_t 实际上是一个16 位或 32 位无符号的数据类型,在STM32中,它是一个32位的无符号整数数据类型!
虽然FreeRTOS使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!
事件标志组与队列、信号量的区别
1. 事件标志组(Event Groups)
特点和用途:
多事件同步:事件标志组由一组标志(bits)组成,每个标志表示一个事件。任务可以等待多个事件标志的设置,适用于需要等待多个事件发生的场景。
事件组合:任务可以等待一个或多个事件的组合(任意一个或所有),这在复杂的同步场景中非常有用。
设置和清除:事件标志可以被设置(置位)或清除(复位),并且可以选择在任务等待时自动清除已满足的标志。
示例用途:
任务需要等待多个条件满足才能继续执行。
系统状态监控,需要等待某些状态改变后执行相应操作。
2. 队列(Queues)
特点和用途:
1.消息传递:队列用于在任务间传递数据,可以是简单的变量或者复杂的数据结构。任务可以将数据放入队列中,或者从队列中读取数据。
2.先进先出(FIFO):队列遵循先进先出原则,确保数据按发送顺序被读取。
3.线程安全:队列操作是线程安全的,适用于多任务同时访问的场景。
示例用途:
任务之间传递传感器数据、消息或命令。
实现生产者-消费者模型,一个任务生成数据并放入队列,另一个任务从队列读取数据进行处理。
3. 信号量(Semaphores)
特点和用途:
1.二元信号量(Binary Semaphores):用于简单的任务间同步,类似于事件标志,但只能表示单个事件。适合于通知一个任务某个事件发生。
2.计数信号量(Counting Semaphores):用于资源管理,可以用于限制对共享资源的并发访问次数。
3.互斥信号量(Mutexes):一种特殊的二元信号量,用于实现任务间的互斥访问。它具有优先级继承特性,防止优先级反转问题。
示例用途:
二元信号量用于任务间简单的事件通知,比如ISR(中断服务程序)通知任务某个中断发生。
计数信号量用于限制资源访问次数,比如限制同时访问某个共享资源的任务数量。
互斥信号量用于保护临界区,确保任务在访问共享资源时不被其他任务中断。
4. 比较和选择
复杂同步:事件标志组适用于需要复杂事件组合和多事件同步的场景。
数据传递:队列适用于任务间需要传递数据的场景,特别是涉及到顺序处理的数据流。
简单同步和资源管理:信号量适用于简单的任务间同步和资源访问控制。二元信号量用于简单事件通知,计数信号量用于资源管理,互斥信号量用于保护共享资源的访问。
事件标志组相关函数
1. 创建事件标志组
FreeRTOS 提供了两种创建事件标志组的方式,分别为动态方式创建事件标志组和静态方
式创建事件标志组,两者的区别在于静态方式创建事件标志组时,需要用户提供创建事件标志
组所需的内存空间,而使用动态方式创建事件标志组时,FreeRTOS 会自动从 FreeRTOS 管理的堆中分配创建事件标志组所需的内存空间。
动态方式创建事件标志组
EventGroupHandle_t xEventGroupCreate(void);
返回值
NULL:事件标志组创建失败
其他值:事件标志组创建成功,返回其句柄
静态方式创建事件标志组
EventGroupHandle_t xEventGroupCreateStatic(
StaticEventGroup_t * pxEventGroupBuffer);
pxEventGroupBuffer:创建事件标志组所需的内存空间
返回值
NULL:事件标志组创建失败
其他值:事件标志组创建成功,返回其句柄
2. 删除事件标志组
void vEventGroupDelete(EventGroupHandle_t xEventGroup);
xEventGroup:待删除的事件标志组句柄
3. 等待事件标志位
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait)
xEvenrGroup:等待的事件标志组
uxBitsToWaitFor:等待的事件标志位,可以用逻辑或等待多个事件标志位
xClearOnExit:成功等待到事件标志位后,清除事件组中对应的事件标志位
xWaitForAllBits:等待 uxBitsToWaitFor 中的所有事件标志位(逻辑与)
xTicksToWait:获取等待的阻塞时间
返回值
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
其他值:等待事件标志位失败,返回事件组中的事件标志位
4. 设置事件标志位
FreeRTOS 提供了两个用于设置事件标志位的 API 函数,这个两个函数分别用于在任务和
在中断中设置事件标志位。
在任务中设置事件标志位
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet)
xEventGroup:待操作的事件标志组
uxBitsToSet:待设置的事件标志位
返回值
整数:函数返回时,事件组中的事件标志位值
在中断中设置事件标志位
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken)
xEventGroup:待操作的事件标志组
uxBitsToSet:带设置的事件标志位
pxHigherPriorityTaskWoken:用于标记函数退出后是否需要进行任务切换
返回值
pdPASS:事件标志位设置成功
pdFAIL:事件标志位设置失败
5. 清零事件标志位
FreeRTOS 提供了两个用于清零事件标志位的 API 函数,这个两个函数分别用于在任务和
在中断中清零事件标志位。
在任务中清零事件标志位
EventBits_t xEventGroupClearBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear)
xEventGroup:待操作的事件标志组
uxBitsToSet:待清零的事件标志位
返回值
整数:清零事件标志位之前事件组中事件标志位的值
在中断中清零事件标志位
BaseType_t xEventGroupClearBitsFromISR(
EventGroupHandle_t EventGroup,
const EventBits_t uxBitsToClear)
xEventGroup:待操作的事件标志组
uxBitsToSet:带清零的事件标志位
返回值
pdPASS:事件标志位清零成功
pdFAIL:事件标志位清零失败
6. 函数 xEventGroupSync()
此函数一般用于多任务同步,其中每个任务都必须等待其他任务达到同步点,然后才能继续执行。
EventBits_t xEventGroupSync(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait)
xEventGroup:等待事件标志所在事件组
uxBitsToSet:达到同步点后,要设置的事件标志
uxBitsToWaitFor:等待的事件标志
xTicksToWait:等待的阻塞时间
返回值
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
其他值:等待事件标志位失败,返回事件组中的事件标志位
事件标志组实验
#include "stm32f4xx.h" // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"
#include "mydelay.h"
#include "mykey.h"
#include "semphr.h"
#include "event_groups.h"
/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define START_TASK_STACK_SIZE128 //定义堆栈大小为128字(1字等于4字节)
#define START_TASK_PRIO 1 //定义任务优先级,0-31根据任务需求
TaskHandle_t start_task_handler; //定义任务句柄(结构体指针)
void start_task(void* args);
/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#defineTASK1_STACK_SIZE128 //定义堆栈大小为128字(1字等于4字节)
#defineTASK1_PRIO 2 //定义任务优先级,0-31根据任务需求
TaskHandle_t task1_handler; //定义任务句柄(结构体指针)
void task1(void* args);
/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#defineTASK2_STACK_SIZE128 //定义堆栈大小为128字(1字等于4字节)
#defineTASK2_PRIO 3 //定义任务优先级,0-31根据任务需求
TaskHandle_t task2_handler; //定义任务句柄(结构体指针)
void task2(void* args);
EventGroupHandle_t eventgroup_handle; //事件标志组句柄
#define EVENTBIT_0 (1<<0) //设置的事件标志位
#define EVENTBIT_1 (1<<1) //设置的事件标志位
/*********开始任务用来创建一个任务,只创建一次,不能是死循环,创建完这个任务后删除开始任务本身***********/
void start_task(void* args)
{
taskENTER_CRITICAL(); /*进入临界区*/
eventgroup_handle = xEventGroupCreate(); //创建事件标志组
if(eventgroup_handle!=NULL)
{
printf("事件标志组创建成功!\n");
}
xTaskCreate( (TaskFunction_t) task1,
(char *) "task1",
( configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void *) NULL,
(UBaseType_t) TASK1_PRIO ,
(TaskHandle_t *)&task1_handler );
xTaskCreate( (TaskFunction_t) task2,
(char *) "task2",
( configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void *) NULL,
(UBaseType_t) TASK2_PRIO ,
(TaskHandle_t *)&task2_handler );
vTaskDelete(NULL); //删除开始任务自身,传参NULL
taskEXIT_CRITICAL(); /*退出临界区*/
//临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式
}
int main(void)
{
//硬件初始化
My_UsartInit();
//调用入口函数
/***开始任务的创建***/
xTaskCreate( (TaskFunction_t) start_task,
(char *) "start_task",
( configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void *) NULL,
(UBaseType_t) START_TASK_PRIO ,
(TaskHandle_t *)&start_task_handler );
vTaskStartScheduler();//开启任务调度器
}
/********任务1的任务函数,无返回值且是死循环***********/
/*****任务1:模拟事件发生,按下按键,相应位置1*******/
void task1(void* args)
{
uint8_t key = 0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
xEventGroupSetBits( eventgroup_handle,EVENTBIT_0); //将事件标志组bit0置1,或者采0X01
}
else if(key==KEY1_PRES)
{
xEventGroupSetBits( eventgroup_handle,EVENTBIT_1); //将事件标志组bit1置1,或者采0X02
}
vTaskDelay(10); //FreeRTOS自带的延时函数,延时10毫秒
}
}
/********任务2的任务函数,无返回值且是死循环***********/
/***任务2:*******/
void task2(void* args)
{
EventBits_t event_bit = 0;
while(1)
{
event_bit = xEventGroupWaitBits(eventgroup_handle, //事件标志组句柄
EVENTBIT_0|EVENTBIT_1,//等待事件标志组的bit0和bit1位
pdTRUE, //成功等待到事件标志位后,清除事件标志组中的bit0和bit1位
pdTRUE, //等待事件标志组bit0和bit1位都置1,就成立
portMAX_DELAY); //一直阻塞等待
printf("等到的事件标志位值为:%#x\n",event_bit); //结果应该为:0x03
}
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_55389449/article/details/147575200
页:
[1]