freertos学习笔记11--个人自用-第15章 任务通知(Task Notifications)

所谓"任务通知",你可以反过来读"通知任务"。

我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。

使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信:

 

使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知":

 

 


一:知识点

 

 

1. 什么是任务通知?

想象一下,在 FreeRTOS 的世界里,每一个 任务 (Task) 都是一个独立的“工人”。

在传统的通信方式(如队列、信号量)中,如果工人 A 想给 工人 B 发消息,他们需要通过一个中间的公共信箱(队列对象)。

  • A 把信放进公共信箱。

  • B 去公共信箱取信。

而在 任务通知 中,每个工人(任务)随身自带了一个私人的“小口袋”(这就是任务控制块 TCB 中的一个 32位变量)。

  • 直接交互: 工人 A 不需要找公共信箱,直接把东西塞进 工人 B 的“小口袋”里,或者拍一下 B 的肩膀。


 

2. 为什么要用它?(优点)

对于初学者来说,你只需要记住两个词:更快、更省

  1. 速度更快: 因为不需要创建中间的“公共信箱”对象,直接操作任务,处理速度比信号量和队列快 45% 左右。

  2. 内存更省: 不需要额外的 RAM 来创建队列或信号量结构体,因为它利用了任务本身自带的空间。


 

3. 它能做什么?(三种主要用法)

这个“小口袋”虽然简单,但玩法很多。通过改变发送的方式,它可以替代三种传统工具:

A. 代替“二值信号量” (Binary Semaphore)

  • 场景: 任务 A 完成了一件事,通知任务 B “嘿,可以开工了”。

  • 原理: 就像拍一下肩膀。不管拍几下,B 醒来只知道“有人拍我了”。

  • API 关键词: xTaskNotifyGive() (发送), ulTaskNotifyTake() (接收).

B. 代替“计数信号量” (Counting Semaphore)

  • 场景: 任务 A 按了 3 次按钮,任务 B 需要处理 3 次。

  • 原理: 就像在小口袋里放豆子。A 放一颗,数字加 1;B 拿一颗,数字减 1。

  • API 关键词: 同上,但接收时参数不同。

C. 代替“轻量级邮箱” (Mailbox/Queue)

  • 场景: 任务 A 采集到了一个传感器数值(比如温度 25℃),直接传给任务 B。

  • 原理: A 直接把 32位的数据(如 25)写进 B 的口袋里。

  • 注意: 只能传一个 32位的数据(或者指针),不能像队列那样存很长的数据串。

  • API 关键词: xTaskNotify() (发送), xTaskNotifyWait() (接收).


4. 代码实战示例

我们来看一个最简单的场景:任务 1 (发送者) 每隔 1 秒叫醒 任务 2 (接收者) 打印一条消息。

接收者代码 (Task 2):

void Task2_Handler(void *pvParameters)
{
    while(1)
    {
        // 1. 等待通知
        // 参数1: pdTRUE 表示退出时把计数清零
        // 参数2: portMAX_DELAY 表示死等,直到有通知来
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        // 2. 收到通知后执行的代码
        printf("收到通知,干活啦!\r\n");
    }
}

发送者代码 (Task 1):

void Task1_Handler(void *pvParameters)
{
    while(1)
    {
        // 延时 1000ms
        vTaskDelay(pdMS_TO_TICKS(1000));

        // 1. 发送通知给 Task2
        // xHandleTask2 是创建 Task2 时得到的句柄
        xTaskNotifyGive(xHandleTask2); 
        
        printf("已发送通知\r\n");
    }
}

 


5. 什么时候不能用它?(局限性)

虽然任务通知很好用,但它不是万能的。以下情况你必须使用传统的队列或信号量:

  1. 一对多广播: 任务通知只能是 一对一 的(指定发给某个任务)。如果你想发一个信号,让 5 个任务同时动起来,得用事件组(Event Group)。

  2. ISR 发送给 ISR: 中断服务函数之间不能用。

  3. 发送方无法阻塞: 如果 B 的口袋满了(还没有处理),A 想把数据塞进去,A 无法进入“等待状态”直到口袋变空。A 只能选择覆盖旧数据或者放弃发送。

  4. 传输复杂大数据: 如果你要传一个很大的结构体,队列更方便。


