Tutorials - PIC

 

 

Il Program Counter


Interazioni tra PC e PCLATH

I PIC con bus dati a 8 bit si possono dividere in tre gruppi, a seconda della larghezza del bus istruzioni. A seconda della lunghezza delle istruzioni sono disponibili più o meno bit per determinare l' indirizzo di un salto, di una chiamata a subroutine, ecc.

Istruzione Dimensione pagina
12 bit 512
14 bit 2048
16 bit 8192

Abbiamo visto come varie istruzioni ( CALL, GOTO, RETLW, RETFIE, RETURN,ecc) modifichino il contenuto del Program Counter, che è costituito da array di 13 bit in su.

Le istruzioni di rientro (RETLW, RETFIE, RETURN) modificano TUTTI i bit del Program Counter, mentre quelle di chiamata o salto hanno possibilità di modifica limitate.

Il problema, dunque nasce quando si voglia raggiungere un indirizzo al di la delle possibilità di indirizzamento intrinseche nell' istruzione.

I linguaggi ad alto livello si preoccupano di questo e integrano i meccanismi adeguati. Non ne dispone invece l' Assembly.

Ora, se il vostro programma sta interamente nella pagina di 2 k, non occorre interessarsi del PCLATH, dato che CALL e GOTO avranno abbastanza bit per un corretto indirizzamento.
Se, però, parte del programma sta in pagine successive, si presenta il problema di gestire il cambio pagina, trattando il PCLATH.

Una normale esecuzione di istruzioni che non forzino il contenuto del Program Counter non presenta problemi anche se a cavallo di due pagine: gli indirizzi nel program Counter verranno correttamente adeguati al cambio pagina.

Va notato che il PCLATH è un registro a se stante, che può venire scritto, mentre la parte alta del PC non è accessibile. 
PCLATH non fa parte del meccanismo di avanzamento automatico del PC. Ne deriva che:
  • Il contenuto di PCLATH cambia solo in conseguenza di una modifica apportata attraverso le istruzioni (MOVWF, CLRF, BSF, BCF, ecc).
     
  • la parte alta del Program Counter è aggiornata ogni volta che viene aggiornata la parte bassa, dato che il Program Counter è un' unica entità.
     
  • le istruzioni generiche che non danno origine a salti (es. movlw, bsf, addlw, ecc) utilizzano un incremento automatico del PC e non richiedono alcuna manipolazione del PCLATH
     
  • Le istruzioni che generano un salto, sua diretto (goto) sia di chiamata a subroutine (call), richiedono che si tenga conto della presenza della paginazione se la destinazione è su una pagina diversa dalla partenza.

 


Una disattenzione nella gestione delle pagine fa si che il PC venga posizionato in modo errato e il programma vada in crash.

 

Il paging in pratica

Per maggiore chiarezza, riprendiamo quanto visto prima a riguardo del PC, con qualche esempio.

Durante la sequenza di istruzioni che non eseguono salti, il PC incrementa o decrementa all' interno dei bit che costituiscono una singola pagina (11 bit di indirizzo). 

Supponiamo, ad esempio, di avere la sequenza:

  movlw  0xAA
  andlw  mask1
  mowvf  result
nop 

Essa viene eseguita allo stesso modo, sia che si trovi in pagina 0 , sia che sia in un' altra qualsiasi pagina, dato che i bit alkti di PCLATH non sono interessati.

Di quale pagina si tratti, tra quelle disponibili è determinato proprio dal PCLATH.

Pagina PCLATH
4 3
0 0 0
1 0 1
2 1 0
3 1 1

Alcuni processori hanno una sola pagina di memoria; per questi non si pone alcun problema di paging, in quanto il programma non può eccedere l'estensione di una pagina, la pagina 0.  Non esiste il problema del passaggio ad altre pagine.
Per inciso, questo è un "vantaggio" supposto per i principianti che partono dal glorioso 16F84 e che non devono, perciò, scontrarsi con il paging. Vantaggio solo supposto, in quanto, necessitando di più memoria, occorre passare ad altri processori e, a meno di scegliere i 18F, le pagine diventano due o quattro e cominciano i problemi.

Supponiamo un semplice programma:

;blink-a-LED
    p=16F628 
  include "P16F628.inc" 

#define  LED PORTB,0

  org 0x00
 
  movlw   0x07
  movwf   CMCON       ;turn comparators off

  banksel TRISB       ;select bank 1
  movlw   b'00000000' ;set PortB all outputs
  movwf   TRISB
  banksel PORTB       ;select bank 0

Loop:  
  bcf     LED         ; LED on
  call    delay       
  bsf     LED         ; LED off
  call    delay  
  goto    Loop

delay
 movlw....

 ...
 return

  end

Tutto il programma è contenuto nella pagina 0, per cui le istruzioni call delay  inviano correttamente alla subroutine delay, pure in pagina 0. Vediamo il listato compilato dall' Assembler:

Program
memory
 Istruzione

.

 

Cosa succede

