从 DREQ 到中断:RP2040 DMA 的 “快递系统” 全解析

DMA

内核的本职是 “运算、处理任务”,如果让它手动搬数据(比如从传感器读数据存到内存),会浪费算力(搬数据时没法干别的);而 DMA 是 “专职搬运工具”,能高效完成批量传输,让内核可以去处理其他任务,甚至进入低功耗休眠,既提升效率又节省电量。

DMA 直接存储器存取控制器与总线结构有单独的读和写主连接,并可以代替处理器执行批量数据传输。这使得处理器可以自由地处理其他任务,或进入低功耗睡眠状态。

DMA 单独读写最多可以操控 32 位数据,具有 12 个独立的通道,每个通道都监督一系列总线传输。

DMA 通常用于以下三种情况的数据传输:

  • ​内存到外设:​当需要更多数据时,外设向 DMA 发出信号。DMA 从 RAM 或 flash 中的数组中读取数据,并写入外围设备的数据 FIFO;

  • ​外设到内存:​当一个外围设备在接收到外部的传感器/通信接口的数据时,它就会向 DMA 发出信号。DMA 从外围设备的数据 FIFO 中读取这些数据,并将其写入 RAM 中的一个数组;

  • ​内存到内存:​DMA 在 RAM 中的两个缓冲区之间传输数据。
    c3db2062-cfa6-4314-9cf1-34c7ee145753.png
    图 2.17 RP2040 DMA 控制器架构框图
    架构图是 DMA 的 “内部工作流程”,每个模块的作用如下:

  • ​AHB-lite Read/Write Master 总线:​是 DMA 连接到系统总线(AHB-lite)的 “接口:

    • Read Master:负责从系统(内存 / 外设)读数据;
    • Write Master:负责向系统(内存 / 外设)写数据。
  • ​Read/Write Address FIFO:​FIFO 是 “先进先出的缓冲区”,这里存的是要读取 / 写入的地址(比如从内存的 0x20000000 开始传输,地址会存在这里)。它可以地址请求,避免地址生成和数据传输 “节奏不匹配” 导致卡顿。

  • ​Transfer Data FIFO:​它存的是正在传输的数据,同样是缓冲作用,让 “地址请求” 和 “数据搬运” 的节奏对齐,保证传输流畅。

  • ​Address Generator:​DMA 的 “地址自动计算器”:比如要从内存的 0x20000000 开始,连续传 100 个字节,它会自动生成 0x20000000、0x20000001… 这些连续地址,不用内核手动算地址。

  • ​Control/Status Registers:​是 DMA 的控制中心 + 状态显示器:

    • 控制:软件通过这里配置 DMA(比如传输方向是 “外设 → 内存” 还是 “内存 → 外设”、数据大小是 8/16/32 位、要传多少数据);
    • 状态:软件可以在这里查看传输进度(比如传了多少、有没有传完、有没有出错)。
  • ​AHB-lite Slave Interface:​是 “内核配置 DMA 的入口”:内核通过这个接口,向 Control/Status Registers 写入配置参数,告诉 DMA“要搬什么数据、怎么搬”。

接下来,我们以 “外设 → 内存” 传输(比如传感器数据存到内存)为例:

  1. 内核通过​AHB-lite Slave Interface​,给 Control/Status Registers 写配置(比如源地址是传感器的 FIFO 地址、目标地址是内存数组地址、传输长度是 100 字节);
  2. Address Generator 自动生成要读取的外设地址、要写入的内存地址,分别存到 Read Address FIFO、Write Address FIFO;
  3. AHB-lite Read Master 从外设读数据,放到 Transfer Data FIFO 缓冲;
  4. AHB-lite Write Master 从 Transfer Data FIFO 取数据,写到内存的目标地址;

整个过程中,内核不用参与,完全由 DMA 自动完成。

DMA的每个通道都有对应状态寄存器,软件可以通过它来编程和监控该通道数据读写的进度。 我们把每个 DMA 通道想象成一个独立的快递小哥,而状态寄存器就是这个小哥的 “任务清单 + 工作日志本”:

  • 编程(写寄存器) ​:你(软件)在小哥出发前,把任务写在日志本上 —— 比如 “从内存的 A 位置搬 100 个数据到 UART 外设”“搬数据时要听 UART 的 DREQ 信号指挥”,这就是给 DMA 通道配置任务;
  • 监控进度(读寄存器):小哥干活时,会实时在日志本上记录进度 ——“已搬了 50 个数据”“任务完成”“搬数据时出错了”,你(软件)随时看日志本,就能知道这个 DMA 通道的工作状态,不用一直盯着它干活。

DMA 传输数据的大小可以是 32、16 或 8 位。每个通道配置一次:源传输大小和目标传输大小相同:

  • 这是规定了 DMA “搬数据的打包方式”——DMA 每次搬数据时,可选择搬​1 个字节(8 位)、2 个字节(16 位)或 4 个字节(32 位)(相当于快递小哥一次搬 1 个包裹、2 个包裹或 4 个包裹);
  • 源和目标大小必须相同​:意思是 “从哪里搬” 和 “搬到哪里去” 的打包方式要一致 —— 不能从内存里一次搬 4 个字节(32 位),却往外设里一次只写 1 个字节(8 位),否则会出现数据错乱(就像小哥一次抱 4 个包裹,却只能往快递柜里塞 1 个,剩下的会掉地上);
  • 每个通道配置一次​:一个 DMA 通道一旦定了用 32 位传输,那这次任务全程都用 32 位,不用中途改;要换方式的话,得重新配置这个通道。

