1. 项目概述
如果你正在使用TI的TMS320F2837xD系列微控制器,并且对如何高效地搬运数据、减轻CPU负担感到头疼,那么今天这篇关于DMA寄存器的深度解析,就是为你准备的。DMA,也就是直接存储器访问,是嵌入式系统里提升性能的“王牌”技术。简单来说,它就像一个独立的、高效的搬运工,能在内存和外设之间直接搬数据,完全不用CPU操心。在F2837xD这种双核高性能MCU里,DMA功能尤其强大,但它的配置也相对复杂,核心就在于那一大堆寄存器。
很多开发者拿到技术手册,看到几十个寄存器,每个寄存器又有十几个位域,往往就懵了,只能照着例程配置,知其然而不知其所以然。一旦遇到复杂的数据搬运模式,比如需要地址回绕、突发传输,或者多通道优先级管理时,就容易抓瞎。这篇文章的目的,就是帮你彻底吃透F2837xD的DMA寄存器。我不会只给你翻译手册,而是结合我实际项目中的使用经验,从全局控制到每个通道的精细配置,掰开揉碎了讲清楚每个关键寄存器的作用、配置逻辑以及那些手册里没写的“坑”。无论你是想实现ADC的乒乓缓冲,还是SPI的高速数据流,理解这些寄存器都是你精准控制DMA、榨干硬件性能的第一步。
2. DMA寄存器架构总览与设计思路
在深入每个寄存器之前,我们必须先建立起对F2837xD DMA模块整体架构的认知。这就像打仗前先看地图,理解了整体布局,才能知道每个部队(寄存器)该放在什么位置,执行什么任务。
F2837xD的DMA控制器是一个高度模块化、可灵活配置的子系统。它并非一个单一的黑盒,而是由全局控制寄存器组(DMA_REGS)和六个独立的通道寄存器组(DMA_CHx_REGS)构成的层次化结构。这种设计思路非常清晰:全局寄存器负责“司令部”的调度和管控,比如整个DMA模块的复位、调试模式、以及通道间的优先级仲裁;而每个通道的寄存器组则像一个独立的“作战单元”,拥有完整的配置项来控制自己的数据传输行为,包括源/目的地址、传输量、步进、工作模式等。
这种架构带来的最大好处是并行性与独立性。六个通道可以同时工作,互不干扰,每个通道都可以绑定到不同的外设(如ADC、SPI、ePWM等)或由软件触发。全局的优先级控制寄存器(如PRIORITYCTRL1)则允许我们在必要时打破默认的轮询调度,为某个关键通道(比如通道1)赋予高优先级,确保其实时性。理解这个“全局+通道”的两级模型,是后续所有配置的基础。在配置时,我们的操作流程通常是:先通过全局寄存器进行模块级初始化(如必要时进行硬复位),然后逐个配置每个需要用到的通道,最后再通过全局或通道控制位来启动传输。
3. 全局控制寄存器(DMA_REGS)深度解析
全局寄存器位于DMA控制器的“司令部”,地址从0x0000_1000开始。它们管理着DMA模块的顶层行为,虽然数量不多,但每一个都至关重要,用错了可能导致整个DMA行为异常。
3.1 DMACTRL:全局控制与复位寄存器
DMACTRL寄存器是DMA的“总开关”和“复位键”,偏移地址为0h。它只有两个有效的控制位,但功能非常强力。
位1:PRIORITYRESET(优先级复位)这是一个“写1清零”类型的位(W1S)。当你向该位写入1时,它会复位DMA内部的轮询优先级状态机。这是什么意思呢?在默认情况下(无高优先级通道),DMA控制器会以轮询方式依次服务各个使能的通道。这个状态机内部维护着一个指针,记录当前轮到哪个通道。写入PRIORITYRESET会把这个指针拉回到起始位置(通常是编号最小的已使能通道)。
重要提示:这个复位操作是“优雅”的。手册明确提到,如果写入此位时,某个通道(比如高优先级的CH1)正在执行一次突发(Burst)传输,那么DMA会等待这个突发传输完成,甚至还会完成下一个等待中的低优先级突发,然后再复位状态机。这避免了在数据传输中途被强行打断导致的数据丢失或地址指针错乱。如果你改变了通道的优先级配置(例如通过
PRIORITYCTRL1寄存器),强烈建议在重新使能通道前,先执行一次优先级复位,以确保调度状态从一个干净、确定的状态开始。
位0:HARDRESET(硬复位)这是DMA模块的“核按钮”,同样是W1S类型。向该位写入1,会复位整个DMA模块,其效果类似于给整个芯片复位。所有寄存器(除了少数只读的状态位)都会恢复到默认值,任何正在进行的数据传输都会被立即中止。
这里有一个非常关键的实操细节,手册里提到了但很容易被忽略:向HARDRESET位写1后,该操作需要一个时钟周期才能生效。因此,在软件中,执行完写HARDRESET=1的操作后,必须手动插入一个空操作指令(如NOP)或确保有一段延迟,之后才能去访问其他DMA寄存器。如果立即访问,可能会因为DMA模块正处于复位不稳定状态而导致访问失败或得到错误数据。这个坑我在早期调试时踩过,现象就是复位后立即配置通道,配置值偶尔会丢失,加入一个NOP后问题解决。
3.2 DEBUGCTRL:调试控制寄存器
DEBUGCTRL寄存器只有一个有效位FREE(位15),它专门用于仿真调试场景。当你在CCS(Code Composer Studio)中设置断点、单步执行或暂停CPU时,就会发生“仿真暂停事件”。
- FREE = 0:仿真暂停时,DMA会完成当前正在进行的读写操作,然后停止。这是默认且最安全的行为,可以让你在断点处清晰地观察内存和外设的数据状态,而不会因为DMA在后台持续修改数据导致调试信息混乱。
- FREE = 1:仿真暂停时,DMA无视暂停事件,继续运行。这个模式主要用于调试一些与实时性紧密相关的场景,比如你不想让DMA传输因为断点而中断,以免影响后续外设的触发时序。但使用此模式要格外小心,因为内存数据可能在你不注意时被更改。
3.3 PRIORITYCTRL1:优先级控制寄存器
这个寄存器(偏移4h)目前只定义了一个位:CH1PRIORITY(位0)。它决定了通道1是否享有高优先级。
- CH1PRIORITY = 0:通道1与其他通道(2-6)优先级相同,大家平等地参与轮询调度。
- CH1PRIORITY = 1:通道1被设置为高优先级。这意味着只要通道1有传输请求(PERINTFLG被置位),它就可以打断当前正在服务的低优先级通道,立即获得服务。被中断的低优先级通道的现场(比如当前地址、计数)会被保存,待通道1的传输完成后,再恢复执行。
配置禁忌:手册用加粗字体警告:优先级只能在所有通道都被禁用(RUNSTS=0)时才能更改!如果你在通道运行期间修改此位,结果是未定义的,很可能导致DMA控制器工作异常。正确的操作序列是:1. 停止所有通道;2. 修改PRIORITYCTRL1;3. 执行DMACTRL.PRIORITYRESET;4. 重新配置并启动通道。
3.4 PRIORITYSTAT:优先级状态寄存器
这是一个只读的状态寄存器(偏移6h),用于监视DMA控制器的内部调度状态,在调试多通道、带优先级抢占的场景时非常有用。
- 位[2:0] ACTIVESTS:这3个比特指示当前正在活跃传输的通道编号(1-6)。值为0表示没有通道在传输。你可以通过轮询这个字段来了解DMA控制器正在为谁服务。
- ��[6:4] ACTIVESTS_SHADOW:这是一个“影子”状态位,仅在通道1设置为高优先级时才有意义。它记录了被高优先级通道1打断的那个低优先级通道的编号。当通道1的服务完成后,DMA控制器会依据这个影子寄存器的值,恢复对那个低优先级通道的服务。如果这个影子字段的值是0或者与
ACTIVESTS相同,则说明没有通道因通道1中断而挂起。这个机制是高优先级抢占功能能够正确实现的关键,保证了被抢占通道的现场恢复。
4. 通道寄存器(DMA_CHx_REGS)核心配置详解
这是DMA配置的“主战场”,每个通道都有完全相同的一套寄存器组。理解它们,你就能像指挥交响乐一样,精确控制数据的流动。我们将其分为几个功能组来理解。
4.1 模式与控制组:设定传输行为基调
这组寄存器定义了DMA通道的“性格”和“状态”。
MODE寄存器(偏移0h):这是通道的“模式开关面板”。
- CHINTE(位15):通道中断使能。置1后,当满足
CHINTMODE条件时,会向CPU产生中断。这是实现“传输完成通知”的关键。 - DATASIZE(位14):数据宽度选择。这是第一个容易混淆的点。0代表每次读写操作传输16位(一个字),1代表传输32位(双字)。但请注意,无论此位如何设置,其他所有寄存器(如
BURST_SIZE,TRANSFER_SIZE)中关于“长度”和“步进”的参数,其单位始终是16位字!如果你设置为32位模式,那么SRC_BURST_STEP这类地址步进值就需要设置为2,因为每传输一个32位数据,地址实际需要前进2个16位字的位置。 - CONTINUOUS(位11)与ONESHOT(位10):这两个位组合定义了通道的工作循环模式,是理解DMA行为的关键。
CONTINUOUS=0, ONESHOT=0:单次触发单次突发模式。这是最基础的模式。一次外设事件触发(或软件强制)只执行一个突发(Burst)传输。整个传输(Transfer)完成后,通道停止,RUNSTS位清零。CONTINUOUS=0, ONESHOT=1:单次触发完整传输模式。一次触发会完成整个配置的传输(即连续执行多个突发,直到TRANSFER_COUNT减为0)。完成后通道停止。CONTINUOUS=1, ONESHOT=0:连续触发单次突发模式。通道永不停止。每当TRANSFER_COUNT减到0,DMA会自动用TRANSFER_SIZE重新加载它,并等待下一个触发。每次触发仍只执行一个突发。这适用于需要持续处理但数据包较小的场景。CONTINUOUS=1, ONESHOT=1:连续触发完整传输模式。通道永不停止。一次触发完成整个传输后,DMA自动重新初始化所有计数器和地址(从Shadow寄存器加载),等待下一次触发。这是实现“乒乓缓冲”、“循环缓冲”的常用模式。
- CHINTMODE(位9):中断产生模式。0=在传输开始时产生中断;1=在传输结束时产生中断。根据你的应用选择,例如,如果你需要在传输完成后处理数据,就选结束中断。
- PERINTE(位8):外设事件触发使能。必须置1,否则DMA通道只会响应软件强制触发(
PERINTFRC),而不会响应外设(如ADC转换完成)的信号。 - PERINTSEL(位[4:0]):注意!手册明确标注这是遗留位(Legacy bits)。在F2837xD上,DMA通道与具体外设事件的映射关系,是由另一个独立的寄存器组
DMACHSRCSELn(属于DMA_CLA_SRC_SEL_REGS)来配置的。此处通常只需简单地将其设置为通道号本身(例如,对于通道1就写1)。真正的源选择需要在DMACHSRCSELn寄存器中完成,将特定外设的事件编号映射到该DMA通道。
CONTROL寄存器(偏移1h):这是通道的“状态面板和控制台”。
- 状态标志位(只读):
RUNSTS(位13):通道运行状态。1表示已使能,正在等待或进行传输。BURSTSTS(位12):突发状态。1表示当前正在执行一个突发传输。TRANSFERSTS(位11):传输状态。1表示当前正在执行一个完整的传输(可能包含多个突发)。PERINTFLG(位8):外设事件触发标志。为1表示有触发信号在等待处理,当DMA开始处理该触发的第一个突发时,此位自动清零。OVRFLG(位14):溢出标志。如果在前一个PERINTFLG还未被处理(即PERINTFLG=1)时,又来了一个新的外设事件,此位置1,表示可能丢失了一次触发。这通常意味着CPU处理太慢或DMA配置的传输速度跟不上外设产生数据的速度。
- 控制位(W1S类型):
RUN(位0):启动通道。写1使能通道,RUNSTS随之置1。这是配置完所有参数后,让DMA开始等待触发的“点火”操作。HALT(位1):暂停通道。写1会暂停通道,但保持当前所有寄存器状态(地址、计数器等)。可用于调试或临时停止数据流。SOFTRESET(位2):通道软复位。写1会将本通道复位到默认状态(停止,计数器清零),但不影响其他通道。比全局硬复位更温和。PERINTFRC(位3):软件强制触发。即使没有外设事件,写1也能让PERINTFLG置位,模拟一次触发,启动DMA传输。用于测试或软件启动传输。PERINTCLR(位4):清除触发标志。写1可手动清除PERINTFLG位,取消一个等待中的触发。ERRCLR(位7):清除错误标志。写1可清除OVRFLG溢出标志。
4.2 传输尺度组:定义数据搬运的“量”
这组寄存器定义了每次搬多少数据(Burst)以及总共搬多少轮(Transfer)。
BURST_SIZE寄存器(偏移2h):定义单个突发(Burst)传输的数据量。其低5位BURSTSIZE[4:0]有效。这里有个非常重要的公式:实际突发大小 = BURSTSIZE + 1(单位:16位字)。也就是说,这个寄存器配置的是“次数-1”。如果你想设置一个突发传输16个字,那么需要写入BURSTSIZE = 15 (0xF)。突发是DMA传输的最小不可分割单元,一旦开始,就会连续搬运完BURST_SIZE定义的所有数据,期间不能被其他通道打断(除非高优先级通道抢占)。
TRANSFER_SIZE寄存器(偏移6h):定义一次完整传输(Transfer)包含的突发(Burst)数量。这是一个16位寄存器TRANSFERSIZE[15:0]。同样遵循公式:实际传输大小(突发数)= TRANSFERSIZE + 1。一次传输由1到65536个突发组成。ONESHOT模式就是针对这个“传输”而言的。
BURST_COUNT(偏移3h)与 TRANSFER_COUNT(偏移7h)寄存器:这两个是只读的递减计数器。在传输开始时,它们分别被加载为BURST_SIZE和TRANSFER_SIZE的值。BURST_COUNT在每个字传输后递减,减到0表示一个突发完成;TRANSFER_COUNT在每个突发完成后递减,减到0表示一次完整传输完成。你可以通过读取它们来查询DMA传输的实时进度。
4.3 地址与步进组:指挥数据的“流向”
这是DMA配置中最灵活也最强大的部分,决定了数据从哪里来、到哪里去,以及地址指针如何移动。
地址寄存器:每个通道有四个32位的地址寄存器,分为“影子(Shadow)”和“活动(Active)”两组。
- SRC_BEG_ADDR_SHADOW / DST_BEG_ADDR_SHADOW:源/目的起始地址影子寄存器。这���你在软件中配置的地址。在传输开始时,这个值会被加载到对应的“活动”寄存器中。
- SRC_ADDR_SHADOW / DST_ADDR_SHADOW:源/目的当前地址影子寄存器。同样由软件配置,传输开始时加载到“活动”寄存器,作为传输的起始当前地址。
- SRC_BEG_ADDR_ACTIVE / DST_BEG_ADDR_ACTIVE:源/目的起始地址活动寄存器。只读,反映传输过程中当前的“基准”起始地址,在地址回绕(Wrap)时可能会被更新。
- SRC_ADDR_ACTIVE / DST_ADDR_ACTIVE:源/目的当前地址活动寄存器。只读,实时反映DMA传输引擎当前正在使用或即将使用的地址。
“影子-活动”双缓冲机制是DMA流畅运行的关键。你可以在DMA传输进行中,安全地修改影子寄存器,为下一次传输做好准备。当前传输继续使用活动寄存器的值,不受影响。等到当前传输结束,下一次传输开始时,新的影子值会自动加载到活动寄存器。这就实现了传输间的无缝衔接,是配置连续、循环传输的基础。
步进(Step)寄存器:控制每次传输后地址指针的跳跃幅度。
- SRC_BURST_STEP / DST_BURST_STEP:突发内步进。这是一个16位有符号补码值,范围-4096到+4095。在一个突发(Burst)内部,每完成一次读写(一个16位或32位数据),源或目的地址就加上这个值。通常,对于连续内存区域,设置为+1(16位模式)或+2(32位模式)。如果你需要从某个外设数据寄存器(地址固定)读取数据到递增的内存,那么源步进可以设为0,目的步进设为+1。
- SRC_TRANSFER_STEP / DST_TRANSFER_STEP:传输间步进。同样是一个16位有符号补码值。在完成一个突发(Burst)之后,开始下一个突发之前,源或目的地址会加上这个值。这常用于处理二维数组或非连续的数据块。例如,从一个矩阵中按行读取数据,突发步进对应行内元素的间隔,传输步进则对应跳到下一行起始地址的偏移。
4.4 地址回绕(Wrap)组:实现循环与复杂寻址
回绕功能是DMA地址生成器的“高级技巧”,用于实现环形缓冲区或处理具有周期性的数据模式。它允许地址指针在前进一段距离后,自动跳回到一个“基址”。
WRAP_SIZE寄存器(SRC_WRAP_SIZE / DST_WRAP_SIZE):定义在多少次突发(Burst)之后发生地址回绕。公式同样是:实际回绕前突发数 = WRAPSIZE + 1。例如,设置WRAPSIZE = 9,则表示每传输10个突发后,触发一次回绕。如果你想禁用回绕功能,手册给出的方法是:将WRAPSIZE设置为一个大于TRANSFERSIZE的值。这样,在整个传输完成前,回绕计数器都不会减到0,回绕就不会发生。
WRAP_COUNT寄存器(SRC_WRAP_COUNT / DST_WRAP_COUNT):只读的回绕递减计数器。初始值为WRAP_SIZE,每完成一个突发减1,减到0时触发回绕,并重新加载WRAP_SIZE值。
WRAP_STEP寄存器(SRC_WRAP_STEP / DST_WRAP_STEP):这是回绕功能的精髓所在。当WRAP_COUNT减到0触发回绕时,对应的起始地址活动寄存器(BEG_ADDR_ACTIVE)会加上WRAP_STEP这个值。注意,是起始地址,不是当前地址。
理解回绕的关键在于区分“起始地址”和“当前地址”。回绕操作更新的是BEG_ADDR_ACTIVE。而ADDR_ACTIVE会在下一次传输开始时,从更新后的BEG_ADDR_ACTIVE(或对应的影子寄存器)重新加载。一个典型应用是乒乓缓冲:设置两个缓冲区A和B。BEG_ADDR_SHADOW指向A区开头,TRANSFER_SIZE设为半缓冲区大小,WRAP_SIZE设为1(即每传输完一个半缓冲区就回绕),WRAP_STEP设为半缓冲区的大小(指向B区开头)。这样,DMA就会在A区和B区之间自动交替填充数据。
5. 寄存器配置实战流程与核心环节
理解了每个寄存器的作用后,我们来看如何将它们组合起来,完成一个完整的DMA通道配置。这里以一个常见的场景为例:将ADC结果序列(假设16个结果)通过DMA搬运到内存中的一个数组,并希望每次搬运完成后产生中断通知CPU。
5.1 配置流程详解
全局初始化(可选):如果是从不确定状态开始,可以先进行软复位。
DmaRegs.DMACTRL.bit.HARDRESET = 1; // 全局硬复位 asm(“ NOP”); // 关键!等待一个周期让复位生效禁用目标通道:在配置任何通道寄存器前,确保通道是停止的。
volatile struct DMA_CH_REGS* DmaCh = &DmaRegs.CH1_REGS; // 以通道1为例 DmaCh->CONTROL.bit.RUN = 0; // 确保RUN位为0 while(DmaCh->CONTROL.bit.RUNSTS == 1); // 可选,等待RUNSTS确认停止配置通道工作模式(MODE寄存器):
DmaCh->MODE.bit.CHINTE = 1; // 使能通道中断 DmaCh->MODE.bit.DATASIZE = 0; // 16位数据(ADC结果通常是16位) DmaCh->MODE.bit.CONTINUOUS = 0; // 非连续模式 DmaCh->MODE.bit.ONESHOT = 1; // 一次触发完成整个传输 DmaCh->MODE.bit.CHINTMODE = 1; // 传输结束时产生中断 DmaCh->MODE.bit.PERINTE = 1; // 使能外设事件触发 DmaCh->MODE.bit.PERINTSEL = 1; // 遗留位,设为通道号1 // 注意:实际外设源选择需配置DMACHSRCSEL1寄存器,例如映射ADCINT1事件配置传输尺度:
// 假设ADC序列有16个转换值,我们希望一次触发搬完。 // 设置突发大小:一次突发搬1个数据(字) DmaCh->BURST_SIZE.bit.BURSTSIZE = 0; // 实际大小 = 0 + 1 = 1 word // 设置传输大小:一次传输包含16个突发 DmaCh->TRANSFER_SIZE.all = 15; // 实际突发数 = 15 + 1 = 16配置地址与步进:
// 假设源地址是ADC结果寄存器(固定地址) DmaCh->SRC_BEG_ADDR_SHADOW = (uint32_t)&AdcResult.ADCRESULT0; DmaCh->SRC_ADDR_SHADOW = (uint32_t)&AdcResult.ADCRESULT0; DmaCh->SRC_BURST_STEP.all = 0; // 源地址固定,突发内不步进 DmaCh->SRC_TRANSFER_STEP.all = 0; // 传输间源地址也不变 // 假设目的地址是内存数组MyAdcBuffer[16] DmaCh->DST_BEG_ADDR_SHADOW = (uint32_t)&MyAdcBuffer[0]; DmaCh->DST_ADDR_SHADOW = (uint32_t)&MyAdcBuffer[0]; DmaCh->DST_BURST_STEP.all = 1; // 内存地址递增,每次+1个16位字 DmaCh->DST_TRANSFER_STEP.all = 1; // 每个突发后,目的地址也+1(因为突发大小是1) // 这里注意:由于BURST_SIZE=1,所以每个突发只搬1个字,传输步进设为1,刚好让目的地址在每次突发后指向下一个数组元素。配置回绕(本例不需要,保持默认):
// 默认WRAP_SIZE=0xFFFF(最大值),大于TRANSFER_SIZE,回绕功能被禁用。清除状态与强制触发(用于测试):
DmaCh->CONTROL.bit.PERINTCLR = 1; // 清除可能存在的旧触发标志 DmaCh->CONTROL.bit.ERRCLR = 1; // 清除错误标志 // 可以先用软件触发测试一下 // DmaCh->CONTROL.bit.PERINTFRC = 1;启动通道:
DmaCh->CONTROL.bit.RUN = 1; // 点火!通道进入等待触发状态 // 此时RUNSTS位应自动变为1配置外设触发源(此步骤在DMA寄存器之外,但至关重要):
// 需要配置DMACHSRCSEL1寄存器,将ADC的某个中断(如ADCINT1)映射到DMA通道1 DmaClaSrcSelRegs.DMACHSRCSEL1.bit.CH1 = 1; // 假设值1对应ADCINT1 // 同时需要配置ADC模块,使其在转换完成时产生相应的事件。
5.2 核心环节:中断服务程序(ISR)中的处理
当传输完成中断产生后,在ISR中你需要做以下几件事:
- 确认中断源:检查PIE分组确认是DMA通道1中断。
- 处理数据:此时
MyAdcBuffer数组中应该已经存好了16个ADC结果,可以进行后续计算或转发。 - 清除DMA中断标志:DMA通道中断标志需要手动清除。
volatile struct DMA_CH_REGS* DmaCh = &DmaRegs.CH1_REGS; // 通常需要向通道控制寄存器的某个位写1来清除中断标志,但F2837xD DMA通道中断标志在PIE控制器中。 // 更常见的做法是,在DMA ISR中重新配置影子寄存器(如果需要连续传输),然后清除PIE中的中断标志。 PieCtrlRegs.PIEACK.all = PIEACK_GROUPx; // 清除对应的PIE组应答位 - 重新武装DMA(对于连续模式):如果你配置的是
CONTINUOUS=1的模式,DMA会自动重载。如果是单次模式,并且需要再次传输,你需要:- 检查并清除可能的错误(
OVRFLG)。 - 重新给影子地址寄存器赋值(如果目的地址需要更新)。
- 再次将
RUN位置1(如果之前已停止)。
- 检查并清除可能的错误(
6. 常见问题排查与调试技巧实录
即使理解了所有寄存器,实际调试中还是会遇到各种问题。下面是我在多个项目中总结出的常见“坑点”和解决方法。
6.1 DMA不启动,无数据传输
这是最常见的问题。请按以下清单逐项检查:
- 通道使能了吗?:确认
CONTROL.RUN位已被置1,并且RUNSTS状态位也为1。如果RUNSTS不为1,说明通道未就绪。 - 触发源配置正确吗?:双重检查:
MODE.PERINTE是否置1?(使能外设触发)DMACHSRCSELn寄存器是否将正确的外设事件映射到了该DMA通道?这是最容易被遗漏的一步!- 外设本身是否配置为产生DMA请求事件?例如,ADC的SOC配置中是否使能了DMA触发?
- 触发标志来了吗?:监控
CONTROL.PERINTFLG位。如果外设事件已发生,但此位仍为0,问题出在事件路由上。如果此位为1但DMA不动,可能是配置有误。 - 地址对齐问题:确保源和目的地址符合访问要求。例如,如果配置为32位模式(
DATASIZE=1),那么地址必须是4字节对齐的。非对齐访问可能导致总线错误,DMA静默失败。 - 寄存器保护:DMA寄存器受EALLOW保护。确保你的配置代码被
EALLOW;和EDIS;指令包裹。
6.2 数据传输地址错乱或数据损坏
- 步进(STEP)寄存器配置错误:这是地址错乱的罪魁祸首。牢记所有步进值的单位是16位字。在32位模式下,如果你想地址按字节顺序递增,
BURST_STEP应该设为2。一个快速验证方法是:在调试器中单步执行,触发一次DMA后,立即查看SRC_ADDR_ACTIVE和DST_ADDR_ACTIVE寄存器,看它们是否按预期变化。 - 影子 vs 活动地址混淆:你配置的是影子寄存器。传输开始时,活动寄存器从影子寄存器加载。如果你在传输中修改了影子寄存器,不会影响本次传输。要查看DMA引擎真正使用的地址,必须读
*_ADDR_ACTIVE和*_BEG_ADDR_ACTIVE寄存器。 - 缓冲区溢出:计算好你的传输总量(
(BURSTSIZE+1) * (TRANSFERSIZE+1))和步进,确保不会写到分配的内存区域之外。特别是使用回绕功能时,要精确计算WRAP_SIZE和WRAP_STEP,确保在缓冲区边界内循环。
6.3 中断不产生或产生过于频繁
- 中断使能了吗?:检查
MODE.CHINTE位。 - 中断模式理解对吗?:
CHINTMODE=0(传输开始中断)和CHINTMODE=1(传输结束中断)行为完全不同。如果你在传输结束处理数据,却配置了开始中断,就会找不到数据。 - CPU中断配置好了吗?:DMA通道中断需要像其他外设中断一样,在PIE向量表中注册ISR,并使能对应的PIE组和CPU中断(IER寄存器)。DMA寄存器只控制是否产生中断事件,到CPU的路径还需要PIE配置。
- 中断标志清除了吗?:DMA通道中断标志在PIE模块中。必须在ISR中清除PIE组应答位(
PIEACK),否则只会产生一次中断。 - 连续模式下的中断:如果配置了
CONTINUOUS=1,那么每次传输完成(TRANSFER_COUNT归零并重载)都会产生中断。如果你的处理速度跟不上中断频率,会导致系统卡死。此时可以考虑使用“半满”或“全满”的缓冲区策略,配合回绕功能,减少中断频率。
6.4 性能优化与高级技巧
- 突发(Burst)大小的选择:增大
BURST_SIZE可以显著提升总线利用率和传输效率,因为DMA在单个突发内占用总线,减少了仲裁开销。但突发太大会增加单次传输的延迟,可能影响实时性。需要根据外设数据产生速度和系统实时性要求折中。对于ADC流,通常设置为一次突发搬运一个完整序列的数据。 - 优先级与通道仲裁:对于有实时性要求的数据流(如电机控制的PWM更新),可以将其所在的DMA通道(例如CH1)设置为高优先级(
PRIORITYCTRL1.CH1PRIORITY=1)。但要小心优先级反转问题,确保高优先级通道不会饿死低优先级通道。 - 使用“影子”寄存器实现双缓冲:这是DMA编程的核心技巧。在ISR中,当处理完一半缓冲区数据后,立即修改影子寄存器指向另一半缓冲区。这样,DMA在完成当前传输后,会自动开始向新的缓冲区填充数据,实现CPU处理和DMA填充的并行,几乎零延迟切换。
- 调试利器:状态寄存器:遇到复杂问题时,别光猜。充分利用
CONTROL寄存器中的RUNSTS、BURSTSTS、TRANSFERSTS状态位,以及PRIORITYSTAT寄存器,可以清晰地看到DMA控制器内部的状态机处于哪个阶段,哪个通道在活动,是否被抢占。结合CCS的实时内存查看和寄存器查看功能,是定位问题的有效手段。
最后,关于文档版本的一个小提醒:你提供的资料基于SPRUHM8K手册(2013年12月发布,2024年5月修订)。TI的文档更新有时会修正一些细微的行为描述或添加新的特性。在开始一个关键项目前,最好去TI官网确认一下是否有该芯片最新版本的技术参考手册(TRM),以确保获取最准确的信息。寄存器地址和基本功能通常很稳定,但一些边角情况的行为描述可能会被完善。