天天看点

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

1)资料下载:点击资料即可下载

2)对正点原子Linux感兴趣的同学可以加群讨论:935446741

3)关注正点原子公众号,获取最新资料更新

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

本章我们将向大家介绍 STM32F4 的 DMA。在本章中,我们将利用 STM32F4 的 DMA 来

实现串口数据传送,并在 TFTLCD 模块上显示当前的传送进度。本章分为如下几个部分:

28.1 STM32F4 DMA 简介

28.2 硬件设计

28.3 软件设计

28.4 下载验证

28.1 STM32F4 DMA 简介

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接

控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备

开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每

一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8

个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

STM32F4 的 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 错误、

直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

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

STM32F4 的 DMA 控制器框图如图 28.1.1 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

图 28.1.1 DMA 控制器框图

DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来

启动 AHB 事务。它可以执行下列事务:

1,外设到存储器的传输

3, 存储器到外设的传输

3,存储器到存储器的传输

这里特别注意一下,存储器到存储器需要外设接口可以访问存储器,而仅 DMA2 的外设接

口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持。

图 28.1.1 中数据流的多通道选择,是通过 DMA_SxCR 寄存器控制的,如图 28.1.2 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

图 28.1.2 DMA 数据流通道选择

从上图可以看出,DMA_SxCR 控制数据流到底使用哪一个通道,每个数据流有 8 个通道可

供选择,每次只能选择其中一个通道进行 DMA 传输。接下来,我们看看 DMA2 的各数据流通

道映射表,如表 28.1.1 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

表 28.1.1 DMA2 各数据流通道映射表

上表就列出了 DMA2 所有可能的选择情况,来总共 64 种组合,比如本章我们要实现串口 1

的 DMA 发送,即 USART1_TX,就必须选择 DMA2 的数据流 7,通道 4,来进行 DMA 传输。这里注

意一下,有的外设(比如 USART1_RX)可能有多个通道可以选择,大家随意选择一个就可以了。

接下来,我们介绍一下 DMA 设置相关的几个寄存器。

第一个是 DMA 中断状态寄存器,该寄存器总共有 2 个:DMA_LISR 和 DMA_HISR,每个寄存器

管理 4 数据流(总共 8 个),DMA_LISR 寄存器用于管理数据流 0~3,而 DMA_HISR 用于管理数据

流 4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。

这里,我们仅以 DMA_LISR 寄存器为例进行介绍,DMA_LISR 各位描述如图 28.1.3 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

图 28.1.3 DMA_LISR 寄存器各位描述

如果开启了 DMA_LISR 中这些位对应的中断,则在达到条件后就会跳到中断服务函数里面去,

即使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx

位,即数据流 x 的 DMA 传输完成与否标志。注意此寄存器为只读寄存器,所以在这些位被置位

之后,只能通过其他的操作来清除。DMA_HISR 寄存器各位描述通 DMA_LISR 寄存器各位描述完

全一样,只是对应数据流 4~7,这里我们就不列出来了。

第二个是 DMA 中断标志清除寄存器, 该寄存器同样有 2 个:DMA_LIFCR 和 DMA_HIFCR,同样

是每个寄存器控制 4 个数据流,DMA_LIFCR 寄存器用于管理数据流 0~3,而 DMA_ HIFCR 用于管

理数据流 4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。

这里,我们仅以 DMA_LIFCR 寄存器为例进行介绍,DMA_LIFCR 各位描述如图 28.1.4 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

图 28.1.4 DMA_LIFCR 寄存器各位描述

DMA_LIFCR 的各位就是用来清除 DMA_LISR 的对应位的,通过写 1 清除。在 DMA_LISR 被置

位后,我们必须通过向该位寄存器对应的位写入 1 来清除。DMA_HIFCR 的使用同 DMA_LIFCR 类

似,这里就不做介绍了。

第三个是 DMA 数据流 x 配置寄存器(DMA_SxCR)(x=0~7,下同)。该寄存器的我们在这里就

不贴出来了,见《STM32F4xx 中文参考手册》第 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_DR)。如

果使用其他外设,就修改成相应外设的地址就行了。

最后一个是 DMA 数据流 x 的存储器地址寄存器,由于 STM32F4 的 DMA 支持双缓存,所以存

储器地址寄存器有两个:DMA_SxM0AR 和 DMA_SxM1AR,其中 DMA_SxM1AR 仅在双缓冲模式下,才

有效。本章我们没用到双缓冲模式,所以存储器地址寄存器就是:DMA_SxM0AR,该寄存器和

DMA_CPARx 差不多,但是是用来放存储器的地址的。比如我们使用 SendBuf[8200]数组来做存储

器,那么我们在 DMA_SxM0AR 中写入&SendBuff 就可以了。

DMA 相关寄存器就为大家介绍到这里,关于这些寄存器的详细描述,请参考《STM32F4xx

中文参考手册》第 9.5 节。本章我们要用到串口 1 的发送,属于 DMA2 的数据流 7,通道 4,接

下来我们就介绍下使用库函数的配置步骤和方法。首先这里我们需要指出的是,DMA 相关的库

函数支持在文件 stm32f4xx_dma.c 以及对应的头文件 stm32f4xx_dac.h 中。具体步骤如下:

1)使能 DMA2 时钟,并等待数据流可配置。

DMA 的时钟使能是通过 AHB1ENR 寄存器来控制的,这里我们要先使能时钟,才可以配置 DMA

相关寄存器。HAL 库方法为:

__HAL_RCC_DMA2_CLK_ENABLE();//DMA2 时钟使能

__HAL_RCC_DMA1_CLK_ENABLE();//DMA1 时钟使能

2) 初始化 DMA2 数据流 7,包括配置通道,外设地址,存储器地址,传输数据量等。

DMA 的某个数据流各种配置参数初始化是通过 HAL_DMA_Init 函数实现的,该函数声明为:

HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);

该函数只有一个 DMA_HandleTypeDef 结构体指针类型入口参数,结构体定义为:

typedef struct __DMA_HandleTypeDef

{

DMA_Stream_TypeDef *Instance;

DMA_InitTypeDef Init;

HAL_LockTypeDef Lock;

__IO HAL_DMA_StateTypeDef State;

void *Parent;

void (* XferCpltCallback)(

struct __DMA_HandleTypeDef * hdma);

void (* XferHalfCpltCallback)(

struct __DMA_HandleTypeDef * hdma);

void (* XferM1CpltCallback)(

struct __DMA_HandleTypeDef * hdma);

void (* XferErrorCallback)(

struct __DMA_HandleTypeDef * hdma);

__IO uint32_t ErrorCode;

uint32_t StreamBaseAddress;

uint32_t StreamIndex;

}DMA_HandleTypeDef;

成员变量 Instance 是用来设置寄存器基地址,例如要设置为 DMA2 的数据流 7,那么取值

为 DMA2_Stream7。

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

成员变量 XferCpltCallback(传输完成回调函数), XferHalfCpltCallback(半传输完成

回调函数), XferM1CpltCallback(Memory1 传输完成回调函数)和 XferErrorCallback(传输

错误回调函数)是四个函数指针,用来指向回调函数入口地址。

成员变量 StreamBaseAddress 和 StreamIndex 是数据流基地址和索引号,这个是 HAL 库处

理的时候会自动计算,用户无需设置。

其他成员变量 HAL 库处理过程状态标识变量,这里就不做过多讲解。接下来我们着重看看

成员变量 Init,它是 DMA_InitTypeDef 结构体类型,该结构体定义为:

typedef struct

{

uint32_t Channel; //通道,例如:DMA_CHANNEL_4

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;

该结构体成员变量非常多,但是每个成员变量配置的基本都是 DMA_SxCR 寄存器和

DMA_SxFCR 寄存器的相应位。我们把结构体各个成员变量的含义都通过注释的方式列出来了。

例如本实验我们要用到 DMA2_Stream7 的 DMA_CHANNEL_4,把内存中数组的值发送到串口外设发

送寄存器 DR,所以方向为存储器到外设 DMA_MEMORY_TO_PERIPH,一个一个字节发送,需要数字

索引自动增加,所以是存储器增量模式 DMA_MINC_ENABLE,存储器和外设的字宽都是字节 8 位。

具体配置如下:

DMA_HandleTypeDef UART1TxDMA_Handler;

//DMA 句柄

UART1TxDMA_Handler.Instance= DMA2_Stream7; //数据流选择

UART1TxDMA_Handler.Init.Channel=DMA_CHANNEL_4; //通道选择

UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设

UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式

UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式

UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设:8 位

UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存储器:8 位

UART1TxDMA_Handler.Init.Mode=DMA_NORMAL; //普通模式

UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级

UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;

UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;

UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发单次传输

UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输

这里大家要注意,HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用

一个宏定义标识符,来连接 DMA 和外设句柄。例如要使用串口 DMA 发送,所以方式为:

__HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

其中 UART1_Handler 是串口初始化句柄,我们在 usart.c 中定义过了。UART1TxDMA_Handler

是 DMA 初始化句柄。hdmatx 是外设句柄结构体的成员变量,在这里实际就是 UART1_Handler 的

成员变量。在 HAL 库中,任何一个可以使用 DMA 的外设,它的初始化结构体句柄都会有一个

DMA_HandleTypeDef 指针类型的成员变量,是 HAL 库用来做相关指向的。Hdmatx 就是

DMA_HandleTypeDef 结构体指针类型。

这 句 话 的 含 义 就 是 把 UART1_Handler 句 柄 的 成 员 变 量 hdmatx 和 DMA 句 柄

UART1TxDMA_Handler 连接起来,是纯软件处理,没有任何硬件操作。

这里我们就点到为止,如果大家要详细了解 HAL 库指向关系,请查看本实验宏定义标识符

__HAL_LINKDMA 的定义和调用方法,就会很清楚了。

3)使能串口 1 的 DMA 发送

串口 1 的 DMA 发送实际是串口控制寄存器 CR3 的位 7 来控制的,在 HAL 库中,多次操作该

寄存器来使能串口 DMA 发送,但是它并没有提供一个独立的使能函数,所以这里我们可以通过

直接操作寄存器方式来实现:

USART1->CR3 |= USART_CR3_DMAT;//使能串口 1 的 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)使能 DMA2 数据流 7,启动传输。

使能 DMA 数据流的函数为:

HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,

uint32_t DstAddress, uint32_t DataLength);

这个函数比较好理解,第一个参数是 DMA 句柄,第二个是传输源地址,第三个是传输目标

地址,第四个是传输的数据长度。

通过以上 4 步设置,我们就可以启动一次 USART1 的 DMA 传输了。

5)查询 DMA 传输状态

在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的方法是:

__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);

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

__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);

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

__HAL_DMA_SET_COUNTER(&UART1TxDMA_Handler,1000);

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

6)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);//传输出错回调函数

对于串口 DMA 开启,使能数据流,启动传输,这些步骤,如果使用了中断,可以直接调

用 HAL 库函数 HAL_USART_Transmit_DMA,该函数声明如下:

HAL_StatusTypeDef HAL_USART_Transmit_DMA(USART_HandleTypeDef *husart,

uint8_t *pTxData, uint16_t Size);

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

28.2 硬件设计

所以本章用到的硬件资源有:

1) 指示灯 DS0

2) KEY0 按键

3) 串口

4) TFTLCD 模块

5) DMA

本章我们将利用外部按键 KEY0 来控制 DMA 的传送,每按一次 KEY0,DMA 就传送一次数据到

USART1,然后在 TFTLCD 模块上显示进度等信息。DS0 还是用来做为程序运行的指示灯。

本章实验需要注意 P6 口的 RXD 和 TXD 是否和 PA9 和 PA10 连接上,如果没有,请先连接。

28.3 软件设计

打开本章的实验工程可以看到,我们在 FWLIB 分组下面增加了 DMA 支持文件

stm32f4xx_hal_dma.c,同时引入了 stm32f4xx_hal_dma.h 头文件支持。在 HARDWARE 分组下

面我们新增了 dma.c 以及对应头文件 dma.h 用来存放 dma 相关的函数和定义。

打开 dma.c 文件,代码如下:

DMA_HandleTypeDef UART1TxDMA_Handler; //DMA 句柄

//DMAx 的各通道配置

//这里的传输形式是固定的,这点要根据不同的情况来修改

//从存储器->外设模式/8 位数据宽度/存储器增量模式

//DMA_Streamx:DMA 数据流,DMA1_Stream0~7/DMA2_Stream0~7

//chx:DMA 通道选择,@ref DMA_channel DMA_CHANNEL_0~DMA_CHANNEL_7

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx)

{

if((u32)DMA_Streamx>(u32)DMA2)//得到当前 stream 是属于 DMA2 还是 DMA1

{

__HAL_RCC_DMA2_CLK_ENABLE();//DMA2 时钟使能

}else

{

__HAL_RCC_DMA1_CLK_ENABLE();//DMA1 时钟使能

}

__HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

//将 DMA 与 USART1 联系起来(发送 DMA)

//Tx DMA 配置

UART1TxDMA_Handler.Instance=DMA_Streamx; //数据流选择

UART1TxDMA_Handler.Init.Channel=chx; //通道选择

UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设

UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式

UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式

UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;

//外设数据长度:8 位

UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;

//存储器数据长度:8 位

UART1TxDMA_Handler.Init.Mode=DMA_NORMAL; //外设普通模式

UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级

UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;

UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;

UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发单次传输

UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE;//外设突发单次传输

HAL_DMA_DeInit(&UART1TxDMA_Handler);

HAL_DMA_Init(&UART1TxDMA_Handler);

}

//开启一次 DMA 传输

//huart:串口句柄

//pData:传输的数据指针

//Size:传输的数据量

void MYDMA_USART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData,

uint16_t Size)

{

HAL_DMA_Start(huart->hdmatx, (u32)pData, (uint32_t)&huart->Instance->DR,

Size);//开启 DMA 传输

huart->Instance->CR3 |= USART_CR3_DMAT;//使能串口 DMA 发送

}

该部分代码仅仅 2 个函数,MYDMA_Config 函数,基本上就是按照我们上面介绍的步骤来初

始化 DMA 的,该函数是一个通用的 DMA 配置函数,DMA1/DMA2 的所有通道,都可以利用该函数

配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。该函数在外部只能修改

DMA 及数据流编号、通道号、外设地址、存储器地址(SxM0AR)传输数据量等几个参数,更多的

其他设置只能在该函数内部修改。MYDMA_Enable函数就是设置DMA缓存大小并且使能DMA

数据流。对照前面的配置步骤的详细讲解看看这部分代码即可。

dma.h 头文件内容比较简单,主要是函数申明,这里我们不细说。

接下来我们看看那 main 函数如下:

#define SEND_BUF_SIZE 8200

u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区

const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"};

int main(void)

