What is STM32F407 DAC Deep Dive-Embedded C Training for Freshers

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Tutorial 17 — Digital to Analog Conversion

STM32F407 DAC Deep Dive

Master the dual-channel 12-bit DAC — registers explained bit-by-bit, output modes, noise/triangle wave generation, and DMA-driven sine wave output with bare-metal C.

12-bit Resolution2 ChannelsPA4/PA5 Output Pins0–VDDA Output RangeDMA Waveform Support
===== 1: OVERVIEW =====

01 DAC Overview

The STM32F407 contains a 12-bit dual-channel DAC with two independent output channels. Unlike the ADC (which converts voltage to number), the DAC converts a digital value to an analog voltage on a GPIO output pin. The output voltage is:

Vout = VDDA × DHR / 4095

DAC Key Features
  • Two independent 12-bit channels (Channel 1 on PA4, Channel 2 on PA5)
  • Support for 8-bit or 12-bit data (left or right aligned)
  • Output buffer (built-in op-amp) for driving low-impedance loads
  • Hardware trigger sources: TIM2/4/5/6/7/8 TRGO, EXTI9, software
  • DMA support for automatic waveform generation
  • Built-in LFSR noise generator and triangle wave generator
  • Simultaneous dual-channel update via DHR12RD register
Output Buffer: The DAC output buffer (enabled by default — BOFF=0) allows driving loads down to 5kΩ / 50pF. If you disable the buffer (BOFF=1), the output impedance is high (~15kΩ) — you need an external op-amp to drive any load.

DAC Internal Architecture

STM32F407 DAC Architecture
DAC (0x40007400)
CPU/DMA
DHR12R1
(holding register)
(trigger?)
(latch on trigger)
DOR1
(output reg)
R-2R ladder
+ buffer
PA4
CPU/DMA
DHR12R2
(holding register)
(trigger?)
(latch on trigger)
DOR2
(output reg)
R-2R ladder
+ buffer
PA5
Internal generators:
LFSR noise
Triangle wave
APB1 bus
(RCC_APB1ENR DAC bit 29)
APB1 = 42 MHz

Output Pins

ChannelPinGPIO ModeNotes
DAC1_OUTPA4Analog (MODER=11)Shared with SPI1_NSS, ADC1_IN4. Disable pull-up/down.
DAC2_OUTPA5Analog (MODER=11)Shared with SPI1_SCK, ADC1_IN5. Disable pull-up/down.
GPIO for DAC: Set MODER=11 (analog). No need to set AFRL (no alternate function needed for DAC — it’s directly controlled by the DAC peripheral). Also disable any pull resistors: PUPDR=00.
===== 2: REGISTERS =====

02 DAC Register Map

All DAC registers base address: 0x40007400 (on APB1).

OffsetRegisterDescription
0x00DAC_CRControl register — enable, trigger, wave, buffer, DMA
0x04DAC_SWTRIGRSoftware trigger register — write 1 to trigger CH1/CH2
0x08DAC_DHR12R112-bit right-aligned data for CH1
0x0CDAC_DHR12L112-bit left-aligned data for CH1
0x10DAC_DHR8R18-bit right-aligned data for CH1
0x14DAC_DHR12R212-bit right-aligned data for CH2
0x18DAC_DHR12L212-bit left-aligned data for CH2
0x1CDAC_DHR8R28-bit right-aligned data for CH2
0x20DAC_DHR12RD12-bit right-aligned simultaneous data (CH1[15:0] | CH2[31:16])
0x24DAC_DHR12LD12-bit left-aligned simultaneous data
0x28DAC_DHR8RD8-bit right-aligned simultaneous data
0x2CDAC_DOR1Output register CH1 (read-only, actual DAC value)
0x30DAC_DOR2Output register CH2 (read-only)
0x34DAC_SRStatus register — DMA underrun flags

DAC_CR — Control Register (Offset 0x00)

DAC_CR controls both channels. Bits 15:0 configure Channel 1, bits 31:16 configure Channel 2 (same layout, offset by 16).

DAC_CR @ 0x40007400 (showing CH1 bits 15:0, CH2 bits 31:16 mirror)
31DU2
30DMAE2
29MMP3
28MMP2
27MMP1
26MMP0
25WV21
24WV20
23TS22
22TS21
21TS20
20TEN2
19BOF2
18EN2
17r
16r
15r
14r
13DU1
12DMAE1
11MMP3
10MMP2
9MMP1
8MMP0
7WV11
6WV10
5TS12
4TS11
3TS10
2TEN1
1BOF1
0EN1
Bit and field descriptions
Bit / FieldDescription
31 DU2DMAUDRIE2 (bit 29 = CH2)
30 DMAE2DMAEN2 — DMA enable for CH2
29 MMP3MAMP2[3] bit 27 — Wave amplitude for CH2
28 MMP2MAMP2[2]
27 MMP1MAMP2[1]
26 MMP0MAMP2[0]
25 WV21WAVE2[1] bits 23:22 — Wave type CH2: 00=off, 01=noise, 10=triangle
24 WV20WAVE2[0]
23 TS22TSEL2[2] bits 21:19 — Trigger source CH2
22 TS21TSEL2[1]
21 TS20TSEL2[0] — 000=TIM6, 111=SW
20 TEN2TEN2 — Trigger enable CH2:
19 BOF2BOFF2 — Buffer off CH2:
18 EN2EN2 — Enable DAC Channel 2
17 rReserved
13 DU1DMAUDRIE1 (bit 13) — DMA underrun interrupt enable for CH1
12 DMAE1DMAEN1 — DMA enable CH1: 1=DMA requests after each trigger
11 MMP3MAMP1[3] bits 11:8 — Wave amplitude: 0000=1LSB, 1011=4095
10 MMP2MAMP1[2]
9 MMP1MAMP1[1]
8 MMP0MAMP1[0]
7 WV11WAVE1[1] bits 7:6 — Wave type CH1: 00=disabled, 01=noise LFSR, 10=triangle
6 WV10WAVE1[0]
5 TS12TSEL1[2] bits 5:3 — Trigger source: 000=TIM6, 001=TIM8, 100=TIM2, 111=SW
4 TS11TSEL1[1]
3 TS10TSEL1[0]
2 TEN1TEN1 — Trigger enable CH1:
1 BOF1BOFF1 — Output buffer disable CH1:
0 EN1EN1 — Enable DAC Channel 1:
Bits (CH1/CH2)NameDescription
0 / 16EN1/EN2Channel enable: 1=DAC channel on
1 / 17BOFF1/BOFF2Output buffer disable: 0=buffer ON (drives loads), 1=buffer OFF (high-Z output)
2 / 18TEN1/TEN2Trigger enable: 0=DHR→DOR immediately, 1=DHR→DOR on trigger event
5:3 / 21:19TSEL1/TSEL2Trigger selection: 000=TIM6, 001=TIM8, 010=TIM7, 011=TIM5, 100=TIM2, 101=TIM4, 110=EXTI9, 111=SW trigger
7:6 / 23:22WAVE1/WAVE2Wave generation: 00=disabled, 01=noise wave, 10=triangle wave, 11=reserved
11:8 / 27:24MAMP1/MAMP2Wave amplitude: 0000=1LSB, …, 1011=4095 (full scale). Controls LFSR mask or triangle max value.
12 / 28DMAEN1/DMAEN2DMA enable: 1=DMA request generated when DHR register is updated
13 / 29DMAUDRIE1/2DMA underrun interrupt enable: fires if DMA underrun occurs

DHR Registers — Data Holding Registers

You write your desired output value to a DHR (Data Holding Register). When a trigger occurs (or immediately if TEN=0), the value is transferred to DOR and the analog output is updated. There are 6 DHR variants:

RegisterFormatBits UsedValue Range
DHR12R1/212-bit right-aligned[11:0]0–4095
DHR12L1/212-bit left-aligned[15:4]0–65520 (LSB always 0)
DHR8R1/28-bit right-aligned[7:0]0–255 (mapped to full scale)
DHR12RDDual 12-bit rightCH1[11:0], CH2[27:16]Simultaneous update

