STM32入门之DMA直接存储器存取
一、DMA简介
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
STM32F103两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
二、DMA数据传输方式
DMA传输方式是一种高速的数据传输操作,允许在外部设备和存储器之间利用系统总线直接读/写数据,既不通过微处理器,也不需要微处理器干预。整个数据传输操作在一个称为“DMA控制器”的控制下进行。微处理器除了在数据传输开始和结束时控制一下,在传输过程中微处理器可以进行其他的工作。
三、DMA传输的基本要素
1. 传输源地址和目的地址
这两个地址分别定义了数据传输的起点和终点,即数据来源与存放位置。
2. 触发信号
触发信号用于启动DMA的数据传输。对于存储器之间的数据传输,通常可以由软件一次性触发,然后连续传输直至完成;而在外设传输中,数据的传输时机依赖于外设的工作状态,可能需要多次触发才能完成整个过程。
3. 传输数据量
指的是每次DMA传输中实际传输的数据量及相应存储器的容量限制。
4. DMA通道
每个DMA控制器通常支持多个通道,每个通道都有独立的传输源地址、目的地址、触发信号和传输数据量设置。同时,不同通道在使用总线资源时,其优先级也是各不相同的。
5. 传输方式
- 传输介质:数据传输是在两块存储器之间,还是在存储器和外设之间进行;
- 传输方向:数据是从存储器传输到外设,还是反向传输;
- 存储器地址处理:包括地址是否递增、递增的步长以及每次传输的数据宽度(例如8位、16位或32位);
- 循环方式:到达存储区域边界后,地址是否需要循环,这一点在存储器与外设之间的DMA传输中较为常见。
6. 其他要素
还包括DMA通道在总线资源使用中的优先级设定,以及在传输完成或出现错误时,是否触发中断等控制机制。
四、STM32中的DMA
STM32的DMA主要特性:
- 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
- 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置
- 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)
- 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐
- 支持循环的缓冲器管理
- 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求
- 存储器和存储器间的传输
- 外设和存储器、存储器和外设之间的传输
- 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标
- 可编程的数据传输数目:最大为65535
STM32的DMA内部结构如下所示,主要包含DMA请求、DMA通道、仲裁器
1. DMA 请求
如果外设希望通过DMA传输数据,首先必须向DMA控制器发送DMA请求信号。当DMA控制器收到该请求后,它会向外设发出一个应答信号。外设在接收到应答信号后,向DMA控制器反馈确认信息,随后DMA控制器便启动数据传输过程,并持续工作直至整个传输任务完成。
如前所述,DMA体系中包括DMA1和DMA2两个控制器,其中DMA1具有7个通道,而DMA2则包含5个通道。每个通道对应特定的外设请求,这使得不同的外设数据传输请求可以通过各自独立的DMA通道进行管理和调度,从而实现高效的数据搬运。
2. DAM 通道
DMA具有12个独立可编程的通道,其中DMA1包含7个通道,DMA2包含5个通道。每个通道都对应着不同外设发起的DMA请求。尽管每个通道设计上可以接收多个外设的请求,但在任一时刻,它只能响应一个请求,无法同时处理多个请求。
3. 仲裁器
当多个 DMA 通道同时发出请求时,系统需要按照一定的顺序来响应和处理这些请求,这个顺序由仲裁器进行管理。仲裁器对 DMA 通道请求的管理可分为两个阶段:
- 软件阶段
在该阶段,优先级可以在 DMA_CCRx 寄存器中进行设置,分为四个等级:非常高、高、中和低。通过软件设定,可以预先确定不同请求的处理优先级。
- 硬件阶段
当两个或多个 DMA 通道请求的优先级设置相同时,其实际处理顺序由通道编号决定:编号越低的通道具有更高的优先权,例如通道0的优先级高于通道1。
五、STM32 DMA数据配置
1. 数据传输方向
DMA支持三种数据传输方向:
- 外设到存储器:例如,在ADC数据采集中,将ADC数据寄存器作为数据源,而用于存储采集数据的内存区域则作为数据目的地,从而实现外设到存储器的数据传输。
- 存储器到外设:例如,在串口数据传输中,先将待发送的数据存储于内存缓冲区,再传输至串口数据寄存器,实现内存向外设的数据传送。
- 存储器到存储器:例如,在内部FLASH与内部SRAM之间复制数据时,系统将FLASH作为数据源,SRAM作为目的地。此种模式下,需要启用存储器到存储器传输模式,以确保数据在两个存储区域之间正确复制。
2. 数据量与数据单元
配置好传输方向后,还必须明确传输的数据项数量和数据单元的位宽。
例如,在串口数据传输中,系统可一次性传送大量数据,而具体传输的数据项数由预设参数决定;同时,为确保数据传输正确,数据单元的位宽须与外设和内存中数据的格式保持一致(如8位、16位或32位)。此外,还需设置地址指针的自动增量模式,以便在传输过程中正确更新内存地址,而外设地址通常保持固定。
3. 传输完成的判定与传输模式
数据传输完成后,系统可通过状态标志或中断机制进行检测。当数据传输达到预定数量或出现错误时,系统将产生相应标志并(在中断功能使能的情况下)触发中断服务程序。
传输模式方面,DMA支持两种方式:
- 一次性传输:完成预设数据传输后停止工作,若需再次传输则必须重新配置。
- 循环传输:在完成一次传输后,系统自动恢复初始配置,实现连续循环传输。
六、DMA实践应用
本次使用DMA进行两个数组间的数据搬运(存储器到存储器的搬运方式),使用的是STM32的DMA1,通道可在7个通道中任选一个。
1.配置步骤
- 使能DMA1外设时钟
- 初始化DMA,主要设置搬运的数据来源和去向,数据的地址,每次搬运的数据宽度等
- 使能DMA
- 等待数据搬运完成
2.代码实现
编写dma_drv.c文件
#include "dma_drv.h"uint32_t SRC_buffer[5]={0,1,2,3,4};
uint32_t DST_buffer[5]={0};void DMA_M2M_Config(void)
{//开启外设时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//初始化DMADMA_InitTypeDef DMA_InitStruct;//外设基地址DMA_InitStruct.DMA_PeripheralBaseAddr =(uint32_t)SRC_buffer;//外设数据宽度DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;//外设地址是否自增DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//数据大小DMA_InitStruct.DMA_BufferSize=5;//内存基地址DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)DST_buffer;//内存数据宽度DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Word;//内存地址是否自增DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//数据传输方向 外设作为数据的来源DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//使能存储器到存储器搬运DMA_InitStruct.DMA_M2M=DMA_M2M_Enable;//搬运方式 正常 (非循环搬运)DMA_InitStruct.DMA_Mode=DMA_Mode_Normal;//DMA请求优先级DMA_InitStruct.DMA_Priority=DMA_Priority_High; DMA_Init(DMA1_Channel1,&DMA_InitStruct);//使能DMADMA_Cmd(DMA1_Channel1,ENABLE);}
编写dma_drv.h文件
#ifndef __DMA_DRV_H__
#define __DMA_DRV_H__#include "stm32f10x.h"extern uint32_t SRC_buffer[5];
extern uint32_t DST_buffer[5];void DMA_M2M_Config(void);#endif
编写main.c文件
#include "stm32f10x.h"
#include "dma_drv.h"
#include "usart_drv.h"void HardWare_Init(void)
{Usart_MyConfig(115200);//初始化串口DMA_M2M_Config();//初始化DMA
}int main(void)
{HardWare_Init();uint8_t i=0;while(1){//等待数据搬运完成if(DMA_GetFlagStatus(DMA1_FLAG_TC1) == SET){for(i=0;i<5;i++){printf("DST_buffer[%d]=%d\n",i,DST_buffer[i]);}DMA_ClearFlag(DMA1_FLAG_TC1);}}
}