目录
前言
学校组织秋游,组长在等待: 张三:我到了
李四:我到了
王五:我到了
组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。
在这个日常生活场景中: 出发:要等待这3个人都到齐,他们是"与"的关系
交报告:只需等待这3人中的任何一个,他们是"或"的关系
在FreeRTOS中,可以使用事件组(event group)来解决这些问题。
一、事件组概述
事件组可以简单地认为就是一个整数: 事件组的每一位表示一个事件
每一位事件的含义由程序员决定,比如:Bit0表示用来串口是否就绪,Bit1表示按键是否被按下
这些位,值为1表示事件发生了,值为0表示事件没发生
一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位
可以等待某一位、某些位中的任意一个,也可以等待多位
注意:等待的事件中,它们要么是或的关系,要么是与的关系。也就是可以等待若干个事件中的任一个,可以等待若干个事件中的所有。不能在若干个事件中指定某些事件。
事件组用一个整数来表示,其中的高8位留给内核使用,只能用其他的位来表示事件。那么这个整数是多少位的? 如果configUSE_16_BIT_TICKS是1,那么这个整数就是16位的,低8位用来表示事件
如果configUSE_16_BIT_TICKS是0,那么这个整数就是32位的,低24位用来表示事件
二、事件组函数
1.创建
使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。 有两种创建方法:动态分配内存、静态分配内存。函数原型如下:
/* 创建一个事件组,返回它的句柄。
* 此函数内部会分配事件组结构体
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );
/* 创建一个事件组,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *
pxEventGroupBuffer );
2.删除
对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。
vEventGroupDelete可以用来删除事件组,函数原型如下:
/*
* xEventGroup: 事件组句柄,你要删除哪个事件组
*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
3.设置事件
可以设置事件组的某个位、某些位,使用的函数有2个:
在任务中使用 xEventGroupSetBits()
在ISR中使用 xEventGroupSetBitsFromISR()
有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。
函数原型如下:
/* 设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位?
* 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
* 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
* 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
/* 设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位?
* 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
* 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
* pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有,
pdFALSE-没有
* 返回值: pdPASS-成功, pdFALSE-失败
*/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken );
4.等待事件
使用 xEventGroupWaitBits 来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位; 等到期望的事件后,还可以清除某些位。
函数原型如下:
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
函数参数说明列表如下:
一个任务在等待事件发生时,它处于阻塞状态;当期望的事件发生时,这个状态就叫"unblock condition",非阻塞条件,或称为"非阻塞条件成立";当"非阻塞条件成 立"后,该任务就可以变为就绪态。
举例如下:
三、事件组代码示例
要使用事件组,代码中要有如下操作:
/* 1. 工程中添加event_groups.c */
/* 2. 源码中包含头文件 */
#include "event_groups.h"
假设大厨要等手下做完这些事才可以炒菜:洗菜、生火。
本程序创建3个任务:
任务1:洗菜
任务2:生火
任务3:炒菜。
main函数代码如下,它创建了3个任务:
int main( void )
{
prvSetupHardware();
/* 创建事件组 */
xEventGroup = xEventGroupCreate( );
if( xEventGroup != NULL )
{
/* 创建3个任务: 洗菜/生火/炒菜
*/
xTaskCreate( vWashingTask, "Task1", 1000, NULL, 1, NULL );
xTaskCreate( vFiringTask, "Task2", 1000, NULL, 2, NULL );
xTaskCreate( vCookingTask, "Task3", 1000, NULL, 3, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建事件组 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
任务代码如下:
洗菜和生火:
炒菜:
(每个事件对应的位事先用宏定义了)
这3个任务的代码和执行流程如下: A:"炒菜任务"优先级最高,先执行。它要等待的2个事件未发生:洗菜、生火,进入阻塞状态
B:"生火任务"接着执行,它要等待的1个事件未发生:洗菜,进入阻塞状态
C:"洗菜任务"接着执行,它洗好菜,发出事件:洗菜,然后调用F等待"炒菜"事件
D:"生火任务"等待的事件满足了,从B处继续执行,开始生火、发出"生火"事件
E:"炒菜任务"等待的事件满足了,从A出继续执行,开始炒菜、发出"炒菜"事件
F:"洗菜任务"等待的事件满足了,退出F、继续执行C
运行结果如下图所示:
四、同步点
有一个事情需要多个任务协同,比如: 任务A:炒菜
任务B:买酒
任务C:摆台
A、B、C做好自己的事后,还要等别人做完;大家一起做完,才可开饭
使用 xEventGroupSync() 函数可以同步多个任务: 可以设置某位、某些位,表示自己做了什么事
可以等待某位、某些位,表示要等等其他任务
期望的时间发生后, xEventGroupSync() 才会成功返回
xEventGroupSync 成功返回后,会清除事件
xEventGroupSync 函数原型如下:
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );
参数列表如下:
五、同步点代码示例
假设ABC三人要吃饭,各司其职: A:炒菜
B:买酒
C:摆台
三人都做完后,才可以开饭。
main函数代码如下,它创建了3个任务:
int main( void )
{
prvSetupHardware();
/* 创建事件组 */
xEventGroup = xEventGroupCreate( );
if( xEventGroup != NULL )
{
/* 创建3个任务: 洗菜/生火/炒菜
*/
xTaskCreate( vCookingTask, "task1", 1000, "A", 1, NULL );
xTaskCreate( vBuyingTask, "task2", 1000, "B", 2, NULL );
xTaskCreate( vTableTask, "task3", 1000, "C", 3, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建事件组 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
被创建的3个任务,代码都很类似,以任务1为例:
static void vCookingTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
int i = 0;
/* 无限循环 */
for( ;; )
{
/* 做自己的事 */
printf("%s is cooking %d time....\r\n", (char *)pvParameters, i);
/* 表示我做好了, 还要等别人都做好 */
xEventGroupSync(xEventGroup, COOKING, ALL, portMAX_DELAY);
/* 别人也做好了, 开饭 */
printf("%s is eating %d time....\r\n", (char *)pvParameters, i++);
vTaskDelay(xTicksToWait);
}
}
要点在于 xEventGroupSync 函数,它有3个功能: 设置事件:表示自己完成了某个、某些事件
等待事件:跟别的任务同步
成功返回后,清除"等待的事件"
运行结果如下图所示: