OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 39|回复: 0

《STM32H7R7开发指南 V1.1 》第三十二章 DMA实验

[复制链接]

1318

主题

1334

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5635
金钱
5635
注册时间
2019-5-8
在线时间
1507 小时
发表于 9 小时前 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2026-4-27 09:49 编辑

第三十二章 DMA实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

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


32.1 DMA简介
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
STM32H7R7内部有一个GPDMA和一个HPDMA(也可以直接称为DMA)。这两种DMA主要区别是性能的不同,本章我们仅使用到GPDMA,下面来看一下GPDMA的特性:
①双向AHB master接口
②支持外设到存储器、存储器到外设、存储器到存储器和外设到外设传输
③低功耗模式下自主数据传输
④基于4级优先级原则传输仲裁
⑤每个通道都可以生成事件,如有:传输完成、一半传输完成、数据传输错误、用户设置错误、链路传输错误、完成挂起和触发器超限
⑥每个通道中断生成,每个事件单独编程中断启用
⑦16个并发GPDMA通道:
-每个通道FIFO排队源和目的传输
-通道内GPDMA通过可编程链表在存储器中传输链,支持两种执行模式:运行到完成模式和链接步进模式
-通道内和通道间的GPDMA传输链通过可编程的GPDMA输入触发连接到GPDMA任务完成事件
⑧每个通道对应一个链表项:
-可单独编程的源和目的传输
-源和目标之间的可编程数据处理:基于字节的重新排序,打包或拆包,填充或截断,符号扩展和左/右对齐
-可编程的数据字节数从源传输,定义块级别
-线性源和目标寻址:固定或连续递增寻址,在块级编程寻址,在连续突发传输寻址
-2D源和目标寻址:连续突发传输之间的可编程签名地址偏移量(块内的非连续寻址)
-支持散射-收集(多缓冲区传输),数据交错和去交错通过二维寻址
-可编程GPDMA请求和触发选择
-可编程GPDMA半传输和完整事件传输生成
-指向内存中下一个链表项及其数据结构的指针,具有GPDMA链表控制寄存器的自动更新
⑨Debug:
-通道暂停和恢复支持
-通道状态报告,包括FIFO级别和事件标志
⑩特权/无特权支持:
-支持特权和非特权GPDMA传输,独立的通道级别
-特权感知的AHB slave接口
其它特性,请大家参考《STM32H7Rx参考手册_V6(英文版).pdf》第12章。


32.1.1 DMA框图
通过学习GPDMA框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。STM32H7R7的GPMDA框图如图32.1.1.1所示:

第三十二章 DMA实验1142.png
图32.1.1.1 GPDMA框图

我们把GPDMA的框图大致分为四个部分进行介绍:
①第一部分是通道数据路径和传输输入控制,包括DMA通道请求、DMA触发、DMA通道、DMA仲裁器等。
STM32H7R7的GPDMA不同通道存在一定差异,具体如下表所示:


第三十二章 DMA实验1273.png
表32.1.1.1 GPDMA1的通道描述

大家在使用GPDMA前,最好先查询该表,看看选择的通道是否能实现自己想要的功能。
接下来看一下GPDMA硬件请求源,请看下表:


第三十二章 DMA实验1361.png
表32.1.1.2 GPDMA硬件请求源

注:该表只是展示了GPDMA部分的硬件请求源,详细的请查看《STM32H7Rx参考手册_V6(英文版).pdf》的602页,共有109个硬件请求源。
这些硬件请求源可以由用户分配给GPDMA的任意一个通道,通过GPDMA_CxTR2的位REQSEL[6:0]进行配置即可,前提是位SWREQ为0。位SWREQ为1,表示内存到内存传输的软件请求。
接下来看一下GPDMA触发源,请看下表:


第三十二章 DMA实验1578.png
表32.1.1.3 GPDMA触发源

注:该表只是展示了GPDMA部分的触发源,详细的请查看《STM32H7Rx参考手册_V6(英文版).pdf》的606页,共有55个GPDMA触发源。
这些触发源可以由用户分配给GPDMA的任意一个通道,通过GPDMA_CxTR2的位TRIGSEL[5:0]进行配置即可,前提是要先通过位TRIGPOL[1:0]定义了所选触发器是上升沿或者下降沿触发。
②第二部分是传输控制,包括数据传输、Link传输、AHB master端口0、AHB master端口1等。
GPDMA通道支持两种工作模式:
1,直接配置模式,无linked list,GPDMA_CxLLR[31:0]=0
指定数据传输完成后,通道进入IDLE状态,通道可重新配置和使能
2,Linked list模式
run-to-completion 模式
link step 模式
关于这几个模式,请查看《STM32H7Rx参考手册_V6(英文版).pdf》第12章,里面都有很直观和详细的流程图。篇幅原因就不给大家贴出来了。
③第三部分是AHB从接口,包括GPDMA时钟、在debug模式下停止GPDMA通道信号、GPDMA寄存器等。
④第四部分是GPDMA的各个管理部分,包括中断、事件、通道管理、特权管理、时钟管理等。

