Pourquoi le code AVR utilise le décalage de bits [fermé]

7

Dans la programmation AVR, les bits de registre sont invariablement définis en déplaçant a 1vers 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, PB3et 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.

Blair Fonville
la source
3
J'appuie ceci. Même l'utilisation de l'omniprésent _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).
calcium3000
2
Une fois qu'il a réalisé que le compilateur les interprètera comme la même constante, laquelle utiliser dans le code source est vraiment une question de préférence. Dans votre propre code, faites ce que vous pensez être le plus sage; en modifiant les projets existants, respectez leurs traditions.
Chris Stratton
3
"Puisqu'une approche de masque binaire donnerait clairement un code d'utilisateur final plus lisible" - votre opinion personnelle. Je trouve qu'il est beaucoup plus clair de déplacer les 1 et les 0 au bon endroit que d'avoir à deviner si plusieurs nombres ajoutés ensemble sont des masques de bit ou non.
Tom Carpenter
3
@TomCarpenter Intéressant. Eh bien, j'ai peut-être posé par inadvertance une question d'opinion. Quoi qu'il en soit, il y a eu de bons commentaires. Venant de plus d'un arrière-plan DSP (TI) (où le masque de bits est la norme), cela ressemblait à une syntaxe si étrange que je pensais qu'il y avait une raison concrète à cela.
Blair Fonville
1
@BlairFonville Peut-être que vous le savez déjà, mais ARM fonctionne exactement comme vous le décrivez (avec des masques de bit).
Chi

Réponses:

7

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.

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

  • Donc, cela ne fonctionne pas seulement, il ne crée pas plus de taille de code.
  • 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é).

fromage blanc
la source
1
Je comprends que. Je ne me demande pas si cela fonctionne ou non. Je le sais. Je demande pourquoi les définitions sont écrites telles quelles. Pour moi, cela améliorerait considérablement la lisibilité du code s'ils étaient définis comme des masques plutôt que des positions de bits.
Blair Fonville
5
Je pense que cette réponse manque le point. Il ne parle jamais d'efficacité de code ou de compilateur. Il s'agit de l' encombrement du code source .
pipe
4
@Blair Fonville: il n'y a pas de moyen simple de définir une telle macro. Il fallait calculer le logarithme à la base 2. Il n'y a pas de fonctionnalité de préprocesseur calculant le logarithme. C'est-à-dire que cela ne pourrait être fait qu'en utilisant une table et que, je pense, ce serait une très mauvaise idée.
Curd
2
@pipe: Je n'en parle pas parce que je ne le considère pas comme une "pollution de code" ou un "encombrement de code source" (ou comme vous voulez l'appeler). Au contraire, je pense qu'il est même utile de rappeler au programmeur / lecteur que la constante qu'il utilise est une puissance de deux (et cela se fait en utilisant l'expression shift).
Curd
1
@RJR, Blair Fonville: bien sûr, il est facilement possible de définir de telles macros MAIS tout en utilisant des définitions de préprocesseur simples est ok j'éviterais les macros de préprocesseur (aka functios de préprocesseur) chaque fois que possible car elles peuvent faire du débogage (parcourir le code source C avec le débogueur) extrêmement transparent.
Curd
4

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ù les PB*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 ces PB*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.

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);
}
kkrambo
la source