STM32 Advanced Timer PWM:
Dead-Time e Uscite Complementari
Una guida a registro dei timer avanzati TIM1/TIM8: generazione PWM centro-allineata, inserimento del dead-time per pilotaggio half-bridge, uscite complementari CHxN, gestione dell'ingresso di break e un esempio pratico per controllo motori testato su STM32F4.
Se hai mai progettato un gate driver half-bridge, un convertitore buck sincrono o un inverter trifase, sai bene che pilotare simultaneamente il MOSFET lato alto e quello lato basso — anche solo per un nanosecondo — significa corrente di shoot-through, transistori esplosi e un silenzio molto costoso. I timer avanzati STM32 (TIM1 e TIM8 sulle famiglie F4/G4/H7) risolvono questo problema a livello hardware con inserimento programmabile del dead-time, coppie di uscite complementari e spegnimento automatico tramite ingresso di break.
Questo articolo copre la configurazione di TIM1 a livello registri: dalla configurazione del clock e modalità PWM centro-allineata fino al calcolo del dead-time, abilitazione dei canali complementari e collegamento dell'ingresso di break. Nessuna astrazione HAL — vedrai ogni scrittura di registro e capirai esattamente cosa fa il silicio. Gli esempi di codice sono per un STM32F407VG a 168 MHz, ma i principi sono identici per le famiglie F4, G4 e H7.
Cosa Rende TIM1/TIM8 "Avanzati"
Un timer general-purpose come TIM2–TIM5 può produrre segnali PWM indipendenti sui suoi quattro canali. TIM1 e TIM8 aggiungono tre funzionalità essenziali per applicazioni di potenza e controllo motori:
- Uscite complementari (CHxN). Ogni canale può pilotare un secondo pin con la forma d'onda invertita — indispensabile per topologie half-bridge e full-bridge.
- Inserimento dead-time. Un ritardo programmabile tra lo spegnimento di un'uscita e l'accensione del suo complemento. Il timer hardware inserisce questo intervallo automaticamente, con risoluzione a 8 bit scalata dal clock del timer.
- Ingresso di break (BRK). Un pin dedicato che, quando asserito, forza tutte le uscite in uno stato sicuro predefinito (tipicamente OFF). Si collega direttamente al segnale di protezione sovracorrente del gate driver IC.
Queste funzionalità permettono di costruire un anello di controllo per azionamenti motori o convertitori di potenza completamente in hardware, senza affidarsi al software per gestire le bande morte di commutazione — una proprietà di sicurezza critica che sopravvive anche al blocco della CPU.
Il Registro BDTR: Il Centro di Controllo del Dead-Time
Il registro TIM1_BDTR (Break and Dead-Time Register) è il singolo registro più importante per il PWM con timer avanzati. La sua struttura:
| Bit | Campo | Descrizione |
|---|---|---|
| 15 | MOE | Main Output Enable — deve essere 1 affinché qualsiasi uscita appaia sui pin |
| 14 | AOE | Automatic Output Enable — se impostato, MOE viene ripristinato automaticamente dopo l'evento di update successivo al break |
| 13 | BKP | Break Polarity — 0: break attivo basso, 1: break attivo alto |
| 12 | BKE | Break Enable — 1 per abilitare l'ingresso BRK |
| 11 | OSSR | Off-State Selection for Run mode — 0: uscite inattive quando MOE=0 |
| 10 | OSSI | Off-State Selection for Idle mode — stessa logica per lo stato idle |
| 9–8 | LOCK | Protezione scrittura: 00 = nessun blocco, 01/10/11 = livelli progressivi |
| 7–0 | DTG[7:0] | Dead-Time Generator — il valore di ritardo, scalato per tDTS |
Il dead-time si calcola come:
t_DEAD = DTG[7:0] × t_DTS
dove t_DTS è il clock del prescaler del dead-time, derivato da TIMx_CR1.CKD[1:0]:
| CKD[1:0] | t_DTS | Dead-Time Max a 168 MHz |
|---|---|---|
| 00 | 1 × t_CK_INT | 255 × 5,95 ns = 1,52 µs |
| 01 | 2 × t_CK_INT | 255 × 11,90 ns = 3,04 µs |
| 10 | 4 × t_CK_INT | 255 × 23,81 ns = 6,07 µs |
| 11 | Riservato (uguale a 10 su alcune famiglie) | — |
Per la maggior parte dei gate driver (IR2110, IRS2003, L6387), un dead-time di 200–500 ns è tipico. Con CKD=00 a 168 MHz si ottiene una granularità di ~6 ns, più che adeguata.
Esempio Pratico: PWM Half-Bridge con Dead-Time su TIM1
Configuriamo TIM1 per pilotare un half-bridge sul canale 1 (CH1 = PA8, CH1N = PA7) con PWM centro-allineato a 20 kHz, dead-time di 300 ns e ingresso di break su PB12.
Step 1: Configurazione GPIO e Clock
// Abilita i clock RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN; RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // PA8 = TIM1_CH1 (AF1), PA7 = TIM1_CH1N (AF1) GPIOA->MODER &= ~(GPIO_MODER_MODER7 | GPIO_MODER_MODER8); GPIOA->MODER |= (GPIO_MODER_MODER7_1 | GPIO_MODER_MODER8_1); // modalità AF GPIOA->AFR[0] &= ~(0xF << 28); // PA7 AF1 GPIOA->AFR[0] |= (0x1 << 28); GPIOA->AFR[1] &= ~(0xF << 0); // PA8 AF1 GPIOA->AFR[1] |= (0x1 << 0); // PB12 = TIM1_BKIN (AF1) GPIOB->MODER &= ~GPIO_MODER_MODER12; GPIOB->MODER |= GPIO_MODER_MODER12_1; GPIOB->AFR[1] &= ~(0xF << 16); GPIOB->AFR[1] |= (0x1 << 16);
Step 2: Configurazione Base del Timer — Modalità Centro-Allineata
// PWM a 20 kHz con clock 168 MHz in modalità centro-allineata: // f_PWM = f_CLK / (2 × ARR) // ARR = 168e6 / (2 × 20e3) = 4200 TIM1->CR1 = 0; // Azzera il registro di controllo TIM1->CR1 |= TIM_CR1_CMS_0; // CMS=01: centro-allineato modo 1 (up/down) TIM1->CR1 |= TIM_CR1_ARPE; // Auto-reload preload abilitato TIM1->CR1 &= ~TIM_CR1_DIR; // DIR=0: conteggio up di default TIM1->PSC = 0; // Nessun prescaler sul clock del timer TIM1->ARR = 4199; // Periodo = 4200-1 TIM1->RCR = 0; // Contatore di ripetizione (non usato)
La modalità centro-allineata (CMS=01) conta fino a ARR e poi scende a 0. Questo raddoppia il periodo effettivo per un dato valore ARR rispetto alla modalità edge-aligned. Il PWM centro-allineato è lo standard per gli azionamenti motori perché riduce le EMI distribuendo i fronti di commutazione e produce un ripple di corrente simmetrico.
Step 3: Configurazione PWM Canale 1
// CH1 PWM modo 1: attivo quando CNT < CCR, inattivo quando CNT >= CCR TIM1->CCMR1 &= ~(TIM_CCMR1_OC1M | TIM_CCMR1_CC1S); TIM1->CCMR1 |= (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1); // OC1M=110: PWM modo 1 TIM1->CCMR1 |= TIM_CCMR1_OC1PE; // Output compare preload // Duty cycle iniziale: 50% = 2100 TIM1->CCR1 = 2100; // Abilita uscite CH1 e CH1N // CC1P=0: attivo alto, CC1NP=0 (CH1N attivo alto dopo dead-time) TIM1->CCER |= TIM_CCER_CC1E; // Abilita uscita CH1 TIM1->CCER |= TIM_CCER_CC1NE; // Abilita uscita CH1N TIM1->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP); // Entrambi attivi alti
Importante: CH1N NON è una semplice inversione di CH1. TIM1 inserisce il dead-time tra il fronte di discesa di CH1 e il fronte di salita di CH1N, e viceversa. Durante la banda morta, entrambe le uscite sono basse (stato inattivo per polarità active-high). Il segnale CH1N è il complemento logico di CH1, traslato dal ritardo di dead-time.
Step 4: Configurazione Dead-Time e Break
// Dead-time: 300 ns a 168 MHz (t_CK_INT = 5,95 ns, CKD=00) // DTG = t_DEAD / t_CK_INT = 300 ns / 5,95 ns ≈ 50 // DTG=50 rientra nel range 0–127 (DTG[7]=0): dead-time = DTG × t_DTS TIM1->BDTR = 0; TIM1->BDTR |= (50 << 0); // DTG[7:0] = 50 → ~298 ns dead-time TIM1->BDTR |= TIM_BDTR_BKE; // Abilita break TIM1->BDTR |= TIM_BDTR_BKP; // Break attivo alto TIM1->BDTR |= TIM_BDTR_AOE; // Riabilitazione automatica dopo break TIM1->BDTR |= TIM_BDTR_MOE; // Main Output Enable — CRITICO
MOE è protetto da scrittura quando LOCK[1:0] è diverso da zero. Anche con LOCK=00, ST raccomanda di scrivere MOE nell'ultima operazione su BDTR, non nella stessa istruzione con DTG/BKE/AOE. Su alcune revisioni del silicio STM32F4, scrivere MOE simultaneamente a DTG può causare un glitch di un ciclo in cui le uscite si abilitano brevemente con DTG=0. Una scrittura separata è più sicura. Inoltre, se l'ingresso BRK è asserito nel momento in cui MOE viene impostato, le uscite non si abiliteranno — la protezione da sovracorrente è funzionante fin dall'accensione.
Step 5: Avvio del Timer
// Genera un evento di update per caricare i registri shadow TIM1->EGR |= TIM_EGR_UG; // Abilita il contatore TIM1->CR1 |= TIM_CR1_CEN;
A questo punto, PA8 e PA7 dovrebbero emettere PWM complementare a 20 kHz con una banda morta di 300 ns. Verifica con un oscilloscopio: sonda CH1 e CH1N simultaneamente e fai zoom sui fronti di salita/discesa — dovresti vedere entrambi i segnali bassi durante la transizione.
I Tre Range del Dead-Time — Quando DTG Supera 127
Il registro BDTR contiene un campo DTG a 8 bit ma supporta dead-time ben oltre 255 × t_DTS utilizzando tre range di codifica:
| DTG[7:5] | Formula | Step | Max a 168 MHz, CKD=00 |
|---|---|---|---|
| 0xx | DTG × t_DTS | 1 × t_DTS | 127 × 5,95 ns = 755 ns |
| 10x | (64 + DTG[5:0]) × 2 × t_DTS | 2 × t_DTS | 127 × 2 × 5,95 ns = 1,51 µs |
| 11x | (32 + DTG[4:0]) × 8 × t_DTS | 8 × t_DTS | 63 × 8 × 5,95 ns = 3,0 µs |
| 111 | (32 + DTG[4:0]) × 16 × t_DTS | 16 × t_DTS | 63 × 16 × 5,95 ns = 6,0 µs |
Per un inverter a IGBT che commuta a 5 kHz, potresti aver bisogno di 2–4 µs di dead-time. Imposta CKD=01 (2 × t_CK_INT) e usa DTG nel terzo range. Ecco una funzione helper che calcola il valore DTG corretto per qualsiasi dead-time:
/**
* Calcola il valore DTG per TIM1/TIM8 dato un dead-time.
* @param dead_time_ns Dead-time desiderato in nanosecondi
* @param timer_clk_hz Frequenza clock di ingresso del timer (APB2 × prescaler)
* @param ckd Valore CKD[1:0] (0–3)
* @return Valore DTG[7:0] da scrivere in BDTR
*/
uint8_t compute_dtg(uint32_t dead_time_ns, uint32_t timer_clk_hz, uint8_t ckd) {
uint32_t t_dts_ps = (1000000000000ULL / timer_clk_hz) * (1UL << ckd);
uint32_t dead_ps = dead_time_ns * 1000UL;
// Range 1: 0 ≤ DTG ≤ 127, step = t_DTS
if (dead_ps <= 127UL * t_dts_ps)
return (uint8_t)(dead_ps / t_dts_ps);
// Range 2: DTG=10xxxxxx, step = 2 × t_DTS
uint32_t t_dts2 = t_dts_ps * 2;
if (dead_ps <= (127UL * t_dts2))
return 0x80 | (uint8_t)((dead_ps - 64UL * t_dts2) / t_dts2);
// Range 3: DTG=110xxxxx, step = 8 × t_DTS
uint32_t t_dts8 = t_dts_ps * 8;
if (dead_ps <= (63UL * t_dts8))
return 0xC0 | (uint8_t)((dead_ps - 32UL * t_dts8) / t_dts8);
// Range 4: DTG=111xxxxx, step = 16 × t_DTS
uint32_t t_dts16 = t_dts_ps * 16;
return 0xE0 | (uint8_t)((dead_ps - 32UL * t_dts16) / t_dts16);
}
Ingresso di Break: La Rete di Sicurezza Hardware
L'ingresso di break non è opzionale nei progetti con stadi di potenza. Collegalo direttamente dall'uscita di fault del gate driver (pin 5 dell'IR2110, pin 6 dell'L6387, o un comparatore che monitora la resistenza di shunt). Quando il pin BRK viene asserito, l'hardware TIM1 forza immediatamente tutte le uscite nello stato sicuro — indipendentemente dallo stato della CPU, dalla latenza degli interrupt o da bug software.
Configura l'azione di break con TIM1_BDTR e il filtro opzionale in TIM1_BDTR_BKF:
// Filtro break: N=4 campioni consecutivi a f_DTS per antirimbalzo del BRK
TIM1->BDTR |= (4 << 16); // BKF[3:0] = 4 → BRK stabile per 4 × t_DTS
// Dopo un break: pulisci il flag, riabilita MOE
// Nella ISR o nel fault handler:
void TIM1_BRK_IRQHandler(void) {
if (TIM1->SR & TIM_SR_BIF) {
TIM1->SR &= ~TIM_SR_BIF; // Pulisci flag interrupt break
// Tutte le uscite sono già in stato sicuro. Registra l'evento.
// NON riabilitare MOE alla cieca — verifica prima che
// la condizione di fault esterna sia stata risolta.
}
}
La sorgente di break può anche essere attivata via software (TIM1_EGR |= TIM_EGR_BG) o dall'evento di clock failure su alcune famiglie STM32 — utile per testare il fault handler senza cortocircuitare hardware reale.
Come Lo Affronterei su un Progetto Cliente
Quando consegno un modulo firmware per azionamento motori o convertitore di potenza, la configurazione di TIM1 segue una sequenza rigorosa che ho affinato su diversi progetti cliente:
- Creare una funzione
tim1_pwm_init(TIM1_Config *cfg)con una struct che contiene tutti i parametri: frequenza, dead_time_ns, polarità, polarità break, duty iniziale. Nessun numero magico. - Aggiungere un helper
tim1_set_duty(uint16_t duty)che scriveCCR1con il preload già abilitato — questo garantisce nessun glitch a metà ciclo. - Verificare il dead-time con l'oscilloscopio su ogni prototipo. Anche se i calcoli dei registri sono corretti, le differenze di ritardo di propagazione del gate driver tra percorso high-side e low-side possono erodere la banda morta. Misura, non dare per scontato.
- Collegare l'ingresso di break a un latch di fault. L'uscita di fault del gate driver è spesso un impulso, non un livello. Aggiungi un latch SR (o usa l'EXTI interno dell'MCU con un flag) in modo che il break resti asserito finché il firmware non lo riconosce.
- Testare il percorso di break a ogni accensione della scheda. Asserisci BRK via software (
EGR.BG) e verifica che tutte le uscite vadano nello stato sicuro. Poi rilascialo e conferma che il PWM riprenda correttamente. Sono 30 secondi e catturano errori di cablaggio che sarebbero distruttivi a piena potenza. - Non modificare mai il dead-time a runtime. Il registro BDTR ha livelli di protezione scrittura che possono interferire. Se hai bisogno di dead-time variabile per diversi punti operativi, configura il valore peggiore e usa una strategia di modulazione diversa.
Errori Comuni
- MOE mai impostato. Il problema più frequente in fase di bring-up: il timer funziona, CCR è caricato, ma nessun segnale sui pin.
BDTR.MOEè 0 dopo il reset. Devi impostarlo esplicitamente. - AF non configurato sul pin CHxN. CH1 (PA8) viene configurato, ma PA7 (CH1N) resta come input GPIO. Il timer genera la forma d'onda complementare internamente ma non arriva mai al pin.
- Velocità uscita GPIO troppo bassa. A 168 MHz, i tempi di salita/discesa delle uscite GPIO dominano le perdite di commutazione. Imposta
GPIOx->OSPEEDRa 11 (very high speed) per tutti i pin di uscita del timer. - Pin di break flottante. Se BKE è impostato ma il pin BRK non è collegato (o è tirato al livello attivo da un pull-up/down interno debole), le uscite non si abiliteranno mai. Verifica sempre lo stato del pin BRK con un multimetro prima di debuggare il timer.
- Usare TIM1 CHxN senza dead-time. Se vuoi uscite complementari senza dead-time (es. per segnalazione differenziale), imposta DTG=0. L'uscita CHxN sarà l'inverso esatto di CHx, allineata sui fronti.
Checklist Pratica
- ☐ Clock abilitato per GPIOA, GPIOB e TIM1 (
RCC->AHB1ENR,RCC->APB2ENR). - ☐ GPIO AF impostato su AF1 per i pin CH1, CH1N e BRK.
- ☐ Velocità uscita GPIO impostata a 11 (very high) su tutti i pin PWM.
- ☐ TIM1_CR1 configurato con CMS=01 (centro-allineato), ARPE=1.
- ☐ ARR e CCR caricati; prescaler impostato per la frequenza PWM desiderata.
- ☐ CCMR1 configurato per PWM modo 1 con preload abilitato.
- ☐ CCER: CC1E=1, CC1NE=1, polarità come richiesto.
- ☐ BDTR: DTG calcolato, BKE/BKP configurati, MOE=1 scritto PER ULTIMO in un'istruzione separata.
- ☐ Pin di break tirato esternamente al livello inattivo; verificato con multimetro.
- ☐ Verifica con oscilloscopio: CH1 e CH1N entrambi bassi durante le transizioni di dead-time.
- ☐ Test break software:
TIM1->EGR |= TIM_EGR_BG→ uscite in stato sicuro; rilascio → PWM riprende.
Scegliere la Modalità Timer Giusta per Ogni Topologia
| Applicazione | Timer | Modalità | Canali | Dead-Time |
|---|---|---|---|---|
| Half-bridge DC-DC | TIM1 CH1 | Centro-allineato | CH1 + CH1N | 200–500 ns |
| BLDC trifase | TIM1 CH1–CH3 | Centro-allineato | CH1/1N + CH2/2N + CH3/3N | 500–2000 ns |
| Ponte H motore DC | TIM1 CH1–CH2 | Edge-aligned | CH1/1N + CH2/2N | 300–800 ns |
| Convertitore LLC risonante | TIM1 CH1 | Edge-aligned | CH1 + CH1N | 200–400 ns |
| BLDC con sensori Hall | TIM1 CH1–CH3 + TIM4 Hall | Centro-allineato | CH1/1N + CH2/2N + CH3/3N | Usa commutazione, non PWM dead-time |
Riferimenti
- RM0090: Manuale di Riferimento STM32F4 — Capitolo 17: Advanced-Control Timers (TIM1 & TIM8)
- RM0440: Manuale di Riferimento STM32G4 — Capitolo 26: Advanced-Control Timers
- AN4013: Panoramica Timer Cross-Series STM32
- STM32CubeF4 Esempi TIM (GitHub) — Complementary Signals, 6-Step, PWM Input
- International Rectifier AN-978: HV Floating MOS-Gate Driver ICs (nota applicativa IR2110/IR2113)
- STM32F4xx HAL Driver: stm32f4xx_hal_tim.c — HAL_TIM_PWM_ConfigChannel(), HAL_TIMEx_ConfigBreakDeadTime()
Commenti
Hai debuggato un problema di dead-time su STM32? Inviami un'email — aggiorno l'articolo con i riscontri dal campo.

Commenti
Hai commenti? Inviami un'email.