cancel
Showing results for 
Search instead for 
Did you mean: 

What could be the cause of a constant voltage offset on the STM32L041G6 ADC?

Gbnusko
Associate II

I have been testing the ADC on my STM32L0 with 12-bit resolution and for some reason, every measurement I take has a 0.048 V offset. I've tried a different supply voltage (from 1.8 V to 3.3 V), but the offset stays there.

This offset starts at the first count, when I apply 0 V to the ADC input and read 130 counts (with 1.8 V Vdda) it stays for the whole range.

My voltage source is very stable and I've verified my ADC input voltage and the STM32 supply voltage with 3 different accurate pieces of measuring equipment. 

The board I'm using is a custom PCB, but I only placed the STM32 and some passive components to get it to function (coupling capacitors and reset pull-up).

I also tested multiple boards with different configurations and I always get this offset voltage. I outputed Vrefint and measured it, and I got 1.225 V (specified is 1.224 V) so the ADC reference seems fine too. 

I was wondering if someone has a suggestion on the possible cause of this offset error? I've been looking for a solution but I'm having no luck so far, so I would really appreciate the input of more experienced developers.

Here is a simplified version of the code I use : 

```

  /* USER CODE BEGIN Header */

/**

 ******************************************************************************

 * @file  adc.c

 * @brief  This file provides code for the configuration

 *     of the ADC instances.

 ******************************************************************************

 * @attention

 *

 * Copyright (c) 2022 STMicroelectronics.

 * All rights reserved.

 *

 * This software is licensed under terms that can be found in the LICENSE file

 * in the root directory of this software component.

 * If no LICENSE file comes with this software, it is provided AS-IS.

 *

 ******************************************************************************

 */

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/

#include "adc.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

ADC_HandleTypeDef hadc;

/* ADC init function */

void MX_ADC_Init(void)

{

 /* USER CODE BEGIN ADC_Init 0 */

 /* USER CODE END ADC_Init 0 */

 ADC_ChannelConfTypeDef sConfig = {0};

 /* USER CODE BEGIN ADC_Init 1 */

 /* USER CODE END ADC_Init 1 */

 /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)

 */

 hadc.Instance = ADC1;

 hadc.Init.OversamplingMode = DISABLE;

 hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;

 hadc.Init.Resolution = ADC_RESOLUTION_12B;

 hadc.Init.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;

 hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;

 hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;

 hadc.Init.ContinuousConvMode = DISABLE;

 hadc.Init.DiscontinuousConvMode = DISABLE;

 hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;

 hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;

 hadc.Init.DMAContinuousRequests = DISABLE;

 hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;

 hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;

 hadc.Init.LowPowerAutoWait = DISABLE;

 hadc.Init.LowPowerFrequencyMode = ENABLE;

 hadc.Init.LowPowerAutoPowerOff = DISABLE;

 if (HAL_ADC_Init(&hadc) != HAL_OK)

 {

  Error_Handler();

 }

 /** Configure for the selected ADC regular channel to be converted.

 */

 sConfig.Channel = ADC_CHANNEL_0;

 sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;

 if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)

 {

  Error_Handler();

 }

 /* USER CODE BEGIN ADC_Init 2 */

 /* USER CODE END ADC_Init 2 */

}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)

{

 GPIO_InitTypeDef GPIO_InitStruct = {0};

 if(adcHandle->Instance==ADC1)

 {

 /* USER CODE BEGIN ADC1_MspInit 0 */

 /* USER CODE END ADC1_MspInit 0 */

  /* ADC1 clock enable */

  __HAL_RCC_ADC1_CLK_ENABLE();

  __HAL_RCC_GPIOA_CLK_ENABLE();

  /**ADC GPIO Configuration

  PA0-CK_IN   ------> ADC_IN0

  */

  GPIO_InitStruct.Pin = SENSOR_ANALOG_IN_Pin;

  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;

  GPIO_InitStruct.Pull = GPIO_NOPULL;

  HAL_GPIO_Init(SENSOR_ANALOG_IN_GPIO_Port, &GPIO_InitStruct);

 /* USER CODE BEGIN ADC1_MspInit 1 */

 /* USER CODE END ADC1_MspInit 1 */

 }

}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)

{

 if(adcHandle->Instance==ADC1)

 {

 /* USER CODE BEGIN ADC1_MspDeInit 0 */

 /* USER CODE END ADC1_MspDeInit 0 */

  /* Peripheral clock disable */

  __HAL_RCC_ADC1_CLK_DISABLE();

  /**ADC GPIO Configuration

  PA0-CK_IN   ------> ADC_IN0

  */

 /* USER CODE BEGIN ADC1_MspDeInit 1 */

 /* USER CODE END ADC1_MspDeInit 1 */

 }

}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

```

