1. 项目概述:从寄存器手册到实战配置

如果你在嵌入式开发,特别是基于TI C2000系列MCU(比如TMS320F28069)做实时控制或信号处理,肯定绕不开DMA。手册里那几十页的寄存器描述,读起来是不是感觉每个字都认识,但连起来就不知道从哪下手?我当年第一次看TMS320x2806x的DMA章节也是这个感觉,表格密密麻麻,字段含义抽象,更别提把它们组合成一个能跑起来的实际应用了。

这份技术参考手册(SPRUH18I)的第11.9节,详细列出了DMA模块的所有寄存器。但手册是“字典”,它告诉你每个“单词”的意思,却不教你如何“造句”和“写文章”。直接内存访问的核心价值在于解放CPU,让数据在内存和外设(比如ADC结果寄存器、SPI发送缓冲区)之间自动搬运。而实现这一切的“开关”和“规则”,就藏在这些寄存器里。从全局的控制与状态寄存器(如DMACTRL、DEBUGCTRL),到每个通道独立的模式、控制、地址指针寄存器,它们共同构成了一套精密的自动化数据传输流水线。

理解这些寄存器,不仅仅是知道某个比特位是0还是1,更是要理解它们如何联动,如何响应事件,以及配置不当会引发哪些隐蔽的问题。比如,BURST_SIZE和TRANSFER_SIZE有什么区别?影子寄存器(Shadow Register)在传输中扮演什么角色?为什么修改某些寄存器前必须确保通道已停止(RUNSTS=0)?这篇文章,我就结合自己多年在电机控制、数字电源项目中折腾DMA的经验,带你把这些寄存器“盘”明白,让你能真正用起来,而不是停留在手册的表格里。

2. DMA寄存器全景与功能架构解析

拿到一份寄存器列表,最怕的就是一头扎进某个寄存器的细节里,结果“只见树木,不见森林”。我们先从顶层看看TMS320x2806x的DMA模块到底提供了哪些“控制面板”。

2.1 寄存器地图与分类

根据手册中的Table 11-2,整个DMA的寄存器空间从地址0x1000开始,可以清晰地分为三大类:

  1. 全局控制与状态寄存器 :这些寄存器作用于整个DMA模块,而不是某个特定通道。

    • DMACTRL :DMA全局控制寄存器。这里有两个关键位: HARDRESET (硬复位整个DMA)和 PRIORITYRESET (复位轮询优先级状态机)。 特别注意 :手册提到写 HARDRESET 后需要至少插入一个 NOP 指令才能访问其他DMA寄存器,这是一个极易忽略的硬件时序要求。
    • DEBUGCTRL :调试控制寄存器。核心是 FREE 位,它决定了在仿真器暂停(Emulation Halt)时,DMA是继续完成当前读写访问后冻结,还是完全不受影响地“自由运行”。在调试实时性要求极高的数据流时,这个位的设置直接影响你能否捕获到真实运行状态下的数据。
    • PRIORITYCTRL1 :优先级控制寄存器1。目前只用了最低位 CH1PRIORITY ,用来设置通道1是否为最高优先级。 关键限制 :手册明确写道,修改通道优先级必须在所有通道都禁用时进行,并且在修改后、重启通道前,应执行一次优先级复位(操作 PRIORITYRESET 位)。
    • PRIORITYSTAT :优先级状态寄存器。 ACTIVESTS 位段实时指示当前正在服务的通道号。当通道1设为高优先级时, ACTIVESTS_SHADOW 位段用于保存被通道1中断的那个低优先级通道号,待通道1服务完成后恢复。这是实现高优先级通道“插队”机制的关键状态记录。
  2. 通道级寄存器组 :这是配置的重头戏,每个通道都有一套完全相同的寄存器组,从CH1到CH6,地址连续分布。每一套都包含:

    • 控制与模式类 MODE , CONTROL 。这是通道的“大脑”,决定通道如何响应、何时启动、何时中断。
    • 传输尺度类 BURST_SIZE , TRANSFER_SIZE 。定义了一次“传输”的粒度。这里必须厘清概念:一次 传输 由多个 突发 组成,一次 突发 由多个 组成。 BURST_SIZE 定义了一个突发包含多少个数据字(16位或32位), TRANSFER_SIZE 定义了一次传输包含多少个突发。
    • 地址步进类 SRC_BURST_STEP , DST_BURST_STEP , SRC_TRANSFER_STEP , DST_TRANSFER_STEP 。它们控制每次突发后、每次传输后,源地址和目的地址指针的自动偏移量。支持正负偏移,是实现灵活数据搬运(如数组遍历、间隔采样)的核心。
    • 地址环绕类 SRC_WRAP_SIZE , SRC_WRAP_COUNT , SRC_WRAP_STEP (目的端同理)。这是高级功能,允许地址指针在达到设定的 WRAP_SIZE (以突发数为单位)后,不是简单地按 TRANSFER_STEP 递增,而是跳转回一个基地址(通过 WRAP_STEP 调整)。常用于处理循环缓冲区或二维数据块。
    • 地址指针类 :分为“影子”和“活动”两组。 SRC_BEG_ADDR_SHADOW / DST_BEG_ADDR_SHADOW 是用户配置的起始地址。当传输开始时,这些值被复制到 SRC_BEG_ADDR / DST_BEG_ADDR ,并在传输过程中作为基准。 SRC_ADDR_SHADOW / DST_ADDR_SHADOW 是用户可设置的当前地址,传输开始时被复制到只读的 SRC_ADDR / DST_ADDR 中,由DMA硬件在传输过程中自动更新。

