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

从 DREQ 到中断:RP2040 DMA 的 “快递系统” 全解析 DMA内核的本职是 “运算、处理任务”如果让它手动搬数据比如从传感器读数据存到内存会浪费算力搬数据时没法干别的而 DMA 是 “专职搬运工具”能高效完成批量传输让内核可以去处理其他任务甚至进入低功耗休眠既提升效率又节省电量。DMA 直接存储器存取控制器与总线结构有单独的读和写主连接并可以代替处理器执行批量数据传输。这使得处理器可以自由地处理其他任务或进入低功耗睡眠状态。DMA 单独读写最多可以操控 32 位数据具有 12 个独立的通道每个通道都监督一系列总线传输。DMA 通常用于以下三种情况的数据传输​内存到外设​当需要更多数据时外设向 DMA 发出信号。DMA 从 RAM 或 flash 中的数组中读取数据并写入外围设备的数据 FIFO​外设到内存​当一个外围设备在接收到外部的传感器/通信接口的数据时它就会向 DMA 发出信号。DMA 从外围设备的数据 FIFO 中读取这些数据并将其写入 RAM 中的一个数组​内存到内存​DMA 在 RAM 中的两个缓冲区之间传输数据。图 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“要搬什么数据、怎么搬”。接下来我们以 “外设 → 内存” 传输比如传感器数据存到内存为例内核通过​AHB-lite Slave Interface​给 Control/Status Registers 写配置比如源地址是传感器的 FIFO 地址、目标地址是内存数组地址、传输长度是 100 字节Address Generator自动生成要读取的外设地址、要写入的内存地址分别存到 Read Address FIFO、Write Address FIFOAHB-lite Read Master从外设读数据放到 Transfer Data FIFO 缓冲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 信号给 DMADMA 收到后就把内存里的数据传到 UART 的 FIFO比如 UART 要收数据时当 UART 的接收 FIFO 满了它发 DREQ 给 DMADMA 就把 FIFO 里的数据取到内存。不同外设和 DREQ 通道对应关系可以查看 DREQ 表它是“外设功能 →DREQ 通道” 的对应清单每个 DREQ 编号对应一个外设的收 / 发功能图 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传感器数据搬运、小哥 2UART 数据传输的通知都路由到热线 0INTE0**​把​**小哥 3内存复制、小哥 4SPI 数据传输的通知都路由到热线 1INTE1**​甚至可以把所有小哥的通知都路由到同一个热线或者让某几个小哥的通知同时走两个热线如果你觉得小哥 3 的任务不重要还能通过掩蔽寄存器把小哥 3 的通知屏蔽掉他就算完工了也打不进热线老板不会收到他的通知。这种设计的好处是​灵活管理多个 DMA 通道的中断避免中断信号混乱同时让内核可以按热线的优先级处理不同类型的 DMA 任务​比如把紧急的任务路由到优先级高的热线。