STM32采用Vrefint校准Vref值以降低ADC采样误差_stm32 vref
一、Vref实际值与理论值的不同造成ADC测量误差
由于采用专用的电压基准芯片会增加成本,通常在不需要特别高精度ADC采样的情况下,STM32的Vref引脚经常会直接接到VDDA上(如果没有Vref引脚,那么Vref内部接到VDDA上),这样一来,Vref的大小往往随着VDDA而同工况的变化而变化,而且通常ADC的数据要还原到电压值需要通过下面的公式进行计算(当选用8位分辨率时,分母为255):
那么Vref若存在误差,那么就会直接造成测量值Vmeas出现较大误差,事实上,通常STM32最小系统供电采用线性稳压器输出的3.3V,若采用精度较低/温度稳定性较低的线性稳压器就会造成误差,下面是某品牌1117稳压芯片的输出电压范围:
为了减小成本提升精度,不可能要求每块板子出厂前都测一下Vref/VDDA的电压值,更何况随着温度变化,线性稳压器的输出也会改变,这只是改变多少的问题(所幸一般变化不会太大)。
二、采用Vrefint可以求得Vref的较精确值
相比之下,stm32的Vrefint(internal Voltage reference)的值比较稳定,即使Vref电压、温度都进行变化,Vrefint都能保持在一个很小的变化范围内,比如说stm32g431:
为了得到更加精准的ADC值(相比于普通的ADC_DATA*3.3/4095),就可以通过计算得到更准确的Vref值(部分单片机Vref内接VDDA)
那么如何求得Vref呢?
已知:STM32G4(很多其他型号也有)的存储空间有一个固定的地址用于存放出厂校准的Vrefint对应的12位ADC值:(注意:不同的型号位置很可能不同,HAL库可以直接使用地址宏定义VREFINT_CAL_ADDR,其类型为uint16_t *。注意,这个值用VREFINT_CAL来指代)
通过读取,我手里的stm32g431的VREFINT_CAL等于1654,意味着,理论上这片stm32的Vrefint值:
又通过ADC1内部专门测量Vrefint 的一个Channel(Cubemx选择Vrefint Channel):
测量得到当前Vrefint的ADC值大致为1501,记现在的Vref的较精确值为Vref_real,可知:
上面两个等式左边都是Vrefint,联立得到:
进一步得到:
那么对于其他通道的ADC值,更精准的电压值为:
为了简便计算,可以在最开始时求出参数𝑘=𝑉ref_real/4095.f,则:
三、相关代码
注:下面代码基于HAL库,下面的代码用于获得Vref的较精确值
(1)读取VREFINT_CAL的值
#define VREFINT_CAL (*(uint16_t*)VREFINT_CAL_ADDR) //Vrefint的出厂ADC值
(2)读取Vrefint的实际ADC值
uint32_t get_vrefint_adc()//读取ADC值,Channel已经在CubeMX中配置为Vrefint所在的Channel{ uint32_t ADC_Value = 0; HAL_ADC_Start(&hadc1); //开启ADC转换 HAL_ADC_PollForConversion(&hadc1, 1000); //等待转换完成 ADC_Value = HAL_ADC_GetValue(&hadc1); //得到ADC值 HAL_ADC_Stop(&hadc1); //停止ADC转换 return ADC_Value;}
(3)main函数中求取较精确Vref值(省略了cubemx生成在前面的初始化函数)
while(HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED)!=HAL_OK);//校准ADC HAL_Delay(1);//不知道为什么前面若加上ADC校准后,如果不延时adc就立即读取Vrefint就会有较大误差 uint32_t Vrefint_CAL = VREFINT_CAL;//读取Vrefint的出厂ADC值 uint32_t Vrefint_DATA = get_vrefint_adc();//Vrefint实际测量得到的ADC值 float VDDA =3.f*(float)(VREFINT_CAL)/(float)(Vrefint_DATA);//求取Vref较精确值 while(1);
(4)把断点打在while(1)这一行,并把相关参数加到WATCH中,当运行到while行时,观察到几个变量值如下: