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,然后让它自动链接到下一个,直到全部完成。

链式传输的配置要点:

  1. 每个PaRAM Set的LINK字段指向下一个PaRAM Set的地址
  2. 最后一个PaRAM Set的LINK字段要指向一个“空”传输或者循环回第一个
  3. 链的长度没有限制,但要注意PaRAM的总数

我的小技巧:调试链式传输时,先只配2-3个链节,跑通了再扩展。否则一旦链断了,你都不知道问题出在哪。

自动加载——DMA的“自我修养”

自动加载和链式传输是孪生兄弟。链式传输说的是“传完一个接下一个”,自动加载说的是“下一个的配置从哪里来”。

EDMA3的自动加载机制是这样的:

  • 每个通道完成传输后,会自动从LINK指定的地址加载新的PaRAM
  • 加载过程是硬件完成的,不需要CPU参与
  • 加载完成后,通道自动重新启动

嗯,这里有个坑:自动加载时,原来的PaRAM内容会被覆盖。如果你需要保留原始配置,记得在别处备份一份。我曾经因为这个丢过配置,查了半天才发现是自动加载把参数冲掉了。

知识体系总览

下面这张图是我自己画的,把EDMA3的核心逻辑串起来了。你看一遍应该就能明白整体流程。

EDMA3 核心知识体系 通道控制器 参数RAM (PaRAM) 传输控制器 调度 配置 传输同步事件:硬件事件 | 软件事件 | 链式事件 PaRAM Set 内部结构 SRC DST ACNT BCNT CCNT BIDX LINK 链式传输流程:PaRAM Set 1 → 传输完成 → 自动加载 PaRAM Set 2 → 传输完成 → ... 图: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这块你就拿下了。


专注资料整理