基于STM32的FreeRTOS事件组详解

概述

文章对事件组的,应用场景,运作机制,以及事件的创建,删除,等待,置位,同步等操作

文章目录

概述

一、事件标志组简介

1、事件位(事件标志)

2、事件组

3、事件标志组和事件位的数据类型

二、事件的应用场景

三、事件运作机制

四、事件控制块

 五、事件组函数

1.事件创建函数 xEventGroupCreate()

2.事件删除函数 vEventGroupDelete()

3.事件组置位函数 xEventGroupSetBits()(任务)

4.等待事件函数 xEventGroupWaitBits()

5. xEventGroupClearBits()与 xEventGroupClearBitsFromISR()

 六、事件实验

七、实验现象


一、事件标志组简介

1、事件位(事件标志)

事件位用来表明某个事件是否发生,事件位通常用作事件标志,比如下面的几个例子:

● 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有 消息需要处理的时候就可以将这个位(标志)置 0。

● 当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要 从网络发送出去的话就将这个位(标志)置 0。

● 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1。现在不需要向网络中发送 心跳信息,这个位(标志)置 0。

2、事件组

一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问,同样,以上面列出 的三个例子为例:

● 事件标志组的 bit0 表示队列中的消息是否处理掉。

● 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。

● 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。

3、事件标志组和事件位的数据类型

事件标志组的数据类型为 EventGroupHandle_t,当 configUSE_16_BIT_TICKS 为 1 的时候 事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24 个事件位。

事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中,EventBits_t 在 event_groups.h 中有如下定义:

typedef TickType_t EventBits_t;

数据类型 TickType_t 在文件 portmacro.h 中有如下定义:

#if( configUSE_16_BIT_TICKS == 1 )
   typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff 
#else
 typedef uint32_t TickType_t;
 #define portMAX_DELAY ( TickType_t ) 0xffffffffUL
 #define portTICK_TYPE_IS_ATOMIC 1
 #endif

可以看出当 configUSE_16_BIT_TICKS 为 0 的时候 TickType_t 是个 32 位的数据类型,因 此 EventBits_t 也是个 32 位的数据类型。EventBits_t 类型的变量可以存储 24 个事件位,另外的 那高 8 位有其他用。事件位 0 存放在这个变量的 bit0 上,变量的 bit1 就是事件位 1,以此类推。 对于 STM32 来说一个事件标志组最多可以存储 24 个事件位,如图1所示

图1 事件标志组和事件位

二、事件的应用场景

      FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做 标志位,判断某些事件是否发生了,然后根据结果做处理,那很多人又会问了,为什么我 不直接用变量做标志呢,岂不是更好更有效率?非也非也,若是在裸机编程中,用全局变 量是最为有效的方法,这点我不否认,但是在操作系统中,使用全局变量就要考虑以下问 题了:

 如何对全局变量进行保护呢,如何处理多任务同时对它进行访问?

      如何让内核对事件进行有效管理呢?使用全局变量的话,就需要在任务中轮询查 看事件是否发送,这简直就是在浪费 CPU 资源啊,还有等待超时机制,使用全局 变量的话需要用户自己去实现。

所以,在操作系统中,还是使用操作系统给我们提供的通信机制就好了,简单方便还 实用。

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

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

        各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件,任务仅对感 兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的条件,任务将被唤 醒并进行后续的处理动作。

三、事件运作机制

      接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件 接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收 到的 事件 ,这样就需要用户显式清除事 位。

      用户可 以自 定义 通过 传入参 数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。 

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

       清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。 事件不与任务相关联,事件相互独立,一个 32位的变量(事件集合,实际用于表示事 件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表 示该事件类型未发生、1表示该事件类型已经发生),一共 24种事件类型具体见图2

图2事件集合 set(一个 32 位的变量)

     事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的 时候会被唤醒,其过程具体见图3

图3  事件唤醒任务示意图

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

四、事件控制块

       事件标志组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义,具 体见代码清单 20-1 加粗部分。如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就 是 16 位 的 , 其 中 有 8 个 位 用来存储 事 件 组 , 如 果 宏 configUSE_16_BIT_TICKS 定义为 0,那么变量 uxEventBits 就是 32 位的,其中有 24 个位 用来存储事件组,每一位代表一个事件的发生与否,利用逻辑或、逻辑与等实现不同事件 的不同唤醒处理。在 STM32 中,uxEventBits 是 32 位的,所以我们有 24 个位用来实现事件 组。除了事件标志组变量之外,FreeRTOS 还使用了一个链表来记录等待事件的任务,所有 在等待此事件的任务均会被挂载在等待事件列表 xTasksWaitingForBits。

 typedef struct xEventGroupDefinition {
   EventBits_t uxEventBits;
    List_t xTasksWaitingForBits;
 
 #if( configUSE_TRACE_FACILITY == 1 )
   UBaseType_t uxEventGroupNumber;
 #endif
 
 #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) \
   && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
   uint8_t ucStaticallyAllocated;
 #endif
   } EventGroup_t;

 五、事件组函数

