Freertos——任务通知

任务通知

任务通知是 FreeRTOS 内置的轻量级任务间同步通信机制,它的内核对象不需要单独创建,因为每个任务的任务控制块中天生自带一个 32 位的通知值和通知状态,其他任务或中断可以直接向指定任务发送通知,目标任务可以阻塞等待通知

它是 FreeRTOS 中性能最高、内存开销最小的 IPC 机制,官方测试数据显示,它比二值信号量的同步速度快约 45%,核心原因是省去了内核对象的创建、链表挂载等额外开销

优势与局限性

任务通知的优势
  • 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高
  • 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
  • 功能复用性强:一套机制可模拟二值信号量、计数信号量、事件组、轻量消息邮箱四种常用场景
任务通知的局限性
  • 一对一接收:只有目标任务自己能等待自己的通知,不能多个任务同时等待同一个通知源
  • 无数据缓冲:仅一个 32 位通知值,不支持多条数据排队缓存;未处理的新通知要么覆盖旧值,要么发送失败
  • 发送永不阻塞:发送通知是直接写目标任务的通知值,永远立即返回,发送方不会阻塞
  • 定向发送:发送时必须明确指定目标任务的句柄,不能像信号量那样

任务通知机制

  • 每个任务都有一个结构体:TCB,里面有2个成员:
    一个是uint8_t类型,用来表示通知状态
    一个是uint32_t类型,用来表示通知值
c 复制代码
typedef struct tskTaskControlBlock
{
    ......
    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatileuint32_tulNotifiedValue[configTASK_NOTIFICATION_ARRAY_ENTRIES];
    volatileuint8_tucNotifyState[configTASK_NOTIFICATION_ARRAY_ENTRIES];
    ......
}tskTCB;
  • 通知状态有3种取值:
    taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
    taskWAITING_NOTIFICATION:任务在等待通知
    taskNOTIFICATION_RECEIVED:任务接收到了通知

当任务处于未等待状态时,可以自己进入等待状态阻塞,直到接收到了通知退出阻塞同时重新回到未等待状态。

当任务处于未等待状态时,接收到了通知,任务会进入接收状态,当任务自己要进入等待状态时不会阻塞,而是直接回到为等待状态。

任务通知的使用

简化版 专业版
发出通知 xTaskNotifyGive xTaskNotify
中断发出通知 vTaskNotifyGiveFromISR xTaskNotifyFromISR
取出通知 ulTaskNotifyTake xTaskNotifyWait
简化版
c 复制代码
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
voidvTaskNotifyGiveFromISR(TaskHandle_t xTaskHandle,BaseType_t*pxHigherPriorityTa
skWoken);
uint32_tulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t xTicksToWait);

在任务中使用xTaskNotifyGive函数,在ISR中使用vTaskNotifyGiveFromISR函数,都是直接给其他任务发送通知:

  • 使得通知值加一
  • 并使得通知状态变为"pending",也就是
    taskNOTIFICATION_RECEIVED,表示有数据了、待处理

可以使用ulTaskNotifyTake函数来取出通知值:

  • 如果通知值等于0,则阻塞(可以指定超时时间)
  • 当通知值大于0时,任务从阻塞态进入就绪态
  • 在ulTaskNotifyTake返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零

xTaskNotifyGive

参数 说明
xTaskToNotify 任务句柄,给哪个任务发通知
返回值 必定返回pdPASS

vTaskNotifyGiveFromISR

参数 说明
xTaskToNotify 任务句柄,给哪个任务发通知
pxHigherPriorityTaskWoken 被通知的任务,可能正处于阻塞状态。此函数发出通知后,会把它从阻塞状态切换为就绪态。如果被唤醒的任务的优先级,高于当前任务的优先级,则"*pxHigherPriorityTaskWoken"被 设 置 为pdTRUE,这表示在中断返回之前要进行任务切换

ulTaskNotifyTake

参数 说明
xClearCountOnExit 函数返回前是否清零:pdTRUE:把通知值清零;pdFALSE:如果通知值大于0,则把通知值减一
xTicksToWait 任务进入阻塞态的超时时间,它在等待通知值大于0。0:不等待,即刻返回;portMAX_DELAY:一直等待,直到通知值大于0;其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count
返回值 函数返回之前,在清零或减一之前的通知值。如果xTicksToWait非0,则返回值有2种情况:1.大于0:在超时前,通知值被增加了 2.等于0:一直没有其他任务增加通知值,最后超时返回0
专业版
c 复制代码
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify,uint32_tulValue,eNotifyActio
n eAction);

BaseType_t xTaskNotifyFromISR(TaskHandle_t xTaskToNotify,
uint32_tulValue,
eNotifyAction eAction,
BaseType_t*pxHigherPriorityTaskWoken);

BaseType_t xTaskNotifyWait(uint32_tulBitsToClearOnEntry,
uint32_tulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_t xTicksToWait);

xTaskNotify函数功能更强大,可以使用不同参数实现各类功能,比如:

  • 让接收任务的通知值加一:这时xTaskNotify()等同于xTaskNotifyGive()
  • 设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的
    事件组
  • 把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成
    功。这就是轻量级的、长度为1的队列
  • 用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖
    都成功。类似xQueueOverwrite()函数,这就是轻量级的邮箱。

