news 2026/7/21 6:50:43

TMS320F28004x DMA模块架构解析与驱动开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TMS320F28004x DMA模块架构解析与驱动开发实战

1. DMA模块核心架构与设计思路拆解

在嵌入式实时控制系统中,CPU的算力是宝贵的资源。当系统需要频繁地在内存与外设之间搬运数据时,例如ADC连续采样、SPI/UART通信数据收发,如果这些操作都由CPU通过软件循环来完成,会大量占用CPU时间,导致系统响应变慢,实时性下降。TMS320F28004x的DMA模块就是为了解决这个问题而设计的硬件“搬运工”。

它的核心价值在于“解放CPU”。DMA模块可以独立于CPU工作,当特定事件(如ADC转换完成、SPI接收缓冲区满)发生时,自动将数据从源头(如ADC结果寄存器)搬运到目的地(如一块指定的RAM区域)。整个过程无需CPU介入,CPU可以继续执行主控制循环或复杂的算法运算。这不仅提升了系统整体吞吐量,也使得CPU能够更专注于核心的控制与计算任务,对于电机控制、数字电源、高性能传感等对实时性要求苛刻的应用至关重要。

TMS320F28004x的DMA模块提供了6个独立的通道(Channel 1-6),每个通道都可以独立配置,互不干扰。其中,Channel 1可以被赋予更高的优先级,这在处理紧急数据流时非常有用。模块的架构设计围绕几个核心概念展开:触发源地址指针控制数据传输模式

触发源是DMA工作的“发令枪”。它可以是软件手动触发,也可以是来自众多外设的硬件事件。从你提供的资料中可以看到,触发源的选择非常灵活,通过DMACHSRCSELxCHx.MODE.PERINTSEL寄存器配合,几乎可以连接到芯片上所有能产生中断的外设,包括多个ADC的各个中断事件、ePWM的SOC信号、SPI的TX/RX DMA请求、外部中断XINT、定时器中断等。这种设计使得DMA能够紧密地与各种数据产生单元协同工作。

地址指针控制是DMA的“导航系统”,决定了数据从哪里来、到哪里去、以及如何遍历。这是DMA配置中最灵活也最核心的部分。它采用了两层嵌套循环的机制:

  • 突发循环:每次触发事件到来时,DMA会连续搬运一定数量的数据单元(字)。这个数量由BURST_SIZE定义。在搬运每个字时,源地址和目的地址会根据SRC_BURST_STEPDST_BURST_STEP进行递增或递减。
  • 传输循环:它定义了需要完成多少次这样的“突发”才算一次完整的DMA传输,次数由TRANSFER_SIZE定义。每次完成一个突发后,地址指针会再根据SRC_TRANSFER_STEPDST_TRANSFER_STEP进行第二次调整。

更强大的是地址环绕功能。通过设置WRAP_SIZEWRAP_STEP,可以在一次大的传输循环内部,实现类似“乒乓缓冲区”或循环缓冲区的效果。当传输的突发数达到WRAP_SIZE设定值时,地址指针不是简单地加上TRANSFER_STEP,而是跳回到由BEG_ADDR定义的起始地址,并加上WRAP_STEP作为偏移,形成一个新的起始点。这非常适合处理需要将数据分块、重组或循环覆盖的场景。

数据传输模式则决定了DMA的“工作节奏”。ONESHOT模式决定了一次触发是搬运一个突发(ONESHOT=0)还是搬运完整个传输循环的所有数据(ONESHOT=1)。CONTINUOUS模式决定了在一次完整的传输循环结束后,通道是自动禁用(CONTINUOUS=0)还是保持使能,等待下一次触发(CONTINUOUS=1)。CHINTMODE则选择是在传输开始时还是在传输结束时产生中断,这在与CPU进行“乒乓”缓冲区切换时非常关键。

理解了这个“触发-搬运-寻址”的核心架构,我们就能明白,配置DMA的本质就是:告诉DMA硬件,在什么事件发生时(触发源),从哪里(源地址及步进规则)搬运多少数据(突发大小和传输大小),放到哪里去(目的地址及步进规则),以及以何种节奏进行(单次/连续、中断时机)。接下来的所有寄存器配置和Driverlib函数调用,都是围绕实现这个目标而展开的。

