1 DMA

DMA(Direct memory access)直接存储器存取,用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输,无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。

DMA,即直接存储器访问,DMA 传输将数据从一个地址空间复制到另外一个地址空间。当 CPU 初始化这个传输动作,传输动作本身是由DMA 控制器 来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

STM32有两个DMA控制器共12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权,要注意的是 DMA2 只存在于大容量的单片机中。

1.1 DMA作用

DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种数据传输模式,其本质还是一样的,都是地址到地址的数据传输。

  • 外设到内存
  • 内存到外设
  • 内存到内存
  • 外设到外设

1.2 DMA传输相关参数

源地址
目标地址
数据传输量

1.3 DMA的主要特征

12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道,每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置:

  • 每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  • 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。
  • 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  • 支持循环的缓冲器管理每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
  • 存储器和存储器间的传输
  • 外设和存储器,存储器和外设的传输
  • 闪存、SRAM、外设的 SRAM、APB1 APB2 和 AHB 外设均可作为访问的源和目标。
  • 可编程的数据传输数目:最大为 65536

从外设(TIMx、ADC、SPIx、I2Cx 和 USARTx)产生的 DMA 请求,通过逻辑或输入到DMA 控制器,这就意味着同时只能有一个请求有效。外设的 DMA 请求,可以通过设置相应的外设寄存器中的控制位,被独立地开启或关闭。

2 stm32DMA资源

2.1 DMA1控制器

从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器。

外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭

2.2 DMA2控制器

从外设(TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器

外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭。

注意:DMA2控制器及相关请求仅存在于大容量产品和互联型产品中

3 stm32DMA相关固件库函数说明

3.1 初始化函数

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct)

参数

==DMAy_Channelx:DMA通道==

  • y:1或2,用于选择DMA1或DMA2
  • x:对于DMA1:1-7,对于DMA2:1-5

==DMA_InitStruct==:DMA配置结构体指针

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个参数DMA_PeripheralBaseAddr 用来设置 DMA 传输的外设基地址,比如要进行串口DMA 传输,那么外设基地址为串口接受发送数据存储器 USART1->DR的地址,表示方法为&USART1->DR
  • 第2个参数DMA_MemoryBaseAddr为内存基地址,也就是我们存放DMA传输数据的内存地址。
  • 第3个参数DMA_DIR 设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数据发送到外设,也就是外设是源地还是目的地,这里我们设置为从内存读取数据发送到串口,所以外设自然就是目的地了,所以选择值为DMA_DIR_PeripheralDST
  • 第4个参数DMA_BufferSize设置一次传输数据量的大小,这个很容易理解。
  • 第5个参数DMA_PeripheralInc设置传输数据的时候外设地址是不变还是递增。如果设置为递增,那么下一次传输的时候地址加 1,这里因为我们是一直往固定外设地址&USART1->DR发送数据,所以地址不递增,值为 DMA_PeripheralInc_Disable
  • 第6个参 数DMA_MemoryInc 设置传输数据时候内存地址是否递增。这个参数 和DMA_PeripheralInc 意思接近,只不过针对的是内存。这里我们的场景是将内存中连续存储单元的数据发送到串口,毫无疑问内存地址是需要递增的,所以值为 DMA_MemoryInc_Enable
  • 第7个参数DMA_PeripheralDataSize 用来设置外设的数据长度是为字节传输(8bits),半字传输 (16bits) 还 是 字 传 输 (32bits) , 这 里 我 们 是 8 位 字 节 传 输 , 所 以 值 设 置 为DMA_PeripheralDataSize_Byte
  • 第8个参数DMA_MemoryDataSize 是用来设置内存的数据长度,和第七个参数意思接近,这里我们同样设置为字节传输 DMA_MemoryDataSize_Byte
  • 第9个参数DMA_Mode 用来设置 DMA 模式是否循环采集,也就是说,比如我们要从内存中采集 64 个字节发送到串口,如果设置为重复采集,那么它会在 64 个字节采集完成之后继续从内存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。所以设置值为DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,不会中断,大家在实验中可以修改这个参数测试一下。
  • 第10个参数:是设置 DMA 通道的优先级,有低,中,高,超高三种模式,这个在前面讲解过,这里我们设置优先级别为中级,所以值为 DMA_Priority_Medium。如果要开启多个通道,那么这个值就非常有意义。
  • 第 11 个 参 数DMA_M2M设 置 是 否 是 存 储 器 到 存 储 器 模 式 传 输 , 这 里 我 们 选 择DMA_M2M_Disable

例如

DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = &USART1->DR; //DMA 外设 ADC 基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = 64; //DMA 通道的 DMA 缓存的大小
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 通道 x 拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据指定的参数初始化

传输方向

#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)

DMA通道x配置寄存器(DMA_CCRx):

==外设地址增量模式==

#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)

==存储器地址增量模式==

#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)

==外设数据宽度==

#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)

==存储器数据宽度==

#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)

==模式选择(DMA_Mode)==

#define DMA_Mode_Circular ((uint32_t)0x00000020) //循环模式
#define DMA_Mode_Normal ((uint32_t)0x00000000) //正常模式

==通道优先级==

#define DMA_Priority_VeryHigh ((uint32_t)0x00003000)
#define DMA_Priority_High ((uint32_t)0x00002000)
#define DMA_Priority_Medium ((uint32_t)0x00001000)
#define DMA_Priority_Low ((uint32_t)0x00000000)

==存储器到存储器模式==

#define DMA_M2M_Enable ((uint32_t)0x00004000)
#define DMA_M2M_Disable ((uint32_t)0x00000000)

3.2 DMA通道使能

 void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx,  FunctionalState NewState)

3.2.1 DMA中断配置

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState)

3.2.2 其他函数

==DMA清除中断挂起==

void DMA_ClearITPendingBit(uint32_t DMAy_IT)

==DMA获取中断状态==

ITStatus DMA_GetITStatus(uint32_t DMAy_IT)

==获取传输剩余个数==

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)

4 DMA Code

通过定义dma.c 文件和其头文件 dma.h,同时我们要引入 dma 相关的库函数文件 stm32f10x_dma.c 和 stm32f10x_dma.h。

#include "dma.h"
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;//保存 DMA 每次数据传送的长度 
//DMA1 的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8 位数据宽度/存储器增量模式
//DMA_CHx:DMA 通道 CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟
    DMA_DeInit(DMA_CHx); //将 DMA 的通道 1 寄存器重设为缺省值
    DMA1_MEM_LEN=cndtr;
    DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA 外设 ADC 基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 内存基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存到外设
    DMA_InitStructure.DMA_BufferSize = cndtr; //DMA 通道的 DMA 缓存的大小
    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; //DM 通道拥有中优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输
    DMA_Init(DMA_CHx, &DMA_InitStructure); //初始化 DMA 的通道
} 
//开启一次 DMA 传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
    DMA_Cmd(DMA_CHx, DISABLE ); //关闭 USART1 TX DMA1 所指示的通道 
    DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//设置 DMA 缓存的大小
    DMA_Cmd(DMA_CHx, ENABLE); //使能 USART1 TX DMA1 所指示的通道
}