STM32F1+HAL库+FreeTOTS学习17——事件标志组

server/2024/10/20 12:24:07/

STM32F1+HAL库+FreeTOTS学习17——事件标志组

  • 1. 事件标志组
    • 1.1 事件标志组的的引入
    • 1.2 事件标志组简介
    • 1.3 事件标志组与队列、信号量的区别
  • 2. 事件标志组下相关API函数
    • 2. 1 xEventGroupCreate()
    • 2. 2 xEventGroupCreateStatic()
    • 2. 3 vEventGroupDelete()
    • 2. 4 xEventGroupWaitBits()
    • 2. 5 xEventGroupSetBits()
    • 2. 7 xEventGroupSetBitsFromISR()
    • 2. 8 xEventGroupClearBits()
    • 2. 9 xEventGroupClearBitsFromISR()
    • 2. 10 xEventGroupGetBits()
    • 2. 11 xEventGroupGetBitsFromISR()
    • 2. 12 xEventGroupSync()
  • 3. 事件标志组操作实验
    • 3.1. 实验内容
    • 3.2 代码实现
    • 3.2 实验结果

上期我们介绍了队列集,这一期我们来开始学习事件标志组

1. 事件标志组

1.1 事件标志组的的引入

前面我们在介绍信号量的时候有提到过,信号量是为了解决任务与任务之间的同步问题而引入的,但是对于任务之间的多个事件同步,使用信号量也会比较麻烦,为了实现任务之间多个事件的同步,方便统一管理,我们引入事件标志组。

1.2 事件标志组简介

  • 在事件标志组中,每一个位都可以用来表征一个事件的标志,这样的一个位叫做事件标志位,而事件标志组就是事件标志位的集合。
  • 当有个标志位被置1了,表示某个事件已经发送,反正则未发生。
  • 事件标志组包含了一个 EventBits_t 类型的变量,实际上就是一个整数,EventBits_t 类型变量的具体定义如下:
	typedef TickType_t               EventBits_t;#if ( configUSE_16_BIT_TICKS == 1 )typedef uint16_t     TickType_t;#define portMAX_DELAY              ( TickType_t ) 0xffff#elsetypedef uint32_t     TickType_t;#define portMAX_DELAY              ( TickType_t ) 0xffffffffUL
  • 可以见的,EventBits_t 的变量在我们这里是32位变量,但实际上,我们能够使用的只有0~23位,高八位不可用,即一个事件组最大可以存储24个事件标志
    在这里插入图片描述

  • 在实际使用中,事件标志组支持同时等待、设置(置位)多个标志位

  • 事件标志组的置位、等待、清除标志位、获取标志位信息等操作支持在任务和中断中使用。

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

  • 队列和信号量:在事件发生时,只会唤醒一个任务是消耗型的资源,队列中的数据被读走就没有了,信号量被获取之后就减少,需要再次写入队列或者释放消息给信号量。
    事件标志组:事件发生时,会唤醒所有符合条件的任务,被唤醒的任务有两个选择,可以让事件标志位保持不变,也可以清除事件标志。

2. 事件标志组下相关API函数

FreeRTOS 提供了事件标志组的一些相关操作函数,如下表所示:

函数描述
xEventGroupCreate()使用动态方式创建事件标志组
xEventGroupCreateStstic()使用静态方式创建事件标志组
vEventGroupDelete()删除事件标志组
xEventGroupWaitBits()等待事件标志位
xEventGroupSetBits()设置事件标志位列
xEventGroupSetBitsFromISR()在中断中设置事件标志位
xEventGroupClearBits()清零事件标志位
xEventGroupClearBitsFromISR()在中断中清零事件标志位
xEventGroupGetBits()获取事件组中各事件标志位的值
xEventGroupGetBitsFromISR()在中断中获取事件组中各事件标志位的值
xEventGroupSync()设置事件标志位,并等待事件标志位

2. 1 xEventGroupCreate()

此函数用于动态方式创建事件标志组,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupCreate:动态方式创建事件标志组* @param       void* @retval      返回值为NULL,表示创建失败,其他值表示为创建事件标志组的句柄*/
EventGroupHandle_t xEventGroupCreate(void);

2. 2 xEventGroupCreateStatic()

此函数用于动态方式创建事件标志组,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupCreateStatic:静态方式创建事件标志组* @param       pxEventGroupBuffer: 指向StaticEventGroup_t 变量类型的指针,用来存放创建完成的事件标志组* @retval      返回值为NULL,表示创建失败,其他值表示为创建事件标志组的句柄*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer );

2. 3 vEventGroupDelete()

此函数用于删除事件标志组,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       vEventGroupDelete:删除事件标志组* @param       xEventGroup:待删除的事件标志组句柄* @retval      void*/
void vEventGroupDelete(EventGroupHandle_t xEventGroup);

