STM32-DMA直接存储器存取

目录

一、概述

1、简介

2、DMA框图

3、DMA基本结构

4、DMA请求映像

5、数据宽度与对齐

二、存储器到存储器转运数据代码

三、外设设备到存储器转运数据代码


一、概述

1、简介

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

  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)
  • 每个通道都支持软件触发和特定的硬件触发

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

存储器映像

fc843705ea6148658ce3df3ca1eb1000.png

2、DMA框图

如下:

DCode总线是专门访问Flash的,系统总线是访问其他东西的,由于DMA要转运数据,所以DMA也必须要有访问的主动权。

虽然DMA有多个通道可以独立转运数据,但DMA总线只有一个,仲裁器会根据通道的优先级来决定谁先用,谁后用。在总线矩阵这里也有个仲裁器,当CPU和DMA同时访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突,不过总线仲裁器,仍然会保证CPU得到一半的总线带宽,使CPU也能正常工作。

DMA请求就是DMA的硬件触发源,比如ADC转换数据,需要转运数据就需要通过这条线路,向DMA发出硬件触发信号,之后DMA就可以执行转运的动作了,数据寄存器都是可以正常读写


b800261d4a68468794ce869d2f09860e.png

3、DMA基本结构

如下:

外设寄存器其实就是外设与存储器用于连接的通道,也就是转换机构。外设寄存器也能写Flash 、SRAM的地址,存储器也能写外设寄存器的地址。

传输计数器是用来指定总共需要转运几次的,这个传输寄存器是一个自减寄存器,比如给它指定5次,没转运一次就会减1,如果不使用自动重装器,减为0时就是转运结束。使用这个自动重装器,当传输计数器减为0时会立即重装到初始值为5。如果不重装,就单次模式转运,重装就是循环模式转运。修改传输计数器时,要使DMA_Cmd先使能。

接着就是触发源,有软件触发源和硬件触发源,具体是哪个由M2M(memory to memory)参数决定,当我们给这个M2M为1时,就会进行软件触发,它这个软件触发的逻辑是以最快的速度,连续不断地触发DMA,争取早日把传输计数器清零,完成这一轮的转换。自动重装器和软件触发不能同时使用。软件触发一般就是应用存储器到存储器的转运。硬件(这里硬件可以理解为外设)触发一般与外设有关的转运,比如ADC转换完成,来触发DMA转运。

这开关控制就是用DMA_Cmd()进行使能。

8c027188f11b44c08c4c17d0eb1d227a.png

4、DMA请求映像

EN位决定这个寄存器工作不工作。EN位为0表示不工作,EN位为1表示工作。

固定的硬件优先级进行优先级判断,通道号越小,优先级越高

88f1f4b492bb48d7b335012703b573a4.png

5、数据宽度与对齐

源端宽度小于目标宽度时,数据的高位补零,也就是右对齐; 源端宽度大于目标宽度时,数据的高位丢弃掉,

b907fd84641d4404a849de3b431fd24e.png

数据转运+DMA:

DMA外设地址可以自增,DMA存储器地址也可以自增,

7f3e0bd56cf94bec96d053d0b3026495.png

二、存储器到存储器转运数据代码

MYDMA.c:

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size=0;


void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t size)
{
	DMA_InitTypeDef DMA_InitStruct;
	MyDMA_Size=size;

	//在AHB总线上
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	//DMA初始化
	DMA_InitStruct.DMA_BufferSize=size  ;              //传输次数
	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC  ;    //方向
	DMA_InitStruct.DMA_M2M=DMA_M2M_Enable  ;
	DMA_InitStruct.DMA_MemoryBaseAddr=AddrB;
	DMA_InitStruct.DMA_MemoryDataSize=DMA_PeripheralDataSize_Byte ;
	DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;
	DMA_InitStruct.DMA_Mode=DMA_Mode_Normal;          //单次
	DMA_InitStruct.DMA_PeripheralBaseAddr=AddrA;
	DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte ;
	DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable  ;
	DMA_InitStruct.DMA_Priority=DMA_Priority_Medium ;
	
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);
	
	//打开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);
	DMA_ClearFlag(DMA1_FLAG_TC1);
}

MyDMA.h:

#ifndef _MYDMA_H
#define _MYDMA_H

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t size);

void MyDMA_Transfer(void);

#endif

main.c:

#include  "stm32f10x.h"                  // Device header
#include  "OLED.h"
#include  "delay.h"
#include  "MyDMA.h"

//const uint8_t aa=0x66; //加了const,会把数据存到flash中,只读不能写,不加存到SRAM

uint8_t DataA[]={0x01,0x02,0x03,0x08};

uint8_t DataB[]={0,0,0,0};