32.1.2 DMA寄存器
GPDMA通道x传输寄存器2(GPDMA_CxTR2),(x = 0到15)
GPDMA通道x传输寄存器2(GPDMA_CxTR2)描述如图32.1.2.1所示:


第三十二章 DMA实验2246.png
图32.1.2.1 GPDMA通道x传输寄存器2

位REQSEL [6:0]用于选择输出通道x的硬件请求源,H7R7系列有109个硬件请求源,具体请参考《STM32H7Rx参考手册_V6(英文版).pdf》的602页。比如:本实验用到串口1的发送,所以要位REQSEL [6:0] = 74即可。
GPDMA通道x状态寄存器(GPDMA_CxSR),(x = 0到15)
GPDMA通道x状态寄存器(GPDMA_CxSR)描述如图32.1.2.2所示:


第三十二章 DMA实验2475.png
图32.1.2.2 GPDMA通道x状态寄存器

位IDLEF是空闲标志位。
位TCF是传输完成标志位。
位HTF是传输一半完成标志位。
位DTEF是数据传输错误标志位。
位ULEF是更新链表传输错误标志位。
位USEF是用户设置错误标志位。
位SUSPF是完成暂停标志位。
位TOF是触发超限标志位。
GPDMA通道x标志清除寄存器(GPDMA_CxFCR),(x = 0到15)
GPDMA通道x标志清除寄存器(GPDMA_CxFCR)描述如图32.1.2.3所示:


第三十二章 DMA实验2712.png
图32.1.2.3 GPDMA通道x标志清除寄存器

往该寄存器相应的位写1可以清除CxSR寄存器的对应标志位,写0无效。
GPDMA通道x控制寄存器(GPDMA_CxCR),(x = 0到15)
GPDMA通道x控制寄存器(GPDMA_CxCR)描述如图32.1.2.4所示:


第三十二章 DMA实验2853.png
图32.1.2.4 GPDMA通道x控制寄存器

位EN用于使能该GPDMA通道,当出现传输错误或者传输完成时,该位由硬件清零。
位[14:8]是对应中断使能位。
位LAP用于设置链表分配的端口。
位PRIO[1:0]用于设置通道传输优先级。
第五个是GPDMA通道x传输寄存器1(GPDMA_ CxTR1)。该寄存器控制着GPDMA的很多参数,包括源和目的数据宽度、源和目的增量模式等。
第六个是GPDMA通道x块寄存器1(GPDMA_ CxBR1)。这个寄存器控制GPDMA通道x每次传输的数据量,其设置范围为0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为0的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA传输的进度。特别注意,这里是数据项数目,而不是指的字节数。比如设置数据位宽为16位,那么传输一次(一个项)就是2个字节。
第七个是GPDMA通道x源地址寄存器(GPDMA_ CxSAR)。该寄存器用来存储GPDMA搬运数据的源地址,如果外设作为源地址,比如我们要搬运串口1的数据,那么该寄存器必须写入0x40013828(其实就是&USART1_TDR)。如果使用其它外设,就修改成相应外设的地址就行了。
第八个是GPDMA通道x目的地址寄存器(GPDMA_ CxDAR),该寄存器用来存储GPDMA搬运数据的目的地址。如果目的地址是其它外设,直接给该寄存器赋值为其它外设的寄存器就可以了。
GPDMA通道x链表地址寄存器(GPDMA_CxLLR),(x = 0到11)
GPDMA通道x链表地址寄存器(GPDMA_CxLLR)描述如图32.1.2.5所示:


第三十二章 DMA实验3571.png
图32.1.2.5 GPDMA通道x链表地址寄存器

位LA[15:2]用于指向下一个链表数据结构的指针(低16位地址有效,该指针编程为32位对齐)。
位ULL是从内存更新GPDMA_CxLLR寄存器。
位UDA是从内存更新GPDMA_CxDAR寄存器。
位USA 是从内存更新GPDMA_CxSAR寄存器。
位UB1是从内存更新GPDMA_CxBR1寄存器。
位UT2是从内存更新GPDMA_CxTR2寄存器。
位UT1 是从内存更新GPDMA_CxTR1寄存器。


32.2 硬件设计