0000 movlw 0x07 PC +1
0001 movwf CMCON PC +1
0002 banksel TRISB PC +2 (macro a due istruzioni)
0004 movlw b'00000000' PC +1
0005 movwf TRISB PC +1
0006 banksel PORTB PC +2 (macro a due istruzioni)
0008 Loop: bcf LED PC +1
0009 call delay La  linea invia il program counter all' indirizzo della label delay , che è a 000Dh. Trattandosi di una chiamata di subroutine, l' indirizzo della locazione successiva (000Ah) viene salvato nello stack ed utilizzato per il ritorno
000A bsf LED
000B call delay

La linea invia  il program counter all' indirizzo della label delay , che è 000Dh, ma nello stack finisce l' indirizzo 000Ch per il ritorno.

000C goto Loop

La linea  invia il program counter alla label Loop, che si trova a 0008h. Essendo un salto diretto, non c'è indirizzo di ritorno. Questo chiude un loop indefinito

000D delay: movlw .250 PC +1
000E movwf d1 PC +1

Se però strutturiamo il programma in modo da avere le subroutines in pagina 1:

;blink-a-LED
    p=16F628 
  include "P16F628.inc" 

#define  LED PORTB,0

  org 0x00
 
  movlw   0x07
  movwf   CMCON       ;turn comparators off

  banksel TRISB       ;select bank 1
  movlw   b'00000000' ;set PortB all outputs
  movwf   TRISB
  banksel PORTB       ;select bank 0

Loop:  
  bcf     LED         ; LED on
  call    delay       
  bsf     LED         ; LED off
  call    delay  
  goto    Loop

 org 0x800           ; page 1

delay
 movlw....

 ...
 return

  end

Ci troviamo con una diversa situazione di memoria programma

Program
memory
 Istruzione

 

Cosa succede

 

0000 movlw 0x07 PC +1
0001 movwf CMCON PC +1
0002 banksel TRISB PC +2 (macro a due istruzioni)
0004 movlw b'00000000' PC +1
0005 movwf TRISB PC +1
0006 banksel PORTB PC +2 (macro a due istruzioni)
0008 Loop: bcf LED PC +1
0009 call delay La  linea porta il program counter all' indirizzo della label delay , che ora è a 0800h. Però, siccome i bit alti di PCLATH sono a 0 (pagina 0), il PC sarà posizionato a 0000h, il che manda in crash il programma
000A bsf LED
000B call delay

000C goto Loop

0800 delay: movlw .250
0801 movwf d1
0802 dl1 movlw .200

L' istruzione call delay  invia  il PC sempre alla pagina che è impostata in quel momento nel PCLATH, indipendentemente da dove si trova in memoria la destinazione reale.
Questo, ripetiamo, dipende dal fatto che al reset il PC è posizionato in pagina 0, con il suoi bit 3 e 4 = 0.
Questi bit NON vengono modificati dall call, che dispone di soli 11 bit per l' indirizzo.
Quindi viene chiamata non la locazione 800h (b'0100000000000') ma la locazione 000h (b'0000000000000), dove, come in questo caso, ci sono altre istruzioni che nulla hanno a che vedere con la delay . Il risultato è un crash del programma.  Questo accade sia per CALL che per GOTO.

I due bit alti del PCLATH non sono trattati automaticamente, ne dalla logica di governo del processore, nè tanto meno dall' Assembler, ma vanno manipolati manualmente a seconda della necessità. Quindi, se la delay  si trova in pagina 1, per accedervi a partire dalla pagina 0, devo portare manualmente a 1 il bit 3 di PCLATH:

Loop:  
 bcf     LED         ; LED on
 bsf     PCLATH,3    ; page 1
 call    delay       
 bsf     LED         ; LED off
 call    delay  
 goto    Loop

 org 0x800           ; page 1

delay
 movlw....

 ...
 return

Ora la linea  call delay, trovandosi con PCLATH3 = 1 e PCLATH4 = 0, eseguirà la chiamata alla locazione 0x800, eseguendo la subroutine. Il ritorno, abbiamo detto, avviene senza problemi, in quanto le istruzioni di ritorno agiscono con 13 bit di indirizzo.

E' di vitale importanza una osservazione: non è necessario ripetere la linea  bsf PCLATH,3 prima della seconda chiamata alla subroutine, in quanto, se nessuno agisce sul PCLATH, esso conserva l' ultimo valore attribuitogli. Quindi la seconda call  indirizza correttamente la delay in pagina 1.
Il tutto potrebbe essere quindi riscritto anche così:

 bsf     PCLATH,3    ; page 1
Loop:  
 bcf     LED         ; LED on
 call    delay       
 bsf     LED         ; LED off
 call    delay  
 goto    Loop

 org 0x800           ; page 1

delay
 movlw....
 ...
 return

Però, se facciamo eseguire questo programma, ci troveremo di nuovo un crash disastroso!

Perchè?

Osserviamo cosa succede più avanti delle call: ci troviamo con una ulteriore istruzione di salto nella linea goto Loop . Appena eseguita, il programma va in crash.

La ragione è semplice e deriva da quanto detto prima: il PCLATH è posizionato ad indicare la pagina 1 e il goto si troverà diretto non alla locazione della label  (008h), bensì alla locazione 808h, ovvero la stessa posizione della label Loop, ma in pagina 1! 
Per poter eseguire correttamente il salto, occorre riportare il PCLATH a pagina 0. Quindi la corretta scrittura del programma sarà:

Loop:  
 bcf     LED         ; LED on
 bsf     PCLATH,3    ; page 1
 call    delay       
 bsf     LED         ; LED off
 call    delay  
 bcf     PCLATH,3    ; page 0 <---
 goto    Loop

 org 0x800           ; page 1

delay
 movlw....
 ...
 return

Ora le cose sono a posto e il programma viene eseguito correttamente.

Ovviamente ci si trova davanti ad una situazione quanto mai macchinosa e facile origine di fatali crash del programma, le cui cause possono non essere facili da individuare, sopratutto se si tratta di un programma più complesso di quello appena esemplificato.

Ne deriva l' assoluta necessità di ricorrere a qualche ulteriore  strategia per gestire il problema delle pagine.


Gestione delle Pagine

Nella programmazione in Assembly dei PIC Base e Mid una delle principali preoccupazioni del programmatore è quella di gestire i banchi dei registri e della RAM e le pagine della memoria programma.

  • Nei PIC Enhanced la memoria programma NON è paginata e le istruzioni come call e goto consentono di accedere a TUTTA l' area della memoria programma.
    Quindi non è richiesta alcuna azione sul PCLATH.
     
  • Se con i PIC18 si usano le tabelle RETLW, allora occorre avere alcune precauzioni, onde non corre il rischio di crash del programma.
    Questo problema, però, è del tutto eliminato se si effettua l' accesso alle tabelle con le istruzioni specifiche del set Enhanced.

 

Nei PIC Base e Mid, se subroutines, destinazioni di salti, lookup tables e simili si trovano su più di una pagina, allora è necessario una qualche strategia per gestire questi eventi.

Per evitare di passare la maggior parte del tempo dedicato alla programmazione a curare questi aspetti (che con la programmazione vera e propria non hanno nulla a che fare) e/o nel debug di problemi connessi ad errori di paging o di banco, è indispensabile mettere in atto alcune semplici manovre.

E' assolutamente opportuno che ci si ponga il problema e si adottino queste strategie anche quando il programma non supera la prima pagina, in quanto non si tratta solamente di soluzioni per il problema del paging, ma dell' applicare al proprio lavoro un metodo valido non solo per la limitata circostanza di un programma scritto per il PIC16F84, bensì di qualcosa di essenziale per il vero lavoro della programmazione.

In generale, quindi, è sempre ottima cosa cosa:

  1. Organizzare il programma per moduli funzionali. Questo consente non solo di recuperare codice scritto i precedenza o renderlo disponibile per il futuro, ma anche di ottimizzare il lavoro.
    Certamente una strutturazione del programma come quello per far lampeggiare il LED non è necessaria e può appesantire il codice, ma l' errore di non considerare comunque questo aspetto è qualcosa che si sconterà non appena il programma comincia a crescere. Programmi di 1000 e più righe sono facilmente possibili ed in questi casi la mancanza di una struttura logica rende la programmazione ed il debug impossibili.
     
  2. Strutturare il programma in moduli in modo ordinato, ponendo, ad esempio, le tabelle in una pagina, le subroutines in un' altra, ecc., in modo tale che si sappia a priori, senza incertezza come modificare il PCLATH.  Così si potrà manipolarlo in maniera ben determinata. Ad esempio, con tutte le subroutines in page 1:

           call subroutine

    dalla pagina 0 diventa costantemente

                    bsf   PCLATH, 3
          call  subroutine


    Per un programma semplice, che rientra ampiamente in pagina 0, l' idea di portare tutte le sub in pagina 1 può essere poco sensata, ma se il programma base eccede la pagina 0, ecco che l' idea diventa meno peregrina. Se ci sono più call successive, non ho alcun bisogno di manipolare nuovamente il PCLATH, che resta già posizionato, mentre se ci sono goto, so a priori che sarà necessario un  bcf PCLATH,3. Anche perchè da origine ad un aumento trascurabile del codice, già impiegato, in questa classe di processori, con la gestione degli SFR su banchi diversi, mentre riduce drasticamente le possibilità di errore. 

  3. In ogni caso, è buona norma non avere mai moduli a cavallo di due pagine, sopratutto tabelle. Verificate se questo succede e spostate di conseguenza il modulo. Questo si può ottenere assegnando indirizzi precisi con le direttive dell' Assembler o creando sezioni separate per il linker di ogni pagina, mentre messaggi di avviuso possono essere automatizzati con le funzioni di MPASM.
     
  4. Una ulteriore soluzione è quella di creare un set di macro che permettano i salti e le chiamate interpagina senza bisogno di preoccuparsi di dove il target sia posizionato.
     

Ovviamente l' uso di macro avrà come risultato di ottenere un codice apparentemente più pesante, ma, rispetto a soluzioni estemporanee, ha gli enormi vantaggi di rendere il sorgente facilmente leggibile e di impedire l'incorrere in errori di indirizzamento, le cui cause poi sarebbero lunghe e difficili da scoprire.


 

    

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