1,FreeRTOS 任务计数信号量

两个关键词: 计数信号量(之前章节) , 任务计数信号量 (本章,推荐用这个)

基于任务通知(Task Notifications) 的计数信号量,这里我们将这种方式实现的计数信号量称之为任务计数信号量。

任务计数信号量效率更高, 需要的 RAM 空间更小。

1.1任务通知(Task Notiiffiicatiions)介绍

FreeRTOS每个已经创建的任务都有一个任务控制块(task control block),任务控制块就是一个结构体变量,用于记录任务的相关信息。结构体变量中有一个32位的变量成员ulNotifiedValue是专门用于任务通知的。

通过任务通知方式可以实现计数信号量二值信号量事件标志组消息邮箱(消息邮箱就是消息队 列长度为 1 的情况)。使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来 实现。任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改变量

  • 设置接收任务控制块中的变量 ulNotifiedValue 可以实现消息邮箱。
  • 如果接收任务控制块中的变量 ulNotifiedValue 还没有被其接收到,也可以用新数据覆盖原有数据 ,这就是覆盖方式的消息邮箱。
  • 设置接收任务控制块中的变量 ulNotifiedValue 的 bit0-bit31 数值可以实现事件标志组。
  • 设置接收任务控制块中的变量 ulNotifiedValue 数值进行加一或者减一操作可以实现计数信号量和二 值信号量

优点:

介绍了这么多,那么问题来了,采用这种方式有什么优势呢?根据官方的测试数据,唤醒由于信号量 和事件标志组而处于阻塞态的任务,速度提升了 45%,而且这种方式需要的 RAM 空间更小。

缺点:

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

1.2任务计数信号量

本章节讲解的任务计数信号量与前面文章讲解的计数信号量要实现的功能是一样的,但是实现方法不一样。

1.3任务计数信号量 APII 函数

函数 xTaskNotifyGive

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); /* 任务句柄 */

函数 xTaskNotifyGive 用于释放信号量(含任务二值信号量,任务计数信号量)。

返回值,仅有一个返回值 pdPASS。

任务信号量的初始计数值是 0。任务信号量不像前面章节讲解的信号量,无需单独创建即可使用。

define configUSE_TASK_NOTIFICATIONS 1

函数 vTaskNotifyGiveFromISR

void vTaskNotifyGiveFromISR(
TaskHandle_t xTaskToNotify, /* 任务句柄 */
BaseType_t *pxHigherPriorityTaskWoken ); /* 高优先级任务是否被唤醒的状态保存 */

函数 xTaskNotifyGive 用于释放信号量(含任务二值信号量,任务计数信号量)。

第 2 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,说明有高优先级任务要执行,否则没有。

函数 ulTaskNotifyTake

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

函数 xTaskNotifyGive 用于释放信号量(含任务二值信号量,任务计数信号量)。

第 1 个参数配置为 pdFALSE 表示函数返回前用于任务信号量的内部变量 ulNotifiedValue数值减一,这种方式用于任务计数信号量
参数配置为 pdTRUE 表示函数返回前用于任务信号量的内部变量 ulNotifiedValue数值被清零,这种方式用于任务二值信号量

第 2 个参数是没有任务信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

/* 
 函数 ulTaskNotifyTake 第一个参数说明:
 1. 此参数设置为 pdFALSE,任务 vTaskMsgPro 的 TCB(任务控制块)中的变量 ulNotifiedValue 减一
 2. 此参数设置为 pdTRUE,任务 vTaskMsgPro 的 TCB(任务控制块)中的变量 ulNotifiedValue 清零
*/

2,任务二值信号量

FreeRTOS 二值信号量的另一种实现方式—-基于任务通知(Task Notifications) 的二值信号量,这里我们将这种方式实现的二值信号量称之为任务二值信号量。

二值信号量只有两种数值 0 和 1

大于 0 的计数在通过函数 ulTaskNotifyTake()获取二值信号量的时候统一清零,这样就实现了二值信号量 的功能。

下面的代码感觉有些问题,pdTRUE和pdFLASE在ulTaskNotifyTake()的使用,什么时候是二值?例程是 ulTaskNotifyTake(pdFALSE,portMAX_DELAY);

uint32_t ulNotifiedValue;

/* 
 函数 ulTaskNotifyTake 第一个参数说明:
 1. 此参数设置为 pdFALSE,任务 vTaskMsgPro 的 TCB(任务控制块)中的变量 ulNotifiedValue 减一
 2. 此参数设置为 pdTRUE,任务 vTaskMsgPro 的 TCB(任务控制块)中的变量 ulNotifiedValue 清零
*/

ulNotifiedValue=ulTaskNotifyTake(pdFALSE,pdMS_TO_TICKS(500));//
ulNotifiedValue=ulTaskNotifyTake(pdTRUE,portMAX_DELAY);//
if(ulNotifiedValue>0)
{}

3,FreeRTOS 任务事件标志组