总结

  • 简单理解: 它是 FreeRTOS 任务自带的“私聊”功能。

  • 核心优势: 极快、极省内存。

  • 推荐策略: 只要能用任务通知解决的问题,就优先用它,不要用信号量。 只有当它满足不了需求时(比如需要多对多通信),才考虑用传统的 IPC 机制。


 

二:通知状态和通知值

 

在 FreeRTOS 的任务控制块(TCB,即任务的“身份证”)里,专门留了两个位置给任务通知:

  1. 通知值 (Notification Value)

  2. 通知状态 (Notification State)

我们继续用**“私人小口袋”**的比喻来拆解。


1. 通知值 (Notification Value)

定义:它本质上就是一个 32位的无符号整数 (uint32_t)

作用:它是用来存放东西的。

就像口袋里的“内容”。

  • 当做信号量时:这个数值代表“计数值”。(例如:3 代表口袋里有 3 颗豆子)。

  • 当做事件组时:这个数值的每一位(Bit)代表不同的标志。(例如:0x01 代表按键被按下)。

  • 当做消息邮箱时:这个数值就是传递的具体数据。(例如:100 代表当前速度是 100)。

初始状态:任务刚创建时,这个值通常是 0。


2. 通知状态 (Notification State)

定义:它是一个枚举变量 (uint8_t),用来记录**“接收者当前在干什么”以及“口袋里有没有新东西”**。

作用:它是用来做判断的。

就像口袋外面挂了一个“红旗标志”或者“状态牌”。

FreeRTOS 内部主要有三种状态:

  1. 未等待通知 (taskNOT_WAITING_NOTIFICATION)

    • 含义:任务正在忙别的事,没有在查收消息。或者任务虽然没事做,但还没有准备好接收通知。

    • 比喻:工人在干活,根本没看口袋。

  2. 等待通知 (taskWAITING_NOTIFICATION)

    • 含义:任务调用了接收函数(如 ulTaskNotifyTake),进入了**阻塞(Blocked)**状态,正在死等消息的到来。

    • 比喻:工人停下手里的活,盯着口袋看,说:“谁来给我发个消息啊,不发我就不动了。”

  3. 收到通知 (taskNOTIFICATION_RECEIVED)

    • 含义:发送者已经发了通知,把状态改成了“已收到”,但接收者还没来得及处理。

    • 比喻:有人往口袋里塞了东西,并把口袋外的红旗举起来了:“嘿!里面有新货,下次你检查的时候直接拿走就行,不用等了。”


3. 它们是如何配合工作的?(全过程演示)

让我们看一个完整的流程,看看这两个变量是如何变化的。

场景:任务 A (发送者) -> 任务 B (接收者)

阶段一:初始状态

  • 任务 B 的值0

  • 任务 B 的状态未等待 (Not Waiting)

  • 情况:任务 B 刚启动,正在运行其他代码。

阶段二:任务 B 想收消息 (阻塞等待)

  • 任务 B 调用 ulTaskNotifyTake(..., portMAX_DELAY)

  • FreeRTOS 内核检查 B 的状态:

    • 发现状态不是“已收到”,说明没消息。

    • 于是将 B 的状态改为 等待通知 (Waiting)

    • 结果:任务 B 进入休眠(Blocked),让出 CPU。

阶段三:任务 A 发送消息

  • 任务 A 调用 xTaskNotifyGive(TaskB_Handle)

  • FreeRTOS 内核操作 B 的 TCB:

    1. 更新 通知值:把值从 0 加到 1

    2. 检查 通知状态:发现 B 正在 等待通知

    3. 关键动作:内核把 B 叫醒(也就是把 B 从阻塞列表中移到就绪列表)。

    4. 将 B 的状态临时标记为 收到通知 (或者在唤醒后处理)。

阶段四:任务 B 醒来处理

  • 任务 B 恢复运行。

  • 它检查 通知状态,发现是“收到通知”。

  • 它读取 通知值(读到 1)。

  • 它根据参数决定是否清零通知值。

  • 最后:将 通知状态 重置回 未等待,准备下一次循环。


4. 为什么要有“状态”?(核心理解)

你可能会问:“为什么不直接看数值是不是 0?非要搞个状态变量?”