1. 例程功能
本例程包含2个源码:实验21-1 DMA标准模式实验和实验21-2 DMA链表模式实验,它们除了使用的DMA工作模式不一样外,实现的功能是一样的。
每次按下按键KEY0,串口1就会以DMA方式发送数据,同时在LCD上面显示传送进度。打开串口调试助手,可以收到DMA发送的内容。LED0闪烁用于提示程序正在运行。

2. 硬件资源
1)LED灯
       LED0 – PD14
2)独立按键
       KEY0 – PE9
3)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
4)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
5)GPDMA

3. 原理图
DMA属于STM32H7R7内部资源,通过软件设置好就可以了。本实验通过按键KEY0触发使用DMA的方式向串口发送数据,LCD显示传送进度,通过串口上位机可以看到传输的内容。


32.3 程序设计

32.3.1 DMA的HAL库驱动
DMA在HAL库中的驱动代码在stm32h7rsxx_hal_dma.c和stm32h7rsxx_hal_dma_ex.c文件(及其头文件)中。

1. HAL_DMA_Init函数
DMA的初始化函数,其声明如下:
  1. HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
复制代码
函数描述:
用于初始化GPDMA1。
函数形参:
形参1是DMA_HandleTypeDef结构体类型指针变量,其定义如下:

  1. typedef struct __DMA_HandleTypeDef
  2. {
  3.   DMA_Channel_TypeDef       *Instance;         /* 寄存器基地址 */
  4.   DMA_InitTypeDef           Init;              /* DAM通道初始化参数 */
  5.   DMA_InitLinkedListTypeDef InitLinkedList;    /* DMA通道链表初始化参数 */
  6.   HAL_LockTypeDef           Lock;              /* DMA锁定对象 */
  7.   uint32_t                  Mode;              /* DMA传输方式 */
  8.   __IO HAL_DMA_StateTypeDef State;             /* DMA传输状态 */
  9.   __IO uint32_t             ErrorCode;         /* DMA 错误代码 */
  10.   void                      *Parent;           /* Parent 对象状态 */
  11. /* DMA传输完成回调函数指针 */
  12.   void (* XferCpltCallback)(struct __DMA_HandleTypeDef *hdma);     
  13. /* DMA传输一半完成回调函数指针 */
  14.   void (* XferHalfCpltCallback)(struct __DMA_HandleTypeDef *hdma);
  15. /* DMA传输错误回调函数指针 */
  16.   void (* XferErrorCallback)(struct __DMA_HandleTypeDef *hdma);   
  17. /* DMA传输中止回调函数指针 */
  18.   void (* XferAbortCallback)(struct __DMA_HandleTypeDef *hdma);   
  19. /* DMA传输暂停回调函数指针 */
  20.   void (* XferSuspendCallback)(struct __DMA_HandleTypeDef *hdma);  
  21.   struct __DMA_QListTypeDef  *LinkedListQueue;         /* DMA链表队列 */
  22. } DMA_HandleTypeDef;
复制代码
这个结构体内容比较多,上面已注释中文翻译,下面列出两个成员说明一下。
*Instance:是用来设置寄存器基地址,例如要使用GPDMA1通道0,那么取值为GPDMA1_Channel0。
Init:用于设置DAM通道初始化参数,是我们接触最多的结构体了。
其它成员变量这里就不做过多讲解。
接下来我们重点介绍Init,它是DMA_InitTypeDef结构体类型变量,该结构体定义如下:

  1. typedef struct
  2. {
  3.   uint32_t Request;                       /* 指定DMA通道请求 */
  4.   uint32_t BlkHWRequest;                  /* 指定DMA通道的块硬件请求模式 */
  5.   uint32_t Direction;                     /* 指定DMA通道的传输方向 */
  6.   uint32_t SrcInc;                 /* 指定DMA通道的源增量模式 */
  7.   uint32_t DestInc;                       /* 指定DMA通道的目标增量模式 */
  8.   uint32_t SrcDataWidth;                  /* 指定DMA通道的源数据宽度 */
  9.   uint32_t DestDataWidth;                 /* 指定DMA通道的目标数据宽度 */
  10.   uint32_t Priority;                      /* 指定DMA通道的优先级 */
  11.   uint32_t SrcBurstLength;                /* 指定DMA的源突发长度(突发内的节拍数)通道 */
  12.   uint32_t DestBurstLength;               /* 指定对象的目标突发长度DMA通道 */
  13.   uint32_t TransferAllocatedPort;        /* 指定传输分配的端口 */
  14.   uint32_t TransferEventMode;             /* 指定DMA通道的传输事件模式 */
  15.   uint32_t Mode;                          /* 指定DMA通道的传输模式 */
  16. } DMA_InitTypeDef;
