1. 项目概述与核心价值
在嵌入式系统开发,尤其是涉及大量数据搬运的应用中,CPU常常被频繁的、琐碎的拷贝操作所拖累。想象一下,一个摄像头模块每秒产生数十兆的原始图像数据,如果每一帧都需要CPU亲自从外设寄存器搬到内存,再进行处理,那CPU基本就“废了”,什么核心算法都跑不动。这时候,DMA(直接内存访问)技术就是我们的救星。它像一个专职的“搬运工”,只要CPU告诉它“从哪里搬”、“搬到哪里”、“搬多少”,它就能独立完成数据搬运,让CPU腾出手来处理更有价值的计算任务。
而德州仪器(TI)的增强型直接内存访问控制器EDMA3,则是这个“搬运工”中的高级工程师。它不仅仅是简单的一维搬运,而是引入了三维数据传输的概念,能够理解数据在内存中的复杂排布,比如一个视频帧(二维图像)的多行数据,或者多帧视频(第三维)的连续搬运。这种能力对于图像处理、音频流、雷达信号处理等场景至关重要。其核心的“大脑”就是参数RAM,我们通过配置PaRAM中的一系列参数,来定义一次复杂的传输任务。EDMA3传输控制器则负责高效、无差错地执行这个任务。
本文将从一个嵌入式开发者的实战视角,深入解析EDMA3传输控制器的工作机制,并手把手带你理解如何配置其核心的PaRAM。我会结合手册中的原理图,用更直白的语言和实际场景的例子,把那些看似晦涩的寄存器位和三维索引讲清楚。无论你是刚开始接触EDMA的新手,还是想深入优化现有代码的老手,相信都能从中获得一些直接可用的“干货”。
2. EDMA3架构核心:CC与TC的分工协作
要理解EDMA3,首先要明白它的“双核”架构:通道控制器和传输控制器。这不是两个独立的模块,而是一个高效流水线上的两个关键工位。
2.1 通道控制器:任务调度中心
通道控制器是EDMA3的“前台”和“调度中心”。它的核心职责是接收并管理来自外设或软件的传输请求(事件)。你可以把它想象成一个医院的挂号分诊台。
- 事件队列:当外设(如摄像头接口、串口)需要传输数据时,会向EDMA3CC发送一个事件信号。CC内部有多个事件队列(例如Q0, Q1, Q2, Q3),不同的事件源可以被映射到不同的队列,这实现了基本的优先级管理和流量控制。事件按照FIFO(先进先出)的顺序在队列中排队等待处理。
- 参数读取与校验:当队列头部的某个事件轮到被处理时,CC会根据该事件对应的通道号,去参数RAM中找到预先配置好的那组参数(一个PaRAM Set)。这组参数完整定义了一次传输的所有细节:源地址、目标地址、传输维度、同步方式等。CC会校验这个传输请求是否有效。
- 任务派发:校验通过后,CC会将这个传输请求打包成一个传输请求包,然后根据预先的映射关系(例如Q0映射到TC0),派发给对应的传输控制器去执行。至此,CC的调度工作暂时告一段落,它可以继续处理队列中的下一个事件。
- 完成与链式反应:CC还负责“善后”。当TC完成传输后,会向CC回送一个完成信号。此时,CC根据PaRAM中的配置,决定是否触发一个中断通知CPU(“老板,活干完了”),或者触发另一个通道的事件(链式传输,即“干完这个,自动开始干下一个”)。
注意:CC支持“早期完成”和“正常完成”两种报告模式。早期完成是指CC一把TRP提交给TC,就认为传输完成并触发中断/链式事件,此时数据可能还在TC的流水线上搬运。这适用于对延迟极其敏感,但对传输完成时刻精度要求不高的场景。正常完成则是等TC真正搬完所有数据才报告。绝大多数情况下,为了数据一致性,我们选择“正常完成”。
2.2 传输控制器:高效执行引擎
传输控制器是EDMA3的“后台”和“执行引擎”。它接收来自CC的TRP,并负责实际的数据搬运工作。它的设计目标就是“快”和“高效”。
TC内部是一个精密的流水线结构,主要包含以下几个关键部分:
- DMA程序寄存器组:可以把它看作“待办任务列表”。当TC空闲时,它从这里加载第一个TRP开始执行。如果CC又派发了一个新任务,这个新TRP会被存放到这里,等待当前任务完成后立即接上,实现了任务预取,减少了空闲等待时间。
- DMA源激活寄存器组与读控制器:这是当前正在执行的任务的“源地址管理单元”。读控制器根据TRP中的信息(源地址、ACNT等),向系统内存或外设发起读操作,将数据读入。
- 目标FIFO寄存器组与写控制器:这是当前任务的“目标地址管理单元”。写控制器负责将读入的数据,写入到指定的目标地址。FIFO结构允许写操作稍微滞后于读操作,以平滑总线访问。
- 数据FIFO:这是一个临时缓冲区,用于存放“在途数据”。读控制器读出的数据先放在这里,写控制器再从里面取数据写入。它解耦了读和写操作,使得TC可以在源端或目标端总线繁忙时,依然保持一定程度的流水线作业。
- 命令分片与优化:这是TC高效的关键。对于一次大的传输(比如传输一个64KB的数组),TC不会傻傻地发起一次64KB的读写(这可能会长时间独占总线,阻塞其他主设备)。相反,它会根据总线位宽、FIFO深度等因素,自动将大请求分片成多个更小的、总线友好的突发传输。同时,它还会进行优化,比如合并对连续地址的访问,最大化总线利用率。
TC和CC的协作,构成了EDMA3高效处理并发、复杂传输任务的基础。CC负责宏观的任务调度和流程控制,而TC则专注于微观的数据搬运效率。
3. 三维数据传输模型:理解数据在内存中的“形状”
这是EDMA3最强大也最需要理解的概念。传统DMA通常只关心“从A地址开始,连续搬N个字节”。但在现实世界中,数据往往不是简单的一维线性排列。
3.1 三维定义:ACNT, BCNT, CCNT
EDMA3用三个维度来定义一个完整的数据块:
- 第一维:数组:这是最小的传输单元,由
ACNT个连续字节构成。你可以把它理解为一行像素,或者一个音频采样点序列。 - 第二维:帧:一帧由
BCNT个数组构成。每个数组之间,在内存地址上可能不是连续的,它们通过SRCBIDX(源B索引)和DSTBIDX(目标B索引)来间隔。这就像一帧图像中的多行,行与行之间在内存中可能有间隔(行跨度)。 - 第三维:块:一个块由
CCNT帧构成。帧与帧之间通过SRCCIDX(源C索引)和DSTCIDX(目标C索引)来间隔。这就像一段视频中的多个连续帧。
举个例子:你要搬运一个RGB565格式的图像,分辨率是320x240。
- 每个像素2字节,那么一行(320像素)就是
ACNT = 320 * 2 = 640字节。 - 图像有240行,那么
BCNT = 240。 - 你只想搬运一帧,那么
CCNT = 1。 - 内存中,一行数据是连续的。但行与行之间,可能为了内存对齐,会有额外的填充字节。假设每行实际占用的内存跨度是
stride = 1024字节。那么,从第N行首地址到第N+1行首地址的偏移就是SRCBIDX = 1024(假设源和目标布局相同,DSTBIDX也是1024)。 - 如果你要搬运连续10帧图像,且帧与帧在内存中是紧密排列的(无间隔),那么
CCNT=10,���SRCCIDX和DSTCIDX等于一帧图像的总大小:240 * 1024 = 245760字节。
3.2 同步类型:A同步 vs AB同步
三维模型定义了数据的“形状”,而同步类型则定义了每一次触发事件所搬运的数据量。这是配置时的关键选择。
3.2.1 A同步传输
在A同步模式下,每一个EDMA3同步事件,只触发搬运一个数组(ACNT个字节)。
- 工作方式:CC每收到一个事件,就从PaRAM中读取参数,向TC提交一个只包含
ACNT字节的TRP(此时BCNT和CCNT在TRP中均为1,实际维度由CC内部维护)。然后CC更新地址(加上B索引)和内部BCNT计数器。 - 事件数量:要完成整个三维数据块(
BCNT * CCNT个数组)的搬运,你需要触发BCNT * CCNT次事件。 - 适用场景:适用于数据产生或消耗速率较慢,或者需要精细控制每次传输量的场景。例如,一个低速ADC每转换完一个点就触发一次DMA,将单个采样值存入数组。
地址更新规则(重点):
- 在帧内(B维度):每搬运一个数组后,源和目标地址分别增加
SRCBIDX和DSTBIDX。 - 在帧间(C维度):当一帧内的所有数组(
BCNT个)都搬完后(即完成一次C-update),地址的更新是基于当前帧的最后一个数组的起始地址,加上C索引。即:新地址 = 最后一个数组的起始地址 + SRC/DST CIDX。
3.2.2 AB同步传输
在AB同步模式下,每一个EDMA3同步事件,触发搬运完整的一帧(BCNT个数组,即 ACNT * BCNT 字节)。
- 工作方式:CC每收到一个事件,就向TC提交一个包含
BCNT个数组的TRP(CCNT在TRP中为1)。TC会负责在内部循环,搬完这一帧的所有数组。 - 事件数量:要完成整个三维数据块(
CCNT帧)的搬运,你只需要触发CCNT次事件。 - 适用场景:这是更高效、更常用的模式。适用于数据以“帧”为单位产生或处理的场景。例如,摄像头产生一整帧图像后发出一个VSYNC信号作为DMA事件,DMA就可以用AB同步模式,一次事件搬完一整帧。
地址更新规则(重点,与A同步不同):
- 在帧内(B维度):地址更新由TC在搬运每个数组时负责,基于
SRCBIDX/DSTBIDX。 - 在帧间(C维度):当一帧搬运完成后,地址的更新是基于当前帧的第一个数组的起始地址,加上C索引。即:
新地址 = 第一个数组的起始地址 + SRC/DST CIDX。
为什么有这个区别?这源于两种同步模式对“当前处理单元”的定义不同。A同步每次处理一个“点”(数组),所以它的参考点是这个点。AB同步每次处理一个“面”(帧),所以它的参考点自然就是这个面的起点。理解这一点对于正确计算C索引值至关重要,算错了会导致数据搬运到错误的内存区域。
实操心得:在图像处理中,几乎总是使用AB同步。因为一帧图像的数据在物理上或逻辑上就是一个整体。配置时,
ACNT=一行图像的字节数,BCNT=图像高度,CCNT=需要搬运的帧数(例如双缓冲就是2)。SRCBIDX/DSTBIDX等于内存中的行跨度,SRCCIDX/DSTCIDX通常设为0(乒乓缓冲)或帧大小(连续存储)。
4. 参数RAM深度解析:EDMA3的“任务清单”
PaRAM是EDMA3的灵魂所在。所有传输任务的蓝图都存储在这里。它是一个位于EDMA3CC内部的RAM表,通常包含256个参数集,每个参数集占32字节(8个32位字)。
4.1 PaRAM结构总览
每个PaRAM Set的布局是固定的,如下表所示:
| 偏移地址 (字节) | 缩写 | 参数名 | 描述与配置要点 |
|---|---|---|---|
| 0x00 | OPT | 通道选项 | 传输的全局配置,包含同步类型、地址模式、完成中断/链使能等,是配置的重中之重。 |
| 0x04 | SRC | 源地址 | 传输起始的源字节地址。 |
| 0x08 | ACNT | 第一维计数 | 数组大小(1-65535字节)。必须≥1,否则为无效/空传输。 |
| BCNT | 第二维计数 | 每帧中的数组数量(1-65535)。 | |
| 0x0C | DST | 目标地址 | 传输起始的目标字节地址。 |
| 0x10 | SRCBIDX | 源B索引 | 帧内,下一个源数组的地址偏移(有符号,-32768 ~ +32767)。 |
| DSTBIDX | 目标B索引 | 帧内,下一个目标数组的地址偏移。 | |
| 0x14 | LINK | 链接地址 | 当前参数集用完后,下一个参数集的地址偏移(低5位必须为0)。0xFFFF表示空链接(传输终止)。 |
| BCNTRLD | BCNT重载值 | 仅用于A同步。当BCNT减到0时,用于重新加载BCNT的值。 | |
| 0x18 | SRCCIDX | 源C索引 | 帧间,下一帧第一个源数组的地址偏移(有符号)。注意A/AB同步的参考点不同。 |
| DSTCIDX | 目标C索引 | 帧间,下一帧第一个目标数组的地址偏移。 | |
| 0x1C | CCNT | 第三维计数 | 块中的帧数量(1-65535)。 |
| RSVD | 保留 | 必须写0。 |
4.2 关键参数详解与配置实战
4.2.1 通道选项参数
OPT寄存器包含了控制传输行为的核心位域。我们逐位分析其工程意义:
- SYNCDIM (位2):同步维度。
0为A同步,1为AB同步。这是首要配置项。 - SAM/DAM (位1和位0):源/目标地址模式。
0= 递增模式。在传输一个数组(ACNT字节)时,地址在每个字节传输后递增。这是最常用的模式。1= 常量地址模式。地址在达到FWID指定的FIFO宽度后回绕。这用于实现硬件FIFO。例如,从一个外设的固定数据寄存器(FIFO)读取数据到内存。此时,SRC地址必须对齐到FWID对应的边界(如FWID=32bit,则地址低2位必须为0)。
- FWID (位10-8):FIFO宽度。仅在SAM或DAM为常量地址模式时有效。定义了“回绕”的边界宽度(8/16/32/64/128/256位)。
- TCINTEN/ITCINTEN (位20/21):传输完成/中间传输完成中断使能。使能后,当传输完成(或AB同步中一帧内的每个数组完成)时,会在中断挂起寄存器中置位对应
TCC的位,如果中断使能寄存器也打开了,则向CPU产生中断。 - TCCHEN/ITCCHEN (位22/23):传输完成/中间传输完成链使能。使能后,在传输完成(或中间完成)时,会在链事件寄存器中置位对应
TCC的位,从而自动触发另一个通道的传输。这是实现复杂传输链(如乒乓缓冲)的关键。 - TCC (位17-12):传输完成码。一个6位的标签,用于关联中断和链式事件。你可以将其理解为这个传输任务的“ID号”。当中断或链事件发生时,系统通过检查是哪个TCC被置位,来判断是哪个任务完成了。
- TCCMODE (位11):传输完成码模式。
0=正常完成(数据全部搬完),1=早期完成(TR提交给TC即认为完成)。除非有特殊低延迟需求,否则建议使用正常完成,以确保数据一致性。 - STATIC (位3):静态集。
0=非静态,传输中PaRAM集的内容会被更新(地址、计数);1=静态,传输中PaRAM内容不变。对于需要重复触发的相同传输(如循环从固定外设地址读数),可以设为静态以提升性能。
4.2.2 索引参数的计算与陷阱
索引参数是三维传输的“导航仪”,配置错误会导致数据搬运到完全错误的地方。
- SRCBIDX/DSTBIDX:这是帧内数组间的步进。例如,你的数据在内存中是二维数组
data[BCNT][ACNT],且按行连续存储,那么SRCBIDX = sizeof(data[0]) = ACNT。但如果内存布局有间隔(比如为了对齐),则SRCBIDX = 实际的行跨度。 - SRCCIDX/DSTCIDX:这是帧间的步进。这是最容易出错的地方。
- A同步:
SRCCIDX= (下一帧第一个数组的地址) - (当前帧最后一个数组的地址)。 - AB同步:
SRCCIDX= (下一帧第一个数组的地址) - (当前帧第一个数组的地址)。
- A同步:
一个具体例子: 假设你有3帧(CCNT=3)图像,每帧2行(BCNT=2),每行100字节(ACNT=100)。内存中紧密排列。
- A同步:
- 帧0,数组0地址 = Addr0。
- 帧0,数组1地址 = Addr0 + 100。
- 帧1,数组0地址 = Addr0 + 200。
- 因此,
SRCCIDX = (Addr0+200) - (Addr0+100) = 100。
- AB同步:
- 帧0,第一个数组地址 = Addr0。
- 帧1,第一个数组地址 = Addr0 + 200。
- 因此,
SRCCIDX = (Addr0+200) - Addr0 = 200。
可以看到,同样的内存布局,因为同步模式不同,C索引值差了一倍(等于ACNT * BCNT)。务必在配置时根据同步类型仔细计算。
4.2.3 链接与重载
- LINK:链接地址。当当前PaRAM集的
CCNT和BCNT都递减到0(即传输自然结束)时,如果STATIC=0,EDMA3CC会自动将LINK地址指向的另一个PaRAM集的内容拷贝到当前集,从而实现传输参数的自动重载。这是实现乒乓缓冲和循环传输链表的基础。LINK地址必须是32字节对齐的(低5位为0)。设置为0xFFFF表示空链接,传输终止,并且CC会清空当前PaRAM集(LINK字段除外)。 - BCNTRLD:仅用于A同步。当
BCNT从1减到0时(即完成一帧内的最后一个数组),CC会用BCNTRLD的值重新加载BCNT计数器,同时CCNT减1,地址加上C索引。这保证了在A同步模式下,跨帧时能正确重置行计数器。
4.3 空传输与伪传输
这是一个重要的边界情况和错误处理机制。
- 空传输:当
ACNT、BCNT、CCNT全部为0时,定义为一个空参数集。如果某个通道关联的PaRAM集是空的,当该通道有事件到来时,CC会将其视为错误,在事件丢失寄存器中置位,并且该通道在次级事件寄存器中的位保持置位,导致该通道后续所有事件被忽略。你必须手动清除错误位才能恢复。这是一种需要避免的配置错误状态。 - 伪传输:当
ACNT、BCNT、CCNT中至少一个为0,但并非全为0时,定义为一个伪参数集。例如ACNT=100, BCNT=0, CCNT=1。当该通道被触发时,CC会正常处理,但提交给TC的传输请求是0字节。TC不会进行实际数据搬运,但会正常报告完成(触发中断/链)。这可以用于实现纯事件触发或软件延时等逻辑功能,是一种合法的传输。
5. 实战配置流程与代码示例
理论讲完了,我们来看如何动手配置。以下是一个典型的AB同步传输配置流程,以从摄像头接口搬运一帧YUV图像到内存为例。
5.1 场景定义与参数计算
- 场景:摄像头输出一帧 1280x720 的YUV422数据。YUV422每个像素占2字节。图像数据通过FIFO接口连接到EDMA。
- 目标:使用EDMA3,在每帧VSYNC事件触发时,将一帧数据搬运到SDRAM中的缓冲区。
- 参数计算:
ACNT= 一行像素的字节数 = 1280 * 2 = 2560。BCNT= 图像的行数 = 720。CCNT= 1 (单次触发搬一帧)。SRC= 摄像头FIFO的数据寄存器地址(常量地址)。DST= SDRAM中目标缓冲区的起始地址。SAM= 1 (常量地址模式,从固定寄存器读)。DAM= 0 (递增模式,写入内存)。FWID= 根据摄像头数据总线宽度设定,假设为32位,则设置为2(对应32-bit)。SRCBIDX= 0 (源为固定寄存器,无行偏移)。DSTBIDX= 内存中行跨度。假设缓冲区紧密排列,则为ACNT = 2560。SRCCIDX= 0 (单帧,无帧偏移)。DSTCIDX= 0 (单帧,无帧偏移)。SYNCDIM= 1 (AB同步)。TCC= 设为某个值,例如10,用于标识此传输。TCINTEN= 1 (使能传输完成中断,便于CPU知道一帧已就绪)。LINK= 可以指向另一个PaRAM集实现乒乓缓冲,这里先设为0xFFFF。
5.2 寄存器配置步骤(伪代码风格)
假设我们使用C语言和TI的驱动程序库进行配置。以下代码展示了核心的配置逻辑。
// 1. 定义并初始化PaRAM结构体 EDMA3_PaRAM_Config myParamSet = {0}; // 2. 配置OPT选项字 // 假设我们使用TCC=10, AB同步,源常量地址,目标递增,使能传输完成中断 myParamSet.opt = EDMA3_OPT_MAKE( EDMA3_OPT_PRI_MEDIUM, // 优先级,中等 10, // TCC = 10 0, // TCCMODE: 正常完成 2, // FWID: 32-bit (因为SAM=1) 0, // STATIC: 非静态 1, // SYNCDIM: AB同步 0, // DAM: 目标递增 1 // SAM: 源常量地址 ); myParamSet.opt |= EDMA3_OPT_TCINTEN_ENABLE; // 使能传输完成中断 // 3. 配置地址和计数 myParamSet.src = (uint32_t)CAMERA_FIFO_DATA_REG_ADDR; myParamSet.dst = (uint32_t)frameBuffer; myParamSet.acnt = 2560; // ACNT myParamSet.bcnt = 720; // BCNT myParamSet.ccnt = 1; // CCNT // 4. 配置索引 myParamSet.srcBidx = 0; // 源为固定寄存器,无行偏移 myParamSet.dstBidx = 2560; // 目标内存行跨度 myParamSet.srcCidx = 0; // 单帧,无帧偏移 myParamSet.dstCidx = 0; // 单帧,无帧偏移 myParamSet.bcntRld = 0; // AB同步,此字段忽略,但建议初始化 // 5. 配置链接地址 (假设不使用链接,传输一次后停止) myParamSet.link = 0xFFFF; // 空链接 // 6. 将配置写入EDMA3的PaRAM区域 // 假设paramSetNum是分配给此通道的PaRAM集编号,比如12 EDMA3_setPaRAM(edmaHandle, paramSetNum, &myParamSet); // 7. 将DMA通道映射到该PaRAM集,并关联事件源 // 假设使用DMA通道20,事件源是摄像头VSYNC EDMA3_channelMapDMA(edmaHandle, 20, paramSetNum); // 将通道20映射到PaRAM集12 EDMA3_eventMap(edmaHandle, 20, CAMERA_VSYNC_EVENT_NUM); // 将事件源绑定到通道20 // 8. 使能该通道的中断(对应TCC=10) EDMA3_intEnable(edmaHandle, 10); // 使能TCC 10对应的中断线 // 9. 使能该DMA通道,等待事件触发 EDMA3_channelEnable(edmaHandle, 20);5.3 实现乒乓缓冲
单一缓冲区的问题在于,当DMA在向缓冲区写新一帧数据时,CPU可能还在读取上一帧数据,会造成冲突。乒乓缓冲使用两个缓冲区交替工作。
// 定义两个PaRAM集,分别对应BufferA和BufferB EDMA3_PaRAM_Config paramSetA, paramSetB; // 初始化paramSetA和paramSetB,目标地址分别指向bufferA和bufferB // ... 省略初始化代码,与上述类似 ... // 关键:配置链接,形成环 paramSetA.link = (uint16_t)((uint32_t)¶mSetB - EDMA_PARAM_BASE) >> 5; // 计算PaRAM B的相对偏移地址 paramSetB.link = (uint16_t)((uint32_t)¶mSetA - EDMA_PARAM_BASE) >> 5; // 计算PaRAM A的相对偏移地址 // 将paramSetA写入PaRAM集12, paramSetB写入PaRAM集13 EDMA3_setPaRAM(edmaHandle, 12, ¶mSetA); EDMA3_setPaRAM(edmaHandle, 13, ¶mSetB); // 将DMA通道映射到PaRAM集12(从A开始) EDMA3_channelMapDMA(edmaHandle, 20, 12); // 使能通道和中断... // 当通道20第一次触发,使用paramSetA搬运到bufferA。 // 完成后,由于paramSetA的link指向B,CC会自动将paramSetB的内容加载到通道20对应的PaRAM集(即集12)。 // 下一次事件触发时,就会使���paramSetB的参数搬运到bufferB。 // 再完成后,又链接回A,如此循环往复。 // CPU可以通过检查中断TCC,并交替处理bufferA和bufferB,实现无冲突的数据流。6. 高级主题:性能优化与问题排查
6.1 性能优化要点
- 对齐访问:虽然EDMA3支持非对齐访问,但对齐的地址(通常是32位或64位边界)能获得最佳的总线效率。尽量保证
SRC和DST地址以及ACNT是总线位宽的整数倍。 - 合理使用AB同步:在数据以块/帧为单位时,尽量使用AB同步。它减少了CC和TC之间的交互次数,降低了开销,并且让TC能更好地优化内部的数据流。
- 利用链式传输与乒乓缓冲:对于连续数据流,务必使用链接功能实现乒乓或多缓冲。这避免了软件重新配置PaRAM带来的延迟,实现了“永动”传输。
- 优化FIFO深度:TC内部的数据FIFO深度是有限的。对于非常大的
ACNT,TC会自动进行命令分片。但了解你所用芯片的具体FIFO深度,有助于理解潜在的性能瓶颈。通常,一次传输的ACNT不应远大于FIFO深度能容纳的数据量。 - 事件队列分配:将高优先级、实时性要求高的外设事件分配到独立的事件队列(如Q0),避免被低优先级事件阻塞。
6.2 常见问题与排查技巧
即使配置正确,在实际调试中也可能遇到问题。下面是一个常见问题排查表:
| 现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| DMA不启动 | 1. 通道未使能。 2. 事件未正确映射到通道。 3. PaRAM集为空集或伪集(ACNT=0)。 4. 事件被屏蔽。 | 1. 检查EDMA3_channelEnable是否调用。2. 检查 EDMA3_eventMap映射关系。3. 检查PaRAM中 ACNT、BCNT、CCNT,确保至少一个非零且ACNT>=1。4. 检查事件使能寄存器。 |
| 数据搬运错位 | 1.SRCBIDX/DSTBIDX计算错误。2. SRCCIDX/DSTCIDX计算错误(混淆A/AB同步)。3. 源/目标地址模式配置错误。 | 1. 重新计算行列跨度。用一个小数组(如2x2)测试。 2.重点检查!根据同步类型重新计算C索引。 3. 确认 SAM/DAM:外设FIFO用常量地址,内存用递增。 |
| 只搬运了一次,不循环 | 1.LINK地址配置错误(未对齐或指向无效集)。2. STATIC位被设置为1。3. CCNT或BCNT未减到0(对于A同步,还需检查BCNTRLD)。 | 1. 确保LINK地址是32字节对齐,并指向一个有效PaRAM集。2. 检查 OPT寄存器,确保STATIC=0。3. 单次触发后,检查PaRAM集中 BCNT和CCNT的值是否已更新。 |
| 中断不触发 | 1.TCINTEN或ITCINTEN未使能。2. TCC值未正确配置或与中断使能寄存器不匹配。3. CPU全局中断或EDMA模块中断未开启。 4. 中断服务程序未清除中断标志。 | 1. 检查OPT寄存器中的中断使能位。2. 确认 OPT.TCC的值,并在EDMA3_intEnable中使用相同的值。3. 检查CPU和EDMA3CC的中断控制器配置。 4. 在ISR中,必须清除 IPR中对应的中断挂起位。 |
| 系统不稳定或数据损坏 | 1. 传输跨越了内存保护边界或访问了非法地址。 2. 源/目标缓冲区大小不足,导致DMA写越界。 3. 总线竞争激烈,缺乏仲裁或带宽不足。 | 1. 确保传输范围在有效的、可访问的内存/外设区域。 2. 精确计算所需缓冲区大小: 总字节数 = ACNT * BCNT * CCNT。3. 优化内存布局,使用缓存对齐,或调整DMA优先级。 |
调试心得:
- 从小处着手:先用一个极简的配置测试(如
ACNT=4, BCNT=1, CCNT=1,从内存一个位置搬到另一个位置),用调试器查看内存变化,确保基础功能正常。 - 善用寄存器查看:在调试器中,实时查看EDMA3的PaRAM区域、事件寄存器、中断寄存器的值,是定位问题的直接手段。
- 理解“完成”的含义:区分“CC提交完成”和“TC搬运完成”。如果你在中断服务程序里立刻使用数据,确保使用的是“正常完成”模式,或者通过查询TC状态寄存器确认数据真正搬完。
- 数据一致性:如果源或目标地址位于CPU缓存区域,在启动DMA传输前,需要执行缓存写回或无效化操作,以确保DMA看到的是内存中最新的数据,以及CPU读取的是DMA更新后的数据。这是多核/带Cache系统中最常见的坑。