初识STM32单片机-ADC和DMA

初识STM32单片机-ADC和DMA

  • 一、ADC(模拟数字转换器)简介
  • 二、ADC基本结构
  • 三、DMA(直接存储器读取)简介
  • 四、DMA框图和基本结构
  • 五、DMA应用实例
    • 5.1 数据转运+DMA
    • 5.2 ADC扫描+DMA
  • 六、程序编码
    • 6.1 ADC单通道-电位器
    • 6.2 ADC多通道-电位器和光敏\热敏\反射红外传感器
    • 6.3 DMA数据转运
    • 6.4 ADC扫描+DMA

一、ADC(模拟数字转换器)简介

  • ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
  • 12位逐次逼近型ADC,1us转换时间(转换频率1MHz)
  • 输入电压范围:0-3.3V转换结果范围0-4095
  • 18个输入通道,可以测量16个外部(GPIO)和2个内部信号源(内部温度传感器和内部参考电压)
  • 规则组注入组两个转换单元
  • 模拟看门狗自动检测输入电压范围(阈值判断)
  • STM32F103C8T6 ADC资源:ADC1、ADC2、10个外部输入通道

  下面给出STM32的ADC框图
  左边ADCx_IN0-IN15两个内部通道一共18个输入通道进入,然后到达模拟多路开关,指定想要选择的某个通道(可以多个),进入模数转换器(逐次逼近),转换结果放在数据寄存器里,读取数据寄存器就知道ADC的结果了
  模拟多路开关中,进入注入通道的最多4个,进入规则通道的最多16个。规则组虽然可以进入16个通道,但是每次数据寄存器只能存一个结果,如果不想被覆盖,就得在结果转换完以后,配合DMA(数据转运),尽快把结果拿走。注入组一次性可以放4个数据,也同时有4个数据寄存器,不用担心数据覆盖的问题。得到数据以后,注入组的完成信号是JEOC规则组EOC,这两个信号会在状态寄存器里置一个标志位读取标志位,就可以知道是否转换结束,也可以到NVIC申请中断
  左下角的开始触发部分就是ADC的Start信号,对于STM32的ADC,触发开始转换的信号有两种,一种是软件触发,程序中调用,一种是硬件触发,就是左下角部分的触发源,主要是来自定时器的通道和TRGO定时器主模式的输出或者外部引脚
  左上角的Vref+Vref-VDDAVSSA分别代表ADC的参考电压(ADC输入电压范围)和供电引脚,一般情况下Vref+接VDDA,Vrerf-接VSSA。ADCCLK是ADC的时钟,用于驱动内部逐次比较的时钟,来自ADC预分频器
  模拟看门狗阈值高限阈值低限,启动看门狗,指定通道,看门狗就会关注它看门的通道,一旦超过阈值,就会申请中断,然后通往NVIC

在这里插入图片描述

二、ADC基本结构

  左边是输入通道16个GPIO两个内部通道,进入AD转换器,AD转换器里有规则组(16个通道)和注入组(4个通道),转换好的数据放在AD数据寄存器里,同时转换好时会有一个EOC信号,用于触发中断。规则组有1个寄存器,注入组有4个寄存器。其他部分包括触发控制提供Start信号,可以选择硬件和软件触发,RCC提供逐次逼近的时钟,可以布置一个看门狗范围,控制阈值报警进入中断,最后一块是开关控制,给ADC上电

在这里插入图片描述

  下面给出输入通道,对应引脚定义图即可

在这里插入图片描述
  下面给出四种转换模式

  • 单次转换—非扫描模式:每次单独触发一个通道,然后判断标志位

在这里插入图片描述

  • 连续转换—非扫描模式:一次转换结束后不会停止,立刻开始下一轮的转换,只需要触发一次

在这里插入图片描述

  • 单次转换—扫描模式:每触发一次。转换完成后就停止,然后重新开始触发,扫描模式就是在每次触发后,依次对前7个位置进行AD转换,防止数据被覆盖,需要用DMA将数据挪走,7个通道转换完之后,产生EOC信号,转换结束,然后再触发下一次