And here is the main file where I call the function to read the adc and send it to uart to be read :

 ```

#include "main.h"

#include "gpio.h"

#include "adc.h"

#include "usart.h"

#include <stdlib.h>

extern "C" 

void execute(){

HAL_Delay(10);

HAL_ADC_Start(&hadc);

HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY);

const auto value = HAL_ADC_GetValue(&hadc);

char buffer [5];

itoa(value, buffer, 10);

HAL_UART_Transmit(&huart2, reinterpret_cast<uint8_t*>(buffer), 5, HAL_MAX_DELAY);

}

```

Here is the schematic, as I said, the test circuit only had the mcu and some passive components

1 ACCEPTED SOLUTION

Accepted Solutions

AN2834 does not properly deal with the various ADC models in individual STM32 families, which vary quite a lot. The 'L0 ADC has a built-in calibration (i.e. hardware which subtracts the offset from every subsequent ADC conversion's result), but you have to start it manually before starting the ADC. This subchapter clearly describes the procedure:

0693W00000QMzyKQAT.pngThere may be some way to do this in Cube. I don't know, I don't use Cube.

JW

PS. Are you sure all your ground pins including VSSA and on large packages VREF- are at the exact same potential?

View solution in original post

6 REPLIES 6
S.Ma
Principal

Gpio programmed with pullup, uncalibrated adc, too high input impedence, erratasheet, check vref channel value.

Gbnusko
Associate II

I did a test where I outputted VrefInt and it was very stable at 1.225V (1.224V is the typical vrefint), so im fairly certaint that part is good. For the high imput impedance, I also tryed with 50 ohm but I got exactly the same offset. The gpio isn't set to a pull-up in stm32cubemx so I doubt it would be that. As for the calibration, I looked at this document : https://www.st.com/resource/en/application_note/cd00211314-how-to-get-the-best-adc-accuracy-in-stm32-microcontrollers-stmicroelectronics.pdf "AN2834 Application note How to get the best ADC accuracy in STM32 microcontrollers" and if I understand correctly, I would have to design my own calibration function? Basically, I could read gnd and set that to 0 point instead of 130 and the offset would be gone but I feel like this would be a bad way of solving the issue and it could backfire in the future.

AN2834 does not properly deal with the various ADC models in individual STM32 families, which vary quite a lot. The 'L0 ADC has a built-in calibration (i.e. hardware which subtracts the offset from every subsequent ADC conversion's result), but you have to start it manually before starting the ADC. This subchapter clearly describes the procedure:

0693W00000QMzyKQAT.pngThere may be some way to do this in Cube. I don't know, I don't use Cube.

JW

PS. Are you sure all your ground pins including VSSA and on large packages VREF- are at the exact same potential?

Thank you for this information, I wasn't aware of the adc cal and It could be the cause of my offset, I will have to test it out. The chip im using doesn't have pins for VSSA or VREF- so im not sure how to probe them.

S.Ma
Principal

This means vdda and vssa pads are package bonded with vdd and vss. Once calibrated, play with sample and hold time to fine tune adc result value.

The calibration was what I was missing, I just added this line in initialize() and it made the offset disapear. I tested my adc with the same setup and I have a precision of around 3 count (1.2mV) which is really good. Thank you for the help again!

void initialize()
{
    HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED);
    HAL_Delay(10);
}