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) oDAC_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
| Verifica | Motivo |
|---|---|
| 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

💬 Commenti
Domande, correzioni o esperienze con DAC STM32 + DMA? Scrivimi una email. Includi lo slug dell'articolo nell'oggetto.