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,Fper 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.
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.
; ============================================================ ; 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.
🧪 Prova tu
- 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.
- 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.
- 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.
- 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