基于stm32F1的ADC模数转换

笔记来源于江科协议

芯片stm32F103C8T6

ADC

ADC简介

•ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁,stm32主要是数字电路,数字电路只有高低电平,没有多少V电压的概念。想要读取电压值,就要借助ADC模数转换器,ADC读取引脚上的模拟电压,转换位一个数据,存在寄存器里,在把数据读取出来,这样就可以操作了

•12位逐次逼近型ADC,1us转换时间 ,这里涉及到ADC的两个参数,一个是分辨率,一般用多少位表示,12位AD值,它的表示范围就是0~4095,位数越高,量化结果越精细,对应分辨率越高。第二个是转换时间也就是转换频率,AD转换是需要花一小段时间的,这里1us就表示从AD转换开始,到产生结果,需要花1us的时间,对应AD转换的频率就是1MHz,这个就是stm32ADC的最快转换频率。

•输入电压范围:0~3.3V,转换结果范围:0~4095

ADC的输入电压,一般要求都是要在芯片供电的负极和正极之间的变化,最低电压0V,最高3.3V,经过ADC转换之后,最小是0,最大是4095,0:0V,3.3V:4095,中间是线性关系,

•18个输入通道,可测量16个外部和2个内部信号源

外部信号源就是16个GPIO口,在引脚上接模拟信号就可以了,引脚直接测电压,2个内部信号源是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,内部参考电压是一个1.2V左右的基准电压,这个基准电压是不随外部供电 电压变化而变化的,如果芯片供电不是3.3V,这时可以读取这个基准电压,这就可以得到正常的电压值了。

•规则组和注入组两个转换单元

普通AD转换流程:启动一次转换,读取一次值,

STM32读取流程:可以列一组,一次启动一个组,连续转换多个值,一个是常用的规则组,一个用于突发事件的注入组,

•模拟看门狗自动监测输入电压范围

这个ADC一般可以用于测量光线强度、温度,经常用于高于、低于某个阈值取执行一些操作,可以用模拟看门狗来执行,模拟看门狗可以检测执行的某些通道,当低于 、高于某些阈值,就会申请终端 ,在中断函数里执行相应操作。

•STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道

最多测量10个外部引脚的模拟信号。

逐次逼近型ADC

这个是ADC0809的内部结构图,是一个独立的8位逐次逼近型ADC芯片,首先是左边IN0~IN7,是8路输入通道,通过通道选择开关,选择一路,输入到这个点进行转换,下面是地址锁存和译码,就是想选中哪个通道,就把通道号放在这三个引脚上,然后给一个锁存信号,上面对应的通路开关就可以拨好了。 相当于可以通过模拟信号的数据选择器,stm32中对应的是18路选择开关。

通道选择开关后面是一个电压比较器,它可以判断两个输入信号电压的大小关系,它的两个输入端 ,一个是待测的电压,另一个DAC的电压输出端,两个输入同时输入到电压比较器,进行大小判断,如果DAC的输出电压大,那就调小DAC数据,如果比较小,就增大DAC,直到DAC输出的电压和外部通道输入的电压近似相等,这样DAC输入的数据就是外部电压的编码数据了。这就是DAC的实现原理,这个电压调节的过程就是这个逐次逼近DAR来完成的。为了最快找到未知电压的编码,通常会是会使用二分法进行寻找。

比如:这里是8位的ADC,那编码就是从0~255,第一次比较就是给DAC输入128进行比较,如果DAC大了,那就给DAC64,如果还大,那就给64的一半,如果小了,那就给32~64中间的值,参考二分查找法。如果用二进制表示,128、64、32......正好是二进制每一位的位权,相当于对二进制每一位判断1还是0的过程。对于8位的adc,从高位到低位依次判断8次就能找到未知电压的编码了,对于12位的adc,就需要判断12次,然后,AD转换结束后,DAC的输入数据,就是未知电压的编码,通过三态锁存缓冲缓冲器输出,8位ADC就是8位,12位ADC就是12根线。上面的EOC就是转换结束信号,START是开始转换,给一个输入脉冲,开始转换,CLOCK是ADC时钟,因为ADC内部是一步一步进行判断的,所以需要这个时钟推动这个过程。Vref+和Vref-是DAC的参考电压,比如:给一个255,对应的是3.3V还是5V就是这个参考电压来决定。这个DAC的参考电压也决定了ADC的输入范围,所以也是ADC参考电压。VCC和GND则是供电和地。通常参考电压的正极和VCC是一样的,会接在一起,参考电压的负极也是一样的,也接在一起。所以一般情况下,ADC输入电压的范围和ADC的供电是一样的。

