写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
一、任务通知的特性
通过任务通知,无需单独的通信对象,任务就可以与其他任务进行交互,以及与 ISR 同步。通过使用任务通知,任务或 ISR 可以直接向接收任务发送事件。
任务通知可以通过以下几种方式更新接收任务的通知值:
- 直接设置而不用覆写接收任务的通知值
- 覆写接收任务的通知值
- 设置接收任务通知值的一个或多个 bit位
- 增加接收任务的通知值
相对于使用中介对象发送到任务(这样的例子对象是队列,信号量,互斥对象和事件组)如下图:
任务通知是一种将事件直接发送到任务的方法,而无需这样做中介对象:
任务通知功能是可选的,要包含任务通知功能,请在 FreeRTOSConfig.h 中将 configUSE_TASK_NOTIFICATIONS 设置为 1。
当 configUSE_TASK_NOTIFICATIONS 设置为 1 时,每个任务都有一个通知状态(待处理或未在等待处理)和一个通知值(一个 32 位无符号整数)。当任务收到通知时,其通知状态将设置为待处理。当任务读取其通知值时,其通知状态将设置为未在等待处理。如果出于节省空间的考虑(每个任务可以节省 4个字节),可以设置 configUSE_TASK_NOTIFICATIONS 为 0来禁用。
任务可在 “被阻止” 状态中等待其通知状态变为待处理,还可以选择指定超时。
二、存在的限制
在以下情形中不能使用任务通知:
• 向 ISR 发送事件或数据。
通信对象可供 ISR 和任务相互发送事件和数据。
任务通知可用于从 ISR 向任务发送事件和数据,但不能用于从任务向 ISR 发送事件或数据。
• 启用多个接收任务。
通信对象可由任何知其句柄(可能是队列句柄、信号灯句柄或事件组句柄)的任务或 ISR 访问。任意数量的任务和 ISR 可处理发送到任何给定通信对象的事件或数据。
任务通知直接发送给接收任务,因此只能由接收通知的任务来处理。这在多数情况下并不是限制,因为尽管通常有多个任务和 ISR 向相同的通信对象发送事件或数据,但是很少有多个任务和 ISR 接收来自同一通信对象的事件或数据。
• 缓冲多个数据项。
队列是一次可保存多个数据项的通信对象。已发送到队列但尚未从队列中接收的数据将在队列对象中缓冲。
任务通知通过更新接收任务的通知值向任务发送数据。任务的通知值一次只能保存一个值。
• 广播到多个任务。
事件组是可用于一次向多个任务发送事件的通信对象。
任务通知直接发送给接收任务,因此只能由接收任务来处理。
• 在 “被阻止” 状态中等待发送完成。
如果通信对象暂时处于无法向其中写入更多数据或事件的状态(例如,当队列已满、无法向该队列发送更多数据时),尝试向该对象写入内容的任务可以选择进入“被阻止”状态,以等待其写入操作完成。
如果一个任务尝试向已有待处理通知的另一个任务发送任务通知,则发送任务无法在“被阻止”状态中等待接收任务重置其通知状态。在大多数使用任务通知的情况下,这极少成为限制。
三、API函数
任务通知使用 API函数 xTaskNotify()
或 xTaskNotifyGive()
来发送,这个通知将一直挂起直到接收任务使用API函数 xTaskNotifyWait()
或 ulTaskNotifyTake()
来接收通知。如果接收任务在通知到来时已经被阻塞,则会从阻塞态恢复,同时通知被清除。
功能 | API 接口 | 实际执行函数 | 其它 |
发送通知 | xTaskNotifyGive() | xTaskGenericNotify() | 没有通知值 (信号量类型) |
发送通知(用于中断中) | vTaskNotifyGiveFromISR() | ||
接收通知 | ulTaskNotifyTake() | 单独对应 xTaskNotifyGive() | |
发送通知 | xTaskNotify() | xTaskGenericNotify() | 带通知值 |
发送通知(用于中断中) | xTaskNotifyFromISR() | xTaskGenericNotifyFromISR() | |
发送通知 | xTaskNotifyAndQuery() | xTaskGenericNotify() | 带通知值,并且返回原通知值 |
发送通知(用于中断中) | xTaskNotifyAndQueryFromISR() | ||
接收通知 | xTaskNotifyWait() | ||
清除通知 | xTaskNotifyStateClear() |
在大多数情况下,并不需要 xTaskNotify() 和 xTaskNotifyWait() API 函数提供的灵活性。更简单的函数就足够了。xTaskNotifyGive() API 函数可替代 xTaskNotify(),它更简单、但灵活性稍差。ulTaskNotifyTake() API 函数可替代 xTaskNotifyWait(),它更简单、但灵活性稍差。
1、xTaskNotifyGive() 和 vTaskNotifyGiveFromISR() API函数
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
传入参数:
- xTaskToNotify :需要通知的任务句柄。这个句柄即是调用 xTaskCreate()创建任务时传递的句柄
- pxHigherPriorityTaskWoken:如果调用 vTaskNotifyGiveFromISR()发送通知导致被发送通知的任务离开阻塞状态,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么 xSemaphoreGiveFromISR()会在函数内部将 *pxHigherPriorityTaskWoken 设为 pdTRUE。如果 vTaskNotifyGiveFromISR() 将此值设为 pdTRUE,则在中断退出前应当进行一次上下文切换
返回参数(始终返回 pdPASS):
- pdPASS:发送成功
2、ulTaskNotifyTake() API函数
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
传入参数:
- xClearCountOnExit:当设置为 pdFALSE,则在函数退出时任务的通知值递减,这样,通知值就像一个计数信号量;如果是 pdTRUE,那么当函数退出时,任务的通知值将被清除为零,这样,通知值就像一个二进制信号量
- xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果 xTicksToWait为 0,则 ulTaskNotifyTake()在通知无效时会立即返回;如果把 xTicksToWait 设置为 portMAX_DELAY,那么阻塞等待将没有超时限制
返回参数:
- uint32_t:在任务的通知计数清空为零或递减之前的任务通知值
3、xTaskNotify() 和 xTaskNotifyFromISR() API函数
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
传入参数:
- xTaskToNotify :需要通知的任务句柄。这个句柄即是调用 xTaskCreate()创建任务时传递的句柄
- ulValue:与通知一起发送的数据,如何使用 ulValue取决于 eNotifyAction值
- eAction:枚举类型,用于指定如何更新接收任务的通知值。
枚举成员 | 描述 |
eNoAction | 通知任务,而不更新其通知值;参数 ulValue未使用。可用作更快速的二进制信号量轻型替代方案。始终返回 pdPASS |
eSetBits | 对被通知任务的通知值按位执行或运算。可用作更快速的事件组轻型替代方案。始终返回 pdPASS |
eIncrement | 接收任务的通知值将递增。可用作更快速的计数信号量轻型替代方案。始终返回 pdPASS |
eSetValueWithOverwrite | 即使任务尚未读取前一个值,也将任务的通知值设置为 ulValue参数中传递的值。始终返回 pdPASS |
eSetValueWithoutOverwrite | 如果任务当前没有通知值,则设置任务的通知值并返回 pdPASS;如果任务没有取出前一个通知值,则不执行任何操作,将返回 pdFAIL |
- pxHigherPriorityTaskWoken:如果调用 xTaskNotifyFromISR()发送通知导致被发送通知的任务离开阻塞状态,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么 xTaskNotifyFromISR()会在函数内部将 *pxHigherPriorityTaskWoken 设为 pdTRUE。如果 xTaskNotifyFromISR() 将此值设为 pdTRUE,则在中断退出前应当进行一次上下文切换
返回参数:
- BaseType_t:取决于 eAction的值
4、xTaskNotifyWait() API函数
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
传入参数:
- ulBitsToClearOnEntry:在使用通知之前,先将任务的通知值与参数 ulBitsToClearOnEntry的按位取反值按位与操作。设置参数 ulBitsToClearOnEntry为 0xFFFFFFFF(ULONG_MAX),表示清零任务通知值
- ulBitsToClearOnExit:在退出函数之前,将任务的通知值与参数 ulBitsToClearOnExit的按位取反值按位与操作。设置参数 ulBitsToClearOnExit为 0xFFFFFFFF(ULONG_MAX),表示清零任务通知值
- pulNotificationValue:用于向外回传任务的通知值。这个通知值在参数 ulBitsToClearOnExit起作用前将通知值拷贝到 *pulNotificationValue中。可选参数,如果不需要,可将其设置为 NULL
- xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果 xTicksToWait为 0,则 xTaskNotifyWait()在通知无效时会立即返回;如果把 xTicksToWait 设置为 portMAX_DELAY,那么阻塞等待将没有超时限制
返回参数:
- pdPASS:接收成功
- pdFAIL:接收失败(超时)
四、应用
1、替代二值信号量(binary semaphore)
任务通知比二值信号量快 45%并且内存占用更小
- 发送处理:
xTaskNotifyGive()
和vTaskNotifyGiveFromISR()
用来代替xSemaphoreGive()
和xSemaphoreGiveFromISR()
- 接收处理:
ulTaskNotifyTake()
用来替代xSemaphoreTake()
(ps:ulTaskNotifyTake()的第一个参数 xClearCountOnExit应设置为 pdTRUE)
2、替代计数信号量(counting semaphore)
与替代二值信号量类似,当使用任务通知来代替计数信号量的时候,接收任务的通知值被用来代替计数信号量的计数值
- 发送处理:
xTaskNotifyGive()
和vTaskNotifyGiveFromISR()
用来代替xSemaphoreGive()
和xSemaphoreGiveFromISR()
- 接收处理:
ulTaskNotifyTake()
用来替代xSemaphoreTake()
(ps:ulTaskNotifyTake()的第一个参数 xClearCountOnExit应设置为 pdFALSE)
3、替代事件组(event group)
当任务通知用来代替时间组的时候,通知值被用来代替事件组的任务值,通知值的每一位被用来代表某个标志
- 发送处理:
xTaskNotify()
和xTaskNotifyFromISR()
用来代替xEventGroupSetBits()
和xEventGroupSetBitsFromISR()
- 接收处理:
xTaskNotifyWait()
用来代替xEventGroupWaitBits()
4、替代邮箱队列(mailbox)
RTOS任务通知只能用来发送数据到一个任务中,相比用队列实现,有一些限制:
- 只能发送 32-bit数据
- 保存的是接收任务的值,同一个时刻只能存在一个接收任务
因此,使用 “轻量邮箱” 这个短语代替 “轻量队列”。任务的通知值就是邮箱值
- 发送处理:使用
xTaskNotify()
和xTaskNotifyFromISR()
发送至任务,可以用来替换xQueueOverwtite()
和xQueueOverwtiteFromISR()
- 接收处理:使用
xTaskNotifyWait()
来获取通知值,可以用来代替xQueueReceive()
和xQueuePeek()