2.2 关键设计理念:影子寄存器与三级传输模型

TI在这个DMA设计中,贯穿了两个非常重要的理念,理解了它们,配置起来就不会混乱。

第一,影子寄存器机制。 你会发现很多关键参数(如地址、尺寸)都有“Shadow”和“Active”两组。 SHADOW 寄存器是用户可读写的配置寄存器,而 ACTIVE 寄存器是DMA硬件实际使用的、只读的状态寄存器。当一次传输(Transfer)启动时(由事件触发,且 RUNSTS=1 ),DMA硬件会将 SHADOW 组的值一次性拷贝到 ACTIVE 组,然后基于 ACTIVE 组的值进行本次传输。这意味着,在一次传输进行中,你可以安全地修改 SHADOW 寄存器的值,为下一次传输做准备,而不会影响当前正在进行的传输。这实现了传输间的“双缓冲”配置,对于连续、流式数据处理至关重要。

第二,三级传输模型:传输 -> 突发 -> 字。 这是理解所有 SIZE COUNT STEP 寄存器的关键。

  • :单次数据总线访问的最小单位,由 MODE.DATASIZE 决定是16位还是32位。
  • 突发 :一次不可中断的连续数据字传输序列。 BURST_SIZE 定义了突发包含的字数。在突发内部,每传输一个字,地址指针按 BURST_STEP 更新。
  • 传输 :由单个外设中断事件触发的一个完整数据块搬运任务。一个传输包含 TRANSFER_SIZE 个突发。每个突发完成后,地址指针按 TRANSFER_STEP 更新。当 TRANSFER_COUNT 递减到0,一次传输完成。

举个例子:你需要把ADC结果寄存器(16位)的连续100个采样值搬到内存数组。你可以配置: BURST_SIZE = 1 (每个突发搬1个字), TRANSFER_SIZE = 100 (一次传输包含100个突发), SRC_BURST_STEP = 0 (ADC结果寄存器地址固定), DST_TRANSFER_STEP = 2 (内存中每个16位值占2字节,地址+2)。这样,每个ADC中断触发一次传输,该传输包含100个突发(每个突发1个字),自动将100个采样值存放到连续的内存位置。

3. 核心控制寄存器深度解读与配置策略

了解了全景,我们深入最核心、最容易出错的几个寄存器,看看每个比特位在实际项目中该如何设置。

3.1 模式寄存器:定义通道行为