DMA 不同通道之间可以相互组合,例如一个通道可以配置另一个通道,从内存中的一系列控制块加载配置数据,然后当需要重新配置时,第二个通道可以通过 CHAIN_TO 选项调用到第一通道:

  • 这是 DMA 通道的 “接力干活” 或 “互相配合” 功能,让多个 DMA 通道能协同完成复杂任务,不用软件(内核)中途插手;
  • 假设你有两个 DMA 通道,通道 1 是 “配置员”,通道 2 是 “搬运工”:
    • 你先在内存里存好​多套搬运任务的配置​(比如 “任务 1:搬 100 个 32 位数据到 UART0”“任务 2:搬 200 个 8 位数据到 SPI0”“任务 3:把内存 A 的数据复制到内存 B”),这些配置叫 “控制块”。
    • 你给通道 2 配置初始任务:“先找通道 1,让它从内存里读第一套控制块(任务 1),然后按这个控制块干活”。
    • 当通道 2 完成任务 1 后,不需要软件(内核)来重新配置,它可以通过​CHAIN_TO 选项​(相当于 “呼叫接力”),让通道 1 再从内存里读第二套控制块(任务 2),然后通道 2 继续干任务 2;以此类推,直到所有控制块的任务都完成。

同时 DMA 也可以和其他外设配合使用,外设和 DMA 直接通过 DREQs 通道传输,所谓 DREQ,就是外设给 DMA 发的“暗号”,告诉 DMA“我准备好收 / 发数据了,你可以开始传了”:

  • 比如 UART 要发数据时:当 UART 的发送 FIFO 空了,它会发 DREQ 信号给 DMA,DMA 收到后就把内存里的数据传到 UART 的 FIFO;
  • 比如 UART 要收数据时:当 UART 的接收 FIFO 满了,它发 DREQ 给 DMA,DMA 就把 FIFO 里的数据取到内存。

不同外设和 DREQ 通道对应关系可以查看 DREQ 表,它是“外设功能 →DREQ 通道” 的对应清单,每个 DREQ 编号对应一个外设的收 / 发功能:
6194e007-ba3b-403c-8017-a45f4f493c57.png
图 2.18 RP2040 外设 DREQ 通道与 DMA 映射表
以上表为例,我们可以看到:

  • DREQ20 对应 “UART0_TX”(UART0 的发送请求);
  • DREQ17 对应 “SPI0_RX”(SPI0 的接收请求)。

DMA 也可以和中断配合使用,当 DMA 中某个通道数据读写完成后可以触发中断,使得程序进行对应操作(比如数据传完了,内核去分析这些数据):

  • 咱们把 DMA 通道比作​负责不同搬运任务的快递小哥​,内核就是公司里的 “老板”;
    • 之前说过,小哥干活的时候,老板可以摸鱼(处理其他任务)或者睡觉(低功耗休眠),不用盯着小哥;
    • 当小哥把一批数据搬完了(比如把传感器的 100 个数据全存到内存里了),他不能直接跑去找老板 —— 而是会按一个 “紧急呼叫铃”,这个​呼叫铃就是中断​;
    • 老板听到铃响,就知道 “某个小哥的活干完了”,然后放下手里的事,去做后续工作(比如分析刚搬来的传感器数据、把数据再传给 U 盘、清空内存准备下一次搬运)。
  • 简单说,​DMA 完成任务后触发中断,是给内核发 “完工通知”,让内核能及时处理后续任务,不用一直轮询 “DMA 干完没?干完没?”,既省了内核的精力,又保证了任务的及时性。

RP2040 DMA 提供了两个系统 IRQs,具有独立的掩蔽和状态寄存器(例如,INTE0、INTE1)。任何通道中断请求的组合都可以被路由到任何一个系统 IRQ:

  • 先解释这段话里的关键名词,再用例子串起来:
    • 系统 IRQs​:可以理解成公司里的​两个 “总客服热线”(IRQ0 和 IRQ1,对应 INTE0、INTE1),所有 DMA 小哥的 “完工通知”(中断请求),都要通过这两个热线打给老板(内核)。
    • 掩蔽寄存器​:“掩蔽” 就是 “屏蔽、静音” 的意思,这个寄存器相当于热线的 “静音开关”—— 比如你可以设置 “让小哥 1 的通知打不进热线 0”,或者 “让热线 1 暂时静音,所有通知都走热线 0”,避免不重要的通知打扰老板。
    • 状态寄存器​:相当于热线的 “来电记录本”,里面会记着 “是小哥 3 的通知打进来了”“小哥 7 的任务完工了”,老板看到记录本,就知道该处理哪个小哥的后续任务。
    • 路由​:就是 “分配、定向” 的意思 —— 你可以随便安排哪个小哥的通知走哪个热线。
  • RP2040 的 DMA 有 12 个通道(12 个小哥),但只留了 2 个客服热线(两个系统 IRQs)。你可以这样配置:
    • 把​小哥 1(传感器数据搬运)、小哥 2(UART 数据传输)的通知,都路由到热线 0(INTE0)​;
    • 把​小哥 3(内存复制)、小哥 4(SPI 数据传输)的通知,都路由到热线 1(INTE1)​;
    • 甚至可以把所有小哥的通知都路由到同一个热线,或者让某几个小哥的通知同时走两个热线;
    • 如果你觉得小哥 3 的任务不重要,还能通过掩蔽寄存器,把小哥 3 的通知屏蔽掉,他就算完工了,也打不进热线,老板不会收到他的通知。
  • 这种设计的好处是:​灵活管理多个 DMA 通道的中断,避免中断信号混乱,同时让内核可以按热线的优先级处理不同类型的 DMA 任务​(比如把紧急的任务路由到优先级高的热线)。
posted @ 2026-07-28 18:57  FreakStudio  阅读(4)  评论(0)    收藏  举报