cancel
Showing results for 
Search instead for 
Did you mean: 

How to use the STM32 ADC's internal reference voltage

MCU Support TD
ST Employee

Introduction

In some applications, specifically battery-powered ones, it is common for the supply voltage to drop as the battery discharges. Sometimes, both VDDA and VREF+ is powered directly by the battery. The problem with this is if the VDDA is based off of this battery voltage, then you may have difficulties to get precise voltage conversions from the ADC. This is due to the math that converts the raw ADC value to a voltage relies on knowing VDDA/VREF+.

One solution to this is to separate VDDA and VREF+, using an external LDO to drop the VREF+ voltage below the battery voltage. This is done to ensure that the LDO maintains a stable voltage for VREF+ even as the battery is close to discharged. On some packages, VDDA, and VREF+ are internally bonded, which removes this as a solution.
For those who do not want the following:

  • Having to rely on an external solution
  • Have a package where VDDA and VREF+ are internally bonded
  • Reduce the number of components used

VREF internal can be used to calculate the voltage of VDDA in software.   

As an example, I am using the STM32CubeMX/STM32CubeIDE with the NUCLEO-C031. However, this example generally applies to any STM32 with an internal VREF.

1. CubeMX Setup

In CubeMX, we set up two channels: A channel of a voltage we want to read, and another channel, which is the VREF internal channel. We need to understand how long to sample the VREF internal channel. The datasheet for C031 shows that the internal reference voltage needs to be sampled for at least 4us. From DS13867, Table 27:

MCUSupportTD_0-1703079924456.png
In CubeMX, we enable the ADC channels we want to use:
MCUSupportTD_1-1703080096772.png
In the clock tree, the ADC is getting its clock from the system clock running at 48MHz, which is prescaled by 2 in the ADC configuration.
MCUSupportTD_2-1703080175617.png

 MCUSupportTD_4-1703080629752.png

Knowing our ADC clock is 24MHz, we can calculate the minimum sampling time:
4u / (1 / 24M) = 96

 

Out of the available sampling time options, 160.5 cycles is the only option that is greater than or equal to 96. Therefore, we use that sampling time.

MCUSupportTD_5-1703081109747.png

I am using the same sampling time for Channel 0, but you can feel free to use whatever sampling time you prefer for that channel. That is all for the CubeMX configuration and now we can generate code.

2. VREF Internal Theory

During factory programming, a calibration value of the raw ADC data of VREF internal at a known temperature and VDDA was collected and stored in ROM. From DS13867, Table 6:

MCUSupportTD_1-1703083116610.png

Using this calibration value presented below, along with the measured VREF internal in the application, the VDDA can be calculated. The formula below was obtained from the C0x1 reference manual, RM0490 page 274:

MCUSupportTD_0-1703083083297.png
 
Since the HAL library provides a macro to do this calculation, there is not a need to implement this, but it is good to understand how it works. It is also worth mentioning that VREF internal does have some variance over temperature, which is roughly characterized in the datasheet, DS13867 Figure 13:
MCUSupportTD_2-1703083503748.pngMCUSupportTD_3-1703083517272.png
Since the characterizations are not precise and there is per device variance, for best performance in extreme temperature ranges it would be necessary to characterize this temperature coefficient experimentally.

3. Using VREF internal in the application

The HAL libraries make the usage of VREF internal quite simple. In this example, I am using a simple polling method to get the ADC readings, but this could be implemented using interrupts or DMA as well. 

 

 

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
// Always calibrate ADC first
HAL_ADCEx_Calibration_Start(&hadc1);

// Start the conversion sequence
HAL_ADC_Start(&hadc1);

/*
 * Rank 1: vref internal
 */
HAL_ADC_PollForConversion(&hadc1, 0xFFFF);
uint32_t vrefint_raw = HAL_ADC_GetValue(&hadc1);

/*
 * Rank 2: measurement channel
 */
HAL_ADC_PollForConversion(&hadc1, 0xFFFF);
uint32_t measurement_raw = HAL_ADC_GetValue(&hadc1);

/*
 * This macro calculates the vdda voltage (as a uint32_t representing the voltage in milliVolts)
 * using the vref internal raw adc value, and the internal calibration value in ROM
 */
uint32_t vdda_voltage = __HAL_ADC_CALC_VREFANALOG_VOLTAGE(vrefint_raw, ADC_RESOLUTION_12B);

/*
 * A helper function for doing a raw ADC value to voltage conversion, essentially
 * 
 * vdda_voltage * measurement_raw / 2^12
 * 
 */
uint32_t measurement_voltage = __HAL_ADC_CALC_DATA_TO_VOLTAGE(vdda_voltage, measurement_raw, ADC_RESOLUTION_12B);

/* USER CODE END 2 */

 

 

Conclusion 

In a situation where your application is battery powered, your VDDA voltage will drop over time as the battery discharges. If absolute voltage measurements are required regardless of VDDA's voltage, the internal reference voltage can be used to measure the voltage of VDDA. By using the measured voltage of VDDA in our ADC data conversions, precise voltage measurements can be made without a consistent VDDA. 

Related links

Datasheet DS13867: STM32C031x4/x6 Arm Cortex®-M0+ 32-bit MCU 

Reference Manual RM0490: STM32C0x1 advanced Arm®-based 32-bit MCUs 

Version history
Last update:
‎2024-06-18 12:51 AM
Updated by: