In questa pagina verrà mostrato come gestire in assembly le interruzioni del PIC18 generate da una sorgente esterna, quale un semplice interruttore. Qui una breve descrizione del concetto di interruzione.
Al solito, è vivamente consigliata la lettura in parallelo della spiegazione semplificata qui riportata e della la spiegazione "ufficiale" presente nei fogli tecnici.
Il PIC18 prevede due distinte modalità di uso delle interruzioni:
Di seguito verranno esaminate entrambe le modalità anche se la prima ha valenza soprattutto per chi già ha utilizzato i PIC16 (oppure semplicemente vuole un approccio più semplice).
L'hardware di riferimento è il PICdemo oppure il PICdemo R2 anche se il circuito è realizzabile senza alcun problema direttamente su breadboard. In particolare vengono utilizzati:
Gli esempi utilizzano il PIC18F26K20, ma sono facilmente estensibili a qualunque PIC18.
Questa modalità è attiva all'accensione e quindi per attivarla non occorre fare nulla; è associata al valore zero del flag Interrupt Priority ENable bit (IPEN=0).
Il codice di esempio, scaricabile a fondo pagina, fa scorrere di una posizione l'unico LED acceso quando viene premuto un pulsante, in corrispondenza del fronte di salita; per fare ciò vengono utilizzate le interruzioni generate da un segnale esterno applicato direttamente al pin RB0/INT0.
Per la sua gestione vengono utilizzati tre flag del registro INTCON (REGISTER 9-1: INTERRUPT CONTROL REGISTER):
Il codice (scaricabile a fondo pagina) è suddiviso in tre parti:
Occorre evidentemente come prima cosa configurare PORTC e PORTB rispettivamente come uscita (per collegare gli otto LED) e come ingresso con pull-up interno (per collegare un interruttore). Nulla di nuovo:
clrf TRISC ; PORTC come uscita, tutti gli 8 pin
movlw B'00000001' ; LED0 acceso; LED1-7 spenti
movwf LATC
movlw 0xFF ; PORTB come ingresso, tutti gli 8 pin
movwf TRISB
bsf WPUB, INT0 ; Abilita il pull-up interno su INT0
bcf INTCON2, RBPU ; Pull-up attivo su tutti i pin abilitati
Occorre quindi procedere alla abilitazione delle interruzioni, impostando il flag relativo a INT0 (INT0 Interrupt Enable) e quello globale (Global Interrupt Enable), relativa all'intero processore:
bsf INTCON, INT0IE ; Abilito INT0 a generare
interruzioni
bsf INTCON, GIE ; Abilito la CPU a ricevere interruzioni
Dopo le configurazioni iniziali, il processore inizia un ciclo infinito dove non vene fatto assolutamente nulla di significativo. In particolare il codice NON legge il valore dell'interruttore e NON imposta l'accensione dei LED.
repeat
nop ; non fa nulla (No OPeration), per sempre
nop
;...
bra repeat
La Interrupt Service Routine è il codice che viene eseguito quando l'hardware rileva la generazione di un interrupt. Alcune operazioni vengono fatte automaticamente quando l'interrupt è generata e quindi non corrispondono ad alcuna istruzione:
In genere il codice della ISR inizia con la ricerca del dispositivo hardware ha effettivamente generato l'interruzione. Nell'esempio solo INT0 è stata abilitata e quindi una interruzione è sicuramente generata da essa. In una applicazione reale invece sono molte le sorgenti di interrupt e quindi l'unica ISR deve esaminate tutti i vari flag alla ricerca di quello attivo (nota 5).
Inoltre, obbligatoriamente, la ISR deve azzerare in modo esplicito il flag dalla periferica corrispondente all'interruzione generata, lo stesso descritto nel paragrafo precedente. Saltando tale passaggio infatti, subito dopo la conclusione della ISR, verrebbe generata una nuova interruzione, evidentemente non esistente, in una ripetizione infinita. Nell'esempio:
bcf INTCON, INT0IF ; Azzera il flag che segnala INT0
La ISR termina infine sempre con l'istruzione retfie, a volte seguita da FAST. L'istruzione ha i seguenti effetti:
Di seguito l'intera struttura di una ISR tipica dove vengono esaminate a titolo di esempio tre possibili sorgenti di interrupt (INT0, Timer0, cambiamento di RB), osservando i corrispondenti flag ed eseguendo di conseguenza parti specifiche di codice (non tutte implementate):
ISR CODE 0x0008
btfsc INTCON, INT0IF ; Interruzione causata da INT0 ?
bra GestioneINT0 ; Esegue il codice corrispondente
btfsc INTCON, TMR0IF ; Interruzione da Timer0 ?
bra GestioneTimer0 ;
btfsc INTCON, RBIF ; Interruzione dal cambiamento di un bit di RB ?
bra GestioneRB0 ;
retfie FAST ; Nessuna interrupt riconosciuta... Indizio di un errore?
GestioneINT0
rlncf LATC ; "Ruota" i LED
bcf INTCON, INT0IF ; Azzera il flag che segnala INT0
retfie FAST ; Torna ad eseguire il codice principale
GestioneTimer0 ; Non implementato
bcf INTCON, TMR0IF
retfie FAST
GestioneRB0 ; Non implementato
bcf INTCON, RBIF
retfie FAST
Prima di proseguire è utile riflettere sui quesiti 2 e 3.
Altri esempi di interruzione, generati da specifiche periferiche:
La latenza, in questo contesto, misura il tempo necessario al PIC18 per "rispondere" ad una richiesta di interruzione. Dipende da diversi fattori:
Il grafico seguente fa riferimento al codice in Compatibility Mode, eseguito alla frequenza di 1 MHz. Esso mostra il tempo che intercorre tra l'innesco dell'interruzione (fronte di salita di INT0, in blu nel grafico) e l'accensione del LED, in rosso). Tale tempo è misurato in circa 32 µs con una variabilità casuale dell'ordine di qualche microsecondo.
Tale tempo è compatibile con quanto indicato nei fogli tecnici, ottenuto dalla somma di
Complessivamente tra i 7 e gli 8 cicli macchina della durata di 4 µs ciascuno, quindi tra 28 e 32 microsecondi totali.
Come confronto, il codice generato dal compilatore C introduce una latenza di circa 150 µs, dovuta soprattutto al salvataggio di oltre 10 variabili interne e ad altrettante nop (nota 4)
Data di creazione di questa pagina: maggio 2016
Ultima modifica: 18 aprile 2018
Assembly PIC18 - Versione 0.5 - aprile 2018
Copyright 2016-2018, Vincenzo Villa (https://www.vincenzov.net)
Assembly PIC18 di Vincenzo Villa è distribuito con Licenza Creative Commons Attribuzione 4.0 Internazionale