【万字长文】树莓派 Pico DMA 底层全解析!从寄存器到链式传输,一次讲透!
1.树莓派 Pico 上的 DMA 单元简介
RP2040 的 DMA 控制器具有独立的读写主控连接,它们分别与芯片的总线结构相连接,允许 DMA 进行并行的读写操作。具体架构特点如下:

- 读写主控器:DMA 的读主控器可以每个时钟周期从某个地址读取数据,写主控器可以在另一个地址写入数据。这意味着 DMA 在每个时钟周期内能够同时执行一次读访问和一次写访问,每次最多传输 32 位数据。
- 地址生成器:地址生成器负责生成匹配的读地址和写地址,并通过地址 FIFO 提供给读写主控器,FIFO 在 DMA 架构中起到了关键的缓冲作用,确保数据传输的顺畅。
- 独立通道:RP2040 的 DMA 控制器支持 12 个独立的通道。这些通道可以独立管理一系列的总线传输任务,允许多个传输序列同时进行,每个通道都有自己的控制和状态寄存器(CSRs),供软件进行配置和监控。
- 多通道并发:当多个通道同时活跃时,DMA 使用循环调度(round-robin)策略在请求数据传输的所有通道之间公平分配带宽,确保每个通道的资源使用均衡。
- RP2040 的DMA通道支持三种传输:内存到外设、外设到内存和内存到内存。
每个 DMA 通道都有自己的控制和状态寄存器,软件可以通过这些寄存器对通道进行配置,包括以下参数:
- 传输方向:配置数据传输的方向,可以是从内存到外设、从外设到内存,或内存到内存传输。
- 传输数据大小:传输的数据大小可以是 32 位、16 位或 8 位。每个通道的源数据大小和目标数据大小必须相同。
- 地址更新:DMA 会根据配置自动更新源地址和目标地址,确保数据按顺序从源传输到目标。
- 传输方式:DMA 支持单次传输和循环传输模式。循环模式下,传输可以持续进行,不需要重新配置。
- 链式传输(Chaining):一个 DMA 通道可以配置另一个通道来执行后续传输任务,通过
CHAIN_TO选项实现通道之间的回调与协同工作,使得 DMA 控制器能够更加自主地执行复杂的任务。

CHAIN_TO 数据位在 DMA 通道的触发控制寄存器中
2.DMA 通道配置
每个 DMA 通道都有四个主要的控制和状态寄存器(CSR),用于管理数据传输的过程:
- READ_ADDR(读地址寄存器):这是读取操作的地址指针,每当一次读操作完成后,寄存器会自动更新到下一个将要读取的地址

- WRITE_ADDR(写地址寄存器):这是写入操作的地址指针,每次写操作完成后,该寄存器也会自动更新到下一个要写入的地址

- TRANS_COUNT(传输计数器):这个寄存器用于设置 DMA 通道在一次传输序列中执行的传输次数(每次传输可以是 1 字节、2 字节或 4 字节);当传输正在进行时,读取这个寄存器会显示剩余的传输次数,随着每次写操作完成,寄存器中的值会自动更新

- CTRL(控制触发寄存器):这个寄存器控制 DMA 通道的启用、配置以及状态监控,主要控制位包括 EN(启用位)、BUSY(忙标志)、TREQ_SEL(传输请求选择)、CHAIN_TO(链式触发)、DATA_SIZE(数据大小)、地址环绕(RING_SIZE),除此之外还有一些错误指示标志位和优先级设置数据位

2.1 读地址和写地址寄存器
READ_ADDR 和 WRITE_ADDR 是用于存储 DMA 通道下次将从中读取数据或写入数据的内存地址的寄存器。这些寄存器在每次读写操作后会自动更新,且会根据配置在 CTRL 寄存器中的传输大小设置进行递增。

