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

细说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灯不亮。

http://www.xdnf.cn/news/410149.html

相关文章:

  • C++核心编程解析:模板、容器与异常处理全指南
  • AIGC时代的内容安全:AI检测技术如何应对新型风险挑战?
  • 【八股消消乐】慢SQL优化手段总结
  • Claude深度解析:从技术原理到实战应用的全栈指南
  • 大模型剪枝技术介绍
  • Kotlin 懒初始化值
  • Android音频解码中的时钟同步问题:原理、挑战与解决方案
  • 基于SpringBoot3实现MyBatis-Plus两种条件构造器(QueryWrapper、UpdateWrapper)入门实战
  • AI工具分享篇|VDraw.ai免费生成长图
  • 第十部分:文件与动静态库
  • C# 基础 try-catch代码块
  • Hugging Face推出了一款免费AI代理工具,它能像人类一样使用电脑
  • 蓝桥杯13届国赛 2022
  • MySQL的sql_mode详解:从优雅草分发平台故障谈数据库模式配置-优雅草卓伊凡
  • 295. 数据流的中位数解题思路(通俗易懂大小堆解法)
  • PyTorch随机数控制全指南:从种子设置到状态管理
  • 【C++】”如虎添翼“:模板初阶
  • AI-Agent@spring ai概览
  • 动态IP技术赋能业务创新:解锁企业数字化转型新维度
  • 智表 ZCELL 插件快速入门指南(原创)
  • 【Redis】SDS结构
  • Redis的IO多路复用
  • 驾驭智能浪潮:AI SEO赋能的操作指南
  • Swift实战:如何优雅地从二叉搜索树中挑出最接近的K个值
  • C++ 中介者模式详解
  • 【嵌入式系统设计师(软考中级)】第三章:嵌入式系统软件基础知识——①软件及操作系统基础
  • 需求变更控制不严,如何防止项目范围扩大
  • CATIA高效工作指南——常规配置篇(二)
  • 黑马k8s(四)
  • windows防火墙