STM32 ADC
目录
ADC简介
逐次逼近型ADC
STM32 ADC框图
输入通道
转换模式
•单次转换,非扫描模式
•连续转换,非扫描模式
•单次转换,扫描模式
•连续转换,扫描模式
触发控制
数据对齐
转换时间
校准
硬件电路
AD单通道
ADC简介
逐次逼近型ADC
左边IN0-7,八路输入通道,通过通道选择开关,选择一路输入到红点进行转换,下面这里是地址锁存和译码,就是你想选择哪一路就把通道号放在左边的三个脚上,然后给一个锁存信号,上面这里对应的通路开关就可以拨好了,这部分就相当于一个可以通过模拟信号的数据选择器,
输入信号选好了,到红点了,怎么才能知道这个电压对应的编码数据是多少呢,这就需要我们用逐次逼近的方法来一一比较了,右边的三角形是一个电压比较器,它可以判断两个输入信号电压的大小关系,两个输入端,一个上面是待测端,一个是DAC的电压输出端,DAC是数模转换器,给它一个数据就可以输出数据对应的电压
现在我们有了一个外部通道输入的未知编码的电压和一个DAC输出的已知编码的电压,它俩同时输入到电压比较器,进行大小判断,如果DAC输出的电压比较大,我就调小DAC数据,如果DAC输出的电压比较小,我就调大DAC数据,直到与外部通道输入的电压近似相等,这样DAC输入的数据就是外部电压的编码数据了,这个电压调节的过程就是逐次逼近寄存器SAR完成的,为了最快找到未知电压的编码,通常我们会使用二分法进行寻找
然后AD转换结束后,DAC的输入数据(蓝点),就是未知电压的编码,通过右边输出,8位就有8根线,12位就有12根线,上面EOC(转换结束信号),START是开始转换,给一个输入脉冲,开始转换,CLOCK是时钟,因为ADC是需要一步一步判断的需要时钟推动这个过程,
VREF+和VREF-是DAC的参考电压,比如给一个数据255,是对应5v还是3.3v呢就由这个参考电压决定,这个DAC的参考电压也决定了ADC的输入范围,所有它也是ADC参考电压,左边是整个芯片的供电VCC和GND
STM32 ADC框图
左上角是VREF+,VREF-,VDDA,VSSA,上面两个是ADC的参考电压决定ADC输入电压的范围,下面是ADC的供电引脚,一般情况下VREF+接VDDA,VREF-接VSSA,这个芯片上VREF+和VREF-引脚,在内部已经接在一起了,VDDA和VSSA是内部模拟部分的电源,在这里VDDA接3.3V,VSSA接GND,所以ADC的输入电压范围是0-3.3v
左边是ADC的输入通道,包括16个GPIO口,IN0-15和两个内部通道温度传感器和VREFINT(内部参考电压),然后到底模拟多路开关,可以知道我们想要选择的通道,右边是多路开关的输出,进入到模数转换器,这里模数转换器就是执行逐次比较的过程,转换结构会直接放进上面数据寄存器里,我们读取寄存器就能知道ADC转换的结果了,在左边对应普通的ADC多路开关只选择一个的,但这个不同,可以同时选择多个,而且在转换的时候,还分成了两个组,规则通道组和注入通道组,其中规则组可以一次性最多选16个通道,注入组最多可以选4个通道(就好比去餐厅点餐,普通ADC是,你指定一个菜(通道几的数据),老板给你做,然后做好了送给你,这里是你指定一个菜单,这个菜单最多可以填16个菜,然后直接递个菜单给老板,老板按照菜单的顺序依次做好,一次性给你端上来,这个菜单也分两种,一个是规则组,但是这个规则组只有一个数据寄存器,就是这桌子比较小,你如果上16个菜,那前15个菜就会被挤掉,你只能得到第16个菜,所有对于规则组来说,最后配合DMA来实现,DMA是个数据转移小能手,每上一个菜,它可以把这个菜挪到其他地方去,防止被覆盖,注入组相当于餐厅VIP,这个座位上一次性可以点4个菜,并且这个数据寄存器有4个,是可以同时上4个菜的)一般情况下使用规则组
左下角是触发转换部分,也就是上图的START信号,开始转换,那对于STM32ADC,触发ADC开始转换的信号有两种,一种是软件触发,就是在程序中手动调用一条代码就可以启动了;另一种是硬件触发就是左边的触发源,上面是注入组触发源,下面是规则组的触发源,主要来自于定时器,比如给TIM3定个1ms的时间,并且把TIM3的更新事件选择位TRGO输出,然后在ADC这里选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就能通过硬件自动触发ADC转换了,整个过程不需要进中断,节省了中断资源,当然这里还可以选择外部中断引脚来触发转换
模拟看门狗,它里面可以存一个阈值高限和阈值低限,如果启动了模拟看门狗,并且指定了看门的通道,那这个看门狗就会关注它看门的通道,一旦超过这个阈值范围了,它就会乱叫,就会在上面,申请一个模拟看门狗的中断,最后通向NVIC,对于规则组和注入组而言,他们转换完成后,也会有一个EOC转换完成的信号,在这里EOC的规则组完成信号,JEOC是注入组完成的信号,这两个信号会在状态寄存器里置一个标志位,我们读取这个标志位,就能知道是不是转换结束了,同时这两个标志位也可以去到NVIC申请中断,如果开启了NVIC对于的通道,它们就会触发中断
右边是ADC的时钟,相当于上图的CLOOK是用于驱动内部逐次比较的时钟,这个时钟是来自ADC的预分频器,这个ADC的预分频器来源于RCC的,只能选择6分频,结果是12M和8分频,结果是9M这两个值
输入通道
转换模式
•单次转换,非扫描模式
这个表就是规则组的菜单,你可以在这里点菜,就是写入你要转换的通道,在非扫描模式下,这个菜单只有第一个序列1的位置有效,这时,菜单同时选中一组的方式就退化为简单地选中一个的方式了,这序列1里指定我们想转换的通道,比如通道2,然后我们触发转换,ADC就会对这个通道2进行模数转换,过一段时间,转换完成,转换结果放进数据寄存器里,同时给EOC标志位置1
如果想换一个通道转换,那在转换之前,把第一个位置通道2改为其他通道,然后在启动转换
•连续转换,非扫描模式
还是非扫描模式,所以菜单列表就只用第一个,然后与单次转换不同的是,它在一次转换结束后不会停止,而是立刻开始下一轮的转换,然后一直持续下去,这样就只需要最开始触发一次,之后就可以一直转换了,这个模式的好处是,开始转换之后不需要等待一段时间的
•单次转换,扫描模式
扫描模式就用到列表了,可以在菜单里点菜,比如第一个菜是通道2,第二个菜是通道5等等,这里每个位置是通道几可以任意指定,并且也是可以重复的
然后初始化结构体里还会有个参数,就是通道数目,因为这16个位置你可以不用完,只用前几个,那你就需要再给一个通道数目的参数,告诉它,我有几个通道,比如这里指定通道数目为7,那就只看前7个位置,然后每次触发之后,就依次对前七个位置进行AD转换,转换结果都放在寄存器里,这个防止数据被覆盖,就需要用DMA及时将数据挪走
•连续转换,扫描模式
一次转换完成后立刻开始下一次的转换
触发控制
数据对齐
ADC是12位的,它的转换结果就是一个12位的数据
但是这个数据寄存器是16位的,所以就存在一个数据对齐的问题
第一种是数据右对齐,12位数据向右靠,高位多出来的就补0
第二种是数据左对齐,12位数据向左靠,低位多出来的就补0
一般使用右对齐
转换时间
校准
硬件电路
第一个是电位器产生一个可调的电压,这里可以接ADC的输入通道,比如PA0口,当滑动端往上滑时,电压增大,往下滑时,电压减小
中间是传感器输出电压的电路,一般来说,像光敏电阻,热敏电阻,红外接收管,麦克风等都可以等效为一个可变电阻,电阻阻值没办法进行测量,所以这里就可以通过和一个固定电阻串联分压,来得到一个反应电阻值电压的电路,传感器阻值变小时,下拉作用变强,输出端口电压就下降。传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压就升高,这个固定电阻一般可以选择和传感器阻值相近的电阻
AD单通道
1.开启ADC和GPIO的时钟//ADC的CLOCK分频器也要配置一下
2.配置GPIO,把需要用的GPIO配置成模拟输入的模式
3.配置多路开关,把左边的通道接到右边的规则组列表里(点菜)
4.配置ADC转换器
ADC的库函数
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);控制某个中断能不能进入NVIC
void ADC_ResetCalibration(ADC_TypeDef* ADCx);复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);获取复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);获取开始校准状态
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);ADC软件开始转换控制,这个就是用于软件触发的函数,调用一下就能软件触发转换了(触发控制)
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);ADC获取软件开始转换状态(跟转换是否结束毫无关系)一般不用
这两个函数是用来配置间断模式的 void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);每隔几个通道简短一次
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);是不是启用间断模式
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);ADC规则组通道模式(在转换模式的地方,给序列的每个位置填写指定的通道)
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);ADC外部触发转换控制,就是是否允许外部触发转换
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);ADC获取转换值,就是获取AD转换的数据寄存器,读取转换结果就要用这个函数
uint32_t ADC_GetDualModeConversionValue(void);ADC获取双模式转换值,这个是ADC模式读取转换结果的函数
下面带有inject的都是对注入组的配置
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);是否启动模拟看门狗
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);配置高低阈值
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);配置看门的通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);ADC温度传感器内部电压控制,这个是用来开启内部的两个通道的
include "stm32f10x.h" // Device headervoid AD_Init(void)
{//1.开启ADC和GPIO的时钟//ADC的CLOCK分频器也要配置一下/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz//2.配置GPIO,把需要用的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); //3.配置多路开关,把左边的通道接到右边的规则组列表里(点菜)ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//4.配置ADC转换器ADC_InitTypeDef ADC_InitStructure; //定义结构体变量ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发//对应机构框图左下角ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换(ENABLE),失能,每转换一次规则组序列后停止ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式(ENABLE),失能,只转换规则组的序列1这一个位置ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1//在规则组菜单列表的第一个位置写入通道0这个通道/*ADC使能*/ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行//根据手册要求还需要校准/*ADC校准*/ADC_ResetCalibration(ADC1); //复位校准 //固定流程,内部有电路会自动执行校准while (ADC_GetResetCalibrationStatus(ADC1) == SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1) == SET);}
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转换的结果
}
#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多通道
#include "stm32f10x.h" // Device headervoid 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;//PA1接震动传感器GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0、PA1引脚初始化为模拟输入/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*//*ADC初始化*/ADC_InitTypeDef ADC_InitStructure; //定义结构体变量ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1ADC_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的数,在非扫描模式下,只能是1ADC_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); //在每次转换前,根据函数形参灵活更改规则组的通道1ADC_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 "Delay.h"
#include "OLED.h"
#include "AD.h"uint16_t AD0, AD1; //定义AD值变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化AD_Init(); //AD初始化/*显示静态字符串*/OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");while (1){AD0 = AD_GetValue(ADC_Channel_0); //单次启动ADC,转换通道0AD1 = AD_GetValue(ADC_Channel_1); //单次启动ADC,转换通道1OLED_ShowNum(1, 5, AD0, 4); //显示通道0的转换结果AD0OLED_ShowNum(2, 5, AD1, 4); //显示通道1的转换结果AD1Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间}
}