2. 寄存器映射与Driverlib函数深度解析

直接操作寄存器是底层开发的终极控制手段,但代码可读性和可维护性差。德州仪器提供的Driverlib库函数,是对寄存器操作的精简封装,让开发者能以更直观的API完成配置。你提供的资料详细列出了从寄存器到函数的映射关系,我们不仅要看懂这个表格,更要理解每个配置项背后的实际意义和典型用法。

2.1 输入交叉开关配置

输入交叉开关是DMA触发信号的路由器。外部的硬件事件信号需要先路由到DMA模块的特定输入引脚,才能被某个DMA通道识别为触发源。

寄存器与函数映射

  • INPUTxSELECT寄存器:这组寄存器(x=1~16)用于选择连接到DMA输入引脚x的具体信号源。例如,INPUT1SELECT寄存器里的值,决定了哪个外设事件(如ADCAINT1)会出现在DMA的输入1号线上。
  • INPUTSELECTLOCK寄存器:这是一个保护锁。一旦对其写入特定的密钥并置位,所有INPUTxSELECT寄存器将被锁定,防止软件意外修改,提高系统可靠性。这在系统初始化完成后、进入稳定运行阶段时非常有用。

对应的Driverlib函数

  • XBAR_setInputPin(uint32_t input, uint32_t output):这是配置路由的核心函数。input参数对应DMA的输入引脚编号(例如,XBAR_INPUT1),output参数是一个枚举值,对应你想要连接的外设事件(例如,XBAR_OUTPUT_ADCA_INT1)。调用这个函数,实质上就是向对应的INPUTxSELECT寄存器写入特定的值。
  • XBAR_lockInput(void):此函数执行对INPUTSELECTLOCK寄存器的锁定操作。调用后,输入路由配置即被冻结。

配置逻辑与示例: 假设我们需要将ADC-A的转换完成中断1(ADCAINT1)作为DMA通道1的触发源。根据数据手册中的映射表(如你提供的Table 10-1),ADCAINT1_DMA对应的选择索引是1。然而,这个索引值并不是直接写入INPUTxSELECT寄存器的值,寄存器位域定义可能不同。Driverlib函数帮我们屏蔽了这些细节。

更常见的做法是,我们需要先将ADCAINT1这个事件信号路由到DMA的某个输入引脚,比如INPUT1。然后,在DMA通道1的触发源选择寄存器DMACHSRCSEL1.CH1CH1.MODE.PERINTSEL中,指定通道1的触发源来自“INPUT1”。但根据你提供的图10-2,PERINTSEL被设置为通道号本身(CH1设为1),而真正的信号选择是在DMACHSRCSELx寄存器中完成的,该寄存器根据PERINTSEL的值选择256选1的MUX输出。

因此,一个更贴近实际Driverlib使用的流程可能是:

  1. 使用XBAR_setInputPinADCAINT1事件连接到XBAR的某个内部输出(这个输出可能已经硬连线到DMA的触发逻辑,而非一个具体的“输入引脚”)。但更典型的Driverlib用法是直接配置DMA的触发源选择。
  2. 实际上,对于DMA触发源,TI的Driverlib通常提供了更直接的函数,例如DMA_setPeripheralTriggerSource()。你资料中映射表提到的xbar.h/c函数,更多是用于配置通用的交叉开关,而DMA特定的触发源设置可能有其专属的API。

注意:你提供的映射表标题是“INPUTXBAR Registers to Driverlib Functions”,这里的INPUTXBAR可能是一个更广义的输入选择模块,并非特指DMA的触发选择。在实际编程中,应优先查阅dma.h中的函数,如DMA_configTriggerSource()来设置DMA通道触发源。理解寄存器映射关系有助于调试时查看寄存器状态,但开发时应以Driverlib的高级API为主。

2.2 输出与标志位控制

这一部分涉及DMA传输状态的管理和外部信号输出控制。