MODE 寄存器是通道的“性格设定”。必须在通道停止( CONTROL.RUNSTS=0 )时配置。

  • CHINTE CHINTMODE :中断使能与模式。这是协调DMA与CPU工作的关键。 CHINTE=1 使能通道中断。 CHINTMODE 决定中断在何时产生: 0 =在新传输开始时产生; 1 =在传输结束时产生。 如何选择? 如果你需要在数据就绪后立刻被CPU处理,用结束中断。如果你需要用中断来触发一个与传输并行的后台任务(比如准备下一个缓冲区),可以用开始中断。更常见的做法是 CHINTMODE=1 (传输结束中断),并在中断服务程序中处理数据或重新配置DMA。
  • DATASIZE :数据宽度。 0 为16位, 1 为32位。 这里有一个巨大的坑 :手册Note里明确写着, 无论此位如何设置,所有DMA寄存器(如地址、步进)所指的“字”都是16位单位! 这意味着,如果你设置 DATASIZE=1 进行32位传输,那么 SRC_BURST_STEP 这类步进寄存器,其步进值仍然是以16位字为单位的。要搬运一个32位数据(占4字节),你需要设置步进为 2 (因为2个16位字=4字节)。很多初学者在这里栽跟头,导致数据错位。
  • CONTINUOUS :连续模式。 1 使能。当 TRANSFER_COUNT 减到0后,DMA会自动用 SHADOW 寄存器的值重新加载 ACTIVE 寄存器,并等待下一个事件触发,开启新一轮传输。这非常适合需要不间断、循环传输的场景,比如实时音频流、电机电流采样。禁用时( 0 ),传输完成一次后DMA停止, RUNSTS 清零。
  • ONESHOT :单次触发模式。 1 使能。使能后,一个外设事件触发后,DMA会连续完成整个 TRANSFER_SIZE 定义的所有突发,而不再需要每个突发都等待事件。 重要限制 :手册明确指出,通道1的高优先级模式( PRIORITYCTRL1.CH1PRIORITY=1 )和单次触发模式不能同时使用。
  • PERINTE PERINTSEL :外设中断触发选择。 PERINTE=1 使能外设触发。 PERINTSEL 这5位是关键,它从31个可能的外设中断源(如ADCINT1, XINT1, ePWMxSOC, TINT0等)中选择一个作为本通道的启动信号。例如,选择ADCINT1,那么每次ADC序列1转换完成,就会触发一次DMA传输。

3.2 控制寄存器:操纵通道状态

CONTROL 寄存器是通道的“操作杆”,用于启动、停止、强制触发和查看状态。

  • RUNSTS , BURSTSTS , TRANSFERSTS :这三个状态位是调试的“眼睛”。 RUNSTS 指示通道是否已就绪( RUN 位写入后置1)。 BURSTSTS 在突发进行中为1。 TRANSFERSTS 在一次传输进行中为1。通过查询它们,可以精确了解DMA处于什么阶段。
  • PERINTFLG , PERINTFRC , PERINTCLR :外设中断标志位及软件操作位。 PERINTFLG 是只读标志,当所选外设中断事件发生时,硬件将其置1。DMA在开始一次传输的第一个突发时,会自动清除它。 PERINTFRC 是软件强制位,写1可以手动置位 PERINTFLG ,用于软件触发一次DMA传输,非常便于测试。 PERINTCLR 是软件清除位,写1可以手动清除 PERINTFLG ,常用于初始化。
  • RUN , HALT , SOFTRESET :运行控制位。 RUN 位写1启动通道(使 RUNSTS=1 )。 HALT 位写1会暂停通道,但会完成当前的数据读写访问, RUNSTS 清零。 SOFTRESET 位写1会软复位整个通道,所有状态和计数器归零, RUNSTS 清零。 操作顺序建议 :初始化时,先配置所有 SHADOW 寄存器,然后置位 RUN 。需要重新配置时,先 HALT ,修改 SHADOW 寄存器,再 RUN 。需要彻底重置通道时,使用 SOFTRESET
  • OVRFLG ERRCLR :溢出与错误处理。 OVRFLG PERINTFLG 已经为1(即上一个事件还未被服务)时,又来了一个新事件,则置1。这表示发生了事件溢出,可能丢失了数据。 ERRCLR 写1可以清除 OVRFLG 以及同步错误标志。在高速数据流中,监控 OVRFLG 是诊断数据是否跟得上的重要手段。

