web-dev-qa-db-fra.com

Bonnes manipulations de manipulations

En tant que programmeur C débutant, je me demande quelle serait la meilleure solution facile à lire et à comprendre pour définir les bits de contrôle dans un périphérique. Existe-t-il des normes ? Un exemple de code à imiter? Google n'a pas donné de réponse fiable.

Par exemple, j'ai une carte de bloc de contrôle: map

La première façon que je vois serait simplement de définir les bits nécessaires. Cela nécessite de nombreuses explications dans les commentaires et ne semble pas être du tout professionnel.

DMA_base_ptr[DMA_CONTROL_OFFS] = 0b10001100;

La deuxième façon que je vois est de créer un champ de bits. Je ne suis pas sûr que ce soit celui-là auquel je devrais me tenir, car je ne l'ai jamais vu utilisé de cette manière (contrairement à la première option que j'ai mentionnée).

struct DMA_control_block_struct
{ 
    unsigned int BYTE:1; 
    unsigned int HW:1; 
    // etc
} DMA_control_block_struct;

L'une des options est-elle meilleure que l'autre? Existe-t-il des options que je ne vois tout simplement pas?

Tout conseil serait hautement apprécié

34
KateOleneva

Le problème avec les champs de bits est que le standard C ne dicte pas que leur ordre de définition est identique à celui de leur implémentation. Donc, vous ne définissez peut-être pas les éléments que vous pensez être.

La section 6.7.2.1p11 du norme C se lit comme suit:

Une mise en œuvre peut allouer une unité de stockage adressable suffisamment grande pour contenir un champ de bits. S'il reste suffisamment d'espace, un champ de bits qui suit immédiatement un autre champ de bits d'une structure doit être compressé dans des bits adjacents de la même unité. S'il ne reste pas assez d'espace, qu'un champ de bits non ajusté soit placé dans l'unité suivante ou qu'il chevauche des unités adjacentes est défini par la mise en oeuvre. L'ordre d'attribution des champs de bits dans une unité (ordre élevé à ordre faible ou ordre faible à ordre élevé) est défini par la mise en oeuvre. L'alignement de l'unité de stockage adressable est non spécifié.

A titre d'exemple, regardez la définition de struct iphdr, qui représente un en-tête IP, à partir du fichier /usr/include/netinet/ip.h sous Linux:

struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#Elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
    u_int8_t tos;
    ...

Vous pouvez voir ici que les champs de bits sont placés dans un ordre différent en fonction de l'implémentation. Vous ne devez pas non plus utiliser cette vérification spécifique car ce comportement dépend du système. Il est acceptable pour ce fichier car il fait partie du système. D'autres systèmes peuvent implémenter cela de différentes manières.

Alors n'utilisez pas de bitfield.

La meilleure façon de faire est de définir les bits requis. Cependant, il serait judicieux de définir des constantes nommées pour chaque bit et d’effectuer un bitwise OR des constantes que vous souhaitez définir. Par exemple:

const uint8_t BIT_BYTE =     0x1;
const uint8_t BIT_HW   =     0x2;
const uint8_t BIT_Word =     0x4;
const uint8_t BIT_GO   =     0x8;
const uint8_t BIT_I_EN =     0x10;
const uint8_t BIT_REEN =     0x20;
const uint8_t BIT_WEEN =     0x40;
const uint8_t BIT_LEEN =     0x80;

DMA_base_ptr[DMA_CONTROL_OFFS] = BIT_LEEN | BIT_GO | BIT_Word;
40
dbush

D'autres réponses ont déjà couvert la plupart des éléments, mais il peut être intéressant de mentionner que même si vous ne pouvez pas utiliser le langage non standard 0b _ syntaxe, vous pouvez utiliser des décalages pour déplacer le 1 bit en position par numéro de bit, à savoir:

#define DMA_BYTE  (1U << 0)
#define DMA_HW    (1U << 1)
#define DMA_Word  (1U << 2)
#define DMA_GO    (1U << 3)
// …

