【保姆级教程】用 MicroPython+DMA 实现 RP2040 内存到内存数据传输

以下代码可以在我们提供的资料包中的 elegance-devkit v1\Demo\66 DMA_MemoryToMemory 文件夹中找到。

在如下代码中,我们通过 DMA 从一个源数组传输数据到目标数组,此时不需要 CPU 参与每个字节的拷贝,减少了 CPU 的工作负荷,提高了传输效率,同时对比了 DMA 传输和数组切片复制的耗时差异。

示例代码如下:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/9/7 上午10:50   
# @Author  : 李清水            
# @File    : main.py       
# @Description : DMA类实验,内存到内存传输

# ======================================== 导入相关模块 =========================================

# 从 rp2 库导入 DMA 控制器
from rp2 import DMA
# 导入 addressof 函数,用于获取数据的内存地址
from uctypes import addressof
# 导入 array 模块,用于创建数组
from array import array
# 导入时间相关模块
import time

# ======================================== 全局变量 ============================================

# 创建一个数组,作为源数据,数据类型为 unsigned 32-bit integers (I),并初始化数据
# 从 0 到 1023,共 1024 个元素
source_data = array("I", range(1024))
# 创建另一个数组,作为目标数据,初始化为空的数据空间,大小与源数据相同
destination_data = array("I", [0] * len(source_data))

# ======================================== 功能函数 ============================================

# ======================================== 自定义类 ============================================

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试信息,表示开始主程序
print("FreakStudio: DMA Memory to Memory Test ")

# 初始化 DMA 对象
dma = DMA()

# 设置DMA控制寄存器,使用pack_ctrl方法封装控制寄存器,size=2表示传输32位长的字
ctrl = dma.pack_ctrl(size=2)

# 配置 DMA 的控制寄存器,设定数据传输模式
dma.config(
    read=addressof(source_data),        # 指定源地址(源数据的内存地址)
    write=addressof(destination_data),  # 指定目的地址(目标数据的内存地址)
    count=len(source_data),             # 指定要传输的数据长度(单位为元素数量)
    ctrl=ctrl,      # 控制寄存器配置
    trigger=False   # 不立即启动传输
)

# ========================================  主程序  ==========================================

# 打印调试数据,启动 DMA 传输
print("Start DMA")

# 测试 DMA 传输速度
# 记录开始时间
start_time = time.ticks_us()
# 启动 DMA 传输
dma.active(1)
# 检查 DMA 是否完成传输,当 dma.count 为 0 时传输完成
while dma.count > 0:
    pass
# 记录结束时间
end_time = time.ticks_us()
# 计算耗时
dma_time = time.ticks_diff(end_time, start_time)
print("DMA Transfer Time (us): ", dma_time)

# 打印调试数据,表示传输完成
print("DMA Finished")

# 打印传输完成后的目标数据,验证传输是否正确
print("Source Data: ", source_data)
print("Destination Data: ", destination_data)

# 测试 array 切片复制速度
start_time = time.ticks_us()
destination_data[:] = source_data[:]
end_time = time.ticks_us()
array_time = time.ticks_diff(end_time, start_time)
print("Array Slice Copy Time (us): ", array_time)

这里,我们首先创建了两个数组:

  • source_data​: 创建了一个包含 1024 个元素的数组,数据类型为 unsigned 32-bit integer,即每个元素占用 4 字节,从 0 到 1023 的序列(共 1024 个元素);
  • destination_data​: 这个数组用于接收 DMA 传输后的数据。初始化时,所有元素都为 0,长度和 source_data 相同。

然后对创建的 DMA 通道对象,进行配置:

  • 通过 ​pack_ctrl​ 方法设置DMA​​控制寄存器​:size=2 表示 32 位数据传输,该方法封装了对 DMA 控制寄存器的配置,包括传输的字长、方向等控制信息,我们可以使用 unpack_ctrl 方法查看其完整的配置

1.png

可以看到,设置其读地址和写地址递增,在每次传输结束时不生成中断而在读取零值后生成中断,单次传输的数据大小为四字节(32 位数据)

  • dma.config()​ 配置​​DMA的传输参数​:源地址设置为 source_data 数组的内存地址,目标地址设置为 destination_data 数组的内存地址,设置要传输的数据长度为源数据的元素数量,即 5 个 32 位元素,使用 pack_ctrl 方法生成的控制寄存器值,配置传输大小为 32 位,并配置其不立即开启传输
    • 这里,trigger=True的含义是立即启动 DMA 传输
    • 而当 trigger=False​ ​时,需要通过 dma.active(1) 方法手动设置其开始传输
      二者都是软件方法,来启动 DMA 传输

在主程序中,我们在启动 DMA 传输后通过 while dma.count > 0: 检查传输是否完成,dma.count 表示 DMA 剩余需要传输的数据数量,当它变为 0 时,说明传输完成。最后打印源数据和目标数据,验证 DMA 是否成功地将数据从源数组传输到目标数组。

同时,记录并打样 DMA 传输耗时,之后测试 array 切片复制的速度。

烧录代码,输出如下:

2.png

000.gif

可以看到,我们成功使用 DMA 实现了数据从内存到内存的传输,对 1024 个元素来说耗时为 104 us,而切片复制操作耗时为 314 us,DMA 数据传输速度在大数据量时相比于切片复制操作快了许多;如果我们将数据长度增大到 4096 个元素,DMA 传输耗时为 110 us,切片复制操作耗时为 923 us,速度差异更为明显。

ScreenShot_2026-06-26_044638_874.png

posted @ 2026-09-03 15:48  FreakStudio  阅读(10)  评论(0)    收藏  举报