5
votes

Macro C pour définir plusieurs bits

Je travaille sur un microcontrôleur en C. Une partie de cela implique des changements de valeurs de bits dans les registres. J'ai mis au point quelques macros pour faciliter les choses:

#define BIT_SET(addr, shift) (addr | (1 << shift))
#define BIT_RESET(addr, shift) (addr & ~(1 << shift))
#define BIT_GET(addr, shift) (addr & (1 << shift))
...
reg1 = BIT_SET(reg1, 3); //set bit 3
...

Cependant, maintenant je veux faire une macro qui changera plusieurs bits à la fois. Par exemple, vous voudrez peut-être changer les 3 premiers bits de 10101010 en 110 , ce qui donnera le numéro 11001010.

Puis-je faire cela en C? Ou est-ce que j'aborde cela de la mauvaise façon?


3 commentaires

Réfléchissez d'abord, comment allez-vous invoquer une telle macro?


D'abord et cela avec -33 et ensuite ou en 192


@cleblanc C'est difficile à lire. Pourriez-vous reformuler / élaborer?


3 Réponses :


3
votes

Mon expérience a été d'avoir des masques de bits pour plusieurs bits est beaucoup plus facile. Ainsi, les bits individuels sont identifiés par une définition particulière dont le nom indique la fonction du bit comme dans

#define BIT_SET(addr, shift) ((addr) | (1 << (shift)))

, puis ceux-ci sont utilisés avec les opérateurs de bits pour activer et désactiver les bits. P >

#define BIT_SET(addr, shift) (addr | (1 << shift))

ou

// is T1 turned on
if (usReg1 & ENABLE_T1) {  

// is either T1 or T2 turned on
if (usReg1 & (ENABLE_T1 | ENABLE_T2)) {  

// is T1 turned on and T2 turned off
if ((usReg1 & (ENABLE_T1 | ENABLE_T2)) == ENABLE_T1) {  

// is either T1 or T2 turned on but not both
if ((usReg1 & (ENABLE_T1 | ENABLE_T2)) && ((usReg1 & (ENABLE_T1 | ENABLE_T2)) != (ENABLE_T1 | ENABLE_T2))) {

ou

// disable both T1 and T2
usReg1 &= ~(ENABLE_T1 | ENABLE_T2);

ou pour tester des bits p>

// enable both T1 and T2
usReg1 |= (ENABLE_T1 | ENABLE_T2);

Une note sur les macros

En passant, vous devez vous assurer que vous utilisez des parenthèses dans les définitions de macro pour isoler les arguments autant que possible pour la sécurité. Sans utiliser de parenthèses pour forcer explicitement un argument de macro à être évalué comme une expression unique, vous serez à la merci des règles de priorité des opérateurs et celles-ci ne seront pas toujours ce que vous voulez (voir https://en.cppreference.com/w/c/language/operator_precedence ). Donc

unsigned short usReg1 = GetRegister(REG1);

// disable T1 and enable T2

usReg1 &= ~ ENABLE_T1;
usReg1 |= ENABLE_T2;

devrait vraiment être écrit comme

#define ENABLE_T1  0x0001
#define ENABLE_T2  0x0002

Pour votre exemple, puisque les opérateurs de décalage au niveau du bit sont plutôt faibles sur le tableau de priorité, ce sera probablement bien dans la plupart des cas, mais avec d'autres macros, selon la priorité des opérateurs, cela peut entraîner des défauts car vous ne savez jamais à quel point quelqu'un sera prudent lors de l'utilisation d'une macro.


2 commentaires

Je suis d'accord; cela donne plus de flexibilité et améliore la lisibilité. (Cependant, je définirais personnellement les macros (ou constantes) comme suit: (1 << 0) , (1 << 1) , (1 << 2 ) , et ainsi de suite.)


@jamesdlin Je pensais que les macros étaient directement associées à des fonctionnalités documentées plutôt qu'à un nombre de bits générique. La plupart de la documentation qui utilise des bits spécifie un nombre octal, hexadécimal ou décimal (généralement les trois) qui fournit la valeur du masque de bits pour activer / désactiver certaines fonctionnalités ou qui indique un état.



2
votes

Vous pouvez introduire un masque pleine largeur définissant les bits que vous souhaitez définir. Par exemple. pour définir les 3 premiers bits, utilisez 0xe0, qui est en binaire 11100000.

Avec ce masque, indiquez la valeur à écrire également en pleine largeur.
Par exemple. 0xc0 pour le binaire 110 souhaité dans les trois premiers bits.

Ensuite, un peu de magie opératoire est nécessaire pour n'écrire que les positions de bits souhaitées.

#define MULTIBIT_SET(addr, mask, value) (((addr)&((0xff)^(mask)))|((value)&(mask)))

Ici ((0xff) ^ (mask)) inverse le masque, de sorte que anding avec ceci supprime les bits existants de la valeur.
((value) & (mask)) est la valeur mais avec uniquement des bits définis dans les positions souhaitées, c'est-à-dire que cela empêche la définition indésirable de bits ailleurs.

Au total, ou deux parties, vous donne le résultat souhaité.

Il y a un choix de conception ici, que je veux mentionner:
Appelez ça de la paranoïa. Si quelqu'un me dit "Je ne veux mettre que les trois premiers bits, à cette valeur qui a des bits ailleurs", alors je préfère le prendre en sécurité de cette façon, c'est-à-dire en supprimant les "bits ailleurs".
Oui, je suppose que définir des bits sans intention est plus un risque que de ne pas les définir, ce qui est une question de goût.


1 commentaires

@MadPhysicist Vous ne semblez plus voir de problème. J'ai cependant pris en compte votre contribution pour rendre ma réponse plus claire. Merci.



1
votes

Vous pouvez toujours le faire en C. :-)

Si vous utilisez un compilateur compatible C99, et étant donné que nous sommes en 2019, je suppose que vous l'êtes, vous pouvez utiliser des macros variadiques pour le faire fonctionner, et plus précisément, P99 .

J'ai eu un exemple où J'extrayais les composants pertinents de P99, mais cela devenait compliqué, car il s'agit d'une utilisation sérieuse du préprocesseur C en tant que langage Turing-complet.

Cela abuse également des parenthèses; en particulier, EXTRACT_VALUE x se développe en EXTRACT_VALUE (a, b) car nous transmettons une liste de tuples.

#include "p99.h"

#define EXTRACT_SHIFT(shift, value) (shift)
#define EXTRACT_VALUE(shift, value) (value)
#define MERGE(NAME, I, A, B) (A), (B)

#define SET_OR_CLEAR(target, x, i) \
    ((EXTRACT_VALUE x) ? \
        ((target) |= (1 << EXTRACT_SHIFT x)) : \
        ((target) &= ~(1 << EXTRACT_SHIFT x)))
#define SET_AND_CLEAR(target, pairs...) \
    P99_FOR(target, P99_NARG(pairs), MERGE, SET_OR_CLEAR, pairs)

// ...
reg1 = BIT_SET(reg3, 3); //set bit 3                                         
// ...
SET_AND_CLEAR(reg3, (7, 1), (6, 1), (5, 0)); // set bits 6 and 7, clear bit 5

Vous devrez peut-être compiler avec -std = c99 . Notez que j'ai utilisé le mot variadique paires au lieu de __VA_ARGS__ , pour essayer de préciser que nous voulons des 2-tuples comme arguments.

Notez que ceci ne masque aucun des registres d'entrée qui ne sont pas définis ou effacés.


1 commentaires

Je l'ai découvert à partir d'une question différente ici! Heureux d'avoir aidé.