寄存器与函数映射

  • FLGx/CLRx寄存器:这些是状态标志位和清除寄存器。FLG1~FLG4可能对应DMA相关的一些状态标志(如错误标志、触发标志),CLR1~CLR4用于清除对应的标志。在你的资料中,它们映射到XBAR_getInputFlagStatusXBAR_clearInputFlag函数,这再次提示它们可能属于更通用的交叉开关状态机,用于指示某个输入事件是否发生。
  • OUTPUTxMUX0TO15CFG,OUTPUTxMUX16TO31CFG,OUTPUTxMUXENABLE寄存器:这些寄存器控制着交叉开关的输出多路复用器。它们允许将内部产生的特定信号(例如,一个DMA传输开始或结束的标志)路由到芯片的某个物理输出引脚,或者路由到其他外设(如PWM的Trip输入)作为触发条件。CFG寄存器配置映射关系,ENABLE寄存器使能该路输出。
  • OUTPUTLATCH,OUTPUTINV,OUTPUTLOCK寄存器:这些寄存器对输出信号进行进一步处理。LATCH控制输出锁存模式(如电平、脉冲),INV控制输出是否反相,LOCK用于锁定输出配置,防止误修改。

对应的Driverlib函数

  • XBAR_getInputFlagStatus(uint32_t flag):用于读取某个输入标志位的状态。
  • XBAR_clearInputFlag(uint32_t flag):用于清除某个输入标志位。
  • XBAR_setOutputMuxConfig(uint32_t output, uint32_t input):配置输出多路选择器。output指定哪个输出端口,input指定将哪个内部信号连接到该端口。
  • XBAR_enableOutputMux(uint32_t output)/XBAR_disableOutputMux(uint32_t output):使能或禁用指定的输出多路器。
  • XBAR_setOutputLatchMode(uint32_t output, uint32_t mode):设置输出锁存模式。
  • XBAR_invertOutputSignal(uint32_t output, bool enable):使能或禁用输出信号反相。
  • XBAR_lockOutput(void):锁定所有输出配置寄存器。

应用场景: 假设我们想利用DMA传输完成事件来触发一个外部动作,比如点亮一个LED或触发另一个外设。我们可以:

  1. 通过DMA_enableInterrupt使能通道传输完成中断(但这会触发CPU中断)。
  2. 更高效的方式是,利用DMA模块可能产生的某个内部状态信号(需要查证具体信号名),通过XBAR_setOutputMuxConfig将其路由到一个GPIO引脚配置为输出功能。
  3. 使用XBAR_setOutputLatchMode将其配置为脉冲模式,这样每次DMA传输完成就会在该引脚上产生一个短脉冲,可用于同步其他设备或作为示波器触发信号。

2.3 外设专用交叉开关配置

TMS320F28004x的DMA与高级外设如ePWM和CLB的集成非常紧密,提供了专用的交叉开关配置。

ePWM XBAR (EPWMXBAR): ePWM模块的Trip-Zone输入通常用于故障保护,可以快速关断PWM输出。EPWMXBAR允许将大量的内部信号(包括DMA事件、比较器输出等)灵活地映射到多达12个ePWM模块的Trip输入上。

  • 寄存器TRIPxMUX0TO15CFG,TRIPxMUX16TO31CFG(x=4~12),TRIPxMUXENABLE,TRIPOUTINV,TRIPLOCK
  • 函数XBAR_setEPWMMuxConfig,XBAR_enableEPWMMux,XBAR_disableEPWMMux,XBAR_invertEPWMSignal,XBAR_lockEPWM
  • 实战意义:例如,在电机控制中,我们可以将一个由DMA搬运完成的ADC过流采样值,通过比较器模块产生一个数字故障信号,再通过EPWMXBAR路由到ePWM1的Trip输入,从而实现基于DMA数据的硬件级快速保护,响应速度远快于CPU中断处理。

