stm32入门-----DMA直接存储器存取(上——实践篇)

 目录

前言

DMA相关函数 

1.初始化 

2.中断相关函数

3.DMA使能

4.设置计数器的值 

5.DMA标志位相关函数

项目实践

1.DMA转运存储器之间的数据

2.DMA转运ADC转换的数据


前言

        上一期我们学习了DMA直接存储器存取的理论知识点(上一期:stm32入门-----DMA直接存储器存取(上——理论篇)-CSDN博客),那么本期我们就来进行关于DMA的项目实践,本期有两个项目,分别是DMA实现数组的转运和DMA实现AD多通道数据转运。(视频:[8-2] DMA数据转运&DMA+AD多通道_哔哩哔哩_bilibili

本期的代码我已上传至百度网盘,可自行下载。

 链接: https://pan.baidu.com/s/1gOml_COgxA9tSxwo_tHFOQ?pwd=0721

提取码: 0721 

DMA相关函数 

下面这些就是DMA的功能函数,这里我会一一讲解。

 

1.初始化 

 下面三个函数是对结构体初始化的函数。

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); //使用默认结构体初始化

 下面看个示例:

//配置结构体
    DMA_InitTypeDef DMA_initstruct;
    //起始外设的三个参数
    DMA_initstruct.DMA_PeripheralBaseAddr = addr_A;  //起始数据的地址
	DMA_initstruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte ;   //数据的宽度,这里选择8位,即一个字节
    DMA_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;  //选择地址是否自增,这里是数组的转运选择自增
    //目标存储器的三个参数
    DMA_initstruct.DMA_MemoryBaseAddr = addr_B;
	DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte ;//选择目标存储器的数据宽度,同样是一个字节宽度
    DMA_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //选择地址是否自增,同样也是选择自增

    DMA_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC; //选择传输方向,这里选择外设到存储器的方向A->B
    DMA_initstruct.DMA_BufferSize = size; //设置传输的次数,其取值范围是0~65535
    DMA_initstruct.DMA_Mode= DMA_Mode_Normal ; //自动重装的选择,如果选择了软件触发方式是不能选择自动重装的,所以我们这边软件触发就选择普通模式
    DMA_initstruct.DMA_M2M = DMA_M2M_Enable; //选择触发方式,我们这里是数组的复制,存储器到存储器的内容,所以选择最快的软件触发
    DMA_initstruct.DMA_Priority = DMA_Priority_Medium; //优先级,如果是多个通道的话就要去选择好,我们这里只有一个通道所以随便选

    DMA_Init(DMA1_Channel1, &DMA_initstruct); //这里我们既选择了DMA几,也选择了通道

2.中断相关函数

下面这个函数是开启DMA中断使能的函数,我们看到这个config都大概联系到中断之类的。

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

下面这两个是DMA中断相关的函数,获取中断状态标志位,还有就是清除中断标志位。 

ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
void DMA_ClearITPendingBit(uint32_t DMAy_IT);

3.DMA使能

下面是DMA使能的函数,前面学了这么多看到cmd的都知道是开启使能的操作了。

DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

4.设置计数器的值 

下面这个函数是用来去设置计数器的值的函数,如果我们没有选择自动重装,那么如果想再次开启DMA的时候就需要去对搬运计数器进行装值。

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

 下面这个是获取当前计数器值的函数,是用来查看计数的次数的,如果你想去看还有多少没有转运就可以用这个看看,不过没有特定需求的话一般不用到。

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

5.DMA标志位相关函数

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//获取DMA转运标志位状态
void DMA_ClearFlag(uint32_t DMAy_FLAG); //清除DMA转运标志位

项目实践

1.DMA转运存储器之间的数据

实验现象:

DMA转运存储器数据

电路连接图:

工程文件:

DMA.c文件代码:

#include "stm32f10x.h"                  // Device header

uint16_t count;

