Tutorials - PIC - XC8

 

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);
       }
   }
}


 

 

Copyright © afg. Tutti i diritti riservati.
Aggiornato il 21/09/21.