登录后台

页面导航

本文编写于 256 天前,最后修改于 256 天前,其中某些信息可能已经过时。

任务通知

前面大多数任务之间交流的方法都需要一个中间对象来完成,例如队列、事件组和各种不同格式的信号量。

当使用交流对象时,事件和数据不是直接的发送给接收任务或中断,而是通过交流对象传递。任务和中断是从交流对象接收事件或数据,而不是直接从发出的任务或中断。

# 用一个交流对象将事件从一个任务发送到另外一个任务
-----------------------------------------
|void vTask1(void *pvParam){            |
| for(;;){                              |
|   /* 这里写一些函数代码*/               |
|   ...                                 |
|   /* 在一些位置VTask1发送事件给vTask2   |
|    * 事件不是直接发送给vTask2           |
|    * 而是通过交流对象                   |
|    */                                 |
|   aSendFunction()                     |
| }                                     |
|}                                      |
-----------------------------------------
                    |
                    |
                    v
             ----------------
             |   交流对象    |------------- 交流对象可以是一个队列
             ----------------              ,事件组,或别的格式的
                    |                      信号量
                    |
                    v
-----------------------------------------
|void vTask2(void *pvParam){            |
| for(;;){                              |
|   /* 这里写一些函数代码*/              |
|   ...                                 |
|   /* 在一些位置VTask2接收事件从vTask1   |
|    * 事件不是直接从vTask2接收           |
|    * 而是通过交流对象                   |
|    */                                 |
|   aReceivFunciton()                   |
| }                                     |
|}                                      |
-----------------------------------------

任务通知是一种任务直接交流的方式。任务通知允许任务同其它任务沟通,也可以和中断同步,而不需要单独的交流对象。

# 用任务通知将事件从一个任务发送到另外一个任务
-----------------------------------------
|void vTask1(void *pvParam){            |
| for(;;){                              |
|   /* 这里写一些函数代码*/               |
|   ...                                 |
|   /* 在一些位置VTask1发送事件给vTask2   |
|    * 事件不是直接发送给vTask2           |
|    * 而是通过交流对象                   |
|    */                                 |
|   aSendFunction()                     |
| }                                     |
|}                                      |
-----------------------------------------
                    |
                    |                    
                    |--------------------这次中间没有交流对象了
                    |
                    v
-----------------------------------------
|void vTask2(void *pvParam){            |
| for(;;){                              |
|   /* 这里写一些函数代码*/               |
|   ...                                 |
|   /* 在一些位置VTask2接收事件从vTask1   |
|    * 事件不是直接从vTask2接收           |
|    * 而是通过交流对象                   |
|    */                                 |
|   aReceivFunciton()                   |
| }                                     |
|}                                      |
-----------------------------------------

任务通知函数是可选项。在FreeRTOSConfig.h中将configUSE_TASK_NOTIFICATIONS设置为1就可以使用了。
当configUSE_TASK_NOTIFICATIONS设置为1时,每个任务都有一个’通知状态’,它可以是待定或非待定,“通知值"是一个32位无符号整数。当一个任务接收到一个通知,它的通知状态设置为"待定”,当任务读取通知值后,它的状态又变成"非待定"。
任务可以以一个带超时的阻塞态,等待它的通知状态变成"待定"。

任务通知优点和限制

优势:

  • 同样的操作用任务通知传递事件或数据给任务,会比使用队列,信号量或事件组都要快。
  • 用任务通知发送事件或数据给任务比使用信号量或事件组操作占用RAM空间都要小。这是因为每个交流对象在使用前都要显式的创建,而使用任务通知功能只要为每一个任务分配固定8字节的RAM就可以了。

限制:

任务通知比交流对象更快,也占用更少的RAM。但下面的情况不能使用任务通知:

  • 发送事件或数据给中断:交流对象可以用于任务和中断之间任意发送,接收事件或数据。但任务事件只可以用于中断向任务发送事件或数据,而不能用于任务向中断发送事件或数据
  • 使能超过一个接收任务:交流对象可以被获取到它句柄的任意个任务或中断访问(可能是队列句柄,信号量句柄,还可能是事件组句柄)。多个任务和中断都可以是事件或数据的发送者或接收者。任务通知是直接发送给接收任务,因此接收都只能是发送者指定的任务。
  • 缓冲多个数据项目:队列作为一个交流对象,可以同时保存已经发送给队列,但没有从队列中读取的多个数据项目。
    任务通知通过发送数据到另一个任务,是通过更新它的任务通知值。任务通知值只能同时保存一个数据。
  • 广播到不只一个任务:事件组可以用于一次发送事件给多个任务。但任务通知是直接发送给任务,因此一次只能被一个任务接收。
  • 在阻塞状态等待数据发送完成:如果交流对象暂时处于一个状态,意味着没有更多的事件或数据可以写入(比如当队列已经满了,就不能发送更多的数据给它)。那么试图发送数据给队列的任务就可以进入阻塞,等待队列有可用空间,以便完成这个操作。如果一个任务试图发送一个任务通知给一个已经处于"待定"状态的任务,那么任务是不可能进入阻塞态,等待另一个任务的任务通知状态改变后再更新它的任务通知值的。

