1. 项目概述与核心价值
在嵌入式系统开发,尤其是基于ARM Cortex-M内核的微控制器项目中,直接存储器访问(DMA)技术是提升系统效率和实时性的关键。它就像一位不知疲倦的“数据搬运工”,能在CPU处理核心业务逻辑的同时,独立完成内存与外设之间的大批量数据搬运。对于TI的Tiva™系列微控制器(如TM4C123系列)而言,其集成的微型直接存储器访问(μDMA)控制器更是将这种能力精细化、灵活化,提供了多种高级传输模式。今天,我想深入聊聊其中一种极具实用价值但理解起来稍显复杂的模式:外设散聚模式,并手把手拆解与之紧密相关的核心寄存器配置。如果你正在为如何高效处理来自ADC、UART、SPI等外设的非连续、突发性数据流而头疼,或者觉得官方手册里关于寄存器的描述过于零散,那么这篇结合了实战经验的解析,或许能给你带来一些清晰的思路。
简单来说,外设散聚模式是为了应对一种特定场景:数据源(通常是外设)产生数据的时间和数量是不确定的、突发性的,但我们需要将这些零散到达的数据,按照某种预定的结构(比如一个环形缓冲区或一组分散的内存块)有序地存放起来。传统的DMA简单传输模式(如基本模式或Ping-Pong模式)可能难以高效处理这种“来多少搬多少,但必须放到指定位置”的需求。而μDMA的外设散聚模式,通过主、副两级控制结构体(Control Structure)的配合,完美解决了这个问题。理解这个模式,以及如何通过寄存器正确配置和控制它,是解锁Tiva微控制器高性能数据吞吐能力的重要一步。接下来,我将从设计思路、寄存器详解、实战配置到常见陷阱,为你层层剥开这层技术面纱。
2. μDMA外设散聚模式深度解析
2.1 模式核心思想与工作流程
要理解外设散聚模式,首先要跳出“一次性搬完所有数据”的固定思维。我们可以把它想象成一个智能的快递分拣系统:
- 主控制结构体:相当于总调度计划。它定义了整个传输任务的“蓝图”,包括源地址(通常是外设数据寄存器)、目的地址的初始指针、传输数据项的总大小(
XFERSIZE)、每个数据项的大小(XFEERSIZE通常为8/16/32位),以及最关键的工作模式——在此必须设置为“外设散聚模式”。 - 副控制结构体:相当于当前批次的分拣指令。它包含了一组“子任务”的描述,主要是本次传输的仲裁大小(
ARBSIZE)。这个大小定义了μDMA控制器每次响应外设请求时,一口气连续搬运的数据项数量。 - 外设请求:相当于快递包裹到达的触发信号。每个外设请求(可能是一个数据就绪信号)会触发一次DMA传输。
- 工作流程:
- 初始化主、副控制结构体,并将通道配置为外设散聚模式。
- 使能DMA通道。
- 外设产生一个请求(例如,ADC转换完成)。
- μDMA控制器不是查看主结构体中的总
XFERSIZE,而是立即查看副结构体中的ARBSIZE。 - 控制器按照
ARBSIZE指定的数量,执行一次数据传输(例如,ARBSIZE=4,则一次搬4个数据项)。 - 一次
ARBSIZE大小的传输完成后,控制器会自动更新副控制结构体中的源/目的地址指针(根据主结构体中定义的地址增量模式),为下一次触发做准备。同时,主结构体中的XFERSIZE会减去本次已传输的数量。 - 等待下一个外设请求,重复步骤4-6。
- 当主结构体中的
XFERSIZE减为0时,表示整个散聚传输任务完成,产生传输完成中断(如果使能了)。
与存储器散聚模式的关键区别:输入材料中已经点明,外设散聚模式与存储器散聚模式流程基本一致。核心区别在于每次触发时传输数量的依据。存储器散聚模式下,每次触发是按照副结构体中的XFERSIZE(子任务总大小)执行,适合从内存中分散读取数据到外设。而外设散聚模式,每次触发是按照副结构体中的ARBSIZE(仲裁大小)执行,更适合应对外设数据突发到达的场景。ARBSIZE通常设置为与外设的FIFO深度或期望的突发传输长度相匹配,以实现总线利用效率最大化。
2.2 控制结构体配置要点
在TivaWare固件库中,控制结构体通常用tDMAControlTable数组来定义。每个通道占用两个这样的结构体(主和副)。对于外设散聚模式,我们需要正确填充这两个结构体的字段。以下是一个概念性的伪代码示例,展示了关键字段的设置思路:
// 假设控制表基地址已对齐到1024字节边界,例如定义在特定段“.dma_table” extern tDMAControlTable g_dmaControlTable[]; // 配置通道5为外设散聚模式,从UART0数据寄存器读取数据,散列存入内存数组 void ConfigurePeripheralScatterChannel5(void) { tDMAControlTable *pPrimary = &g_dmaControlTable[5 * 2]; // 通道5的主结构体 tDMAControlTable *pAlternate = &g_dmaControlTable[5 * 2 + 1]; // 通道5的副结构体 // 1. 配置主控制结构体 pPrimary->ui32SrcEndAddr = (uint32_t)&UART0_DR_R; // 源地址:UART0数据寄存器(固定) pPrimary->ui32DstEndAddr = (uint32_t)&g_uartBuffer[0]; // 目的地址:内存缓冲区起始地址 pPrimary->ui32Control = DMA_DST_INC_8 | // 目的地址按字节递增(假设g_uartBuffer是uint8_t数组) DMA_SRC_INC_NONE | // 源地址(外设寄存器)不递增 DMA_SIZE_8 | // 传输数据大小:8位 (DMA_MODE_PER_SCATTER << 4) | // 关键!工作模式:外设散聚模式 (TOTAL_TRANSFER_COUNT << 8); // XFERSIZE: 总传输数据项数量,例如128字节 // 2. 配置副控制结构体 pAlternate->ui32SrcEndAddr = 0; // 在外设散聚模式下,副结构体的源/目的地址初始值通常由硬件自动从主结构体加载,但安全起见可设为0或与主结构体一致。 pAlternate->ui32DstEndAddr = 0; pAlternate->ui32Control = DMA_DST_INC_8 | // 地址增量模式应与主结构体一致 DMA_SRC_INC_NONE | DMA_SIZE_8 | (DMA_MODE_ALT_PER_SCATTER << 4) | // 关键!副结构体工作模式:副外设散聚模式 (ARB_SIZE_4 << 8); // ARBSIZE: 每次外设请求触发时,连续传输4个数据项 }注意:
DMA_MODE_PER_SCATTER和DMA_MODE_ALT_PER_SCATTER是TivaWare中定义的宏,具体数值需参考数据手册。ARB_SIZE_4也是一个宏,表示仲裁大小为4。TOTAL_TRANSFER_COUNT是你的总传输字节数。
实操心得:配置时最容易混淆的是主、副结构体的ui32Control字段。务必确保主结构体的模式位设置为外设散聚模式,副结构体设置为副外设散聚模式。一个常见的错误是副结构体模式设置错误,导致DMA控制器无法在每次触发时正确识别ARBSIZE,从而行为异常。另外,ARBSIZE的设置需要权衡:设置太小,可能无法充分利用总线带宽,频繁响应请求增加开销;设置太大,如果外设数据产生慢,会导致DMA等待,可能增加延迟。通常建议设置为外设FIFO深度的一半或相等。
3. 核心寄存器详解与配置实战
理解了模式,我们再来啃硬骨头——寄存器。输入材料提供了从DMASTAT到DMAERRCLR共17个寄存器的详细信息。我不会照本宣科地罗列所有位域,而是聚焦于启动和运行一个外设散聚传输所必须配置和关注的几个核心寄存器,并结合代码讲解如何操作。
3.1 基础配置三剑客:DMACFG, DMACTLBASE, DMAENASET
在开始任何通道配置前,必须初始化μDMA控制器全局环境。
1. DMA配置寄存器 (DMACFG) - 偏移 0x004这个寄存器只有一个有效位:MASTEN(位0)。它是μDMA控制器的总开关。
- 作用:写入1,使能整个μDMA控制器模块;写入0,则禁用。禁用状态下,所有寄存器(除了DMASTAT等少数)可能无法访问。
- 配置代码示例:
HWREG(UDMA_BASE + UDMA_O_CFG) = UDMA_CFG_MASTEN_ENABLE; // 使能μDMA控制器注意:务必在配置任何通道或控制表之前使能
MASTEN。有些工程师习惯最后才打开,但在Tiva的μDMA中,必须先使能才能正确写入某些寄存器(如DMACTLBASE)。
2. DMA通道控制基指针寄存器 (DMACTLBASE) - 偏移 0x008这是整个μDMA的“指挥中心”地址寄存器。
- 作用:告诉μDMA控制器,你在系统内存的哪个位置存放了那个至关重要的“控制结构体表”(即前面提到的
tDMAControlTable数组)。 - 关键约束:基地址必须1024字节对齐(即地址的低10位为0)。这是硬件要求,不对齐会导致不可预知的行为。
- 配置代码示例:
避坑指南:在链接器脚本(.ld文件)中为DMA控制表专门分配一个对齐的段是最稳妥的做法。例如,在TivaWare的启动文件里,常看到类似// 假设 g_dmaControlTable 已通过 __attribute__((aligned(1024))) 确保1024字节对齐 #define DMA_CONTROL_TABLE_BASE ((uint32_t)g_dmaControlTable) HWREG(UDMA_BASE + UDMA_O_CTLBASE) = DMA_CONTROL_TABLE_BASE;__attribute__((section(".dma_table")))的定义。绝对不要假设一个普通的全局数组会自动满足1024字节对齐。
3. DMA通道启用置位寄存器 (DMAENASET) - 偏移 0x028这是一个位图寄存器,每位对应一个DMA通道(例如位0对应通道0)。
- 作用:将某个位置1,使能对应的DMA通道。使能后,该通道才能响应请求(无论是外设请求还是软件请求)。
- 与外设散聚模式的关系:在配置好主副控制结构体、并将通道模式设置为外设散聚后,需要通过置位此寄存器的相应位来“激活”该通道的传输能力。
- 配置代码示例:
重要特性:当一个通道的传输任务全部完成(主结构体#define UDMA_CHANNEL_UART0_RX 5 // 假设UART0 RX使用通道5 HWREG(UDMA_BASE + UDMA_O_ENASET) = 1 << UDMA_CHANNEL_UART0_RX; // 使能通道5XFERSIZE耗尽)后,硬件会自动清除该通道在DMAENASET中的使能位。如果你想再次启动传输,需要重新置位,并通常也需要重新初始化控制结构体(或至少重置XFERSIZE)。
3.2 模式与优先级控制:DMAALTSET 与 DMAPRIOSET
1. DMA通道主副置位寄存器 (DMAALTSET) - 偏移 0x030
- 作用:手动控制某个通道是使用主控制结构体(位=0)还是副控制结构体(位=1)。
- 在外设散聚模式下的行为:这是一个关键点!输入材料备注中明确指出:“对于乒乓模式和散聚模式,μDMA控制器会自动将相应位置位,选择副控制结构体。” 这意味着,当你将通道配置为外设散聚模式并启动传输后,硬件会自动管理
DMAALTSET寄存器中该通道的位。在传输过程中,控制器会在主、副结构体之间自动切换(对于散聚模式,主要是使用副结构体中的ARBSIZE)。因此,在初始化后,我们通常不需要手动操作此寄存器。它的主要用途是查询当前通道正在使用哪个结构体。
2. DMA通道优先置位寄存器 (DMAPRIOSET) - 偏移 0x038
- 作用:设置特定通道为高优先级。当多个通道同时产生请求时,高优先级通道会被优先服务。
- 配置考量:对于实时性要求极高的外设(如高速ADC采样),可以将其DMA通道设置为高优先级。但需注意,滥用高优先级可能导致低优先级通道“饿死”。通常,系统默认所有通道为默认优先级(固定优先级或轮询调度,具体取决于芯片设计),已能满足大部分应用。
- 配置代码示例:
// 将UART0 RX DMA通道(通道5)设置为高优先级 HWREG(UDMA_BASE + UDMA_O_PRIOSET) = 1 << UDMA_CHANNEL_UART0_RX;
3.3 请求与错误管理:DMASWREQ, DMAUSEBURSTSET, DMAERRCLR
1. DMA通道软件请求寄存器 (DMASWREQ) - 偏移 0x014
- 作用:通过软件手动触发一次DMA传输请求。即使通道关联的外设没有产生硬件请求,也可以通过写此寄存器来启动传输。
- 在外设散聚模式下的应用:主要用于测试和初始化。你可以先配置好外设散聚通道,然后通过软件请求触发一次传输,验证控制结构体和地址增量逻辑是否正确,而无需依赖真实的外设数据。
// 向通道5发起一次软件DMA请求 HWREG(UDMA_BASE + UDMA_O_SWREQ) = 1 << UDMA_CHANNEL_UART0_RX; // 注意:该位会在请求被响应后自动清零
2. DMA通道采用猝发置位寄存器 (DMAUSEBURSTSET) - 偏移 0x018
- 作用:强制某个通道只响应外设的“猝发请求”(Burst Request),而忽略“单次请求”(Single Request)。
- 与外设散聚模式的协同:这是一个性能优化选项。如果外设支持产生猝发请求(表明它可以一次性提供多个数据),并且你设置的
ARBSIZE与猝发请求的大小匹配,那么设置此位可以确保DMA只在数据“攒够”一定数量后才启动传输,减少总线仲裁和启动开销,提升整体效率。 - 重要警告:如数据手册所述,“假如外设不支持猝发请求模型,则此寄存器中的相关位不得置位。” 盲目置位会导致DMA永远等不到符合条件的请求,传输无法开始。务必查阅具体外设(如UART、ADC)的文档,确认其DMA请求类型。
// 假设我们确认UART0支持猝发DMA请求,并设置通道5只响应猝发请求 HWREG(UDMA_BASE + UDMA_O_USEBURSTSET) = 1 << UDMA_CHANNEL_UART0_RX;
3. DMA总线错误清除寄存器 (DMAERRCLR) - 偏移 0x04C
- 作用:读取和清除总线错误状态标志(
ERRCLR位)。如果DMA控制器在传输过程中试图访问一个无效的或没有权限的内存地址,就会发生总线错误,此位被置1,并且出错通道会被自动禁用。 - 调试必备:当你的DMA传输莫名其妙停止,且通道使能位被清零时,第一件事就是检查这个寄存器。
// 检查并清除总线错误 if (HWREG(UDMA_BASE + UDMA_O_ERRCLR) & UDMA_ERRCLR_ERR) { UARTprintf("DMA Bus Error Occurred!\n"); HWREG(UDMA_BASE + UDMA_O_ERRCLR) = UDMA_ERRCLR_ERR; // 写1清除错误标志 // 需要重新初始化出错通道的控制结构体和使能位 }
4. 完整实战:配置UART外设散聚DMA接收
让我们结合一个具体场景,将上述所有知识点串联起来:使用μDMA的外设散聚模式,从UART0接收不定长的数据包,并自动将数据存入一个环形缓冲区。
4.1 场景与硬件连接
- MCU: TI TM4C123GH6PM
- 外设: UART0,波特率115200,8数据位,1停止位,无校验。
- 目标: UART0每收到一个字节,通过DMA自动存入一个1024字节的环形缓冲区
g_uartRingBuffer。 - DMA通道: 使用UART0的RX DMA请求,对应通道5(根据具体芯片数据手册确定)。
4.2 软件设计与代码实现
#include <stdint.h> #include <stdbool.h> #include "inc/hw_memmap.h" #include "inc/hw_types.h" #include "inc/hw_udma.h" #include "driverlib/udma.h" #include "driverlib/uart.h" #include "driverlib/sysctl.h" // 1. 定义对齐的DMA控制表(通常在启动文件或链接脚本中分配,此处为示例) // 使用编译器属性确保1024字节对齐 __attribute__((aligned(1024))) tDMAControlTable g_dmaControlTable[64]; // 为32个通道的主副结构体预留空间 // 2. 定义环形缓冲区 #define RING_BUFFER_SIZE 1024 static uint8_t g_uartRingBuffer[RING_BUFFER_SIZE]; static volatile uint32_t g_bufferWriteIndex = 0; // 由DMA ISR更新 // 3. DMA通道中断服务程序 void UDMA_Channel5_ISR(void) { uint32_t ui32Status = HWREG(UDMA_BASE + UDMA_O_STAT); // 检查是否是通道5传输完成中断 if (ui32Status & (1 << 5)) { // 清除中断标志(写1清除) HWREG(UDMA_BASE + UDMA_O_STATCLR) = (1 << 5); // 获取当前目的地址,计算写索引 // 注意:在外设散聚模式下,主结构体的目的地址指针可能不会自动更新到最终位置。 // 更可靠的方法是:在初始化时记录缓冲区起始地址,根据总传输量计算。 // 这里我们采用另一种常见策略:配置为Ping-Pong模式或使用半满/全满中断。 // 但为了示例简单,我们假设每次传输完成中断意味着主XFERSIZE耗尽。 // 在实际散聚模式中,可能需要在每次ARBSIZE传输后(即副结构体任务完成)处理数据。 // 这通常通过使能“副控制结构体使用完成”中断来实现,配置更复杂。 // 简化处理:打印提示,并重新配置通道以接收下一批数据(如果需要连续接收) UARTprintf("DMA Channel 5 (UART0 RX) transfer completed or reached a milestone.\n"); // 示例:重新初始化控制结构体,从缓冲区开头重新开始(非环形缓冲实现) // 在实际环形缓冲应用中,需要更新主结构体的目的地址到新的写入位置,并重新设置XFERSIZE。 // 注意:重新初始化前,最好先禁用通道。 HWREG(UDMA_BASE + UDMA_O_ENACLR) = (1 << 5); // 禁用通道5 // ... 重新配置 g_dmaControlTable[10] 和 g_dmaControlTable[11] ... // HWREG(UDMA_BASE + UDMA_O_ENASET) = (1 << 5); // 重新使能 } } // 4. 初始化μDMA控制器和UART0 DMA接收(外设散聚模式) void InitUART0_DMA_ScatterReceive(void) { // 4.1 使能外设时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0) || !SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA)) { // 等待外设就绪 } // 4.2 配置UART0引脚和基本参数(略) UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); UARTEnable(UART0_BASE); // 4.3 初始化μDMA控制表(必须在使用前清零) uDMAChannelControlBaseSet(g_dmaControlTable); // TivaWare库函数,内部设置了DMACTLBASE并清零表 // 4.4 配置通道5(UART0 RX)为主/副控制结构体(外设散聚模式) // 计算控制结构体指针:通道索引 * 2 为主, *2+1 为副 tDMAControlTable *pPrimary = &g_dmaControlTable[5 * 2]; tDMAControlTable *pAlternate = &g_dmaControlTable[5 * 2 + 1]; // 主控制结构体 pPrimary->ui32SrcEndAddr = UART0_BASE + UART_O_DR; // 源:UART数据寄存器 pPrimary->ui32DstEndAddr = (uint32_t)&g_uartRingBuffer[0]; // 目的:环形缓冲区起始 pPrimary->ui32Control = UDMA_DST_INC_8 | // 目的地址字节递增 UDMA_SRC_INC_NONE | // 源地址(外设)不递增 UDMA_SIZE_8 | // 传输大小8位 UDMA_ARB_4 | // 仲裁大小(ARBSIZE)在主结构体中似乎也需设置?注意:这里有个易错点! // 根据TivaWare手册,主结构体的XFERSIZE和ARBSIZE位域是复用的。 // 在设置模式后,某些位域含义会变化。对于外设散聚,主结构体的“size”字段应设置总传输数。 // 更安全的做法是使用库函数: // uDMAChannelTransferSet(UDMA_CHANNEL_UART0_RX, UDMA_MODE_PER_SCATTER, // (void*)(UART0_BASE+UART_O_DR), g_uartRingBuffer, BUFFER_SIZE); // 但为了理解原理,我们手动计算: ( (RING_BUFFER_SIZE) << 8 ) | // XFERSIZE: 总传输字节数(假设一次性填满环形缓冲) ( UDMA_MODE_PER_SCATTER_G << 4 ); // 模式:外设散聚,使用控制结构体中的ARBSIZE // 副控制结构体 pAlternate->ui32SrcEndAddr = UART0_BASE + UART_O_DR; // 通常与主结构体一致或忽略 pAlternate->ui32DstEndAddr = (uint32_t)&g_uartRingBuffer[0]; pAlternate->ui32Control = UDMA_DST_INC_8 | UDMA_SRC_INC_NONE | UDMA_SIZE_8 | ( 4 << 8 ) | // ARBSIZE: 每次UART请求,DMA搬4个字节(假设UART FIFO深度>=4) ( UDMA_MODE_ALT_PER_SCATTER << 4 ); // 模式:副外设散聚 // 4.5 配置UART0,使其在RX FIFO达到一定深度(例如4字节)时产生DMA请求 // 这需要配置UART的DMA控制寄存器,并非所有UART都支持按深度触发,可能仅支持“非空”触发。 // 假设我们使用“接收就绪”触发(每个字节触发一次)。 UARTDMAEnable(UART0_BASE, UART_DMA_RX); // 使能UART0的RX DMA // 4.6 配置DMA通道属性(优先级、猝发模式等) // 设置通道优先级(可选) uDMAChannelPrioritySet(UDMA_CHANNEL_UART0_RX, UDMA_PRI_HIGH); // 设置通道只使用猝发请求(如果UART支持且配置了猝发请求) // uDMAChannelRequestBurst(UDMA_CHANNEL_UART0_RX); // 慎用,需确认硬件支持 // 4.7 使能DMA通道5 uDMAChannelEnable(UDMA_CHANNEL_UART0_RX); // 4.8 注册并使能DMA通道中断(用于处理传输完成) uDMAChannelAssignInterrupt(UDMA_CHANNEL_UART0_RX, 0); // 分配中断,具体向量号查手册 uDMAIntRegister(UDMA_CHANNEL_UART0_RX, UDMA_Channel5_ISR); // 注册ISR uDMAChannelIntEnable(UDMA_CHANNEL_UART0_RX, UDMA_INT_TRANSFER_DONE); // 使能传输完成中断 IntEnable(INT_UDMA); // 使能μDMA总中断 }4.3 关键点分析与避坑
- 控制结构体字段的微妙之处:手动配置
ui32Control字段极易出错。XFERSIZE和ARBSIZE共享位域,其位置和含义受模式影响。强烈建议使用TivaWare库函数uDMAChannelTransferSet()来设置通道传输参数,库函数会帮你正确打包这些位域。上述手动配置代码主要是为了揭示原理。 - 中断处理:在外设散聚模式下,“传输完成”中断何时触发?是每次
ARBSIZE传输完成,还是主XFERSIZE耗尽?这取决于中断使能的配置。通常,我们更关心整个散聚任务(主XFERSIZE耗尽)的完成。但如果你需要在每次ARBSIZE传输后处理数据(例如更新环形缓冲区写指针),可能需要使用“副控制结构体使用完成”中断,这需要更精细的配置。 - 环形缓冲区管理:上面的简化示例没有实现真正的环形缓冲区管理。在实际应用中,DMA的目的地址指针需要能在到达缓冲区末尾时回绕到开头。这可以通过将DMA配置为“Ping-Pong”模式结合两个缓冲区来实现,或者使用更高级的“散聚-收集”列表模式。单纯的外设散聚模式,其目的地址是线性递增的,需要软件在中断中手动处理回绕和指针更新。
- 外设DMA触发条件:务必仔细阅读UART模块的数据手册,明确其DMA请求的产生条件。是每个字节产生一次请求,还是RX FIFO达到某个阈值才产生一次请求?这直接影响你设置的
ARBSIZE是否合理。如果UART只产生单次请求,而你设置了ARBSIZE=4并开启了DMAUSEBURSTSET,那么DMA将永远等不到猝发请求,传输无法开始。
5. 调试技巧与常见问题排查
即使按照手册一步步配置,DMA不工作也是嵌入式开发中的常事。以下是我在调试μDMA,特别是外设散聚模式时,总结的排查清单:
5.1 DMA传输根本未启动
- 检查1:总使能开关。确认
DMACFG.MASTEN位已置1。 - 检查2:控制表基地址。确认
DMACTLBASE寄存器写入的值是1024字节对齐的,并且指向你初始化的g_dmaControlTable数组。可以在调试器中查看该地址的内存内容,确认主副结构体的字段值是否正确。 - 检查3:通道使能。确认
DMAENASET寄存器中对应通道的位已置1。 - 检查4:外设DMA使能。确认外设模块本身的DMA功能已使能(例如UART的
UARTDMACTL寄存器)。 - 检查5:请求屏蔽。确认
DMAREQMASKSET寄存器没有屏蔽你的通道。如果被屏蔽,只有软件请求能触发传输。 - 检查6:软件请求测试。暂时忽略硬件触发,尝试向
DMASWREQ寄存器的对应位写1,手动触发一次传输。如果这样能工作,说明DMA通道本身配置基本正确,问题可能出在外设的请求信号上。
5.2 DMA启动一次后停止,无法连续传输
- 检查1:传输完成自动禁用。DMA通道在完成主
XFERSIZE指定的所有传输后,会自动清除DMAENASET中的使能位。这是正常行为。如果你需要循环传输,必须在中断服务程序中重新配置控制结构体(至少重置XFERSIZE和地址)并重新使能通道。 - 检查2:总线错误。读取
DMAERRCLR寄存器,检查ERRCLR位是否为1。如果是,说明DMA试图访问非法地址。检查你的源地址(外设寄存器地址)和目的地址(内存地址)是否有效,内存区域是否可写(例如,不是代码Flash区)。 - 检查3:控制结构体更新逻辑。在外设散聚模式下,硬件会在每次
ARBSIZE传输后自动更新副结构体中的地址指针。确保你的目的地址增量模式(DST_INC)设置正确。如果你的缓冲区指针没有按预期移动,检查这里。
5.3 数据传输错位或丢失
- 检查1:数据大小与地址增量不匹配。如果你设置传输大小为16位(
SIZE_16),但目的地址增量是8位(DST_INC_8),那么每传输一个16位数据,地址只增加1个字节,会导致数据覆盖。确保SIZE和*_INC设置匹配。 - 检查2:
ARBSIZE与外设不匹配。如果ARBSIZE设置得比外设单次能提供的数据量还大(例如,设置ARBSIZE=8,但UART FIFO深度只有4),DMA可能会在等待足够数据时超时或行为异常。根据外设能力设置合理的ARBSIZE。 - 检查3:中断服务程序处理太慢。如果DMA传输完成中断产生后,ISR没有及时处理(例如重新配置通道),而外设仍在产生数据,可能会导致数据丢失。优化ISR,或者使用双缓冲(Ping-Pong)模式来提供更宽松的处理时间。
5.4 性能未达预期
- 考虑使用猝发模式:如果外设支持,尝试使能
DMAUSEBURSTSET,并设置合适的ARBSIZE,让DMA以猝发方式传输,减少总线访问次数。 - 调整通道优先级:如果系统中有多个DMA通道竞争总线,为高带宽或实时性要求高的通道设置
DMAPRIOSET,确保其优先得到服务。 - 优化内存布局:确保DMA使用的源和目的内存位于支持高速访问的区域(如DTCM或SRAM),并且地址是对齐的(特别是32位访问时),以避免总线分割访问降低性能。
调试DMA时,逻辑分析仪或示波器是 invaluable 的工具。你可以抓取外设的请求信号、DMA的应答信号以及内存总线的活动,直观地看到传输是否被触发、何时发生、每次传输多少数据。结合芯片的调试模块(如ARM的ITM或ETM),可以更深入地追踪DMA控制器的内部状态。