IC:

概述

全局直接内存访问(GDMA)控制器,也称 DMAC,通过 AXI 总线在内存与外设之间传输数据,无需 CPU 介入,从而减轻 CPU 的负担。GDMA 支持:

  • 外设到内存、外设到外设、内存到外设以及内存到内存的传输,每个 DMA 通道为单个源和目的外设(内存)提供单向传输。

  • 基于硬件的优先级仲裁和可编程优先级管理并发的 DMA 传输请求。

  • 提供一个 APB 从接口用于配置寄存器

其架构示意图如下:

../../../rst_peripherals/rtos/8_dmac/figures/dmac_arch.svg

GDMA 具体支持的特性如下:

  • 支持多达 8 个独立通道,且优先级可编程

  • 每个通道均配备 FIFO

  • 支持可编程流控(可选择由源、目的 IP 或 DMAC 控制)

  • 每个通道的源和目的地均可编程

  • 传输类型:支持单次和突发(burst)传输模式

  • DMA 传输:支持单块和多块传输

  • 安全模式:支持安全传输模式

  • 节能功能:支持 DMAC 低功耗模式(内部时钟门控)

  • 支持在无数据丢失的情况下禁用通道(特定情况)

  • 支持暂停和恢复

安全机制

GDMA 支持 Secure 传输,开启该功能后,主从接口的访问均为安全访问。

  • 安全通道仅可在安全域中配置,且安全通道可访问安全内存和非安全内存。

  • 非安全通道仅能访问非安全内存。

要启用安全传输,用户需要开启 Trustzone 功能,然后配置 GDMA 通道的的安全传输控制位:

PGDMA_InitTypeDef->SecureTransfer = 1;

GDMA 配置

DMA 块大小的框图如下所示:

../../../rst_peripherals/rtos/8_dmac/figures/dmac_block_size_diagram.svg

数据大小

上图阐述了 GDMA 传输数据大小的设置。block_ts 表示将在单个数据块中传输的数据量,需要设置为 总数据量/SRC_TR_WIDTH,最大值是 {{IC_PARAM_GDMA_BLOCK_SIZE}}

通道分配和释放

DMA 通过以下两个 API 实现通道的申请和释放:

  • GDMA_ChnlAlloc() :按顺序从通道 0 开始分配

  • GDMA_ChnlFree() :按照指定的通道编号释放通道

通道申请过程中,可能出现两个 CPU 同时申请一个通道,这会导致程序运行异常。为解决该问题,在通道申请过程中,会使用硬件 SEMA 来 保护。

传输方向和流控制器

目前共有四种传输方向和两种流控制器设置,共八种可用配置。

  • 当外设作为流控制器时,DMA 将根据外设发出的单次/突发请求进行数据传输。

  • 当 DMAC 作为流控制器时,所有来自外设的请求都将按照配置的请求进行处理。

CTLx寄存器的TT_FC[2:0]字段(x为通道编号)

方向

流控控制器

000

内存 到 内存

DMAC

001

内存 到 外设

DMAC

010

外设 到 内存

DMAC

011

外设 到 外设

DMAC

100

外设 到 内存

外设

101

外设 到 外设

源外设

110

内存 到 外设

外设

111

外设 到 外设

目的外设

流控制器配置原则:

  • 如果 block_ts 已知,使用 DMAC 作为流控制器。例如如播放音乐、图片等,以及搬运内存数据。

  • 如果 block_ts 未知,使用外设作为流控制器。例如 UART 接受不定长数据,可以让 UART 做流控制器,每当有数据来临再请求传输。

警告

  • 仅当 DMAC 用作流控制器时,才能设置 block_ts 参数。

  • 使用外设作为流控制器,需要确保 IP 在硬件设计上支持触发 DMA 请求。

握手

GDMA 仅支持硬件握手,不支持软件握手。当需要和外设(非 Memory)之间传输数据时,才需要配置 Handshake Interface。当前 IC 支持的硬件握手接口如下表所示:

GDMA 握手接口

功能

握手编号

说明

UART0 TX

0

UART0 RX

1

UART1 TX

2

UART1 RX

3

UART2 TX

4

UART2 RX

5

SPI0 TX

6

SPI0 RX

7

SPI1 TX

8

SPI1 RX

9

SPIC TX

10

SPIC RX

11

SPORT0 TX

12

两个FIFO,占用编号12和13

SPORT0 RX

14

两个FIFO,占用编号14和15

SPORT1 TX

