实验平台

硬件:银杏科技GT7000双核心开发板-ARM-STM32H743XIH6,银杏科技iToolXE仿真器
软件:最新版本STM32CubeH7固件库STM32CubeMX v6.10.0,开发板环境MDK v5.35
网盘资料包:链接: https://pan.baidu.com/s/1Y3nwaY4SMxfoUsdqPm2R3w?pwd=inba 提取码: inba

DMA

DMA介绍

  直接存储器访问(DMA),全称Direct Memory Access,用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何CPU操作的情况下通过DMA快速移动数据。这样节省的CPU资源可供其它操作使用。
  DMA控制器基于复杂的总线矩阵架构,将功能强大的双AHB主总线架构与独立的FIFO结合在一起,优化了系统带宽。两个DMA控制器总共有16个数据流(每个控制器 8 个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理DMA请求间的优先级。

在这里插入图片描述
  上图为STM32H7的内部架构,STM32H7采用电源域分块架构,分为三个独立电源域(D1,D2和D3),每个域都拥有各自的DMA控制器,D1域的DMA控制器, 称为MDMA, D2域的DMA控制器属于通用DMA, D3的DMA控制器,称为BDMA。

  1. MDMA用于实现:内存内存、内存外设、外设内存之间的高速数据传输,MDMA支持AXI和AHBS总线之间的数据传输。注意:仅MDMA支持AHBS的访问(可访问DTCM和ITCM),其他DMA都无法访问AHBS总线。
  2. 双口DMA同样可以实现:内存内存、内存外设、外设内存之间的高速数据传输,双口DMA支持AHB外设之间的数据传输,具有独立的FIFO,支持不同位宽的数据传输。
  3. BDMA用于实现:内存内存、内存外设、外设内存、外设到外设之间的高速数据传输。

  DMA 允许不同速度的硬件装置来沟通,而不需要依于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把他们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
  DMA 传输主要地将一个内存区从一个装置复制到另外一个。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存去。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。所以,DMA传输对于高效能嵌入式系统算法和网络是很重要的。

DMA主要特性

(1) 双AHB主总线架构,一个用于存储器访问,另一个用于外设访问
(2) 仅支持32位访问的AHB从编程接口
(3) 每个DMA控制器有8个数据流,每个数据流有多达115个通道(或称请求)
(4) 每个数据流有四个字深的32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式:

  • FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2或3/4
  • 直接模式:每个DMA请求会立即启动对存储器的传输。当在直接模式(禁止FIFO)下将DMA请求配置为以存储器到外设模式传输数据时,DMA仅会将一个数据从存储器预加载到内部FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。

(5) 通过硬件可以将每个数据流配置为:

  • 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
  • 在存储器端支持双缓冲的双缓冲区通道

(6) 8个数据流中的每一个都连接到专用硬件DMA通道(请求)
(7)DMA数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求0的优先级高于请求1)
(8) 每个数据流还支持通过软件触发的存储器间的传输
(9) 可通过DMAMux1在多达115个可能的通道请求中选择每个数据流请求。此选择可由软件配置,允许多个外设发起DMA请求
(10) 要传输的数据项的数目可以由DMA控制器或外设管理:

  • DMA流控制器:要传输的数据项的数目可用软件编程,从1至65535
  • 外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过
    硬件发出传输结束的信号

(11)独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA
自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用
(12) 对源和目标的增量或非增量寻址
(13) 支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半

DMA框图

在这里插入图片描述

1.编程端口

  AHB从器件编程端口是连接至AHB2外设的。AHB2外设在使用DMA传输时需要相关控制信号。

2/4.外设端口、存储器端口

  DMA控制器实现双AHB主接口,更好利用总线矩阵和并行传输。DMA控制器通过存储器端口和外设端口与存储器和外设进行数据传输,DMA控制器的功能是快速转移内存数据,需要一个连接至源数据地址的端口和一个连接至目标地址的端口。
  DMA2(DMA控制器2)的存储器端口和外设端口都是连接到AHB总线矩阵,可以使用AHB总线矩阵功能。DMA2存储器和外设端口可以访问相关的内存地址, 包括有内部Flash、内部SRAM、AHB1外设、AHB2外设、APB2外设和外部存储器空间。

3.FIFO

  DMA控制器中的 ‌FIFO(First In First Out)缓冲区‌ 是用于优化数据传输的关键组件,尤其在 ‌STM32系列‌(如F4/H7)中广泛使用。FIFO通过缓存数据减少总线访问冲突,提升传输效率。
  DMA传输具有FIFO模式和直接模式。

模式 特点 使用场景
直接模式‌ 无缓冲,实时性高 小数据量即时传输
‌FIFO模式 缓冲数据,支持突发传输 大数据块或速度不匹配

  外设或内存数据写入FIFO缓冲区,当FIFO数据量达到阈值(可以通过DMA数据流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位来控制FIFO的阈值), 分别为1/4、1/2、3/4和满时,DMA控制器启动总线传输。按配置的突发长度(如4字节)批量写入目标地址。

5.仲裁器

  一个DMA控制器对应8个数据流,数据流包含要传输数据的源地址、目标地址、数据等等信息。如果我们需要同时使用同一个DMA控制器(DMA1或DMA2)多个外设请求时, 那必然需要同时使用多个数据流,用于仲裁数据流0~7的请求优先级,保证数据有序传输。
  仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请
求管理,并启动外设/存储器访问序列。
优先级管理分为两个阶段:

  • 软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:
    – 非常高优先级
    – 高优先级
    – 中优先级
    – 低优先级
  • 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据
    流。例如,数据流 2 的优先级高于数据流 4。

6.外设通道

  外设通过设置其 DMA 请求信号来指示存在 DMA 传输请求。DMA 控制器会处理 DMA 请求并生成 DMA 确认信号,而且相应的 DMA 请求信号也将变为无效,但在此之前 DMA 请求一直处于挂起状态。STM32H743系列资源丰富,具有两个DMA控制器,同时外设繁多,为实现正常传输,DMA需要通道选择控制。由表可知,DMAMUX1的DMA请求输入总共有115个,映射情况参考表格 DMA请求来源 ,外设通道选择要解决的主要问题是决定哪一个外设作为该数据流的源地址或者目标地址。

在这里插入图片描述

STM32CubeMX生成工程

我们参考前面章节STM32H743-结合CubeMX新建HAL库MDK工程,打开CubeMX软件,重复步骤不再展示,我们来看配置DMA部分如下图所示:
在这里插入图片描述
在这里插入图片描述

实验代码

1. 主函数

//定义源缓冲区src_buffer
const unsigned long int src_buffer[BUFFER_SIZE] = { 
//BUFFER_SIZE在math.h头文件中定义,数值为32
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
//定义目标缓冲区dst_buffer
unsigned long int dst_buffer[BUFFER_SIZE] = {0}; 
int main(void)
{
    int i;
    MPU_Config();
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    //启动DMA传输,将src_buffer中的数据传输到dst_buffer中
    HAL_DMA_Start(&hdma_memtomem_dma2_stream0,(unsigned long int)src_buffer,(unsigned long int)dst_buffer,(unsigned long int)BUFFER_SIZE);
    //等待DMA_FLAG_TCIF0_4标志位被置位
    while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma2_stream0,DMA_FLAG_TCIF0_4) == SET);
    for(i = 0;i < BUFFER_SIZE;i++)
    {
        if(dst_buffer[i] != src_buffer[i])
        {
            //两缓冲区数据存在不相等情况则测试失败,LED灯持续闪烁
            while(1)
            {
                HAL_Delay(500);
                LED_ON;
                HAL_Delay(500);
                LED_OFF;
            }
        }
    }
    //两缓冲区数据相等,测试成功,LED灯长亮
    LED_ON;
    while (1)
    {

}

}