这是为了处理**“时间差”**问题:

  1. 如果 B 还没等,A 先发了:

    A 把值改为 1,并把状态设为 已收到。当 B 以后想去拿的时候,不用等,看到状态是 已收到,直接拿走运行。这叫“未卜先知”(Pending)。

  2. 避免虚假唤醒:

    状态机明确了任务是因为“真的收到了通知”醒来的,还是因为“超时了”醒来的。

总结图示

组件名称形象比喻关注点
通知值Value信件的内容数据 (是多少?按了几次?)
通知状态State信箱的红旗逻辑 (有人在等吗?有新信件吗?)

 

 

 

二:任务通知的函数及使用

FreeRTOS 的任务通知函数虽然看起来很多,但其实非常有规律

对于初学者,我建议把它们分成两类来记忆:

  1. “傻瓜模式”:专门用来代替信号量,简单好用(Give / Take)。

  2. “万能模式”:功能最强,用来传数据或标志位(Notify / Wait)。

我们一个个来看:

简化版专业版
发出通知xTaskNotifyGive vTaskNotifyGiveFromISRxTaskNotify xTaskNotifyFromISR
取出通知ulTaskNotifyTakexTaskNotifyWait

 

 


第一类:傻瓜模式 (模拟信号量)

这一组函数最简单,专门用来做二值信号量计数信号量。它的特点是不传具体数据,只传“次数”

1. 发送通知:xTaskNotifyGive()

作用:往目标任务的通知值里 +1(相当于释放信号量)。

// 参数:任务句柄
xTaskNotifyGive( xTaskB_Handle );

 

2. 接收通知:ulTaskNotifyTake()

作用:等待通知值大于 0,然后把它减 1 或清零(相当于获取信号量)。

// 返回值:在减 1 或清零之前的通知值
uint32_t ulValue = ulTaskNotifyTake( xClearCountOnExit, xTicksToWait );
  • 参数 xClearCountOnExit (退出时是否清零)

    • pdTRUE:收到通知后,把通知值直接清零。(二值信号量模式,不管你发了多少次,我一次全收走)。

    • pdFALSE:收到通知后,把通知值减 1。(计数信号量模式,你发了3次,我得调用3次这个函数才能收完)。

  • 参数 xTicksToWait:等待时间(可以用 portMAX_DELAY 死等)。

 

 


第二类:万能模式 (传递数值/标志位)

这一组函数更底层,参数更多,决定了通知值具体怎么变(是覆盖、是位操作、还是加法)。

1. 发送通知:xTaskNotify()

这是最强大的发送函数。

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, // 目标任务
                        uint32_t ulValue,           // 要发送的数值
                        eNotifyAction eAction );    // 【关键】决定怎么更新值

最核心的参数 eAction (枚举类型):

这个参数决定了你是在发信号量、发事件还是发消息。

eAction 参数行为描述适用场景
eSetBits目标任务的原通知值 按位或 (OR)ulValue事件组 (如:置位 bit0 表示按键,bit1 表示串口)
eIncrement目标通知值 +1。忽略 ulValue 参数。信号量 (效果等同于 xTaskNotifyGive)
eSetValueWithOverwrite不管目标只有没有旧数据,强制覆盖ulValue更新传感器数据 (我不关心旧数据,只要最新的)
eSetValueWithoutOverwrite如果目标原本是 0 (没数据),就更新为 ulValue;如果目标有旧数据,则发送失败 (返回 pdFAIL)。保险的消息传递 (不能覆盖还没处理的旧消息)
eNoAction啥也不改,只把目标的状态改为“已收到”。纯粹的唤醒 (嘿,醒醒,但我啥也没给你带)

 


 

2. 接收通知:xTaskNotifyWait()

这是最强大的接收函数,它允许你在读取前后对数据进行“位清理”。

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, // 进来时清除哪些位
                            uint32_t ulBitsToClearOnExit,  // 出去时清除哪些位
                            uint32_t *pulNotificationValue,// 保存收到的值
                            TickType_t xTicksToWait );     // 等待时间
  • ulBitsToClearOnEntry (入口掩码)

    • 在函数判断有没有通知之前,先清除通知值里的某些位。

    • 通常设为 0 (不清除)。

  • ulBitsToClearOnExit (出口掩码)

    • 函数返回之后,清除通知值里的某些位。

    • 如果你想把值清零:设为 0xFFFFFFFF (ULONG_MAX)。

    • 如果你想保留值不动:设为 0

    • 如果你想只清除 bit0 (处理完按键事件):设为 0x01

 


 