16

两个FIFO,占用编号16和17

SPORT1 RX

18

两个FIFO,占用编号18和19

LEDC_TX

20

I2C0 TX

21

I2C0 RX

22

I2C1 TX

23

I2C1 RX

24

传输 Msize 和 Width

每次传输的长度可配置:

  • msize > 1:突发传输

  • msize = 1:单次传输

CTLx 寄存器的 SRC_MSIZE[2:0]/DEST_MSIZE[2:0] 字段

传输 msize

000

1

001

4

010

8

011

16

100 及以上

不支持

GDMA 支持以下传输宽度:

CTLx 寄存器的 SRC_TR_WIDTH[2:0]/DST_TR_WIDTH[2:0] 字段

传输宽度(字节)

000

1

001

2

010

4

011 及以上

不支持

备注

  • 读写外设时: SRC_TR_WIDTH/DST_TR_WIDTH 根据外设的数据宽度决定。

  • 读写内存时:

    • 如果缓存被禁用,内存地址无需对齐,但需确保总数据量能被 SRC_TR_WIDTH 整除,以保证 block_ts 为整数。

    • 如果缓存被启用,内存地址必须满足缓冲区边界对齐,且需按缓存行对齐。

  • 当源或者目的是内存时(如 P2M、M2M 模式): 内存对应的 DST_TR_WIDTH`或者 `SRC_TR_WIDTH 参数将被忽略,实际写入/读取操作始终基于总线宽度(默认为 32 位,即 4 字节)。

  • 为保证 FIFO 不会发生 undef flow 或者 overflow, SRC_MSIZE * SRC_TR_WIDTHDST_MSIZE * DST_TR_WIDTH 需要保持相等。

传输类型

GDMA 支持以下传输类型:

  • Single Block: 仅包含一个数据块

  • Multi-Block:包含多个数据块

    • 自动重加载(Auto-reloading)模式

    • 链表(Linked-list)模式

各个模式使用场景及特点如下:

GDMA 各模式特点

模式

子模式

使用场景

特点

Single Block

地址空间连续且仅传输一次

  • 传输完成后dma立即停止

Multi-Block

auto-reload

地址空间连续且源端或者目的端需要重复加载某一个数据块

  • 若开启块中断,每个块传输完成都会暂停,直到中断处理结束

link-list

地址空间非连续

  • 若开启块中断,每个块传输完成后触发中断,但将立即传输下一个块

Auto-reloading 模式

在 auto-reloading 模式下,源和目标可分别选择使用的方法。

Auto-reloading 传输类型

设置

说明

Src auto reload

PGDMA_InitTypeDef->GDMA_ReloadSrc = 1

PGDMA_InitTypeDef->GDMA_ReloadDst = 0

对多块传输(multi-block transfers),每块结束时SAR寄存器会从初始值自动重载,目标地址连续,如

Multi-Block 传输-源地址自动重载&目标地址连续 所示。

Dst auto reload

PGDMA_InitTypeDef->GDMA_ReloadSrc = 0

PGDMA_InitTypeDef->GDMA_ReloadDst = 1

对多块传输,每块结束时DAR寄存器会从初始值自动重载,源地址连续。

Src & Dst auto reload

PGDMA_InitTypeDef->GDMA_ReloadSrc = 1

PGDMA_InitTypeDef->GDMA_ReloadDst = 1

对多块传输,每块结束时SAR和DAR寄存器都会从初始值自动重载,如 Multi-Block 传输-源和目标地址均自动重载 所示。

../../../rst_peripherals/rtos/8_dmac/figures/mbd_source_auto_dest_cont.png

Multi-Block 传输-源地址自动重载&目标地址连续

../../../rst_peripherals/rtos/8_dmac/figures/mbd_source_dest_auto.png

Multi-Block 传输-源和目标地址均自动重载

Linked list 模式

在 linked list(链表)模式下,数据块之间的地址无需连续。

Link list 传输类型

设置

说明

Src: Continue address

Dst: Link list

PGDMA_InitTypeDef->GDMA_SrcAddr = pSrc

PGDMA_InitTypeDef->GDMA_LlpDstEn = 1

源内存为连续数据块,目标数据块以链表方式组织。

Src: Auto-reloading

Dst: Link list

PGDMA_InitTypeDef->GDMA_ReloadSrc = 1

PGDMA_InitTypeDef->GDMA_SrcAddr = pSrc

PGDMA_InitTypeDef->GDMA_LlpDstEn = 1

