FreeRTOS任务通知

       FreeRTOS 内核 V8.2.0 版本发布的时候,FreeRTOS 新增了任务通知这个功能,任务通知也 是用于任务间进行同步和通讯的一种机制,但是相对于前面章节介绍的队列、事件标志组和信 号量等而言,任务通知在内存占用和效率方面都有很大的优势。
FreeRTOS 任务通知简介
       在 FreeRTOS 中,每一个任务都有两个用于任务通知功能的数组,分别为任务通知数组和 任务通知状态数组。其中任务通知数组中的每一个元素都是一个 32 位无符号类型的通知值;而 任务通知状态数组中的元素则表示与之对应的任务通知的状态。
       任务通知数组中的 32 位无符号通知值,用于任务到任务或中断到任务发送通知的“媒介”。 当通知值为 0 时,表示没有任务通知;当通知值不为 0 时,表示有任务通知,并且通知值就是 通知的内容。 
       任务通知状态数组中的元素,用于标记任务通知数组中通知的状态,任务通知有三种状态, 分别为未等待通知状态、等待通知状态和等待接收通知状态。其中未等待通知状态为任务通知 的复位状态;当任务在没有通知的时候接收通知时,在任务阻塞等待任务通知的这段时间内, 任务所等待的任务通知就处于等待通知状态;当有其他任务向任务发送通知,但任务还未接收 这一通知的这段期间内,任务通知就处于等待接收通知状态。 
       任务通知功能所使用到的任务通知数组和任务通知状态数组为任务控制块中的成员变量, 因此任务通知的传输是直接传出到任务中的,不同通过任务的通讯对象(队列、事件标志组和 信号量就属于通讯对象)这个间接的方式。间接通讯示意图如下所示:
      
      任务通知则是直接地往任务中发送通知,直接通讯示意图如下所示:

任务通知的优势

        使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多;并且使用 任务通知代替队列、事件标志组或信号量,可以节省大量的内存,这是因为每个通讯对象在使 用之前都需要被创建,而任务通知功能中的每个通知只需要在每个任务中占用固定的 5 字节内存。

任务通知的缺点
虽然任务通知功能相比通讯对象,有着更快、占用内存少的优点,但是任务通知功能并不 能适用于所有情况,例如以下列出的几种情况: 
1. 发送事件或数据到中断 通讯对象可以发送事件或数据从中断到任务,或从任务到中断,但是由于任务通知依赖于 任务控制块中的两个成员变量,并且中断不是任务,因此任务通知功能并不适用于从任务往中 断发送事件或数据的这种情况,但是任务通知功能可以在任务之间或从中断到任务发送事件或 数据。 
2. 存在多个接收任务 通讯对象可以被已知通讯对象句柄的任意多个任务或中断访问(发送或接收),但任务通知 是直接发送事件或数据到指定接收任务的,因传输的事件或数据只能由接收任务处理。然而在 实际中很少受到这种情况的限制,因为,虽然多个任务和中断发送事件或数据到一个通讯对象 是很常见的,但很少出现多个任务或中断接收同一个通讯对象的情况。 
3. 缓冲多个数据项 通讯对象中的队列是可以一次性保存多个已经被发送到队列,但还未被接收的事件或数据 的,也就是说,通讯对象有着一定的缓冲多个数据的能力,但是任务通知是通过更新任务通知 值来发送事件或数据的,一个任务通知值只能保存一次。 
4. 广播到多个任务 通讯对象中的事件标志组是可以将一个事件同时发送到多个任务中的,但任务通知只能是 被指定的一个接收任务接收并处理。 
5. 阻塞等待接收任务 当通讯对象处于暂时无法写入的状态(例如队列已满,此时无法再向队列写入消息)时, 发送任务是可以选择阻塞等待接收任务接收,但是任务因尝试发送任务通知到已有任务通知但 还未处理的任务而进行阻塞等待的。但是任务通知也很少在实际情况中收到这种情况的限制。 

FreeRTOS 任务通知相关 API 函数  

       FreeRTOS 提供了任务通知的一些相关操作函数,其中任务通知相关 API 函数,如下两表所示:

        以上两表列出了 FreeRTOS 提供的几个任务通知相关的操作函数,从第 17.1 小节《FreeRTOS 任务通知简介》中,可以知道任务的任务控制块中,与任务通知功能相关的两个成员变量,任 务通知值和任务通知状态,是两个数组,也就是说,一个任务可以有多个任务通知,多个通知 就通过数组的下标进行索引。 

       表 17.2.1 所列出的 API 函数都是对任务通知相关数组中下标为 0 的元素进行操作,而表 17.2.2 中列出的 API 函数可以指定对任务通知相关数组中的元素进行操作。两表中对应的 API 函数原理上是一样的,只是表 17.2.1 中的 API 是固定对任务的任务通知 0 进行操作,而表 17.2.2 中的 API 函数可以对任务的指定任务通知进行操作,本文以表 17.2.1 中的函数为例进行讲解。

