stm32-DMA(直接存储器存取)

一、知识点

1.DMA(Direct Memory Access)直接存储器存储

  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 外设:一般指外设的数据计算器DR(data register),比如ADC的数据寄存器等
  • 存储器:运行内存SRAM和程序存储器Flash,是存储变量和程序代码的地方

2.通道资源

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

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

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

若是存储器到存储器的数据转运,比如把flash里的一批数据转运到SRAM中去,就需要软件触发,触发后,DMA就会一股脑的转运,以最快的速度全部转运好
若是外设到存储器的数据转运,不能一股脑的转运,因为外设的数据是有一定时机的,需要硬件触发。比如转运ADC的数据,就得ADC每个通道AD转换完成后,硬件触发一次DMA,之后DMA再转运,触发一次转运一次,才能保证数据正确
特定的硬件触发:每个DMA的通道,它的硬件出发源是不一样的,当使用某个外设的硬件出发源,就得使用它连接的那个通道,而不能任意选择通道

3.存储器和存储器映射

在这里插入图片描述

a.ROM(只读存储器)

  • 一种非易失性、掉电不丢失的存储器
  • 程序存储器flash(主闪存)的用途:存储c语言编译后的程序代码,也是下载程序的位置,运行程序一般也是从主闪存里面开始运行的,数据地址以0800开头的,为主闪存地址
  • 系统存储器:BootLoader程序是芯片出厂自动写入的,一般不允许我们修改
  • 选项字节:用于存储一些独立于程序代码的配置参数,位置是在ROM区的最后面,下载程序可以不刷新选项字节的内容,这样选项字节的配置就可以保持不变,选项字节里,存的主要是flash的读保护、写保护,还有看门狗等等的配置

const常量,存储在flash,则地址开头0x0800 0000,节省SRAM,若有很大的查找表或者字库,最好加const

b.RAM(随机存储器)

  • 一种易失性、掉电丢失的存储器
  • 内核外设寄存器:内核外设就是NVIC和SysTick,内核外设和其他外设不是同个厂家设计的,所以地址被分开

c.寄存器读写

  • CPU/DMA直接访问Flash,是可以读不可写
  • SRAM是运行内存,可以任意读写
  • 外设寄存器:看数据手册(数据寄存器是可以读写的)

4.DMA基本结构

在这里插入图片描述

a.外设和存储器

  • 外设寄存器与存储器的数据转运(需要注意的是存储器中Flash是只读的,不能写入;三者均可以读出,由方向控制)

  • 起始地址:数据的来源/目的地

  • 数据宽度:字节byte(8位,uint8_t),半字HalfWord(16位,uint16_t),字Word(32位,uint32_t)

  • 地址是否自增,读取ADC_DR寄存器是不用自增;一般存储数据的地址要自增(防止数据被覆盖)

b.传输计数器

指定一共需要转运多少次,每转运一次,传输计数器自减,减到0停止。

c.自动重装器

计数传输器减到0后可由自动重装器重装,若使用自动重装器,则进入循环模式,否则为单次模式。

d.M2M(DMA的触发控制)

1为软件触发,0为硬件触发

  • 软件触发
    以最快的速度,连续不断地触发DMA,争取早日把传输计数清0,完成这一轮转换(不能和循环模式同时使用,因为软件触发是清0,循环模式是清0后自动重装,同时用,DMA停不下来)
  • 硬件触发
    硬件触发源可以选择ADC、串口收到数据、定时时间到等等,所以需要硬件触发
    硬件达到时机时,传一个信号,来触发DMA进行转运
    开关控制,给DMA使能后,DMA就准备就绪,可以进行转运
    在这里插入图片描述

e.DMA转运条件

1、开关控制,DMA_Cmd必须使能
2、传输计数器必须大于0
3、触发源必须有触发信号,触发一次转运一次,传输计数器自减一次。当传输计数器等于0,且没有自动重装时,这时无论是否触发,DMA都不会再进行转运了,此时就需要DMA_Cmd给DISABLE,关闭DMA,再为传输计数器写一个大于0的数,在DMA_Cmd给Elable开启DMA,DMA才能继续工作,注意下写传输计数器时,必须要先关闭DMA再进行,不能在DMA开启时写传输计数器,这是规定。

f.数据传输

小写大,空的补0;大写小,高位舍弃

4.数据转运+DMA

在这里插入图片描述
转运时,两边都要自增,要转运7次,所以传输计数器给7,自动重装暂时不需要,是存储器到存储器,要软件触发

5.ADC扫描模式+DMA

在这里插入图片描述

  • 单个通道转换完成后,不产生任何标志位和中断。但是可能会产生DMA请求,去触发DMA转运(Up主测验过)
  • 触发后,7个通道依次进行AD转换,并一次写入ADC1_DR中,DMA依次进行转运

传输计数器:计次7次,因为由7个通道
计数器是否自动重装,取决ADC的配置,ADC如果是单次扫描,DMA不自动重装,连续扫描,DMA就自动重装
在ADC启动下一轮转换的时候,DMA也启动下一轮的转运,ADC和DMA同步工作
触发选择:这里ADValue的值是在ADC单个通道转换完成后才会有效,所以转运DMA时机,需要和ADC单个通道转换完成同步,故DMA的触发要选择ADC的硬件触发

二、实例

1.基本思路

在这里插入图片描述
一、开启时钟
二、配置DMA外设,存储器,和一些细节的配置
三、使能(DMA_Cmd)

2.代码

a.DMA数据转运

mydma.c

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t Addra,uint32_t Addrb,uint16_t size)
{
	MyDMA_Size=size;
	//开启时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	//配置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目的地
	DMA_InitStructure.DMA_MemoryBaseAddr=Addrb;
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
    //DMA具体配置
	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_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);
}

main.c

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

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


int main(void)
{
	
	OLED_Init();
	//传输4次
	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);  //显示地址(为32位)
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
		
	while (1)
	{
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		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);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();//数据会一个个转运,跟用for循环赋值的效果一样
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		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);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);

		Delay_ms(1000);
		
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		
	}
}

b.DMA+AD多通道

AD.c

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//扫描4个通道,相当于点4个菜,菜单上的1~4号的空位,填上了0~3这4个通道
	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_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
	ADC_InitStructure.ADC_NbrOfChannel=4;//一共四个菜
	ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描方式,告诉厨师点了多个菜,不要只盯着一个数看
	ADC_Init(ADC1,&ADC_InitStructure);
	
	//当作服务员
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设地址不自增
	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_Normal;  //不自动重装
	DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;  //硬件触发
	DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1,ENABLE);
	ADC_DMACmd(ADC1,ENABLE);  //选择硬件触发源
	ADC_Cmd(ADC1,ENABLE);
	
	ADC_ResetCalibration(ADC1); 
	while(ADC_GetResetCalibrationStatus(ADC1)==SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1)==SET);
}

	
void  AD_GetValue(void)
{
	DMA_Cmd(DMA1_Channel1,DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1,4);
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);
}

main.c

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

int main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	
	while(1)
	{
		AD_GetValue();
		
		OLED_ShowNum(1,5,AD_Value[0],4);
		OLED_ShowNum(2,5,AD_Value[1],4);
		OLED_ShowNum(3,5,AD_Value[2],4);
		OLED_ShowNum(4,5,AD_Value[3],4);
		
		Delay_ms(100);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值