STM32学习笔记——DMA直接存储器存取

#参考江科大STM32视频#

一、DMA简介

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

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

存储器映像

类型起始地址存储器用途
ROM0x0800 0000程序存储器Flash存储C语言编译后的程序代码
0x1FFF F000系统存储器存储BootLoader,用于串口下载
0x1FFF F800选项字节存储一些独立与程序代码的配置参数
RAM0x2000 0000运行内存SRAM存储运行过程中的临时变量
0x4000 0000外设寄存器存储各个外设的配置参数
0xE000 0000内核外设寄存器存储内核各个外设的配置参数

DMA框图

DMA基本结构图

STM32两大“站点”包括外设寄存器存储器Flash、SRAM。在STM32中,存储器一般特指Flash和SRAM,不包括外设寄存器,外设寄存器一般直接称为外设。

DMA转运方式可以是外设到存储器,也可以是存储器到外设,具体向左还是向右,有个方向的参数进行控制。此外,还有一种转运方式是存储器到存储器,比如Flash到SRAM或者SRAM到SRAM这两种方式。由于Flash是只读的,所以DMA不可以进行SRAM到Flash,或者Flash到Flash的转运操作。

外设和存储器两个站点都有3个参数:

  1. 起始地址,包括外设起始地址和存储器起始地址,两个参数决定了数据从哪里来,到哪里去。
  2. 数据宽度,指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位,uint8_t)、半字HalfWord(16位,uint16_t)和字Word(32位,uint32_t)。比如转运ADC数据,ADC的结果是uint16_t,所以选择半字。
  3. 地址是否自增,指定一次转运完成后,下次转运是不是要把地址移动到下一个位置去,相当于指针p++。比如ADC扫描模式,外设寄存器地址不用自增,存储器地址需要自增,每转运一个数据后,就向后移动,否则就将之前数据覆盖了。

如果要进行存储器到存储器转运,就需要把其中一个存储器的地址放在外设的站点。

 传输计数器:指定DMA进行几次数据转运,它是一个自减计数器,如果我给定一个值为5,每进行一次转运,它的值就减1,当传输计数器减到0后,DMA停止转运,并自增地址也会恢复到起始地址。

自动重装器:当传输计数器减到0后,是否自动恢复到最初的值。比如最初传输计数器给5,如果不使用自动重装器,那在转运5次后,DMA就结束了。它决定了转运的模式,不重装,就是正常的单次模式,重装,就是循环模式。

触发控制:决定DMA需要在什么时机进行转运。触发源包括硬件触发和软件触发,由M2M(Memory to Memory——存储器到存储器)参数决定。

  • 若给M2M位1,DMA选择软件触发(不是调用某个函数进行触发,而是以最快的速度,连续不断的触发DMA,直到传输计数器清零。可以理解为连续触发),软件触发和自动重装器的循环模式不能同时使用,一个是清零,一个是清零后重装,同时用会导致DMA死循环。软件触发适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动、不需要时机,并且想尽快完成任务。
  • 若给M2M位0,DMA选择硬件触发,硬件触发源可以选择ADC、串口、定时器等。使用硬件触发的转运,一般是与外设有关的转运,这些转运需要一定的时机,如ADC转换完成、串口收到数据、定时时间到等等。所以需要使用硬件触发,在硬件达到这些时机时,传入一个信号,触发DMA进行转运。

开关控制:即函数DMA_Cmd。

在写入传输计数器时,必须要先关闭DMA,再进行数据写入,不能在DMA开启时,写传输计数器。

DMA请求

 

DMA每个通道的硬件触发源都不同,如果想使用某个硬件的话,需要找到对应的通道。

 数据宽度与对齐

数据转运 + DMA

起始地址:DMA外设对应DataA[7],DMA存储器对应DataB[7]

数据宽度:都是uint8_t

地址是否自增:两个站点的地址都应该自增

方向:外设站点转运到存储器站点

传输计数器:7个数据所以应该转运7次,传输计数器给7

自动重装器:暂时不需要

触发选择:软件触发,存储器到存储器,不需要特定时机

开启DMA

ADC扫描模式 + DMA

 二、程序部分

1. 变量和常量

#include OLED

uint8_t a = 0x66;

int main(void)
{
    OLED_Init;

    OLED_ShowHexNum(1,1,a,1);
    OLED_ShowHexNum(2,1,(uint32_t)&a,8);
}

定义一个变量a,在OLED中显示它的地址为2000 0000.

由下表可知a被存储在了SRAM中

“&”放在变量前,表示取该变量的地址

并且变量取地址后,应该存在一个指针变量中,若是想作为数字来显示,还要在前面加上强制转换(uint32_t),如果不加强制类型转换,就是指针跨级赋值了。

const uint8_t a = 0x66;

 若在变量前面加关键字 const ,表示常量,在程序中只能读,不能写。

正常情况下我们使用的都是变量,那什么时候需要定义常量?

当程序中出现了大批数据,并且不需要更改时,就可以把它定义为常量,节省SRAM空间,如查找表、字库等数据。

对于变量和常量,它的地址是由编译器决定的,不同程序地址可能不一样,是不固定的。但是对于外设寄存器来说,它的地址是固定的,可以在手册中查阅,在程序中可以用结构体访问寄存器。

//比如访问ADC1的DR寄存器
ADC1->DR

//访问地址
OLED_ShowHexNum(2,1,(uint32_t)&ADC1->DR,8);

 显示地址为4001 244C,对应下表外设寄存器。

存储器映像

类型起始地址存储器用途
ROM0x0800 0000程序存储器Flash存储C语言编译后的程序代码
0x1FFF F000系统存储器存储BootLoader,用于串口下载
0x1FFF F800选项字节存储一些独立与程序代码的配置参数
RAM0x2000 0000运行内存SRAM存储运行过程中的临时变量
0x4000 0000外设寄存器存储各个外设的配置参数
0xE000 0000内核外设寄存器存储内核各个外设的配置参数

2. DMA数据转运

 相关库函数

//恢复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);

//初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

//结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);

//使能
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

//中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

//DMA设置当前寄存器,给传输计数器写入数据
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

//DMA获取当前数据寄存器,返回传输计数器的值
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

//获取标志位状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

//清除标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);

//获取中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);

//清除中断挂起位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);

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_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;				//存储器到存储器(硬件触发 or 软件触发)
	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, 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)
{
	delay_init();
	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);
		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(); 
		
		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);
	}
}

3. ADC多通道+DMA

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);	//ADCCLK = 72MHz / 6 = 12MHz
	
	GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	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_ScanConvMode = ENABLE;							//扫描转换模式
	ADC_InitStructure.ADC_NbrOfChannel = 4; 								//通道数目
	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;			//存储器到存储器(硬件触发 or 软件触发)
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;	//优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	//DMA_Cmd(DMA1_Channel1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);	//开启DMA触发信号
	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();
	delay_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);
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值