OpenEdv-开源电子网

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

《STM32F407 探索者开发指南》第三十章 DMA实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4895
金钱
4895
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-8-1 14:18:43 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-7-31 17:26 编辑

第三十章 DMA实验

1)实验平台:正点原子探索者STM32F407开发板

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


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

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

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

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

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

STM32F407最多有2个DMA控制器(DMA1和DMA2),两个DMA控制器总共有16个数据流。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

STM32F407的DMA有以下一些特性:
① 双AHB主总线架构,一个用于存储器访问,另一个用于外设访问。

② 仅支持32位访问的AHB从编程接口。

③ 每个DMA控制器有8个数据流,每个数据流有多达8个通道(请求)。

④ 每个数据流有单独的四级32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式。

⑤ 通过硬件可以将每个数据流配置为:
1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道。
2,支持在存储器方双缓冲的双缓冲区通道。

⑥ 8个数据流中的每一个都连接到专用硬件DMA通道(请求)。

⑦ DMA 数据流请求之间的优先级可用软件编程(4个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求0的优先级高于请求1)

⑧ 每个数据流也支持通过软件触发存储器到存储器的传输(仅限DMA2控制器)

⑨可供每个数据流选择的通道请求数多达8个。此选择可由软件配置,允许多个外设启动DMA请求。

⑩ 要传输的数据项的数目可以由DMA控制器或外设管理:
1,DMA 流控制器:要传输的数据项的数目是1到65535,可用软件编程。
2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号。

⑪ 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。这个特性仅在FIFO模式下可用。

⑫ 对源和目标的增量或非增量寻址。

⑬ 支持4个、8个和16个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设FIFO大小的一半。

⑭ 每个数据流都支持循环缓冲区管理。

⑮ 5个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求。

30.1.1 DMA框图
STM32F407有两个DMA控制器,DMA1和DMA2,本章,我们仅针对DMA1进行介绍。

下面先来学习DMA控制器框图,通过学习DMA控制器框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。STM32F4的DMA控制器框图如图30.1.1.1所示:                                
image001.png
图30.1.1.1 DMA控制器框图

图中,我们标记了6处位置,起作用分别是:
DMA控制器的从机编程接口,通过该接口可以对DMA的相关控制寄存器进行设置,从而配置DMA,实现不同的功能。
DMA控制器的外设接口,用于访问相关外设,特别的,当外设接口设置的访问地址是内存地址的时候,DMA就可以工作在内存到内存模式了。
DMA控制器的FIFO区,每个数据流(总共8个数据流)都有一个独立的FIFO,可以实现存储器接口到外设接口之间的数据长度非对齐传输。
DMA控制器的存储器接口,用于访问外部存储器,特别的当存储器地址是外设地址的时候,可以实现类似外设到外设的传输效果。
DMA控制器的仲裁器,用于仲裁数据流0~7的请求优先级,保证数据有序传输。
DMA控制器数据流多通道选择,通过DMA_SxCR寄存器控制,每个数据流有多达8个通道请求可以选择,如图30.1.1.2所示。     
image003.png
图30.1.1.2 DMA数据流通道选择

外设的8个请求独立连接到每个通道,由DMA_SxCR控制数据流选择哪一个通道,每个数据流有8个通道可供选择,每次只能选择其中一个通道进行DMA传输。DMA1各数据流通道映射具体见表30.1.1.1,DMA2各数据流通道映射具体见表30.1.1.2。
image005.png
表30.1.1.1 DMA1各数据流通道映射表

image007.png
表30.1.1.2 DMA2各数据流通道映射表

30.1.2 DMA寄存器
l  DMA中断状态寄存器(DMA_LISRDMA_HISR
DMA中断状态寄存器,该寄存器总共有2个:DMA_LISR和DMA_HISR,每个寄存器管理4数据流(总共8个),DMA_LISR寄存器用于管理数据流0~3,而DMA_HISR用于管理数据流4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。
这里我们仅以DMA_LISR寄存器为例进行介绍,DMA_LISR各位描述如图30.1.2.1所示:   
image009.png
图30.1.2.1 DMA_LISR寄存器

如果开启了DMA_LISR中这些位对应的中断,则在达到条件后就会跳到中断服务函数里面去,即使没开启,我们也可以通过查询这些位来获得当前DMA传输的状态。这里我们常用的是TCIFx位,即数据流x的DMA传输完成与否标志。注意此寄存器为只读寄存器,所以在这些位被置位之后,只能通过其他的操作来清除。DMA_HISR寄存器各位描述和DMA_LISR寄存器各位描述完全一样,只是对应数据流4~7,这里我们就不列出来了。

l  DMA中断标志清除寄存器(DMA_LIFCRDMA_HIFCR
DMA中断标志清除寄存器,该寄存器同样有2个:DMA_LIFCR和DMA_HIFCR,同样是每个寄存器控制4个数据流,DMA_LIFCR寄存器用于管理数据流0~3,而DMA_HIFCR用于管理数据流4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。
这里,我们仅以DMA_LIFCR寄存器为例进行介绍,DMA_LIFCR各位描述如图30.1.2.2所示:   
image011.png
图30.1.2.2 DMA_LIFCR寄存器

DMA_LIFCR的各位就是用来清除DMA_LISR的对应位的,通过写1清除。在DMA_LISR被置位后,我们必须通过向该位寄存器对应的位写入1来清除。DMA_HIFCR的使用同DMA_LIFCR类似,这里就不做介绍了。

第三个是DMA数据流x配置寄存器(DMA_SxCR)(x=0~7,下同)。该寄存器的我们在这里就不贴出来了,见《STM32F4xx参考手册_V4(中文版).pdf》第223页9.5.5小节。该寄存器控制着DMA的很多相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能等都是通过该寄存器来设置的。所以DMA_SxCR是DMA传输的核心控制寄存器。

第四个是DMA数据流x数据项数寄存器(DMA_SxNDTR)。这个寄存器控制DMA数据流x的每次传输所要传输的数据量。其设置范围为0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为0的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA传输的进度。特别注意,这里是数据项数目,而不是指的字节数。比如设置数据位宽为16位,那么传输一次(一个项)就是2个字节。

第五个是DMA数据流x的外设地址寄存器(DMA_SxPAR)。该寄存器用来存储STM32F4外设的地址,比如我们使用串口1,那么该寄存器必须写入0x40011004(USART1_TDR的地址)。如果使用其他外设,就修改成相应外设的地址就行了。

最后一个是DMA数据流x的存储器地址寄存器,由于STM32F407的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)LED灯
LED0 – PF9
2)独立按键
KEY0 – PE4
3)串口1(PA9A10板载USB转串口芯片CH340上面)
4)正点原子 2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
5)DMA

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

30.3 程序设计
30.3.1 DMA的HAL库驱动
DMA在HAL库中的驱动代码在stm32f4xx_hal_dma.c文件(及其头文件)中。

1. HAL_DMA_Init函数
DMA的初始化函数,其声明如下:
  1. HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
复制代码

l  函数描述:
用于初始化DMA1,DMA2。

l  函数形参:
形参1是DMA_HandleTypeDef结构体类型指针变量,其定义如下:
  1. typedef struct__DMA_HandleTypeDef
  2. {
  3.   void                              *Instance;  /* 寄存器基地址 */
  4. DMA_InitTypeDef                 Init;        /* DAM通信参数 */
  5. HAL_LockTypeDef                 Lock;        /* DMA锁对象 */
  6.   __IOHAL_DMA_StateTypeDef     State;       /* DMA传输状态 */
  7.   void                              *Parent;    /* 父对象状态,HAL库处理的中间变量 */
  8. void (*XferCpltCallback)( struct__DMA_HandleTypeDef *hdma);/*DMA传输完成回调*/
  9. /* DMA一半传输完成回调 */
  10. void (*XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);
  11.   /* DMA传输完整的Memory1回调 */
  12. void (* XferM1CpltCallback)( struct__DMA_HandleTypeDef * hdma);
  13. /* DMA传输半完全内存回调 */
  14. void (*XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);
  15. /*DMA传输错误回调*/
  16. void (* XferErrorCallback)( struct__DMA_HandleTypeDef * hdma);
  17. /* DMA传输中止回调 */
  18.   void (* XferAbortCallback)( struct__DMA_HandleTypeDef * hdma);
  19.   __IO uint32_t                   ErrorCode;                /* DMA存取错误代码 */
  20.   uint32_t                      StreamBaseAddress;       /* DMA通道基地址 */
  21. uint32_t                      StreamIndex;             /* DMA通道索引 */
  22. }DMA_HandleTypeDef;
复制代码
这个结构体内容比较多,上面已注释中文翻译,下面列出几个成员说明一下。

Instance:是用来设置寄存器基地址,例如要设置的对象是串口1的发送,那么就要参考表30.1.1,串口1的DMA传输需要用到的是DMA2的数据流7,即DMA2_Stream7。

Parent:是HAL库处理中间变量,用来指向DMA通道外设句柄。

StreamBaseAddress和StreamIndex是数据流基地址和索引号,这个是HAL库处理的时候会自动计算,用户无需设置。

其他成员变量是HAL库处理过程状态标识变量,这里就不做过多讲解。

接下来我们重点介绍Init,它是DMA_InitTypeDef结构体类型变量,该结构体定义如下:
  1. typedef struct
  2. {
  3.   uint32_t Channel;               /* 传输通道,例如:DMA_CHANEL_4 */     
  4.   uint32_t Direction;             /* 传输方向,例如存储器到外设DMA_MEMORY_TO_PERIPH */
  5.   uint32_t PeriphInc;             /* 外设(非)增量模式,非增量模式DMA_PINC_DISABLE */  
  6.   uint32_t MemInc;                /* 存储器(非)增量模式,增量模式DMA_MINC_ENABLE */  
  7.   uint32_tPeriphDataAlignment; /* 外设数据大小:8/16/32位 */
  8.   uint32_t MemDataAlignment;    /* 存储器数据大小:8/16/32位 */
  9.   uint32_t Mode;                  /* 模式:外设流控模式/循环模式/普通模式 */   
  10.   uint32_t Priority;             /* DMA优先级:低/中/高/非常高 */
  11.   uint32_t FIFOMode;             /* FIFO模式开启或者禁止 */
  12.   uint32_t FIFOThreshold;       /* FIFO阈值选择 */
  13.   uint32_t MemBurst;             /* 存储器突发模式:单次/4个节拍/8个节拍/16个节拍 */  
  14.   uint32_t PeriphBurst;          /* 外设突发模式:单次/4个节拍/8个节拍/16个节拍 */   
  15. }DMA_InitTypeDef;
复制代码
我们通过该结构体成配置DMA_SxCR寄存器和DMA_SxFCR寄存器的相应位。

l  函数返回值:
HAL_StatusTypeDef枚举类型的值。

DMA的方式传输串口数据的配置步骤
1)使能DMA时钟。
DMA的时钟使能是通过AHB1ENR寄存器来控制的,这里我们要先使能时钟,才可以配置DMA相关寄存器。HAL库方法为:
  1. __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
  2. __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
复制代码
2)初始化DMA
调用HAL_DMA_Init函数初始化DMA的相关参数,包括配置通道,外设地址,存储器地址,传输数据量等。

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发送,启动传输。
串口1的DMA发送实际是串口控制寄存器CR3的位7来控制的,在HAL库中操作该寄存器来使能串口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);  /* 恢复 */
复制代码
4)查询DMA传输状态。
在DMA传输过程中,我们要查询DMA传输通道的状态,使用的方法是通过检测DMA寄存器的相关位实现:
  1. __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7);
复制代码
获取当前传输剩余数据量:
  1. __HAL_DMA_GET_COUNTER(&g_dma_handle);
复制代码
同样,我们也可以设置对应的DMA数据流传输的数据量大小,函数为:
  1. __HAL_DMA_SET_COUNTER (&g_dma_handle, 1000);
复制代码
DMA相关的库函数我们就讲解到这里,大家可以查看HAL库中文手册详细了解。

5DMA中断使用方法。
DMA中断对于每个流都有一个中断服务函数,比如DMA2_Stream7的中断服务函数为DMA2_Stream7_IRQHandler。HAL库提供了通用DMA中断处理函数HAL_DMA_IRQHandler,在该函数内部,会对DMA传输状态进行分析,然后调用相应的中断处理回调函数:
  1. voidHAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);     /* 发送完成回调函数 */
  2. voidHAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);/* 发送一半回调函数 */
  3. voidHAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);     /* 接收完成回调函数 */
  4. voidHAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);/* 接收一半回调函数 */
  5. voidHAL_UART_ErrorCallback(UART_HandleTypeDef *huart);      /* 传输出错回调函数 */
