2022-08-04 08:32 AM
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
Solved! Go to Solution.
2022-08-04 09:31 AM
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:
There 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?
2022-08-04 08:41 AM
Gpio programmed with pullup, uncalibrated adc, too high input impedence, erratasheet, check vref channel value.
2022-08-04 09:17 AM
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.
2022-08-04 09:31 AM
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:
There 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?
2022-08-04 10:58 AM
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.
2022-08-04 02:47 PM
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.
2022-08-05 06:07 AM
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);
}