我们在利用传感器测量的时候,一般是利用电阻电容等受的外界测量的物理量,导致测量电路电压发生变化,产生模拟信号,而单片机只能读取数字信号,所以就需要我们把模拟信号转变为数字信号方法:
1.可以通过ADC直接读取;
2.经过比较放大电路转变为高低电平的数字信号(抽样,保持,量化,编码);
然后把数字信号传递给单片机寄存器,进行相应操作。
今天我主要介绍
ADC(Analog-Digital Converter)模拟-数字转换器;
作用:ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
STM32-ADC:12位逐次逼近型(工作模式)ADC,1us转换时间;
ADC关键参数:
1.分辨率:一般是用多少位表示,12位AD值表示量化结果范围就是0~2^12 -1(0~4095);
位数越多结果越精细;
2.转换时间:也就是转换频率,就是从AD转换到产生结果最少要1us,也就是1MHZ(STM32最快转换频率)
注意:当你需要采样一个特别高的频率的模拟信号需要考虑,转换频率够不够用;
输入电压范围:0~3.3v(注意一般要求,输入电压要在芯片供电范围内变化),转换结果为0~4095;与输入电压范围一一对应。可以根据比例换算。
输入通道:18个输入通道,可测量16个外部(f系列最多)和2个内部信号源(内部传感器和内部参考电压)
我使用的STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道(最多测量10个);
下面介绍一个经典的一个8位逐次逼近型的ADC:
具体的每一个地方的作用我标注在下图:
过程:
首先我们开启时钟CLOCK,START给予一个脉冲信号,让ADC开始转换。我们可能有多路的输入,这时候可以根据,地址所存译码器,选择对应通道,输入电压A,然后SAR给DAC数据,DAC根据SAR的数据输出对应的电压B,比较器比较B和A的大小关系。然后SAR采用二分法的方法输出数据给DAC,使得DAC输出的电压B,不断和A的缩小差距近似相等。当近似相等时候,SAR将数据传递给8位锁存器,并将EOC置0,标志着转换完成,我们就可以根据8位地址锁存器的数据,DAC参考电压,与255~0的对应关系进行转换就可以得到数据,这里电压我以3.3举例(具体以实际为准)。
理解了ADC0809的模型我们理解32的ADC模型就不难了:
看一下数据手册,找到32的ADC结构图:
上半部分:
32ADC触发的方式是软件触发和硬件触发;
介绍一下半部分图:
定时器触发就是利用定时器每个一段时间进入中断,触发一次转换,但是由于每次都要进入中断,会打断主程序的进。并且由于优先级的不同,不同中断之间会产生拥挤和打断导致某些事件得不到及时处理。我们可以选择事件触发,有中断信号以后选择触发一个事件,这样就不会频繁打断主程序。
接下来还有一点也就是上面例子当中介绍的ADC0809当中的CLOCK时钟信号,在32当中也就是ADDCLK用于驱动内部比较的时钟,从图上不难看出,来自于RCC。
我们打开时钟树看一看。
RCCAPB2最大为72MHZ,经过预分频处理以后,得到ADC所需要的ADCCLK,但是时钟树规定,CLOCK最大不能超过14MHZ,意思很明确,经过分频以后的频率不能超过14MHZ,我们对72MHZ可以进行2,4,6,8分频,但是不能超过14MHZ。所以只能选择6或8分频,72/6=12MHZ或者,72/8=9MHZ,其他的就会超过。
这里补充介绍一下这里的模拟看门狗,可以存测量阈值,上限和下限,一旦输入超过阈值,模拟看门狗就会狗叫,发送看门狗请求中断。
以上就是绝大部分ADC结构图的介绍。
对于配置看门狗就可以按照以下框图配置:
首先介绍一下输入资源GPIO:总共18个
规则组四种转换模式:主要分为(连续)单次转换和(非)扫描转换;
单次转换非扫描:每次执行一次adc转换执行完以后停下来,需要手动再启动,不扫描列表只执行第一个;
连续转换非扫描:连续执行adc转换,不扫描列表只执行第一个;
单次转换扫描模式:执行一次adc转换执行完以后停止,扫描执行列表ADC转换,需要借助DMA数据转运否则会被覆盖;
连续转换扫描模式:连续执行adc转换,扫描执行列表ADC转换,需要借助DMA数据转运否则会被覆盖;
触发源:
确定IO使用时,需要引脚重映射。
数据对齐:由于我们的ADC是12位的,所以要进行补位的操作。
一般选择右对齐,因为左对齐会放大ADC所测得数据。
接下来介绍一下采样时间的计算:
STM32 ADC的总转换时间为:TCONV = 采样时间 + 12.5个ADC周期
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期 TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs;具体参考实际。
校准:
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差 建议在每次上电后执行一次校准 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期。在HAL库中,ADC启动后添加四个函数。
总结一下步骤:1.开启RCC时钟,包括ADC,GPIO
包括ADC预分频ADCCLK也要配置(RCC库函数中);
2.GPIO配置,为模拟输入;
3.配置多路选择开关;
4.配置ADC结构体;
5.配置校准(复位校准和开始校准);
下面是我写的一些函数不使用DMA
单次非扫描:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO时钟;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开启ADC时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//开启ADC时钟CLOCK,这里选择6分频。72/6=12MHZ,在RCC头文件找
//GPIO配置
GPIO_InitTypeDef GPIOA_InitStructure;
GPIOA_InitStructure.GPIO_Mode=GPIO_Mode_AIN ;//模拟输入
GPIOA_InitStructure.GPIO_Pin= GPIO_Pin_0;
GPIOA_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIOA_InitStructure);
//选择规则组通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,0, ADC_SampleTime_55Cycles5);
//ADC,ADC通道0,ADC序列,ADC采样时间为:55.5个ADCCLOCK。
//初始化ADC
/*模式为:
独立ADC;
数据为右对齐;
触发源为软件触发;
单次非扫描模式
扫描一个通道
*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ;// 独立模式|ADC工作模式配置独立ADC工作模式和双ADC工作模式
ADC_InitStructure. ADC_DataAlign=ADC_DataAlign_Right ;//右对齐|ADC数据对齐方式左对齐和右对齐
ADC_InitStructure. ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不选择外部触发源,软件触发|触发源选择;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//单次模式|连续转换模式/单次模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式|扫描模式/不扫描模式
ADC_InitStructure.ADC_NbrOfChannel=1;//指定扫描模式下扫描几个通道序列
ADC_Init(ADC1,&ADC_InitStructure);
//开启ADC电源
ADC_Cmd(ADC1,ENABLE);
//ADC校准
ADC_ResetCalibration(ADC1); //复位校准
while(ADC_GetResetCalibrationStatus(ADC1)==SET)
; //复位校准完成
ADC_StartCalibration(ADC1);//ADC开始校准
while(ADC_GetCalibrationStatus(ADC1)==SET);//ADC开始校准完成
}
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发转换
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
//等待时间采样时间55.5,转换周期12.5固定=68
//12mhz/68=5.6us
return ADC_GetConversionValue(ADC1);//获取AD转换值;
}
连续非扫描:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO时钟;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开启ADC时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//开启ADC时钟CLOCK,这里选择6分频。72/6=12MHZ,在RCC头文件找
//GPIO配置
GPIO_InitTypeDef GPIOA_InitStructure;
GPIOA_InitStructure.GPIO_Mode=GPIO_Mode_AIN ;//模拟输入
GPIOA_InitStructure.GPIO_Pin= GPIO_Pin_0;
GPIOA_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIOA_InitStructure);
//选择规则组通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,0, ADC_SampleTime_55Cycles5);
//ADC,ADC通道0,ADC序列,ADC采样时间为:55.5个ADCCLOCK。
//初始化ADC
/*模式为:
独立ADC;
数据为右对齐;
触发源为软件触发;
连续非扫描模式
扫描一个通道
*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ;// 独立模式|ADC工作模式配置独立ADC工作模式和双ADC工作模式
ADC_InitStructure. ADC_DataAlign=ADC_DataAlign_Right ;//右对齐|ADC数据对齐方式左对齐和右对齐
ADC_InitStructure. ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不选择外部触发源,软件触发|触发源选择;
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//连续模式|连续转换模式/单次模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式|扫描模式/非扫描模式
ADC_InitStructure.ADC_NbrOfChannel=1;//指定扫描模式下扫描几个通道序列
ADC_Init(ADC1,&ADC_InitStructure);
//开启ADC电源
ADC_Cmd(ADC1,ENABLE);
//ADC校准
ADC_ResetCalibration(ADC1); //复位校准
while(ADC_GetResetCalibrationStatus(ADC1)==SET)
; //复位校准完成
ADC_StartCalibration(ADC1);//ADC开始校准
while(ADC_GetCalibrationStatus(ADC1)==SET);//ADC开始校准完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发转换,连续只需要触发一次就会一直转换
}
uint16_t AD_GetValue(void)
{
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
//等待时间采样时间55.5,转换周期12.5固定=68
//12mhz/68=5.6us
return ADC_GetConversionValue(ADC1);//获取AD转换值;
}
以及通过改通道实现多序列测量:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO时钟;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//开启ADC时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//开启ADC时钟CLOCK,这里选择6分频。72/6=12MHZ,在RCC头文件找
//GPIO配置
GPIO_InitTypeDef GPIOA_InitStructure;
GPIOA_InitStructure.GPIO_Mode=GPIO_Mode_AIN ;//模拟输入
GPIOA_InitStructure.GPIO_Pin= GPIO_Pin_0| GPIO_Pin_1| GPIO_Pin_2| GPIO_Pin_3;
GPIOA_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIOA_InitStructure);
//初始化ADC
/*模式为:
独立ADC;
数据为右对齐;
触发源为软件触发;
单次非扫描模式
扫描一个通道
*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent ;// 独立模式|ADC工作模式配置独立ADC工作模式和双ADC工作模式
ADC_InitStructure. ADC_DataAlign=ADC_DataAlign_Right ;//右对齐|ADC数据对齐方式左对齐和右对齐
ADC_InitStructure. ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不选择外部触发源,软件触发|触发源选择;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//单次模式|连续转换模式/单次模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式|扫描模式/非扫描模式
ADC_InitStructure.ADC_NbrOfChannel=1;//指定扫描模式下扫描几个通道序列
ADC_Init(ADC1,&ADC_InitStructure);
//开启ADC电源
ADC_Cmd(ADC1,ENABLE);
//ADC校准
ADC_ResetCalibration(ADC1); //复位校准
while(ADC_GetResetCalibrationStatus(ADC1)==SET)
; //复位校准完成
ADC_StartCalibration(ADC1);//ADC开始校准
while(ADC_GetCalibrationStatus(ADC1)==SET);//ADC开始校准完成
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
//选择规则组通道
ADC_RegularChannelConfig(ADC1,ADC_Channel,1, ADC_SampleTime_55Cycles5);
//ADC,ADC通道0,ADC序列,ADC采样时间为:55.5个ADCCLOCK。
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发转换,连续只需要触发一次就会一直转换
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
//等待时间采样时间55.5,转换周期12.5固定=68
//12mhz/68=5.6us
return ADC_GetConversionValue(ADC1);//获取AD转换值;
}
此为ADC封装好可以直接使用,下面是测量四个的实际情况:
以上就是对于单个ADC,不使用DMA的一些介绍,接下来会结合DMA数据转运继续介绍,有错误地方欢迎指正。