复制代码
30.3.2 程序流程图
QQ截图20230731172547.png
图30.3.2.1 DMA实验程序流程图

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

dma.h头文件只有函数得声明,就不解释了,我们直接介绍dma.c的程序。下面是与DMA初始化相关的函数,其定义如下:
  1. /**
  2. *@brief       串口TX DMA初始化函数
  3. *  @note      这里的传输形式是固定的, 这点要根据不同的情况来修改
  4. *             从存储器 -> 外设模式/8位数据宽度/存储器增量模式
  5. *
  6. *@param       dma_stream_handle : DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
  7. *@retval      无
  8. */
  9. void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch)
  10. {
  11. /* 得到当前stream是属于DMA2还是DMA1 */
  12.     if ((uint32_t)dma_stream_handle > (uint32_t)DMA2)     
  13.     {
  14.        __HAL_RCC_DMA2_CLK_ENABLE();                     /* DMA2时钟使能 */
  15.     }
  16.     else
  17.     {
  18.        __HAL_RCC_DMA1_CLK_ENABLE();                     /* DMA1时钟使能 */
  19.     }
  20. /* 将DMA与USART1联系起来(发送DMA) */
  21.    __HAL_LINKDMA(&g_uart1_handle,hdmatx, g_dma_handle);  
  22.     /* Tx DMA配置 */
  23.    g_dma_handle.Instance =dma_stream_handle;                    /* 数据流选择 */
  24.    g_dma_handle.Init.Channel = ch;                                 /* DMA通道选择 */
  25.    g_dma_handle.Init.Direction =DMA_MEMORY_TO_PERIPH;         /* 存储器到外设 */
  26.    g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;              /* 外设非增量模式 */
  27.    g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;                   /* 存储器增量模式 */
  28.    g_dma_handle.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;/*外设数据长度:8位*/
  29. g_dma_handle.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;  /*存储器数据长度:8位*/
  30.    g_dma_handle.Init.Mode = DMA_NORMAL;                           /* 外设流控模式 */
  31.    g_dma_handle.Init.Priority =DMA_PRIORITY_MEDIUM;           /* 中等优先级 */
  32.    g_dma_handle.Init.FIFOMode =DMA_FIFOMODE_DISABLE;          /* 关闭FIFO模式 */
  33.    g_dma_handle.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL; /* FIFO阈值配置 */
  34.    g_dma_handle.Init.MemBurst = DMA_MBURST_SINGLE;          /* 存储器突发单次传输 */
  35.    g_dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE;      /* 外设突发单次传输 */
  36.    HAL_DMA_DeInit(&g_dma_handle);
  37.    HAL_DMA_Init(&g_dma_handle);
  38. }
复制代码
该函数是一个通用的DMA配置函数,DMA1/DMA2的所有通道,都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。该函数在外部只能修改DMA数据流编号和通道号,更多的其他设置只能在该函数内部修改。对照前面的配置步骤的详细讲解来分析这部分代码即可。