DAC_DOR — Data Output Register

Read-only. DOR reflects the value actually driving the DAC converter at any moment. After writing DHR, if TEN=0, DOR updates within one APB1 cycle. With TEN=1, DOR updates on the next trigger edge. You rarely need to read DOR in practice.

DAC_SR — Status Register (Offset 0x34)

BitNameDescription
13DMAUDR1DMA underrun CH1: Set when DMA fails to provide new data before DAC needs it. Write 1 to clear.
29DMAUDR2DMA underrun CH2: Same for channel 2.
===== 3: SIMPLE DC OUTPUT =====

03 Simple DC Output

The simplest DAC use case: write a constant value to DHR12R1. The DAC immediately outputs a fixed voltage. No trigger, no DMA needed.

#include <stdint.h>

/* ── DAC Register Definitions ── */
#define RCC_APB1ENR   (*(volatile uint32_t *)0x40023840)
#define RCC_AHB1ENR   (*(volatile uint32_t *)0x40023830)
#define GPIOA_MODER   (*(volatile uint32_t *)0x40020000)
#define GPIOA_PUPDR   (*(volatile uint32_t *)0x4002000C)

#define DAC_CR        (*(volatile uint32_t *)0x40007400)
#define DAC_DHR12R1   (*(volatile uint32_t *)0x40007408)
#define DAC_DHR12R2   (*(volatile uint32_t *)0x40007414)
#define DAC_DOR1      (*(volatile uint32_t *)0x4000742C)
#define DAC_DOR2      (*(volatile uint32_t *)0x40007430)

/* ── Initialize DAC Channel 1 (PA4) ── */
void DAC1_Init(void) {
    /* 1. Enable clocks: GPIOA and DAC */
    RCC_AHB1ENR |= (1 << 0);   /* GPIOAEN */
    RCC_APB1ENR |= (1 << 29);  /* DACEN bit 29 */

    /* 2. PA4 → Analog mode (no AFRL needed) */
    GPIOA_MODER |= (0x3 << 8);    /* bits 9:8 = 11 → analog */
    GPIOA_PUPDR &= ~(0x3 << 8);  /* no pull-up/pull-down */

    /* 3. Configure DAC_CR for CH1:
          EN1=1 (enable), BOFF1=0 (buffer on),
          TEN1=0 (no trigger — DHR→DOR immediately),
          WAVE1=00 (no wave gen), DMAEN1=0 */
    DAC_CR &= ~(0xFFFF);   /* clear CH1 bits */
    DAC_CR |=  (1 << 0);   /* EN1 = 1 */
    /* BOFF1=0 (leave), TEN1=0 (leave) */
}

/* ── Set DAC1 output voltage ──
   val: 0–4095 (0V to VDDA=3.3V)
   mV: pass millivolts, function converts: val = mV*4095/3300 */
void DAC1_Set(uint16_t val) {
    DAC_DHR12R1 = val & 0x0FFF;
}

void DAC1_SetVoltage_mV(uint32_t mV) {
    uint16_t val = (uint16_t)((mV * 4095) / 3300);
    DAC1_Set(val);
}

int main(void) {
    DAC1_Init();

    /* Output 1.65V (mid-scale) */
    DAC1_SetVoltage_mV(1650);

    while (1) {
        /* Measure PA4 with a voltmeter — should read ~1.65V */
    }
}
No trigger mode (TEN=0): When TEN1=0, any write to DHR12R1 is immediately reflected in DOR1 within 1 APB1 clock cycle, no triggering needed. The analog output updates as fast as you write.
===== 4: TRIGGERED OUTPUT =====

04 Triggered Output

With TEN=1, the DHR value is latched but not sent to the output until a trigger event occurs. This lets you pre-load the next value while the current one is still active — essential for precise timing (e.g., synchronized with a PWM period).

C — Software trigger example

