FreeRTOS — 任务计数信号量,任务二值信号量,任务事件标志组,任务消息邮箱**

以下内容转载自安富莱电子:http://forum.armfly.com/forum.php

FreeRTOS 计数信号量的另一种实现方式----基于任务通知(Task Notifications)的计数信号量,这里我们将这种方式实现的计数信号量称之为任务计数信号量。任务计数信号量效率更高,需要的 RAM 空间更小。当然,缺点也是有的,它没有前面介绍的计数信号量实现的功能全面。

1、任务通知(Task Notification)介绍

  FreeRTOS 每个已经创建的任务都有一个任务控制块(task control block),任务控制块就是一个结构体变量,用于记录任务的相关信息。结构体变量中有一个 32 位的变量成员 ulNotifiedValue 是专门用于任务通知的。
  通过任务通知方式可以实现计数信号量,二值信号量,事件标志组和消息邮箱(消息邮箱就是消息队列长度为 1 的情况)。使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来实现。任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改变量ulNotifiedValue 实现的:
 设置接收任务控制块中的变量 ulNotifiedValue 可以实现消息邮箱。
 如果接收任务控制块中的变量 ulNotifiedValue 还没有被其接收到,也可以用新数据覆盖原有数据,这就是覆盖方式的消息邮箱。
 设置接收任务控制块中的变量 ulNotifiedValue 的 bit0-bit31 数值可以实现事件标志组。
 设置接收任务控制块中的变量 ulNotifiedValue 数值进行加一或者减一操作可以实现计数信号量和二值信号量。
介绍了这么多,那么问题来了,采用这种方式有什么优势呢?根据官方的测试数据,唤醒由于信号量和事件标志组而处于阻塞态的任务,速度提升了 45%,而且这种方式需要的 RAM 空间更小。但这种方式实现的信号量和事件标志组也有它的局限性,主要表现在以下两个方面:

 任务通知方式仅可以用在只有一个任务等待信号量,消息邮箱或者事件标志组的情况,不过实际项目中这种情况也是最多的。
 使用任务通知方式实现的消息邮箱替代前面章节讲解的消息队列时,发送消息的任务不支持超时等待,即消息队列中的数据已经满了,可以等待消息队列有空间可以存新的数据,而任务通知方式实现的消息邮箱不支持超时等待。

2 任 务 计 数 信 号 量

前面对计数信号量进行了讲解,计数信号量就是对一个变量进行计数,变量的范围是从0到用户创建计数信号量时所设置的大小。当计数变量大于0的时候计数信号量管理的资源才可以使用,计数变量的具体数值就是可用的资源大小。
本章节讲解的任务计数信号量与前面讲解的计数信号量要实现的功能是一样的,不同的是调用的函数和使用的计数变量:
 任务计数信号量的计数变量是通过任务控制块中的一个 32 位变量 ulNotifiedValue 实现计数。前面讲解的计数信号量创建后会有自己的计数变量。
 任务计数信号量是通过函数 ulTaskNotifyTake()替代,计数信号量讲解的函数 xSemaphoreTake()实现资源获取,即对计数信号量数值进行减一操作。
 任务计数信号量是通过函数 xTaskNotifyGive() 和 vTaskNotifyGiveFromISR()替代,计数信号量讲解的函数 xSemaphoreGive() 和 xSemaphoreGiveFromISR()实现资源释放,即对计数信号量的数值进行加一操作。
实际项目中,如果使用计数信号量和任务计数信号量都能实现相应功能,强烈建议使用任务计数信号量

 3、任务计数信号量API函数

使用如下 9 个函数可以实现 FreeRTOS 的任务信号量(含任务计数信号量和任务二值信号量):
 xTaskNotifyGive()
 vTaskNotifyGiveFromISR()
 ulTaskNotifyTake()
 xTaskNotify()
 xTaskNotifyAndQuery()
 xTaskNotifyAndQueryFromISR()
 xTaskNotifyFromISR()

 xTaskNotifyWait()
 xTaskNotifyStateClear()
关于这 9 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:

3.1、函数 xTaskNotifyGive

函数原型:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); /* 任务句柄 */
函数描述:
函数 xTaskNotifyGive 用于释放信号量(含任务二值信号量,任务计数信号量)。
 第 1 个参数是任务句柄。
 返回值,仅有一个返回值 pdPASS。
使用这个函数要注意以下问题: 