复制代码
该结构体成员变量非常多,决定了DMA的工作方式。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

2. HAL_DMAEx_List_Init函数
DMA的初始化函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DMAEx_List_Init(DMA_HandleTypeDef *const hdma);
复制代码
函数描述:
以链表的方式初始化DMA通道。
函数形参:
形参1是DMA_HandleTypeDef结构体类型指针变量,该结构体类型指针变量上面已经详细介绍过了,我们介绍下关于链表的结构体成员变量。
InitLinkedList:用于设置DMA通道链表初始化参数,当我们使用DMA链表模式时,就需要配置这个结构体了。
接下来我们重点介绍InitLinkedList,它是DMA_InitLinkedListTypeDef结构体类型变量,该结构体定义如下:

  1. typedef struct
  2. {
  3.   uint32_t Priority;          /* 指定DMA通道的优先 */
  4.   uint32_t LinkStepMode;      /* 指定DMA通道的链路步进模式 */
  5.   uint32_t LinkAllocatedPort; /* 指定链表为DMA通道分配的端口 */
  6.   uint32_t TransferEventMode; /* 指定DMA通道的传输事件模式 */
  7.   uint32_t LinkedListMode;    /* 指定DMA通道的链表传输模式 */
  8. } DMA_InitLinkedListTypeDef;
复制代码
该结构体成员变量都有详细的配置参数,我们这里不一一介绍,大家可以在对应的stm32h7rsxx_hal_dma_ex.h查找。
函数返回值:
HAL_StatusTypeDef枚举类型的值。

3. HAL_DMAEx_List_BuildNode函数
构建DMA通道节点函数,其声明如下:

  1. HAL_StatusTypeDef HAL_DMAEx_List_BuildNode(DMA_NodeConfTypeDef const *const pNodeConfig, DMA_NodeTypeDef *const pNode);
复制代码
函数描述:
根据DMA_NodeConfTypeDef中指定的参数构建DMA通道节点。
函数形参:
形参1是DMA_NodeConfTypeDef结构体类型指针变量,其定义如下:

  1. typedef struct
  2. {
  3.   uint32_t                    NodeType;           /* 指定DMA通道节点类型。*/
  4.   DMA_InitTypeDef             Init;               /* 指定DMA通道的基本配置 */
  5.   DMA_DataHandlingConfTypeDef DataHandlingConfig; /* 指定DMA通道处理通道配置 */
  6.   DMA_TriggerConfTypeDef      TriggerConfig;      /* 指定DMA通道触发配置 */
  7.   DMA_RepeatBlockConfTypeDef  RepeatBlockConfig;  /* 指定DMA通道重复块配置 */
  8.   uint32_t                    SrcAddress;         /* 指定源内存地址 */
  9.   uint32_t                    DstAddress;         /* 指定目的内存地址 */
  10.   uint32_t                    DataSize;           /* 指定源数据大小(以字节为单位)*/
  11. } DMA_NodeConfTypeDef;
复制代码
这个结构体内容比较多,上面已注释中文翻译,下面列出两个成员说明一下。
NodeType:是用来指定DMA通道节点类型,GPDMA共有两种寻址类型,可通过DMA_GPDMA_LINEAR_NODE和DMA_GPDMA_2D_NODE这两个参数选择线性寻址节点类型和二维寻址节点类型。
形参2是DMA_NodeTypeDef结构体类型指针变量,用于链表节点结构定义,指向DMA_NodeTypeDef结构体的指针,该结构体包含链表节点寄存器
函数返回值:
HAL_StatusTypeDef枚举类型的值。

以DMA的方式传输串口数据配置步骤

1)使能DMA时钟
DMA的时钟使能是通过AHB1ENR寄存器来控制的,这里我们要先使能时钟,才可以配置DMA相关寄存器。HAL库方法为:

  1. __HAL_RCC_GPDMA1_CLK_ENABLE ();         /* GPDMA1时钟使能 */
复制代码
2)初始化DMA
调用HAL_DMA_Init函数(标准模式)或者HAL_DMAEx_List_Init函数(链表模式)初始化DMA的相关参数,包括配置通道,外设地址,存储器地址,传输数据量等,如果使用链表模式还需要通过HAL_DMAEx_List_BuildNode函数构建链表节点。
HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。例如要使用串口DMA发送,所以方式为:

  1. __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发送,启动传输
对于标准DMA例程,直接调用HAL_UART_Transmit_DMA函数进行传输,调用该函数后会开启相应的DMA中断,对于本章实验,我们是通过查询的方法获取数据传输状态,所以并没有做中断相关处理,也没有编写中断服务函数。
HAL库还提供了对串口的DMA发送的停止,暂停,继续等操作函数:

  1. HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart);           /* 停止 */
  2. HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart);          /* 暂停 */
  3. HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart);         /* 恢复 */