2. 4 xEventGroupWaitBits()

此函数用于等待事件标志组中的某一个或多个标志位,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupWaitBits:等待事件标志组中的某一个或多个标志位* @param       xEventGroup:等待的事件标志组句柄* @param       uxBitsToWaitFor:等待的事件标志位,可以使用逻辑或等待多个事件标志位* @param       xClearOnExit:等待成功后是否清除对应标志位,pdTRUE清除,pdFALSE不清除* @param       xWaitForAllBits:等待事件标志位中的一个还是所有,pdTRUE等待所有,pdFLASE等待一个* @param       xTicksToWait:等待阻塞时间* @retval      void*/
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait)

2. 5 xEventGroupSetBits()

此函数用于设置(置位)事件标志位,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupSetBits:设置(置位)事件标志位* @param       xEventGroup:待设置的事件标志组句柄* @param       uxBitsToSet :需要设置的事件标志位,可以通过逻辑或的方式,同时设置多个事件标志位* @retval      事件标志组值,可以表征事件标志组中的事件标志位的设置(置位)情况。*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );

2. 7 xEventGroupSetBitsFromISR()

此函数用于在中断中设置(置位)事件标志位,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupSetBitsFromISR:在中断中设置(置位)事件标志位* @param       xEventGroup:待设置的事件标志组句柄* @param       uxBitsToSet :需要设置的事件标志位,可以通过逻辑或的方式,同时设置多个事件标志位* @param       pxHigherPriorityTaskWoken :是否需要进行任务切换,如果为pdTRUE,表示需要进行任务切换,为pdFALSE则不需要* @retval      如果消息已发送到 RTOS 守护进程任务,则返回 pdPASS,否则返回 pdFAIL。 如果定时器服务队列已满,则返回 pdFAIL。*/
BaseType_t xEventGroupSetBitsFromISR(  EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,BaseType_t *pxHigherPriorityTaskWoken );

【注】:在事件组中设置位将自动解除 所有等待位的任务的阻塞状态。这个操作是不确定的,因为有可能同时存在多个任务解除阻塞,FreeRTOS不允许在中断中出现这种操作,因此xEventGroupSetBitsFromISR()会向RTOS 守护进程任务发送一条消息, 从而在守护进程任务(也叫做定时器服务任务)的上下文中执行设置操作,其中使用的是调度器锁 而非临界区。

总结一句话:就是xEventGroupSetBitsFromISR()函数中的标志位置位操作会被推迟到 RTOS 守护进程任务中进行。

2. 8 xEventGroupClearBits()

此函数用于在任务中清零事件标志位,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupClearBits:在任务中清除事件标志位* @param       xEventGroup:需要清除标志位的事件标志组句柄* @param       uxBitsToClear :需要清除的事件标志位,可以通过逻辑或的方式,同时清除多个事件标志位* @retval     清除指定位之前的事件组的值。*/
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToClear );

2. 9 xEventGroupClearBitsFromISR()

此函数用于在中断中清零事件标志位,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupClearBitsFromISR:在中断中清除事件标志位* @param       xEventGroup:需要清除标志位的事件标志组句柄* @param       uxBitsToClear :需要清除的事件标志位,可以通过逻辑或的方式,同时清除多个事件标志位* @retval     如果返回pdPASS表示操作成功延迟到RTOS守护进程任务,否则表示定时器命令队列已满,事件标志位清零失败。*/
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToClear );

2. 10 xEventGroupGetBits()

此函数用于获取事件标志组的值,可以表征事件标志组内成员的置位情况,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupGetBits:获取事件标志组的值* @param       xEventGroup:需要查询的事件标志组句柄* @retval      事件标志组的值*/
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );

2. 11 xEventGroupGetBitsFromISR()

此函数用于中断中获取事件标志组的值,可以表征事件标志组内成员的置位情况,该函数在 event_groups.c 文件中有定义,函数的原型如下所示:

/*** @brief       xEventGroupGetBitsFromISR:获取事件标志组的值* @param       xEventGroup:需要查询的事件标志组句柄* @retval      事件标志组的值*/
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );

2. 12 xEventGroupSync()

此函数用于设置事件标志组中的位,并且等待同一事件标志组的标志位,常用于同步多个任务(通常称为任务集合),其中每个任务必须等待其他任务到达同步点后才能继续,且不能在中断中使用此函数。

举个栗子来说:我们在打王者荣耀进入游戏之前,需要先匹配队友,如何等待所有队友点击“确认”才可以进入游戏,如果中途有玩家未点击确认,则无法进入游戏。