int main(void)
{
	
   OLED_Init();
	OLED_ShowString(1,1,"DataA:");
	OLED_ShowHexNum(1,7,(uint32_t)&DataA,8);
	OLED_ShowNum(2,1,DataA[0],2);
	OLED_ShowNum(2,5,DataA[1],2);
	OLED_ShowNum(2,9,DataA[2],2);
	OLED_ShowNum(2,13,DataA[3],2);
	
	OLED_ShowString(3,1,"DataB:");
	OLED_ShowHexNum(3,7,(uint32_t)&DataB,8);
	OLED_ShowNum(4,1,DataB[0],2);
	OLED_ShowNum(4,5,DataB[1],2);
	OLED_ShowNum(4,9,DataB[2],2);
	OLED_ShowNum(4,13,DataB[3],2);
	MyDMA_Init((uint32_t)&DataA,(uint32_t)&DataB,4);
	
	while(1) 
	{
		
		DataA[0]++;
		DataA[1]++;
		DataA[2]++;
		DataA[3]++;
      
		//OLED_ShowHexNum(1,1,aa,2);
		OLED_ShowNum(4,1,DataB[0],2);
	   OLED_ShowNum(4,5,DataB[1],2);
	   OLED_ShowNum(4,9,DataB[2],2);
	   OLED_ShowNum(4,13,DataB[3],2);
		
		Delay_ms(1000);
		MyDMA_Transfer();
		OLED_ShowNum(2,1,DataA[0],2);
	   OLED_ShowNum(2,5,DataA[1],2);
	   OLED_ShowNum(2,9,DataA[2],2);
	   OLED_ShowNum(2,13,DataA[3],2);
		OLED_ShowNum(4,1,DataB[0],2);
	   OLED_ShowNum(4,5,DataB[1],2);
	   OLED_ShowNum(4,9,DataB[2],2);
	   OLED_ShowNum(4,13,DataB[3],2);
	}
	
}

三、外设设备到存储器转运数据代码

ADC通过DMA请求进行转运数据。每个通道转换完成DMA就会转运1次

b883b7ea036246a887bcd353d841e954.png

AD.c:

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[2]={0};

void AD_DMA_Init(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStruct;
	
	//时钟外设初始化
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	//GPIO初始化
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN ;         //模拟输入
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
	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,配置数据格式,模式
	ADC_InitStruct.ADC_Mode=ADC_Mode_Independent ;
	ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right  ;  //数据右对齐
	ADC_InitStruct.ADC_ExternalTrigConv= ADC_ExternalTrigConv_None ;  //不用外部触发源,用硬件触发
	ADC_InitStruct.ADC_NbrOfChannel=2;              //多通道
	ADC_InitStruct.ADC_ScanConvMode=ENABLE;         //扫描
	ADC_InitStruct.ADC_ContinuousConvMode=ENABLE;   //连续
	ADC_Init(ADC1,&ADC_InitStruct);
	
	
	//DMA存储器的地址变化
	//定时器触发ADC,ADC触发DMA
	//DMA初始化
	DMA_InitStruct.DMA_BufferSize=2  ;                 //传输次数
	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC  ;    //方向
	DMA_InitStruct.DMA_M2M=DMA_M2M_Disable  ;
	DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)AD_Value;
	DMA_InitStruct.DMA_MemoryDataSize= DMA_MemoryDataSize_HalfWord   ;
	DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable ;
	DMA_InitStruct.DMA_Mode=DMA_Mode_Circular   ;                //循环还是单次,这里是循环
	DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;  
	DMA_InitStruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_HalfWord ;
	DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable  ;
	DMA_InitStruct.DMA_Priority=DMA_Priority_Medium ;            //软件优先级,比硬件优先级权限高
	
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);
	
	//打开DMA
	DMA_Cmd(DMA1_Channel1,ENABLE);
	//确定外设触发源ADC1
	ADC_DMACmd(ADC1,ENABLE);
	//启动ADC
	ADC_Cmd(ADC1,ENABLE);
	
	//校准复位寄存器
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1)==SET);  
	//开始校准,为0 校准结束
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1)==SET);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}

//void AD_GetValue(void)
//{
//	DMA_Cmd(DMA1_Channel1,DISABLE);
//DMA_SetCurrDataCounter(DMA1_Channel1,2);
//	DMA_Cmd(DMA1_Channel1,ENABLE);
//	
//	
//	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
//	DMA_ClearFlag(DMA1_FLAG_TC1);
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
	return ADC_GetConversionValue(ADC1);
//}

AD.h:

#ifndef _AD_H
#define _AD_H

extern uint16_t AD_Value[2];
void AD_DMA_Init(void);

//void AD_GetValue(void);




#endif

main.c:

#include  "stm32f10x.h"                  // Device header
#include  "OLED.h"
#include  "delay.h"
#include  "AD.h"

int main(void)
{
	
   OLED_Init();
	AD_DMA_Init();
	OLED_ShowString(1,1,"AD1:");
	OLED_ShowString(2,1,"AD2:");

	while(1) 
	{
	 
	 OLED_ShowNum(1,5,AD_Value[0],4);
	 OLED_ShowNum(2,5,AD_Value[1],4);
	
	 Delay_ms(500);
	
	}
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值