源端,SAR寄存器可在每块结束时由初始值自动重载,目标数据块为链表结构,见 Multi-block 传输-源地址自动重载&目标地址为链表结构

Src: Link list

Dst: Continue address

PGDMA_InitTypeDef->GDMA_LlpSrcEn = 1

PGDMA_InitTypeDef->GDMA_DstAddr = pDst

源数据块以链表结构组织,目标内存为连续数据块,见 Multi-block 传输-源地址为链表结构&目标地址连续

Src: Link list

Dst: Auto-reloading

PGDMA_InitTypeDef->GDMA_LlpSrcEn = 1

PGDMA_InitTypeDef->GDMA_DstAddr = pDst

PGDMA_InitTypeDef->GDMA_ReloadDst = 1

源数据块为链表结构,目标数据块为自动重载。

Src: Link list

Dst: Link list

PGDMA_InitTypeDef->GDMA_LlpSrcEn = 1

PGDMA_InitTypeDef->GDMA_LlpDstEn = 1

源和目标数据块均以链表结构组织,见 Multi-block 传输-源和目标地址均为链表结构

如果目标和源都是连续的数据块,不建议使用多块传输,应采用单块传输模式。

地址递增类型

源地址递增

包含两种模式:

  • 递增(Increment):表示在每次源端传输时,源地址是否递增。递增操作用于对齐到下一个 CTLx.SRC_TR_WIDTH 边界。

  • 不变(No change):如果设备从具有固定地址的源外设 FIFO 读取数据,则应将该字段设置为“不变”。

目标地址递增

包含两种模式:

  • 递增(Increment):表示在每次目的端传输时,目的地址是否递增。递增操作用于对齐到下一个 CTLx.DST_TR_WIDTH 边界。

  • 不变(No change):如果设备将数据写入到具有固定地址的目的外设 FIFO,则应将该字段设置为“不变”。

配置原则:

  • 如果源端或者目的端为 Memory,地址模式一般设置为 Increment

  • 如果源端或者目的端为外设,地址模式一般设置为 No Change

FIFO

GDMA 的每个通道都有自己独立的 FIFO,各个通道的 FIFO 大小并不相同。

FIFO 大小

通道编号

CH0

CH1

CH2~CH8

FIFO size/Bytes

128

128

32

实时状态获取

GDMA 支持实时获取当前传输的源地址、目标地址和已传输数据量。

需调用对应 API 读取。

备注

若要获取已传输数据量,block_ts 必须至少大于 768,且不可在中断函数中读取;否则,获取的值始终为 0。

中断类型

DMAC 支持多种中断类型,这些中断可独立使用或组合使用。

中断类型

说明

块中断

单个数据块传输完成触发

传输中断

所有数据块传输完毕时产生

错误中断

传输过程中发生错误

备注

  • 多块传输时,若 auto-reload 模式中的块传输被中断,数据将在中断处理函数完成后继续传输。

  • inked list 模式的传输完成条件为:最后一个数据块的下一数据块指针为空。

  • linked list 模式下,即使产生块中断,数据传输仍会继续进行。

挂起和终止

GDMA 支持通道的挂起恢复和强制终止。

  • 挂起通道:直接配置 CFGx.CH_SUSP,但不保证当前数据传输已完成。需结合 CFGx.INACTIVE 状态检测,方可安全暂停且不丢失数据。

  • 恢复传输:清除 CFGx.CH_SUSP 即可继续进行数据传输。

  • 终止传输:需持续轮询 CFGx.INACTIVE 直至该位设为 1,方可终止传输。

备注

通道进入非活跃状态的场景:

  • CFGx.INACTIVE 仅可在内存写入操作完成后被激活,且需手动取消。

  • 外设数据为 4 字节,但 DMAC FIFO 仅 2 字节。若当前无写入操作,则直接激活 CFGx.INACTIVE

聚集和分散

聚集

聚集传输是将一片内存区域中等间隔的多段数据拷贝到一段连续内存中。示例如下:

  • SRC_TR_WIDTH 为 4Bytes

  • Source Gather Interval(SGI) 为 1

  • Source Gather Count(SGC) 为 4

即源端每传输 16Bytes 的数据,然后跳过 4Bytes 的地址区间,最终数据将连续存放到目的端。

../../../rst_peripherals/rtos/8_dmac/figures/dmac_source_gather.svg

分散

