Dans la programmation AVR, les bits de registre sont invariablement définis en déplaçant a 1
vers la gauche à la position de bit appropriée - et ils sont effacés par un complément à un du même.
Exemple: pour un ATtiny85, je pourrais définir PORTB, b 4 comme ceci:
PORTB |= (1<<PB4);
ou effacez-le comme ceci:
PORTB &= ~(1<<PB4);
Ma question est: pourquoi est-ce fait de cette façon? Le code le plus simple finit par être un gâchis de décalages de bits. Pourquoi les bits sont-ils définis comme des positions de bits au lieu de masques.
Par exemple, l'en-tête IO pour l'ATtiny85 inclut ceci:
#define PORTB _SFR_IO8(0x18)
#define PB5 5
#define PB4 4
#define PB3 3
#define PB2 2
#define PB1 1
#define PB0 0
Pour moi, il serait beaucoup plus logique de définir les bits comme masques à la place (comme ceci):
#define PORTB _SFR_IO8(0x18)
#define PB5 0x20
#define PB4 0x10
#define PB3 0x08
#define PB2 0x04
#define PB1 0x02
#define PB0 0x01
Nous pourrions donc faire quelque chose comme ceci:
// as bitmasks
PORTB |= PB5 | PB3 | PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;
pour activer et désactiver les bits b 5 , b 3 et b 0 , respectivement. Par opposition à:
// as bit-fields
PORTB |= (1<<PB5) | (1<<PB3) | (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);
Le code bitmask lit beaucoup plus clairement: les bits set PB5
, PB3
et PB0
. De plus, il semblerait que les opérations soient sauvegardées car les bits n'ont plus besoin d'être décalés.
J'ai pensé que cela avait peut-être été fait de cette façon pour préserver la généralité afin de permettre le portage du code d'un AVR à n bits vers un m bits (par exemple 8 bits à 32 bits). Mais cela ne semble pas être le cas, car se #include <avr/io.h>
résout en fichiers de définition spécifiques au microcontrôleur cible. Même le changement de cibles d'un ATtiny 8 bits en un Atmega 8 bits (où les définitions de bits changent syntaxiquement de PBx
à PORTBx
, par exemple), nécessite des changements de code.
la source
_BV(b)
au lieu de(1<<b)
rend les choses inutilement désordonnées. Je définis généralement les mnémoniques de bits avec_BV()
, par exemple#define ACK _BV(1)
.Réponses:
Non pas du tout. Les décalages sont uniquement dans le code source C, pas dans le code machine compilé. Tous les exemples que vous avez montrés peuvent et seront résolus par le compilateur au moment de la compilation car ce sont de simples expressions constantes.
(1<<PB4)
est juste une façon de dire "bit PB4".Il est également logique pour le programmeur humain de nommer les bits par leur index (par exemple 5) et non par leur masque de bits (par exemple 32) car de cette façon, les nombres consécutifs 0..7 peuvent être utilisés pour identifier les bits au lieu de la puissance maladroite de deux (1, 2, 4, 8, .. 128).
Et il y a une autre raison (peut-être la principale):
les fichiers d'en-tête C peuvent être utilisés non seulement pour le code C mais aussi pour le code source de l'assembleur (ou le code assembleur inséré dans le code source C). Dans le code assembleur AVR, vous ne voulez certainement pas seulement utiliser des masques de bits (qui peuvent être créés à partir d'index par décalage de bits). Pour certaines instructions d'assembleur de manipulation de bits AVR (par exemple SBI, CBI, BST, BLD), vous devez utiliser des indices de bits comme opérateur immédiat dans leur code d'opération d'instruction.
Uniquement si vous identifiez des bits de SFR par des indices(pas par masque de bits), vous pouvez utiliser ces identifiants directement comme l'opérande immédiat des instructions d'assembleur. Sinon, vous deviez avoir deux définitions pour chaque bit SFR: une définissant son index binaire (qui peut être utilisé, par exemple, comme opérande dans les instructions de l'assembleur de manipulation de bits susmentionnées) et une définissant son masque de bits (qui ne peut être utilisé que pour les instructions où l'octet entier est manipulé).
la source
Le décalage de bits n'est peut-être pas le seul cas d'utilisation
PB*
définitions. Il existe peut-être un autre cas d'utilisation où lesPB*
définitions sont utilisées directement plutôt que sous forme de montants de poste. Si c'est le cas, je pense que le principe DRY vous amènerait à implémenter un ensemble de définitions pouvant être utilisé pour les deux cas d'utilisation (comme cesPB*
définitions) plutôt que deux ensembles de définitions différents qui contiennent des informations répétitives.Par exemple, j'ai écrit une application qui peut prendre des mesures jusqu'à 8 canaux ADC. Il dispose d'une interface pour démarrer une nouvelle mesure dans laquelle vous pouvez spécifier plusieurs canaux via un champ de 8 bits, un bit pour chaque canal. (Les mesures sont effectuées en parallèle lorsque plusieurs canaux sont spécifiés.) Il dispose ensuite d'une autre interface qui renvoie les résultats de mesure pour un canal individuel. Ainsi, une interface utilise le numéro de canal comme décalage dans un champ binaire et l'autre interface utilise directement le numéro de canal. J'ai défini une seule énumération pour couvrir les deux cas d'utilisation.
la source