CLB XBAR: 可配置逻辑块是一个小型、灵活的FPGA-like单元。CLBXBAR用于将内部信号路由到CLB的输入,从而在CLB内部实现自定义的组合或时序逻辑。

  • 寄存器AUXSIGxMUX0TO15CFG,AUXSIGxMUX16TO31CFG(x=0~7),AUXSIGxMUXENABLE,AUXSIGOUTINV
  • 函数XBAR_setCLBMuxConfig,XBAR_enableCLBMux,XBAR_disableCLBMux,XBAR_invertCLBSignal
  • 实战意义:这打开了硬件协处理的大门。例如,可以将ADC采样值和DMA搬运的阈值数据在CLB中进行实时比较,产生自定义的触发事件,再反馈给DMA或ePWM,形成完全由硬件处理的闭环控制链路,极大减轻CPU负担并提升响应速度。

3. DMA驱动开发实战与核心环节实现

理解了寄存器映射和架构,我们现在进入实战环节,手把手完成一个典型的DMA驱动配置。我们将以实现“ADC连续采样,DMA自动搬运至RAM缓冲区”这一经典场景为例。

3.1 环境准备与工程配置

首先,确保你的开发环境已就绪。我们假设使用TI的Code Composer Studio和C2000Ware库(包含Driverlib)。

  1. 创建工程与包含头文件:在CCS中创建一个新的C2000项目。在工程属性中,确保包含了C2000Ware的库文件路径。在你的主源文件或DMA配置文件中,需要包含以下关键头文件:

    #include "driverlib.h" // 主驱动库 #include "inc/hw_memmap.h" // 存储映射定义 #include "inc/hw_types.h" // 类型定义 // 根据具体外设添加 #include "driverlib/adc.h" // ADC驱动 #include "driverlib/dma.h" // DMA驱动 #include "driverlib/xbar.h" // 交叉开关驱动(如需配置XBAR)
  2. 初始化系统时钟与外设时钟:DMA和ADC都需要正确的时钟驱动。通常在主函数初始化阶段调用:

    InitSysCtrl(); // 初始化系统控制,包括PLL和时钟

    这个函数(或其内部实现)会配置PLL将外部晶振时钟倍频到CPU运行频率(例如200MHz),并分配低速和高速外设时钟。

  3. 初始化GPIO(如果使用外部触发):如果DMA触发源来自GPIO引脚的外部中断,则需要配置该引脚为输入功能并映射到XINT模块。本例使用ADC内部触发,可跳过此步。

3.2 DMA通道参数化配置详解

这是DMA驱动的核心。我们将配置DMA通道1,由ADC-A的序列器1中断(ADCAINT1)触发,每次触发搬运ADC结果寄存器ADCRESULT0-ADCRESULT7(共8个16位结果)到全局共享RAM GS0中的一个数组。

步骤1:使能DMA模块并配置通道基本模式

// 1. 使能DMA模块时钟(通常InitSysCtrl已做,但显式调用更安全) SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_DMA); // 2. 初始化DMA控制器(复位DMA状态机) DMA_initController(); // 3. 配置通道1的基本工作模式 uint32_t base = DMA_CH1_BASE; // 通道1的基地址 // 设置数据大小:16位(ADC结果为16位)。若传输32位数据,则使用DMA_DATA_SIZE32。 DMA_setDataSize(base, DMA_DATA_SIZE16); // 设置通道中断模式:在每次传输(Transfer)完成时产生中断。 // DMA_CH_INT_END 表示传输结束时中断。DMA_CH_INT_START表示开始时中断。 DMA_setInterruptMode(base, DMA_CH_INT_END); // 禁用OneShot模式:每次ADC触发只搬运一个突发(Burst)。 // 如果使能,则一次触发会搬运完所有突发(TRANSFER_SIZE+1次),可能阻塞其他通道。 DMA_disableOneShot(base); // 使能连续模式:一次完整的传输循环结束后,通道保持使能,等待下一次触发。 // 这样ADC可以持续采样,DMA持续搬运,形成循环。 DMA_enableContinuousMode(base);

步骤2:配置触发源这是连接外设事件与DMA通道的关键。

// 设置通道1的触发源为 ADC-A 序列器1中断 // ADCA_INT_SEQ1 是在 driverlib/inc/hw_dma.h 或相关头文件中定义的宏,对应触发源索引。 // 具体索引值需查表(如你资料中的Table 10-1),ADCAINT1_DMA 的索引是1。 // Driverlib 函数内部会处理 DMACHSRCSELx 和 PERINTSEL 的配置。 DMA_configTriggerSource(base, DMA_TRIGGER_ADCAINT1); // 注意:函数名和参数宏可能因库版本略有不同,例如可能是 DMA_setPeripheralTriggerSource。 // 务必查阅你所使用的C2000Ware版本中的 dma.h 文件确认正确的API。

