Lezione 19 di 43

Lezione 18

🎓 PIC16 — From Zero to Hero · Lezione 18 · Modulo 3

Interrupt su Timer0 — La CPU finalmente libera ⚡

Come abilitare GIE e T0IE, scrivere la prima ISR, salvare il contesto
e disaccoppiare il timer dal main loop con il pattern flag condiviso.

🎯 Cosa impari in questa lezione

  • Come funziona il meccanismo hardware degli interrupt sul PIC16
  • Configurare GIE e T0IE in INTCON per abilitare l’interrupt su Timer0
  • Scrivere una ISR al vettore 0x0004 con RETFIE
  • Salvare e ripristinare W e STATUS con la tecnica SWAPF (senza alterare i flag)
  • Perché MOVF STATUS,W è sbagliato nel prologo ISR
  • Il pattern flag condiviso: ISR setta un bit, main loop lo legge e agisce
  • Regole pratiche per ISR brevi e sicure

📋 Scheda lezione

Lezione: 18 / 42
Modulo: 3 — Timer
Device: PIC16F84A
Prerequisiti: Lez. 16–17
Difficoltà: ★★★★☆
Istruzione nuova: RETFIE
Tecnica chiave: ISR, salvataggio contesto, flag


1 · Come funzionano gli interrupt sul PIC16

Quando una periferica setta un flag IF (es. T0IF) e il corrispondente bit IE è abilitato (T0IE=1) e il Global Interrupt Enable è attivo (GIE=1), il PIC completa l’istruzione corrente, azzera GIE automaticamente, salva il PC sullo stack hardware e salta al vettore di interrupt 0x0004. L’istruzione RETFIE chiude la ISR, ripristina il PC e riabilita GIE in un solo ciclo.

Flusso di esecuzione con interrupt abilitato (GIE=1, T0IE=1) Main loop NOP GOTO MAIN CPU libera può fare qualsiasi cosa PC = 0x0020… T0IF=1 GIE→0 ISR @ 0x0004 MOVWF W_SAVE SWAPF STATUS,W… ① salva contesto BCF T0IF toggle LED… ② esegui task SWAPF… RETFIE ③ ripristina, GIE→1 RETFIE GIE→1 Riprende il main all’istruzione successiva al NOP

⚠ GIE non viene riabilitato all’interno della ISR. Se una seconda sorgente di interrupt scatta durante la ISR, il flag viene settato ma la ISR non viene interrotta (GIE=0). Il PIC16 non supporta interrupt annidati nativamente. Al RETFIE, GIE torna a 1 e il secondo interrupt viene servito subito dopo.


2 · Salvataggio del contesto — perché SWAPF

La ISR può essere invocata in qualsiasi punto del main loop. Se il main stava eseguendo un’operazione che dipende da W o dai flag di STATUS (Z, C, DC) e la ISR li sovrascrive senza salvare, al ritorno il main trova dati corrotti — un bug intermittente quasi impossibile da debuggare. La soluzione canonica PIC16 usa SWAPF perché è l’unica istruzione che legge un registro senza alterare nessun flag.

Salvataggio e ripristino contesto — perché SWAPF invece di MOVF ❌ Approccio sbagliato MOVWF W_SAVE ; OK, non tocca STATUS MOVF STATUS,W ; ← altera Z e C ! MOVWF STATUS_SAVE MOVF legge STATUS ma setta Z=1 se STATUS=0. STATUS_SAVE conterrebbe Z=1 invece del valore originale — il contesto è corrotto. → bug intermittente, difficile da riprodurre ✓ Approccio corretto MOVWF W_SAVE ; salva W SWAPF STATUS,W ; legge STATUS, ; non altera flag MOVWF STATUS_SAVE ; salva SWAPF scambia nibble alto/basso. Non tocca Z, C, DC. Al ripristino si applica SWAPF due volte: annullate.

