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:
- 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.
- 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.
- 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.
- 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.
|