title: Calibrating STM32 ADC (VREFINT)
body: <p>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.</p>
  
  <h3>output:</h3>
  
  <pre><code>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
  </code></pre>
  
  <h3>adc_test.c:</h3>
  
  <pre class="lang-c prettyprint-override"><code>#include &lt;stdio.h&gt;
  #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-&gt;APB2ENR |= RCC_APB2ENR_ADC1EN;     /* enable ADC peripheral clock */
      RCC-&gt;CR2 |= RCC_CR2_HSI14ON;            /* start ADC HSI */
      while (!(RCC-&gt;CR2 &amp; RCC_CR2_HSI14RDY)); /* wait for completion */
      /* calibration */
      ADC1-&gt;CR |= ADC_CR_ADCAL;               /* start ADc CALibration */
      while (ADC1-&gt;CR &amp; ADC_CR_ADCAL);        /* wait for completion */
      ADC1-&gt;CR |= ADC_CR_ADEN;                /* ADc ENable */
      while (!(ADC1-&gt;ISR &amp; ADC_ISR_ADRDY));   /* wait for completion */
      ADC1-&gt;SMPR |= ADC_SMPR1_SMPR_0 |        /* sampling mode: longest */
        ADC_SMPR1_SMPR_1 |
        ADC_SMPR1_SMPR_2;
      /* VDD reference */
      ADC-&gt;CCR |= ADC_CCR_VREFEN;             /* VREF Enable */
      ADC1-&gt;CHSELR = ADC_CHSELR_CHSEL17;      /* CH17 = VREFINT */
  
      initialise_monitor_handles();           /* enable semihosting */
  
      while (1) {
          ADC1-&gt;CR |= ADC_CR_ADSTART;             /* start ADC conversion */
          while (!(ADC1-&gt;ISR &amp; ADC_ISR_EOC));     /* wait for completion */
          uint32_t vdda = 3300UL * *VREFINT_CAL / ADC1-&gt;DR; /* ref. manual p. 252; constant and result in millivolts */
          printf("VREFINT=%lu; VREFINT_CAL=%lu; VDDA=%lu mV\n",
                  (unsigned long)ADC1-&gt;DR,
                  (unsigned long)*VREFINT_CAL,
                  (unsigned long)vdda);
      }
  }
  </code></pre>
  
  <h2>Screenshot from Datasheet:</h2>
  
  <p><a href="https://i.stack.imgur.com/AXOtH.png" rel="noreferrer"><img src="https://i.stack.imgur.com/AXOtH.png" alt="enter image description here"></a></p>
  
  <h2>Screenshot from Reference Manual</h2>
  
  <h3>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</h3>
  
  <p><a href="https://i.stack.imgur.com/oJYth.png" rel="noreferrer"><img src="https://i.stack.imgur.com/oJYth.png" alt="enter image description here"></a></p>