在这里插入图片描述

  • 连续转换—扫描模式:自动完成触发,不需要手动

在这里插入图片描述

  下面给出触发控制源表格,分别对应规则组注入组
在这里插入图片描述

在这里插入图片描述

  由于STM32的ADC12位的,但是数据寄存器16位的,所以需要数据对齐

  • 右对齐:直接读取就是转换结果

在这里插入图片描述

  • 左对齐:结果比实际值大16倍

在这里插入图片描述

三、DMA(直接存储器读取)简介

  • DMA可以提供外设数据寄存器和存储器(运行内存SRAM和程序存储器Flash)存储器和存储器之间的高速数据传输,无需CPU干预,节省CPU资源
  • 12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)
  • 每个通道都支持软件(存储器与存储器之间)和特定的硬件触发(外设与存储器)
  • STM32F103C8T6DMA资源:DMA1(7个通道)

  由于DMA是在存储器之间进行数据转运的,下面给出存储器的地址

在这里插入图片描述

  Flash只读不可写,SRAM任意读写,外设寄存器看参考手册描述,但其中的数据寄存器是可以正常读写的

四、DMA框图和基本结构

  下面给出DMA框图

  总线矩阵的左边是主动单元,也就是拥有存储器访问权,右边的部分是被动单元,它们的存储器只能被主动单元读写。主动单元里,内核DCode和系统总线,可以访问右边的存储器DCode访问Flash系统总线访问其他东西DMA也有访问的主动权,包括DMA1DMA2,里面分别有7个和5个通道,但是同一时刻只有一条通道在DMA总线上,仲裁器可以分配优先级,各个通道可以分别设置它们的源地址目的地址。其中AHB从设备连在了AHB总线上,说明DMA既是总线矩阵的主动单元,也是AHB上的被动单元,AHB上通过DMA请求(硬件触发源),需要触发DMA转换数据的时候,就会通过此条线路,向DMA发出硬件触发信号,之后DMA就可以执行数据转运的工作了

在这里插入图片描述

  下面给出DMA的基本结构
  其中外设寄存器存储器(Flash和SRAM)是DMA的两大站点,具体方向如箭头所示,它们的参数是起始地址(外设端的起始地址和存储器端的存储地址)—决定了数据从哪里到哪里去数据宽度数据一次转运要按多大的数据宽度来进行地址是否自增一次转运过后,下一次转运是否需要把地址移动到下一个位置去

  传输计数器用来指定总共需要转运几次,是一个自减计数器(写5,那么DMA就只能进行5次转运),自动重装器的作用是传输计数器减到0以后,是否要恢复到最初的值
  写传输计数器时,必须要先关闭DMA

  DMA的触发控制分为硬件触发(ADC、串口、定时器等)和软件触发(计数器快速清零尽快完成),由M2M决定,M2M给1选择软件触发、M2M给0就是硬件触发

  DMA开始转运的条件:开关控制-DMA使能(DMA_cmd)传输计数器必须大于0触发源必须有触发信号(软硬件触发)

在这里插入图片描述

  下面给出DMA1的外设请求信号,硬件触发时,不同的请求信号使用不同的通道,如果使用软件触发,则可以任意选择

在这里插入图片描述

  下面给出数据宽度对齐方式

  如果源端比目标宽度,则在目标宽度多出来的位补0。如果源端比目标宽度,则把多出来的高位舍弃

在这里插入图片描述

五、DMA应用实例

5.1 数据转运+DMA

  下面是数据转运+DMA的示例图(存储器-存储器)
  将SRAM数组里面的DataA转运到另一个数组DataB,相当于把DataA的数据复制到DataB的位置

在这里插入图片描述

5.2 ADC扫描+DMA

  下面给出ADC扫描+DMA的示例图(外设-存储器)

  ADC有7个通道,触发一次后,7个通道依次进行AD转换,转换结果都放在ADC_DR寄存器里,每个单独的通道转换完成以后,利用DMA进行数据转运,所以触发方式使用硬件触发,并且目的地址进行自增,这样数据就不会被覆盖。
  外设地址就是ADC_DR,存储器里定义的数组的地址就是存储器的地址。外设地址自增,存储器地址自增。ADC是单次扫描,DMA的计数器就自动重装,ADC是连续扫描开启自动重装,当ADC启动下一轮转换的时候,DMA也启动下一次的转运

在这里插入图片描述

六、程序编码

6.1 ADC单通道-电位器

  电位器输出口接到stm32的PA0口
  配置步骤如下:开启RCC时钟(ADC和GPIO)配置ADC_CLK和GPIO(模拟输入模式)配置多路开关(规则组的通道)初始化ADC转换器(ADC模式、数据对齐、触发源等等) — (看门狗和中断) — 开关控制(开启ADC) — ADC校准
  下面是AD.c的具体程序,AD_GetValue()返回值就是AD转换后的数字量

void AD_Init()
{
	//开启RCC时钟ADC/GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//开启ADC_CLK 6分频 72MHz/6
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	//配置GPIOA-模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;			//模拟输入模式:断开GPIO
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	//配置规则组的通道 规则组:通道0
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	//初始化ADC
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//ADC模式:独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;		//数据对齐:右
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//触发源:软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//单次
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//非扫描
	ADC_InitStructure.ADC_NbrOfChannel = 1;				//通道数目
	ADC_Init(ADC1,&ADC_InitStructure);
	//中断和模拟看门狗不需要
	//运行控制
	ADC_Cmd(ADC1,ENABLE);
	//ADC校准
	ADC_ResetCalibration(ADC1);					//复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);		//返回复位校准的状态
	ADC_StartCalibration(ADC1);					//启动校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);			//获取校准标志位
}


uint16_t AD_GetValue()
{
	//触发转换(也可以使用连续非扫描模式,将软件触发转换函数放在AD初始化里面
	//不需要获取标志位,直接返回AD转换值就行)
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);			//软件触发转换
	
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);	//获取转换完成标志位
	return ADC_GetConversionValue(ADC1);			//获取ADC转换值
}

  在main.c调用,修改格式,显示电位器电压和AD数字量

uint16_t ADValue;	//AD转换后的数字量
float Voltage;		//电压

int main(void)
{
	AD_Init();
	OLED_Init();
	OLED_ShowString(1,1,"Voltage:0.00V");
	OLED_ShowString(2,1,"ADValue:");
	
	while(1)
	{
		ADValue = AD_GetValue();
		Voltage = (float)ADValue / 4095 * 3.3;			//把0-4095变换到0-3.3V
		
		OLED_ShowNum(2,9,ADValue,4);				//电位器数字量

		OLED_ShowNum(1,9,Voltage,1);				//模拟电压整数部分
		OLED_ShowNum(1,11,(uint16_t)(Voltage * 100) % 100,2);	//模拟电压小数部分
		Delay_ms(1000);
	}
}

6.2 ADC多通道-电位器和光敏\热敏\反射红外传感器

  电位器数据输出口接在PA0口,光敏、热敏和反射红外传感器的模拟量输出口AO分别接在了PA1、PA2和PA3口(DO是数字量输出—高低电平)
  具体步骤和单通道AD的区别在:GPIO口初始化要配置多个Pin转换通道数变多
  下面是AD.c

void AD_Init()
{
	//开启RCC时钟ADC/GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//开启ADCCLK 6分频 72MHz/6
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	//配置GPIOA:模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;	//模拟输入模式:断开GPIO
	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
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;			//ADC模式:独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;			//数据对齐:右
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//触发源:软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;			//单次
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;				//非扫描
	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数目
	ADC_Init(ADC1,&ADC_InitStructure);
	//中断和模拟看门狗不需要
	//运行控制
	ADC_Cmd(ADC1,ENABLE);
	//ADC校准
	ADC_ResetCalibration(ADC1);						//复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);			//返回复位校准的状态
	ADC_StartCalibration(ADC1);						//启动校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);				//获取校准标志位
}


