Lezione 34 di 43

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

FSM Semaforo — diagramma degli stati VERDE RB0=1 30s GIALLO RB1=1 3s ROSSO RB2=1 30s R+G RB1+RB2 2s timer=0 timer=0 timer=0 timer=0 → torna VERDE START
; ============================================================
; 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

FSM Debounce tastiera — stati e transizioni IDLE nessun tasto PRESSED conta 20ms HELD tasto valido ✓ RELEASED conta 10ms tasto rilevato 20ms stabile → KB_READY=1 tasto rilasciato 10ms stabile → IDLE tasto cambiato → reset 20ms KB_READY=1 solo nella transizione PRESSED→HELD Il main loop legge KB_CODE e azzera KB_READY — un solo evento per pressione, rimbalzi eliminati
; ============================================================
; 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

FSM Controllo porta — stati, condizioni e azioni di ingresso LOCKED LED_RED=1 LOCK_OUT=0 attesa tasto ENTERING LED_YELLOW=1 conta 4 tasti timeout 10s UNLOCKED LED_GREEN=1 LOCK_OUT=1 timer 5s ALARM (30s lampeggio) tasto ricevuto PIN corretto timer=0 timeout / PIN errato (<3 tentativi) 3 errori 30s
; ============================================================
; 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

  1. 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.
  2. 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?
  3. 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.
  4. 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