FreeRTOS 事件标志组的另一种实现方式—-基于任务通知(Task Notifications) 的事件标志组,这里我们将这种方式实现的事件标志组称之为任务事件标志组

功能是一样的,不同的是调用的函数和支持的事件标志个数:任务事件标志组支持 32 个事件标志设置,而前面介绍的事件标志组,每创建一个支持 24 个事件标志设置

xTaskNotifyWait()xEventGroupWaitBits ()
xTaskNotify()xEventGroupSetBits()
任务事件标志事件标志

3.1函数 xTaskNotify

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, /* 任务句柄 */
                         uint32_t ulValue, /* 更新任务控制块中的变量 ulNotifiedValue */
                        eNotifyAction eAction ); /* 任务通知模式设置 */

函数 xTaskNotify 通过设置任务控制块中的变量 ulNotifiedValue 可以在任务代码中实现任务事件标志组, 任务计数信号量,任务消息邮箱和任务二值信号量四种方式的消息通知

  • 第 1 个参数是任务句柄。
  • 第 2 个参数是用来更新任务控制块中的 32 位变量 ulNotifiedValue。
  • 第 3 个参数是任务通知模式设置,支持以下 5 个参数:
  • 返回值,根据上面第 3 个参数的说明,将其设置为 eSetValueWithoutOverwrite,有可能返回 pdFALSE,其余所有情况都返回值 pdPASS。

根据 FreeRTOS 的建议,实现二值信号量和计数信号量时使用函数 xTaskNotifyGive()替代此函数 xTaskNotify()。

3.2函数 xTaskNotifyFromISR

3.3函数 xTaskNotifyWait

BaseType_t xTaskNotifyWait( 
    /* 设置函数执行前清零任务控制块中变量 ulNotifiedValue 那些位 */
    uint32_t ulBitsToClearOnEntry, 
    /*设置函数退出前清零任务控制块中变量 ulNotifiedValue 那些位 */
    uint32_t ulBitsToClearOnExit,
    /* 保存任务控制块中的变量 ulNotifiedValue 到指针变量 pulNotifiedValue 所指向的存储单元 */
    uint32_t *pulNotificationValue, 
    /* 等待消息通知的最大等待时间 */ 
    TickType_t xTicksToWait );

函数 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,比如在设置的超时时间内没有收 到消息。

如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配 置为 portMAX_DELAY,那么此函数会永久等待直到消息可用。

根据 FreeRTOS 的建议,实现二值信号量和计数信号量时使用函数 ulTaskNotifyTake ()替代此函数 xTaskNotifyWait ()

/*
第一个参数 ulBitsToClearOnEntry 的作用(函数执行前):
 ulNotifiedValue &= ~ulBitsToClearOnEntry
 简单的说就是参数 ulBitsToClearOnEntry 那个位是 1,那么 notification value
 的那个位就会被清零。
 这里 ulBitsToClearOnEntry = 0x00000000 就是函数执行前保留所有位。
 第二个参数 ulBitsToClearOnExit 的作用(函数退出前):
 ulNotifiedValue &= ~ulBitsToClearOnExit
 简单的说就是参数 ulBitsToClearOnEntry 那个位是 1,那么 notification value
 的那个位就会被清零。
 这里 ulBitsToClearOnExi = 0xFFFFFFFF 就是函数退出前清楚所有位。
 注:ulNotifiedValue 表示任务 vTaskMsgPro 的任务控制块里面的变量。
*/
xResult = xTaskNotifyWait(0x00000000, 
 0xFFFFFFFF, 
 &ulValue, /* 保存 ulNotifiedValue 到变量 ulValue 中 */
 xMaxBlockTime); /* 最大允许延迟时间 */
if( xResult == pdPASS )
{
/* 接收到消息,检测那个位被按下 */
if((ulValue & BIT_0) != 0)
{
printf("接收到 K2 按键按下后的定时器中断消息, ulNotifiedValue = 0x%08x\r\n", ulValue);
}
if((ulValue & BIT_1) != 0)
{
printf("接收到 K3 按键按下后的定时器中断消息, ulNotifiedValue = 0x%08x\r\n", ulValue);
} }

4,FreeRTOS 任务消息邮箱

实现的消息队列(消息队列长度固定为 1)称之为任务消息邮箱。

  • xTaskNotify
  • xTaskNotifyFromISR
  • xTaskNotifyWait
xTaskNotifyFromISR(xHandleTaskMsgPro, /* 目标任务 */
 g_uiCount++, /* 发送数据 */
 eSetValueWithOverwrite,/* 如果目标任务上次的数据还没有处理,上次的数据会被覆盖 */
 &xHigherPriorityTaskWoken);
/* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);




/* 通过此函数的返回值可以判断是否发送成功,这里没有做判断 */
xTaskNotifyFromISR(xHandleTaskMsgPro, /* 目标任务 */
 g_uiCount++, /* 发送数据 */
 eSetValueWithoutOverwrite,/* 如果目标任务上次的数据还没有处理,上次的数据不会被覆盖 */
 &xHigherPriorityTaskWoken);