ADC Voltage Measurement

A project log for Coin Cell Powered Temperature/Humidity Display

Using an ePaper Display, investigate getting the lowest possible current usage from an STM32L0.

Kevin KesslerKevin Kessler 04/29/2018 at 18:250 Comments

The Analog to Digital Converter and internal 1.25 V Voltage reference is used to measure the battery voltage so it can be displayed as an icon. There are 2 methods that impact current usage during STANDBY related to the internal voltage reference: HAL_PWREx_EnableUltraLowPower and HAL_PWREx_EnableFastWakeup.

Enable Ultra Low Power turns the internal voltage reference off in standby. It takes 3ms for the voltage reference to stabilize and by default, the STANDBY wake routine waits for it. Enable Fast Wake Up does not wait for the internal voltage reference, which in my case, works out because I poll the Si7021 sensor before measuring the voltage, which takes longer than 3ms.

With these setting enabled, the STANDBY current drops down to 1.1 uA. Playing around with disabling the RTC, I found that the Real Time Clock used to wake up the uController, uses about 200nA during standby, and the datasheet claims STANDBY current as low as 250nA, the Si7021 sensor claims 60nA in sleep mode. With the ePaper Display gated off and using pretty much zero current, this means that there is somewhere around 500nA of current usage that might be able to eliminated, although testing firmware that initializes nothing, and immediately goes to STANDBY mode the lowest current usage I could get was about 800nA.

The ADC turned out to be a real rabbit hole to get it to work well. 

In normal use, you calculate the voltage of the unknown with:


Where VDA is the ADC Voltage on the VDDA pin, 4095 is the maximum value in 12 bit resolution,  VCOUNT is the reading from the ADC, and VUNKNOWN is the measured voltage. In the case of measuring the voltage of the battery supplying the STM32, your unknown is the VDA, and your VUNKNOWN is the 1.25V internal reference so, rearranging the equation above:

VDA = 4095 X 1250 /VCOUNT

Where 1250 is 1.25V in mV, so the answer is returned in mV. This gives reasonable accuracy for this application since it is only displaying 1 of 5 icons to indicate the voltage level, but the STM32s have something to improve accuracy.

At the factory, the STM32L0 are calibrated against a 3.0V source, and that ADC count value is stored in read only memory. You can then read this calibration value, and use the following formula to use the calibrated value to get your supply voltage:


where VREF_CAL is the value stored in memory location 0x1FF80078. Note this location varies by processor, and some processors like the STM32F0s are calibrated to 3.3V, and not 3.0. You have to look both of these up in the datasheet.

I put this formula in my code, turned the crank, and... the calculated values were terrible; far worse than using the uncalibrated equations. What was not clear to me is that you have to run a calibration step, with HAL_ADCEx_Calibration_Start, before you start your ADC, in order to use this calibrated reference. Doing that, and the calculated voltage using the calibration value was spot on.

Running this calibration step is relatively time consuming, and since the ADC (along with every other peripheral) is powered off during STANDBY, it has to be run after waking up every time. It is possible, after running HAL_ADCEx_Calibration_Start, to read the calibration value with HAL_ADCEx_Calibration_GetValue, and store it with HAL_ADCEx_Calibration_SetValue. So, in the firmware, when the uController first boots up, it runs the calibration step and stores the results of the calibration in a RTC Backup Register. When the controller returns from STANDBY, it reads the calibration value from the backup register, and sets it in the ADC. I coded this up, ran it, and ... it didn't work.

Heading down the ADC rabbit hole further, I had to consider how the ADC is configured for low power. First you have to enable Low Frequency Mode, whenever the clock is less than 3.5 MHz, which it is in this case. That one did not cause an issue, but  Auto Off did. When Auto Off is enabled, the ADC is powered off automatically when it is not doing a conversion to save power. The issue arises because, in order to set the calibration value with HAL_ADCEx_Calibration_Start, the ADC must be enabled, but not running a conversion, a state that never happens when Auto Off is enabled.  Disabling Auto Off allowed me to set the calibration value.

The code for this step in the process is tagged ADC.