freeRTOS事件组(event group)

目录

前言

一、事件组概述

二、事件组函数

1.创建

2.删除

 3.设置事件

4.等待事件

 三、事件组代码示例

四、同步点

五、同步点代码示例


前言

学校组织秋游,组长在等待:
\bullet 张三:我到了
\bullet 李四:我到了
\bullet 王五:我到了
\bullet 组长说:好,大家都到齐了,出发!

秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的

在这个日常生活场景中:
\bullet 出发:要等待这3个人都到齐,他们是""的关系
\bullet 交报告:只需等待这3人中的任何一个,他们是""的关系

在FreeRTOS中,可以使用事件组(event group)来解决这些问题。


一、事件组概述

事件组可以简单地认为就是一个整数:
\bullet 事件组的每一位表示一个事件
\bullet 每一位事件的含义由程序员决定,比如:Bit0表示用来串口是否就绪,Bit1表示按键是否被按下
\bullet 这些位,值为1表示事件发生了,值为0表示事件没发生
\bullet 一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位
\bullet 可以等待某一位、某些位中的任意一个,也可以等待多位

注意:等待的事件中,它们要么是或的关系,要么是与的关系。也就是可以等待若干个事件中的任一个,可以等待若干个事件中的所有。不能在若干个事件中指定某些事件。

        事件组用一个整数来表示,其中的高8位留给内核使用,只能用其他的位来表示事件。那么这个整数是多少位的? 
\bullet 如果configUSE_16_BIT_TICKS是1,那么这个整数就是16位的,低8位用来表示事件
\bullet 如果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个:
        \bullet 在任务中使用 xEventGroupSetBits()
        \bullet 在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个任务:
        \bullet 任务1:洗菜
        \bullet 任务2:生火
        \bullet 任务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个任务的代码和执行流程如下: 
\bullet A:"炒菜任务"优先级最高,先执行。它要等待的2个事件未发生:洗菜、生火,进入阻塞状态
\bullet B:"生火任务"接着执行,它要等待的1个事件未发生:洗菜,进入阻塞状态
\bullet C:"洗菜任务"接着执行,它洗好菜,发出事件:洗菜,然后调用F等待"炒菜"事件
\bullet D:"生火任务"等待的事件满足了,从B处继续执行,开始生火、发出"生火"事件
\bullet E:"炒菜任务"等待的事件满足了,从A出继续执行,开始炒菜、发出"炒菜"事件
\bullet F:"洗菜任务"等待的事件满足了,退出F、继续执行C

运行结果如下图所示:


四、同步点

有一个事情需要多个任务协同,比如:
\bullet 任务A:炒菜
\bullet 任务B:买酒
\bullet 任务C:摆台
\bullet A、B、C做好自己的事后,还要等别人做完;大家一起做完,才可开饭 

使用 xEventGroupSync() 函数可以同步多个任务:
\bullet 可以设置某位、某些位,表示自己做了什么事
\bullet 可以等待某位、某些位,表示要等等其他任务
\bullet 期望的时间发生后, xEventGroupSync() 才会成功返回
\bullet xEventGroupSync 成功返回后,会清除事件

xEventGroupSync 函数原型如下:

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
                             const EventBits_t uxBitsToSet,
                             const EventBits_t uxBitsToWaitFor,
                             TickType_t xTicksToWait );

参数列表如下:


五、同步点代码示例

假设ABC三人要吃饭,各司其职:
\bullet A:炒菜
\bullet B:买酒
\bullet 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个功能:
\bullet 设置事件:表示自己完成了某个、某些事件
\bullet 等待事件:跟别的任务同步
\bullet 成功返回后,清除"等待的事件"

运行结果如下图所示:

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sakabu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值