Sequenza corretta salvataggio: MOVWF W_SAVE → SWAPF STATUS,W → MOVWF STATUS_SAVE
Sequenza corretta ripristino: SWAPF STATUS_SAVE,W → MOVWF STATUS → SWAPF W_SAVE,F → SWAPF W_SAVE,W → RETFIE
Il doppio SWAPF su W_SAVE annulla la permutazione dei nibble e riconsegna W esattamente com’era.


3 · Prima ISR completa — Blink con CPU libera

Timer0 con prescaler ÷256: ogni overflow ≈ 65.5ms. La ISR decrementa un contatore; ogni 8 overflow (~524ms) fa il toggle del LED su RA0 e ricarica il contatore. Il main loop è completamente libero.

; ============================================================
; Lezione 18 — Prima ISR: interrupt su Timer0
; LED su RA0 blink 500ms. CPU libera nel main loop.
; PIC16F84A @ 4 MHz
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF

    CBLOCK  0x0C
W_SAVE          ; salvataggio contesto ISR
STATUS_SAVE
OVF_COUNT       ; contatore overflow (8 overflow × 65.5ms ≈ 524ms)
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; VETTORE INTERRUPT — 0x0004
; Il PIC salta qui automaticamente quando GIE=1 e T0IF=1
; GIE viene azzerato HW all'ingresso, riabilitato da RETFIE
; ============================================================
    ORG     0x0004
ISR
    ; --- Salvataggio contesto ---
    MOVWF   W_SAVE             ; salva W (non tocca STATUS)
    SWAPF   STATUS,W           ; legge STATUS senza alterare flag
    MOVWF   STATUS_SAVE        ; salva STATUS (nibble invertiti)

    ; --- Identifica sorgente interrupt ---
    BTFSS   INTCON,T0IF
    GOTO    ISR_EXIT            ; non era TMR0, esci

    ; --- Gestione TMR0 overflow ---
    BCF     INTCON,T0IF        ; pulisci flag (OBBLIGATORIO)
    DECFSZ  OVF_COUNT,F        ; conta overflow
    GOTO    ISR_EXIT

    ; OVF_COUNT è arrivato a 0: ~524ms trascorsi
    MOVLW   D'8'
    MOVWF   OVF_COUNT          ; ricarica contatore
    MOVLW   0x01
    XORWF   PORTA,F            ; toggle RA0

ISR_EXIT
    ; --- Ripristino contesto ---
    SWAPF   STATUS_SAVE,W      ; re-inverte i nibble
    MOVWF   STATUS             ; ripristina STATUS
    SWAPF   W_SAVE,F           ; inverte nibble di W_SAVE
    SWAPF   W_SAVE,W           ; re-inverte → W originale, Z non alterato
    RETFIE                     ; ritorna e riabilita GIE

; ============================================================
; INIT
; ============================================================
    ORG     0x0020
INIT
    BSF     STATUS,RP0         ; Bank 1
    MOVLW   b'10000111'        ; RBPU=1, T0CS=0, PSA=0, PS=111 (÷256)
    MOVWF   OPTION_REG
    CLRF    TRISA              ; RA tutto output
    BCF     STATUS,RP0         ; Bank 0

    CLRF    PORTA
    MOVLW   D'8'
    MOVWF   OVF_COUNT

    ; Abilita interrupt
    MOVLW   b'10100000'        ; GIE=1, T0IE=1
    MOVWF   INTCON

; --- Main loop: CPU libera ---
MAIN
    ; Qui si può fare qualsiasi altra cosa
    NOP                        ; placeholder
    GOTO    MAIN

    END

Posizionamento del codice: la ISR deve stare a ORG 0x0004. Il main INIT parte più avanti (qui ORG 0x0020) lasciando spazio. Se la ISR è più lunga di ~10 istruzioni, a 0x0004 si mette GOTO ISR e la ISR vera e propria va altrove in memoria.


4 · Pattern flag condiviso — ISR e main disaccoppiati

