Fundamentals 13 min read

Mastering STM32 ADC: From Sampling to Accurate Voltage Measurement

This article explains the fundamental concepts and step‑by‑step workflow of ADC conversion in embedded systems, covering sampling, hold, quantization, encoding, and output stages, and provides detailed STM32 HAL code examples along with tips for improving accuracy.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Mastering STM32 ADC: From Sampling to Accurate Voltage Measurement

1. Basic Concept of ADC

ADC (Analog‑to‑Digital Converter) transforms continuous analog signals into discrete digital values so that a microcontroller can process real‑world measurements such as sensor voltages or battery levels.

For example, a temperature sensor may output any voltage between 0 V and 3.3 V, but the MCU can only handle discrete numbers like 0, 1, 2, 3. The ADC bridges this gap by converting, say, 2.5 V into a digital code such as 3100 (assuming a 12‑bit ADC with a 3.3 V reference).

2. Main ADC Conversion Process

2.1 Sampling Phase

The ADC first captures the input signal using an internal sampling capacitor. When the sampling switch closes, the capacitor charges until its voltage equals the input voltage. The required time, called the sampling time, directly affects conversion accuracy.

ADC_ChannelConfTypeDef sConfig = {0};

sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;  // 239.5 clock cycles
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;

if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    Error_Handler();
}

Longer sampling improves accuracy but slows conversion; the optimal time depends on source impedance and required precision.

2.2 Hold Phase

After sampling, the switch opens and the capacitor holds the captured voltage, freezing the value for the remaining conversion steps. This prevents changes in the external signal from affecting the result.

2.3 Quantization Phase

Quantization maps the held analog voltage to a discrete digital code. A 12‑bit ADC divides the reference range into 4096 levels; each level represents ≈0.8 mV when Vref = 3.3 V. The smallest voltage step is the LSB, and quantization error is bounded by ±0.5 LSB.

Theoretical digital value = 1.65 V / 3.3 V × 4096 = 2048

Actual 1.6504 V → 2048.2 → 2048 after rounding

Actual 1.6508 V → 2048.5 → may become 2048 or 2049

2.4 Encoding Phase

Encoding converts the quantized number into binary code. For a 12‑bit result of 2048, the binary representation is 100000000000. Most STM32 ADCs use straight binary encoding; some devices support two's‑complement for signed measurements.

2.5 Output Phase

After encoding, the result is stored in the ADC data register and a conversion‑complete flag or interrupt notifies the CPU. The CPU can then read the value via polling, interrupt, or DMA.

3. STM32 ADC Practical Example

The following HAL‑based code demonstrates a complete single‑conversion workflow on an STM32 MCU.

// ADC initialization
void MX_ADC1_Init(void) {
    ADC_ChannelConfTypeDef sConfig = {0};
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 4‑fold prescale
    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.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    if (HAL_ADC_Init(&hadc1) != HAL_OK) {
        Error_Handler();
    }
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
        Error_Handler();
    }
}

// Read ADC value
uint32_t ADC_Read(void) {
    uint32_t value = 0;
    HAL_ADC_Start(&hadc1);
    if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
        value = HAL_ADC_GetValue(&hadc1);
    }
    HAL_ADC_Stop(&hadc1);
    return value;
}

// Convert raw value to millivolts (Vref = 3300 mV, 12‑bit)
uint32_t ADC_ToVoltage(uint32_t raw) {
    return (raw * 3300) / 4096;
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();
    while (1) {
        uint32_t adc = ADC_Read();
        uint32_t voltage = ADC_ToVoltage(adc);
        printf("ADC Value: %lu, Voltage: %lu mV
", adc, voltage);
        HAL_Delay(1000);
    }
}

This code shows the full flow: start conversion, poll for completion, read the result, convert to voltage, and repeat.

4. Factors Influencing ADC Accuracy

4.1 Reference Voltage Stability

Since the ADC result is a ratio to the reference voltage, any fluctuation in Vref directly causes measurement drift. High‑precision applications should use a dedicated voltage‑reference IC instead of the supply rail.

4.2 Source Impedance

A high source impedance slows the charging of the sampling capacitor, requiring longer sampling times. Keeping source impedance below ~10 kΩ or adding a buffer op‑amp improves accuracy.

4.3 PCB Layout and Grounding

Analog traces are sensitive to noise. Separate analog and digital grounds, use a single‑point ground, keep analog inputs short, and place decoupling capacitors near the ADC pins to reduce interference.

4.4 Software Filtering

Even with perfect hardware, the raw ADC result can jitter. Applying software techniques such as averaging, median filtering, or moving‑average smoothing mitigates this noise.

// Simple averaging filter
uint32_t ADC_Read_Average(uint8_t times) {
    uint32_t sum = 0;
    for (uint8_t i = 0; i < times; i++) {
        sum += ADC_Read();
        HAL_Delay(5); // 5 ms between samples
    }
    return sum / times;
}

5. Summary

ADC conversion consists of five stages: sampling, hold, quantization, encoding, and output. Proper configuration of resolution, sampling time, and conversion mode, together with stable reference voltage, low source impedance, careful PCB layout, and optional software filtering, yields reliable and accurate measurements essential for embedded development.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

embeddedMicrocontrollerSTM32ADCAnalog-to-Digital
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.