Lezione 22 di 43

Lezione 21

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

Interrupt avanzati — FSM e Debounce hardware 🚦

Macchine a stati finiti guidate da interrupt, gestione dell’emergenza con priorità,
e debounce hardware che disabilita e riabilita INTE tramite Timer0.

🎯 Cosa impari in questa lezione

  • Progettare una FSM (Finite State Machine) con stati, transizioni e durate
  • Usare interrupt per eventi asincroni (tick temporale + emergenza) senza bloccare il main
  • Implementare transizioni di priorità: un evento forza uno stato indipendentemente da quello corrente
  • Tabella di salto ADDWF PCL,F per selezionare il comportamento in base allo stato
  • Tecnica di debounce con interrupt: disabilita INTE, avvia timer, riabilita dopo ~20ms
  • Differenza tra debounce software (polling + delay) e debounce con interrupt (non bloccante)
  • Pattern di separazione ISR / FLAGS / MAIN: ognuno fa una sola cosa

📋 Scheda lezione

Lezione: 21 / 42
Modulo: 4 — Interrupt avanzati
Device: PIC16F84A
Prerequisiti: Lez. 18–19
Difficoltà: ★★★★★
Pattern: FSM + interrupt + flag
Tecnica nuova: ADDWF PCL,F come switch/case


1 · FSM — Macchina a stati finiti con interrupt

Una FSM è un sistema con un numero finito di stati, regole di transizione tra stati e azioni associate a ogni stato. Sul PIC16 si implementa con una variabile STATE in RAM e una tabella di salto ADDWF PCL,F che esegue codice diverso in base al valore di STATE. Gli interrupt pilotano le transizioni asincrone (eventi non prevedibili), mentre il main gestisce le transizioni temporali e gli output.

FSM semaforo — transizioni normali e transizione di emergenza VERDE LED_G ON 5 secondi GIALLO LED_Y ON 2 secondi ROSSO LED_R ON 4 secondi PHASE=0 PHASE=0 PHASE=0 EMRG_FLAG (RB0/INT) → forza ROSSO immediato da qualsiasi stato

Tabella di salto con ADDWF PCL,F: PCL è il byte basso del Program Counter. Aggiungendo STATE a PCL si salta di STATE istruzioni avanti rispetto all’istruzione ADDWF. Ogni caso deve essere esattamente una istruzione (GOTO oppure BSF/BCF). Se STATE=0 → esegue la prima istruzione dopo ADDWF; se STATE=1 → salta quella e esegue la seconda; ecc. Attenzione: la tabella deve stare tutta nella stessa pagina da 256 word (PCL non può “attraversare” un confine di pagina).

Codice — Semaforo con emergenza

; ============================================================
; Lezione 21 — FSM guidata da interrupt
; Semaforo a 3 stati: VERDE→GIALLO→ROSSO
; Timer0 ISR scandisce i tempi, RB0/INT forza ROSSO immediato
; PIC16F84A @ 4 MHz
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF

; Uscite semaforo su PORTA
#DEFINE LED_R   PORTA,0        ; RA0 = rosso
#DEFINE LED_Y   PORTA,1        ; RA1 = giallo
#DEFINE LED_G   PORTA,2        ; RA2 = verde

; Stati FSM
#DEFINE ST_GREEN  0x00
#DEFINE ST_YELLOW 0x01
#DEFINE ST_RED    0x02

    CBLOCK  0x0C
W_SAVE
STATUS_SAVE
STATE           ; stato corrente FSM
OVF_COUNT       ; contatore overflow Timer0
PHASE_COUNT     ; contatore fasi (durata stato corrente)
FLAGS
    ENDC

#DEFINE TICK_FLAG   FLAGS,0    ; 1s trascorso
#DEFINE EMRG_FLAG   FLAGS,1    ; emergenza: forza rosso (RB0)

; Durate stati (secondi)
#DEFINE DUR_GREEN   D'5'
#DEFINE DUR_YELLOW  D'2'
#DEFINE DUR_RED     D'4'

    ORG     0x0000
    GOTO    INIT

    ORG     0x0004