复制代码
对于链表模式的DMA传输,则通过HAL_DMAEx_List_Start_IT函数开启中断并以链表模式启动DMA通道传输,然后调用ATOMIC_SET_BIT函数控制USART_CR3寄存器的DMAT位使能DMA传输。
4)查询DMA传输状态
在DMA传输过程中,我们要查询DMA传输通道的状态,使用的方法是:

  1. __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC); /* 传输完成标志位,还可以是其它 */
复制代码
获取当前传输剩余数据量:
  1. __HAL_DMA_GET_COUNTER(&g_dma_handle);
复制代码
同样,我们也可以设置对应的DMA数据流传输的数据量大小函数为:
  1. __HAL_DMA_SET_COUNTER (&g_dma_handle, 1000);
复制代码
DMA相关的库函数我们就讲解到这里,大家可以查看固件库手册详细了解。
5)DMA中断使用方法
对于GPDMA的每个通道都有对应的中断服务函数,比如GPDMA1通道0的中断服务函数为GPDMA1_Channel0_IRQHandler。HAL库提供了通用DMA中断处理函数HAL_DMA_IRQHandler,在该函数内部,会对DMA传输状态进行分析,然后调用相应的中断处理回调函数:

  1. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);   /* 发送完成回调函数 */
  2. void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);/* 发送一半回调函数 */
  3. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);    /* 接收完成回调函数 */
  4. void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);/* 接收一半回调函数 */
  5. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);     /* 传输出错回调函数 */
复制代码

32.3.2 程序解析

32.3.2.1 DMA标准模式

1. DMA驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DMA驱动源码包括两个文件:dma.c和dma.h。
dma.h头文件只有函数的声明,下面我们直接介绍dma.c的程序,下面是DMA的初始化函数,其定义如下:

  1. /**
  2. * @brief   初始化DMA
  3. * [url=home.php?mod=space&uid=271674]@param[/url]   无
  4. * @retval  无
  5. */
  6. void dma_init(void)
  7. {
  8.     /* 使能时钟 */
  9.     __HAL_RCC_GPDMA1_CLK_ENABLE();
  10.    
  11.     /* 初始化DMA */
  12.     g_dma_handle.Instance = GPDMA1_Channel0;
  13.     g_dma_handle.Init.Request = GPDMA1_REQUEST_USART1_TX;  /* 通道请求 */
  14.     g_dma_handle.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;/* 块硬件请求模式 */
  15.     g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;  /* 传输方向 */
  16.     g_dma_handle.Init.SrcInc = DMA_SINC_INCREMENTED;     /* 传输源地址增量模式 */
  17.     g_dma_handle.Init.DestInc = DMA_DINC_FIXED;          /* 传输目标地址增量模式 */
  18.     g_dma_handle.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;  /* 传输源数据宽度 */
  19.     g_dma_handle.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;/*传输目标数据宽度*/
  20.     g_dma_handle.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT; /* 优先级 */
  21.     g_dma_handle.Init.SrcBurstLength = 1;                /* 传输源突发长度 */
  22.     g_dma_handle.Init.DestBurstLength = 1;               /* 传输目标突发长度 */
  23. g_dma_handle.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 |
  24. DMA_DEST_ALLOCATED_PORT0;                            /* 传输端口分配 */
  25.     g_dma_handle.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
  26.     g_dma_handle.Init.Mode = DMA_NORMAL;                 /* 传输模式 */
  27.     HAL_DMA_Init(&g_dma_handle);
  28.    
  29.     /* 关联外设与DMA */
  30.     __HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle);
  31.    
  32.     /* 配置通道属性 */
  33.     HAL_DMA_ConfigChannelAttributes(&g_dma_handle, DMA_CHANNEL_NPRIV);
  34.    
  35.     /* 配置中断优先级并使能中断 */
  36.     HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 0, 0);
  37.     HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
  38. }
复制代码
该函数是一个通用的DMA配置函数,GPDMA1的所有通道都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。该函数在外部只能修改DMA数据流编号和通道号,更多的其他设置只能在该函数内部修改。对照前面的配置步骤的详细讲解来分析这部分代码即可。
接下来就是GPDMA中断相关的代码了,具体如下:

  1. /**
  2. * @brief   GPDMA1 Channel0中断服务函数
  3. * @param   无
  4. * @retval  无
  5. */
  6. void GPDMA1_Channel0_IRQHandler(void)
  7. {
  8.     HAL_DMA_IRQHandler(&g_dma_handle);
  9. }
