STM32-DMA

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

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

ROM与RAM

具体的外设地址用起始地址加上偏移量,或者直接用取地址符取出来。

数据宽度与对齐

若传输数据宽度大于接收数据宽度,会舍去数据的高位数据;若传输数据宽度小于接收数据宽度,会在高位补0;若二者相等,则正常传输。

DMA的基础结构

在进行代码编写时,DMA的配置顺序与上图一致。

一、DMA数据转运

本实验是在运行内存(SRAM)内的两个存储器之间进行数据的转运。

先对DMA模块进行配置,DMA.c文件:

复制代码
#include "stm32f10x.h"                  // Device header

uint16_t SIZE;//创建变量用于设置传输计数器
//运行内存中分配地址是不确定的,所以这里创建两个变量来对传输双方地址进行设置
void DMA_init(uint32_t Addra,uint32_t Addrb,uint16_t Size)
{
    //将传输计数器值传给全局变量SIZE
	SIZE = Size;
    //打开DMA1时钟
	RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1 ,ENABLE );
	
    //对DMA单元进行初始化配置
	DMA_InitTypeDef DMA_InitStructer;
	DMA_InitStructer.DMA_PeripheralBaseAddr = Addra;//外设寄存器地址
	DMA_InitStructer.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Word;//数据宽度
	DMA_InitStructer.DMA_PeripheralInc =DMA_PeripheralInc_Enable;//地址是否自增	
	DMA_InitStructer.DMA_MemoryBaseAddr = Addrb;//存储器寄存器地址
	DMA_InitStructer.DMA_MemoryDataSize =DMA_MemoryDataSize_Word;//数据宽度
	DMA_InitStructer.DMA_MemoryInc =DMA_MemoryInc_Enable;//地址是否自增
	DMA_InitStructer.DMA_Priority =DMA_Priority_Medium;//DMA传输优先级设置
	DMA_InitStructer.DMA_BufferSize =Size;//传输计数器值设置
	DMA_InitStructer.DMA_Mode =DMA_Mode_Normal;//DMA模式选择,这里选择普通模式
	DMA_InitStructer.DMA_DIR =DMA_DIR_PeripheralSRC;//传输方向的选择
	DMA_InitStructer.DMA_M2M =DMA_M2M_Enable;//触发源的选择
	DMA_Init(DMA1_Channel1,&DMA_InitStructer);
	
	DMA_Cmd(DMA1_Channel1,ENABLE );//打开DMA1
	
}	

//定义一个函数用来进行传输控制
void DMA_Transfer(uint16_t Size)
{
	DMA_Cmd(DMA1_Channel1,DISABLE );//先将DMA1对应通道关闭
	DMA_SetCurrDataCounter(DMA1_Channel1,SIZE);//重新手动设置传输计数器值
	DMA_Cmd(DMA1_Channel1,ENABLE );//打开DMA1
}

DMA.h文件:

复制代码
#ifndef _DMA_H
#define _DMA_H
void DMA_init(uint32_t Addra,uint32_t Addrb,uint16_t Size);
void DMA_Transfer(uint16_t Size);
extern uint16_t SIZE;
#endif

最后在主函数中调用DMA模块的函数实现数据的转运,main.c文件:

复制代码
#include "stm32f10x.h"    // Device header
#include "Delay.h"
#include "OLED.h"
#include "DMA.h"

//在SRAM中定义两个数组用于存储数据
uint32_t Data1[3]={0x12,0x22,0x32};
uint32_t Data2[3]={0,0,0};
int main(void)
{	
	OLED_Init();
    //对DMA进行初始化,初始化时需要将存储器地址当作参数传入
	DMA_init((uint32_t)Data1,(uint32_t )Data2,3);
	OLED_ShowString (1,1,"Data1:");//显示提示字符串
	while(1)
	{
		OLED_ShowNum (1,7,Data1[0],2);//依次实时显示两个数组的数据
		OLED_ShowNum (1,10,Data1[1],2);
		OLED_ShowNum (1,13,Data1[2],2);
		OLED_ShowNum (2,7,Data2[0],2);
		OLED_ShowNum (2,10,Data2[1],2);
		OLED_ShowNum (2,13,Data2[2],2);
		DMA_Transfer (3);//对传输计数器值进行手动重装
		Data1[0]++;//对源存储器中数据进行自增,更好的观察传输效果
		Data1[1]++;
		Data1[2]++;
		Delay_ms (500);//延时0.5s,放大可视效果
	}
}

最终效果:源存储器(Data1)中数据递增,依次传输到目标存储器(Data2)中,在OLED显示屏上可以直观的看到传输效果。

二、DMA+ADC多通道

由于ADC规则组只有一个寄存器,在使用扫描模式下的ADC转换时,转换了一个通道的数据,就得配合DMA进行数据传输,当下一个通道的数据转换完后会覆盖上一个通道转换的数据。ADC1的通道0连接继电器,通道1连接光敏传感器,通道2连接热敏传感器,通道3连接发射式红外传感器。

先对ADC模块进行编写,ADC.c文件:

复制代码
#include "stm32f10x.h"                  // Device header

