DAC Architecture Overview
The STM32 DAC block contains up to two independent output channels (DAC1 and DAC2 on larger packages). Each channel has:
- A 12-bit right-aligned data holding register (
DAC_DHR12R1,DAC_DHR12R2) - An output data register (
DAC_DOR1,DAC_DOR2) that actually drives the pin - A trigger selection mux (timer, software, or external) in
DAC_CR - DMA capability per channel via
DAC_DHR12RD(dual) orDAC_DHR12R1(single)
The key insight: the DAC does not convert continuously. Each conversion is triggered by an event — timer update, timer TRGO, external line, or a software write to the SWTRIG bit. Between triggers, the output holds the last value. You set the trigger source in DAC_CR->TSELx[2:0] and enable it with TENx.
Why Timer + DMA?
For waveform generation, the timer defines the sample rate. Each timer update event tells the DAC to latch DAC_DHR12R1 into DAC_DOR1 and start a new conversion. The DAC conversion itself takes about 3 µs (typical for F4 at 84 MHz APB1), so the maximum sample rate is around 330 kS/s with the 12-bit settling time.
The DMA sits between a RAM buffer holding your waveform samples and the DAC data register. Configured in circular mode, it automatically reloads from the start when it reaches the end of the buffer. The CPU writes the waveform table once, enables everything, and never touches the DAC again until it needs to change the waveform.
Register-Level Configuration (STM32F4 Example)
I will use DAC channel 1 on PA4, TIM6 as the update trigger, and DMA1 Stream 5 Channel 7 on STM32F407. Every STM32 variant has a slightly different DMA mapping — always check the DMA request mapping table in the reference manual.
Step 1 — Clock Gating
RCC->APB1ENR |= RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
// DAC output pin: PA4, analogue mode
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER |= (3 << 8); // PA4 = analog mode
Step 2 — Timer Configuration (TIM6)
TIM6 is a basic timer, purpose-built as a DAC trigger source. Set the prescaler and period to match your desired sample rate.
// Desired sample rate: 48 kHz (audio-friendly)
// APB1 timer clock: 84 MHz
// TIM6 update frequency = 84 MHz / (PSC+1) / (ARR+1)
TIM6->PSC = 0; // prescaler = 1
TIM6->ARR = (84000000 / 48000) - 1; // auto-reload for 48 kHz
TIM6->CR2 |= TIM_CR2_MMS_1; // Master mode: update event on TRGO
TIM6->CR1 |= TIM_CR1_CEN; // start timer
The MMS_1 bit configures TIM6 to output its update event (UEV) on the TRGO line. On STM32F4, TIM6 TRGO is hardwired to the DAC trigger input when TSEL1 = 000.
Step 3 — DAC Trigger Configuration
DAC->CR = 0; // start from known state
// TSEL1 = 000 (TIM6 TRGO), TEN1 = 1 (enable trigger),
// DMAEN1 = 1 (enable DMA request), EN1 = 1 (enable DAC)
DAC->CR = DAC_CR_TSEL1_0 << 0 // TSEL1 = 000 → keep default
| DAC_CR_TEN1 // timer trigger enable
| DAC_CR_DMAEN1 // DMA request enable
| DAC_CR_EN1; // DAC channel 1 enable
The DAC needs a stabilisation time after EN1 is set. Wait about 1 µs (10 NOPs at 168 MHz) before starting the timer.
Step 4 — DMA Configuration (Circular Mode)
The DMA transfers one 16-bit half-word (the 12-bit DAC value, left-aligned in the upper bits) from RAM to DAC_DHR12R1 on each request. The DAC generates a DMA request after each conversion completes.
#define WAVEFORM_SIZE 256
static uint16_t waveform[WAVEFORM_SIZE]; // pre-computed sine table
// DMA1 Stream 5, channel 7 (DAC1 mapping on F407)
DMA1_Stream5->CR = 0; // disable during configuration
while (DMA1_Stream5->CR & DMA_SxCR_EN);
DMA1_Stream5->PAR = (uint32_t)&DAC->DHR12R1;
DMA1_Stream5->M0AR = (uint32_t)waveform;
DMA1_Stream5->NDTR = WAVEFORM_SIZE;
DMA1_Stream5->FCR = DMA_FCR_DMDIS // direct mode (no FIFO)
| DMA_FCR_FTH_0; // FIFO threshold 1/4
DMA1_Stream5->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 // channel 7
| DMA_SxCR_MSIZE_0 // memory size = 16-bit
| DMA_SxCR_PSIZE_0 // peripheral size = 16-bit
| DMA_SxCR_MINC // memory increment on
| DMA_SxCR_CIRC // circular mode
| DMA_SxCR_DIR_0 // memory-to-peripheral
| DMA_SxCR_EN; // enable DMA
Critical detail: Without CIRC, the DMA transfers the waveform once and stops. The DAC continues outputting the last sample forever. With CIRC, it wraps around indefinitely.
Step 5 — Compute the Sine Wave Table
for (int i = 0; i < WAVEFORM_SIZE; i++) {
float sample = sinf(2.0f * M_PI * i / WAVEFORM_SIZE);
// Map [-1.0, 1.0] to [0, 4095], left-align in uint16
uint32_t val = (uint32_t)((sample + 1.0f) * 2047.5f);
if (val > 4095) val = 4095;
waveform[i] = (uint16_t)(val << 4); // left-aligned 12-bit
}
The left alignment (<< 4) is important: the DAC expects data in bits [15:4] of the 16-bit register for 12-bit mode. Writing into bits [3:0] has no effect.
Practical Example: 1 kHz Sine at 48 kS/s
With a 48 kHz sample rate and a 256-sample waveform table, the generated sine frequency is:
f_sine = f_sample / WAVEFORM_SIZE = 48000 / 256 = 187.5 Hz
To get exactly 1 kHz:
WAVEFORM_SIZE = 48; // 48000 / 1000 = 48 samples per period
Or keep 256 samples and adjust the timer to fire at 256 kHz:
TIM6->ARR = (84000000 / (256 * 1000)) - 1; // 256 kHz → 1 kHz output
The output amplitude on PA4 is:
Vout = (Vref / 4096) * sample_12bit
With Vref = 3.3 V and a full-scale sine, the peak-to-peak amplitude is about 3.3 V centred at 1.65 V. Add a DC-blocking capacitor if you need a bipolar signal.
Dual-Channel Operation (Lissajous, I/Q)
For two simultaneous analog outputs, enable DAC2 on PA5, configure DMA1 Stream 6 (channel 7) for the second channel, and use the dual 12-bit register DAC_DHR12RD. Both channels convert on the same timer trigger:
typedef struct {
uint16_t ch1; // left-aligned 12-bit, DAC1
uint16_t ch2; // left-aligned 12-bit, DAC2
} __attribute__((packed)) dual_dac_t;
dual_dac_t dual_buf[256];
// Fill ch1 and ch2 with sine / cosine for quadrature output
DMA1_Stream5->PAR = (uint32_t)&DAC->DHR12RD; // dual register
DMA1_Stream5->CR |= DMA_SxCR_MSIZE_0; // still 16-bit (two transfers per trigger)
The DAC converts both channels back-to-back on a single trigger event. This is the STM32 approach to generating I/Q signals or Lissajous figures without external DACs.
Practical Checklist
| Check | Why |
|---|---|
| DAC clock enabled in RCC? | Writes to DAC registers silently ignored without clock |
| Pin in analog mode? | Digital output drivers fight the DAC output |
| TEN1 = 1 and TSEL1 matches your timer? | Without this, the DAC ignores timer events |
| DMA channel matches the request mapping table? | Wrong channel = no DMA requests after DAC conversion |
| CIRC bit set? | Without circular mode, one-shot output then silence |
| Data left-aligned in register? | Right-alignment needs different register (DAC_DHR12R1 vs DAC_DHR12L1) |
| Timer prescaler/ARR set for target sample rate? | Check with oscilloscope or TIMx->CNT |
| DAC stabilisation wait after EN1? | Without ~1 µs delay, first samples may be invalid |
How I Would Approach This on a Client Project
On a recent project using STM32G474 for a motor control application, we needed two synchronised analog outputs — one for the torque reference, one for a shunt current monitor test signal. The register-level approach described above worked directly on G4 with only a DMA channel number change (DMAMUX remapping).
The gotcha that cost us half a day: the G4 DAC has a sample-and-hold mode that is enabled by default in some CubeMX configurations. If DAC_MCR->MODE1 is set to 10 (sample-and-hold with external capacitor), the output is internally disconnected from the pin. Always check DAC_MCR when the DAC is enabled but the pin stays at 0 V.
On L4/L5 series, the DAC also offers a waveform generator built-in (noise/triangle) and a buffer calibration mode that reduces offset error. For precision applications (< 10 mV tolerance), run the calibration sequence in the reference manual before enabling the output.
If your client requires a sample rate above 500 kS/s, consider using the DAC HR Timer on the STM32H7 series, which can trigger up to 1 MS/s through the H7's high-resolution timer (HRTIM).
Sources consulted:
- STM32F4 Reference Manual (RM0090) — Chapter 13: DAC, Chapter 9: DMA
- STM32G4 Reference Manual (RM0440) — Chapter 15: DAC
- STM32L4 Reference Manual (RM0351) — Chapter 12: DAC
- ST Application Note AN3126 — Audio and waveform generation with the DAC
- ST Application Note AN4566 — Using the STM32 DAC to get maximum performance
- ARM Cortex-M4 Generic User Guide — DMA controller
- CMSIS-Core 5.6.0 —
stm32f407xx.hregister definitions

💬 Comments
Have questions, corrections, or experience with STM32 DAC + DMA setups? Drop me an email. Include the article slug in the subject line.