STM32 单片机ADC 使用内部电压基准
无校准测量
ADC 默认使用VDDA 作为电压基准,一般就是指外加的+3.3V 电源电压。ADC 默认分辨率12bit,也就是4095 格分辨率,测量值每一格对应的电压是:
K=3.3V4095 K = \frac{3.3V}{4095} K=40953.3V
要知道测量值对应的电压是多少,就用实测值乘以上面这个系数。如果是用+3.3V LDO 供电,一般精度至少能有2%,精度高一点的LDO 能到1%,比如ME6211,此时基本不需要额外处理,直接这么用就行了。有时+3.3V 电源电压精度比较低,比如用DC-DC 电源,精度可能会到5%,这表示最坏情况下,ADC 测量3V 电池电压,误差能有0.15V,要是外部加了分压电路来测量较高的电池电压,那么误差就更大了。
内部电压基准
STM32 和很多别的单片机都内置了电压基准,用来在不变更硬件电路配置的条件下尽量提高测量精度。根据F410 的数据手册的说法,内置基准电压为+1.2V,精度1%。
此时ADC 系数可以按如下计算:
K=1.2VREF K = \frac{1.2V}{REF} K=REF1.2V
其中REFREFREF 是用ADC 采样VREFINTV_{REFINT}VREFINT 通道得到的实测值,这样的精度一般就够用了。
内部基准校准值
理论上精度还可以更高一点,就是使用芯片出厂时内部参考的校准值来替代手写的1.2V,这个校准值就是1.2V 基准的实测电压。根据F410 的手册,校准值是在外加VDDA = 3.3V 时测量的结果,就是芯片厂里用外置高精度3.3V 电源作为基准,测量内置基准电压。
校准值是用12bit 模式测量的ADC 输出值。用HAL库的话,adc 头文件里提供了两个宏定义:VREFINT_CAL_ADDR
和VREFINT_CAL_VREF
,前者是指向校准值的指针,后者是工厂里用的外部参考电压,也就是3300mV。
1.2V 基准的校准电压可以用下式计算:
VREFINT=3.3V4095∗CAL V_{REFINT} = \frac{3.3V}{4095} * CAL VREFINT=40953.3V∗CAL
其中,VREFINTV_{REFINT}VREFINT 是实测电压,CALCALCAL 是校准值。然后就用这个校准电压代替手写的1.2V 来计算ADC 系数:
K=VREFINTREF K = \frac{V_{REFINT}}{REF} K=REFVREFINT
实际上用了这个校准值以后精度可能没什么提升,相比直接手写1.2V 的算法,差异可能也就几十毫伏。
整数电压单位
实际写代码的时候有一个要注意的地方。如果不用浮点数计算电压,用整数计算的话,要注意整数除法损失精度的问题,最好是用μV 作为单位。
比方说,如果用mV 单位,要计算基准电压的话,上面的算式就写成:
auto cal = *VREFINT_CAL_ADDR; // 读取校准值
auto v_refint = 3300 / 4095 * cal;
这样结果算出来就是0。改为μV 单位,计算ADC 系数:
auto cal = *VREFINT_CAL_ADDR; // 读取校准值
auto v_refint = 3300'000 / 4095 * cal;
auto k = v_refint / ref;
然后要把ADC 输出结果计算为电压值,注意单位转换:
auto micro_volt = adc * k;
auto milli_volt = micro_volt / 1000;
有另一种算法,用mV 当单位,但是先不计算K 的值:
auto kk = 3300 * cal / ref;
然后转换ADC 输出结果:
auto milli_volt = adc * kk / 4095;
这就是官方手册里的算法:
V=3.3V⋅CAL⋅ADCREF⋅FullScale V = \frac{3.3V \cdot CAL \cdot ADC}{REF \cdot FullScale} V=REF⋅FullScale3.3V⋅CAL⋅ADC
还是要注意整数除法运算的次序问题,比如,一定不能单独先计算CAL/REFCAL / REFCAL/REF。此外,这种算法不能把系数 K 缓存下来,每次测量结果都要除以4095。当然,用μV 算法,每次也还是要除以1000 才能得到毫伏电压。