DMA控制器原理:EDMA3的通道、参数RAM、链式传输与自动加载
各位同学,今天我们来聊聊EDMA3。说实话,DMA这东西在DSP系统里就是个“隐形搬运工”。没有它,CPU得累死。我最早接触EDMA3是在一个视频处理项目里,那时候数据量一大,CPU直接被中断淹没了。后来换成EDMA3,整个世界清净了。
EDMA3的核心架构
EDMA3,全称是Enhanced Direct Memory Access 3。它比上一代强在哪?说白了,就是通道更多、传输更灵活、还能自己“链”起来干活。
它的核心部件有三个:
- 通道控制器:负责调度和仲裁。谁先传、谁后传,它说了算。
- 传输控制器:真正干活的。它负责读写数据总线。
- 参数RAM(PaRAM):存放传输配置的地方。每个通道都有自己的“小本本”。
嗯,这里要注意:EDMA3的通道数通常是64个,但具体芯片可能不同。我建议你拿到芯片手册后,先翻到EDMA3章节,数一数通道编号范围。
参数RAM(PaRAM)——传输的“剧本”
PaRAM是EDMA3的灵魂。每个通道对应一个PaRAM Set,里面记录了传输的所有细节。你想想看,没有这个,DMA怎么知道从哪里搬到哪里?
一个PaRAM Set包含以下关键字段:
| 字段 | 作用 | 我踩过的坑 |
|---|---|---|
| SRC | 源地址 | 记得对齐!我曾经没对齐,结果传出来的数据全是乱的 |
| DST | 目的地址 | 同上,对齐很重要 |
| ACNT | 第一维数据个数(字节) | 别搞混了,这是字节数,不是字长 |
| BCNT | 第二维数据帧数 | 配合ACNT使用,形成二维传输 |
| CCNT | 第三维数据帧数 | 三维传输用得少,但视频处理时很香 |
| SRC_DST_BIDX | 源/目的地址索引 | 决定了每次传输后地址跳多少 |
| LINK | 链接地址 | 链式传输的关键! |
重要提醒:PaRAM的配置必须在DMA启动前完成。一旦启动,修改PaRAM是无效的,除非你重新加载。
传输同步事件——谁叫醒DMA?
EDMA3不是自己瞎跑的。它需要“事件”来触发。这些事件可以是:
- 硬件事件:比如外设的接收中断、定时器溢出。我在项目中常用McBSP的接收事件来触发DMA,这样CPU完全不用管数据接收。
- 软件事件:你手动写寄存器触发。调试时特别好用。
- 链式事件:一个传输完成后,自动触发下一个。这个后面细说。
为什么会需要这么多事件类型?你想想看,如果所有传输都用CPU轮询,那还要DMA干嘛?事件机制让DMA能“按需工作”,省电又高效。
链式传输——让DMA自己“接活”
链式传输是EDMA3最酷的功能之一。简单说,就是一个传输完成后,自动加载下一个PaRAM Set,然后继续传。
我曾经在一个数据采集系统里用过这个。需要连续采集1024个点,每个点触发一次DMA。如果用单次传输模式,CPU得配置1024次DMA,累死。用链式传输,我只需要配置好第一个PaRAM,然后让它自动链接到下一个,直到全部完成。
链式传输的配置要点:
- 每个PaRAM Set的LINK字段指向下一个PaRAM Set的地址
- 最后一个PaRAM Set的LINK字段要指向一个“空”传输或者循环回第一个
- 链的长度没有限制,但要注意PaRAM的总数
我的小技巧:调试链式传输时,先只配2-3个链节,跑通了再扩展。否则一旦链断了,你都不知道问题出在哪。
自动加载——DMA的“自我修养”
自动加载和链式传输是孪生兄弟。链式传输说的是“传完一个接下一个”,自动加载说的是“下一个的配置从哪里来”。
EDMA3的自动加载机制是这样的:
- 每个通道完成传输后,会自动从LINK指定的地址加载新的PaRAM
- 加载过程是硬件完成的,不需要CPU参与
- 加载完成后,通道自动重新启动
嗯,这里有个坑:自动加载时,原来的PaRAM内容会被覆盖。如果你需要保留原始配置,记得在别处备份一份。我曾经因为这个丢过配置,查了半天才发现是自动加载把参数冲掉了。
知识体系总览
下面这张图是我自己画的,把EDMA3的核心逻辑串起来了。你看一遍应该就能明白整体流程。
实际项目中的配置示例
最后,我贴一段实际项目中用过的配置代码。这是初始化一个EDMA3通道,用于从ADC搬数据到内存。
// EDMA3 通道配置示例
// 从ADC数据寄存器搬移到内存缓冲区
void EDMA3_ConfigChannel(void)
{
// 1. 选择通道(这里用通道0)
EDMA3_Channel ch = EDMA3_CHANNEL_0;
// 2. 配置PaRAM Set
PaRAM_Set param;
param.SRC = (uint32_t)&ADC_DATA_REG; // 源:ADC数据寄存器
param.DST = (uint32_t)g_adcBuffer; // 目的:内存缓冲区
param.ACNT = 2; // 每次传2字节(16位ADC)
param.BCNT = 128; // 传128次
param.CCNT = 1; // 单次循环
param.SRC_BIDX = 0; // 源地址不跳(固定寄存器)
param.DST_BIDX = 2; // 目的地址每次+2字节
param.LINK = 0; // 不链式传输
// 3. 写入PaRAM
EDMA3_setPaRAM(ch, ¶m);
// 4. 使能事件
EDMA3_enableEvent(ch);
// 5. 启动传输(等待硬件事件触发)
// 或者用软件触发:EDMA3_triggerEvent(ch);
}
注意:上面的代码是简化版。实际项目中,你还需要配置中断、处理错误状态等。千万别忘了检查EDMA3的传输完成标志,否则数据可能还没到位你就去读了。
好了,EDMA3的核心内容就这些。记住三个关键词:PaRAM是剧本,事件是闹钟,链式传输是自动流水线。把这三点吃透,DMA这块你就拿下了。