ADC and DAC

ADC (Analog-to-Digital Converter) turns a continuous voltage into a discrete digital number. DAC (Digital-to-Analog Converter) does the reverse. These are the bridge between the analog physical world and the digital MCU.

Why It Matters

Temperature sensors, microphones, light sensors, battery voltage monitors — anything that produces a continuous voltage needs an ADC to be read by software. DAC output drives audio, control voltages for PID loops, and waveform generation.

How It Works

ADC: Sampling Theorem (Nyquist)

To faithfully digitize a signal of frequency f, you must sample at at least 2f (the Nyquist rate). Sampling below this causes aliasing — the signal appears as a lower frequency that cannot be distinguished from the real one.

Signal:  1 kHz sine wave
Minimum sample rate:  2 kHz (Nyquist)
Practical sample rate: 5-10x the signal frequency for clean reconstruction

An anti-aliasing filter (low-pass RC or active filter using Op Amps) must be placed before the ADC input to remove frequencies above Nyquist. Without it, high-frequency noise folds into the measurement band.

Resolution and LSB

Resolution determines how finely the ADC divides the voltage range:

ResolutionLevelsLSB (at 3.3V Vref)
8-bit25612.9 mV
10-bit10243.22 mV
12-bit40960.806 mV
16-bit655360.050 mV

LSB (Least Significant Bit) = Vref / (2^N - 1). This is the smallest voltage change the ADC can detect.

Conversion formulas:

Digital value = (Vin / Vref) * (2^N - 1)
Voltage       = digital_value * Vref / (2^N - 1)

Example: Vin = 1.65V, Vref = 3.3V, 12-bit
  digital = (1.65 / 3.3) * 4095 = 2047
  voltage = 2047 * 3.3 / 4095  = 1.649V

SAR Conversion Process

Most MCU ADCs use Successive Approximation Register (SAR) architecture:

1. Sample-and-hold captures Vin
2. Compare Vin to Vref/2         -> MSB = 1 if Vin > Vref/2, else 0
3. Compare Vin to next threshold -> bit 11 = ?
4. ... repeat for each bit       (12 comparisons for 12-bit)
5. Result ready in SAR register

Total time = N clock cycles + sample time

SAR ADCs are fast (1-5 Msps on STM32), low power, and good enough for most embedded use. Delta-sigma ADCs are slower but achieve higher resolution (16-24 bit) for precision measurement.

ADC Configuration on STM32

Key configuration parameters:

  • Channel: which analog pin (ADC_IN0 = PA0, ADC_IN1 = PA1, etc.)
  • Sampling time: how many clock cycles the sample-and-hold capacitor charges. Longer = more accurate for high-impedance sources. STM32F4 offers 3 to 480 cycles per channel.
  • Sequence: ADCs can scan multiple channels in order, storing results via DMA
  • Trigger: software start, timer event, or external signal
// ADC1, channel 0 (PA0), 12-bit, software trigger
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
 
ADC1->CR1  = 0;                       // 12-bit (default), no scan
ADC1->CR2  = ADC_CR2_ADON;            // power on ADC
ADC1->SMPR2 = (7 << 0);               // 480 cycles sampling for CH0
ADC1->SQR3  = 0;                      // first conversion = channel 0
ADC1->SQR1  = 0;                      // 1 conversion in sequence
 
// Single conversion
ADC1->CR2 |= ADC_CR2_SWSTART;         // start
while (!(ADC1->SR & ADC_SR_EOC));     // wait for end of conversion
uint16_t raw = ADC1->DR;              // read 12-bit result (0-4095)
float voltage = raw * 3.3f / 4095.0f;

Signal Conditioning

The analog signal path before the ADC matters as much as the ADC itself:

Sensor --> [Voltage divider] --> [Anti-alias LPF] --> [Buffer amp] --> ADC pin
           (scale to 0-3.3V)    (fc < Nyquist/2)     (low impedance
                                                       drive for S&H)
  • Voltage divider: scale 0-12V battery voltage to 0-3.3V range. See Voltage Current Resistance.
  • Anti-aliasing filter: simple RC low-pass, cutoff below half the sample rate. See Capacitors and Inductors.
  • Buffer amplifier: op-amp in voltage follower config. The ADC sample-and-hold capacitor needs a low-impedance source to charge quickly during the sampling window.

DAC (Digital to Analog)

Outputs a voltage proportional to a digital value. STM32F4 has two 12-bit DAC channels.

// DAC channel 1 (PA4), output 1.65V
RCC->APB1ENR |= RCC_APB1ENR_DACEN;
 
DAC->CR  = DAC_CR_EN1;                // enable channel 1
DAC->DHR12R1 = 2048;                  // 12-bit right-aligned: 2048/4096 * 3.3V = 1.65V

DAC for Waveform Generation

Combine DAC with a timer interrupt to output a sine wave, sawtooth, or arbitrary waveform:

// 1 kHz sine wave using DAC + TIM6 interrupt
// Lookup table: 100 points per cycle -> TIM6 at 100 kHz
const uint16_t sine_lut[100] = { 2048, 2176, 2304, ... };  // precomputed
volatile uint8_t idx = 0;
 
void TIM6_DAC_IRQHandler(void) {
    TIM6->SR &= ~TIM_SR_UIF;
    DAC->DHR12R1 = sine_lut[idx];
    idx = (idx + 1) % 100;
}

PWM as a DAC Alternative

When no hardware DAC is available, PWM output through a low-pass RC filter approximates a DC voltage:

PWM (1 kHz, 50% duty) --> [10k R] --> [1uF C] --> ~1.65V DC
                                       fc = 1/(2*pi*RC) = 16 Hz

The RC filter cutoff must be well below the PWM frequency to smooth the output. Trade-off: slower response vs. less ripple.