复制代码
上面的代码比较简单,使用HAL_DMA_IRQHandler()函数处理DMA中断。

2. main.c代码
在main.c里面编写如下代码:

  1. char temp[] = {"正点原子 STM32 DMA实验\r\n"};
  2. uint8_t buf[(sizeof(temp) - 1) * 200] = {0};
  3. uint8_t uart_ready = 1;

  4. /**
  5. * @brief   HAL库UART传输完成回调函数
  6. * @param   无
  7. * @retval  无
  8. */
  9. void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
  10. {
  11.     /* 标记传输完成,可以进行下一次传输 */
  12.     uart_ready = 1;
  13. }

  14. int main(void)
  15. {
  16.     uint8_t t = 0;
  17.     uint8_t key;
  18.     uint16_t buf_index;
  19.     uint8_t temp_index;
  20.    
  21.     sys_mpu_config();                   /* 配置MPU */
  22.     sys_cache_enable();                 /* 使能Cache */
  23.     HAL_Init();                         /* 初始化HAL库 */
  24.     sys_stm32_clock_init(240, 5, 2);    /* 配置时钟,600MHz */
  25.     delay_init(600);                    /* 初始化延时 */
  26.     usart_init(115200);                 /* 初始化串口 */
  27.     led_init();                         /* 初始化LED */
  28.     key_init();                         /* 初始化按键 */
  29.     hyperram_init();                    /* 初始化HyperRAM */
  30.     lcd_init();                         /* 初始化LCD */
  31.     dma_init();                         /* 初始化DMA */
  32.    
  33.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  34.     lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
  35.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  36.     lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
  37.    
  38.     /* 准备数据 */
  39. for (buf_index = 0; buf_index < (sizeof(buf) / (sizeof(temp) - 1));
  40. buf_index++)
  41.     {
  42.         for (temp_index = 0; temp_index < (sizeof(temp) - 1); temp_index++)
  43.         {
  44.             buf[buf_index * (sizeof(temp) - 1)+temp_index] = temp[temp_index];
  45.         }
  46.     }
  47.     SCB_CleanDCache();
  48.    
  49.     while (1)
  50.     {
  51.         key = key_scan(0);
  52.         if (key == KEY0_PRES)
  53.         {
  54.             if (uart_ready == 1)
  55.             {
  56.                 uart_ready = 0;
  57.                 /* DMA传输串口数据 */
  58.                 HAL_UART_Transmit_DMA(&g_uart1_handle, buf, sizeof(buf));
  59.             }
  60.         }
  61.         
  62.         if (++t == 20)
  63.         {
  64.             t = 0;
  65.             LED0_TOGGLE();
  66.         }
  67.         
  68.         delay_ms(10);
  69.     }
  70. }
复制代码
main函数的流程大致是:先初始化发送数据缓冲区g_text_to_send的值,然后通过KEY0开启串口DMA发送,将buf里的数据依次发送完。

32.3.2.2 DMA链表模式

1. DMA驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DMA驱动源码包括两个文件:dma.c和dma.h。
dma.h头文件定义了DMA链表的最大节点数,如下所示:

  1. /* 功能定义 */
  2. #define DMA_MAX_NODE    10  /* DMA链表最大节点数量 */
