## ISR 通信 ## 判别规则(核心,唯一要记的) ISR 里用任何 API,按顺序套: 1. **会阻塞/带超时等待吗?** 会(take、带超时的 send、vTaskDelay)→ **禁用** 2. **不阻塞的"发送/通知"类 → 用带 `FromISR` 后缀的版本**(QueueSend / QueueReceive / Give / Notify / EventGroupSetBits) 3. **互斥量(mutex)在 ISR 里彻底禁用**——它靠优先级继承,需要任务上下文,无 FromISR 版 一句话:**会阻塞或要任务身份的,ISR 里都禁用;其余一律 FromISR 版。** ## 抽象描述(一句话本质) 中断是"快进快出"的上下文——不能睡、不能等。FromISR 版 API 不阻塞,且不立即切换上下文,只打标志等中断返回后再调度。 ## 为什么 FromISR 版特殊(两个关键机制) - **不阻塞**:队列满/空时直接返回 pdTRUE/pdFALSE,由调用方决定重试或丢弃 - **不立即切换**:唤醒更高优先级任务时只把 `pxHigherPriorityTaskWoken` 写 pdTRUE,ISR 末尾调 `portYIELD_FROM_ISR(...)`,调度器在**中断返回的安全点**才真正切换 ## 为什么互斥量不能进 ISR("任务上下文") 优先级继承要绑定"哪个任务持有资源"并临时提升它的优先级。ISR 不是任务——没有身份、没有可提升的优先级。所以互斥量的 give/take 只发生在任务上下文。类比:VIP 插队权只发给占坑位的"任务",不发给"不排队的外来者"。二值/计数信号量的 give 不需要知道是谁,所以有 FromISR 版;互斥量靠持有者身份,没有。 ## 实际例子:UART 中断唤醒高优先级任务 ```c /* data_task(优先级 3):阻塞等队列 */ xQueueReceive(&q, &data, portMAX_DELAY); /* 队列空 → 挂起 */ /* USART1 中断 */ void USART1_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; uint8_t byte = USART1->DR; xQueueSendFromISR(&q, &byte, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } ``` 时序推演(关键是第 3~5 步): | 步骤 | 状态 | | ---- | ---------------------------------------------------------------------------------------------------------------------- | | 0 | `data_task` 阻塞在队列上;`idle_task`(优先级 1)正在跑 | | 1 | 串口来字节 → CPU 进入 ISR,硬件自动把当前寄存器压进中断栈 | | 2 | `xQueueSendFromISR`:队列空→非空,`data_task` 从 Blocked 变 Ready;发现其优先级(3) > 正在跑的(1) → 把局部标志写 pdTRUE | | 3 | **此刻不切换!** ISR 还在执行,中断现场还挂在栈上 | | 4 | ISR 末尾 `portYIELD_FROM_ISR(pdTRUE)` → 触发 PendSV(优先级最低,保证等所有中断处理完) | | 5 | ISR 正常返回,硬件恢复中断现场 → 紧接着 PendSV 执行真正的切换:存 `idle_task` 现场、载入 `data_task` 现场 | | 6 | `data_task` 开始跑,处理这个字节 | 若没唤醒更高优先级任务,标志保持 pdFALSE → 零切换开销,直接返回原任务。 **一句话**:`pxHigherPriorityTaskWoken` = 中断里的"请假条",ISR 只填"有没有人需要换上来",真正换班(PendSV)在中断收工后的安全点进行。 ## 底层原理:中断跳转流程(计算机组成原理补课) **CPU 怎么知道下一步干嘛**:靠 PC(程序计数器)指向下一条指令的内存地址,循环执行「按 PC 取指令 → 执行 → 更新 PC」。程序 = 内存代码区里的一排排指令,PC 像书签指到哪读到哪。 **跳转 = 改 PC**: - 函数调用 `BL func`:先把返回地址存进 LR,再把 PC 改成 func 地址 - 函数返回 `BX LR`:把 LR 的地址装回 PC,回到调用处继续 **中断跳转 = 强制改 PC + 先存现场**(区别于程序自己安排的普通跳转,中断是外部突然插入的): | 步骤 | 动作 | | ---- | ---------------------------------------------------------------------------------------------- | | ① | 硬件压栈:PC(被打断位置)+ R0~R3 + R12 + LR + xPSR 共 8 个寄存器(异常帧,24 字节)存入当前栈 | | ② | 查**向量表**(内存开头的"电话簿",每个中断号对应一个 ISR 入口地址),取出 ISR 地址 | | ③ | 强制跳转:PC = ISR 地址,CPU 开始执行 ISR | | ④ | ISR 干活(如 xQueueSendFromISR) | | ⑤ | ISR 返回:BX LR(LR 是 EXC_RETURN 特殊值) | | ⑥ | 硬件弹栈:把栈里存的旧 PC 弹回 PC | | ⑦ | 接着执行被打断的那条指令,像没发生过中断 | **压栈压到谁的栈**: | 运行模式 | 用哪个栈 | 异常帧压哪 | | ------------------------------------ | -------- | ---------------- | | 裸机(Thread mode + MSP) | MSP | 主栈 | | RTOS 任务被打断(Thread mode + PSP) | PSP | **当前任务的栈** | **这解释了为什么栈估算必须算中断**:RTOS 里任务正跑着被中断 → 异常帧压进这个任务的栈。任务栈只算局部变量+调用帧、不算中断嵌套现场,ISR 一触发就可能溢出。(嵌套中断会连续压多个异常帧。) **为什么硬件自动压栈**:ISR 要用寄存器/调函数,会覆盖 R0~R3 等。硬件先存好现场,ISR 才能随便用;返回时自动弹栈,原代码无感知。类比:接电话前先把手上东西和"看到第几页"记下来。 **类比**:PC=读书记号,跳转=翻页,压栈=夹书签。普通跳转=自己翻页,中断跳转=电话铃响,硬件先帮你夹好书签再翻到 ISR 那页。 ## 易混对比 | | 普通版 | FromISR 版 | | ---------------- | ---------------- | -------------------------- | | 阻塞吗 | 会(带超时等待) | 不会(失败立即返回) | | 唤醒高优先级任务 | 立即切换上下文 | 只置标志,中断返回后才切换 | | 适用 | 任务上下文 | 中断 / 任务都可用 | ## 变体验证(3 题,全过=学会) 1. ISR 里 `xQueueReceiveFromISR(&q, &buf, &woken);` → **合法**(receive 不阻塞,队列空就返回失败) 2. ISR 里 `xSemaphoreTakeFromISR(...)` → **不存在**(take 会"等",必然阻塞);give 只给信号量、不给互斥量 3. 陷阱题:ISR 里 `xEventGroupSetBitsFromISR(&eg, 0x01);` → **合法**(事件组 setBits 不阻塞、无需任务身份,有 FromISR 版;别以为只有队列/信号量才有) ## 口述要点(面试怎么讲) - **结论先行**:ISR 里看 API 两件事——会阻塞吗?要任务身份吗? - **为什么有 FromISR 版**:① 普通 API 资源不可用时会把"当前任务"挂起阻塞,但 ISR 不属于任何任务、没有当前任务可挂,一挂就崩;② 普通 API 靠关中断保护数据结构,ISR 里再关中断会压后更紧急的中断,FromISR 版用中断掩码;③ 切换时机推迟到中断返回后的安全点 - **pxHigherPriorityTaskWoken**:为什么不能直接切——中断中途切换会搞乱硬件压栈的中断现场,嵌套中断更危险;所以只登记标志,由 PendSV 在安全点切换 - **易错点**:在中断里给互斥量 give;用带超时的普通 send;以为只有队列/信号量有 FromISR 版 ## 关系网络 | 相邻概念 | 关系 | 孤立理解会犯的错 | | ------------ | -------------------------------------------------------- | -------------------------------------- | | 临界区 | FromISR 版内部也有数据保护,但用中断掩码而非全局关中断 | 以为中断里不能有任何保护机制 | | 任务同步方式 | `xTaskNotifyFromISR` 是 ISR→任务一对一最轻的通道 | 以为中断里只有队列能用 | | 优先级反转 | 互斥量不能进 ISR 的根因 = 优先级继承需要任务上下文 | 在中断里给互斥量 give | | 裸机中断 | 裸机只有一种上下文;RTOS 引入任务上下文 → 中断和任务隔离 | 沿用裸机习惯直接改共享变量、不通知任务 | ## 学习日期 2026-08-03 --- _每主题一页,复习时只翻本目录。_