通过配置 CTRL 寄存器的 INCR_WRITE 数据位和 INCR_READ 数据位可以使能或失能读写地址自动更新功能
具体地,递增步长可以是 1、2 或 4 字节,取决于传输大小的设置。需要注意的是,READ_ADDR寄存器和 WRITE_ADDR寄存器必须始终与当前的传输大小(CTRL.DATA_SIZE 中指定的大小)对齐:如果传输大小为字节(1 字节)、半字(2 字节)或字(4 字节),则相应的地址必须对齐到 1、2 或 4 字节的边界。

通过配置 CTRL 寄存器的 DATA_SIZE 数据位可以配置数据传输大小
通常情况下,每当新的传输序列开始时,软件需要重新为这些寄存器编程,设置新的起始地址。但也有不需要地址递增的场景:
- 地址不递增的场景:如果该地址是一个外设的 FIFO(先进先出)缓冲区地址,而下一个传输序列也需要从该地址读写数据,那么不需要再次为寄存器编程,即保持原有的地址值
- 连续内存传输的场景:当 DMA 被配置为从内存中传输多个连续的数据块时,每个块的数据通常存储在内存的不同地址中,但这些块可能是预先定义好的一组内存缓冲区。为了高效处理这些数据块,DMA 的地址寄存器会在每次完成对一个数据块的传输后,自动递增到下一个缓冲区的起始地址,而不需要软件手动干预
此时,DMA 将使用当前的值作为下一次传输的起始地址。
2.2 传输计数寄存器
TRANS_COUNT 寄存器负责管理当前传输序列中剩余的传输次数,并在每次传输开始时决定传输的总次数:
- 读取 TRANS_COUNT:在传输过程中,读取
TRANS_COUNT寄存器会返回当前传输序列中剩余的传输次数,随着传输的进行,该值会自动递减,表示还剩下多少次传输; - 写入 TRANS_COUNT:写入
TRANS_COUNT寄存器用于设置下一次传输序列的传输次数。当启动一个新的传输序列时,刚刚写入的值将被加载到传输计数器中,并开始随着每次传输递减; - 传输计数器的重载机制:每次开始新的传输序列时,最近一次写入
TRANS_COUNT的值会被复制到活动的传输计数器中,随着传输的进展,该计数器的值将递减,直到传输完成;如果需要调试,最近一次写入的值可以从DBG_TCR(即TRANS_COUNT重载值寄存器)中读取。

