All the pins we've dealt with so far were either 0 or 1, that is, digital. However, most signals in the world are continuous analog signals, like temperature, brightness, or sound. ADC (Analog-to-Digital Converter) converts analog voltage to numbers, allowing the MCU to handle those values. Inputs such as potentiometers, light sensors, and temperature sensors all go through the ADC.
The ADC of STM32F103 is 12-bit, representing voltage as integers from 0 to 4095. In this chapter, we read the voltage on PA0 using ADC1 and convert that count into millivolts. This is the final chapter of the STM32 peripheral series (Chapters 13–20), where the clock, pin, and polling concepts we learned earlier are all used at once.
Simulator Note: The STM32F103 ADC model in μVision may have limitations on input voltage injection. In this chapter, check the conversion sequence (calibration, EOC, DR) via By-Register/Watch, and verify the count→voltage conversion formula itself on a PC using
pc_test.c.
Learning Objectives
- Understand the role of ADC, 12-bit resolution (0–4095), and the concept of reference voltage.
- Set the ADC clock constraint (≤14MHz) and prescaler.
- Configure channel, sample time, and conversion sequence.
- Implement F1 ADC calibration (RSTCAL/CAL) and End-Of-Conversion (EOC) polling.
- Convert ADC counts into millivolts.
Target Environment
- Compiler: Keil MDK-ARM (Arm Compiler 6).
- Device: STM32F103C8 (μVision simulator).
- Verification: μVision simulator (ADC1 registers, Watch). Conversion formula is verified on PC
gcc(pc_test.c).
Key Concepts
1) Resolution and Reference Voltage
A 12-bit ADC divides the input voltage range (0 ~ Vref, usually 3.3V) into 2¹² = 4096 steps. One step (LSB) is approximately 3300mV / 4095 ≈ 0.806mV.
mV = adc * 3300 / 4095
| ADC Count | Voltage |
|---|---|
| 0 | 0 mV |
| 2048 | Approximately 1650 mV |
| 4095 | 3300 mV |
2) ADC Clock Constraints
The ADC clock (ADCCLK) is maximum 14MHz. Using PCLK2=72MHz directly is too fast, so it is divided by ADCPRE of RCC->CFGR. part/6 fits within the limit at 12MHz.
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; /* 72/6 = 12MHz */
3) Channels and Sample Time
- Channel: PA0=Channel0, PA1=Channel1 … put the channel to be converted into the first slot (SQ1) of the sequence register
SQR3. - Sample Time: Set with
SMPR. High impedance sources need a longer time (e.g., 239.5 cycles) for accuracy.
ADC1->SQR3 = 0u; /*First conversion channel 0 (PA0)*/
ADC1->SMPR2 |= (7u << 0); /*Channel 0 maximum sample time*/
4) Calibration and Conversion (F1 specific)
F1 ADC must perform calibration once after power-on to be accurate.
ADC1->CR2 |= ADC_CR2_ADON; /*Power ON*/
/*tSTAB Standby*/
ADC1->CR2 |= ADC_CR2_RSTCAL; while (ADC1->CR2 & ADC_CR2_RSTCAL) { }
ADC1->CR2 |= ADC_CR2_CAL; while (ADC1->CR2 & ADC_CR2_CAL) { }
For conversion, reset ADON again to start, wait for EOC (End of Conversion) in SR, then read DR.
5) Reading Results
DR holds the 12-bit result (right-aligned by default). Reading it will clear the EOC.
ADC1->CR2 |= ADC_CR2_ADON;
while (!(ADC1->SR & ADC_SR_EOC)) { }
uint16_t v = ADC1->DR & 0x0FFF;
Creating a μVision Project
Project → New µVision Project…→ name itstm32_adc.- Device: STM32F103C8.
Manage Run-Time Environment→ CMSIS:CORE, Device:Startup.- After adding
main.c, copy and paste. - Apply
STM32F10X_MDtoC/C++ → Define. Debug → Use Simulator,-pSTM32F103C8.- Rebuild(F7) → Debug(Ctrl+F5).
Examples
Example 1 — main.c: Reading Voltage of PA0
#include "stm32f10x.h"
/*
*Lesson 20 Example — Single conversion of ADC1 Channel 0 (PA0).
*Target: STM32F103C8, Compiler: Keil MDK-ARM.
*
*Read the voltage of PA0 with a 12-bit ADC (0~4095) and convert it to millivolts.
*mV = adc * 3300 / 4095 (assuming reference voltage 3.3V)
*For F1 ADC, after turning it on, calibration (RSTCAL → CAL) must be done once to be accurate.
*/
static void short_delay(void)
{
volatile uint32_t i;
for (i = 0; i < 10000u; i++) { } /*tSTAB short wait for stabilization*/
}
static void adc1_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN;
RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; /*ADC clock 72/6=12MHz (≤14MHz)*/
/*PA0 = analog input (CNF=00, MODE=00 → 0x0)*/
GPIOA->CRL &= ~(0xFu << (0u * 4u));
ADC1->SQR3 = 0u; /*Primary conversion channel = 0 (PA0)*/
ADC1->SMPR2 |= (7u << (3u * 0u)); /*Channel 0 sample time 239.5 cycle*/
ADC1->CR2 |= ADC_CR2_ADON; /*ADC power ON*/
short_delay();
ADC1->CR2 |= ADC_CR2_RSTCAL; /*Reset calibration register*/
while (ADC1->CR2 & ADC_CR2_RSTCAL) { }
ADC1->CR2 |= ADC_CR2_CAL; /*Start calibration*/
while (ADC1->CR2 & ADC_CR2_CAL) { } /*Wait for calibration complete*/
}
static uint16_t adc1_read(void)
{
ADC1->CR2 |= ADC_CR2_ADON; /*ADON reset = start conversion*/
while (!(ADC1->SR & ADC_SR_EOC)) { } /*Wait for conversion complete (EOC)*/
return (uint16_t)(ADC1->DR & 0x0FFFu); /*12-bit result*/
}
int main(void)
{
volatile uint16_t adc_val = 0;
volatile uint32_t mv = 0;
adc1_init();
while (1) {
adc_val = adc1_read();
mv = (uint32_t)adc_val * 3300u / 4095u; /*Conversion to millivolts*/
}
}
Example 2 — pc_test.c: Count → Voltage Conversion
#include <stdio.h>
#include <stdint.h>
/*
*Lesson 20 PC Verification — Converting ADC counts (0~4095) to millivolts.
*mV = adc * 3300 / 4095 (Reference voltage 3.3V, 12-bit)
*Since it is integer arithmetic, multiply first then divide to reduce error.
*/
static uint32_t adc_to_mv(uint16_t adc)
{
return (uint32_t)adc * 3300u / 4095u;
}
int main(void)
{
uint16_t samples[5] = { 0u, 1024u, 2048u, 3072u, 4095u };
int i;
for (i = 0; i < 5; i++) {
printf("ADC=%u -> %lu mV\n",
samples[i], (unsigned long)adc_to_mv(samples[i]));
}
return 0;
}
Checking with Simulator
- Add
adc_valandmvto Watch and run. - If the input voltage can be set in
Peripherals → A/D Converter → ADC1(depends on the simulation version), change the value and observe the changes inDRandmv. - In ADC1's CR2, check that ADON=1, CAL=0 after calibration, and EOC=1 in SR after conversion.
- Expected behavior:
adc_val(04095) and3300) are updated proportionally to the input voltage.mv(0
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
ADC=0 -> 0 mV
ADC=1024 -> 825 mV
ADC=2048 -> 1650 mV
ADC=3072 -> 2475 mV
ADC=4095 -> 3300 mV
Common Mistakes
Q. It stops waiting for EOC without finishing the conversion.
A. Check if the ADC power (ADON) is turned on, and if the conversion has been started (ADON reset or SWSTART). Also, verify that the ADC clock (RCC_APB2ENR_ADC1EN) is active.
Q. The values are fluctuating and inaccurate.
A. ① F1 is inaccurate if calibration (RSTCAL/CAL) is skipped. ② If the sample time is short, high-impedance sources will cause large errors. ③ The ADC clock must not exceed 14MHz (check ADCPRE).
Q. The mV calculation results in 0.
A. If in adc/4095*3300 order, it will result in 0 in integers. Always multiply first, then divide: adc * 3300 / 4095. Also, multiply using 32-bit to avoid overflow ((uint32_t)adc * 3300).
Q. PA0 is set as input pull-up.
A. ADC input must be analog input (0x0). If digital input Buffer/Pull-up is enabled, measurements will be distorted.
Summary
- ADC converts analog voltage to 12-bit integers (0~4095).
- mV = adc × 3300 / 4095 (multiply then divide, calculated in 32-bit).
- ADC clock ≤14MHz — divide with ADCPRE.
- For F1, convert after calibration (RSTCAL/CAL), then read DR after EOC polling.
- Set ADC pins as analog input (0x0).
Try it yourself
Solve the assignment below. The reference solution is shown below.