xTaskNotify()比xTaskNotifyGive()更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR()是它对应的ISR版本。这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?使用xTaskNotifyWait()函数!它比ulTaskNotifyTake()更复杂:

  • 可以让任务等待(可以加上超时时间),等到任务状态为"pending"(也就是有数
    据)
  • 还可以在函数进入、退出时,清除通知值的指定位

xTaskNotify

参数 说明
xTaskToNotify 任务句柄,给哪个任务发通知
ulValue 怎么使用ulValue,由eAction参数决定
eAction 见下表
返回值 pdPASS:成功,大部分调用都会成功pdFAIL:只有一种情况会失败,当eAction为eSetValueWithoutOverwrite,并且通知状态为"pending"(表示有新数据未读),这时就会失败

eNotifyAction

eNotifyAction取值 说明
eNoAction 仅仅是更新通知状态为"pending",未使用ulValue。 这个选项相当于轻量级的、更高效的二进制信号量。
eSetBits 通知值=原来的通知值相;当于轻量级的、更高效的事件组。
eIncrement 通知值=原来的通知值+ 1,未使用ulValue。相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于xTaskNotifyGive()函数。
eSetValueWithoutOverwrite 不覆盖。如果通知状态为"pending"(表示有数据未读),则此次调用xTaskNotify不做任何事,返回pdFAIL。如果通知状态不是"pending"(表示没有新数据),则:通知值= ulValue。
eSetValueWithOverwrite 覆盖。无论如何,不管通知状态是否为"pendng",通知值= ulValue。

xTaskNotifyFromISR

跟xTaskNotify很类似,就多 了最后一个参数pxHigherPriorityTaskWoken。在很多ISR函数中,这个参数的作用都是类似的,使用场景如下:

  • 被通知的任务,可能正处于阻塞状态
  • xTaskNotifyFromISR函数发出通知后,会把接收任务从阻塞状态切换为就
    绪态
  • 如果被唤醒的任务的优先级,高于当前任务的优先级,则"*pxHigherPriorityTaskWoken"被设置为pdTRUE,这表示在中断返回之前要进行任务切换。
    xTaskNotifyWait
参数 说明
ulBitsToClearOnEntry 在xTaskNotifyWait入口处,要清除通知值的哪些位?通知状态不是"pending"的情况下,才会清除。它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。能清零的话:通知值=通知值& ~(ulBitsToClearOnEntry)。比如传入0x01,表示清除通知值的bit0;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0
ulBitsToClearOnExit 在xTaskNotifyWait出口处,如果不是因为超时推出,而是因为得到了数据而退出时:通知值=通知值& ~(ulBitsToClearOnExit)。在清除某些位之前,通知值先被赋给"*pulNotificationValue"。比如入0x03,表示清除通知值的bit0、bit1;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0
pulNotificationValue 用来取出通知值。在函数退出时,使用ulBitsToClearOnExit清除之前,把通知值赋给"*pulNotificationValue"。如果不需要取出通知值,可以设为NULL。
xTicksToWait 任务进入阻塞态的超时时间,它在等待通知状态变为"pending"。0:不等待,即刻返回;portMAX_DELAY:一直等待,直到通知状态变为"pending";其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count
返回值 1. pdPASS:成功这表示xTaskNotifyWait成功获得了通知:可能是调用函数之前,通知状态就是"pending";也可能是在阻塞期间,通知状态变为了"pending"。 2. pdFAIL:没有得到通知。
相关推荐
Zhao1368245539139 分钟前
DP4330A 与DP4363 测试对比
嵌入式硬件
辛迪聊物业数字化44 分钟前
智慧社区小程序落地避坑指南:3类物业方案选型+成本测算,线上服务零翻车
大数据·嵌入式硬件·百度·系统架构
电子科技圈1 小时前
Arm电机控制开发工具哪家强?
arm开发·单片机·嵌入式硬件·设计模式·软件构建
JasmineX-11 小时前
下位机直接控制57步进电机匀速转动
stm32·单片机·嵌入式硬件
聆思科技AI芯片2 小时前
【AI硬件开发上手教程】怎么使用AI编程实现小聆AI语音控制舵机
人工智能·嵌入式硬件·开源·ai编程·智能硬件
abc light2 小时前
ARM 裸机开发(6)——时钟树与定时器
arm开发·单片机·fpga开发
吠品12 小时前
智谱开源 ZCode 之后,真正的考试才开始
单片机·嵌入式硬件·1024程序员节
Chery114013 小时前
高速ADC与精密DAC怎么选?DP9230D与DP8532D应用解析
嵌入式硬件·物联网·射频工程
小李不想当小白16 小时前
SPI通信外设与W25Q64 (硬件SPI)(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
LCG米16 小时前
STM32驱动W5500以太网模块实测:TCP吞吐瓶颈定位、TX指针回绕陷阱与断线重连保活落地
stm32·单片机·嵌入式硬件·tcp/ip