[Search] [Contact Us]
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Sponsor di 8052.it ![]() µC Family FAQ Strumenti di Sviluppo Libri Bookmarks Mailing List Forum Download Area Prodotti Il sito 8052.it e' in continua evoluzione: se avete suggerimenti o richieste contattateci |
Corso 8051 CAPITOLO 9- Interrupt Come implicito nel nome stesso, un interrupt e' un evento
che interrompe la normale esecuzione di un programma.
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_TOGGLEPoiche' 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.0Prima 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.
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:
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.
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
Il vostro programma puo' abilitare o disabilitare gli interrupt mediante il registro IE (A8h):
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 oppureAmbedue 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.
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. 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:
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. Un interrupt termina quando viene eseguita l'istruzione RETI (Return from Interrupt). A quel punto il microcontrollore esegue i seguenti passi:
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:
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 carryAl 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 ACCLe 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 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 R0Questo 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 00hNaturalmente 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.
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. ![]() < Porta Seriale > Set d'istruzioni 8051 (C)Copyright 1997, 1998 by Vault Information
Services. All
Rights Reserved.
Le informazioni sono fornite senza alcuna garanzia (vedi dettagli) Contattate l'autore Craig Steiner (presso 8052.com) per l'uso e/o il permesso di divulgazione di questo corso. Traduzione italiana di: Sergio Salvitti |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Interrupt |
Site made with Sworg - Simple Web Organizer
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||