1. 项目概述与μDMA核心价值
在嵌入式系统开发中,尤其是面对实时数据采集、高速通信或图形刷新这类任务时,CPU如果被频繁的数据搬运工作所拖累,整个系统的响应速度和效率就会大打折扣。想象一下,你正在用微控制器处理一个音频流,CPU每收到一个字节的音频数据,就要停下手中的计算任务,去执行一次内存读写操作,这无疑是巨大的资源浪费。这时,直接内存访问(DMA)技术就成为了解放CPU、提升系统性能的关键角色。而德州仪器(TI)的Tiva™系列微控制器,比如我们这次深入探讨的TM4C123GE6PM,其内置的微直接内存访问(μDMA)控制器,更是将这种能力精细化、可编程化,为开发者提供了强大的数据搬运引擎。
简单来说,μDMA允许外设(如ADC、UART、SPI)或软件直接与内存进行数据交换,整个过程无需CPU介入。CPU只需要在传输开始前告诉μDMA:“从A地址搬N个数据到B地址”,然后就可以去处理其他更重要的任务了。传输完成后,μDMA会通过中断通知CPU:“活儿干完了,数据在B地址,请查收”。这种“甩手掌柜”模式,对于需要维持高数据吞吐量的应用至关重要。
本文将以Tiva TM4C123GE6PM的μDMA模块为蓝本,为你彻底拆解其工作原理。我不会仅仅罗列寄存器手册,而是结合我多年在电机控制、传感器数据流处理中实际使用μDMA的经验,从 通道配置的底层逻辑 、 中断处理的实战流程 ,到 关键寄存器的每一个比特位如何影响传输行为 ,进行深度剖析。无论你是正在调试一个ADC多通道扫描采集,还是想优化SPI Flash的读写速度,理解并掌握μDMA,都能让你的嵌入式系统设计如虎添翼。接下来,我们就从最核心的通道控制结构开始,一步步揭开它的神秘面纱。
2. μDMA通道控制结构深度解析
如果把μDMA控制器比作一个建筑工地,那么通道控制结构(Channel Control Structure)就是发给每个施工队(DMA通道)的“施工图纸”。这张图纸详细规定了“从哪里搬砖(源地址)”、“搬到哪里去(目的地址)”、“一次搬几块(数据大小)”、“怎么搬(地址递增模式)”以及“总共要搬多少(传输数量)”等所有关键信息。在Tiva的μDMA架构中,每个通道都拥有两张这样的图纸:一张主控结构(Primary),一张备用控制结构(Alternate),这正是实现Ping-Pong、Scatter-Gather等高级传输模式的基础。
2.1 核心三寄存器:定义传输的骨架
通道控制结构主要由三个寄存器在内存中构成,它们位于由
DMACTLBASE
寄存器指定的通道控制表(Channel Control Table)中。每个通道的主控结构和备用结构在表中都有固定的偏移位置。
1. DMA通道源地址结束指针(DMASRCENDP)
这个寄存器存放的是传输
源地址区域的最后一个字节的地址
。这里有个非常关键且容易误解的点:它叫“结束指针”,但实际指向的是传输范围的末尾(包含该地址)。例如,如果你要从数组
srcBuffer[100]
(起始地址0x2000.0000)传输100个字节,且地址递增,那么
DMASRCENDP
应设置为
0x2000.0000 + 100 - 1 = 0x2000.0063
。
注意 :当源地址不递增时(
SRCINC字段为0x3),此寄存器直接指向源外设的数据寄存器地址本身。此时,μDMA会反复从这个固定地址读取数据。
2. DMA通道目的地址结束指针(DMADSTENDP)
与源地址指针同理,它定义了传输
目的地址区域的最后一个字节的地址
。设置规则与
DMASRCENDP
完全一致。同样,当目的地址不递增时(
DSTINC
为
0x3
),它指向一个固定的外设数据寄存器。
3. DMA通道控制字(DMACHCTL) 这是整个控制结构的“大脑”,一个32位的寄存器包含了决定传输行为的所有控制位。我们需要逐字段理解:
-
DSTINC (位 31:30) 与 SRCINC (位 27:26) :目的和源地址增量。它决定了每次传输后,地址指针如何移动。可选值为:
0x0(字节递增)、0x1(半字递增)、0x2(字递增)、0x3(不递增)。这里有一个 硬性规则 :增量值必须大于或等于对应的数据大小(DSTSIZE/SRCSIZE)。例如,数据大小是字(4字节),那么地址增量至少要是字(0x2),不能是字节(0x0)。通常,从内存到内存的传输,两者都设为递增;从外设到内存,源(外设)不递增,目的(内存)递增;反之亦然。 -
DSTSIZE (位 29:28) 与 SRCSIZE (位 25:24) :目的和源数据项大小。定义每次传输操作的数据宽度:
0x0(8位)、0x1(16位)、0x2(32位)。另一个 关键规则 :DSTSIZE和SRCSIZE必须设置为相同的值。你不能用一次32位读取搭配一次16位写入。 -
ARBSIZE (位 17:14) :仲裁大小。这个字段决定了μDMA控制器在“抢占”总线进行传输时,一次性连续传输多少个数据项,然后再释放总线给其他主设备(如CPU)。它是以2的幂次方定义的,从1(
0x0)到1024(0xA,此时由于单次传输已达最大项数,故不仲裁)。设置更大的仲裁大小可以提高总线利用率和传输效率,尤其是在传输大块连续数据时。但如果你需要更低的传输延迟,让CPU能更频繁地介入,则可以设小一些。 -
XFERSIZE (位 13:4) :传输大小(减1)。这是你需要传输的总数据项数量减1。这是一个10位字段,最大值为1023,代表一次传输最多1024个数据项。 务必记住 :这里填写的是“项数-1”。如果你想传输200个32位字,这里应填199(0xC7)。同时,这个值指的是“数据项”的个数,而非字节数。如果数据大小是半字(16位),传输100项,实际搬运的数据量是200字节。
-
XFERMODE (位 2:0) :传输模式。这是μDMA的灵魂,决定了传输如何被触发和进行。我们将在下一章详细展开。
2.2 传输模式(XFERMODE)详解与选型策略
XFERMODE
字段定义了七种工作模式,理解它们的区别是灵活运用μDMA的关键。
-
停止(Stop, 0x0) :通道未配置或配置无效,传输停止。在初始化或重新配置前,应确保模式为此值。
-
基本模式(Basic, 0x1) :最直观的模式。 每次外设请求或软件请求触发一次,μDMA就执行
ARBSIZE所指定数量的传输 。例如,ARBSIZE=4(即8次传输),那么每来一个请求,就搬8个数据项,直到总共完成XFERSIZE+1个项。适用于需要外设频繁请求、数据流不连续的场景。 -
自动请求模式(Auto-Request, 0x2) : 只需一次初始请求(软件或外设触发),μDMA就会自动完成整个
XFERSIZE+1个数据项的传输 ,期间不再需要额外请求。这非常适合内存到内存的块搬运,或者外设能够一次性提供大量数据的情况。 -
乒乓模式(Ping-Pong, 0x3) : 高级双缓冲模式 。该模式会同时使用主控和备用两个控制结构。假设主控结构指向缓冲区A,备用结构指向缓冲区B。传输启动后,控制器先用主结构(A)传输。当A传输完成,立即产生中断,并自动切换到备用结构(B)继续传输,同时将主结构的
XFERMODE改为Stop。在中断服务程序(ISR)中,你处理完缓冲区A的数据后,需要重新配置主结构(例如,指向一个新的缓冲区或重置参数),并将其XFERMODE改回Ping-Pong。如此循环,实现数据处理和传输的并行,几乎无延迟。这是实现连续数据流(如音频DAC输出、摄像头采集)的黄金方案。 -
内存分散-聚集模式(Memory Scatter-Gather, 0x4)与备用内存分散-聚集模式(0x5) :用于执行一个由多个独立传输任务(Task)组成的链表。主控结构(模式0x4)的源地址指向一个任务描述符表(每个描述符包含一个完整的备用控制结构)。μDMA控制器会自动从表中读取一个任务到备用结构(模式必须为0x5)并执行,完成后再读取下一个,直到遇到一个模式不是0x5的任务为止。这允许你用一次设置,完成一系列非连续地址的数据搬运。
-
外设分散-聚集模式(Peripheral Scatter-Gather, 0x6)与备用外设分散-聚集模式(0x7) :与内存分散-聚集类似,但专为外设设计。区别在于,备用结构(0x7)的每次传输,需要外设为每个仲裁块(
ARBSIZE)发出一次请求,而不是一次性完成整个任务项。更适用于外设驱动的复杂传输序列。
模式选择心得 :
-
单次块传输
:用
Auto-Request。 -
外设驱动的流式数据
:用
Basic。 -
零延迟连续数据处理
:首选
Ping-Pong。 -
复杂、多目的地的传输序列
:考虑
Scatter-Gather。
3. 通道配置、使能与中断处理全流程实战
理解了控制结构后,我们来看如何让一个通道真正动起来。这个过程就像组装并启动一台机器:放置图纸(配置控制结构)、接通电源(使能控制器和通道)、然后处理它完成工作后的汇报(中断)。
3.1 通道配置与使能步骤
假设我们要配置通道8,使用Ping-Pong模式,从ADC结果寄存器(假设地址0x4003.8000)传输到内存中的两个缓冲区
adc_buffer_a[256]
和
adc_buffer_b[256]
,每个缓冲区存放256个16位的采样值。
步骤1:分配并初始化通道控制表
首先,我们需要在内存中(通常是SRAM)分配一块对齐的区域作为通道控制表。
DMACTLBASE
寄存器要求基地址必须1024字节对齐。我们可以静态定义:
// 确保控制表1024字节对齐。许多编译器支持此属性。
__attribute__((aligned(1024)))
static uint32_t uDMA_ControlTable[256]; // 大小根据通道数定,通常256字足够
然后,在初始化函数中设置基地址指针:
HWREG(UDMA_CTLBASE) = (uint32_t)uDMA_ControlTable;
步骤2:填写主控和备用控制结构 我们需要计算两个控制结构在表中的偏移。对于通道8:
-
主控结构偏移 = 通道号 * 0x10 = 8 * 16 = 128 字节。在
uDMA_ControlTable数组中,每个uint32_t是4字节,所以索引偏移是 128 / 4 = 32。 - 备用结构偏移 = 0x200 + 通道号 * 0x10 = 0x200 + 128 = 0x280 字节。数组索引为 0x280 / 4 = 160。
接着,填写结构(以下为伪代码,展示思路):
// 获取主控结构指针
tDMAControlTable *pPrimary = (tDMAControlTable*)&uDMA_ControlTable[32];
// 获取备用结构指针
tDMAControlTable *pAlternate = (tDMAControlTable*)&uDMA_ControlTable[160];
// 配置主控结构(指向缓冲区A)
pPrimary->srcEndAddr = (void*)0x40038000; // ADC数据寄存器地址,不递增
pPrimary->dstEndAddr = (void*)(adc_buffer_a + 255); // 指向缓冲区A末尾,半字递增
pPrimary->control = UDMA_CHCTL_DSTINC_HALF | // 目的半字递增
UDMA_CHCTL_DSTSIZE_HALF | // 目的数据半字
UDMA_CHCTL_SRCINC_NONE | // 源不递增
UDMA_CHCTL_SRCSIZE_HALF | // 源数据半字
UDMA_CHCTL_ARBSIZE_64 | // 仲裁大小64项
((256 - 1) << 4) | // 传输大小255
UDMA_CHCTL_XFERMODE_PINGPONG; // 乒乓模式
// 配置备用结构(指向缓冲区B)
pAlternate->srcEndAddr = (void*)0x40038000; // 同上
pAlternate->dstEndAddr = (void*)(adc_buffer_b + 255);
pAlternate->control = UDMA_CHCTL_DSTINC_HALF |
UDMA_CHCTL_DSTSIZE_HALF |
UDMA_CHCTL_SRCINC_NONE |
UDMA_CHCTL_SRCSIZE_HALF |
UDMA_CHCTL_ARBSIZE_64 |
((256 - 1) << 4) |
UDMA_CHCTL_XFERMODE_PINGPONG;
重要提示 :在写入
control字段的最后,才设置XFERMODE为非零值(如PINGPONG)。在初始化阶段,应先将其清零(Stop模式),待所有参数配置完毕再启用传输模式,防止误触发。
步骤3:使能μDMA控制器及通道
首先,必须确保μDMA模块的时钟已使能(通过系统控制模块的
RCGCUDMA
寄存器)。然后,使能控制器主开关:
HWREG(UDMA_CFG) = UDMA_CFG_MASTEN_ENABLE; // 设置DMACFG寄存器的MASTEN位
接着,使能具体的通道8:
HWREG(UDMA_ENASET) = (1 << 8); // 设置DMAENASET寄存器的第8位
至此,通道配置完成,处于就绪状态,等待ADC(外设)发出DMA请求信号。
3.2 中断处理程序(ISR)的编写逻辑
当通道8完成一个缓冲区(比如A)的传输并切换到B时,会产生中断。我们的ISR需要完成以下工作:
-
确定完成的是哪个缓冲区
:通过读取通道控制字中的
XFERMODE字段来判断。在Ping-Pong模式下,完成传输的那个控制结构的XFERMODE会被硬件自动改为Stop (0x0)。 - 处理数据 :对已满的缓冲区进行数据处理(例如,计算均值、滤波、发送到下一级)。
-
重新武装缓冲区
:将已完成传输的控制结构重新配置(指向新的或清空的缓冲区),并将其
XFERMODE改回Ping-Pong,以便下一次传输。
以下是中断处理程序的示例逻辑:
void UDMA_Channel8_ISR(void) {
uint32_t ulStatus;
// 1. 读取通道中断状态并清除标志(可选,取决于具体芯片的中断清除方式)
ulStatus = HWREG(UDMA_CHIS); // 读取DMA通道中断状态寄存器
if (ulStatus & (1 << 8)) {
// 2. 判断哪个缓冲区完成
// 读取主控结构控制字(偏移需根据实际控制表基地址计算)
tDMAControlTable *pPrimary = ...; // 主结构地址
tDMAControlTable *pAlternate = ...; // 备结构地址
uint32_t primaryControl = pPrimary->control;
uint32_t alternateControl = pAlternate->control;
// 检查XFERMODE字段(最低3位)
if ((primaryControl & 0x7) == 0) {
// 主结构模式为Stop,说明缓冲区A传输完成
// 3. 处理缓冲区A的数据
process_buffer(adc_buffer_a, 256);
// 4. 重新武装主结构(例如,重置目的地址到缓冲区A开头)
// 注意:在乒乓模式下,通常我们交换缓冲区角色,这里假设简单重置
pPrimary->dstEndAddr = (void*)(adc_buffer_a + 255);
// 重新设置控制字,将模式改回PINGPONG
primaryControl &= ~0x7; // 清除模式位
primaryControl |= UDMA_CHCTL_XFERMODE_PINGPONG;
pPrimary->control = primaryControl;
}
if ((alternateControl & 0x7) == 0) {
// 备结构模式为Stop,说明缓冲区B传输完成
process_buffer(adc_buffer_b, 256);
pAlternate->dstEndAddr = (void*)(adc_buffer_b + 255);
alternateControl &= ~0x7;
alternateControl |= UDMA_CHCTL_XFERMODE_PINGPONG;
pAlternate->control = alternateControl;
}
// 清除该通道的中断标志(具体寄存器可能是DMACHIS的写1清除位)
HWREG(UDMA_CHIS) = (1 << 8); // 写1清除第8通道中断
}
// ... 可能还有其他通道中断需要处理
}
关键点与避坑指南 :
-
原子性操作
:在ISR中修改控制结构(特别是
control字)时,最好确保这是一个原子操作,或者是在DMA传输不可能发生的时刻(例如,在判断和重新配置之间,该通道的另一个缓冲区正在被使用是安全的,因为硬件已切换)。 - 地址对齐 :确保源/目的地址符合数据大小的对齐要求(例如,32位传输要求地址4字节对齐)。非对齐访问可能导致硬件错误或性能下降。
-
传输大小限制
:单个
XFERSIZE最大1024项。对于更大的传输,需要结合Scatter-Gather或软件分多次启动。 -
使能顺序
:务必先配置好控制表并使能控制器(
MASTEN),最后再使能具体通道。关闭时顺序相反。
4. 关键功能寄存器详解与应用场景
除了通道控制结构,μDMA控制器还有一组功能寄存器,用于全局控制、状态查询和通道行为微调。理解它们能让你更精细地掌控DMA传输。
4.1 配置与状态寄存器
-
DMA配置寄存器(DMACFG) :只有一个有效位
MASTEN(位0)。写1使能整个μDMA控制器,写0禁用。这是总开关。 注意 :在访问任何μDMA寄存器前,必须先使能其模块时钟,并等待至少3个系统时钟周期。 -
DMA状态寄存器(DMASTAT) :提供控制器的全局状态。
-
DMACHANS(位20:16):只读,指示控制器支持的通道数减1。对于TM4C123,此值通常为0x1F(31),表示有32个通道。 -
STATE(位7:4):控制状态机当前状态。调试时非常有用,可以查看DMA控制器是空闲(Idle)、正在读源数据(Reading source data)还是正在写目的数据(Writing destination data)等。 -
MASTEN(位0):只读,反映DMACFG.MASTEN的当前值。
-
4.2 通道控制寄存器
这些寄存器通常以“SET”和“CLR”成对出现,通过写1到特定比特位来设置或清除对应通道的某个属性。
| 寄存器名称 | 偏移地址 | 核心功能 | 应用场景 |
|---|---|---|---|
| DMAUSEBURSTSET | 0x018 | 设置某通道只响应 突发(Burst)请求 。 | 当外设支持突发请求(即一次请求可传输多个数据项)时,设置此位可以提升效率。如果剩余传输项数小于仲裁大小,控制器会自动清除该位,改用单次请求完成。 |
| DMAUSEBURSTCLR | 0x01C | 清除对应通道的USEBURST设置,使其响应单次和突发请求。 | 手动恢复通道对单次请求的响应。 |
| DMAREQMASKSET | 0x020 | 屏蔽 指定通道的外设DMA请求。 | 当你希望该通道仅用于 软件触发传输 时,屏蔽外设请求。例如,进行内存到内存的复制。 |
| DMAREQMASKCLR | 0x024 | 清除屏蔽,允许外设请求。 | 在软件传输完成后,重新允许外设接管。 |
| DMAENASET | 0x028 | 使能 指定通道。 | 通道配置完成后,最后一步就是写此寄存器使能通道,使其可以响应请求。 |
| DMAENACLR | 0x02C | 禁用 指定通道。 | 传输完成或需要停止通道时使用。传输完成时硬件也会自动清除使能位。 |
| DMAALTSET | 0x030 | 强制通道使用 备用控制结构 。 | 在Ping-Pong模式初始化后,可以手动设置此位来指定从哪个缓冲区开始。通常由硬件自动管理。 |
| DMAALTCLR | 0x034 | 强制通道使用 主控控制结构 。 | 手动切换回主结构。 |
| DMAPRIOSET | 0x038 | 设置通道为 高优先级 。 | 当多个通道同时请求时,高优先级通道优先获得服务。用于保障关键外设(如高速ADC)的数据不被丢失。 |
| DMAPRIOCLR | 0x03C | 清除通道的高优先级设置。 |
软件请求寄存器(DMASWREQ, 0x014) :这是一个非常有用的寄存器。向某一位写1,会为该通道产生一个软件DMA请求,就像外设发出请求一样。这让你可以 手动启动 任何配置好的DMA传输,无论其请求是否被屏蔽。常用于内存到内存的传输测试,或在特定时刻触发一次采集。
4.3 通道映射寄存器(DMACHMAPn)
这是一个强大的功能,允许你
重新映射
外设到不同的μDMA通道。芯片出厂时,每个外设(如UART0 RX、ADC0 SS0)都固定连接到一个特定的通道(参见数据手册的“Channel Assignments”表)。但通过
DMACHMAP0
~
DMACHMAP3
这四个寄存器,你可以改变这种固定关联。
每个寄存器管理8个通道的映射,每个通道用4个比特位表示。这4位值指定了实际服务于该通道的“请求源”编号。例如,默认情况下,通道0可能映射给ADC0 SS0(请求源编号假设为0x0)。你可以通过编程,将通道0映射给UART0 RX(请求源编号0x1)。
应用场景 :
- 冲突解决 :当两个高优先级外设默认分配到同一个DMA通道(时分复用)但你希望它们能同时使用DMA时,可以将其中一个重映射到一个空闲通道。
- 优先级管理 :将一个重要外设重映射到一个具有更高仲裁优先级的通道号(通常低通道号有更高优先级)。
- 灵活性 :使软件设计不再受硬件固定连接的束缚。
操作注意 :修改通道映射通常应在所有相关DMA通道禁用、且无进行中传输时进行。
5. 高级应用模式与性能优化技巧
掌握了基础配置和寄存器操作后,我们可以探索一些高级用法和优化策略,让μDMA发挥最大效能。
5.1 Ping-Pong模式实现零延迟数据流
如前所述,Ping-Pong模式是处理连续流数据的利器。其核心思想是 双缓冲 :DMA在向缓冲区A填充数据时,CPU可以处理缓冲区B中的数据,反之亦然。实现的关键在于中断服务程序(ISR)必须高效、快速。
优化技巧 :
- ISR最小化 :在ISR中只做最必要的工作——判断缓冲区、设置数据就绪标志、重新武装DMA控制结构。复杂的数据处理应放到主循环或低优先级任务中,基于标志位进行。
-
缓冲区大小与ARBSIZE的权衡
:缓冲区大小最好是
ARBSIZE的整数倍。例如,ARBSIZE设置为64(即一次突发传输64项),缓冲区大小为256项,这样每次中断对应4次完整的突发传输,效率最高。如果缓冲区大小不是其整数倍,最后一次传输会以单次请求形式完成,可能略微影响时序。 -
使用
NXTUSEBURST位 :在控制字的NXTUSEBURST位。如果设置此位,即使在最后一次传输(剩余项数小于ARBSIZE)时,控制器也会尝试使用突发传输。这可以保证传输的突发连续性,但要求外设支持。
5.2 使用Scatter-Gather处理复杂传输链
对于需要从多个非连续源地址向多个非连续目的地址传输数据的场景,手动配置多次DMA传输很繁琐。Scatter-Gather模式可以解决这个问题。
操作流程 :
-
在内存中创建一个“任务描述符”数组。每个描述符就是一个完整的μDMA通道控制结构(
DMASRCENDP,DMADSTENDP,DMACHCTL)。 -
将主控结构的源地址指向这个任务描述符数组的起始地址,目的地址指向备用控制结构所在的内存地址,传输模式设置为
Memory Scatter-Gather (0x4),传输大小设置为任务描述符的大小(以数据项计)。 -
将备用控制结构的传输模式设置为
Alternate Memory Scatter-Gather (0x5),并配置好第一次实际传输的参数(源、目的、大小等)。 - 启动传输。μDMA会使用主结构,将第一个任务描述符从数组“搬运”到备用结构中,然后备用结构开始执行这个实际的DMA传输。
-
第一个任务完成后,硬件会自动用主结构搬运第二个任务描述符到备用结构,并继续执行,如此循环,直到遇到一个
XFERMODE不是0x5的描述符为止。
这非常适合 :
- 将一幅图像的多个位平面(分散存储)搬运到LCD显存的连续区域。
- 从多个不同的传感器寄存器读取数据到��一的数据包缓冲区。
5.3 性能调优与常见问题排查
1. 总线竞争与仲裁优先级 μDMA与CPU、其他总线主设备共享系统总线。频繁的DMA传输可能阻塞CPU访问Flash或SRAM,导致CPU性能下降。优化策略:
-
合理设置
ARBSIZE:增大ARBSIZE让DMA单次占用总线时间变长,整体效率高,但可能增加CPU的访问延迟。减小ARBSIZE则相反。需要根据系统实时性要求权衡。 -
利用通道优先级
:通过
DMAPRIOSET为关键数据通道设置高优先级。 - 调整存储器加速模块 :确保芯片的存储器加速模块(如预取指、缓存)已使能,可以缓解总线竞争的影响。
2. 传输错误与调试
- 总线错误 :如果DMA试图访问无效的或受保护的内存地址,会触发总线错误(BusFault)。检查源/目的地址指针是否正确,是否越界。
-
传输未完成/中断不触发
:
-
检查
DMAENASET和DMACFG.MASTEN是否已使能。 - 检查外设的DMA请求是否已使能(例如,UART的DMACTL寄存器)。
-
检查
DMAREQMASKSET是否意外屏蔽了该通道的请求。 -
使用调试器查看
DMASTAT寄存器的STATE字段,确认DMA状态机是否在运行。 -
检查通道控制结构中的
XFERMODE是否已正确设置为非Stop值。
-
检查
-
数据错位或损坏
:
-
地址对齐
:确保
SRCINC/DSTINC与SRCSIZE/DSTSIZE匹配,且地址符合对齐要求。 -
缓冲区溢出
:确保
XFERSIZE设置正确,且目的缓冲区足够大。 - 时钟问题 :确保μDMA和外设的时钟都已使能且稳定。
-
地址对齐
:确保
3. 低功耗模式下的考虑 在微控制器进入深度睡眠时,系统时钟可能关闭,DMA传输会停止。如果需要在低功耗模式下维持DMA(例如从低速ADC采样),则需确保DMA时钟源(如内部低速振荡器)在睡眠模式下仍然有效,并配置相应的功耗模式。
6. 实战案例:配置ADC序列采样与DMA传输
让我们结合一个具体案例,将上述所有知识点串联起来:使用Tiva TM4C123的ADC0序列采样器(Sequencer)和μDMA,实现连续采集4个模拟通道,并通过Ping-Pong模式将数据存入内存。
目标 :ADC0序列采样器(SS3)以1Msps速率循环采样AIN0-AIN3四个通道,通过μDMA通道8(ADC0 SS3的默认DMA通道)将数据搬运到内存中的双缓冲区。
步骤 :
-
初始化系统与引脚 :使能ADC0和μDMA模块时钟,配置AIN0-AIN3对应的GPIO引脚为模拟输入。
-
配置ADC采样序列 :
// 选择采样序列SS3,配置为软件触发,优先级0 ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0); // 为SS3的4个步进分别配置采样通道(AIN0, AIN1, AIN2, AIN3) ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH0); ADCSequenceStepConfigure(ADC0_BASE, 3, 1, ADC_CTL_CH1); ADCSequenceStepConfigure(ADC0_BASE, 3, 2, ADC_CTL_CH2); ADCSequenceStepConfigure(ADC0_BASE, 3, 3, ADC_CTL_CH3 | ADC_CTL_IE | ADC_CTL_END); // 使能ADC0 SS3的DMA请求 ADCSequenceDMAEnable(ADC0_BASE, 3); // 使能采样序列SS3 ADCSequenceEnable(ADC0_BASE, 3); -
分配与初始化μDMA控制表及缓冲区 :
__attribute__((aligned(1024))) static uint32_t uDMA_CtrlTable[256]; static uint16_t adc_ping_buffer[4 * 256]; // 4通道 * 256次采样 static uint16_t adc_pong_buffer[4 * 256]; -
配置μDMA通道8为Ping-Pong模式 :
// 设置控制表基地址 HWREG(UDMA_CTLBASE) = (uint32_t)uDMA_CtrlTable; // 获取主/备结构指针(通道8) tDMAControlTable *pPri = (tDMAControlTable*)&uDMA_CtrlTable[8 * 4]; // 8通道 * 4字/通道 tDMAControlTable *pAlt = (tDMAControlTable*)&uDMA_CtrlTable[(8 * 4) + (0x200 / 4)]; // 配置主结构(指向ping缓冲区) pPri->srcEndAddr = (void*)(ADC0_BASE + ADC_O_SSFIFO3); // ADC FIFO地址 pPri->dstEndAddr = (void*)(&adc_ping_buffer[4*256 - 1]); // 指向缓冲区末尾 pPri->control = UDMA_CHCTL_DSTINC_HALF | // 目的地址半字递增 UDMA_CHCTL_DSTSIZE_HALF | UDMA_CHCTL_SRCINC_NONE | // ADC FIFO地址固定 UDMA_CHCTL_SRCSIZE_HALF | // ADC数据为12位,存储在16位FIFO中 UDMA_CHCTL_ARBSIZE_16 | // 仲裁大小16项 ((4*256 - 1) << 4) | // 传输总数:4通道*256次 -1 UDMA_CHCTL_XFERMODE_PINGPONG; // 配置备结构(指向pong缓冲区),参数类似,仅目的地址不同 pAlt->srcEndAddr = (void*)(ADC0_BASE + ADC_O_SSFIFO3); pAlt->dstEndAddr = (void*)(&adc_pong_buffer[4*256 - 1]); pAlt->control = ... // 与主结构相同的控制字,模式也为PINGPONG -
使能μDMA控制器与通道,配置中断 :
// 使能μDMA控制器 HWREG(UDMA_CFG) = UDMA_CFG_MASTEN_ENABLE; // 使能通道8 HWREG(UDMA_ENASET) = (1 << 8); // 配置μDMA中断,使能通道8传输完成中断 // ... (依赖具体的中断控制器配置,如NVIC) -
编写中断服务程序 :如前文所述,在ISR中判断完成的缓冲区,处理数据(如求平均、发送到上位机),并重新武装对应的控制结构。
-
启动传输 :通过软件触发ADC序列采样,或配置定时器触发。一旦ADC开始转换并产生数据,DMA传输便会自动开始。
ADCProcessorTrigger(ADC0_BASE, 3); // 软件触发一次 // 或者配置定时器周期性触发ADC
通过这个案例,你可以看到,一个高效的实时数据采集系统是如何构建的。CPU几乎不参与数据搬运,只在缓冲区满时被中断通知去处理数据,从而有充足的时间运行复杂的控制算法或通信协议。这正是μDMA在嵌入式系统中的核心价值所在。



433

被折叠的 条评论
为什么被折叠?