DBG_TCR 寄存器中 Read 数据位可以读取 DREQ 计数器的值
- 自动重复传输次数:如果 DMA 通道在多次触发之间没有重新写入
TRANS_COUNT,它将在每次触发时执行相同数量的传输;比如在通道链式传输(即多个 DMA 通道依次工作,互相触发)中,假设有一个 DMA 通道专门负责将某个固定大小的控制块加载到另一个 DMA 通道的控制寄存器(CSR)中,在这种场景下,软件只需要一次性设置TRANS_COUNT,之后每次该通道被链式触发时,都会自动使用这个已经设定好的传输次数,不需要再重新设定TRANS_COUNT的值; - 自定义传输次数:在启动每个传输序列之前,也可以选择向
TRANS_COUNT寄存器写入新的值,以动态调整每次传输的次数。- 如果你需要每次传输的次数不同,那么在每次启动传输序列之前,你可以向
TRANS_COUNT寄存器写入新的值;写入该寄存器后,DMA 通道会立即根据刚刚写入的值来启动,并按照这个新的传输次数执行,而不会使用以前保存的重载值 - 如果
TRANS_COUNT寄存器本身被用作 DMA 通道的触发条件(即每次向TRANS_COUNT写入值时,DMA 通道都会被自动触发),那么一旦向它写入新值,通道会立即开始新的传输操作,使用刚写入的次数进行传输,而不是使用之前设定的值
- 如果你需要每次传输的次数不同,那么在每次启动传输序列之前,你可以向
这里,需要注意的是,TRANS_COUNT 表示要执行的传输次数,总传输的字节数由 TRANS_COUNT 和每次传输的数据大小(由 CTRL.DATA_SIZE 寄存器定义)共同决定:
- 如果 DATA_SIZE 设置为每次传输 1 字节,传输的总字节数就是 TRANS_COUNT
- 如果 DATA_SIZE 设置为每次传输 2 字节(半字),总字节数为 TRANS_COUNT × 2
- 如果 DATA_SIZE 设置为每次传输 4 字节(字),总字节数为 TRANS_COUNT × 4
2.3 控制触发寄存器
CTRL 寄存器负责配置和管理 DMA 通道的各种行为,与其他寄存器相比,CTRL 寄存器包含了更多的数据位,用于对 DMA 通道的各个方面进行更加精确细致的控制,例如配置数据传输的大小、读写地址的递增方式、链式传输、外设数据请求信号的选择以及传输状态的监控和错误检测:
-
配置传输数据大小 (CTRL.DATA_SIZE):该数据位用于配置此 DMA 通道的数据传输大小,传输大小可以设置为 1 字节、2 字节或 4 字节;该数据位控制每次数据读取和写入的大小,读取和写入的数据块大小是相同的。
-
配置读写地址的递增方式:
CTRL.INCR_WRITE和CTRL.INCR_READ数据位控制在每次读写操作后,写地址(WRITE_ADDR)和读地址(READ_ADDR)是否递增;递增的幅度应当根据 CTRL.DATA_SIZE 的配置来决定。CTRL.RING_SEL和CTRL.RING_SIZE数据位与环形传输相关:
![10.png]()
- 环形传输意味着地址指针可以在预定义的边界处回绕到缓冲区的起点,通常用于外设的 FIFO(先进先出)缓冲区相关的数据读写操作
CTRL.RING_SEL选择将 RING_SIZE 应用于读地址还是写地址CTRL.RING_SIZE用于设置环形传输数据大小,可以设置 2 到 32768 字节的环形缓冲区大小,需要注意的,这里传入的值是缓冲区字节数对应数据位的多少,简单来说如果ring_size设置为 8,那么地址寄存器的最低 8 位会变动,这意味着地址会在 256 字节边界上回绕;ring_size 的值应该是 0 到 31 之间的整数
-
配置链式传输 (CTRL.CHAIN_TO):
![11.png]()
CTRL.CHAIN_TO数据位用于选择在当前通道完成传输时触发的另一个通道:当一个 DMA 通道传输完成后,它可以自动触发下一个 DMA 通道继续执行操作。- 这种方式称为链式传输(Channel Chaining),在复杂的传输场景中非常有用,因为它允许多个 DMA 通道按顺序执行,不需要 CPU 干预。
- 如果
CTRL.CHAIN_TO设置为某个通道编号,当当前通道完成时,指定的下一个通道会被自动触发。
-
选择外设数据请求信号 (CTRL.TREQ_SEL):

-
CTRL.TREQ_SEL数据位用于选择一个外设的数据请求信号 (DREQ),以控制 DMA 通道的传输速率。 -
DREQ信号通常由外设产生,用于请求 DMA 从外设传输数据:例如,UART、SPI 或 I2C 等外设可以产生这种请求信号,用于控制 DMA 通道在合适的时机传输数据,确保数据同步。 -
如果不选择外设信号,DMA 将不受外设的数据读写速度的限制,而是按照 DMA 控制器的内部时钟频率进行数据传输。
-
查看通道是否空闲 (CTRL.BUSY):
![13.png]()
CTRL.BUSY数据位用于指示 DMA 通道当前是否正在忙于执行传输操作:- 当该数据位为
1时,表示 DMA 通道正在进行传输操作; - 当该数据位为
0时,表示通道空闲,可以接受新的传输指令
- 当该数据位为
- 通过检查
CTRL.BUSY,软件可以决定是否提交新的传输任务或等待当前任务完成 - 要提前终止传输序列并清除
BUSY标志位,可以使用CHAN_ABORT寄存器
-
检测总线错误 (CTRL.AHB_ERROR, CTRL.READ_ERROR, CTRL.WRITE_ERROR):当以下这些错误数据位中的任意一个被置位时,表示 DMA 传输过程中发生了问题,需要通过软件进行调试和错误处理:

CTRL.AHB_ERROR数据位表示 DMA 通道在传输过程中是否发生了 AHB 错误,这可能由于访问了无效地址或不支持的外设地址空间导致CTRL.READ_ERROR数据位表示在 DMA 读取过程中是否发生了错误,例如由于访问了无效的内存位置或外设地址CTRL.WRITE_ERROR数据位表示在 DMA 写入过程中是否发生了错误,例如写入了无效的内存位置或外设地址
3.启动 DMA 通道进行数据传输
启动 DMA 通道有三种方式:
- 写入通道控制触发寄存器:通过向特定的通道控制触发寄存器写入值来启动通道,这种方式简单且高效,尤其适合在中断服务程序(ISR)中配置和启动通道

- 链式触发(来自另一个通道的触发):当一个通道完成传输后,如果它的控制状态寄存器的
CHAIN_TO数据位配置了链式触发,可以触发另一个通道启动; - 多通道触发寄存器(MULTI_CHAN_TRIGGER):使用多通道触发寄存器可以一次性启动多个通道,这种方式适合需要同时启动多个数据传输的场景。

在启动DMA通道时,需要注意,如果尝试触发一个已经在运行的通道,不会产生任何效果。
3.1 寄存器别名和触发机制
每个 DMA 通道有四个主要的控制/状态寄存器(Control and Status Registers, CSR),分别是:
READ_ADDR:读取地址寄存器WRITE_ADDR:写入地址寄存器TRANS_COUNT:传输计数寄存器CTRL_TRIG:控制触发寄存器
这些寄存器在内存中有四组不同的别名(Aliases),访问每组别名都会访问相同的物理寄存器,但以不同的顺序排列。

DMA 通道 1 相关的控制/状态寄存器以及四个别名
下表中显示了这些寄存器别名的地址偏移和排列顺序:

每个别名的最后一个寄存器(偏移量 +0xC)是触发寄存器,写入该寄存器会启动 DMA 通道。
使用别名的主要好处是你可以只写入关键寄存器就能启动 DMA 通道,而不需要一次性写入所有配置寄存器:
- 当数据从内存读取并传输到外设时:可以只写入
READ_ADDR_TRIG(读取地址的触发寄存器),这个操作会同时配置读取地址并启动 DMA 通道 - 当数据从外设传输到内存时:可以只写入
WRITE_ADDR_TRIG(写入地址的触发寄存器),这个操作会同时配置写入地址并启动 DMA 通道
在 DMA 通道之间进行链式操作时(即一个通道完成任务后启动另一个通道),别名使得可以通过更紧凑的控制块列表来实现传输控制;使用别名,可以避免重复写入所有的寄存器,只需写入那些发生变化的寄存器:
-
(WRITE_ADDR, TRANS_COUNT_TRIG): 这个组合用于从外设分散数据到内存的操作(scatter),只需写入写入地址和传输计数的触发寄存器即可启动通道;所谓数据分散就是将外设的数据分散写入到多个固定大小的内存块。 -
(TRANS_COUNT, READ_ADDR_TRIG): 这个组合用于从内存中聚集数据到外设(gather),同样可以减少写入的寄存器数量,只写入传输计数和读取地址的触发寄存器;所谓数据聚集就是将多个固定大小的内存块聚集到一个外设的相关寄存器(例如串口)。
在以下情况中,DMA 通道无法启动:
- 通道已禁用:如果通道通过
CTRL.EN数据位被禁用,则写入触发寄存器不会启动通道 - 通道已在运行:如果通道已经在执行传输,触发操作不会对它产生影响
- 写入 0:当你向触发寄存器写入 0 时,通道不会被启动
3.2DMA 通道的链式触发
链式触发是一种机制,允许一个 DMA 通道在完成传输后,立即触发另一个通道。通过这种方式,可以实现两个或多个 DMA 通道之间的协同工作,常见的应用场景包括让一个通道在完成传输后立即配置并启动另一个通道,甚至可以通过链式触发来重新配置第一个通道。
链式触发通过 DMA 通道的 CTRL 寄存器中的 CHAIN_TO 数据位进行配置,该数据位是一个 4 位值,用于指定在当前通道完成传输后需要启动的目标通道。

CTRL 寄存器中的 CHAIN_TO 数据位
需要注意的是,通道不能触发自己,如果 CHAIN_TO 设置为通道本身的索引,则不会发生链式触发;链式触发与来自其他源(如触发寄存器)的触发操作行为相同,它会导致目标通道的传输计数器(TRANS_COUNT)重新加载。
链式触发有多种应用,以下是两个典型的场景:
- 通道重新配置:一个通道完成任务后,让另一个通道重新配置并启动第一个通道,这种方法常用于需要多个传输任务依次完成的场景
- 例如通道 A 负责从内存传输数据到通道 B 的控制寄存器(包括 B 的触发寄存器),通道 B 完成任务后,通过链式触发重新启动通道 A,从而实现多个任务的连续执行。
- 在这个过程中,控制块(control block)可以用于存储 DMA 通道的配置,并且通过控制块的链式结构,使得配置和触发过程更加紧凑和高效:控制块是用于存储 DMA 通道的配置信息的结构,它包含了每个通道所需的关键信息,如源地址、目标地址、传输计数和控制寄存器的配置。
- 乒乓操作:通道 A 完成任务后触发通道 B,通道 B 完成任务后又触发通道 A。这样两个通道轮流执行任务,形成一种“乒乓”效应;在这种配置下,CPU 可以在通道完成任务后进行重新配置,但链式触发的通道已经预先配置好,可以立即启动,从而实现任务的流水线处理,这个过程极大地提高了短传输任务的性能,因为通道不必等待 CPU 的重新配置。
3.3 空触发和链中断
在 DMA 控制中,空触发(Null Trigger) 是指向触发寄存器写入全 0 值的操作,当触发寄存器被写入全 0 值时,通道不会启动;这种操作被称为“空触发”,主要有两个用途:
- 停止控制块序列的执行:在控制块链的末尾,可以通过附加一个全零的控制块来结束传输过程;当 DMA 通道读取到全零控制块时,由于这是一个空触发,它不会继续启动下一个控制块,从而导致传输停止,这是实现自动停止的一种方法
- 减少中断数量:默认情况下,每当一个通道完成传输序列时,它会产生一个中断,除非该通道的中断被屏蔽(通过
INTE0或INTE1屏蔽中断);但是,当控制块序列非常长时,中断的频率可能会过高,这会给处理器带来不必要的负担,尤其是当处理器在控制块序列进行期间不需要频繁干预时,通过空触发,可以减少不必要的中断,只有在控制块链结束时才触发中断
在 DMA 通道的 CTRL 寄存器中,有一个名为 IRQ_QUIET 的数据位,它的默认值是 0,即每次完成一个传输序列时都会产生中断;如果将其设置为 1,则通道只在收到空触发时产生中断,而不会在其他时间产生中断:

CTRL 寄存器的 IRQ_QUIET 数据位
4.DMA 数据请求
在嵌入式系统中,外设(例如 UART、SPI、ADC 等)以各自的速率生成或消耗数据,而这些速率通常是不固定的;为确保数据传输稳定进行并避免丢失或损坏,DMA(直接存储器访问)控制器引入了一个机制,叫做 DREQ(传输请求,Data Request)。
DREQ 充当外设和 DMA 之间的通信通道,用于调节传输速度,使 DMA 的数据传输与外设的速率保持同步。其主要作用是让外设告诉 DMA,当前能否继续传输数据,或应暂时停止:
- 外设需要更多数据时,它通过发出 DREQ 信号让 DMA 知道可以继续传输数据到外设。
- 外设正在处理数据或满载时,它不会发出 DREQ 信号,这样 DMA 会暂停数据传输,避免数据溢出
这种机制可以确保外设不会因为传输过快而导致数据丢失,或者因为传输过慢而导致数据堆积。
在 DMA 的 CTRL.TREQ_SEL 数据位中,可以配置 DMA 如何选择传输请求信号来源:
- 外部 DREQ 信号:
TREQ_SEL数据位选择某个外设的 DREQ 信号,DMA 会根据该外设的传输请求信号,调节数据的传输速率,确保和外设的数据吞吐量匹配;例如,当 SPI 外设准备好接收数据时,它会发出 DREQ,通知 DMA 继续传输数据 - 内部定时器:TREQ_SEL 还可以选择 DMA 内部的节奏定时器(pacing timer),在这种模式下,DMA 通过定时器调节传输速率,而不是依赖外设的 DREQ 信号,这样可以实现一些固定速率的传输需求,比如音频数据流的传输
- 不使用 TREQ(无 DREQ):如果 TREQ_SEL 设置为“不使用 TREQ”(即无 DREQ),DMA 将不依赖任何外部或内部节奏信号,而是以最快速度传输数据,这种模式通常用于内存到内存的传输,因为内存操作的速度通常足够快,不需要进行速率控制
以下是系统 DREQ 表,它列出了各种外设的 DREQ 通道号和对应的功能:

其中:
- DREQ_PIO0_TX0 到 DREQ_PIO0_RX3:这些 DREQ 通道与 PIO(可编程输入输出) 外设相关,
TX表示数据传输方向是输出,RX表示数据传输方向是输入 - DREQ_PIO1_TX0 到 DREQ_PIO1_RX3:这些 DREQ 通道与 PIO1 外设相关,功能类似于 PIO0
- DREQ_UART0_TX 和 DREQ_UART0_RX:这些 DREQ 通道分别用于 UART0 外设的发送(TX)和接收(RX)数据请求
- DREQ_UART1_TX 和 DREQ_UART1_RX:类似于 UART0 的 DREQ 通道,用于 UART1 外设的发送和接收数据请求
- DREQ_SPI0_TX 和 DREQ_SPI0_RX:这些 DREQ 通道分别用于 SPI0 外设的数据传输请求,
TX表示数据传输方向是输出,RX表示数据传输方向是输入 - DREQ_SPI1_TX 和 DREQ_SPI1_RX:类似于 SPI0 的 DREQ 通道,用于 SPI1 外设的数据传输请求
- DREQ_PWM_WRAP0 到 DREQ_PWM_WRAP7:这些 DREQ 通道与 PWM(脉宽调制) 外设相关,主要用于处理 PWM 的周期结束请求
- DREQ_I2C0_TX 和 DREQ_I2C0_RX:这些 DREQ 通道分别用于 I2C0 外设的数据传输请求
- DREQ_I2C1_TX 和 DREQ_I2C1_RX:类似于 I2C0 的 DREQ 通道,用于 I2C1 外设的数据传输请求
- DREQ_ADC:这个 DREQ 通道与 ADC 外设相关
- DREQ_XIP_STREAM, DREQ_XIP_SSITX 和 DREQ_XIP_SSIRX:这些 DREQ 通道与 XIP(执行在内存中) 外设相关,用于处理流数据和 SPI 传输请求
5.DMA 通道中断
每个 DMA 通道可以生成中断,中断请求会在以下情况下触发:
- 传输序列完成时:当通道完成一个传输序列时,它会产生一个中断请求,这种中断在
CTRL.IRQ_QUIET被禁用的情况下触发 - 接收到空触发(Null Trigger)时:当通道接收到空触发信号,并且
CTRL.IRQ_QUIET被启用时,也会产生中断请求