3.3 地址与步进寄存器:规划数据路径

这是DMA灵活性的体现,也是最需要仔细计算的地方。

  • 地址寄存器 SRC_BEG_ADDR_SHADOW DST_BEG_ADDR_SHADOW 存放22位的起始地址。注意地址对齐问题,特别是进行32位访问时( DATASIZE=1 ),源和目的地址都应该是32位对齐的(地址低2位为0)。
  • 步进寄存器 SRC_BURST_STEP DST_BURST_STEP 定义了在一个突发内部,每搬运一个字后,地址指针的增量。 SRC_TRANSFER_STEP DST_TRANSFER_STEP 定义了一个突发完成后,在下一个突发开始前,地址指针的增量。
    • 典型场景1:连续存储 。从ADC固定地址读数据到连续内存。设 DATASIZE=0 (16位)。 SRC_BURST_STEP = 0 (源地址不变)。 DST_BURST_STEP = 1 (目的地址每次+1个16位字)。 TRANSFER_STEP 通常与 BURST_STEP 协同作用,如果 BURST_SIZE=1 ,那么 TRANSFER_STEP 的效果等同于每个数据字后的步进。
    • 典型场景2:间隔采样 。从内存数组发送数据到SPI发送缓冲区,但数组元素间隔存放。设数组为 uint16_t buffer[100] ,但只发送偶数索引元素。 SRC_BURST_STEP = 2 (每次跳过1个元素)。 DST_BURST_STEP = 0 (SPI数据寄存器地址固定)。
  • 环绕寄存器 :用于实现循环缓冲区或处理二维数据。例如,你需要将来自ADC的数据连续填充到一个长度为 L 的循环缓冲区中。可以设置 DST_WRAP_SIZE = L (以突发数为单位), DST_WRAP_STEP = -L (或 -L+1 ,取决于 TRANSFER_STEP )。当目的地址指针在填充了 L 个突发后, WRAP_COUNT 减到0,地址指针会加上 WRAP_STEP ,从而跳回缓冲区开头。 WRAP_STEP 的值需要精心计算,以确保指针正确回绕。

4. 实战配置流程与代码示例

理论说再多,不如一行代码。下面我以一个在TMS320F28069上最常见的应用为例:将ADC序列1(SEQ1)的连续8个通道转换结果,通过DMA搬运到内存中的一个数组 adcResults[] ,并在每次传输完成后产生中断通知CPU。

4.1 步骤一:外设与DMA时钟使能及初始化

首先,确保DMA模块的时钟被使能(通常默认是使能的)。接着,配置ADC模块,这里假设ADC已经初始化,SEQ1配置为连续自动转换8个通道。

// 1. 包含必要的头文件和定义变量
#include "F2806x_Device.h"
#include "F2806x_Examples.h"
#define ADC_RESULTS_SIZE 8
Uint16 adcResults[ADC_RESULTS_SIZE];

// 2. 初始化系统控制,使能外设时钟(略,根据具体工程配置)
// 3. 配置ADC,启动SEQ1(略,假设已完成)

4.2 步骤二:配置DMA通道寄存器

我们使用DMA通道1。 关键原则:在配置前,确保通道已停止(CONTROL.RUNSTS=0)。 最稳妥的做法是先发一个软复位。

