2. DMA基础原理
在嵌入式与通用计算系统中,CPU 直接搬运数据是一种“昂贵”的操作:每一次读写都要占用取指、译码、执行与总线周期。DMA(Direct Memory Access,直接存储器访问)的核心思想,是让一个独立的硬件控制器代替 CPU 完成数据搬运,CPU 只负责“配置任务”和“处理完成事件”。理解 DMA 的工作模式、传输方向、地址空间、总线带宽与中断机制,是后续使用 dmaengine 接口和描述符链管理的前提。
一、核心原理:DMA 如何把 CPU 从搬运中解放出来
DMA 控制器(DMAC)内部通常包含源地址寄存器、目的地址寄存器、传输长度寄存器、控制寄存器以及状态寄存器。CPU 通过总线写入这些寄存器,DMAC 便向总线发起读写事务,把数据从源端搬到目的端。整个过程不需要 CPU 逐字节参与,CPU 可以继续执行其他任务,或在低功耗状态下等待。
从“谁发起访问”的角度看,DMA 工作模式可分为三类:
- 周期窃取(Cycle Stealing):DMAC 与 CPU 交替使用总线,每次只搬一个数据单元,适合对实时性要求高的系统。
- 突发传输(Burst Transfer):DMAC 连续占用总线搬完一整块数据,吞吐高,但会短暂阻塞 CPU 访问。
- 透明传输(Transparent):DMAC 只在 CPU 不使用总线时搬运,对 CPU 几乎无影响,但速度最慢。
这三种模式本质上是“总线占用权”的分配策略,选择哪一种,取决于系统对带宽和实时性的权衡。
二、传输方向与地址空间
DMA 的传输方向由源端和目的端共同决定,常见有四类:外设到存储器(如 ADC 采样写入内存)、存储器到外设(如 UART 发送缓冲区)、存储器到存储器(如内存拷贝)、外设到外设(较少见,通常需要特殊控制器支持)。方向配置错误是实际调试中最常见的故障之一,往往表现为数据错位或外设无响应。
地址空间方面,DMA 需要区分三类地址:
- 物理地址:DMAC 直接访问的总线地址,必须真实存在且对齐。
- 虚拟地址:CPU 视角的地址,需经 IOMMU 或平台映射转换为物理地址后才能交给 DMA。
- 总线地址:设备视角的地址,在多总线系统中可能与物理地址不同。
三、总线带宽与中断机制
DMA 的吞吐受总线带宽限制。若外设数据率超过总线可用带宽,或 DMA 与 CPU、其他主设备争用总线,就会出现丢包、溢出或延迟抖动。评估时需关注:总线位宽、时钟频率、仲裁策略、突发长度以及是否与其他高带宽设备共享通道。
中断机制是 DMA 与 CPU 的“握手”方式。常见中断类型包括:
- 传输完成中断:整块数据搬完,通知 CPU 处理。
- 半传输中断:搬完一半时触发,适合双缓冲或流式处理。
- 错误中断:总线错误、地址错误、溢出等异常。
中断过多会加重 CPU 负担,因此常配合描述符链、批量完成或轮询模式来降低中断频率。下面这张图展示了 DMA 的典型数据通路与中断回传路径:
四、案例场景:ADC 连续采样中的 DMA 配置
以 STM32 类平台为例,ADC 以 1 MHz 采样率连续转换,若用中断逐次读取,CPU 每秒要进 100 万次中断,几乎无法执行其他任务。改用 DMA 后,ADC 每次转换完成触发 DMA 请求,DMAC 把结果写入内存缓冲区,仅在搬完一整块(如 1024 个样本)后产生一次完成中断。
/* 伪代码:ADC + DMA 循环缓冲配置要点 */
dma_config.src_addr = &ADC1->DR; // 外设数据寄存器
dma_config.dst_addr = adc_buffer; // 内存缓冲区
dma_config.length = 1024; // 传输单元数
dma_config.src_width = DMA_WIDTH_16BIT;
dma_config.dst_width = DMA_WIDTH_16BIT;
dma_config.mode = DMA_CIRCULAR; // 循环模式
dma_config.irq = DMA_IRQ_HALF | DMA_IRQ_FULL;
dma_start(&dma_config);
/* 半传输中断处理前半缓冲,完成中断处理后半缓冲 */
这里体现了 DMA 的三个关键价值:降低中断频率、释放 CPU 算力、保证采样节奏稳定。若把 length 设错或方向配反,常见现象是缓冲区只有首字节有效、数据整体偏移或完全无数据。
五、误区与复盘
| 误区 | 现象 | 正确做法 |
|---|---|---|
| 用虚拟地址配置 DMA | 数据写入错误位置或触发总线错误 | 使用 dma_map_single / dma_alloc_coherent 获取总线地址 |
| 忽略 cache 一致性 | CPU 读到旧数据,DMA 写入被覆盖 | 传输前后做 cache flush / invalidate,或使用一致性内存 |
| 方向配置错误 | 外设无响应或数据错位 | 明确源端与目的端,核对 DEV_TO_MEM / MEM_TO_DEV |
| 中断过密 | CPU 占用率高,实时任务抖动 | 使用半传输、描述符链或批量完成降低中断次数 |
| 忽视总线争用 | 高负载下丢包、溢出 | 评估带宽预算,合理设置突发长度与仲裁优先级 |
总结来看,DMA 的工作模式决定总线占用策略,传输方向决定数据流向,地址空间决定访问合法性,总线带宽决定吞吐上限,中断机制决定 CPU 参与频率。把这五个维度理解清楚,后续使用 dmaengine 接口申请通道、准备描述符、提交事务和管理回调时,才能准确判断每个参数背后的硬件含义。