#define DAC_SWTRIGR   (*(volatile uint32_t *)0x40007404)

/* Configure CH1 with software trigger */
void DAC1_Init_SWTrig(void) {
    RCC_AHB1ENR |= (1<<0);
    RCC_APB1ENR |= (1<<29);
    GPIOA_MODER |= (3<<8);
    GPIOA_PUPDR &= ~(3<<8);

    DAC_CR = (1<<0) |   /* EN1 */
             (1<<2) |   /* TEN1 = trigger enable */
             (7<<3);    /* TSEL1 = 111 = software trigger */
}

/* Output value and trigger immediately */
void DAC1_SWUpdate(uint16_t val) {
    DAC_DHR12R1 = val & 0xFFF;
    DAC_SWTRIGR = (1<<0);  /* SWTRIG1 — fire! self-clearing */
}

/* Staircase ramp using software trigger */
int main(void) {
    DAC1_Init_SWTrig();
    uint16_t step = 0;
    while (1) {
        DAC1_SWUpdate(step);
        step = (step + 256) & 0xFFF;  /* 16 steps of 256 */
        for (volatile int i=0;i<50000;i++); /* delay */
    }
}
Timer Trigger Configuration (TIM6)

TIM6 is the simplest timer for DAC triggering (no output capture/PWM, just TRGO). Configure TIM6 as a periodic trigger:

/* TIM6: APB1 = 42MHz → 84MHz timer clock */
/* Want 44.1 kHz trigger for audio: PSC=0, ARR = 84MHz/44100 - 1 = 1904 */
#define RCC_APB1ENR  (*(volatile uint32_t *)0x40023840)
#define TIM6_CR1     (*(volatile uint32_t *)0x40001000)
#define TIM6_CR2     (*(volatile uint32_t *)0x40001004)
#define TIM6_PSC     (*(volatile uint32_t *)0x40001028)
#define TIM6_ARR     (*(volatile uint32_t *)0x4000102C)
#define TIM6_EGR     (*(volatile uint32_t *)0x40001014)

void TIM6_Init_44100Hz(void) {
    RCC_APB1ENR |= (1<<4);  /* TIM6EN bit 4 */

    TIM6_CR1  = 0;        /* basic upcounter */
    TIM6_CR2  = (2<<4);  /* MMS=010: TRGO on Update event */
    TIM6_PSC  = 0;        /* no prescaler → 84 MHz */
    TIM6_ARR  = 1904;     /* 84MHz / 1904+1 ≈ 44.1 kHz */

    TIM6_EGR  = (1<<0);  /* UG: force reload */
    TIM6_CR1 |= (1<<0);  /* CEN: start timer */
}

/* DAC CH1 triggered by TIM6 TRGO */
void DAC1_Init_TIM6Trig(void) {
    GPIOA_MODER |= (3<<8);
    GPIOA_PUPDR &= ~(3<<8);

    /* TSEL1=000=TIM6 TRGO, TEN1=1, EN1=1 */
    DAC_CR = (1<<0)|(1<<2)|(0<<3);
}
===== 5: NOISE WAVE =====

05 LFSR Noise Wave Generator

The DAC has a built-in 12-bit LFSR (Linear Feedback Shift Register) pseudo-random noise generator. Set WAVE1 = 01 to add LFSR noise on top of the DHR value. MAMP1 controls the noise amplitude mask.

MAMP[3:0]LFSR MaskPeak-to-Peak Range
00000x0012 LSB (≈1.6mV)
00010x0034 LSB
00100x0078 LSB
00110x00F16 LSB
01000x01F32 LSB
01010x03F64 LSB
01100x07F128 LSB
01110x0FF256 LSB
10000x1FF512 LSB
10010x3FF1024 LSB
10100x7FF2048 LSB
10110xFFF4096 LSB (full scale)

C — LFSR noise generator on PA4

