← Back to Embedded C series
🔌
STM32
STM32 · Prereq: lesson 19

20. ADC Analog Input

Every pin so far has been digital (0/1). Most real-world signals — temperature·brightness·sound — are continuous analog. An ADC (Analog-to-Digital Converter) turns analog voltage into numbers the MCU can use. With STM32F103's 12-bit ADC we read PA0's voltage as 0–4095 and convert to millivolts. As the last of the STM32 peripheral series (13–20), it uses clock·pin·polling together.

ADC12-bitEOCcalibrationADCPREmillivolts
Duration
~1.5–2 hours
Level
📊 Intermediate
Prerequisite
🎯 Lesson 19
OUTCOME
Configure the ADC clock (≤14MHz)·channel·sample time, calibrate (RSTCAL/CAL) on F1, poll EOC to read a 12-bit value, and convert to millivolts.

What you'll learn

  • 1Understand 12-bit resolution (0–4095) and reference voltage
  • 2Set the ADC clock limit (≤14MHz) and prescaler
  • 3Configure channel·sample time·conversion sequence
  • 4Implement F1 ADC calibration (RSTCAL/CAL) and EOC polling
  • 5Convert the ADC count to millivolts

Introduction

A 12-bit ADC splits the input range (0–Vref, usually 3.3V) into 4096 steps. One step (LSB) is ~0.806mV. mV = adc × 3300 / 4095. Potentiometers·light·temperature sensors all come in via the ADC.

Key concepts

1) Clock limit and channel

c
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;   /* ADC clock ≤14MHz: 72/6 = 12MHz */
ADC1->SQR3  = 0u;                    /* 1st conversion channel 0 (PA0) */
ADC1->SMPR2 |= (7u << 0);            /* channel0 max sample time (239.5 cycle) */
/* ADC pin must be analog input (0x0) — no digital buffer/pull-up */

2) F1 calibration and conversion (EOC)

c
ADC1->CR2 |= ADC_CR2_ADON;            /* power on (wait tSTAB) */
ADC1->CR2 |= ADC_CR2_RSTCAL; while(ADC1->CR2 & ADC_CR2_RSTCAL){}
ADC1->CR2 |= ADC_CR2_CAL;    while(ADC1->CR2 & ADC_CR2_CAL){}   /* calibrate */

ADC1->CR2 |= ADC_CR2_ADON;            /* re-set ADON = start conversion */
while (!(ADC1->SR & ADC_SR_EOC)) { }  /* wait for conversion done */
uint16_t v = ADC1->DR & 0x0FFFu;      /* 12-bit result (reading clears EOC) */
⚠️

F1 needs calibration (RSTCAL→CAL) once after power-on for accuracy. For millivolt conversion always multiply then divide, in 32-bit: (uint32_t)adc * 3300 / 4095.

Core example

c
#include "stm32f10x.h"
static void adc1_init(void){
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN;
    RCC->CFGR    |= RCC_CFGR_ADCPRE_DIV6;          /* 12MHz */
    GPIOA->CRL &= ~(0xFu << 0);                    /* PA0 analog input (0x0) */
    ADC1->SQR3 = 0u; ADC1->SMPR2 |= (7u << 0);
    ADC1->CR2 |= ADC_CR2_ADON; for(volatile int i=0;i<10000;i++){}
    ADC1->CR2 |= ADC_CR2_RSTCAL; while(ADC1->CR2 & ADC_CR2_RSTCAL){}
    ADC1->CR2 |= ADC_CR2_CAL;    while(ADC1->CR2 & ADC_CR2_CAL){}
}
static uint16_t adc_read(void){
    ADC1->CR2 |= ADC_CR2_ADON; while(!(ADC1->SR & ADC_SR_EOC)){}
    return (uint16_t)(ADC1->DR & 0x0FFFu);
}
int main(void){
    adc1_init();
    while (1) {
        uint16_t adc = adc_read();
        volatile uint32_t mv = (uint32_t)adc * 3300u / 4095u;
        (void)mv;
    }
}

Conversion check: ADC=0→0mV, 2048→1650mV, 4095→3300mV.

Common mistakes

Q. It stalls waiting for EOC.

A. Confirm ADC power (ADON) is on, the conversion is started (re-set ADON/SWSTART), and the ADC clock (ADC1EN) is active.

Q. Values are noisy and inaccurate.

A. ① skipped calibration (RSTCAL/CAL), ② sample time too short (high-impedance source), ③ ADC clock over 14MHz — check ADCPRE.

Q. The mV calculation gives 0.

A. adc/4095*3300 truncates to 0 in integers. Always multiply then divide: (uint32_t)adc * 3300 / 4095.

Summary

  • The ADC turns analog voltage into a 12-bit integer (0–4095)
  • mV = adc × 3300 / 4095 (multiply then divide, in 32-bit)
  • ADC clock ≤14MHz — divide via ADCPRE
  • F1 calibrates (RSTCAL/CAL), converts, polls EOC, then reads DR
  • Set the ADC pin to analog input (0x0)

Exercises

  1. Send the read ADC value via lesson-19 USART as "adc=NNNN mv=MMMM"
  2. Average several reads to reduce noise (a simple filter)
  3. Add channel1 (PA1) by changing SQR3/SMPR
Example code / lecture materials

All lecture materials and example code (with homework and answers) are openly available on GitHub.

View on GitHub ↗