{

u16 i;u8 t=0;u8 j,mask=0;float pro=0;

HAL_Init(); //初始化 HAL 库

Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz

delay_init(168); //初始化延时函数

uart_init(115200); //初始化 USART

usmart_dev.init(84);

//初始化 USMART

LED_Init();

//初始化 LED

KEY_Init();

//初始化 KEY

LCD_Init();

//初始化 LCD

MYDMA_Config(DMA2_Stream7,DMA_CHANNEL_4);//初始化 DMA

POINT_COLOR=RED;

LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");

LCD_ShowString(30,70,200,16,16,"DMA TEST");

LCD_ShowString(30,90,200,16,16,"[email protected]");

LCD_ShowString(30,110,200,16,16,"2017/4/13");

LCD_ShowString(30,130,200,16,16,"KEY0:Start");

POINT_COLOR=BLUE;//设置字体为蓝色

//显示提示信息

j=sizeof(TEXT_TO_SEND);

for(i=0;i<SEND_BUF_SIZE;i++)//填充 ASCII 字符集数据

{

if(t>=j)//加入换行符

{

if(mask)

{

SendBuff[i]=0x0a;

t=0;

}else

{

SendBuff[i]=0x0d;

mask++;

}

}else//复制 TEXT_TO_SEND 语句

{

mask=0;

SendBuff[i]=TEXT_TO_SEND[t];

t++;

}

}

POINT_COLOR=BLUE;//设置字体为蓝色

i=0;

while(1)

{

t=KEY_Scan(0);

if(t==KEY0_PRES) //KEY0 按下

{

printf("rnDMA DATA:rn");

LCD_ShowString(30,150,200,16,16,"Start Transimit....");

LCD_ShowString(30,170,200,16,16," %") ; //显示百分号

HAL_UART_Transmit_DMA(&UART1_Handler,SendBuff,SEND_BUF_SIZE);

//启动传输,使能串口 1 的 DMA 发送 //等待 DMA 传输完成,此时我们来做

另外一些事,点灯实际应用中,传输数据期间,可以执行另外的任务

while(1)

{

if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7))

//等待 DMA2_Steam7 传输完成

{

__HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler,

DMA_FLAG_TCIF3_7);//清除 DMA2_Steam7 传输完成标志

HAL_UART_DMAStop(&UART1_Handler);

//传输完成以后关闭串口 DMA

break;

}

pro=__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);

//得到当前还剩余多少个数据

pro=1-pro/SEND_BUF_SIZE; //得到百分比

pro*=100;

//扩大 100 倍

LCD_ShowNum(30,170,pro,3,16);

}

LCD_ShowNum(30,170,100,3,16);//显示 100%

LCD_ShowString(30,150,200,16,16,"Transimit Finished!");

//提示传送完成

}

i++;

delay_ms(10);

if(i==20)

{

LED0=!LED0;//提示系统正在运行

i=0;

}

}

}

main 函数的流程大致是:先初始化内存 SendBuff 的值,然后通过 KEY0 开启串口 DMA 发送,

在发送过程中,通过__HAL_DMA_GET_COUNTER()函数获取当前还剩余的数据量来计算传输百分比,

最后在传输结束之后清除相应标志位,提示已经传输完成。这里还有一点要注意,因为是使用

的串口 1 DMA 发送,所以代码中使用 HAL_UART_Transmit_DMA 函数开启串口的 DMA 发送:

至此,DMA 串口传输的软件设计就完成了。

28.4 下载验证

在代码编译成功之后,我们通过串口下载代码到 ALIENTEK 探索者 STM32F4 开发板上,

可以看到 LCD 显示如图 28.4.1 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

图 28.4.1 DMA 实验测试图

伴随 DS0 的不停闪烁,提示程序在运行。我们打开串口调试助手,然后按 KEY0,可以看

到串口显示如图 28.4.2 所示的内容:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

图 28.4.2 串口收到的数据内容

可以看到串口收到了探索者 STM32F4 开发板发送过来的数据,同时可以看到 TFTLCD 上显

示了进度等信息,如图 28.4.3 所示:

stm32f407 6个串口dma_正点原子【STM32-F407探索者】第二十八章 DMA 实验

28.4.3 DMA 串口数据传输中

至此,我们整个 DMA 实验就结束了,希望大家通过本章的学习,掌握 STM32F4 的 DMA

使用。DMA 是个非常好的功能,它不但能减轻 CPU 负担,还能提高数据传输速度,合理的应

用 DMA,往往能让你的程序设计变得简单。

继续阅读