uint16_t AD_GetValue(uint8_t ADC_Channel)
{
	//配置规则组的通道--参数指定通道0 1 2 3
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
	//触发转换(也可以使用连续非扫描模式,将软件触发转换函数放在AD初始化里面
	//不需要获取标志位,直接返回AD转换值就行)
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);					//软件触发转换
	
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);			//获取标志位状态
	return ADC_GetConversionValue(ADC1);					//获取ADC转换值
}

  下面给出main.c,显示出各个传感器的数字量

uint16_t AD0,AD1,AD2,AD3;		//电位器、光敏、热敏、反射红外数字量

int main(void)
{
	AD_Init();
	OLED_Init();
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	while(1)
	{
		AD0 = AD_GetValue(ADC_Channel_0);		//四个通道
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		OLED_ShowNum(1,5,AD0,4);
		OLED_ShowNum(2,5,AD1,4);
		OLED_ShowNum(3,5,AD2,4);
		OLED_ShowNum(4,5,AD3,4);
		Delay_ms(1000);	
	}
}

6.3 DMA数据转运

  本次代码只需要在内存中定义两个数组,即两个地址,将其中一个数组的数据复制到另一个数组,所以不需要连接其他外设,只需要接上OLED显示屏显示结果即可
  具体步骤如下:开启RCC时钟(DMA1)初始化DMA(外设和存储站点、传输计数器、模式等)DMA使能DMA转换标志位
  下面给出MyDMA.c

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size; 
/*
@param:外设站点地址 存储器站点地址 传输计数器值 
*/
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
	MyDMA_Size = Size;
	//开启RCC时钟:DMA1
	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_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;			//外设站点(本质上都是SRAM,根据实际地址来)-存储器站点
	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使能
	DMA_Cmd(DMA1_Channel1,DISABLE);						//等Transfer调用后进行转运
}

void MyDMA_Transfer()
{
	DMA_Cmd(DMA1_Channel1,DISABLE);						//失能
	DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);			//计数器写值
	DMA_Cmd(DMA1_Channel1,ENABLE);						//使能
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1==RESET));				//等待标志位置1(完成转运)
	DMA_ClearFlag(DMA1_FLAG_TC1);						//清除标志位
}

  下面给出主函数中显示效果的内容

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);			//DMA初始化
	OLED_ShowString(1,1,"DataA");
	OLED_ShowHexNum(1,8,(uint32_t)DataA,8);				//DataA的地址
	OLED_ShowString(3,1,"DataB");
	OLED_ShowHexNum(3,8,(uint32_t)DataB,8);				//DataB的地址
	
	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);
	}
}

6.4 ADC扫描+DMA

  电位器数据输出口接在PA0口,光敏、热敏和反射红外传感器的模拟量输出口AO分别接在了PA1、PA2和PA3口
  ADC扫描配合DMA,就可以将多路ADC的数据,搬运到指定的存储器中,就是将外设地址(ADC->DR)的数据复制到存储器地址里(可以是定义的数组)
  具体步骤如下:开启RCC时钟(ADC、GPIO和DMA)配置ADC_CLK和GPIO(模拟输入模式)配置多路开关(规则组的通道为四个)初始化ADC转换器(ADC模式、数据对齐、触发源等等)初始化DMA(外设和存储站点、传输计数器、模式等)DMA使能打开ADC到DMA的使能ADC使能ADC校准DMA计数器赋值ADC软件触发使能DMA转换完成标志位判断

  下面给出AD.c,其中包含了ADC和DMA的部分

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init()
{
	//开启RCC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	//开启ADCCLK 6分频 72MHz/6
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	//配置GPIOA-模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;	//模拟输入:断开GPIO
	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);
	//配置规则组的通道 规则组0123通道
	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_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//ADC模式-独立模式
	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;		//4个通道
	ADC_Init(ADC1,&ADC_InitStructure);
	//DMA初始化
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;			//外设站点起始地址:ADC1->DR
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设站点数据宽度:半字16位
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设站点地址不自增
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;		//存储器站点起始地址:ADValue
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;		//存储器站点数据宽度
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;				//存储器站点自增
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;				//外设站点(本质上都是SRAM,根据实际地址来)-存储器站点
	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);		//ADC1在DMA1的通道1里
	DMA_Cmd(DMA1_Channel1,DISABLE);
	ADC_DMACmd(ADC1,ENABLE);		//打开ADC到DMA的数据输出使能
	//运行控制
	ADC_Cmd(ADC1,ENABLE);
	//ADC校准
	ADC_ResetCalibration(ADC1);		//复位校准
	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,4);		//写计数器的值,需要失能DMA
	DMA_Cmd(DMA1_Channel1,ENABLE);
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);			//ADC软件触发使能
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);
}

  给出主函数的显示部分

