ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H562开发指南 V1.0 》第二十八章 DMA实验

《STM32H562开发指南 V1.0 》第二十八章 DMA实验

原创 嵌入式/内核开发 作者:正点原子 时间:2025-12-05 09:39:14 0 删除 编辑

第二十八章 DMA实验


       本章,我们将介绍STM32H562的DMA。我们将利用DMA来实现串口数据传送,并在LCD模块上显示当前的传送进度。

       本章分为如下几个小节:

       28.1 DMA简介

       28.2 硬件设计

       28.3 程序设计

       28.4 下载验证


        28.1 DMA简介

       DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。

       STM32H562内部有2个GPDMA控制器(也可以直接称为DMA)。下面来看一下GPDMA的特性:

       ① 双向AHB master接口

       ② 支持外设到存储器、存储器到外设、存储器到存储器和外设到外设传输

       ③ 集成DMAMUX

       ④ 8个并发通道:每个通道都支持FIFO

       ⑤ 可基于Linked-list配置

       ⑥ 突发传输,块传输,重复块传输

       ⑦ 低功耗模式下自主数据传输(只支持Sleep模式)

       ⑧ 基于4级优先级原则传输仲裁

       ⑨ 软件操作通道挂起与终止

       ⑩ 灵活的通道内部和通道之间传输控制

       ⑪ 每个通道都可以生成事件,如有:传输完成、一半传输完成、数据传输错误、用户设置错误、链路传输错误、完成挂起和触发器超限

       ⑫ 每个通道中断生成,每个事件单独编程中断启用

       ⑬ 每个通道对应一个链表项:

       -可单独编程的源和目的传输

       -源和目标之间的可编程数据处理:基于字节的重新排序,打包或拆包,填充或截断,符号扩展和左/右对齐

       -可编程的数据字节数从源传输,定义块级别

       -线性源和目标寻址:固定或连续递增寻址,在块级编程寻址,在连续突发传输寻址

       -2D源和目标寻址:连续突发传输之间的可编程签名地址偏移量(块内的非连续寻址)

       -支持散射-收集(多缓冲区传输),数据交错和去交错通过二维寻址

       -可编程GPDMA请求和触发选择

       -可编程GPDMA半传输和完整事件传输生成

       -指向内存中下一个链表项及其数据结构的指针,具有GPDMA链表控制寄存器的自动更新

       ⑭ Debug:

       -通道暂停和恢复支持

       -通道状态报告,包括FIFO级别和事件标志

       ⑮ 支持TrustZone和特权管理

       其它特性,请大家参考《STM32H5xx参考手册_V1(英文版).pdf》第16章。


       28.1.1 DMA框图

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


图28.1.1.1 GPDMA框图


       我们把GPDMA的框图大致分为四个部分进行介绍:

       ①第一部分是通道数据路径和传输输入控制,包括DMA通道请求、DMA触发、DMA通道、DMA仲裁器等。

       STM32H5的GPDMA不同通道可能存在差异,具体如下表所示:


表28.1.1.1 GPDMA1/2的通道描述


       大家在使用GPDMA前,最好先查询该表,看看选择的通道是否能实现自己想要的功能。

       接下来看一下GPDMA硬件请求源,请看下表:


表28.1.1.2 GPDMA硬件请求源


       注:该表只是展示了GPDMA部分的硬件请求源,详细的请查看《STM32H5xx参考手册_V1(英文版).pdf》的621页,共有136个硬件请求源。

       这些硬件请求源可以由用户分配给GPDMA的任意一个通道,通过GPDMA_CxTR2的位REQSEL[7:0]进行配置即可,前提是位SWREQ为0。位SWREQ为1,表示内存到内存传输的软件请求。

       接下来看一下GPDMA触发源,请看下表:


表28.1.1.3 GPDMA触发源 


       注:该表只是展示了GPDMA部分的触发源,详细的请查看《STM32H5xx参考手册_V1(英文版).pdf》的626页,共有44个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 模式

       关于这几个模式,请查看《STM32H5xx参考手册_V1(英文版).pdf》第16章,里面都有很直观和详细的流程图。篇幅原因就不给大家贴出来了。

       ③第三部分是AHB从接口,包括GPDMA时钟、在debug模式下停止GPDMA通道信号、GPDMA寄存器等。

       ④第四部分是GPDMA的各个管理部分,包括中断、事件、通道管理、安全和特权管理、时钟管理等。


       28.1.2 DMA寄存器

       l GPDMA通道x传输寄存器2(GPDMA_CxTR2),(x = 0到7)

       GPDMA通道x传输寄存器2(GPDMA_CxTR2)描述如图28.1.2.1所示:


图28.1.2.1 GPDMA通道x传输寄存器2


       位REQSEL [7:0]用于选择输出通道x的硬件请求源,H562系列有136个硬件请求源,具体请参考《STM32H5xx参考手册_V1(英文版).pdf》的621页。比如:本实验用到串口1的发送,所以要位REQSEL [7:0] = 22即可。

       l GPDMA通道x状态寄存器(GPDMA_CxSR),(x = 0到7)

       GPDMA通道x状态寄存器(GPDMA_CxSR)描述如图28.1.2.2所示:


图28.1.2.2 GPDMA通道x状态寄存器


       位IDLEF是空闲标志位。

       位TCF是传输完成标志位。

       位HTF是传输一半完成标志位。

       位DTEF是数据传输错误标志位。

       位ULEF是更新链表传输错误标志位。

       位USEF是用户设置错误标志位。

       位SUSPF是完成暂停标志位。

       位TOF是触发超限标志位。

       l GPDMA通道x标志清除寄存器(GPDMA_CxFCR),(x = 0到7)

       GPDMA通道x标志清除寄存器(GPDMA_CxFCR)描述如图28.1.2.3所示:


图28.1.2.3 GPDMA通道x标志清除寄存器


       往该寄存器相应的位写1可以清除CxSR寄存器的对应标志位,写0无效。

       l GPDMA通道x控制寄存器(GPDMA_CxCR),(x = 0到7)

       GPDMA通道x控制寄存器(GPDMA_CxCR)描述如图28.1.2.4所示:


图28.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搬运数据的目的地址。如果目的地址是其它外设,直接给该寄存器赋值为其它外设的寄存器就可以了。 


        28.2 硬件设计


       1. 例程功能

       每次按下按键KEY0,串口1就会以DMA方式发送数据,同时在LCD上面显示传送进度。打开串口调试助手,可以收到DMA发送的内容。LED0闪烁用于提示程序正在运行。


       2. 硬件资源


       1)LED灯

              LED0 - PB4

       2) 独立按键  KEY0 - PA1

       3) 串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)

       4)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)

       5)DMA


       3. 原理图

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


        28.3 程序设计 


       28.3.1 DMA的HAL库驱动

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

       1. HAL_DMA_Init函数

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

HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);

        l 函数描述:

       用于初始化DMA1/2。

       l 函数形参:

       形参1是DMA_HandleTypeDef结构体类型指针变量,其定义如下:

typedef struct __DMA_HandleTypeDef
{
  DMA_Channel_TypeDef       *Instance;             /* 寄存器基地址 */
  DMA_InitTypeDef           Init;                  /* DAM通道初始化参数 */
  DMA_InitLinkedListTypeDef InitLinkedList;    /* DMA通道链表初始化参数 */
  HAL_LockTypeDef           Lock;                  /* DMA锁定对象 */
  uint32_t                  Mode;                   /* DMA传输方式 */
  __IO HAL_DMA_StateTypeDef State;              /* DMA传输状态 */
  __IO uint32_t             ErrorCode;            /* DMA 错误代码 */
  void                      *Parent;                 /* Parent 对象状态 */
/* DMA传输完成回调函数指针 */
  void (* XferCpltCallback)(struct __DMA_HandleTypeDef *hdma);     
/* DMA传输一半完成回调函数指针 */
  void (* XferHalfCpltCallback)(struct __DMA_HandleTypeDef *hdma); 
/* DMA传输错误回调函数指针 */
  void (* XferErrorCallback)(struct __DMA_HandleTypeDef *hdma);    
/* DMA传输中止回调函数指针 */
  void (* XferAbortCallback)(struct __DMA_HandleTypeDef *hdma);    
/* DMA传输暂停回调函数指针 */
  void (* XferSuspendCallback)(struct __DMA_HandleTypeDef *hdma);  
  struct __DMA_QListTypeDef  *LinkedListQueue;  /* DMA链表队列 */
} DMA_HandleTypeDef;

       这个结构体内容比较多,上面已注释中文翻译,下面列出两个成员说明一下。

       *Instance:是用来设置寄存器基地址,例如要使用GPDMA1通道0,那么取值为GPDMA1_Channel0。

       Init:用于设置DAM通道初始化参数,是我们接触最多的结构体了。

       其它成员变量这里就不做过多讲解。

       接下来我们重点介绍Init,它是DMA_InitTypeDef结构体类型变量,该结构体定义如下:

typedef struct
{
  uint32_t Request;                /* 指定DMA通道请求 */
  uint32_t BlkHWRequest;           /* 指定DMA通道的块硬件请求模式 */
  uint32_t Direction;              /* 指定DMA通道的传输方向 */
  uint32_t SrcInc;                  /* 指定DMA通道的源增量模式 */
  uint32_t DestInc;                /* 指定DMA通道的目标增量模式 */
  uint32_t SrcDataWidth;           /* 指定DMA通道的源数据宽度 */
  uint32_t DestDataWidth;          /* 指定DMA通道的目标数据宽度 */
  uint32_t Priority;               /* 指定DMA通道的优先级 */
  uint32_t SrcBurstLength;         /* 指定DMA的源突发长度(突发内的节拍数)通道 */
  uint32_t DestBurstLength;        /* 指定对象的目标突发长度(突发内的节拍数)DMA通道 */
  uint32_t TransferAllocatedPort; /* 指定传输分配的端口 */
  uint32_t TransferEventMode;      /* 指定DMA通道的传输事件模式 */
  uint32_t Mode;                    /* 指定DMA通道的传输模式 */
} DMA_InitTypeDef;

       该结构体成员变量非常多,决定了DMA的工作方式。

       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。


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

       1)使能DMA时钟

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

__HAL_RCC_GPDMA1_CLK_ENABLE ();  /* GPDMA1时钟使能 */
__HAL_RCC_GPDMA2_CLK_ENABLE ();  /* GPDMA2时钟使能 */


       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的位DMAT来控制的,在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_TC); /* 传输完成标志位,还可以是其它 */

       获取当前传输剩余数据量:

__HAL_DMA_GET_COUNTER(&g_dma_handle);

       同样,我们也可以设置对应的DMA数据流传输的数据量大小函数为:

__HAL_DMA_SET_COUNTER (&g_dma_handle, 1000);

       DMA相关的库函数我们就讲解到这里,大家可以查看固件库手册详细了解。


       5)DMA中断使用方法

       对于GPDMA的每个通道都有对应的中断服务函数,比如GPDMA1通道0的中断服务函数为GPDMA1_Channel0_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);      /* 传输出错回调函数 */


       28.3.2 程序流程图


图28.3.2.1 DMA实验程序流程图


       28.3.3 程序解析


       1. DMA驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DMA驱动源码包括两个文件:dma.c和dma.h。

       dma.h头文件只有函数的声明,下面我们直接介绍dma.c的程序,下面是DMA的初始化函数,其定义如下:

/**
 * @brief       串口TX DMA初始化函数
 *   @note      这里的传输形式是固定的, 这点要根据不同的情况来修改
 *               从存储器 -> 外设模式/8位数据宽度/存储器增量模式
 *
 * @param       dmax_chx
 * @retval      无
 */