void ConfigureDMAForADC(void)
{
    // 为了方便,我们直接操作寄存器结构体。确保已包含F2806x_Dma.h
    volatile struct DMA_REGS *DmaRegs = &DmaRegs;

    // Step 1: 软复位通道1,确保处于已知状态
    DmaRegs->CH1.CONTROL.bit.SOFTRESET = 1;
    // 等待复位完成,可以插入少量空操作或检查状态
    asm(" NOP");
    asm(" NOP");

    // Step 2: 配置模式寄存器 (MODE)
    // 注意:以下所有对MODE及后续EALLOW保护寄存器的写操作,都需要在EALLOW内进行
    EALLOW;
    DmaRegs->CH1.MODE.bit.CHINTE = 1;       // 使能通道中断
    DmaRegs->CH1.MODE.bit.CHINTMODE = 1;    // 传输结束时产生中断
    DmaRegs->CH1.MODE.bit.DATASIZE = 0;     // 16位数据传输(ADC结果是12位,存在16位寄存器中)
    DmaRegs->CH1.MODE.bit.CONTINUOUS = 1;   // 连续模式,传输完成后自动���装,等待下次ADC触发
    DmaRegs->CH1.MODE.bit.ONESHOT = 0;      // 每个突发都需要事件触发(对于ADC,每个SOC触发一个突发)
    DmaRegs->CH1.MODE.bit.PERINTE = 1;      // 使能外设中断触发
    DmaRegs->CH1.MODE.bit.PERINTSEL = 1;    // 选择ADCINT1作为触发源 (值1对应ADCINT1)

    // Step 3: 配置传输尺寸
    // 我们希望每次ADC序列完成(8个转换)触发一次DMA传输,搬运8个数据字。
    // 假设我们设置每个突发搬运1个字,一次传输包含8个突发。
    DmaRegs->CH1.BURST_SIZE.all = 0;        // BURST_SIZE = 0 表示1个字/突发
    DmaRegs->CH1.TRANSFER_SIZE.all = 7;     // TRANSFER_SIZE = N, 表示传输 N+1 个突发。7表示8个突发。

    // Step 4: 配置地址步进
    // 源地址:ADC结果寄存器ADC_RESULT1(假设基址0x0B00)。每个转换结果占一个16位寄存器。
    // 因此,在一个突发内部(BURST_STEP),源地址不变(因为每次触发都从ADC_RESULT1读)。
    // 在一次传输内部的各个突发之间(TRANSFER_STEP),源地址也不变(因为ADC总是更新同一组结果寄存器)。
    DmaRegs->CH1.SRC_BURST_STEP.all = 0;    // 突发内步进0
    DmaRegs->CH1.SRC_TRANSFER_STEP.all = 0; // 传输间步进0

    // 目的地址:内存数组adcResults。数组元素为Uint16,连续存放。
    // 突发内:每搬运一个字,目的地址+1(指向数组下一个元素)。
    // 传输间:8个突发完成后,目的地址应回到数组开头,以便下次传输覆盖/填充。
    // 这里我们利用“环绕”功能来实现自动回到开头。
    DmaRegs->CH1.DST_BURST_STEP.all = 1;    // 突发内,每字后地址+1
    DmaRegs->CH1.DST_TRANSFER_STEP.all = 0; // 传输间,我们靠环绕功能复位地址,这里设为0

    // Step 5: 配置地址环绕(实现循环缓冲区)
    // 我们希望每完成一次传输(8个突发),目的地址指针回到数组起始处。
    // WRAP_SIZE 定义了在多少个突发后触发环绕。我们设为8个突发。
    DmaRegs->CH1.DST_WRAP_SIZE.all = 7;     // WRAP_SIZE = N, 表示 N+1 个突发后环绕。7表示8个突发。
    // WRAP_STEP 定义了环绕发生时,地址指针的调整值。我们需要从数组末尾跳回开头。
    // 经过8个突发,DST_ADDR增加了 8 * DST_BURST_STEP = 8。
    // 要跳回开头,需要减去8。注意步进值是有符号的16位补码。
    DmaRegs->CH1.DST_WRAP_STEP.all = -8;    // 环绕步进 = -8

    // Step 6: 配置起始地址指针 (影子寄存器)
    DmaRegs->CH1.SRC_BEG_ADDR_SHADOW.all = (Uint32)&AdcResult.ADCRESULT1; // ADC结果寄存器起始地址
    DmaRegs->CH1.DST_BEG_ADDR_SHADOW.all = (Uint32)&adcResults[0];        // 内存数组起始地址
    // 当前地址影子寄存器通常也配置为起始地址
    DmaRegs->CH1.SRC_ADDR_SHADOW.all = (Uint32)&AdcResult.ADCRESULT1;
    DmaRegs->CH1.DST_ADDR_SHADOW.all = (Uint32)&adcResults[0];

    EDIS; // 退出受保护寄存器写模式

    // Step 7: 清除可能存在的挂起中断标志,并启用DMA通道中断(在PIE级)
    DmaRegs->CH1.CONTROL.bit.PERINTCLR = 1; // 清除外设中断标志
    DmaRegs->CH1.CONTROL.bit.ERRCLR = 1;    // 清除错误标志

    // 配置PIE,将DMA CH1中断(对应INTx.y,需查数据手册)映射到中断服务函数
    // 假设DMA CH1中断对应 INT1.7 (这是一个例子,具体需查器件手册)
    EALLOW;
    PieVectTable.DMA_CH1_INT = &DmaCh1Isr; // 注册中断服务程序
    EDIS;
    PieCtrlRegs.PIEIER1.bit.INTx7 = 1;      // 使能PIE组1的第7个中断
    IER |= M_INT1;                          // 使能CPU级INT1中断
    EINT;                                   // 全局中断使能

    // Step 8: 启动DMA通道!
    DmaRegs->CH1.CONTROL.bit.RUN = 1;
}

