STM32 ADC的学习

  1. ADC介绍
    ADC指的是模拟数字转换器,把模拟信号转换成数字信号。在STM32 MCU上,它有多达19个通道。可测量16个外部和3个内部信号源(从外部GPIO口连接的16通道模拟输入(这是使用了GPIO的模拟功能,如果选择普通GPIO作为ADC信号源的输入,则需要选择GPIO的MODER配置为模拟输入的模式),1通道内部温度传感(VSENSE)输入,1通道的内部参考电压(VREFINT)输入,1通道的外部电池VBAT供电引脚输入)。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐的方式存储在16位数据寄存器中。一般来说对ADC的配置不是很难。只要按照参考手册进行配置就可以了。但是我们采集的数据如何输出为我们想要量化的值就比较难以理解了。在下面的标题3就会详细讲解这一部分。
  2. ADC 时钟
    具有双时钟域构架,ADC时钟(ADC_ CLK)独立于APB时钟(PCLK)。ADC_CLK可由两种可能的时钟源产生。可由RCC寄存器的选择来产生。
    选项一:专用的14MHZ内部振荡器
    选项二:PCLK时钟/2或/4/(最大不能超过14MHZ的ADC_CLK)
    选项1的优势是ADC一直具有最佳的ADC时钟频率(14MHZ)无论HCLK/PCLK时钟方案是如何选择的。
  3. 重点来了!就是比如我接入一个电池,要测出这个电池电压的大小,怎么操作?
    请见下图,并详细的了解一下连接的电池与寄存器中如何存储我们的电压数据:
    ADC
    首先必须弄明白我们的目的是通过GPIO口接入一个电池,采集电池电压的模拟信号量的大小。其中VCC代表我们连接的电池(电压的大小)。如果我们连接的电池电压比GPIO口的参考电压大,就不能直接连接 GPIO 口,否则会烧坏MCU. 这要怎么办呢?可以直接连接两个电阻进行分压,如上图所示GPIO口连接的是R8和R6的中间,使接入GPIO口的电压小于 GPIO 口的参考电压最大值3.3v. 如上图,R8接的是10k, R6接的是39k。V(Voltage)的最大电压是参考电压V(reference)3.3v. 则VCC的电压最大值是:VCC/(R6+R8)=V(Voltage)/R8. 则VCC=V(Voltage)* (R6+R8)/R8=3.3* (39+10)/10=16.17v. 则VCC可以连接的电压范围是0~16.17v.
    我们接入VCC电池,首先要算出VCC的值。VCC经过两个电阻分压,使的进入GPIO口的电压数据范围在0~3.3v之内。假设进入GPIO口的电压是V(Voltage),这里有一点要提前弄清楚,必须在开始时设置GPIO的MODER寄存器的模式为模拟输入模式,是模拟输入哦。模拟输入的采样数据是存放在GPIO的ADC->DR寄存器里面,我们要的V(Voltage)就是存放在ADC->DR寄存器里。
    看到上面的一段,你一定以为电压值可以直接从ADC->DR中读取出来就可以了,这就大错特错了。存放在ADC->DR的值并不是V(Voltage),如果V(Voltage)的值是1.5v。难道ADC->DR还能存小数位不成。当然是不行的。那怎么办?这就要仔细看数据手册了,看下面的一段:
    —1*12bit.1.0us ADC(up to 10 channels)
    —Conversion range:0~3.6v
    —Separate analog supply from 2.4 up to 3.6v.
    上面最重要的是1* 12bit ,是指的分辨率为 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 =4096. 这句话就是指的是在ADC->DR的数据寄存器中存储的是0~4096的范围。这12bit的数据是把参考电压V(DDA)=V(reference)=3.3v分成4096份进行存储, 从ADC->DR寄存器中读出的数据就是Get_ ADCData ,如果要求PA12的电压V(Voltage):
    V(Voltage) / Get_ADCData(从ADC->DR寄存器读到的数据)=3300mv / 4096.
    则由上面可以得到两个计算公式:
    V(Voltage) / Get_ADCData (从ADC->DR寄存器读到的数据)=V(reference)(从数据手册中知道的GPIO口参考电压) / 4096(1*12bit分辨率).
    VCC/(R6+R8)=V(Voltage)/R8.
    则可得到我们的要测量的电池电压为VCC=V(Voltage)(R6+R8)/R8=(V(reference) Get_ADCData) * (R6+R8)/(R8 * 4096). 其实上面就是一个数学问题,数学好的话,可能会理解的更快一些。
  4. 下面一个实例来讲解一下ADC的配置过程。
    实例:就是如上图所示,电池连接VCC脚,要求求出VCC的值。
    分析:如上图所示VCC连接两个电阻。其中从中间接入PA1口。我们主要是要测量VCC的值,而根据上面的第3点分析可知。我们只要读出在PA1口作为模拟输入时ADC->DR里面的数据,就可以根据公式的出VCC的值。
    请见下面的引脚图,通用GPIOA的PA1脚可以作为ADC进行模拟输入配置。
    ADC -GPIOA -PA1
    4.1 由上图可知,我们首先要使能ADC(ADC寄存器在APB2里)和GPIOA口为模拟输入。
    GPIOA->MODER=(GPIOA->MODER&~(3<<(1* 2)))|(3<<(1*2));
    RCC->APB2ENR|=RCC_APB2ENR_ADC1EN;
    4.2 要复位ADC的寄存器所有值为默认值。复位完后就得把复位值置位,不能一直让ADC处于复位状态。
    RCC->APB2RSTR|=RCC_ APB2RSTR_ADC1RST;
    RCC->APB2RSTR&=~RCC_ APB2RSTR_ADC1RST;
    要知道,当我们把ADC设置复位后,则ADC所有的寄存器都复位为它们的复位值。参考数据手册,如ADC->CR的寄存器,复位值为0x0000 0000。如下面红色部分标识:
    Reset
    由上图可见,ADSTP,ADSTART,ADDIS,ADEN都置位为0。
    4.3 下面4.3这一段在如果执行了4.2后是可以不用写的,因为4.2是已经复位了ADC,ADC->CR的所有值都复位为0, 则所有位都已经ADCAL=0,ADSTART=0,ADSTP=0,ADDIS=0,ADEN=0,可以直接进行4.4的步骤,当程序没有复位之前,或者已经设置了某些位,因此不能直接复位,如果复位了就会把ADC之前设置的位的值清除掉。使得又要重新进行设置。4.3主要是用于我们要关断ADC时所用的程序。首先要了解寄存器ADC->CR里面的几个位,因为它们的描述让人看着有些互相矛盾且让人摸不着头脑。
    4.3.1, ADCAL: ADC校准。
    该位由软件设置来启动ADC校准。当校准完成后,由硬件清零。
    0:校准完成
    1:写1时校准ADC,读为1时意味着校准进行中。
    注:只有在ADC禁止下(ADCAL=0,ADSTART=0,ADSTP=0,ADDIS=0, ADEN=0)才允许软件设置ADCL.
    4.3.2,ADSTP: ADC停止转换命令
    该位由软件设置来停止和对其正在进行中的转换。
    当停止转换结束时,该位由硬件清零且ADC已准备好接受新的转换命令。
    0:不发ADC停止转换命令
    1:写1用来停止ADC,读为1时表明ADSTP命令正在执行中。
    注:只有当ADSTART=1和ADDIS=0(ADC开启且可能正在转换,但无禁止ADC挂起的请求)软件才能对该位进行设置。
    4.3.3,ADSTART:ADC开始转换命令
    该位由软件设置来启动ADC转换。一次转换可由立即启动(由软件配置)或硬件触发产生(硬件触发配置)两种方式来启动,启动方式有EXTEN[1:0]位的配置来决定。
    其位由硬件清零:
    —在单词转换模式中,当选择为软件触发(EXTSEL=0x0)时:序列转换结束(EOS置位)后该位清零。
    —当执行完ADSTP命令后,同时ADSTP位由硬件清零。
    0:无进行中的ADC转换
    1:写1开始ADC转换。读为1是表明ADC正在进行转换
    注:只有当ADEN=1和ADDIS=0(ADC开启且可能正在转换,但无禁止ADC挂起的请求)时才允许软件设置ADSTART位。
    4.3.4,ADDIS:ADC禁止命令
    该位由软件设置来禁止ADC(ADDIS命令)并让ADC处于掉电状态(关断状态)。一旦ADC有效关闭(ADEN同时被硬件清零)后由硬件清楚该位。
    0:无ADDIS命令进行中
    1:写1为关闭ADC.读为1时表明ADDIS命令正在执行中。
    注:只有当ADEN=1和ADSTART=0(确信无进行中的转换)时才允许软件设置ADDIS位。
    4.3.5,ADEN:ADC禁用(关断状态)
    由软件设置该位来使能ADC。一旦ADRDY标志置为1时表明ADC可供使用了。执行ADDIS命令后,ADC关断且该位被硬件清零。
    0:ADC禁用(关断状态)
    1:写1来使能ADC.
    注:只有ADC_CR寄存器所有位为0(ADCAL=0,ADSTP=0,ADSTART=0,ADDIS=0 and ADEN=0)的情况下,软件才能设置ADEN位。
    看到上面的好几个位要能是指时需要其他的位处于不同的状态。如下图所示:
    D
    上面就是总结来的逻辑,但是根据上面这个逻辑来进行配置是有疑惑的,这是因为我们忘了很重要的一点就是,在上面各个位的描述过程中,没有仔细辨别当读一个位和写一个位的区别。
    比如在进行复位之前。寄存器的ADEN在读的时候不一定是ADEN=0. 所以的插入if语句。开始按照上面这个图来进行描写会出现悖论,假如ADEN读的值为1,ADSTART的位子读的值也是1,同理ADDIS读的位置的值也为1.就会出现问题。因为如果我们要使所有的位都为0.则需要把ADEN,ADSTART,ADDIS都置位为0. 但是这样是做不到的。原因是因为如果我要使ADSTART为0的话,则需要在ADEN=1,ADDIS=0的情况下进行设置。因而需要把ADDIS由软件写为0. 但是ADDIS要更改的条件又是需要ADEN=1同时ADSTART=1, 则这三者一直是个死循环,不知道该怎么操作。
    你可能看了上面的问题觉得摸不着头脑,可是我却为这个事情烦恼了好一阵。后来终于想通了,会出现这个问题是因为我只是在注重它们之间的逻辑关系,却忽略了两个特别重要的点。
    第一个:首先要考虑一下我上面提的问题是否会出现?仔细一下, ADSTART(ADC开始转换命令),ADDIS(ADC禁止命令)和ADEN(ADC禁用(关断状态)置1时表示ADC使能)。如果在寄存器中读到数据ADEN=1.则表示ADC使能,如果还读到ADSTART=1,表示ADC正在进行转换。而这个时候要注意了ADDIS如果读到1的话表明ADDIS命令(ADC禁止状态,ADC会掉电关断)正在执行中。一看这三个状态怎么可能会同时存在呢。ADSTART和ADEN表示转换的进行与ADDIS (ADC关断命令)不可能同时存在。
    第二个:要特别注意这些位的描述。有些位是可读可写的。写1和读1代表的意思是不一样的。这个就体现在下面的程序中。因为我们首先要确认一下,在我们要使能和进行校准之前,一定要确认ADCAL=0,ADSTART=0,ADSTP=0,ADDIS=0,ADEN=0。如果ADC的转换正在进行则需要进行ADC的关断掉电处理。所以开始之前要对寄存器的位进行读取,然后把ADC关断。
    首先读取ADC->CR的ADEN位,看是否为1,如果为1则表示ADC还没有关断,则查看ADC是否在进行ADC的转换,则查看读取一下ADSTART位是否为1;如果为1,代表程序正在进行转换,则需要ADSTP为写入ADC停止转换命令;之后要进行ADC关断处理。 直到ADEN=0.这段的程序如下:
    // Disable the ADC if previously enabled
    if (ADC1->CR&ADC_CR_ADEN)
    {
        // Ongoing conversion?
        if (ADC1->CR&ADC_CR_ADSTART)
        {
            ADC1->CR|=ADC_CR_ADSTP;
            while (ADC1->CR&ADC_CR_ADSTP);
        }
        // Disable the ADC
        ADC1->CR|=ADC_CR_ADDIS;
        while (ADC1->CR&ADC_CR_ADEN);
    }