void dma_init(DMA_Channel_TypeDef* dmax_chx)
{
    __HAL_RCC_GPDMA1_CLK_ENABLE();          /* GPDMA1时钟使能 */
 
    g_dma_handle.Instance = dmax_chx;        /* 通道 */
g_dma_handle.Init.Request = GPDMA1_REQUEST_USART1_TX;     /* DMA通道请求 */
/* 单/突发级别的硬件请求协议 */
g_dma_handle.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;  
/* DMA通道的传输方向:内存到外设 */
g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;   
 /* 源增量模式:增量模式 单个/突发 */
    g_dma_handle.Init.SrcInc = DMA_SINC_INCREMENTED;    
    g_dma_handle.Init.DestInc = DMA_DINC_FIXED;/* 目标增量模式:固定模式 单个/突发 */
    g_dma_handle.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE; /* 源数据宽度:8位 */
    g_dma_handle.Init.DestDataWidth =DMA_DEST_DATAWIDTH_BYTE;/*目标数据宽度:8位*/
    g_dma_handle.Init.Priority = DMA_HIGH_PRIORITY;  /* DMA优先级:高优先级 */
    g_dma_handle.Init.SrcBurstLength = 1;   /* 指定DMA的源突发长度,范围: 1 到 64 */
    g_dma_handle.Init.DestBurstLength = 1;  /* 指定的目标突发长度,范围: 1 到 64 */
g_dma_handle.Init.TransferAllocatedPort = 
DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0; /* 指定传输分配的端口 */
/* 指定DMA通道的传输事件模式 */
    g_dma_handle.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;   
    g_dma_handle.Init.Mode = DMA_NORMAL;    /* DMA模式:正常模传输 */
    HAL_DMA_Init(&g_dma_handle);
 
    __HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle);
 
    HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 1, 1);
    HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
 
    /* 配置DMA通道安全性和特权属性 */
    HAL_DMA_ConfigChannelAttributes(&g_dma_handle, DMA_CHANNEL_NPRIV);
}

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

       接下来就是GPDMA中断相关的代码了,具体如下:

/**
 * @brief       GPDMA1通道0中断服务函数
 * @param       无
 * @retval      无
 */
void GPDMA1_Channel0_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&g_dma_handle);
}
 
/**
  * @brief  串口发送数据完成回调函数
 * @param   huart: UART句柄类型指针
 * @retval  无
 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    g_uart_tx_completed = 1;
}

       上面的代码比较简单,使用HAL_DMA_IRQHandler()函数处理DMA中断,然后调用HAL_UART_TxCpltCallback回调函数,给全局变量g_uart_tx_completed赋值为1,表示串口发送数据完成,方便main函数那边进行处理。


       2. main.c代码

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

/* 要循环发送的字符串 */
const uint8_t g_text_to_send[] = {"正点原子 STM32H562 DMA 串口实验"}; 
/* 发送数据长度,等于sizeof(TEXT_TO_SEND) + 2的200倍 */   
#define SEND_BUF_SIZE       ((sizeof(g_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;                                /* 进度 */
 
    HAL_Init();                                    /* HAL库初始化 */
    HAL_ICACHE_Enable();                           /* 使能指令缓存 */
    sys_stm32_clock_init(250, 4, 2, 2);        /* 系统时钟初始化, 250Mhz */
    delay_init(250);                                /* 延时初始化 */
    usart_init(115200);                           /* 串口初始化 */
    led_init();                                      /* LED初始化 */
    key_init();                                     /* 按键初始化 */
    lcd_init();                                       /* LCD初始化 */
    dma_init(GPDMA1_Channel0);                     /* 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(g_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              /* 复制g_sendbuf语句 */
        {
            mask = 0;
            g_sendbuf[i] = g_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); /* 显示百分号 */
/* 开始一次DMA传输! */
            HAL_UART_Transmit_DMA(&g_uart1_handle, g_sendbuf, SEND_BUF_SIZE);   
 
            /* 等待串口传输完成,此时我们来做另外一些事情,比如点灯
             * 实际应用中,传输数据期间,可以执行另外的任务
             */
            while (1)
            {
                if (g_uart_tx_completed == 1)    /* 等待串口发送数据完成 */
                {
                    g_uart_tx_completed = 0;    /* 清除串口发送数据完成标志 */
                    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;
        }
    }
}

       main函数的流程大致是:先初始化发送数据缓冲区g_text_to_send的值,然后通过KEY0开启串口DMA发送,在发送过程中,通过__HAL_DMA_GET_COUNTER(&g_dma_handle)获取当前还剩余的数据量来计算传输百分比,最后在传输结束之后清除相应标志位,提示已经传输完成。


        28 .4 下载验证

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


图28.4.1 DMA实验测试图


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


图28.4.2 串口收到的数据内容


       可以看到串口收到了开发板发送过来的数据,同时可以看到TFTLCD上显示了进度等信息,如图28.4.3所示:


28.4.3 DMA串口数据传输中


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


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3104452/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764246