4.3 步骤三:编写中断服务程序

在DMA传输完成中断中,我们处理数据,或者简单地清除标志位。

// DMA通道1中断服务程序
__interrupt void DmaCh1Isr(void)
{
    // 1. 处理数据:此时adcResults数组中已经包含了最新的8个ADC采样值
    ProcessADCResults(adcResults);

    // 2. 清除DMA通道中断标志(根据手册,传输完成中断标志由硬件在响应中断时自动清除?)
    // 实际上,对于DMA通道中断,通常需要手动清除PIE级的中断标志。
    // 首先,确认DMA通道中断源已处理完毕(可选)。
    // 然后,清除PIE应答位。
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // 假设DMA CH1在GROUP1

    // 3. 如果需要,也可以在这里重新配置DMA影子寄存器(例如切换双缓冲区)。
    // 因为CONTINUOUS=1,DMA会自动开始下一轮传输,无需软件干预。
}

5. 调试技巧与常见问题排查

配置完代码,下载运行,结果不对?或者根本没触发?别急,DMA调试需要耐心和策略。

5.1 调试技巧

  1. 状态位是你的朋友 :充分利用 CONTROL 寄存器中的 RUNSTS TRANSFERSTS BURSTSTS PERINTFLG 。在初始化后、预期触发事件前后,读取并打印或观察这些位,可以确认DMA是否已启动、是否在等待事件、事件是否已发生。
  2. 软件强制触发 :在怀疑外设触发信号有问题时,可以使用 PERINTFRC 位。在配置好DMA并启动后,手动写 PERINTFRC=1 ,模拟一个外设事件。如果DMA能正常搬运一次数据,说明DMA配置本身没问题,问题可能出在外设(如ADC、ePWM)的触发配置上。
  3. 检查地址和步进 :数据错位是最常见的问题。使用调试器查看 SRC_ADDR DST_ADDR (活动寄存器)在传输过程中的变化是否符合预期。特别注意 DATASIZE=1 (32位模式)时,步进值是否按 16位字 计算正确(通常是2)。
  4. 利用HALT功能 :在复杂调试时,可以在代码中适时置位 HALT ,冻结DMA状态,然后仔细检查所有地址、计数器和状态寄存器,就像给高速运转的机器按下了暂停键。

5.2 常见问题速查表