步骤3:配置地址指针与步进这是DMA的“导航系统”配置,决定了数据搬运的路径。

// 定义源地址和目的地址 // 源地址:ADC-A的结果寄存器0的地址。这是一个外设寄存器地址。 uint32_t srcAddr = (uint32_t)&AdcaResultRegs.ADCRESULT0; // 目的地址:GS0 RAM中的数组首地址。GS0是全局共享RAM0,CPU和DMA均可访问。 extern uint16_t gAdcResultsBuffer[1024]; // 在.c文件中定义一个大数组 uint32_t dstAddr = (uint32_t)gAdcResultsBuffer; // 配置源地址和目的地址的起始指针(Shadow Registers) DMA_setSrcStartAddress(base, srcAddr); DMA_setDestStartAddress(base, dstAddr); // 配置突发步进(Burst Step) // 源步进:ADC结果寄存器是连续排列的,每个寄存器间隔2字节(16位)。 // 因此,每搬运一个数据后,源地址应增加2(以字节为单位,但函数内部可能按数据大小处理)。 // 对于16位数据,地址递增2;对于32位数据,地址递增4。 DMA_setSrcBurstStep(base, DMA_ADDR_INC_2); // 常用宏:地址增加2字节 // 目的步进:我们希望数据在RAM中连续存放,所以目的地址也每次增加2字节。 DMA_setDestBurstStep(base, DMA_ADDR_INC_2); // 配置传输步进(Transfer Step) // 本例中,一个突发搬运8个ADC结果。完成一个突发后,我们希望源地址回到ADCRESULT0(因为ADC会刷新这个寄存器组), // 而目的地址则跳到下一个空闲的8个字位置,以存放下一次触发的数据。 // 因此,源传输步进应为0(地址回绕由Wrap功能处理更合适,这里先设为0)。 DMA_setSrcTransferStep(base, DMA_ADDR_INC_0); // 目的传输步进:增加 8个字 * 2字节/字 = 16字节。 DMA_setDestTransferStep(base, DMA_ADDR_INC_16); // 配置地址环绕(Wrap)以实现乒乓缓冲区 // 假设我们想实现一个双缓冲(乒乓缓冲):缓冲区A和B各存放8个结果。 // 当DMA写满缓冲区A后,自动跳转到缓冲区B,如此循环。 // 设置环绕大小(Wrap Size):1个突发后环绕(即每搬运1个突发,就考虑换缓冲区)。 DMA_setSrcWrapSize(base, 0); // 源地址不环绕,始终是ADC寄存器 DMA_setDestWrapSize(base, 0); // 目的地址环绕,0表示1个突发后环绕 // 设置环绕步进(Wrap Step) // 源环绕步进:0。 DMA_setSrcWrapStep(base, DMA_ADDR_INC_0); // 目的环绕步进:从缓冲区A跳到缓冲区B,地址偏移是 8个字 * 2字节/字 = 16字节。 // 但注意:Wrap Step是在完成Wrap Size个突发后,加到BEG_ADDR上的。 // 我们需要设置初始的BEG_ADDR和WRAP_STEP,使得地址在A和B之间切换。 // 更常见的做法是设置一个大的缓冲区,用Transfer Step线性递增,用中断在缓冲区半满/全满时处理。 // 这里为了演示Wrap,我们配置两个独立的缓冲区地址。 uint32_t dstAddrB = (uint32_t)(gAdcResultsBuffer + 8); // 缓冲区B紧接A之后 // 设置目的起始地址(BEG_ADDR)为缓冲区A首地址。 DMA_setDestBeginAddress(base, dstAddr); // 设置目的环绕步进为缓冲区A和B的间距。 DMA_setDestWrapStep(base, 16); // 16字节的偏移 // 配置突发大小(Burst Size)和传输大小(Transfer Size) // 突发大小:每次触发搬运8个数据(ADCRESULT0-7)。BURST_SIZE寄存器写入值 = 数据个数 - 1。 DMA_setBurstSize(base, 7); // 8 - 1 = 7 // 传输大小:我们希望连续搬运,由CONTINUOUS模式控制,这里可以设置一个很大的值。 // 或者,设置为1,让每次“传输”只包含1个突发,然后依靠CONTINUOUS模式循环。 // 这里设置为1,即一次传输包含1个突发(8个数据)。 DMA_setTransferSize(base, 0); // 1 - 1 = 0 // 这样,每次ADC触发(一个突发)就完成一次“传输”,产生一次中断(如果中断使能)。