4.4 上面的程序确保所有的位均为0, 下面开始进行校准因子ADCAL 和ADEN 的ADC使能设置。
// Calibrate the ADC
ADC1->CR|=ADC_ CR_ADCAL;
while (ADC1->CR&ADC_ CR_ADCAL);
ADC1->CR|=ADC_ CR_ADEN;
// Enable the ADC
do
{
ADC1->CR|=ADC_ CR_ADEN;
if (!(ADC1->CR&ADC_ CR_ADEN))
ADC1->CR;
} while (!(ADC1->CR&ADC_ CR_ADEN));
while (!(ADC1->ISR&ADC_ ISR_ADRDY));
4.5 下面开始配置ADC的基本参数:
// Set PCLK/4 as ADC clock source (12MHz)
ADC1->CFGR2=ADC_ CFGR2_CKMODE_1;
// Set the ADC channel 1 as source
ADC1->CHSELR|=(1<<1);
// Set the sample time to 28.5 cycles
ADC1->SMPR|=ADC_ SMPR1_SMPR_0 | ADC_SMPR1_SMPR_1;
ADC1->CFGR1=0;
4.6 下面应该开始从寄存器ADC->DR读取模拟信号数据了,其中读取的数值是会有变化的,所以最好是多读取几组数据。
4.7 之后把4.6读取的数据平均值套用到标题3中的公式。可以算出VCC的大小,就是我们要得到的数据。

猜你喜欢

转载自blog.csdn.net/Flylily9997/article/details/70837766