深入解析TMS320F2837xD DMA:从核心机制到高效数据搬运实战
1. 项目概述:为什么需要深入理解DMA?
在嵌入式系统,尤其是像TMS320F2837xD这样的高性能双核实时微控制器(MCU)中,CPU的计算能力是宝贵的资源。想象一下,你正在设计一个电机控制系统,需要以1MHz的频率高速采集三相电流,同时还要处理复杂的控制算法。如果每次ADC转换完成都让CPU去搬运数据,CPU将深陷于简单重复的“搬运工”角色,无暇顾及核心的算法计算,系统实时性会大打折扣。
这时,直接存储器访问(DMA)控制器就成为了系统的“隐形管家”。它的核心价值在于,能够在外设(如ADC、SPI、ePWM)和存储器(如RAM)之间,建立一条独立于CPU的“数据高速公路”。CPU只需在初始化时告诉DMA:“从A地址开始,搬运N个数据到B地址,每次触发条件C满足时自动执行”,之后DMA便会默默地在后台完成所有数据传输工作。这不仅将CPU从繁重的数据搬运任务中解放出来,更重要的是,它实现了数据传输与数据处理的真正并行,是提升系统整体吞吐量和实时响应能力的关键。
然而,DMA的配置远比简单的“设置源地址和目标地址”复杂。其高效运作的背后,是一套精密的触发、传输和仲裁机制。理解这些机制,是避免DMA使用中常见陷阱(如数据覆盖、传输延迟、总线冲突)的前提。本文将深入TMS320F2837xD DMA控制器的内部,拆解其触发架构、双循环状态机、优先级管理以及总线仲裁等核心机制,并结合实际配置经验和避坑指南,帮助你在下一个项目中游刃有余地驾驭DMA。
2. 核心架构与访问权限:谁可以访问谁?
在深入DMA的运作细节前,我们必须先厘清一个基础但至关重要的概念:在TMS320F2837xD的双核系统中,DMA、CPU和CLA(控制律加速器)对系统资源的访问权限是如何划分的。这直接决定了你的DMA通道能否成功访问到目标外设。
2.1 CPU子系统与从属主控器
TMS320F2837xD有两个C28x CPU核心(CPU1和CPU2),每个CPU核心及其附属的从属主控器(对于CPU1是DMA,对于CPU2是CLA1)构成了一个“CPU子系统”。每个外设(如ADC、ePWM、SPI)都有一个CPUSEL位,用于定义它“属于”哪个CPU子系统。
- 关键规则:如果一个外设通过
CPUSEL位配置为属于某个CPU子系统,那么该子系统的CPU及其从属主控器(DMA或CLA)才能正常访问它。 - 访问后果:如果某个主控器(例如CPU1的DMA)试图访问一个不属于CPU1子系统的外设,那么所有写操作将被忽略,所有读操作将返回
0x0。这不会导致硬件错误,但你的数据传输会静默失败,数据全部为0,这是一个非常隐蔽的Bug来源。
2.2 从属主控器选择寄存器
每个CPU子系统通过CPUx.SECMSEL寄存器静态地选择使用哪一个从属主控器。对于CPU1,通常选择DMA;对于CPU2,通常选择CLA1。如果未选择任何从属主控器,那么来自该主控器的所有访问(无论目标外设属于谁)都将被忽略。
实操心得:在双核项目中启动DMA传输前,务必进行两项检查:
- 确认目标外设(源或目的)的
CPUSEL位配置正确,使其属于运行DMA的CPU所在的子系统。- 确认
CPUx.SECMSEL寄存器已正确配置,使能了DMA作为从属主控器。这两步是DMA能正常工作的“准生证”,经常在调试初期被忽略。
2.3 总线仲裁的独立性
文档中特别指出,即使某个主控器试图访问一个不属于其子系统的外设,总线仲裁仍然会发生。这意味着,错误的访问虽然不会成功,但依然会占用总线周期,可能影响其他合法访问的时序。因此,正确的权限配置不仅是功能需求,也是优化系统性能的需要。
3. 触发机制:如何让DMA开始工作?
DMA不会无缘无故地搬运数据,它需要一个“启动信号”,这就是触发。TMS320F2837xD的DMA提供了高度灵活的触发源选择机制。
3.1 触发源架构
每个DMA通道(CH1-CH6)的触发源都可以独立配置,主要由两个寄存器控制:
DMACHSRCSELx寄存器:这是一个系统级的256选1多路选择器,为每个通道从庞大的系统触发源池(包括所有外设中断事件、外部中断等)中选择一个信号。CHx.MODE.PERINTSEL位域:这个5位的字段用于选择“通道自触发”模式。当将其设置为通道自身的编号(例如,CH1的PERINTSEL = 1)时,该通道的触发源将来自DMACHSRCSELx寄存器所选的系统事件。这是最常用的配置。
其架构可以理解为:所有可能的DMA触发源(共256个)作为一个全局列表。每个通道通过自己的DMACHSRCSELx寄存器从这个列表中“订阅”一个特定源。而PERINTSEL则决定了通道是响应这个“订阅”的源,还是响应其他内部信号。
3.2 丰富的触发源
触发源列表(如表5-1所示)极其丰富,涵盖了芯片几乎所有能产生周期性或事件性信号的外设:
- ADC模块:包括每个ADC核心的4个常规中断(
ADCxINT1-4_DMA)和事件中断(ADCxEVT)。这是数据采集应用中最常见的触发源。 - ePWM模块:每个ePWM模块的SOCA和SOCB(启动转换)事件。可用于与PWM波形同步的精确数据采集或输出。
- 外部中断:
XINT1至XINT5。允许外部GPIO引脚信号直接触发DMA。 - CPU定时器:
CPU_TINT0/1/2。用于产生周期性的DMA传输。 - 通信外设:SPI的发送/接收DMA请求(
SPIx_TXDMA/RXDMA),MCBSP的事件。用于高效管理通信缓冲区。 - 软件触发:
DMA_SOFTWARE_TRIGGER(索引0)。通过设置CONTROL.CHx[PERINTFRC]位来强制产生一个触发事件,用于手动启动或测试。
3.3 触发事件的处理流程
- 事件锁存:当配置的硬件触发事件发生时,对应通道的
CONTROL.CHx[PERINTFLG]标志位会被置位。这个标志位就像一个“待办事项”便签。 - 标志清除:这个“便签”不会自动消失。当DMA状态机根据优先级轮到处理该通道,并开始一次突发传输时,
PERINTFLG标志位才会被自动清除。 - 软件干预:软件可以通过
CONTROL.CHx[PERINTCLR]位随时手动清除挂起的触发标志,也可以通过PERINTFRC位强制产生一个软件触发。 - 溢出处理:这是一个关键机制。如果在
PERINTFLG已置位(即上一次触发还未被服务)到它被清除(即突发传输开始)之间,又发生了新的触发事件,那么第二个事件就会丢失,并置位CONTROL.CHx[OVRFLG]溢出标志。如果使能了溢出中断,会向PIE模块产生中断。这通常意味着DMA的传输速度跟不上触发事件产生的速度,需要调整DMA的突发大小、优先级或检查触发频率。
注意事项:对于ADC触发DMA读取结果寄存器的应用,务必查阅芯片勘误表。TMS320F2837xD存在一个著名的勘误“ADC:DMA Read of Stale Result”,即DMA可能在ADC转换结果真正准备好之前就去读取结果寄存器,导致读到旧数据。解决方案通常是在ADC中断或事件与DMA触发之间插入一个小的、确定的延迟,或者通过配置ADC的后期中断来触发DMA。
4. 传输模式与地址控制:DMA如何搬运数据?
这是DMA的核心,理解其状态机是进行高效、复杂数据传输配置的基础。TMS320F2837xD的DMA采用了一个精巧的“双循环嵌套”状态机。
4.1 双循环状态机解析
DMA的传输由两层循环构成:
- 突发循环:这是内层循环。每次接收到一个有效的DMA触发事件,就会执行一次突发循环,传输
BURST_SIZE + 1个数据单元(16位或32位字)。你可以把它想象成“一铲子能挖多少土”。 - 传输循环:这是外层循环。一个完整的DMA传输(从启动到结束)由
TRANSFER_SIZE + 1次突发循环构成。这就像是“一共要挖多少铲子”。
关键寄存器与流程:
- 初始化:当通道使能(
RUN位置1)并开始传输时,所有影子寄存器(Shadow Registers)的值被复制到对应的活动寄存器(Active Registers)。活动寄存器是硬件实际使用的指针。 - 突发循环内:
- 每传输一个数据字,源和目的的活动地址指针会分别加上
SRC_BURST_STEP和DST_BURST_STEP。这两个步进值可以是正数(地址递增)、负数(地址递减)或零(地址不变,适用于访问固定寄存器,如外设数据寄存器)。 - 突发计数器
BURST_COUNT从BURST_SIZE值开始递减,减到0时,本次突发传输结束。
- 每传输一个数据字,源和目的的活动地址指针会分别加上
- 突发循环间(传输循环内):
- 一次突发结束后,
TRANSFER_COUNT减1。 - 然后,根据是否启用地址环绕,更新地址指针为下一次突发做准备:
- 默认模式(无环绕):源和目的地址指针分别加上
SRC_TRANSFER_STEP和DST_TRANSFER_STEP。这适用于线性缓冲区。 - 地址环绕模式:当
SRC/DST_WRAP_SIZE小于TRANSFER_SIZE时启用。每完成WRAP_SIZE + 1次突发,地址指针会“绕回”到起始位置并加上一个WRAP_STEP偏移。这用于实现乒乓缓冲区或循环缓冲区。例如,你可以设置一个包含两个子缓冲区的乒乓缓冲区,当DMA写满第一个子缓冲区后,自动跳转到第二个子缓冲区开始写,同时通过中断通知CPU处理第一个缓冲区的数据。
- 默认模式(无环绕):源和目的地址指针分别加上
- 一次突发结束后,
- 传输结束:当
TRANSFER_COUNT减到0时,一次完整的DMA传输完成。根据CONTINUOUS模式的设置,通道可能自动停止或等待下一次触发。
4.2 关键模式:ONESHOT与CONTINUOUS
- ONESHOT模式:此模式改变了对单个触发事件的响应行为。
- 禁用(默认):一次触发只执行一次突发传输(
BURST_SIZE+1个字)。即使该通道还有未完成的传输次数(TRANSFER_COUNT > 0),DMA也会在处理完本次突然后,根据优先级去服务其他通道。这保证了总线带宽的公平性。 - 启用:一次触发会连续执行,直到完成整个传输循环(
(BURST_SIZE+1) * (TRANSFER_SIZE+1)个字)。这会让该通道长时间占用DMA总线,可能阻塞其他通道。必须谨慎使用,通常用于对实时性要求极高、不允许被中断的短时大数据块传输。
- 禁用(默认):一次触发只执行一次突发传输(
- CONTINUOUS模式:此模式决定了传输完成后的通道状态。
- 禁用(默认):当
TRANSFER_COUNT为0时,通道自动禁用(RUNSTS位清零)。需要软件重新使能RUN位才能开始下一次传输。 - 启用:传输完成后通道保持使能状态,等待下一个触发事件,并自动重新加载所有计数器和指针(影子寄存器值复制到活动寄存器),开始新一轮传输。这适用于需要持续不断进行的周期性数据传输。
- 禁用(默认):当
4.3 数据大小与地址计算
这里有一个极易出错的细节:所有SIZE和STEP寄存器的值,其单位都是16位地址偏移量(即半字)。
BURST_SIZE与DATASIZE:BURST_SIZE定义的是以16位为单位的传输次数。如果DATASIZE设置为32位,那么实际传输的32位字数将是(BURST_SIZE + 1) / 2(向上取整)。例如,BURST_SIZE = 5(意为6个16位单元):- 当
DATASIZE=16-bit:传输6个16位数据。 - 当
DATASIZE=32-bit:传输3个32位数据(占用6个连续的16位地址空间)。
- 当
SRC_BURST_STEP等STEP寄存器:步进值也是以16位地址为单位的。如果你想在传输32位数据时,让源地址指针每次递增一个32位字(即4字节),那么需要设置SRC_BURST_STEP = 2(因为2个16位地址单位=1个32位字)。
避坑指南:在配置涉及32位数据传输时,务必在心中进行“16位单位”的换算。一个常见的错误是,源数据是32位浮点数数组,目标也是32位区域,但设置了
BURST_SIZE=15(心想传输16个数据)和SRC_BURST_STEP=1。结果DMA只传输了8个32位数据(因为16/2=8),并且地址每次只递增2字节,导致数据错位。正确的配置应是BURST_SIZE=31(传输32个16位单元,即16个32位字),SRC_BURST_STEP=2。
4.4 中断生成
每个DMA通道可以在传输开始或结束时产生EPIE中断,由CHINTMODE位控制。
CHINTMODE = 0:在传输开始时产生中断。这在乒乓缓冲区场景中非常有用:当DMA开始向备用缓冲区写入数据时,立即通知CPU处理当前已满的缓冲区。CHINTMODE = 1:在传输结束时产生中断。用于通知CPU一批数据已全部搬运完成。
5. 通道优先级与仲裁:当多个DMA通道同时需要服务时
当多个外设同时产生触发事件,或者多个通道的传输请求 pending 时,DMA控制器需要决定先为谁服务。TMS320F2837xD的DMA提供了两种优先级策略。
5.1 轮询模式
这是默认模式,所有已使能的通道(CH1-CH6)具有完全平等的优先级。DMA状态机按照固定的顺序循环服务:CH1 → CH2 → CH3 → CH4 → CH5 → CH6 → CH1 ...
工作机制:
- 状态机从空闲状态开始。
- 当有通道触发时,从CH1开始按顺序检查并服务第一个挂起的通道。
- 服务完一个通道的一次突发传输后,状态机移动到下一个通道(无论其是否有挂起请求)进行检查。
- 如果一轮检查完所有通道都没有挂起请求,状态机回到空闲状态。
关键特性:
- 公平性:每个通道都能获得均等的服务机会,不会出现低优先级通道“饿死”的情况。
- 服务顺序的确定性:服务顺序是固定的,与触发到来的先后顺序无关,只与通道编号和当前服务位置有关。这在规划多个通道的实时性时非常重要。
- 重置:可以通过设置
DMACTRL[PRIORITYRESET]位将轮询状态机强制重置到空闲状态(从CH1开始)。
5.2 通道1高优先级模式
在此模式下,通道1拥有绝对最高优先级,可以打断其他任何通道(CH2-CH6)的传输。通道2到6之间则采用轮询模式。
工作机制:
- 如果通道1有挂起的触发请求,它可以在当前正在服务的任何其他通道(CH2-CH6)完成当前字的传输后立即被响应。当前通道的传输被挂起,等通道1的整个突发传输完成后,再恢复执行。
- 通道1不能打断另一个通道1的传输(自身不能嵌套)。
- 通道2-6之间互相不能打断,遵循轮询规则。
典型应用:此模式专为服务最高实时性要求的数据源设计。例如,一个高速ADC以固定频率采样,其数据必须被及时搬走,否则会发生溢出。将ADC触发连接到DMA通道1并设置为高优先级模式,可以确保ADC数据总能得到及时响应。
重要限制:通道1的高优先级模式和ONESHOT模式不能同时启用!因为ONESHOT模式会让一个通道连续传输大量数据,如果高优先级的通道1也启用ONESHOT,它一旦启动就会长时间霸占总线,使“高优先级”失去意义,并可���阻塞所有其他通道。其他通道(CH2-CH6)则可以在通道1为高优先级时使用ONESHOT。
5.3 总线仲裁:当DMA与CPU/CLA争抢资源时
DMA、CPU和CLA可能同时访问同一块内存或外设,这时就需要总线仲裁器来裁决。
冲突与仲裁规则:
- 相同类型外设:访问同一类型的不同外设实例也会冲突(例如,CPU访问CAN-A,同时DMA访问CAN-B),因为它们共享同一个外设总线接口。
- 共享总线接口:不同外设可能共享一个总线接口(如外设帧1:ePWM, eCAP等;外设帧2:SPI, PMBus等)。访问共享同一接口的不同外设也会冲突。
- 全局共享RAM:访问不同的全局共享RAM块(GS0, GS1, GS2...)不会冲突。
- ADC结果寄存器:这是一个特例,ADC结果寄存器为每个总线主控器(CPU1, CPU2, DMA, CLA)都进行了硬件复制,因此可以同时被所有主控器读取,无冲突无延迟。
仲裁优先级(针对存在冲突的外设帧访问):
- CLA/DMA 写操作(最高)
- CLA/DMA 读操作
- CPU 写操作
- CPU 读操作(最低)注:此优先级是固定的。对于GSx RAM的访问,则采用轮询仲裁。
对性能的影响:仲裁会导致被抢占的主控器操作停顿,直到高优先级的访问完成。DMA传输本身包含“发送源地址、读数据、发送目的地址、写数据”四个阶段,仲裁可能发生在任何阶段之间。如果CPU在DMA读数据阶段尝试访问冲突资源,CPU会被 stall,直到DMA完成该次读操作(而非整个DMA传输)。
严重警告:绝对避免CPU和DMA同时写入同一内存位置!如果CPU正在进行一个“读-修改-写”操作(例如
RAM[0]++),而DMA在CPU“读”和“写”之间向RAM[0]写入新值,那么CPU随后进行的“写”会覆盖掉DMA刚写入的值,导致DMA的数据丢失。这种错误非常难以调试。解决方案是使用软件锁(如标志位)、双缓冲区,或者确保CPU和DMA访问的区域在时间或空间上完全分离。
6. 性能考量与配置实战
理解了原理,最终要落实到配置上。如何配置DMA才能达到最优性能?
6.1 吞吐量计算与优化
DMA传输并非零开销。文档指出,其传输管道存在一些固有延迟:
- 每个突发传输开始时有1个周期的延迟。
- 从通道1高优先级中断返回时有1个周期的延迟。
- 与CPU/CLA的总线冲突会增加额外的延迟周期。
- 最关键的一点:32位传输的吞吐量是16位传输的两倍。因为32位传输每个周期搬运32位数据,而16位传输每个周期只搬运16位。
吞吐量估算公式(理想情况,无冲突):总周期数 ≈ 突发次数 * [(每字周期数 * 每突发字数) + 1]
- 对于16位传输,每字通常需要3个周期(地址、读、写)。
- 对于32位传输,每32位字也大约需要3个周期,但数据量是16位传输的两倍。
优化建议:
- 优先使用32位传输:只要源和目的地址都是32位对齐的,尽量将
DATASIZE配置为32位。这能直接带来吞吐量翻倍的收益。 - 合理设置突发大小:较大的
BURST_SIZE能摊薄每次突发开始的固定延迟开销,提高效率。但过大的突发会单次占用总线时间过长,影响其他通道或CPU的实时性。需要根据系统整体负载权衡。 - 避免频繁的小数据量传输:如果可能,将多个分散的小传输合并为一次较大的DMA传输。
6.2 典型应用场景配置示例
场景一:ADC多通道循环采集到RAM(乒乓缓冲区)
- 需求:ADC1的4个通道连续采样,每采样完一组(4个值)就通过DMA存入RAM,并通知CPU处理。
- 配置思路:
- 触发源:配置为
ADCAINT1_DMA(ADC1序列器1中断)。 - 传输模式:
CONTINUOUS = 1(连续模式),ONESHOT = 0。 - 数据与地址:
SRC_ADDR: ADC结果寄存器基地址(固定,BURST_STEP=0)。DST_ADDR: RAM中乒乓缓冲区A的起始地址。BURST_SIZE = 3(传输4个16位结果)。TRANSFER_SIZE:设置为缓冲区大小(例如,缓冲区能存100组数据,则设为99)。DST_WRAP_SIZE:设置为缓冲区一半的大小减1(例如,每组4字,缓冲区共200字,一半为100字,WRAP_SIZE=99)。这样,当写满前半部分后,地址自动绕回到后半部分。DST_WRAP_STEP:设置为缓冲区一半的大小(100)。这样,当环绕发生时,起始地址跳到另一半缓冲区。
- 中断:
CHINTMODE = 0(传输开始中断)。当DMA开始写另一半缓冲区时,中断CPU处理已满的前一半缓冲区。
- 触发源:配置为
场景二:通过SPI发送大量数据
- 需求:从RAM中一个大数组通过SPI发送出去。
- 配置思路:
- 触发源:可以是软件触发启动,然后靠SPI的
SPIx_TXDMA请求来维持(每次发送FIFO有空位就触发DMA搬运下一个数据)。 - 传输模式:
CONTINUOUS = 0,ONESHOT = 1(因为希望一次性发完,中途不希望被其他通道打断)。 - 数据与地址:
SRC_ADDR: 数组起始地址,SRC_BURST_STEP = 2(假设是32位数据)。DST_ADDR: SPI发送数据寄存器地址(固定,DST_BURST_STEP = 0)。BURST_SIZE:根据SPI FIFO深度合理设置,例如7(一次突发发8个字)。TRANSFER_SIZE:数组总大小 / 每突发字数 - 1。
- 触发源:可以是软件触发启动,然后靠SPI的
6.3 调试与问题排查技巧
DMA不启动:
- 检查
CPUSEL和SECMSEL寄存器,确保访问权限正确。 - 检查通道是否使能(
CONTROL.RUN位)。 - 检查触发源配置(
DMACHSRCSELx和PERINTSEL)是否正确,触发事件是否真的发生(查看外设中断标志)。 - 使用软件强制触发(
PERINTFRC)测试DMA本身是否工作。
- 检查
数据传输错误(数据错位、覆盖):
- 重点检查所有STEP和SIZE寄存器的值,确认其单位是16位地址。这是最常见错误。
- 检查源和目的地址的对齐情况(特别是32位传输时是否32位对齐)。
- 在
CONTINUOUS模式下,检查传输完成后影子寄存器的值是否被正确重置或更新。
数据丢失(溢出):
- 检查
CONTROL.OVRFLG溢出标志位。如果置位,说明DMA服务速度跟不上触发速度。 - 解决方案:增大
BURST_SIZE,减少单次触发需要服务的次数;或者提高该通道的优先级(使用通道1高优先级模式);或者优化触发频率。
- 检查
系统实时性变差:
- 可能是DMA占用总线时间过长。检查是否有通道配置了
ONESHOT=1且传输量很大。 - 使用高优先级通道1来处理最紧急的数据,其他数据使用轮询模式。
- 利用
HALT功能(或仿真暂停)在调试时暂停DMA,观察系统表现。
- 可能是DMA占用总线时间过长。检查是否有通道配置了
掌握TMS320F2837xD的DMA控制器,意味着你能够精细地调度数据流,让CPU专注于核心算法。从理解子系统权限这个“准入规则”,到灵活运用触发、双循环、优先级这些“调度策略”,再到规避总线冲突和配置陷阱,每一步都需要结合芯片手册的理论和实际项目的需求。建议在项目初期就规划好DMA的使用策略,并编写模块化的DMA配置函数,这将为复杂嵌入式系统的稳定高效运行打下坚实基础。
