
1. 项目概述为什么我们需要DMA在嵌入式系统开发尤其是像TMS320x2806x这类高性能数字信号控制器DSC的应用中我们常常面临一个核心矛盾CPU的计算能力很强但它的时间非常宝贵。想象一下你正在运行一个复杂的电机控制算法或者处理一个音频流此时模数转换器ADC以每秒百万次的速率采集数据。如果每来一个数据都需要CPU停下手中的计算去执行一次“从ADC结果寄存器读取数据再写入内存”的简单搬运工作那无疑是巨大的浪费。CPU就像一位顶尖的工程师你却让他每天花大量时间在收发室签收快递这显然不是最优的人力配置。直接内存访问DMA模块就是为了解决这个“快递搬运”问题而生的硬件搬运工。它的核心思想极其直接在内存如RAM和外设如ADC、串口、USB之间开辟一条独立于CPU的数据高速公路。当外设产生数据或者需要发送数据时由DMA控制器这个“专用快递员”直接完成搬运全程无需CPU插手。CPU只需要在数据搬运开始前告诉DMA“把这批货从A地搬到B地搬完了通知我一声”然后就可以继续专心执行它的核心计算任务。对于TMS320x2806x来说其集成的DMA模块远不止一个简单的搬运工。它更像一个智能物流中心拥有6个独立的运输通道通道能根据29种不同的“订单来源”外设中断触发启动运输还能在运输过程中对货物进行“分拣重组”数据重排和“交替装卸”乒乓缓冲。这种能力对于将ADC采集的杂乱数据流整理成便于CPU进行快速傅里叶变换FFT或滤波处理的整齐数据块或者实现无延迟的音频流双缓冲至关重要。理解并驾驭这个模块是从嵌入式新手迈向系统优化高手的关键一步。2. DMA核心架构与工作原理解析要高效使用DMA不能只停留在“配置-使用”的层面必须深入其内部架构明白它如何运转才能规避陷阱发挥最大效能。TMS320x2806x的DMA模块是一个精密的、事件驱动的状态机系统。2.1 模块级架构与总线交互从芯片顶层看DMA模块通过专用的DMA总线与特定资源相连。这条总线包含22位地址线、32位数据读总线和32位数据写总线。关键点在于并非所有内存和外设都能被DMA访问。在TMS320x2806x上DMA可以访问的资源主要包括L5-L8 SARAM这是四块32K x 16位的静态RAM是DMA与CPU交换数据的主要舞台。ADC结果寄存器ADC转换完成的数据就放在这里DMA可以直接读取。McBSP多通道缓冲串行口数据寄存器用于高速串行通信数据的收发。ePWM/HRPWM寄存器DMA可以读写这些寄存器实现波形参数的动态更新。USB端点缓冲区用于USB数据传输。注意CPU和DMA共享对这些资源的访问权限。当它们同时访问同一资源如同时读写L5 RAM时会触发仲裁。DMA通常拥有更高的优先级CPU访问会被暂时挂起Stall。这意味着如果DMA长时间霸占总线例如配置了超大的单次传输ONESHOT模式会导致CPU性能急剧下降。因此合理规划DMA传输的爆发Burst大小和时机是系统性能调优的核心。2.2 事件触发机制DMA的“发令枪”DMA是一个“事件驱动”的模块它自己不会主动发起传输必须等待一个触发信号。这个信号来源于外设中断。触发源选择每个DMA通道的MODE.CHx[PERINTSEL]位域独立配置其触发源。选项非常丰富包括ADC中断1/2、外部中断XINT1-3、CPU定时器溢出、McBSP缓冲区空/满、ePWM的ADC启动转换信号、USB端点缓冲区事件等。例如你可以将通道1配置为ADCINT1触发这样每次ADC序列1转换完成就会自动启动一次DMA传输。软件强制触发除了硬件事件软件可以通过置位CONTROL.CHx[PERINTFRC]来模拟一个触发事件这在调试和初始化时非常有用。中断标志与清除当触发事件发生时CONTROL.CHx[PERINTFLG]标志位被置起。DMA状态机在开始为该通道服务即开始一次Burst传输时会自动清除此标志并向触发源发送一个清除信号以允许下一个中断事件产生。这个机制保证了事件和传输的同步。2.3 通道优先级仲裁谁先谁后的规则当多个DMA通道同时有传输请求时需要仲裁机制来决定服务顺序。TMS320x2806x的DMA支持两种模式轮询模式所有6个通道优先级相同。状态机按照CH1 - CH2 - CH3 - CH4 - CH5 - CH6 - CH1...的顺序循环服务。如果一个通道正在传输新到达的请求会按此顺序排队。这里有一个容易误解的细节假设当前正在服务CH4此时CH1和CH5的请求到达。服务完CH4的当前Burst后下一个被服务的将是CH5轮询顺序中CH4的下一个是CH5然后才是CH1。这确保了公平性防止低编号通道垄断。通道1高优先级模式此模式下CH1拥有绝对优先权。如果CH1的触发事件到来无论当前正在服务哪个通道假设是CHxDMA都会在完成当前字的传输或下一个字传输后立即挂起CHx转而完整地服务CH1的一个Burst。待CH1的Burst完成后再恢复被挂起的CHx通道继续执行。其他通道CH2-CH6之间仍采用轮询模式。实操心得高优先级模式通常留给数据率最高、最不容丢失的外设例如高速ADC。但使用时需格外小心因为频繁的CH1高优先级中断会打断其他通道的传输增加其延迟。务必评估所有通道的实时性要求。一个重要的限制是通道1的高优先级模式和ONESHOT模式不能同时启用。3. DMA传输的精细控制地址指针与状态机DMA的强大与灵活很大程度上体现在其对传输过程的精细控制上。这通过一系列地址指针寄存器、步进寄存器和模式位来实现其核心是一个两层嵌套循环的状态机。3.1 核心概念Burst与Transfer理解DMA传输首先要分清两个循环层级Burst突发这是DMA响应一次触发事件所传输的最小数据单元。其大小由BURST_SIZE寄存器定义最大为32个16位字。一次Burst传输是连续、不可被其他通道中断的除非通道1高优先级模式。Transfer传输这是一个更大的概念由多次Burst组成。TRANSFER_SIZE寄存器定义了完成整个传输需要多少个Burst。完成整个Transfer后可以产生一次CPU中断如果使能。例如你需要从ADC搬运1024个采样点到RAM。你可以配置BURST_SIZE 16字TRANSFER_SIZE 64次。那么每次ADC中断触发DMA会连续搬运16个字一个Burst。需要64次ADC中断64个Burst才能完成全部1024个字的传输并在最后产生一次CPU中断通知其处理整块数据。3.2 地址指针的“舞蹈”SRC_ADDR, DST_ADDR与WrapDMA通道有两套地址指针源地址SRC_ADDR和目的地址DST_ADDR。每套指针又包含“影子寄存器”和“活动寄存器”。影子寄存器由CPU配置当一次传输开始时影子寄存器的值被复制到活动寄存器由DMA硬件在传输过程中动态修改。这种“影子-活动”机制是实现乒乓缓冲等高级功能的基础。地址指针在传输过程中的移动由三组“步进”值控制Burst步进在每个Burst内部每传输一个字后/目的地址指针增加或减少SRC/DST_BURST_STEP值。对于访问固定地址的外设寄存器如McBSP的DRR此值应设为0。Transfer步进当一个Burst完成准备下一个Burst时地址指针会增加SRC/DST_TRANSFER_STEP值。这常用于访问内存中间隔排列的数据块。Wrap步进与地址回绕这是实现环形缓冲区Circular Buffer或乒乓缓冲的关键。SRC/DST_WRAP_SIZE定义了经过多少次Burst后地址指针需要“回绕”。当WRAP_COUNT减到0时发生回绕首先BEG_ADDR起始地址指针增加WRAP_STEP然后这个新的BEG_ADDR被加载到ADDR中。WRAP_COUNT则被重置为WRAP_SIZE。一个典型乒乓缓冲配置示例 假设我们有两个连续的RAM缓冲区BufA地址0x8000和BufB地址0x8100每个缓冲区大小对应8个Burst。我们希望DMA在写满BufA后自动跳转到BufB开始写如此循环。DST_ADDR_SHADOW 0x8000(初始指向BufA起始)DST_BEG_ADDR_SHADOW 0x8000(初始回绕地址)DST_BURST_STEP 2(假设32位传输地址2)DST_TRANSFER_STEP 0(在Wrap模式下此步进在回绕时无效)DST_WRAP_SIZE 8(每8个Burst回绕一次)DST_WRAP_STEP 0x100(0x8100 - 0x8000即缓冲区大小)这样DMA会先向BufA写8个Burst的数据然后BEG_ADDR增加0x100变为0x8100指向BufB并被加载到ADDR接着向BufB写入数据。同时CPU可以在DMA写BufB时处理BufA中的数据实现无冲突的数据生产和消费。3.3 关键模式位解析ONESHOT单次触发完成整个传输此位置1时一次外设触发将导致DMA连续进行Burst传输直到整个TRANSFER完成。这可以最大化传输效率但会长时间占用DMA总线可能阻塞其他通道并导致CPU停顿。必须谨慎使用通常用于对实时性要求不高的大块数据搬运。CONTINUOUS连续模式此位置1时完成一个TRANSFER后通道的RUNSTS位不会自动清零通道保持使能状态等待下一个触发开始新一轮传输。这对于需要持续不断搬运数据的流式应用非常有用。CHINTMODE通道中断模式决定DMA通道中断是在一个TRANSFER开始时产生还是在结束时产生。在乒乓缓冲配合CONTINUOUS模式的应用中通常设置为“开始中断”这样CPU一收到中断就知道DMA即将开始向另一个缓冲区写数据而前一个缓冲区已经就绪可供处理。4. TMS320x2806x DMA配置与实战指南理论最终要服务于实践。下面我们以最典型的应用——将ADC的采样数据通过DMA搬运到RAM并整理成块——为例详细拆解配置步骤和代码片段。4.1 场景设定与初始化流程目标ADC序列1ADCINT1每完成一次16通道的转换就触发DMA通道1将16个结果寄存器每个结果12位存放在16位寄存器中搬运到L5SARAM中的一个数组并配置为乒乓缓冲模式方便CPU处理。步骤1外设与内存规划ADC配置ADC工作在序列发生器模式16通道转换结束产生ADCINT1中断。内存在L5SARAM中定义两个缓冲区AdcBufA[16]和AdcBufB[16]。每个缓冲区刚好容纳一次ADC序列转换的所有结果。DMA通道使用通道1配置为高优先级模式因ADC数据率高。步骤2DMA通道寄存器配置以下是关键的寄存器配置思路和示例代码片段使用C语言和TI的驱动程序库风格// 假设基地址定义 #define DMA_CH1_BASE 0x00001000 // DMA通道1寄存器组基地址 volatile struct DMA_CH_REGS* DmaCh1 (volatile struct DMA_CH_REGS*)DMA_CH1_BASE; // 1. 配置模式寄存器 MODE DmaCh1-MODE.bit.PERINTSEL 1; // 触发源选择 ADCINT1 (根据手册映射表) DmaCh1-MODE.bit.PERINTE 1; // 使能外设中断触发 DmaCh1-MODE.bit.CHINTE 1; // 使能DMA通道中断通知CPU DmaCh1-MODE.bit.CHINTMODE 0; // 中断产生在Transfer开始时乒乓缓冲常用 DmaCh1-MODE.bit.ONESHOT 0; // 禁用每个ADCINT1触发只搬运一个Burst DmaCh1-MODE.bit.CONTINUOUS 1;// 连续模式完成一次Transfer后通道保持使能 DmaCh1-MODE.bit.OVRINTE 1; // 使能过载错误中断建议开启 // 2. 配置地址指针影子寄存器 DmaCh1-SRC_ADDR_SHADOW (Uint32)AdcResult.ADCRESULT0; // ADC结果寄存器起始地址 DmaCh1-DST_ADDR_SHADOW (Uint32)AdcBufA[0]; // 初始目的地址指向BufA DmaCh1-SRC_BEG_ADDR_SHADOW (Uint32)AdcResult.ADCRESULT0; // 源地址固定无需Wrap DmaCh1-DST_BEG_ADDR_SHADOW (Uint32)AdcBufA[0]; // 目的起始回绕地址 // 3. 配置步进与大小 DmaCh1-SRC_BURST_STEP 0; // 源是固定寄存器组Burst内地址不变 DmaCh1-DST_BURST_STEP 2; // 目的地址在Burst内每次216位字地址实际移动一个16位数据 DmaCh1-SRC_TRANSFER_STEP 0; // 源地址在Transfer间不变 DmaCh1-DST_TRANSFER_STEP 0; // 使用Wrap此步进无效 DmaCh1-SRC_WRAP_STEP 0; // 源地址无需回绕 DmaCh1-DST_WRAP_STEP 32; // 两个缓冲区间隔 16个字 * 2字节/字 32 字节 DmaCh1-BURST_SIZE 15; // 注意写入值为 N-1 16个字对应写入15 DmaCh1-SRC_WRAP_SIZE 0; // 源地址不回绕 DmaCh1-DST_WRAP_SIZE 1; // 每完成 1 个 Burst 就检查Wrap不对 // 我们需要每完成一次完整的Transfer即一次乒乓才Wrap。 // 但这里Burst Size16 Transfer Size1所以Wrap Size也应设为1。 // 更通用的如果Transfer Size 1 Wrap Size应等于Transfer Size。 DmaCh1-TRANSFER_SIZE 0; // 每个Transfer包含 1 个 Burst (写入值为0) // 4. 配置控制寄存器 CONTROL最后使能通道 DmaCh1-CONTROL.bit.RUNSTS 1; // 使能通道运行 // PERINTFRC 可用于软件触发测试正常运行时由ADC硬件触发步骤3PIE与CPU中断服务程序配置在PIE中断向量表中使能对应的DMA通道1中断例如INT7.1。编写DMA通道1的中断服务函数DMA_CH1_ISR。在该函数中检查是传输开始中断还是过载错误中断。如果是传输开始中断根据当前DMA正在写入的缓冲区通过检查DST_BEG_ADDR或自定义标志切换CPU去处理另一个已满的缓冲区。清除DMA通道中断标志DMA-CONTROL.bit.CHxINTFLGCLR 1和PIE中断应答位。4.2 性能估算与优化根据手册给出的流水线时序我们可以估算传输耗时公式总周期数 突发次数 * [ (4周期/字 * 每突发字数) 1 ]本例BURST_SIZE16(16位字)每次触发传输1个Burst。耗时 1 * [(4 * 16) 1] 65个系统时钟周期。优化如果ADC结果寄存器支持32位访问取决于具体型号和配置且数据对齐可以将MODE.DATASIZE设为32位。此时16个16位数据被视为8个32位“字”。新耗时 1 * [(4 * 8) 1] 33个系统时钟周期效率提升近一倍。注意事项使用32位传输时务必确保源地址和目的地址都是32位对齐的地址是4的倍数否则可能导致数据错误或性能下降。同时地址步进寄存器BURST_STEP,TRANSFER_STEP,WRAP_STEP的值始终代表16位地址的增量。因此对于32位传输若想地址指针移动一个32位数据4字节步进值应设置为2。5. 常见问题排查与调试技巧实录即使理解了原理实际调试DMA时常会遇到各种问题。以下是我在项目中积累的一些常见问题与解决方法。5.1 DMA传输完全不启动检查触发源确认外设是否确实产生了中断信号。可以通过监控外设中断标志位或先使用CPU中断服务程序来验证。检查通道使能CONTROL.RUNSTS位是否已置1这是最容易被忽略的一步。检查外设触发使能MODE.PERINTE位是否使能检查PIE配置虽然DMA传输本身不依赖CPU但DMA通道的中断如果使能了CHINTE需要PIE正确配置才能到达CPU。不过即使PIE未配DMA传输也应进行。更常见的是外设的中断信号是否正确地路由到了DMA模块这由MODE.PERINTSEL选择。软件触发测试尝试置位CONTROL.PERINTFRC软件强制触发看DMA是否启动。这是隔离硬件触发问题的最佳方法。5.2 数据错位或地址错误地址对齐确保源地址和目的地址符合访问宽度要求16位或32位对齐。步进值计算错误牢记所有SIZE寄存器写入值是N-1所有STEP寄存器值是基于16位地址的偏移量。32位传输时地址增量应为2。影子 vs 活动寄存器配置的是影子寄存器_SHADOW后缀。在一次传输进行中修改影子寄存器不会影响当前传输。新的配置会在下一次传输开始时生效。调试时可以读取活动寄存器无后缀来查看DMA硬件当前实际使用的地址。Wrap逻辑混淆WRAP_SIZE定义的是多少次Burst后回绕而不是多少次Transfer。如果TRANSFER_SIZE大于1需要仔细计算WRAP_SIZE以实现预期的缓冲区切换点。5.3 CPU性能骤降或系统卡顿ONESHOT模式滥用检查是否在某个通道上使能了ONESHOT并设置了巨大的TRANSFER_SIZE。这会导致该通道长时间独占DMA总线阻塞其他通道和CPU访问共享内存。改为由定时器触发单个Burst的模式。通道1高优先级模式的影响如果通道1触发非常频繁它会不断打断其他通道的传输。评估其他通道的延迟容忍度必要时调整其数据率或缓冲深度。内存访问冲突CPU和DMA频繁访问同一块RAM如L5。使用乒乓缓冲策略确保CPU和DMA分别操作不同的缓冲区。例如DMA向BufA写时CPU处理BufB下一轮交换。5.4 过载错误OVRFLG原因DMA处理速度跟不上外设数据产生速度。在两个触发事件之间DMA未能完成前一个Burst并清除PERINTFLG标志导致第二个触发事件丢失OVRFLG置位。解决方法优化DMA性能增大BURST_SIZE减少每次触发时的开销使用32位传输确保源/目的位于DMA能全速访问的内存如SARAM而不是慢速内存。降低外设数据率如果可能降低ADC采样率或McBSP的通信波特率。增加缓冲区深度使用乒乓缓冲甚至多级缓冲为CPU处理争取更多时间。但这不能从根本上解决DMA搬运本身跟不上节奏的问题。启用过载中断在中断服务程序中检查并处理错误至少可以知道数据发生了丢失。调试DMA时结合芯片的仿真器和寄存器查看窗口至关重要。实时观察PERINTFLG、OVRFLG、地址活动寄存器的变化以及内存中的数据内容是定位问题最直接的手段。从一个简单的、可验证的配置开始例如用软件触发从RAM的一个固定地址搬运几个字到另一个地址逐步增加复杂度加入外设触发、Wrap、乒乓缓冲是稳健开发DMA驱动的有效路径。