C2000 DMA寄存器详解与多核通信实战配置指南

C2000 DMA寄存器详解与多核通信实战配置指南

1. 项目概述与DMA核心价值

在嵌入式实时控制系统的开发中,尤其是在处理电机控制、数字电源、高频数据采集这类对时序和效率要求极其苛刻的场景时,CPU的每一滴算力都显得弥足珍贵。如果让CPU亲自去搬运ADC采样得到的大量数据,或者频繁地在内存与串行外设接口(SPI)之间拷贝数据,无异于让一位高级工程师去干快递员的活,不仅大材小用,更会严重拖慢整个系统的响应速度。这时,直接存储器访问(DMA)模块的价值就凸显出来了。

你可以把DMA想象成系统内部一个高度专业、不知疲倦的“数据搬运工”。它独立于CPU核心,拥有自己的控制逻辑和地址生成单元。当外设(比如ADC转换完成)或软件产生一个“有货要搬”的信号(事件触发)时,DMA控制器就会自动启动,按照你预先设定好的“搬运清单”(即寄存器配置),精准地从源地址读取数据,再写入目标地址,整个过程无需CPU插手。CPU只需要在初始化时写好这份“清单”,并在搬运完成后处理一下中断(如果需要),其余时间可以完全专注于执行控制算法等核心任务。

对于德州仪器(TI)的C2000系列微控制器,如TMS320F2837xS,其DMA模块的功能尤为强大。它不仅支持高效的内存到内存、外设到内存的数据传输,更在多核(C28x+CLA)架构中扮演了关键角色,是实现核间高效、无干扰通信的基石。理解并熟练配置其寄存器,是从“能用”到“用好”C2000的必经之路。本文将从最基础的寄存器位定义讲起,结合官方示例代码,带你一步步拆解DMA的工作机制,并最终实现一个实用的多核间通过共享外设触发DMA的通信案例。

2. DMA寄存器全景与核心概念解析

在深入每个寄存器之前,我们必须先建立几个核心概念,这有助于理解后续所有配置的意图。C2000的DMA模块通常包含多个独立的通道(例如6个),每个通道都有一套完全独立的寄存器组来控制其行为。数据传输过程被组织为三个层级:传输(Transfer)突发(Burst)字(Word)

一个完整的传输由若干次突发构成,而一次突发则包含若干个的连续搬运。这种层级结构非常灵活。例如,在ADC采样场景中,你可以配置一次传输(Transfer)对应一整个采样帧(比如256个点),而一次突发(Burst)则对应ADC结果寄存器被填满时产生的一个事件(比如每次产生8个采样值)。当DMA完成一个字的搬运后,源地址和目标地址会根据SRC_BURST_STEPDST_BURST_STEP寄存器进行更新(通常是+1或+2);完成一次突发后,地址则会根据SRC_TRANSFER_STEPDST_TRANSFER_STEP更新;当整个传输完成,如果使能了地址环绕(Wrap)功能,地址还可能根据SRC_WRAP_STEP等寄存器进行大幅度的跳转,这常用于循环缓冲区的实现。

所有通道共享少数几个全局控制寄存器,而每个通道则拥有多达二十几个专属寄存器。为了方便管理,TI的软件库(如DriverLib)和寄存器定义头文件会将这些寄存器组织成清晰的结构体。理解这个结构是手动配置或调试的基础。

2.1 全局控制寄存器:指挥官

全局寄存器控制着整个DMA模块的顶层行为,它们位于一个固定的基地址(例如0x0000_1000)。虽然数量不多,但每一个都至关重要。

DMACTRL (DMA Control Register): 这是模块的总开关和复位控制。

  • HARDRESET (位0): 硬复位位。向此位写1会复位整个DMA模块,立即中止所有正在进行的数据传输,并将所有寄存器恢复为默认值。这相当于给整个DMA部门拉闸断电再重启,是一种非常粗暴但彻底的复位方式。手册特别强调,写此位后需要插入一个空操作指令(如NOP)等待一个周期,再访问其他DMA寄存器,以确保复位生效。
  • PRIORITYRESET (位1): 优先级复位位。在多通道环境下,DMA内部有一个轮询调度状态机。向此位写1会重置这个状态机,让服务从第一个使能的通道重新开始。如果通道1被设置为高优先级,则复位后从通道2开始。这个复位不会中止当前正在进行的突发传输,会等待其完成,行为相对“温和”。