void DAC1_Noise_Init(void) {
    RCC_AHB1ENR |= (1<<0);
    RCC_APB1ENR |= (1<<29);
    GPIOA_MODER |= (3<<8);
    GPIOA_PUPDR &= ~(3<<8);

    /* Need TIM6 to clock the LFSR — configure 10kHz */
    RCC_APB1ENR |= (1<<4);  /* TIM6EN */
    volatile uint32_t *TIM6_CR2 = (volatile uint32_t *)(0x40001004);
    volatile uint32_t *TIM6_PSC = (volatile uint32_t *)(0x40001028);
    volatile uint32_t *TIM6_ARR = (volatile uint32_t *)(0x4000102C);
    volatile uint32_t *TIM6_EGR = (volatile uint32_t *)(0x40001014);
    volatile uint32_t *TIM6_CR1 = (volatile uint32_t *)(0x40001000);
    *TIM6_CR2 = (2<<4);   /* TRGO on update */
    *TIM6_PSC = 0;
    *TIM6_ARR = 8399;     /* 84MHz / 8400 = 10kHz update rate */
    *TIM6_EGR = 1; *TIM6_CR1 = 1;

    /* DAC_CR: EN1, TEN1 (TIM6 TRGO), WAVE1=01 (noise), MAMP1=1011 (full) */
    DAC_CR = (1<<0) |   /* EN1 */
             (1<<2) |   /* TEN1 */
             (0<<3) |   /* TSEL1 = 000 = TIM6 */
             (1<<6) |   /* WAVE1 = 01 = noise (bit 6) */
             (0xB<<8);  /* MAMP1 = 1011 = full amplitude */

    /* Set DC offset = mid-scale so noise is centered */
    DAC_DHR12R1 = 2048;  /* base value; noise added on top */
}
===== 6: TRIANGLE WAVE =====

06 Hardware Triangle Wave Generator

Set WAVE1 = 10 and the DAC hardware automatically generates a triangle wave, counting up then down. The amplitude is set by MAMP1. The frequency depends on the trigger rate.

Triangle wave frequency formula: f_tri = f_trigger / (2 × (MAMP_max + 1))

C — Triangle wave at 1kHz

/* Triangle: MAMP=0111 (256 steps), TIM6=512kHz → f_tri = 512k/(2*256) = 1kHz */
void DAC1_Triangle_1kHz(void) {
    RCC_AHB1ENR |= (1<<0);
    RCC_APB1ENR |= (1<<29)|(1<<4);
    GPIOA_MODER |= (3<<8);
    GPIOA_PUPDR &= ~(3<<8);

    /* TIM6 at 512 kHz: PSC=0, ARR = 84MHz/512000 - 1 = 163 */
    volatile uint32_t *TIM6_CR2 = (volatile uint32_t *)(0x40001004);
    volatile uint32_t *TIM6_PSC = (volatile uint32_t *)(0x40001028);
    volatile uint32_t *TIM6_ARR = (volatile uint32_t *)(0x4000102C);
    volatile uint32_t *TIM6_EGR = (volatile uint32_t *)(0x40001014);
    volatile uint32_t *TIM6_CR1 = (volatile uint32_t *)(0x40001000);
    *TIM6_CR2 = (2<<4);
    *TIM6_PSC = 0;
    *TIM6_ARR = 163;    /* 84MHz/164 ≈ 512kHz */
    *TIM6_EGR = 1; *TIM6_CR1 = 1;

    /* DAC_CR: WAVE1=10 (triangle), MAMP1=0111 (256 LSB), TEN1, TIM6, EN1 */
    DAC_CR = (1<<0) |    /* EN1 */
             (1<<2) |    /* TEN1 */
             (0<<3) |    /* TSEL1=TIM6 */
             (2<<6) |    /* WAVE1=10=triangle */
             (7<<8);     /* MAMP1=0111=256 steps */

    DAC_DHR12R1 = 0;  /* base = 0V, triangle goes 0→256 */
}
===== 7: SINE WAVE + DMA =====

07 Sine Wave Generation with DMA

For arbitrary waveforms (sine, sawtooth, custom), pre-compute a lookup table (LUT) and use DMA in circular mode to feed DAC from the table. TIM6 triggers the DAC at the desired sample rate. CPU is free while the waveform plays continuously.

