STM32f103入门(11)DMA直接存储器读取

DMA

  • DMA简介
    • 数据转运
    • ADC扫描模式+DMA
      • DMA + ADC 连续单词扫描初始化如下
      • 连续循环扫描

DMA简介

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

STM32F103C8T6 DMA资源:DMA1(7个通道)STM32f103入门(11)DMA直接存储器读取_第1张图片
STM32f103入门(11)DMA直接存储器读取_第2张图片
STM32f103入门(11)DMA直接存储器读取_第3张图片
STM32f103入门(11)DMA直接存储器读取_第4张图片

数据转运

Flash 只读 SRAM 可读可写
可以外设 到 存储器 双向的转运
也可以存储器内部 FLas 到 SRAM ,SRAM到SRAM

软件触发 存储器到存储器 不需要等待硬件时机
STM32f103入门(11)DMA直接存储器读取_第5张图片
const 定义 存储到Flash内存里 不能被修改
根据上述的地址信息 如果我们定义一个int类型的a 那么 a的起始地址就是0x2000 0000

外设寄存器的地址也可查找到,

初始化第一步,RCC开启DMA的时钟
第二步 直接调用DMA_Init() 初始化这里的各个参数,包括外设和存储器站点的起始地址,数据宽度,地址是否自增,方向,传输计数器,是否需要自动重装,选择触发源,还有一个通道优先级,之后就可以进行开关控制,DMA_Cmd

函数解释如下

//恢复缺省配置
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);

//Cmd使能
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); 

//获取当前数据寄存器 返回传输计数器的值
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);

DMA初始化代码如下

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;
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, DISABLE);
}

对上述代码进行解释

这段代码是用来初始化 DMA1 通道1 的 DMA(Direct Memory Access)配置。

首先,代码将传入的参数 Size 赋值给全局变量 MyDMA_Size。

接着,代码调用 RCC_AHBPeriphClockCmd 函数打开 DMA1 的时钟。

然后,代码声明了一个 DMA_InitTypeDef 结构体变量 DMA_InitStructure,并对其成员进行了配置:

DMA_InitStructure.DMA_PeripheralBaseAddr:指定了外设的基地址,即数据的源地址,这里为 AddrA。
DMA_InitStructure.DMA_PeripheralDataSize:指定了外设数据大小,这里设置为字节单位。
DMA_InitStructure.DMA_PeripheralInc:指定了外设递增模式,使能后每次传输后自动增加外设地址。
DMA_InitStructure.DMA_MemoryBaseAddr:指定了存储器的基地址,即数据的目标地址,这里为 AddrB。
DMA_InitStructure.DMA_MemoryDataSize:指定了存储器数据大小,这里设置为字节单位。
DMA_InitStructure.DMA_MemoryInc:指定了存储器递增模式,使能后每次传输后自动增加存储器地址。
DMA_InitStructure.DMA_DIR:指定了传输方向,这里设置为外设作为数据源。
DMA_InitStructure.DMA_BufferSize:指定了数据传输的大小,即需要传输的数据字节数。
DMA_InitStructure.DMA_Mode:指定了 DMA 的工作模式,这里设置为普通模式。
DMA_InitStructure.DMA_M2M:指定了是否允许存储器到存储器的传输,这里设置为使能。
DMA_InitStructure.DMA_Priority:指定了 DMA 的优先级,这里设置为中等优先级。
最后,代码调用 DMA_Init 函数,将上述配置应用到 DMA1 通道1 上。

最后一行代码调用 DMA_Cmd 函数将 DMA1 通道1 禁用。你可能需要在适当的时候通过 DMA_Cmd 函数启用 DMA 传输操作。

请注意,这段代码只是初始化了 DMA 的配置,实际的数据传输操作需要进一步编写。你可能需要编写中断服务函数来处理 DMA 传输完成中断,并在其中进行相应的操作,如处理传输完成事件或重置传输相关的标志位等。

DMA使能模块

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);
}

对上面代码进行解读

这段代码是一个用于 DMA 数据传输的函数 MyDMA_Transfer。

首先,代码使用 DMA_Cmd 函数将 DMA1 通道1 禁用,以确保在开始传输之前 DMA 处于停止状态。

然后,代码使用 DMA_SetCurrDataCounter 函数设置 DMA1 通道1 的当前传输数据数量为 MyDMA_Size。MyDMA_Size 是一个全局变量,代表需要传输的数据大小(字节数)。

