1. 从CAN回环例程理解DMA基础
我第一次接触C2000的DMA功能时,就是从TI官方提供的CAN回环例程(can_ex4_loopback_dma)入手的。这个例程完美展示了如何用DMA实现CAN数据自动搬运,让CPU从繁重的数据搬运工作中解放出来。当时用的MCU是TMS320F280049,它的DMA控制器有6个独立通道,每个通道都能配置不同的触发源。
在例程中,关键配置代码是这样的:
DMA_configAddresses(DMA_CH5_BASE, rxMsgData, (uint16_t *)(CANA_BASE + CAN_O_IF2DATA)); DMA_configBurst(DMA_CH5_BASE, DMA_BURST, 2, 1); DMA_configTransfer(DMA_CH5_BASE, DMA_TRANSFER, 0, 0);这三行代码分别设置了数据源地址(CAN接口寄存器)、目标地址(内存数组),以及突发传输参数。这里有个新手容易忽略的细节:地址配置时要注意数据宽度对齐。比如CAN接口寄存器地址需要强制转换为uint16_t指针,因为CAN数据是按16位访问的。
2. DMA配置的三重门:地址、突发与传输
2.1 地址配置的玄机
地址配置看似简单,但实际项目中我踩过不少坑。有一次调试时发现DMA死活不搬运数据,最后发现是目标数组定义在了不支持DMA访问的内存区域。C2000的内存映射很关键,比如:
- LSxRAM:全系列支持DMA
- GSxRAM:部分型号需要特殊配置
- 外设寄存器:必须使用外设地址映射
建议在cmd文件中明确定义DMA缓冲区位置,例如:
#pragma DATA_SECTION(dmaBuffer, ".dmaBuffer"); uint16_t dmaBuffer[256];然后在cmd文件中添加:
.dmaBuffer : > LS0RAM, PAGE = 12.2 突发传输的效能密码
突发传输配置直接影响效率。参数DMA_BURST设置每次触发的传输字数,实测发现:
- 小数据包(<8字):设为1-2效率最高
- 大数据流(>32字):建议设为最大值32
但要注意外设限制,比如ADC结果寄存器连续读取时,突发大小不能超过FIFO深度。我曾经在ADC+DMA项目中因为突发设置过大导致数据丢失,后来通过示波器抓取触发信号才发现问题。
2.3 传输次数的隐藏技巧
TRANSFER_SIZE参数决定了单次触发完成的总传输量。这里有个实用技巧:设置为0时表示65536次传输,适合需要持续搬运的场景。在电机控制中,我常用这个特性实现PWM周期自动触发的ADC采样序列。
3. 触发源选择的实战经验
3.1 硬件触发与软件触发
C2000的DMA触发源分两类:
- 硬件触发:外设事件(如ADC完成、CAN接收)
- 软件触发:手动启动(调试时很有用)
配置代码示例:
DMA_configMode(DMA_CH5_BASE, DMA_TRIGGER_CANAIF2, DMA_CFG_ONESHOT_DISABLE | DMA_CFG_CONTINUOUS_DISABLE | DMA_CFG_SIZE_16BIT);这里DMA_TRIGGER_CANAIF2表示使用CAN接口2的接收中断作为触发源。特别注意ONESHOT和CONTINUOUS模式的选择:
- 单次模式:适合非周期任务
- 连续模式:适合流数据传输
3.2 通道分配的潜规则
每个外设触发源对应固定的DMA通道,这个映射关系在芯片手册的"DMA Channel Assignment"表格中有详细说明。比如:
- CANA接收:通道5或6
- ADC序列1:通道1
我曾经犯过一个错误:试图用通道1处理CAN数据,结果当然不工作。后来总结出快速查询技巧:在头文件F28004x_dma.h中有所有触发源的宏定义。
4. 避坑指南:为什么配置正确却不工作
4.1 内存映射检查
DMA对内存区域有严格限制。通过.map文件可以检查关键信息:
- 查找变量地址范围
- 对照内存映射表确认是否支持DMA
例如发现变量被分配到.bss段,而对应的内存区域是C1RAM(某些型号不支持DMA),就需要修改链接脚本。
4.2 通道配置验证
DMA通道与外设的绑定关系必须严格匹配。推荐使用TI提供的通道分配表工具(Excel格式),可以快速查询各外设对应的合法通道号。我在多个项目中都验证过这个方法的可靠性。
4.3 寄存器级调试技巧
当DMA不工作时,建议按这个顺序检查:
- DMA通道使能位:
DMACTRL[EN] - 触发标志:
DMACTRL[TRIGGER] - 传输完成中断标志:
DMAINTFLAG
可以用CCS的寄存器视图实时监控这些状态位。有次发现TRIGGER标志一直置位但无数据传输,最终查出是时钟配置错误导致DMA控制器未正常工作。
5. 高效数据搬运的进阶技巧
5.1 乒乓缓冲实现
在实时信号处理中,我常用双缓冲技术避免数据冲突:
#pragma DATA_SECTION(pingBuffer, ".dmaBuffer"); uint16_t pingBuffer[256]; #pragma DATA_SECTION(pongBuffer, ".dmaBuffer"); uint16_t pongBuffer[256]; void DMA_ISR(void) { if(DMA_getInterruptStatus(DMA_CH5_BASE) == DMA_INT_TC) { // 处理已完成缓冲 processBuffer = (activeBuffer == ping) ? pingBuffer : pongBuffer; // 切换缓冲 DMA_configAddresses(DMA_CH5_BASE, (activeBuffer == ping) ? pongBuffer : pingBuffer, destAddress); activeBuffer = !activeBuffer; } }5.2 数据重排技巧
利用DMA的地址增量模式可以实现数据重组。比如ADC多通道采样时,可以通过设置不同的源/目标地址增量,将交错的通道数据分离到不同数组。具体参数在DMA_configTransfer()函数中设置。
5.3 性能优化实测数据
在我的280049测试平台上,不同配置的DMA吞吐量对比如下:
| 配置模式 | 传输速率(MB/s) | CPU占用率 |
|---|---|---|
| 单字传输(16bit) | 2.1 | 15% |
| 突发32字(16bit) | 8.7 | <1% |
| 突发32字(32bit) | 17.4 | <1% |
可以看出,合理配置突发大小和数据宽度能显著提升性能。不过要注意,32位模式需要数据源和目标都支持32位对齐。
调试DMA就像在跟硬件玩捉迷藏,有时候明明所有配置都检查过了,数据就是不动。这时候最有效的方法就是拿出芯片手册,对照寄存器映射一个个bit核对。记得有次熬夜调试,最后发现是DMA时钟门控没打开,这个教训让我养成了检查时钟树的习惯。