DEBUGCTRL (Debug Control Register): 主要用于仿真调试。

  • FREE (位15): 仿真运行控制位。当你在CCS中设置断点暂停CPU时,这个位决定了DMA的行为。设为0,DMA会在完成当前读写操作后停止;设为1,DMA则无视仿真暂停,继续运行。在调试涉及DMA的实时数据流时,这个位的设置需要格外小心,否则你可能永远抓不到“正在传输”的瞬间。

PRIORITYCTRL1 (Priority Control 1 Register): 用于配置通道1的优先级。

  • CH1PRIORITY (位0): 通道1优先级选择。默认为0,表示通道1与其他通道平等参与轮询。设置为1时,通道1拥有最高优先级,只要其有触发事件,就会立即中断当前服务的低优先级通道,抢占DMA总线进行传输。关键点:修改此优先级必须在所有通道均被禁用的情况下进行,并且在修改后、重新使能通道前,必须执行一次优先级复位(写PRIORITYRESET位)。

PRIORITYSTAT (Priority Status Register): 用于监视DMA内部状态。

  • ACTIVESTS (位[2:0]): 指示当前正在执行传输的通道编号(1-6)。值为0表示没有通道活跃。
  • ACTIVESTS_SHADOW (位[6:4]): 当通道1为高优先级时,此影子寄存器用于保存被通道1中断的那个低优先级通道的编号。当通道1的服务完成后,DMA会依据此影子寄存器的值恢复被中断通道的服务。这是一个用于实现优先级抢占和恢复的内部机制,对软件透明,但在调试复杂抢占场景时,查看此寄存器有助于理解DMA的内部调度序列。

2.2 通道控制寄存器:每个搬运工的工单

每个DMA通道都有一套完整的寄存器,用于定义其具体的搬运任务。这套寄存器可以大致分为四类:模式与控制类尺寸与计数类地址与步进类地址环绕类

MODE寄存器定义了通道的工作模式。

  • PERINTE (位8): 外设事件触发使能。这是DMA自动工作的关键,必须置1,DMA才会响应外设(如SPI发送完成、ADC序列转换结束)或软件强制触发的事件。
  • PERINTSEL (位[4:0]):注意,在F2837x等较新器件中,这个位域是“遗产”位,实际的外设触发源选择是通过另一组独立的DMACHSRCSELn寄存器完成的。通常这里只需简单地写入通道号本身(例如通道1就写1)。真正的触发源映射需要查阅芯片的《技术参考手册》。
  • CHINTMODE (位9): 通道中断生成模式。0=在一次传输开始时产生中断;1=在一次传输完成时产生中断。绝大多数应用场景我们选择1,在数据就绪后再通知CPU。
  • CHINTE (位15): 通道中断使能。需要产生中断时置1。
  • DATASIZE (位14): 数据大小。0=每次搬运16位(半字);1=每次搬运32位(字)。重要:无论此位如何设置,其他所有涉及长度、步进的寄存器(如BURST_SIZE,SRC_BURST_STEP)其单位都是16位字。如果选择32位模式,那么地址步进值需要相应调整(例如,想每次地址+1个32位字,则需要设置步进为+2)。
  • ONESHOT (位10): 单次触发模式。如果置1,一个外设事件触发会导致DMA完成整个传输(即搬完TRANSFER_SIZE个突发)。如果为0,则一个触发只引起一次突发传输。对于需要与每个外设事件严格同步的场景(如每个PWM周期更新一次比较寄存器),常用ONESHOT模式。
  • CONTINUOUS (位11): 连续模式。置1后,当一次传输完成(TRANSFER_COUNT减到0),DMA会自动用影子寄存器的值重新加载所有活动寄存器,并等待下一个触发,从而实现循环传输。这在实现“乒乓”缓冲区或持续数据流时非常有用。

