STM32F1核心板:ADC模数转换器、AD单通道、AD多通道

2023-12-27 14:21:16

ADC简介

????????ADC(Analog-Digital Converter)模拟-数字转换器(AD转换器)
????????ADC可以将引脚上连续变化的模拟电压转换位内存中存储的数字变量,建立模拟电路到数字电路的桥梁

? ? ? ? 1、STM32主要是数字电路,数字电路只有高低电平,没有几V电压的概念,所以若想读取电压值,就需要借助ADC模数转换器来实现了。ADC读取引脚上的模拟电压,转换为一个数据,存在寄存器里,再把这个数据读取到变量里来,就可以进行显示、判断、纪记录等等操作。ADC可以将模拟信号转换为数字信号,是模拟电路到数字电路的桥梁。

? ? ? ? 2、DAC,数字-模拟转换器,使用DAC就可以将数字变量转化为模拟电压。

? ? ? ? 3、PWM也是一个数字到模拟的桥梁,使用PWM来控制LED的亮度,电机的速度,这就是DAC的功能,同时PWM只有完全导通和完全断开两种状态,在这两种状态上都没有功率损耗,所以在直流电机调速这种大功率的引用场景,使用PWM来等效模拟量,是比DAC更好的选择。并且PWM电路更加简单更加常用,所以可以看出PWM还是挤占了DAC的很多应用空间。目前DAC的应用场景主要是在波形生成这些领域,比如信号发生器,音频解码芯片等。

????????12位逐次逼近型ADC,1us转换时间

? ? ? ? 1、逐次逼近型这是ADC的工作模式

????????2、ADC的两个关键参数,第一个分辨率,一般用多少位来表示,12位AD值,它的表示范围就是0~2^12-1,就是量化结果的范围是0~4095,位数越高,量化结果越精细,对应分辨率就越高。

? ? ? ? 3、第二个转换时间,就是转换频率,AD转换是需要花一小段时间的,这里的1us就是表示从AD转换开始,到产生结果,需要花1us的时间,对应AD转换的频率就是1MHz,这个就是STM32 ADC的最快转换频率。若是需要转换一个频率非常高的信号,就要考虑这个转换频率是不是够用。如果信号频率比较低,这个最大1MHz的转换频率也够用了。

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

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

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

? ? ? ? 1、外部信号源就是16个GPIO口,在引脚上直接接模拟信号就行,不需要任何额外的电路,引脚就直接能测电压。

? ? ? ? 2、两个内部信号源是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,内部参考电压是一个1.2V左右的基准电压,这个基准电压是不随外部供电电压变化而变化的,所以如果芯片的供电不是标准的3.3V,测量外部引脚的电压可能就不对,这时就可以读取这个基准电压进行校准,这样就能得到正确的电压值。

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

? ? ? ? STM32 ADC的增强功能,普通的AD转换流程是,启动一次转换,读一次值,然后再启动,再读值,这样的流程。STM32的ADC 就比较高级,可以列一个组,一次性启动一个组,连续转换多个值,并且有两个组,一个是用于常规使用的规则组,一个是用于突发事件的注入组。

????????模拟看门狗自动检测输入电压范围

? ? ? ?这个ADC,一般可以用于测量光线强度,温度这些值,并且经常会有个需求,就是如果光线高于某个阈值,低于某个阈值,或者温度高于某个阈值、低于某个阈值时,执行一些操作,高于某个阈值、低于某个阈值的判断,就可以用模拟看门狗来自动执行,模拟看门狗,可以检测指定的某些通道,当AD值高于它设定的上阈值或者低于下阈值时,就会申请中断,就可以在中断函数里执行相应的操作。

STM32F103C8T6 ADC资源:ADC1、ADC2、10个外部输入通道,它最多只能测量10个外部引脚的模拟信号

AD转换的步骤:采样,保持,量化,编码

STM32 ADC的总转换时间为
?? ?Tconv=采样时间 + 12.5个ADC周期
?? ?
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
?? ?Tconv=1.5+12.5=14个ADC周期=1us
?? ?
校准:?
????????ADC有一个内置自校准模式,校准可大幅减小因内部电容器组的变化而造成的准精度误差,校准期间,在每个电容器上都会计算出以恶搞误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。
建议在每次上电后执行一次校准
????????启动校准前,ADC必须处于关电状态超过至少两个ADC时钟周期

AD单通道

实验现象


将电位器(滑动变阻器)往左拧,AD值减小,电压值也减小
AD值最小是0,对应的电压就是0V。往右宁,AD值变大,对应电压值也变大。


STM32的ADC是12位的,所以AD结果最大值是4095,也就是2的12次方减1,对应的电压是3.3V

对GPIO来说,它只能读取引脚的高低电平,要么是高电平,要么是低电平,只有两个值,使用ADC之后,就可以对这个高电平和低电平之间的任意电压进行量化,最终用一个变量来表示,读取这个变量,就可以知道引脚的具体电压到底是多少。所以ADC就是一个电压表,把引脚的电压值测出来,放在一个变量里,这就是ADC的作用。

步骤