Sine wave LUT generation formula: lut[i] = 2048 + 2047 * sin(2π × i / N) where N is the number of samples (e.g., 256).

0V1.65V3.3V

C — 256-sample sine wave via DMA to DAC1

#include <stdint.h>
#include <math.h>    /* for sinf — or precompute table */

/* ── Sine wave LUT (256 samples, 12-bit) ──
   Pre-computed: lut[i] = 2048 + (int)(2047 * sin(2π*i/256))
   Use Python: [int(2048 + 2047*math.sin(2*math.pi*i/256)) for i in range(256)]
────────────────────────────────────────── */
static const uint16_t sine_lut[256] = {
    2048,2098,2148,2198,2248,2298,2347,2397,
    2446,2495,2543,2592,2639,2687,2734,2780,
    2826,2871,2916,2960,3003,3046,3088,3129,
    3169,3209,3247,3285,3322,3357,3392,3425,
    3458,3489,3519,3548,3576,3602,3628,3652,
    3675,3697,3717,3736,3754,3771,3786,3800,
    3812,3823,3833,3841,3848,3854,3858,3861,
    3863,3863,3861,3858,3854,3848,3841,3833,
    3823,3812,3800,3786,3771,3754,3736,3717,
    3697,3675,3652,3628,3602,3576,3548,3519,
    3489,3458,3425,3392,3357,3322,3285,3247,
    3209,3169,3129,3088,3046,3003,2960,2916,
    2871,2826,2780,2734,2687,2639,2592,2543,
    2495,2446,2397,2347,2298,2248,2198,2148,
    2098,2048,1997,1947,1897,1847,1797,1748,
    1699,1650,1602,1553,1506,1458,1411,1365,
    1319,1274,1229,1185,1142,1099,1057,1016,
    976, 936, 898, 860, 823, 788, 753, 720,
    688, 656, 626, 597, 569, 543, 517, 493,
    470, 448, 428, 409, 391, 374, 359, 345,
    332, 321, 311, 302, 295, 289, 285, 282,
    280, 280, 282, 285, 289, 295, 302, 311,
    321, 332, 345, 359, 374, 391, 409, 428,
    448, 470, 493, 517, 543, 569, 597, 626,
    656, 688, 720, 753, 788, 823, 860, 898,
    936, 976, 1016,1057,1099,1142,1185,1229,
    1274,1319,1365,1411,1458,1506,1553,1602,
    1650,1699,1748,1797,1847,1897,1947,1997
};

/* ── DMA1 Stream 5 Channel 7 → DAC Channel 1 ── */
/* (Per reference manual Table 42: DMA1_S5_CH7 = DAC1)  */
#define DMA1_BASE    0x40026000
#define DMA1_S5CR    (*(volatile uint32_t *)(DMA1_BASE+0x088))
#define DMA1_S5NDTR  (*(volatile uint32_t *)(DMA1_BASE+0x08C))
#define DMA1_S5PAR   (*(volatile uint32_t *)(DMA1_BASE+0x090))
#define DMA1_S5M0AR  (*(volatile uint32_t *)(DMA1_BASE+0x094))