发送任务通知

        三个用于在任务中发送任务通知的函数,实际上都是调用了函 数 xTaskGenericNotify()来发送任务通知的,只是传入了不同的参数。函数 xTaskGenericNotify() 的函数原型如下所示:

       函数 xTaskGenericNotify()的形参描述,如下表所示:

       结合函数 xTaskNotify()、函数 xTaskNotifyAndQuery()、函数 xTaskNotifyGive()的定义和以 上代码,可以知道函数 xTaskNotify()、函数 xTaskNotifyAndQuery()、函数 xTaskNotifyGive()的 作用如下所示:

函数 xTaskNotify(): 此函数用于往指定任务发送任务通知,通知方式可以自由指定,并且不获取发送任务通知 前任务通知的通知值。

函数 xTaskNotifyAndQuery(): 此函数用于往指定任务发送任务通知,通知方式可以自由指定,并且获取发送任务通知前 任务通知的通知值。

函数 xTaskNotifyGive(): 此函数用于往指定任务发送任务通知,通知方式为将通知值加 1,并且不获取发送任务通 知前任务通知的通知值。

在中断中发送任务通知
       函数 xTaskNotifyFromISR()和函数 xTaskNotifyAndQueryFromISR() 实际上都是调用了函数 xTaskGenericNotifyFromISR(),而函数 vTaskNotifyGiveFromISR()实际上 则是调用了函数 vTaskGenericNotifyGiveFromISR()。下面就分别看一下以上这两个实际被调用 的函数。 
        函数 xTaskGenericNotifyFromISR()的函数原型如下所示:

       函数 xTaskGenericNotifyFromISR()的形参描述,如下表所示:

       函数 xTaskGenericNotifyFromISR()于函数 xTaskNotify()是很相 似的,只是多了对中断做了一些相应的处理。     

      函数 vTaskGenericNotifyGiveFromISR()的函数原型如下所示:

       结合以上函数 xTaskGenericNotifyFromISR()和函数 vTaskGenericNotifyGiveFromISR()的源 代码和函数 xTaskNotifyFromISR() 和函数 xTaskNotifyAndQueryFromISR() 和函数 vTaskNotifyGiveFromISR()的定义,表 17.2.1 中列出的三个在中断中发送任务通知的 API 函数作 用如下:

函数 xTaskNotifyFromISR()

       此函数用于在中断中往指定任务发送任务通知,通知方式可以自由指定,并且不获取发送 任务通知前任务通知的通知值,但获取发送通知后是否需要进行任务切换的标志。

函数 xTaskNotifyAndQueryFromISR()

       此函数用于在中断中往指定任务发送任务通知,通知方式可以自由指定,并且获取发送任 务通知前任务通知的通知值,和发送通知后是否需要进行任务切换的标志。

函数 vTaskNotifyGiveFromISR()

       此函数用于在中断中往指定任务发送任务通知,通知方式为将通知值加 1,并且不获取发 送任务通知前任务通知的通知值,但获取发送通知后是否需要进行任务切换的标志。

接收任务通知
       用 于 获 取 任 务 通 知 的 API 函 数 有 两 个 , 分 别 为 函 数 ulTaskNotifyTake() 和函数 xTaskNotifyWait()。
函数 ulTaskNotifyTake()
       此函数用于获取任务通知的通知值,并且在成功获取任务通知的通知值后,可以指定将通知 值清零或减 1。此函数实际上是一个宏定义,具体的代码如下所示:

函数 xTaskNotifyWait()

       此函数用于等待任务通知通知值中的指定比特位被置一,此函数可以在等待前和成功等待 到任务通知通知值中的指定比特位被置一后清零指定比特位,并且还能获取等待超时后任务通 知的通知值。此函数实际上是一个宏定义,具体的代码如下所示:

       从 上 面 的 代 码 中 可 以 看 出 , 函 数 xTaskNotifyWait() 实 际 上 是 调 用 了 函 数 xTaskGenericNotifyWait(),函数 xTaskGenericNotifyWait()的函数原型如下所示:

FreeRTOS 任务通知模拟二值信号量实验
        二值信号量的模拟还是相对比较简单的,程序设计验证思想还是以LED_GREEN任务来发送任务,API任务负责接收任务同时来进行显示。
NOTE: notify_val = ulTaskNotifyTake((BaseType_t) pdTRUE, (TickType_t)portMAX_DELAY);
因为是二值信号量,所以基本就是0和1的状态,所以需要将此参数设置为pdTRUE, 每次任务通知获取成功后就将该信号通知清零
       也需要特别注意一下两个任务的延时,为了获得比较好的实时响应,所以获取任务通知的函数频率要高于任务发出函数。
void led_green_task(void *pvParameters)
{
    unsigned char led_green_on_flag = 0;
    
    while(1)
    {
        if(led_green_on_flag)
        {
             LED_GREEN_ON;    
             
             if(ApiTask_Handler != NULL)
             {
                  xTaskNotifyGive((TaskHandle_t)ApiTask_Handler);
             }
            
        }else
        {    
             LED_GREEN_OFF;     
        }
        
        led_green_on_flag = ~led_green_on_flag;
        
        vTaskDelay(50);
    }
}

void api_task(void *pvParameters)
{    
     uint32_t notify_val = 0;
    
     while(1)
     {    
         notify_val = ulTaskNotifyTake((BaseType_t)pdTRUE, (TickType_t)portMAX_DELAY);
         
         if(notify_val != 0)
         {
              printf("\r\n接收到LED RED任务通知:%d.\r\n", notify_val);
         }
         
         vTaskDelay(20);
     }
}

下载验证:

FreeRTOS 任务通知模拟计数型信号量实验

       计数型信号量的模拟也是相对比较简单的,程序设计验证思想还是以LED_GREEN任务来发送任务,API任务负责接收任务同时来进行显示。

       NOTE: notify_val = ulTaskNotifyTake((BaseType_t)pdFALSE, (TickType_t)portMAX_DELAY);

        因为是计数型信号量,所以需要将此参数设置为pdFALSE,每次任务通知获取成功后就将该信号通知数量-1。

       也需要特别注意一下两个任务的延时,为了验证计数型信号量,所以任务发出函数的运行频率要远大于任务通知接收函数,以此可以达到任务通知计数的目的,为了防止溢出,限定了任务通知次数最大为10。

void led_green_task(void *pvParameters)
{
    unsigned char led_green_on_flag = 0;
    unsigned char task_give_counter = 0;
    
    while(1)
    {
        if(led_green_on_flag)
        {
             LED_GREEN_ON;    
             
             if(ApiTask_Handler != NULL && task_give_counter <= 10)
             {
                  task_give_counter++;
                  xTaskNotifyGive((TaskHandle_t)ApiTask_Handler);
             }
            
        }else
        {    
             LED_GREEN_OFF;     
        }
        
        led_green_on_flag = ~led_green_on_flag;
        
        vTaskDelay(50);
    }
}

void api_task(void *pvParameters)
{    
     uint32_t notify_val = 0;
    
     while(1)
     {    
         notify_val = ulTaskNotifyTake((BaseType_t)pdFALSE, (TickType_t)portMAX_DELAY);
         
         if(notify_val != 0)
         {
              printf("\r\n接收到LED RED任务通知:%d.\r\n", notify_val);
         }
         
         vTaskDelay(1000);
     }
}

下载验证:可以看到还是符合程序设计预期的,任务通知会积累到10,任务接收函数会按照自己的频率一个一个清除。

FreeRTOS 任务通知模拟消息邮箱实验
      程序设计验证思想是以LED_RED任务以及WS2812B任务来发送任务通知的消息邮箱,COUNTER任务负责接收任务同时来进行显示。
void led_red_task(void *pvParameters)
{
    unsigned char led_red_on_flag = 0;
    unsigned char led_red_task_num = 0X0A;
    
    while(1)
    {
        if(led_red_on_flag)
        {
             LED_RED_ON;
             
             if(CounterTask_Handler != NULL)
             {
                  xTaskNotify((TaskHandle_t)CounterTask_Handler,
                              (uint32_t)led_red_task_num,
                              (eNotifyAction)eSetValueWithoutOverwrite);
             }
             
        }else
        {
             LED_RED_OFF;
        }    
        
        led_red_on_flag = ~led_red_on_flag;
        
        vTaskDelay(2000);
    }
}

