• 【正点原子STM32连载】 第三十章 DMA实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1


    1)实验平台:正点原子MiniPro H750开发板
    2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560
    3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html
    4)对正点原子STM32感兴趣的同学可以加群讨论:879133275

    第三十章 DMA实验

    本章,我们将介绍STM32H750的DMA。我们将利用DMA来实现串口数据传送,并在LCD模块上显示当前的传送进度。
    本章分为如下几个小节:
    30.1 DMA简介
    30.2 硬件设计
    30.3 程序设计
    30.4 下载验证

    30.1 DMA简介

    DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
    STM32H750内部有4个DMA控制器,它们的连接关系请参考前面第十一章的图11.3.2 STM32H7的总线架构图。图中可以看到每个域都拥有各自的 DMA控制器,D1域有1个高速主DMA(MDMA);D2 域有2个双口DMA,DMA1和DMA2;D3域有1个基本DMA(BDMA)。
    1、MDMA用于实现:内存内存、内存外设、外设内存之间的高速数据传输,MDMA支持AXI和AHBS总线之间的数据传输。注意:仅MDMA支持AHBS的访问(可访问DTCM和ITCM),其他DMA都无法访问AHBS总线。
    2、双口DMA同样可以实现:内存内存、内存外设、外设内存之间的高速数据传输,双口DMA支持AHB外设之间的数据传输,具有独立的FIFO,支持不同位宽的数据传输。
    3、BDMA用于实现:内存内存、内存外设、外设内存、外设到外设之间的高速数据传输。
    本章我们用到的是双口DMA,因此接下来,我们重点介绍双口DMA。
    STM32H750内部有2个双口DMA控制器(DMA1和DMA2),共16个数据流(每个控制器8个),每一个双口DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流可以有多达115个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理DMA请求间的优先级。
    STM32H750的双口DMA有以下一些特性:
    ① 双AHB主总线架构,一个用于存储器访问,另一个用于外设访问
    ② 仅支持32位访问的AHB从编程接口
    ③ 每个DMA控制器有8个数据流,每个数据流有多达115个通道(或称请求)
    ④ 每个数据流有单独的四级32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式
    ⑤ 通过硬件可以将每个数据流配置为:
    1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
    2,支持在存储器方双缓冲的双缓冲区通道
    ⑥ 8个数据流中的每一个都连接到专用硬件DMA通道(请求)
    ⑦ DMA 数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求0的优先级高于请求1)
    ⑧ 每个数据流也支持通过软件触发存储器到存储器的传输
    ⑨ 每个数据流的通道请求可以由DMA请求复用器1(DMAMUX1)选择,可供选择的通道请求数多达115个。此选择可由软件配置,允许多个外设启动DMA请求
    ⑩ 要传输的数据项的数目可以由DMA控制器或外设管理:
    1,DMA 流控制器:要传输的数据项的数目是1到65535,可用软件编程
    2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号
    ⑪ 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用。
    ⑫ 对源和目标的增量或非增量寻址
    ⑬ 支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半
    ⑭ 每个数据流都支持循环缓冲区管理
    ⑮ 5个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
    30.1.1 DMA框图
    STM32H750有两个双口DMA控制器,DMA1和DMA2,本章,我们仅针对DMA2进行介绍。下面先来学习双口DMA控制器框图,通过学习DMA控制器框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。STM32H750的双口MDA控制器框图如图30.1.1.1所示:
    在这里插入图片描述

    图30.1.1.1 DMA控制器框图
    图中,我们标记了6处位置,起作用分别是:
    ①,DMA控制器的从机编程接口,通过该接口可以对DMA的相关控制寄存器进行设置,从而配置DMA,实现不同的功能。同时,该接口可以输出dma_it[0:7]的中断信号到NVIC,以及dma_tcif[0:7]的信号到MDMA。
    ②,DMA控制器的外设接口,用于访问相关外设,特别的,当外设接口设置的访问地址是内存地址的时候,DMA就可以工作在内存到内存模式了。
    ③,DMA控制器的FIFO区,每个数据流(总共8个数据流)都有一个独立的FIFO,可以实现存储器接口到外设接口之间的数据长度非对齐传输。
    ④,DMA控制器的存储器接口,用于访问外部存储器,特别的当存储器地址是外设地址的时候,可以实现类似外设到外设的传输效果。
    ⑤,DMA控制器的仲裁器,用于仲裁数据流0~7的请求优先级,保证数据有序传输。
    ⑥,这是DMA控制器数据流0~7的通道请求信号,由DMAMUX1的选择,每个数据流有多达115个通道请求可以选择。我们必须根据实际需求来选择对应的通道请求。
    这里重点介绍一下DMAMUX1,其全称是DMA请求复用器1,用于管理DMA1和DMA2的通道请求,而DMAMUX2则用来管理BDMA的通道请求,这里重点介绍DMAMUX1。
    DMAMUX1总共有16个通道,其中通道07对应DMA1的数据流07,通道815对应DMA2的数据流07。DMAMUX框图如图30.1.1.2所示:
    在这里插入图片描述

    图30.1.1.2 DMAMUX控制器框图
    整个DMAMUX的功能比较复杂,包括:选择具体的DMA请求通道(源)、同步控制、请求生成、请求计数和中断等,本章我们只用到其最简单的应用:选择具体的DMA请求通道,即通过DMAMUX1选择DMA1/DMA2的数据流通道(请求通道)。为了方便说明,我们在图中标出了几个关键点:
    ①,外设请求,即STM32H7芯片内部外设的请求,这部分总共有p+1个外设请求,对于DMAMUX1来说,总共有107个(p=106)。具体的对应关系详见:《STM32H7xx参考手册_V7(英文版).pdf》第694页,Table 120。
    ②,通道选择,通过DMAMUX_CmCR(对DMAMUX1来说,m=0~15,该寄存器也写作:DMAMUX_CCRm,下同)寄存器的DMAREQ_ID[7:0]位来选择DMA的具体请求通道,总共有115个通道,其中①处有107个,还有8个通道来自DMAMUX内部的请求生成器,我们一般用①处的107个请求通道。
    ③,同步控制,用于同步DMA请求,最终输出给DMA控制器,我们本章不使用同步控制,因此可以关闭同步(通过DMAMUX_CmCR寄存器的SE位设置)。
    ④,输出到DMA控制器的请求信号,对DMAMUX1来说总共有16个输出请求信号(m=15),分别对应DMA1和DMA2的数据流0~7。
    因此,我们一般只需要通过设置DMAMUX1通道m的DMAMUX1_CmCR寄存器来选择输入通道,即可完成DMA1/DMA2某个数据流的DMA输入请求通道选择。
    举例来说,假设我们要设置DMA2数据流3的DMA输入请求通道为:串口7的TX(UART7_TX),则:
    1,DMA2数据流3对应DMAMUX1的通道11(从0开始算起)。
    2,串口7的TX对应的外设DMA请求编号为:80(查《STM32H7xx参考手册_V7(英文版).pdf》第696页Table 121)。
    因此,我们只需要设置DMAMUX1_CCR11的DMAREQ_ID[7:0]位为80即可完成DMA2数据流3的请求来自UART7_TX的设置。
    DMAMUX1的DMA请求资源分配表,如表30.1.1.1所示:
    在这里插入图片描述

    表30.1.1.1 DMAMUX1 DMA请求资源分配表(部分)
    由表可知,DMAMUX1的DMA请求输入总共有115个,表中我们省略了一部分内容,详见:《STM32H7xx参考手册_V7(英文版)》第696页Table 120和Table 121。
    由于DMA1/DMA2数据流0~7的DMA请求全部是来自DMAMUX1的设置(16个通道),DMAMUX1的所有通道都可以独立设置,因此,对于STM32H7来说,DMA1/DMA2的任何一个数据流的请求都可以来自115个通道的任意一个,因此相对于STM32其他系列来说,STM32H7的DMA配置更加灵活,无需固定通道对应固定外设请求,可以随意设置。
    30.1.2 DMA寄存器
    DMAMUX1请求线复用器通道x配置寄存器(DMAMUX1_CxCR),(x = 0到15)
    DMAMUX1请求线复用器通道x配置寄存器描述如图30.1.2.1所示:
    在这里插入图片描述

    图30.1.2.1 DMAMUX1_CxCR寄存器各位描述
    本章,该寄存器我们只需要关心DMAREQ_ID[7:0]这8个位,其他位全部用不到(设置为0即可),DMAREQ_ID[7:0]用于选择输出通道x的DMA输入请求,我们需要通过这8个位选择DMA1/DMA2数据流的请求源。请求列表见表30.1.1(完整的表见《STM32H7xx参考手册_V7(英文版)》第696页Table 120和Table 121)。
    DMA中断状态寄存器(DMA_LISR和DMA_HISR)
    DMA中断状态寄存器,该寄存器总共有2个:DMA_LISR和DMA_HISR,每个寄存器管理4数据流(总共8个),DMA_LISR寄存器用于管理数据流03,而DMA_HISR用于管理数据流47。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。
    这里我们仅以DMA_LISR寄存器为例进行介绍,DMA_LISR各位描述如图30.1.2.2所示:
    在这里插入图片描述

    图30.1.2.2 DMA_LISR寄存器
    如果开启了DMA_LISR中这些位对应的中断,则在达到条件后就会跳到中断服务函数里面去,即使没开启,我们也可以通过查询这些位来获得当前DMA传输的状态。这里我们常用的是TCIFx位,即数据流x的DMA传输完成与否标志。注意此寄存器为只读寄存器,所以在这些位被置位之后,只能通过其他的操作来清除。DMA_HISR寄存器各位描述通DMA_LISR寄存器各位描述完全一样,只是对应数据流4~7,这里我们就不列出来了。
    DMA中断标志清除寄存器(DMA_LIFCR和DMA_HIFCR)
    DMA中断标志清除寄存器, 该寄存器同样有2个:DMA_LIFCR和DMA_HIFCR,同样是每个寄存器控制4个数据流,DMA_LIFCR寄存器用于管理数据流0~3,而DMA_ HIFCR用于管理数据流4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。
    这里,我们仅以DMA_LIFCR寄存器为例进行介绍,DMA_LIFCR各位描述如图30.1.2.3所示:
    在这里插入图片描述

    图30.1.2.3 DMA_LIFCR寄存器
    DMA_LIFCR的各位就是用来清除DMA_LISR的对应位的,通过写1清除。在DMA_LISR被置位后,我们必须通过向该位寄存器对应的位写入1来清除。DMA_HIFCR的使用同DMA_LIFCR类似,这里就不做介绍了。
    第四个是DMA数据流x配置寄存器(DMA_SxCR)(x=0~7,下同)。该寄存器的我们在这里就不贴出来了,见《STM32H7xx参考手册_V3(中文版).pdf》第565页15.5.5小节。该寄存器控制着DMA的很多相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能等都是通过该寄存器来设置的。所以DMA_ SxCR是DMA传输的核心控制寄存器。
    第五个是DMA数据流x数据项数寄存器(DMA_SxNDTR)。这个寄存器控制DMA数据流x的每次传输所要传输的数据量。其设置范围为0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为0的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA传输的进度。特别注意,这里是数据项数目,而不是指的字节数。比如设置数据位宽为16位,那么传输一次(一个项)就是2个字节。
    第六个是DMA数据流x的外设地址寄存器(DMA_SxPAR)。该寄存器用来存储STM32H750外设的地址,比如我们使用串口1,那么该寄存器必须写入0x40011028(其实就是&USART1_TDR)。如果使用其他外设,就修改成相应外设的地址就行了。
    最后一个是DMA数据流x的存储器地址寄存器,由于STM32H750的DMA支持双缓存,所以存储器地址寄存器有两个:DMA_SxM0AR和DMA_SxM1AR,其中DMA_SxM1AR仅在双缓冲模式下,才有效。本章我们没用到双缓冲模式,所以存储器地址寄存器就是:DMA_SxM0AR,该寄存器和DMA_CPARx差不多,但是是用来放存储器的地址的。比如我们使用SendBuf[7800]数组来做存储器,那么我们在DMA_SxM0AR中写入&SendBuff就可以了。
    30.2 硬件设计

    1. 例程功能
      每按下按键KEY0,串口1就会以DMA方式发送数据,同时在LCD上面显示传送进度。打开串口调试助手,可以收到DMA发送的内容。LED0闪烁用于提示程序正在运行。
    2. 硬件资源
      1)RGB灯
      RED : LED0 - PB4
      2)独立按键 KEY0 - PA1
      3)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
      4)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
      5)DMA
    3. 原理图
      DMA属于STM32H750内部资源,通过软件设置好就可以了。本实验通过按键KEY0触发使用DMA的方式向串口发送数据,LCD显示传送进度,通过串口上位机可以看到传输的内容。
      30.3 程序设计
      30.3.1 DMA的HAL库驱动
      DMA在HAL库中的驱动代码在stm32h7xx_hal_dma.c文件(及其头文件)中。
    4. HAL_DMA_Init函数
      DMA的初始化函数,其声明如下:
      HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
      函数描述:
      用于初始化DMA1,DMA2和BDMA。
      函数形参:
      形参1是DMA_HandleTypeDef结构体类型指针变量,其定义如下:
    typedef struct __DMA_HandleTypeDef
    {
      void                              *Instance;       	/* 寄存器基地址 */
      DMA_InitTypeDef                 Init;             /* DAM通信参数 */
      HAL_LockTypeDef                 Lock;             /* DMA锁对象 */
      __IO HAL_DMA_StateTypeDef     State;          	/* DMA传输状态 */
      void                              *Parent;         	/* 父对象状态,HAL库处理的中间变量 */
      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传输半完全内存回调 */
      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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这个结构体内容比较多,上面已注释中文翻译,下面列出几个成员说明一下。
    Instance:是用来设置寄存器基地址,例如要设置为DMA2的数据流7,那么取值为DMA2_Stream7。
    Parent:是HAL库处理中间变量,用来指向DMA通道外设句柄。
    StreamBaseAddress和StreamIndex是数据流基地址和索引号,这个是HAL库处理的时候会自动计算,用户无需设置。
    其他成员变量是HAL库处理过程状态标识变量,这里就不做过多讲解。
    接下来我们重点介绍Init,它是DMA_InitTypeDef结构体类型变量,该结构体定义如下:

    typedef struct
    {
      uint32_t Request;              	/* 请求设置,设置是哪个外设请求的 */     
      uint32_t Direction;            	/* 传输方向,例如存储器到外设DMA_MEMORY_TO_PERIPH */ 
      uint32_t PeriphInc;            	/* 外设(非)增量模式,非增量模式DMA_PINC_DISABLE */  
      uint32_t MemInc;               	/* 存储器(非)增量模式,增量模式DMA_MINC_ENABLE */  
      uint32_t PeriphDataAlignment;	/* 外设数据大小:8/16/32位 */
      uint32_t MemDataAlignment;   	/* 存储器数据大小:8/16/32位 */
      uint32_t Mode;                 	/* 模式:外设流控模式/循环模式/普通模式 */    
      uint32_t Priority;           	/* DMA优先级:低/中/高/非常高 */
      uint32_t FIFOMode;            	/* FIFO模式开启或者禁止 */ 
      uint32_t FIFOThreshold;     	/* FIFO阈值选择 */
      uint32_t MemBurst;            	/* 存储器突发模式:单次/4个节拍/8个节拍/16个节拍 */  
      uint32_t PeriphBurst;         	/* 外设突发模式:单次/4个节拍/8个节拍/16个节拍 */    
    }DMA_InitTypeDef;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    该结构体成员变量非常多,但是每个成员变量配置的基本都是DMA_SxCR寄存器和DMA_SxFCR寄存器的相应位。
    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
    以DMA的方式传输串口数据配置步骤
    1)使能DMA时钟
    DMA的时钟使能是通过AHB1ENR寄存器来控制的,这里我们要先使能时钟,才可以配置DMA相关寄存器。HAL库方法为:
    __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 /
    __HAL_RCC_DMA2_CLK_ENABLE(); /
    DMA2时钟使能 */
    2)初始化DMA
    调用HAL_DMA_Init函数初始化DMA的相关参数,包括配置通道,外设地址,存储器地址,传输数据量等。
    HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。例如要使用串口DMA发送,所以方式为:
    __HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle);
    其中g_uart1_handle是串口初始化句柄,我们在usart.c中定义过了。g_dma_handle是DMA初始化句柄。hdmatx是外设句柄结构体的成员变量,在这里实际就是g_uart1_handle的成员变量。在HAL库中,任何一个可以使用DMA的外设,它的初始化结构体句柄都会有一个DMA_HandleTypeDef指针类型的成员变量,是HAL库用来做相关指向的。hdmatx就是DMA_HandleTypeDef结构体指针类型。
    这句话的含义就是把g_uart1_handle句柄的成员变量hdmatx和DMA句柄g_dma_handle连接起来,是纯软件处理,没有任何硬件操作。
    这里我们就点到为止,如果大家要详细了解HAL库指向关系,请查看本实验宏定义标识符__HAL_LINKDMA的定义和调用方法,就会很清楚了。
    3)使能串口的DMA发送,启动传输
    串口1的DMA发送实际是串口控制寄存器CR3的位7来控制的,在HAL库中操作该寄存器来使能串口DMA发送的函数为HAL_UART_Transmit_DMA。
    这里大家需要注意,调用该函数后会开启相应的DMA中断,对于本章实验,我们是通过查询的方法获取数据传输状态,所以并没有做中断相关处理,也没有编写中断服务函数。
    HAL库还提供了对串口的DMA发送的停止,暂停,继续等操作函数:
    HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef huart); / 停止 */
    HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef huart); / 暂停 */
    HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef huart); / 恢复 */
    4)查询DMA传输状态
    在DMA传输过程中,我们要查询DMA传输通道的状态,使用的方法是:
    __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7);
    获取当前传输剩余数据量:
    __HAL_DMA_GET_COUNTER(&g_dma_handle);
    同样,我们也可以设置对应的DMA数据流传输的数据量大小,函数为:
    __HAL_DMA_SET_COUNTER (&g_dma_handle, 1000);
    DMA相关的库函数我们就讲解到这里,大家可以查看固件库中文手册详细了解。
    5)DMA中断使用方法
    DMA中断对于每个数据流都有一个中断服务函数,比如DMA2_Stream7的中断服务函数为DMA2_Stream7_IRQHandler。HAL库提供了通用DMA中断处理函数HAL_DMA_IRQHandler,在该函数内部,会对DMA传输状态进行分析,然后调用相应的中断处理回调函数:
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef huart); / 发送完成回调函数 */
    void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef huart);/ 发送一半回调函数 */
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef huart); / 接收完成回调函数 */
    void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef huart);/ 接收一半回调函数 */
    void HAL_UART_ErrorCallback(UART_HandleTypeDef huart); / 传输出错回调函数 */
    30.3.2 程序流程图
    在这里插入图片描述

    图30.3.2.1 DMA实验程序流程图
    30.3.3 程序解析

    1. DMA驱动代码
      这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DMA驱动源码包括两个文件:dma.c和dma.h。
      dma.h头文件只有函数的声明,下面我们直接介绍dma.c的程序,下面是DMA的初始化函数,其定义如下:
    /**
     * @brief   		串口TX DMA初始化函数
     *   @note    	这里的传输形式是固定的, 这点要根据不同的情况来修改
     *              	从存储器 -> 外设模式/8位数据宽度/存储器增量模式
     *
     * @param       	dma_stream_handle : DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
     * @retval      	无
     */
    void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch)
    { 
    /* 得到当前stream是属于DMA2还是DMA1 */
        if ((uint32_t)dma_stream_handle > (uint32_t)DMA2)     
        {
            __HAL_RCC_DMA2_CLK_ENABLE();                      /* DMA2时钟使能 */
        }
        else 
        {
            __HAL_RCC_DMA1_CLK_ENABLE();                      /* DMA1时钟使能 */
        }
    /* 将DMA与USART1联系起来(发送DMA) */
        __HAL_LINKDMA(&g_uart1_handle,hdmatx, g_dma_handle);  
    
        /* Tx DMA配置 */
        g_dma_handle.Instance = dma_stream_handle;                   	/* 数据流选择 */
        g_dma_handle.Init.Request = ch;                                	/* DMA通道选择 */
        g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;        	/* 存储器到外设 */
        g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;             	/* 外设非增量模式 */
        g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;                  	/* 存储器增量模式 */
        g_dma_handle.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;/*外设数据长度:8位*/
        g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE/*存储器数据长度:8位*/
        g_dma_handle.Init.Mode = DMA_NORMAL;                        	/* 外设流控模式 */
        g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;        	/* 中等优先级 */
        g_dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;       	/* 关闭FIFO模式 */
        g_dma_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;	/* FIFO阈值配置 */
        g_dma_handle.Init.MemBurst = DMA_MBURST_SINGLE;         	/* 存储器突发单次传输 */
        g_dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE;     	/* 外设突发单次传输 */
    
        HAL_DMA_Init(&g_dma_handle);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    该函数是一个通用的DMA配置函数,DMA1/DMA2的所有通道,都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。该函数在外部只能修改DMA数据流编号和通道号,更多的其他设置只能在该函数内部修改。对照前面的配置步骤的详细讲解来分析这部分代码即可。
    2. main.c代码
    在main.c里面编写如下代码:

    /* 要循环发送的字符串 */
    const uint8_t TEXT_TO_SEND[] = {"正点原子 MiniPRO STM32H7 DMA 串口实验"};   
    #define SEND_BUF_SIZE       (sizeof(TEXT_TO_SEND) + 2) * 200   /* 发送数据长度 */
    uint8_t g_sendbuf[SEND_BUF_SIZE];       		/* 发送数据缓冲区 */
    
    int main(void)
    {
        uint8_t  key = 0;
        uint16_t i, k;
        uint16_t len;
        uint8_t  mask = 0;
        float pro = 0;                          	/* 进度 */
    
        sys_cache_enable();                    	/* 打开L1-Cache */
        HAL_Init();                              	/* 初始化HAL库 */
        sys_stm32_clock_init(240, 2, 2, 4); 	/* 设置时钟, 480Mhz */
        delay_init(480);                        	/* 延时初始化 */
        usart_init(115200);                    	/* 串口初始化为115200 */
        mpu_memory_protection();              	/* 保护相关存储区域 */
        led_init();                              	/* 初始化LED */
        lcd_init();                              	/* 初始化LCD */
        key_init();                              	/* 初始化按键 */
    
        dma_init(DMA2_Stream7, DMA_REQUEST_USART1_TX);  /* 初始化DMA */
    
        lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
        lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
        lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
        lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
    
        len = sizeof(TEXT_TO_SEND);
        k = 0;
        
        for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集数据 */
        {
            if (k >= len)   /* 入换行符 */
            {
                if (mask)
                {
                    g_sendbuf[i] = 0x0a;
                    k = 0;
                }
                else
                {
                    g_sendbuf[i] = 0x0d;
                    mask++;
                }
            }
            else     /* 复制TEXT_TO_SEND语句 */
            {
                mask = 0;
                g_sendbuf[i] = TEXT_TO_SEND[k];
                k++;
            }
        }
         i = 0;
        while (1)
        {
            key = key_scan(0);
    
            if (key == KEY0_PRES)   /* KEY0按下 */
            {
                printf("\r\nDMA DATA:\r\n");
                lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);
                lcd_show_string(30, 150, 200, 16, 16, "   %", BLUE);    /* 显示百分号 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    /* 开始一次DMA传输! */
    HAL_UART_Transmit_DMA(&g_uart1_handle,g_sendbuf,SEND_BUF_SIZE);

            /* 等待DMA传输完成,此时我们来做另外一些事情,比如点灯  
             * 实际应用中,传输数据期间,可以执行另外的任务 */
    
    • 1
    • 2
                while (1)
                {
    /* 等待DMA1_Steam7传输完成 */
                    if (__HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7)) 
                    {
    /* 清除DMA1_Steam7传输完成标志 */
                        __HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7); 
                        HAL_UART_DMAStop(&g_uart1_handle);  /* 传输完成以后关闭串口DMA */
                        break;
                    }
                    Pro =__HAL_DMA_GET_COUNTER(&g_dma_handle);/*得到当前还剩余多少个数据*/
                    len = SEND_BUF_SIZE;        /* 总长度 */
                    pro = 1 - (pro / len);      /* 得到百分比 */
                    pro *= 100;                 /* 扩大100倍 */
                    lcd_show_num(30, 150, pro, 3, 16, BLUE);
                } 
                lcd_show_num(30, 150, 100, 3, 16, BLUE);    /* 显示100% */
    /* 提示传送完成 */
                lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); 
            }
    
            i++;
            delay_ms(10);
    
            if (i == 20)
            {
                LED0_TOGGLE();  /* LED0闪烁,提示系统正在运行 */
                i = 0;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    main函数的流程大致是:先初始化发送数据缓冲区g_sendbuf的值,然后通过KEY0开启串口DMA发送,在发送过程中,通过__HAL_DMA_GET_COUNTER(&g_dma_handle)获取当前还剩余的数据量来计算传输百分比,最后在传输结束之后清除相应标志位,提示已经传输完成。
    30.4 下载验证
    将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图30.4.1所示:
    在这里插入图片描述

    图30.4.1 DMA实验测试图
    我们打开串口调试助手,然后按KEY0,可以看到串口显示如图30.4.2所示的内容:
    在这里插入图片描述

    图30.4.2 串口收到的数据内容
    可以看到串口收到了开发板发送过来的数据,同时可以看到TFTLCD上显示了进度等信息,如图30.4.3所示:
    在这里插入图片描述

    30.4.3 DMA串口数据传输中
    至此,我们整个DMA实验就结束了,希望大家通过本章的学习,掌握STM32H750的DMA使用。DMA是个非常好的功能,它不但能减轻CPU负担,还能提高数据传输速度,合理的应用DMA,往往能让你的程序设计变得简单。

  • 相关阅读:
    算法|每日一题|只出现一次的数字Ⅱ|位运算
    【设计模式】适配器模式:攻敌三分,自留七分,以超兽武装的例子来谈谈适配器模式
    Kotlin高仿微信-第57篇-VIP管理列表
    低代码平台权限管理设计实践
    基于Java毕业设计知识库系统源码+系统+mysql+lw文档+部署软件
    【Python】Matplotlib-多张图像的显示
    零代码编程:用ChatGPT多线程批量将PDF文档转换为word格式
    consul部署
    [ARM入门]ARM模式及其切换、异常
    系列六、Java垃圾回收器主要有哪些?
  • 原文地址:https://blog.csdn.net/weixin_55796564/article/details/126778087