1. 项目概述与DMA的核心价值在嵌入式系统开发尤其是实时控制和高性能数据采集领域我们常常面临一个核心矛盾CPU的算力是宝贵的但大量时间却被“搬砖”——也就是在内存和外设之间搬运数据——这类简单重复的I/O操作所消耗。想象一下你是一位工厂里最顶尖的工程师却不得不花大量时间亲自去仓库取零件、再把成品搬回仓库这无疑是巨大的人才浪费。直接内存访问DMA技术就是为了解决这个矛盾而生的“自动化物流系统”。DMA的本质是在芯片内部集成一个专司数据搬运的“协处理器”。当外设如ADC模数转换器产生了一批新数据或者内存中有一批数据需要发送给通信接口如SPI时不再是CPU停下手中的计算任务通过执行load/store指令来一个字节一个字节地搬运而是由DMA控制器接管这个任务。CPU只需要在初始化时告诉DMA“从A地址开始搬N个数据到B地址搬完了告诉我一声”就可以继续去执行核心算法或响应其他事件。DMA会在后台默默完成所有数据传输整个过程无需CPU干预从而将CPU彻底解放出来。以我手头常用的TI C2000系列微控制器特别是TMS320F2837xS这款高性能实时MCU为例其DMA模块的设计堪称典范。它不仅仅是一个简单的数据搬运工更是一个高度可编程、具备数据预处理能力的智能引擎。它支持多达6个独立通道每个通道都可以由不同的事件如ADC转换完成、定时器溢出、外部中断触发它能在传输过程中对数据进行“正交重排”把杂乱的数据流整理成便于CPU处理的规整块它还支持“乒乓缓冲”这种高级数据流管理技术实现数据搬运和处理的完美流水线化。理解并用好DMA是从嵌入式新手迈向资深工程师的关键一步它能让你设计的系统在性能、实时性和能效上产生质的飞跃。2. DMA架构与核心工作机制深度解析要驾驭DMA不能只停留在“配置寄存器让它动起来”的层面必须深入理解其内部架构和工作原理。这就像开车知道踩油门能走只是第一步了解发动机、变速箱和传动系统如何协同工作才能应对复杂路况发挥车辆的全部性能。2.1 整体架构与总线仲裁TMS320F2837xS的DMA模块是一个独立于CPU的子系统拥有自己的32位地址总线和32位数据读写总线。这意味着DMA可以和CPU并行工作访问共享的内存和外设资源。其架构核心是一个精巧的状态机和紧密耦合的地址控制逻辑。一个关键且容易忽略的细节是“公共外设架构”。芯片上的许多外设如ePWM、SPI的寄存器可以被CPU、DMA以及另一个协处理器CLA访问。这就引入了资源冲突的可能性。为了解决这个问题芯片内部有一个仲裁机制。例如当DMA和CPU在同一周期试图访问同一个外设时DMA拥有更高的优先级CPU的访问会被“阻塞”一个周期直到DMA操作完成。这种设计保证了DMA传输的确定性和低延迟但对于CPU端的实时任务规划提出了要求——你需要避免让CPU频繁访问那些可能被DMA占用的外设寄存器。注意这里有一个重要的实践细节。手册中特别警告如果CPU正在对一个内存位置进行“读-修改-写”操作例如对一个控制寄存器进行位操作而DMA恰好在这条指令的“读”和“写”之间向同一位置写入数据那么DMA的这次写入可能会丢失。因此在软件设计时应避免CPU和DMA对同一内存地址进行写操作或者使用信号量等机制进行保护。2.2 事件触发机制让数据动起来的“发令枪”DMA是一个“事件驱动”的机器它不会自己周期性地启动传输必须等待一个触发信号。这个设计非常符合嵌入式系统响应外部事件的本质。TMS320F2837xS为每个DMA通道提供了极其丰富的触发源选择通过DMACHSRCSELx和CHx.MODE.PERINTSEL寄存器进行配置。触发源列表几乎涵盖了所有可能产生数据的外设ADC模块四个ADC模块A, B, C, D各自的序列转换完成中断INT1-4或事件EVT这是数据采集应用最常用的触发源。ePWM模块12个ePWM模块的SOCA/SOCB启动转换信号可以实现与PWM波形严格同步的精确采样。外部中断XINT5个外部中断引脚可以将GPIO上的外部事件如传感器信号直接作为DMA触发。定时器中断TINT3个CPU定时器中断可以将DMA配置为定时启动的“软周期”传输。通信外设SPI的发送/接收FIFO就绪信号McBSP的多通道缓冲串行口事件等。软件触发通过设置CONTROL.CHx[PERINTFRC]位可以随时用软件强制触发一次DMA传输用于初始化或测试。触发逻辑内部有一个锁存器Latch。当外设产生一个触发事件PERINTFLG标志位会被置起表示该通道有一个传输请求“待处理”。DMA的状态机根据通道优先级依次处理这些待处理的请求。一旦某个通道的“突发传输”开始其PERINTFLG标志位就会被自动清除以准备接收下一个触发事件。这个机制是理解DMA流控的基础。3. DMA传输的精密控制地址指针与状态机DMA传输的控制逻辑本质上是由两个嵌套循环构成的状态机内层的“突发循环”和外层的“传输循环”。理解这两个循环以及相关的地址指针、步进和包装机制是进行复杂数据传输编程的关键。3.1 核心概念突发与传输突发Burst这是DMA响应一次触发事件所执行的最小传输单元。一次突发传输的数据量由BURST_SIZE寄存器定义。例如BURST_SIZE 15表示一次突发传输16个数据单元因为实际传输数是BURST_SIZE 1。这个数据单元可以是16位或32位由MODE.DATASIZE位决定。突发传输是原子性的一旦开始就会连续完成指定数量的数据传输期间不会被同优先级的其他通道打断。传输Transfer一个完整的DMA传输任务由多次突发构成。总突发次数由TRANSFER_SIZE寄存器定义。完成整个传输后DMA可以产生一个中断通知CPU。因此一个完整的DMA传输总共搬运的数据量是(BURST_SIZE 1) * (TRANSFER_SIZE 1)个数据单元。3.2 地址指针的“双缓冲”与步进控制DMA的地址管理非常灵活它有两套指针“影子寄存器”和“活动寄存器”。影子寄存器Shadow Registers如SRC_ADDR_SHADOW、DST_ADDR_SHADOW。这些寄存器由软件在初始化时配置代表了传输任务的“蓝图”。在传输过程中软件可以随时修改它们而不会影响正在进行的传输。活动寄存器Active Registers如SRC_ADDR_ACTIVE、DST_ADDR_ACTIVE。这些寄存器由硬件在每次传输开始时从对应的影子寄存器加载并在传输过程中实时更新指向当前正在读写的内存地址。地址指针的移动由三组“步进”寄存器控制它们都是有符号数支持地址递增或递减突发步进BURST_STEP在一次突发传输内部每完成一个数据的搬运后源地址和目的地址增加的偏移量。例如从ADC结果寄存器地址连续搬运数据到内存数组通常将SRC_BURST_STEP设为0因为总是读同一个ADC结果寄存器地址DST_BURST_STEP设为2如果数据宽度是16位地址2指向下一内存字。传输步进TRANSFER_STEP在两次突发传输之间即完成一次突发后、等待下一个触发事件前源地址和目的地址增加的偏移量。这常用于处理非连续的数据块。包装步进WRAP_STEP这是实现“乒乓缓冲”或“环形缓冲区”的关键。当使能地址包装功能后每完成一定数量的突发由WRAP_SIZE定义活动地址指针不会简单地加上TRANSFER_STEP而是会被重置为BEG_ADDR开始地址同时这个BEG_ADDR自身会加上WRAP_STEP。这实现了指针在多个缓冲区之间“跳跃”。3.3 关键工作模式OneShot与ContinuousOneShot模式此模式默认禁用。当ONESHOT0时每次触发事件只引起一次突发传输。这是最常用的模式确保了单个通道不会独占DMA总线。当ONESHOT1时一次触发事件将导致整个传输循环所有突发被连续执行完毕。这非常危险因为一次触发可能占用极长的总线时间阻塞其他所有通道仅在对实时性要求极高、且数据块较小的特定场景下谨慎使用。连续模式此模式默认禁用。当CONTINUOUS0时完成整个传输循环TRANSFER_COUNT减到0后该DMA通道会自动禁用需要软件重新设置RUN位才能开始下一次传输。当CONTINUOUS1时传输完成后通道保持使能状态自动等待下一轮触发适用于需要持续不断进行数据搬运的场景。3.4 数据传输的“流水线”与吞吐量计算DMA内部采用三级流水线操作发送源地址 - 读取源数据 - 写入目的数据。在理想无冲突情况下传输一个数据单元16位或32位需要3个时钟周期。但实际吞吐量还需考虑以下因素每个突发传输开始时有1个周期的固定开销。如果被高优先级通道如通道1打断恢复时会有1个周期的延迟。与CPU或CLA访问冲突时会产生额外的等待周期。最关键的一点32位传输的效率是16位的两倍。因为硬件总线是32位的传输一个32位字和传输一个16位字的时间几乎相同。我们来算一笔账假设需要从GS0 RAM搬运128个16位数据到GS3 RAM。方案A16位模式配置为8次突发每次突发16个数据。耗时 8 bursts * [(3 cycles/word * 16 words) 1 cycle overhead] 392 cycles。方案B32位模式将数据视为64个32位字。同样8次突发每次突发8个32位字等效16个16位数据。耗时 8 bursts * [(3 cycles/word * 8 words) 1 cycle overhead] 200 cycles。效率提升接近一倍因此在内存对齐允许的情况下应优先使用32位数据宽度进行DMA传输。4. 通道优先级与仲裁策略当多个DMA通道同时有数据传输请求时需要一套公平且高效的仲裁规则。TMS320F2837xS的DMA支持两种优先级模式理解它们对设计多任务数据流系统至关重要。4.1 轮询模式这是默认模式所有6个通道1-6优先级平等。DMA内部维护一个轮询指针按照CH1 - CH2 - CH3 - CH4 - CH5 - CH6 - CH1...的顺序依次服务。这里有一个非常关键且反直觉的细节轮询的顺序是固定的与触发到达的先后无关只与当前服务的通道和轮询顺序有关。举个例子假设CH1、CH4、CH5三个通道使能。当前DMA正在服务CH4的一次突发传输。在CH4传输完成前CH1和CH5的触发事件同时到达。CH4传输完成后DMA的轮询指针会指向列表中的下一个已使能通道即CH5。因此即使CH1先触发也会先服务CH5。CH5完成后指针指向CH1服务CH1。这种机制保证了所有通道的长期公平性避免了某个高频率触发通道饿死其他通道。但它要求工程师在设计时必须考虑最坏情况下的通道服务延迟。4.2 通道1高优先级模式在此模式下通道1拥有绝对优先权可以打断正在进行的其他通道2-6的传输。通道2-6之间仍采用轮询模式。工作流程如下DMA正在服务CH4的传输可能一次突发还没完成。CH1的触发事件到达。DMA会在完成当前正在进行的这一个数据字的传输后立即暂停CH4转而去服务CH1的整个突发传输。CH1的突发传输全部完成后DMA再回到CH4被暂停的地方继续完成剩余的传输。这种模式专为对延迟极其敏感、数据率高的外设设计例如高速ADC。手册特别警告通道1的高优先级模式和OneShot模式不能同时启用。因为OneShot模式下通道1一次传输的数据量可能非常大如果它还能打断别人将导致其他通道被长时间阻塞违背了实时系统的设计原则。5. 高级应用乒乓缓冲与数据重排DMA不仅仅是搬运工更是数据预处理的高手。其“乒乓缓冲”和“正交数据重排”功能能极大优化CPU的数据处理流程。5.1 乒乓缓冲实现零等待数据处理乒乓缓冲是一种经典的双缓冲区技术。它需要两个大小相同的内存缓冲区Buffer A和Buffer B。阶段一DMA被配置为连续模式并将目的地址指向Buffer A。当外设如ADC产生数据时DMA自动将数据填入Buffer A。阶段二当Buffer A填满时DMA产生一个中断通过设置CHINTMODE为传输结束中断。CPU收到中断后开始处理Buffer A中的数据。关键点在CPU处理Buffer A的同时DMA并没有闲着。通过配置地址包装功能在Buffer A满的瞬间DMA的目的地址指针会自动“包装”到Buffer B的起始地址。因此新的外设数据会源源不断地填入Buffer B而不会覆盖正在被CPU处理的Buffer A。阶段三Buffer B填满DMA再次产生中断并将目的地址指针包装回Buffer A。此时CPU应已处理完Buffer A可以转而处理Buffer B而DMA则向Buffer A写入新数据。如此循环往复实现了数据采集和处理的完全并行CPU永远在处理“上一帧”完整数据DMA永远在填充“下一帧”数据两者互不干扰系统吞吐量达到最大。配置要点设置CONTINUOUS 1使通道在传输完成后自动重新使能。配置TRANSFER_SIZE和BURST_SIZE使得一次传输正好填满一个缓冲区。配置DST_WRAP_SIZE使其等于TRANSFER_SIZE或略小并设置DST_WRAP_STEP为两个缓冲区之间的地址偏移量。这样每次传输完成即填满一个缓冲区后目的地址会自动跳转到另一个缓冲区。将CHINTMODE设置为在传输结束时产生中断以通知CPU处理数据。5.2 数据重排化乱为整的魔法许多外设特别是高速ADC其采样数据在内存中的存储顺序可能并不符合CPU高效处理的需求。例如一个多通道ADC交替采样两个通道CH1, CH2得到的数据流可能是CH1_S1, CH2_S1, CH1_S2, CH2_S2, CH1_S3, CH2_S3...。如果CPU想分别处理所有CH1的数据和所有CH2的数据就需要在内存中重新排序这又会消耗CPU周期。DMA的数据重排功能可以在传输过程中自动完成这个工作。它通过巧妙地配置源/目的地址的BURST_STEP和TRANSFER_STEP来实现。示例将交错的ADC数据分离两个独立的数组。假设ADC结果寄存器基地址为AdcResult我们想将100个点的CH1和CH2数据分别存放到数组Ch1_Data[100]和Ch2_Data[100]中。思路将一次“传输”定义为处理一对数据CH1和CH2各一个。一次“突发”只搬运一个数据。配置BURST_SIZE 0(每次突发搬1个数据)TRANSFER_SIZE 99(总共100对数据即100次突发)SRC_BURST_STEP 4(假设每个ADC结果寄存器是16位地址偏移为2。交错存储所以从CH1结果跳到下一个CH1结果地址需要跳过一个CH2因此步进为4)SRC_TRANSFER_STEP -198 * 2(这是一个技巧。完成一次传输即搬完一对数据后需要将源地址回退到起始位置附近以便开始搬下一对数据。具体计算需要根据内存布局来调整)DST_BURST_STEP 2(目的地址每次增加一个数据单元的长度)DST_TRANSFER_STEP 200(完成一对数据的搬运后目的地址需要跳到另一个数组的起始位置两个数组各100个数据所以偏移是200个数据单元地址)通过这样的配置DMA就能自动将交错的数据流整理成两个连续的数据块。这背后的硬件逻辑就是利用了之前提到的“传输循环”和“突发循环”中对地址指针的不同步进控制。掌握这个技巧能解决很多实际项目中的数据格式化难题。6. 实战配置指南与常见问题排查理论最终要服务于实践。下面我将以一个典型的ADC多通道采样并通过DMA存储的案例拆解配置步骤并分享几个我踩过的“坑”。6.1 典型配置流程ADCDMA数据采集目标配置ADC序列采样4个通道每次序列转换完成触发DMA将4个通道的结果搬运到长度为100的循环缓冲区中。初始化ADC配置ADC工作模式、采样通道、触发源例如ePWM的SOC和序列长度4个转换。配置DMA通道选择触发源在DMACHSRCSELx寄存器中选择对应ADC模块的序列完成中断作为触发源例如ADCAINT1。配置通道模式MODE.CHx[PERINTSEL]设置为该通道号。DATASIZE根据ADC结果寄存器宽度选择通常16位。ONESHOT和CONTINUOUS根据需求设置本例中CONTINUOUS1以实现循环缓冲。配置传输尺寸BURST_SIZE 3(一次突发搬运4个数据对应4个ADC通道)。TRANSFER_SIZE 99(总共搬运100次突发即100组4通道数据)。配置地址指针与步进源地址SRC_ADDR_SHADOW设置为ADC结果寄存器的基地址。SRC_BURST_STEP 2(ADC结果寄存器通常是连续地址每个结果占16位地址步进为2)。SRC_TRANSFER_STEP 0(每次突发后源地址应回到起始的ADC结果寄存器地址)。目的地址DST_ADDR_SHADOW设置为循环缓冲区在内存中的起始地址如AdcBuffer[0]。DST_BURST_STEP 2(数据在内存中连续存放)。DST_TRANSFER_STEP 8(每次突发搬运4个16位数据地址增加8。这样100次突发后正好写满400个数据单元的缓冲区)。配置包装实现循环缓冲DST_WRAP_SIZE 99(与TRANSFER_SIZE相等表示每次传输完成时触发包装)。DST_WRAP_STEP -800(100次突发 * 每次地址增加8 总地址增量800。包装步进设为-800使得目的地址指针在缓冲区满后跳回缓冲区起始地址)。DST_BEG_ADDR_SHADOW设置为与DST_ADDR_SHADOW相同的值。配置中断使能通道中断(CHINTE1)并设置CHINTMODE1在传输完成即缓冲区满时产生中断通知CPU处理数据。启动传输设置CONTROL.CHx[RUN] 1使能DMA通道。随后ADC在ePWM触发下开始转换每次序列完成即触发DMA搬运4个数据。6.2 常见问题与排查技巧实录问题1DMA似乎没有启动数据没有搬运。检查清单触发源是否正确确认DMACHSRCSELx和PERINTSEL配置匹配。最稳妥的方法是先用软件触发(PERINTFRC)测试DMA配置本身是否正确。外设中断是否产生确认ADC/ePWM等触发源本身工作正常能产生中断事件。可以先用CPU中断来验证。DMA通道是否使能确认CONTROL.CHx[RUN]位已置1。内存映射是否正确确保源地址外设寄存器和目的地址内存是可访问的并且没有越界。特别是目的地址是否在有效的RAM区域内。优先级与阻塞检查是否被更高优先级的通道如通道1高优先级模式长期阻塞或者CPU正在访问冲突的外设导致DMA被持续仲裁等待。问题2数据搬运错位或者只搬运了一部分。检查清单数据宽度确认DATASIZE设置与外设数据宽度和内存访问预期是否一致。16位外设数据用32位模式访问会导致错位。步进计算这是最容易出错的地方。反复核对BURST_STEP、TRANSFER_STEP和WRAP_STEP的值。记住这些步进值都是针对16位地址单元的。如果你操作的是32位数据4字节地址步进应该是2而不是1。缓冲区溢出检查TRANSFER_SIZE和BURST_SIZE的乘积是否超过了目的缓冲区的容量。同时检查包装逻辑是否正确避免写穿缓冲区。Overrun标志检查CONTROL.CHx[OVRFLG]是否被置位。如果置位说明DMA处理速度跟不上外设触发速度有触发事件丢失。需要增大DMA带宽如改用32位传输、提高DMA优先级或降低外设数据率。问题3系统运行一段时间后出现数据错误或死机。检查清单内存对齐确保源地址和目的地址符合数据宽度的对齐要求。例如32位访问通常要求地址是4字节对齐的。共享资源冲突检查是否有其他主设备CPU、CLA与DMA访问同一块内存或外设且存在写冲突。避免对DMA正在写入的内存区域进行写操作。中断服务程序如果DMA传输完成中断服务程序执行时间过长可能导致错过后续的DMA事件或系统其他关键任务。优化ISR或考虑使用乒乓缓冲结合DMA半满中断来分散处理压力。电源与时钟在低功耗模式下DMA模块的时钟可能被关闭。确保在操作DMA前其所在的外设时钟域已使能。一个独家避坑技巧在调试复杂的DMA传输特别是涉及地址包装和步进时我习惯先用一个简单的、已知的数据模式进行测试。例如在源内存区域填充递增数列(0x0001, 0x0002, 0x0003...)然后运行DMA再检查目的区域的数据。通过观察实际搬运过去的数据序列可以非常直观地判断出是步进计算错误、数据宽度错误还是包装逻辑错误。这比单纯看寄存器配置要高效得多。
深入解析DMA技术:从原理到TMS320F2837xS实战应用
1. 项目概述与DMA的核心价值在嵌入式系统开发尤其是实时控制和高性能数据采集领域我们常常面临一个核心矛盾CPU的算力是宝贵的但大量时间却被“搬砖”——也就是在内存和外设之间搬运数据——这类简单重复的I/O操作所消耗。想象一下你是一位工厂里最顶尖的工程师却不得不花大量时间亲自去仓库取零件、再把成品搬回仓库这无疑是巨大的人才浪费。直接内存访问DMA技术就是为了解决这个矛盾而生的“自动化物流系统”。DMA的本质是在芯片内部集成一个专司数据搬运的“协处理器”。当外设如ADC模数转换器产生了一批新数据或者内存中有一批数据需要发送给通信接口如SPI时不再是CPU停下手中的计算任务通过执行load/store指令来一个字节一个字节地搬运而是由DMA控制器接管这个任务。CPU只需要在初始化时告诉DMA“从A地址开始搬N个数据到B地址搬完了告诉我一声”就可以继续去执行核心算法或响应其他事件。DMA会在后台默默完成所有数据传输整个过程无需CPU干预从而将CPU彻底解放出来。以我手头常用的TI C2000系列微控制器特别是TMS320F2837xS这款高性能实时MCU为例其DMA模块的设计堪称典范。它不仅仅是一个简单的数据搬运工更是一个高度可编程、具备数据预处理能力的智能引擎。它支持多达6个独立通道每个通道都可以由不同的事件如ADC转换完成、定时器溢出、外部中断触发它能在传输过程中对数据进行“正交重排”把杂乱的数据流整理成便于CPU处理的规整块它还支持“乒乓缓冲”这种高级数据流管理技术实现数据搬运和处理的完美流水线化。理解并用好DMA是从嵌入式新手迈向资深工程师的关键一步它能让你设计的系统在性能、实时性和能效上产生质的飞跃。2. DMA架构与核心工作机制深度解析要驾驭DMA不能只停留在“配置寄存器让它动起来”的层面必须深入理解其内部架构和工作原理。这就像开车知道踩油门能走只是第一步了解发动机、变速箱和传动系统如何协同工作才能应对复杂路况发挥车辆的全部性能。2.1 整体架构与总线仲裁TMS320F2837xS的DMA模块是一个独立于CPU的子系统拥有自己的32位地址总线和32位数据读写总线。这意味着DMA可以和CPU并行工作访问共享的内存和外设资源。其架构核心是一个精巧的状态机和紧密耦合的地址控制逻辑。一个关键且容易忽略的细节是“公共外设架构”。芯片上的许多外设如ePWM、SPI的寄存器可以被CPU、DMA以及另一个协处理器CLA访问。这就引入了资源冲突的可能性。为了解决这个问题芯片内部有一个仲裁机制。例如当DMA和CPU在同一周期试图访问同一个外设时DMA拥有更高的优先级CPU的访问会被“阻塞”一个周期直到DMA操作完成。这种设计保证了DMA传输的确定性和低延迟但对于CPU端的实时任务规划提出了要求——你需要避免让CPU频繁访问那些可能被DMA占用的外设寄存器。注意这里有一个重要的实践细节。手册中特别警告如果CPU正在对一个内存位置进行“读-修改-写”操作例如对一个控制寄存器进行位操作而DMA恰好在这条指令的“读”和“写”之间向同一位置写入数据那么DMA的这次写入可能会丢失。因此在软件设计时应避免CPU和DMA对同一内存地址进行写操作或者使用信号量等机制进行保护。2.2 事件触发机制让数据动起来的“发令枪”DMA是一个“事件驱动”的机器它不会自己周期性地启动传输必须等待一个触发信号。这个设计非常符合嵌入式系统响应外部事件的本质。TMS320F2837xS为每个DMA通道提供了极其丰富的触发源选择通过DMACHSRCSELx和CHx.MODE.PERINTSEL寄存器进行配置。触发源列表几乎涵盖了所有可能产生数据的外设ADC模块四个ADC模块A, B, C, D各自的序列转换完成中断INT1-4或事件EVT这是数据采集应用最常用的触发源。ePWM模块12个ePWM模块的SOCA/SOCB启动转换信号可以实现与PWM波形严格同步的精确采样。外部中断XINT5个外部中断引脚可以将GPIO上的外部事件如传感器信号直接作为DMA触发。定时器中断TINT3个CPU定时器中断可以将DMA配置为定时启动的“软周期”传输。通信外设SPI的发送/接收FIFO就绪信号McBSP的多通道缓冲串行口事件等。软件触发通过设置CONTROL.CHx[PERINTFRC]位可以随时用软件强制触发一次DMA传输用于初始化或测试。触发逻辑内部有一个锁存器Latch。当外设产生一个触发事件PERINTFLG标志位会被置起表示该通道有一个传输请求“待处理”。DMA的状态机根据通道优先级依次处理这些待处理的请求。一旦某个通道的“突发传输”开始其PERINTFLG标志位就会被自动清除以准备接收下一个触发事件。这个机制是理解DMA流控的基础。3. DMA传输的精密控制地址指针与状态机DMA传输的控制逻辑本质上是由两个嵌套循环构成的状态机内层的“突发循环”和外层的“传输循环”。理解这两个循环以及相关的地址指针、步进和包装机制是进行复杂数据传输编程的关键。3.1 核心概念突发与传输突发Burst这是DMA响应一次触发事件所执行的最小传输单元。一次突发传输的数据量由BURST_SIZE寄存器定义。例如BURST_SIZE 15表示一次突发传输16个数据单元因为实际传输数是BURST_SIZE 1。这个数据单元可以是16位或32位由MODE.DATASIZE位决定。突发传输是原子性的一旦开始就会连续完成指定数量的数据传输期间不会被同优先级的其他通道打断。传输Transfer一个完整的DMA传输任务由多次突发构成。总突发次数由TRANSFER_SIZE寄存器定义。完成整个传输后DMA可以产生一个中断通知CPU。因此一个完整的DMA传输总共搬运的数据量是(BURST_SIZE 1) * (TRANSFER_SIZE 1)个数据单元。3.2 地址指针的“双缓冲”与步进控制DMA的地址管理非常灵活它有两套指针“影子寄存器”和“活动寄存器”。影子寄存器Shadow Registers如SRC_ADDR_SHADOW、DST_ADDR_SHADOW。这些寄存器由软件在初始化时配置代表了传输任务的“蓝图”。在传输过程中软件可以随时修改它们而不会影响正在进行的传输。活动寄存器Active Registers如SRC_ADDR_ACTIVE、DST_ADDR_ACTIVE。这些寄存器由硬件在每次传输开始时从对应的影子寄存器加载并在传输过程中实时更新指向当前正在读写的内存地址。地址指针的移动由三组“步进”寄存器控制它们都是有符号数支持地址递增或递减突发步进BURST_STEP在一次突发传输内部每完成一个数据的搬运后源地址和目的地址增加的偏移量。例如从ADC结果寄存器地址连续搬运数据到内存数组通常将SRC_BURST_STEP设为0因为总是读同一个ADC结果寄存器地址DST_BURST_STEP设为2如果数据宽度是16位地址2指向下一内存字。传输步进TRANSFER_STEP在两次突发传输之间即完成一次突发后、等待下一个触发事件前源地址和目的地址增加的偏移量。这常用于处理非连续的数据块。包装步进WRAP_STEP这是实现“乒乓缓冲”或“环形缓冲区”的关键。当使能地址包装功能后每完成一定数量的突发由WRAP_SIZE定义活动地址指针不会简单地加上TRANSFER_STEP而是会被重置为BEG_ADDR开始地址同时这个BEG_ADDR自身会加上WRAP_STEP。这实现了指针在多个缓冲区之间“跳跃”。3.3 关键工作模式OneShot与ContinuousOneShot模式此模式默认禁用。当ONESHOT0时每次触发事件只引起一次突发传输。这是最常用的模式确保了单个通道不会独占DMA总线。当ONESHOT1时一次触发事件将导致整个传输循环所有突发被连续执行完毕。这非常危险因为一次触发可能占用极长的总线时间阻塞其他所有通道仅在对实时性要求极高、且数据块较小的特定场景下谨慎使用。连续模式此模式默认禁用。当CONTINUOUS0时完成整个传输循环TRANSFER_COUNT减到0后该DMA通道会自动禁用需要软件重新设置RUN位才能开始下一次传输。当CONTINUOUS1时传输完成后通道保持使能状态自动等待下一轮触发适用于需要持续不断进行数据搬运的场景。3.4 数据传输的“流水线”与吞吐量计算DMA内部采用三级流水线操作发送源地址 - 读取源数据 - 写入目的数据。在理想无冲突情况下传输一个数据单元16位或32位需要3个时钟周期。但实际吞吐量还需考虑以下因素每个突发传输开始时有1个周期的固定开销。如果被高优先级通道如通道1打断恢复时会有1个周期的延迟。与CPU或CLA访问冲突时会产生额外的等待周期。最关键的一点32位传输的效率是16位的两倍。因为硬件总线是32位的传输一个32位字和传输一个16位字的时间几乎相同。我们来算一笔账假设需要从GS0 RAM搬运128个16位数据到GS3 RAM。方案A16位模式配置为8次突发每次突发16个数据。耗时 8 bursts * [(3 cycles/word * 16 words) 1 cycle overhead] 392 cycles。方案B32位模式将数据视为64个32位字。同样8次突发每次突发8个32位字等效16个16位数据。耗时 8 bursts * [(3 cycles/word * 8 words) 1 cycle overhead] 200 cycles。效率提升接近一倍因此在内存对齐允许的情况下应优先使用32位数据宽度进行DMA传输。4. 通道优先级与仲裁策略当多个DMA通道同时有数据传输请求时需要一套公平且高效的仲裁规则。TMS320F2837xS的DMA支持两种优先级模式理解它们对设计多任务数据流系统至关重要。4.1 轮询模式这是默认模式所有6个通道1-6优先级平等。DMA内部维护一个轮询指针按照CH1 - CH2 - CH3 - CH4 - CH5 - CH6 - CH1...的顺序依次服务。这里有一个非常关键且反直觉的细节轮询的顺序是固定的与触发到达的先后无关只与当前服务的通道和轮询顺序有关。举个例子假设CH1、CH4、CH5三个通道使能。当前DMA正在服务CH4的一次突发传输。在CH4传输完成前CH1和CH5的触发事件同时到达。CH4传输完成后DMA的轮询指针会指向列表中的下一个已使能通道即CH5。因此即使CH1先触发也会先服务CH5。CH5完成后指针指向CH1服务CH1。这种机制保证了所有通道的长期公平性避免了某个高频率触发通道饿死其他通道。但它要求工程师在设计时必须考虑最坏情况下的通道服务延迟。4.2 通道1高优先级模式在此模式下通道1拥有绝对优先权可以打断正在进行的其他通道2-6的传输。通道2-6之间仍采用轮询模式。工作流程如下DMA正在服务CH4的传输可能一次突发还没完成。CH1的触发事件到达。DMA会在完成当前正在进行的这一个数据字的传输后立即暂停CH4转而去服务CH1的整个突发传输。CH1的突发传输全部完成后DMA再回到CH4被暂停的地方继续完成剩余的传输。这种模式专为对延迟极其敏感、数据率高的外设设计例如高速ADC。手册特别警告通道1的高优先级模式和OneShot模式不能同时启用。因为OneShot模式下通道1一次传输的数据量可能非常大如果它还能打断别人将导致其他通道被长时间阻塞违背了实时系统的设计原则。5. 高级应用乒乓缓冲与数据重排DMA不仅仅是搬运工更是数据预处理的高手。其“乒乓缓冲”和“正交数据重排”功能能极大优化CPU的数据处理流程。5.1 乒乓缓冲实现零等待数据处理乒乓缓冲是一种经典的双缓冲区技术。它需要两个大小相同的内存缓冲区Buffer A和Buffer B。阶段一DMA被配置为连续模式并将目的地址指向Buffer A。当外设如ADC产生数据时DMA自动将数据填入Buffer A。阶段二当Buffer A填满时DMA产生一个中断通过设置CHINTMODE为传输结束中断。CPU收到中断后开始处理Buffer A中的数据。关键点在CPU处理Buffer A的同时DMA并没有闲着。通过配置地址包装功能在Buffer A满的瞬间DMA的目的地址指针会自动“包装”到Buffer B的起始地址。因此新的外设数据会源源不断地填入Buffer B而不会覆盖正在被CPU处理的Buffer A。阶段三Buffer B填满DMA再次产生中断并将目的地址指针包装回Buffer A。此时CPU应已处理完Buffer A可以转而处理Buffer B而DMA则向Buffer A写入新数据。如此循环往复实现了数据采集和处理的完全并行CPU永远在处理“上一帧”完整数据DMA永远在填充“下一帧”数据两者互不干扰系统吞吐量达到最大。配置要点设置CONTINUOUS 1使通道在传输完成后自动重新使能。配置TRANSFER_SIZE和BURST_SIZE使得一次传输正好填满一个缓冲区。配置DST_WRAP_SIZE使其等于TRANSFER_SIZE或略小并设置DST_WRAP_STEP为两个缓冲区之间的地址偏移量。这样每次传输完成即填满一个缓冲区后目的地址会自动跳转到另一个缓冲区。将CHINTMODE设置为在传输结束时产生中断以通知CPU处理数据。5.2 数据重排化乱为整的魔法许多外设特别是高速ADC其采样数据在内存中的存储顺序可能并不符合CPU高效处理的需求。例如一个多通道ADC交替采样两个通道CH1, CH2得到的数据流可能是CH1_S1, CH2_S1, CH1_S2, CH2_S2, CH1_S3, CH2_S3...。如果CPU想分别处理所有CH1的数据和所有CH2的数据就需要在内存中重新排序这又会消耗CPU周期。DMA的数据重排功能可以在传输过程中自动完成这个工作。它通过巧妙地配置源/目的地址的BURST_STEP和TRANSFER_STEP来实现。示例将交错的ADC数据分离两个独立的数组。假设ADC结果寄存器基地址为AdcResult我们想将100个点的CH1和CH2数据分别存放到数组Ch1_Data[100]和Ch2_Data[100]中。思路将一次“传输”定义为处理一对数据CH1和CH2各一个。一次“突发”只搬运一个数据。配置BURST_SIZE 0(每次突发搬1个数据)TRANSFER_SIZE 99(总共100对数据即100次突发)SRC_BURST_STEP 4(假设每个ADC结果寄存器是16位地址偏移为2。交错存储所以从CH1结果跳到下一个CH1结果地址需要跳过一个CH2因此步进为4)SRC_TRANSFER_STEP -198 * 2(这是一个技巧。完成一次传输即搬完一对数据后需要将源地址回退到起始位置附近以便开始搬下一对数据。具体计算需要根据内存布局来调整)DST_BURST_STEP 2(目的地址每次增加一个数据单元的长度)DST_TRANSFER_STEP 200(完成一对数据的搬运后目的地址需要跳到另一个数组的起始位置两个数组各100个数据所以偏移是200个数据单元地址)通过这样的配置DMA就能自动将交错的数据流整理成两个连续的数据块。这背后的硬件逻辑就是利用了之前提到的“传输循环”和“突发循环”中对地址指针的不同步进控制。掌握这个技巧能解决很多实际项目中的数据格式化难题。6. 实战配置指南与常见问题排查理论最终要服务于实践。下面我将以一个典型的ADC多通道采样并通过DMA存储的案例拆解配置步骤并分享几个我踩过的“坑”。6.1 典型配置流程ADCDMA数据采集目标配置ADC序列采样4个通道每次序列转换完成触发DMA将4个通道的结果搬运到长度为100的循环缓冲区中。初始化ADC配置ADC工作模式、采样通道、触发源例如ePWM的SOC和序列长度4个转换。配置DMA通道选择触发源在DMACHSRCSELx寄存器中选择对应ADC模块的序列完成中断作为触发源例如ADCAINT1。配置通道模式MODE.CHx[PERINTSEL]设置为该通道号。DATASIZE根据ADC结果寄存器宽度选择通常16位。ONESHOT和CONTINUOUS根据需求设置本例中CONTINUOUS1以实现循环缓冲。配置传输尺寸BURST_SIZE 3(一次突发搬运4个数据对应4个ADC通道)。TRANSFER_SIZE 99(总共搬运100次突发即100组4通道数据)。配置地址指针与步进源地址SRC_ADDR_SHADOW设置为ADC结果寄存器的基地址。SRC_BURST_STEP 2(ADC结果寄存器通常是连续地址每个结果占16位地址步进为2)。SRC_TRANSFER_STEP 0(每次突发后源地址应回到起始的ADC结果寄存器地址)。目的地址DST_ADDR_SHADOW设置为循环缓冲区在内存中的起始地址如AdcBuffer[0]。DST_BURST_STEP 2(数据在内存中连续存放)。DST_TRANSFER_STEP 8(每次突发搬运4个16位数据地址增加8。这样100次突发后正好写满400个数据单元的缓冲区)。配置包装实现循环缓冲DST_WRAP_SIZE 99(与TRANSFER_SIZE相等表示每次传输完成时触发包装)。DST_WRAP_STEP -800(100次突发 * 每次地址增加8 总地址增量800。包装步进设为-800使得目的地址指针在缓冲区满后跳回缓冲区起始地址)。DST_BEG_ADDR_SHADOW设置为与DST_ADDR_SHADOW相同的值。配置中断使能通道中断(CHINTE1)并设置CHINTMODE1在传输完成即缓冲区满时产生中断通知CPU处理数据。启动传输设置CONTROL.CHx[RUN] 1使能DMA通道。随后ADC在ePWM触发下开始转换每次序列完成即触发DMA搬运4个数据。6.2 常见问题与排查技巧实录问题1DMA似乎没有启动数据没有搬运。检查清单触发源是否正确确认DMACHSRCSELx和PERINTSEL配置匹配。最稳妥的方法是先用软件触发(PERINTFRC)测试DMA配置本身是否正确。外设中断是否产生确认ADC/ePWM等触发源本身工作正常能产生中断事件。可以先用CPU中断来验证。DMA通道是否使能确认CONTROL.CHx[RUN]位已置1。内存映射是否正确确保源地址外设寄存器和目的地址内存是可访问的并且没有越界。特别是目的地址是否在有效的RAM区域内。优先级与阻塞检查是否被更高优先级的通道如通道1高优先级模式长期阻塞或者CPU正在访问冲突的外设导致DMA被持续仲裁等待。问题2数据搬运错位或者只搬运了一部分。检查清单数据宽度确认DATASIZE设置与外设数据宽度和内存访问预期是否一致。16位外设数据用32位模式访问会导致错位。步进计算这是最容易出错的地方。反复核对BURST_STEP、TRANSFER_STEP和WRAP_STEP的值。记住这些步进值都是针对16位地址单元的。如果你操作的是32位数据4字节地址步进应该是2而不是1。缓冲区溢出检查TRANSFER_SIZE和BURST_SIZE的乘积是否超过了目的缓冲区的容量。同时检查包装逻辑是否正确避免写穿缓冲区。Overrun标志检查CONTROL.CHx[OVRFLG]是否被置位。如果置位说明DMA处理速度跟不上外设触发速度有触发事件丢失。需要增大DMA带宽如改用32位传输、提高DMA优先级或降低外设数据率。问题3系统运行一段时间后出现数据错误或死机。检查清单内存对齐确保源地址和目的地址符合数据宽度的对齐要求。例如32位访问通常要求地址是4字节对齐的。共享资源冲突检查是否有其他主设备CPU、CLA与DMA访问同一块内存或外设且存在写冲突。避免对DMA正在写入的内存区域进行写操作。中断服务程序如果DMA传输完成中断服务程序执行时间过长可能导致错过后续的DMA事件或系统其他关键任务。优化ISR或考虑使用乒乓缓冲结合DMA半满中断来分散处理压力。电源与时钟在低功耗模式下DMA模块的时钟可能被关闭。确保在操作DMA前其所在的外设时钟域已使能。一个独家避坑技巧在调试复杂的DMA传输特别是涉及地址包装和步进时我习惯先用一个简单的、已知的数据模式进行测试。例如在源内存区域填充递增数列(0x0001, 0x0002, 0x0003...)然后运行DMA再检查目的区域的数据。通过观察实际搬运过去的数据序列可以非常直观地判断出是步进计算错误、数据宽度错误还是包装逻辑错误。这比单纯看寄存器配置要高效得多。