在项目中需要用到freertos的事件标志组,看到一篇很好的博客,将其摘录出来。
原地址:http://blog.sina.com.cn/s/blog_98ee3a930102wgev.html
18.1 事件标志组
18.1.1 为什么要使用事件标志
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦,搞个全局变量不是更简单?其实不然,在裸机编程时,使用全局变量的确比较方便,但是在加上RTOS后就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:
u 使用事件标志组可以让RTOS内核有效地管理任务,而全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
u 使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心。
u 使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。
18.1.2 FreeRTOS任务间事件标志组的实现
任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。
下面我们来说说FreeRTOS中事件标志的实现,根据用户在FreeRTOSConfig.h文件中的配置:
u #define configUSE_16_BIT_TICKS 1
配置宏定义configUSE_16_BIT_TICKS为1时,每创建一个事件标志组,用户可以使用的事件标志是8个。
u #define configUSE_16_BIT_TICKS 0
配置宏定义configUSE_16_BIT_TICKS为0时,每创建一个事件标志组,用户可以使用的事件标志是24个。
上面说的8个和24个事件标志应该怎么理解呢?其实就是定义了一个16位变量,仅使用了低8bit或者定义了一个32位变量,仅使用了低24bit。每一个bit用0和1两种状态来代表事件标志。反映到FreeRTOS上就是将事件标志存储到了EventBits_t类型的变量中,这个变量又是怎么回事呢?定义如下:
typedef TickType_t EventBits_t;
进一步跟踪TickType_t的数据类型,定义如下:
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#define portTICK_TYPE_IS_ATOMIC 1
#endif
由上面定义可以看出,TickType_t数据类型可以是16位数或者32位数,这样就跟上面刚刚说的configUSE_16_BIT_TICKS 宏定义呼应上了。教程配套的例子都是配置宏定义configUSE_16_BIT_TICKS为0,即用户每创建一个事件标志组,有24个标志可以设置。如下图所示,这里仅使用bit0,bit1和bit2。
注意:后面的讲解中,默认全是创建一个事件标志,支持24个事件标志设置。
下面我们通过如下的框图来说明一下FreeRTOS事件标志的实现,让大家有一个形象的认识。
运行条件:
u 创建2个任务:Task1和Task2
运行过程描述如下:
u 任务Task1运行过程中调用函数xEventGroupWaitBits,等待事件标志位被设置,任务Task1由运行态进入到阻塞态。
u 任务Task2设置Task1等待的事件标志,任务Task1由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单的FreeRTOS任务间事件标志通信过程。
18.1.3 FreeRTOS中断方式事件标志组的实现
FreeRTOS中断方式事件标志组的实现是指中断函数和FreeRTOS任务之间使用事件标志。下面我们通过如下的框图来说明一下FreeRTOS事件标志的实现,让大家有一个形象的认识。
运行条件:
u 创建一个任务和一个串口接收中断
运行过程描述如下:
u 任务Task1运行过程中调用函数xEventGroupWaitBits,等待事件标志位被设置,任务Task1由运行态进入到阻塞态。
u Task1阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中设置Task1等待的事件标志,任务Task1由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单的FreeRTOS中断方式事件标志通信过程。实际应用中,中断方式的消息机制要注意以下四个问题:
u 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
u 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
u 中断服务程序中一定要调用专用于中断的事件标志设置函数,即以FromISR结尾的函数。
u 在操作系统中实现中断服务程序与裸机编程的区别。
l 如果FreeRTOS工程的中断函数中没有调用FreeRTOS的事件标志组API函数,与裸机编程是一样的。
l 如果FreeRTOS工程的中断函数中调用了FreeRTOS的事件标志组的API函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点跟裸机编程稍有区别,详见18.4小节实验例程说明(中断方式):
l 另外强烈推荐用户将Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407,F429的NVIC优先级分组设置为4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
l 用户要在FreeRTOS多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
18.2事件标志组API函数
使用如下11个函数可以实现FreeRTOS的事件标志组:
u xEventGroupCreate()
u xEventGroupCreateStatic()
u vEventGroupDelete()
u xEventGroupWaitBits()
u xEventGroupSetBits()
u xEventGroupSetBitsFromISR()
u xEventGroupClearBits()
u xEventGroupClearBitsFromISR()
u xEventGroupGetBits()
u xEventGroupGetBitsFromISR()
u xEventGroupSync()
关于这11个函数的讲解及其使用方法可以看FreeRTOS在线版手册:
这里我们重点的说以下4个函数:
u xEventGroupCreate()
u xEventGroupWaitBits()
u xEventGroupSetBits()
u xEventGroupSetBitsFromISR()
因为本章节配套的例子使用的是这4个函数。
18.2.1 函数xEventGroupCreate
函数原型:
EventGroupHandle_t xEventGroupCreate( void );
函数描述:
函数xEventGroupCreate用于创建事件标志组。
u 返回值,如果创建成功,此函数返回事件标志组的句柄,如果FreeRTOSConfig.h文件中定义的heap空间不足会返回NULL
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
使用举例:
static EventGroupHandle_t xCreatedEventGroup = NULL;
static void AppObjCreate (void)
{
xCreatedEventGroup = xEventGroupCreate();
if(xCreatedEventGroup == NULL)
{
}
}
18.2.2函数xEventGroupSetBits
函数原型:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
函数描述:
函数xEventGroupSetBits用于设置指定的事件标志位为1。
u 第1个参数是事件标志组句柄。
u 第2个参数表示24个可设置的事件标志位,EventBits_t是定义的32位变量(详解18.1.2小节说明),低24位用于事件标志设置。变量uxBitsToSet的低24位的某个位设置为1,那么被设置的事件标志组的相应位就设置为1。变量uxBitsToSet设置为0的位对事件标志相应位没有影响。比如设置变量uxBitsToSet = 0x0003就表示将事件标志的位0和位1设置为1,其余位没有变化。
u 返回当前的事件标志组数值。
使用这个函数要注意以下问题:
1. 使用前一定要保证事件标志组已经通过函数xEventGroupCreate创建了。
2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是
xEventGroupSetBitsFromISR
3. 用户通过参数uxBitsToSet设置的标志位并不一定会保留到此函数的返回值中,下面举两种情况:
a. 调用此函数的过程中,其它高优先级的任务就绪了,并且也修改了事件标志,此函数返回的事件标志位会发生变化。
b. 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数xEventGroupWaitBits清除掉,等从高优先级任务返回到低优先级任务后,函数xEventGroupSetBits的返回值已经被修改。
使用举例:
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)
static EventGroupHandle_t xCreatedEventGroup = NULL;
static void vTaskTaskUserIF(void *pvParameters)
{
uint8_t ucKeyCode;
EventBits_t uxBits;
while(1)
{
ucKeyCode = bsp_GetKey();
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K2:
uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0);
if((uxBits & BIT_0) != 0)
{
printf("K2键按下,事件标志的bit0被设置\r\n");
}
else
{
printf("K2键按下,事件标志的bit0被清除,说明任务vTaskMsgPro已经接受到bit0和
bit1被设置的情况\r\n");
}
break;
case KEY_DOWN_K3:
uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_1);
if((uxBits & BIT_1) != 0)
{
printf("K3键按下,事件标志的bit1被设置\r\n");
}
else
{
printf("K3键按下,事件标志的bit1被清除,说明任务vTaskMsgPro已经接受到bit0和
bit1被设置的情况\r\n");
}
break;
default:
break;
}
}
vTaskDelay(20);
}
}
18.2.3函数xEventGroupSetBitsFromISR
函数原型:
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken );
函数描述:
函数xEventGroupSetBits用于设置指定的事件标志位为1。
u 第1个参数是事件标志组句柄。
u 第2个参数表示24个可设置的事件标志位,EventBits_t是定义的32位变量(详解18.1.2小节说明),低24位用于事件标志设置。变量uxBitsToSet的低24位的某个位设置为1,那么被设置的事件标志组的相应位就设置为1。变量uxBitsToSet设置为0的位对事件标志相应位没有影响。比如设置变量uxBitsToSet = 0x0003就表示将事件标志的位0和位1设置为1,其余位没有变化。
u 第3个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。
u 返回值,如果消息成功发送给daemon任务(就是FreeRTOS的定时器任务)返回pdPASS,否则返回pdFAIL,另外daemon任务中的消息队列满了也会返回pdFAIL。
使用这个函数要注意以下问题:
1. 使用前一定要保证事件标志已经通过函数xEventGroupCreate创建了。同时要在FreeRTOSConfig.h文件中使能如下三个宏定义:
#define INCLUDE_xEventGroupSetBitFromISR 1
#define configUSE_TIMERS 1
#define INCLUDE_xTimerPendFunctionCall 1
2. 函数xEventGroupSetBitsFromISR是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xEventGroupSetBits。
3. 函数xEventGroupSetBitsFromISR对事件标志组的操作是不确定性操作,因为不知道当前有多少个任务在等待此事件标志。而FreeRTOS不允许在中断服务程序和临界段中执行不确定性操作。为了不在中断服务程序中执行,就通过此函数给FreeRTOS的daemon任务(就是FreeRTOS的定时器任务)发送消息,在daemon任务中执行事件标志的置位操作。同时也为了不在临界段中执行此不确定操作,将临界段改成由调度锁来完成。这样不确定性操作在中断服务程序和临界段中执行的问题就都得到解决了。
4. 由于函数xEventGroupSetBitsFromISR对事件标志的置位操作是在daemon任务里面执行的,如果想让置位操作立即生效,即让等此事件标志的任务能够得到及时执行,需要设置daemon任务的优先级高于使用此事件标志组的所有其它任务。
5. 通过下面的使用举例重点一下函数xEventGroupSetBitsFromISR第三个参数的规范用法,初学者务必要注意。
使用举例:
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)
static EventGroupHandle_t xCreatedEventGroup = NULL;
static void TIM2_IRQHandler(void)
{
BaseType_t xResult;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
……
xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup,
BIT_0 ,
&xHigherPriorityTaskWoken );
if( xResult != pdFAIL )
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
18.2.4函数xEventGroupWaitBits
函数原型:
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
函数描述:
函数xEventGroupWaitBits等待事件标志被设置。
u 第1个参数是事件标志组句柄。
u 第2个参数表示等待24个事件标志位中的指定标志,EventBits_t是定义的32位变量(详解18.1.2小节说明),低24位用于事件标志设置。比如设置变量uxBitsToWaitFor = 0x0003就表示等待事件标志的位0和位1设置为1。此参数切不可设置为0。
u 第3个参数选择是否清除已经被置位的事件标志,如果这个参数设置为pdTRUE,且函数xEventGroupWaitBits在参数xTicksToWait设置的溢出时间内返回,那么相应被设置的事件标志位会被清零。如果这个参数设置为pdFALSE,对已经被设置的事件标志位没有影响。
u 第4个参数选择是否等待所有的标志位都被设置,如果这个参数设置为pdTRUE,要等待第2个参数uxBitsToWaitFor所指定的标志位全部被置1,函数才可以返回。当然,超出了在参数xTicksToWait设置的溢出时间也是会返回的。如果这个参数设置为pdFALSE,第2个参数uxBitsToWaitFor所指定的任何标志位被置1,函数都会返回,超出溢出时间也会返回。
u 第5个参数设置等待时间,单位时钟节拍周期。如果设置为 portMAX_DELAY,表示永久等待。
u 返回值,由于设置的时间超时或者指定的事件标志位被置1,导致函数退出时返回的事件标志组数值。
使用这个函数要注意以下问题:
1. 此函数切不可在中断服务程序中调用。
2. 这里再着重说明下这个函数的返回值,通过返回值用户可以检测是哪个事件标志位被置1了。
u 如果由于设置的等待时间超时,函数的返回值可会有部分事件标志位被置1。
u 如果由于指定的事件标志位被置1而返回,并且设置了这个函数的参数xClearOnExit为pdTRUE,那么此函数的返回值是清零前的事件标志组数值。
另外,调用此函数的任务在离开阻塞状态到退出函数xEventGroupWaitBits之间这段时间,如果一个高优先级的任务抢占执行了,并且修改了事件标志位,那么此函数的返回值会跟当前的事件标志组数值不同。
使用举例:
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)
static EventGroupHandle_t xCreatedEventGroup = NULL;
static void vTaskMsgPro(void *pvParameters)
{
EventBits_t uxBits;
const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;
while(1)
{
uxBits = xEventGroupWaitBits(xCreatedEventGroup,
BIT_ALL,
pdTRUE, /* 退出前bit0和bit1被清除,这里是bit0和bit1
都被设置才表示“退出”*/
pdTRUE,
xTicksToWait);
if((uxBits & BIT_ALL) == BIT_ALL)
{
printf("接收到bit0和bit1都被设置的消息\r\n");
}
else
{
bsp_LedToggle(3);
}
}
}