【保姆级教程】用 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方法查看其完整的配置

可以看到,设置其读地址和写地址递增,在每次传输结束时不生成中断而在读取零值后生成中断,单次传输的数据大小为四字节(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 切片复制的速度。
烧录代码,输出如下:


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


以下代码可以在我们提供的资料包中的 `elegance-devkit v1\Demo\66 DMA_MemoryToMemory` 文件夹中找到。
浙公网安备 33010602011771号