第三类:中断专用版 (ISR)

初学者必须注意:如果你在中断服务函数(如定时器中断、串口中断)里发送通知,绝对不能用上面的函数,必须带 FromISR 后缀!

  • vTaskNotifyGiveFromISR()

  • xTaskNotifyFromISR()

不仅名字变了,参数也多了一个核心的指针:

void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, 
                             BaseType_t *pxHigherPriorityTaskWoken );
  • pxHigherPriorityTaskWoken

    • 这是一个“出参”(指针)。

    • 作用:如果发送通知后,发现接收任务的优先级比当前正在运行的任务(被中断打断的那个)还要高,FreeRTOS 会把这个变量置为 pdTRUE

    • 你需要做:在中断退出的最后,检查这个变量。如果是 pdTRUE,你需要手动触发一次“任务切换”(Context Switch),这样中断结束后会立刻切到那个高优先级任务,而不是切回原来被打断的低优先级任务。这保证了实时性


 

总结速查表

你的需求推荐发送函数推荐接收函数eAction 设置
二值信号量 (0/1)xTaskNotifyGiveulTaskNotifyTake(pdTRUE, ...)(内部自动处理)
计数信号量 (Count)xTaskNotifyGiveulTaskNotifyTake(pdFALSE, ...)(内部自动处理)
事件组 (Flags)xTaskNotifyxTaskNotifyWaiteSetBits
消息邮箱 (Data)xTaskNotifyxTaskNotifyWaiteSetValueWithOverwrite

作为初学者,建议先从 xTaskNotifyGiveulTaskNotifyTake 用起,把任务间的同步跑通了,再去研究那个复杂的 xTaskNotifyWait

 

 


三:任务通知的使用

使用任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。

任务通知之所以能模拟这么多功能,核心在于怎么操作那个 32 位的通知值 (Value) 以及 接收端怎么处理通知状态

为了方便演示,假设我们都有一个全局句柄 xTaskHandle 指向接收任务。


 

1. 模拟“二值信号量” (Binary Semaphore)

应用场景:同步。比如:串口接收完数据(ISR)通知处理任务(Task)“数据好了,快去拿”。

核心逻辑:

  • 发送:不管发多少次,通知值不重要,关键是把任务叫醒。

  • 接收:收到后,一定要清零(pdTRUE)。就像把唯一的令牌拿走了。

代码示范:

/* ================= 发送端 (比如中断或任务A) ================= */
// 相当于 xSemaphoreGive()
// 只是简单地把通知值 +1,并唤醒任务
xTaskNotifyGive(xTaskHandle); 


/* ================= 接收端 (任务B) ================= */
// 相当于 xSemaphoreTake()
void TaskB(void *pvParameters)
{
    while(1)
    {
        // 参数1: pdTRUE -> 退出时把通知值 清零 (变为0)
        // 参数2: portMAX_DELAY -> 死等
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        // 执行到这里说明收到了信号
        printf("二值信号量:处理完一次同步事件\r\n");
    }
}

 

2. 模拟“计数型信号量” (Counting Semaphore)

应用场景:资源计数。比如:按键按了 3 次,任务就需要处理 3 次日志打印。

核心逻辑:

  • 发送:每发一次,通知值 +1

  • 接收:每收一次,通知值 -1(pdFALSE)。直到减到 0 为止。

代码示范

/* ================= 发送端 ================= */
// 连发3次
xTaskNotifyGive(xTaskHandle); // 值变 1
xTaskNotifyGive(xTaskHandle); // 值变 2
xTaskNotifyGive(xTaskHandle); // 值变 3


/* ================= 接收端 ================= */
void TaskB(void *pvParameters)
{
    uint32_t ulCurrentValue;
    while(1)
    {
        // 参数1: pdFALSE -> 退出时把通知值 减 1 (而不是清零)
        // 返回值: 减1之前的数值
        ulCurrentValue = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
        
        printf("计数信号量:处理一次,剩余次数: %d\r\n", ulCurrentValue - 1);
    }
}

 


3. 模拟“事件组” (Event Group)

应用场景:多事件标志。比如:Bit 0 代表“温度过高”,Bit 1 代表“电量过低”。

核心逻辑:

  • 发送:使用 eSetBits,将具体的某一位设为 1(按位或运算)。

  • 接收:使用 xTaskNotifyWait,读取并清除具体的位。