void DAC1_Sine_DMA_Init(uint32_t freq_hz) {
    /* 1. Clocks */
    RCC_AHB1ENR |= (1<<0)|(1<<21);  /* GPIOA + DMA1EN (bit 21) */
    RCC_APB1ENR |= (1<<29)|(1<<4); /* DAC + TIM6 */

    /* 2. PA4 analog */
    GPIOA_MODER |= (3<<8);
    GPIOA_PUPDR &= ~(3<<8);

    /* 3. TIM6: sample rate = freq_hz * 256 (256 samples per cycle) */
    uint32_t sample_rate = freq_hz * 256;
    uint32_t arr = (84000000 / sample_rate) - 1;

    volatile uint32_t *TIM6_CR2 = (volatile uint32_t *)(0x40001004);
    volatile uint32_t *TIM6_PSC = (volatile uint32_t *)(0x40001028);
    volatile uint32_t *TIM6_ARR = (volatile uint32_t *)(0x4000102C);
    volatile uint32_t *TIM6_EGR = (volatile uint32_t *)(0x40001014);
    volatile uint32_t *TIM6_CR1 = (volatile uint32_t *)(0x40001000);
    *TIM6_CR2 = (2<<4);  /* TRGO on update */
    *TIM6_PSC = 0;
    *TIM6_ARR = arr;
    *TIM6_EGR = 1;
    *TIM6_CR1 = 1;

    /* 4. DAC CR: EN1, TEN1 (TIM6), WAVE=00, DMAEN1 */
    DAC_CR = (1<<0)|(1<<2)|(0<<3)|(1<<12);
    /*       EN1    TEN1  TSEL=TIM6  DMAEN1 */

    /* 5. DMA1 Stream 5 Channel 7 */
    DMA1_S5CR = 0; while(DMA1_S5CR & 1);

    DMA1_S5PAR  = 0x40007408;          /* DAC_DHR12R1 address */
    DMA1_S5M0AR = (uint32_t)sine_lut;
    DMA1_S5NDTR = 256;
    DMA1_S5CR   =
        (7<<25) |  /* CHSEL = 7 (DAC CH1) */
        (1<<13) |  /* MSIZE = 01 (16-bit memory) */
        (1<<11) |  /* PSIZE = 01 (16-bit periph) */
        (1<<10) |  /* MINC = 1 */
        (1<<8)  |  /* CIRC = 1 (circular) */
        (1<<6)  |  /* DIR = 1 (memory→periph) */
        (1<<0);    /* EN */
    /* DMA now feeds DAC continuously — CPU is free! */
}

int main(void) {
    DAC1_Sine_DMA_Init(440);  /* 440 Hz = musical note A */
    while (1) {
        /* Sine wave plays automatically via DMA — do other work here */
    }
}
DMA1 Stream 5 Channel 7 = DAC Channel 1. This is defined in the STM32F407 reference manual Table 42 (DMA1 request mapping). Use DMA1 Stream 6 Channel 7 for DAC Channel 2. Note: these are DMA1 streams, not DMA2!
===== 8: DUAL CHANNEL =====

08 Dual Channel Simultaneous Output

Use DHR12RD (dual 12-bit right-aligned register at offset 0x20) to update both DAC channels atomically in a single 32-bit write. Bits [11:0] = CH1, bits [27:16] = CH2.

C — Dual channel simultaneous update

#define DAC_DHR12RD   (*(volatile uint32_t *)0x40007420)

void DAC_Dual_Init(void) {
    RCC_AHB1ENR |= (1<<0);
    RCC_APB1ENR |= (1<<29);

    /* PA4 and PA5 both analog */
    GPIOA_MODER |= (3<<8)|(3<<10);
    GPIOA_PUPDR &= ~((3<<8)|(3<<10));

    /* CR: EN1+EN2, no trigger, no wave */
    DAC_CR = (1<<0)|(1<<16);
}

void DAC_Dual_Set(uint16_t ch1, uint16_t ch2) {
    DAC_DHR12RD = ((uint32_t)(ch2 & 0xFFF) << 16) |
                   (uint32_t)(ch1 & 0xFFF);
}

/* Example: generate two out-of-phase sine waves
   CH1 = sin, CH2 = cos (90° phase shift) */
void dual_sine_step(uint8_t *phase) {
    /* sine and cosine LUTs would both be 256 entries */
    uint16_t ch1_val = sine_lut[*phase];
    uint16_t ch2_val = sine_lut[(*phase + 64) & 0xFF]; /* +90° */
    DAC_Dual_Set(ch1_val, ch2_val);
    (*phase)++;
}
===== 9: AUDIO =====

09 Audio Output

The DAC can drive a small speaker or headphone amplifier. For audio playback at 44.1 kHz (CD quality), use TIM6 at 44100 Hz and DMA in circular mode with a ping-pong buffer (double buffer) to allow the CPU to prepare the next block while the current one plays.