第一步:开始RCC时钟,包括ADC和GPIO的时钟,另外ADCCLK的分频器,也需要配置一下。
第二步:配置GPIO,把需要用到的GPIO配置成模拟输入模式
第三步:配置这里的多路开关,把左边的通道接入到右边的规则组列表里
第四步:配置ADC转换器(使用结构体来配置)
第五步:开关控制,调用ADC_Cmd函数,开启ADC

函数说明?

RCC_ADCCLKConfig();
这个函数是用来配置ADCCLK分频器的,它可以对APB2的72MHz时钟选择2,4,6,8分频,输入到ADCCLK,在RCC库函数里面

ADC_Init();
初始化

ADC_Cmd();
用于给ADC上电


用于控制校准的函数,在ADC初始化完成之后,依次调用就行
ADC_ResetCalibration();
复位校准

ADC_GetResetCalibrationStatus();
获取复位校准状态

ADC_StartCalibration();
开始校准

ADC_GetCalibrationStatus();
获取开始校准状态


ADC_SoftwareStartConvCmd();
ADC获取软件开始转换状态,用于软件触发的函数

ADC_GetFlagStatus();
获取标志位状态,参数给EOC的标志位,判断标志位EOC标志位是不是置1了,如果转换结束,EOC标志位置1,然后调用这个函数,判断标志位,判断转换是否结束的方法

ADC_RegularChannelConfig();
ADC规则组通道配置

ADC_ExternalTrigConvCmd();
ADC外部触发转换控制,就是是否允许外部触发转换

?ADC_GetConversionValue();
?ADC获取转换值,获取AD转换的数据寄存器,读取转换结果就要使用这个函数
?

AD.c文件

#include "stm32f10x.h"                  // Device header

//AD初始化
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;	//模拟输入
	/*
	在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压照成干扰
	AIN模式可以说是ADC的专属模式
	*/
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//将PA0引脚初始化为模拟输入
	
	/*规则组通道配置*/
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//规则组序列1的位置,配置为通道0
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;	//定义结构体变量
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;	//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;	//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;		//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel= 1;	//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1,&ADC_InitStructure);		//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1,ENABLE);	//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);		//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);

}

//获取AD转换的值,范围0~4095
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);	//软件触发AD转换一次
	while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET );	//等待EOC标志位,即等待AD转换结束
	return ADC_GetConversionValue(ADC1);	//读数据寄存器,得到AD转换的结果
}

AD.h

#ifndef _AD_H
#define _AD_H
void AD_Init(void);
uint16_t AD_GetValue(void);

#endif

?main.c

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

uint16_t ADValue;	//定义AD值变量
float Voltage;		//定义电压变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();	//OLED初始化
	AD_Init();		//AD初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1,1,"ADValue:");
	OLED_ShowString(2,1,"Voltage:0.00V");
	
	while(1)
	{
		ADValue = AD_GetValue();	//获取AD转换的值
		Voltage = (float)ADValue /4095 * 3.3;	//将AD值线性变换到0~3.3的范围,表示电压
		
		OLED_ShowNum(1,9,ADValue,4);	//显示AD值
		OLED_ShowNum(2,9,Voltage,1);	//显示电压值的整数部分
		OLED_ShowNum(2,11,(uint16_t)(Voltage * 100) % 100,2);	//显示电压值的小数部分
		
		Delay_ms(100);		//延时100ms,手动增加一些转换的间隔时间	
	}
}

AD多通道

?????????再外接三个模块,光敏电阻,热敏电阻和反射红外模块,将他们的AO、模拟电压输出端,分别接在了A1、A2、A3引脚,加上电位器,总共4个输出通道,测出的4个AD数据分别显示在屏幕上。

第一个电位器AD0,往左拧变小,往右拧增大。
第二个光敏电阻AD1,遮挡光敏电阻,光线减小,AD值增大,移开,光线增大,AD减小。
第三个热敏电阻AD2,用手热一下热敏电阻,温度升高,AD值减小,移开,温度降低,AD值增大
第四个反射红外传感器AD3,手靠近,有反光,AD值减小,移开,没有反光,AD值增大

AD.c

#include "stm32f10x.h"                  // Device header

//AD初始化
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);		//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);	//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;	//模拟输入
	/*
	在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压照成干扰
	AIN模式可以说是ADC的专属模式
	*/
	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);	//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
		
	/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
	
	//ADC初始化
	ADC_InitTypeDef ADC_InitStructure;	//定义结构体变量
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;	//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;			//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode=DISABLE;					//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel= 1;				//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1,&ADC_InitStructure);			//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1,ENABLE);		//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);		//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);

}

/**
  * 函    数:获取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转换的结果
}

AD.h

#ifndef _AD_H
#define _AD_H
void AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);

#endif

main.c

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

uint16_t AD0,AD1,AD2,AD3;	//定义AD值变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();	//OLED初始化
	AD_Init();		//AD初始化

	/*显示静态字符串*/
	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);	//单次启动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);	//显示通道0的转换结果AD0
		OLED_ShowNum(2,5,AD1,4);	//显示通道1的转换结果AD1
		OLED_ShowNum(3,5,AD2,4);	//显示通道2的转换结果AD2
		OLED_ShowNum(4,5,AD3,4);	//显示通道3的转换结果AD3
		 
		Delay_ms(100);	//延时100ms,手动增加一些转换的间隔时间	
	}
}

文章来源:https://blog.csdn.net/weixin_47001540/article/details/135233390
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。