Architettura del DAC

Il blocco DAC STM32 ha fino a due canali indipendenti (DAC1 e DAC2). Ogni canale ha:

  • Un registro dati di holding a 12 bit allineato a destra (DAC_DHR12R1, DAC_DHR12R2)
  • Un registro di uscita (DAC_DOR1, DAC_DOR2) che pilota realmente il pin
  • Un multiplexer di selezione del trigger (timer, software o esterno) in DAC_CR
  • Capacità DMA per canale via DAC_DHR12RD (doppio) o DAC_DHR12R1 (singolo)

Il punto chiave: il DAC non converte in continuo. Ogni conversione è innescata da un evento — timer update, timer TRGO, linea esterna o scrittura software del bit SWTRIG. Tra un trigger e l'altro, l'uscita mantiene l'ultimo valore. Si imposta la sorgente del trigger in DAC_CR->TSELx[2:0] e la si abilita con TENx.

Perché Timer + DMA?

Per la generazione di forme d'onda, il timer definisce la frequenza di campionamento. Ogni evento di update del timer comanda al DAC di caricare DAC_DHR12R1 in DAC_DOR1 e avviare una nuova conversione. La conversione DAC richiede circa 3 µs (tipico per F4 a 84 MHz APB1), quindi la frequenza massima è circa 330 kS/s con tempo di assestamento a 12 bit.

Il DMA si pone tra un buffer RAM contenente i campioni della forma d'onda e il registro dati del DAC. Configurato in modalità circolare, si ricarica automaticamente dall'inizio quando raggiunge la fine del buffer. La CPU scrive la tabella della waveform una volta, abilita tutto e non tocca mai più il DAC finché non serve cambiare forma d'onda.

Configurazione a Livello di Registro (Esempio STM32F4)

Uso il canale 1 del DAC su PA4, TIM6 come trigger di update e DMA1 Stream 5 Canale 7 su STM32F407. Ogni variante STM32 ha un mapping DMA leggermente diverso — controllare sempre la tabella dei mapping delle richieste DMA nel manuale di riferimento.

Passo 1 — Abilitazione dei Clock

RCC->APB1ENR |= RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
// Pin di uscita DAC: PA4, modo analogico
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER |= (3 << 8);  // PA4 = modo analogico

Passo 2 — Configurazione del Timer (TIM6)

TIM6 è un timer base, progettato appositamente come sorgente di trigger per il DAC. Imposta prescaler e periodo per ottenere la frequenza di campionamento desiderata.

// Frequenza di campionamento desiderata: 48 kHz
// Clock timer APB1: 84 MHz
// Frequenza update TIM6 = 84 MHz / (PSC+1) / (ARR+1)
TIM6->PSC = 0;                // prescaler = 1
TIM6->ARR = (84000000 / 48000) - 1;  // auto-reload per 48 kHz
TIM6->CR2 |= TIM_CR2_MMS_1;  // Master mode: update event su TRGO
TIM6->CR1 |= TIM_CR1_CEN;    // avvia timer

Il bit MMS_1 configura TIM6 per emettere il suo evento di update (UEV) sulla linea TRGO. Su STM32F4, TRGO di TIM6 è cablato all'ingresso trigger del DAC quando TSEL1 = 000.

Passo 3 — Configurazione del Trigger DAC

DAC->CR = 0;  // parte da uno stato noto
// TSEL1 = 000 (TIM6 TRGO), TEN1 = 1 (abilita trigger),
// DMAEN1 = 1 (abilita richiesta DMA), EN1 = 1 (abilita DAC)
DAC->CR = DAC_CR_TSEL1_0 << 0
        | DAC_CR_TEN1
        | DAC_CR_DMAEN1
        | DAC_CR_EN1;

Il DAC necessita di un tempo di stabilizzazione dopo EN1. Attendere circa 1 µs (10 NOP a 168 MHz) prima di avviare il timer.

Passo 4 — Configurazione DMA (Modo Circolare)

Il DMA trasferisce una half-word a 16 bit (il valore DAC a 12 bit, allineato a sinistra nei bit alti) dalla RAM a DAC_DHR12R1 a ogni richiesta. Il DAC genera una richiesta DMA dopo ogni conversione completata.

#define WAVEFORM_SIZE 256
static uint16_t waveform[WAVEFORM_SIZE];

// DMA1 Stream 5, canale 7 (mapping DAC1 su F407)
DMA1_Stream5->CR = 0;
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 | DMA_FCR_FTH_0;
DMA1_Stream5->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0
                 | DMA_SxCR_MSIZE_0
                 | DMA_SxCR_PSIZE_0
                 | DMA_SxCR_MINC
                 | DMA_SxCR_CIRC
                 | DMA_SxCR_DIR_0
                 | DMA_SxCR_EN;

Dettaglio critico: Senza CIRC, il DMA trasferisce la waveform una volta e si ferma. Il DAC continua a emettere l'ultimo campione per sempre. Con CIRC, il DMA ricomincia dall'inizio all'infinito.

Passo 5 — Calcolo della Tabella Sinusoidale

for (int i = 0; i < WAVEFORM_SIZE; i++) {
    float sample = sinf(2.0f * M_PI * i / WAVEFORM_SIZE);
    uint32_t val = (uint32_t)((sample + 1.0f) * 2047.5f);
    if (val > 4095) val = 4095;
    waveform[i] = (uint16_t)(val << 4);
}

L'allineamento a sinistra (<< 4) è importante: il DAC si aspetta i dati nei bit [15:4] del registro a 16 bit per la modalità a 12 bit. Scrivere nei bit [3:0] non ha effetto.

Esempio Pratico: Sinusoide a 1 kHz a 48 kS/s

Con una frequenza di campionamento di 48 kHz e una tabella waveform di 256 campioni, la frequenza della sinusoide generata è:

f_sine = f_campionamento / WAVEFORM_SIZE = 48000 / 256 = 187.5 Hz

Per ottenere esattamente 1 kHz:

WAVEFORM_SIZE = 48;  // 48000 / 1000 = 48 campioni per periodo

Oppure mantieni 256 campioni e regola il timer per 256 kHz:

TIM6->ARR = (84000000 / (256 * 1000)) - 1;  // 256 kHz → 1 kHz in uscita

L'ampiezza in uscita su PA4 è:

Vout = (Vref / 4096) * sample_12bit

Con Vref = 3.3 V e una sinusoide in piena scala, l'ampiezza picco-picco è circa 3.3 V centrata a 1.65 V. Aggiungi un condensatore di blocco DC se serve un segnale bipolare.

Operazione a Due Canali (Figure di Lissajous, I/Q)

Per due uscite analogiche simultanee, abilita DAC2 su PA5, configura DMA1 Stream 6 (canale 7) per il secondo canale e usa il registro doppio DAC_DHR12RD. Entrambi i canali convertono sullo stesso trigger timer.

Checklist Pratica

VerificaMotivo
Clock DAC abilitato in RCC?Le scritture ai registri DAC vengono ignorate senza clock
Pin in modo analogico?I driver digitali contrastano l'uscita DAC
TEN1 = 1 e TSEL1 corrisponde al timer?Senza, il DAC ignora gli eventi timer
Canale DMA corrisponde alla tabella di mapping?Canale sbagliato = nessuna richiesta DMA dopo conversione DAC
Bit CIRC impostato?Senza modalità circolare, una sola emissione poi silenzio
Dati allineati a sinistra nel registro?Allineamento a destra usa registro diverso
Prescaler/ARR timer per frequenza target?Verifica con oscilloscopio o TIMx->CNT
Attesa stabilizzazione DAC dopo EN1?Senza ~1 µs di attesa, i primi campioni possono essere invalidi

Come lo Affronterei su un Progetto Cliente

Su un progetto recente con STM32G474 per un'applicazione di controllo motori, servivano due uscite analogiche sincronizzate — una per il riferimento di coppia e una per un segnale di test del monitor di corrente. L'approccio a livello di registro descritto sopra ha funzionato direttamente su G4, con solo un cambio di numero di canale DMA (rimappatura DMAMUX).

L'imprevisto che ci è costato mezza giornata: il DAC del G4 ha una modalità sample-and-hold abilitata di default in alcune configurazioni CubeMX. Se DAC_MCR->MODE1 è impostato a 10 (sample-and-hold con condensatore esterno), l'uscita è internamente disconnessa dal pin. Controlla sempre DAC_MCR quando il DAC è abilitato ma il pin rimane a 0 V.

Sulle serie L4/L5, il DAC offre anche un generatore di waveform integrato (rumore/triangolo) e una calibrazione del buffer che riduce l'offset. Per applicazioni di precisione (< 10 mV di tolleranza), esegui la sequenza di calibrazione dal manuale di riferimento prima di abilitare l'uscita.

Fonti consultate:

  • STM32F4 Reference Manual (RM0090) — Capitolo 13: DAC, Capitolo 9: DMA
  • STM32G4 Reference Manual (RM0440) — Capitolo 15: DAC
  • STM32L4 Reference Manual (RM0351) — Capitolo 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 — definizioni dei registri stm32f407xx.h