Audio Circuit
DAC output stage for audio
PA4
100Ω
10µF (DC block)
headphone amp / LM386
GND

The 100Ω resistor prevents oscillation from capacitive loads. The 10µF capacitor removes the DC offset (~1.65V mid-scale). The DAC output buffer can source ~1mA — use an amplifier for speakers.

C — Ping-pong DMA buffer for audio

#define AUDIO_BUF_SIZE  256

static uint16_t audio_buf[2][AUDIO_BUF_SIZE]; /* double buffer */
volatile uint8_t buf_ready = 0;  /* which half is ready to fill */

/* Called from DMA1 Stream5 interrupt (half-transfer and full-transfer) */
void DMA1_Stream5_IRQHandler(void) {
    volatile uint32_t *DMA1_HISR  = (volatile uint32_t *)(0x40026004);
    volatile uint32_t *DMA1_HIFCR = (volatile uint32_t *)(0x4002600C);

    if (*DMA1_HISR & (1<<10)) {  /* HTIF5 — half transfer */
        *DMA1_HIFCR = (1<<10);   /* clear */
        buf_ready = 0;             /* fill first half */
    }
    if (*DMA1_HISR & (1<<11)) {  /* TCIF5 — full transfer */
        *DMA1_HIFCR = (1<<11);   /* clear */
        buf_ready = 1;             /* fill second half */
    }
}

/* Main audio loop */
int main(void) {
    /* init DAC + DMA with double buffer audio_buf[0] + audio_buf[1] */
    /* DMA M0AR = audio_buf[0], total 512 transfers, HTIE+TCIE enabled */

    while (1) {
        /* Wait until a half is ready to fill */
        uint8_t half = buf_ready;
        /* Fill audio_buf[half] with your audio data */
        for (int i=0; i<AUDIO_BUF_SIZE; i++) {
            audio_buf[half][i] = sine_lut[i];  /* or real PCM data */
        }
    }
}
===== 10: TIPS =====

10 Tips, Pitfalls & Best Practices

Common Pitfalls
  • Not enabling GPIO analog mode: PA4/PA5 must be set to MODER=11 (analog). Forgetting this causes the DAC output to fight the GPIO driver.
  • Conflicting peripherals on PA4/PA5: These pins are shared with ADC1_IN4/IN5 and SPI1_NSS/SCK. Never use both simultaneously without reconfiguring the GPIO.
  • DMA underrun (DMAUDR): If the DMA can’t keep up with the DAC trigger rate, the DAC re-outputs the previous value, causing waveform artifacts. Check DMA clock and priority.
  • Output buffer oscillation: With large capacitive loads (>50pF), the buffer can oscillate. Add a 100Ω series resistor at the output.
  • Forgetting BOFF when sharing ADC: If you use PA4 for ADC in one mode and DAC in another, disable DAC EN when ADC is active, or use different pins.
  • DMA channel selection: DAC1 uses DMA1 (not DMA2!). Stream 5 Channel 7 = DAC CH1, Stream 6 Channel 7 = DAC CH2.
Performance Tips
  • 8-bit mode: Use DHR8R for audio if you only have 8-bit PCM data. The DAC scales it to full range (0→255 maps to 0→VDDA).
  • Waveform frequency accuracy: For audio, calculate TIM6 ARR precisely: ARR = (F_tim / (f_out × N_samples)) - 1. At 84 MHz with 256 samples for 440 Hz: ARR = 84000000/(440×256) – 1 = 745.
  • Noise filtering: Add a simple RC low-pass filter (e.g., 10kΩ + 10nF = 1.6 kHz cutoff) to smooth DAC output steps. Useful for audio applications.
  • VREF accuracy: For precision voltage output, use an external precision voltage reference connected to VREF+ pin instead of VDDA.

STM32F407 Tutorial Series — Tutorial 17: DAC  | ← ADC | DMA →

Bare-metal register-level programming  ·  No HAL library  ·  STM32F407VGT6

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *