目录
1. 什么是DMA
2. DMA的主要特性
3. DMA功能
3.1 DMA功能框图
3.2 DMA事务
3.3 通道选择
3.4 仲裁器
3.5 DMA数据流
3.6 源、目标和传输模式
3.6.1 外设到存储器模式
3.6.2 存储器到外设模式
3.6.3 存储器到存储器模式
3.7 指针递增
3.8 DMA内存占用
3.9 存储器映像
4. DMA中断
5. 初始化结构体
6. DMA相关寄存器
6.1 中断状态寄存器:DMA_LISR和DMA_HISR
6.1.1 DMA低中断状态寄存器:DMA_LISR
6.1.2 DMA高中断状态寄存器:DMA_HISR
6.2 DMA中断状态清除寄存器:DMA_LIFCR和DMA_HIFCR
6.3 DMA数据流x配置寄存器:DMA_SxCR
6.4 DMA数据流x数据项数寄存器:DMA_SxNDTR
6.5 DMA数据流x的外设地址寄存器:DMA_SxPAR
6.6 DMA数据流x的存储器地址寄存器:DMA_SxMOAR和DMA_SxM1AR
7. 库函数配置DMA
8. 实验程序
8.1 回车换行符
8.2 代码
8.2.1 main.c
8.2.2 DMA.c
8.2.3 DMA.h
1. 什么是DMA
DMA全称为:Direct Memory Access;即直接存储器访问。
DMA传输数据就是从地址A复制到地址B,地址A和地址B可以是外设和存储器之间,也可以是存储器和存储器之间。
单片机的核心是CPU,我们知道CPU无时无刻不在处理着大量的事务,其中包括转移数据、计算数据、控制程序转移等等。正如中断一样,CPU处理的事务也有轻重缓急之分,有些事情对于CPU来说并不是那么重要,比方说转移数据、存储数据、复制数据,如果通过其他功能去处理这些事,而让原本应该处理这些事件的CPU转而去处理更加重要、复杂的事务,是不是可以更好的利用CPU资源呢?
DMA就是基于这种设想而设计的,转移数据(特别是大量的数据费时费力)是可以不用CPU参与的,这部分CPU转而去处理更加复杂重要的事务,可以大大的增强CPU资源利用率。比方说我们希望资源 A 的数据复制到资源 B,是可以不用CPU参与的,只需要在资源 A 和资源 B 之间开辟一个DMA通道来复制数据即可。CPU可以转而去处理更加重要的事务。
DMA的作用就是为了解决大量数据转移过度消耗CPU资源的问题。有了DMA使得CPU更专注于更加重要实用的操作、计算、控制等。
DMA控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结合在一起,优化了系统带宽。两个DMA控制器总共有16个数据流(每个控制器8个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理DMA请求间的优先级(类似于中断中的NVIC)。
2. DMA的主要特性
- 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
- 仅支持 32 位访问的 AHB 从编程接口
- 每个DMA控制器有8个数据流,每个数据流有多达8个通道(或称请求)
- 每个数据流有单独的四级 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直接模式
- 通过硬件可以将每个数据流配置为:
- —支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
- —也支持在存储器方双缓冲的双缓冲区通道
- 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
- DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)
- 每个数据流也支持通过软件触发存储器到存储器的传输
3. DMA功能
3.1 DMA功能框图
DMA控制器执行直接存储器传输:采用AHB主总线,它可以控制AHB总线矩阵来启动AHB事务;
直接存储器执行的事务包括:外设到存储器的传输、存储器到外设的传输、存储器到存储器的传输。
DMA控制器提供两个AHB主端口:AHB存储器端口(用于连接存储器)和 AHB外设端口(用于连接外设)。但是需要注意:当执行存储器与存储器之间的传输时,AHB外设端口必须也可以访问存储器。
3.2 DMA事务
DMA事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8位、16位 或 32位)可由软件编程。
每个DMA传输包含三项操作:
- 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据。
- 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元。
- DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
在产生事件后,外设会向DMA控制器发送请求信号。DMA控制器根据通道优先级处理该请求。只要DMA控制器访问外设,DMA控制器就会向外设发送确认信号。外设获得DMA控制器的确认信号后,便会立即释放其请求。一旦外设使请求变得失效,那么DMA控制器就会释放确认信号。如果有更多的请求,外设可以启动下一个事务。
3.3 通道选择
每一个数据流都与一个DMA请求相关联,此DMA请求可以从8个可能的通道请求中选出。此选择由DMA_SxCR寄存器中的CHSEL[2:0]位控制。
3.4 仲裁器
仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的 8 个DMA数据流请求管理,并启动 外设/存储器 访问序列。
优先级管理分为两个阶段:
- 软件:每个数据流优先级都可以在DMA_SxCR寄存器中配置。分为:非常高优先级、高优先级、中优先级、低优先级。
- 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如:数据流 2 的优先级高于数据流 4 的优先级。
3.5 DMA数据流
8个DMA控制器数据流都能提供源和目标(传输的双方)之间的单向传输链路。
每个数据流配置后都可以执行:
- 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。
- 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输。
3.6 源、目标和传输模式
源传输和目标传输在整个 4GB 区域(也就是地址在0x0000 0000和0xFFFF FFFF之间)都可以寻址外设和存储器。
传输方向使用的是DMA_SxCR寄存器中的DIR[1:0]位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。
3.6.1 外设到存储器模式
使能了这个模式(将DMA_SxCR寄存器中的位EN置1)时,每次产生外设请求,数据流都会启动数据源到FIFO的传输。
FIFO简介:
FIFO用于在源数据传输到目标之前临时存储这些数据。
每个数据流都有一个独立的4字FIFO,阈值级别可由软件配置为1/4、1/2、3/4或满。为了使能FIFO阈值级别,必须通过将DMA_SxFCR寄存器中的DMDIS位置1来禁止直接模式。FIFO的结构随源与目标数据宽度而不同。
当达到了FIFO(源数据传输到目标之前临时存储)的阈值级别时,FIFO的内容移出并存储到目标中。
如果 DMA_SxNDTR 寄存器达到零、外设请求传输终止(在使用外设流控制器的情况下)或 DMA_SxCR 寄存器中的 EN 位由软件清零,传输即会停止。
在直接模式下(当 DMA_SxFCR 寄存器中的 DMDIS 值为“0”时),不使用 FIFO 的阈值级别控制:每完成一次从外设到 FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。
3.6.2 存储器到外设模式
使能了这种模式(将DMA_SxCR寄存器中的EN位置1)时,数据流会立即启动传输,从源完全填充FIFO。
每次发生外设请求,FIFO的内容都会移出并存储到目标中。当FIFO的级别小于或等于预定义的阈值级别时,将使用存储器中的数据完全重载FIFO。
如果 DMA_SxNDTR 寄存器达到零、外设请求传输终止(在使用外设流控制器的情况下)或 DMA_SxCR 寄存器中的 EN 位由软件清零,传输即会停止。
3.6.3 存储器到存储器模式
DMA通道在没有外设请求触发的情况下同样可以工作。
通过将DMA_SxCR寄存器中的使能位(EN)置1来使能数据流时,数据流会立即开始填充FIFO,直至达到阈值级别。达到阈值级别后,FIFO的内容便会移出,并存储到目标中。
如果DMA_SxNDTR寄存器达到零或DMA_SxCR寄存器中的EN位由软件清零,传输即会停止。
注意:
- 使用存储器到存储器模式时,不允许循环模式和直接模式。
- 只有DMA2控制器能够执行存储器到存储器的传输。
3.7 指针递增
根据DMA_SxCR寄存器中的PINC和MINC位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。
通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。
如果使能了递增模式,则根据在DMA_SxCR寄存器PSIZE和MSIZE位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增1(对于字节)、2(对于半字)或4(对于字)。
为了优化封装操作,可以不管AHB外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA_SxCR寄存器中的PINCOS位用于将增量偏移大小与外设AHB端口或32位地址上的数据大小对齐。PINCOS位仅对AHB外设端口有影响。
如果将PINCOS位置1,则不论PSIZE值是多少,下一次传输的地址总比前一次传输的地址递增4(自动与32位地址对齐)。但是,AHB存储器端口不受此操作影响。
如果AHB外设端口或AHB存储器端口分别请求突发事务,为了满足AMBA协议,则需要将 PINC 或 MINC 位置1。
3.8 DMA内存占用
在STM32控制器中,芯片采用Cortex-MX架构,总线结构有了很大的优化,DMA占用另外的地址总线,并不会与CPU的系统总线发生冲突。也就是说,DMA的使用不会影响CPU的运行速度。
3.9 存储器映像
我们知道计算机系统的5大组成部分是运算器、控制器、存储器、输入设备和输出设备。其中运算器和控制器通常合在一起,称为CPU。所以计算机的关键部分就是CPU和存储器。
4. DMA中断
对于每个DMA数据流,可在发生以下事件时产生中断:
- 达到半传输
- 传输完成
- 传输错误
- FIFO错误(上溢、下溢或FIFO级别错误)
- 直接模式错误
注意:在将使能控制位置 “1” 前,应将相应的事件标志清零,否则会立即产生中断。
总结:
数据的传输主要需要 1. 数据的源地址(数据从哪里发出的)2. 数据传输位置的目标地址(需要将数据传输到哪里)3. 传递数据多少的数据传输量 4. 进行多少次传输的传输模式
用户在使用DMA直接存储器传输时,去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及:源地址、目标地址、数据传输量这三个参数,需要用户设置好这三个参数,DMA控制器就会启动数据传输,当剩余传输数据量为0时,就会达到传输终点,结束DMA传输。也可以说只要剩余传输量不是0,并且DMA还处于使能状态,那么就会发生数据传输。
特殊的:DMA还存在循环传输模式,当到达传输终点时会重新启动DMA传输。
DMA的应用主要是单纯的DMA转运(不依靠CPU的情况)和ADC搭配DMA使用,其中ADC搭配DMA使用是非常普遍的。
首先理解为什么ADC需要搭配DMA使用? 单纯的ADC是可以独立进行工作的,并且通过中断服务函数获取每次采样的值,每次采样完毕后存储到数据寄存器中,但是ADC有一个致命的缺陷,也可以是不足,就是ADC每次转换得到的结果会覆盖上一次的值。而配合DMA进行传输,不仅仅可以利用DMA进行多路信号的采集,实现一次性处理,最重要的是不会出现转换结果覆盖的情况。
5. 初始化结构体
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_MemoryBaseAddr; //存储器地址
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_M2M; //存储器到存储器模式
}DMA_InitTypeDef;
1. 数据从哪里来,要到哪里去?
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_MemoryBaseAddr; //存储器地址
前两个定义数据从哪里来:M->P P->M M->M
uint32_t DMA_DIR; //传输方向
传输方向定义要到哪里去。
外设地址:DMA_CPAR 存储器地址:DMA_CMAR 传输方向:DMA_CCR:DIR
2. 数据要传多少,传的单位是什么?
uint32_t DMA_BufferSize; //传输数目
uint32_t DMA_PeripheralInc; //外设地址增量模式
uint32_t DMA_MemoryInc; //存储器地址增量模式
uint32_t DMA_PeripheralDataSize; //外设数据宽度
uint32_t DMA_MemoryDataSize; //存储器数据宽度
3. 什么时候传输结束
uint32_t DMA_Mode; //模式选择
模式选择:DMA_CCRx:CIRC
传输过半、传输完成、传输出错,DMA_ISR;
6. DMA相关寄存器
6.1 中断状态寄存器:DMA_LISR和DMA_HISR
中断状态寄存器:DMA_LISR和DMA_HISR,每个寄存器管理四个数据流(总共8个),DMA_LISR寄存器用于管理数据流0~3,而DMA_HISR用于管理数据流4~7。这两个寄存器各位的描述一模一样,只是管理的数据流不同。
6.1.1 DMA低中断状态寄存器:DMA_LISR
DMA低中断状态寄存器:DMA_LISR(DMA low interrupt status register)
位27、21、11、5 TCIFx:数据流x传输完成中断标志
0:数据流x上无传输完成事件
1:数据流x上发生传输完成事件
位26、20、10、4 HTIFx:数据流x半传输中断标志
0:数据流x上无半传输事件
1:数据流x上发生半传输事件
位25、19、9、3 TEIFx:数据流x传输错误中断标志
0:数据流x上无传输错误
1:数据流x上发生传输错误
位24、18、8、2 DMEIFx:数据流x直接模式错误中断标志
0:数据流x上无直接模式错误
1:数据流x上发生直接模式错误
位22、16、6、0 FEIFx:数据流x FIFO错误中断标志
0:数据流x上无FIFO错误事件
1:数据流x上发生FIFO错误事件
6.1.2 DMA高中断状态寄存器:DMA_HISR
DMA高中断状态寄存器:DMA_HISR(DMA high interrupt status register)
位27、21、11、5 TCIFx:数据流x传输完成中断标志
0:数据流x上无传输完成事件
1:数据流x上发生传输完成事件
位26、20、10、4 HTIFx:数据流x半传输中断标志
0:数据流x上无半传输事件
1:数据流x上发生半传输事件
位25、19、9、3 TEIFx:数据流x传输错误中断标志
0:数据流x上无传输错误
1:数据流x上发生传输错误
位24、18、8、2 DMEIFx:数据流x直接模式错误中断标志
0:数据流x上无直接模式错误
1:数据流x上发生直接模式错误
位22、16、6、0 FEIFx:数据流x FIFO错误中断标志
0:数据流x上无FIFO错误事件
1:数据流x上发生FIFO错误事件
总结:如果开启了中断状态寄存器中对应的中断,则在达成条件后就会跳到中断服务函数里面去,即使没有开启,我们也可以通过查询这些位来获得当前DMA传输的状态。我们常用的是该寄存器的TCIFx位,也就是数据流x的DMA传输完成与否标志。
需要注意的是:此寄存器为只读寄存器,所以在这些位被置位以后,只能通过其他的操作来清除。
6.2 DMA中断状态清除寄存器:DMA_LIFCR和DMA_HIFCR
DMA中断状态清除寄存器:DMA_LIFCR和DMA_HIFCR,每个寄存器控制4个数据流,DMA_LIFCR寄存器管理数据流0~3,DMA_HIFCR管理数据流4~7。同样地,每个寄存器各位的描述完全相同,只是管理的数据流不同
DMA中断状态清除寄存器:DMA_LIFCR
6.3 DMA数据流x配置寄存器:DMA_SxCR
DMA数据流x配置寄存器:DMA_SxCR(DMA stream x configuration register)
注:该寄存器是DMA传输的核心控制寄存器。该寄存器控制着DMA的很多信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能都是通过该寄存器来设置的。该寄存器具体每一位的功能可自行查看中文参考手册。
6.4 DMA数据流x数据项数寄存器:DMA_SxNDTR
DMA数据流x数据项数寄存器:DMA_SxNDTR(DMA stream x number of data register)
该寄存器控制着DMA数据流x的每次传输所要传输的数据量。其设置范围是0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为0的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA传输的进度。
6.5 DMA数据流x的外设地址寄存器:DMA_SxPAR
DMA数据流x的外设地址寄存器:DMA_SxPAR(DMA stream x peripheral address register)
该寄存器用来存储STM32F4外设的地址,比如使用的是串口1,那么对应的该寄存器必须写入(&USART1_DR)0x4001 1004。如果使用其他外设,就修改成相应外设的地址就行。
6.6 DMA数据流x的存储器地址寄存器:DMA_SxMOAR和DMA_SxM1AR
其中DMA_SxM1AR仅在双缓冲模式下,才有效。DMA_SxM0AR,该寄存器和DMA_CPARx差不多,但是是用来放存储器的地址的。比如我们使用SendBuff[8200]数组来做存储器,那么我们在DMA_SxM0AR中写入&SendBuff就可以了。
7. 库函数配置DMA
本次实验用到串口1的发送,属于DMA2的数据流7,通道4。
1. 使能DMA2时钟,等待数据流可配置
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //等待DMA可配置
对配置寄存器DMA_SxCR进行设置,等待其最低位为0,也就是DMA禁止传输了,才对DMA进行配置,while函数中获取寄存器状态不等于DISABLE
一旦离开while循环,那么就意味着状态位等于DISABLE了,也就是DMA禁止传输了。
2. 初始化DMA2数据流7,包括配置通道,外设地址,存储器地址,传输数据量等
void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct); //初始化DMA
typedef struct { uint32_t DMA_Channel; //设置DMA数据流对应通道,可供选择的通道请求多达8个,取值为DMA_Channel_0~DMA_Channel_0; uint32_t DMA_PeripheralBaseAddr; //设置DMA传输的外设地址 uint32_t DMA_Memory0BaseAddr; //设置DMA内存地址,存放DMA传输数据的地址 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; //设置DMA模式是否循环采集 uint32_t DMA_Priority; //设置DMA通道优先级 低 中 高 超高 uint32_t DMA_FIFOMode; //设置是否开启FIFO模式 uint32_t DMA_FIFOThreshold; //选择FIFO阈值 uint32_t DMA_MemoryBurst; //配置存储器突发传输配置 uint32_t DMA_PeripheralBurst; //配置外设突发传输设置 }DMA_InitTypeDef; 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发送
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
4. 使能DMA2数据流7,启动传输
DMA_Cmd (DMA2_Stream7,ENABLE); //使能DMA数据流
5. 查询DMA传输状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG) //查询DMA传输状态
ag. DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7); //查询DMA数据流7传输状态
uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx); //获取当前剩余数据量大小
ag. DMA_GetCurrDataCounter(DMA1_Channel4); //获取DMA数据流7还有多少个数据没有传输
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter); //设置DMA数据流传输的数据流大小
8. 实验程序
DMA通常情况下是配合ADC一起进行使用的,可以很直接的解决ADC的短板。
在初始化的ADC代码的基础之上添加DMA初始化,修改器结构体变量即可。
本次实验实现串口1的DMA发送,即USART1_TX,就必须选择DMA2的数据流7,通道4来进行DMA传输。
因为:
8.1 回车换行符
if(t>=j)//加入换行符
{
if(mask)
{
SendBuff[i]=0x0a;
t=0;
}
else
{
SendBuff[i]=0x0d;
mask++;
}
}
//回车换行符一般用于windows的TXT文件,包含两个字符“\r\n”,即先回车,光标回到首行,然后换行,光标另起一行
0x0a和0x0d都是回车键的ASCII码值,是window系统中定义的控制字符。
当我们按键盘上的回车键时,系统接收到其实是0x0d 0x0a两个控制字符,所以显示出来具有换行功能。
8.2 代码
8.2.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "usmart.h"
#include "KEY.h"
#include "DMA.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍
#define SEND_BUF_SIZE 8200
u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"}; //const修饰的地址存在了Flash中
int main(void)
{
u16 i;
u8 t=0,j,mask=0;
float pro=0; //进度
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
Key_Init();
//DMA2 数据流7,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,"2023/20/23");
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==1)
{
printf("\r\nDMA DATA:\r\n");
LCD_ShowString(30,150,200,16,16,"Start Transimit……");
LCD_ShowString(30,170,200,16,16," %");//显示百分号
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1DMA发送
MyDMA_Eable(DMA2_Stream7,SEND_BUF_SIZE); //开始一次DMA传输
while(1)
{
if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET) //等待DMA2_Stream7传输完成
{
DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7); //清除传输完成标志
break;
}
pro=DMA_GetCurrDataCounter(DMA2_Stream7); //得到当前剩余数据数
pro=1-pro/SEND_BUF_SIZE; //得到百分比
pro=pro*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;
}
}
}
8.2.2 DMA.c
#include "stm32f4xx.h"
#include "DMA.h"
//DMA各通道配置
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_Streamx:DMA数据流 DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择 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)
{
if((u32)DMA_Streamx>(u32)DMA2) // 得到当前Stream是属于DMA1还是DMA2
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可配置,当离开while循环后,DMA就已经使能了
//配置DMA结构体
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_BufferSize=ndtr; //数据传输量
DMA_InitStructure.DMA_Channel=chx; //DMA通道选择
DMA_InitStructure.DMA_DIR=DMA_DIR_MemoryToPeripheral; //存储器到外设模式
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Disable; //FIFO模式禁止
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full; //FIFO阈值
DMA_InitStructure.DMA_Memory0BaseAddr=mar;
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//存储器数据长度,8位
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//存储器增量模式,也就是存储器依次向后递增
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal; //使用普通模式
DMA_InitStructure.DMA_PeripheralBaseAddr=par;
DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single; //外设突发单次传输
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//外设数据长度,8个字节
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //外设非增量模式,也就是外设的指针不是依次向后递增的
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium; //中等优先级
DMA_Init(DMA_Streamx,&DMA_InitStructure);
}
//开启一次DMA传输
//DMA_Streamx:DMA数据流 DMA1_Stream0~7/DMA2_Stream0~7
//ndtr:数据传输量
void MyDMA_Eable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
DMA_Cmd(DMA_Streamx,DISABLE); //关闭DMA传输,只有DMA传输被关闭,才能获取DMA传输状态位
while(DMA_GetCmdStatus(DMA_Streamx)!=DISABLE); //确保DMA可以被设置
DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量
DMA_Cmd(DMA_Streamx,ENABLE); //开启DMA传输
}
8.2.3 DMA.h
#ifndef _DMA__H_
#define _DMA__H_
void MyDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr);
void MyDMA_Eable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr);
#endif