STM32学习-DMA介绍及一般使用方法

前言

撰写不易,转载请注明出处。

DMA原理简易介绍

DMA全称为Direct Memory Access,即直接存储器访问。以上对DMA的介绍可能有些生涩难懂,说的通俗一些,DMA就是一个特殊的通道,能够在数据传输途径中开辟一条新道路,以避免经过CPU,从而达到减轻CPU负担且同时实现高速传输的目的。其工作原理简图如下所示:
数据从外设1到外设2不经过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浅显的理解,如果大家有什么不明白或者文章有需要修正的地方,欢迎在评论区留言和探讨。

  • 17
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值