ADC结构框图

左边是ADC的输入通道,包括16个IO口,IN0~IN15,和两个内部的通道,一个是内部温度传感器,另一个是内部参考电压,到达模拟多路开关,可以指定我们选择的通道,右边是多路开关的输出,进入到模数转换器,这个模数转换器就是执行逐次比较的过程,转换结果会放在注入通道数据寄存器和规则通道数据寄存器。对于普通ADC,多路开关都是选中一个,就是选中某个通道、开始转换、等待转换结果,取出结果,这是普通ADC转换流程。 这里可以选中多个转换的时候还分成了两个组,规则通道,和注入通道,规则通道一次最多选择16通道,注入通道则一次最多选择4通道,规则通道只有一个数据寄存器,一般配合DMA来实现。而注入通道则有四个数据寄存器,不用担心数据被覆盖的问题了。

左下角则是触发转换的部分,也就是ADC的转换信号,对于STM32的ADC,触发ADC转换的信号有两种,一种是软件触发,就是在程序中调代码,就启动转换了,另一种就是硬件触发,就是左下角的触发源,第一部分是注入组的触发源,第二部分则是规则组的触发源,规则组的触发源主要来源于定时器,有定时器的各个通道,还有TRGO定时器主模式的输出。定时器可以通向ADC、DAC这些外设,用于触发转换。 因为ADC经常需要过一个固定时间段转换一次,如:每隔1ms转换一次,正常思路:用定时器每隔1ms申请中断,在中断里手动转换一次。但是频繁进中断肯定有影响,在中断里只完成了简单的转换,程序要频繁进中断。 就可以用这个思路:如:TIM3定1ms的时间,把TIM3的更新事件选择为TRGO输出,在ADC这里,选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就能通过硬件自动触发ADC转换了,整个过程不需要进中断,节省中断资源,这就是定时器触发的作用。 当然这里还可以选择外部触发引脚来判断。

接着ADC输入通道的左上角Vref+、Vref-、VSSA、VDDA,上面两个是ADC的参考电压,决定了ADC的输入电压范围,下面是供电和接地引脚,一般情况下,Vref+接电源,Vref-接地,在这里VDDA、VSSA是内部模拟部分的电源,如:ADC、RC振荡器、锁相环,这里,VDDA接3.3V,VSSA接地,所以ADC的电压范围0~3.3V,然后往右边看ADCCLK是ADC的时钟,也就是ADC的时钟,来自于ADC预分频器,这个预分频器来源于RCC,最大14Mhz,但对于ADC分频只能选择6分频和8分频。

ADC通向DMA的通道:数据转运

注入通道寄存器和规则通道寄存器用来存放结果。

在往上看是模拟看门狗,它可以存一个 阈值高限和阈值低限,如果启动了看门狗,并指定了看门的通道,那这个看门狗就会关注它看门的通道,一旦超出了阈值范围,就会申请模拟看门狗的中断,最后通向NVIC。对于规则组和注入组而言,他们转换完成之后,也会有一个EOC转换完成的信号,EOC是规则组转换完成的信号,JEOC是注入组转换完成的信号。这两个信号会在状态寄存器里置一个标志位,读取标志位,就直到是否转换完成了。同时这两个标志位可以NVIC,申请中断。

ADC逻辑框图

