Tutorials - PIC

 

 

Il Program Counter


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.

 


 

 

Copyright © afg . Tutti i diritti riservati.
Aggiornato il 12/01/13 .