La ISR deve essere breve (poche istruzioni) per non bloccare altre sorgenti di interrupt. Il lavoro pesante (aggiornare display, calcolare dati, comunicare) va nel main loop. Il meccanismo di comunicazione è un flag in RAM: la ISR setta un bit, il main lo legge, lo consuma e agisce. Il flag garantisce che nessun evento vada perso anche se il main è lento a rispondere.

Pattern ISR + flag condiviso — disaccoppiamento temporale TMR0 overflow ogni ~65.5ms ISR decrementa OVF_COUNT BSF TICK_FLAG ← setta dura ~10 istruzioni FLAGS (RAM) TICK_FLAG = 1 Main loop BTFSS TICK_FLAG BCF TICK_FLAG aggiorna display… ISR: ~10 istruzioni, esecuzione garantita (non può essere interrotta da altri T0 finché GIE=0) Main: legge il flag ogni volta che ci passa — non importa quanto tardi: il flag lo aspetta in RAM Regola: ISR breve e veloce · main fa il lavoro pesante · flag come “messaggero”
; ============================================================
; Lezione 18 — ISR con variabili condivise (flag)
; La ISR aggiorna un flag; il main loop lo legge e agisce.
; Pattern fondamentale per disaccoppiare ISR e main.
; ============================================================
    LIST    P=16F84A
    #INCLUDE <p16f84a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF

    CBLOCK  0x0C
W_SAVE
STATUS_SAVE
OVF_COUNT
FLAGS           ; bit 0 = TICK_FLAG (settato dalla ISR)
SECONDS         ; contatore secondi (0-59)
DIGIT0          ; unità per display
DIGIT1          ; decine per display
TEMP
D_LO
    ENDC

#DEFINE TICK_FLAG   FLAGS,0    ; alias leggibile

    ORG     0x0000
    GOTO    INIT

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

    BTFSS   INTCON,T0IF
    GOTO    ISR_EXIT
    BCF     INTCON,T0IF

    DECFSZ  OVF_COUNT,F
    GOTO    ISR_EXIT

    MOVLW   D'15'              ; 15 × ~65.5ms ≈ 982ms ≈ 1s
    MOVWF   OVF_COUNT
    BSF     TICK_FLAG          ; segnala al main: è passato 1s

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

    ORG     0x0030
INIT
    BSF     STATUS,RP0
    MOVLW   b'10000111'        ; PS=÷256, T0CS=0, PSA=0
    MOVWF   OPTION_REG
    CLRF    TRISA
    CLRF    TRISB
    BCF     STATUS,RP0

    CLRF    PORTA
    CLRF    PORTB
    CLRF    FLAGS
    CLRF    SECONDS
    MOVLW   D'15'
    MOVWF   OVF_COUNT

    MOVLW   b'10100000'        ; GIE=1, T0IE=1
    MOVWF   INTCON

MAIN
    ; Polling del flag — non-bloccante
    BTFSS   TICK_FLAG
    GOTO    MAIN               ; nessun tick ancora, continua

    BCF     TICK_FLAG          ; consuma il flag

    ; Azione: aggiorna cronometro
    INCF    SECONDS,F
    MOVF    SECONDS,W
    SUBLW   D'60'
    BTFSC   STATUS,Z
    CLRF    SECONDS

    CALL    SPLIT_DIGITS
    CALL    SHOW_DISPLAY       ; aggiorna PORTB con cifra unità (semplificato)
    GOTO    MAIN

SPLIT_DIGITS
    MOVF    SECONDS,W
    MOVWF   TEMP
    CLRF    DIGIT1
SD_L
    MOVLW   D'10'
    SUBWF   TEMP,F
    BTFSS   STATUS,C
    GOTO    SD_D
    INCF    DIGIT1,F
    GOTO    SD_L
SD_D
    MOVLW   D'10'
    ADDWF   TEMP,F
    MOVF    TEMP,W
    MOVWF   DIGIT0
    RETURN