在这个例子里面,自己点击“确认”,是自己将标志位置位,但此时需要等待其他队友点击“确认”,是等待同一事件标志组的其他标志位。这个就是多个任务之间的消息同步。同时每个队友(任务),都需要等待其他队友(任务)点击确认(所有人都到达同步点)才能继续。

上述是我自己对于此函数的理解,下面我们来看一下函数原型:

/*** @brief       xEventGroupSync:设置事件标志组中的位,并且等待同一事件标志组的标志位,常用于同步多个任务* @param       xEventGroup:待设置和等待位的事件标志组句柄* @param       uxBitsToSet:在确定uxBitsToWait参数指定的所有位是否都已设置(可能还要等待)之前,要在事件组中设置的一个或多个位。* @param       uxBitsToWaitFor:指定要在事件组中等待的一个或多个位的按位值。* @param       xTicksToWait: 等待 uxBitsToWaitFor 参数值指定的所有位被设置的最长时间(以滴答为单位) 。* @retval      如果等待事件标志位成功,返回等待到的事件标志位;如果等待事件标志位失败,返回事件组中的事件标志位*/
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );

3. 事件标志组操作实验

3.1. 实验内容

在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的事件标志位操作,具体要求如下:

  • 定义一个事件标志位
  • 定义任务1:按下按键0,按键0对应的事件标志位置1,LED指示灯亮起;按下按键1,按键1对应的事件标志位置1,LED指示灯亮起。
  • 定义任务2:等待事件标志组中按键0和按键1对于的标志位,当两者都被置1时,串口打印相关信息,并且关闭LED指示灯。

3.2 代码实现

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. freertos_demo.c
#include "freertos_demo.h"
#include "gpio.h"
#include "queue.h" 		//需要包含队列和任务相关的头文件
#include "key.h"		//包含按键相关头文件/*FreeRTOS*********************************************************************************************//******************************************************************************************************/
/*FreeRTOS配置*//* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 */
#define TASK1_PRIO      1                  /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);					/*任务函数*//* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 */
#define TASK2_PRIO      2                  /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);					/*任务函数*//** 事件标志组配置*/
EventGroupHandle_t EventGroup_Handler;		/* 事件标志组句柄 *//******************************************************************************************************//*** @brief       FreeRTOS例程入口函数* @param       无* @retval      无*/
void freertos_demo(void)
{taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*//* 创建事件标志组 */EventGroup_Handler = xEventGroupCreate();if(EventGroup_Handler == NULL){printf("事件标志组创建失败!!!\r\n");}else{printf("事件标志组创建成功!!!\r\n");}/* 创建任务1 */xTaskCreate((TaskFunction_t )task1,(const char*    )"task1",(uint16_t       )TASK1_STK_SIZE,(void*          )NULL,(UBaseType_t    )TASK1_PRIO,(TaskHandle_t*  )&Task1Task_Handler);/* 创建任务2 */xTaskCreate((TaskFunction_t )task2,(const char*    )"task2",(uint16_t       )TASK2_STK_SIZE,(void*          )NULL,(UBaseType_t    )TASK2_PRIO,(TaskHandle_t*  )&Task2Task_Handler);taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */vTaskStartScheduler();		//开启任务调度
}/**
* @brief       task1:用于按键扫描,按键0或1按下,自动置位事件标志组,并开启相应的LED指示* @param       pvParameters : 传入参数(未用到)* @retval      无*/
void task1(void *pvParameters)
{while(1){Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);vTaskDelay(10);}
}	
/**
* @brief       task2:当按键1和0的事件标志组位都被置1,打印相关信息,并关闭相应的LED指示* @param       pvParameters : 传入参数(未用到)* @retval      无*/
void task2(void *pvParameters)	
{	EventBits_t eventBits;while(1){	/* 等待按键0和1的事件标志位 */eventBits = xEventGroupWaitBits(EventGroup_Handler,Key0_EventBit|Key1_EventBit,pdTRUE,pdTRUE,portMAX_DELAY);/* 打印相关信息 */printf("等待到的事件标志为:%#x\r\n",eventBits);/* 关闭LED指示 */HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,LED_OFF);HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,LED_OFF);vTaskDelay(50);}}
  1. key.c
