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.
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
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)
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
#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
- Send the read ADC value via lesson-19 USART as "adc=NNNN mv=MMMM"
- Average several reads to reduce noise (a simple filter)
- Add channel1 (PA1) by changing SQR3/SMPR
All lecture materials and example code (with homework and answers) are openly available on GitHub.
View on GitHub ↗