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
- PWM resolution:
steps = f_timer / f_PWM, bits = log₂(steps) - Average PWM voltage:
V_avg = D × V_high - PWM-DAC ripple (1st-order RC):
ΔV ≈ V D (1 − D) / (f_PWM R C) - DMA half-transfer rate:
f_irq = 2 f_s / N_buf
Worked example: PWM resolution
Given: 72 MHz timer, 20 kHz PWM
Find: Steps and bits.
- 72 × 10⁶/20 × 10³ = 3600 steps
- log₂ 3600 = 11.8 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.
- ΔV ≈ 3.3 × 0.25/(20 000 × 0.001) = 41 mV
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
- Wrong: Software-timed ADC reads for DSP. — Right: Use timer triggers for jitter-free sampling.
- Wrong: PWM frequency in the audible range for motors. — Right: Use > 20 kHz to avoid whine.
Applications
- Motor control: Complementary PWM + synchronised ADC current sampling.
- Data acquisition: Timer-triggered ADC with DMA streaming.
- LED dimming & audio: High-frequency PWM.
Key takeaways
- Trigger ADCs from timers.
- PWM bits = log₂(f_timer/f_PWM).
- RC filters turn PWM into DC.
- DMA ping-pong for streaming.
Going further
- Injected ADC channels: Motor current sampling at PWM centre.
- Oversampling hardware: Extra ADC bits by averaging.
- Cache maintenance: Invalidate buffers on Cortex-M7.