基于stm32F103的DMA搬运

笔记来源于江科协议的视频

芯片采用与stm32F103C8T6

DMA简介

简介

DMA主要用于协助CPU,完成数据转运的工作,DMA可以直接访问STM32内部的存储器,包括运行内存SRAM,程序存储器Flash,寄存器等,DMA可以提供外设和存储器或者存储器与存储器之间的高速数据传输,无需CPU干预,节省CPU资源。外设包括外设存储器DR,如:ADC的数据存储器、串口的数据寄存器,存储器指SRAM和Flash ,外设与存储器、存储器与存储器之间传输就可以用DMA进行转运。

STM32有12个独立可配置的通道,DMA1:7个通道 DMA2:5个通道,这个通道就是转运数据的路径,从一个地方转运另一个地方,就需要另一个通道,多通道进行转运时,它们之间互不干扰,

每个通道支持软件触发和特定的硬件触发,如:DMA要进行存储器到存储器之间的数据搬运,Falsh到SRAM里就需要软件触发,使用软件触发时,DMA会直接以最快的速度转运完成, 如果时外设到存储器的转运,就不能直接转运了,因为外设数据有一定时机,所以需要硬件触发,如:转运ADC数据,就需要ADC每个通道转换完成之后进行搬运,硬件触发一次DMA,之后DMA再进行转运,触发一次,转运一次,

存储器到存储器之间一般用软件触发,外设到存储器一般用硬件触发,

STM32F103C8T6只有DMA1资源,没有DMA2。

DMA存储器映像

ROM:只读存储器,是一种非易失性,掉电不丢失的存储器,

RAM:随机存储器,易失性、掉电容易丢失的存储器

ROM:分为了3部分,第一块是程序存储器Flash,主闪存,用于存储C语言编译后的程序代码,下载程序的部分,运行程序 ,一般也是从主闪存里开始运行的,起始地址0x0800 0000,然后剩余字节的地址依次增长,每个字节都分配独一无二的地址,系统存储器和选项字节,也是ROM的一种,掉电不丢失,实际上存储介质也是Flash,地址都是0x1FFF开头,这两块区域都在ROM区域的最后面,系统存储器的作用用于存储BootLoader,用于串口下载,BootLoader是芯片出厂自动写入的,一般不允许修改, 选项字节的作用是用于存储一些独立于程序代码的配置参数,选项字节,存储的主要是Flash的读保护、写保护、还有看门狗的配置等。

RAM:运行内存SRAM,分配的地址是0x2000 0000 作用是存储运行过程中的临时变量,也就是在程序中定义变量、数组、结构体的地方。类比电脑,就是电脑的内存条, 外设寄存器,作用是存储各个外设的配置参数,初始化各个参数,最终读写的东西,外设存储器也是存储器的一种,存储介质其实也是SRAM, 内核外设寄存器,地址是0xE000 0000 作用是存储内核各个外设的配置参数。内核外设就是NVIC和SysTick,因为内核外设和其他外设不是一个厂家设计的,所以地址也是分开的。

DMA框图

左上角是Cortex-M3的内核,包含了CPU和内核外设等,其他的所有东西都可以看成存储器,Flash是主闪存,SRAM是运行内存,各个外设都可以看成存储器,也是SRAM存储器,寄存器是一种特殊的存储器,CPU可以对其进行读写,就像写运行内存一样,每个寄存器都连接了一根导线,每根导线可以用于控制外设电路的状态,如:引脚的高低电平、导通和断开开关、切换数据选择器, 所以寄存器是连接软件和硬件的桥梁 ,软件读写寄存器,就相当于控制硬件的执行。

总线矩阵的左端是主动单元,也是拥有存储器的访问权,右边是被动单元,他们的存储单元只能被左边的存储器读取,内核有DCode、系统总线,可以访问右边的存储器,DCode专门用于访问Flash,系统总线用于访问其他东西的,DMA要转运数据,DMA也拥有主动访问权,主动单元有内核CPU、DMA总线,DMA1和DMA2都各有一条DMA总线,DMA1有7个总线,DMA2有5个总线,各个通道可以设置他们转运数据的源地址和目的地地址, 多个通道可以独立转运数据,但DMA总线只有一条,所有通道都只能分时复用一条DMA总线,如果产生了冲突,那就由仲裁器来根据通道的优先级决定通道的使用顺序, 总线矩阵这里也有一个总裁器,如果CPU和DMA访问同一目标,那么DMA就会停止访问,防止冲突,不过总线总裁器,仍然会保证得到CPU一半的总线带宽。

