1. 项目概述
在嵌入式系统开发,尤其是基于德州仪器(TI)C2000系列高性能微控制器的项目中,直接内存访问(DMA)技术是释放CPU算力、实现高效实时数据处理的核心武器。对于像TMS320F2837xS这样的双核C28x架构处理器,DMA的作用更是被放大,它不仅是加速外设数据搬运的“搬运工”,更是实现双核间高效、低延迟通信的“数据桥梁”。很多工程师在初次接触F2837xS的DMA时,往往会被其复杂的寄存器结构所困扰,手册上密密麻麻的位域描述让人望而生畏,更别提将其灵活应用于实际的多核通信场景了。
我自己在多个电机控制和数字电源项目中使用F2837xS时,也经历过从“照搬例程”到“理解并驾驭”DMA的漫长过程。我发现,仅仅知道如何开启一个DMA传输是远远不够的。要想真正发挥其威力,尤其是在双核协同的场景下,必须深入理解其寄存器配置的逻辑,明白每一个配置位背后对数据流、时序和系统性能的影响。例如,如何配置源地址和目标地址的步进,以实现对数组或特定数据结构的灵活访问?如何设置突发(Burst)和传输(Transfer)的大小,来匹配外设的FIFO深度或内存对齐要求?在多核共享外设时,如何确保DMA触发的同步性与数据一致性?这些问题都需要我们从寄存器层面找到答案。
本文就将以TMS320F2837xS的DMA模块为焦点,抛开那些泛泛而谈的理论,直接切入最核心的寄存器配置逻辑。我会结合官方例程和实际项目中的踩坑经验,为你系统梳理从全局控制到通道细粒度配置的所有关键寄存器,并重点剖析如何利用这些配置来实现高效、可靠的多核间数据通信。无论你是正在评估F2837xS用于新项目,还是已经在项目中遇到了DMA相关的性能瓶颈或稳定性问题,相信这篇深入寄存器细节的解析都能给你带来直接的帮助。
2. DMA核心架构与寄存器全景
在深入每个寄存器之前,我们必须先建立起对F2837xS DMA模块整体架构的认知。这就像看地图前先了解地形,能让你后续的配置“导航”更加清晰。
2.1 F2837xS DMA模块设计哲学
F2837xS的DMA控制器是一个独立于CPU的专用数据搬运引擎。它的设计目标非常明确:将CPU从频繁、琐碎的数据搬运工作中彻底解放出来。在传统的编程模型中,CPU需要不断响应外设中断,然后执行memcpy之类的数据搬移指令,这期间CPU无法执行其他计算任务。而DMA则接管了这项工作,它可以在后台完成数据在内存与外设之间、内存与内存之间的传输,仅在传输开始、结束或出错时通过中断通知CPU。
该DMA控制器支持最多6个独立的通道(CH1-CH6),每个通道都可以独立配置,服务于不同的外设或内存区域。其核心工作流程可以概括为“传输-突发-字”三级流水:
- 一次传输(Transfer):由一次事件(软件触发或外设触发)启动的完整数据搬运任务。它定义了本次任务的总数据量。
- 多次突发(Burst):一个传输由若干次突发组成。每次突发是一组连续、不间断的数据读写操作。突发传输可以更好地利用总线带宽,减少仲裁开销。
- 多个字(Word):一次突发内包含多个连续的16位或32位数据字的搬运。
这种层级结构非常灵活。例如,你可以配置一次传输(比如搬运1KB的ADC采样数据)由64次突发完成,每次突发搬运16个字。这样的设计特别适合处理来自ADC、SPI等外设的流式数据。
2.2 寄存器地图与分类解析
F2837xS的DMA寄存器分为两大组:全局控制寄存器组(DMA_REGS)和通道专用寄存器组(DMA_CH_REGS)。理解这个分类是正确配置的前提。
**全局控制寄存器(DMA_REGS)**位于一个统一的基地址(如0x0000_1000),它们管理着DMA控制器的整体行为,影响所有通道。这部分寄存器数量不多,但至关重要:
- DMACTRL (DMA Control Register):包含硬复位(HARDRESET)和优先级复位(PRIORITYRESET)位。硬复位会中止所有传输并将整个DMA模块恢复到默认状态;优先级复位则用于重置通道仲裁状态机。
- DEBUGCTRL (Debug Control Register):主要包含
FREE位,用于控制仿真器暂停时DMA的行为(是继续运行还是停止),这在调试实时性要求高的应用时非常关键。 - PRIORITYCTRL1 (Priority Control 1 Register):用于设置通道1(CH1)是否为高优先级通道。这是一个重要的优化手段,可以确保关键通道的数据流不被阻塞。
- PRIORITYSTAT (Priority Status Register):用于查询当前正在服务的通道(ACTIVESTS)以及被高优先级通道(CH1)中断的通道状态(ACTIVESTS_SHADOW),对于调试多通道并发传输的场景很有用。
**通道专用寄存器(DMA_CH_REGS)**每个通道都有一套独立的副本,它们定义了该通道具体的传输行为。这是配置工作的核心,数量众多,功能细致。我们可以进一步将其分为几个功能子集:
- 控制与状态寄存器:如
MODE,CONTROL,用于启停通道、配置中断、查看运行状态。 - 传输尺度寄存器:如
BURST_SIZE,TRANSFER_SIZE,定义了“一次搬多少”的宏观参数。 - 地址与步进寄存器:这是DMA灵活性的精髓所在。包括源/目标地址寄存器(
SRC_BEG_ADDR_SHADOW,DST_ADDR_ACTIVE等)和各级步进寄存器(SRC_BURST_STEP,DST_TRANSFER_STEP等),它们共同决定了数据从哪里来、到哪里去,以及每次操作后地址指针如何变化。 - 地址环绕(Wrap)寄存器:如
SRC_WRAP_SIZE,SRC_WRAP_STEP,用于实现循环缓冲区,这在处理音频、波形生成等环形数据队列时必不可少。
关键理解:
SHADOW(影子)寄存器和ACTIVE(活动)寄存器的设计是TI DMA的一个精妙之处。SHADOW寄存器可以在DMA传输进行中安全地写入,用于准备下一次传输的参数。而ACTIVE寄存器则保存着当前正在进行的传输所使用的实际参数,通常只读。当一次传输(Transfer)启动时,对应SHADOW寄存器的值会被自动加载到ACTIVE寄存器中。这种双缓冲机制是实现连续、无间断数据传输(即CONTINUOUS模式)的基础。
3. 关键寄存器深度解析与配置策略
了解了全景,我们就可以深入最核心、最常打交道的几个寄存器,看看每个比特位到底在控制什么,以及在实际项目中如何设置它们。
3.1 MODE寄存器:定义通道工作模式
MODE寄存器是通道的“大脑”,它决定了DMA通道以何种方式响应事件、如何产生中断。
- CHINTE (Channel Interrupt Enable):通道中断使能位。这是最常用的位之一。设置为1,DMA会在特定时刻(由
CHINTMODE决定)向CPU发出中断请求。在双核通信中,我们经常利用这个中断来通知另一个核“数据已就绪”。例如,CPU1的DMA完成从共享内存到某个外设的数据搬运后,触发中断通知CPU2。 - DATASIZE:数据大小选择。0代表16位传输,1代表32位传输。这里有一个极易出错的点:无论此位如何设置,其他所有寄存器(如
BURST_SIZE, 地址步进)中关于“长度”和“偏移”的参数,其单位都是16位字(Word)。如果你选择32位传输(DATASIZE=1),意味着一次读/写操作会搬运32位数据,但你的地址步进(SRC_BURST_STEP等)配置必须按2个16位字为单位来递增,因为地址总线寻址的最小单位通常是字节或16位字。例如,要连续搬运32位数据数组,SRC_BURST_STEP应设置为2。 - CONTINUOUS:连续模式。这是实现“永动”传输的关键。当设置为1且一次传输(Transfer)完成后,DMA不会停止,而是自动用
SHADOW寄存器的值重新加载ACTIVE寄存器,并等待下一个触发事件。这非常适合需要持续不断搬运数据的场景,如实时音频流、电机电流采样环。在配置此模式前,务必确保已正确初始化所有SHADOW寄存器。 - ONESHOT:单次触发模式。此位控制每次外设事件触发DMA做什么。如果为0,一次事件触发只执行一次突发(Burst)传输;如果为1,一次事件触发会执行完整个传输(Transfer)的所有突发。例如,ADC每采样一次产生一个事件,如果你希望每16次采样(一个突发)搬运一次数据,就设为0;如果你希望ADC采集满1024个点(一个传输)后才启动一次完整的DMA搬运,就设为1,并由软件或另一个事件来启动整个传输。
- CHINTMODE:中断产生模式。0=在新传输开始时产生中断;1=在传输结束时产生中断。对于数据生产者(如ADC采样DMA),通常设为1(结束时中断),以通知消费者数据已完整就绪。对于数据消费者(如将处理结果发送出去的DMA),可能设为0(开始时中断),以便在传输开始后立即准备下一批数据。
- PERINTE:外设事件触发使能。必须置1,DMA通道才会响应外设(如SPI、ADC、ePWM)的硬件触发信号。如果只想用软件触发,也需要将此位置1,然后通过写
CONTROL寄存器的PERINTFRC位来模拟事件。 - PERINTSEL:这是一个历史遗留位,在F2837xS上通常应设置为该通道的编号(例如,对于通道1,设为1)。真正的外设触发源选择是通过另一组独立的寄存器
DMACHSRCSELn(属于DMA_CLA_SRC_SEL_REGS)来配置的。这是一个常见的配置陷阱,务必注意。
3.2 CONTROL寄存器:通道的启停与状态监控
CONTROL寄存器是通道的“手脚”,用于启动、停止、强制触发DMA,并查看其实时状态。
- RUN:运行位。向此位写1是启动DMA通道的标准操作。写1后,
RUNSTS位会被置1,通道进入就绪状态,等待触发事件。一个标准的DMA初始化流程是:先配置所有参数寄存器(MODE, BURST_SIZE, 地址等),最后再写RUN=1。 - HALT:暂停位。写1会暂停当前通道,但保留其所有状态(地址、计数等)。这对于临时挂起DMA以访问其正在使用的内存区域非常有用。恢复运行只需再次写
RUN=1。 - SOFTRESET:通道软复位。写1会将本通道复位到默认状态(停止,所有计数清零),但不会影响其他通道。比全局硬复位(
HARDRESET)更温和。手册特别强调,写此位后需要插入一个NOP指令等待一个周期,才能访问其他DMA寄存器。 - PERINTFRC:强制外设事件触发。这是实现软件触发DMA的关键。当
PERINTE=1时,向此位写1会立即置起PERINTFLG标志,从而启动一次DMA传输。在多核通信中,一个核可以通过写这个位来主动触发另一个核所属DMA通道的工作。 - 状态标志位(只读):
RUNSTS:通道是否已使能并等待触发。BURSTSTS:通道是否正在执行一次突发传输。TRANSFERSTS:通道是否正处于一个传输周期内。PERINTFLG:是否有外设触发事件正在等待处理。OVRFLG:溢出标志。如果在前一个事件还未被处理(PERINTFLG=1)时又来了一个新事件,此位置1。这通常意味着CPU处理DMA中断太慢或DMA配置的传输速度跟不上事件产生速度,是系统设计需要优化的信号。
3.3 传输尺度寄存器:BURST_SIZE与TRANSFER_SIZE
这两个寄存器定义了DMA传输的“形状”。
- BURST_SIZE:设置一次突发传输包含多少个16位字。实际突发大小 =
BURST_SIZE + 1。这意味着该寄存器设置为0时,突发大小为1个字。它的最大值取决于具体芯片型号,F2837xS通常支持最大32个字(即BURST_SIZE=31)。最佳实践是让突发大小匹配外设FIFO的深度或内存总线的最佳访问长度,以达到最高的传输效率。 - TRANSFER_SIZE:设置一个完整的传输包含多少次突发。实际传输包含的突发数 =
TRANSFER_SIZE + 1。它和BURST_SIZE共同决定了单次触发事件搬运的总数据量:总字数 = (BURST_SIZE + 1) * (TRANSFER_SIZE + 1)。
配置示例:假设我们需要通过DMA将ADC的结果寄存器(16位)搬运到内存中的一个1000字的数组。
- 方案A(单次触发搬全部):设置
BURST_SIZE = 31(一次搬32字),TRANSFER_SIZE = 30(需要32次突发,32*32=1024 > 1000,最后一次突发只搬部分)。配置ONESHOT=1,由一次事件(如ADC序列转换结束)触发整个1000字的搬运。 - 方案B(多次触发搬运):设置
BURST_SIZE = 99(一次搬100字),TRANSFER_SIZE = 0(一次传输就是一次突发)。配置ONESHOT=0,CONTINUOUS=1。这样,每产生10次触发事件(每次搬100字),就能完成全部数据搬运。这种方式对触发事件的实时性要求更高,但每次搬运的数据块更小,内存占用更平滑。
3.4 地址与步进寄存器:实现灵活的数据访问模式
这是DMA最强大也最复杂的部分。它让DMA不仅能搬运连续内存块,还能处理非连续、循环的数据结构。
- SRC_BURST_STEP / DST_BURST_STEP:突发内步进。在每个突发内部,每完成一个字的传输后,源地址和目标地址增加的偏移量。单位是16位字。通常,对于连续内存搬运,设置为1(或2,如果
DATASIZE=32)。但你可以设置为其他值来实现“隔点采样”。例如,从一个16位整数数组中每隔一个元素取一个值(用于降采样),可以将SRC_BURST_STEP设为2。 - SRC_TRANSFER_STEP / DST_TRANSFER_STEP:传输间步进。在完成一次突发(Burst)后,源地址和目标地址增加的偏移量。这个值用于在内存中“跳”到下一个数据块的起始位置。这是实现二维数据搬运的关键。例如,搬运一个10x10的矩阵,你可以设置
BURST_SIZE=9(一次搬一行10个元素),SRC_BURST_STEP=1,SRC_TRANSFER_STEP=10。这样,每次突发搬运一行,突发结束后地址自动跳到下一行的开头。 - 地址环绕(Wrap)寄存器组:
SRC_WRAP_SIZE,SRC_WRAP_COUNT,SRC_WRAP_STEP(目标端同理)。这组寄存器用于实现循环缓冲区。WRAP_SIZE:定义在多少次突发后进行地址环绕。实际环绕前突发数 =WRAP_SIZE + 1。WRAP_STEP:当环绕发生时,地址指针(BEG_ADDR)调整的偏移量。这是理解环绕的关键:环绕不是简单地将地址指针重置为起始值,而是给起始地址加上一个WRAP_STEP。通常,如果你希望实现一个简单的、固定大小的环形缓冲区,WRAP_STEP应设置为整个缓冲区的大小(以字为单位)。例如,一个100字的环形缓冲区,配置TRANSFER_SIZE很大(或使用连续模式),WRAP_SIZE=99,WRAP_STEP=100。这样,当指针在缓冲区末尾环绕时,会自动回到开头。
实操心得:地址计算陷阱:所有步进(
*_STEP)寄存器都是16位有符号整数,取值范围是-4096到+4095。这意味着它们可以是负数,从而实现地址递减访问。但这也意味着最大步进绝对值是4095。如果你需要更大的地址跳跃(比如在两个相距很远的内存区域间搬运),单靠步进寄存器无法实现。此时,你需要配置多个DMA通道接力,或者利用传输完成中断,在中断服务程序中手动更新SHADOW地址寄存器。
4. 多核通信应用实战:基于共享外设的DMA触发
理论最终要服务于实践。F2837xS作为双核器件,其DMA在多核通信中扮演着核心角色。官方例程dma_ex1_shared_periph提供了一个经典范式:一个核(CPU2)控制的外设(如Timer)产生事件,触发另一个核(CPU1)所属的DMA通道进行传输。
4.1 场景还原与配置拆解
我们以例程描述的场景为例:CPU2通过一个定时器中断,启动一次SPI传输。这次SPI传输完成的事件,会触发CPU1的DMA通道。CPU1的DMA随后将数据从某个源地址(比如共享RAM)搬运到目标地址(比如CPU1控制的ePWM模块的比较寄存器CMPA),从而改变PWM输出。
整个链条的配置要点如下:
外设触发源映射(最关键的一步):
- 首先,需要明确是哪个外设事件作为DMA的触发源。在F2837xS中,每个DMA通道的触发源是通过
DMACHSRCSELn寄存器(n为通道号)独立选择的。这个寄存器不在DMA_REGS或DMA_CH_REGS中,而是在DMA_CLA_SRC_SEL_REGS寄存器组里。 - 例如,要将SPI-A的发送缓冲空事件(SPITXINT)映射到CPU1的DMA通道1,你需要找到对应的
DMACHSRCSEL1寄存器,并设置为SPI-A的相应事件编码。 - 重要:
MODE.PERINTSEL位在这个芯片上通常只需设置为通道号本身,真正的选择由DMACHSRCSELn完成。
- 首先,需要明确是哪个外设事件作为DMA的触发源。在F2837xS中,每个DMA通道的触发源是通过
CPU1 DMA通道配置:
MODE寄存器:PERINTE=1(使能外设触发),CHINTE=1(使能中断,可选,用于通知CPU1传输完成),ONESHOT根据需求设置(如果每次SPI事件只搬一块数据,就设为1)。CONTROL寄存器:先保持RUN=0,配置完所有参数后再置1。- 地址寄存器:
SRC_BEG_ADDR_SHADOW指向存放数据的共享内存(如RAMGS0),DST_BEG_ADDR_SHADOW指向ePWM1的CMPA寄存器地址(如0x0000_5C00)。 - 尺度与步进:根据数据块大小设置
BURST_SIZE和TRANSFER_SIZE。由于是修改PWM比较值,通常数据量很小,可能BURST_SIZE=0(1个字),TRANSFER_SIZE=0(1次突发)就够了。
CPU2的配置(触发方):
- CPU2需要配置一个定时器(例如CPUTimer),使其周期性地产生中断。
- 在定时器中断服务程序(ISR)中,CPU2执行一次SPI数据写入操作(例如向SPI发送缓冲器写一个数据)。这个操作本身会启动SPI传输。
- 关键点:CPU2的SPI模块和CPU1的DMA通道虽然物理上是同一个SPI外设,但在多核系统中,外设的所有权(Ownership)需要明确。通常,一个外设模块(如SPI-A)在某一时刻只能由一个CPU核主控。在例程中,SPI-A由CPU2配置和启动,但其产生的事件可以触发CPU1的DMA。这需要芯片支持跨核事件触发机制,F2837xS的IPC(处理器间通信)和事件路由机制支持这一点。
共享内存与数据一致性:
- 数据源位于共享内存(如RAMGS0)。必须确保在CPU1的DMA启动读取之前,CPU2已经将有效数据写入该内存区域。
- 这通常需要通过软件同步机制来实现,例如使用IPC标志位、消息队列或者简单的“数据就绪”标志变量。CPU2写完数据后设置标志,CPU1的DMA配置或中断服务程序检查该标志。更优雅的做法是使用硬件信号量(如果芯片支持)来保护共享内存的访问。
4.2 核心代码逻辑片段(概念性伪代码)
以下是上述流程的概念性代码展示,帮助你理解配置顺序和核间协作:
// ==== CPU1 代码 (DMA所有者) ==== // 1. 配置DMA触发源 (通过DMACHSRCSEL寄存器,假设有相关宏定义) DMACHSRCSEL1 = SRC_SEL_SPIA_TX; // 将DMA CH1触发源设为SPI-A发送事件 // 2. 配置DMA通道1参数 DmaCh1Regs.MODE.bit.PERINTE = 1; // 使能外设触发 DmaCh1Regs.MODE.bit.CHINTE = 1; // 使能传输完成中断 DmaCh1Regs.MODE.bit.ONESHOT = 1; // 一次触发完成整个传输 DmaCh1Regs.MODE.bit.CHINTMODE = 1; // 传输完成时产生中断 DmaCh1Regs.MODE.bit.PERINTSEL = 1; // 传统位,设为通道号1 DmaCh1Regs.BURST_SIZE = 0; // 突发大小 = 1字 DmaCh1Regs.TRANSFER_SIZE = 0; // 传输大小 = 1次突发 (总共搬1个字) DmaCh1Regs.SRC_BEG_ADDR_SHADOW = (Uint32)&shared_data_buffer; // 源:共享内存 DmaCh1Regs.DST_BEG_ADDR_SHADOW = (Uint32)&EPwm1Regs.CMPA.half.CMPA; // 目标:PWM比较寄存器 DmaCh1Regs.SRC_BURST_STEP = 1; // 突发内,每搬一个字源地址+1 DmaCh1Regs.DST_BURST_STEP = 0; // 突发内,目标地址不变(总是写到CMPA) // 3. 启动DMA通道,等待CPU2触发 DmaCh1Regs.CONTROL.bit.RUN = 1; // 4. DMA中断服务函数 (当传输完成时被调用) interrupt void DMA_CH1_ISR(void) { // 可以在这里进行后续处理,例如更新下一个数据指针 DmaClrIntFlag(DMA_CH1); // 清除DMA中断标志 PieCtrlRegs.PIEACK.all = PIEACK_GROUP7; // 应答PIE中断 } // ==== CPU2 代码 (触发者) ==== // 1. 配置定时器 (例如每秒触发一次) ConfigCpuTimer(&CpuTimer2, 100, 1000000); // 100MHz SysClk, 1秒周期 CpuTimer2Regs.TCR.bit.TSS = 0; // 启动定时器 // 2. 定时器中断服务函数 interrupt void CPU_TIMER2_ISR(void) { // 向SPI发送缓冲器写入数据,这将启动SPI传输 // SPI传输完成事件会自动触发CPU1的DMA SpiaRegs.SPITXBUF = dummy_data_to_trigger_transfer; // ... 其他处理 CpuTimer2.InterruptCount++; PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; }5. 常见问题排查与调试技巧实录
即使理解了所有寄存器,实际调试DMA时依然会遇到各种问题。下面是我在项目中总结的一些典型故障和排查思路。
5.1 DMA不启动或数据传输错误
这是最常见的问题。请按照以下清单逐项检查:
- 时钟与模块使能:首先确认DMA模块的时钟是否使能。在F2837xS上,这通常通过
PCLKCR0或PCLKCR1寄存器中的DMA相关时钟控制位来配置。没有时钟,DMA是“死”的。 - 触发源配置:
- 硬件触发:检查
DMACHSRCSELn寄存器是否已正确映射到期望的外设事件。使用示波器或仿真器查看该外设事件是否确实产生。 - 软件触发:检查
MODE.PERINTE是否已置1,然后确认是否正确写入了CONTROL.PERINTFRC位(写1)。可以单步调试,观察CONTROL.PERINTFLG位是否被置起。
- 硬件触发:检查
- 通道使能顺序:确保是先配置所有参数寄存器,最后才将
CONTROL.RUN位置1。如果在RUN=1之后错误地改动了某些关键配置寄存器,可能导致不可预知的行为。 - 地址对齐与权限:
- 地址有效性:确保源地址和目标地址都是可访问的有效地址。例如,试图从只读的Flash地址执行DMA读取,或者向未初始化的内存区域写入,都会导致错误。
- 内存保护:检查相关内存区域(尤其是共享内存)是否被MPU(内存保护单元)或芯片的访问控制规则所限制。CPU1能访问的内存,其DMA不一定有权限访问。
- 数据位宽对齐:如果配置了32位传输(
DATASIZE=1),确保源和目标地址是32位对齐的(地址最低位为0)。非对齐访问在某些架构上会导致硬件错误或性能下降。
- 中断与事件清除:如果DMA配置了完成中断,但似乎只工作了一次,检查中断服务程序是否清除了DMA的中断标志(
DMAINTFLG寄存��中对应的位)以及PIE组应答位。
5.2 多核通信中的数据一致性问题
当CPU和DMA,或者两个核的DMA同时访问同一块内存时,数据一致性问题就会浮现。
- 现象:CPU读取到的由DMA(或另一核)写入的数据是旧的、不完整的,或者是错乱的。
- 根本原因:现代微控制器通常有缓存(Cache)和写缓冲(Write Buffer)。CPU写入的数据可能还停留在Cache或写缓冲中,并未立即更新到主存(DMA直接访问的位置)。反之,DMA写入主存的数据,CPU也可能因为Cache中存有旧副本而读取不到最新值。
- 解决方案:
- 禁用Cache:对于关键的共享数据缓冲区,最简单粗暴的方法是在链接器命令文件(.cmd)中将其分配到非缓存(non-cacheable)的内存区域,或者通过寄存器显式禁用该内存区域的Cache。
- 内存屏障操作:在CPU写入共享数据后、启动DMA之前,插入数据同步屏障(DSB)或清洗Cache(Clean)指令,确保数据落盘到主存。在CPU读取DMA可能修改过的数据前,插入无效化Cache(Invalidate)指令,丢弃旧副本,从主存重新加载。TI的C2000 Ware库通常提供了
memcpy或DMA相关的函数,其内部可能已处理Cache一致性,但自己操作内存时需格外小心。 - 使用IPC进行同步:不要依赖简单的“标志变量”。使用芯片提供的IPC(Inter-Processor Communication)模块的硬件信号量、消息邮箱等机制来进行核间同步和通信,这些机制通常设计为Cache友好的。
5.3 性能优化与实时性保障
DMA用得好是帮手,用不好反而会成为系统实时性的瓶颈。
- 总线竞争:DMA与CPU共享系统总线。当DMA进行大规模数据搬运时,会占用总线带宽,可能导致CPU取指或访问内存变慢,影响中断响应。优化策略:合理规划DMA传输的时机和带宽。对于实时性要求极高的任务,可以考虑使用高优先级DMA通道(如CH1),并利用
PRIORITYCTRL1寄存器进行配置,确保其传输不被延迟。也可以将CPU的关键代码和数据放到零等待状态的RAM中执行,减少对总线的依赖。 - 中断延迟:虽然DMA解放了CPU,但DMA传输完成中断如果处理不当,其延迟本身也会影响系统。优化策略:确保DMA中断服务程序(ISR)尽可能短小精悍,只做必要的标志设置或指针更新,繁重的数据处理放到主循环或低优先级任务中。合理配置PIE和CPU的中断优先级。
- 配置开销:对于需要频繁改变传输参数的DMA应用(如处理不同长度的数据包),反复配置寄存器会产生CPU开销。优化策略:充分利用
SHADOW寄存器。在DMA传输进行时,CPU可以提前将下一组传输参数写入SHADOW寄存器组。当前传输结束时,DMA会自动加载SHADOW值,实现“乒乓”操作或流水线处理,几乎无感切换。
调试DMA时,仿真器是你的好朋友。充分利用CCS(Code Composer Studio)的实时内存查看、寄存器观察和事件触发功能。可以设置当DMA的CONTROL.RUNSTS位变化,或者特定内存地址被写入时触发断点,这能帮你精准定位DMA的启动和停止时刻,观察数据传输的过程。