2. main.c代码
main.c代码如下:
  1. const uint8_tTEXT_TO_SEND[] ={"正点原子 STM32 DMA 串口实验"};/* 要循环发送的字符串 */
  2. #define SEND_BUF_SIZE       (sizeof(TEXT_TO_SEND) + 2) * 200      /* 发送数据长度*/
  3. uint8_t g_sendbuf[SEND_BUF_SIZE];   /* 发送数据缓冲区 */
  4. int main(void)
  5. {
  6.     uint8_t  key = 0;
  7.     uint16_t i, k;
  8.     uint16_t len;
  9.     uint8_t  mask = 0;
  10.     float pro = 0;                                /* 进度 */
  11.     HAL_Init();                                   /* 初始化HAL库 */
  12.    sys_stm32_clock_init(336, 8, 2, 7);         /* 设置时钟, 168Mhz */
  13.     delay_init(168);                                /* 延时初始化 */
  14.     usart_init(115200);                            /* 串口初始化为115200 */
  15.     led_init();                                      /* 初始化LED */
  16.     lcd_init();                                     /* 初始化LCD */
  17.     key_init();                                     /* 初始化按键 */
  18.     dma_init(DMA2_Stream7,DMA_CHANNEL_4);     /* 初始化串口1 TX DMA */
  19.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  20.     lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
  21.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  22.     lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
  23.     len = sizeof(TEXT_TO_SEND);
  24.     k = 0;
  25.    
  26.     for (i = 0; i < SEND_BUF_SIZE; i++)         /* 填充ASCII字符集数据 */
  27.     {
  28.         if (k >= len)                            /* 入换行符 */
  29.         {
  30.             if (mask
  31.             {
  32.                g_sendbuf = 0x0a;
  33.                 k = 0;
  34.             }
  35.             else
  36.             {
  37.                g_sendbuf = 0x0d;
  38.                mask++;
  39.             }
  40.         }
  41.         else                                      /* 复制TEXT_TO_SEND语句 */
  42.         {
  43.             mask = 0;
  44.            g_sendbuf = TEXT_TO_SEND[k];
  45.             k++;
  46.         }
  47.     }
  48.     i = 0;
  49.     while (1)
  50.     {
  51.         key = key_scan(0);
  52.        if (key == KEY0_PRES)   /* KEY0按下 */
  53.        {
  54.            printf("\r\nDMADATA:\r\n");
  55.            lcd_show_string(30, 130, 200, 16, 16, "StartTransimit....", BLUE);
  56.            lcd_show_string(30, 150, 200, 16, 16, "   %", BLUE);    /* 显示百分号 */
  57. /* 开始一次DMA传输! */
  58.            HAL_UART_Transmit_DMA(&g_uart1_handle,g_sendbuf,SEND_BUF_SIZE);
  59.            /* 等待DMA传输完成,此时我们来做另外一些事情,比如点灯
  60.             * 实际应用中,传输数据期间,可以执行另外的任务 */
  61.            while (1)
  62.            {
  63. /* 等待DMA2_Steam7传输完成 */
  64.                 if (__HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7))
  65.                 {
  66. /* 清除DMA2_Steam7传输完成标志 */
  67.                     __HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7);
  68.                     HAL_UART_DMAStop(&g_uart1_handle);  /* 传输完成以后关闭串口DMA */
  69.                     break;
  70.                 }
  71.                 Pro =__HAL_DMA_GET_COUNTER(&g_dma_handle);/*得到当前还剩余多少个数据*/
  72.                 len = SEND_BUF_SIZE;      /* 总长度 */
  73.                 pro = 1 - (pro / len);    /* 得到百分比 */
  74.                 pro *= 100;                 /* 扩大100倍 */
  75.                 lcd_show_num(30, 150, pro, 3, 16, BLUE);
  76.            }
  77.            lcd_show_num(30, 150, 100, 3, 16, BLUE);    /* 显示100% */
  78. /* 提示传送完成 */
  79.            lcd_show_string(30, 130, 200, 16, 16, "TransimitFinished!", BLUE);
  80.        }
  81.        i++;
  82.        delay_ms(10);
  83.        if (i == 20)
  84.        {
  85.            LED0_TOGGLE();  /* LED0闪烁,提示系统正在运行 */
  86.            i = 0;
  87.        }
  88.     }
  89. }
复制代码
main函数的流程大致是:先初始化发送数据缓冲区g_sendbuf的值,然后通过KEY0开启串口DMA发送,在发送过程中,通过__HAL_DMA_GET_COUNTER(&g_dma_handle)获取当前还剩余的数据量来计算传输百分比,最后在传输结束之后清除相应标志位,提示已经传输完成。

30.4 下载验证
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图30.4.1所示:     
image015.png
图30.4.1 DMA实验测试图

我们打开串口调试助手,然后按KEY0,可以看到串口显示如图30.4.2所示的内容:     
image017.png
图30.4.2 串口收到的数据内容

可以看到串口收到了开发板发送过来的数据,同时可以看到TFTLCD上显示了进度等信息,如图30.4.3所示:     
image019.png
30.4.3 DMA串口数据传输中

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

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2025-2-24 08:08

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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