2. DMA 基础概念
直接存储器访问(Direct Memory Access, DMA)是嵌入式系统中用于在无需 CPU 持续干预的情况下,高效搬运数据的硬件引擎。理解 DMA 的工作原理、传输类型及其与 CPU 的协同机制,是掌握 RAW 格式输出与缓冲区管理的前提。
2.1 DMA 工作原理
DMA 控制器作为总线上的独立主设备,在 CPU 配置完成后接管总线控制权,自动完成数据从源地址到目的地址的搬运。其核心工作流程如下:
- CPU 配置 DMA 通道:设置源地址、目的地址、传输长度、传输宽度及触发源。
- DMA 请求触发:外设事件(如 ADC 转换完成、UART 接收非空)或软件触发启动传输。
- 总线仲裁与传输:DMA 向总线仲裁器申请总线控制权,获得授权后执行单次或突发传输。
- 地址递增与计数:每传输一个单元,源/目的地址按设定模式递增或保持不变,传输计数器递减。
- 传输完成中断:计数器归零时,DMA 向 CPU 发出完成中断,或触发链式传输。
关键指标: DMA 传输效率取决于总线带宽、突发传输大小(Burst Size)以及通道优先级仲裁策略。在 RAW 数据流场景中,通常配置为循环模式(Circular Mode)以避免频繁重配。
2.2 DMA 传输类型
根据源和目的的设备类型,DMA 传输分为三种基本模式。在 RAW 格式输出场景中,外设到内存和内存到外设是最常用的两种类型。
| 传输类型 | 源设备 | 目的设备 | 典型应用 | RAW 场景相关性 |
|---|---|---|---|---|
| 内存到内存 | SRAM / Flash | SRAM | 数据拷贝、缓冲区整理、CRC 计算 | 低:用于 RAW 数据预处理(如像素重排) |
| 外设到内存 | ADC / SPI / I2S / DCMI | SRAM | 传感器数据采集、音频录制、图像捕获 | 高:RAW 数据从传感器搬入缓冲区 |
| 内存到外设 | SRAM | DAC / SPI / I2S / LCD | 波形输出、音频播放、显示刷新 | 高:RAW 数据从缓冲区输出到外设 |
实战提示: 在 RAW 图像传感器驱动中,通常使用双 DMA 通道:通道 1 负责将传感器数据(外设到内存)搬入 ping-pong 缓冲区,通道 2 负责将处理后的 RAW 数据(内存到外设)输出到显示或存储接口。两个通道可并行工作,最大化吞吐量。
2.3 DMA 与 CPU 的协同
DMA 并非完全取代 CPU,而是将数据搬运的重复性工作卸载到专用硬件上,使 CPU 能够专注于控制逻辑、协议解析和算法处理。协同工作的核心原则如下:
- 配置阶段:CPU 负责初始化 DMA 通道、设置传输参数、注册完成回调。
- 传输阶段:DMA 独立搬运数据,CPU 可执行其他任务或进入低功耗模式。
- 同步阶段:通过中断或轮询标志位,CPU 感知传输完成并处理数据。
- 冲突避免:CPU 和 DMA 同时访问同一内存区域时,需通过互斥机制(如关中断、乒乓缓冲区)防止数据竞争。
常见陷阱: 当 CPU 和 DMA 同时访问同一外设 FIFO 或内存地址时,可能产生总线冲突或数据一致性问题。解决方案包括:使用 DMA 的循环模式配合双缓冲区(Ping-Pong Buffer),或启用 CPU 缓存一致性协议(如 Cortex-M 的 D-Cache 清洁/无效操作)。
2.4 关键配置参数
在实际驱动开发中,DMA 初始化涉及以下核心参数,这些参数直接影响 RAW 数据流的稳定性和效率:
| 参数 | 选项 | RAW 场景推荐 | 说明 |
|---|---|---|---|
| 传输宽度 | 8-bit / 16-bit / 32-bit | 匹配传感器位宽(如 10-bit 用 16-bit) | 宽度不匹配时需填充或截断 |
| 突发大小 | 1 / 4 / 8 / 16 拍 | 4 或 8 拍(平衡总线占用与延迟) | 突发越大,总线占用时间越长,但传输效率越高 |
| 地址模式 | 递增 / 递减 / 固定 | 源固定(外设 FIFO),目的递增(内存) | 外设 FIFO 地址固定,内存缓冲区地址递增 |
| 循环模式 | 启用 / 禁用 | 启用(连续 RAW 流) | 计数器归零后自动重载,无需 CPU 重配 |
| 中断使能 | 完成中断 / 半传输中断 / 错误中断 | 完成中断 + 半传输中断(乒乓切换) | 半传输中断用于切换缓冲区,避免数据覆盖 |
实战代码片段: 以下为 STM32 HAL 库中配置 DMA 用于 RAW 数据采集的典型初始化序列(外设到内存,循环模式):
// DMA 句柄定义
DMA_HandleTypeDef hdma_raw_rx;
// 配置 DMA 通道
hdma_raw_rx.Instance = DMA1_Channel1;
hdma_raw_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到内存
hdma_raw_rx.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址固定
hdma_raw_rx.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增
hdma_raw_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 16-bit
hdma_raw_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_raw_rx.Init.Mode = DMA_CIRCULAR; // 循环模式
hdma_raw_rx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_raw_rx);
// 链接 DMA 到外设(如 ADC 或 DCMI)
__HAL_LINKDMA(&hraw_sensor, hdma_rx, hdma_raw_rx);