代码示范:

/* ================= 发送端 ================= */
#define EVENT_TEMP_HIGH  (1 << 0) // 0x01
#define EVENT_BATT_LOW   (1 << 1) // 0x02

// 设置 Bit 0,通知温度高
xTaskNotify(xTaskHandle, EVENT_TEMP_HIGH, eSetBits);

// 设置 Bit 1,通知电量低 (此时通知值可能是 0x03)
xTaskNotify(xTaskHandle, EVENT_BATT_LOW, eSetBits);


/* ================= 接收端 ================= */
void TaskB(void *pvParameters)
{
    uint32_t ulNotifiedValue;
    while(1)
    {
        // 参数1: 0 -> 进入函数前不清除任何位
        // 参数2: 0xFFFFFFFF -> 退出函数后清除所有位 (相当于拿走所有事件)
        // 参数3: 保存收到的值
        xTaskNotifyWait(0, 0xFFFFFFFF, &ulNotifiedValue, portMAX_DELAY);
        
        if ((ulNotifiedValue & EVENT_TEMP_HIGH) != 0)
        {
            printf("处理温度报警...\r\n");
        }
        if ((ulNotifiedValue & EVENT_BATT_LOW) != 0)
        {
            printf("处理低电量...\r\n");
        }
    }
}

4. 模拟“邮箱” (Mailbox - 覆盖模式)

应用场景:广播传感器数据。比如陀螺仪一直在更新数据,但我只需要最新的那个值,旧的丢掉没关系。

核心逻辑:

  • 发送:使用 eSetValueWithOverwrite。不管接收方有没有读,强制覆盖。

  • 接收:读取数据。

代码示范:

/* ================= 发送端 ================= */
uint32_t sensor_data = 100;

// 强制写入 100,不管里面有没有旧数据
xTaskNotify(xTaskHandle, sensor_data, eSetValueWithOverwrite);

sensor_data = 200;
// 再次强制写入 200,旧的 100 被覆盖了
xTaskNotify(xTaskHandle, sensor_data, eSetValueWithOverwrite);


/* ================= 接收端 ================= */
void TaskB(void *pvParameters)
{
    uint32_t ulCreateValue;
    while(1)
    {
        // 参数1,2 都为 0,表示我不清除数据,我只是读一下
        // 或者 参数2 为 0xFFFFFFFF 表示读完就清空
        xTaskNotifyWait(0, 0, &ulCreateValue, portMAX_DELAY);
        
        printf("收到最新数据: %d\r\n", ulCreateValue);
    }
}

5. 模拟“轻量级队列” (Queue - 长度为1,不覆盖)

应用场景:传递重要指令。比如“保存配置”。如果上一条指令还没处理完,下一条指令不能发,否则会丢失。

核心逻辑:

  • 发送:使用 eSetValueWithoutOverwrite。如果接收方上次没读走(状态是 pending),发送会失败。

  • 接收:读走数据,并将状态复位。

代码示范:

/* ================= 发送端 ================= */
uint32_t cmd_code = 0x55;
BaseType_t xStatus;

// 尝试发送
xStatus = xTaskNotify(xTaskHandle, cmd_code, eSetValueWithoutOverwrite);

if (xStatus == pdPASS) {
    printf("指令发送成功\r\n");
} else {
    printf("发送失败!对方还没处理完上一条消息\r\n");
}


/* ================= 接收端 ================= */
void TaskB(void *pvParameters)
{
    uint32_t ulRecvValue;
    while(1)
    {
        // 必须处理数据,让状态变为“未等待”,这样发送方才能发下一条
        xTaskNotifyWait(0, 0xFFFFFFFF, &ulRecvValue, portMAX_DELAY);
        
        printf("处理指令: %x\r\n", ulRecvValue);
        // 模拟处理很慢,导致发送方可能失败
        vTaskDelay(pdMS_TO_TICKS(1000)); 
    }
}

总结对比

模式发送参数 (eAction)接收函数核心特点
二值信号量xTaskNotifyGiveulTaskNotifyTake(pdTRUE)收到就清零,只看“有没有”
计数信号量xTaskNotifyGiveulTaskNotifyTake(pdFALSE)收到减 1,看“有多少”
事件组eSetBitsxTaskNotifyWait按位操作,多事件并存
邮箱eSetValueWithOverwritexTaskNotifyWait喜新厌旧,只留最新的
队列(长1)eSetValueWithoutOverwritexTaskNotifyWait先来后到,满了就报错