2. DMA初始化函数

void MX_DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();                                                                                        
    //使能DMA控制器时钟
    //配置DMA工作方式
    hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
    hdma_memtomem_dma2_stream0.Init.Request = DMA_REQUEST_MEM2MEM;
    hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
    //从存储器到存储器
    hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
    hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
    hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    //外设数据宽度
    hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; //存储器数据宽度
    hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
    hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH; //优先级为高
    hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;               //存储器突发单次传输
    hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;            //外设突发单次传输
    if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK)
    {
        Error_Handler();
    }

    /* DMA interrupt init */
    /* DMA2_Stream0_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}


3. DMA寄存器结构体
DMA相关的寄存器是通过HAL库中的结构体DMA_TypeDef和DMA_Stream_TypeDef定义的。

typedef struct
{
    __IO uint32_t CR;        /*!< DMA stream x 配置寄存器          */
    __IO uint32_t NDTR;     /*!< DMA stream x 数据寄存器数        */
    __IO uint32_t PAR;       /*!< DMA stream x 外设地址寄存器     */
    __IO uint32_t M0AR;     /*!< DMA stream x 存储器0地址寄存器 */
    __IO uint32_t M1AR;     /*!< DMA stream x 存储器1地址寄存器 */
    __IO uint32_t FCR;       /*!< DMA stream x FIFO控制寄存器    */
} DMA_Stream_TypeDef;


4. DMA句柄结构体DMA_HandleTypeDef
HAL库在DMA_TypeDef的基础上封装了一个结构体DMA_HandleTypeDef,定义如下:

typedef struct __DMA_HandleTypeDef  
{  
    void                         *Instance;       /*!< 注册基地址    */  
    DMA_InitTypeDef              Init;           /*!< DMA通讯参数 */  
    HAL_LockTypeDef             Lock;          /*!< DMA锁定对象 */  
    __IO HAL_DMA_StateTypeDef   State;          /*!< DMA传输状态 */  
    void           *Parent;                       /*!< 父对象状态   */  
    void          (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);           /*!< DMA传输完成回调 */  
    void           (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);      /*!< DMA半传输完成回调 */  
    void           (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA传输完成Memory1回调 */  
    void           (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);   /*!< DMA传输半完成Memory1回调 */  
    void           (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);         /*!< DMA传输错误回调 */  
    void           (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA传输中止回调 */  
    __IO uint32_t               ErrorCode;            /*!< DMA错误代码 */  
    uint32_t                    StreamBaseAddress;    /*!< DMA流基址 */   
    uint32_t                      StreamIndex;        /*!< DMA流索引 */  
    DMAMUX_Channel_TypeDef     *DMAmuxChannel; /*!< DMAMUX通道基地址  */  
    DMAMUX_ChannelStatus_TypeDef    *DMAmuxChannelStatus;                              /*!< DMAMUX通道状态基地址 */  
    uint32_t                           DMAmuxChannelStatusMask; 
    /*!< DMAMUX通道状态掩码 */
    DMAMUX_RequestGen_TypeDef        *DMAmuxRequestGen;                                /*!< DMAMUX请求生成器基地址 */  
    DMAMUX_RequestGenStatus_TypeDef  *DMAmuxRequestGenStatus;                          /*!< DMAMUX请求生成器状态地址 */  
    uint32_t                          DMAmuxRequestGenStatusMask;                      /*!< DMAMUX请求生成器状态掩码 */  
}DMA_HandleTypeDef;  


注: DTCM虽然和其它的MCU的RAM起始地址一样,但是无法被DMA访问。也就是通用RAM的地址变了,但是编译器还是会把DTCM的地址作为通用RAM的起始地址。若用户在编写程序实验过程中,发现DMA不能传输数据。请将内存起始地址修改为D1域。

实验现象

实验成功LED灯常亮,实验失败LED灯闪烁。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