中断与DMA使用要点与常见坑
本专题合并整理自「立芯嵌入式」星球中关于 NVIC 中断机制、中断优化与 DMA 使用的 20 余篇问答,提炼出可直接落地的原则与典型坑。
一、中断延时与上半部/下半部模型
中断延时指从中断请求产生到进入 ISR 之前的时间。ISR 内部又分两半:上半部是必须立刻做的事,核心是清除对应标志位;下半部是与中断相关但耗时的处理,应安排在 ISR 之外执行,以缩短中断服务时间、降低系统中断延时(什么是中断延时: 中断延时是,Jack;更早的同主题总结见 中断延时是中断请求到进入ISR之前的时间,上半部是ISR内必须要做的部分,Jack)。
中断在系统里是紧俏资源:ISR 处理时间过长会影响实时性,甚至丢中断。优化思路分两层(关于中断优化: 中断在系统是一,Jack):
- 上半部:精简逻辑、减少不必要的局部变量(避免压栈出栈开销);数据通知优先用全局变量而非 IPC,中断线程化以降低消耗。
- 下半部:把耗时处理剥离到线程中;配合显式/隐式双 buffer、原地/异地解包等手段(详见 数据缓冲与解包: 以串口为例)。
同时要认识到系统实时性有上限:负载超过芯片能力时丢中断是必然的,此时用看门狗、GPIO+示波器测量定位瓶颈,软件优化到极限后只剩升级硬件一条路(同上,Jack)。判断"中断资源是否溢出"不是看单一中断长不长,而是看所有中断延时叠加构成的系统中断偏移——产生偏移即说明中断任务已不具实时性(单一中断久不久不重要,重要的是所有中断延时加起来是不是构成了整个系统的中断偏移,Jack)。
二、NVIC 行为的关键细节
- 减少中断延迟的四个手段:提高优先级;减少更高优先级 ISR 的时间(用全局标志实现"快进快出");减少临界区;把 ISR 分散加载到 RAM 执行,减少流水线取指令时间(老师,中断的延迟处理时间要怎么,Jack)。
- 如何验证中断嵌套:在低优先级 ISR 中人为挂起高优先级中断并设断点,若能进入高优先级 ISR 则嵌套发生;若卡死在低优先级循环里,说明优先级不够或被屏蔽(怎么看中断有没有嵌套? 观察,Jack)。
- 同优先级同时触发:NVIC 按 IRQ 中断号小的先执行("If multiple interrupts are pending for the same priority level, the interrupt with the lowest IRQ number is processed first")(老师,如果这两个定时器的中断优先级设置的一样,如果同时触发是定时器6会先执行,Jack)。
- 中断屏蔽寄存器屏蔽的中断会被悬起但不响应,解除屏蔽后仍会触发(使用中断屏蔽寄存器组屏蔽的中断是可以悬起不能响应,还是连悬起都没有,Jack)。
- 中断里不要直接修改被多任务共享的全局变量,应置标志位或发信号量,让线程有秩序地修改(中断里面不建议修改被共享的全局变量,中断里面可以置位一个标志位,或者发信号量,Jack)。
三、RTOS 环境下中断与任务的衔接
ISR 中向队列发送数据可能唤醒更高优先级任务:若不主动处理,中断退出后会继续执行被打断的低优先级任务,直到其时间片用尽或阻塞,高优先级任务的实时性就没了;正确做法是让调度器在中断退出后立刻切换到高优先级任务(在中断服务程序中向队列中发送数据时,可能会有更高优先级的任务会因此被唤醒,这里的更高,Jack)。
底层机制是:中断中调用 FreeRTOS 队列 API 时,即使唤醒了高优先级任务,也只置位 PendSV 悬起,等退出所有中断后才响应 PendSV 完成切换——这本身就是一种中断下半部优化(在0-4的中断里面调用os的api函数,也算是执行了这个中断的ISR,Jack)。而消息队列发送后检查并触发 PendSV 做上下文切换,正是软件中断在产品代码中最典型的应用(老师,请问在开发中,软件中断是,Jack)。
四、DMA 使用要点
- 速率上限:DMA 不会快过 AHB 总线速度,还受 CPU 抢占总线、外设读写速度限制;想跑高速可用 Burst 模式抢占总线(老师,dma的速率是多少?,Jack;Burst 图解见 DMA的Burst的理解,Jack;Burst 被打断的行为见 关于burst的打断,Jack)。
- "DMA 降低 AHB 带宽占用"的正确理解:不是"让 CPU 干别的事",而是 DMA 可以不经 AHB 直接访问外设,只把数据从外设经 AHB 写入 SRAM,从而省下带宽(老师,DMA能降低ahb总线带宽的意思是不是:我们通过dma搬运数据,Jack)。
- 主控双端口设计:DMA 有内存端口和外设端口,外设端口无法执行内存写操作,两端口的访问权限不同(老师,DMA主控有两个端口——内存端口和外设端口。这两个端口都连接到总线矩阵,Jack)。
- 总线仲裁:CM4 与 DMA2 同时经 APB2 访问 UART/ADC 时由仲裁器串行化,UART 和 ADC 不会被同时访问(老师,根据您的对DMA的讲解,ARM CM4和DMA2同时通过APB2访问UART,Jack);同一 APB2 上多个外设发送数据不会因带宽竞争互相卡住(Q: 老师,如果多个外设连接到,Jack)。
- ADC/外设 + DMA 的回调嵌套:HAL 库中
HAL_ADC_Start_DMA启用 DMA 后,所谓"ADC 转换完成回调"实际是在 DMA 传输完成回调中被调用的,能进回调就说明 DMA 已搬完(老师,还有一个问题,你们给的参考例程中,为什么这里使用adc转换完成的中断回调,Jack)。HAL 回调行为与预期不符时,要能一路跟到库的最底层调试(hal库的有些操作,尤其是中断回调,有时候和我们预期是不一致,Jack)。 - 双 buffer 切换不及时:线程切换时 buffer 已满、通知又没来得及发,会丢数据。实践解法是测试时测出峰值、把 buffer 设到足够大,且绝不在运行时 malloc(adc+dma,双buffer,我们在a线程进行切换的时候,Jack)。
五、真实复盘:UART + DMA 接收错位(RH850)
一个典型案例:DMA 启动接收后数据整体后移,表面像 DMA 搬错,根因却是只发送不接收阶段 Rx 引脚仍处于 UART 复用输入,总线波形被误解析成数据残留在接收寄存器里,下一次 enable DMA 时先把脏数据搬进 buffer。解法是按接收状态动态切换 Rx 引脚模式:不接收时配成普通 DIO 输入断开外设输入链路,接收时再切回复用输入(最近在 RH850 平台调试 时,遇到一个比较典型的问题,zzz)。
核心经验:DMA 只是被动搬运工具,不判断数据有效性。排查"外设+DMA"问题不能只盯 DMA 配置,还要检查外设输入源是否干净、request 是否挂起、enable 前有无残留数据。
六、不定长接收的两条路线
- 空闲中断 + 指针偏移:不要靠 DMA 定长中断,每次空闲中断产生就解析数据,且不要去干预 DMA 本身,否则容易挂(这里不能靠dma中断去定长度进,Jack)。
- 无空闲中断的芯片:查询 DMA 剩余传输长度并结合时间来断帧,很多没有串口空闲中断的单片机都这么做,顺带节省中断资源(串口dma接收,不用idle的话,可以自己查询dma剩余传输长度结合时间来断帧,Jack;图解见 单片机的串口本身没有空闲中断怎,Jack)。
来源笔记
- 什么是中断延时: 中断延时是 | Jack
- 中断延时是中断请求到进入ISR之前的时间,上半部是ISR内必须要做的部分 | Jack
- 关于中断优化: 中断在系统是一 | Jack
- 单一中断久不久不重要,重要的是所有中断延时加起来是不是构成了整个系统的中断偏移 | Jack
- 老师,中断的延迟处理时间要怎么 | Jack
- 怎么看中断有没有嵌套? 观察 | Jack
- 老师,如果这两个定时器的中断优先级设置的一样,如果同时触发是定时器6会先执行 | Jack
- 使用中断屏蔽寄存器组屏蔽的中断是可以悬起不能响应,还是连悬起都没有 | Jack
- 中断里面不建议修改被共享的全局变量,中断里面可以置位一个标志位,或者发信号量 | Jack
- 在中断服务程序中向队列中发送数据时,可能会有更高优先级的任务会因此被唤醒,这里的更高 | Jack
- 在0-4的中断里面调用os的api函数,也算是执行了这个中断的ISR | Jack
- 老师,请问在开发中,软件中断是 | Jack
- 老师,dma的速率是多少? | Jack
- DMA的Burst的理解 | Jack
- 关于burst的打断 | Jack
- 老师,DMA能降低ahb总线带宽的意思是不是:我们通过dma搬运数据 | Jack
- 老师,DMA主控有两个端口——内存端口和外设端口。这两个端口都连接到总线矩阵 | Jack
- 老师,根据您的对DMA的讲解,ARM CM4和DMA2同时通过APB2访问UART | Jack
- Q: 老师,如果多个外设连接到 | Jack
- 老师,还有一个问题,你们给的参考例程中,为什么这里使用adc转换完成的中断回调 | Jack
- hal库的有些操作,尤其是中断回调,有时候和我们预期是不一致 | Jack
- adc+dma,双buffer,我们在a线程进行切换的时候 | Jack
- 最近在 RH850 平台调试 时,遇到一个比较典型的问题 | zzz
- 这里不能靠dma中断去定长度进 | 匿名提问 / Jack 答
- 串口dma接收,不用idle的话,可以自己查询dma剩余传输长度结合时间来断帧 | Jack
- 单片机的串口本身没有空闲中断怎 | Jack
- 数据缓冲与解包: 以串口为例 | Jack