Appunti su XC8: modificare i registri.
|
Queste pagine contengono alcuni appunti su come utilizzare bit unions,
bit
mask e bit position quando si agisce sui registri speciali. Il
riferimento è la TB3216 di Microchip.
Occorre ricordare sempre che all'inizio del
sorgente è necessario includere il file header xc.h: questo contiene tutte le definizioni
dei registri, insieme a maschere di bit, maschere di campo di bit, posizioni di bit e definizioni di unione per i registri. La mappa della memoria I/O è disposta in modo che tutti i registri per un dato modulo periferico siano collocati in un blocco di memoria continuo.
Ogni registro ha un'unione dichiarata nel file header per i singoli bit in quel registro. Questo permette l'accesso ad un campo
bit o a singoli bit del registro usando la dichiarazione di unione.
Ad esempio, per il registro ADCON0:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
ADON |
ADCONT |
- |
ADCS |
- |
ADFM |
- |
ADGO |
La dichiarazione macro di unione dei bit contenuta nell'header
è la seguente:
typedef union
{
struct
{
unsigned ADGO
:1;
unsigned
:1;
unsigned ADFM
:1;
unsigned
:1;
unsigned ADCS
:1;
unsigned
:1;
unsigned ADCONT
:1;
unsigned ADON
:1;
};
struct {
unsigned GO
:1;
unsigned
:1;
unsigned ADFM0
:1;
};
} ADCON0bits_t;
extern volatile ADCON0bits_t ADCON0bits __at(0xF5B);
La serie dei bit è elencata a partire dal bit 0 (ADGO).
Si nota che questo bit è identificabile sia come GO
(nome corto) che come ADGO (nome lungo).
Così pure il bit 2 può essere chiamato come ADFM
o ADFM0.
Si può accedere a questo registro come un intero usando la dichiarazione macro o come un
campo di bit usando la dichiarazione di unione. Ecco un
esempio di come è possibile portare a 1 il bit 0 del registro ADCON0:
ADCON0 = 0x01;
/* usando la dichiarazione macro */
ADCON0bits.GO = 1; /* usando l'unione
con il nome corto del bit */
ADCON0bits.ADGO = 1; /* usando l'unione con
il nome lungo del bit */
Da notare che la prima riga porta il bit 0=1, ma azzera tutti i bit da 7 a 1.
In dettagli la convenzione dell'unione è costruita così:
ADCON0 |
bits. |
ADGO |
nome del registro |
suffisso |
.nome del bit |
Per i registri composti da una coppia di bytes, si può accedere come un intero usando la dichiarazione macro o come un singolo campo di
bit usando la dichiarazione di unione. Ecco un esempio delle definizioni nell'header
per i registri del risultato della con versione AD: #define ADRES
ADRES
extern volatile unsigned short ADRES __at(0xF5E);
#define ADRESL ADRESL
extern volatile unsigned char ADRESL __at(0xF5E);
#define ADRESH ADRESH
extern volatile unsigned char ADRESH __at(0xF5F); I bit del registro possono essere manipolati usando maschere predefinite o posizioni di bit. Le maschere di bit predefinite dal file di intestazione sono o relative a singoli bit, chiamate maschere di bit, o relative a un campo di bit, chiamate maschere di campo di bit.
Una maschera di bit è impiegabile sia quando si impostano che quando si cancellano i singoli bit.
Una maschera di campo di bit è usata principalmente quando si cancellano più bit in un campo di bit.
Alcune funzioni sono controllate da bit singoli e altre da un campo di bit.
Per esempio, il registro ADCON2 contiene
sia bit singoli (ADPSIS e ADCLR)
che i campi di bit (ADCRS e ADMD):
Bit field
|
- |
ADCRS |
- |
ADMD |
nome del bit |
ADPSIS |
ADCRS2 |
ADCRS1 |
ADCRS0 |
ADCLR |
ADMD2 |
ADMD1 |
ADMD0 |
posizione |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
maschera |
0x80 |
0x40 |
0x20 |
0x10 |
0x08 |
0x04 |
0x02 |
0x01 |
Nell'esempio sopra, ADCRS[2:0] e
ADMD[2:0] del registro ADCON2 sono bit raggruppati. Il valore dei bit in un campo seleziona una configurazione specifica.
Le maschera sono predefinita nell'header. Ad esempio #define _ADCON2_ADPSIS_MASK
0x80
#define _ADCON2_ADMD_MASK 0x7 La convenzione
per i campi di bit interni al byte è la seguente:
_ADCON2 |
_ADPSIS |
_ADGO |
_nome registro |
_nomebit |
_maschera |
Da notare la presenza del segno _
Quando si cambiano i bit in un campo di bit, è anche richiesto di cancellare i bit della vecchia configurazione prima di assegnare un nuovo valore. Per facilitare questo, viene definita
la maschera di campo di bit. I bit di un campo di bit sono accessibili come bit individuali. Per differenziare questi bit, un suffisso (indice di ogni bit nel campo di bit) viene aggiunto al nome del campo.
Ad esempio, ecco come alcune definizioni:
#define _ADCON2_ADMD0_MASK 0x1
#define _ADCON2_ADMD1_MASK 0x2
#define _ADCON2_ADMD2_MASK 0x3 È possibile utilizzare le posizioni dei bit come alternativa. Una posizione di bit all'interno di un registro è definita usando
la stessa convenzione di denominazione usata per le maschere di bit, con il suffisso
'_POSITION' o '_POSN' invece di
'_MASK'. Questo è implementato per ragioni di compatibilità.
Ad esempio: #define _ADCON2_ADPSIS_POSN
0x7
#define _ADCON2_ADPSIS_POSITION 0x7
Vediamo l'applicazione pratica.
Impostare, cancellare e leggere i bit
di un registro usando le bit unions.
Lo stile di codifica più comune e raccomandato per impostare o cancellare un bit specifico in un registro è
quello che utilizza la dichiarazione di unione del registro.
L'esempio seguente mostra come impostare e cancellare il bit Enable ADON
del registro ADCON0: ADCON0bits.ADON = 1;
/* set il bit enable dell'ADC */
ADCON0bits.ADON = 0; /* clear il bit
enable dell'ADC */ Il codice che segue mostra come interrogare il bit
ADGO, aspettando finché non viene cancellato: /* wait while the ADGO bit is set */
while(ADCON0bits.ADGO)
{
/* wait */
} Come leggere il valore di un pin in un PORT usando le unioni di bit ed eseguire una serie di istruzioni se quel pin è
basso: /* if pin 0 of the PORTA is clear execute a set of instructions */
if(!PORTAbits.RA0)
{
/* istruzioni */
} Impostare, cancellare e leggere i bit
di un registro usando maschere di bit. Ci sono, però, modi alternativi per impostare e cancellare i bit del registro usando maschere di bit o posizioni di bit.
Per impostare un bit da un registro usando maschere di bit, l'operatore OR binario sarà applicato tra il registro e
la maschera di bit. L'utilizzo dell'operatore OR binario assicurerà che le altre impostazioni di bit fatte all'interno del registro rimangano
stesse e non siano influenzate da questa operazione. ADCON0 |= _ADCON0_ADON_MASK;
/* ADON bit set */ Per cancellare un bit da un registro usando maschere di bit,
l'operatore binario AND sarà applicato tra il registro e il valore della maschera di bit. Questa operazione mantiene anche le altre impostazioni di bit invariate. La maschera di bit per il bit ADC Enable
(ADON) ha la seguente dichiarazione nel file di intestazione. #define _ADCON0_ADON_MASK
0x80 Quindi la riga ADCON0 &= ~_ADCON0_ADON_MASK;
/* ADON bit cleared */ equivale
all'and di ADCON0 con 01111111 Il codice qui sotto mostra come leggere il valore di un pin PORT usando maschere di bit ed eseguire una serie di istruzioni se quel pin è basso. if(PORTA & _PORTA_RA0_MASK)
{
/* istruzioni */
} Essendo #define _ADCON2_ADMD0_MASK 0x1,
la riga if verifica
che l'and tra PORTA_RA0 e 1 sia vero (cioè
=1). Impostare, cancellare e leggere i bit del registro usando le
posizioni dei bit.
Per settare un bit da un registro usando le posizioni dei bit, l'operatore OR binario sarà applicato tra il registro e il valore risultante dallo spostamento di '1' con il valore della posizione del bit.
Per cancellare un bit usando le posizioni di bit si usa l'operatore binario AND viene utilizzato con il valore negato del risultato dello spostamento. ADCON0 |= (1 << _ADCON0_ADON_POSITION);
/* ADC Enable set */
ADCON0 &= ~(1 << _ADCON0_ADON_POSITION); /* ADC Enable
cleared */ dato che la
posizione di bit per il bit ADON ha la seguente dichiarazione nel file di intestazione. #define
_ADCON0_ADON_POSITION 0x7 Il codice qui sotto mostra come leggere il valore di un pin PORT usando le posizioni dei bit ed eseguire una serie di
istruzioni se quel pin è basso. if(PORTA & (1<< _PORTA_RA0_POSITION))
{
/* istruzioni */
}
Inizializzazione di un registro.
Inizializzare un registro significa impostare la configurazione del registro per ottenere la funzionalità
voluta ( per conoscere le opzioni occorre consultare il foglio dati del
dispositivo).
L'inizializzazione del registro è spesso eseguita come parte dell'inizializzazione del dispositivo dopo il reset, quando il registro è in uno
stato noto, ma può essere effettuata in qualsiasi punto del programma.
Da notare che dopo il reset il contenuto della maggior parte dei registri PIC è '0', ma ci sono
varie eccezioni. Inoltre reset diversi da POR possono imporre situazioni
diverse. Usando una sola riga con la dichiarazione macro
. Per esempio, configuriamo il registro di controllo del Timer0:
- Abilitare il timer - T0EN = 1
- Selezionare la modalità a 8 bit - T016BIT = 0 (predefinito)
- Selezionare un postscaler 1:10 - T0OUTPS = 1001
Il valore risultante può essere scritto direttamente nel registro T0CON0,
usando una delle seguenti forme:
T0CON0 = 0b1000 1001; /* binary */
T0CON0 = 0x89; /* hexadecimal */
T0CON0 = 137; /* decimal */ Tuttavia, per migliorare la leggibilità (e potenzialmente la portabilità) del codice, si raccomanda di usare i
modi
che sono mostrati nelle prossime sezioni. Inizializzazione del registro usando le
unioni di bit.
L'inizializzazione dei registri tramite unioni di bit e campi di bit dovrà sempre essere fatta in diverse righe di codice, se si configura
più di un bit o campo di bit.
L'esempio seguente mostra il modo raccomandato di inizializzare un registro, usando la dichiarazione di unione del
dal file di intestazione.
T0CON0bits.T0EN = 1; /* Enable TMR0 */
T0CON0bits.T016BIT = 0; /* Select 8-bit operation mode */
T0CON0bits.T0OUTPS = 0x9; /* Select 1:10 postscaler */
Inizializzazione del registro usando maschere di
bit.
Le operazioni di lettura-modifica-scrittura non sono necessarie, quando si lavora con maschere di bit o posizioni di bit, se il valore di reset del
registro è 0x00 e il registro si configura in una sola riga.
L'esempio seguente mostra come ottenere la stessa configurazione usando maschere di bit.
T0CON0 = _T0CON0_T0EN_MASK
/* Enable TMR0 */
| _T0CON0_T0OUTPS0_MASK /* Select 1:10 postscaler */
| _T0CON0_T0OUTPS3_MASK; /* 8-bit operation mode */
Questo modo di inizializzare un registro deve essere fatta in una sola linea di codice
C.
Scrivendo su più righe la maschera di bit
usato nella seconda e terza linea cancellerebbe i bit impostati nelle linee precedenti. /*
inizializzazione non corretta */
T0CON0 = _T0CON0_T0EN_MASK;
T0CON0 = _T0CON0_T0OUTPS0_MASK;
T0CON0 = _T0CON0_T0OUTPS3_MASK; |
Le maschere di bit possono impostare solo i bit in una singola linea di codice, quindi le configurazioni che richiedono che i bit siano cancellati sono lasciate
poiché sono correttamente configurate dal loro valore di reset.
In questo esempio, nessuna maschera è usata per configurare esplicitamente il timer nella modalità a 8 bit. Questo è possibile perché il valore di reset
del T016BIT è '0' il che corrisponde alla modalità a 8 bit.
Se la scrittura del registro parte da un punto del programma dove il bit
potrebbe non essere a 0 o semplicemente per evidenziare la configurazione di questo bit come 0,
si può selezionare esplicitamente la modalità a 8 bit utilizzando una linea di codice separata
che lasci il resto del registro invariato: T0CON0 = _T0CON0_T0EN_MASK
/* Enable TMR0 */
| _T0CON0_T0OUTPS0_MASK /* 1:10 postscaler */
| _T0CON0_T0OUTPS3_MASK;
T0CON0 &= ~_T0CON0_T016BIT_MASK; /*
8-bit mode */
Inizializzazione del registro usando le posizioni di
bit.
L'elenco di codice che segue mostra come inizializzare un registro usando le posizioni di bit.
T0CON0 = (1 << _T0CON0_T0EN_POSITION)
/* Enable TMR0 */
| (0 << _T0CON0_T016BIT_POSITION)
/* 16-bit mode*/
| (1 << _T0CON0_T0OUTPS0_POSITION)
| (1 << _T0CON0_T0OUTPS3_POSITION); /*
1:10 postscaler */
in questo caso, l'uso dello shift per il numero di posizioni indicato dal valore
della posizione codificato nell'header, porta il valore voluto nella posizione
voluta.
Cambiare le configurazioni del campo di bit del registro.
Il seguente campo di bit sarà usato come esempio, per un aggiornamento rispetto alla configurazione di inizializzazione
T0OUTPS[3:0] Selezionare postscaler di uscita
TMR0:
- Seleziona un postscaler 1:10 - T0OUTPS: 1001 (impostazione precedente)
- Selezionare un postscaler 1:8 - T0OUTPS: 0111 (nuova impostazione)
Cambiare le configurazioni dei campi bit dei registri usando le
unioni di bit.
La dichiarazione di unione dei registri offre l'accesso ai bit del registro e ai campi di bit senza influenzare il resto del
registro. Questo è il modo raccomandato per aggiornare le configurazioni dei registri dei campi di
bit ed è la più semplice delle alternative.
/* usando un valore hex */
T0CON0bits.T0OUTPS = 0x7; /* 1:10 postscaler */
/* usando un valore binario */
T0CON0bits.T0OUTPS = 0b0111; /* 1:10 postscaler */
Cambiare le configurazioni del campo di bit del registro usando maschere di
bit.
Quando si aggiorna solo un campo di bit in un registro, si deve usare un read-modify-write, per mantenere le altre impostazioni inalterate.
Pertanto, per cambiare la configurazione di un campo di bit del registro, si raccomanda di cancellare prima il campo di bit e
poi impostare una nuova configurazione. Tuttavia, per evitare di mettere il registro in uno stato non voluto tra la cancellazione
e l'impostazione della nuova configurazione, questo dovrebbe essere fatto in una singola linea di codice. Per semplicità, i passi sono prima
trattati singolarmente.
Le maschere dei campi di bit possono essere usate per cancellare un campo di bit prima di assegnare una nuova configurazione. Nell'esempio, la maschera del campo di bit
T0OUTPS è usata per cancellare il campo di bit.
/* T0OUTPS bit field cleared
(postscaler of 1:1 (T0OUTPS = 0)) */
T0CON0 &= ~_T0CON0_T0OUTPS_MASK;
/* New configuration (0b0111) of the T0OUTPS bit field */
T0CON0 |= _T0CON0_T0OUTPS2_MASK | _T0CON0_T0OUTPS1_MASK | _T0CON0_T0OUTPS0_MASK;
questo perchè la maschera del campo di bit per il TMR0
Output Postscaler Select (T0OUTPS) ha la seguente dichiarazione nel file header.
#define _T0CON0_T0OUTPS_MASK 0xF
Anche se può sembrare più semplice dividere il codice in due linee separate, una per cancellare la vecchia
e un'altra per impostare la configurazione desiderata, va osservato che, se il codice è implementato come due linee separate, la prima linea di codice selezionerà per un breve periodo, un postscaler
di 1:1 (T0OUTPS=0). Questo potrebbe non
essere desiderato.
Quindi, questi passi devono essere implementati in una sola linea per evitare di mettere il
microcontroller in uno stato non previsto.
T0CON0 = (T0CON0 & ~_T0CON0_T0OUTPS_MASK) | _T0CON0_T0OUTPS2_MASK
| _T0CON0_T0OUTPS1_MASK | _T0CON0_T0OUTPS0_MASK;
|
Cambiare le configurazioni del campo di bit del registro usando le posizioni dei
bit.
L'esempio seguente mostra come aggiornare un campo di bit del registro usando le posizioni dei bit per impostare la nuova configurazione. Simile al
processo di aggiornamento della configurazione di un registro usando maschere di bit, la configurazione corrente deve essere cancellata e la
nuova configurazione imposta in una sola linea di codice.
/* Changing a bit field configuration with bit positions */
T0CON0 = (T0CON0 & ~_T0CON0_T0OUTPS_MASK)
| (0 << _T0CON0_T0OUTPS3_POSITION)
| (1 << _T0CON0_T0OUTPS2_POSITION)
| (1 << _T0CON0_T0OUTPS1_POSITION)
| (1 << _T0CON0_T0OUTPS0_POSITION);
Impostazione dei bit di configurazione.
Tutti i dispositivi bit devono essere configurati per assicurare il corretto
funzionamento; alcune impostazioni di configurazione influenzano il funzionamento fondamentale
del dispositivo: se i bit di configurazione che specificano la
sorgente dell'oscillatore sono sbagliati, per esempio, il clock del dispositivo non può funzionare.
La mancata impostazione dei bit di configurazione può impedire persino il lampeggiamento di un LED.
Va ricordato che se il registro TRIS è settato e un valore viene scritto sulla porta, ci sono diverse cose che possono impedire che questo apparentemente semplice
programma funzioni. Questo dipende dalle opzioni disponibili per quel pin,
come la presenza di funzioni analogiche.
E' opportuno assicurarsi che ogni bit in questi registri sia esplicitamente specificato, non lasciandoli nel loro stato
predefinito se non dove questo è voluto o indifferente per l'applicazione.
Per configurare il dispositivo usando MPLAB X, si possono usare usare i
pragma di configurazione. Ad esempio:
// External Oscillator mode selection bits: EC above 8MHz, PFM set to high
power
// Power-up default value for COSC bits: EXTOSC operating per FEXTOSC
// Clok Out Enable bit: CLKOUT function disabled/ i/o or scillator on OSC2
// Clock Switch Enable bit: writing to NOSC and NDIV
// Fail-safe Clock Monitor Enable bit: FSCH timer enabled
#pragma config FEXTOSC=ECH, RSTOSC=EXT1X, CLKOUTEN=OFF, CSWEN=ON, FCMEN=ON
Però, il modo più semplice per configurare il dispositivo è quello di utilizzare la
Configuration Bits Window di MPLAB X IDE.
Seguite questi passi per ottenere le informazioni per completare la
configurazione:
- Aprite la finestra Configuration Bits (Window > Target Memory Views > Configuration Bits
o Production > Set
Configuration Bits).
- Esaminare ogni impostazione nella finestra Configuration Bits ed
effettuare le scelte volute.
- Generate le linee di configurazione con le postazioni scelte cliccando il pulsante Generate Source Code to
Output.
- Occorrerà poi copiare il codice generato da questa finestra nel file sorgente.
Una ulteriore alternativa è quella di utilizzare MCC.
Per maggiori dettagli, vedi i seguenti riferimenti:
- Consultare la MPLAB XC8
Gettinhttp://g Started Guide,, sezione Specifying Device Configuration Bits.
- Microchip Developer Help: View
and Set Configuration Bits.
- Consultare il video MPLAB X
IDE Advanced Debugging - Ehttp://vent
Breakpoints.
Esempio di modi alternativi di scrivere
codice che esegue la stessa operazione.
L'esempio seguente dimostra come configurare il microcontrollore per accendere un LED quando un pulsante utente viene
premuto. Per ottenere questo, l'utente deve identificare i pin del microcontrollore indirizzati al LED utente e al
pulsante utente.
Questo esempio è sviluppato per la scheda di sviluppo PIC18F16Q41 Curiosity
Nano. Il LED utente è collegato pin 1 del PORTC
(RC1). Il pulsante utente è indirizzato al pin
0 del PORTC (RC0).
Usando le unioni di bits.
#include <xc.h>
void main(void)
{
/* setting pin RC1 as output (LED) */
TRISEbits.TRISC1 = 0;
/* setting pin RC0 as input (button) */
TRISEbits.TRISC0 = 1;
/* enable digital input buffer for pin
RC2 (button) */
ANSELEbits.ANSELC2 = 0;
/* enable internal pull-up for pin RC2
(button) */
WPUEbits.WPUC2 = 1;
/* main program loop */
while(1)
{
/* if button is pressed (pin
RC0 high) */
if(PORTEbits.RC0)
{
/* turn on the LED (pin
RC1 high) */
LATEbits.LATC0 = 1;
}
else
{
/* turn off the LED (pin
RC1 low) */
LATEbits.LATC1 = 0;
}
}
}
Usando le bit Mask:
#include <xc.h>
void main(void)
{
/* setting pin RC1 as output (LED) */
TRISE &= ~_TRISE_TRISC1_MASK;
/* setting pin RC0 as input (button) */
TRISE |= _TRISE_TRISC0_MASK;
/* enable digital input buffer for pin
RC0 (button) */
ANSELE &= ~_ANSELE_ANSELC0_MASK;
/* enable internal pull-up for pin RC0
(button) */
WPUE |= _WPUE_WPUC0_MASK;
/* main program loop */
while(1)
{
/* if button is pressed (pin
RC0 high) */
if(PORTE &
_PORTE_RC0_MASK)
{
/* turn on the LED (pin
RC1 high) */
LATE |= _LATE_LATC1_MASK;
}
else
{
/* turn off the LED (pin
RC1 low) */
LATE &=
~_LATE_LATC1_MASK;
}
}
}
Usando le posizioni dei bit.
#include <xc.h>
void main(void)
{
/* setting pin RC1 as output (LED) */
TRISE &= ~(1 << _TRISE_TRISC1_POSITION);
/* setting pin RC0 as input (button) */
TRISE |= (1 << _TRISE_TRISC0_POSITION);
/* enable digital input buffer for pin
RC0 (button) */
ANSELE &= ~(1 << _ANSELE_ANSELC0_POSITION);
/* enable internal pull-up for pin RC0
(button) */
WPUE |= (1 << _WPUE_WPUC0_POSITION);
/* main program loop */
while(1)
{
/* if button is pressed (pin
RC0 high) */
if(PORTE & (1 <<
_PORTE_R0C0_POSITION))
{
/* turn on the LED (pin
RC1 high) */
LATE |= (1 <<
_LATE_LATC1_POSITION);
}
else
{
/* turn off the LED (pin
RC1 low) */
LATE &= ~(1 <<
_LATE_LATC1_POSITION);
}
}
}
|