CONTROL寄存器是通道的实时控制与状态中心。

  • RUN (位0): 运行位。写1使能通道,通道状态RUNSTS位会随之置1。这是启动DMA通道的“启动按钮”。
  • HALT (位1): 暂停位。写1会暂停通道,但保持当前所有寄存器状态。可用于临时挂起传输。
  • SOFTRESET (位2): 通道软复位。写1会复位该通道到默认状态(停止,计数器清零),但不会像硬复位那样影响其他通道。同样,操作后需要NOP等待。
  • PERINTFRC (位3): 软件强制触发位。即使没有外设事件,向此位写1也能手动启动一次DMA传输,非常便于测试。
  • PERINTFLG (位8): 外设事件触发标志位。当触发事件到来(或软件强制)时,此位由硬件置1。当DMA开始处理第一个突发时,此位自动清零。你可以通过查询此位来了解是否有待处理的触发。
  • TRANSFERSTS (位11) & BURSTSTS (位12): 传输状态和突发状态标志。分别指示通道是否正处于一次传输或一次突发中。
  • RUNSTS (位13): 运行状态标志。为1表示通道已使能,正在等待或进行传输。

尺寸与计数寄存器定义了搬运的“量”。

  • BURST_SIZE: 定义一个突发包含多少个(16位)。实际突发大小 = 设定值 + 1。例如,想一次突发搬8个字,则写入7。
  • BURST_COUNT: 只读寄存器,显示当前突发中还剩多少个字待搬运。
  • TRANSFER_SIZE: 定义一次传输包含多少个突发。实际传输大小 = 设定值 + 1。
  • TRANSFER_COUNT: 只读寄存器,显示当前传输中还剩多少个突发待完成。

地址与步进寄存器定义了搬运的“路径”。

  • SRC_BEG_ADDR_SHADOW/DST_BEG_ADDR_SHADOW:影子开始地址寄存器。这是你在DMA传输过程中也可以安全写入的配置寄存器。当一次新的传输启动时,这里的值会被加载到对应的“活动”寄存器中。
  • SRC_ADDR_SHADOW/DST_ADDR_SHADOW:影子当前地址寄存器。同样可安全更新,传输开始时加载到活动地址。
  • SRC_BEG_ADDR_ACTIVE/DST_BEG_ADDR_ACTIVE:活动开始地址寄存器。只读,反映当前传输周期使用的起始地址。
  • SRC_ADDR_ACTIVE/DST_ADDR_ACTIVE:活动当前地址寄存器。只读,实时显示DMA当前正在读/写的地址。这是调试时观察数据流的关键窗口。
  • SRC_BURST_STEP/DST_BURST_STEP:突发内步进。每搬运一个字后,源/目标地址的增量(可正可负)。例如,从连续内存区域读取,通常设为+1或+2(32位模式)。
  • SRC_TRANSFER_STEP/DST_TRANSFER_STEP:传输间步进。每完成一个突发后,源/目标地址的增量。用于在内存中跳转到下一个数据块。

地址环绕寄存器实现了复杂的缓冲区管理。

  • SRC_WRAP_SIZE/DST_WRAP_SIZE:环绕大小。定义在多少次突发后,地址需要“绕回”到起始点。实际环绕前突发数 = 设定值 + 1。如果想禁用环绕,将此值设得比TRANSFER_SIZE大即可。
  • SRC_WRAP_COUNT/DST_WRAP_COUNT:环绕计数器。只读,显示当前距离下一次环绕还有多少个突发。
  • SRC_WRAP_STEP/DST_WRAP_STEP:环绕步进。当环绕发生时,不是简单地跳回BEG_ADDR,而是在此地址基础上加上WRAP_STEP的值。这是实现“乒乓缓冲区”或“多缓冲区”的精华所在。例如,你可以设置一个大小为N的缓冲区,WRAP_SIZE设为N-1,WRAP_STEP设为+N。这样,DMA会在填满第一个缓冲区后,自动将起始地址跳到第二个缓冲区的开头,实现自动切换。