分散传输:将一片连续内存数据搬运到一片不连续(等间隔)的的内存空间。示例如下:

  • DST_TR_WIDTH 为 4Bytes

  • Destination Scatter Interval(DSI) 为 16

  • Destination Scatter Count(DSC) 为 4

即源端连续发送数据,目的端每接受 16Bytes 的数据,然后跳过 64Bytes.

../../../rst_peripherals/rtos/8_dmac/figures/dmac_destination_scatter.svg

警告

  • 当使用 Source Gather 功能,将源内存 memory 收集到目的内存,则 block_ts 要和有效数据量保持一致,且必须和 SRC_TR_WIDTH 对齐。

  • 当使用 Destination Scatter 功能,将源内存数据分散到目的内存,则 block_ts 要和源地址空间大小保持一致,且必须和 DST_TR_WIDTH 对齐。

优先级

DMAC 支持两种通道优先级:

  • 软件:各通道优先级可通过 CFGx.CH_PRIOR 配置,有效值范围为 0 ~ (DMAC_NUM_CHANNELS-1)。其中, DMAC_NUM_CHANNELS 为最低优先级。

  • 硬件:若两个通道请求的软件优先级相同,或未配置软件优先级,通道编号较小的通道优先级更高(例如:通道 2 优先级高于通道 4)。

DMA 和缓存

当使用 DMA 在内存之间或者内存和外设之间传输数据,如果此时 Cache 也正常启用,需要注意 Cache 和内存数据不一致的问题。

DMA TX

当 DMA 的源端是 Memory,需要发送数据时,一般流程如下:

  1. 申请发送缓冲区,需要确保起始地址和大小都与 Cache Line 对齐。

  2. CPU 将数据写入 Memory 缓冲区。

  3. 调用 Dcache_Clean() 函数清理数据缓存

  4. 配置 DMA 发送参数

  5. DMA 启动传送

DMA RX

  1. CPU 分配接收缓冲区

  2. 执行 DCache_Clean() 确保接收缓冲区处于 clean 状态(如果接收缓冲区处于 clean 状态,可跳过此步骤)

    小心

    此步骤执行的原因是:

    • 对于 Cortex-A32,如果 Cache 中的接收缓冲区处于 dirty 状态,执行第 5 步 DCache_Invalidate() 将同时执行 cleaninvalidate 操作,可能会导致意外写入行为。

    • 如果 Cache 中的接收缓冲区处于 dirty 状态,当 CPU 的 D-Cache 满了,CPU 可能会将接收缓冲区中的脏数据写回内存,覆盖 DMA 已经写入的内容。

  3. 配置 DMA Rx 参数

  4. DMA Rx 中断处理

  5. 执行 DCache_Invalidate() 将 Cache 数据写回内存,确保 Cache 没有残留旧的接收缓冲区数据。

小心

此步骤必须执行,原因如下:

  • 对于具有自动数据预取功能的 CPU(如 Cortex-A32 和 DSP),当 CPU 读取接收缓冲区相邻地址的内容时,CPU 会在后台执行行填充操作,自动将接收缓冲区的旧值重新加载到 Cache 中。

  • 防止 CPU 在 DMA 处理期间将旧值读入 Cache。

  1. CPU 读取接收缓冲区(DMA Rx 返回的值)

备注

将缓冲区地址与 Cache Line 行对齐将减少 Cache 与内存数据不一致的问题。

DMAC 示例