仲裁器下面是AHB从设备,也就是DMA自身的寄存器,DMA作为一个外设,它自己也会有相应的配置寄存器,这里连接了AHB总线上, DMA,即是总线矩阵的主动单元,可以读写各种存储器,也是AHB的被动单元,CPU可以对其进行配置,DMA请求是DMA的硬件资源,可以得到各个外设触发的DMA请求。

右上角的Flash是ROM的一种,如果通过总线直接访问,无论是DMA、CPU都是只读,而不能写入。而SRAM可以任意读写。外设寄存器得看参考手册,有的寄存器只能读,有的寄存器只能写。

DMA逻辑框图

两大数据转运站点,左边是外设寄存器,右边是存储器站点,包括Flash,SRAM, DMA的数据转运可以从外设寄存器到存储器,也可以从存储器到外设寄存器, 具体方向是可以由参数控制,外设和存储器都有三个参数,第一个是起始地址,有外设端的起始地址,和存储端的起始地址,这两个参数决定了数据从哪里来到哪里去,第二个参数是数据宽度,指一次转运要按多大的数据宽度进行,可以选择字节Byte,半字HalfWord和字Word,字节8位(uint8_t),半字16位(uint16_t),字32位,第三个参数是地址是否自增。第一次数据转运后,地址是否增加到第二位,。 如果要进行存储器到存储器之间的转运,就需要把其中一个存储器的地址,放到外设这个站点处。这样就能进行存储器到存储器之间的转运了。只要你在外设起始地址写Flash或者SRAM的地址,那他就回去Flash或者SRAM找数据。

传输计数器,用来指定总共需要转运几次,是一个自减计数器,每转运一次,计数器就会减1,一直到0,DMA就不会在转运数据了。减到0之后,之前自增的地址,也会恢复到起始地址的位置,在传输计数器的右边还有一个自动重装器,作用就是传输计数器减到0之后,是否要恢复到最初的值,不重装,DMA转运完就结束了,如果重装,传输计数器会恢复到初始值,如:给个5,减到0之后,传输计数器恢复到5.一直循环。

下面的部分就是触发部分,决定了DMA在什么时机进行运行,触发源有硬件触发和软件触发,由M2M这个参数来决定。给1时就是软件触发,这个软件触发的逻辑是以最快的速度,连续不断的触发DMA,争取早点把DMA清零,完成这一轮的转换,软件触发和循环模式不能同时用,如果同时用,DMA就停不下来,一般用于存储器到存储器之间的搬运。给0时,就是硬件触发,硬件触发源可以选择ADC、串口、定时器等等。 还有右下角的开关控制。

DMA转运的条件:第一、开关控制 第二、传输计数器必须大于0 第三、必须有触发信号, 注意:传输计数器减到0之后,再写传输计数器,必须先关闭DMA,再写。不能在DMA 开启的时候写DMA。

一些细节问题

DMA1的请求映像,有7个通道,每个通道都有数据选择器,可以选择硬件触发和软件触发,EN是决定这个选择器要不要工作,en=0,不工作,en=1,数据选择器工作,M2M等于1时,选择软件触发,最左边的一列时硬件触发源,每个通道的硬件触发源都是不同的,如果用ADC1触发,那只能选择通道1。如果需要定时器2的更新事件来触发的话,那就必须选择通道2。如果用软件触发的话,那么通道就随便选择了,因为每个通道的软件触发都一样。 最后七个通道进入仲裁器进行判断,最终产生内部的DMA请求。 通道号越小,通道优先级越高,也可以在程序中配置优先级。

数据宽度对齐: 数据转运时,数据宽度都一样,正常转运,如果不一样,上个表就是说传输宽度不一样时,会产生哪些反应。如果目标数据宽度比源端数据宽度大,那就在目标数据宽度多出来的空位补0.。当目标数据宽度比源端数据宽度小时,那么多出来的高字节部分就舍弃掉。

逻辑运行例子

数据转运加DMA : 将SRAM里的DataA转运到DataB里 首先是外设站点和存储器站点,地址、数据宽度、是否自增三个参数。在这里,外设A填写DataA的地址,存储器则写DataB的地址。数据宽度两个都是uint8_t 都是8字节传输。两边地址都应自增。传输计数器给7,自动重装则不需要。触发选择则选择软件触发,M2M给1。这里转运是一种复制转运,转运完成之后,数据并不会消失。

