DMA(STM32学习笔记)

DMA简介

·DMA(Direct Memory Access)直接存储器存取

·DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了   CPU的资源

·12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)

·每个通道都支持软件触发和特定的硬件触发

·STM32F103C8T6 DMA资源:DMA1(7个通道)


配置DMA基本结构

图1 

 左右两个绿色框的外设存储器其实只是名字,外设里面可以放存储器的地址,存储器里可以放外设的地址,只要选好方向就行了。

传输计数器:比如我要传输一个数组,里面有4个元素,那么就填4,计数器就会自减到0,为0时停止工作

自动重装器:是否循环计数(自动重装不能和软件触发同时使用!!!

M2M:选择软件触发还是硬件触发


初始化配置模板

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
	MyDMA_Size=Size;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,开启AHB外设时钟
	
	//初始化DMA
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;//外设站点的起始地址
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据长度
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//是否自增
	DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//存储器站点的起始地址
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据长度
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
	DMA_InitStructure.DMA_BufferSize=Size;//缓存区大小(传输计数器)
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//传输模式(是否自动重装)
	DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//硬件触发|软件触发
	DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	//使能DMA
	DMA_Cmd(DMA1_Channel1,DISABLE);
}

void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1,DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);
	DMA_Cmd(DMA1_Channel1,ENABLE);
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//检查转运完成标志位 完成后标志位置1
	DMA_ClearFlag(DMA1_FLAG_TC1);//需要手动清除标志位
}

(这个代码用于两个数组之间的数据传递)

在传输地址时,要将数据类型强转为32位无符号整型数据,以免出错

可见配置DMA大部分是用一个DMA_Init函数配置结构体的各个参数就行了

结构体参数DMA_PeripheralInc和DMA_MemoryInc决定地址是否自增,我们要进行数组之间的传递,见下图

图2

转运一次,源地址和目标地址应该都+1,才能一一对应,所以这里选择地址自增

DMA_DIR有两个参数: DMA_DIR_PeripheralSRC选择外设地址为源地址DMA_DIR_PeripheralDST选择外设地址为目的地址,这里的外设只是个名字,只是为了区分,要怎么配置可以按照实际需要选择,我们要把数组A的元素传递给数组B,又把数组A的首地址放在了外设站点,所以选择外设地址为源地址。

DMA_M2M选择硬件或软件触发:ENABLE是软件触发,DISABLE是硬件触发,我们要实现数组之间的传递,因此不需要用到硬件触发,所以选择软件触发,那么自动重装就不能选了。

最后使能DMA,这里通道数可以选择1~7任意一个。

在同时满足触发源有触发信号(软件触发一直有触发信号)、传输计数器大于0DMA使能这三个条件,DMA就会开始工作。这里还没给DMA使能和给传输计数器赋值。

在while循环里不断调用自定义的第二个函数,就能不断的将变化的数组A里的值传递到数组B里,比如每秒都将数组A里的元素+1,可以用OLED观察数组的变化情况。

注意写传输计数器时,必须要先关闭DMA,再进行,这也是第二个自定义函数先关闭DMA使能的原因。


ADC扫描模式+DMA

DMA最常用的组合就是ADC

图3

上一节我们用的是ADC的非扫描模式,主要是因为,ADC多通道扫描时,会不断把数据运到ADC的DR寄存器,但又没有人将数据运出去,就会导致前面的数据被覆盖,所以DMA就派上用场了。

下面是代码示例

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//ADC都是挂载在APB2总线上
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,开启AHB外设时钟
	
	//配置ADC时钟
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择6分频 ADC时钟最大频率为14MHz
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//选择模拟输入模式 ADC专属
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
		
	//初始化ADC
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//选择独立模式还是双ADC模式
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//选择数据对齐方式
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//外部触发转换选择(触发控制的触发源选择)
	ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//选择连续转换
	ADC_InitStructure.ADC_ScanConvMode=ENABLE;//选择扫描模式
	ADC_InitStructure.ADC_NbrOfChannel=4;//选择通道数目(1~16)
	ADC_Init(ADC1,&ADC_InitStructure);
	
	
	//初始化DMA
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//ADC转换结果放在DR寄存器里,防止数据被覆盖,从这里取数据
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//DR寄存器只有低16位有效,所以选择半字长度
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//不自增,始终转运DR的数据
	DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//存储器站点的起始地址
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//数据长度
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//选择自增
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
	DMA_InitStructure.DMA_BufferSize=4;//缓存区大小(传输计数器)
	DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//传输模式(是否自动重装)
	DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//硬件触发|软件触发
	DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	//使能DMA
	DMA_Cmd(DMA1_Channel1,ENABLE);
	//开启ADC到DMA的通道!!!
	ADC_DMACmd(ADC1,ENABLE);
	//上电ADC
	ADC_Cmd(ADC1,ENABLE);
	
	//对ADC进行校准
	ADC_ResetCalibration(ADC1);//这里会将复位校准状态标志位置1
	while(ADC_GetResetCalibrationStatus(ADC1));//获取复位校准状态,复位校准完后状态位标志为0,跳出循环
	ADC_StartCalibration(ADC1);//开始校准
	while(ADC_GetCalibrationStatus(ADC1));//等待校准完成
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//触发转换,选择软件触发	
}

 这里使用的是ADC的连续转换+扫描模式

外部有四个模拟信号输入,初始化DMA时,前六个函数和传递数组时不同,外设地址(源地址)是ADC1的DR寄存器,DMA是从这里转运数据的,我们定义一个数组用来存放转换值,那么存储器地址(目的地址)就是数组名,注意数据类型要强转为32位无符号整型!

数据长度选择半字(16位长度)因为DR寄存器只有低16位有效,外设地址不自增、存储器地址自增(参考图3来配置)

传输计数器选择4,传输模式选择自动重装模式,那么对应只能选择硬件触发,DMA_M2M_Disable就是硬件触发

最重要的一点,要打开ADC1到DMA的通道,使用ADC_DMACmd函数,ADC就会硬件触发DMA,不需要我们手动检测标志位等操作。

注意:DMA初始化时只能选择通道1

DMA的外设触发源要想是ADC1,那么只能走通道1这条路(DMA1_Channel1)

回顾上节内容,ADC开始工作是需要一次软件触发的,所以在初始化的最后一条我们还需要加一个软件触发ADC,因为我们选择的是ADC的连续转换模式

以上就是全部代码示例,只需要在主函数里调用一次初始化代码,ADC和DMA就会开始工作了


重要函数

DMA_SetCurrDataCounter:设置当前数据寄存器(设置传输长度)

DMA_ITConfig:开启DMA中断

  • 56
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sakabu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值