ADC (Analog to Digital Converter):模数转换器,它负责将模拟信号转化为数字信号。
1.概述
对于我们的STM32F103来说,它拥有3个ADC器件,其中每个ADC都是12位的精度。每个ADC器件都拥有18个通道,分别为16个外部通道和2个内部通道,这里说的通道就是ADC采集的信号源头。需要注意的是,尽管我们每个ADC都拥有16个外部通道,但是并不是我们STM32F103的ADC就能够采集48个外部通道的信息,因为这三个ADC共用了21个外部通道,这意味着什么呢?意味着同一信号,我们可以使用这三个ADC并行采集它,当然了这是后话。ADC采集得到的结果存储在ADC_DR这个寄存器里面,注意这个寄存器如果使用DMA搬运数据的话,那么这个寄存器的地址就是源头。关于采集数据需要注意的几点:1.位数:因为这里的ADC是32位的,所以说采集结果是12位的数据,但是我们规定将这12位的数据存储到寄存器的16位之中;2.存储方式:左对齐还是右对齐的问题,由于我们的机器是32位的,所以寄存器单元也就是32位,那么这16位数据是存储在高16位呢还是低16位呢?答案就是由开发人员配置。可高可低。
ADC的时钟是经过PCLK2分频产生的,这说明了什么,说明ADC是APB2的外设。需要注意的是,对于我们的STM32F103来说,规定最高时钟的频率不得超过14mhz。说到时钟问题,我们知道对于我们的STM32F103来说,它的时钟频率默认是72mhz的,而对于我们的高速外设APB2桥来说,它的时钟信号是PCLK2,而PCLK2默认就是SYSCLK系统时钟也就是72mhz。但是前面也说了我们的adc器件所支持的最高时钟频率也就是14mhz,因此需要分频了。而关于分频那也是后话,详见后文。
分辨率:12位的分辨率,不能直接测量负电压。最小量化单元LSB=VREF+ / 4096
转换时间:由于ADC的时钟频率是可配置的,所以说采样时间也是可以修改的。规定采样一次至少需要14个时钟周期,而ADC的时钟频率最高又为14mhz。所以说采样时间最小为:14 x 1 / 14 us = 1us。
2.工作流程分析
ADCX_IN0~ADCX_IN15是ADC器件的外部输入通道,即连接到了某些GPIO引脚。ADC器件要受到触发信号才会开始转换,比如说EXTI外部中断触发,定时器触发,或者是软件触发。当ADC器件受到触发信号之后,就会在ADCCLK时钟的驱动下对输入通道的信号进行采样,并且进行模数转换。
模数转换之后得到的结果存储在ADC_DR外设寄存器之中,我们可以通过CPU指令或者DMA将这个数据读取到内存里面。对于DMA来说,也就是模数转换之后,可以触发DMA请求。
3.ADC_InitTypeDef结构体介绍
typedef struct{
uint32_t ADC_Mode;
uint32_t ADC_DataAlign;
uint8_t ADC_NbrOfChannel;
uint32_t ADC_ExternalTrigConv;
FunctionalState ADC_ScanConvMode;
FunctionalState ADC_ContinuousConvMode;
} ADC_InitTypeDef
下面介绍一下各个成员的作用:
1.ADC_Mode:设置ADC的工作模式,我们知道STM32有三个ADC器件,这三个器件又共用21个通道,因此难免出现这种情况:多个ADC采集同一个输入通道的情况,那么问题来了该如何协调他们之间的工作方式呢?答案就是使用ADC_Mode设置模式。
2.ADC_DataAlign:设置数据对齐方式。我们知道ADC精度是12位,规定作16位存储,所以对于我们的32位机器来说,这采集得到的数据被存储在ADC_DR寄存器的高16位或者低16位(取决于是规则组还是注入组)。问题是我们的数据只有12位,所以这就带来了一个问题,这12位数据是左对齐还是右对齐。在STM32中,我们可以选择是左对齐还是右对齐。所谓右对齐(不论是规则组还是注入组),DATA0对应存储器的最低地址在这里就是DR0或者DR16,剩余的位注入组填SEXT,规则组填0。而对应左对齐那就稍许复杂,规则组好说就是DATA11对应就是DR15,剩余位填0;注入组是DATA11对应DR30,DR31填的是SEXT,剩余位填0。
3.ADC_NbrOfChannel:设置要采集多少个通道,我们知道我们STM32的ADC可以采集16个外部通道以及2个内部通道。这里成员的最大值并不是18个,而是16个。
4.ADC_ExternalTrigConv:设置是否使用外部中断触发信号。我们知道ADC当受到触发信号的时候才会开始工作,其中触发信号可以是外部中断或者是软件控制触发。
5.ADC_ScanConvMode:当我们希望使用某个ADC器件对多个通道进行数据采集的时候,我们就需要把这个ADC器件配置为按一定的顺序来对这多个通道进行扫描转换,即轮流采集各个通道的值。也就是说,如果是采集多个通道的话,那么必须开启这个模式,后面接着利用其它函数进行采集顺序的配置。如果只是采集一个通道的话,那么就不需要开启这个模式了,值设置为DISABLE即可。
6.ADC_ContinuousConvMode:是否开启连续转换模式。连续转换模式意味着:当上一次 ADC转换完成后,立即开启下一次转换。
参阅STM32库件库使用手册,我们可以明白各个成员可取的值以及各个值所代表的意义:
1.ADC_Mode
2.ADC_DataAlign
值 | 意义 |
---|---|
ADC_DataAlign_Right | 数据右对齐 |
ADC_DataAlign_Left | 数据左对齐 |
3.ADC_NbrOfChannel:[1,16]
4.ADC_ExternalTrigConv
5.ADC_ScanConvMode:规定了ADC是工作在扫描模式(多通道)还是单通道模式,即分别对应ENABLE和DISABLE。
6.ADC_ContinuousConvMode:规定了是循环采集还是只采集一次。分别对应ENABE和DISABLE。
4.库函数使用方式
是这样的,并不是说将ADC_InitTypeDef对象填充完毕了接着调用ADC_Init(ADCX,&ADC_InitStructure)然后再调用ADC_Cmd(ADCX,ENABLE),要希望ADC以正确的方式工作的话那么还得做额外的一些工作。分别是下面这些工作:
- 1.设置好ADC的时钟信号频率,时钟信号频率越高则采样时间越小。通过前面的学习我们知道ADC的一次工作流程至少要14个周期,ADC的时钟信号的最高频率是14mhz。所以说ADC采样时间最少也就是1s了。这里提到了ADC的时钟信号频率的问题,那么问题来了如何配置呢?
答案就是利用RCC_ADCCLKConfig()。这个函数所接受的参数就是分频因子,从上图我们可以看出ADC的时钟来源是APB2外设时钟,所接受的分频因子有2,4,6,8。对应的参数值分别是RCC_PCLK2_Div2,......,RCC_PCLK2_Div8。很显然,72除上面的这些分频因子是得不到最高频率14mhz的。那么问题来了要如何得到呢?答案就是将APB2的时钟配置为56mhz,接着ADC再4分频。那么问题来了如何将APB2的时钟配置为56mhz呢?很简单,修改system_stm32f10x.c文件的如下宏定义即可:
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
#define SYSCLK_FREQ_56MHz 56000000
//#define SYSCLK_FREQ_72MHz 72000000
#endif
- 2.配置好了ADC的时钟信号频率后,我们可以接着着手第二步工作:配置工作通道,我们知道ADC器件有16个外部通道,2个内部通道。那么究竟该让ADC工作与哪个通道呢?所以我们需要使用ADC_RegularChannelConfig()函数来完成具体通道的配置工作。下面我们来看看ADC_RegularChannelConfig()函数的签名:
void ADC_RegularChannelConfig(ADC_TypeDef *ADCX,
uint8_t ADC_Channel_X,
uint8_t Rank,
uint8_t ADC_SampleTime
)
下面解释一下函数的具体参数意义:
ADC_ChannelX,其中X属于[0,17]。因为ADC器件有16个外部通道,2个内部通道嘛,至于到底是哪个通道得看端口复用情况。
Rank属于[1,16],我们再前面提到了一个ADC_InitTypeDef结构体成员:ADC_ScanConvMode,这个成员可以开启扫描模式即多通道模式,那么问题来了如何协调这多个通道之间的采集顺序呢?答案就是这里的Rank了,前面我们也提到过只有16个通道可能并行被采集。所以这里的Rank的值就是属于[1,16]。Rank即排名,所以值越小那么越先被ADC采集。
ADC_SampleTime:配置本通道的采样周期。通过前面我们了解到了通道的采样周期最小为14个周期,那么是不是这里的最小值也为14呢?不是这样的。看采样周期数的计算公式:
采样周期=ADC_SampleTime + 12.5
所以说ADC_SampleTime的最小值是1.5,下面把这个参数所有可选值给列举出来:1.5,7.5,13.5,28.5,41.5,55.5,71.5,239.5。用如下形式的值表示:
ADC_SampleTime_xCycles5,其中x就是上面列举出来的整数部分。
- 3.是不是经过RCC_ADCCLKConfig(RCC_PCLK2_Divx);和ADC_RegularChannelConfig(ADCX,ADC_ChannelX,Rank,ADC_SampleTime_xCycles5);这两个函数就ok了呢?答案是不是这样的。。。还需要开启DMA搬运许可ADC_DMACmd(ADCX, ENABLE);接着在使能ADC器件ADC_Cmd(ADCX, ENABLE);接着我们可以加入可选的ADC自检验代码,以使得转换结果更为的精确,这部分代码如下:
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
到了这一步ADC的配置工作就基本完成了,我们只需要产生触发信号让ADC工作了,比如说产生外部中断或者软件中断,如果是利用软件中断来驱使ADC器件工作的话,那么就使用ADC_SoftwareStartConvCmd(ADCX, ENABLE)函数。
到了这个时候就真正OK了。
所以整个代码流程大致就是这样的:
void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//开启ADC时钟以及复用功能是ADC通道的那个GPIO的时钟,ADC都是APB2外设
RCC_APB2PeriphClockCmd(APB2Periph_ADCX | APB2Periph_GPIOX, ENABLE);
//配置GPIO作为ADC通道,由于是默认的复用功能所以无需开启AFIO时钟
//配置GPIO为输入模式,注意一定得是GPIO_Mode_AIN模拟输入
//配置好了GPIO_Init即可。
//配置ADC,第一步是填充结构体
ADC_InitStructure.ADC_Mode = ;
ADC_InitStructure.ADC_DataAlign = ;
ADC_InitStructure.ADC_NbrOfChannel = ;
ADC_InitStructure.ADC_ExternalTrigConv = ;
ADC_InitStructure.ADC_ScanConvMode = ;
ADC_InitStructure.ADC_ContinuousConvMode = ;
ADC_Init(ADCX, &ADC_InitStructure);
RCC_ADCCLKConfig(RCC_PCLK2_DivX);
ADC_RegularChannelConfig(ADCX,ADC_Channel_X,Rank,ADC_SampleTime_xCyces5);
ADC_DMACmd(ADCX, ENABLE);
ADC_Cmd(ADCX, EANBLE);
//ADC自校验
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
//产生触发信号让ADC开始工作
ADC_SoftwareStartConvCmd(ADCX, ENABLE);
}