Il Program Counter (letteralmente, contatore del programma) o
Instruction Pointer è uno speciale registro del processore che
contiene l' indirizzo della prossima istruzione (opcode) da eseguire nella
memoria programma.
Il Program Counter, o PC, è il contatore
ordinale del processore, ovvero il sistema con cui vengono messe in sequenza e
avanzate le istruzioni.
|
La sua caratteristica,
in tutti i PIC, è quella
di essere inizializzato al reset in modo da partire con il
conteggio dalla locazione 0000h, ovvero dal vettore del
RESET.
In questa locazione si troverà la PRIMA istruzione
che verrà eseguita.
La sua lunghezza in bytes e la sua funzione stabilirà il valore
successivo del PC, che andrà a puntare all' istruzione logicamente
seguente.
|
Il suo funzionamento si può schematizzare così:
- al Reset, il Program Counter viene precaricato con l' indirizzo che
contiene la prima istruzione da eseguire.
- Il meccanismo del Program Counter fa si che il contenuto della locazione
di memoria a quell' indirizzo venga prelevato, decodificato ed eseguito.
- a seconda di quale opcode si tratti, il Program Counter viene aggiornato con l'
indirizzo che contiene l' istruzione successiva.
- e così via
Nei PIC, l'indirizzo iniziale al Reset è posto a 0000h, che è definito come
Vettore del Reset. |
Qui trova posto la prima istruzione da eseguire. La direttiva:
ORG 0000h
prima_istruzione
serve a posizionare l' istruzione a questo punto della memoria programma.
Per inciso, va notato che la direttiva ORG
non fa parte dei codici di istruzione inseriti nella memoria
programma, ma è semplicemente un "ordine" inviato all'
Assembler per posizionare l' istruzione che segue in quella
determinata posizione.
|
L' avanzamento del Program Counter lungo la lista delle istruzioni è
automatico, nel senso che la decodifica di ogni istruzione informa il
Program Counter di quale sarà l' indirizzo successivo.
Consideriamo un esempio:
|
Memoria programma |
P.C. |
Operazione |
0 |
PowerOnReset |
0000h |
Al Power On Reset (POR), il Program Counter viene caricato con l'
indirizzo del Vettore di Reset. |
1 |
0000 |
nop |
0001h |
All' indirizzo della memoria programma 0000h viene prelevata la
prima istruzione. La sua decodifica indica che si tratta di una istruzione
generica lunga 1 byte, per cui il P. C. viene incrementato di 1,
puntando alla successiva a 0001h. |
2 |
0001 |
movlw
0x7F |
0002h |
Lì viene prelevato il contenuto, che è di nuovo una istruzione
generica lunga 1 byte. Il Program Counter viene ulteriormente
incrementato |
3 |
0002 |
movwf
TRISC |
0003h |
e così via. |
4 |
0003 |
. . .
|
|
|
Questo meccanismo è valido anche per le chiamate di subroutine, dove viene
abbandonata la lista principale per eseguire una porzione di codice posta in
un altra zona della memoria programma.
L' istruzione CALL (o RCALL) fa si che il Program Counter punti alla locazione
di inizio della subroutine chiamata.
La conclusione della subroutine è costituita dall' istruzione RETURN
(o RETLW). Le istruzioni di
chiamata hanno modificato il Program Counter con il nuovo indirizzo, ma hanno
anche fatto si che il contenuto del Program Counter stesso, ovvero l'
indirizzo dell' istruzione seguente a quella di chiamata, venisse salvato
nello stack.
Ora l' istruzione di ritorno recupera questo indirizzo e lo ricarica nello
stack: la prossima istruzione che eseguita sarà quella successiva alla CALL.
Consideriamo un esempio:
|
Memoria programma |
P.C. |
Stack |
Operazione |
0 |
POR |
0000 |
- |
POR - P.C.= 0000h |
1 |
0000 |
nop |
0001 |
- |
Istruzione a 1 byte |
2 |
0001 |
call
SUB1 |
0050 |
0003 |
Chiamata a subroutine - 2 bytes |
3 |
0003 |
bsf
PORTC,7 |
0003 |
- |
Istruzione a 1 byte |
4 |
0004 |
. |
. |
. |
|
. |
. |
. |
. |
. |
|
. |
|
|
|
|
|
5 |
0050 |
SUB1 movlw
0x7F |
0051 |
0003 |
Entry subroutine - 1 byte |
6 |
0051 |
movwf
TRISC |
0052 |
0003 |
Istruzione a 1 byte |
7 |
0052 |
return |
0003 |
- |
Rientro da subroutine |
8 |
0053 |
SUB2 ... |
. |
. |
|
- Al Power On Reset (POR), il Program Counter viene caricato con l'
indirizzo del Vettore di Reset.
- All' indirizzo della memoria programma 0000h viene prelevata la
prima istruzione. La sua decodifica indica che si tratta di una istruzione
generica lunga 1 byte, per cui il Program Counter viene incrementato di 1,
puntando a 0001h.
- Lì viene prelevato il contenuto, che è una istruzione di chiamata a
subroutine (call).
La sua decodifica produce le seguenti operazioni:
- Il contenuto del Program Counter, che contiene l' indirizzo dell'
istruzione successiva alla call
viene copiato nello stack
- il contenuto del Program Counter viene sostituito con l' indirizzo della
subroutine che è l'oggetto della chiamata (SUB1
).
- Di conseguenza il Program Counter punta alla locazione dove si trova la
prima istruzione della subroutine, che esegue. Nell' esempio si tratta di
una istruzione generica lunga 1 byte, per cui il Program Counter viene
incrementato di 1, puntando a 0051h.
- Qui preleva il successivo opcode, che dà risultati come il precedente.
- Nella locazione seguente viene incontrata una istruzione return
. Questa à luogo al seguente risultato:
- il contenuto del Program Counter, che avrebbe puntato all' istruzione
successiva (SUB2 ) viene invece
sostituito con il il contenuto dello stack. Il quale conserva l' indirizzo
precedentemente salvato a seguito dell' istruzione call.
- Ne deriva che ora il Program Counter punta "all' indietro"
all' istruzione successiva alla call,
riprendendo il flusso del programma principale.
Va quindi considerato che il Program Counter, in alcune condizioni, si
appoggia allo stack per conservare questo indirizzo di rientro.
Da notare che lo stack è una "pila" che
viene caricata in seguito alle necessità del Program Counter e
scaricata in conseguenza delle istruzioni di ritorno.
Nell' esempio sopra, fino al punto 2 lo stack è vuoto. Qui viene
caricato un primo indirizzo.
Al punto 7, quando l' istruzione di ritorno sostituisce il contenuto
del Program Counter con il contenuto dello stack, lo stack si trova di
nuovo ad essere vuoto.
|
Dunque, ricapitolando, CALL
o RCALLL settano un valore
specifico nel Program Counter e fanno si che esso conservi l' indirizzo dell'
istruzione successiva alla CALL.
RETURN (o RETLW)
fanno si che il Program Counter recuperi questo indirizzo e che quindi l'
istruzione successiva da eseguire sia quella dopo la CALL.
Del tutto analoga la chiamata ad interrupt, che agisce come per la
subroutine, ma con alcune differenze:
- prima di tutto, la chiamata ad interrupt non comporta istruzioni, ma
viene generata dall' hardware specifico, che modifica il Program Counter
- in secondo luogo, mentre nella chiamata a subroutine l' istruzione di
chiamata informa il Program Counter dell' indirizzo di destinazione, che
può essere qualunque nell' area di memoria programma indirizzabile, nell'
interrupt esiste un indirizzo specifico unico verso cui dirigersi
Questo indirizzo, detto Vettore dell' Interrupt, per i PIC è posto a 0004h (e
0008h) ed è questo valore che il meccanismo avviato dalla sorgente di
interruzione carica nel Program Counter.
Ne deriva che la prima istruzione eseguita dopo l' interruzione è quella
posta al vettore di interrupt.
Anche l' interruzione, come dice il nome, è una sospensione momentanea dell'
esecuzione del programma principale, a cui veine fatto ritorno attraverso un
sistema identico a quello visto per le subroutines:
- alla chiamata dell' interrupt, per prima cosa il contenuto del PC viene
salvato nello stack
- poi viene caricato l' indirizzo del vettore di interrupt
Ovvero anche qui lo stack serve da deposito dell' indirizzo di ritorno, a
cui si accede a seguito dell' istruzione RETFIE.
ATTENZIONE:
se è chiaro quanto finora detto, una linea che comandi
GOTO Vettore_Interrupt
oppure
GOTO
Subroutine
creerà rilevanti problemi all' esecuzione del programma.
Infatti, a seguito di queste istruzioni, il Program Counter punterà
al Vettore di Interrupt o all' inizio della subroutine.
Abbiamo visto come l' istruzione di salto modifichi il Program
Counter, ma non operi alcun salvataggio di indirizzi di
"ritorno", che non sono previsti.
Però, ad un certo punto la gestione dell' interrupt o la subroutine
avranno termine con una istruzione di ritorno (RETURN, RETLW, RETFIE).
La quale per prima cosa provvederà ad estrarre dallo stack l'
indirizzo di ritorno e iniettarlo nel Program Counter. Ma questo
indirizzo di ritorno NON c'è e dallo stack verrà estratto il
primo indirizzo della pila col risultato di inviare il Program Counter
ad eseguire istruzioni in un punto casuale del programma, se
non in un' area vuota !!!
Per contro, un:
GOTO Vettore_diReset
sarà possibile, in quanto si limiterà ad iniziare il programma
dal suo punto di ingresso, che è il vettore del Reset.
Ma anche in questo caso va considerato che il salto al vettore di
Reset NON è un Reset, nè tanto meno un POR !!!
Infatti il Reset generato dal pin MCLR o dal POR non
solo modificano il contenuto del Program Counter caricando il vettore
0000h, ma svolgono anche numerose altre funzioni, come i
settaggi di default degli I/O ed altre inizializzazioni che il
semplice salto all' indirizzo 0000h NON svolge.
|
Dunque, allo scatto dei meccanismi hardware di interrupt, il Program
Counter abbandona la lista di istruzioni che sta eseguendo, salva l'
indirizzo dell' istruzione successiva a quella appena eseguita e viene
caricato con l' indirizzo del Vettore di Interrupt.
RETFIE fa si che il Program Counter recuperi l' indirizzo salvato e
che quindi l' istruzione prossima da eseguire sia esattamente la successiva a
quella dove la lista primaria è stata interrotta.
Ma esiste un ulteriore gruppo di istruzioni, dette salti o branch o jump
(GOTO, BRA,
BTFSS, BTFSC, ecc) hanno la funzione di modificare il Program Counter.
Esse modificano il PC caricandolo con l' indirizzo specificato dall' istruzione stessa. In questo
modo la prossima istruzione da eseguire non sarà, ad esempio, quella dopo il GOTO, ma quella indicata nell' oggetto del
GOTO stesso.
Qui, però, a differenza della chiamata di una subroutine, dove viene
salvato un indirizzo di ritorno, i jump e branch non prevedono alcun ritorno e
la continuità del flusso del programma dovrà essere assicurata dalla logica coerente
dello stesso.
Consideriamo un esempio:
|
Memoria programma
|
P.C.
|
Stack |
Operazione |
0 |
POR |
0000 |
- |
|
1 |
0000 |
nop |
0001 |
- |
|
2 |
0001 |
goto
JUMP1 |
0050 |
- |
GOTO - cambia P.C. ma non salva nulla nello stack |
3 |
0003 |
bsf
PORTC,7 |
0003 |
- |
|
4 |
0004 |
. |
. |
. |
|
. |
. |
. |
. |
. |
|
. |
|
|
|
|
|
5 |
0050 |
JUMP1 movlw
0x7F |
0051 |
- |
|
6 |
0051 |
movwf
TRISC |
0052 |
- |
|
7 |
0052 |
movlw
0x55 |
0053 |
- |
|
8 |
0053 |
... |
. |
. |
|
- Al punto 2, viene incontrata una istruzione di salto (goto).
La sua esecuzione produce le seguenti operazioni:
- Il contenuto del Program Counter, che contiene l' indirizzo dell'
istruzione successiva al goto
viene sostituito con l' indirizzo del salto che è l'oggetto della
chiamata (JUMP1 ).
- Di conseguenza il Program Counter punta a questa nuova locazione, dove
preleva il codice e lo passa all' esecuzione
Da osservare che lo stack non viene modificato in quanto NON esiste una
istruzione di ritorno da un salto, per cui l' esecuzione delle istruzioni
continua linearmente.
Questo vuol dire che l' istruzione alla locazione 0003h e seguenti non saranno
eseguite a meno che vengano puntate con una istruzione che cambi il contenuto
del Program Counter puntando a queste locazioni.
Come detto, tutti questi sono meccanismi automatici in cui l' utente non mettere mano.
Ma è possibile attraverso istruzioni modificare in tutto o in parte il
contenuto del Program Counter e anche lo stack ed attraverso queste
manipolazioni ottenere funzioni particolari.
E' evidente la delicatezza con cui è necessario intervenire su questi
registri da cui dipende la corretta esecuzione del programma.
E' opportuno notare anche che le operazioni determinate dalle istruzioni di
ritorno (RETURN, RETLW, RETFIE)
consistono esclusivamente nel
prelevare dallo stack l' ultimo indirizzo salvato. Ovvero, se è
stata effettuata una chiamata a subroutine in un certo punto, ad esempio all'
indirizzo 0345h, il di quella subroutine NON contiene nè è
collegato al l' indirizzo di rientro, nell' esempio 0347h) !
Il collegamento è effettuato dal fatto che gli indirizzi di ritorno vengono
salvati sovrapponendoli nella pila dello stack e da questa prelevati nell'
ordine opposto al caricamento (LIFO = last in first out - l' ultimo messo è
il primo ad essere prelevato).
Questo meccanismo funziona se la sequenza di salvataggio e recupero nello
stack non viene alterata da latre azioni, ad esempio un intervento manuale, o,
come indicato prima, un salto senza rientro che in contra durante
l' esecuzione un rientro senza chiamata.
In questo caso viene scaricato dallo stack l' ultimo valore contenuto, che non
corrisponderà ad alcun rientro, alterando l' esecuzione del programma. Questo,
però, si presta ad alcuni trucchi, tra i quali il fatto che qualsiasi
chiamata a subroutine (o interrupt) può rientrare con qualsiasi istruzione
di , dovunque essa sia posta.
Ad esempio:
|
Memoria programma |
P.C. |
Stack |
Operazione |
0 |
POR |
0000 |
- |
POR - P.C.= 0000h |
1 |
0000 |
nop |
0001 |
- |
Istruzione a 1 byte |
2 |
0001 |
call
SUB1 |
0050 |
0003 |
Chiamata a subroutine - 2 bytes |
3 |
0003 |
bsf
PORTC,7 |
0003 |
- |
Istruzione a 1 byte |
4 |
0004 |
. |
. |
. |
|
. |
. |
. |
. |
. |
|
. |
|
|
|
|
|
5 |
0050 |
SUB1 btfsc
PORTC,7 |
0051 |
0003 |
Entry subroutine - 1 byte |
6 |
0051 |
return |
0003 |
- |
Rientro da subroutine |
7 |
0052 |
bsf
PORTC,6 |
|
- |
|
8 |
0053 |
SUB2 bsf
PORTC,7 |
- |
- |
|
9 |
0054 |
return |
. |
- |
|
- Al punto 2, indirizzo 0001h, il programma incontra una call
.
Lo stack viene caricato con l' indirizzo dell' istruzione
successiva (003h) e il P.C. con l' indirizzo di destinazione del salto
- Viene eseguita la prima istruzione della SUB1
.
- Se il bit 7 di è a 1, il branch non è eseguito e l' istruzione
successiva è un return.
- Il return
preleva dallo stack l' indirizzo di rientro e riporta il P.C. al punto 3
Lo stack si è svuotato.
Se però il branch viene eseguito:
|
Memoria programma |
P.C. |
Stack |
Operazione |
0 |
POR |
0000 |
- |
POR - P.C.= 0000h |
1 |
0000 |
nop |
0001 |
- |
Istruzione a 1 byte |
2 |
0001 |
call
SUB1 |
0050 |
0003 |
Chiamata a subroutine - 2 bytes |
3 |
0003 |
bsf
PORTC,7 |
0003 |
- |
Istruzione a 1 byte |
4 |
0004 |
. |
. |
. |
|
. |
. |
. |
. |
. |
|
. |
|
|
|
|
|
5 |
0050 |
SUB1 btfsc
PORTC,7 |
0052 |
0003 |
Entry subroutine - 1 byte |
6 |
0051 |
return |
|
0003 |
non eseguita - saltata |
7 |
0052 |
bsf
PORTC,6 |
0053 |
0003 |
Istruzione a 1 byte |
8 |
0053 |
SUB2 bsf
PORTC,7 |
0054 |
0003 |
Istruzione a 1 byte |
9 |
0054 |
return |
0003 |
- |
Rientro da subroutine |
- Se il bit 7 di è a 0, il branch è eseguito e l' istruzione
successiva è bsf
PORTC,6.
Lo stack non viene toccato.
- Viene quindi eseguita l' istruzione alla riga 7.
- Viene poi eseguita l' istruzione alla riga 8. Il fatto che a questa
posizione sia applicata una label, non ha alcuna importanza in questa
situazione.
- L' istruzione successiva, al punto 9, è un return.
- Il return
preleva dallo stack l' indirizzo di rientro, che non è stato modificato,
e riporta il P.C. al punto 3. Lo stack si è svuotato.
Osservare quindi come la relazione tra stack e P.C. sia di pura dipendenza
sequenziale.
E come la funzione delle label sia relativa al fatto di avere valore per la
logica del programma e per l' assemblaggio, sostituendo semplicemente con una
"etichetta" un indirizzo assoluto.
Ultima
nota: RETURN, RETLW, RETFIE
non sono la stessa cosa, pur operando similmente
- RETURN
ha lo scopo specifico di effettuare il rientro da una call,
chiamata a subroutine.
Il suo effetto è quello di recuperare dallo stack un indirizzo
per il P.C.
- RETLW agisce
esattamente come la precedente, ma in più carica il registro WREG
con il valore literal che è oggetto dell' istruzione stessa. Il
suo scopo è di rientrare da una chiamata con un valore
indicativo, ma è anche parte essenziale delle tabelle RETLW
- RETFIE
è specificamente indicata per il rientro dopo una
chiamata di interrupt. Ha sempre la funzione di recuperare dallo
stack un indirizzo per il P.C., ma aggiunge l' operazione di
abilitare il bit GIE (switch generale dell' interrupt), che era
stato disabilitato all' ingresso della chiamata dell' interrupt.
Sarebbe dunque possibile utilizzare anche per un ritorno da
subroutine, ma l' abilitazione dello switch generale dell'interrupt
potrebbe non essere una buona idea.
Inoltre, nei PIC18F, il suffisso FAST sommato all' istruzione, fa si
che i registri salvati automaticamente al momento della chiamata
interrupt vengano recuperati.
Dunque è opportuno utilizzare ogni opcodes nell' ambito previsto.
|
|