ISR
    MOVWF   W_SAVE
    SWAPF   STATUS,W
    MOVWF   STATUS_SAVE

    BTFSS   INTCON,T0IF
    GOTO    CHK_INT
    BCF     INTCON,T0IF
    DECFSZ  OVF_COUNT,F
    GOTO    CHK_INT
    MOVLW   D'15'
    MOVWF   OVF_COUNT
    BSF     TICK_FLAG

CHK_INT
    BTFSS   INTCON,INTF
    GOTO    ISR_EXIT
    BCF     INTCON,INTF
    BSF     EMRG_FLAG          ; emergenza: veicolo prioritario

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

; ============================================================
; INIT
; ============================================================
    ORG     0x0040
INIT
    BSF     STATUS,RP0
    MOVLW   b'11000111'        ; prescaler ÷256, T0CS=0
    MOVWF   OPTION_REG
    CLRF    TRISA              ; PORTA output
    MOVLW   0x01               ; RB0 input
    MOVWF   TRISB
    BCF     STATUS,RP0

    CLRF    PORTA
    CLRF    FLAGS
    MOVLW   D'15'
    MOVWF   OVF_COUNT
    MOVLW   ST_GREEN
    MOVWF   STATE
    MOVLW   DUR_GREEN
    MOVWF   PHASE_COUNT

    ; Attiva stato iniziale
    CALL    APPLY_STATE

    MOVLW   b'10110000'        ; GIE=1, T0IE=1, INTE=1
    MOVWF   INTCON

; ============================================================
; MAIN LOOP
; ============================================================
MAIN
    ; Emergenza: forza rosso immediato
    BTFSC   EMRG_FLAG
    CALL    ON_EMERGENCY

    ; Tick 1s: avanza FSM
    BTFSC   TICK_FLAG
    CALL    ON_TICK

    GOTO    MAIN

; ============================================================
; ON_EMERGENCY — forza stato ROSSO e resetta timer
; ============================================================
ON_EMERGENCY
    BCF     EMRG_FLAG
    MOVLW   ST_RED
    MOVWF   STATE
    MOVLW   DUR_RED
    MOVWF   PHASE_COUNT
    CALL    APPLY_STATE
    RETURN

; ============================================================
; ON_TICK — avanza il contatore di fase; se 0 → cambia stato
; ============================================================
ON_TICK
    BCF     TICK_FLAG
    DECFSZ  PHASE_COUNT,F
    RETURN                     ; fase corrente non ancora finita

    ; Transizione di stato
    MOVF    STATE,W
    ADDWF   PCL,F
    GOTO    TRANS_GREEN        ; ST_GREEN=0
    GOTO    TRANS_YELLOW       ; ST_YELLOW=1
    GOTO    TRANS_RED          ; ST_RED=2

TRANS_GREEN
    MOVLW   ST_YELLOW
    MOVWF   STATE
    MOVLW   DUR_YELLOW
    MOVWF   PHASE_COUNT
    GOTO    DO_APPLY

TRANS_YELLOW
    MOVLW   ST_RED
    MOVWF   STATE
    MOVLW   DUR_RED
    MOVWF   PHASE_COUNT
    GOTO    DO_APPLY

TRANS_RED
    MOVLW   ST_GREEN
    MOVWF   STATE
    MOVLW   DUR_GREEN
    MOVWF   PHASE_COUNT

DO_APPLY
    CALL    APPLY_STATE
    RETURN

; ============================================================
; APPLY_STATE — accende i LED in base a STATE
; ============================================================
APPLY_STATE
    CLRF    PORTA              ; spegni tutto
    MOVF    STATE,W
    ADDWF   PCL,F
    BSF     LED_G              ; ST_GREEN
    RETURN
    BSF     LED_Y              ; ST_YELLOW
    RETURN
    BSF     LED_R              ; ST_RED
    RETURN

    END

2 · Debounce hardware con interrupt

Un pulsante meccanico non chiude il contatto in modo netto: per 5–20ms oscilla tra aperto e chiuso (rimbalzo, o bounce). Senza debounce, un’unica pressione fisica genera 5–50 interrupt. La soluzione con interrupt è elegante: alla prima rilevazione si disabilita INTE (BCF INTCON,INTE), si avvia un timer di ~20ms, e solo alla scadenza si verifica lo stato stabile del pin e si riabilita INTE. Nessun delay bloccante, nessun ciclo di polling.

