ADC and DAC on bare metal

SkillDev tools

Use when configuring ADC sampling time, DMA-driven ADC, calibration, or DAC channel setup on bare-metal MCUs. Not for the DMA stream itself: use dma-baremetal.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the ADC and DAC on bare metal skill

What this skill tells your AI

The instructions your AI receives, as published by outlinedriven/outline-driven-development in .devin/skills/adc-dac-baremetal/SKILL.md and read by ahel’s review.

Contract

FieldBound contract
TriggerThe task is to sample an analog input, run the ADC continuously into a DMA buffer, calibrate the ADC after reset, or drive a DAC channel on an MCU without a vendor HAL.
AuthorityRead-only: emits register sequences, formulas, and diagnostics to chat; the user pastes them into their firmware. Rollback is not needed because no file is written. No remote mutation.
Side effectChat output only.
DoneThe guidance names the clock enable, pin mode, sampling time, calibration step, trigger, and readout for the target family, and every register bit is cited to the reference manual chapter it comes from.

Inputs

  • Target MCU family and part number (STM32F1, STM32F4, STM32G4, STM32L4, or another family with a reference manual at hand).
  • The analog pin and ADC channel, from the datasheet pin table.
  • Sample rate and source impedance of the signal.
  • Whether readout is polled, interrupt-driven, or DMA circular.
  • The ADC clock frequency after prescaling, from the clock tree.

Procedure

  1. Enable the ADC and GPIO clocks and put the pin in analog mode. The ADC reads nothing from a pin still in digital input mode. Done when: the RCC enable bit for the ADC bus and the GPIO port are set and MODER for the pin reads analog (11).

  2. Power the ADC on and wait the stabilization time the datasheet gives as tSTAB. On STM32F4 this is ADC_CR2_ADON followed by a short wait; the wait is a datasheet number, not a magic loop count. Done when: ADON is set and the wait matches the datasheet value for the ADC clock in use.

    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    ADC1->CR2 |= ADC_CR2_ADON;
    delay_us(ADC_TSTAB_US);   /* tSTAB from the datasheet, not a guessed loop */
    
  3. Calibrate where the family requires it. STM32F1 exposes ADC_CR2_RSTCAL then ADC_CR2_CAL in CR2 and the firmware polls each bit until hardware clears it. STM32L4 and STM32G4 expose ADCAL in ADC_CR with the ADC disabled. STM32F4 has no software calibration step; the part is factory calibrated. Done when: the family's calibration sequence has run, or the reference manual confirms the family has none.

  4. Set the sampling time from the datasheet graph of sampling time against source impedance. On STM32F4 SMPR2 field SMP0 value 100 (ADC_SMPR2_SMP0_2) selects 84 ADC clock cycles for channel 0; the other encodings are 3, 15, 28, 56, 112, 144, and 480 cycles. A high-impedance source needs the longer settings. Done when: the chosen sampling time covers the source impedance at the ADC clock in use.

  5. Select the channel sequence and start one conversion, then poll end-of-conversion and read the data register. Done when: a polled single conversion returns a plausible value for a known input (mid-rail or ground).

    ADC1->SMPR2 |= ADC_SMPR2_SMP0_2;   /* 84 cycles on channel 0 */
    ADC1->SQR3   = 0;                  /* one conversion, channel 0 */
    ADC1->CR2   |= ADC_CR2_SWSTART;
    while (!(ADC1->SR & ADC_SR_EOC)) { }
    uint16_t sample = (uint16_t)ADC1->DR;
    
  6. For continuous sampling, enable continuous mode and the ADC DMA request, then configure the DMA stream in circular mode with a half-word data size into a uint16_t buffer. On STM32F4, ADC1 maps to DMA2 Stream 0 or Stream 4, channel 0; confirm the request matrix in the DMA chapter. Process the first half of the buffer on the half-transfer interrupt and the second half on the transfer-complete interrupt. The stream configuration itself belongs to dma-baremetal. Done when: the buffer fills continuously and the two interrupts alternate.

    ADC1->CR2 |= ADC_CR2_DMA | ADC_CR2_CONT;
    /* stream configured per dma-baremetal, then: */
    DMA2_Stream0->CR |= DMA_SxCR_EN;
    ADC1->CR2 |= ADC_CR2_SWSTART;
    
  7. For a timer-paced sample rate, select the timer TRGO as the external trigger in CR2 (EXTSEL, EXTEN) instead of continuous mode, so the sample period comes from the timer and not from conversion time. Timer setup belongs to timers-pwm-baremetal. Done when: the sample rate equals the timer update rate.

  8. For DAC output, enable the DAC clock, set the output pin to analog mode, enable the channel, and write the data holding register. On STM32F4 the DAC sits on APB1 and channel 1 drives PA4; DHR12R1 takes a right-aligned 12-bit value, so 2048 is mid-scale against VREF+. Done when: a meter on the pin reads half of VREF+ for the value 2048.

    RCC->APB1ENR |= RCC_APB1ENR_DACEN;
    DAC->CR      |= DAC_CR_EN1;
    DAC->DHR12R1  = 2048;
    
  9. Convert counts to volts with the measured reference, not the nominal one: V = counts * VREF / 4096 for a 12-bit result. Many families provide an internal reference channel (VREFINT) with a factory value stored in system memory; use it to compute the actual VDDA. Done when: a known input converts to within the datasheet accuracy.

Failure and recovery

SymptomCauseFix
Noisy readingsSampling time too short for the source impedanceIncrease the SMP field; check the datasheet impedance graph.
EOC never setsADC clock not enabled or ADON not setSet the RCC enable bit, then ADON, then wait tSTAB.
Reading is offset or scaled wrongVDDA is not the assumed valueMeasure VDDA or compute it from VREFINT.
DMA buffer holds interleaved garbageData size mismatch or unaligned bufferUse a uint16_t buffer with half-word DMA size on both sides.
DAC pin stays at 0 V or railsPin not in analog mode, or channel not enabledSet MODER to analog and EN1 in DAC_CR.

Output

A register-level sequence for the target family covering clock enable, pin mode, calibration, sampling time, channel sequence, trigger or DMA setup, readout, and the counts-to-volts conversion, with each bit named after its reference manual definition.

Signals

GitHub stars
52
Forks
9
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
adc-dac-baremetal
Source
github.com/outlinedriven/outline-driven-development