DMA控制器架构:从寄存器到状态机

聊DMA控制器架构,我习惯先从它的“骨架”说起。说白了,一个DMA控制器就是一块专门处理数据传输的微引擎。它不需要CPU插手,自己就能把数据从A搬到B。

我在项目中遇到过不少工程师,总觉得DMA控制器不过是一堆寄存器加一个状态机。其实没那么简单。内部每个模块都有各自的分工,配合不好就会出各种奇怪的问题。

内部组成模块

先来看DMA控制器的“五脏六腑”。一个典型的DMA控制器,至少包含以下几个核心部件:

  • 系统总线接口:负责与系统总线握手,发起读写请求。这是DMA与外界通信的唯一通道。
  • DMA引擎:整个控制器的“大脑”,包含地址发生器、字节计数器和传输逻辑。它决定下一步该做什么。
  • FIFO缓冲:临时存放数据。当源和目的速度不匹配时,FIFO能缓解压力。我见过一次因为FIFO深度不够,导致频繁欠载或溢出,最后传输效率还不如CPU直接搬。
  • 控制/状态寄存器组:软件与硬件交互的窗口。后面会单独讲。
  • 状态机:驱动整个传输流程的小型有限状态机。它会根据当前状态和外部信号,决定下一跳。

重点:DMA引擎里的地址发生器不是简单累加器。它支持递增、递减、固定模式,还能按二维/三维地址跳转。这是实现“分散-聚集”传输的基础。

寄存器组详解

寄存器是DMA控制器的“控制面板”。每个DMA通道至少有一组独立的寄存器。我通常把它们分为四类:

寄存器类型典型名称作用
源地址寄存器SAR存放数据源的起始地址。可配置为内存地址或外设地址。
目的地址寄存器DAR存放数据目的地的起始地址。同样支持多种地址空间。
字节计数器BCR / NDTR记录剩余待传输的字节数。每次传输后递减,到零触发完成信号。
控制/状态寄存器CR / SR控制位包括:传输方向、数据宽度、增量模式、中断使能等。状态位包括:忙、完成、错误等。

避坑指南:我曾经在一个项目中,因为没仔细看寄存器说明,把源地址和目的地址的增量模式配反了。结果数据全被写成同一个地址,找了好久才发现。建议新项目第一次使用DMA时,先验证一遍所有寄存器配置。

状态机:传输的“心脏”

状态机是DMA控制器工作的核心。大多数DMA控制器的状态机都类似,下面是我常用的简化模型:

  • IDLE(空闲):等待启动信号。此时所有寄存器可以配置。
  • PREP(准备):检查源和目的地址有效性,锁存参数。这个状态很短,但很关键。我在调试时发现,如果这个阶段地址校验失败,状态机会直接进入ERROR状态。
  • XFER(传输):实际产生总线请求,传输数据。根据传输模式(单次、突发、行),这个状态会反复进入。
  • DONE(完成):字节计数器归零,产生中断(如果使能)。然后自动回到IDLE。

注意:有些DMA控制器在传输过程中会因总线错误进入ABORT(终止)状态。该状态下寄存器内容可能被清零或锁定,需要软件重新初始化。建议在中断服务程序里增加超时检测,避免死锁。

为什么会需要这么复杂的状态机?想想看,CPU随时可能改变DMA的配置,外设也可能产生暂停信号。没有状态机保证事务原子性,传输逻辑就会乱掉。

工作原理:一条完整的数据流

我们来走一遍完整的传输过程。假设你要把外设FIFO里的数据搬进内存缓冲区:

  1. CPU配置DMA通道:SAR指向外设FIFO地址,DAR指向内存缓冲区,BCR设为1024字节,传输方向设为“外设→内存”,并使能完成中断。
  2. CPU写控制寄存器启动位。
  3. DMA控制器进入PREP状态:检查地址是否对齐(我的经验:很多系统要求源地址对齐数据宽度,否则浪费时间做拆分)。
  4. 进入XFER状态:DMA引擎向总线发出读请求,外设返回数据,存入内部FIFO。
  5. 如果FIFO有数据,DMA继续发写请求,将数据写入内存地址,然后递增目的地址(或保持,取决于配置)。
  6. 每完成一次读写,BCR递减。当BCR减到0,状态机进入DONE。
  7. DMA产生中断,CPU处理完成事件。

这个过程中,DMA引擎不需要CPU参与任何一次总线事务。你想想看,如果这个传输由CPU完成,每次搬一个字都需要取指、解码、访问内存、写回,开销至少是DMA的5倍以上。

内部结构示意图

下面这张图展示了DMA控制器的内部模块和状态机流转关系:

DMA控制器内部架构与状态机 系统总线接口 AHB/APB/AXI DMA引擎 地址发生器 字节计数器 传输逻辑 控制/状态寄存器组 SAR / DAR BCR / CR / SR FIFO缓冲 深度 4~16 字 状态机 IDLE ──→ PREP PREP ──→ XFER XFER ──→ DONE XFER → IDLE (错误) DONE → IDLE IDLE → ERROR 图例说明 蓝色:系统总线接口,负责总线协议 红色:DMA引擎,核心计算单元 绿色:寄存器组,软件配置接口 紫色:FIFO缓冲,数据暂存 橙色:状态机,控制传输流程 实线:数据流 / 箭头:控制流 虚线:间接控制(地址/参数传递)

实际项目中的体会

讲几个我踩过的坑。

  • FIFO深度不足:某次做高速ADC采集,DMA FIFO只有4个字,而总线延迟较大,导致频繁欠载。后来把DMA配置改为“突发传输长度 = FIFO深度的一半”,问题解决。
  • 状态机未考虑总线错误:有一次外设掉电,DMA还在不停请求总线,导致系统挂死。后来在状态机里增加了超时复位机制,并在中断中检测ABORT状态。
  • 寄存器访问时序:某些DMA控制器的控制寄存器在传输过程中写入无效。我习惯在空闲状态才修改配置,或者先停掉通道再更新。

一点建议:如果你在调试DMA传输时发现数据错位或丢失,优先检查地址对齐和FIFO深度匹配。这两个点出问题的概率最高。

一个设计良好的DMA控制器,能让你的系统在CPU零干预的情况下,以总线带宽的90%以上搬运数据。但所有前提是——你真正理解了它的内部架构。接下来,你会看到这些模块如何协同实现各种高级传输模式。