ADC (Analog-Digital Converter) 模拟-数字转换器
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
12位逐次逼近型ADC**,1us转换时间
输入电压范围∶0-3.3V,转换结果范围∶0~4095。
18个输入通道,可测量16个外部和2个内部信号源规则组和注入组两个转换单元
模拟看门狗自动监测输入电压范围**
12位ADC的值,量化的范围就是0 - 2^12-1 ,即0-4095。位数越高。量化结果就越精细。对应分辨率就越高。
关于这个模拟看门狗,我们可以设定阈值。当AD值高于它设定的上阈值或者低于下阈值时,它就会申请中断,你就可以在中断函数里执行相应的操作。
STM32的ADC的原理和这个一样,这个是ADC0809的内部结构
首先进行通道开关的选择。在左方有个通道选择开关。选择的通道的数字是存在下方的ADDA,ADDB,ADDC中。你想选择IN0-IN7中的哪一个通道,就把那个通道号放在这三个里面。ALE是锁存信号,给ALE置位,上面这里对应的通路开关就可以自动拨好了。
然后来到了比较器的上面一根线。
在比较器中用逐次逼近的方法来一一比较
比较器的作用就是判断两个输入信号电压的大小关系。
比较器的上面一根线是一个外部通道输入的,未知编码的电压。
比较器的下面一根线是一个DAC输出的,已知编码的电压。
如果DAC输出的电压比较小。我就增大DAC数据。直到DAC输出的电压和外部通道输入的电压近视相等。这样DAC输入的数据就是外部电压的编码数据了。(这里就是输入到8位三态锁存缓冲器)
而这个增大或减小去调整DAC的数据的过程,是那个逐次逼近寄存器SAR在起作用。
它通常会使用二分法进行查找,比如在这里是8位的ADC,就是0-255。第一次比较的时候,我们就给DAC输入255的一半进行比较。第二次比较的时候。再就给128的一半64。就这么继续往下比较。而且128,64,32正好是二进制每一位的位权。也就是说,这个比较的过程,就是对二进制从高位到低位依次判断是1是0的过程。那对于8位的ADC,从高位到低依次判断8次就能找到未知电压的编码。12位的就是判断12次。
EOC是End Of Gonvert转换结束信号
START是开始转换。给一个输入脉冲,开始转换。
CLOCK是ADC时钟。
下面。VRVF+和VREF-是DAC的参考电压。
比如你给一个数据255。是对应5V还是3.3V呢,就由这个参考电压决定。
这个DAC的参考电压也决定了ADC的输入范围,所以它也是ADC参考电压。
通常参考电压的正极和VCC是一样的,会接一起。参考电的负极和GND也是一样的。也接在一起。
左边是输入通道,16个GPO口,外加两个内部的通道。
然后进入AD转换器
AD转换器里有两个组,一个是规则组,一个是注入组
规则组最多可以选中16个通道。注入组最多可以选择4个通道。
然后转换的结果可以存放在AD数据寄存器里,规则组有1个数据寄存器,注入组有4个数据寄存器。
然后下面这里有触发控制。提供了开始转换的START信号。
触发控制可以选择硬件触发和软件触发,硬件触发主要来自于定时器和外部中断触发。
右边这里是来自RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的
有三个方式可以申请中断,首先规则组和注入组转换完成会有EOC信号,会置一个标志位。其次是看门狗,如果超过阈值,就通过中断输出控制,像NVIC申请中断。
规则组
触发—进入第一个序列位置(选中1个)---- 然后就转换完毕了
此时生成一个EOC的信号
如果要转换就要再次触发
触发—进入第一个序列位置(选中1个)---- 此次转换完毕 -----生成EOC ----- 不需要触发继续重复上述过程
这个模式也是触发一次用一次
但是一次有好几个序列
所以要给一个通道数目,就是有几个序列能用,有几个就从序列1开始往下排几个
每次触发后,就依次对这前7个(通道数目)进行AD转换,且为了防止数据覆盖,要及时用DMA挪走数据
那7个通道转换完成之后,产生EOC信号,转换结束。
1、ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。
2、 建议在每次上电后执行一次校准。
3、 启动校准前,ADC必须处于关电状态超过至少两个ADC时钟周期。
我使用的板子是正点原子的迷你板
STM32的型号是 STM32F103RCT
有三个ADC
我们在这里使用ADC1的通道1
ADC规则组通道配置
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
是否允许外部触发转换
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
获取转换值
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
双ADC模式读取转换结果
uint32_t ADC_GetDualModeConversionValue(void);
#include "stm32f10x.h"
void Adc_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
//1、开启时钟ADC1的时钟和GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
//2、配置分频,因为最大不能超过14MHz 72/6=12
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//3、配置GPIOA和ADC结构体 PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//4、使能ADC1
ADC_Cmd(ADC1, ENABLE);
//5、校准并等待校准结束
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
首先PA1是ADC通道1
所以这个函数中的ADC_Channel要选择如图所示
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)
adc.c
#include "stm32f10x.h"
void Adc_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
//1、开启时钟ADC1的时钟和GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
//2、配置分频,因为最大不能超过14MHz 72/6=12
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//3、配置GPIOA和ADC结构体 PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//4、使能ADC1
ADC_Cmd(ADC1, ENABLE);
//5、校准并等待校准结束
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
uint16_t AD_GetValue(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
//多获取几次数据求平均值更稳定
uint16_t Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=AD_GetValue(ch);
delay_ms(5);
}
return temp_val/times;
}
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "ADC.h"
#include "lcd.h"
int main(void)
{
float temp;
u16 adcx;
delay_init(); //延时函数初始化
LCD_Init();
Adc_Init();
LCD_ShowString(10,70,200,16,16,"AD_GetValue");
LCD_ShowString(10,100,200,16,16,"AD_Average");
LCD_ShowString(10,150,200,16,16,"ADC_CH1_VOL:0.000V");
while(1)
{
LCD_ShowxNum(110,70,AD_GetValue(ADC_Channel_1),4,16,0);
LCD_ShowxNum(110,100,Get_Adc_Average(ADC_Channel_1,10),4,16,0);
adcx=Get_Adc_Average(ADC_Channel_1,10);
temp=(float)adcx*(3.3/4096);
adcx=temp;
LCD_ShowxNum(106,150,adcx,1,16,0);//显示整数
temp-=adcx;
temp*=1000;
LCD_ShowxNum(122,150,temp,3,16,0X80); //显示小数
}
}
接3.3v和接GND以及不接是浮空,电平不稳定一直变
4、在不使用DMA的情况下,进行多个通道的AD转换
在这里就是自己选择通道获取一次
#include "stm32f10x.h"
void Adc_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
//1、开启时钟ADC1的时钟和GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
//2、配置分频,因为最大不能超过14MHz 72/6=12
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//3、配置GPIOA和ADC结构体
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4| GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//4、使能ADC1
ADC_Cmd(ADC1, ENABLE);
//5、校准并等待校准结束
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
/*
uint16_t AD_GetValue(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
*/
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
//多获取几次数据求平均值更稳定
uint16_t Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=AD_GetValue(ch);
delay_ms(5);
}
return temp_val/times;
}
#include "stm32f10x.h"
#include "delay.h"
#include "ADC.h"
#include "lcd.h"
int main(void)
{
float temp;
u16 adcx;
delay_init(); //延时函数初始化
LCD_Init();
Adc_Init();
LCD_ShowString(10,70,200,16,16,"AD_GetValue");
LCD_ShowString(10,100,200,16,16,"AD_Average");
//LCD_ShowString(10,150,200,16,16,"ADC_CH1_VOL:0.000V");
while(1)
{
LCD_ShowxNum(110,70,AD_GetValue(ADC_Channel_4),4,16,0);
LCD_ShowxNum(110,100,Get_Adc_Average(ADC_Channel_4,10),4,16,0);
LCD_ShowxNum(110,170,AD_GetValue(ADC_Channel_5),4,16,0);
LCD_ShowxNum(110,200,Get_Adc_Average(ADC_Channel_5,10),4,16,0);
}
}