iter
iter

Reputation: 4323

Calibrating STM32 ADC (VREFINT)

I'm trying to read VDDA on an STM32F042 microcontroller. I'm getting unexpected results with VDD at 3.29V. I must be missing something fundamental.

output:

VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
VREFINT=1885; VREFINT_CAL=1524; VDDA=2668 mV
VREFINT=1913; VREFINT_CAL=1524; VDDA=2628 mV
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV

adc_test.c:

#include <stdio.h>
#include "stm32f0xx.h"

#define VREFINT_CAL_ADDR                0x1FFFF7BA  /* datasheet p. 19 */
#define VREFINT_CAL ((uint16_t*) VREFINT_CAL_ADDR)

extern void initialise_monitor_handles(void);

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;     /* enable ADC peripheral clock */
    RCC->CR2 |= RCC_CR2_HSI14ON;            /* start ADC HSI */
    while (!(RCC->CR2 & RCC_CR2_HSI14RDY)); /* wait for completion */
    /* calibration */
    ADC1->CR |= ADC_CR_ADCAL;               /* start ADc CALibration */
    while (ADC1->CR & ADC_CR_ADCAL);        /* wait for completion */
    ADC1->CR |= ADC_CR_ADEN;                /* ADc ENable */
    while (!(ADC1->ISR & ADC_ISR_ADRDY));   /* wait for completion */
    ADC1->SMPR |= ADC_SMPR1_SMPR_0 |        /* sampling mode: longest */
      ADC_SMPR1_SMPR_1 |
      ADC_SMPR1_SMPR_2;
    /* VDD reference */
    ADC->CCR |= ADC_CCR_VREFEN;             /* VREF Enable */
    ADC1->CHSELR = ADC_CHSELR_CHSEL17;      /* CH17 = VREFINT */

    initialise_monitor_handles();           /* enable semihosting */

    while (1) {
        ADC1->CR |= ADC_CR_ADSTART;             /* start ADC conversion */
        while (!(ADC1->ISR & ADC_ISR_EOC));     /* wait for completion */
        uint32_t vdda = 3300UL * *VREFINT_CAL / ADC1->DR; /* ref. manual p. 252; constant and result in millivolts */
        printf("VREFINT=%lu; VREFINT_CAL=%lu; VDDA=%lu mV\n",
                (unsigned long)ADC1->DR,
                (unsigned long)*VREFINT_CAL,
                (unsigned long)vdda);
    }
}

Screenshot from Datasheet:

enter image description here

Screenshot from Reference Manual

note this refers to .3V, but I believe this to be a typo, as the datasheet above and the longer formula below refer to 3.3V, and .3V is below minimum operating voltage for this part

enter image description here

Upvotes: 5

Views: 27063

Answers (4)

iter
iter

Reputation: 4323

The answer (big thanks to @jasonharper) is a missing ground connection. Jason't comments on the OP are the best source of wisdom in this thread. I post a summary here so this question can have an accepted answer.

The board went through a number of revisions and in this iteration we forgot to connect the thermal pad, which on this part is the only ground connection. The chip was getting ground through ESD diodes on pins that were connected to ground. It's surprising to me that it worked at all. I was able to increase the current to the chip by configuring grounded GPIOs as outputs and setting them low.

Upvotes: 0

Alberto Stolowich
Alberto Stolowich

Reputation: 51

as @Artur said Vref + is not Vdda, but usually (that's how I have it in my hardware design) Vref + is connected to Vdda (with the corresponding filters according to the datasheet), so calculating Vdda is the same as calculating Vref +.

I will show you how to calculate vdda, as I have it based on the STM32L431.

. You must first configure the ADC to measure VREFINT:

void MX_ADC1_Init(void)
{

    /* USER CODE BEGIN ADC1_Init 0 */

    /* USER CODE END ADC1_Init 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_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc1.Init.OversamplingMode = DISABLE;
    if (HAL_ADC_Init(&hadc1) != 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();
    }
    /* USER CODE BEGIN ADC1_Init 2 */

    /* USER CODE END ADC1_Init 2 */
}

. Now I will show you the code where the equation is executed:

vdda = 3.0 * (VREFINT_CAL /average);
vch = VREF * (average / ADC_RESOLUTION);

log("vdd = %.5f - ", vdda);
log("vchn = %.5f", vch);

where:

#define ADC_RESOLUTION 4095.0           // adc resolution 12 bits
#define VREFINT_CAL 1655.00             // Raw data acquired at a temperature of 30 °C (± 5 °C), VDDA = VREF+ = 3.0 V (± 10 mV)
#define VREF 3.3                        // voltage reference 3.3V

note:

'average' is an average of 256 samples taken by the adc (it's just a simple filter).

'log' is a function created by me similar to printf for the uart.

'VREFINT_CAL' varies according to the model.

result:

vdd = 3.28035 - vchn = 1.21343

as we see VREFINT matches the datasheet (1.212V typ.):

VREFINT

Upvotes: 1

Tristan
Tristan

Reputation: 1

Actually it is calculating Vdda, since the Vref calculation is very simple, you have to read the corresponding channel of the ADC with a sample time longer than the one marked in the data sheet (usually 10 us). If Vdda is 2.0 V, a value of 4095 corresponds to 2.0 (or more) V absolute (related GND). In a linear way, the value of Vref will be much higher than if it is read with Vdda = 3.30 V. Therefore, the compensation of the values ​​read with 2.0 V is necessary to know the absolute values ​​of voltage that the ADC is measuring. If they are not compensated, they will be values ​​relative to the voltage level that Vdda has at that moment. In addition, the power supply value is achieved, which will be useful in order not to go beyond the specifications of the microcontroller.

Upvotes: 0

Artur Sparwasser
Artur Sparwasser

Reputation: 31

I'm currently developing an ADC driver for STM32L4. During implementation I encounter almost the same problem. In my opinion the first formula enter image description here

is not calculating the VDDA, but VREF+. It's the voltage against which the ADC is evaluating the ADC-IN channels. Further the VREFINT_DATA is not measured VREF+ voltage, but an internal reference voltage which is controller dependent. In my case it defined in controller datasheet: enter image description here

Here's a pic how I am using the posted formulas: enter image description here

Some comments: ln 102: calculating VREF+ not VDDA

ln 105-110: calculate all ranks/configured sequence

ln 108: calculate voltage measured by ADCpin_x

ln 109: multiply by gain to get real value

In my opinion by calculating the VREF+ for each conversion sequence, I'll get better results, because so some ripples on VREF+ are compensated.

Upvotes: 1

Related Questions