问题现象 可能原因 排查步骤
DMA完全不工作,数据不搬运 1. DMA模块时钟未使能。
2. 通道未启动 ( RUN 位未置1)。
3. 外设触发未使能 ( PERINTE=0 ) 或选择错误 ( PERINTSEL )。
4. 外设本身未产生中断事件。
1. 检查系统时钟配置。
2. 检查 CONTROL.RUNSTS 是否为1。
3. 检查 MODE.PERINTE PERINTSEL
4. 检查外设配置,用 PERINTFRC 软件触发测试。
只搬运了一次数据就停止 1. CONTINUOUS 模式未使能 ( MODE.CONTINUOUS=0 )。
2. 外设只产生了一次触发事件。
1. 检查 MODE.CONTINUOUS 位。
2. 检查外设是否配置为连续触发(如ADC连续转换)。
数据搬运到了错误的内存位置 1. 源/目的起始地址 ( BEG_ADDR_SHADOW ) 设置错误。
2. 步进 ( BURST_STEP , TRANSFER_STEP ) 计算错误,特别是32位模式。
3. 环绕 ( WRAP ) 配置错误,导致地址跳转异常。
1. 核对地址值,确保是物理地址。
2. 重新计算步进值,牢记寄存器以16位字为单位。
3. 检查 WRAP_SIZE WRAP_STEP ,确保其与 TRANSFER_SIZE TRANSFER_STEP 的逻辑一致。
CPU收不到DMA完成中断 1. DMA通道中断未使能 ( MODE.CHINTE=0 )。
2. PIE中断未正确配置(向量表、IER、PIEIER)。
3. 中断标志未清除,导致后续中断被屏蔽。
1. 检查 MODE.CHINTE CHINTMODE
2. 检查PIE向量表赋值、组内和CPU级中断使能。
3. 在ISR中正确清除PIE应答位。
系统运行不稳定,偶尔卡死 1. 在DMA运行 ( RUNSTS=1 ) 时修改了不应修改的寄存器(如 MODE , BURST_SIZE 等)。
2. 中断冲突或优先级问题。
3. 内存访问冲突(DMA与CPU访问同一区域)。
1. 严格遵守规则 :修改配置前,先 HALT SOFTRESET 通道。
2. 检查中断优先级,高优先级DMA通道(CH1)是否打断���关键代码段。
3. 考虑使用不同的内存块,或确保CPU在DMA不访问时操作数据。

5.3 一个关于32位传输的深度避坑案例

我曾在一个项目中将DMA配置为32位模式,从外设 FIFO 搬数据到32位宽的存储器。配置如下: DATASIZE=1 SRC_BURST_STEP=0 DST_BURST_STEP=1 TRANSFER_SIZE=100 。预期结果是连续搬运100个32位数。

结果发现内存中的数据是交错的:第一个32位数据正确,第二个数据的高16位是0,低16位是下一个有效数据,如此交替。 问题根源 :我错误地认为 DST_BURST_STEP=1 代表地址+4字节。但实际上,DMA寄存器中的“步进”永远以16位字为单位。在32位模式下,一次访问会取走2个16位字(即4字节)。所以,要让目的地址指向下一个32位位置, DST_BURST_STEP 必须设置为 2

修正后: DST_BURST_STEP = 2 。同时,确保目的起始地址是32位对齐的(地址末位十六进制为0x0, 0x4, 0x8, 0xC)。这个问题手册的Note里提了,但不够醒目,一不留神就会出错。

配置TMS320x2806x的DMA,就像在给一个非常能干但有点“刻板”的助手编写工作清单。清单(寄存器配置)必须绝对清晰、无歧义。这份清单要告诉他:从哪里搬东西(源地址),搬到哪里去(目的地址),一次搬多少(突发大小),搬多少趟(传输大小),搬完一个和搬完一趟后朝哪个方向走几步(步进),以及搬完了要不要喊你一声(中断)。任何一点含糊,都会导致他要么不动,要么乱搬。通过彻底理解每个寄存器的含义,遵循“先停止,后配置”的黄金法则,并善用状态位进行调试,你就能让这个高效的助手完美地承担起数据搬运的重任,从而让你的CPU专注于核心算法,提升整个嵌入式系统的实时性和效率。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