建议:最常用的其实是 二值信号量(做同步)和 邮箱(传简单数值)。先把这两个练熟!

 


 

四:任务通知局限性

既然任务通知这么好用,为什么还要用计数型信号量、二进制信号量、事件组等函数?

 

这是一个非常深刻的问题!很多初学者(甚至有经验的工程师)都会有这个疑惑:既然有一把瑞士军刀(任务通知),为什么还需要专门的螺丝刀、锤子和扳手(信号量、队列、事件组)?

答案在于:任务通知虽然快,但它“太独了”。它的局限性决定了它无法完全取代传统的通信机制。

我们可以把任务通知比作**“发私信”(微信私聊),把队列/信号量比作“公共公告板”“公共储物柜”**。

以下是必须使用传统 IPC(信号量、队列、事件组)的四大场景


1. 场景一:向“一群人”发送信号 (一对多广播)

任务通知的局限: 它是一对一的。

你必须知道接收者的“名字”(任务句柄),而且只能发给它一个人。如果你想让 5 个任务同时开始工作,你需要写个循环,调用 5 次 xTaskNotifyGive。

传统优势 (事件组 Event Group):

事件组是一对多的。

就像在广场上鸣枪,所有听到枪声的运动员(任务)都会同时起跑。

  • 例子: 系统遇到致命故障,需要通知“网络任务”、“电机任务”、“显示任务”全部立刻停止。用事件组只需设置一个标志位,所有任务都能读到。

2. 场景二:谁有空谁来做 (多对一 / 抢占式处理)

任务通知的局限: 你必须指定具体要把活儿派给谁。

传统优势 (队列/计数信号量):

这就像一个**“公共任务池”**。

假设你有 3 个“工人任务”负责处理网络数据包,但你不关心具体是工人 A、B 还是 C 来处理,只要有人处理就行。

  • 做法: 你把数据扔进公共队列。

  • 结果: 3 个工人谁现在闲着,谁就去队列里拿数据。

  • 任务通知做不到: 因为发送时你必须填 xTaskToNotify 参数,你得硬性指定给某个人,哪怕那个人正忙得不可开交,而其他人正闲着。

3. 场景三:发送方需要“排队等待” (阻塞发送)

任务通知的局限: 发送方不能阻塞。

这是最容易被忽视的区别!

  • 当你调用 xTaskNotify() 时,如果接收方的“口袋”满了(上一次通知还没处理),发送方只有两个选择:

    1. 覆盖旧数据(旧数据丢失)。

    2. 放弃发送(返回失败)。

    • 发送方无法说: “我不走,我就死等在这里,直到你的口袋空出来为止。”

传统优势 (队列 Queue):

队列允许发送阻塞。

  • xQueueSend(xQueue, &data, portMAX_DELAY);

  • 含义: 如果队列满了,发送任务会进入休眠状态,直到队列有空位了,它才会被唤醒把数据放进去。这在流量控制(Flow Control)中非常关键,防止生产速度快于消费速度导致的数据丢失。

4. 场景四:传递“大”数据

任务通知的局限: 只能传 32位(4字节)。

虽然你可以传一个指针(地址),但这就涉及到了内存管理(谁申请?谁释放?这就复杂了)。

传统优势 (队列 Queue):

队列可以复制任意大小的数据结构。

  • 你可以定义一个 struct { int id; char msg[20]; float temp; },直接扔进队列里。FreeRTOS 会帮你把整个结构体复制一份存起来,非常安全且省心。


总结:决战紫禁之巅

为了方便记忆,我做了一个对比表:

特性任务通知 (私信)队列/信号量/事件组 (公共设施)
速度极快 (快45%)普通
内存占用极低 (无需额外创建对象)较高 (需要申请堆空间)
通信对象明确指定的 1 个任务任何持有句柄的任务 (不限人数)
发送方阻塞不支持 (满了只能走或覆盖)支持 (满了可以死等)
接收方只能是拥有该通知的任务任何任务都可以去读/取
数据大小仅 32位 (数值或指针)任意大小 (结构体、数组)
ISR 交互只能 ISR->Task支持 ISR->Task, Task->ISR, Task->Task

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值