步骤4:使能DMA通道与触发

// 清除可能存在的未决触发标志 DMA_clearTriggerFlag(base); // 使能DMA通道1。此操作会拷贝所有Shadow寄存器到Active寄存器。 DMA_enableChannel(base); // (可选)如果需要软件手动启动一次传输进行测试,可以强制触发 // DMA_forceTrigger(base);

步骤5:配置ADC以触发DMA最后,需要配置ADC,使其在转换完成后产生触发DMA的信号。

// 假设ADC已初始化,序列器SEQ1配置为转换8个通道 // 使能ADC SEQ1在转换完成时产生DMA触发信号 ADC_enableDMA(ADCA_BASE, ADC_DMA_ENABLE_SEQ1); // 配置ADC的SOC(Start-of-Conversion)触发源,例如由ePWM定时触发 // 这样,ePWM定期触发ADC采样,ADC采样完成触发DMA搬运,形成一个自动化的数据流。

至此,一个完整的、由硬件事件驱动的自动数据搬运链路就配置完成了。ADC在ePWM的定时触发下开始转换,转换完成后自动产生DMA请求,DMA控制器则无声无息地将8个采样值从ADC结果寄存器搬运到指定的RAM缓冲区中,整个过程无需CPU参与。

4. 高级配置、调试与常见问题排查

掌握了基础配置后,我们探讨一些高级特性和实际开发中必然会遇到的“坑”。

4.1 乒乓缓冲区与双缓冲技术

这是DMA最经典的应用模式之一,用于实现数据采集和处理的并行流水线。

场景:ADC以1MHz速率连续采样,CPU需要对每256个采样点进行一次FFT处理。如果只有一个缓冲区,当CPU处理数据时,新来的采样数据无处存放;如果让DMA等待,则会丢失数据。

解决方案:配置两个大小均为256字的缓冲区(Buffer A和B)。DMA配置为连续模式,传输大小设为255(256个数据),不使能地址环绕(Wrap),而是利用传输完成中断。