ADC扫描加DMA 左边是ADC扫描模式的执行流程,有7个通道,触发后依次进行通道AD转化,转换完成之后都放在ADC_DR寄存器里面(规则组通道存储寄存器),在每个单独通道转换完成后,进行一次数据搬运,目的地址进行自增,防止数据被覆盖, 配置如下:外设地址,写入ADC_DR地址,存储器站点写DMA存储器的地址,要配置地址自增。 数据宽度都要uint16的数据,都是半字传输。外设地址步自增,存储器地址自增。传输方向是ADC_DR外设地址到DMA存储器地址 。传输计数器7次,计数器是否重装,要看ADC配置,如果是单次扫描,计数器就不重装,如果是连续扫描,DMA就重装,ADC和DMA同时工作,触发源选择,ADC_DR的值在ADC单个通道转换完成才有效,DMA搬运数据的时机要和ADC单个通道转换才有效,ADC扫描模式触发转换后,它不会产生中断和标志位信号,但会产生DMA请求信号。

DMA配置步骤

第一步:打开时钟

第二步:配置参数

第三步:使能

代码

DMA软件触发,

存储器到存储器搬运

#include "stm32f10x.h"                  // Device header

uint16_t Mysize;

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t size)
{
	Mysize = size;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_BufferSize = size;				
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;		//传输方向外设-》存储器
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;						//软件触发
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;					//存储器地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;		//以字节传输
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;			//地址自增
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;					//模式 正常模式
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;			//存储器地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;		//以字节传输
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;		//地址自增
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;			//优先级中等
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1,ENABLE);
}

void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1,ENABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1,Mysize);		//写入计数器
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);		//等待标志位完成
	DMA_ClearFlag(DMA1_FLAG_TC1);
}

主函数

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

uint8_t DataA[] = {0xFF,0x11,0x90,0x45};
uint8_t DataB[] = {0x00,0x00,0x00,0x00};

int main(void)
{
	OLED_Init();
	MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
	
		/*显示静态字符串*/
	OLED_ShowString(1, 1, "DataA");
	OLED_ShowString(3, 1, "DataB");
	
		/*显示数组的首地址*/
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);

	
	
	while(1)
	{
//		DataA[0] ++;		//变换测试数据
//		DataA[1] ++;
//		DataA[2] ++;
//		DataA[3] ++;
				
		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);		//显示数组DataB
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);		//延时1s,观察转运前的现象
		
		MyDMA_Transfer();	//使用DMA转运数组,从DataA转运到DataB
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);		//显示数组DataB
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);

		Delay_ms(1000);		//延时1s,观察转运后的现象

	}
}

DMA配合ADC

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];					//定义用于存放AD转换结果的全局数组

/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	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);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
	
	/*规则组通道配置*/
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	//规则组序列1的位置,配置为通道0
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);	//规则组序列2的位置,配置为通道1
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);	//规则组序列3的位置,配置为通道2
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);	//规则组序列4的位置,配置为通道3
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;											//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;							//模式,选择独立模式,即单独使用ADC1
	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_NbrOfChannel确定
	ADC_InitStructure.ADC_NbrOfChannel = 4;										//通道数,为4,扫描规则组的前4个通道
	ADC_Init(ADC1, &ADC_InitStructure);											//将结构体变量交给ADC_Init,配置ADC1
	
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStructure;											//定义结构体变量
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;				//外设基地址,给定形参AddrA
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据宽度,选择半字,对应16为的ADC数据寄存器
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址自增,选择失能,始终以ADC数据寄存器为源
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;					//存储器基地址,给定存放AD转换结果的全局数组AD_Value
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			//存储器数据宽度,选择半字,与源数据宽度对应
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//存储器地址自增,选择使能,每次转运后,数组移到下一个位置
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
	DMA_InitStructure.DMA_BufferSize = 4;										//转运的数据大小(转运次数),与ADC通道数一致
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;								//模式,选择循环模式,与ADC的连续转换一致
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;								//存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;						//优先级,选择中等
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);								//将结构体变量交给DMA_Init,配置DMA1的通道1
	
	/*DMA和ADC使能*/
	DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能
	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
	ADC_Cmd(ADC1, ENABLE);									//ADC1使能
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
	/*ADC触发*/
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}

主函数

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

int main(void)
{
	/*模块初始化*/
	OLED_Init();				//OLED初始化
	AD_Init();					//AD初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	OLED_ShowString(3, 1, "AD2:");
	OLED_ShowString(4, 1, "AD3:");
	
	while (1)
	{
		OLED_ShowNum(1, 5, AD_Value[0], 4);		//显示转换结果第0个数据
		OLED_ShowNum(2, 5, AD_Value[1], 4);		//显示转换结果第1个数据
		OLED_ShowNum(3, 5, AD_Value[2], 4);		//显示转换结果第2个数据
		OLED_ShowNum(4, 5, AD_Value[3], 4);		//显示转换结果第3个数据
		
		Delay_ms(100);							//延时100ms,手动增加一些转换的间隔时间
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值