FreeRTOS基础(十四):事件标志组

         FreeRTOS中的事件标志组(Event Groups)是一种同步机制,用于多个任务间的事件通信和同步。它允许任务等待多个事件的发生,事件标志组中的每个位代表一个独立的事件。下面让我们共同学习事件标志组。

目录

一、事件标志组简介

1.1 事件标志位

1.2 事件标志组

1.3 事件标志组的特点

1.4 事件标志组与队列、信号量的区别

二、事件标志组相关API函数介绍

2.1 动态方式创建事件标志组函数

2.2 清除事件标志位函数

2.3 设置事件标志位函数

2.4 等待事件标志位

2.5 同步函数

三、事件标志组实验


一、事件标志组简介

1.1 事件标志位

        用一个位,来表示事件是否发生,比如之前定义的标志位全局变量 uint8_t flag,一共8位。值为1代表事件发生,值为0代表事件未发生。

1.2 事件标志组

        事件标志组是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数

1.3 事件标志组的特点

  1. 它的每一个位表示一个事件(高8位不算)
  2. 每一位事件的含义,由用户自己决定,如:bit0表示按键是否按下,bit1表示是否接受到消息 … …这些位的值为1:表示事件发生了;值为0:表示事件未发生
  3. 任意任务或中断都可以读写这些位
  4. 每个任务可以等待某一位成立,或者等待多位同时成立再作出响应(也就是可以等待一个事件发生或者等待多个事件发生)

一个事件组就包含了一个 EventBits_t 数据类型的变量,变量类型 EventBits_t 的定义如下所示:

      EventBits_t 实际上是一个16 位或 32 位无符号的数据类型,在STM32中,它是一个32位的无符号整数数据类型!

        虽然FreeRTOS使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!

总结:

       事件标志组是由一组标志(bits)组成,每个标志可以单独设置或清除。任务可以等待一个或多个标志的设置,从而实现任务之间的同步。 

1.4 事件标志组与队列、信号量的区别

1. 事件标志组(Event Groups)

特点和用途:

  • 多事件同步:事件标志组由一组标志(bits)组成,每个标志表示一个事件。任务可以等待多个事件标志的设置,适用于需要等待多个事件发生的场景。
  • 事件组合任务可以等待一个或多个事件的组合(任意一个或所有),这在复杂的同步场景中非常有用。
  • 设置和清除:事件标志可以被设置(置位)或清除(复位),并且可以选择在任务等待时自动清除已满足的标志。

示例用途:

  • 任务需要等待多个条件满足才能继续执行。
  • 系统状态监控,需要等待某些状态改变后执行相应操作。

2. 队列(Queues)

特点和用途:

  • 消息传递:队列用于在任务间传递数据,可以是简单的变量或者复杂的数据结构。任务可以将数据放入队列中,或者从队列中读取数据。
  • 先进先出(FIFO):队列遵循先进先出原则,确保数据按发送顺序被读取。
  • 线程安全:队列操作是线程安全的,适用于多任务同时访问的场景。

示例用途:

  • 任务之间传递传感器数据、消息或命令。
  • 实现生产者-消费者模型,一个任务生成数据并放入队列,另一个任务从队列读取数据进行处理。

3. 信号量(Semaphores)

特点和用途:

  • 二元信号量(Binary Semaphores):用于简单的任务间同步,类似于事件标志,但只能表示单个事件。适合于通知一个任务某个事件发生。
  • 计数信号量(Counting Semaphores):用于资源管理,可以用于限制对共享资源的并发访问次数。
  • 互斥信号量(Mutexes):一种特殊的二元信号量,用于实现任务间的互斥访问。它具有优先级继承特性,防止优先级反转问题。

示例用途:

  • 二元信号量用于任务间简单的事件通知,比如ISR(中断服务程序)通知任务某个中断发生。
  • 计数信号量用于限制资源访问次数,比如限制同时访问某个共享资源的任务数量。
  • 互斥信号量用于保护临界区,确保任务在访问共享资源时不被其他任务中断。

4. 比较和选择

  • 复杂同步:事件标志组适用于需要复杂事件组合和多事件同步的场景。
  • 数据传递:队列适用于任务间需要传递数据的场景,特别是涉及到顺序处理的数据流。
  • 简单同步和资源管理:信号量适用于简单的任务间同步和资源访问控制。二元信号量用于简单事件通知,计数信号量用于资源管理,互斥信号量用于保护共享资源的访问。

功能

唤醒对象

事件清除

队列、信号量

事件发生时,只会唤醒一个任务(其他任务会阻塞)

是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了

事件标志组

事件发生时,会唤醒所有符合条件的任务,可以理解为“广播”的作用

被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件

二、事件标志组相关API函数介绍

函数

描述

xEventGroupCreate()

使用动态方式创建事件标志组

xEventGroupCreateStatic()

使用静态方式创建事件标志组

xEventGroupClearBits()

清零事件标志位

xEventGroupClearBitsFromISR()

在中断中清零事件标志位

xEventGroupSetBits()

设置事件标志位

xEventGroupSetBitsFromISR()

在中断中设置事件标志位

xEventGroupWaitBits()

等待事件标志位(等待一位或多位)

xEventGroupSync()

设置事件标志位,并等待事件标志位

2.1 动态方式创建事件标志组函数

EventGroupHandle_t    xEventGroupCreate ( void ) ; 

返回值

描述

NULL

事件标志组创建失败

其他值

事件标志组创建成功,返回其句柄

从上面可以知道,该函数可以动态创建事件标志组,创建成功返回其事件标志组句柄,因此,我们需要提前定义好时间标志组句柄!

2.2 清除事件标志位函数

EventBits_t  xEventGroupClearBits( EventGroupHandle_t 	xEventGroup,					      const EventBits_t 	uxBitsToClear ) 

2.3 设置事件标志位函数

EventBits_t   xEventGroupSetBits(  EventGroupHandle_t 	xEventGroup,					  const EventBits_t 		uxBitsToSet    ) 

2.4 等待事件标志位

2.5 同步函数

  xEventGroupSync 是 FreeRTOS 中提供的一个用于同步多个任务的函数。它允许任务在一个事件标志组上等待一组事件,同时设置它们自己的事件标志,从而实现复杂的同步操作。xEventGroupSync 函数用于任务间的同步。调用任务设置指定的事件标志,然后阻塞自己,直到指定的事件标志(uxBitsToWaitFor)被设置。这样,可以确保多个任务在同一时间点同步执行。

举例:

Task1:做饭  Task2:做菜,Task1做好自己的事之后,需要等待菜也做好,大家在一起吃饭。

特点:同步!

三、事件标志组实验

创建任务文件

#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_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)
void start_task(void* args);




/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK1_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK1_PRIO         2             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task1_handler;           //定义任务句柄(结构体指针)
void task1(void* args);




/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK2_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK2_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();   /*退出临界区*/
		

    //临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式						
}




/********任务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
	}	
}



//FreeRTO入口例程函数,无参数,无返回值
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_PRIO ,
                        (TaskHandle_t *)  &start_task_handler );
							
							
	vTaskStartScheduler();  //开启任务调度器
	
}




主函数调用任务文件

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "myled.h"
#include "myusart.h"


#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"

extern TaskHandle_t Start_Handle;

int main(void)
{
    //硬件初始化
     My_UsartInit();
    
	
	
	
	 //调用入口函数
     freertos_demo();
	 
}

 至此,已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来可期,静待花开~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值