1.事件创建函数 xEventGroupCreate()

    事件创建函数,顾名思义,就是创建一个事件,与其他内核对象一样,都是需要先创 建才能使用的资源,FreeRTOS 给我们提供了一个创建事件的函数 xEventGroupCreate(),当 创建一个事件时,系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进 行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。所以,在使用创建函数 之前,我们需要先定义有个事件的句柄。

/* 创建一个事件组,返回它的句柄。
 * 此函数内部会分配事件组结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreate( void );

/* 创建一个事件组,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );

2.事件删除函数 vEventGroupDelete()

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

/*
 * xEventGroup: 事件组句柄,你要删除哪个事件组
 */
void vEventGroupDelete( EventGroupHandle_t xEventGroup )

3.事件组置位函数 xEventGroupSetBits()(任务)

        xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的 任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然 后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对 象标志值匹配,如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位 为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

xEventGroupSetBits()函数说明

xEventGroupSetBits()函数使用实例

#define KEY1_EVENT  (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT  (0x01 << 1)//设置事件掩码的位1
static EventGroupHandle_t Event_Handle =NULL;
Event_Handle = xEventGroupCreate();	 
  if(NULL != Event_Handle)
    printf("Event_Handle 事件创建成功!\r\n");
static void KEY_Task(void* parameter)
{	 
    /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       //如果KEY2被单击
		{
      printf ( "KEY1被按下\n" );
			/* 触发一个事件1 */
			xEventGroupSetBits(Event_Handle,KEY1_EVENT);  					
		}
    
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       //如果KEY2被单击
		{
      printf ( "KEY2被按下\n" );	
			/* 触发一个事件2 */
			xEventGroupSetBits(Event_Handle,KEY2_EVENT); 				
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

4.等待事件函数 xEventGroupWaitBits()

       既然标记了事件的发生,那么我怎么知道他到底有没有发生,这也是需要一个函数来 获 取 事 件 是 否 已 经 发 生 , FreeRTOS 提 供 了 一 个 等 待 指 定 事 件 的 函 数 — — xEventGroupWaitBits(),通过这个函数,任务可以知道事件标志组中的哪些位,有什么事 件发生了,然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个 函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息。 在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任 务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。 当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转 移为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则 返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能 确定的事件值,所以需要判断任务所等待的事件是否真的发生。

xEventGroupWaitBits()函数说明 

xEventGroupWaitBits()使用实例

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 ( "KEY1与KEY2都按下\n");		
      LED1_TOGGLE;       //LED1	反转
    }
    else
      printf ( "事件错误!\n");	
  }
}

5. xEventGroupClearBits()与 xEventGroupClearBitsFromISR()

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

xEventGroupClearBits()与 xEventGroupClearBitsFromISR()函数说明

 xEventGroupClearBits()函数使用实例

 #define BIT_0 ( 1 << 0 )
 #define BIT_4 ( 1 << 4 )
 
 void aFunction( EventGroupHandle_t xEventGroup )
 {
    EventBits_t uxBits;
 
    /* 清楚事件组的 bit 0 and 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 中创建了两个任务,一个是设置事件任务,一个是等 待事件任务,两个任务独立运行,设置事件任务通过检测按键的按下情况设置不同的事件 标志位,等待事件任务则获取这两个事件标志位,并且判断两个事件是否都发生,如果是 则输出相应信息,LED 进行翻转。等待事件任务的等待时间是 portMAX_DELAY,一直在等待事件的发生,等待到事件之后清除对应的事件标记位,具体见代码清单 20-13 加粗部 分。


/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.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 BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
	printf("这是一个FreeRTOS事件标志组实验!\n");
   /* 创建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();   /* 启动任务,开启调度 */
  else
    return -1;  
  
  while(1);   /* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : 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");
    
  /* 创建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任务成功!\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : LED_Task
  * @ 功能说明: 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 ( "KEY1与KEY2都按下\n");		
      LED1_TOGGLE;       //LED1	反转
    }
    else
      printf ( "事件错误!\n");	
  }
}

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

/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();

}

/********************************END OF FILE****************************/

七、实验现象

        程序编译好,用 USB 线连接电脑和开发板的 USB 接口(对应丝印为 USB 转串口), 用 仿真器把配套程序下载到 STM32 开发板,在电脑上打开串口调试助手,然后复位开发板就可 以在调试助手中看到串口的打印信息,按下开发版的 KEY1 按键发送事件 1,按下 KEY2 按键发送事件 2;我们按下 KEY1 与 KEY2 试试,在串口调试助手中可以看到运行结果, 并且当事件 1 与事件 2都发生的时候,开发板的 LED 会进行翻转。

物联沃分享整理
物联沃-IOTWORD物联网 » 基于STM32的FreeRTOS事件组详解

发表评论