配置要点

  1. 设置目的地址为Buffer A首地址,传输步进为0(因为我们在中断里手动切换地址)。
  2. 使能传输完成中断(CHINTMODE = 1)。
  3. 在DMA通道的传输完成中断服务函数中:
    __interrupt void dmaCh1Isr(void) { // 1. 判断当前DMA的目的地址是Buffer A还是Buffer B uint32_t currentDstAddr = DMA_getDestActiveAddress(DMA_CH1_BASE); // 2. 处理刚刚被填满的缓冲区(例如,设置一个标志通知主循环) if(currentDstAddr == (uint32_t)bufferA) { g_bufferReadyFlag = BUFFER_A_READY; } else { g_bufferReadyFlag = BUFFER_B_READY; } // 3. 在下一个传输开始前,将DMA的目的地址切换到另一个空闲缓冲区 // 注意:应修改Shadow Register,并重新使能通道以加载Shadow值。 // 更安全的方法是先禁用通道,修改地址,再使能通道。 DMA_disableChannel(DMA_CH1_BASE); if(currentDstAddr == (uint32_t)bufferA) { DMA_setDestStartAddress(DMA_CH1_BASE, (uint32_t)bufferB); } else { DMA_setDestStartAddress(DMA_CH1_BASE, (uint32_t)bufferA); } DMA_enableChannel(DMA_CH1_BASE); // 此操作会加载新的Shadow地址 // 4. 清除DMA中断标志 DMA_clearInterruptFlag(DMA_CH1_BASE); // 5. 确认PIE中断组应答 Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP9); // DMA CH1属于PIE GROUP 9 }
  4. 主循环中检测g_bufferReadyFlag,并对就绪的缓冲区进行FFT处理。

关键技巧:在中断中切换缓冲区地址时,务必遵循“先禁用通道 -> 修改Shadow寄存器 -> 再使能通道”的序列。直接修改Active寄存器是无效的,因为Active寄存器由硬件控制。使能通道的操作会触发Shadow到Active的拷贝。

4.2 数据打包与32位传输优化

当源数据是16位(如ADC结果),但目的希望是32位数组时,可以利用DMA的32位传输模式提升效率。

配置

  • 设置DATASIZE = 32
  • 源突发步进SRC_BURST_STEP = 2(字节地址+2)。
  • 目的突发步进DST_BURST_STEP = 4(字节地址+4)。
  • 关键点BURST_SIZETRANSFER_SIZE寄存器始终以16位字为单位计数。当DATASIZE=32时,DMA每传输一个“字”实际上是搬运2个16位数据单元。
  • 例如,要搬运8个16位的ADC结果到4个32位变量中,应设置BURST_SIZE = 7(8个16位单元)。DMA会执行4次32位访问来完成。

优势:如图10-4和10.4节所述,32位传输的吞吐量理论上是16位的两倍,因为总线利用率更高。在从外设(如ADC结果寄存器,是32位可访问的)到32位对齐的RAM进行大量数据搬运时,此优化效果显著。

4.3 常见问题与调试技巧实录

问题1:DMA不启动,数据没有搬运。

  • 检查触发源:首先确认触发事件是否真的产生了。可以使用示波器查看对应的GPIO引脚(如果触发源是外部中断),或者在代码中先使用CPU中断来验证该事件能否触发中断。
  • 检查DMA通道使能:确认DMA_enableChannel()已被调用。RUNSTS位应该为1。
  • 检查触发标志:触发事件会产生PERINTFLG。如果此标志置位但DMA不动,可能是优先级被其他通道占用,或者通道被HALT。检查CONTROL寄存器中的HALT位和PERINTFRC位。
  • 检查地址对齐:确保源地址和目的地址符合访问要求。例如,对32位字的访问地址必须是4字节对齐。

问题2:数据搬运错位或地址跑飞。

  • 核对步进值:这是最常见的原因。仔细计算BURST_STEPTRANSFER_STEPWRAP_STEP。记住,这些步进值是以字节地址偏移为单位的,但SIZE寄存器是以16位字为单位的。
  • 使用调试器查看寄存器:在CCS调试模式下,实时查看SRC_ADDR_ACTIVEDST_ADDR_ACTIVE寄存器的变化,看是否按预期递增。同时观察BURST_COUNTTRANSFER_COUNT
  • 简化测试:先将所有步进设为0,BURST_SIZE设为0(只搬1个字),TRANSFER_SIZE设为0(只搬1次),验证最基本的单次触发单字搬运是否工作。再逐步增加复杂性。

问题3:DMA中断无法进入。

  • 确认PIE配置:DMA通道中断需要通过PIE(外设中断扩展)模块路由到CPU。确保:
    1. 已调用Interrupt_initModule()Interrupt_initVectorTable()初始化PIE。
    2. 已调用Interrupt_register()注册了你的DMA中断服务函数。
    3. 已调用Interrupt_enable()使能了对应的CPU中断线(如INT9对应DMA CH1)。
    4. 在PIE级,已使能对应的组和中断(如Interrupt_enable(INT_DMA_CH1)PieCtrlRegs.PIEIER9.all = 0x...)。
  • 确认DMA中断模式:检查CHINTMODE位,确认中断是在传输开始还是结束时产生,并对应地等待。
  • 清除中断标志:在ISR中,必须清除DMA通道的中断标志DMA_clearInterruptFlag(),并应答PIE组Interrupt_clearACKGroup()

问题4:系统偶尔卡死或数据损坏,怀疑是总线仲裁冲突。

  • 回顾10.5节仲裁规则:DMA WRITE优先级最高。如果CPU频繁访问DMA正在写入的RAM区域,CPU会被阻塞。避免CPU和DMA同时读写同一内存区域。使用乒乓缓冲区可以很好地解决:CPU处理缓冲区A时,DMA写入缓冲区B。
  • 检查共享外设冲突:如CPU和DMA同时访问同一个SPI或CAN模块的FIFO。需要通过软件设计避免,例如让DMA负责搬运,CPU只在DMA完成中断后读取整理好的数据块。
  • 注意原子操作:资料中特别警告,CPU的读-修改-写操作(如|=,&=~)若被DMA写打断,可能导致DMA写入丢失。对于共享变量,考虑使用禁用中断或信号量进行保护。

调试利器:CCS的寄存器视图和内存浏览器

  • 寄存器视图:在CCS的Registers窗口中,展开DMA寄存器组,可以实时监控所有配置寄存器和状态寄存器(ACTIVE地址、COUNT值、FLG位等),这是诊断DMA状态最直接的方法。
  • 内存浏览器:在Memory Browser中查看目的RAM区域的数据。可以设置断点或周期性地刷新,观察数据是否被正确写入。结合DMA的触发条件(如ADC采样),可以验证整个数据链路的正确性。

通过以上实战配置和问题排查指南,你应该能够驾驭TMS320F28004x的DMA模块,将其强大的数据搬运能力融入到你的实时控制系统中,从而释放CPU潜力,构建出更高性能、更可靠的嵌入式应用。记住,DMA配置的黄金法则是:先简化后复杂,先验证触发,再验证数据,最后优化性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/21 6:50:09

C++模拟算法入门:从“津津的储蓄计划”掌握循环与条件判断

1. 项目概述:从“津津的储蓄计划”看编程与生活的结合最近在洛谷上看到一个挺有意思的题目,编号P1089,叫“津津的储蓄计划”。乍一看标题,还以为是什么理财软件或者生活管理App,点进去才发现,这其实是一个经…

作者头像 李华
网站建设 2026/7/21 6:50:01

敏捷开发聊天机器人:LLM与Prompt工程实战

1. 项目概述:当敏捷遇上聊天机器人开发三年前我接手第一个企业级聊天机器人项目时,团队花了三个月才产出第一个可演示版本。而去年我们用新方法,在两周内就完成了从零到生产环境部署的全流程。这种转变的核心,就在于放弃了传统&qu…

作者头像 李华
网站建设 2026/7/21 6:49:19

混合主动降噪算法——SFANC‐FxNLMS算法

算法思路总结于论文:A Hybrid SFANC-FxNLMS Algorithm for Active Noise Control Based on Deep Learning。算法背景主动降噪(ANC)的核心原理是产生与不需要的噪声振幅相等、相位相反的抗噪声来抑制噪声 。但在实际应用中,传统的…

作者头像 李华
网站建设 2026/7/21 6:48:08

AI Agent核心交互机制:MCP协议与Function Calling详解

1. 从零理解AI Agent的核心交互机制当我在2023年第一次尝试构建AI Agent时,面对MCP和Function Calling这两个概念完全摸不着头脑。直到在真实项目中踩了无数坑后才明白,这其实就是AI与外部世界对话的"普通话"和"方言"——MCP提供了标…

作者头像 李华
网站建设 2026/7/21 6:47:44

C++多线程高性能金融系统架构:从零构建微秒级行情处理引擎

1. 项目概述:从零构建一个金融市场的“高速引擎”在上海证券交易所这样的核心金融市场里,每一笔交易指令的到达、每一笔行情的跳动,都是以微秒甚至纳秒来计算的。我参与设计和优化的这个“C多线程高性能金融行情处理系统”,本质上…

作者头像 李华
网站建设 2026/7/21 6:46:18

嵌入式PSC寄存器深度解析:从原理到实战的低功耗电源管理

1. 项目概述:为什么嵌入式开发者必须懂PSC 在嵌入式开发领域,尤其是面向电池供电的物联网设备、便携式医疗仪器或工业传感器节点时,功耗管理从来都不是一个“锦上添花”的选项,而是决定产品成败的核心指标。我经历过不止一个项目&…

作者头像 李华