Lezione 23 di 43

Lezione 22

🎓 PIC16 — From Zero to Hero · Lezione 22 · Modulo 4

Watchdog Timer e Sleep Mode 🐕💤

Il WDT come cane da guardia contro i blocchi software. La modalità SLEEP
per scendere a ~1µA. Risveglio con interrupt o WDT per applicazioni a batteria.

🎯 Cosa impari in questa lezione

  • Come funziona il Watchdog Timer: counter autonomo che resetta il PIC se non viene azzerato
  • Istruzione CLRWDT: dove e quanto spesso inserirla nel codice
  • Configurare il postscaler WDT con PSA=1 per timeout da 18ms a 2.3s
  • Rilevare la causa del reset leggendo i bit TO e PD di STATUS all’avvio
  • Istruzione SLEEP: ferma il clock, scende a ~1µA, mantiene la RAM
  • Risveglio da SLEEP con interrupt esterno (RB0/INT) o con scadenza WDT
  • Calcolo dell’autonomia a batteria con duty cycle attivo/sleep

📋 Scheda lezione

Lezione: 22 / 42
Modulo: 4 — Interrupt avanzati
Device: PIC16F84A
Prerequisiti: Lez. 18–19
Difficoltà: ★★★★☆
Istruzioni nuove: SLEEP, CLRWDT
Bit nuovi: TO, PD in STATUS


1 · Watchdog Timer — il cane da guardia

Il WDT è un counter completamente indipendente dal clock principale: continua a contare anche se il programma si blocca in un loop infinito, si perde in un salto errato o viene corrotto da un glitch elettrico. Se il software non esegue CLRWDT entro il timeout configurato, il WDT forza un reset hardware del PIC, facendo ripartire l’esecuzione da 0x0000. È il meccanismo di affidabilità fondamentale nei sistemi embedded industriali.

Watchdog Timer — meccanismo di reset automatico Main loop esegue correttamente CLRWDT azzera il contatore WDT counter conta fino a timeout ~18ms × postscaler CLRWDT → azzera → riparte da 0 overflow RESET TO=0 in STATUS HANG: GOTO HANG ← CLRWDT non viene mai eseguito → WDT scade → RESET Postscaler WDT (PSA=1): PS=000 → ×1 ≈ 18ms PS=011 → ×16 ≈ 288ms PS=110 → ×64 ≈ 1.15s PS=111 → ×128 ≈ 2.3s

Dove mettere CLRWDT: all’inizio del main loop principale, in modo che venga eseguito ad ogni iterazione. Se il loop ha rami che possono essere molto lenti (delay lunghi, attese bloccanti), aggiungere CLRWDT anche lì. Regola pratica: il timeout WDT deve essere almeno 2× il tempo massimo di iterazione del loop principale in condizioni normali.

Codice — WDT con rilevamento reset

; ============================================================
; Lezione 22 — Watchdog Timer (WDT)
; Il WDT resetta il PIC se il software si blocca.
; CLRWDT deve essere eseguito entro il timeout WDT.
; PIC16F84A @ 4 MHz   WDT abilitato in __CONFIG
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_ON & _PWRTE_ON & _CP_OFF

; WDT timeout base = ~18ms (senza postscaler)
; Postscaler WDT (PSA=1, PS bits): 000=×1, 111=×128 → max ~2.3s
; Con PSA=1 e PS=110 → ×64 → WDT timeout ≈ 18ms × 64 = 1.152s

    CBLOCK  0x0C
LOOP_COUNT      ; contatore main loop
WDT_RESET_CNT   ; quante volte il WDT ha fatto reset (debug)
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    ; Controlla se stiamo venendo da un reset WDT
    ; TO=0 e PD=1 dopo WDT reset (vedi STATUS bits 4:3)
    BTFSS   STATUS,4       ; TO bit: 0=WDT timeout, 1=normale
    INCF    WDT_RESET_CNT,F ; conta i reset da WDT

    BSF     STATUS,RP0
    ; PSA=1 (postscaler al WDT), PS=110 → ×64 → ~1.15s timeout
    MOVLW   b'00001110'    ; PSA=1, PS=110
    MOVWF   OPTION_REG
    CLRF    TRISA
    BCF     STATUS,RP0

    CLRF    PORTA
    CLRF    LOOP_COUNT

MAIN
    CLRWDT                 ; &lt;&lt;&lt; deve essere eseguito entro 1.15s

    ; Lavoro del main loop
    INCF    LOOP_COUNT,F
    MOVF    LOOP_COUNT,W
    ANDLW   0x0F
    MOVWF   PORTA          ; mostra contatore su PORTA

    ; Simula un delay breve (sicuro)
    CALL    SHORT_DELAY

    ; --- Test: per simulare un blocco, metti un GOTO infinito qui ---
    ; GOTO HANG
    ; HANG GOTO HANG      ← questo provocherebbe reset WDT dopo ~1.15s

    GOTO    MAIN

SHORT_DELAY
    MOVLW   D'50'
    MOVWF   0x70
SDL DECFSZ  0x70,F
    GOTO    SDL
    RETURN

    END

2 · SLEEP mode — risparmio energetico estremo

L’istruzione SLEEP mette il PIC in modalità a bassissimo consumo: il clock principale si ferma, il Timer0 (se su clock interno) si ferma, la CPU si ferma. La RAM viene mantenuta grazie a un circuito di hold separato. Il consumo scende da ~2mA a ~1µA — un fattore 2000×. Il PIC si sveglia con un interrupt abilitato (se GIE=1) o con la scadenza del WDT. Dopo il risveglio, esegue l’istruzione immediatamente successiva a SLEEP, poi — se GIE=1 — salta alla ISR.

SLEEP mode — ciclo attivo / dormiente t ATTIVO ~2mA @ 4MHz SLEEP ~1µA RB0↑ INT NOP ISR BCF INTF RETFIE ATTIVO ~2mA @ 4MHz SLEEP ~1µA Consumo medio ∝ duty cycle attivo/sleep Se attivo 1% del tempo: I_medio ≈ 0.01 × 2mA + 0.99 × 1µA ≈ 21µA Con batteria da 1000mAh → autonomia ≈ 1000/0.021 ≈ 47.000 ore ≈ 5 anni

Codice — SLEEP con risveglio su RB0/INT

; ============================================================
; Lezione 22 — SLEEP mode
; Il PIC entra in sleep, si sveglia con RB0/INT
; Consumo in sleep: ~1µA (vs ~2mA attivo @ 4MHz)
; PIC16F84A @ 4 MHz
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF

    CBLOCK  0x0C
W_SAVE
STATUS_SAVE
WAKE_COUNT      ; contatore risvegli
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; ISR — eseguita AL RISVEGLIO da RB0/INT
; Nota: dopo SLEEP, l'istruzione successiva a SLEEP viene
; eseguita PRIMA di saltare alla ISR se GIE=1
; ============================================================
    ORG     0x0004
ISR
    MOVWF   W_SAVE
    SWAPF   STATUS,W
    MOVWF   STATUS_SAVE

    BTFSS   INTCON,INTF
    GOTO    ISR_EXIT
    BCF     INTCON,INTF
    INCF    WAKE_COUNT,F

ISR_EXIT
    SWAPF   STATUS_SAVE,W
    MOVWF   STATUS
    SWAPF   W_SAVE,F
    SWAPF   W_SAVE,W
    RETFIE

    ORG     0x0020
INIT
    BSF     STATUS,RP0
    MOVLW   b'11000000'    ; RBPU=1, INTEDG=1 (fronte salita)
    MOVWF   OPTION_REG
    CLRF    TRISA
    MOVLW   0x01           ; RB0 input
    MOVWF   TRISB
    BCF     STATUS,RP0

    CLRF    PORTA
    CLRF    WAKE_COUNT

    ; Abilita INT (non GIE ancora — lo attiviamo prima di SLEEP)
    MOVLW   b'10010000'    ; GIE=1, INTE=1
    MOVWF   INTCON

; ============================================================
; MAIN LOOP — lavora, poi va a dormire
; ============================================================
MAIN
    ; Fase attiva: esegui operazioni (qui esempio)
    BSF     PORTA,0        ; LED ON: &quot;sono sveglio&quot;
    CALL    WORK_PHASE     ; simula elaborazione
    BCF     PORTA,0        ; LED OFF: vado a dormire

    ; Preparazione sleep
    CLRWDT                 ; azzera WDT prima di sleep (se WDT abilitato)

    ; *** SLEEP ***
    ; Il clock si ferma. Consumo scende a ~1µA.
    ; Tutte le periferiche si fermano (TMR0 si ferma se T0CS=0).
    ; Lo stato della RAM è mantenuto.
    SLEEP

    ; <<< Esecuzione riprende qui dopo risveglio >>>
    ; (l'istruzione dopo SLEEP viene eseguita prima della ISR)
    NOP                    ; buona pratica: NOP dopo SLEEP

    GOTO    MAIN

WORK_PHASE
    ; Simula 200 cicli di elaborazione
    MOVLW   D'200'
    MOVWF   0x70
WP  DECFSZ  0x70,F
    GOTO    WP
    RETURN

    END

3 · SLEEP + WDT come timer a basso consumo

Combinando SLEEP e WDT si ottiene un timer a bassissimo consumo: il PIC dorme, il WDT lo sveglia ogni ~2.3 secondi, campiona un sensore, torna a dormire. È il pattern ideale per datalogger, stazioni meteo e dispositivi IoT alimentati a batteria. A differenza del risveglio da interrupt, il risveglio da WDT genera un reset — l’esecuzione riparte da 0x0000 e bisogna leggere TO per capire la causa.

STATUS bits TO e PD — riconoscere la causa del reset/risveglio TO (bit4) PD (bit3) Causa 1 1 Power-on reset (POR) — prima accensione 1 0 Istruzione SLEEP eseguita normalmente 0 0 WDT timeout durante SLEEP → reset 0 1 WDT timeout durante esecuzione normale → reset x x Reset esterno su /MCLR — TO e PD non significativi

Codice — datalogger periodico con WDT

; ============================================================
; Lezione 22 — SLEEP + WDT come timer a basso consumo
; Il WDT sveglia il PIC periodicamente (no interrupt esterno)
; Utile per: datalogger, sensori periodici, batterie
; PIC16F84A  WDT ON, postscaler ×128 → ~2.3s
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_ON & _PWRTE_ON & _CP_OFF

    CBLOCK  0x0C
SAMPLE_COUNT    ; conta i campionamenti
SENSOR_VAL      ; valore letto dal &quot;sensore&quot; (simulato)
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    BSF     STATUS,RP0
    ; PSA=1, PS=111 → WDT postscaler ×128 → ~18ms × 128 = ~2.3s
    MOVLW   b'00001111'    ; PSA=1, PS=111
    MOVWF   OPTION_REG
    CLRF    TRISA
    CLRF    TRISB
    BCF     STATUS,RP0

    CLRF    PORTA
    CLRF    PORTB
    CLRF    SAMPLE_COUNT
    CLRF    SENSOR_VAL

; ============================================================
; MAIN: campiona → mostra → dormi ~2.3s → ripeti
; ============================================================
MAIN
    ; 1. Campiona sensore (simulato: leggi PORTA come ADC fittizio)
    MOVF    PORTA,W
    MOVWF   SENSOR_VAL
    INCF    SAMPLE_COUNT,F

    ; 2. Mostra valore su PORTB
    MOVF    SENSOR_VAL,W
    MOVWF   PORTB

    ; 3. Lampeggia LED di conferma campionamento
    BSF     PORTA,0
    CALL    SHORT_BLINK
    BCF     PORTA,0

    ; 4. Entra in SLEEP — il WDT lo sveglierà dopo ~2.3s
    ; GIE=0: nessun interrupt, solo WDT può svegliare
    ; Al risveglio da WDT: TO=0 in STATUS
    CLRWDT
    SLEEP
    NOP                    ; eseguita dopo il risveglio

    ; 5. Verifica sorgente risveglio (TO=0 → WDT, altrimenti INT)
    BTFSS   STATUS,4       ; TO=1 → risveglio normale (non WDT)
    GOTO    MAIN           ; TO=0 → era WDT → ricampiona

    ; Se TO=1 arriverei qui solo se GIE=1 e c'è un INT — non configurato
    GOTO    MAIN

SHORT_BLINK
    MOVLW   D'100'
    MOVWF   0x70
SBL DECFSZ  0x70,F
    GOTO    SBL
    RETURN

    END

4 · WDT vs SLEEP — quando usare quale

WDT senza SLEEP
Sempre attivo in background. Non richiede modifiche al flusso. Protegge da blocchi software in qualsiasi applicazione. Overhead: zero — basta aggiungere CLRWDT nel loop principale. Abilitare sempre nei prodotti finali.

SLEEP + risveglio WDT
Per applicazioni a batteria che campionano raramente. Il programma richiede struttura speciale (check TO all’avvio). RAM mantenuta ma non i registri periferici (da reinizializzare). Autonomia fino a 5+ anni su batteria AA.

🧪 Prova tu

  1. Carica il programma WDT. Rimuovi la riga CLRWDT (commentala). Esegui e osserva: dopo il timeout simulato il PC torna a 0x0000, STATUS bit4 (TO) è 0, e WDT_RESET_CNT si incrementa.
  2. Carica il programma SLEEP. Porta RB0 a 1 nel pannello I/O mentre il PIC è in SLEEP (PC puntato sull’istruzione SLEEP). Osserva: l’esecuzione riprende sul NOP, poi entra nella ISR, poi torna al MAIN.
  3. Analisi TO/PD: dopo il risveglio da RB0/INT, leggi STATUS nel pannello registri. Quali sono i valori di TO (bit4) e PD (bit3)? Consulta la tabella SVG e identifica la causa.
  4. Calcolo autonomia: nel datalogger con WDT ×128 (≈2.3s), se la fase attiva dura 1000 cicli @ 4MHz (250µs), qual è il duty cycle? Con batteria da 2000mAh, quante ore dura il sistema?

❓ Quiz rapido

1. Perché dopo SLEEP si mette sempre un NOP?

Mostra risposta

Al risveglio da SLEEP il clock del PIC riprende ma impiega 1024 cicli del clock RC interno (o 1024 cicli dell’oscillatore esterno con PWRTE) per stabilizzarsi. Il datasheet Microchip raccomanda di inserire almeno un NOP dopo SLEEP per garantire che la prima istruzione vera venga eseguita con il clock stabile. In pratica il NOP è una buona norma difensiva: non costa nulla e previene comportamenti erratici su alcuni device o con certi circuiti di clock.


2. Il programma usa __CONFIG _WDT_ON. Si può abilitare o disabilitare il WDT a runtime con un’istruzione assembly?

Mostra risposta

No. Sul PIC16F84A il WDT è controllato esclusivamente dal bit di configurazione WDT in __CONFIG (word di configurazione a 0x2007), programmato al momento del flashing. Non esiste un registro accessibile a runtime per abilitare/disabilitare il WDT. Si può però controllare il suo effetto: CLRWDT lo azzera, e PSA/PS in OPTION_REG ne regolano il timeout tramite il postscaler. Nei PIC più recenti (es. PIC16F1xxx) esistono invece registri di controllo WDT accessibili a runtime.


3. Nel datalogger con SLEEP+WDT, perché la variabile SAMPLE_COUNT non si azzera ad ogni risveglio nonostante il WDT faccia un reset?

Mostra risposta

Un reset da WDT (o da /MCLR) non azzera la RAM del PIC16. I registri file (GPR) mantengono il loro valore attraverso qualsiasi tipo di reset — solo il power-on reset da zero volt azzera la RAM in modo affidabile (e nemmeno sempre, dipende dalla velocità di discesa della tensione). SAMPLE_COUNT sopravvive quindi al reset WDT. Questo è un comportamento documentato e utilizzabile: si può mantenere stato tra cicli di sleep/wake tramite variabili in RAM, verificando all’avvio che il valore sia sensato (es. range check) per escludere corruzione da power-on.

Prossima lezione →

Lezione 23 — EEPROM: lettura e scrittura dati non volatili
I 64 byte di EEPROM del PIC16F84A. La sequenza di scrittura con disabilitazione GIE. Salvataggio di configurazioni e contatori attraverso i cicli di alimentazione.

© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 22