Perché il codice AVR utilizza lo spostamento dei bit [chiuso]


7

Nella programmazione AVR, i bit di registro sono invariabilmente impostati spostando a sinistra 1nella posizione dei bit appropriata - e vengono cancellati da un complemento di uno degli stessi.

Esempio: per un ATtiny85, potrei impostare PORTB, b 4 in questo modo:

PORTB |= (1<<PB4);

o cancellalo in questo modo:

PORTB &= ~(1<<PB4);

La mia domanda è: perché è fatto in questo modo? Il codice più semplice finisce per essere un casino di bit-shift. Perché i bit sono definiti come posizioni dei bit anziché come maschere.

Ad esempio, l'intestazione IO per ATtiny85 include questo:

#define PORTB   _SFR_IO8(0x18)
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

Per me, sarebbe molto più logico definire i bit come maschere (in questo modo):

#define PORTB   _SFR_IO8(0x18)
#define PB5     0x20
#define PB4     0x10
#define PB3     0x08
#define PB2     0x04
#define PB1     0x02
#define PB0     0x01

Quindi potremmo fare qualcosa del genere:

// as bitmasks
PORTB |=  PB5 |  PB3 |  PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;

per attivare e disattivare i bit b 5 , b 3 e b 0 , rispettivamente. Al contrario di:

// as bit-fields
PORTB |=  (1<<PB5) |  (1<<PB3) |  (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);

Il codice maschera di bit legge molto più chiaramente: imposta bit PB5,PB3 e PB0. Inoltre, sembrerebbe salvare le operazioni poiché i bit non devono più essere spostati.

Ho pensato che forse è stato fatto in questo modo per preservare la generalità al fine di consentire il porting del codice da un AVR n -bit a un m -bit (esempio da 8 bit a 32 bit). Ma questo non sembra essere il caso, poiché si #include <avr/io.h>risolve in file di definizione specifici per il microcontrollore di destinazione. Anche cambiando gli obiettivi da un 8-bit ATtiny a un 8-bit Atmega (dove definizioni dei bit cambiano sintatticamente da PBxa PORTBx, per esempio), richiede modifiche al codice.


3
Io secondo questo. Anche usando l'onnipresente _BV(b)invece di (1<<b)rendere le cose inutilmente disordinate. In genere definisco bit mnemonici con _BV(), ad es #define ACK _BV(1).
soccer3000,

2
Una volta realizzato che il compilatore li interpreterà come la stessa costante, che utilizzare nel codice sorgente è davvero una questione di preferenza. Nel tuo codice fai quello che pensi sia più saggio; nel modificare i progetti esistenti, attenersi alle loro tradizioni.
Chris Stratton,

3
"Dato che un approccio con maschera di bit produrrebbe chiaramente un codice per l'utente finale più leggibile" - la tua opinione personale. Trovo molto più chiaro spostare 1 e 0 nel posto giusto piuttosto che indovinare se diversi numeri che vengono sommati sono maschere di bit o meno.
Tom Carpenter,

3
@TomCarpenter Interessante. Bene, forse ho inavvertitamente fatto una domanda basata sull'opinione. Ad ogni modo, c'è stato un buon feedback. Provenendo da più di uno sfondo DSP (TI) (dove la maschera di bit è la norma), mi è sembrata una sintassi così strana che ho pensato che ci fosse una ragione concreta per questo.
Blair Fonville,

1
@BlairFonville Forse lo sai già, ma ARMs funziona esattamente come lo descrivi (con maschere di bit).
Chi,

Risposte:


7

Il codice più semplice finisce per essere un casino di bit-shift. Perché i bit sono definiti come posizioni dei bit anziché come maschere.

No. Niente affatto. I turni sono solo nel codice sorgente C, non nel codice macchina compilato. Tutti gli esempi che hai mostrato possono e saranno risolti dal compilatore in fase di compilazione perché sono semplici espressioni costanti.