使用任务通知

任务通知函数选项

任务通知是一个非常有用的功能,经常可以用来替代二进制信号量,普通信号量,事件组甚至是队列。可以用xTaskNotify()函数来实现发送任务通知这一广泛应用,用xTaskNotifyWait()接收任务通知。
尽管大多数情况下不需要xTaskNotify()和xTaskNotifyWait()提供的全部功能,用一个简单版本的就可以了。因此xTaskNotifyGive()就是一个xTaskNotify()的简单版本,ulTaskNotifyTask()是xTaskNotifyWait()的简单版本。

xTaskNotifyGive()函数

xTaskNotifyGive()直接发送一个通知给任务,然后增加接收任务的通知值。调用xTaskNotifyGive()函数会设置接收任务的通知状态为"待定"。

xTaskNotifyGive()函数时将任务通知用做一个高性能的二进制或普通信号量。

// xTaskNotifyGive()函数原型
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
/* 参数
 * xTaskToNotify: 通知要发送到的任务句柄,查看xTaskCreate()函数的pxCreatedTask参数,获取很多任务句柄信息
 * 返回值
 * xTaskNotifyGive()是一个调用xTaskNotify()的宏。它的参数会传递给xTaskNotify(),它只能返回pdPASS。下面章节会介绍xTaskNotify()
 */
// vTaskNotifyGiveFromISR()函数原型
void vTaskNotifyGiveFromISR(TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken); 
/* 参数
 * xTaskToNotify: 通知会被发送到的任务句柄,查看xTaskCreate()函数的pxCreatedTask参数了解更多详细信息
 * pxHigherPriorityTaskWoken: 如果接受通知的任务在阻塞状态等待接收一个通知,那么发送通知会引起任务离开阻塞状态。如果调用vTaskNotifyGiveFromISR()引起一个任务离开阻塞状态,而且离开阻塞状态的任务优先级比当前运行任务优先级高,那么vTaskNotifyGiveFromISR()就会把*pxHigherPriorityTaskWoken设置为pdTRUE。
 * 如果vTaskNotifyGiveFromISR()将这个值设置为pdTRUE,那么中断退出之前就需要进行一个上下文切换。这样可以确保中断返回立即运行就绪状态最高优先级任务。和其他所有中断安全函数一样。pxHigherPriorityTaskWoken参数在使用之前需要手动设置为pdFALSE
 */

ulTaskNotifyTake()函数

ulTaskNotifyTake()是任务在阻塞状态等待任务的通知值大于0时,然后在返回之后自动减或者清除任务通知值。
可以使用ulTaskNotifyTake()替代二进制信号量或普通信号量,从而实现高效的任务通信。

// ulTaskNotifyTake()函数原型
uint32_t ulTaskNotifyTake(BaseType_t xClearCounuOnExit, TickType_t xTicksToWait);
/* 参数
 * xClearCounuOnExit: 如果这个参数为pdTRUE,那么任务通知值会在ulTaskNotifyTake()返回之前被清除。
 * 如果这个参数是pdFALSE,而且调用任务的通知值大于0,那么ulTaskNotifyTake()返回之前会将任务通知值自减1。
 * xTicksToWait: 调用任务进入阻塞,等待通知值大于0的最大保持时间计数。这里的阻塞时间是用tick的数量表示的。绝对时钟时间和tick周期有关,可以用pdMS_TO_TICKS()宏将绝对时钟时间转换成特定的tick周期数量。设置xTicksToWait为portMAX_DELAY会让函数无限等待,但要想设置为这个参数需要将FreeRTOSConfig.h中的INCLUDE_vTaskSuspend设置为1。
 * 返回值
 * 这里的返回值是调用任务的通知值被清除或自动减一前的值,具体是会被清除还是减一取决于xClearCounuOnExit的值。
 * 如果返回值不为0,阻塞时间也不是0。可能是因为任务进入阻塞,等待任务通知值大于0。然后通知值在阻塞时间超时之前被更新了。
 * 如果阻塞时间不是0,返回值是0。那么就是调用任务进入阻塞,等待任务通知值被设置,但阻塞超时时间都到了任务通知值都没有被设置。
 */
博主已关闭本页面的评论功能