extern uint16_t Data[4];//声明外部数组Data
void ADC_init(void)
{
    //打开ADC1时钟、GPIOA时钟、DMA1时钟
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC1 ,ENABLE);
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA ,ENABLE);
	RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1 ,ENABLE); 
	
    //对ADC的时钟分频进行配置
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
    //对PA0、PA1、PA2、PA3进行初始化配置,这里配置为模拟输入模式
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	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);
	
    //"点菜":对ADC1的规则组通道进行配置
	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单元进行初始化配置,这里配置为:连续扫描模式,软件触发
	ADC_InitTypeDef ADC_InitStructer;
	ADC_InitStructer.ADC_Mode =ADC_Mode_Independent;
	ADC_InitStructer.ADC_DataAlign =ADC_DataAlign_Right;
	ADC_InitStructer.ADC_ExternalTrigConv =ADC_ExternalTrigConv_None;
	ADC_InitStructer.ADC_ContinuousConvMode =ENABLE;
	ADC_InitStructer.ADC_ScanConvMode =ENABLE;
	ADC_InitStructer.ADC_NbrOfChannel =4;
	ADC_Init(ADC1 ,&ADC_InitStructer);
	
    //对DMA1进行初始化配置
	DMA_InitTypeDef DMA_InitStructer;
	DMA_InitStructer.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR ;//源地址为:ADC的DR寄存器
    //以半字宽度进行传输
	DMA_InitStructer.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructer.DMA_PeripheralInc =DMA_PeripheralInc_Disable;//源地址不自增	
	DMA_InitStructer.DMA_MemoryBaseAddr = (uint32_t)Data;//目标地址为数组地址
    //以半字宽度进行接收
	DMA_InitStructer.DMA_MemoryDataSize =DMA_MemoryDataSize_HalfWord;
	DMA_InitStructer.DMA_MemoryInc =DMA_MemoryInc_Enable;//目标地址要自增
	DMA_InitStructer.DMA_Priority =DMA_Priority_Medium;
	DMA_InitStructer.DMA_BufferSize =4;
	DMA_InitStructer.DMA_Mode =DMA_Mode_Circular;//选择自动重装载模式
	DMA_InitStructer.DMA_DIR =DMA_DIR_PeripheralSRC;
	DMA_InitStructer.DMA_M2M =DMA_M2M_Disable;//选择DMA为硬件触发
	DMA_Init(DMA1_Channel1,&DMA_InitStructer);
	
	
	DMA_Cmd(DMA1_Channel1,ENABLE );//打开DMA1通道
	ADC_DMACmd(ADC1 ,ENABLE);//打开ADC和DMA之间的配合
	ADC_Cmd(ADC1 ,ENABLE);//打开ADC1

    //进行ADC1的校正
	ADC_ResetCalibration(ADC1 );
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	
    //将ADC1选择为软件触发方式
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

ADC.h文件:

复制代码
#ifndef _ADC_H
#define _ADC_H
void ADC_init(void);
uint16_t Get_Value(void);

#endif

最后在主函数中调用ADC模块函数,main.c文件:

复制代码
#include "stm32f10x.h"    // Device header
#include "Delay.h"
#include "OLED.h"
#include "ADC.h"

uint16_t Data[4]={0,0,0,0};//创建数组用于接收外设传输的数据

int main(void)
{	
	OLED_Init();
	ADC_init();//对各个模块进行初始化配置
	OLED_ShowString (1,1,"Value0:");
	OLED_ShowString (2,1,"Value1:");
	OLED_ShowString (3,1,"Value2:");
	OLED_ShowString (4,1,"Value3:");//显示每个通道的提示字符串
	while(1)
	{
		OLED_ShowNum (1,8,Data[0],4);//实时显示每个通道传输的数据值
		OLED_ShowNum (2,8,Data[1],4);
		OLED_ShowNum (3,8,Data[2],4);
		OLED_ShowNum (4,8,Data[3],4);
		Delay_ms (100);//进行0.1s的延时
	}
}

最终效果:当转动继电器时,通道0的传输值发生改变,并显示在OLED上;当对其余三个通道的传感器进行操作时,其对应通道的传输值也会随之改变,并显示在OLED上。

注意事项:

  • DMA使用时如果配置了自动重装载,就不能再使用软件触发传输数据;
  • 当ADC配置为扫描模式时,必须与DMA进行配合使用;
  • 在ADC与DMA进行配套使用时,要分清各自配置触发源的地方,容易混淆。
相关推荐
Jaixln_HRF1 小时前
率能SS8837T 单通道1.8A/12V H桥电机驱动芯片,低功耗睡眠模式120nA,用于摄像机/玩具/机器人
驱动开发·嵌入式硬件·机器人·硬件工程
小鱼儿电子1 小时前
62-基于STM32的多功能水壶控制系统设计
stm32·单片机·嵌入式硬件·多功能水壶·智能水壶
vickycheung32 小时前
基于RV1126B核心板监护摄像机应用解决方案
经验分享·嵌入式硬件·arm 嵌入式开发
工业一体机老司机2 小时前
Python实现工业一体机OPC-UA通信:从SDK选型到数据采集实战
开发语言·python·单片机
深圳市宝华视联2 小时前
移动示教推车功能介绍
网络·嵌入式硬件·音视频·视频编解码·嵌入式实时数据库
泛凡(Linyongui)2 小时前
告别原始编辑器:VS Code 搭建 中微CMS IDE 编译环境完整指南
ide·单片机·编辑器
三佛科技-187366133972 小时前
HN32516直接替代KP3211,改电感参数,输出电流可到900mA~1A
单片机·嵌入式硬件
国科安芯2 小时前
小卫星综合电子系统中RISC-V抗辐射MCU的功能安全与多接口集成架构分析
单片机·嵌入式硬件·安全·fpga开发·架构·risc-v
唯创知音3 小时前
WiFi+TTS语音播报方案:ESP32搭配WT3000TX实现智能语音通知
单片机·嵌入式硬件·wifi+tts语音播报方案