void MyDMA_init(uint32_t addr_A, uint32_t addr_B, uint16_t size) {
    count = size; //先存一份在全局变量,有大用

    //开启DMA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    //配置结构体
    DMA_InitTypeDef DMA_initstruct;
    //起始外设的三个参数
    DMA_initstruct.DMA_PeripheralBaseAddr = addr_A;  //起始数据的地址
	DMA_initstruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte ;   //数据的宽度,这里选择8位,即一个字节
    DMA_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;  //选择地址是否自增,这里是数组的转运选择自增
    //目标存储器的三个参数
    DMA_initstruct.DMA_MemoryBaseAddr = addr_B;
	DMA_initstruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte ;//选择目标存储器的数据宽度,同样是一个字节宽度
    DMA_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //选择地址是否自增,同样也是选择自增

    DMA_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC; //选择传输方向,这里选择外设到存储器的方向A->B
    DMA_initstruct.DMA_BufferSize = size; //设置传输的次数,其取值范围是0~65535
    DMA_initstruct.DMA_Mode= DMA_Mode_Normal ; //自动重装的选择,如果选择了软件触发方式是不能选择自动重装的,所以我们这边软件触发就选择普通模式
    DMA_initstruct.DMA_M2M = DMA_M2M_Enable; //选择触发方式,我们这里是数组的复制,存储器到存储器的内容,所以选择最快的软件触发
    DMA_initstruct.DMA_Priority = DMA_Priority_Medium; //优先级,如果是多个通道的话就要去选择好,我们这里只有一个通道所以随便选

    DMA_Init(DMA1_Channel1, &DMA_initstruct); //这里我们既选择了DMA几,也选择了通道

    //开启DMA
    DMA_Cmd(DMA1_Channel1, DISABLE);

}

//给传输计数器重新赋值
void MyDMA_transfer() {
    //根据DMA的要求DMA是必须在停止工作的时候才能去设置参数
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, count); //对计数器重新赋值
    DMA_Cmd(DMA1_Channel1, ENABLE);//开启DMA

    //等待转运完成标志位
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//如果是Reset状态就说明没有转运完成,所以就继续在循环里面等着

    //清除标志位,是需要手动清除的
    DMA_ClearFlag(DMA1_FLAG_TC1);
}

DMA.h文件代码:

#ifndef __DMA_H
#define __DMA_H
void MyDMA_init(uint32_t addr_A, uint32_t addr_B, uint16_t size);
void MyDMA_transfer();
#endif // !__DMA_H

main.c文件代码:

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

uint8_t A[] = { 0x01,0x02,0x03,0x04 };
uint8_t B[]={0,0,0,0};
uint8_t i;

int main(void)
{	
	OLED_Init();
	
	OLED_ShowString(1, 1, "arr_A:");
	OLED_ShowString(3, 1, "arr_B:");
	OLED_ShowHexNum(1, 7, (uint32_t)A, 8);
	OLED_ShowHexNum(3, 7, (uint32_t)B, 8);

	MyDMA_init((uint32_t)A, (uint32_t)B, 4);

	while (1)
	{
		for (i = 0;i < 4;i++)
			A[i]++;
		
		for (i = 0;i < 4;i++)
			OLED_ShowHexNum(2, 3 * i + 1, A[i], 2);
		for (i = 0;i < 4;i++)
			OLED_ShowHexNum(4, 3 * i + 1, B[i], 2);
		
		
		Delay_ms(1000);
		
		
		MyDMA_transfer();

		for (i = 0;i < 4;i++)
			OLED_ShowHexNum(2, 3 * i + 1, A[i], 2);
		for (i = 0;i < 4;i++)
			OLED_ShowHexNum(4, 3 * i + 1, B[i], 2);
		
		Delay_ms(1000);
	}
	
}


 

2.DMA转运ADC转换的数据

实验现象:

DMA转运ADC转换结果

电路连接图:

工程文件:

DMA.c文件代码:

#include "stm32f10x.h"                  // Device header

uint16_t AD_value[4];

