之前我们学习了队列和队列延伸出的信号量、互斥量的使用,但是这些有些局限性,只能同时进行一项任务,我们这次实验来学习如何同一时间使多个任务(优先级可以不相同)同时运行:这就是今天实验的主角:事件组。
本次实验采用STM32F103ZET6主芯片的开发板,使用HAL库开发。
目录
事件组特点
事件组允许任务等待一个或者多个事件的组合,也可以一个事件对应多个任务,当事件置位时,这个事件对应的所有任务都会同时运行。
事件组是由一个32位的变量组成的:
它的24-31位是保留位,后面的0-23位是事件位,一个位就表示一个事件的发生与否(发生为1,未发生为0)。所以一个事件组最多可以处理24个事件。
事件组相关函数
事件位置位:xEventGroupSetBits()用于设置事件组中的一个或多个事件位,也可用于通知任务当前已设置的位。它的各项参数为:
xEventGroup事件组的句柄;
uxBitsToSet是一个位掩码,指定事件组中哪些位设置为1。例如:KEY1为0x04,则将事件组中的bit 2(从bit 0开始)设置为1,而其他位不变。
注意:不要在中断中使用它,中断中应使用中断安全版API:xEventGroupSetBitsFromISR();
事件位清零:xEventGroupClearBits()用于在任务函数中将事件组某些位清零,和上面事件位置位刚好相反。它的各项参数是:
xEventGroup事件组的句柄;
uxBitsToClear是一个位掩码,指定事件组中哪些位清零。例如:KEY1为0x04,则将事件组中的bit 2(从bit 0开始)设置为0,而其他位不变;
这个函数的返回值是清零之前的事件组的值。
读取事件组当前值:xEventGroupGetBits()可以读取事件组的当前值,这个函数实际上执行的是事件位清零函数(xEventGroupClearBits()),这个函数只有一个参数
xEventGroup事件组的句柄;
返回值是事件组的值。
等待事件组条件成立:xEventGroupWaitBits()函数允许任务读取事件组的值,并且事件尚未发生,可以在阻塞状态下选择等待事件组中的一个或多个事件。它的各项参数:
xEventGroup事件组的句柄;
uxBitsToWaitFor是一个位掩码,指定要在事件组中测试的事件位(一个或多个);
xClearOnExit如果调用任务满足解除阻塞条件,并且xClearOnExit为pdTRUE,则在xEventGroupWaitBits()退出前,清除uxBitsToWaitFor指定的事件位。如果xClearOnExit为pdFALSE,则xEventGroupWaitBits()函数不会修改事件组的事件位。
xWaitForAllBits这位可以设置为pdFALSE或pdTRUE,当设置为pdFALSE时uxBitsToWaitFor中任何位被设置就退出函数(超时后也会退出),为pdTRUE时,uxBitsToWaitFor中设置的所有位被设置才退出函数(超时后也会退出)。
xTicksToWait最长的超时时间,如果在设定时间内没有达到上面的要求则会退出函数,如果设置为portMAX_DELAY表示等待事件无限长。
了解了这些基本参数我们进行实验来熟悉这些参数
实验部分
实验流程:创建一个事件组和三个任务;
-
-
- 一个任务检测KEY1和KEY2按键是否按下,如果按下就修改对应的事件组位;KEY3按下清除所有事件位。
- 另外两个任务检测当两个事件组位成立时分别使LED2和3闪烁几次。
-
CobeMX设置
本次实验我们需要用到LED,按键和LCD,这些之前都设置过,这里就不再强调,这里添加我这块板子的LED和按键对应硬件图:
然后开启FREERTOS选择模式V2,在Takes and Queues一栏配置三个任务,具体配置如图:
图 1 任务配置
再在Events一栏配置一个事件组,这里配置只需要设置事件组的名称,具体如图:
图 2 事件组配置
配置完成后就可以生成代码了。
代码实现
按键和LED宏函数的设定:
#define KEY3_Pin GPIO_PIN_2
#define KEY3_GPIO_Port GPIOE
#define KEY2_Pin GPIO_PIN_3
#define KEY2_GPIO_Port GPIOE
#define KEY1_Pin GPIO_PIN_4
#define KEY1_GPIO_Port GPIOE
#define LED2_Pin GPIO_PIN_5
#define LED2_GPIO_Port GPIOE
#define LED3_Pin GPIO_PIN_5
#define LED3_GPIO_Port GPIOB
这里只用到3个按键,所以只定义了三个按键宏。
在主函数中测试LCD,具体代码如下:
#include "lcd.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_FSMC_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
LCD_ShowString(10, 0, 280, 16, 16, (uint8_t *)"this is the damo seven!");
LCD_ShowString(10, 20, 280, 16, 16, (uint8_t *)"1.Press KEY1 and KEY2");
LCD_ShowString(10, 40, 280, 16, 16, (uint8_t *)"to activate LED2 and LED3");
LCD_ShowString(10, 60, 280, 16, 16, (uint8_t *)"2.Press KEY3 to clear events");
/* USER CODE END 2 */
osKernelInitialize(); /* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
osKernelStart();
while (1)
{
}
}
然后进入freertos.c文件,现在开头处声明几个头文件:
#include "lcd.h"
#include "event_groups.h" // 事件组相关头文件
为了后面方便书写,在这里定义几个事件掩码相关宏:
#define KEY1 0x01 // KEY1的事件位掩码为bit0
#define KEY2 0x02 // KEY2的事件位掩码为bit1
#define CLEAR 0x03 // 将KEY1和KEY2的事件位都清零
完成后配置任务函数。
在StartTask03函数中我们能需要将按键对应到事件组的具体位,具体设置我在注释中有详细注解。
/* USER CODE END Header_StartTask03 */
/* 在这个任务中检测KEY1和KEY2是否都按下,
如果按下将事件组中对应的事件位置位。
如果按下KEY3使整个事件组清零 */
void StartTask03 (void *argument)
{
/* USER CODE BEGIN StartTask03 */
// KEY key = KEY0;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)
{
xEventGroupSetBits(eventGroupHandle,KEY1); // 置位KEY1的掩码
}
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET)
{
xEventGroupSetBits(eventGroupHandle,KEY2); // 置位KEY2的掩码
}
if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET)
{
xEventGroupClearBits(eventGroupHandle,KEY1 | KEY2); // 将KEY1和KEY2的事件位都清零
}
vTaskDelay(200);
}
/* USER CODE END StartTask03 */
}
剩下的LED2和LED3两个任务本质上都没有区别,只是应对事件不同而已。直接上代码:
/* USER CODE BEGIN Header_Task_LED2 */
/**
* 在这个实验中要是KEY1和2连着按下,不管间隔多久都可以,那么就会触发任务1和2
* 然后会删除事件位,如果在两个按键中按下KEY3则不会触发任务。
* xEventGroupWaitBits这个等待事件组条件成立的最后一位是配置等待时间的,
* 这里设置一直等待,如果修改可以设置规定时间。
* @brief Function implementing the myTask_LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_LED2 */
// 在这个任务中检测KEY1和KEY2的事件是否发生,如果发生将LED2闪烁5次
void Task_LED2(void *argument)
{
/* USER CODE BEGIN Task_LED2 */
/* Infinite loop */
for(;;)
{
xEventGroupWaitBits(eventGroupHandle,CLEAR,pdTRUE,pdTRUE,portMAX_DELAY); // 等待事件发生,如果发生再进行操作
uint8_t i;
for(i = 0; i < 5; i++)
{
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
vTaskDelay(200);
}
}
/* USER CODE END Task_LED2 */
}
/* USER CODE BEGIN Header_Task_LED3 */
/**
* @brief Function implementing the myTask_LED3 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Task_LED3 */
// 在这个任务中检测KEY1和KEY2的事件是否发生,如果发生将LED3闪烁5次。
void Task_LED3(void *argument)
{
/* USER CODE BEGIN Task_LED3 */
/* Infinite loop */
for(;;)
{
xEventGroupWaitBits(eventGroupHandle,CLEAR,pdTRUE,pdTRUE,portMAX_DELAY); /* 阻塞等待事件组条件成立
如果条件成立就触发LED灯闪烁 */
uint8_t i;
for(i = 0; i < 5; i++)
{
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
vTaskDelay(200);
}
}
/* USER CODE END Task_LED3 */
}
完成代码部分后,将其编译烧录进开发板,可以看到屏幕上的提示信息,按照提示操作可以看到相关现象。本实验完成。