ADC简介
18个通道:外部信号源就是16个GPIO回。在引脚上直接接模拟信号就行了,不需要侄何额外的电路。引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压。
逐次逼近型ADC:
它是一个独立的8位逐次逼近型ADC芯片,这个ADC0809是一款经典的ADC芯片。现在单片机的性能和集成都有很大的提升,很多单片机内部就有ADC芯片,这样就不用外挂芯片了,引脚可以直接测电压,使用还是非常方便的。
首先左边这里的IN0~IN7,是8路输入通道,通过通道选择开关,选中这一路,输入到这个点进行转换 。下面是地址锁存和译码,就是你想选中那个通道,就把通道号放在这三个脚上 ,然后给一个锁存信号,上面这里对应的通路开关就可以自动拨好了,这部分就相当于一个可必通过模拟信号的数据选择器。因为AD转换是一个很快的过程,你给个开始信号,过几个us就转换完成了,所以说如果你想转换多路信号,只需要一个AD转换器。然后加一个多路选择开,想转换哪路,就先拨一下,选中对应通道,然后再开始转换就行了。
STM32内部的ADC是有18个输入通道的,所以对应这里就是有18路输入的多路开关。
1个外部通道输入的未知编码的电压和一个DAC输出的已知编码的电压。它俩同时输入到电压比较器,进行大小判断。如果DAC输出的电压比较大,我就调小DAC数据,如果DAC输出的电压比较小,我就增大DAC数据,直至DAC输出的电压和外部通道输入的电压相等,这样DAC输入的数据就是外部电压的编码数据了。
ADC框图
左边是ADC的输入通道包括16个GPIO口,IN0~N15;和两个内部的通道,一个是内部温度传感器,另一个是参考电压。总共18个输入通道,然后到达这里,这是一个模拟多路开关,可以指定我们想要选择的通道。右边是多路开关的输出,进入模数转换器,转换结果会直接放在这个数据寄存器,我们读取寄存器就能知道ADC转换的结果了。
注入通道和规则通道
对于普通的ADC,多路开关一般都是只选中某一个通道、开始转换、等待转换完成、取出结果。
在这里它可以同时选中多个,在转换的时候,还分成了两个组,规则通道组和注入通道组。规则组:一次可以最多选中16个通道;注入组:最多可以选中4个通道。规则组虽然可以选中16个通道,但是数据寄存器只能存取一个结果,如果不想之前的结果被覆盖,那在转换完成之后,就要把结果拿走。注入组最多可以选4个通道,但他的数据寄存器有4个,所以他就不用担心数据被覆盖的问题了。
中断触发ADC转换
STM32的ADC,触发ADC开始转换的信号有两种:1、软件触发:就是在程序中手动调用一条代码,就可以启动转换了。2、硬件触发:左下角选中的触发源。这些触发源主要是定时器,有定时器的各个通道。
上面两个是ADC的参考电压,决定了ADC输入电压的范围;下面两个是ADC的供电引脚。一般情况VREF+要接VDDA,VREF-要接VSSA。
ADC的时钟:ADCCLK
ADCCLK来自预分频器,ADC预分频器是来源与RCC的。
ADCCLK最大14MHz,ADC预分频可以选择2、4、6、8分频,选择2分频,36MHz,超出ADCCLK的范围了,所以只能选择6分频或者8分频。
ADC基本结构图
左边是输入通道,16个GPIO口,加两个内部的通道,然后进入AD转换器。
AD转换器里有两个组:
1、规则组:一次可以最多选中16个通道,虽然可以选中16个通道,但是数据寄存器只能存取一个结果,如果不想之前的结果被覆盖,那在转换完成之后,就要把结果拿走。
2、注入组:最多可以选中4个通道,但他的数据寄存器有4个,所以他就不用担心数据被覆盖的问题了。
触发控制:软件触发、硬件触发
RCC的时钟CLOCK:ADC逐次比较的过程就是有这个这个时钟推动的。
输入通道
转换模式
1、单次转换,非扫描模式
2、连续转换,非扫描模式
首先,他还是非扫描模式,所以菜单列表就只用第一个,然后他与单次转换的不同的是,它在第一次转换之后不会停止,而是立刻开始下一轮的转换,然后一直持续。
3、单次转换,扫描模式
这个模式也是单次转换,所以每触发一次,转换结束后,就会停下来,下次转换就得再触发才能开始。然后它是扫描模式,这就会用到这个菜单列表了,这里每个位置是通道几可以任意指定,并且也是可以重复的。
16个通道位置用不完的情况,只用前几个,那就需要再给一个通道数目的参数(几个通道)。比如说这里指定的通道数目为7,那就只看前7个位置,然后每次触发之后,他就一次对这前7个位置进行AD转换,转换结果都放在数据寄存器里。为了防止数据被覆盖,就需要用DMA及时将数据挪走。7个通道转化完成之后,产生EOC信号,转换结束。
4、连续转换,扫描模式
就是在单次转换,扫描模式的基础上,一次转换完之后,立刻开始下一轮的转换。
数据对齐
STM32F103C8T6这个ADC是12位的,它的转换结果就是一个12位的数据,但是数据寄存器是16位的,所以就存在一个数据对齐的问题。
数据右对齐:12位的数据向右靠,高位多出来的几位就补0
数据左对齐:低位多出来的几位补0
我们一般使用的是右对齐,这样读取的16位寄存器,直接就是转换结果。如果是左对齐的话,结果比实际结果大,左移4位,相当于把结果乘16了。
转换时间
校准
示例工程
1、AD单通道实现
#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;
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转换的值
* 参 数:无
* 返 回 值: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转换的结果
}
#ifndef __AD_H
#define __AD_H
void AD_Init(void);
uint16_t AD_GetValue(void);
#endif
#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,手动增加一些转换的间隔时间
}
}
2、AD多通道实现
#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;
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转换的结果
}
#ifndef __AD_H
#define __AD_H
void AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);
#endif
#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,手动增加一些转换的间隔时间
}
}