Watchdog Timer: il segreto per rendere affidabili i tuoi sistemi embedded
Introduzione
Quando si realizza un progetto elettronico, soprattutto nel mondo embedded, si tende a concentrarsi sulle funzionalità: sensori, attuatori, comunicazioni. Tutto funziona perfettamente… almeno all’inizio.
Poi succede qualcosa.
Dopo qualche ora, giorno o magari mesi, il sistema si blocca. Non risponde più. Smette semplicemente di funzionare.
E la cosa peggiore è che spesso questo accade quando il dispositivo è installato in remoto, magari in un ambiente difficile da raggiungere. Pensiamo ad esempio a una serra automatizzata: un blocco del sistema potrebbe interrompere l’irrigazione o il controllo della temperatura, causando danni anche gravi.

La domanda quindi è: come possiamo rendere i nostri sistemi davvero affidabili nel tempo?
Una delle risposte più efficaci è l’utilizzo del Watchdog Timer.
Cos’è il Watchdog Timer
Il Watchdog Timer è una periferica hardware integrata in molti microcontrollori, tra cui l’ATmega328P, molto diffuso nel mondo Arduino.
Il suo funzionamento è semplice ma estremamente efficace.
Si tratta di un timer che deve essere “rinfrescato” periodicamente dal software. Se il programma funziona correttamente, questo aggiornamento avviene in modo regolare. Ma se il codice si blocca o entra in uno stato anomalo, il timer non viene più aggiornato.

A quel punto interviene il watchdog, che forza automaticamente un reset del microcontrollore, riportando il sistema a uno stato iniziale e funzionante.
In pratica, è un meccanismo di sicurezza che evita blocchi permanenti.
Un’analogia concreta
Per capire meglio il concetto, possiamo pensare ad alcuni sistemi reali.
Nei treni, ad esempio, esiste un dispositivo che richiede al macchinista di premere un comando a intervalli regolari. Se questo non avviene, il sistema presume un problema e arresta il treno.

Allo stesso modo, nelle auto moderne, alcuni sistemi di assistenza verificano che il conducente mantenga le mani sul volante. In caso contrario, viene attivata una procedura di sicurezza.
Il Watchdog Timer funziona esattamente così: controlla che il software sia “vivo”. Se smette di rispondere, interviene.
Perché un sistema embedded si blocca
Prima di affidarsi al watchdog, è importante capire perché un sistema può bloccarsi. Le cause possono essere molteplici e spesso non immediatamente evidenti.
Disturbi elettrici e carichi induttivi
Motori, pompe e relè sono componenti molto comuni nei progetti embedded. Tuttavia, questi dispositivi possono generare disturbi elettromagnetici, soprattutto durante le commutazioni.

Questi disturbi possono introdurre:
- picchi di tensione
- interferenze sui segnali
- malfunzionamenti del microcontrollore
Nei casi più gravi, possono persino alterare il contenuto della memoria o bloccare l’esecuzione del codice.
Problemi software
Un’altra causa molto frequente è legata al software.
Ad esempio, un programma potrebbe rimanere bloccato in attesa di un dato da un sensore o da una comunicazione seriale. Se quel dato non arriva mai, il sistema resta fermo in un loop infinito.
Questo tipo di errore è subdolo perché non sempre emerge durante i test iniziali.
Gestione della memoria
Nel microcontrollore ATmega328P, la memoria RAM è limitata e viene utilizzata sia per le variabili che per lo stack.
Due problemi tipici sono:
- Collisione tra stack e variabili, che può causare comportamenti imprevedibili
- Frammentazione della memoria, che rende difficile allocare nuovi dati anche se apparentemente lo spazio è disponibile
Entrambe le situazioni possono portare a blocchi del sistema.

Problemi di alimentazione
Un’alimentazione instabile è un’altra possibile causa di malfunzionamento.
Anche brevi cali di tensione possono portare il microcontrollore in uno stato non definito, da cui non sempre riesce a riprendersi autonomamente.
Il ruolo del Watchdog
Il Watchdog Timer non elimina queste problematiche, ma svolge un ruolo fondamentale: impedisce che il sistema resti bloccato in modo permanente.
Quando il software smette di funzionare correttamente, il watchdog interviene e riavvia il sistema. Questo permette al dispositivo di riprendere l’operatività senza intervento umano.
È una soluzione particolarmente utile nei sistemi:
- installati in remoto
- difficili da raggiungere
- critici per il funzionamento di un impianto
Come funziona nel dettaglio
Nel caso dell’ATmega328P, il watchdog è basato su un clock interno dedicato e su un prescaler che permette di impostare il tempo di intervento.
Questo intervallo può variare da pochi millisecondi fino a diversi secondi.
La scelta del tempo è importante:
- tempi troppo brevi possono causare reset inutili
- tempi troppo lunghi rendono il sistema meno reattivo
In molti casi, un intervallo di circa un secondo rappresenta un buon compromesso.

Monitorare le cause del reset
Un aspetto spesso sottovalutato è la possibilità di capire perché il sistema si è riavviato.
Il microcontrollore mette a disposizione registri che permettono di distinguere tra:
- reset da watchdog
- reset all’accensione
- reset per calo di tensione
- reset esterno
Salvare e analizzare queste informazioni è fondamentale per migliorare il progetto e individuare eventuali problemi.
Buone pratiche di progettazione
Per ottenere un sistema davvero affidabile, il watchdog deve essere affiancato da una progettazione corretta.
Dal punto di vista hardware, è importante:
- isolare i carichi induttivi
- utilizzare optoisolatori quando necessario
- separare le alimentazioni
Dal punto di vista software:
- evitare loop bloccanti
- gestire sempre i timeout
- ottimizzare l’uso della memoria
Il watchdog non deve essere una scorciatoia, ma un ulteriore livello di sicurezza.
Codice di esempio
#include <avr/io.h>
#include <avr/interrupt.h>
/* ─────────────────────────────────────────────
Timeout disponibili (bit WDP3..WDP0 in WDTCSR)
WDP = 0b0000 → 16 ms
WDP = 0b0001 → 32 ms
WDP = 0b0010 → 64 ms
WDP = 0b0011 → 0.125 s
WDP = 0b0100 → 0.25 s
WDP = 0b0101 → 0.5 s
WDP = 0b0110 → 1 s
WDP = 0b0111 → 2 s
WDP = 0b1000 → 4 s
WDP = 0b1001 → 8 s ← WDP3 va nel bit 5
───────────────────────────────────────────── */
/* ─────────────────────────────────────────────
Bit del registro MCUSR (cause di reset)
WDRF bit 3 → Watchdog reset
BORF bit 2 → Brown-out reset (tensione troppo bassa)
EXTRF bit 1 → Reset esterno (pin RESET portato a GND)
PORF bit 0 → Power-on reset (prima accensione)
───────────────────────────────────────────── */
// Salva la causa del reset PRIMA che venga cancellata
// __attribute__((section(".noinit"))) evita che il valore
// venga azzerato dal runtime C durante l'inizializzazione
volatile uint8_t reset_cause __attribute__((section(".noinit")));
// ── Legge e salva la causa del reset ─────────────────────────────────────────
// Da chiamare come primissima cosa in main(), prima di qualsiasi altra init
void save_reset_cause(void)
{
reset_cause = MCUSR; // Copia tutti i flag di reset
MCUSR = 0; // Cancella subito tutti i flag (obbligatorio)
}
// ── Restituisce una stringa descrittiva della causa ───────────────────────────
const char* get_reset_cause_str(void)
{
if (reset_cause & (1 << WDRF)) return "Watchdog reset";
if (reset_cause & (1 << BORF)) return "Brown-out reset";
if (reset_cause & (1 << EXTRF)) return "Reset esterno";
if (reset_cause & (1 << PORF)) return "Power-on reset";
return "Causa sconosciuta";
}
// ── Abilita il watchdog con reset hardware ────────────────────────────────────
void wdt_enable_reset(void)
{
cli();
// MCUSR già azzerato in save_reset_cause(), ma per sicurezza:
MCUSR &= ~(1 << WDRF);
// Sequenza critica obbligatoria (max 4 cicli di clock)
WDTCSR |= (1 << WDCE) | (1 << WDE);
// Timeout 2 s: WDE=1, WDP2=1, WDP1=1, WDP0=1
WDTCSR = (1 << WDE) | (1 << WDP2) | (1 << WDP1) | (1 << WDP0);
sei();
}
// ── Reset del timer (pat the dog) ────────────────────────────────────────────
inline void wdt_reset_timer(void)
{
__asm__ __volatile__("wdr");
}
// ── Disabilita il watchdog ────────────────────────────────────────────────────
void wdt_disable(void)
{
cli();
MCUSR &= ~(1 << WDRF);
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = 0x00;
sei();
}
// ── Esempio: gestione causa reset via UART (opzionale) ───────────────────────
// Se hai UART inizializzato, puoi stampare la causa così:
// uart_print(get_reset_cause_str());
//
// Oppure gestirla con condizioni dirette:
// if (reset_cause & (1 << WDRF)) { /* azioni specifiche per WDT reset */ }
// if (reset_cause & (1 << PORF)) { /* prima accensione, init completa */ }
// ── Loop principale ───────────────────────────────────────────────────────────
int main(void)
{
save_reset_cause(); // PRIMO: salva e azzera MCUSR
wdt_enable_reset(); // Abilita WDT con timeout 2 s
// Gestione della causa di reset
if (reset_cause & (1 << WDRF))
{
// Il sistema è stato resettato dal watchdog:
// probabilmente un blocco nel loop o un'operazione troppo lenta.
// Qui puoi: accendere un LED di errore, salvare in EEPROM,
// inviare un log via UART, ecc.
}
else if (reset_cause & (1 << PORF))
{
// Prima accensione: esegui inizializzazione completa
}
else if (reset_cause & (1 << BORF))
{
// Tensione di alimentazione scesa sotto la soglia brown-out:
// potrebbe indicare un problema di alimentazione o batteria scarica
}
else if (reset_cause & (1 << EXTRF))
{
// Reset manuale via pin RESET (es. pulsante di reset esterno)
}
while (1)
{
// Codice applicativo...
wdt_reset_timer(); // Pat the dog
}
return 0;
}
Descrizione codice
save_reset_cause()
Questa funzione ha il compito di salvare la causa del reset del microcontrollore.
All’avvio, il registro MCUSR contiene informazioni su cosa ha provocato il riavvio:
- watchdog
- accensione (power-on)
- reset esterno
- calo di tensione
La funzione:
- copia il valore del registro in una variabile globale
- azzera subito il registro per evitare letture errate successive
È fondamentale chiamarla all’inizio del main(), prima di qualsiasi altra inizializzazione.
get_reset_cause_str()
Questa funzione restituisce una descrizione leggibile della causa del reset.
Analizza i bit salvati in precedenza e restituisce una stringa, ad esempio:
- “Watchdog reset”
- “Brown-out reset”
- “Power-on reset”
È molto utile per:
- debug
- logging su seriale
- monitoraggio del sistema
wdt_enable_reset()
Questa funzione abilita il Watchdog Timer.
Per farlo correttamente:
- disabilita temporaneamente gli interrupt
- esegue una sequenza obbligatoria di sicurezza
- configura il timeout
Nel codice viene impostato un timeout di circa 2 secondi.
Significa che:
se il watchdog non viene aggiornato entro 2 secondi → il sistema viene resettato.
wdt_reset_timer()
Questa funzione serve per resettare il contatore del watchdog.
È il cuore del meccanismo:
- deve essere chiamata periodicamente
- indica che il software sta funzionando correttamente
Se non viene eseguita in tempo:
- il watchdog scade
- il microcontrollore viene riavviato
È la classica operazione chiamata:
“pat the dog”
Logica generale del sistema
Nel main() il flusso è molto chiaro:
- Si salva la causa del reset
- Si abilita il watchdog
- Si analizza il motivo del riavvio
- Si entra nel loop principale
All’interno del ciclo infinito, il programma esegue le sue funzionalità e resetta continuamente il watchdog.
Se qualcosa va storto (loop bloccato, errore hardware, ecc.):
- il reset del watchdog non avviene
- il sistema viene riavviato automaticamente
Conclusioni
L’affidabilità è uno degli aspetti più importanti nella progettazione di sistemi elettronici, anche quando si tratta di progetti amatoriali.
Un sistema che si blocca senza possibilità di recupero può causare disagi, danni e perdita di fiducia nel progetto stesso.
Il Watchdog Timer rappresenta una soluzione semplice ma estremamente efficace per aumentare la robustezza dei sistemi embedded.
Non risolve i problemi alla radice, ma garantisce che il sistema possa sempre riprendersi.
Ed è proprio questa capacità di reagire agli imprevisti che distingue un progetto funzionante da un progetto realmente affidabile.







