初心者のCプログラマーとして、デバイスに制御ビットを設定するための読みやすく、理解しやすい最良のソリューションは何でしょうか。 規格はありますか?模倣するコード例はありますか? Googleは信頼できる回答をしませんでした。
私が見る最初の方法は、単に必要なビットを設定することです。コメントにはたくさんの説明が必要であり、それほど専門的ではないようです。
DMA_base_ptr[DMA_CONTROL_OFFS] = 0b10001100;
2番目の方法は、ビットフィールドを作成することです。この方法で使用されることは一度もなかったので、これが私がこれにこだわるべきかどうかはわかりません(最初に述べたオプションとは異なります)。
struct DMA_control_block_struct
{
unsigned int BYTE:1;
unsigned int HW:1;
// etc
} DMA_control_block_struct;
オプションの1つは他のオプションよりも優れていますか?表示されないオプションはありますか?
どんなアドバイスも大歓迎です
ビットフィールドの問題は、C標準では、定義されている順序が実装されている順序と同じであるとは規定されていないことです。だから、あなたがあなたがいると思うビットを設定していないかもしれません。
C標準 状態のセクション6.7.2.1p11:
実装は、ビットフィールドを保持するのに十分な大きさのアドレス可能なストレージユニットを割り当てます。十分なスペースが残っている場合、構造内の別のビットフィールドの直後に続くビットフィールドは、同じユニットの隣接するビットにパックされます。 十分なスペースが残っていない場合、適合しないビットフィールドが次のユニットに配置されるか、隣接するユニットと重複するかは、実装で定義されます。ユニット内のビットフィールドの割り当て順序(高位から低位または低位から高位)は実装定義です。アドレス可能なストレージユニットのアライメントは不特定。
例として、Linuxの/usr/include/netinet/ip.hファイルファイルのIPヘッダーを表すstruct iphdr
の定義を見てください。
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;
...
ここで、実装に応じてビットフィールドが異なる順序で配置されていることがわかります。この動作はシステムに依存するため、この特定のチェックも使用しないでください。このファイルはシステムの一部であるため、許容できます。他のシステムでは、これをさまざまな方法で実装できます。
そのため、ビットフィールドを使用しないでください。
これを行う最良の方法は、必要なビットを設定することです。ただし、各ビットに名前付き定数を定義し、設定する定数のビットごとのORを実行することは理にかなっています。例:
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;
他の答えはすでにほとんどのものをカバーしていますが、非標準の0b
構文を使用できない場合でも、シフトを使用して1
ビットをビット番号による位置、すなわち:
#define DMA_BYTE (1U << 0)
#define DMA_HW (1U << 1)
#define DMA_Word (1U << 2)
#define DMA_GO (1U << 3)
// …
最後の番号がドキュメントの「ビット番号」列と一致することに注意してください。
ビットの設定とクリアの使用法は変わりません:
#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
古いCの方法は、たくさんのビットを定義することです:
#define Word 0x04
#define GO 0x08
#define I_EN 0x10
#define LEEN 0x80
その後、初期化は
DMA_base_ptr[DMA_CONTROL_OFFS] = Word | GO | LEEN;
|
を使用して個々のビットを設定できます:
DMA_base_ptr[DMA_CONTROL_OFFS] |= I_EN;
&
および~
を使用して個々のビットをクリアできます。
DMA_base_ptr[DMA_CONTROL_OFFS] &= ~GO;
&
を使用して個々のビットをテストできます:
if(DMA_base_ptr[DMA_CONTROL_OFFS] & Word) ...
ただし、ビットフィールドは絶対に使用しないでください。それらには用途がありますが、ビットが特定の場所にあると外部仕様で定義されている場合はそうではありません。
ビットフィールドの標準はありません。この場合、マッピングとビット操作はコンパイラに依存します。 0b0000
などのバイナリ値も標準化されていません。通常の方法は、各ビットに16進値を定義することです。例えば:
#define BYTE (0x01)
#define HW (0x02)
/*etc*/
ビットを設定する場合は、次を使用できます。
DMA_base_ptr[DMA_CONTROL_OFFS] |= HW;
または、次の方法でビットをクリアできます。
DMA_base_ptr[DMA_CONTROL_OFFS] &= ~HW;
最新のCコンパイラは、些細なインライン関数をオーバーヘッドなしでうまく処理します。すべての抽象化関数を作成するため、ユーザーはビットや整数を操作する必要がなく、実装の詳細を乱用することはほとんどありません。
もちろん、実装の詳細には関数ではなく定数を使用できますが、APIは関数でなければなりません。これにより、古代のコンパイラを使用している場合、関数の代わりにマクロを使用することもできます。
例えば:
#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); }
このようなコードはマシンで生成する必要があります。gsl
(0mq名声)_を使用して、テンプレートとレジスターの詳細をリストするXML入力に基づいてコードを生成します。