实操心得:寄存器配置的顺序逻辑配置DMA通道时,务必遵循一个安全的顺序:先配置所有参数寄存器(模式、尺寸、地址、步进等),最后再置位CONTROL.RUN来启动通道。在修改任何配置参数前,应先通过CONTROL.HALT暂停通道,或通过CONTROL.SOFTRESET复位通道。对于SRC_BEG_ADDR_SHADOW这类影子寄存器,虽然可以在传输中更新,但为了逻辑清晰,建议也在通道停止时修改。乱序操作可能导致DMA行为异常,甚至引发总线错误。

3. 从理论到实践:DMA工程实例深度剖析

TI的C2000Ware软件包提供了丰富的DMA示例,这些示例是学习的最佳起点。我们选取两个最具代表性的例子进行拆解,看看寄存器配置是如何转化为具体功能的。

3.1 实例一:GSRAM间数据传输 (dma_ex1_gsram_transfer)

这个例子展示了DMA最基础的功能:内存到内存的数据搬运。它使用一个DMA通道,将数据从RAMGS0中的一个缓冲区搬运到RAMGS1中的另一个缓冲区。

核心配置解析:

  1. 地址设置:
    • SRC_BEG_ADDR_SHADOW/SRC_ADDR_SHADOW: 设置为源缓冲区(位于RAMGS0)的起始地址。
    • DST_BEG_ADDR_SHADOW/DST_ADDR_SHADOW: 设置为目标缓冲区(位于RAMGS1)的起始地址。
  2. 尺寸与步进设置:
    • BURST_SIZE = 7: 表示一个突发传输8个16位字(7+1)。
    • TRANSFER_SIZE = 15: 表示一次完整传输包含16个突发(15+1)。因此,总数据量 = 8字/突发 * 16突发 = 128字。
    • SRC_BURST_STEP/DST_BURST_STEP = 1: 每搬运一个字,源和目标地址都+1,指向下一个字。
    • SRC_TRANSFER_STEP/DST_TRANSFER_STEP = 8: 每完成一个突发(8个字),源和目标地址在突发内步进累积的基础上,再+8。但在这个简单线性搬运的例子中,由于突发内步进已经让地址连续移动,传输间步进通常设为0。这里设为8可能是个示例特例,或者是为了演示功能。通常线性搬运时,传输间步进设为0。
  3. 模式与控制:
    • MODE.CHINTE = 1: 使能通道中断。
    • MODE.CHINTMODE = 1: 传输完成时产生中断。
    • MODE.PERINTE = 1: 使能外设事件触发(本例中由软件强制模拟)。
    • MODE.ONESHOT = 1? 根据描述,示例是通过反复写PERINTFRC来触发,直到完成16次突发。这意味着它可能没有设置ONESHOT=1,而是采用“一次触发对应一次突发”的模式,由主循环软件控制触发16次。如果设置了ONESHOT=1TRANSFER_SIZE=15,那么一次软件触发就能完成全部128字的搬运。
    • CONTROL.RUN = 1: 使能通道,等待触发。

工作流程:

  1. 主程序初始化源数据缓冲区 (sData)。
  2. 按上述配置初始化DMA通道。
  3. 进入循环,连续16次向CONTROL.PERINTFRC位写1,手动触发DMA突发。
  4. DMA每次被触发,搬运一个突发(8个字)。搬运完16个突发(共128字)后,TRANSFER_COUNT减为0,触发DMA中断。
  5. 在中断服务程序(ISR)中,检查目标缓冲区 (rData) 的数据是否正确,并置位完成标志。

注意事项:软件触发与硬件触发的区别此例使用软件强制触发 (PERINTFRC) 来模拟外设事件。在实际应用中,硬件触发源(如ADC、SPI、ePWM)会在特定时刻自动置起PERINTFLG标志。软件触发常用于测试或手动启动传输。需要注意的是,如果PERINTFLG标志已经为1(即有一个触发未处理),此时再来一个新的硬件触发,会导致溢出标志OVRFLG置位。软件写PERINTFRC则不会引起溢出,它只在PERINTFLG为0时才能将其置1。

3.2 实例二&三:多核共享外设触发 (dma_ex1_shared_periph_cpu1/2)

这个例子展示了C2000多核系统中DMA的高级应用:一个核(CPU2)控制的外设(如SPI)产生的事件,去触发另一个核(CPU1)上的DMA传输。这对于双核协同工作、负载分离至关重要。

场景设定:

  • CPU2: 拥有一个定时器和一个SPI外设(作为主设备)。定时器周期性中断,在中断服务程序中,CPU2启动一次SPI传输。
  • CPU1: 配置了一个DMA通道,其触发源被映射到CPU2所拥有的那个SPI的发送事件(例如SPITXINT)。同时,CPU1还控制着一个ePWM模块。

目标:CPU2的定时器定时触发SPI通信,SPI的发送完成事件自动触发CPU1的DMA。DMA则将预设的数据搬运到CPU1的ePWM模块的比较寄存器(CMPA)中,从而动态改变PWM的占空比。最终,在CPU1的GPIO引脚上可以用示波器观察到受DMA更新的PWM波形。

关键配置与核间协作:

  1. 触发源映射(关键!): 这是多核DMA的核心。在C28x_DUAL器件中,外设中断源是分配到具体CPU的。默认情况下,CPU2的SPI中断不会直接触发CPU1的DMA。需要通过DMACHSRCSELn寄存器(属于DMA_CLA_SRC_SEL_REGS组)进行交叉触发映射。你需要将CPU1上某个DMA通道的触发源,选择为来自CPU2的SPI发送中断。这个过程通常由TI的SysConfig图形化工具完成,它会自动生成正确的配置代码。手动配置则需要仔细查阅芯片数据手册的“Interrupt”和“DMA”章节,找到正确的映射关系位域。
  2. CPU1的DMA配置:
    • MODE.PERINTE = 1: 使能外设触发。
    • MODE.PERINTSEL: 按惯例写入通道号,但实际触发源由上述DMACHSRCSELn决定。
    • MODE.ONESHOT = 1: 很可能设置为1,这样一次SPI发送完成事件就能让DMA完成整个传输(例如,更新一个CMPA值只需要一次突发搬运一个字)。
    • DST_BEG_ADDR_SHADOW: 目标地址设置为ePWM1的CMPA寄存器地址。这是一个外设寄存器地址,而非内存地址。DMA可以访问整个内存映射空间,包括外设寄存器帧。
    • BURST_SIZE = 0: 一次突发只搬运1个字(0+1)。
    • TRANSFER_SIZE = 0: 一次传输只包含1个突发(0+1)。所以,一次触发就搬运一个字到CMPA。
    • CONTROL.RUN = 1: 使能DMA通道,等待来自CPU2的SPI事件。
  3. CPU2的软件流程:
    • 配置定时器和SPI。
    • 在定时器中断中,向SPI数据寄存器写入数据,启动SPI发送。
    • SPI硬件自动发送数据,发送完成后产生中断事件。这个事件通过芯片内部的交叉触发网络,送到了CPU1的DMA通道。
  4. 执行顺序建议: 示例注释中提到,建议先运行CPU1的代码(初始化DMA并使其进入等待状态),然后再运行CPU2的代码(开始定时触发)。这样可以确保DMA就绪在先,避免第一个触发事件丢失。

这个例子的精妙之处在于,它实现了时间解耦功能解耦。CPU2只负责按节奏产生“触发信号”(通过SPI),它完全不知道也不关心CPU1用这个信号具体做了什么。CPU1则专注于根据触发信号更新PWM,它不需要主动去查询或请求数据。DMA作为无声的桥梁,以极低的延迟和CPU开销,将两个核的动作精确地同步起来。这种模式非常适用于主从核架构,例如CPU2处理通信和调度,CPU1专精于高速实时控制。

