ADC, PWM & DMA

Three peripherals that connect MCUs to the analog world efficiently. Learn MCU ADC configuration (sampling time, resolution, scan and continuous modes, triggers), PWM generation (frequency vs resolution trade-off, duty, complementary outputs), PWM-as-DAC filtering, and DMA with circular double buffers.

Motor controllers, power supplies, audio and data loggers all sample analog signals with the ADC, drive outputs with PWM, and move data with DMA so the CPU can focus on algorithms.

An analogy

DMA is a conveyor belt between the ADC and memory: the CPU doesn't carry each sample by hand, it just checks the full boxes at the end.

MCU ADCs

Typically 10–12-bit SAR ADCs with multiplexed channels. Settings: resolution, sample time (longer for high-impedance sources), scan across channels, continuous mode, and hardware triggers from timers for exact sampling rates.

PWM

Timer PWM sets frequency by ARR and duty by CCR. Higher PWM frequency reduces resolution for a given timer clock. Complementary outputs with dead-time drive half-bridges in motor control and converters.

PWM as a DAC

An RC low-pass filter averages PWM into a DC level; ripple falls with higher f_PWM and larger RC but response slows. Second-order filters improve ripple.

DMA

DMA channels move data peripheral ↔ memory with configurable widths, increments and circular mode. Double buffering or ping-pong enables continuous streaming. Mind cache coherency on Cortex-M7 and bus contention.

Formulas

Worked example: PWM resolution

Given: 72 MHz timer, 20 kHz PWM

Find: Steps and bits.

Answer: 3600 steps (≈ 11.8 bits)

Worked example: PWM-DAC ripple

Given: 3.3 V, D = 0.5, 20 kHz, RC = 1 ms

Find: Ripple.

Answer: ≈ 41 mV peak-to-peak

ADC + DMA double buffering (STM32 HAL)

#define N 1024
static uint16_t adc_buf[N];                 /* circular DMA buffer */

void start_sampling(void) {
    /* ADC triggered by TIM3 at 1 kHz ... 1 MHz; DMA in circular mode */
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, N);
    HAL_TIM_Base_Start(&htim3);
}

/* First half filled while DMA continues writing the second half */
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *h) { process(&adc_buf[0],   N / 2); }
/* Second half filled while DMA wraps to the first half */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *h)     { process(&adc_buf[N/2], N / 2); }

A hardware timer triggers conversions at an exact rate; DMA writes into a circular buffer and interrupts at half and full points, so the CPU processes one half while the other fills — no samples lost.

Common mistakes

Applications

Key takeaways

Going further