引言
本文深入探讨STM32与FreeRTOS结合时的事件机制,从事件的创建、触发到处理,剖析其在复杂任务调度中的关键作用,助力开发者更好地掌握这一核心技术,提升系统运行效率与稳定性。
事件的定义
在 FreeRTOS 中,事件组中的每个事件位通常是一个二进制位,可以表示一个特定的事件。事件位通常通过宏定义来表示,以便于代码的可读性和维护性。
通俗的讲,事件的核心就是设置事件标志位,然后读取该标志位状态,进而执行要执行的逻辑。类似按键检测一样,不断检测按键有没有被按下,如果被按下则执行按下逻辑。
事件
步骤跟前面的差不多,这里就不再一一介绍,感兴趣的可以看看我前面的文章。
首先是定义事件的句柄,并给其赋初值为NULL。
EventGroupHandle_t Event_Handle =NULL; //事件句柄赋初值
接着在临界区创建事件组:
/* 创建 Event_Handle */
Event_Handle = xEventGroupCreate();
xEventGroupCreate()
该函数原型为:
EventGroupHandle_t xEventGroupCreate(void);
可以看出,该函数是一个无参数有返回值的函数。该函数的主要作用分配内存并初始化一个新的事件组,如果创建成功则返回一个非空事件组的句柄;如果创建失败则返回NULL,这通常是由于内存空间分配不足导致。
xEventGroupSetBits()
xEventGroupSetBits()是 FreeRTOS 中的一个函数,用于向事件组中设置位。
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t xEventGroup
);
可以看出,该函数是含参且有返回值的函数,参数xEventGroup:填入事件组的句柄;参数xEventGroup:要设置的位掩码。返回值:返回事件组当前设置的位掩码。
xEventGroupWaitBits()
xEventGroupWaitBits 是 FreeRTOS 中用于等待事件组中指定位的函数。它允许任务阻塞等待事件组中的一个或多个位被设置。该函数的原型为:
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);
第一个参数xEventGroup:填入事件组的句柄;
第二个参数uxBitsToWaitFor:要等待的掩码;
第三个参数xClearOnExit:当任务退出等待时,是否删除事件组中已设置的位
pdTRUE :如果任务等待的位被设置,则在任务退出等待时清除这些位。
pdFALSE :不自动清除事件组中的位。
第四个参数xWaitForAllBits:指定任务是等待所有指定的位都被设置,还是等待任意一个指定的位被设置。
pdTRUE :任务会等待所有指定的位都被设置。
pdFALSE :任务会等待任意一个指定的位被设置。
第五个参数xTicksToWait:任务等待事件组中位被设置的最大时间。如果在指定时间内没有位被设置,则任务会超时退出等待。
返回值:返回每一位掩码,包括被设置和未被设置的掩码
示例代码
cs
#include "myfreertos.h"
#include "FreeRTOS.h"
#include "event_groups.h"
#include "semphr.h"
#include "queue.h"
#include "Usart.h"
#include "Task.h"
#include "led.h"
#include "key.h"
TaskHandle_t MyTaskHandler;//任务句柄
TaskHandle_t LED_TASK_Handler;//LED任务句柄
TaskHandle_t KEY_TASK_Handler;//LED任务句柄
void MyTask(void *pvParameters); //声明启动函数
void LED_TASK(void *pvParameters); //声明LED任务函数
void KEY_TASK(void *pvParameters); //声明KEY任务函数
EventGroupHandle_t Event_Handle =NULL; //事件句柄赋初值
#define KEY_EVENT1 (0x01 << 0)//设置事件掩码的位0
#define KEY_EVENT2 (0x01 << 1)//设置事件掩码的位1
void Start_Task(void)
{
xTaskCreate(MyTask,"MyTask",128,NULL,1,&MyTaskHandler);//动态方法创建任务
vTaskStartScheduler();//启动任务调动
}
void MyTask(void *pvParameters) //开始创建任务函数
{
taskENTER_CRITICAL(); //进入临界区
/* 创建 Event_Handle */
Event_Handle = xEventGroupCreate();
xTaskCreate(LED_TASK,"LED_TASK",50,NULL,3,&LED_TASK_Handler);//动态方法创建LED任务
xTaskCreate(KEY_TASK,"KEY_TASK",50,NULL,4,&KEY_TASK_Handler);//动态方法创建LED任务
vTaskDelete(MyTaskHandler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
void LED_TASK(void *pvParameters)
{
EventBits_t R_EVENT; /* 定义一个事件接收变量 */
while(1)
{
R_EVENT = xEventGroupWaitBits(Event_Handle,KEY_EVENT1|KEY_EVENT2,pdTRUE,pdFALSE,portMAX_DELAY);
if( ((R_EVENT & KEY_EVENT1) == KEY_EVENT1) || ((R_EVENT & KEY_EVENT2) == KEY_EVENT2))
{
LED = !LED;
}
vTaskDelay(200);
}
}
void KEY_TASK(void *pvParameters)
{
while(1)
{
if( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8) != 0 )
{
xEventGroupSetBits(Event_Handle,KEY_EVENT1);
}
// if( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6) != 0 )
// {
// xEventGroupSetBits(Event_Handle,KEY_EVENT2);
// }
vTaskDelay(200);
}
}
示例代码讲解
首先时事件掩码:
#define KEY_EVENT1 (0x01 << 0)//设置事件掩码的位0
#define KEY_EVENT2 (0x01 << 1)//设置事件掩码的位1
该掩码的大小由configEVENT_GROUP_MAX_BITS宏定义决定,通常是32位。对于上面这两个掩码来说,对于第一个KEY_EVENT1(0x01 << 0)来说,就是把1向左移0位,结果就是将32位掩码中的第0位设置为1。对于第二个KEY_EVENT2 (0x01 << 1)来说,就是把1向左移1位,结果就是将32位掩码中的第1位设置为1。
其次是:
xEventGroupSetBits(Event_Handle,KEY_EVENT1);
我的思路是如果按键PA8按下,则设置该掩码KEY_EVENT1。当然,下面的则设置该掩码KEY_EVENT2。
然后是:
R_EVENT = xEventGroupWaitBits(Event_Handle,KEY_EVENT1|KEY_EVENT2,pdTRUE,pdFALSE,portMAX_DELAY);//等待key1或key2
if( ((R_EVENT & KEY_EVENT1) == KEY_EVENT1) || ((R_EVENT & KEY_EVENT2) == KEY_EVENT2))
{
LED = !LED;
}
这个KEY_EVENT1|KEY_EVENT2的作用是等待KEY_EVENT1或KEY_EVENT2中的任意一个位被设置。
这个pdTRUE的作用是当任务退出等待时,清除事件组中已设置的位。就是说每次执行完这个函数后,事件组已设置都会被清除;如果把pdTRUE改成pdFALSE ,那么该已设置的位将会保持设置状态。如果想删除它,就要调用EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);该函数是一个含参有返回值的函数,参数xEventGroup:填入事件组的句柄;参数uxBitsToClear:要删除的掩码。返回值返回的是当前事件组中设置的位掩码。如果把全部掩码都删除,那就返回0。
这个pdFALSE的作用是等待设置的位掩码的任意一位被设置,该事件就被触发,然后该函数就会返回对应的位掩码。如果将pdFALSE改为pdTRUE ,则需要对应的掩码被全部设置,该事件才会被触发
最后是这个:
(R_EVENT & KEY_EVENT1) == KEY_EVENT1)
R_EVENT 是等待事件函数返回的位掩码,然后与KEY_EVENT1掩码相与,作用是判断对应的事件是否被触发。
总结
本文仅是个人观点,不代表最终解释,如有不足,欢迎指出。