void AD_init() {
    //1.开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择时钟分频

    //2.配置GPIO
    GPIO_InitTypeDef GPIO_initstruct;
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AIN; //选择模拟输入 ,是ADC专属模式,其他是无效的
    GPIO_initstruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_initstruct);

    //扫描的通道,  规则组列表中的通道,序列写入通道
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_28Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_28Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_28Cycles5);

    // 3.ADC结构体初始化
    ADC_InitTypeDef ADC_initstruct;
    ADC_initstruct.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,左右可选,这里选择右对齐
    ADC_initstruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//选择外部触发源,这里选择none是用软件触发
    ADC_initstruct.ADC_Mode = ADC_Mode_Independent;//选择ADC的模式,独立和双模式选择,这里选择独立模式
            //选择模式
    ADC_initstruct.ADC_ContinuousConvMode = ENABLE; //连续转换
    ADC_initstruct.ADC_ScanConvMode = ENABLE;  // 扫描模式,这里选择扫描模式,多个序列
    ADC_initstruct.ADC_NbrOfChannel = 4;//通道数目,有4个通道和序列

    ADC_Init(ADC1, &ADC_initstruct);

    //配置结构体
    DMA_InitTypeDef DMA_initstruct;
    //起始外设的三个参数,ADC
    DMA_initstruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  //起始数据的地址
	DMA_initstruct.DMA_PeripheralDataSize= DMA_PeripheralDataSize_HalfWord ;   //数据的宽度
    DMA_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //选择地址是否自增,因为ADC地址是固定的,不能改变
    //目标存储器的三个参数,数组
    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_DIR = DMA_DIR_PeripheralSRC; //选择传输方向,这里选择外设到存储器的方向A->B
    DMA_initstruct.DMA_BufferSize = 4; //设置传输的次数,其取值范围是0~65535
    DMA_initstruct.DMA_Mode= DMA_Mode_Circular ; //自动重装的选择,如果选择了软件触发方式是不能选择自动重装的,所以我们这边软件触发就选择普通模式
    DMA_initstruct.DMA_M2M = DMA_M2M_Disable; //选择触发方式,我们这里是ADC 转换,是需要硬件触发
    DMA_initstruct.DMA_Priority = DMA_Priority_Medium; //优先级,如果是多个通道的话就要去选择好,我们这里只有一个通道所以随便选

    DMA_Init(DMA1_Channel1, &DMA_initstruct); //这里必须选择DMA1的通道1,因为ADC1只接在DMA1的通道1上,其他的都不行

    //开启DMA
    DMA_Cmd(DMA1_Channel1, ENABLE);
    //开启ADC到DMA的触发信号
    ADC_DMACmd(ADC1, ENABLE);
    //4.开启ADC电源
    ADC_Cmd(ADC1, ENABLE);

    //5.校准
    ADC_ResetCalibration(ADC1); //复位校准
    while (ADC_GetResetCalibrationStatus(ADC1) == SET); //获取复位校准状态,如果复位完成了之后就标志位就会清理
    ADC_StartCalibration(ADC1); //开启校准
    while (ADC_GetCalibrationStatus(ADC1) == SET);//如果标志位清零的话说明校准完成,不然就一直等待校准标志位

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//选择软件触发,开启ADC软件触发
}

//上面设置的单次转换,所以这里要去开启ADC软件触发转换
//实际上DMA的速度是比ADC快很多的,所以ADC都没转换好的时候DMA会一直处于等待的状态,这里是选择先开启DMA再开启ADC的,顺序很重要
void AD_getvalue() {
    
    //根据DMA的要求DMA是必须在停止工作的时候才能去设置参数
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, 4); //对计数器重新赋值
    DMA_Cmd(DMA1_Channel1, ENABLE);//开启DMA

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//选择软件触发,开启ADC软件触发

    //等待转运完成标志位
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//如果是Reset状态就说明没有转运完成,所以就继续在循环里面等着
    //清除标志位,是需要手动清除的
    DMA_ClearFlag(DMA1_FLAG_TC1);
}

DMA.h文件代码:

#ifndef __AD_h
#define __AD_h
extern uint16_t AD_value[4];
void AD_init();
void AD_getvalue();
#endif // !__AD_h#define __AD_h

main.c文件代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
#include "LED.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);

	}
}


 

以上就是本期的全部内容了,我们下次见!

今日壁纸:

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fitz&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值