细说STM32单片机FreeRTOS事件组及其编程应用实例
目录
一、事件组的原理和功能
1、事件组的功能特点
2、事件组的工作原理
二、事件组相关函数
1、相关函数概述
2、部分函数详解
(1)创建事件组
(2)事件位置位
(3)事件位清零
(4)读取事件组当前值
(5)等待事件组条件成立
三、事件组使用示例
1、示例功能和CubeMX项目设置
(1)RCC、SYS、Code Gennerator、USART3、TIM6、NVIC
(2)FreeRTOS
(3)GPIO
2、程序功能实现
(1)主程序
(2)FreeRTOS对象初始化
3、程序运行测试
事件组(event group)是FreeRTOS中另外一种进程间通信技术,与队列、信号量等进程间通信技术相比,它具有不同的特点。事件组适用于多个事件触发一个或多个任务运行,可以实现事件的广播,还可以实现多个任务的同步运行。
一、事件组的原理和功能
1、事件组的功能特点
队列、信号量等进程间通信技术有如下特点:
- 一次进程间通信通常只处理一个事件,例如,等待一个按键的按下;而不能等待多个事件的发生,例如,等待KeyLeft键和KeyRight键先后按下。如果需要处理多个事件,可能需要分解为多个任务,设置多个信号量。
- 可以有多个任务等待一个事件的发生,但是在事件发生时,只能解除最高优先级的任务的阻塞状态,而不能同时解除多个任务的阻塞状态。也就是说,队列或信号量具有排他性,不能解决某些特定的问题,例如,当某个事件发生时,需要两个或多个任务同时解除阻塞状态做出响应。
事件组是FreeRTOS中另外一种进程间通信技术,与队列和信号量不同,它有自己的一些特点,具体如下:
- 事件组允许任务等待一个或多个事件的组合。例如,先后按下KeyLeft键和KeyRight键,或只按下其中一个键。
- 事件组会解除所有等待同一事件的任务的阻塞状态。例如,TaskA使用LED1闪烁报警,TaskB使用蜂鸣器报警,当报警事件发生时,两个任务同时解除阻塞状态,两个任务都开始运行。
事件组的这些特性使其适用于以下场景:任务等待一组事件中的某个事件发生后做出响应(或运算关系),或一组事件都发生后做出响应(与运算关系);将事件广播给多个任务;多个任务之间的同步。
2、事件组的工作原理
事件组是FreeRTOS中的一种对象,FreeRTOS中默认就是可以使用事件组的,无须设置什么参数。使用之前需要用函数xEventGroupCreate()或xEventGroupCreateStatic()创建事件组对象。
一个事件组对象有一个内部变量存储事件标志,变量的位数与参数configUSE_16_BIT_TICKS有关,当configUSE_16_BIT_TICKS为0时,这个变量是32位的,否则,是16位的。STM32 MCU是32位的,所以事件组内部变量是32位的。
事件标志只能是0或1,用单独的一个位来存储。一个事件组中的所有事件标志保存在一个EventBits_t类型的变量里,所以一个事件又称为一个“事件位”。在一个事件组变量中,如果一个事件位被置为1,就表示这个事件发生了,如果是0,就表示这个事件还未发生。
32位的事件组变量存储结构如图7-1所示。其中的31至24位是保留的,23至0位是事件位(event bits)。每一个位是一个事件标志(event flag),事件发生时,相应的位会被置为1。所以,32位的事件组最多可以处理24个事件。
使用事件组进行多个事件触发任务运行的原理如上图所示,各部分的功能和工作流程如下:
- 设置事件组中的位与某个事件对应,如EventA对应于Bit2,EventB对应于Bit0。在检测到事件发生时,通过函数xEventGroupSetBits()将相应的位置为1,表示事件发生了。
- 可以有1个或多个任务等待事件组中的事件发生,可以是各个事件都发生(事件位的与运算),也可以是某个事件发生(事件位的或运算)。
- 假设图中的Task1和Task2都在阻塞状态等待各自的事件发生,当Bit2和Bit0都被置为1后(不分先后顺序),两个任务都会被解除阻塞状态。所以,事件组具有广播功能;可以使多个任务同时解除阻塞后运行。
除了图中的基本功能,事件组还可以使多个任务同步运行。
二、事件组相关函数
1、相关函数概述
事件组相关的函数在文件event_groups.h中定义,在文件event_groups.c中实现。事件组相关的函数在FreeRTOS中总是可以使用的,无须设置什么参数。
事件组相关的函数清单见下表,这些函数可分为3组。
分组 | 函数 | 功能 |
事件组 | osEventFlagsNew() | CubeMX图形化设置事件组,并自动生成句柄变量和定义的代码 |
xEventGroupCreate() | 以动态分配内存方式创建事件组,用于手动创建事件组 | |
xEventGroupCreateStatic() | 以静态分配内存方式创建事件组,用于手动创建事件组 | |
vEventGroupDelete() | 删除已创建的事件组,用于手动删除事件组 | |
vEventGroupSetNumber() | 给事件组设置编号,编号的作用由用户定义 | |
uxEventGroupGetNumber() | 读取事件组编号 | |
事件位 | xEventGroupSetBit() | 将1个或多个事件位置为1,设置的事件位用掩码表示 |
xEventGroupSetBitsFromISR() | xEventGroupSetBits()的ISR版本 | |
xEventGroupClearBits() | 将某些事件位清零,清零的事件位用掩码表示 | |
xEventGroupClearBitsFromISR() | xEventGroupClearBits()的ISR版本 | |
xEventGroupGetBits() | 返回事件组当前的值 | |
xEventGroupGetBitsFromISR() | xEventGroupGetBits()的ISR版本 | |
等待 | xEventGroupWaitBits() | 任务进入阻塞状态,等待事件组合条件成立后解除阻塞状态 |
xEventGroupSync() | 用于多任务同步 |
- 第一组是操作事件组的函数,包括创建和删除事件组。
- 第二组是操作事件位的函数,包括事件位的置位和清零,或返回事件组当前的值。可以一次操作多个事件位,操作的事件位通过掩码表示。
- 第三组是等待事件发生的函数,任务在等待事件时会进入阻塞状态,在等待的事件条件成立时解除阻塞状态。函数xEventGroupWaitBits()用于一般的事件触发响应,函数xEventGroupSync()专门用于多个任务在某个同步点的同步运行。
2、部分函数详解
(1)创建事件组
osEventFlagsNew()是CubeMX上可视化设置事件组并自动生成事件组句柄和定义的函数,其原型如下:
/// Create and Initialize an Event Flags object.
/// \param[in] attr event flags attributes; NULL: default values.
/// \return event flags ID for reference by other functions or NULL in case of error.
osEventFlagsId_t osEventFlagsNew (const osEventFlagsAttr_t *attr);
除此之外,还可以手动创建事件组,函数xEventGroupCreate()以动态分配内存方式创建事件组,函数xEventGroupCreateStatic()以静态分配内存方式创建事件组。其原型定义如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreate( void )
{//
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
/*-----------------------------------------------------------*/#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreate( void )
{
//
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
/*-----------------------------------------------------------*/
创建事件组无须传递任何参数,函数返回的是所创建事件组的句柄变量,是一个指针变量。其他函数在操作事件组时,都需要使用事件组句柄变量作为输入参数。类型EventGroupHandle_t的定义如下:
/**
* event_groups.h
*
* Type by which event groups are referenced. For example, a call to
* xEventGroupCreate() returns an EventGroupHandle_t variable that can then
* be used as a parameter to other event group functions.
*
* \defgroup EventGroupHandle_t EventGroupHandle_t
* \ingroup EventGroup
*/
struct EventGroupDef_t;
typedef struct EventGroupDef_t * EventGroupHandle_t;
(2)事件位置位
在某些事件发生时,用函数xEventGroupSetBits()在任务函数中将事件组的某些事件位置位,其原型定义如下:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
其中,参数xEventGroup是所操作的事件组句柄;EventBits_t类型的参数uxBitsToSet是需要置位的事件位掩码。函数的返回值类型是EventBits_t,是置位成功后事件组当前的值。
特别地,当类型EventBits_t定义为TickType_t类型时,在STM32上等同于类型uint32_t,即:typedef TickType_t EventBits_t;
这个函数的使用关键是掩码uxBitsToSet的设置,需要置位的事件位在掩码中用1表示,其他位用0表示。事件位的编号如本文第一个附图所示,如果需要置位事件组中的Bit7,则掩码是0x80;如果需要同时置位Bit7和Bit0,则掩码是0×81。一般情况下,一个事件只对应事件组中的一个事件位,一个事件发生时,只需设置事件组中的一个位。
函数xEventGroupSetBitsFromISR()是在ISR中将事件组的某些事件位置位的函数,根据参数configUSE_TRACE_FACILITY的值(是1还是0),这个函数有两种不同的参数形式。默认情况下,参数configUSE_TRACE_FACILITY的值是1,对应的xEventGroupGetBitsFromISR()函数原型如下:
#if( configUSE_TRACE_FACILITY == 1 )
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#else
#define xEventGroupSetBitsFromISR( xEventGroup, uxBitsToSet, pxHigherPriorityTaskWoken ) xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken )
#endif
对事件组进行置位操作不是一个确定性的操作,因为可能有其他多个任务也在设置事件位。FreeRTOS不允许在中断或临界代码段进行不确定的操作,所以在ISR中对事件组进行置位操作时,FreeRTOS实际上是向定时器守护任务(timer daemon task)发送一个消息,将事件组置位操作延后到定时器守护任务里去执行。
参数pxHigherPriorityTaskWoken是一个返回值,如果定时器守护任务的优先级高于当前运行任务(中断抢占的任务)的优先级,pxHigherPriorityTaskWoken就被设置为pdTRUE,表示在ISR退出之前需要申请进行一次上下文切换。所以,在调用函数xEventGroupSetBitsFromISR()的时候,参数pxHigherPriorityTaskWoken不能直接使用常量pdTRUE或pdFALSE,需要使用一个变量的地址,而且需要初始化为pdFALSE,调用的示意代码如下:
BaseType_t highTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(xEventGroup,uxBitsToSet,&highTaskWoken);
portYIELD_FROM_ISR(highTaskWoken); //申请进行一次任务调度
函数xEventGroupSetBitsFromISR()的返回值是pdTRUE或pdFALSE。返回值为pdTRUE,表示延后处理的消息成功发送给了定时器守护任务,当定时器守护任务的消息队列满时,函数会无法接收新的消息,返回值就是pdFALSE。
(3)事件位清零
函数xEventGroupClearBits()用于在任务函数中将事件组的某些事件位清零,其原型定义如下:
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ) PRIVILEGED_FUNCTION;
其中,参数xEventGroup是所操作的事件组的句柄,参数uxBitsToClear是需要清零的事件位的掩码,掩码的意义与函数xEventGroupSetBits()中的一样。函数的返回值是事件位被清零之前事件组的值。
函数xEventGroupClearBitsFromISR()是xEventGroupClearBits()的ISR版本,它同样有两种参数形式的版本,当configUSE_TRACE_FACILITY的值为1时,其原型定义如下:
#if( configUSE_TRACE_FACILITY == 1 )
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ) PRIVILEGED_FUNCTION;
#else
#define xEventGroupClearBitsFromISR( xEventGroup, uxBitsToClear ) xTimerPendFunctionCallFromISR( vEventGroupClearBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToClear, NULL )
#endif
在ISR中,事件位清零操作同样会被延后到定时器守护任务中处理,函数的返回值为pdTRUE或pdFALSE。如果返回值为pdTRUE,表示延后处理的消息成功发送给了定时器守护任务,否则就是没有发送成功。
(4)读取事件组当前值
函数xEventGroupGetBits()可以读取事件组的当前值,其原型定义如下:
#define xEventGroupGetBits( xEventGroup ) xEventGroupClearBits( xEventGroup, 0 )
这是个宏函数,实际上就是执行了函数xEventGroupClearBits(),只是传递的事件位掩码是0,也就是不清除任何事件位,而返回事件组当前的值。
函数xEventGroupGetBitsFromISR()用于在ISR中读取事件组的当前值,其原型定义如下:
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );
(5)等待事件组条件成立
函数xEventGroupWaitBits()用于使当前任务进入阻塞状态,等待事件组中多个事件位表示的事件成立。事件组成立的条件可以是多个事件位都被置位(逻辑与运算),也可以是其中一个事件位被置位(逻辑或运算)。函数xEventGroupWaitBits()的原型定义如下:
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )
{//
}
几个参数的意又如下:
- 参数xEventGroup是所操作的事件组的句柄。
- 参数uxBitsToWaitFor是所等待事件位的掩码。如果需要等待某个事件位置1,掩码中相应的位就设置为1。
- 参数xClearOnExit,设定值为pdTRUE或pdFALSE。如果设置为pdTRUE,则当函数在事件组条件成立而退出阻塞状态时,会将掩码uxBitsToWaitFor中指定的所有位全部清零。如果函数是因为超时而退出阻塞状态,那么,即使将xClearOnExit设置为pdTRUE,也不会对事件位清零。
- 参数xWaitForAllBits,设定值为pdTRUE或pdFALSE。如果设置为pdTRUE,表示需要将掩码中所有事件位都置1,条件才算成立(逻辑与运算);如果设置为pdFALSE,表示将掩码中的某个事件位置1,条件就成立(逻辑或运算)。当事件条件成立时,函数就会退出,任务退出阻塞状态。
- 参数xTicksToWait是当前任务进入阻塞状态等待事件成立的超时节拍数。取值为0,表示不等待;取值为portMAX_DELAY,表示无限等待;取值为其他中间数,表示等待的节拍数。当事件组条件成立时,任务会提前退出阻塞状态。
从事件组的事件表示特点,以及xEventGroupWaitBits()的参数设置可知,事件组可以等待多个事件发生后做出响应,而队列或信号量只能对一个事件做出响应。另外,在使用事件组时,可以有多个任务执行函数xEventGroupWaitBits(),等待同一个事件组的同一个条件成立。当事件组条件成立时,多个任务都解除阻塞状态,起到事件广播的作用。而使用队列或信号量时,当事件发生时,只能有一个最高优先级的任务解除阻塞状态。这两点是事件组区别于队列和信号量的主要特点。
函数xEventGroupSync(),也可以使任务进入阻塞状态,等待事件组条件成立,它主要用于在某个同步点对多个任务进行同步。
三、事件组使用示例
1、示例功能和CubeMX项目设置
本示例演示事件组的使用,示例的主要功能和工作流程如下:
- 创建1个事件组和3个任务。
- 在任务Task_ScanKeys中,检测KeyLeft和KeyRight两个按键是否按下,按下时,将事件组中对应的事件位置位。检测到KeyDown键按下时,将事件组清零。
- 任务Task_LED和Task_ADC均等待事件组中两个按键都按下的事件。条件成立时,任务Task_LED使LED1闪烁几次,任务Task_ADC将采集电位器当前数值几次。
- 所有任务重需要人机交互的信息,通过串口发送到串口助手上。
- 引用文件夹KEYLED里的文件。使用方法详见本文作者写的其他文章。
- 继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。
(1)RCC、SYS、Code Gennerator、USART3、TIM6、NVIC
该部分的设置可以参考本文作者发布的其他文章。
(2)FreeRTOS
在SYS组件配置中,请设置TIM6作为HAL基础时钟源。启用FreeRTOS,设置接口为CMSIS_V2,所有参数都保持为默认值。在Tasks and Queues页面设计3个不同优先级的任务,这3个任务的主要属性如下图所示。任务Task_ScanKeys用于检测按键的状态,所以其优先级最高。其他两个任务是对事件组的响应,之所以设置为两个不同的优先级,是为了测试它们是否会被同时解除阻塞状态。
在事件组界面,可以CubeMX里可视化地设计事件组,自动生成事件组的初始程序。
(3)GPIO
根据开发板的原理图,选择使用其上的按键和LED灯,GPIO设置如下:
2、程序功能实现
(1)主程序
完成设置后,在CubeMX中生成代码。在CubeIDE项目中打开项目,自动生成代码并手动添加用户功能代码后,主程序代码如下:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "adc.h"
#include "usart.h"
#include "gpio.h"/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);/*** @brief The application entry point.* @retval int*/
int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_USART3_UART_Init();MX_ADC3_Init();/* USER CODE BEGIN 2 */// Start Menuuint8_t startstr[] = "Demo7_1: Using Event Group.\r\n";HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);uint8_t startstr1[] = "1. Press KeyLeft and KeyRight to activate ADC converter and LED1.\r\n";HAL_UART_Transmit(&huart3,startstr1,sizeof(startstr1),0xFFFF);uint8_t startstr2[] = "2. Press KeyDown to clear events.\r\n\r\n";HAL_UART_Transmit(&huart3,startstr2,sizeof(startstr2),0xFFFF);/* USER CODE END 2 *//* Init scheduler */osKernelInitialize();/* Call init function for freertos objects (in cmsis_os2.c) */MX_FREERTOS_Init();/* Start scheduler */osKernelStart();/* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}
main.c中,其它自动生成的代码不再列举。
(2)FreeRTOS对象初始化
函数MX_FREERTOS_Init()用于创建在CubeMX中设计的3个任务和1个事件组。在文件freertos.c和函数MX_FREERTOS_Init()中,自动生成任务和事件组对象的句柄变量和代码:
自动生成includes:
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
手动添加项目需要的includes:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "usart.h"
#include "adc.h"
#include "event_groups.h" //事件组相关头文件
#include "keyled.h"
/* USER CODE END Includes */
手动添加宏定义:
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BITMASK_KEY_LEFT 0x04 //KeyLeft的事件位掩码,使用Bit2
#define BITMASK_KEY_RIGHT 0x01 //KeyRight的事件位掩码,使用Bit0
/* USER CODE END PD */
自动生成3个任务和1个事件组的句柄变量:
/* Definitions for Task_ADC */
osThreadId_t Task_ADCHandle;
const osThreadAttr_t Task_ADC_attributes = {.name = "Task_ADC",.stack_size = 256 * 4,.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for Task_LED */
osThreadId_t Task_LEDHandle;
const osThreadAttr_t Task_LED_attributes = {.name = "Task_LED",.stack_size = 128 * 4,.priority = (osPriority_t) osPriorityBelowNormal,
};
/* Definitions for Task_ScanKeys */
osThreadId_t Task_ScanKeysHandle;
const osThreadAttr_t Task_ScanKeys_attributes = {.name = "Task_ScanKeys",.stack_size = 128 * 4,.priority = (osPriority_t) osPriorityAboveNormal,
};
/* Definitions for myEvent01 */
osEventFlagsId_t myEvent01Handle;
const osEventFlagsAttr_t myEvent01_attributes = {.name = "myEvent01"
};
事件组相关的函数定义都在头文件event_groups.h中,需要包含此头文件。此头文件定义了一个EventGroupHandle_t类型的变量myEvent01Handle,用作事件组对象句柄,该句柄是自动创建的。
自动生成任务函数和FreeRTOS初始化函数的原型:
void AppTask_ADC(void *argument);
void AppTask_LED(void *argument);
void AppTask_ScanKeys(void *argument);void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
自动生成FreeRTOS初始化函数,创建3个任务函数和1个事件。不需要手动添加框架内的私有函数定义内容:
/*** @brief FreeRTOS initialization* @param None* @retval None*/
void MX_FREERTOS_Init(void)
{/* Create the thread(s) *//* creation of Task_ADC */Task_ADCHandle = osThreadNew(AppTask_ADC, NULL, &Task_ADC_attributes);/* creation of Task_LED */Task_LEDHandle = osThreadNew(AppTask_LED, NULL, &Task_LED_attributes);/* creation of Task_ScanKeys */Task_ScanKeysHandle = osThreadNew(AppTask_ScanKeys, NULL, &Task_ScanKeys_attributes);/* Create the event(s) *//* creation of myEvent01 */myEvent01Handle = osEventFlagsNew(&myEvent01_attributes);}
自动生成3个任务函数定义的框架,需要手动添加任务函数里的代码内容:
/* USER CODE BEGIN Header_AppTask_ADC */
/*** @brief Function implementing the Task_ADC thread.* @param argument: Not used* @retval None*/
/* USER CODE END Header_AppTask_ADC */
void AppTask_ADC(void *argument)
{/* USER CODE BEGIN AppTask_ADC *//* Infinite loop */BaseType_t clearOnExit=pdTRUE; //pdTRUE=退出时清除事件位BaseType_t waitForAllBits=pdTRUE; //等待所有位置1,pdTRUE=逻辑与, pdFALSE=逻辑或EventBits_t bitsToWait=BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT; //Press the left and right buttons at the same time.for(;;){xEventGroupWaitBits(myEvent01Handle, bitsToWait,clearOnExit,waitForAllBits,portMAX_DELAY );for(uint8_t i=0; i<10; i++){HAL_ADC_Start(&hadc3); //必须每次启动时convertif (HAL_ADC_PollForConversion(&hadc3,200)==HAL_OK){// Data in the format of uint32_t cannot be sent through HAL_UART_Transmit(),// because this function can only send byte data in the format of uint8_t.uint32_t val = HAL_ADC_GetValue(&hadc3);printf("Data in ADC3_IN6 = %ld\r\n",val);// Convert the ADC3 sampling value to the engineering value// and then transmit it to the serial port through printf().uint32_t Volt = 3300*val; //mVVolt = Volt >> 12; //除以2^12,转换为工程值printf("Engineering Value = %ld\r\n",Volt );}HAL_ADC_Stop(&hadc3); //可停止也可不停止vTaskDelay(pdMS_TO_TICKS(500));}}/* USER CODE END AppTask_ADC */
}/* USER CODE BEGIN Header_AppTask_LED */
/**
* @brief Function implementing the Task_LED thread.
* 2 key pressed,LED1 shine 10 times。
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED */
void AppTask_LED(void *argument)
{/* USER CODE BEGIN AppTask_LED *//* Infinite loop */BaseType_t clearOnExit=pdTRUE; //pdTRUE=退出时清除事件位BaseType_t waitForAllBits=pdTRUE; //等待所有位置1,pdTRUE=逻辑与, pdFALSE=逻辑或EventBits_t bitsToWait=BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT; //等待的事件位for(;;){xEventGroupWaitBits(myEvent01Handle, bitsToWait,clearOnExit,waitForAllBits,portMAX_DELAY );for(uint8_t i=0; i<10; i++) //使LED1闪烁几次{LED1_Toggle();vTaskDelay(pdMS_TO_TICKS(500));}}/* USER CODE END AppTask_LED */
}/* USER CODE BEGIN Header_AppTask_ScanKeys */
/**
* @brief Function implementing the Task_ScanKeys thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_ScanKeys */
void AppTask_ScanKeys(void *argument)
{/* USER CODE BEGIN AppTask_ScanKeys *//* Infinite loop */KEYS keyCode = KEY_NONE;for(;;){EventBits_t curBits=xEventGroupGetBits(myEvent01Handle); //读取事件组当前值printf("Current event bits = %ld\r\n",curBits);keyCode=ScanPressedKey(50); //最多等待50ms,不能使用参数KEY_WAIT_ALWAYSswitch (keyCode){case KEY_LEFT: //Press S4 to set bit2 to 1.xEventGroupSetBits(myEvent01Handle, BITMASK_KEY_LEFT); //事件位Bit2置位break;case KEY_RIGHT: //Press S5 to set bit2 to 1.xEventGroupSetBits(myEvent01Handle, BITMASK_KEY_RIGHT); //事件位Bit0置位break;case KEY_DOWN: //press S3 to reset all bits.xEventGroupClearBits(myEvent01Handle,BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT); //两个事件位都清零default:break;}if (keyCode == KEY_NONE)vTaskDelay(50); //未按下任何按键,延时不能太长,否则按键响应慢elsevTaskDelay(300); //消除按键后抖动影响,也用于事件调度}/* USER CODE END AppTask_ScanKeys */
}/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END Application */
在函数MX_FREERTOS_Init()中创建了3个任务。文件freertos.c中有3个任务函数的代码框架,让我们根据程序要实现的功能,为3个任务函数编写代码。
这里使用了KeyLeft和KeyRight两个按键事件,对应于事件组中的两个事件位。为使程序便于修改,我们定义了两个事件位掩码,即
#define BITMASK_KEY_LEFT 0x04 //KeyLeft的事件位掩码,使用Bit2位
#define BITMASK_KEY_RIGHT 0x01 //KeyRight的事件位掩码,使用Bit0位
这样定义后,KeyLeft按键事件对应于事件组中的Bit2位,KeyRight按键事件对应于Bit0位。
任务Task_ScanKeys的任务函数里,使用文件keyled.h中定义的函数ScanPressedKey()检测按键输入。注意,这个函数中使用的延时函数是HAL_Delay(),在调用函数ScanPressedKey()时,设置的等待时间不能太长,更不能传递参数KEY_WAIT_ALWAYS,因为任务Task_ScanKeys的优先级在3个任务中是最高的。
Task_ScanKeys任务函数的for循环里,还使用函数xEventGroupGetBits()读取并显示了事件组的当前值。当KeyLeft键或KeyRight键被按下时,调用函数xEventGroupSetBits()将相应的事件位置1。当KeyDown键被按下时,调用函数xEventGroupClearBits()清除两个事件位。
任务Task_LED的任务函数中,使用函数xEventGroupWaitBits()等待事件组中的条件成立,执行的是下面的代码:
BaseType_t clearOnExit = pdTRUE; //pdTRUE=退出时清除事件位
BaseType_t waitForAllBits = pdTRUE; //等待所有位置1,pdTRUE=逻辑与,pdFALSE=逻辑或
EventBitB_t bitsTOWait = BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT; //等待的事件位
xEventGroupwaitBits(eventGroupHandle,bitsToWait,clearOnExit,waitForAl1Bits,portMAX_DELAY);
参数bitsToWait是两个事件位的按位或,其值为0x05。参数waitForAIlBits设置为pdTRUE,表示将这两个事件位都置1,条件才算成立。参数clearOnExit表示事件组条件成立,任务退出阻塞状态时,是否清除事件位。最后的等待节拍数设置为常数portMAX_DELAY,表示一直等待。
任务在使用函数xEventGroupWaitBits()等待事件组条件成立时,一直处于阻塞状态,在条件成立后就退出阻塞状态,执行后面的代码。任务Task_LED在事件组条件成立后,使LED1闪烁几次。任务Task_ADC等待的事件组条件与任务Task_LED的相同,在事件组条件成立后,2个任务均执行几次。
3、程序运行测试
构建项目后,将其下载到开发板并加以测试,运行时可以发现:先后按下KeyLeft键和KeyRight键后,LED1闪烁,采集到的ADC数据在串口助手上连续显示,这说明两个任务都被解除了阻塞状态,虽然两个任务调用xEventGroupWaitBits()函数时,参数clearOnExit的值都设置为了pdTRUE。
在任务函数AppTask_LED()中,如果将调用函数xEventGroupWaitBits()时传递的参数waitForAllBits设置为pdFALSE,触发条件就是事件位的或运算,也就是KeyLeft键或KeyRight键按下时事件成立,LED1闪烁。
下载后或按下S6复位键后,立刻显示启动菜单里的消息,并且,如果什么键也不按下连续滚动 “"Current event bits = 0”,LED1不亮。
继续,如果先按下S5(RightKey),屏幕显示变更为连续显示消息“Current event bits = 1”,LED1仍然不亮。
然后,如果再按下S4(LeftKey)屏幕显示采集到的ADC数值,当前事件位复位,显示消息“Current event bits = 0”,LED1灯闪烁。闪烁次数=for循环次数的1半,采集ADC的次数=for循环次数。
退出for循环后,就连续显示消息“Current event bits = 0”,LED1灯不亮。