1. 任务信号量的初始计数值是 0。任务信号量不像前面章节讲解的信号量,无需单独创建即可使用。
2. 默认配置此函数可以使用的的宏定义已经在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
当然,如果用户不需要使用任务通知功能相关的函数,可以在 FreeRTOSConfig.h 文件中配置此宏定义为 0 来禁止,这样创建的每个任务可以节省 8 个字节的需求。
3. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是vTaskNotifyGiveFromISR。
 

3.2 函数 vTaskNotifyGiveFromISR

函数原型:
void vTaskNotifyGiveFromISR(
            TaskHandle_t xTaskToNotify, /* 任务句柄 */
            BaseType_t *pxHigherPriorityTaskWoken ); /* 高优先级任务是否被唤醒的状态保存 */
函数描述:
函数 xTaskNotifyGive 用于释放信号量(含任务二值信号量,任务计数信号量)。
 第 1 个参数是任务句柄。
 第2个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,
说明有高优先级任务要执行,否则没有。
使用这个函数要注意以下问题:
1. 任务信号量的初始计数值是 0。任务信号量不像前面讲解的信号量,无需单独创建即可使用。
2. 默认配置此函数可以使用的的宏定义已经在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
当然,如果用户不需要使用任务通知功能相关的函数,可以在 FreeRTOSConfig.h 文件中配置此宏定义为 0 来禁止,这样创建的每个任务可以节省 8 个字节的需求。
3. 此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xTaskNotifyGive。
使用举例:

static TaskHandle_t xHandleTaskMsgPro = NULL;/* 任务句柄,操作此句柄前一定要保证相应任务已经创建 */
/*
*********************************************************************************************************
* 函 数 名: TIM2_IRQHandler
* 功能说明: 定时器中断。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void TIM2_IRQHandler (void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 中断消息处理,此处省略 */
……
/* 发送任务通知 */
vTaskNotifyGiveFromISR(xHandleTaskMsgPro, &xHigherPriorityTaskWoken);
/* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

3.3 函数 ulTaskNotifyTake

函数原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, /* 选择是否清零用于任务通知的 ulNotifiedValue */
            TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

函数描述:
函数 ulTaskNotifyTake用于任务信号量的获取(含任务二值信号量,任务计数信号量)。
 第 1 个参数配置为 pdFALSE 表示函数返回前用于任务信号量的内部变量 ulNotifiedValue(详情看1 小节的解释说明)数值减一,这种方式用于任务计数信号量。参数配置为 pdTRUE 表示函数返回前用于任务信号量的内部变量 ulNotifiedValue(详情看 1 小节的解释说明)数值被清零,这种方式用于任务二值信号量。
 第 2 个参数是没有任务信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
使用这个函数要注意以下问题:
1. 任务信号量的初始计数值是 0。任务信号量不像前面章节讲解的信号量,无需单独创建即可使用。
2. 默认配置此函数可以使用的的宏定义已经在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
当然,如果用户不需要使用任务通知功能相关的函数可以在 FreeRTOSConfig.h 文件中配置此宏定义为 0 来禁止,这样创建的每个任务可以节省 8 个字节的需求。
3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。

未完待续~~(实验任务间通信,中断方式)

 

 FreeRTOS 任务二值信号量

任务二值信号量

前面 我们对二值信号量进行了讲解,二值信号量只有两种数值 0 和 1。本章节讲解的任务二值信号量与第 22 章讲解的二值信号量要实现的功能是一样的,不同的是调用的函数和使用的计数变量:

 任务二值信号量的计数变量是通过任务控制块中的一个 32 位变量 ulNotifiedValue 实现计数。前面讲解的二值信号量创建后会有自己的计数变量。
 任务二值信号量是通过函数 ulTaskNotifyTake()替代前面讲解的函数 xSemaphoreTake()实现资源获取,即对二值信号量数值进行清零操作。
 任务二值信号量是通过函数 xTaskNotifyGive() 和 vTaskNotifyGiveFromISR()替代前面讲解的函数 xSemaphoreGive() 和 xSemaphoreGiveFromISR()实现资源释放,即对二值信号量的数值进行加一操作。

多次调用函数 xTaskNotifyGive ()难免会出现计数值大于 1 的情况,用作任务二值信号量时,我们可以将所有大于 1 的计数理解为一种情况,即二值信号量管理的资源可用。因此,不管当前的计数是多少,大于 0 的计数在通过函数 ulTaskNotifyTake()获取二值信号量的时候统一清零,这样就实现了二值信号量的功能。

