前言
撰写不易,转载请注明出处。
DMA原理简易介绍
DMA全称为Direct Memory Access,即直接存储器访问。以上对DMA的介绍可能有些生涩难懂,说的通俗一些,DMA就是一个特殊的通道,能够在数据传输途径中开辟一条新道路,以避免经过CPU,从而达到减轻CPU负担且同时实现高速传输的目的。其工作原理简图如下所示:
如上图,数据从外设1到外设2而不经过CPU。
各外设对DMA的通道请求
知道了DMA的工作目的,那下一步我们就需要知道什么样的外设能够使用这个特殊的通道。对于大容量的STM32芯片有2个DMA控制器,其中DMA1有7个通道,DMA2有5个通道。每个通道都可以配置一些外设的地址。在操作手册中我们可以找到各外设对DMA的通道请求。
DMA1
DMA1外设请求信号的优先级
所谓请求信号的优先级,很好理解,就是假设有多个信号都像DMA发送请求,申请通道,那么DMA优先处理优先级最高的信号。
上图由上到下,优先级从高到低,例如ADC1优先级高于SPI/I**2S,以此类推;
由左到右,优先级从高到低,通道1(Channel1)优先级高于通道2(Channel2),以此类推。
DMA2
DMA2外设请求信号的优先级
和DMA1一样,其优先级排序也是由上到下,优先级从高到低;由左到右,优先级从高到低。
DMA初始化函数讲解
明白了以上基础知识,我们来看看如何配置DMA的初始化函数。
- 函数DMA_DeInit
该函数作用为将DMA恢复为初始默认值。这一步是在配置DMA初始化函数前必不可少的。
其函数原型为:
void DMA_DeInit(DMA_Channel_TypeDef* DMA_Channelx);
// 其中DMAx_Channelx:x是要选的通道
// 调用示例为:
DMA_DeInit(DMA1_Channel1); // 调用DMA1的通道1
// 注意在调用时要表明调用DMA1还是DMA2
- 函数DMA_Init
在配置函数之前,我们先来看看该函数的结构体是怎样的。以下是操作手册中给出的该函数结构体:
typedef struct
{
u32 DMA_PeripheralBaseAddr;
u32 DMA_MemoryBaseAddr;
u32 DMA_DIR;
u32 DMA_BufferSize;
u32 DMA_PeripheralInc;
u32 DMA_MemoryInc;
u32 DMA_PeripheralDataSize;
u32 DMA_MemoryDataSize;
u32 DMA_Mode;
u32 DMA_Priority;
u32 DMA_M2M;
} DMA_InitTypeDef;
下面我们来由上到下一一讲解结构体中成员的具体含义及其取值:
(1)DMA_PeripheralBaseAddr ,该参数为 DMA 外设基地址,一般外设的地址都能在操作手册中找到,后面在举例中会提到如何确定这个值。
(2)DMA_MemoryBaseAddr,该参数用以定义 DMA 内存基地址,也就是外设传输的信息经过DMA后所要存储的地方。可以自定义一个变量,取其地址作为存储地址。
(3)DMA_DIR,该参数规定了数据传输的方向,即是DMA发送信号给外设,还是外设传信号给DMA。
该参数有两个取值:
<1>DMA_DIR_PeripheralDST,DMA发送信号给外设
<2>DMA_DIR_PeripheralSRC,外设传信号给DMA
(4)DMA_BufferSize,用以定义指定 DMA 通道的 DMA 缓存的大小,即传输大小。
(5)DMA_PeripheralInc,该参数规定外设地址是随传输递增还是不变。
该参数有两个取值:
<1>DMA_PeripheralInc_Enable,外设地址随传输递增
<2>DMA_PeripheralInc_Disable,外设地址不随传输改变
(6)DMA_MemoryInc,该参数和第五个参数相似,不过该参数规定的是内存地址递增与否。
该参数有两个取值:
<1>DMA_PeripheralInc_Enable,内存地址随传输递增
<2>DMA_PeripheralInc_Disable,内存地址不随传输改变
(7)DMA_PeripheralDataSize,该参数规定了外设数据宽度。
该参数有三个取值:
<1>DMA_PeripheralDataSize_Byte,数据宽度为 8 位
<2>DMA_PeripheralDataSize_HalfWord,数据宽度为 16 位
<3>DMA_PeripheralDataSize_Word,数据宽度为 32 位
(8)DMA_MemoryDataSize,该参数规定了内存的数据长度。
该参数有三个取值:
<1>DMA_MemoryDataSize_Byte,数据宽度为 8 位
<2>DMA_MemoryDataSize_HalfWord,数据宽度为 16 位
<3>DMA_MemoryDataSize_Word,数据宽度为 32 位
(9)DMA_Mode,该参数规定DMA的工作模式,是循环工作还是正常工作,其中循环模式用于处理循环缓冲区和连续的数据传输(如ADC的扫描模式)。
该参数有两个取值:
<1>DMA_Mode_Circular,DMA工作在循环模式
<2>DMA_Mode_Normal,DMA工作在正常模式
(10)DMA_Priority,该参数可修改DMA 通道 x 的优先级。
该参数有四个取值:
<1>DMA_Priority_VeryHigh DMA,通道 x 拥有非常高优先级
<2>DMA_Priority_High DMA,通道 x 拥有高优先级
<3>DMA_Priority_Medium,DMA 通道 x 拥有中优先级
<4>DMA_Priority_Low DMA,通道 x 拥有低优先级
(11)DMA_M2M,该参数使能 DMA 通道的内存到内存传输,其中DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是内存到内存传输模式。
该参数有两个取值:
<1>DMA_M2M_Enable DMA,通道 x 设置为内存到内存传输
<2>DMA_M2M_Disable DMA,通道 x 没有设置为内存到内存传输
- DMA使能函数DMA_Cmd
其原型为:
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
// 其中要注意的是参数y和x,其含义比较明显,在此不再赘述
// 调用示例为:
DMA_Cmd(DMA1_Channel1 , ENABLE); // 调用DMA1的通道1
- DMA中断使能函数
其原型为:
void DMA_ITConfig(DMA_Channel_TypeDef* DMA_Channelx, u32 DMA_IT,
FunctionalState NewState);
// DMA_IT有三个取值,其值及含义分别为:
// DMA_IT_TC:传输完成
// DMA_IT_HT:传输一半
// DMA_IT_TE:传输错误
// 调用示例:
DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE);
整合
上面我们了解了这些基本构造,现在来整合为一个初始化函数。以ADC1采样,DMA传输其值到内存为例。在这之前我们要做一个工作,就是知道外设ADC1的地址,这也是在介绍函数DMA_Init时留下的问题。在寄存器组起始地址中我们可以看到ADC1的起始地址:
此外,再找到它的偏移值,我们要用ADC的规则数据采集,所以在目录中找到“ADC规则数据寄存器(ADC_DR)”,查看其偏移值:
起始地址加上偏移值,便是ADC1的地址:0x4001244C
做完以上工作后,开始整合!
void DMA1_Init(u32 ADC1_Adress,u32 DMA_Adress,u16 A2D_Size)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA1
DMA_DeInit(DMA1_Channel1); //复位DMA1
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_Adress; //DMA外设ADC1地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&DMA_Adress; //内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设到内存
DMA_InitStructure.DMA_BufferSize = A2D_Size; //传输内容的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址保持不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作状态为循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA1通道1拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA1通道1没有内存到内存的传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //ADC1匹配DMA1通道1
DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE); //使能DMA1传输中断
//配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE); //使能DMA1通道1
}
以上代码有关于中断与事件的知识,如果不了解,可以看看这篇博客:
传送门
以上就是笔者关于DMA浅显的理解,如果大家有什么不明白或者文章有需要修正的地方,欢迎在评论区留言和探讨。