4. 寄存器配置实战指南与避坑要点

理解了原理和示例,现在我们来梳理一套完整的、可复用的DMA通道配置流程,并分享一些从实践中总结出来的“坑”与技巧。

4.1 标准配置流程

以下是一个通用的DMA通道初始化函数应遵循的步骤:

void DMA_Channel_Config(uint16_t ch) { // 步骤0:确保通道被禁用或复位 DmaRegs.CH[ch].CONTROL.bit.RUN = 0; // 确保RUN位为0 // 或者使用软复位,更彻底 DmaRegs.CH[ch].CONTROL.bit.SOFTRESET = 1; asm(" NOP"); // 等待一个周期,确保复位生效 // 步骤1:配置工作模式 (MODE寄存器) DmaRegs.CH[ch].MODE.bit.PERINTE = 1; // 使能外设触发 DmaRegs.CH[ch].MODE.bit.PERINTSEL = ch; // 按惯例设为本通道号(实际源由DMACHSRCSELn决定) DmaRegs.CH[ch].MODE.bit.CHINTE = 1; // 使能中断(如果需要) DmaRegs.CH[ch].MODE.bit.CHINTMODE = 1; // 传输完成产生中断 DmaRegs.CH[ch].MODE.bit.ONESHOT = 0; // 根据需求选择:0=单次突发/触发,1=单次传输/触发 DmaRegs.CH[ch].MODE.bit.CONTINUOUS = 0; // 根据需求选择:0=单次传输,1=连续循环 DmaRegs.CH[ch].MODE.bit.DATASIZE = 0; // 0=16-bit, 1=32-bit // 步骤2:配置传输尺寸 (BURST_SIZE, TRANSFER_SIZE) DmaRegs.CH[ch].BURST_SIZE.bit.BURSTSIZE = BURST_LEN - 1; // 突发字数 DmaRegs.CH[ch].TRANSFER_SIZE.bit.TRANSFERSIZE = TRANSFER_BURSTS - 1; // 传输包含的突发数 // 步骤3:配置地址与步进 // 3.1 配置源地址相关 DmaRegs.CH[ch].SRC_BEG_ADDR_SHADOW = (uint32_t)src_buffer; // 影子起始地址 DmaRegs.CH[ch].SRC_ADDR_SHADOW = (uint32_t)src_buffer; // 影子当前地址 DmaRegs.CH[ch].SRC_BURST_STEP.bit.SRCBURSTSTEP = src_burst_step; // 突发内地址增量,通常为1或2 DmaRegs.CH[ch].SRC_TRANSFER_STEP.bit.SRCTRANSFERSTEP = src_transfer_step; // 传输间地址增量 // 3.2 配置目标地址相关 DmaRegs.CH[ch].DST_BEG_ADDR_SHADOW = (uint32_t)dst_buffer; DmaRegs.CH[ch].DST_ADDR_SHADOW = (uint32_t)dst_buffer; DmaRegs.CH[ch].DST_BURST_STEP.bit.DSTBURSTSTEP = dst_burst_step; DmaRegs.CH[ch].DST_TRANSFER_STEP.bit.DSTTRANSFERSTEP = dst_transfer_step; // 步骤4:配置地址环绕(如果需要,例如乒乓缓冲区) if(wrap_enable) { DmaRegs.CH[ch].SRC_WRAP_SIZE.bit.WRAPSIZE = WRAP_SIZE - 1; DmaRegs.CH[ch].SRC_WRAP_STEP.bit.WRAPSTEP = src_wrap_step; // 环绕后起始地址的偏移 // 同样配置 DST_WRAP_SIZE 和 DST_WRAP_STEP } else { // 禁用环绕:将WRAPSIZE设置为大于TRANSFERSIZE的值 DmaRegs.CH[ch].SRC_WRAP_SIZE.bit.WRAPSIZE = 0xFFFF; // 最大值 DmaRegs.CH[ch].DST_WRAP_SIZE.bit.WRAPSIZE = 0xFFFF; } // 步骤5:(关键)配置外设触发源选择 (DMACHSRCSELn) // 这是多核或复杂外设触发的关键!需要根据具体外设和CPU映射来设置。 // 例如,将通道ch的触发源选择为ADCINT1: // DmaClaSrcSelRegs.DMACHSRCSEL1.bit.CH1 = 0; // 假设CH1对应ADCINT1,需查表确认 // 此部分代码高度依赖具体器件和连接,通常由SysConfig生成。 // 步骤6:清除任何可能存在的旧标志位 DmaRegs.CH[ch].CONTROL.bit.PERINTCLR = 1; // 清除未决的触发标志 DmaRegs.CH[ch].CONTROL.bit.ERRCLR = 1; // 清除溢出错误标志 // 步骤7:使能通道,等待触发 DmaRegs.CH[ch].CONTROL.bit.RUN = 1; // 步骤8:如果需要,使能对应的CPU中断(PIE组和中断线) // PieCtrlRegs.PIEIERx.bit.INTyx = 1; // 使能PIE组内中断 // IER |= M_INTx; // 使能CPU级中断 // EINT; // 全局开中断 }

4.2 常见问题排查与调试技巧

即使按照流程配置,DMA有时也会“沉默”或不按预期工作。以下是一个排查清单和调试思路:

现象可能原因排查步骤与解决方法
DMA完全不启动1. 通道未使能 (RUN位不为1)。
2. 外设触发未使能 (PERINTE位为0)。
3. 触发源映射错误 (DMACHSRCSELn配置错误)。
4. 外设本身未产生中断事件。
1. 检查CONTROL.RUNSTS位,确认是否为1。
2. 检查MODE.PERINTE位。
3.重点检查DMACHSRCSELn寄存器配置,对照数据手册的“Interrupt Mux”表格,确认物理中断源是否正确映射到了DMA通道。
4. 检查外设配置,确认其中断是否已使能并正确产生。可以先用CPU中断测试该外设事件是否正常。
DMA只搬运一次后停止1.CONTINUOUS模式未使能,且ONESHOT=1时,一次触发完成整个传输后停止。
2. 触发事件未持续产生。
1. 检查MODE.CONTINUOUSMODE.ONESHOT的设置是否符合预期。如果需要循环搬运,应设置CONTINUOUS=1
2. 检查外设是否周期性产生触发事件。对于软件触发,检查触发代码是否在循环中。
数据搬运到了错误地址1. 源/目标地址寄存器 (SHADOW) 配置错误。
2. 步进寄存器 (BURST_STEP,TRANSFER_STEP) 配置错误,特别是32位模式时。
3. 地址环绕配置有误。
1. 在调试器中查看SRC_ADDR_ACTIVEDST_ADDR_ACTIVE,观察其变化是否符合预期。
2. 确认DATASIZE设置。若为32位模式,地址步进应以16位字为单位。想地址+4字节(1个32位字),步进应设为+2。
3. 检查WRAP_SIZEWRAP_STEP的计算。确保环绕逻辑符合缓冲区设计。
DMA中断未触发1. 通道中断未使能 (CHINTE=0)。
2. CPU级或PIE级中断未使能。
3. 中断标志未清除或中断被屏蔽。
4.CHINTMODE设置与预期不符(期望结束时中断却设成了开始时)。
1. 检查MODE.CHINTEMODE.CHINTMODE
2. 检查PIE配置 (PIEIER,PIEIFR) 和CPU的IER、IFR寄存器。
3. 在DMA ISR中,需要手动清除DMA通道的中断标志(向DMAINTFLG寄存器的对应位写1)并应答PIE (PieCtrlRegs.PIEACK)。
多核系统中,对端CPU的DMA无响应1. 触发源未正确映射到对端CPU的DMA通道。
2. 对端CPU的DMA通道未正确初始化或未使能。
3. 核间内存共享区域未正确配置或存在一致性问题。
1.这是最常见原因。双核对DMACHSRCSELn寄存器的配置必须一致且正确。确保触发源是从“属”CPU映射到了“主”CPU的DMA通道。
2. 确认对端CPU的代码已运行,且DMARUN位已置1。
3. 如果涉及共享RAM数据,确保双核对该内存区域的访问权限已配置(通过CSMMPU),并且考虑缓存一致性问题(必要时使用CACHE操作或UNCACHED内存段)。

高级调试技巧:

  • 使用CCS的寄存器视图和内存浏览器:实时监控CONTROL寄存器中的状态位 (RUNSTS,TRANSFERSTS,BURSTSTS,PERINTFLG),以及ACTIVE地址寄存器的变化,可以直观看到DMA是否在工作、处于哪个阶段。
  • 利用软件强制触发 (PERINTFRC):在硬件触发逻辑复杂时,先使用软件触发测试DMA配置是否正确。如果能正常搬运,则问题可能出在触发源映射或外设本身。
  • 关注时序与同步:在多核或DMA与CPU共享总线/内存的场景,如果DMA访问的内存区域被CPU频繁访问,可能导致总线竞争,影响实时性。合理规划内存布局,将DMA缓冲区放在冲突少的RAM块(如GSRAM),或使用CPU缓存锁定技术。
  • 理解EALLOW保护:DMA的配置寄存器大多受EALLOW保护。在写这些寄存器前,需要执行EALLOW指令,写完后执行EDIS指令。TI的DriverLib函数内部已经处理了这一点,但如果是直接操作寄存器,务必注意。

5. 进阶应用:构建一个双核乒乓缓冲区数据流

让我们综合运用以上知识,设计一个更复杂的实战场景:CPU1进行高速ADC采样,通过DMA将数据存入一个乒乓缓冲区。CPU2通过另一个DMA通道,在缓冲区半满时,将数据搬移到外部存储器或通过通信接口发送出去。

系统设计:

  1. 内存规划:在共享的GSRAM中定义两个缓冲区BufferA[256]BufferB[256]
  2. CPU1的ADC-DMA配置
    • 配置ADC序列,每完成256次转换产生一个中断(DMA触发源)。
    • 配置DMA通道1为连续模式 (CONTINUOUS=1),单次触发模式 (ONESHOT=1),TRANSFER_SIZE=255(256次突发),BURST_SIZE=0(每次突发1个字,即ADC一个结果)。
    • 设置SRC_BEG_ADDR_SHADOW为ADC结果寄存器地址。
    • 设置DST_BEG_ADDR_SHADOW初始指向BufferA
    • 关键:配置地址环绕。DST_WRAP_SIZE = 255(256次突发后环绕),DST_WRAP_STEP = 256(环绕后,目标起始地址跳转256个字,即从BufferA切换到BufferB)。
    • 使能传输完成中断。在中断中,无需搬运数据,仅设置一个标志通知CPU2哪个缓冲区已满。
  3. CPU2的搬运-DMA配置
    • 配置一个由CPU1软件标志触发(可通过IPC中断或共享变量)的DMA通道2。
    • 源地址设置为已满的缓冲区(BufferABufferB),目标地址为外部存储器接口或SPI发送缓冲区。
    • 配置合适的突发和传输大小。
    • CPU1在ADC-DMA中断中,通过核间通信(IPC)触发CPU2的DMA开始搬运另一个缓冲区。
  4. 优势:ADC采样是连续、硬实时的,DMA保证了极低的延迟和CPU零开销。数据处理和传输由CPU2并行完成,实现了高效的流水线操作。乒乓缓冲区避免了数据竞争,确保了数据的完整性。

这个案例体现了DMA在复杂实时系统中的核心价值:它不仅是数据搬运工,更是构建高效、确定性系统架构的关键组件。通过精细的寄存器配置,你可以让DMA按照精确的“剧本”自动完成复杂的数据流管理,将CPU解放出来去处理真正需要智能决策的任务。