FreeRTOS内部机制(三)
是一种轻量级、高速的机制,用于替代二值信号量、计数信号量、事件组和队列(在单数据传递场景下)。**用途:**普通外设中断(串口、定时器、GPIO、SPI、I2C 等)**用途:**超高实时性要求场景:电机驱动、高精度采样、紧急保护中断等。(通常是 Cortex-M 的 SysTick 系统滴答定时器),每次硬件定时器中断 +1,如果是 32 位:最大到。将相当于一个标志位,用来标记是否唤醒了更高优
文章目录
FreeRTOS内部机制(三)
7. 任务通知(Task Notification)

使用队列、信号量、互斥量、事件组等机制时,步骤都是这样的:
- 创建对象
- 写/Give
- 读/Take
对于任务通知,不需要"创建对象",因为要操作的对象就在TCB里:

任务通知 (Task Notifications) 是一种轻量级、高速的机制,用于替代二值信号量、计数信号量、事件组和队列(在单数据传递场景下)。它直接操作 TCB(任务控制块)中的 ulNotifiedValue 和 eNotifyState,无需额外的内核对象,因此速度最快且节省 RAM
7.1 任务核心: 通知状态、通知值
7.1.1 通知状态
一个任务的"通知状态"有三种:
- taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
- taskWAITING_NOTIFICATION:任务在等待通知
- taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为 pending(有数据了,待处理)
一个任务想等待别人发来通知,可以调用ulTaskNotifyTake 或xTaskNotifyWait :
- 可能别人早就发来通知:"通知状态"为taskNOTIFICATION_RECEIVED,那么函数立刻返回
- 可能别人还没发来通知:这些函数把"通知状态"从taskNOT_WAITING_NOTIFICATION改为taskWAITING_NOTIFICATION,然后休眠
别的任务可以使用xTaskNotifyGive或xTaskNotify 给某个任务发通知:
- 会马上唤醒对方
- 无条件唤醒对方,不管对方期待什么数据

7.1.2 通知值
一个整数,调用xTaskNotifyGive或xTaskNotify 时,传入不同的参数可以去设置这个数值:
- 不改变它的数值,只想唤醒任务
- 增加1
- 设置为某个数
8.软件定时器
8.1软件定时器核心:硬件定时器+链表+守护任务
8.1.1 硬件定时器
FreeRTOS 必须依赖一个硬件定时器(通常是 Cortex-M 的 SysTick 系统滴答定时器)
它做的唯一一件事:每隔固定时间产生一次中断 → 让系统时钟 +1
SysTick_Handler
->xPortSysTickHandler
->xTaskIncrementTick
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
8.1.2 定时器列表
FreeRTOS 内核会维护两个双向链表():
pxCurrentTimerList当前到期链表:当前时钟周期内会到期的定时器pxOverflowTimerList溢出链表:下一个时钟周期(溢出后) 才会到期的定时器

每当硬件滴答中断触发一次:
xTickCount +1- 内核检查有没有定时器到期
- 如果有,就把这个定时器的回调函数命令发送到「定时器队列」
注意:中断里只发命令,不执行回调!
问:定时器列表为什么有两个?
答:系统时钟是会溢出的,FreeRTOS 有一个全局变量:volatile TickType_t xTickCount,每次硬件定时器中断 +1,如果是 32 位:最大到 0xFFFFFFFF,加满后会溢出回 0;如果一个定时器的超时时间跨越了溢出点,内核怎么判断它有没有到期?用两个列表分开存放!
对于pxCurrentTimerList,要进行的操作是:
- 内核每一个 tick 都会检查这个列表
- 找到最近要到期的定时器
- 时间一到 → 移入定时器队列 → 执行回调
对于pxOverflowTimerList,要进行的操作是:
- 正常情况下不检查
- 只有当 xTickCount 溢出归 0 时
- 才把 整个溢出列表 直接替换给 Current
当系统时钟溢出(xTickCount 从最大值 → 0)执行一次切换动作:
- 把
Current清空 - 把
Overflow的所有定时器 全部移动到 Current - 重置
Overflow为空

8.1.3 守护任务
FreeRTOS 会自动创建一个系统任务,它的工作:
- 阻塞等待「定时器队列」的命令
- 收到命令 → 找到对应定时器
- 执行定时器回调函数
- 如果是周期定时器 → 重新挂载到链表

FreeRTOS 定时器守护任务的主循环由两个关键函数构成:
- prvProcessTimerOrBlockTask:负责等待事件,要么等定时器到期,要么等命令到来,无任务时阻塞休眠,不占用 CPU 资源
- prvProcessReceivedCommands:负责处理命令,被唤醒后一次性处理所有定时器控制命令
8.2 软件定时器流程
应用层
xTimerCreate() → 创建定时器(休眠)
xTimerStart() → 发命令 → xTimerQueue
内核层(守护任务)
阻塞等待 Queue
↓
收到命令
↓
插入定时器到 Current / Overflow 链表
9.中断
9.1两套API
在学习队列、信号量、互斥量时,我们会发现会有两套API,一套是普通的,另一套会有FromISR后缀,我们将队列作为示例来讲解:
xQueueSend( xQueue, pvItemToQueue, xTicksToWait )
xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
首先,为什么要有两套API?
中断上下文不能阻塞,必须专用 API
FreeRTOS 的很多 API(如 xQueueSend())内部会:
- 等待队列空闲
- 挂起任务
- 进入死循环等待
但是!中断里不能等!
- 中断必须快进快出
- 中断没有任务调度的概念
- 一旦阻塞 = 系统死机 / 卡死
所以,中断绝对不能直接调用任务级 API
那pxHigherPriorityTaskWoken的作用是什么呢?
首先要知道的是:中断执行时,任务调度器是暂停的,而且中断里不能直接调用任务切换,因为假如下一时刻有一个优先级更高的任务在中断中被唤醒,那么任务切换会一直进行下去,操作系统的实时性得不到保障,因此在中断即将结束时,需要判断:要不要切换去执行更高优先级任务,pxHigherPriorityTaskWoken将相当于一个标志位,用来标记是否唤醒了更高优先级的任务
// 1. 定义变量,初始化为 pdFALSE
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 2. 调用中断API,把地址传进去
xQueueSendFromISR( xQueue, pvItemToQueue, &xHigherPriorityTaskWoken );
// 3. 内核内部逻辑:
// 如果这个操作唤醒了一个更高优先级任务
// 内核会自动把 xHigherPriorityTaskWoken 设为 pdTRUE
// 4. 最后判断:需要切换就切换
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
9.2 两类中断
FreeRTOS 把 MCU 硬件中断分成两类,唯一区别是:能不能调用 FreeRTOS 的 API(FromISR)
-
可管理中断(受控中断)
→ 可以调用 FreeRTOS FromISR API
-
不可管理中断(不受控中断)
→ 绝对不能调用任何 FreeRTOS API
- 可管理中断(FreeRTOS 能管的)
- 优先级 低于或等于
configMAX_SYSCALL_INTERRUPT_PRIORITY - FreeRTOS 会临时屏蔽这类中断(进入临界区时)
- 可以安全调用 FromISR 系列 API
**用途:**普通外设中断(串口、定时器、GPIO、SPI、I2C 等)
需要和任务交互、发信号、发队列、启动定时器
- 不可管理中断(FreeRTOS 管不了的)
- 优先级 高于
configMAX_SYSCALL_INTERRUPT_PRIORITY - FreeRTOS 永远不会屏蔽这类中断(即使临界区也不屏蔽)
- 绝对不能调用任何 FreeRTOS API
- 执行速度最快、最实时
**用途:**超高实时性要求场景:电机驱动、高精度采样、紧急保护中断等
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 5
通过配置这个宏,我们可以区别两类中断
- 优先级 0 ~ 4:不可管理中断(最高优先级)
- 优先级 5 ~ 15:可管理中断
9.3 优先级
即使最低优先级的中断也可以打断优先级最高的任务
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)