Lezione 33
🎓 PIC16 — From Zero to Hero · Lezione 33 · Modulo 6
Macchine a Stati Finiti (FSM) 🔄
Progettare comportamenti complessi con stati, transizioni e tabelle dispatch.
Semaforo a 4 stati, debounce tastiera e controller porta con PIN e allarme.
🎯 Cosa impari in questa lezione
- Cos’è una macchina a stati finiti: stati, transizioni, azioni di ingresso/uscita
- Rappresentazione FSM in assembly: variabile di stato + tabella dispatch tramite PCL
- Tabelle RETLW per output, durate e transizioni: ogni tabella è una funzione pura indicizzata dallo stato
- FSM semaforo: 4 stati con durate diverse, loop senza IF annidati
- FSM debounce tastiera: 4 stati (IDLE → PRESSED → HELD → RELEASED), contatori millisecondo
- FSM controller porta: stati multipli con timeout, tentativi PIN, transizione verso allarme
- Principio chiave: separare cosa fare (azioni di ingresso) da quando cambiare (condizioni di transizione)
📋 Scheda lezione
Lezione: 33 / 42
Modulo: 6 — Tecniche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 07 (tabelle RETLW), Lez. 21 (interrupt)
Difficoltà: ★★★★☆
Tecnica: FSM + tabelle dispatch via PCL
Applicazioni: semaforo, tastiera, controllo accessi
1 · Anatomia di una FSM in assembly
Una macchina a stati finiti è un modo formale di descrivere un sistema che reagisce a eventi e ricorda in quale fase si trova. In assembly PIC la struttura è semplice: un registro FSM_STATE contiene lo stato corrente (un intero 0…N−1), e una tabella dispatch via ADDWF PCL,F usa quello stato come indice per saltare al handler corretto. Le tre tabelle RETLW (output, durata, next state) rendono le transizioni leggibili come dati, non come logica if-else. Aggiungere un nuovo stato significa aggiungere una riga a ogni tabella — nessuna modifica al codice di controllo.
; Pattern base FSM in assembly PIC
FSM_STATE → registro che vale 0, 1, 2… (stato corrente)
TABELLA_NEXT: ; stato → prossimo stato
ADDWF PCL,F
RETLW 1 ; da stato 0 → vai a 1
RETLW 2 ; da stato 1 → vai a 2
RETLW 0 ; da stato 2 → torna a 0
FSM_DISPATCH: ; esegui handler dello stato corrente
ADDWF PCL,F
GOTO HANDLER_0
GOTO HANDLER_1
GOTO HANDLER_2
2 · FSM semaforo — 4 stati con tabelle
; ============================================================ ; Lezione 33 — FSM: semaforo a 4 stati ; PIC16F877A @ 4 MHz ; Stati: VERDE(0) → GIALLO(1) → ROSSO(2) → ROSSO_GIALLO(3) ; Uscite su PORTB: RB0=verde, RB1=giallo, RB2=rosso ; Tempi: VERDE=30s, GIALLO=3s, ROSSO=30s, ROSSO_GIALLO=2s ; Tecnica: tabella dispatch in Flash (RETLW) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 FSM_STATE ; stato corrente (0-3) FSM_TIMER ; contatore secondi nello stato DELAY_CNT1 DELAY_CNT2 DELAY_CNT3 ENDC ; Bit output semaforo #DEFINE LED_VERDE PORTB,0 #DEFINE LED_GIALLO PORTB,1 #DEFINE LED_ROSSO PORTB,2 ; Codici uscite per ogni stato (bitmask PORTB) #DEFINE OUT_VERDE b'00000001' #DEFINE OUT_GIALLO b'00000010' #DEFINE OUT_ROSSO b'00000100' #DEFINE OUT_ROSSO_GIALLO b'00000110' ORG 0x0000 GOTO INIT ; ============================================================ ; Tabella uscite: indice=stato → bitmask PORTB ; ============================================================ FSM_OUTPUT_TBL ADDWF PCL,F RETLW OUT_VERDE ; stato 0 RETLW OUT_GIALLO ; stato 1 RETLW OUT_ROSSO ; stato 2 RETLW OUT_ROSSO_GIALLO ; stato 3 ; ============================================================ ; Tabella durate: indice=stato → secondi ; ============================================================ FSM_DURATION_TBL ADDWF PCL,F RETLW D'30' ; stato 0: VERDE 30s RETLW D'3' ; stato 1: GIALLO 3s RETLW D'30' ; stato 2: ROSSO 30s RETLW D'2' ; stato 3: ROSSO+GIALLO 2s ; ============================================================ ; Tabella transizioni: indice=stato → stato successivo ; ============================================================ FSM_NEXT_TBL ADDWF PCL,F RETLW D'1' ; 0→1 (VERDE→GIALLO) RETLW D'2' ; 1→2 (GIALLO→ROSSO) RETLW D'3' ; 2→3 (ROSSO→ROSSO_GIALLO) RETLW D'0' ; 3→0 (ROSSO_GIALLO→VERDE) ORG 0x0050 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 CLRF PORTB CLRF FSM_STATE CALL FSM_ENTER_STATE ; ============================================================ ; MAIN LOOP — macchina a stati ; ============================================================ MAIN ; Attendi 1 secondo CALL DELAY_1S ; Decrementa timer stato DECFSZ FSM_TIMER,F GOTO MAIN ; ancora nel medesimo stato ; Timer esaurito: transizione MOVF FSM_STATE,W CALL FSM_NEXT_TBL MOVWF FSM_STATE CALL FSM_ENTER_STATE GOTO MAIN ; ============================================================ ; FSM_ENTER_STATE — azioni di ingresso per lo stato corrente ; Imposta uscite e carica timer durata ; ============================================================ FSM_ENTER_STATE ; Imposta uscite PORTB MOVF FSM_STATE,W CALL FSM_OUTPUT_TBL MOVWF PORTB ; Carica timer durata MOVF FSM_STATE,W CALL FSM_DURATION_TBL MOVWF FSM_TIMER RETURN ; ============================================================ ; DELAY_1S — circa 1 secondo con Fosc=4MHz ; ============================================================ DELAY_1S MOVLW D'100' MOVWF DELAY_CNT1 D1 MOVLW D'250' MOVWF DELAY_CNT2 D2 MOVLW D'40' MOVWF DELAY_CNT3 D3 DECFSZ DELAY_CNT3,F GOTO D3 DECFSZ DELAY_CNT2,F GOTO D2 DECFSZ DELAY_CNT1,F GOTO D1 RETURN END
⚠️ Limite tabelle RETLW con PCL: la tecnica ADDWF PCL,F funziona solo se la tabella è interamente nella stessa pagina di 256 byte della Flash. Se la tabella attraversa un confine di pagina (PCL overflow da 0xFF a 0x00) il salto finirà nel posto sbagliato. Soluzione: posizionare le tabelle all’inizio del programma (ORG bassa) dove la pagina è garantita, oppure usare PCLATH per gestire salti su pagine diverse.
3 · FSM debounce tastiera 4×4
; ============================================================ ; Lezione 33 — FSM: tastiera a matrice 4×4 con debounce ; PIC16F877A @ 4 MHz ; Matrice: RB7:RB4 = righe (output), RB3:RB0 = colonne (input) ; Scansione: attiva una riga alla volta, leggi colonne ; Codici tasto: 0-9, A-D, *, # ; FSM interna per debounce: IDLE→PRESSED→HELD→RELEASED ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; FSM tastiera KB_STATE ; 0=IDLE, 1=PRESSED, 2=HELD, 3=RELEASED KB_DEBOUNCE ; contatore debounce (ms) KB_RAW ; riga:colonna raw (high nibble=riga, low=col) KB_PREV_RAW ; lettura precedente KB_CODE ; codice ASCII del tasto KB_READY ; flag: tasto pronto da elaborare ; Scansione SCAN_ROW ; riga corrente (0-3) COL_READ ; colonne lette SCRATCH ENDC ; Soglie debounce #DEFINE DEBOUNCE_PRESS D'20' ; 20ms per conferma pressione #DEFINE DEBOUNCE_REL D'10' ; 10ms per conferma rilascio ORG 0x0000 GOTO INIT ; ============================================================ ; Tabella codici tasto: indice = riga*4 + colonna → ASCII ; Layout fisico: ; 1 2 3 A ; 4 5 6 B ; 7 8 9 C ; * 0 # D ; ============================================================ KB_TABLE ADDWF PCL,F RETLW '1' RETLW '2' RETLW '3' RETLW 'A' RETLW '4' RETLW '5' RETLW '6' RETLW 'B' RETLW '7' RETLW '8' RETLW '9' RETLW 'C' RETLW '*' RETLW '0' RETLW '#' RETLW 'D' ORG 0x0050 INIT BSF STATUS,RP0 ; RB7:RB4 output (righe), RB3:RB0 input con pull-up MOVLW b'00001111' MOVWF TRISB ; Abilita pull-up interni PORTB BCF OPTION_REG,7 ; RBPU=0 → pull-up attivi BCF STATUS,RP0 CLRF KB_STATE CLRF KB_DEBOUNCE CLRF KB_READY CLRF SCAN_ROW MOVLW 0xFF MOVWF KB_PREV_RAW ; nessun tasto precedente ; ============================================================ ; MAIN — ciclo ogni 1ms (chiamata a FSM_KEYBOARD ogni ms) ; ============================================================ MAIN CALL DELAY_1MS CALL KB_SCAN ; scansione + FSM in un passo ; Se tasto pronto, elabora BTFSS KB_READY,0 GOTO MAIN BCF KB_READY,0 ; Azione: mostra ASCII su PORTD (debug) ; In sistema reale: invia via UART, aggiorna display... MOVF KB_CODE,W ANDLW 0x0F ; nibble basso MOVWF PORTB ; (riuso PORTB, in realtà usare altro) GOTO MAIN ; ============================================================ ; KB_SCAN — scansione una riga, aggiorna FSM debounce ; ============================================================ KB_SCAN ; Attiva riga corrente: metti 0 sulla riga, 1 sulle altre MOVLW 0xF0 ; tutte righe alte (no riga attiva) MOVWF SCRATCH ; Azzera il bit della riga corrente ; SCAN_ROW: 0→bit4, 1→bit5, 2→bit6, 3→bit7 MOVF SCAN_ROW,W ADDLW 4 ; offset a bit4 MOVWF COL_READ ; riuso come contatore shift BSF STATUS,C MOVLW 0xFF SHIFT_MASK RLF SCRATCH,F ; 1 verso sinistra DECFSZ COL_READ,F GOTO SHIFT_MASK ; Ora Carry ha il bit da azzerare ; Approccio semplificato: lookup diretto MOVF SCAN_ROW,W CALL ROW_MASK_TBL MOVWF PORTB ; attiva riga NOP ; stabilizzazione ~250ns NOP ; Leggi colonne (RB3:RB0, attive basse per pull-up) MOVF PORTB,W ANDLW 0x0F ; solo nibble basso COMF WREG,W ; inverti (attivo alto) ANDLW 0x0F MOVWF COL_READ ; Avanza riga per prossima chiamata INCF SCAN_ROW,F MOVF SCAN_ROW,W SUBLW D'4' BTFSS STATUS,Z GOTO KB_HAVE_COL CLRF SCAN_ROW ; wrap KB_HAVE_COL ; Qualche tasto premuto? MOVF COL_READ,F BTFSS STATUS,Z GOTO KB_SOMETHING ; colonna != 0 → tasto premuto ; Nessun tasto premuto GOTO KB_FSM_NO_KEY KB_SOMETHING ; Trova colonna premuta (bit più basso) MOVLW 0 MOVWF SCRATCH FIND_COL BTFSC COL_READ,0 GOTO COL_FOUND RRF COL_READ,F INCF SCRATCH,F GOTO FIND_COL COL_FOUND ; KB_RAW = riga * 4 + colonna MOVF SCAN_ROW,W ADDLW 0xFF ; riga precedente (già incrementata) ANDLW 0x03 MOVWF KB_RAW BCF STATUS,C RLF KB_RAW,F RLF KB_RAW,F ; riga * 4 MOVF SCRATCH,W ADDWF KB_RAW,F ; + colonna GOTO KB_FSM_KEY ; ============================================================ ; FSM debounce — stato IDLE (0) ; ============================================================ KB_FSM_NO_KEY ; Nessun tasto: se eravamo HELD → RELEASED MOVF KB_STATE,W SUBLW D'2' ; HELD=2? BTFSS STATUS,Z GOTO KB_NO_ACTION ; Transizione a RELEASED MOVLW D'3' MOVWF KB_STATE MOVLW DEBOUNCE_REL MOVWF KB_DEBOUNCE KB_NO_ACTION ; Se RELEASED, decrementa debounce MOVF KB_STATE,W SUBLW D'3' BTFSS STATUS,Z RETURN DECFSZ KB_DEBOUNCE,F RETURN CLRF KB_STATE ; torna IDLE RETURN KB_FSM_KEY ; Tasto presente: aggiorna FSM MOVF KB_STATE,W BTFSC STATUS,Z ; IDLE=0? GOTO KB_START_PRESS MOVLW D'1' SUBWF KB_STATE,W BTFSC STATUS,Z ; PRESSED=1? GOTO KB_CONFIRM_PRESS RETURN ; HELD o RELEASED: nessuna azione KB_START_PRESS ; IDLE → PRESSED MOVLW D'1' MOVWF KB_STATE MOVLW DEBOUNCE_PRESS MOVWF KB_DEBOUNCE MOVF KB_RAW,W MOVWF KB_PREV_RAW RETURN KB_CONFIRM_PRESS ; PRESSED: stesso tasto? MOVF KB_RAW,W SUBWF KB_PREV_RAW,W BTFSS STATUS,Z GOTO KB_START_PRESS ; cambiato → ricomincia DECFSZ KB_DEBOUNCE,F RETURN ; Debounce completato → HELD, tasto valido MOVLW D'2' MOVWF KB_STATE ; Decodifica ASCII MOVF KB_RAW,W CALL KB_TABLE MOVWF KB_CODE BSF KB_READY,0 ; segnala al main loop RETURN ; ============================================================ ; Tabella maschere attivazione righe ; ============================================================ ROW_MASK_TBL ADDWF PCL,F RETLW b'11101111' ; riga 0: RB4=0 RETLW b'11011111' ; riga 1: RB5=0 RETLW b'10111111' ; riga 2: RB6=0 RETLW b'01111111' ; riga 3: RB7=0 DELAY_1MS MOVLW D'83' MOVWF SCRATCH DMS DECFSZ SCRATCH,F GOTO DMS RETURN END
4 · FSM controller porta con PIN e allarme
; ============================================================ ; Lezione 33 — FSM: controllo porta con PIN e timeout ; PIC16F877A @ 4 MHz ; Stati: LOCKED → ENTERING → UNLOCKED → ALARM ; PIN corretto: 1234 (sequenza 4 tasti) ; Timeout: 10 secondi per inserire il PIN completo ; 3 tentativi errati → ALARM (led rosso lampeggiante 30s) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 FSM_STATE ; 0=LOCKED 1=ENTERING 2=UNLOCKED 3=ALARM INPUT_POS ; posizione corrente input (0-3) INPUT_BUF0 ; buffer pin inserito INPUT_BUF1 INPUT_BUF2 INPUT_BUF3 TIMEOUT_SEC ; secondi rimanenti ATTEMPTS ; tentativi errati LAST_KEY ; ultimo tasto ricevuto (ASCII) KEY_AVAILABLE ; flag tasto disponibile LED_BLINK ; contatore lampeggio ENDC ; PIN corretto: '1','2','3','4' #DEFINE PIN0 '1' #DEFINE PIN1 '2' #DEFINE PIN2 '3' #DEFINE PIN3 '4' ; Uscite #DEFINE LED_RED PORTB,0 ; allarme / errore #DEFINE LED_GREEN PORTB,1 ; aperto #DEFINE LED_YELLOW PORTB,2 ; inserimento in corso #DEFINE LOCK_OUT PORTB,3 ; relè serratura (1=aperto) ; Costanti #DEFINE MAX_ATTEMPTS D'3' #DEFINE TIMEOUT_INIT D'10' #DEFINE UNLOCK_TIME D'5' ; secondi porta aperta #DEFINE ALARM_TIME D'30' ; secondi allarme ORG 0x0000 GOTO INIT ORG 0x0005 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 CLRF PORTB CLRF FSM_STATE ; stato iniziale: LOCKED CLRF INPUT_POS CLRF ATTEMPTS CLRF KEY_AVAILABLE CALL STATE_LOCKED_ENTER ; ============================================================ ; MAIN — tick ogni 1 secondo + gestione tasti ; ============================================================ MAIN CALL DELAY_1S ; Simulazione tasto: in sistema reale viene dalla FSM KB ; Per demo: KEY_AVAILABLE e LAST_KEY impostati esternamente ; (qui simuliamo la sequenza 1-2-3-4) ; Dispatch sullo stato corrente MOVF FSM_STATE,W ANDLW 0x03 CALL FSM_DISPATCH GOTO MAIN ; ============================================================ ; Tabella dispatch: stato → handler ; (Non si può fare CALL da tabella RETLW direttamente, ; quindi usiamo GOTO calcolato tramite PCL) ; ============================================================ FSM_DISPATCH ADDWF PCL,F GOTO FSM_LOCKED GOTO FSM_ENTERING GOTO FSM_UNLOCKED GOTO FSM_ALARM ; ============================================================ ; Stato 0: LOCKED ; ============================================================ FSM_LOCKED ; Attendi primo tasto BTFSS KEY_AVAILABLE,0 RETURN BCF KEY_AVAILABLE,0 ; Qualsiasi tasto → ENTERING MOVLW D'1' MOVWF FSM_STATE CLRF INPUT_POS MOVLW TIMEOUT_INIT MOVWF TIMEOUT_SEC CALL STATE_LOCKED_ENTER + 4 ; usa enter di entering ; Memorizza primo tasto MOVF LAST_KEY,W MOVWF INPUT_BUF0 INCF INPUT_POS,F RETURN ; ============================================================ ; Stato 1: ENTERING ; ============================================================ FSM_ENTERING ; Decrementa timeout ogni secondo DECFSZ TIMEOUT_SEC,F GOTO ENTER_CHECK_KEY ; Timeout: torna a LOCKED CALL STATE_LOCKED_ENTER GOTO FSM_DONE ENTER_CHECK_KEY BTFSS KEY_AVAILABLE,0 RETURN BCF KEY_AVAILABLE,0 ; Memorizza tasto nella posizione corrente MOVF INPUT_POS,W ADDWF PCL,F GOTO STORE_0 GOTO STORE_1 GOTO STORE_2 GOTO STORE_3 STORE_0 MOVF LAST_KEY,W MOVWF INPUT_BUF0 GOTO STORED STORE_1 MOVF LAST_KEY,W MOVWF INPUT_BUF1 GOTO STORED STORE_2 MOVF LAST_KEY,W MOVWF INPUT_BUF2 GOTO STORED STORE_3 MOVF LAST_KEY,W MOVWF INPUT_BUF3 STORED INCF INPUT_POS,F MOVF INPUT_POS,W SUBLW D'4' BTFSS STATUS,Z RETURN ; non ancora 4 tasti ; 4 tasti inseriti: verifica PIN CALL CHECK_PIN BTFSC STATUS,Z ; Z=1 → PIN corretto GOTO PIN_WRONG PIN_OK CLRF ATTEMPTS MOVLW D'2' ; stato UNLOCKED MOVWF FSM_STATE CALL STATE_UNLOCKED_ENTER RETURN PIN_WRONG INCF ATTEMPTS,F MOVF ATTEMPTS,W SUBLW MAX_ATTEMPTS BTFSS STATUS,Z GOTO FSM_RETRY ; non ancora max tentativi ; Troppi errori → ALARM MOVLW D'3' MOVWF FSM_STATE CALL STATE_ALARM_ENTER RETURN FSM_RETRY CALL STATE_LOCKED_ENTER FSM_DONE RETURN ; ============================================================ ; Stato 2: UNLOCKED ; ============================================================ FSM_UNLOCKED DECFSZ TIMEOUT_SEC,F RETURN ; Tempo scaduto → torna LOCKED CALL STATE_LOCKED_ENTER RETURN ; ============================================================ ; Stato 3: ALARM ; ============================================================ FSM_ALARM DECFSZ TIMEOUT_SEC,F GOTO ALARM_BLINK ; Allarme finito → torna LOCKED CLRF ATTEMPTS CALL STATE_LOCKED_ENTER RETURN ALARM_BLINK ; Lampeggia LED rosso MOVLW b'00000001' XORWF PORTB,F RETURN ; ============================================================ ; CHECK_PIN — confronta INPUT_BUF con PIN corretto ; Return: Z=1 se corretto, Z=0 se errato ; ============================================================ CHECK_PIN MOVLW PIN0 SUBWF INPUT_BUF0,W BTFSS STATUS,Z RETURN ; Z=0 già MOVLW PIN1 SUBWF INPUT_BUF1,W BTFSS STATUS,Z RETURN MOVLW PIN2 SUBWF INPUT_BUF2,W BTFSS STATUS,Z RETURN MOVLW PIN3 SUBWF INPUT_BUF3,W ; Z=1 se uguale RETURN ; ============================================================ ; Azioni di ingresso per ogni stato ; ============================================================ STATE_LOCKED_ENTER CLRF FSM_STATE CLRF INPUT_POS CLRF PORTB BSF LED_RED ; LED rosso = bloccato BCF LOCK_OUT ; serratura chiusa RETURN STATE_UNLOCKED_ENTER CLRF PORTB BSF LED_GREEN ; LED verde = aperto BSF LOCK_OUT ; serratura aperta MOVLW UNLOCK_TIME MOVWF TIMEOUT_SEC RETURN STATE_ALARM_ENTER CLRF PORTB MOVLW ALARM_TIME MOVWF TIMEOUT_SEC CLRF LED_BLINK RETURN DELAY_1S MOVLW D'100' MOVWF 0x70 D1 MOVLW D'250' MOVWF 0x71 D2 MOVLW D'40' MOVWF 0x72 D3 DECFSZ 0x72,F GOTO D3 DECFSZ 0x71,F GOTO D2 DECFSZ 0x70,F GOTO D1 RETURN END
🧪 Prova tu
- Carica il semaforo. Osserva la sequenza di PORTB: 0x01 (verde, 30s) → 0x02 (giallo, 3s) → 0x04 (rosso, 30s) → 0x06 (rosso+giallo, 2s) → 0x01… Per accelerare il test, modifica temporaneamente FSM_DURATION_TBL: metti tutti i valori a 3 (3 secondi) e verifica il ciclo completo in ~12 secondi.
- Aggiungi al semaforo un quinto stato: LAMPEGGIO (LED giallo che lampeggia a 1Hz quando il semaforo è in modalità notturna). L’ingresso in LAMPEGGIO avviene da un input esterno su RB7. Aggiungi una riga a tutte e tre le tabelle RETLW e un handler LAMPEGGIO che controlla TMR0 per il toggle. Come gestisci l’uscita dallo stato LAMPEGGIO?
- Carica il controller porta. Imposta LAST_KEY=’1′ e KEY_AVAILABLE=1 ogni secondo (simulando l’inserimento di ‘1’,’2′,’3′,’4′). Dopo 4 tasti corretti: FSM_STATE deve valere 2 (UNLOCKED), LED_GREEN alto, LOCK_OUT alto. Dopo 5 secondi: torna automaticamente a LOCKED.
- Sfida: combina la FSM debounce (codice 2) con il controller porta (codice 3). La FSM tastiera genera KB_CODE e KB_READY; la FSM porta consuma KB_CODE come LAST_KEY. I due sistemi girano in tempi diversi: la tastiera ha bisogno di un tick ogni 1ms, la porta ogni 1s. Struttura il main loop con un contatore ms che chiama KB_SCAN ogni ms e aggiorna il timer porta ogni 1000 ticks.
❓ Quiz rapido
1. Nel semaforo, la transizione avviene tramite FSM_NEXT_TBL che restituisce il prossimo stato. Cosa succederebbe se si usasse invece INCF FSM_STATE,F con un reset a 0 quando raggiunge 4?
Mostra risposta
Funzionerebbe per questa FSM specifica (gli stati sono già in sequenza 0→1→2→3→0), ma è fragile e non generalizzabile. Il problema principale è che con INCF le transizioni sono sempre sequenziali — non è possibile avere transizioni condizionali (es. dallo stato 2 si va al 3 normalmente ma allo stato 0 in caso di emergenza). La tabella FSM_NEXT_TBL separa la logica di transizione dai dati: per cambiare il comportamento basta modificare una RETLW, senza toccare il codice di controllo. È anche auto-documentante: leggendo la tabella si vede immediatamente la topologia della FSM.
2. Perché il debounce per il rilascio (10ms) è più corto di quello per la pressione (20ms)? Non dovrebbero essere simmetrici?
Mostra risposta
I rimbalzi meccanici sono asimmetrici: alla pressione il contatto si chiude e rimbalza più volte (oscillazioni per 5–20ms), mentre al rilascio il contatto si apre e l’oscillazione è più breve e meno energetica (2–5ms). Quindi 20ms alla pressione garantisce di aspettare la fine di tutti i rimbalzi, mentre 10ms al rilascio è sufficiente per confermare l’apertura. Usare 20ms anche per il rilascio è più sicuro ma introduce un ritardo percepibile nella risposta del sistema. In applicazioni critiche (tastature veloci, encoder) si usano valori ancora più bassi (5–8ms per entrambi) con hardware di filtraggio RC aggiuntivo.
3. Nel controller porta, il PIN è memorizzato come costanti #DEFINE nel codice sorgente (visibili nel file .asm e nel .hex). In un sistema reale, quali sono le implicazioni di sicurezza e come si potrebbe migliorare?
Mostra risposta
Le costanti #DEFINE vengono compilate come RETLW/SUBLW nel codice macchina — chiunque legga il file .hex con un programmer può estrarre il PIN in pochi secondi. Miglioramenti possibili: (1) Memorizzare il PIN in EEPROM cifrata o XOR con una chiave fissa, e confrontarlo a runtime — non è sicurezza forte ma ostacola la lettura diretta del .hex. (2) Abilitare il Code Protect bit nella config word (__CONFIG _CP_ON) che impedisce la lettura della Flash tramite ICSP/MCLR. (3) In un sistema serio, usare un microcontrollore con True Secure Boot e storage cifrato per le credenziali. Per scopi didattici il #DEFINE è accettabile, ma va sempre documentato come “non per uso produzione”.
Prossima lezione →
Lezione 34 — Lookup table in Flash e aritmetica avanzata
Tabelle seno/coseno per display, moltiplicazione 8×8→16 bit, divisione con resto, numeri in virgola fissa. Ottimizzazione: codice < 1µs per operazione.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 33