第二十八章 DMA实验
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0
[/mw_shl_code]
本章我们将向大家介绍
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所示:
图
28.1.1
DMA控制器框图
DMA控制器执行直接存储器传输:因为采用
AHB主总线,它可以控制
AHB总线矩阵来启动
AHB事务。它可以执行下列事务:
1,外设到存储器的传输
1, 存储器到外设的传输
3,存储器到存储器的传输
这里特别注意一下,存储器到存储器需要外设接口可以访问存储器,而仅
DMA2的外设接口可以访问存储器,所以仅
DMA2控制器支持存储器到存储器的传输,
DMA1不支持。
图
28.1.1中数据流的多通道选择,是通过
DMA_SxCR寄存器控制的,如图
28.1.2所示:
图
28.1.2
DMA数据流通道选择
从上图可以看出,
DMA_SxCR控制数据流到底使用哪一个通道,每个数据流有
8个通道可供选择,每次只能选择其中一个通道进行
DMA传输。接下来,我们看看
DMA2的各数据流通道映射表,如表
28.1.1所示:
表
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所示:
图
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所示:
图
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相关寄存器。所以先要使能
DMA2的时钟。另外,要对配置寄存器(
DMA_SxCR)进行设置,必须先等待其最低位为
0(也就是
DMA传输禁止了),才可以进行配置。
库函数使能
DMA2时钟的方法为:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
等待
DMA可配置,也就是等待
DMA_SxCR寄存器最低位为
0的方法为:
while (DMA_GetCmdStatus(DMA_Streamx)
!= DISABLE){}//等待
DMA可配置
2) 初始化DMA2数据流7,包括配置通道,外设地址,存储器地址,传输数据量等。
DMA的某个数据流各种配置参数初始化是通过
DMA_Init函数实现的:
void DMA_Init(DMA_Stream_TypeDef*
DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);
函数的第一个参数是指定初始化的
DMA的数据流编号,这个很容易理解。入口参数范围为:
DMAx_Stream0~ DMAx_Stream7(x=1,2)。下面我们主要看看第二个参数。跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,下面我们来看看
DMA_InitTypeDef结构体的定义:
typedef struct
{
uint32_t DMA_Channel;
uint32_t DMA_PeripheralBaseAddr;
uint32_t DMA_Memory0BaseAddr;
uint32_t DMA_DIR;
uint32_t DMA_BufferSize;
uint32_t DMA_PeripheralInc;
uint32_t DMA_MemoryInc;
uint32_t DMA_PeripheralDataSize;
uint32_t DMA_MemoryDataSize;
uint32_t DMA_Mode;
uint32_t DMA_Priority;
uint32_t DMA_FIFOMode;
uint32_t DMA_FIFOThreshold;
uint32_t DMA_MemoryBurst;
uint32_t DMA_PeripheralBurst;
}DMA_InitTypeDef;
这个结构体的成员比较多,但是每个成员变量的意义我们在前面基本都已经讲解过,这里我们一一做个简要的介绍。
第一个参数
DMA_Channel用来设置
DMA数据流对应的通道。前面我们已经讲解过,可供每个数据流选择的通道请求多达8个,取值范围为:DMA_Channel_0~
DMA_Channel_7。
第二个参数
DMA_PeripheralBaseAddr用来设置
DMA传输的外设基地址,比如要进行串口
DMA传输,那么外设基地址为串口接受发送数据存储器
USART1->DR的地址,表示方法为
&USART1->DR。
第三个参数
DMA_Memory0BaseAddr为内存基地址,也就是我们存放
DMA传输数据的内存地址。
第四个参数
DMA_DIR设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数据发送到外设,也就是外设是源地还是目的地,这里我们设置为从内存读取数据发送到串口,所以外设自然就是目的地了,所以选择值为
DMA_DIR_PeripheralDST。
第五个参数
DMA_BufferSize设置一次传输数据量的大小,这个很容易理解。
第六个参数
DMA_PeripheralInc设置传输数据的时候外设地址是不变还是递增。如果设置为递增,那么下一次传输的时候地址加
1,这里因为我们是一直往固定外设地址
&USART1->DR发送数据,所以地址不递增,值为
DMA_PeripheralInc_Disable;
第七个参数
DMA_MemoryInc设置传输数据时候内存地址是否递增。这个参数和
DMA_PeripheralInc意思接近,只不过针对的是内存。这里我们的场景是将内存中连续存储单元的数据发送到串口,毫无疑问内存地址是需要递增的,所以值为
DMA_MemoryInc_Enable。
第八个参数
DMA_PeripheralDataSize用来设置外设的数据长度是为字节传输(
8bits),半字传输
(16bits)还是字传输
(32bits),这里我们是
8位字节传输,所以值设置为
DMA_PeripheralDataSize_Byte。
第九个参数
DMA_MemoryDataSize是用来设置内存的数据长度,和第七个参数意思接近,这里我们同样设置为字节传输
DMA_MemoryDataSize_Byte。
第十个参数
DMA_Mode用来设置
DMA模式是否循环采集,也就是说,比如我们要从内存中采集
64个字节发送到串口,如果设置为重复采集,那么它会在
64个字节采集完成之后继续从内存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。所以设置值为
DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,不会中断,大家在实验中可以修改这个参数测试一下。
第十一个参数
DMA_Priority是用来设置
DMA通道的优先级,有低,中,高,超高三种模式,这个在前面讲解过,这里我们设置优先级别为中级,所以值为
DMA_Priority_Medium。优先级可以随便设置,因为我们只有一个数据流被开启了。假设有多个数据流开启(最多
8个),那么就要设置优先级了,
DMA仲裁器将根据这些优先级的设置来决定先执行那个数据流的
DMA。优先级越高的,越早执行,当优先级相同的时候,根据硬件上的编号来决定哪个先执行(编号越小越优先)。
第十二个参数
DMA_FIFOMode用来设置是否开启
FIFO模式。这里我们不开启所以选择
DMA_FIFOMode_Disable。
第十三个参数
DMA_FIFOThreshold用来选择
FIFO阈值。根据前面讲解可以为
FIFO容量的
1/4,1/2,3/4以及
1倍。这里我们实际并没有开启
FIFO模式,所以可以不关心。
第十四个参数
DMA_MemoryBurst用来配置存储器突发传输配置。可以选择为
4个节拍的增量突发传输
DMA_MemoryBurst_INC4,
8个节拍的增量突发传输
DMA_MemoryBurst_INC8,
16个街拍的增量突发传输
DMA_MemoryBurst_INC16以及单次传输
DMA_MemoryBurst_Single。
第十五个参数
DMA_PeripheralBurst用来配置外设突发传输配置。跟前面一个参数
DMA_MemoryBurst作用类似,只不过一个针对的是存储器,一个是外设。这里我们选择单次传输
DMA_PeripheralBurst_Single。
参数含义我们就给大家讲解到这里,具体详细配置,大家可以参考中文参考手册相关寄存器配置可以更加详细的了解含义。接下来我们给出上面场景的实例代码:
/* 配置
DMA Stream */
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr =
par;//DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA
存储器
0地址
DMA_InitStructure.DMA_DIR =
DMA_DIR_MemoryToPeripheral;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc =
DMA_PeripheralInc_Disable;
//外设非增量模式
DMA_InitStructure.DMA_MemoryInc =
DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte;
//外设数据长度
:8位
DMA_InitStructure.DMA_MemoryDataSize =
DMA_MemoryDataSize_Byte;
//存储器数据长度
:8位
DMA_InitStructure.DMA_Mode =
DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority =
DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode =
DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold =
DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst =
DMA_MemoryBurst_Single;//单次传输
DMA_InitStructure.DMA_PeripheralBurst =
DMA_PeripheralBurst_Single;
//外设突发单次传输
DMA_Init(DMA_Streamx,
&DMA_InitStructure);//初始化
DMA Stream
3)使能串口1的DMA发送
进行
DMA配置之后,我们就要开启串口的
DMA发送功能,使用的函数是:
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
如果是要使能串口
DMA接受,那么第二个参数修改为
USART_DMAReq_Rx即可。
4)使能DMA2数据流7,启动传输。
使能
DMA数据流的函数为:
void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx,
FunctionalState NewState)
使能
DMA2_Stream7,启动传输的方法为:
DMA_Cmd (
DMA2_Stream7,
ENABLE)
;
通过以上
4步设置,我们就可以启动一次
USART1的
DMA传输了。
5)查询DMA传输状态
在
DMA传输过程中,我们要查询
DMA传输通道的状态,使用的函数是:
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
比如我们要查询
DMA数据流
7传输是否完成,方法是:
DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7);
这里还有一个比较重要的函数就是获取当前剩余数据量大小的函数:
uint16_t
DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
比如我们要获取
DMA数据流
7还有多少个数据没有传输,方法是:
DMA_GetCurrDataCounter(DMA1_Channel4);
同样,我们也可以设置对应的
DMA数据流传输的数据量大小
,函数为:
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef*
DMAy_Streamx, uint16_t Counter);
DMA相关的库函数我们就讲解到这里,大家可以查看固件库中文手册详细了解。
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_dma.c,同时引入了
stm32f4xx_dma.h头文件支持。在
HARDWARE分组下面我们新增了
dma.c以及对应头文件
dma.h用来存放
dma相关的函数和定义。
打开
dma.c文件,代码如下:
//DMAx的各通道配置
//这里的传输形式是固定的
,这点要根据不同的情况来修改
//从存储器
->外设模式
/8位数据宽度
/存储器增量模式
//DMA_Streamx
MA数据流
,DMA1_Stream0~7/DMA2_Stream0~7
//chx
MA通道选择
, @ref
DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
mar:存储器地址
ndtr:数据传输量
void MYDMA_Config(DMA_Stream_TypeDef
*DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{
DMA_InitTypeDef
DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前
stream是属于
DMA2还是
DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx)
!= DISABLE){}//等待
DMA可配置
/* 配置
DMA Stream */
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr =
par;//DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr =
mar;//DMA 存储器
0地址
DMA_InitStructure.DMA_DIR =
DMA_DIR_MemoryToPeripheral;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc =
DMA_PeripheralInc_Disable;
//外设非增量模式
DMA_InitStructure.DMA_MemoryInc =
DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte;
//外设数据长度
:8位
DMA_InitStructure.DMA_MemoryDataSize =
DMA_MemoryDataSize_Byte;
//存储器数据长度
:8位
DMA_InitStructure.DMA_Mode =
DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority =
DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode =
DMA_FIFOMode_Disable;//FIFO模式禁止
DMA_InitStructure.DMA_FIFOThreshold =
DMA_FIFOThreshold_Full;//FIFO阈值
DMA_InitStructure.DMA_MemoryBurst =
DMA_MemoryBurst_Single;
//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst =
DMA_PeripheralBurst_Single;
//外设突发单次传输
DMA_Init(DMA_Streamx,
&DMA_InitStructure);//初始化
DMA Stream
}
//开启一次
DMA传输
//DMA_Streamx
MA数据流
,DMA1_Stream0~7/DMA2_Stream0~7
//ndtr:数据传输量
void
MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
DMA_Cmd(DMA_Streamx, DISABLE); //关闭
DMA传输
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保
DMA可以被设置
DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量
DMA_Cmd(DMA_Streamx, ENABLE); //开启
DMA传输
}
该部分代码仅仅
2个函数,
MYDMA_Config函数,基本上就是按照我们上面介绍的步骤来初始化
DMA的,该函数是一个通用的
DMA配置函数,
DMA1/DMA2的所有通道,都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位宽,传输方向等)。该函数在外部只能修改
DMA及数据流编号、通道号、外设地址、存储器地址
(SxM0AR)传输数据量等几个参数,更多的其他设置只能在该函数内部修改。
MYDMA_Enable函数就是设置
DMA缓存大小并且使能
DMA数据流。对照前面的配置步骤的详细讲解看看这部分代码即可。
dma.h头文件内容比较简单,主要是函数申明,这里我们不细说。
接下来我们看看那
main函数如下:
/*发送数据长度
,最好等于
sizeof(TEXT_TO_SEND)+2的整数倍
.*/
#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,j,mask=0;
float pro=0;//进度
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组
2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为
115200
LED_Init(); //初始化
LED
LCD_Init(); //LCD初始化
KEY_Init(); //按键初始化
/*DMA2,STEAM7,CH4,外设为串口
1,存储器为
SendBuff,长度为
:SEND_BUF_SIZE.*/
MYDMA_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)SendBuff,
SEND_BUF_SIZE);
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,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/5/6");
LCD_ShowString(30,130,200,16,16,"KEY0:Start");
POINT_COLOR=BLUE;//设置字体为蓝 {MOD}
//显示提示信息
j=sizeof(TEXT_TO_SEND);
for(i=0;i<SEND_BUF_SIZE;i++)//填充
ASCII字符集数据
{
if(t>=j)//加入换行符
{
if(mask)
{
SendBuff
=0x0a;t=0;
}else
{
SendBuff=0x0d;mask++;
}
}else//复制TEXT_TO_SEND语句
{
mask=0;
SendBuff=TEXT_TO_SEND[t];t++;
}
}
POINT_COLOR=BLUE;//设置字体为蓝 {MOD}
i=0;
while(1)
{
t=KEY_Scan(0);
if(t==KEY0_PRES)
//KEY0按下
{
printf("
DMA DATA:
");
LCD_ShowString(30,150,200,16,16,"Start Transimit....");
LCD_ShowString(30,170,200,16,16," %");//显示百分号
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE); //开始一次DMA传输!
//等待DMA传输完成,此时我们来做另外一些事,点灯
//实际应用中,传输数据期间,可以执行另外的任务
while(1)
{
if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET)
//等待DMA2_Steam7传输完成
{
DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);//清传输完成标志
break;
}
pro=DMA_GetCurrDataCounter(DMA2_Stream7);//得到当前剩余数据数
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发送,在发送过程中,通过DMA_GetCurrDataCounter()函数获取当前还剩余的数据量来计算传输百分比,最后在传输结束之后清除相应标志位,提示已经传输完成。这里还有一点要注意,因为是使用的串口1 DMA发送,所以代码中使用USART_DMACmd函数开启串口的DMA发送:
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
至此,DMA串口传输的软件设计就完成了。
28.4 下载验证
在代码编译成功之后,我们通过串口下载代码到ALIENTEK探索者STM32F4开发板上,可以看到LCD显示如图28.4.1所示:
图28.4.1 DMA实验测试图
伴随DS0的不停闪烁,提示程序在运行。我们打开串口调试助手,然后按KEY0,可以看到串口显示如图28.4.2所示的内容:
图28.4.2 串口收到的数据内容
可以看到串口收到了探索者STM32F4开发板发送过来的数据,同时可以看到TFTLCD上显示了进度等信息,如图28.4.3所示:
28.4.3 DMA串口数据传输中
至此,我们整个DMA实验就结束了,希望大家通过本章的学习,掌握STM32F4的DMA使用。DMA是个非常好的功能,它不但能减轻CPU负担,还能提高数据传输速度,合理的应用DMA,往往能让你的程序设计变得简单。
实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779
---------------------------------
对,我的F411也发现这个错误,为什么呢?
一周热门 更多>