/* USER CODE BEGIN 2 */#include "key.h"
#include "freertos_demo.h"
#include "usart.h"
#include "event_groups.h"	//包含事件标志组头文件
#include "gpio.h"void Key0_Down_Task(void)
{/* 设置事件标志位 */HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,LED_ON);/* 开启LED指示 */xEventGroupSetBits(EventGroup_Handler,Key0_EventBit);}
void Key0_Up_Task(void)
{}
void Key1_Down_Task(void)
{/* 设置事件标志位 */HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,LED_ON);/* 开启LED指示 */xEventGroupSetBits(EventGroup_Handler,Key1_EventBit);}
void Key1_Up_Task(void)
{}
void Key2_Down_Task(void)
{}
void Key2_Up_Task(void)
{}
void WKUP_Down_Task(void)
{}
void WWKUP_Up_Task(void)
{}void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存switch(KeyName){case Key_Name_Key0:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin));    //读取Key0按键值break;case Key_Name_Key1:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin));   //读取Key1按键值break;case Key_Name_Key2:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin));   //读取Key2按键值break;
//        case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin));   //读取WKUP按键值
//            break; default:break;}
//    if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理
//    {
//        //WKUP特殊情况
//        //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
//        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
//        {
//            (*OnKeyOneDown)();
//           Key_Flag[KeyName] = 0;
//        }
//        //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
//        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
//        {
//            (*OnKeyOneUp)();
//           Key_Flag[KeyName] = 1;
//        } 
//    }
//    else                               //Key0~2按键逻辑判断
//    {//Key0~2常规判断//当按键标志为1(松开)是,判断是否按下if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下)![,判断按键是否松开](https://i-blog.csdnimg.cn/direct/6c5767c84bbe42e5995cf1cb841b61e4.png)if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;}  }//}
/* USER CODE END 2 */

3.2 实验结果

在这里插入图片描述
以上就是本期所有内容,感谢观看。


http://www.ppmy.cn/server/133333.html

相关文章

python 爬虫 入门 三、登录以及代理。

目录 一、登录 &#xff08;一&#xff09;、登录4399 1.直接使用Cookie 2.使用账号密码进行登录 可选观看内容&#xff0c;使用python对密码进行加密&#xff08;无结果代码&#xff0c;只有过程分析&#xff09; 二、代理 免费代理 后续&#xff1a;协程&#xff0c;…

fastjson注解说明,fastjson注解有那些?fastjson是java的json序列化和反序列化工具包

fastjson注解说明,fastjson注解有那些?fastjson是java的json序列化和反序列化工具包 包版本说明 fastjson请使用1.2.83以上版本,小于这个版本的存在漏洞。 fastjson请使用1.2.83以上版本,小于这个版本的存在漏洞。 fastjson请使用1.2.83以上版本,小于这个版本的存在漏洞…

Golang Map简介

Go Map Map 简介 在Go语言中提供了map数据结构来存储键值对数据。map的数据类型为map[K]V&#xff0c;其中K为键的类型&#xff0c;V为值的类型。map的键类型必须支持操作符&#xff0c;用来比较两个键是否相等。Go语言提供了4种内置的map操作: len、delete、comparison、ass…

关于C语言——对一个数据定义的两种属性

对一个数据的定义&#xff0c;需要去定义它的两种属性:数据类型和存储类型。 对于数据类型主要有: intcharlongfloatdouble 对于存储类型有这四种: auto static register extern 平时使用的时候一般不标明存储类型&#xff0c;而存储类型主动是为auto&#xff0c;自动变…

基于x86_64汇编语言简单教程1: 环境预备与尝试

目录 前言 环境配置 基本硬件与操作系统要求 WSL VSCode基本配置(For Windows) 安装基本的依赖 为您的VSCode安装插件&#xff1a; 学习要求 入门 先试试味道 前言 笔者最近正在梭哈使用NASM汇编器的x86 32位汇编&#xff0c;笔者这里记录一下一个晚上的成果。 环境…

Java爬虫:获取商品评论数据的高效工具

在电子商务的激烈竞争中&#xff0c;商品评论作为消费者购买决策的重要参考&#xff0c;对于商家来说具有极高的价值。它不仅能够帮助商家了解消费者的需求和反馈&#xff0c;还能作为改进产品和服务的依据。Java爬虫技术&#xff0c;以其稳健性和高效性&#xff0c;成为了获取…

探索 Python Web 开发:从框架到爬虫

Python 是 Web 开发中广泛使用的编程语言&#xff0c;因其简单、灵活和强大的生态系统&#xff0c;适合构建各种类型的 Web 应用和 API。在本篇博客中&#xff0c;我们将讨论 Web 开发的几个重要主题&#xff0c;包括 Flask 和 Django 框架、API 开发、HTTP 请求处理以及网页爬…

Python支持向量机(SVM)算法:面向对象的实现与案例详解

目录 Python支持向量机&#xff08;SVM&#xff09;算法&#xff1a;面向对象的实现与案例详解引言一、支持向量机算法概述1.1 支持向量机的基本思想1.2 SVM的分类问题1.3 SVM的优化目标 二、面向对象的SVM实现2.1 类的设计2.2 Python代码实现2.3 代码详解 三、案例分析3.1 案例…