单块

  1. 分配一个空闲通道

    ch_num = GDMA_ChnlAlloc(gdma.index, (IRQ_FUN) Dma_memcpy_int, (u32)(&gdma), 3);
    

    这个函数同时包含以下操作:

    • 注册 IRQ 处理程序(如果使用中断模式)

    • 启用 NVIC 中断

    • 注册要使用的 GDMA 通道

  2. 配置中断类型

    PGDMA_InitTypeDef->GDMA_IsrType = (TransferType | ErrType);
    
  3. 配置中断处理函数

    在中断处理函数中清除挂起的中断。

    GDMA_ClearINT(0, PGDMA_InitTypeDef->GDMA_ChNum);
    
  4. 传输配置

    PGDMA_InitTypeDef->GDMA_SrcMsize   = MsizeEight;
    PGDMA_InitTypeDef->GDMA_SrcDataWidth = TrWidthFourBytes;
    PGDMA_InitTypeDef->GDMA_DstMsize = MsizeEight;
    PGDMA_InitTypeDef->GDMA_DstDataWidth = TrWidthFourBytes;
    PGDMA_InitTypeDef->GDMA_BlockSize = DMA_CPY_LEN >> 2;
    PGDMA_InitTypeDef->GDMA_DstInc = IncType; // if dst type is peripheral:no change
    PGDMA_InitTypeDef->GDMA_SrcInc = IncType; // if src type is peripheral:no change
    
  5. 配置硬件握手接口(如果从机是外设)

    GDMA_InitStruct->GDMA_SrcHandshakeInterface= GDMA_HANDSHAKE_INTERFACE_AUDIO_RX;
    

    GDMA_InitStruct->GDMA_DstHandshakeInterface = GDMA_HANDSHAKE_INTERFACE_AUDIO_TX;
    
  6. 配置传输地址

    PGDMA_InitTypeDef->GDMA_SrcAddr = (u32)BDSrcTest;
    PGDMA_InitTypeDef->GDMA_DstAddr = (u32)BDDstTest;
    
  7. 使用 GDMA_Init() 函数设置 GDMA 索引、GDMA 通道、数据宽度、msize、传输方向、地址递增模式、硬件握手接口、重载控制、中断类型、块大小、多块配置,以及源地址和目标地址

    GDMA_Init(gdma.index, gdma.ch_num, PGDMA_InitTypeDef);
    
  8. 清理缓存

    DCache_CleanInvalidate();
    
  9. 启用 GDMA 通道

GDMA_Cmd(gdma.index, gdma.ch_num, ENABLE);

多块

这是 SRC 自动重载模式的示例。与单块相比,多块的 步骤 2 ~ 步骤 4 不同。

  1. 分配一个空闲通道

    ch_num = GDMA_ChnlAlloc(gdma.index, (IRQ_FUN) Dma_memcpy_int, (u32)(&gdma), 3);
    

    这个函数同时包含以下操作:

    • 注册 IRQ 处理程序(如果使用中断模式)

    • 启用 NVIC 中断

    • 注册要使用的 GDMA 通道

  2. 配置中断类型

    PGDMA_InitTypeDef->GDMA_IsrType = (BlockType | TransferType | ErrType);
    
  3. 配置中断处理函数

    1. 清除中断

      GDMA_ClearINT(0, GDMA_InitStruct->GDMA_ChNum);
      
    2. 在最后一个数据块开始之前清除自动重载模式

      GDMA_ChCleanAutoReload(0, GDMA_InitStruct->GDMA_ChNum, CLEAN_RELOAD_SRC);
      
  4. 传输配置

    PGDMA_InitTypeDef->GDMA_SrcMsize   = MsizeEight;
    PGDMA_InitTypeDef->GDMA_SrcDataWidth = TrWidthFourBytes;
    PGDMA_InitTypeDef->GDMA_DstMsize = MsizeEight;
    PGDMA_InitTypeDef->GDMA_DstDataWidth = TrWidthFourBytes;
    PGDMA_InitTypeDef->GDMA_BlockSize = DMA_CPY_LEN >> 2;
    PGDMA_InitTypeDef->GDMA_DstInc = IncType; // If DST type is peripheral: no change
    PGDMA_InitTypeDef->GDMA_SrcInc = IncType; // If SRC type is peripheral: no change
    PGDMA_InitTypeDef->GDMA_ReloadSrc = 1;
    PGDMA_InitTypeDef->GDMA_ReloadDst = 0;
    
  5. 配置硬件握手接口(如果从机是外设)

    GDMA_InitStruct->GDMA_SrcHandshakeInterface= GDMA_HANDSHAKE_INTERFACE_AUDIO_RX;
    

    GDMA_InitStruct->GDMA_DstHandshakeInterface = GDMA_HANDSHAKE_INTERFACE_AUDIO_TX;
    
  6. 配置传输地址

    PGDMA_InitTypeDef->GDMA_SrcAddr = (u32)BDSrcTest;
    PGDMA_InitTypeDef->GDMA_DstAddr = (u32)BDDstTest;
    
  7. 使用 GDMA_Init() 函数设置 GDMA 索引、GDMA 通道、数据宽度、msize、传输方向、地址递增模式、硬件握手接口、重载控制、中断类型、块大小、多块配置,以及源地址和目标地址

    GDMA_Init(gdma.index, gdma.ch_num, PGDMA_InitTypeDef);
    
  8. 清理缓存

    DCache_CleanInvalidate();
    
  9. 启用 GDMA 通道

    GDMA_Cmd(gdma.index, gdma.ch_num, ENABLE);