实际项目中,如果使用二值信号量和任务二值信号量都能实现相应功能,强烈建议使用任务二值信号量。

 

 

未完待续~~(实验任务间通信,中断方式)

 

 

FreeRTOS任务事件标志组

任 务 事 件 标 志 组

前面,我们对事件标志组进行了讲解。本节讲解的任务事件标志组与前面讲解的事件标志组要实现的功能是一样的,不同的是调用的函数和支持的事件标志个数,任务事件标志组支持 32 个事件标志设置,而前面介绍的事件标志组,每创建一个支持 24 个事件标志设置:

 任务事件标志组的事件标志位是通过任务控制块中的一个 32 位变量 ulNotifiedValue 实现。前面讲解的事件标志组创建后会有自己可以设置的事件标志位。
 任务事件标志组是通过函数 xTaskNotifyWait()替代前面讲解的函数 xEventGroupWaitBits ()实现等待事件标志位被设置。
 任务事件标志组是通过函数 xTaskNotify() 和 xTaskNotifyFromISR()替代前面讲解的函数xEventGroupSetBits() 和 xEventGroupSetBitsFromISR 实现对事件标志位的设置。
 前面讲解的函数 xEventGroupSetBitsFromISR 是通过给 Daemon 任务(定时器任务)发消息,在定时器任务中执行实际的操作,而我们本节要介绍的函数 xTaskNotifyFromISR 是直接在中断服务程序里面执行操作,效率要高很多。

实际项目中,如果使用事件标志组和任务事件标志组都能实现相应功能,强烈建议使用任务事件标志组。

 

 函数 xTaskNotify

函数原型:

函数描述:
函数 xTaskNotify 通过设置任务控制块中的变量 ulNotifiedValue 可以在任务代码中实现任务事件标志组,任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息通知。
 第 1 个参数是任务句柄。
 第 2 个参数是用来更新任务控制块中的 32 位变量 ulNotifiedValue。
 第 3 个参数是任务通知模式设置,支持以下 5 个参数:

 返回值,根据上面第 3 个参数的说明,将其设置为 eSetValueWithoutOverwrite,有可能返回pdFALSE,其余所有情况都返回值 pdPASS。

使用这个函数要注意以下问题:
1. 任务创建后,任务控制块中的变量 ulNotifiedValue 初始计数值是 0。
2. 默认配置此函数可以使用的的宏定义已经在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
当然,如果用户不需要使用任务通知功能相关的函数,可以在 FreeRTOSConfig.h 文件中配置此宏定义为 0 来禁止,这样创建的每个任务可以节省 8 个字节的需求。
3. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xTaskNotifyFromISR。
4. 根据 FreeRTOS 的建议,实现二值信号量和计数信号量时使用函数 xTaskNotifyGive()替代此函数xTaskNotify()。

函数 xTaskNotifyFromISR 

函数描述:
函数 xTaskNotifyFromISR 通过设置任务控制块中的变量 ulNotifiedValue 可以在中断服务程序中实现任
务事件标志组,任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息通知(见 26.1 说明)。
 第 1 个参数是任务句柄。
 第 2 个参数是用来更新任务控制块中的 32 位变量 ulNotifiedValue。
 第 3 个参数是任务通知模式设置,支持5 个参数,如上面5个参数一样: 

 第 4 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,说明有高优先级任务要执行,否则没有。
 返回值,根据上面第 3 个参数的说明,将其设置为 eSetValueWithoutOverwrite,有可能返回pdFALSE,其余所有情况都返回值 pdPASS。
使用这个函数要注意以下问题:
1. 任务创建后,任务控制块中的变量 ulNotifiedValue 初始计数值是 0。
2. 默认配置此函数可以使用的的宏定义已经在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
当然,如果用户不需要使用任务通知功能相关的函数,可以在 FreeRTOSConfig.h 文件中配置此宏定义为 0 来禁止,这样创建的每个任务可以节省 8 个字节的需求。
3. 此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xTaskNotify。
4. 根据 FreeRTOS 的建议,实现二值信号量和计数信号量时使用函数 vTaskNotifyGiveFromISR ()替代此函数 xTaskNotifyFromISR ()。

 

函数 xTaskNotifyWait

