当前位置: 首页 > news >正文

FreeRTOS菜鸟入门(十四)·事件

目录

1.  基本概念

2.  应用场景

3.  运作机制

4.  控制块

5.  事件函数接口

5.1  事件创建函数 xEventGroupCreate()

5.2  事件删除函数 vEventGroupDelete()

5.3  事件组置位函数 xEventGroupSetBits()(非中断)

5.4  事件组置位函数 xEventGroupSetBitsFromISR()(中断)

5.5  等待事件函数 xEventGroupWaitBits()

5.6  xEventGroupClearBits()与 xEventGroupClearBitsFromISR()


1.  基本概念

        事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。

        每一个事件组只需要很少的 RAM 空间来保存事件组的状态。事件组存储在一个 EventBits_t 类 型 的变量中 , 该变量在事件组结构体中定义 。

  •  如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就是 16 位的,其中有 8 个位用来存储事件组;
  • 如果宏 configUSE_16_BIT_TICKS 定义为 0,那么变量 uxEventBits 就是 32 位的 , 其中有 24 个位用来存储事件组 。

        在 STM32 中 , 我们一般将 configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事件标志组。每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。

  • 事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;
  • 事件“逻辑与”则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。

        多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务、中断与任务间的同步。事件可以提供一对多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发。

        任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件仅用于同步,不提供数据传输功能。

FreeRTOS 提供的事件具有如下特点:

  1. 事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有 24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共 24 种事件类型。
  2. 事件仅用于同步,不提供数据传输功能。
  3. 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。
  4. 允许多个任务对同一事件进行读写操作。
  5. 支持事件等待超时机制。

2.  应用场景

        比如一些危险机器的启动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不能一下子检测完毕啊,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机器才允许启动,这只是事件的其中一个应用。

        事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作是不可累计的,而信号量的释放动作是可累计的。事件另外一个特性是,接收任务可等待多种事件,即多个事件对应一个任务或多个任务。同时按照任务等待的参数,可选择是“逻辑或”触发还是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作,而不能同时等待多个事件的同步。

3.  运作机制

        事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。 用户可以自定义通过传入参数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。

        设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。

        清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。

        事件不与任务相关联,事件相互独立,一个 32位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1表示该事件类型已经发生),一共 24种事件类型具体见图:

        事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的时候会被唤醒:

        任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作。

        而任务 2 对事件 3 与事件 5 感兴趣(逻辑与),当且仅当事件 3 与事件 5 都发生的时候,任务 2 才会被唤醒,如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果接在收事件函数中设置了清除事件位 xClearOnExit,那么当任务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。

4.  控制块

        事件标志组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义:

typedef struct xEventGroupDefinition {EventBits_t uxEventBits;               /* 当前事件标志位(32-bit变量) */List_t xTasksWaitingForBits;           /* 等待事件位的任务链表 */#if (configUSE_TRACE_FACILITY == 1)UBaseType_t uxEventGroupNumber;        /* 调试用的事件组编号 */
#endif#if ( (configSUPPORT_STATIC_ALLOCATION == 1) && \(configSUPPORT_DYNAMIC_ALLOCATION == 1) )uint8_t ucStaticallyAllocated;         /* 标识是否静态分配:pdTRUE/pdFALSE */
#endif
} EventGroup_t;
  • 如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就是 16 位的,其中有 8 个位用来存储事件组;
  • 如果宏 configUSE_16_BIT_TICKS 定义为 0,那么变量 uxEventBits 就是 32 位的 , 其中有 24 个位用来存储事件组 。

5.  事件函数接口

5.1  事件创建函数 xEventGroupCreate()

        xEventGroupCreate()用于创建一个事件组,并返回对应的句柄。要想使用该函数必须在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1(在FreeRTOS.h 中默认定义为 1)且需要把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。

  • 如果使用函数 xEventGroupCreate()来创建一个事件,那么需要的 RAM 是动态分配的。
  • 如果使用函数xEventGroupCreateStatic()来创建一个事件,那么需要的 RAM 是静态分配的。

xEventGroupCreate()源码:

#if (configSUPPORT_DYNAMIC_ALLOCATION == 1)EventGroupHandle_t xEventGroupCreate(void)
{EventGroup_t *pxEventBits;/* 动态分配事件控制块内存 */pxEventBits = (EventGroup_t *)pvPortMalloc(sizeof(EventGroup_t));  // (1)if (pxEventBits != NULL) {                                        // (2)/* 初始化事件标志位 */pxEventBits->uxEventBits = 0;/* 初始化等待任务链表 */vListInitialise(&(pxEventBits->xTasksWaitingForBits));#if (configSUPPORT_STATIC_ALLOCATION == 1){/* 标记为动态分配(与静态分配区分) */pxEventBits->ucStaticallyAllocated = pdFALSE;             // (3)}#endiftraceEVENT_GROUP_CREATE(pxEventBits);                        // (4)} else {traceEVENT_GROUP_CREATE_FAILED();                            // (5)}return (EventGroupHandle_t)pxEventBits;                          // (6)
}#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
关键点说明
(1) 内存分配使用FreeRTOS内存管理函数pvPortMalloc动态分配EventGroup_t结构体内存
(2) 空指针检查验证内存是否分配成功
(3) 静态分配标记当同时启用静态分配时,明确标记该实例为动态分配
(4) 追踪钩子调试用追踪宏,记录创建成功事件
(5) 失败追踪记录内存分配失败事件
(6) 类型转换将内部结构体指针转换为对外句柄类型

        举个例子,这里我们以之前创建的空白模版为例,在其中进行更改:

基于STM32F1系列移植FreeRTOS模版资源-CSDN文库

        首先进行头文件包含:

#include "event_groups.h"

        并且在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1,也就是动态内存分配,我们已经设成1了因此不用动:

        在使用创建函数前我们需要有一个事件的句柄,以便后续访问:

static EventGroupHandle_t Event_Handle =NULL;

        然后在任务创建函数AppTaskCreate()调用事件创建函数:

//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");  elseprintf("Event_Handle 事件创建失败!\r\n");  		vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}

        完整main函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h"  /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
static EventGroupHandle_t Event_Handle =NULL;/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*///一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */(const char*    )"AppTaskCreate",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )1, /* 任务的优先级 */(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */           if(pdPASS == xReturn)vTaskStartScheduler();   /* 启动任务,开启调度 */elsereturn -1;  }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");  elseprintf("Event_Handle 事件创建失败!\r\n");  		vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化	*/USART_Config();//按键初始化Key_GPIO_Config();}

        运行结果:

完整工程:

FreeRTOS事件-创建函数.zip资源-CSDN文库

5.2  事件删除函数 vEventGroupDelete()

        在很多场合,某些事件只用一次的,就好比在事件应用场景说的危险机器的启动,假如各项指标都达到了,并且机器启动成功了,那这个事件之后可能就没用了,那就可以进行销毁了。想要删除事件怎么办?FreeRTOS 给我们提供了一个删除事件的函数——vEventGroupDelete(),使用它就能将事件进行删除了。当系统不再使用事件对象时,可以通过删除事件对象控制块来释放系统资源,vEventGroupDelete()源码:

/*-----------------------------------------------------------*/
void vEventGroupDelete(EventGroupHandle_t xEventGroup)
{EventGroup_t *pxEventBits = (EventGroup_t *)xEventGroup;const List_t *pxTasksWaitingForBits = &(pxEventBits->xTasksWaitingForBits);vTaskSuspendAll();  /* (1) 挂起所有任务,因为接下来的操作不知道需要多长的时间,并且在删除的时候,不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。 */{traceEVENT_GROUP_DELETE(xEventGroup);/* (2) 处理所有等待该事件的任务 */while (listCURRENT_LIST_LENGTH(pxTasksWaitingForBits) > (UBaseType_t)0) {configASSERT(pxTasksWaitingForBits->xListEnd.pxNext != (ListItem_t *)&(pxTasksWaitingForBits->xListEnd));/* (3) 将任务移出事件等待列表 */(void)xTaskRemoveFromUnorderedEventList(pxTasksWaitingForBits->xListEnd.pxNext,eventUNBLOCKED_DUE_TO_BIT_SET);}#if ((configSUPPORT_DYNAMIC_ALLOCATION == 1) && \(configSUPPORT_STATIC_ALLOCATION == 0)){/* (4) 释放动态分配的内存 */vPortFree(pxEventBits);}#endif/* 静态分配处理部分已省略 */}(void)xTaskResumeAll();  /* (5) 恢复任务调度 */
}
/*-----------------------------------------------------------*/

        vEventGroupDelete()用于删除由函数 xEventGroupCreate()创建的事件组,只有被创建成功的事件才能被删除,但是需要注意的是该函数不允许在中断里面使用。当事件组被删除之后,阻塞在该事件组上的任务都会被解锁,并向等待事件的任务返回事件组的值为 0。

        这里在之前代码的基础上对AppTaskCreate()进行修改,其实只要添加一句话就够了:

		vEventGroupDelete(Event_Handle);

        不过为了更容易看到实验现象,增加一些验证:

		Event_Handle = NULL;              // 手动置空句柄// 验证是否删除if (Event_Handle == NULL) {printf("事件组已删除!\r\n");} else {printf("事件组删除失败!\r\n");}

        如果事件组已被删除,再次操作(如 xEventGroupSetBits()下面会讲,这里先使用验证一下)会导致内存错误或断言失败:

		// 尝试操作已删除的事件组(验证是否崩溃)xEventGroupSetBits(Event_Handle, 0x01);  // 如果已删除,可能会触发断言或 HardFault

        完整更改代码:

//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle){printf("Event_Handle 事件创建成功!\r\n"); vEventGroupDelete(Event_Handle);Event_Handle = NULL;              // 手动置空句柄// 验证是否删除if (Event_Handle == NULL) {printf("事件组已删除!\r\n");} else {printf("事件组删除失败!\r\n");}// 尝试操作已删除的事件组(验证是否崩溃)xEventGroupSetBits(Event_Handle, 0x01);  // 如果已删除,可能会触发断言或 HardFault}elseprintf("Event_Handle 事件创建失败!\r\n");  		vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}

        运行结果,会发现事件删除后,再去调用会出现错误:

完整工程:

FreeRTOS事件-删除函数.zip资源-CSDN文库

5.3  事件组置位函数 xEventGroupSetBits()(非中断)

        xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。

        简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

注意的是该函数不允许在中断中使用。

函数原型

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,

                                                    const EventBits_t uxBitsToSet);

功能置位事件组中指定的位。
参数xEventGroup事件句柄。
uxBits ToSet指定事件中的事件标志位。如设置uxBitsToSet为0x08则只置位位3,如果设置uxBitsToSet为0x09则位3和位0都需要被置位。
返回值返回调用xEventGroupSetBits()时事件组中的值。

函数源码:

/*-----------------------------------------------------------*/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet)
{ListItem_t *pxListItem, *pxNext;ListItem_t const *pxListEnd;List_t *pxList;EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;EventGroup_t *pxEventBits = (EventGroup_t *)xEventGroup;BaseType_t xMatchFound = pdFALSE;/* 参数有效性检查 */configASSERT(xEventGroup);configASSERT((uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES) == 0);  /* (1) */pxList = &(pxEventBits->xTasksWaitingForBits);pxListEnd = listGET_END_MARKER(pxList);vTaskSuspendAll();  /* (2) 挂起所有任务 */{traceEVENT_GROUP_SET_BITS(xEventGroup, uxBitsToSet);/* 设置事件标志位 */pxEventBits->uxEventBits |= uxBitsToSet;  /* (3)根据用户指定的 uxBitsToSet 设置事件标志位。 *//* 遍历等待事件的任务列表 */pxListItem = listGET_HEAD_ENTRY(pxList);while (pxListItem != pxListEnd) {  /* (4)设置这个事件标志位可能是某个任务在等待的事件,就需要遍历等待事件列表中的任务,看看这个事件是否与任务等待的事件匹配。 */pxNext = listGET_NEXT(pxListItem);uxBitsWaitedFor = listGET_LIST_ITEM_VALUE(pxListItem);xMatchFound = pdFALSE;/* 解析等待条件,是逻辑与还是逻辑或 */uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;  /* (5) 获取要等待事件的标记信息,是逻辑与还是逻辑或*/uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;  /* (6) 再获取任务的等待事件是什么*//* 检查事件匹配条件 */if ((uxControlBits & eventWAIT_FOR_ALL_BITS) == (EventBits_t)0) {  /* (7) *//* 任意位匹配模式 */if ((uxBitsWaitedFor & pxEventBits->uxEventBits) != (EventBits_t)0) {xMatchFound = pdTRUE;  /* (8) 判断要等待的事件是否发生了,发生了就需要把任务恢复,在这里记录一下要恢复的任务。*/} else {mtCOVERAGE_TEST_MARKER();}}else if ((uxBitsWaitedFor & pxEventBits->uxEventBits) == uxBitsWaitedFor) {  /* (9) 如果任务等待的事件都要发生的时候(也是我们常说的“逻辑与”),就需要就要所有判断事件标志组中的事件是否都发生,如果是的话任务才能从阻塞中恢复,同样也需要标记一下要恢复的任务。*//* 全部位匹配模式 */xMatchFound = pdTRUE;} else {/* 不满足全部位条件 */  /* (10) */}/* 处理匹配成功的任务 */if (xMatchFound != pdFALSE) {  /* (11) *//* 检查是否需要清除事件位 */if ((uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT) != (EventBits_t)0) {uxBitsToClear |= uxBitsWaitedFor;  /* (12)运用或运算,标记一下要清除的事件标志位是哪些。 */} else {mtCOVERAGE_TEST_MARKER();}/* 将任务移出等待列表 */(void)xTaskRemoveFromUnorderedEventList(pxListItem,pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET);  /* (13) 将满足事件条件的任务从等待列表中移除,并且添加到就绪列表*/}pxListItem = pxNext;  /* (14) 移至下一任务 */}/* 清除需要复位的事件位 */pxEventBits->uxEventBits &= ~uxBitsToClear;  /* (15) */}(void)xTaskResumeAll();  /* (16) 恢复任务调度 */return pxEventBits->uxEventBits;  /* (17) 返回当前事件位 */
}
/*-----------------------------------------------------------*/

(1):断言,判断要设置的事件标志位是否有效,因为一个 32 位的事件标志组变量只有 24 位是用于设置事件的,而 16 位的事件标志组变量只有 8 位用于设置事件,高 8 位不允许设置事件,有其他用途,事件组高8位的用途:

#if configUSE_16_BIT_TICKS == 1
#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x0100U
#define eventUNBLOCKED_DUE_TO_BIT_SET 0x0200U
#define eventWAIT_FOR_ALL_BITS 0x0400U
#define eventEVENT_BITS_CONTROL_BYTES 0xff00U
#else
#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x01000000UL
#define eventUNBLOCKED_DUE_TO_BIT_SET 0x02000000UL
#define eventWAIT_FOR_ALL_BITS 0x04000000UL
#define eventEVENT_BITS_CONTROL_BYTES 0xff000000UL
#endif

(2):挂起调度器,因为接下来的操作不知道需要多长的时间,因为需要遍历等待事件列表,并且有可能不止一个任务在等待事件,所以在满足任务等待的事件时候,任务允许被恢复,但是不允许运行,只有遍历完成的时候,任务才能被系统调度,在遍历期间,系统也不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。

        举个例子,主要完成逻辑,判断KEY1还是KEY2按下,如果KEY1按下设置KEY1_EVENT,如果KEY2按下设置KEY2_EVENT,其他任务可以等待这两个事件的发生在进行操作(事件的等待函数下面会将,这里主要熟悉事件组置位函数的使用):

        下面我们在5.1任务创建代码的基础上进行更改,首先由于我们需要用的按键,那就在创建一个按键的任务,开始创建按键任务的句柄:

static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task任务句柄 */

        我们知道,事件组置位函数 xEventGroupSetBits()有两个参数,一个是事件句柄,这个我们在创建函数的时候已经声明过了不需要再进行声明,第二个参数是指定事件中的事件标志位,这里我们用了两个事件按键KEY1按下和按键KEY2按下,那么我们给他们创建两个宏定义(直接写可以,不过为了方便维护,我们宏定义的方式展现出来):

#define KEY1_EVENT  (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT  (0x01 << 1)//设置事件掩码的位1

        说明一下,不计入实际代码工程当中:

数值(十进制)二进制位等效宏定义(若存在)
10b0001(0x01 << 0)
20b0010(0x01 << 1)
40b0100(0x01 << 2)
80b1000(0x01 << 3)

        假设我们不写宏定义,需要写入的形式:

xEventGroupSetBits(Event_Handle, 0x01);   // 等价于设置bit0(最低位)
xEventGroupSetBits(Event_Handle, 0x02);   // 等价于设置bit1 
xEventGroupSetBits(Event_Handle, 0x03);   // 同时设置bit0和bit1(1 | 2)

        引入宏定义:

#define KEY1_EVENT  (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT  (0x01 << 1)//设置事件掩码的位1xEventGroupSetBits(Event_Handle,KEY1_EVENT);  	
xEventGroupSetBits(Event_Handle,KEY2_EVENT); 			
xEventGroupSetBits(Event_Handle, KEY1_EVENT | KEY2_EVENT); 

        也可以:

#define KEY1_EVENT  (0x01 << 0)
#define KEY2_EVENT  (0x01 << 1)
#define KEY12_EVENT  (KEY1_EVENT | KEY2_EVENT)xEventGroupSetBits(Event_Handle,KEY1_EVENT);  	
xEventGroupSetBits(Event_Handle,KEY2_EVENT); 			
xEventGroupSetBits(Event_Handle,KEY12_EVENT); 

        这样后期维护只要在宏定义即可,不需要一个一个去找了。


        创建按键的任务主体,如果KEY1按下设置KEY1_EVENT,如果KEY2按下设置KEY2_EVENT:

//KEY_Task任务主体
static void KEY_Task(void* parameter)
{	 /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       //如果KEY2被单击{printf ( "KEY1被按下!\r\n" );/* 触发一个事件1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT);  					}if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       //如果KEY2被单击{printf ( "KEY2被按下!\r\n" );	/* 触发一个事件2 */xEventGroupSetBits(Event_Handle,KEY2_EVENT); 				}vTaskDelay(20);     //每20ms扫描一次		}
}

        在AppTaskCreate()将任务的参数设置一下:

//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");  elseprintf("Event_Handle 事件创建失败!\r\n");  	/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */(const char*    )"KEY_Task",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )3, /* 任务的优先级 */(TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n");	vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}

        main函数完整代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h"  /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
static EventGroupHandle_t Event_Handle =NULL;/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*/
#define KEY1_EVENT  (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT  (0x01 << 1)//设置事件掩码的位1//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */static void KEY_Task(void* pvParameters);/* KEY_Task 任务实现 */static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */(const char*    )"AppTaskCreate",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )1, /* 任务的优先级 */(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */           if(pdPASS == xReturn)vTaskStartScheduler();   /* 启动任务,开启调度 */elsereturn -1;  }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");  elseprintf("Event_Handle 事件创建失败!\r\n");  	/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */(const char*    )"KEY_Task",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )3, /* 任务的优先级 */(TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n");	vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}//KEY_Task任务主体
static void KEY_Task(void* parameter)
{	 /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       //如果KEY2被单击{printf ( "KEY1被按下!\r\n" );/* 触发一个事件1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT);  					}if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       //如果KEY2被单击{printf ( "KEY2被按下!\r\n" );	/* 触发一个事件2 */xEventGroupSetBits(Event_Handle,KEY2_EVENT); 				}vTaskDelay(20);     //每20ms扫描一次		}
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化	*/USART_Config();//按键初始化Key_GPIO_Config();}

运行结果:

完整工程:

FreeRTOS事件-事件组置位函数.zip资源-CSDN文库

5.4  事件组置位函数 xEventGroupSetBitsFromISR()(中断)

        xEventGroupSetBitsFromISR()是 xEventGroupSetBits()的中断版本,用于置位事件组中指定的位。置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的。FreeRTOS 是不允许不确定的操作在中断和临界段中发生的,所以xEventGroupSetBitsFromISR()给 FreeRTOS 的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成,守护任务是基于调度锁而非临界段的机制来实现的。

        正如上文提到的那样,在中断中事件标志的置位是在守护任务(也叫软件定时器服务任务)中完成的。因此 FreeRTOS 的守护任务与其他任务一样,都是系统调度器根据其优先级进行任务调度的,但守护任务的优先级必须比任何任务的优先级都要高,保证在需要的时候能立即切换任务从而达到快速处理的目的,因为这是在中断中让事件标志位置位,其优先级由 FreeRTOSConfig.h 中的宏 configTIMER_TASK_PRIORITY 来定义。

        其实 xEventGroupSetBitsFromISR()函数真正调用的也是 xEventGroupSetBits()函数,只不过是在守护任务中进行调用的,所以它实际上执行的上下文环境依旧是在任务中。

        要想使用该函数,必须 把 configUSE_TIMERS 和 INCLUDE_xTimerPendFunctionCall 这些宏在 FreeRTOSConfig.h 中都定义为 1,并且把FreeRTOS/source/event_groups.c 这个 C文件添加到工程中编译。

函数原型

BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,

                                                                    const EventBits_t uxBitsToSet,                                                                     BaseType_t*pxHigherPriorityTask Woken);

功能置位事件组中指定的位,在中断函数中使用。
参数xEventGroup事件句柄。
uxBitsToSet指定事件组中的哪些位需要置位。如设置 uxBitsToSet 为0x08 则只置位位 3, 如果设置 uxBitsToSet 为 0x09 则位 3和位 0 都需要被置位。
pxHigherPriority TaskWokenpxHigherPriorityTaskWoken 在使用之前必须初始化成pdFALSE。调用xEventGroupSetBitsFromISR()会给守护任务发送一个消息,如果守护任务的优先级高于当前被中断的任务的优先级的话(一般情况下都需要将守护任务的优先级设置为所有任务中最高优先级),pxHigherPriorityTaskWoken会被置为 pdTRUE,然后在中断退出前执行一次上下文切换。
返回值消息成功发送给守护任务之后则返回 pdTRUE,否则返回 pdFAIL。如果定时器服务队列满了将返回 pdFAIL。