左边是输入通道,16个GPIO口,外加两个内部通道,然后进入AD转换器,AD转换器有两个组,一个是规则组,一个是注入组,规则组可以选择16个通道,注入组最多一次选择四个,转换的结果可以放在AD数据寄存器里,规则组有一个数据寄存器,而注入组有四个寄存器,下面有START触发信号,提供开始信号,触发控制可以选择软件控制和硬件控制,硬件触发主要来源于定时器,也可以选择外部中断的引脚,下面右边则是来源于RCC的ADC时钟,ADC逐次比较的过程就是这个时钟推动的,上面可以布置一个模拟看门狗用于检测转换的范围,如果超出阈值范围,那就通过中断输出控制,向NVIC申请中断。另外注入组和规则组转换后有一个EOC信号,它会置一个标志位,也可以通向NVIC,右下角有一个开关控制,用于ADC上电。

转换和通道

上图是ADC通道和引脚对应表,这里通道一共有18个,通道16对应ADC1的温度传感器,通道17对应ADC1的内部参考电压,这里只有ADC1有通道16和通道17, ADC2、ADC3没有。上面是引脚对应,ADC1、ADC2是相同的,ADC3中间有些变化。

转换模式

例如:这个表是规则组里面的菜单,16个序列,可以写入要转换的通道,在非扫描模式下,只有第一个序列有效,只能选中一个,然后触发转换,ADC对通道进行模数转换,等待转换完成后,结果放在数据寄存器里面,同时EOC标志位置1。 判断EOC标志位,如果转换完成就可以读取结果。如果在启动一次转换,那就在触发一次。如果想要换一个通道,在转换之前需要改通道。

与单次转换非扫描模式不同的是,转换一次后并不会停止,而是立刻开始下一轮转换,然后一直持续下去,这样只需要最开始触发一次,之后就可以一直转换了。这个模式转换完成之后不需要等待一段时间,因为一直都在转换,所以不需要手动转换。也不用判断标志位,读取AD值,直接在寄存器读取即可。

这个模式每触发一次,就会停下来,和单次转换非扫描模式一样,下次转换就要在触发。这个序列每个位置的通道可以任意指定,并且可以重复,16个序列可以不用完,在结构体参数中有个选择序列前几个有效的参数。然后每次触发之后,就可以只对前几个位置有效。转换结果都放在数据寄存器里面,为了防止数据被覆盖,就要及时用DMA将数据搬运走。几个通道转换完成后,转换结束,产生EOC信号。然后在触发下一次。

这个模式就是在上一个模式中把等待时间去掉,立刻开始下一次的转换。

在扫描模式的情况下,还可以有另外一种模式,叫间断模式,就是在扫描模式中,每隔几个转换暂停一次,需要再次触发,才能继续。

ADC是12位的,但数据寄存器是16位的存在一个数据对齐问题,第一中是数据右对齐,高位多出来补0,第二位是数据左对齐,低位多出来的补零。一般使用右对齐,可以直接使用结果。 如果要降低分辨率的话,可以选择左对齐,把数据高8位提取出来,后面四位精度可以去掉。就变成了8位ADC。

如果不需要非常高的转换速率,那么转换时间可以忽略掉,AD转换需要一段时间,AD转换步骤:采样,保持,量化,编码,采样保持可以放在一起,量化编码也可以放在一起,一共两大步骤,量化编码就是ADC逐次比较的过程,位数越多,花的时间越多。AD转换是需要一小段时间的,如果在这段时间里,输入的电压还在不断变化,那就没办法定位输入电压在哪,在量化编码之前,需要设置一个采样开关,打开采样开关,收集电压,如:可以用一个小容量电容存储电压,存储好之后,断开开关,再进行后面的AD转换,这里再量化编码的期间,电压始终不变,这样才能定位电压的位置。采样保持的过程,需要闭合采样开关,过一段时间再断开,这里就会产生一个采样时间。

•STM32 ADC的总转换时间为: TCONV = 采样时间 + 12.5个ADC周期

采样时间是采样保持花费的时间,采样时间越大,越能避免一些毛刺信号的干扰,转换时间也相应的延长,12.5个ADC周期是量化编码花费的时间,因为是12位的,所以要花费12个周期,

ADC配置步骤