Debounce con interrupt — timeline rimbalzo meccanico t=0 t=30ms segnale RB0 (rimbalzo meccanico) INTF→BCF INTE debounce attivo — INTE disabilitato — 10 overflow TMR0 ≈ 20ms verifica RB0 → BSF BTN_VALID → riabilita INTE main: BTFSC BTN_VALID → azione rimbalzi ignorati (INTE=0)
; ============================================================
; Lezione 21 — Debounce hardware/software con interrupt
; Tecnica: prima ISR disabilita INTE, avvia timer debounce,
;          seconda ISR (T0) riabilita INTE dopo ~20ms
; Elimina false rilevazioni da rimbalzo meccanico pulsante
; PIC16F84A @ 4 MHz
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF

    CBLOCK  0x0C
W_SAVE
STATUS_SAVE
DEBOUNCE_CNT    ; contatore overflow per debounce (~20ms)
PRESS_COUNT     ; numero pressioni valide rilevate
FLAGS
    ENDC

#DEFINE DEBOUNCING  FLAGS,0    ; 1 = in fase di debounce
#DEFINE BTN_VALID   FLAGS,1    ; pressione valida confermata

    ORG     0x0000
    GOTO    INIT

    ORG     0x0004
ISR
    MOVWF   W_SAVE
    SWAPF   STATUS,W
    MOVWF   STATUS_SAVE

    ; --- RB0/INT: prima pressione rilevata ---
    BTFSS   INTCON,INTF
    GOTO    CHK_T0
    BCF     INTCON,INTF
    ; Disabilita ulteriori interrupt su RB0 durante debounce
    BCF     INTCON,INTE
    BSF     DEBOUNCING
    ; Avvia timer debounce: 10 overflow × ~2ms = ~20ms
    ; (prescaler ÷8, PS=010, overflow ogni ~2.048ms)
    MOVLW   D'10'
    MOVWF   DEBOUNCE_CNT
    GOTO    ISR_EXIT

    ; --- Timer0: scadenza debounce ---
CHK_T0
    BTFSS   INTCON,T0IF
    GOTO    ISR_EXIT
    BCF     INTCON,T0IF
    BTFSS   DEBOUNCING
    GOTO    ISR_EXIT           ; T0 normale, non in debounce

    DECFSZ  DEBOUNCE_CNT,F
    GOTO    ISR_EXIT           ; debounce non ancora scaduto

    ; Debounce completato: verifica che RB0 sia ancora premuto
    BCF     DEBOUNCING
    BTFSS   PORTB,0            ; RB0=0 → ancora premuto (active low)
    BSF     BTN_VALID          ; pressione confermata
    ; Riabilita interrupt su RB0
    BSF     INTCON,INTE

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

    ORG     0x0040
INIT
    BSF     STATUS,RP0
    ; PS=010 → prescaler ÷8, overflow ogni ~2ms
    MOVLW   b'10000010'        ; RBPU=1, T0CS=0, PSA=0, PS=010
    MOVWF   OPTION_REG
    MOVLW   0x01               ; RB0 input
    MOVWF   TRISB
    CLRF    TRISA
    BCF     STATUS,RP0

    CLRF    PORTA
    CLRF    FLAGS
    CLRF    PRESS_COUNT

    MOVLW   b'10110000'        ; GIE=1, T0IE=1, INTE=1
    MOVWF   INTCON

MAIN
    BTFSC   BTN_VALID
    CALL    ON_VALID_PRESS
    GOTO    MAIN

ON_VALID_PRESS
    BCF     BTN_VALID
    INCF    PRESS_COUNT,F
    ; Mostra conteggio su PORTA (0-15)
    MOVF    PRESS_COUNT,W
    ANDLW   0x0F
    MOVWF   PORTA
    RETURN

    END

3 · Separazione delle responsabilità

