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.
⚠ 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.
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.
; ============================================================ ; 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
- 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.
- Simula il bug: sostituisci
SWAPF STATUS,WconMOVF STATUS,Wnel prologo ISR. Metti unaSUBLW 0x00nel main (setta Z). Osserva comportamento erratico nel timing del blink. - 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). - 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