CTRL 寄存器的 IRQ_QUIET 数据位
RP2040 的 DMA 提供了两个系统中断(IRQ),分别具有独立的掩码和状态寄存器(例如 INTE0 和 INTE1),中断请求可以被路由到这两个系统中断中的任意一个。我们可以根据需要将不同通道的中断请求路由到不同的系统中断,例如,某些时间要求严格的通道可以被设置为具有更高的优先级;在多处理器系统中,可以将不同通道的中断独立地路由到不同的处理核心,从而实现更高效的中断处理。
我们可以通过查看 INTR 寄存器得到 DMA 通道的原始中断状态,16 个数据位提供了 DMA 通道 0 到 15 的原始中断状态:

我们我们可以通过 INTS 寄存器查看每个通道的掩码中断状态位,要清除中断,可以向 INTS 写入一个位掩码:

我们可以通过配置 INTE 寄存器按通道掩码中断,允许用户选择哪些通道的中断是有效的,用户可以在这些寄存器中设置或清除位,以启用或禁用特定通道的中断请求:


我们可以通过配置 INTF 寄存器强制触发 DMA 中断:

6.DMA 的其他特性
6.1 节拍定时器
在 RP2040 微控制器中,节拍定时器用于控制数据传输的节奏,通过在每个 clk_sys 时钟周期产生数据请求(TREQ),而不依赖外部外设的 DREQ 信号,这些定时器使用分数(X/Y)分频器来设置请求的生成频率。

每个定时器的生成速率最高为每个 sys_clk 周期 1 次请求,TREQ 的生成速率为 ((X/Y) * sys_clk)。

6.2CRC 校验
DMA 功能中包括一个 CRC 计算器,它可以在数据通过 DMA 通道的数据 FIFO 时计算校验和;这个特性是被动的,即它不会修改数据,只是观察数据并计算校验和。
我们可以通过 SNIFF_CTRL 寄存器配置 CRC 计算的具体参数,配置 SNIFF_DATA 寄存器来设置数据处理的具体细节。


这里,DMA 支持 CRC-32、CRC-16-CCITT、简单求和和偶检验等校验类型。
6.3 通道中止
在 RP2040 微控制器中,DMA 通道可能会进入无法恢复的状态,例如,当通道被指示传输的数据量超过外设的请求能力时,它将无法完成传输。在这种情况下,仅仅清除 CTRL.EN 位只是暂停了通道,并不能解决问题。为了在出现此类问题时能够恢复而不必对整个 DMA 块进行硬重置,需要使用通道中止机制。
我们可以通过配置 CHAN_ABORT 寄存器来强制通道提前完成,写入 1 到特定通道的数据位会清除该通道的传输计数器,并将通道设置为非活动状态,这种方式允许在遇到无法恢复的错误时将通道强制置于安全状态,从而避免对整个 DMA 块的重置。

这里需要注意的是,由于 RP2040 芯片的特性,当中止一个正在进行的 DMA 通道时(即该通道没有因不活动的 DREQ 而被挂起),可能会导致完成中断请求(IRQ)被触发。因此,在执行中止操作之前,应先清除通道的中断使能标志,并在中止操作后检查并清除中断。
当触发中止操作时,通道可能会有正在进行的总线传输。这些传输是无法撤销的;CTRL.BUSY 标志将保持高电平,直到这些传输完成,通道达到安全状态,通常这个过程只需要几个时钟周期;在通道的 CTRL.BUSY 标志到达低电平之前,不应重新启动通道,如果在旧的传输序列仍在进行时启动新的传输序列,会导致不可预测的行为。
6.4 调试
在 RP2040 微控制器中,每个 DMA 通道都配备了调试寄存器,这些寄存器用于显示和调试 DMA 操作的状态;调试寄存器包括 DBG_CTDREQ 和 DBG_TCR,它们提供了有关 DMA 通道当前状态的详细信息,并且可以在需要时用于重置 DMA 通道。
DBG_CTDREQ 寄存器用于显示 DMA 通道当前的 DREQ(传输请求)计数器的值:

DBG_TCR 寄存器用于显示当前传输计数器的值:


RP2040 的 DMA 控制器具有独立的读写主控连接,它们分别与芯片的总线结构相连接,允许 DMA 进行并行的读写操作。具体架构特点如下



浙公网安备 33010602011771号