5.5  等待事件函数 xEventGroupWaitBits()

        上面我们5.3我们通过函数标记了事件的发生,但是我们如何知道他到底有没有发生呢?

        FreeRTOS 提供了一个等待指定事件的函数— —xEventGroupWaitBits(),通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息。

        在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。

函数原型

EventBits_t xEventGroupWaitBits(const EventGroupHandle_txEventGroup,

                                                      const EventBits_t uxBitsToWaitFor,

                                                      const BaseType_txClearOnExit,

                                                      const BaseType_txWaitForAllBits,

                                                      Tick Type_txTicksToWait);

功能用于获取任务感兴趣的事件。
参数xEventGroup事件句柄。
uxBitsToWaitFor一个按位或的值,指定需要等待事件组中的哪些位置 1。如果需要等待bit 0 and/or bit 2那么uxBitsToWaitFor配置为0x05(0101b)。如果需要等待 bits 0 and/or bit 1 and/or bit 2那么 uxBitsToWaitFor 配置为0x07(0111b)。
xClearOnExitpdTRUE:当xEventGroupWaitBits()等待到满足任务唤醒的事件时,系统将清除由形参uxBitsToWaitFor指定的事件标志位。pdFALSE:不会清除由形参 uxBitsToWaitFor指定的事件标志位。
xWaitForAllBitspdTRUE:当形参uxBitsToWaitFor指定的位都置位的时候,xEventGroupWaitBits()才满足任务唤醒的条件,这也是“逻辑与”等待事件,并且在没有超时的情况下返回对应的事件标志位的值。pdFALSE:当形参 uxBitsToWaitFor 指定的位有其中任意一个置位的时候,这也是常说的“逻辑或”等待事件,在没有超时的情况下函数返回对应的事件标志位的值。
xTicksToWait最大超时时间,单位为系统节拍周期,常量portTICK_PERIOD_MS用于辅助把时间转换成 MS。
返回值返回事件中的哪些事件标志位被置位,返回值很可能并不是用户指定的事件位,需要对返回值进行判断再处理。
/*-----------------------------------------------------------*/
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait)
{EventGroup_t *pxEventBits = (EventGroup_t *)xEventGroup;EventBits_t uxReturn, uxControlBits = 0;BaseType_t xWaitConditionMet, xAlreadyYielded;BaseType_t xTimeoutOccurred = pdFALSE;/* 参数有效性检查 */configASSERT(xEventGroup);configASSERT((uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES) == 0);configASSERT(uxBitsToWaitFor != 0);#if ((INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1)){configASSERT(!((xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0)));}
#endifvTaskSuspendAll();  /* (1) 挂起调度器 */{const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;/* (2) 检查当前事件位是否满足条件,其实就是判断一下用户等待的事件是否与当前事件标志位一致 */xWaitConditionMet = prvTestWaitCondition(uxCurrentEventBits,uxBitsToWaitFor,xWaitForAllBits);if (xWaitConditionMet != pdFALSE) {  /* (3) 条件已满足 */uxReturn = uxCurrentEventBits;xTicksToWait = (TickType_t)0;/* (4) 检查是否需要清除事件位,如果 xClearOnExit 为 pdTRUE 则需要清除事件标志位,如果为 pdFALSE 就不需要清除。 */if (xClearOnExit != pdFALSE) {pxEventBits->uxEventBits &= ~uxBitsToWaitFor;} else {mtCOVERAGE_TEST_MARKER();}}/* (5) 不满足条件且不等待 */else if (xTicksToWait == (TickType_t)0) {uxReturn = uxCurrentEventBits;}/* (6) 需要阻塞等待 */else {/* 设置控制位 */if (xClearOnExit != pdFALSE) {uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;}if (xWaitForAllBits != pdFALSE) {uxControlBits |= eventWAIT_FOR_ALL_BITS;}/* (7) 将任务加入事件等待列表,任务将被阻塞指定时间 xTicksToWait,并且这个列表项的值是用于保存任务等待事件需求的信息标记,以便在事件标志位置位的时候对等待事件的任务进行相应的操作。 */vTaskPlaceOnUnorderedEventList(&(pxEventBits->xTasksWaitingForBits),(uxBitsToWaitFor | uxControlBits),xTicksToWait);uxReturn = 0;traceEVENT_GROUP_WAIT_BITS_BLOCK(xEventGroup, uxBitsToWaitFor);}}xAlreadyYielded = xTaskResumeAll();  /* (8) 恢复调度器 */if (xTicksToWait != (TickType_t)0) {if (xAlreadyYielded == pdFALSE) {portYIELD_WITHIN_API();  /* (9) 触发任务切换 */}/* (10) 获取被唤醒时的事件值 */uxReturn = uxTaskResetEventItemValue();if ((uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET) == (EventBits_t)0) {  /* (11) 超时唤醒 */taskENTER_CRITICAL();{uxReturn = pxEventBits->uxEventBits;/* (12) 再次检查事件条件 */if (prvTestWaitCondition(uxReturn, uxBitsToWaitFor, xWaitForAllBits) != pdFALSE) {if (xClearOnExit != pdFALSE) {pxEventBits->uxEventBits &= ~uxBitsToWaitFor;  /* (13) 清除事件位 */}}}taskEXIT_CRITICAL();xTimeoutOccurred = pdFALSE;}/* (14) 过滤控制位 */uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;}traceEVENT_GROUP_WAIT_BITS_END(xEventGroup, uxBitsToWaitFor, xTimeoutOccurred);return uxReturn;
}
/*-----------------------------------------------------------*/

        举个例子来看一下其实际的调用,改代码是在5.4的代码基础上进行添加的:

        首先为了方便观察实验现象我们在LED.c里面先去创建一个电平翻转函数,主要作用就是每次调用能够进行一次电平翻转,观察灯珠的亮灭,来查看实验现象,记得在.h文件声明一下:

//红灯电平翻转
void Toggle_LED_R(void)
{BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}

        然后我们回到main函数,既然观察电平翻转,那么我们就创建一个LED的任务,首先创建一个LED的任务句柄:

static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task任务句柄 */

        然后是LED的任务主体:

//LED_Task任务主体
static void LED_Task(void* parameter)
{	EventBits_t r_event;  /* 定义一个事件接收变量 *//* 任务都是一个无限循环,不能返回 */while (1){/******************************************************************** 等待接收事件标志 * * 如果xClearOnExit设置为pdTRUE,那么在xEventGroupWaitBits()返回之前,* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置* 的uxBitsToWaitFor中的任何位都将被清除。 * 如果xClearOnExit设置为pdFALSE,* 则在调用xEventGroupWaitBits()时,不会更改事件组中设置的位。** xWaitForAllBits如果xWaitForAllBits设置为pdTRUE,则当uxBitsToWaitFor中* 的所有位都设置或指定的块时间到期时,xEventGroupWaitBits()才返回。 * 如果xWaitForAllBits设置为pdFALSE,则当设置uxBitsToWaitFor中设置的任何* 一个位置1 或指定的块时间到期时,xEventGroupWaitBits()都会返回。 * 阻塞时间由xTicksToWait参数指定。          *********************************************************/r_event = xEventGroupWaitBits(Event_Handle,  /* 事件对象句柄 */KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */pdTRUE,   /* 退出时清除事件位 */pdTRUE,   /* 满足感兴趣的所有事件 */portMAX_DELAY);/* 指定超时事件,一直等 */if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) {/* 如果接收完成并且正确 */printf ( "\r\nKEY1与KEY2都按下!\r\n");		Toggle_LED_R();       //电平反转}elseprintf ( "事件错误!\r\n");	}
}

不在函数代码中,仅解释,这些宏定义是FreeRTOS内部封装好的:

#define pdTRUE      ( ( BaseType_t ) 1 )  // 代表逻辑真
#define pdFALSE     ( ( BaseType_t ) 0 )  // 代表逻辑假

        在 FreeRTOS 中,pdTRUE 是一个宏定义,表示逻辑真(true),通常用于布尔类型的参数或返回值。它的值通常是 1,而对应的 pdFALSE 表示逻辑假(false),通常是 0。

#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

        portMAX_DELAY 是 FreeRTOS 的一个宏,通常定义为 0xFFFFFFFF(或类似的最大值),表示 任务可以无限期阻塞,直到事件发生。

        如果希望任务 有限时间等待,可以设置一个具体的 tick 值(如 100 / portTICK_PERIOD_MS 表示 100ms)。

r_event = xEventGroupWaitBits(Event_Handle,KEY1_EVENT | KEY2_EVENT,pdTRUE,   // 退出时清除事件位pdTRUE,   // 需要所有事件都发生pdMS_TO_TICKS(1000)  // 等待 1000ms,超时后返回
);

        回归正题,既然任务主体创建好了,那么我们还需要对LED分配好栈空间,优先级等参数,找到AppTaskCreate()函数,添加:

  /* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char*    )"LED_Task",/* 任务名字 */(uint16_t       )512,   /* 任务栈大小 */(void*          )NULL,	/* 任务入口函数参数 */(UBaseType_t    )2,	    /* 任务的优先级 */(TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");

        如:

//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");  elseprintf("Event_Handle 事件创建失败!\r\n");  	/* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char*    )"LED_Task",/* 任务名字 */(uint16_t       )512,   /* 任务栈大小 */(void*          )NULL,	/* 任务入口函数参数 */(UBaseType_t    )2,	    /* 任务的优先级 */(TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */(const char*    )"KEY_Task",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )3, /* 任务的优先级 */(TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n");	vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}

        完整的main函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h"  /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
static EventGroupHandle_t Event_Handle =NULL;/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*/
#define KEY1_EVENT  (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT  (0x01 << 1)//设置事件掩码的位1//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */static void LED_Task(void* pvParameters);/* LED_Task 任务实现 */
static void KEY_Task(void* pvParameters);/* KEY_Task 任务实现 */static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */(const char*    )"AppTaskCreate",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )1, /* 任务的优先级 */(TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */           if(pdPASS == xReturn)vTaskStartScheduler();   /* 启动任务,开启调度 */elsereturn -1;  }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL();           //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate();	 if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n");  elseprintf("Event_Handle 事件创建失败!\r\n");  	/* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char*    )"LED_Task",/* 任务名字 */(uint16_t       )512,   /* 任务栈大小 */(void*          )NULL,	/* 任务入口函数参数 */(UBaseType_t    )2,	    /* 任务的优先级 */(TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */(const char*    )"KEY_Task",/* 任务名字 */(uint16_t       )512,  /* 任务栈大小 */(void*          )NULL,/* 任务入口函数参数 */(UBaseType_t    )3, /* 任务的优先级 */(TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n");	vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL();            //退出临界区
}//LED_Task任务主体
static void LED_Task(void* parameter)
{	EventBits_t r_event;  /* 定义一个事件接收变量 *//* 任务都是一个无限循环,不能返回 */while (1){/******************************************************************** 等待接收事件标志 * * 如果xClearOnExit设置为pdTRUE,那么在xEventGroupWaitBits()返回之前,* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置* 的uxBitsToWaitFor中的任何位都将被清除。 * 如果xClearOnExit设置为pdFALSE,* 则在调用xEventGroupWaitBits()时,不会更改事件组中设置的位。** xWaitForAllBits如果xWaitForAllBits设置为pdTRUE,则当uxBitsToWaitFor中* 的所有位都设置或指定的块时间到期时,xEventGroupWaitBits()才返回。 * 如果xWaitForAllBits设置为pdFALSE,则当设置uxBitsToWaitFor中设置的任何* 一个位置1 或指定的块时间到期时,xEventGroupWaitBits()都会返回。 * 阻塞时间由xTicksToWait参数指定。          *********************************************************/r_event = xEventGroupWaitBits(Event_Handle,  /* 事件对象句柄 */KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */pdTRUE,   /* 退出时清除事件位 */pdTRUE,   /* 满足感兴趣的所有事件 */portMAX_DELAY);/* 指定超时事件,一直等 */if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) {/* 如果接收完成并且正确 */printf ( "\r\nKEY1与KEY2都按下!\r\n");		Toggle_LED_R();       //电平反转}elseprintf ( "事件错误!\r\n");	}
}//KEY_Task任务主体
static void KEY_Task(void* parameter)
{	 /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       //如果KEY2被单击{printf ( "\r\nKEY1被按下!\r\n" );/* 触发一个事件1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT);  					}if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       //如果KEY2被单击{printf ( "\r\nKEY2被按下!\r\n" );	/* 触发一个事件2 */xEventGroupSetBits(Event_Handle,KEY2_EVENT); 				}vTaskDelay(20);     //每20ms扫描一次		}
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化	*/USART_Config();//按键初始化Key_GPIO_Config();}

        运行结果:

        可以看出必须两个按键都被按下才会正常运行,如果仅有一个按键按下并不会满足条件,我们才试试不一直等待的情况,这里其他地方不用动,只更改主体代码:

//LED_Task任务主体
static void LED_Task(void* parameter)
{	EventBits_t r_event;  /* 定义一个事件接收变量 *//* 任务都是一个无限循环,不能返回 */while (1){/******************************************************************** 等待接收事件标志 * * 如果xClearOnExit设置为pdTRUE,那么在xEventGroupWaitBits()返回之前,* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置* 的uxBitsToWaitFor中的任何位都将被清除。 * 如果xClearOnExit设置为pdFALSE,* 则在调用xEventGroupWaitBits()时,不会更改事件组中设置的位。** xWaitForAllBits如果xWaitForAllBits设置为pdTRUE,则当uxBitsToWaitFor中* 的所有位都设置或指定的块时间到期时,xEventGroupWaitBits()才返回。 * 如果xWaitForAllBits设置为pdFALSE,则当设置uxBitsToWaitFor中设置的任何* 一个位置1 或指定的块时间到期时,xEventGroupWaitBits()都会返回。 * 阻塞时间由xTicksToWait参数指定。          *********************************************************/r_event = xEventGroupWaitBits(Event_Handle,  /* 事件对象句柄 */KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */pdTRUE,   /* 退出时清除事件位 */pdTRUE,   /* 满足感兴趣的所有事件 */
//                                  portMAX_DELAY/* 指定超时事件,一直等 */pdMS_TO_TICKS(5000)  // 等待 1000ms,超时后返回);if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) {/* 如果接收完成并且正确 */printf ( "\r\nKEY1与KEY2都按下!\r\n");		Toggle_LED_R();       //电平反转}elseprintf("等待超时或事件错误!\r\n"); 	}
}

        这里每隔5s进行一次检测,若是仅有一个按键按下或者没有按键按下,则会返回超时错误,只有两个按键都被按下才会正常显示:

完整工程:

FreeRTOS事件-等待事件函数.zip资源-CSDN文库

5.6  xEventGroupClearBits()与 xEventGroupClearBitsFromISR()

        xEventGroupClearBits()与 xEventGroupClearBitsFromISR()都是用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除,xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能的xEventGroupClearBitsFromISR() 来代替,中断清除事件标志位的操作在守护任务(也叫定时器服务任务) 里面完成。守护进程的优先级由 FreeRTOSConfig.h 中的宏configTIMER_TASK_PRIORITY 来定义 。 要想使用该函数必须把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。

函数原型

EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,

                                                        const EventBits_t uxBitsToClear);

BaseType_t xEventGroupClearBitsFromISR(EventGroupHandlet xEventGroup,

                                                                       const EventBits_t uxBitsToClear);

功能清除事件组中指定的位。
参数xEventGroup事件句柄。
uxBitsToClear

指定事件组中的哪个位需要清除。如设置uxBitsToSet为0x08则只清除位3,如果设置uxBitsToSet为0x09 则位3和位 0 都需要被清除。

返回值事件在还没有清除指定位之前的值。

        简单举个例子:

#define BIT_0 (1 << 0)
#define BIT_4 (1 << 4)void aFunction(EventGroupHandle_t xEventGroup) 
{EventBits_t uxBits;/* 清除事件组的 bit 0 和 bit 4 */uxBits = xEventGroupClearBits(xEventGroup, BIT_0 | BIT_4);if ((uxBits & (BIT_0 | BIT_4)) == (BIT_0 | BIT_4)) {/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都置位但是现在是被清除了 */} else if ((uxBits & BIT_0) != 0) {/* 在调用 xEventGroupClearBits()之前 bit0 已经置位但是现在是被清除了 */} else if ((uxBits & BIT_4) != 0) {/* 在调用 xEventGroupClearBits()之前 bit4 已经置位但是现在是被清除了 */} else {/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都没被置位 */}
}

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

相关文章:

FreeRTOS菜鸟入门(十四)·事件

目录 1. 基本概念 2. 应用场景 3. 运作机制 4. 控制块 5. 事件函数接口 5.1 事件创建函数 xEventGroupCreate() 5.2 事件删除函数 vEventGroupDelete() 5.3 事件组置位函数 xEventGroupSetBits()&#xff08;非中断&#xff09; 5.4 事件组置位函数 xEventGr…...

setData执行后操作方法-微信小程序

在微信小程序中&#xff0c;setData 是异步执行的&#xff0c;如果你需要在 setData 执行完毕后执行某些操作&#xff0c;可以通过以下几种方式实现&#xff1a; 1. 使用 setData 的回调函数 从基础库 2.2.3 开始&#xff0c;setData 支持传入回调函数&#xff0c;回调会在数据…...

SpringAI特性

一、SpringAI 顾问&#xff08;Advisors&#xff09; Spring AI 使用 Advisors机制来增强 AI 的能力&#xff0c;可以理解为一系列可插拔的拦截器&#xff0c;在调用 AI 前和调用 AI 后可以执行一些额外的操作&#xff0c;比如&#xff1a; 前置增强&#xff1a;调用 AI 前改…...

捌拾叁- 量子傅里叶变换

1. 前言 最近公司地震&#xff0c;现在稍微有点时间继续学习。 看了几个算法&#xff0c;都说是基于 量子傅里叶变换 &#xff0c;好&#xff0c;就是他了 Quantum Fourier。 2. 傅里叶变换 大学是学通信的&#xff0c;对于傅里叶变换还是有所理解的。其实就是基于一个 时域…...

SSTI模版注入

1、概念 SSTI是一种常见的Web安全漏洞&#xff0c;它允许攻击者通过注入恶意模板代码&#xff0c;使服务器在渲染模板时执行非预期的操作。 &#xff08;1&#xff09;渲染模版 至于什么是渲染模版&#xff1a;服务器端渲染模板是一种Web开发技术&#xff0c;它允许在服务器端…...

33、前台搜索功能怎么实现?

输入搜索的东西&#xff0c;如果为空 如果有 前端是提交表单&#xff0c;方式是 post 后端接受 调用 mybatisplus的categoryService.getById 用户在搜索框内输入关键字之后&#xff0c;执行 js 中的 load方法&#xff0c;前端提交表单&#xff0c; 后端 controller 中的loa…...

量化解析美英协议的非对称冲击:多因子模型与波动率曲面重构

摘要&#xff1a;基于机器学习算法对市场微观结构的实时监测&#xff0c;黄金价格在3300美元/盎司附近展开技术性反弹。本文通过多因子分析框架&#xff0c;解析美元指数上行、贸易政策突变及资产配置迁移对贵金属市场的复合影响&#xff0c;并构建基于LSTM神经网络的动态支撑位…...

对PyTorch模块进行性能分析

以下是针对PyTorch模块进行性能分析的完整方法与工具指南&#xff0c;结合了多种优化策略和实际应用场景&#xff1a; 一、PyTorch性能分析工具 PyTorch Profiler • 功能&#xff1a;内置的性能分析工具&#xff0c;支持捕获CPU/GPU操作、内存分配、数据形状及硬件利用率。 …...

lvm详细笔记

LVM简介 逻辑卷管理器&#xff0c;是Linux 系统中用于管理磁盘储存的关键技术。 LVM 则打破了磁盘分区一旦确定&#xff0c;其大小调整往往较为复杂&#xff0c;且难以灵活应对业务变化这种限制&#xff0c;它允许用户将多个物理分区组合卷组。例如&#xff0c;系统中的多个物…...

OpenHarmony 以太网卡热插拔事件接口无效

目录 1.背景 2.解决方案 1.背景 在OpenHarmony中调用以太网热插拔时间&#xff0c;发现热插拔没有任何回调&#xff0c;如下接口 import { ethernet } from kit.NetworkKit;ethernet.on(interfaceStateChange, (data: object) > {console.log(on interfaceSharingStateCha…...

SPDK NVMe of RDMA 部署

使用SPDK NVMe of RDMA 实现多NVMe设备共享 一、编译、安装spdk 1.1、下载 1.1.1 下载spdk源码 首先&#xff0c;我们需要从GitHub上克隆SPDK的源码仓库。打开终端&#xff0c;输入以下命令&#xff1a; git clone -b v22.01 https://github.com/spdk/spdk.git cd spdk1.1.2…...

Go语言的逃逸分析是怎么进行的

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…...

纯净IP,跨境账号稳定的底层逻辑

在跨境业务快速扩张的背景下&#xff0c;越来越多的卖家、营销人、数据团队都开始使用代理IP来实现全球网络触达。然而&#xff0c;账号封禁问题始终如影随形&#xff0c;而背后的一个“隐性元凶”常常被忽视——纯净IP的缺失。本文将从实战角度出发&#xff0c;带你深入了解什…...

编译日志:关于编译opencv带有ffmpeg视频解码支持的若干办法

编译日志&#xff1a;关于编译opencv带有ffmpeg视频解码支持的若干办法 前言 ​ 笔者这里是封装了简单的OpenCV视频播放抽象&#xff0c;然后却发现移植到Ubuntu和开发板上都罢工的事情&#xff0c;原来是Windows平台下我们是默认下载了ffmpeg的库的&#xff0c;但是在泛Linu…...

djinn: 3靶场渗透

djinn: 3 来自 <https://www.vulnhub.com/entry/djinn-3,492/> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182&#xff0c;靶场IP192.168.23.243 3&#xff0…...

WHAT - 简单服务发现

文章目录 简单理解举个例子简单服务发现方式1. 静态配置&#xff08;最简单&#xff0c;但不灵活&#xff09;2. DNS 发现3. 使用服务注册中心&#xff08;稍高级&#xff09; 总结 “简单服务发现”&#xff08;Simple Service Discovery&#xff09;通常指的是一种让系统中的…...

auto推导类型原则

auto 是 C11 引入的类型自动推导关键字&#xff0c;它允许编译器根据表达式的类型来推导变量的确切类型。虽然使用 auto 可以让代码更简洁&#xff0c;但理解它的类型推导规则非常关键&#xff0c;尤其是在涉及指针、引用、const、模板等场景时。 ✅ 一、基本推导原则 auto x …...

44.辐射发射整改简易摸底测试方法

辐射发射整改简易摸底测试方法 1. 正式摸底预测试2. 简易方法预测试3. 分析频谱4. 探查传播路径5. 施加措施6. 与简易方法预测试效果对比 1. 正式摸底预测试 去正式实验室做一次预测试&#xff0c;取得频谱图&#xff1b;确定超标频点和超标量&#xff08;备用&#xff09;。 …...

初识C++:入门基础(二)

概述&#xff1a;该篇博客主要介绍C的缺省函数、函数重载、和引用等知识。 目录 1. 缺省参数 2. 函数重载 3. 引用 3.1 引用的概念和定义 3.2 引用的特性 3.3 引用的使用 3.4 const引用 3.5 指针和引用的关系 4. nullptr 5. 小结 1. 缺省参数 缺省参数是声明或定义函…...

我国脑机接口市场规模将破38亿元,医疗领域成关键突破口

当人类仅凭"意念"就能操控无人机编队飞行&#xff0c;当瘫痪患者通过"脑控"重新站立行走&#xff0c;这些曾只存在于科幻电影的场景&#xff0c;如今正通过脑机接口技术变为现实。作为"十四五"规划中重点发展的前沿科技&#xff0c;我国脑机接口…...

笔记,麦克风的灵敏度

麦克风的“灵敏度&#xff08;Sensitivity&#xff09;”决定了它捕捉声音细节的能力。想象麦克风是一只有耳朵的生物。高灵敏度麦克风像长着“超级顺风耳”的精灵&#xff0c;能听见花瓣飘落的声音、远处树叶的沙沙声&#xff0c;甚至你心跳的微弱震动。适合录音棚里捕捉歌手的…...

国产linux系统(银河麒麟,统信uos)使用 PageOffice 在线打开Word文件,并用前端对话框实现填空填表

不管是政府机关、公司企业&#xff0c;还是金融行业、教育行业等单位&#xff0c;在办公过程中都经常需要填写各种文书和表格&#xff0c;比如通知、报告、登记表、计划表、申请表等。这些文书和表格往往是用Word文件制作的模板&#xff0c;比方说一个通知模板中经常会有“关于…...

AKS 支持 Kata Container容器沙盒 -预览阶段

您准备好提升您的云和 DevOps 技能了吗&#xff1f; &#x1f425;《云原生devops》专门为您打造&#xff0c;我们精心打造的数十篇文章库&#xff0c;这些文章涵盖了 Azure、AWS 和 DevOps 方法论的众多重要主题。无论您是希望精进专业知识的资深专业人士&#xff0c;还是渴望…...

Ubuntu通过源码编译方式单独安装python3.12

1、安装依赖 sudo apt-get install zlib1g sudo apt-get install zlib1g-dev sudo apt-get install openssl sudo apt-get install libssl-dev2、编译源码并安装 wget https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tar.xz tar -xvf Python-3.12.0.tar.xz cd Pyth…...

【某OTA网站】phantom-token 1004

新版1004 phantom-token 请求头中包含phantom-token 定位到 window.signature 熟悉的vmp 和xhs一样 最新环境检测点 最新检测 canvas 下的 toDataURL方法较严 过程中 会用setAttribute给canvas 设置width height 从而使toDataURL返回不同的值 如果写死toDataURL的返回值…...

游戏引擎学习第266天:添加顶部时钟概览视图。

简要讨论使用第三方调试工具或在多个项目中复用自己的调试工具 今天的工作主要是在提升调试界面的可用性和美观性。昨天已经整理了布局代码&#xff0c;今天的目标是继续优化调试界面&#xff0c;使其更易用。我们已经为调试工具添加了许多新功能&#xff0c;并且在实现过程中…...

霸王茶姬微信小程序自动化签到系统完整实现解析

霸王茶姬微信小程序自动化签到系统完整实现解析 技术栈&#xff1a;Node.js 微信小程序API MD5动态签名 一、脚本全景架构 功能模块图 #mermaid-svg-0vx5W2xo0IZWn6mH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-s…...

AI与自然语言处理(NLP):从BERT到GPT的演进

AI与自然语言处理&#xff08;NLP&#xff09;&#xff1a;从BERT到GPT的演进 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 AI与自然语言处理&#xff08;NLP&#xff09;&#xff1a;从BERT到GPT的演进摘要引言…...

TRO高能预警,Keith律所×Tane Hannah Meets版权两案王炸维权

紧急避雷贴&#xff0c;keith律所代理Tane Hannah Meets 10个版权连发两案突袭跨境圈&#xff01;案件详情如下&#xff1a; 案件基本情况&#xff1a; 起诉时间&#xff1a;2025-5-8 案件号&#xff1a;25-cv-05079、25-cv-05088 品牌&#xff1a;Tane Meets Works 原告&…...

在 Spring Boot 中选择合适的 HTTP 客户端

在现代的 Spring Boot 应用程序中&#xff0c;与外部服务进行 HTTP 通信是一个常见的需求。Spring Boot 提供了多种方式来实现 HTTP 请求&#xff0c;包括 Java 的 HttpClient、Spring 的 RestTemplate、WebClient&#xff0c;以及第三方库如 Apache HttpClient。本文将详细介绍…...

Go语言中 源文件开头的 // +build 注释的用法

// build注释主要用于实现条件编译。借助设置不同的构建标签&#xff08;build tags&#xff09;&#xff0c;我们能够指定在特定的操作系统、架构或者其他自定义条件下才编译某个文件 1、基本规则 格式要求&#xff1a; 这种注释必须出现在文件的开头部分。注释与包声明之间至…...

C++八股 —— map/unordered_map

1. 底层数据结构 map —— 红黑树 随处可见的红黑树&#xff1a;原理、实现及应用场景 - 知乎 unordered_map —— 散列表 [C] 哈希表&#xff08;散列表&#xff09;详解_c哈希表-CSDN博客 2. 常见面试题 底层为红黑树的容器有哪些 mapmultimapsetmultiset 红黑树和AVL树…...

PostgreSQL创建只读账号

环境说明 部署环境&#xff1a;linux服务器docker容器部署 数据库&#xff1a;PostgreSQL 成果 只读账号/密码&#xff1a;read_only/xxx123 授权可读库&#xff1a;xxx、xxxdata 操作过程 #进入PostgreSQL容器&#xff08;如果你没有用docker容器忽略这一步就可以了&#xff…...

DVWA靶场保姆级通关教程--06不安全验证机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 目录 文章目录 前言 原理详解 1. 前后端验证逻辑不一致 2. 验证码值保存在客户端 3. 验证码可预测或重复 4. 验证码验证与逻辑解耦 一、处理关卡报错 二、low级别源…...

【日撸 Java 三百行】Day 7(Java的数组与矩阵元素相加)

目录 Day 7&#xff1a;Java 的数组与矩阵元素相加 一、基本知识 二、矩阵的建立与基本计算 三、代码及测试 拓展&#xff1a;Arrays类详解 小结 Day 7&#xff1a;Java 的数组与矩阵元素相加 Task&#xff1a; 矩阵的赋值.二重循环. 一、基本知识 在学习 Java 中的数组与矩…...

【递归、搜索和回溯】递归、搜索和回溯介绍及递归类算法例题

个人主页 &#xff1a; zxctscl 专栏 【C】、 【C语言】、 【Linux】、 【数据结构】、 【算法】 如有转载请先通知 文章目录 递归、搜索和回溯递归搜索VS 深度优先遍历 VS 深度优先搜索 VS 宽度优先遍历 VS 宽度优先搜索 VS 暴搜回溯与剪枝 1 面试题 08.06. 汉诺塔问题1.1 分析…...

2025最新精选5款3DMAX建筑可视化插件

在3DMAX建筑可视化领域&#xff0c;各类工具如同繁星般璀璨&#xff0c;它们为设计师们搭建起通往理想作品的桥梁。今天&#xff0c;就让我们一同走进几款极具特色的工具——RetailStore插件、2Dto3D插件、DrawFloorPlan插件、MaxToCAD插件以及EXR透视贴图技术&#xff0c;探寻…...

麒麟系统使用-个性化设置

文章目录 前言一、个性化设置-背景二、个性化设置-主题三、个性化设置-锁屏四、个性化设置-屏保五、个性化设置-字体总结 前言 与windows系统相比&#xff0c;麒麟系统中的个性化设置大体相似&#xff0c;在细节上稍有不同。本文将讲述麒麟系统中的个性化设置中的各个模块。 一…...

flask开启https服务支持

目录 一、背景 二、开启https支持 三、自签名 1、安装openssl 2、验证安装 3、自签名 四、编写代码 五、访问https接口 一、背景 最近在做自动化业务&#xff0c;需要兼容现在主流的框架开发的前端页面&#xff0c;于是到github找到了几个项目&#xff0c;clone下来项目并…...

CNN处理图片

In [5]: 123456789101112131415161718192021222324252627282930313233import tensorflow as tf​import matplotlib.pyplot as plt​import numpy as np​​​​# 平滑均值滤波#KaTeX parse error: Expected EOF, got _ at position 14: \text {filter_̲{arg}np.}.full (sha…...

【Bootstrap V4系列】学习入门教程之 组件-表单(Forms)

Bootstrap V4系列 学习入门教程之 组件-表单&#xff08;Forms&#xff09; 表单&#xff08;Forms&#xff09;一、Overview二、Form controls 表单控件2.1 Sizing 尺寸2.2 Readonly 只读2.3 Readonly plain text 只读纯文本 三、Checkboxes and radios 复选框和单选框3.1 Def…...

图像来源:基于协同推理的双视角超声造影分类隐式数据增强方法|文献速递-深度学习医疗AI最新文献

Title 题目 Image by co-reasoning: A collaborative reasoning-based implicit data augmentation method for dual-view CEUS classification 图像来源&#xff1a;基于协同推理的双视角超声造影分类隐式数据增强方法 01 文献速递介绍 结合了B型超声&#xff08;BUS&…...

MySQL性能分析工具:SHOW PROCESSLIST

概述 以下是一份详细的 MySQL SHOW PROCESSLIST 使用教程&#xff0c;帮助您监控和分析数据库当前活动&#xff0c;快速定位性能问题资料已经分类整理好&#xff0c;喜欢的朋友自取&#xff1a;https://pan.quark.cn/s/f52968c518d3 1. 命令基本作用 SHOW PROCESSLIST 显示当…...

Linux网络编程day8本地套接字

本地套接字 利用cs模型实现本地套接字完成进程间通信 对比网络编程TCP C/S模型&#xff0c;注意以下几点 1、int socket(int domain, int type, int protocol); domain-->AF_INET改为AF_UNIX , type都可写SOCK_STREAM/SOCK_DGRAM2、地址结构 sockaddr_in -->sockadd…...

使用 React Native实现鸿蒙开发的详细方案

一、环境准备 1. 基础环境要求 操作系统:Windows 10/11 或 macOS (建议版本最新)Node.js: v16.x 或更高版本npm: v8.x 或更高版本Java JDK: 11 或更高版本DevEco Studio: 3.1 或更高版本 (鸿蒙官方IDE)2. 安装 DevEco Studio 从华为开发者官网下载安装时选择以下组件: Harmo…...

WebRTC流媒体传输协议RTP点到点传输协议介绍,WebRTC为什么使用RTP协议传输音视频流?

通过上一章《WebRTC工作原理详细介绍、WebRTC信令交互过程和WebRTC流媒体传输协议介绍》&#xff0c;我们知道WEBRTC在完成 SDP 协商和 ICE 候选交换信令后&#xff0c;双方就可以建立 RTP 流&#xff0c;开始传输音视频数据&#xff0c;这时&#xff0c;RTP 数据包就通过在 IC…...

【Unity笔记】PathCreator使用教程:用PathCreator实现自定义轨迹动画与路径控制

在Unity开发过程中&#xff0c;角色移动、摄像机动画、轨道系统、AI巡逻等功能中&#xff0c;路径控制是常见又复杂的需求之一。如何优雅、高效地创建路径并控制对象沿路径运动&#xff0c;是游戏开发、动画制作乃至工业仿真中的关键问题。 在这篇文章中&#xff0c;我将介绍一…...

生产安全管理系统标杆

生产安全不容小视&#xff0c;防患于未然是企业安全生产的基石。好的安全预防系统能让隐患产生最初就被扼杀在摇篮里。国内一些好的生产安全防范系统也有很多&#xff0c;今天我们主要介绍一下众联心安这款产品。 安全生产管理,目标制度管理,风险隐患管理,应急安全管理,设备设…...

temu采购自养号全流程解析:从账号搭建到安全下单的技术闭环

temu 自养号采购下单技术是一个精细的过程&#xff0c;需要从多个方面进行考虑和操作&#xff0c;其核心在于通过技术手段模拟真实用户行为&#xff0c;构建独立、安全的账号环境以确保账号的安全性、真实性和采购下单的成功率。以下是对该技术的详细解析 1. 账号准备 手机号…...

MySQl 数据库操作

目录 一、MySQL 数据库介绍 二、MySQl 库操作 1. 系统数据库 2. 数据库操作 &#xff08;1&#xff09;创建数据库 &#xff08;2&#xff09;数据库命名规则 &#xff08;3&#xff09;选择数据库 &#xff08;4&#xff09;查看数据库 &#xff08;5&#xff09;删除…...