FreeRTOS学习篇七:事件

1.理解基本概念

事件:事件 → 触发任务;可以将事件看作是一种标记,对应的任务需要这种标记来进入就绪态,当这种标记被激活,所对应的任务就会被唤醒,进入就绪态;如果没有被激活,那对应的任务就会处于阻塞态,等待事件触发。

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

同样,也可以是多个任务同步多个事件。

事件组(事件标志组):一组事件,即不同事件的集合体,通过设置和清除特定的位来表示不同的事件状态,任务可以通过等待或等待指定的事件标志组来同步。

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

  • 事件组的存储:

    如果宏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的事件的特点:

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

2.事件的应用场景

  1. 可用做标志位。
  2. 在一定程度上可替代信号量,用于任务与任务间、中断与任务间的同步。

3.事件的运作机制

  1. 任务接收事件,接收感兴趣的单个或者多个事件类型。任务接收事件成功后,需要使用xClearOnExit选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要手动去清除事件位。另外通过传入参数xWaitForAllBits可选择事件读取模式。
  2. 设置事件时,需要对指定事件写入指定的事件类型、设置事件集合的对应事件位为1。可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。
  3. 清除事件时,根据传入的事件句柄和待清除的事件类型,可以对事件对应位进行清0操作。
  4. 事件是独立的,事件不是和任务绑定的,任务只是接收事件,就像在等待标志位变为true,使得可以执行if判断里面的代码一样。
  5. 一个32位的变量(事件集合,实际用于表示事件的只有24位,一共24种事件类型),用于标识任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、 1 表示该事件类型已经发生)。

事件唤醒机制:

  • 任务等待事件发生时,任务将进入阻塞态。
  • 任务所等待的事件发生后,任务将由阻塞态转为就绪态,此时任务被唤醒。

4.事件操作常用函数

①事件创建函数

xEventGroupCreate():

/* 用于创建一个事件组,返回一个事件组句柄 */
EventGroupHandle_t xEventGroupCreate(void);

要想使用该函数必须在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION为1(在FreeRTOS.h中默认定义为1)。且需要把FreeRTOS/source/event_groups.c 这个C文件添加到工程中。

xEventGroupCreateStatic():静态分配。

②事件删除函数

vEventGroupDelete():

/* 用于删除一个事件组,传入事件组句柄即可 */
void vEventGroupDelete(EventGroupHandle_t xEventGroup);

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

③事件组置位函数

1、xEventGroupSetBits():任务中使用

/* 用于将事件组中指定的位设置为1 */
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
  • 形参xEventGroup:事件句柄。
  • 形参uxBitsToSet:指定事件中的事件标志位。如设置uxBitsToSet为0x08则只置位位3,如果设置uxBitsToSet为0x09则位3和位0都会被置位。
  • 返回值:返回在调用xEventGroupSetBits()时事件组中的值。

xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。不能用于中断。

2、xEventGroupSetBitsFromISR():中断中使用

/* 用于将事件组中指定的位设置为1 */
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken);
  • 形参xEventGroup:事件句柄。
  • 形参uxBitsToSet:指定事件组中的哪些位需要置位。如设置 uxBitsToSet 为0x08 则只置位位 3, 如果设置 uxBitsToSet 为 0x09 则位 3和位 0 都需要被置位。
  • 形参pxHigherPriorityTaskWoken:pxHigherPriorityTaskWoken在使用之前必须初始化成pdFALSE。调用 xEventGroupSetBitsFromISR()会给守护任务发送一个消息, 如果守护任务的优先级高于当前被中断的任务的优先级的话(一般情况下都需要将守护任务的优先级设置为所有任务中最高优先级),pxHigherPriorityTaskWoken会被置为pdTRUE,然后在中断退出前执行一次上下文切换。

返回值:消息成功发送给守护任务之后则返回pdTRUE,否则返回 pdFAIL。如果定时器服务队列满了将返回pdFAIL。

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

④事件等待函数

xEventGroupWaitBits():通过这个函数,任务可以知道事件标志组中的(哪些位)什么事件发生了,也就是那些位是1,用于判断出哪个事件发生。
在这里插入图片描述

/* 使用示例 */
static void LED_Task(void* parameter)
{
	EventBits_t r_event; /* 定义一个事件接收变量 */
	while (1) {
		/* 等待接收事件标志 */
	
		r_event = xEventGroupWaitBits(Event_Handle, /* 事件对象句柄 */
		KEY1_EVENT|KEY2_EVENT,/* 接收任务感兴趣的事件 */
		pdTRUE, /* 退出时清除事件位 */
		pdTRUE, /* 满足感兴趣的所有事件 */
		portMAX_DELAY);/* 指定超时事件,一直等 */
		/* 取出标志位并判断是否符合 */
		if ((r_event & (KEY1_EVENT | KEY2_EVENT)) == (KEY1_EVENT | KEY2_EVENT)) {
            /* 事件处理 */
		} else {
            /* error  */
        }
	}
}

⑤位清除函数

1、xEventGroupClearBits():
在这里插入图片描述

/* 使用示例 */
#define BIT_0 (1 << 0)
#define BIT_4 (1 << 4)

void aFunction(EventGroupHandle_t xEventGroup)
{
	EventBits_t uxBits;

	/* 清除事件组的 bit 0 and bit 4,1则清除 */
	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 都没被置位 */
	}
}

2、xEventGroupClearBitsFromISR():

中断中使用,要使用该函数需要将configTIMER_TASK_PRIORITY 定义为1。

5.使用示例

示例:PA0的按键0 —— 事件1,触发LED灯亮;PA2的按键1 —— 事件2,触发LED等灭;通过串口输出信息。

最终效果:按下按键1,事件1被触发,点亮LED;按下按键2,时间2被触发,点亮LED;事件1、事件2都被触发后,关掉LED。
在这里插入图片描述
代码:

0、在FreeRTOSConfig.h配置文件中添加以下:

#define configSUPPORT_DYNAMIC_ALLOCATION 1

1、STM32F103C8_BSP.c:

#include "stm32f10x.h"                  // Device header
void USART1_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStruct);
    
    USART_Cmd(USART1, ENABLE);
}
void KEY_GPIO_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void LED_GPIO_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStruct);
    GPIO_SetBits(GPIOC, GPIO_Pin_13);
}

void USART1_SendByte(uint8_t byte){
    USART_SendData(USART1, byte);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void USART1_SendString(char* str)
{
    uint16_t i;
    for(i = 0; str[i] != '\0'; i++){
        USART1_SendByte(*(str+i));
    }
}

uint8_t KEY_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1){
        while(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == 1);
        return 1;
    }
    return 0;
}

2、main.c:

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "STM32F103C8_BSP.h"
#include "semphr.h"
#include "event_groups.h"

static void STM32F103_Init(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    USART1_Init();
    KEY_GPIO_Init();
    LED_GPIO_Init();
}
/* 定义两个事件 */
#define KEY1_EVENT (0x01 << 0) // 位0
#define KEY2_EVENT (0x01 << 1) // 位1

/* 定义事件句柄 */
EventGroupHandle_t Event_Handle;

/* 声明任务 */
void EventTask(void);
void LEDTask(void);

int main(void) 
{
    STM32F103_Init();
    taskENTER_CRITICAL();
    /* 创建事件 */
    Event_Handle = xEventGroupCreate();
    if(Event_Handle != NULL)
        USART1_SendString("create event successfully!\n");
    /* 创建任务 */
    xTaskCreate((TaskFunction_t)EventTask, "EventTask", 512, NULL, 3, NULL);
    USART1_SendString("create EventTask successfully!\n");
    xTaskCreate((TaskFunction_t)LEDTask, "LEDTask", 512, NULL, 2, NULL);
    USART1_SendString("create LEDTask successfully!\n\n");
    vTaskStartScheduler();
    taskEXIT_CRITICAL();
    while(1);
}
void EventTask(void)
{
    while(1){
        if(KEY_Scan(GPIOA, GPIO_Pin_0) == 1){
            /* 按钮按下,按钮1事件被触发,将位0置为1 */
            xEventGroupSetBits(Event_Handle, KEY1_EVENT);
            GPIO_ResetBits(GPIOC, GPIO_Pin_13);
            USART1_SendString("Event1 triggered!LED ON.\n");
        }
        if(KEY_Scan(GPIOA, GPIO_Pin_2) == 1){
            /* 按钮按下,按钮2事件被触发,将位1置为1 */
            xEventGroupSetBits(Event_Handle, KEY2_EVENT);
            GPIO_ResetBits(GPIOC, GPIO_Pin_13);
            USART1_SendString("Event2 triggered!LED ON.\n");
        }
        vTaskDelay(20);
    }
}

void LEDTask(void)
{
    EventBits_t r_event;
    while(1){
        r_event = xEventGroupWaitBits(Event_Handle, KEY1_EVENT | KEY2_EVENT,
        pdTRUE, pdTRUE, portMAX_DELAY);
        /* 两个事件都触发,那就熄灯 */
        if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)){
            USART1_SendString("Event1 & Event2 triggered!LED OFF.\n\n");
            GPIO_SetBits(GPIOC, GPIO_Pin_13);
        }
        vTaskDelay(20);
    }
}

END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值