FreeRTOS 快速入门(八)之任务通知
[toc]
一、任务通知
1、基本概念
FreeRTOS
从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有 一个 32 位 的通知值,在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)。
相对于以前使用 FreeRTOS
内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列。
想要使用任务通知,必须将 FreeRTOSConfig.h
中的宏定义 configUSE_TASK_NOTIFICATIONS
设置为 1,其实FreeRTOS
默认是为 1 的,所以任务通知是默认使能的。
FreeRTOS
提供以下几种方式发送通知给任务 :
- 发送通知给任务, 如果有通知未读,不覆盖通知值。
- 发送通知给任务,直接覆盖通知值。
- 发送通知给任务,设置通知值的一个或者多个位 ,可以当做事件组来使用。
- 发送通知给任务,递增通知值,可以当做计数信号量使用。
通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS
的信号量,队列、事件组等。
2、优势及限制
任务通知的优势:
- 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。
- 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的限制:
- 不能发送数据给 ISR:ISR 并没有任务结构体,所以无法使用任务通知的功能给 ISR 发送数据。但是 ISR 可以使用任务通知的功能,发数据给任务。
- 数据只能给该任务独享
- 无法缓冲数据
- 无法广播给多个任务
- 如果发送受阻,发送方无法进入阻塞状态等待
3、通知状态和通知值
每个任务都有一个结构体:TCB(Task Control Block),里面有 2 个成员:
- 一个是 uint8_t 类型,用来表示==通知状态==
- 一个是 uint32_t 类型,用来表示==通知值==
1 |
|
通知状态有 3 种取值:
taskNOT_WAITING_NOTIFICATION
:任务没有在等待通知taskWAITING_NOTIFICATION
:任务在等待通知taskNOTIFICATION_RECEIVED
:任务接收到了通知,也被称为 pending(有数据了,待处理)
1 |
|
通知值可以有很多种类型:
- 计数值
- 位(类似事件组)
- 任意数值
二、任务通知的使用
使用任务通知,可以实现轻量级的队列(长度为 1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。
任务通知有两套函数,简化版、专业版,列表如下:
- 简化版函数的使用比较简单,它实际上也是使用专业版函数实现的
- 专业版函数支持很多参数,可以实现很多功能
简化版 | 专业版 | |
---|---|---|
发出通知 | xTaskNotifyGive vTaskNotifyGiveFromISR |
xTaskNotify xTaskNotifyFromISR |
取出通知 | ulTaskNotifyTake |
xTaskNotifyWait |
1、xTaskNotifyGive/ulTaskNotifyTake
在任务中使用 xTaskNotifyGive
函数,在 ISR 中使用 vTaskNotifyGiveFromISR
函数,都是直接给其他任务
发送通知:
- 使得通知值加一
- 并使得通知状态变为”pending”,也就是
taskNOTIFICATION_RECEIVED
,表示有数据了、待处理
可以使用 ulTaskNotifyTake
函数来取出通知值:
- 如果通知值等于 0,则阻塞(可以指定超时时间)
- 当通知值大于 0 时,任务从阻塞态进入就绪态
- 在
ulTaskNotifyTake
返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零
使用 ulTaskNotifyTake 函数可以实现轻量级的、高效的二进制信号量、计数型信号量。
原型如下:
1 |
|
2、xTaskNotify/xTaskNotifyWait
xTaskNotify
函数功能更强大,可以使用不同参数实现各类功能,比如:
- 让接收任务的通知值加一:这时
xTaskNotify()
等同于xTaskNotifyGive()
- 设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的事件组
- 把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成功。这就是轻量级的、长度为1的队列
- 用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖都成功。类似
xQueueOverwrite()
函数,这就是轻量级的邮箱。
xTaskNotify()
比 xTaskNotifyGive()
更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR()
是它对应的 ISR 版本。
使用 xTaskNotifyWait()
函数来取出任务通知。它比 ulTaskNotifyTake()
更复杂:
- 可以让任务等待(可以加上超时时间),等到任务状态为”pending”(也就是有数据)
- 还可以在函数进入、退出时,清除通知值的指定位
原型如下:
1 |
|
其中,eAcrtion
的取值如下:
eAction 取值 | 含义 |
---|---|
eNoAction | 对象任务接收任务通知,但是任务自身的任务通知值不更新,即形参 ulValue 没有用。 |
eSetBits | 对象任务接收任务通知,同时任务自身的任务通知值与 ulValue 按位或。 如果 ulValue 设置为 0x01,那么任务的通知值的位 0 将被置为 1。 同样的如果 ulValue 设置为 0x04,那么任务的通知值的位 2 将被置为 1。 在这种方式下,任务通知可以看成是事件标志的一种轻量型的实现,速度更快。 |
eIncrement | 对象任务接收任务通知,任务自身的任务通知值加 1,即形参ulValue 没有用。 这个时候调用 xTaskNotify() 等同于调用 xTaskNotifyGive()。 |
eSetValueWithOverwrite | 对象任务接收任务通知,且任务自身的任务通知值会无条件的被设置为 ulValue。 在这种方式下,任务通知可以看成是函数 xQueueOverwrite() 的一种轻量型的实现,速度更快。 |
eSetValueWithoutOverwrite | 对象任务接收任务通知,且对象任务没有通知值,那么通知值就会被设置为 ulValue。 对象任务接收任务通知,但是上一次接收到的通知值并没有取走,那么本次的通知值将不会更新,同时函数返回 pdFALSE。 在这种方式下,任务通知可以看成是函数 xQueueSend() 应用在队列深度为 1 的队列上的一种轻量型实现,速度更快。 |
3、xTaskNotifyAndQuery
xTaskNotifyAndQuery()
与 xTaskNotify()
很像,都是调用通用的任务通知发送函数 xTaskGenericNotify()
来实现通知的发送,不同的是多了一个附加的参数 pulPreviousNotifyValue
用于回传接收任务的上一个通知值。
xTaskNotifyAndQuery()
函数不能用在中断中,而是必须使用带中断保护功能的 xTaskNotifyAndQuery()FromISR
来代替。
1 |
|