2026-06-10 · Davide Carrese

STM32 Advanced Timer PWM:
Dead-Time e Uscite Complementari

STM32 · TIM1 · PWM · Dead-Time · Registri

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:

  1. Uscite complementari (CHxN). Ogni canale può pilotare un secondo pin con la forma d'onda invertita — indispensabile per topologie half-bridge e full-bridge.
  2. 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.
  3. 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:

BitCampoDescrizione
15MOEMain Output Enable — deve essere 1 affinché qualsiasi uscita appaia sui pin
14AOEAutomatic Output Enable — se impostato, MOE viene ripristinato automaticamente dopo l'evento di update successivo al break
13BKPBreak Polarity — 0: break attivo basso, 1: break attivo alto
12BKEBreak Enable — 1 per abilitare l'ingresso BRK
11OSSROff-State Selection for Run mode — 0: uscite inattive quando MOE=0
10OSSIOff-State Selection for Idle mode — stessa logica per lo stato idle
9–8LOCKProtezione scrittura: 00 = nessun blocco, 01/10/11 = livelli progressivi
7–0DTG[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_DTSDead-Time Max a 168 MHz
001 × t_CK_INT255 × 5,95 ns = 1,52 µs
012 × t_CK_INT255 × 11,90 ns = 3,04 µs
104 × t_CK_INT255 × 23,81 ns = 6,07 µs
11Riservato (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 va impostato DOPO tutti gli altri campi BDTR

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]FormulaStepMax a 168 MHz, CKD=00
0xxDTG × t_DTS1 × t_DTS127 × 5,95 ns = 755 ns
10x(64 + DTG[5:0]) × 2 × t_DTS2 × t_DTS127 × 2 × 5,95 ns = 1,51 µs
11x(32 + DTG[4:0]) × 8 × t_DTS8 × t_DTS63 × 8 × 5,95 ns = 3,0 µs
111(32 + DTG[4:0]) × 16 × t_DTS16 × t_DTS63 × 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:

  1. 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.
  2. Aggiungere un helper tim1_set_duty(uint16_t duty) che scrive CCR1 con il preload già abilitato — questo garantisce nessun glitch a metà ciclo.
  3. 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.
  4. 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.
  5. 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.
  6. 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

Checklist Pratica

  1. ☐ Clock abilitato per GPIOA, GPIOB e TIM1 (RCC->AHB1ENR, RCC->APB2ENR).
  2. ☐ GPIO AF impostato su AF1 per i pin CH1, CH1N e BRK.
  3. ☐ Velocità uscita GPIO impostata a 11 (very high) su tutti i pin PWM.
  4. ☐ TIM1_CR1 configurato con CMS=01 (centro-allineato), ARPE=1.
  5. ☐ ARR e CCR caricati; prescaler impostato per la frequenza PWM desiderata.
  6. ☐ CCMR1 configurato per PWM modo 1 con preload abilitato.
  7. ☐ CCER: CC1E=1, CC1NE=1, polarità come richiesto.
  8. ☐ BDTR: DTG calcolato, BKE/BKP configurati, MOE=1 scritto PER ULTIMO in un'istruzione separata.
  9. ☐ Pin di break tirato esternamente al livello inattivo; verificato con multimetro.
  10. ☐ Verifica con oscilloscopio: CH1 e CH1N entrambi bassi durante le transizioni di dead-time.
  11. ☐ Test break software: TIM1->EGR |= TIM_EGR_BG → uscite in stato sicuro; rilascio → PWM riprende.

Scegliere la Modalità Timer Giusta per Ogni Topologia

ApplicazioneTimerModalitàCanaliDead-Time
Half-bridge DC-DCTIM1 CH1Centro-allineatoCH1 + CH1N200–500 ns
BLDC trifaseTIM1 CH1–CH3Centro-allineatoCH1/1N + CH2/2N + CH3/3N500–2000 ns
Ponte H motore DCTIM1 CH1–CH2Edge-alignedCH1/1N + CH2/2N300–800 ns
Convertitore LLC risonanteTIM1 CH1Edge-alignedCH1 + CH1N200–400 ns
BLDC con sensori HallTIM1 CH1–CH3 + TIM4 HallCentro-allineatoCH1/1N + CH2/2N + CH3/3NUsa commutazione, non PWM dead-time

Riferimenti

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.