第一步:开启RCC时钟,包括ADC时钟和GPIO时钟,另外ADCCLK的分频器也需要配置

第二步:配置GPIO

第三步:配置通道 规则组还是注入组

第四步:ADC初始化 //ADC模式 扫描、连续 、数据对齐 通道数、 触发模式

第五步:使能 、校准

代码

ADC单通道

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);		//开ADC1、GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);		//ADC六分频

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//ADC1   通道1   序列1		采样时间
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);

	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续关闭
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;			//数据右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;   //外部触发方式
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  //单独模式
	ADC_InitStructure.ADC_NbrOfChannel = 1;						//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;		//关闭扫描
	
	ADC_Init(ADC1,&ADC_InitStructure);			//ADC使能
	
	ADC_ResetCalibration(ADC1);			//ADC校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	
}

/**
  * 函    数:获取AD转换的值
  * 参    数:无
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);		//软件触发ADC转换一次
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);  //等待ADC转换结束
	return ADC_GetConversionValue(ADC1);			//获取ADC的值
}


主函数

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


int main(void)
{
	uint16_t ADValue;		//AD值
	float Voltage;		//电压变量
	
	AD_Init();
	OLED_Init();

		/*显示静态字符串*/
	OLED_ShowString(1, 1, "ADValue:");
	OLED_ShowString(2, 1, "Volatge:0.00V");
	
	
	while(1)
	{
		ADValue = AD_GetValue();
		Voltage = (float)ADValue/4095 * 3.3;
		
		OLED_ShowNum(1,9,ADValue,4);		//显示AD值
		OLED_ShowNum(2,9,Voltage,1);		//显示电压整数部分
		OLED_ShowNum(2,11,(uint16_t)(ADValue*100 %100),2);		//显示电压小数部分
		
		Delay_ms(1000);
	}
}

ADC多通道

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);		//开ADC1、GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);		//ADC六分频

	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);
	
	//ADC1   通道1   序列1		采样时间
	//ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);

	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续关闭
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;			//数据右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;   //外部触发方式
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  //单独模式
	ADC_InitStructure.ADC_NbrOfChannel = 1;						//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;		//关闭扫描
	ADC_Init(ADC1,&ADC_InitStructure);			//ADC使能
	
		/*ADC使能*/
	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
	
	ADC_ResetCalibration(ADC1);			//ADC校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	
}

/**
  * 函    数:获取AD转换的值
* 参    数:ADC_Channel:通道  范围0~3
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
	if(ADC_Channel == 0)
		ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	else if(ADC_Channel == 1)
		ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_55Cycles5);
	else if(ADC_Channel == 2)
		ADC_RegularChannelConfig(ADC1,ADC_Channel_2,1,ADC_SampleTime_55Cycles5);
	else if(ADC_Channel == 3)
		ADC_RegularChannelConfig(ADC1,ADC_Channel_3,1,ADC_SampleTime_55Cycles5);
	
	//ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);		//软件触发ADC转换一次
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);  //等待ADC转换结束
	return ADC_GetConversionValue(ADC1);			//获取ADC的值
}

/**
  * 函    数:获取AD转换的值
  * 参    数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
  * 返 回 值:AD转换的值,范围:0~4095
  */
//uint16_t AD_GetValue(uint8_t ADC_Channel)
//{
//	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);	//在每次转换前,根据函数形参灵活更改规则组的通道1
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
//	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
//	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
//}

主函数

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


int main(void)
{
	uint16_t AD0,AD1,AD2,AD3;		//AD值
	
	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(0);
		AD1 = AD_GetValue(1);
		AD2 = AD_GetValue(2);
		AD3 = AD_GetValue(3);
//		AD0 = AD_GetValue(ADC_Channel_0);		//单次启动ADC,转换通道0
//		AD1 = AD_GetValue(ADC_Channel_1);		//单次启动ADC,转换通道1
//		AD2 = AD_GetValue(ADC_Channel_2);		//单次启动ADC,转换通道2
//		AD3 = AD_GetValue(ADC_Channel_3);		//单次启动ADC,转换通道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);
	}

}

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值