void ws2812b_task(void *pvParameters)
{
    unsigned char ws2812b_change_flag = 0;
    unsigned char ws2812b_task_num = 0X0B;
    
    while(1)
    {
         if(ws2812b_change_flag)
         {
              ws2812b_write_rgb(0, 20, 0);
             
              if(CounterTask_Handler != NULL)
              {
                        xTaskNotify((TaskHandle_t)CounterTask_Handler,
                                                (uint32_t)ws2812b_task_num,
                                                (eNotifyAction)eSetValueWithoutOverwrite);
              }
             
         }else
         {
              ws2812b_write_rgb(0, 0, 0);
         }
         
         ws2812b_change_flag = ~ws2812b_change_flag;
         
         vTaskDelay(3000);
    }
}

COUNTER任务负责处理任务消息:

void counter_task(void *pvParameters)
{
    uint32_t notify_val = 0;
    
    while(1)
    {
         xTaskNotifyWait( (uint32_t )0X00000000,
                          (uint32_t )0XFFFFFFFF,
                          (uint32_t*)&notify_val,
                          (TickType_t)portMAX_DELAY);
        
        switch(notify_val)
        {
            case 0X0A:
                printf("\r\n接收到LED GREEN任务的任务通知。\r\n");
            break;
            
            case 0X0B:
                printf("\r\n接收到WS2812B任务的任务通知。\r\n");
            break;
            
            default:
                break;
        }
        
         vTaskDelay(20);
    }
}

下载验证:一把成功,还是很符合设计预期的。

FreeRTOS 任务通知模拟事件标志组实验

       程序设计验证思想是以LED_RED任务以及WS2812B任务来发送任务通知的消息邮箱,COUNTER任务负责接收任务同时来进行显示。

       其实和上述消息邮箱的机制基本是类似的,无非就是将传递的具体数据转变为标志位,同时任务处理端也是靠获取相应的标志位来进行处理。

#define EVENTBIT_0 (1<<0)
#define EVENTBIT_1 (1<<1)
#define EVENTBIT_ALL (EVENTBIT_0|EVENTBIT_1)

void led_red_task(void *pvParameters)
{
    unsigned char led_red_on_flag = 0;
    unsigned char led_red_task_num = 0X0A;
    
    while(1)
    {
        if(led_red_on_flag)
        {
             LED_RED_ON;
             
             if(CounterTask_Handler != NULL)
             {
                  xTaskNotify((TaskHandle_t)CounterTask_Handler,
                              (uint32_t)EVENTBIT_0,
                              (eNotifyAction)eSetBits); //在实际工程调试时,发现了这个bug!需要修改为eSetBits
             }
             
        }else
        {
             LED_RED_OFF;
        }    
        
        led_red_on_flag = ~led_red_on_flag;
        
        vTaskDelay(2000);
    }
}

void ws2812b_task(void *pvParameters)
{
    unsigned char ws2812b_change_flag = 0;
    unsigned char ws2812b_task_num = 0X0B;
    
    while(1)
    {
         if(ws2812b_change_flag)
         {
              ws2812b_write_rgb(0, 20, 0);
             
              if(CounterTask_Handler != NULL)
              {
                   xTaskNotify((TaskHandle_t)CounterTask_Handler,
                   (uint32_t)EVENTBIT_1,
                   (eNotifyAction)eSetBits);//在实际工程调试时,发现了这个bug!需要修改为eSetBits
              }
             
         }else
         {
              ws2812b_write_rgb(0, 0, 0);
         }
         
         ws2812b_change_flag = ~ws2812b_change_flag;
         
         vTaskDelay(3000);
    }
}

void counter_task(void *pvParameters)
{
    uint32_t notify_val = 0;
    uint32_t event_val  = 0;
    
    while(1)
    {
         xTaskNotifyWait( (uint32_t )0X00000000,
                          (uint32_t )0XFFFFFFFF,
                          (uint32_t*)&notify_val,
                          (TickType_t)portMAX_DELAY);
        
         if(notify_val & EVENTBIT_0)
         {
              printf("\r\n接收到LED GREEN任务的任务通知。\r\n");
              event_val |= EVENTBIT_0;
         }else if(notify_val & EVENTBIT_1)
         {
              printf("\r\n接收到WS2812B任务的任务通知。\r\n");
              event_val |= EVENTBIT_1;
         }
         
         if(event_val == EVENTBIT_ALL)
         {
              printf("\r\n接收到所有任务的任务通知。\r\n");
              event_val = 0;
         }

         vTaskDelay(20);
    }
}

下载验证:

  • 31
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

moon2shine

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

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

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

打赏作者

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

抵扣说明:

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

余额充值