Notez que le dernier numéro correspond à la colonne "numéro de bit" de la documentation.

L'utilisation pour la définition et la suppression des bits ne change pas:

#define DMA_CONTROL_REG DMA_base_ptr[DMA_CONTROL_OFFS]

DMA_CONTROL_REG |= DMA_HW | DMA_Word;    // set HW and Word
DMA_CONTROL_REG &= ~(DMA_BYTE | DMA_GO); // clear BYTE and GO
19
Arkku

La méthode C de la vieille école consiste à définir un ensemble de bits:

#define Word  0x04
#define GO    0x08
#define I_EN  0x10
#define LEEN  0x80

Ensuite, votre initialisation devient

DMA_base_ptr[DMA_CONTROL_OFFS] = Word | GO | LEEN;

Vous pouvez définir des bits individuels à l'aide de |:

DMA_base_ptr[DMA_CONTROL_OFFS] |= I_EN;

Vous pouvez effacer des bits individuels en utilisant & et ~:

DMA_base_ptr[DMA_CONTROL_OFFS] &= ~GO;

Vous pouvez tester des bits individuels en utilisant &:

if(DMA_base_ptr[DMA_CONTROL_OFFS] & Word) ...

N'utilisez certainement pas les champs de bits, cependant. Ils ont leur utilité, mais pas lorsqu'une spécification externe définit que les bits se trouvent à certains endroits, comme je le suppose ici.

Voir aussi les questions 20.7 et 2.26 dans C FAQ liste .

18
Steve Summit

Il n'y a pas de standard pour les champs de bits. Le mappage et les opérations sur les bits dépendent du compilateur dans ce cas. Les valeurs binaires telles que 0b0000 ne sont pas standardisés également. La méthode habituelle consiste à définir des valeurs hexadécimales pour chaque bit. Par exemple:

#define BYTE (0x01)
#define HW   (0x02)
/*etc*/

Lorsque vous souhaitez définir des bits, vous pouvez utiliser:

DMA_base_ptr[DMA_CONTROL_OFFS] |= HW;

Ou vous pouvez effacer des bits avec:

DMA_base_ptr[DMA_CONTROL_OFFS] &= ~HW;
8
kurtfu

Les compilateurs C modernes gèrent très bien les fonctions en ligne triviales - sans surcharge. Je créerais toutes les fonctions d'abstractions pour que l'utilisateur n'ait pas besoin de manipuler de bits ou d'entiers, et n'abuserait probablement pas des détails de la mise en œuvre.

Vous pouvez bien sûr utiliser des constantes et non des fonctions pour les détails d'implémentation, mais l'API doit être une fonction. Cela permet également d’utiliser des macros au lieu de fonctions si vous utilisez un compilateur ancien.

Par exemple:

#include <stdbool.h>
#include <stdint.h>

typedef union DmaBase {
  volatile uint8_t u8[32];
} DmaBase;
static inline DmaBase *const dma1__base(void) { return (void*)0x12340000; }

// instead of DMA_CONTROL_OFFS
static inline volatile uint8_t *dma_CONTROL(DmaBase *base) { return &(base->u8[12]); }
// instead of constants etc
static inline uint8_t dma__BYTE(void) { return 0x01; }

inline bool dma_BYTE(DmaBase *base) { return *dma_CONTROL(base) & dma__BYTE(); }
inline void dma_set_BYTE(DmaBase *base, bool val) {
  if (val) *dma_CONTROL(base) |= dma__BYTE();
  else *dma_CONTROL(base) &= ~dma__BYTE();
}
inline bool dma1_BYTE(void) { return dma_BYTE(dma1__base()); }
inline void dma1_set_BYTE(bool val) { dma_set_BYTE(dma1__base(), val); }

Ce code devrait être généré par la machine: j'utilise gsl (de 0mq gloire) pour générer ceux basés sur un modèle et une entrée XML listant les détails des registres.

4
Kuba Ober