Skip to main content
Visitor II
September 2, 2025
Solved

How to measure VBAT using ADC correctly in STM32L476 MCU

  • September 2, 2025
  • 3 replies
  • 744 views

Hi everyone!
I'm having trouble in measuring the VBAT through ADC. I'm using 3.3V on aNUCLEO_476 board to connect to VBAT and when I measure VBAT through ADC,I cannot obtain the value of the VBAT

Here's my code:

void MX_ADC1_Init(void)
{

 /* USER CODE BEGIN ADC1_Init 0 */

 /* USER CODE END ADC1_Init 0 */

 ADC_MultiModeTypeDef multimode = {0};
 ADC_ChannelConfTypeDef sConfig = {0};

 /* USER CODE BEGIN ADC1_Init 1 */

 /* USER CODE END ADC1_Init 1 */

 /** Common config
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = ENABLE;
 hadc1.Init.NbrOfConversion = 7;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
 hadc1.Init.OversamplingMode = DISABLE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure the ADC multi-mode
 */
 multimode.Mode = ADC_MODE_INDEPENDENT;
 if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_VREFINT;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_3;
 sConfig.Rank = ADC_REGULAR_RANK_2;
 sConfig.SamplingTime = ADC_SAMPLETIME_92CYCLES_5;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_4;
 sConfig.Rank = ADC_REGULAR_RANK_3;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_5;
 sConfig.Rank = ADC_REGULAR_RANK_4;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_7;
 sConfig.Rank = ADC_REGULAR_RANK_5;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_13;
 sConfig.Rank = ADC_REGULAR_RANK_6;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_VBAT;
 sConfig.Rank = ADC_REGULAR_RANK_7;
 sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN ADC1_Init 2 */

 /* USER CODE END ADC1_Init 2 */

}​

 

/* USER CODE BEGIN PV */
uint16_t adc_data_buffer[7]; /* ADC常规转换数据 */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
 * @brief The application entry point.
 * @retval int
 */
int main(void)
{

 /* USER CODE BEGIN 1 */
 //NVIC_Redirect();
 /* USER CODE END 1 */

 /* MCU Configuration--------------------------------------------------------*/

 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
 HAL_Init();

 /* USER CODE BEGIN Init */

 /* USER CODE END Init */

 /* Configure the system clock */
 SystemClock_Config();

 /* Configure the peripherals common clocks */
 PeriphCommonClock_Config();

 /* USER CODE BEGIN SysInit */

 /* USER CODE END SysInit */

 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_DMA_Init();
 MX_ADC1_Init();
 MX_I2C2_Init();
 MX_I2C3_Init();
 MX_UART5_Init();
 MX_TIM2_Init();
 /* USER CODE BEGIN 2 */
 other_Init();

 // ADC Calibration
 HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
 HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_data_buffer, 7);

 /* USER CODE END 2 */

 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
 while (1)
 {
 /* USER CODE END WHILE */

 /* USER CODE BEGIN 3 */
 ProcessADCValues(adc_data_buffer, 7);
 main_Task();
 }
 /* USER CODE END 3 */
}

 

void ProcessADCValues(volatile uint16_t *adcData, uint8_t length)
{
 for (uint8_t i = 0; i < length; i++)
 {
 // 处理每个通道的数据
 switch (i)
 {
 case 0:
 VREF_voltage = get_VREF(adcData[0]); // 
 break;
 case 1:
 ILIM_MonitorI = get_MP5087_ILIM(adcData[1]); // ADC channel 3, ilim_monitor
 break;
 case 2:
 ILIM_MonitorI2 = get_MP5087_ILIM(adcData[2]); // ADC channel 4, ilim_monitor1
 break;
 case 3:
 Tosa_temp1 = get_NBC103_Temp(adcData[3]); // ADC channel 5, TOSA_TEMP1
 break;
 case 4:
 Moudle_temp = get_TH05_Temp(adcData[4]); // ADC channel 7, module_temp
 break;
 case 5:
 Tosa_temp2 = get_NBC103_Temp(adcData[5]); // ADC channel 13, TOSA_TEMP2
 break;
 case 6:
 Vbat_voltage = get_Vbat(adcData[6], VREF_voltage);
 break;
 default:
 break;
 }
 }
}
 
#define VREFINT_ADDR ((uint16_t *)0x1FFF75AA) 

float get_VREF(uint16_t adc_value)
{
 uint16_t vrefint_cal = *VREFINT_ADDR;
 float vref_actual = (3.0f * vrefint_cal) / adc_value;
 return vref_actual;
}

float get_Vbat(uint16_t adc_value, float vref_voltage)
{
 float vbat = (adc_value * vref_voltage) / 4095.0f;
 return vbat;
}​
 
Can anyone please tell me where I'm doing it wrong?
 
 
    This topic has been closed for replies.
    Best answer by eson

    Hi.@Saket_Om

    I've found the reason why I can't collect the VBAT.
    I mistakenly configured the VREF pin as an external reference voltage mode.

    After enabling ENVR and delaying for 100ms, the ADC can now sample normally.

    void McuVref_Config(VREF_TypeDef Vref_Type)
    {
     if (Vref_Type == VREF_INTERNAL)
     {
     /** Configure the internal voltage reference buffer voltage scale */
     LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE1);
    
     /** Configure the internal voltage reference buffer high impedance mode	*/
     LL_VREFBUF_DisableHIZ();
    
     /** Enable the Internal Voltage Reference buffer	*/
     LL_VREFBUF_Enable();
     }
    
     if (Vref_Type == VREF_EXTERNAL)
     {
     /** Disable the Internal Voltage Reference buffer */
     LL_VREFBUF_Disable();
    
     /** Configure the internal voltage reference buffer high impedance mode */
     LL_VREFBUF_EnableHIZ();
     }
    }

     

    3 replies

    Super User
    September 2, 2025

    What are the raw readings of VREFINT and VBAT, what is the *VREFINT_ADDR content, and what is the actual voltage measured at VDDA?

    You may also read out and check/post content of ADC registers.

    JW

    esonAuthor
    Visitor II
    September 3, 2025

    Hi JW 

    *VREFINT_ADDR value is 0x0681,

    But there's a really weird thing: when I connect the VBAT pin on the NUECLEOL476 development board to the 3.3V on the board, all of the adc registers value is 0x0000,I don't konw why

    企业微信截图_17568683837516.png

    And I change some code in my Project:

     

    #define VREFINT_CAL *((__IO uint16_t *)0x1FFF75AA) // VDDA = VREF+ =3.0V(± 10 mV)
    
    float get_VREF(uint16_t adc_value)
    {
     float Vdda = (3.0f * VREFINT_CAL) / adc_value;
     return Vdda;
    }
    
    float get_Vbat(uint16_t adc_value, float vref_voltage)
    {
     float vbat = (vref_voltage / 4095.0f) * adc_value * 3.0f;
     return vbat;
    }

    eson

    Super User
    September 3, 2025

    Isn't VBAT connected to some other circuitry on that board? (I haven't checked).

    JW

    esonAuthor
    Visitor II
    September 3, 2025

    eson_0-1756890861834.jpeg

    yeah,I connect VBAT with the Onboard 3.3V,and following is mine ADC config

     

    ce72b0ed34365827a4b05b1e19cca966.png

     

    eson

    Technical Moderator
    September 3, 2025

    Hello @eson 

    To check your board, can you try to run the example from STM32L4 FW package :
    Projects\NUCLEO-L496ZG\Examples\ADC\ADC_Sequencer

    • The configuration is close to the one of your project:
      It performs 3 ADC conversions (VrefInt, GPIO, internal temperature sensor) with data transfer by DMA.
    • Could you try to run this example, you should get these kinds of data (assuming board setup not modified, with Vref+ pin = 3.3V):
      VrefInt raw data 1500(dec) corresponding to 1200mV
      Temp sensor raw data 920(dec) corresponding to 23degC
    • Then, could you try to replace one of 3 channels by Vbat: "sConfig.Channel = ADC_CHANNEL_VBAT;"
      You should get these kind of data :
      Vbat raw data 1362(dec) corresponding to 1362 * 3 * 3300 / 4095 = 3293mV
       Note : x3 due to Vbat voltage through a divider ladder of factor 1/3
    • About your code :
      In function "get_Vbat()", you have to multiply Vbat variable value by factor 3 (see example above).
      In function "get_VREF()", you can use directly ADC LL helper macro __LL_ADC_CALC_VREFANALOG_VOLTAGE().
      Except this, your code seems correct.
    esonAuthor
    Visitor II
    September 4, 2025

    Hello Saket_Om

    Thanks for your  suggestion,When I tried to use the program under Projects\NUCLEO-L496ZG\Examples\ADC\ADC_Sequencer, it got stuck in the HAL_Init() function. However, I carried out debugging using the program under Projects\NUCLEO-L476RG\Examples_LL\ADC\ADC_MultiChannelSingleConversion.

    I have obtained the following data:

    3cf7bca42d55df09d561b5931b83baf2.png

    These data seem to have some issues.

    I noticed that the VBAT and VDD on my board are connected together and I'm not sure if this affects the VBAT acquisition data.

    eson

     

    Technical Moderator
    September 5, 2025

    Hello @eson 


    @eson wrote:

    Hello Saket_Om

    Thanks for your  suggestion,When I tried to use the program under Projects\NUCLEO-L496ZG\Examples\ADC\ADC_Sequencer, it got stuck in the HAL_Init() function.


    What happen exactly in the function HAL_Init()? On which instruction it get stack?