复制代码
下面我们直接介绍dma.c的程序,下面是DMA的初始化函数,其定义如下:
  1. /**
  2. * @brief   初始化DMA
  3. * @param   bufaddr: 缓冲区地址缓冲区的指针
  4. * @param   bufsize: 缓冲区大小缓冲区的指针
  5. * @param   bufnum: 缓冲区数量
  6. * @retval  无
  7. */
  8. void dma_init(uint32_t *bufaddr, uint32_t *bufsize, uint32_t bufnum)
  9. {
  10.     DMA_NodeConfTypeDef dma_node_conf_struct = {0};
  11.     uint32_t node_index;
  12.    
  13.     if (bufnum > (sizeof(g_dma_node_struct) / sizeof(g_dma_node_struct[0])))
  14.     {
  15.         node_used = sizeof(g_dma_node_struct) / sizeof(g_dma_node_struct[0]);
  16.     }
  17.     else
  18.     {
  19.         node_used = bufnum;
  20.     }
  21.    
  22.     /* 使能时钟 */
  23.     __HAL_RCC_GPDMA1_CLK_ENABLE();
  24.    
  25.     /* 复位链表 */
  26.     HAL_DMAEx_List_ResetQ(&g_dma_qlist_struct);
  27.    
  28.     /* 配置DMA链表节点 */
  29.     dma_node_conf_struct.NodeType = DMA_GPDMA_LINEAR_NODE;       /* 节点类型 */
  30.     dma_node_conf_struct.Init.Request = GPDMA1_REQUEST_USART1_TX;/* 通道请求 */
  31. dma_node_conf_struct.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
  32. /* 块硬件请求模式 */
  33.     dma_node_conf_struct.Init.Direction = DMA_MEMORY_TO_PERIPH;   /* 传输方向 */
  34.     dma_node_conf_struct.Init.SrcInc = DMA_SINC_INCREMENTED;/*传输源地址增量模式*/
  35.     dma_node_conf_struct.Init.DestInc = DMA_DINC_FIXED;  /* 传输目标地址增量模式 */
  36. dma_node_conf_struct.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
  37. /* 传输源数据宽度 */
  38. dma_node_conf_struct.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
  39. /* 传输目标数据宽度 */
  40. dma_node_conf_struct.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
  41. /* 优先级 */
  42.     dma_node_conf_struct.Init.SrcBurstLength = 1;    /* 传输源突发长度 */
  43.     dma_node_conf_struct.Init.DestBurstLength = 1;   /* 传输目标突发长度 */
  44. dma_node_conf_struct.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 |
  45. DMA_DEST_ALLOCATED_PORT0;                        /* 传输端口分配 */
  46. dma_node_conf_struct.Init.TransferEventMode =
  47. DMA_TCEM_LAST_LL_ITEM_TRANSFER;                  /* 传输事件模式 */
  48.     dma_node_conf_struct.Init.Mode = DMA_NORMAL;     /* 传输模式 */
  49. dma_node_conf_struct.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
  50. /* 数据交换模式 */
  51. dma_node_conf_struct.DataHandlingConfig.DataAlignment =
  52. DMA_DATA_RIGHTALIGN_ZEROPADDED;                  /* 数据填充和对齐模式 */
  53. dma_node_conf_struct.TriggerConfig.TriggerPolarity =
  54. DMA_TRIG_POLARITY_MASKED;                        /* 触发事件优先级 */
  55. dma_node_conf_struct.DstAddress = (uint32_t)&g_uart1_handle.Instance->TDR;
  56. /* 目的地址 */
  57.     for (node_index = 0; node_index < node_used; node_index++)
  58.     {
  59.         dma_node_conf_struct.SrcAddress = bufaddr[node_index];    /* 源地址 */
  60.         dma_node_conf_struct.DataSize = bufsize[node_index];      /* 数据大小 */
  61.         
  62.         /* 构建DMA链表节点 */
  63.         HAL_DMAEx_List_BuildNode(&dma_node_conf_struct,
  64. &g_dma_node_struct[node_index]);
  65.         
  66.         /* DMA链表节点插入链表 */
  67.         HAL_DMAEx_List_InsertNode_Tail(&g_dma_qlist_struct,
  68. &g_dma_node_struct[node_index]);
  69.     }
  70.    
  71.     /* 初始化链表模式DMA */
  72.     g_dma_handle.Instance = GPDMA1_Channel0;
  73.     g_dma_handle.InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
  74.     g_dma_handle.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
  75.     g_dma_handle.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
  76. g_dma_handle.InitLinkedList.TransferEventMode =
  77. DMA_TCEM_LAST_LL_ITEM_TRANSFER;                         /* 触发事件模式 */
  78.     g_dma_handle.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_NORMAL;  
  79.     HAL_DMAEx_List_Init(&g_dma_handle);
  80.    
  81.     /* 关联DMA与DMA链表 */
  82.     HAL_DMAEx_List_LinkQ(&g_dma_handle, &g_dma_qlist_struct);
  83.    
  84.     /* 关联外设与DMA */
  85.     __HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle);
  86.    
  87.     /* 配置通道属性 */
  88.     HAL_DMA_ConfigChannelAttributes(&g_dma_handle, DMA_CHANNEL_NPRIV);
  89.    
  90.     /* 注册DMA传输完成回调函数 */
  91. HAL_DMA_RegisterCallback(&g_dma_handle, HAL_DMA_XFER_CPLT_CB_ID,
  92. dma_transfer_complete_cb);
  93.    
  94.     /* 配置中断优先级并使能中断 */
  95.     HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 0, 0);
  96.     HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
  97. }
复制代码
该函数首先计算DMA传输的数据需要使用多少个DMA链表的节点,然后使能GPDMA时钟,因为我们使用的是DMA链表模式,所以首先要配置DMA链表节点,用过HAL库提供的HAL_DMAEx_List_BuildNode函数构建我们要用的DMA链表节点,然后将节点插入到我们的链表中,这样就构建好了我们的DMA链表,接着是配置DMA,我们需要选择到DMA链表模式,配置完成后分别将链表与DAM、外设关联起来,最后注册中断及回调函数。
下面是开启DMA传输函数,具体如下:

  1. /**
  2. * @brief   DMA传输完成回调函数
  3. * @param   无
  4. * @retval  无
  5. */
  6. void dma_start_transfer(void)
  7. {
  8.     if (dma_ready == 1)
  9.     {
  10.         dma_ready = 0;
  11.         
  12.         /* 开启中断模式的DMA链表传输 */
  13.         HAL_DMAEx_List_Start_IT(&g_dma_handle);
  14.         
  15.         /* 使能UART的DMA发送 */
  16.         ATOMIC_SET_BIT(g_uart1_handle.Instance->CR3, USART_CR3_DMAT);
  17.     }
  18. }
复制代码
这个函数非常的简单,我们在32.3.1小节也有对这两个函数功能进行详细介绍,这里就不再叙述。
接下来就是GPDMA中断相关的代码了,具体如下:

  1. /**
  2. * @brief   DMA传输完成回调函数
  3. * @param   hdma: DMA句柄指针
  4. * @retval  无
  5. */
  6. static void dma_transfer_complete_cb(DMA_HandleTypeDef *const hdma)
  7. {
  8.     if (hdma->Instance == GPDMA1_Channel0)
  9.     {
  10.         dma_ready = 1;
  11.     }
  12. }

  13. /**
  14. * @brief   GPDMA1 Channel0中断服务函数
  15. * @param   无
  16. * @retval  无
  17. */
  18. void GPDMA1_Channel0_IRQHandler(void)
  19. {
  20.     HAL_DMA_IRQHandler(&g_dma_handle);
  21. }
复制代码
上面的代码比较简单,使用HAL_DMA_IRQHandler()函数处理DMA中断,然后调用HAL_UART_TxCpltCallback回调函数,给变量dma_ready赋值为1,表示上个数据传输完成,DMA已经准备就绪,方便dma_start_transfer函数那边进行处理。

2. main.c代码
在main.c里面编写如下代码:

  1. /* 传输数据 */
  2. char *buffer[] = {
  3.     "STM32\r\n",
  4.     "DMA TEST\r\n",
  5.     "正点原子 STM32 DMA\r\n",
  6.     "\r\n",
  7. };

  8. /* 传输数据大小 */
  9. uint32_t size[] = {
  10.     sizeof("STM32\r\n") - 1,
  11.     sizeof("DMA TEST\r\n") - 1,
  12.     sizeof("正点原子 STM32 DMA\r\n") - 1,
  13.     sizeof("\r\n") - 1,
  14. };

  15. /* 传输数据数量 */
  16. uint32_t number = sizeof(buffer) / sizeof(buffer[0]);

  17. int main(void)
  18. {
  19.     uint8_t t = 0;
  20.     uint8_t key;
  21.     uint16_t buf_index;
  22.     uint8_t temp_index;
  23.    
  24.     sys_mpu_config();                           /* 配置MPU */
  25.     sys_cache_enable();                         /* 使能Cache */
  26.     HAL_Init();                                 /* 初始化HAL库 */
  27.     sys_stm32_clock_init(240, 5, 2);            /* 配置时钟,600MHz */
  28.     delay_init(600);                            /* 初始化延时 */
  29.     usart_init(115200);                         /* 初始化串口 */
  30.     led_init();                                 /* 初始化LED */
  31.     key_init();                                 /* 初始化按键 */
  32.     hyperram_init();                            /* 初始化HyperRAM */
  33.     lcd_init();                                 /* 初始化LCD */
  34.     dma_init((uint32_t *)buffer, size, number); /* 初始化DMA */
  35.    
  36.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  37.     lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
  38.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  39.     lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
  40.    
  41.     while (1)
  42.     {
  43.         key = key_scan(0);
  44.         if (key == KEY0_PRES)
  45.         {
  46.             /* 开启DMA传输 */
  47.             dma_start_transfer();
  48.         }
  49.         
  50.         if (++t == 20)
  51.         {
  52.             t = 0;
  53.             LED0_TOGGLE();
  54.         }
  55.         
  56.         delay_ms(10);
  57.     }
  58. }
复制代码
main函数的流程大致是:先初始化发送数据缓冲区buffer的值,然后通过KEY0调用dma_start_transfer函数开启串口DMA发送。

32.4 下载验证
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图32.4.1所示:

第三十二章 DMA实验21507.png
图32.4.1 DMA实验测试图

我们打开串口调试助手,然后按KEY0,可以看到串口显示如图32.4.2所示的内容:

第三十二章 DMA实验21568.png
图32.4.2 串口收到的数据内容

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-4-27 18:50

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表