int main(void)
{
	AD_Init();
	OLED_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);	
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/675539.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练Day28|LeetCode93-复原IP地址、LeetCode78-子集问题、LeetCode90-子集2

复原IP地址 题目描述 力扣93-复原IP地址 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 . 分隔。 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 …

贝锐花生壳DDNS:远程访问数据库,仅需简单3步

在当今数字化时代,数据的远程访问和管理变得至关重要。无论是企业还是个人开发者,都需要一种简单、安全的方式来远程访问和管理本地部署的数据库,如MySQL、PostgreSQL、MongoDB等。贝锐花生壳DDNS服务提供了一个完美的解决方案,通…

【YOLOv10改进[Backbone]】图像修复网络AirNet助力YOLOv10目标检测效果 + 含全部代码和详细修改方式 + 手撕结构图 + 全网首发

本文带来的是图像复原网络AirNet,它由基于对比度的退化编码器( CBDE )和退化引导的恢复网络( DGRN )两个模块组成。可以在一个网络中恢复各种退化图像。AirNet不受损坏类型和级别的先验限制,仅使用观察到的损坏图像进行推理。本文中将使用图像修复网络Ai…

SCARA机器人中旋转花键的维护和保养方法!

作为精密传动元件的一种,旋转花键在工作过程中承受了较大的负荷。在自动化设备上运用广泛,如:水平多关节机械手臂(SCARA)、产业用机器人、自动装载机、雷射加工机、搬运装置、机械加工中心的ATC装置等,最适…

R语言安装caret包报错

R语言安装caret包报错:Error: package or namespace load failed for ‘caret’ in loadNamespace(i, c(lib.loc, .libPaths()), versionCheck vI[[i]]): 不存在叫‘recipes’这个名字的程辑包 https://rbasics.org/packages/caret-package-in-r/ R版本的问题&…

什么牌子的洗地机清洁效果强?618热门品牌推荐与详解

近年来,洗地机的销量急剧增长,已成为清洁类家电中销量第二大的产品。其更新迭代速度也非常快,功能和技术层出不穷,许多消费者不知道如何选择合适的型号。为了帮助大家以最少的花费买到清洁力强的洗地机,笔者特意总结了…

输入法不显示选字框

期望效果: 当前效果: 啥也没干突然就这样了 原因:需要以兼容性运行微软输入法 一、进入输入法设置 右键输入法小图标 选择设置 二、进入常规设置 三、开启兼容性运行 完!

跨越百亿营收的今世缘,全国化进程仍挑战重重?

当前,白酒市场正在经历一场深度调整,随着存量时代到来,白酒品牌地位的更替和竞争格局的重构已经展开。这一背景下,今世缘等地方性酒企也正在凭借对区域市场的深耕,展现出较快的成长速度,并希望能借此占领市…

【JAVA |总结】JAVASE基础大总结(含思维导图)

✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 🎈🎈作者主页: 🎈丠丠64-CSDN博客🎈 ✨✨ 帅哥美女们,我们共同加油!一起…

数据动态变化时实现多选及回显

<template><el-dialog title"设置权限" :visible.sync"showDialog" :close-on-click-modal"false" :append-to-body"true" width"800px"><div v-loading"loading"><el-radio-group v-model&…

TDMQ CKafka 版弹性存储能力重磅上线!

导语 自 2024年5月起&#xff0c;TDMQ CKafka 专业版支持弹性存储能力&#xff0c;这种产品形态下&#xff0c;存储可按需使用、按量付费&#xff0c;一方面降低消费即删除、存储使用波动大场景下的存储成本&#xff0c;另一方面存储空间理论上无穷大。 TDMQ CKafka 版产品能…

微服务网关Gateway(上)

大家好呀&#xff0c;我是苍何。 这年头&#xff0c;大家都在开始卷简历了&#xff0c;我也看了很多同学的简历&#xff0c;其中有一个同学的简历&#xff0c;我印象最为深刻&#xff0c;他的项目经历中&#xff0c;写了自定义 Gateway 过滤器实现统计接口调用耗时&#xff0c…

【Hive SQL 每日一题】统计各个商品今年销售额与去年销售额的增长率及排名变化

文章目录 测试数据需求说明需求实现分步解析 测试数据 -- 创建商品表 DROP TABLE IF EXISTS products; CREATE TABLE products (product_id INT,product_name STRING );INSERT INTO products VALUES (1, Product A), (2, Product B), (3, Product C), (4, Product D), (5, Pro…

什么是研学活动?快速了解

说起什么是研学活动&#xff0c;其实就是一种结合学习与实地考察、体验的教育方式&#xff0c;旨在通过实践活动深化学生对课堂知识的理解和应用&#xff0c;培养学生的综合素质和创新能力。让学生在亲身体验中学习和成长。当学校宣布即将组织一次研学活动时&#xff0c;孩子们…

如何批量复制文件名?文件名批量提取的5个工具!(2024新)

在数字化时代&#xff0c;我们经常需要处理大量的文件&#xff0c;其中批量复制文件名或批量提取文件名成为一项常见的任务。这不仅可以提高我们的工作效率&#xff0c;还能使文件管理更为有序。本文将介绍五种2024年最新的文件名批量提取工具&#xff0c;帮助你轻松完成文件名…

手把手教你从0到1开发浏览器插件

使用Chrome插件可以为Chrome浏览器带来一些功能性的扩展&#xff0c;进而提高使用体验&#xff1b;俗话说的好Chrome没插件&#xff0c;香味少一半&#xff0c;Chrome最大的优势还是其支持众多强大好用的扩展程序&#xff1b;今天就来了解一下插件是如何开发的&#xff0c;和普…

C语言基础——数组(2)

ʕ • ᴥ • ʔ づ♡ど &#x1f389; 欢迎点赞支持&#x1f389; 个人主页&#xff1a;励志不掉头发的内向程序员&#xff1b; 专栏主页&#xff1a;C语言基础&#xff1b; 文章目录 前言 一、二维数组的创建 1.1 二维数组的概念 1.2二维数组的创建 二、二维数组…

MySQL中获取时间的方法

大家好&#xff0c;在MySQL数据库开发中&#xff0c;获取时间是一个常见的需求。MySQL提供了多种方法来获取当前日期、时间和时间戳&#xff0c;并且可以对时间进行格式化、计算和转换。 以下是一些常用的MySQL时间函数及其示例&#xff1a; 1、NOW()&#xff1a;用于获取当前…

汇舟问卷:国外问卷调查怎么样?

互联网的发展为我们提供了无数的赚钱机会&#xff0c;其中不乏一些投资小、易上手的小项目&#xff0c;可以让大家充分的利用起业余的时间&#xff0c;赚到日常工作之外的收入。 ​这些项目不仅操作简单&#xff0c;而且时间灵活&#xff0c;非常适合想要利用闲余时间赚外快的…

云渲染农场什么是线程模式?

许多设计师在选择云渲染农场时&#xff0c;常常会遇到48线程、56线程、72线程等选项&#xff0c;然而&#xff0c;不少新手在面对这些选择时&#xff0c;往往无法直观地感受到不同线程数量之间的差异。接下来&#xff0c;我们将共同探讨线程的作用和影响&#xff0c;帮助大家更…