ADC
ADC(Analog-to-Digital Converter),模拟-数字转换器,也叫模数转换器,可以将连续变化的模拟信号转换为离散的数字信号。
我们可以外接上将采集信号转为模拟信号的模块,如光敏电阻传感器,热敏电阻传感器,反射式红外传感器等。
这些传感器模块通过采样,量化,编码,通过AO引脚将模拟信号传出,我们可以使用STM32来接收,并且通过ADC来获取对应的数字信号。
STM32中的ADC
特征
STM32F103的ADC具有12位的分辨率,也就是转换结果的范围是 0~2^12-1 。
ADC的供电要求是2.4V~3.6V。
ADC可以配合DMA转运,也可以配合看门狗进行监控。
输入时钟不得超过14MHz,由APB2(72MHz)分频得到ADC的输入时钟,由于分频系数的限制,不超过14MHz的能得到的最高的频率为12MHz(6分频)
最短的转换时间是1.17us。(1s/12MHz * (1.5(最少的采样周期) + 12.5 (固定量化编码的时间)) ))
通道
每个ADC都有18个通道,ADC1和ADC2的最后两个通道是内部通道,ADC3的最后两个通道是内部通道,其余为外部通道,我们可以连接到外设,另外从STM32F103C8T6的引脚定义表我们可以知道,在C8T6这个型号的芯片中,我们一共可以外接10个模拟信号传感器模块。也就是说尽管我们有18个通道,但最多只能使用12个(10个外部通道+2个内部通道),不过也是够用的。
其中ADC1的第17个通道连接的是内部的温度传感器,可以读取到CPU周围的温度。
通道分为外部通道和内部通道,外部通道又分为规则通道和注入通道,一般使用规则通道。注入通道最多4个,规则通道最多16个。
注入通道是一种不安分的通道,注入通告只有在规则通道存在时才会出现,并且在规则转换通道转换的时候会强行插入要转换,等注入通道转换玩之后才会回到规则通道,和中断很像。我们尽量不使用注入通道。
转换结果
ADC转换后的数据存放在ADC_DR寄存器(规则通道)或JDRx(注入通道)中。
ADC_DR寄存器是32位的寄存器,低16位在单ADC的时候使用(独立工作模式),高16位在双模式下保存ADC2的转换结果。之前说过,STM32中的ADC的精度为12位,但是这里却用16位来存储结果,因此我们可以选择将结果左对齐或是右对齐,左对齐的话就是将结果整体左移4位,也就是数值乘16,一般就右对齐。
由于通道可以有很多个,但是存放数据的寄存器就只有一个,所以一旦数据转换完之后我们就要将数据取走,以免被后续的转换结果覆盖,通常使用多通道转换的时候我们会配合上DMA来替我们自动转运多个结果。
转换的结果是12位的数字,而因为输入的电压范围为0~3.3V,所以我们可以将转换后的结果再乘上3.3再除(2^12-1)得到电压值,然后再根据模块的说明书来进行进一步转换成有效的数据值。
固件库函数
注:红色为固定的函数名,绿色为推荐的参数
设置ADCCLK的预分频器
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
设置ADCCLK的预分频器,之前说过,ADC最多只能14MHz,而APB2位72MHz,因此我们选择6分频得到12MHz。
初始化ADC
ADC_Init(ADC1,&itd1);
初始化ADC,第一个参数指定使用哪个ADC资源,我们一般就使用ADC1或ADC2。
第二个参数传入ADC_InitStruct类型变量的地址。
我们通过给ADC_InitStruct类型变量的成员赋值来配置ADC。
ADC_Mode | 选择工作模式,仅用单个ADC的话,那我们选择ADC_Mode_Independent |
ADC_ScanConvMode | 是否开始扫描模式,ENABLE或是DISABLE,不开启的话,仅转换 待转换队列的第一个通道,开启之后会自动按照顺序来转换每个配置过 的通道,一般扫描模式配合DMA转运一起,因为ADC转换速度极快,而 所有通道的转换结果都是放到同一个寄存器里的,因此手动转移结果太慢 的话就会被覆盖,太快的话就会拿到错误数据。 |
ADC_ContinuousConvMode | 是否开启连续模式,ENABLE或是DISABLE,不开启的话每次转换 完一次待转换队列的通道之后就会停下,等待下次转换指令。 开启的话就会不断地一次次自动转换所有配置过的通道。 |
ADC_ExternalTrigConv | 是否使用外部触发,不使用则软件触发,一般使用 软件触发ADC_ExternalTrigConv_None, 触发函数下面会介绍。 |
ADC_DataAlign | 数据对齐方式,之前说过,ADC转换的结果是12位的,而存放结果的 寄存器是16位的,因此有4位的空白位,我们可以选择左对齐或是右对齐, 一般选择右对齐ADC_DataAlign_Right。 |
ADC_NbrofChannel | 通道数目,我们配置了多少个通道就填多少。 |
配置规则组的输入通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);
配置规则组的输入通道,第一个参数指定使用哪个ADC资源。
第二个参数指定通道,这个我们根据引脚定义表来决定。
第三个参数根据固件库函数给的注释,直译是“常规组序列发生器中的秩。此参数必须介于1到16之间。”,我们可以当成要给ADC转换的通道需要排好队,从1排到16,按照顺序来转换。
第四个参数可以选择转换时间,实际上是选择用几个周期去采样时间,使用的周期越多采样时间越长,得到的数据也就更稳定,没有特殊要求的话可以随意选择一个选项。
这里以内部的温度传感器为例来计算一下这个选项应该如何选择
参考手册里说推荐采样时间为17.1us,而采样时间的计算公式为:
(1s/12MHz)*x。x为我们选择的采样花费的周期数,经过计算我们可以知道,x至少要为205.2,因此如果我们要采样内部的温度传感器的话,我们应该选择ADC_SampleTime_239Cycles5。
上电
ADC_Cmd(ADC1,ENABLE);
参数一选择ADC资源,参数二选择是否上电。
校准
ADC_ResetCalibration(ADC1); //复位校准
while(SET==ADC_GetResetCalibrationStatus(ADC1)); //等到复位校准完成
ADC_StartCalibration(ADC1); //开始校准
while(SET==ADC_GetCalibrationStatus(ADC1)); //等待校准完毕
取值
ADC_GetConversionValue( ADC1 )
软件触发转换
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
获取ADC标志位
ADC_GetFlagStatus( ADC1 , ADC_FLAG_EOC )
参数一指定ADC资源。
参数二指定要获取的标志位,我们要知道转换是否结束,获取的标志位为ADC_FLAG_EOC。具体的做法可以参考下面的代码。
使用内部温度传感器
ADC_TempSensorVrefintCmd(ENABLE);
如果要使用内部的温度传感器的话,使用上面的函数把功能打开,并且这个传感器只能为ADC1使用,通道为第17个通道,也就是 ADC_Channel_16。并且上面说了,推荐的采样时间为17.1us,需要根据这个来配置采样周期。
假设得到的值为x,那么转换为摄氏度的公式为: (1.43-(x*(3.3/4096)))/0.0043+25
接线
将传感器的AO口接到GPIOA的0号口,根据引脚定义表,对应的是通道0,这点在代码里体现,如果要改GPIO口的话,只需要根据引脚定义表来更改代码中指定的通道即可。
思路
首先我们需要打开外设时钟,使用ADC1就把对应的时钟打开。
另外,如果我们是外接模拟信号的话,就把对应的GPIO口配置为模拟输入模式,同时对应的外设时钟也要打开。
第二步是配置ADCCLK的分频器,因为不能超过14MHz,所以至少是6分频。
第三步是初始化ADC,参考上面的函数。
第四步配置输入通道(一般使用规则组的通道)。
第五步上电,上电完之后校准(非必须,但是加上不碍事)
第六步在需要的时候触发转换(如果是连续模式的话就不用手动触发了),然后获取值。
完整代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
//获取ADC转换的值
uint16_t getValue(void){
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //软件触发转换
while(RESET==ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)); //等到转换完毕
return ADC_GetConversionValue(ADC1); //获取到转换的数据
}
int main(void){
OLED_Init();
OLED_ShowString(1,1,"Hello World!");
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIO口外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //使能ADC1外设时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADCCLK分配器
GPIO_InitTypeDef itd;
itd.GPIO_Mode=GPIO_Mode_AIN; //选择GPIO口的模式
itd.GPIO_Pin=GPIO_Pin_0; //选择GPIO口
itd.GPIO_Speed=GPIO_Speed_50MHz; //默认选择50MHz
GPIO_Init(GPIOA,&itd);
//配置输入通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);
ADC_InitTypeDef itd1;
itd1.ADC_ContinuousConvMode=DISABLE; //不开启连续转换模式
itd1.ADC_DataAlign=ADC_DataAlign_Right; //数据右对齐
itd1.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //不使用外部触发(软件触发)
itd1.ADC_Mode=ADC_Mode_Independent; //独立工作模式(ADC1与ADC2不配合)
itd1.ADC_NbrOfChannel=1; //仅1个通道
itd1.ADC_ScanConvMode=DISABLE; //不开启扫描转换模式
ADC_Init(ADC1,&itd1);
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1); //复位校准
while(SET==ADC_GetResetCalibrationStatus(ADC1)); //等到复位校准完成
ADC_StartCalibration(ADC1); //开始校准
while(SET==ADC_GetCalibrationStatus(ADC1)); //等待校准完毕
while(1){
OLED_ShowNum(2,1,getValue(),5);
}
}
参考
STM32F10xxx参考手册(中文)
[7-1] ADC模数转换器_哔哩哔哩_bilibili
《ARM Cortex-M3 嵌入式原理及应用(基于STM32F103微控制器)》
《STM32库开发实战指南基于STM32F103(第二版)》