目录
一. 前言
ADC(Analog-Digital Converter) 模拟-数字转换器,简称模数转换器或者AD转换器。
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
既然有ADC,那么聪明的你,肯定也想到了DAC,那就是数模转换器。作用跟ADC相反。我们之前就有学过有关DAC的操作,比如利用WPM来控制LED的亮度(呼吸灯),电机的速度等等。这些都是DAC的功能。
我们常用的芯片STM32103C8T6,它的ADC资源就有ADC1和ADC2共有两个ADC外设,10个外部输入通道,也就是它最多能测量10个外部引脚的模拟信号。
二. 逐次逼近型ADC
我们下面来了解下逐次逼近型ADC是怎么样工作的,STM32的ADC原理跟这个如出一辙。
其中,左边的IN0~IN7 就是如图所示的ADC0809的8路输入通道,通过通道选择开关,选中一路,输入到这个点就行转换。因为ADC0809它是一个独立的8位逐次逼近型ADC芯片。
在通道选择开关的下面就是地址锁存和译码,就是你想选中哪个通道,就把通道号放在这三个脚上(ADDA,ADDB,ADDC),然后给一个锁存信号,上面这里对应的通路开关就可以自动拨好了。
然后往右边走,外部输入的未知编码电压与DAC的已知电压同时输入到电压比较器中进行大小对比。如果DAC相对较小,就增大它的值;反之,就减小;直到DAC与外部输入电压近似相等,这样,DAC输入的数据就是外部电压的编码数据了。这个电压调节的过程就是这个逐次逼近SAR来完成的。这里为了最快寻找到未知电压的编码,通常我们会选择使用二分法来进行寻找。
右上角的EOC就是End of Convert,转换结束信号。
Start是开始转换,给一个输入脉冲,就开始转换。
CLOCK是ADC时钟,因为ADC内部是一步一步进行判断的。所以我们需要时钟来推动这个过程。
下面的VREF+和VREF-是DAC的参考电压。这个DAC的参考电压也决定了ADC的输入范围,所以它也是ADC的参考电压。
最后左下角区域就是整个芯片电路的供电,VCC和GND。一般情况下,ADC输入电压的范围就和ADC的供电是一样的。
三. STM32ADC框图
在了解了上面的ADC后,我们再来看下STM32的这个ADC框图(一般在产品手册里,每个外设的最前面都有一个整体的结构图):
左边是ADC的输入通道,包括16个GPIO口,IN0~IN15。
在温度传感器的下面那个V就是V Reference Internal,内部参考电压。
16个GPIO口加上两个内部通道(温度传感器和内部参考电压)总共18个输入通道,然后到达模拟多路开关,可以指定我们想要选择的通道。再往右边就是多路开关的输出,进入到模拟转换器中。这里的模数转换器就是执行我们上面讲过的逐次逼近比较的过程,转换结果会放在上面的数据寄存器里,我们读取寄存器就能知道ADC转换的结果。
一般ADC只能选择一个通道,但这里比较高级,可以同时选中多个,而且在转换的时候,还分成了两个组,规则通道组和注入通道组。值得注意的是,如果使用规则组转换的话,最好配合DMA来实现,防止被覆盖。
注入通道就比较高级了,在这里可以一次最多4个通道信号。对于注入组来说就不用担心数据覆盖问题了。
下面我们来看下触发源部分:
这些触发源主要来源于定时器,有定时器的各个通道,还有TRGO的定时器主模式的输出。
当然这里还可以选择外部中断引脚来触发转换。这都可以在程序中配置,这就是触发转换的部分。
接着就是ADC预分频器(来源于RCC)部分,主要提供ADCCLK,而ADCCLK是ADC的时钟。
这里需要注意的是,ADC预分频器手册上的框图,是显示可以2分频,4分频,6分频,8分频的。但是后面其实它又设置了ADCCLK最大为14MHz,也就是说如果我们选择了2分频,那结果就是72M/2=36M,这样就会超出ADCCLK的最大值。所以通过计算,我们一般只能选择6/8分频。
所以大家可以好好看看这个手册结构图,对我们来说是非常重要的。
上面这个就是我们STM32的ADC框图最后一部分了。
接下来我们来讲下规则组的4种转换模式,分别是单次转换,非扫描模式;连续转换,非扫描模式;单次转换,扫描模式;连续转换,扫描模式。
其中,单次转换就代表一次只能转换一个通道,而连续转换就可以自动每次转换一个通道,而不需要我们手动转换。扫描模式和非扫描模式主要就是每次转换的通道数目不一样。
ADC的转换时间如下:
这个时间主要根据AD转换的步骤来计算,步骤主要有:采样,保持,量化,编码。
所以STM32ADC的总转换时间为:T=采样时间+12.5个ADC周期。至于为什么要加12.5个ADC周期时间其实很好理解,就是量化编码的时间(因为是12位的)。ADC周期就是从RCC分频过来的ADCCLK,最大是14MHz。
关于这些,我们都可以查阅产品手册中的第11章关于ADC部分的内容。
四. ADC的基本结构
根据上面内容,我们可以总结出一个关于STM32ADC的结构图,这个结构图可以帮助我们编写相关部分的代码。因为只有结构打通了,程序代码才会运行无误。
根据这个ADC的结构图,我们还需要注意的是,哪些GPIO口可以用来使用ADC。如下所示:
上面就是ADC通道和引脚复用的功能。
这些都可以根据引脚定义表来看出,例如我们可以看到引脚定义表中,PA0后面有一个ADC12_IN0,代表的就是ADC1和ADC2的IN0通道都在PA0引脚上。
五. 代码配置
配置ADC的流程如下所示:
1)开启RCC时钟,包括ADC和GPIO的时钟。另外这里的ADCCLK的分频器也需要配置一下。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz
/ 6 = 12MHz
2) 配置GPIO,把需要的用的GPIO配置成模拟输入的模式。这里选择PA0初始化模拟输入。
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); //规则组序列1的位置,配置为通道0
4)配置ADC转换器。有一大批参数用一个结构体配置就行了。如果需要看门够,那么会有几个函数用来配置阈值和检测通道的,这些在adc.h文件中查找就行了。然后开启中断输出控制和配置NVIC就可以使用看门狗了。
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);
5)开关控制。调用ADC_Cmd函数,开启ADC。当然开启ADC后,我们还需要对ADC进行一下校准,这样可以减小误差。
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
最后为了方便测试,我们可以单独写一个函数,用来获取数据寄存器上面的ADC数据。
如下所示:
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转换的结果
}
以上便是我们ADC数模转换器的所有内容了。感谢观看。