TMS320F28069 DMA寄存器配置实战:从手册到代码的嵌入式数据传输指南
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开始,可以清晰地分为三大类:
-
全局控制与状态寄存器 :这些寄存器作用于整个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服务完成后恢复。这是实现高优先级通道“插队”机制的关键状态记录。
-
通道级寄存器组 :这是配置的重头戏,每个通道都有一套完全相同的寄存器组,从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数据寄存器地址固定)。
- 典型场景1:连续存储 。从ADC固定地址读数据到连续内存。设
- 环绕寄存器 :用于实现循环缓冲区或处理二维数据。例如,你需要将来自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 调试技巧
- 状态位是你的朋友 :充分利用
CONTROL寄存器中的RUNSTS、TRANSFERSTS、BURSTSTS和PERINTFLG。在初始化后、预期触发事件前后,读取并打印或观察这些位,可以确认DMA是否已启动、是否在等待事件、事件是否已发生。 - 软件强制触发 :在怀疑外设触发信号有问题时,可以使用
PERINTFRC位。在配置好DMA并启动后,手动写PERINTFRC=1,模拟一个外设事件。如果DMA能正常搬运一次数据,说明DMA配置本身没问题,问题可能出在外设(如ADC、ePWM)的触发配置上。 - 检查地址和步进 :数据错位是最常见的问题。使用调试器查看
SRC_ADDR和DST_ADDR(活动寄存器)在传输过程中的变化是否符合预期。特别注意DATASIZE=1(32位模式)时,步进值是否按 16位字 计算正确(通常是2)。 - 利用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专注于核心算法,提升整个嵌入式系统的实时性和效率。
更多推荐
所有评论(0)