Caricare documenti e articoli online 
INFtub.com è un sito progettato per cercare i documenti in vari tipi di file e il caricamento di articoli online.


 
Non ricordi la password?  ››  Iscriviti gratis
 

Interrupt - Dovete sempre terminare un interrupt handler con RETI

informatica



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:

  • Overflow del Timer 0
  • Overflow del Timer 1
  • Ricezione/trasmissione di un carattere dalla seriale
  • Evento esterno 0 (INT 0).
  • Evento esterno 1 (INT 1).

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:

  • Interrupt esterno INT 0
  • Interrupt Timer 0
  • Interrupt esterno INT 1
  • Interrupt Timer 1
  • Interrupt della seriale

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:

  • Nulla puo' interrompere un interrupt ad alta priorita', nemmeno un altro dello stesso tipo.
  • Un interrupt ad alta priorita' puo' invece interromperne uno a bassa priorita'.
  • Un interrupt a bassa priorita' puo' essere iniziato soltanto quando nessun altro interrupt e' attivo.
  • Quando si verificano due richieste contemporanee di interrupt, quello a priorita' piu' alta viene servito per primo. Se ambedue gli interrupt hanno la medesima priorita' allora viene scelto secondo l'ordine della sequenza di polling.

Cosa succede quanto si verifica una richiesta d'interrupt?

Al momento che un interrupt viene richiesto, il microcontrollore automaticamente esegue le seguenti operazioni:

  • Il valore del Program Counter viene salvato nello stack, a partire dal byte meno significativo.
  • Tutti gli interrupt della medesima o piu' bassa priorita' sono bloccati.
  • Se l'interrupt riguarda o un timer o una richiesta esterna, viene disattivato il flag corrispondente.
  • L'esecuzione del programma salta all'indirizzo delll'interrupt handler corrispondente

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:

  • Due byte vengono prelevati dallo stack (operazione di pop) e messi nel Program Counter per tornare al programma principale.
  • Lo stato dell' interruput e' rimesso nella condizione precedente all'inizio dell'interrupt stesso

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:

  • PSW
  • DPTR (DPH/DPL)
  • PSW
  • ACC
  • B
  • Registers R0-R7

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:

  • Non aver preservato il valore di un registro che viene usato nella routine di interrupt. Controllate che tutti i registri che usate siano preventivamente salvati nello stack con un'operazione di push
  • Non aver ripristano il valore del registro prima dell'uscita dalla routine dell'interrupt. Controllate di aver effettuato lo stesso numero di operazioni di push e di pop in ordine inverso
  • Aver usato l'istruzione RET invece di RETI oppure non aver usato alcuna istruzione di return. In questo caso l'interrupt non termina e quindi verra' eseguito una volta soltanto e poi misteriosamente sembrera' come se esso fosse bloccato.
  • Non aver chiuso la routine di interrupt con l'istruzione RETI

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


Hits: 1932
Apprezzato: scheda appunto

Commentare questo articolo:

Non sei registrato
Devi essere registrato per commentare

ISCRIVITI



Copiare il codice

nella pagina web del tuo sito.


Copyright InfTub.com 2024