函数描述:
函数 xTaskNotifyWait 可以在任务代码中实现任务事件标志组,任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息获取。
 第 1 个参数 ulBitsToClearOnEntry 用于函数执行之前,将任务控制块中的变量 ulNotifiedValue 进行如下操作 :
ulNotifiedValue &= ~ulBitsToClearOnEntry
简单的说就是参数 ulBitsToClearOnEntry 哪个位是 1,那么变量 ulNotifiedValue 的那个位就会被清零。 比如 ulBitsToClearOnEntry = 0x01 表示将变量 ulNotifiedValue 的 bit0 清零,又比如ulBitsToClearOnEntry = 0xffffffff 表示将变量 ulNotifiedValue 的所有位清零。
 第 2 个参数 ulBitsToClearOnExit 用于函数退出前,将任务控制块中的变量 ulNotifiedValue 进行如下操作 : 

ulNotifiedValue &= ~ ulBitsToClearOnExit

简单的说就是参数 ulBitsToClearOnExit 哪个位是 1,那么变量 ulNotifiedValue 的那个位就会被清零。 比如 ulBitsToClearOnExit= 0x01 表示将变量 ulNotifiedValue 的 bit0 清零,又比如ulBitsToClearOnExit= 0xffffffff 表示将变量 ulNotifiedValue 的所有位清零。
 第 3 个参数用于将任务控制块中的变量 ulNotifiedValue 保存到此参数指针所指向的存储单元。 如果此参数没有用上,可以将其设置为 NULL。
 第 4 个参数是没有消息时,等待消息的最大等待时间,单位系统时钟节拍。
 返回值,如果成功接收到消息返回 pdTRUE,否则返回 pdFALSE,比如在设置的超时时间内没有收到消息。 

 使用这个函数要注意以下问题:
1. 任务创建后,任务控制块中的变量 ulNotifiedValue 初始计数值是 0。
2. 默认配置此函数可以使用的的宏定义已经在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
当然,如果用户不需要使用任务通知功能相关的函数,可以在 FreeRTOSConfig.h 文件中配置此宏定义为 0 来禁止,这样创建的每个任务可以节省 8 个字节的需求。
3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到消息可用。
4. 根据 FreeRTOS 的建议,实现二值信号量和计数信号量时使用函数 ulTaskNotifyTake ()替代此函数xTaskNotifyWait ()。

 

 

未完待续~~(实验任务间通信,中断方式)

 

 

任务消息邮箱

本章节为大家讲解 FreeRTOS 消息队列(消息队列长度固定为 1)的另一种实现方式----基于任务通知(Task Notifications)的消息队列,这里我们将这种方式实现的消息队列(消息队列长度固定为 1)称之为任务消息邮箱。 这种方式实现的消息队列效率更高,需要的 RAM 空间更小。当然,缺点也是有的,它没有前面章节介绍的消息队列实现的功能全面(消息邮箱就是将消息队列长度设置为 1 的情况)。

任务消息邮箱
前面章节,我们对消息队列进行了讲解,而消息邮箱就是将消息队列的长度设置为 1 的情况。 本节讲解的任务消息邮箱与前面章节讲解的消息队列长度是 1 时要实现的功能是一样的,不同的是调用的函数和消息存储的位置:
 任务消息邮箱是通过任务控制块中的一个 32 位变量 ulNotifiedValue 对数据进行存取。 前面章节讲解的消息队列创建后会有自己可以存取数据的空间。
 任务消息邮箱是通过函数 xTaskNotifyWait()替代之前讲解的函数 xQueueReceive ()实现从消息邮箱获取数据。
 任务消息邮箱是通过函数 xTaskNotify() 和 xQueueSendFromISR ()替代之前讲解的函数xQueueSend () 和 xEventGroupSetBitsFromISR 实现向消息邮箱存入数据。
 

本章节为大家讲解了 FreeRTOS 的任务消息邮箱,任务消息邮箱相比消息队列(消息队列长度固定为1)的优势就是执行效率高,需要的 RAM 空间小。

实际项目中,如果使用消息队列和任务消息邮箱都能实现相应功能,强烈建议使用任务事消息邮箱。

 

未完待续~~(实验任务间通信,中断方式)

 

最后强调一下:

上面表格的5个参数,决定了是用于事件标志组,还是信号量,还是消息邮箱。

posted @ 2017-08-27 18:18  Liu_Jing  Views(580)  Comments(0Edit  收藏  举报