Il pattern che emerge da entrambi i programmi è lo stesso: ISR = sensore (rileva e segnala), FLAGS = canale di comunicazione, MAIN = attuatore (decide e agisce). Questa separazione rende il codice modulare, testabile e scalabile: si possono aggiungere nuove sorgenti di interrupt senza toccare la logica del main, e si può cambiare la logica del main senza toccare la ISR.

Pattern FSM con interrupt — separazione responsabilità ISR (Interrupt) Rileva eventi hardware: • TMR0 overflow → TICK_FLAG • RB0/INT → EMRG_FLAG Breve, deterministico Non modifica stato FSM Solo setta flag in RAM FLAGS (RAM) TICK_FLAG EMRG_FLAG MAIN loop Gestisce logica FSM: • ON_TICK → transizione stato • ON_EMERGENCY → forza RED • APPLY_STATE → LED output Può essere lento Non perde eventi (flag)

🧪 Prova tu

  1. Carica il semaforo. Esegui in Step e osserva STATE cambiare 0→1→2→0. Conta i tick (PHASE_COUNT) per ogni stato: 5 per verde, 2 per giallo, 4 per rosso.
  2. Durante lo stato VERDE (STATE=0), porta RB0 a 1 nel pannello I/O. Osserva EMRG_FLAG settarsi e STATE saltare direttamente a ST_RED=2, saltando il giallo.
  3. Carica il debounce. Osserva FLAGS nel pannello: quando porti RB0 a 0→1→0 rapidamente (3 volte in meno di 20ms simulati), DEBOUNCING rimane 1 per tutto il periodo e BTN_VALID si setta solo una volta alla fine.
  4. Sfida: aggiungi un quarto stato ST_BLINK al semaforo: LED giallo che lampeggia (ON/OFF alternato ogni secondo) prima di tornare al verde. PHASE_COUNT = 4 (4 lampeggi). Modifica APPLY_STATE e TRANS_RED per includere il nuovo stato.

❓ Quiz rapido

1. Nella tabella di salto ADDWF PCL,F, cosa succede se STATE=3 ma ci sono solo 3 GOTO (0, 1, 2)?

Mostra risposta

Il PC salta di 3 istruzioni oltre ADDWF e atterza sulla prima istruzione dopo la tabella, che potrebbe essere qualsiasi cosa — un’altra label, un MOVLW, un dato. Il comportamento è imprevedibile e quasi certamente un bug. Difesa: aggiungere un controllo ANDLW 0x03 prima di ADDWF PCL,F per limitare STATE a 0-3, oppure aggiungere un caso di default con GOTO ERROR alla fine della tabella.


2. Nel debounce, perché si verifica lo stato di RB0 alla scadenza del timer invece di considerare la pressione sempre valida?

Mostra risposta

Un pulsante può essere premuto e rilasciato in meno di 20ms (click velocissimo) oppure l’utente può tenere premuto. La verifica serve per distinguere un rimbalzo da una pressione reale: se dopo 20ms RB0 è ancora a livello attivo, significa che c’è stata una vera pressione intenzionale. Se è già tornato al livello di riposo, potrebbe essere stato solo un disturbo o un rimbalzo molto lungo. Questa verifica finale riduce i falsi positivi senza perdere pressioni veloci genuine.


3. Nel semaforo, ON_EMERGENCY imposta PHASE_COUNT = DUR_RED. Cosa succederebbe se si dimenticasse questa riga?

Mostra risposta

PHASE_COUNT manterrebbe il valore residuo dello stato precedente. Se l’emergenza scatta quando mancano ancora 3 secondi al verde (PHASE_COUNT=3), il rosso di emergenza durerebbe solo 3 secondi invece di 4. Peggio: se l’emergenza scatta quando PHASE_COUNT era già 0 (appena dopo una transizione), la prossima transizione da ON_TICK avverrebbe immediatamente al primo tick successivo, uscendo dal rosso dopo 1 secondo. Il reset di PHASE_COUNT è essenziale per garantire la durata corretta ad ogni ingresso forzato nello stato.

Prossima lezione →

Lezione 22 — Watchdog Timer e Sleep Mode
Il WDT come cane da guardia contro i blocchi software. La modalità SLEEP per ridurre il consumo a pochi µA. Svegliarsi con WDT o con interrupt esterno.

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