SEG_TABLE
    ADDWF   PCL,F
    RETLW   0x3F  ; 0
    RETLW   0x06  ; 1
    RETLW   0x5B  ; 2
    RETLW   0x4F  ; 3
    RETLW   0x66  ; 4
    RETLW   0x6D  ; 5
    RETLW   0x7D  ; 6
    RETLW   0x07  ; 7
    RETLW   0x7F  ; 8
    RETLW   0x6F  ; 9

SHOW_DISPLAY
    MOVF    DIGIT0,W
    CALL    SEG_TABLE
    MOVWF   PORTB
    RETURN

    END

5 · Regole pratiche per ISR sicure

✓ Sempre fare
Salvare W e STATUS all’ingresso. Azzerare il flag IF. Usare RETFIE (non RETURN). Tenere la ISR sotto 20 istruzioni. Dichiarare variabili condivise in CBLOCK.

✗ Mai fare
CALL dentro la ISR (rischio stack overflow). GOTO per uscire dalla ISR (perde il PC salvato). Dimenticare BCF sul flag IF (loop infinito). Riabilitare GIE manualmente dentro la ISR.

🧪 Prova tu

  1. Carica il programma ISR blink. Aggiungi TMR0 e INTCON al pannello registri. Osserva T0IF (bit 2 di INTCON) settarsi a 1 e poi azzerarsi istantaneamente all’ingresso della ISR.
  2. Simula il bug: sostituisci SWAPF STATUS,W con MOVF STATUS,W nel prologo ISR. Metti una SUBLW 0x00 nel main (setta Z). Osserva comportamento erratico nel timing del blink.
  3. Carica il programma con flag condiviso. Metti un breakpoint su BSF TICK_FLAG. Verifica che venga raggiunto ogni ~1 secondo (15 overflow × ~65.5ms ≈ 982ms).
  4. Sfida: aggiungi un secondo contatore TICK_COUNT nel main. Ogni 10 tick (10 secondi) fai lampeggiare RA1 tre volte con un loop veloce senza delay — solo XORWF e DECFSZ.

❓ Quiz rapido

1. Cosa succede se si dimentica BCF INTCON,T0IF nella ISR?

Mostra risposta

Al RETFIE GIE torna a 1. Il PIC controlla subito i flag: T0IF è ancora 1, T0IE=1, GIE=1 → il PIC rientra immediatamente nella ISR. Loop infinito. Il main non viene mai eseguito. Il sintomo è un programma “bloccato” apparentemente fermo.


2. Perché si usa RETFIE e non RETURN per uscire dalla ISR?

Mostra risposta

RETFIE estrae il PC dallo stack e setta GIE=1 atomicamente. RETURN estrae solo il PC senza riabilitare GIE — il programma riprende con GIE=0, quindi nessun interrupt successivo verrà mai servito. La prima ISR funzionerebbe, ma tutte le successive sarebbero ignorate silenziosamente.


3. La ISR può chiamare una subroutine con CALL? Quali rischi comporta?

Mostra risposta

Tecnicamente sì, ma ogni CALL consuma un livello dello stack hardware (8 livelli totali). L’interrupt ne consuma già uno. Se il main era a profondità 7 e la ISR fa un CALL, lo stack va in overflow — il PC più vecchio viene sovrascritto silenziosamente causando un salto casuale. Regola: nessuna CALL nella ISR, o al massimo una con profondità totale garantita ≤ 7.


4. Nel pattern flag condiviso, cosa succede se la ISR setta TICK_FLAG due volte prima che il main lo legga?

Mostra risposta

BSF su un flag già a 1 è un no-op — il flag rimane 1. Il main lo legge una volta sola, perdendo il secondo evento. Soluzione: usare un contatore a 8 bit (INCF nella ISR, il main decrementa fino a 0) oppure dimensionare i tempi in modo che il main consumi il flag prima che la ISR lo risetti.

Prossima lezione →

Lezione 19 — Interrupt su RB0/INT e RB4–RB7
Il pin RB0/INT come sorgente di interrupt esterno, la logica di fronte (INTEDG) e il cambiamento di stato su RB4–RB7 (RBIF).

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