DMA的传输完成阶段由中断服务程序(ISR)负责处理,中断服务程序在DMA控制器发出传输完成信号后介入,完成数据校验、状态清除和后续任务调度。
很多开发者容易混淆概念,以为中断服务程序(ISR)在DMA传输过程中逐字节参与搬运,这其实是对DMA机制的误解,DMA的精髓在于其英文全称Direct Memory Access直接内存访问,数据搬运过程由硬件完成,CPU完全不参与,真正需要中断服务程序出场的,是DMA传输完成后那个“接力”瞬间。
DMA完整流程拆解:从配置到中断响应的三个阶段
要彻底搞懂“哪个阶段由中断服务程序完成”,必须先画清楚DMA工作的整体时序图,业内专家指出,理解DMA的核心在于区分“谁在什么时候干活”,整个DMA事务可以拆解为三个阶段,每个阶段涉及的角色完全不同。
CPU初始化配置阶段
这个阶段完全由CPU主导,执行的是驱动程序里的DMA初始化代码,你需要完成以下操作:
- 设置DMA源地址寄存器,指向外设数据寄存器或内存缓冲区起始地址
- 设置DMA目的地址寄存器,指向目标内存区域
- 配置传输长度寄存器,明确本次要搬运多少字节
- 设置控制寄存器,包括传输方向(内存到外设、外设到内存或内存到内存)、突发传输大小、地址递增模式
- 使能DMA通道,并开启中断使能位
这个阶段CPU是“全职保姆”,事无巨细地安排好一切。
DMA硬件自动搬运阶段
当外设产生请求信号,或者软件启动DMA传输后,DMA控制器接管总线控制权,开始自动搬运数据,这个阶段CPU处于“旁观者”状态:
- 对于内存到外设的传输,DMA从内存取出数据写入外设数据寄存器
- 对于外设到内存的传输,DMA读取外设FIFO中的数据存入内存缓冲区
- 每搬运一个字节或一个字(取决于位宽配置),传输计数器递减
- 传输计数器递减到零时,DMA控制器在中断状态寄存器中置位传输完成标志
中断响应与善后处理阶段
传输计数归零的那一刻,DMA控制器会拉起中断请求线(如STM32的DMA_IRQn),CPU收到中断请求后,跳转到对应的中断向量,执行中断服务程序。中断服务程序在这个阶段处理的不是数据搬运,而是搬运完成后的善后工作,典型工作包括读取DMA中断状态寄存器确认中断源、清除传输完成中断标志位、验证实际传输字节数、将缓冲区指针提交给上层协议栈、启动下一轮传输。
从时间占比看:阶段一耗时微秒级,阶段二取决于数据量(千字节级数据在高速DMA下耗时几十微秒到几百微秒),阶段三的ISR执行时间严格控制在微秒级以内,中断服务程序从来不在阶段二“插一脚”,这是DMA设计的核心原则避免CPU干预数据搬运路径。

中断服务程序的核心职责:不是搬运,是善后
根据ARM Cortex-M系列处理器官方技术参考手册,中断服务程序执行有严格的时间预算要求,DMA传完数据后,系统状态就像快递送到了驿站:包裹已经码放整齐,但还没人签收、没人通知收件人,中断服务程序干的就是“签收+通知”的活。
中断标志位的确认与清除时序
进入ISR的第一步永远是对中断状态寄存器进行读-改-写操作,以STM32F4系列为例,代码路径非常固定:
void DMA2_Stream0_IRQHandler(void) {
if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) {
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
// 善后处理逻辑
process_dma_buffer();
}
}
读取中断标志位这一步引出两个关键细节,第一,为什么要先读取再清除?因为DMA中断状态寄存器是硬件置位、软件清零的典型设计,直接写清除指令可能误清其他通道的中断标志,导致中断丢失,第二,清除操作必须写入0而不是1,这与多数外设的清除逻辑相反,写1反而无效。
数据完整性验证机制
中断服务程序需要在清标志后立刻检查DMA控制器的当前传输计数寄存器,也就是剩余字节数是否真的为零,如果外设提前释放了FIFO,或者内存越界导致传输截断,计数寄存器中的值会与预期不符,这时ISR应当记录错误日志,并恢复DMA通道的默认状态。
行业共识认为,在ISR中做数据校验要遵循“最小必要”原则。不要在大数据传输完成后于中断里做CRC或哈希校验,这会严重拖长中断响应时间,正确做法是只核对计数寄存器和状态位,然后将缓冲区指针挂到待处理队列,真正的校验移到任务上下文中执行。
多通道DMA中断的差异化处理
实际项目中极少只用一个DMA通道,例如一个音频采集系统可能同时使用三个DMA通道:麦克风数据采集、I2S音频输出、内存间数据搬运,这三个通道的中断请求往往映射到同一个中断向量,ISR必须逐一读取每个通道的中断标志位,分别处理:
- 音频采集通道传输完成:将当前缓冲区标记为“满”,申请新缓冲区
- 音频输出通道传输完成:释放已播放完成的缓冲区,补入新的静音帧
- 内存搬运通道传输完成:更新内存池分配器状态,唤醒等待数据同步的任务
这直接解释了一个常见的性能优化问题为什么DMA中断里要避免处理耗时操作。 如果在一个通道的ISR里做了耗时的数据拷贝,其余通道的中断请求会被阻塞,轻则丢数据,重则触发硬件超时错误,多数RTOS(如FreeRTOS)在ISR中只做信号量或事件标志组的置位,真正的处理逻辑放在延迟处理任务中。