接下来,代码再次使用 DMA_Cmd 函数启用 DMA1 通道1,此时 DMA 将开始执行数据传输操作。

然后,代码进入一个循环,使用 DMA_GetFlagStatus 函数检查 DMA1 通道1 的传输完成标志位 DMA1_FLAG_TC1 是否被置位(即是否传输完成)。在传输完成之前,循环将一直执行。

当传输完成时,代码使用 DMA_ClearFlag 函数清除 DMA1 通道1 的传输完成标志位。

整个函数的作用是启动 DMA 数据传输,并等待传输完成。请确保在调用 MyDMA_Transfer 函数之前已经正确地配置了 DMA1 通道1 的相关参数,并将传输的数据存储到正确的位置。

需要注意的是,该函数中存在一个死循环,在数据传输完成之前会一直占用 CPU 资源,可以根据具体情况进行优化,例如在循环中添加适当的延时或者结合中断服务函数来处理传输完成事件。

main模块

#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();
	
	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);
	}
}

ADC扫描模式+DMA

STM32f103入门(11)DMA直接存储器读取_第6张图片

DMA + ADC 连续单词扫描初始化如下

#include "stm32f10x.h"                  // Device header

uint16_t AD_value[2];
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);//72/6
	
	//gpio config
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// input moni
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
		

	//gui ze zu 
		ADC_RegularChannelConfig(ADC1, ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
			ADC_RegularChannelConfig(ADC1, ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	
	ADC_InitTypeDef ADC_InitStruture;
	//ADC?????  ???????ADC??
	ADC_InitStruture.ADC_Mode=ADC_Mode_Independent;
	//????
	ADC_InitStruture.ADC_DataAlign=ADC_DataAlign_Right;
	//??????? ????
	ADC_InitStruture.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
	//???? ???
	ADC_InitStruture.ADC_ContinuousConvMode= DISABLE; // dan ci sao miao
	ADC_InitStruture.ADC_ScanConvMode=ENABLE; //sao miao
	//????
	ADC_InitStruture.ADC_NbrOfChannel =2 ;
	ADC_Init(ADC1, &ADC_InitStruture);
	
	

	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //from  a ddr
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // buzi zeng
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_value;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // cun chu qi di zhi zi zeng
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = 2;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//ying jian chi fa
	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);//reset
	while(ADC_GetResetCalibrationStatus(ADC1)==SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1)==SET);
	
}
void AD_GetValue(){
	DMA_Cmd(DMA1_Channel1, DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1, 2);
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//ruan jian chu fa
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	 DMA_ClearFlag(DMA1_FLAG_TC1);
}

解释

这段代码是用于初始化和获取 ADC 数据的函数。

首先,在 AD_Init 函数中,对相关的外设和引脚进行了初始化配置。具体包括:

使能 ADC1、GPIOA 和 DMA1 的时钟。
配置 ADC1 对应的 GPIO 引脚为模拟输入模式。
配置 ADC1 的工作模式、对齐方式、转换触发方式等。
配置 DMA1_Channel1 的参数,包括传输的起始地址、数据大小、传输方向等。
使能 DMA1_Channel1、ADC1 的 DMA 传输和 ADC1。
然后,在 AD_GetValue 函数中,先禁用 DMA1_Channel1,然后使用 DMA_SetCurrDataCounter 函数设置传输数据数量为 2(即 AD_value 数组的大小),再次启用 DMA1_Channel1。
接着,使用 ADC_SoftwareStartConvCmd 函数启动 ADC1 的软件转换。
最后,通过循环判断 DMA1_FLAG_TC1 标志位是否被置位来等待数据传输的完成,并使用 DMA_ClearFlag 函数清除标志位。

请确保在调用 AD_GetValue 函数之前已经正确地配置了 ADC1 和 DMA1_Channel1 的相关参数,并将传输的数据存储到 AD_value 数组中。

main如下

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

uint16_t Speed;
uint16_t AD0,AD1;
int main(void)
{
	OLED_Init();
	AD_Init();
	OLED_ShowString(1,1,"V1");
	OLED_ShowString(2,1,"V2");
	while (1)
	{
		AD_GetValue();
		OLED_ShowNum(1,3,AD_value[0],4);
		OLED_ShowNum(2,3,AD_value[1],4);
	}
}

连续循环扫描

#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_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_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 = ENABLE;
	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_Circular;
	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);
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

main

#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)
	{
		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);
	}
}

你可能感兴趣的:(stm32,嵌入式硬件,单片机)