(1<<PB4) è solo un modo per dire "bit PB4".

  • Quindi non funziona solo, non crea più dimensioni del codice.
  • Ha anche senso per il programmatore umano nominare i bit in base al loro indice (ad es. 5) e non in base alla loro maschera di bit (ad es. 32) perché in questo modo è possibile utilizzare numeri consecutivi 0..7 per identificare i bit invece del potere imbarazzante di due (1, 2, 4, 8, .. 128).

  • E c'è un altro motivo (forse il motivo principale):
    i file di intestazione C non possono essere utilizzati solo per il codice C ma anche per il codice sorgente dell'assemblatore (o il codice dell'assemblatore incorporato nel codice sorgente C). Nel codice dell'assemblatore AVR sicuramente non si desidera solo utilizzare le maschere di bit (che possono essere create dagli indici mediante lo spostamento dei bit). Per alcune istruzioni dell'assemblatore di manipolazione dei bit AVR (ad es. SBI, CBI, BST, BLD) è necessario utilizzare gli indici di bit come operatore immediato nel loro codice operativo dell'istruzione.
    Solo se identifichi bit di SFR per indici(non tramite la maschera di bit) è possibile utilizzare tali identificatori direttamente come l'operando immediato delle istruzioni dell'assemblatore. Altrimenti dovevi avere due definizioni per ciascun bit SFR: uno che definiva il suo indice di bit (che può essere usato ad esempio come operando nelle istruzioni assembler di manipolazione di bit di cui sopra) e uno che definisce la sua maschera di bit (che può essere usata solo per istruzioni in cui l'intero byte viene manipolato).


1
Lo capisco. Non mi sto chiedendo se funziona o no. Lo so. Sto chiedendo perché le definizioni sono scritte così come sono. Per me migliorerebbe notevolmente la leggibilità del codice se fossero definiti come maschere anziché come posizioni di bit.
Blair Fonville,

5
Penso che questa risposta non risponda al punto. Non parla mai dell'efficienza del codice o del compilatore. Si tratta di disordine del codice sorgente .
pipe

4
@Blair Fonville: non esiste un modo semplice per definire una tale macro. Doveva calcolare il logaritmo in base 2. Non esiste alcuna funzionalità di preprocessore che calcoli il logaritmo. Vale a dire che si potrebbe fare solo usando un tavolo e che, penso, sarebbe una pessima idea.
Cagliata

2
@pipe: non ne parlo perché non lo considero "inquinamento del codice" o "disordine del codice sorgente" (o come lo si desidera chiamare). Al contrario, penso che sia utile ricordare al programmatore / lettore che la costante che sta usando è una potenza di due (e ciò viene fatto usando l'espressione shift).
Cagliata

1
@RJR, Blair Fonville: ovviamente è possibile definire facilmente tali macro MA, mentre l'uso di un semplice preprocessore definisce va bene Eviterei macro preprocessore (aka funzioni preprosessor) ogni volta che è possibile perché possono effettuare il debug (passando attraverso il codice sorgente C con il debugger) estremamente intransparente.
Cagliata il

4

Forse lo spostamento dei bit non è l'unico caso d'uso per le PB*definizioni. Forse ci sono altri casi d'uso in cui le PB*definizioni sono usate direttamente piuttosto che come importi di spostamento. In tal caso, credo che il principio DRY ti porterebbe ad implementare una serie di definizioni che possono essere utilizzate per entrambi i casi d'uso (come questi PB*definiti) anziché due diverse serie di definizioni che hanno informazioni ripetitive.

Ad esempio, ho scritto un'applicazione che può eseguire misurazioni da un massimo di 8 canali ADC. Ha un'interfaccia per avviare una nuova misurazione in cui è possibile specificare più canali tramite un campo a 8 bit, un bit per ciascun canale. (Le misurazioni vengono eseguite in parallelo quando vengono specificati più canali.) Quindi ha un'altra interfaccia che restituisce i risultati della misurazione per un singolo canale. Quindi un'interfaccia utilizza il numero del canale come spostamento in un campo bit e l'altra interfaccia utilizza direttamente il numero del canale. Ho definito una singola enumerazione per coprire entrambi i casi d'uso.

typedef enum
{
    CHANNEL_XL_X = 0,
    CHANNEL_XL_Y = 1,
    CHANNEL_XL_Z = 2,
    CHANNEL_G_X = 3,
    CHANNEL_G_Y = 4,
    CHANNEL_G_Z = 5,
    CHANNEL_AUX1 = 6,
    CHANNEL_AUX2 = 7
} ChannelNum;

struct MeasurementResult;

void StartMeasurement(uint8_t channel_mask);
MeasurementResult ReadMeasurementResult(ChannelNum channel_num);

main
{
    ...

    StartMeasurement( (1 << CHANNEL_XL_X) | (1 << CHANNEL_XL_Y) | (1 << CHANNEL_XL_Z) );

    meas_result_x = ReadMeasurementResult(CHANNEL_XL_X);
    meas_result_y = ReadMeasurementResult(CHANNEL_XL_Y);
    meas_result_z = ReadMeasurementResult(CHANNEL_XL_Z);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.