中断触发时机与DMA工作模式的深度绑定
DMA支持多种传输模式,中断触发时机在不同模式下有显著差异,很多人在“DMA哪个阶段由中断服务器完成”这个问题上感到困惑,根源就是他们把不同模式的中断行为混为一谈。
普通模式与循环模式的中断差异
普通模式(Normal Mode)下,DMA完成设定的传输长度后自动停止,传输完成中断只触发一次,适用于一次性数据传输,例如读取一次传感器数据打包发送给上位机。
循环模式(Circular Mode)下,DMA传输完设定的数据长度后自动回卷到起始地址继续传输,传输完成中断周期性触发,适用于连续数据流场景,例如麦克风持续采集音频信号。
这里有个非常容易被忽略的细节:循环模式的中断标志可以在传输一半时额外触发,也就是传输过半中断(Half Transfer Interrupt),这个中断的意义在于:当DMA写满了缓冲区前半部分时,CPU可以处理前半部分数据,而DMA同时在写后半部分,实现双缓冲无缝衔接。在这种模式下,ISR的职责从“善后”变成了“流水线节点”,不光要在完成中断中切换缓冲区,还要在半中断中及时取走前半段数据。
内存到内存传输模式的特别处理
内存到内存的DMA传输(如外部Flash拷贝到RAM)不依赖外设请求,由软件启动传输后DMA自动完成,这种模式下,中断服务程序在传输完成时处理内存一致性维护。 比如清单中至少需要包含:读取L1缓存控制寄存器执行Cache Clean操作,或执行DSB指令确保数据可见性,很多初学者忘掉这一步,导致DMA搬运完的数据在缓存里被改了却没写回内存,读出来的全是旧数据。
实操优化:让中断服务程序更快的四个关键动作
把ISR压缩到极短是DMA工程的核心追求,以下是实践中可验证的优化路径。
优化1:标志位检查与清除合并
多数DMA控制器支持“读状态寄存器后自动清零”模式,合理利用该机制,可减少一条写指令,具体做法:直接读取中断挂起寄存器值,判断对应位,因为读取后硬件自行清除该标志。
优化2:移出非紧急操作
缓冲区指针入队、链表追加、内存池分配这些操作不应该出现在ISR里,以裸机开发为例,推荐做法是:
ISR中只做: - 读取状态寄存器 - 清除中断标志 - 设置一个全局的volatile标志位 - 结束中断(退出) 主循环中做: - 检测到标志位后,正式处理缓冲区数据 - 调用协议栈接口发送数据 - 启动下一轮DMA传输
优化3:利用中断优先级和嵌套
在ARM Cortex-M系列中,为DMA中断分配较高优先级,使其能打断低优先级中断(如定时器中断),同时设置子优先级,确保同一IP核内不同DMA通道的中断按业务紧要程度排序,高实时性任务(如电机控制)的DMA中断应高于次紧要任务(如LED刷新)。
优化4:ISR中的DMA缓冲区切换策略
常规做法是维护三个指针:active(当前DMA正在填充的)、next(下一个空闲待用的)、done(已满待处理的),ISR中将done指针指向刚满的缓冲区,然后交换active和next指针,整个过程只涉及三次指针赋值,不涉及数据拷贝,时间开销约几十个时钟周期。
| 优化项 | 优化前耗时 | 优化后耗时 | 核心操作 |
|---|---|---|---|
| 标志位处理 | 9个时钟周期 | 4个时钟周期 | 读寄存器+与运算判断 |
| 数据交接 | 循环复制缓冲区(数百周期) | 3次指针赋值(6个周期) | 指针交换 |
| 通知任务 | 调用系统API | 原子操作置位 | LDREX/STR指令 |
| 退出中断 | 清理栈帧 | 直接BX LR | 尾链优化 |
常见误区排查与问答
DMA传输完成后,CPU是怎么知道要处理新数据的?
CPU通过中断请求线感知DMA事件,DMA传输完成时硬件将中断挂起寄存器对应位置1,并向NVIC发送中断请求脉冲,NVIC仲裁后触发ISR。在一些无中断场景中,CPU可以通过轮询DMA状态寄存器获取传输状态,但缺失硬件事件驱动的即时性,通常用于对时序要求苛刻且延迟容忍度较高的场合。
中断服务程序能在传输过程中执行吗?
可以,但这并非处理器正常的“丹拿”工作模式,处理器可以在DMA搬运期间响应同优先级或更高优先级中断,但此时执行的中断不会影响DMA硬件资源,当DMA搬运完成信号到来时,中断被置起,待CPU完成当前中断后才会响应。不要在传输中修改DMA控制寄存器,数据冲突可能导致搬运数据错乱。
为什么我的DMA中断服务程序执行了两遍?
多数情况下,这是中断标志清除方式错误导致的,常见原因是,直接对标志位写0时,写入次数和实际挂起的中断事件数量存在匹配偏差,正确做法是核对数据手册中具体的清除要求,并采用软件先读后写方式,可以尝试在ISR末尾查是否还有多余标志位,如果存在,说明中断挂起杂散信号未清干净,强化清标志时序即可。
图片来源于AI模型,如侵权请联系管理员。作者:酷小编,如若转载,请注明出处:https://www.kufanyun.com/ask/743760.html

