![]() | ![]() |
|
|
Interrupt
Come implicito nel nome
stesso, un interrupt e' un evento che interrompe la normale esecuzione
di un programma.
Come gia' precedentemente affermato, il flusso di un programma e sempre di tipo
sequenziale e puo' essere alterato da particolari istruzioni che volutamente
intendono deviare il flusso del progra 252j98c mma stesso. Gli interrupt forniscono
invece un meccanismo di "congelamento" del flusso del programma in
corso, eseguono una subroutine (sottoprogramma) e ripristinano il normale funzionamento
del programma come se nulla fosse accaduto. Questa subroutine,denominate
"interrupt handler" (gestore di interrupt) viene eseguita soltanto
quando si verifica un determinato evento che attiva l'interrupt. L'evento puo'
essere: il timer che va in overflow, la ricezione di un byte dalla seriale, la
fine della trasmissione di un byte dalla seriale e uno o due eventi esterni.
L'8051 puo' essere configurato per gestire un interrupt handler per ognuno di
questi eventi.
La capacita' di interrompere la normale esecuzione di un programma quando un particolare evento si verifica, rende il programma stesso molto piu' efficiente nel gestire processi asincroni. Qualora non disponessimo degli interrupt e volessimo gestire piu' eventi dal programma principale, saremmo costretti a scrivere un codice poco elegante e difficile da capire. Allo stesso tempo il nostro programma, in particolari condizioni, diventerebbe inefficiente, dal momento che perderemmo del tempo prezioso ad aspettare un evento che non avviene molto frequentemente.
Per esempio, supponiamo di avere una grande memoria programma di 16k che esegue molte subroutine per eseguire molti lavori. Supponiamo inoltre che il nostro programma debba automaticamente far cambiare lo stato del pin P3.0 ogni volta che il timer 0 va in overflow.
Il codice per effettuare tale azione non e' tanto difficile:
JNB TF0,SKIP_TOGGLE
CPL P3.0
CLR TF0
SKIP_TOGGLE: ...
Poiche' il flag TF0 e' settato ogni volta che il timer 0 va in overflow, il codice di sopra fara' cambiare lo stato di P3.0 di conseguenza. Questo persegue il nostro scopo ma e' inefficiente. L'istruzione JNB consuma 2 cicli d'istruzoine per determinare che il flag non e' attivo e saltare il codice non necessario. Quando il timer va in overflow le due istruzioni CPL e CLR richiedono due cicli d'istruzione per essere eseguite. Per semplificare i calcoli ammettiamo che il resto del codice nel programma richieda 98 cicli d'istruzione.Quindi, in totale il nostro codice consuma 100 cicli d'istruzione. Se il timer e' configurato in modo 16-bit, esso andra' in overflow ogni 65536 cicli macchina. In tutto questo tempo dovremmo aver eseguito 655 JNB test per un totale di 1310 cicli d'istruzione ed altri 2 per eseguire il vero codice utile. Per ottenere il nostro scopo abbiamo speso il 2,002% del nostro tempo per controllare quando cambiare lo stato di P3.0. Il nostro codice e' brutto perche' dovremmo ripetere questa operazione di controllo per ogni ciclo del loop del programma principale.
Per fortuna, questo non serve. Gli interrupt ci permettono di dimenticarci di controllare la condizione che scatena l'interrupt stesso. Il microcontrollore eseguira' il controllo automaticamente e quando la condizione sara' vera, richiamera' la subroutine (chiamata interrupt handler), eseguira' il codice in esso contenuta e ritornera'. In questo caso la la nostra subroutine dovrebbe ridursi a:
CPL P3.0
RETI
Prima di tutto va notato che l'istruzione CLR TF0 e' sparita. Questo perche', quando l'8051 esegue la routine dell'interrupt del timer 0, cancella automaticamente il flag TF0. Avrete inoltre notato che invece della normale istruzione RET abbiamo usato l'istruzione RETI. Quest' ultima fa le stesse cose della precedente ma informa l'8051 che la routine di interrupt e' finita.
Dovete sempre terminare un interrupt handler con RETI.
Tornando all'esempio precedente,
ogni 65536 cicli d'istruzione eseguiremo le istruzioni CPL e RETI che richiedono
solo 3 cicli. Come risultato finale il nostro codice e' 437 volte piu'
efficiente del precedente senza contare che e' piu' facile da leggere e da
capire. Dobbiamo solo predisporre l'interrupt e dimenticarcene, fiduciosi che
l'8051 eseguira' il nostro codice quando serve.
Lo stesso concetto si applica alla ricezione dei dati dalla porta seriale. Una
possibilita' e' quella di controllare continuamente lo stato del flag RI in un
loop infinito. Oppure noi potremmo controllare il flag all'interno del piu'
grande loop del programma principale. Nel peggiore dei casi potremmo correre il
rischio di perdere il carattere ricevuto. Se un carattere viene ricevuto
subito dopo che e' stato effettuato il controllo del flag RI, nella
prosecuzione del resto del programma, prima di controllare di nuovo RI, un
nuovo carattere potrebbe arrivare e noi avremo perso il precedente. Usando
l'interrupt, l'8051 ferma il programma e richiama l'interrupt handler per
servire la ricezione del carattere. Cosi' non saremo costretti a scrivere un
antiestetico controllo nel nostro codice e nemmeno correremo il rischio di
perdere i caratteri ricevuti.
Quali eventi possono attivare un interrupt
Possiamo configuarare l'8051 affinche' ognuno di questi eventi possa causare un interrupt:
Ovviamente e' necessario poter distinguere tra vari interrupt ed eseguire dei codici differenti per ognuno di loro. Quando l'interrupt viene attivato, il microcontrollore saltera' a degli indirizzi di memoria predefiniti come mostrato nella tabella che segue:
Interrupt |
Flag |
Indirizzo dell'Interrupt Handler |
INT 0 |
IE0 |
0003h |
Timer 0 |
TF0 |
000Bh |
INT 1 |
IE1 |
0013h |
Timer 1 |
TF1 |
001Bh |
Seriale |
RI/TI |
0023h |
Se consultiamo la tabella, possiamo affermare che, se il timer 0 va in overflow, il programma principale sospendera' momentanemante il programma e saltera' all'indirizzo 0003H dove avremmo dovuto scrivere l'interrupt handler opportuno.
Configurare gli interrupt
Al power up, per default, tutti gli interrupt sono
disabilitati. Questo significa che, anche se per esempio il flag TF0 e' attivo,
l'interrupt non verra' accettato. Il vostro programma deve specificatamente
dire all'8051 che intende abilitare la gestione degli interrupt e indicare
quali
interrupt sono abilitati ad essere serviti.
Il vostro programma puo' abilitare o disabilitare gli interrupt mediante il registro IE (A8h):
Bit |
Nome |
Indirizzo a Bit |
Funzione |
|
EA |
AFh |
Abilita globalmente gli inetrrupt |
|
|
AEh |
Non definito |
|
|
ADh |
Non definito |
|
ES |
ACh |
Abilita l'interrupt della seriale |
|
ET1 |
ABh |
Abilita l'interrupt del Timer 1 |
|
EX1 |
AAh |
Abilita l'interrupt esterno INT 1 |
|
ET0 |
A9h |
Abilita l'interrupt del Timer 0 |
|
EX0 |
A8h |
Abilita l'interrupt esterno INT 0 |
Come potete notare, ciascun interrupt dell'8051 dispone si un suo bit nel registro IE. Potete abilitare un determinato interrupt, settando il corrispondente bit. Per esempio, se desiderate abilitare l'interrupt del Timer 1, potreste usare ambedue le seguenti istruzioni:
MOV IE,#08h oppure
SETB ET1
Ambedue le istruzioni settano il bit 3 di IE, e abilitano l'interrupt del Timer 1. Affinche' l'interrupt del Timer 1 (e questo vale anche per gli altri) sia effettivamente abilitato, e' necessario settare anche il bit 7 di IE. Se tale bit rimane a zero, nessun interrupt anche abilitato sara' attivo. Porre ad uno il bit 7 di IE abilita globalmente tutti gli interrupt i cui bit sono allo stato on.
Questo bit e' utile durante l'esecuzione di un programma che abbia una porzione di codice con criticita' temporali. Allora, per evitare che quella parte di codice possa essere interrotta da un qualsiasi interrupt, sara' sufficiente resettare il bit 7 di IE e settarlo di nuovo quando la sezione critica e' terminata.
Sequenza di Polling
Durante ogni istruzione, l'8051 verifica che non vi sia un interrupt da
servire. Durante tale controllo, esso segue il seguente ordine:
Cio' significa che, se ul'interrupt della seriale si attiva nello stesso momento doi quello esterno INT 0, quest'ultimo verra' servito per primo e quello relativo alla porta seriale immediatamente dopo.
Priorita' degli Interrupt
L'8051 dispone di due livelli di priorita' degli interrupt: alto e basso. Usando tali priorita' potete cambiare l'ordine con il quale gli interrupt vengono serviti.
Per esempio, se avete abilitato sia l'interrupt del Timer 1 che quello della porta seriale e ritenete che la ricezione di un carattere sia molto piu' importante della gestione del timer, potreste assegnare alla seriale un priorita' alta, in maniera da cambiare l'ordine standard con il quale il micro serve normalmente gli interrupt.
La priorita' degli interrupt e' controllata dal registro IP (B8h).
Esso ha il seguente
formato:
Bit |
Nome |
Indirizzo a Bit |
Funzione |
|
|
|
Non definito |
|
|
|
Non definito |
|
|
|
Non definito |
|
PS |
BCh |
Priorita' Interrupt Seriale |
|
PT1 |
BBh |
Priorita' Interrupt Timer 1 |
|
PX1 |
BAh |
Priorita' Interrupt esterno INT 1 |
|
PT0 |
B9h |
Priorita' Interrupt Timer 0 |
|
PX0 |
B8h |
Priorita' Interrupt esterno INT 0 |
Quando consideriamo la priorita' degli interrupt, vanno applicate le seguenti regole:
Cosa succede quanto si verifica una richiesta d'interrupt?
Al momento che un interrupt viene richiesto, il microcontrollore automaticamente esegue le seguenti operazioni:
Una speciale attenzione e' richiesta circa il terzo passo nel quale il flag viene automaticamente cancellato. Cio' significa che non e' necessario farlo all'interno del codice.
Che succede quando un interrupt finisce?
Un interrupt termina quando viene eseguita l'istruzione RETI (Return from Interrupt). A quel punto il microcontrollore esegue i seguenti passi:
Interrupt seriali
Gli interrupt seriali sono leggermente diversi da tutti gli altri. Cio' e' dovuto al fatto che ci sono due flag di interrupt: RI e TI. Se uno dei due e' attivo allora anche l'interrupt della seriale diventera' attivo. Questo vuol dire che, al momento di una richiesta di interrupt sulla seriale, non conosciamo quali dei due o tutti e due i flag sono attivi. Allora, l'interrupt handler deve verificare lo stato dei flag e comportarsi di conseguenza. Inoltre deve anche cancellare i flag poiche' l'8051 volutamente non lo fa in maniera automatica.
Un breve esempio di codice vi chiarira' il tutto:
INT_SERIAL: |
JNB RI,CHECK_TI |
; Se il flag RI non e' attivo vai a CHECK_TI |
|
MOV A,SBUF |
; leggi il buffer di ricezione |
|
CLR RI |
; Cancella il flag RI |
CHECK_TI: |
JNB TI,EXIT_INT |
; Se il flag TI non e' attivo vai a EXIT_TI |
|
CLR TI |
; Cancella il flag TI prima di inviare un nuovo carattere |
|
MOV SBUF,#?A? |
; Copia il nuovo carattere da inviare nel buffer di trasmissione. |
EXIT_INT: |
RETI |
|
Una Importante Considerazione sugli interrupt: Preservare il valore di un Registro
Una regola importante deve essere applicata da tutti gli interrupt handler: Ogni interrupt deve lasciare il processore nel medesimo stato che esso aveva al momento di iniziare l'interrupt stesso.
Il programma principale non tiene conto del fatto che i vari interrupt sono eseguiti di nascosto.
Prendiamo in considerazione la seguente porzione di codice:
CLR C ; Cancella il carry
MOV A,#25h ; Carica 25h nell'accumulatore
ADDC A,#10h ; Aggiungi 10h, tenendo conto del carry
Al termine dell'esecuzione di queste istruzioni, l'accumulatore conterra' il valore 35h.
Ma cosa accadrebbe se appena conclusa l'istruzione MOV viene servito un interrupt e questo cambia sia il carry che il valore dell'accumulatore ponendolo ad uno? Quando l'interrupt restituisce il controllo al programma principale, l'istruzione ADDC addizionera' 10h a 40h e aggiungera' 1h perche' trovera' il bit di carry settato. In questo caso l' accumulatore conterra' il valore 51h invece di 35h.
Quello che e' successo nella relata', e' che l'interrupt handler non ha preservato il valore del registro che ha usato. La regola pero' dice : Ogni interrupt deve lasciare il processore nel medesimo stato che esso aveva al momento di iniziare l'interrupt stesso.
Cosa vuol dire l'affermazione precedente? Vuol dire che, se l'interrupt usa l'accumulatore, deve assicurare che il valore di esso rimanga inalterato; cio' viene ottenuto con delle operazioni di PUSH e POP. Per esempio:
PUSH ACC
PUSH PSW
MOV A,#0FFh
ADD A,#02h
POP PSW
POP ACC
RETI
Le istruzioni dell'interrupt handler sono MOV e ADD che modificano sia l'accumulatore che il bit di carry; allora e' necessario salvare nello stack sia l'accumulatore che il registro PSW con due operazioni di PUSH. Una volta eseguite le due istruzioni proprie della routine di interrupt dovranno essere ripristinati i valori dei due registri con due operazioni di POP (Attenzione all'ordine inverso con il quale devono essere eseguite). A questo punto e' possibile terminare l'interrupt con l'istruzione RETI.
Il programma principale trovera' la situazione dei suoi
registri immutata e quindi non si accorgera' minimamente dell' interruzione.
In generale, la routine di interrupt deve preservare il contenuto dei seguenti
registri:
In particolare fate attenzione al registro PSW, che contiene i flag di stato del processore. E' buona norma salvare sempre il contenuto di PSW mediante le operazioni di push e pop all'inizio e alla fine della routine d'interrupt, a meno che non siate assolutamente sicuri di non modificare alcun bit di PSW.
Notate che l'assembler non vi permette di eseguire la seguente istruzione:
PUSH R0
Questo e' dovuto al fatto che l'indirizzo di R0 dipende dal banco di registri "R" selezionato e potrebbe riferirsi alla ram interna in posizione 00h o 08h o 10h oppure 18h.
Percio', se state usando un registro "R" all'interno della routine di interrupt, effettuate l'operazione di push facendo riferimento all'indirizzo assoluto del registro stesso.
Per esempio, invece di usare PUSH R0, dovreste usare:
PUSH 00h
Naturalmente l'istruzione e' corretta solo se state utilizzando il set di registri di default, altrimenti dovete fare il PUSH dell'indirizzo assoluto corrispondente al registro che effettivamente state usando.
Problemi Comuni con l'uso degli Interrupt
Gli interrupt sono degli strumenti molto potenti, ma se
usati in maniera non corretta, sono una fonte di ore spese a trovare problemi
nel programma.
Gli errori sulle routine di interrupt sono spesso molto difficili da
diagnosticare e correggere.
Se state utlizzando degli interrupt e il vostro programma va in crash oppure ha dei comportamenti strani con risultati randomici, ricordatevi di verificare che le regole appena esposte non siano state violate.
In generale i problemi piu' comuni derivano da:
Alcuni simulatori dell'8051 come : Vault Information Services- 8052 Simulator for Windows, hanno delle speciali caratteristiche che vi notificano il fatto che avete dimenticato di preservare il valore di un registro oppure avete commesso un quache errore riguardante l'uso improprio della routine di inetrrupt.
Privacy |
Articolo informazione
Commentare questo articolo:Non sei registratoDevi essere registrato per commentare ISCRIVITI |
Copiare il codice nella pagina web del tuo sito. |
Copyright InfTub.com 2025