5

CのI²Cレジスタマップに関するベストプラクティス、または他の人がよく使用する/好むものについて疑問に思っています。

この時点まで、私は通常、すべてのレジスターに対して 1 つ、すべてのビット、マスク、シフトなどに対して 1 つ、多くの定義を行ってきました。しかし、最近、一部のドライバーが定義済みの代わりに (おそらくパックされた) 構造体を使用しているのを見てきました。これらはLinuxカーネルモジュールだったと思います。

とにかく、彼らは

struct i2c_sensor_fuu_registers {

    uint8_t  id;
    uint16_t big_register;
    uint8_t  another_register;
    ...

} __attribute__((packed));

次に、offsetof (またはマクロ) を使用して i2c レジスタを取得し、sizeof を使用して読み取るバイト数を取得します。

どちらのアプローチにもメリットがあることがわかりました。

構造体アプローチ:

  • (+) レジスタ オフセットはすべて、定義で各レジスタを綴る代わりに、論理的に構造体内に含まれます。
  • (+) エントリのサイズは、適切なサイズのデータ​​型を使用して明示的に記述されています。
  • (-) これは、広く使用されているビット フィールドを考慮していません。
  • (-) これは、オフセット n+0x00 から 2 バイトを読み取るが、n+0x01 は別のレジスタであり、レジスタ n の上位/下位バイトではない、バイト マップされていないレジスタ マップ (LM75 など) を考慮していません。 +0x00
  • (-) これは、アドレス空間の大きなギャップ (たとえば、0x00、0x01、0x80、0xAA のレジスター、中間がないなど) を考慮しておらず、(私が思うに?) 構造体を取り除くためにコンパイラーの最適化に依存しています。 .

アプローチを定義する:

  • (+) 各レジスタは通常、そのビットとともにブロックで定義されるため、適切なシンボルを簡単に見つけることができ、命名規則に依存します。
  • (+) アドレス空間のギャップを透過的/認識しない。
  • (-) ギャップがない場合でも、各レジスタを個別に定義する必要がある
  • (-) 定義はグローバルになる傾向があるため、通常、名前は非常に長くなり、ソース コードには何十もの長いシンボル名が散らばっています。
  • (-) 読み取るデータのサイズは、通常、ハードコードされたマジック ナンバーか、長いシンボル名を持つ (終了 - 開始 + 1) スタイルの計算のいずれかです。
  • (o) データ サイズとマップ内のアドレスを透過的/認識しない。

基本的に、私はこれらのケースを処理するためのよりスマートな方法を探しています。私はよく、すべてのレジスターと各ビット、そして場合によってはマスクとシフト (データ型に応じて後者の 2 つ) に対して、非常に長いシンボル名を大量に入力して、それらのほんの一部を使用することになります (ただし、不足しているシンボルを後で再定義するため、1 つのセッションですべてを入力するのはそのためです)。それでも、読み書きするバイトのサイズはほとんどがマジック ナンバーであり、通常、最も基本的な相互作用を理解するには、データシートとソース コードを並べて読む必要があることに気付きました。

他の人はこの種の状況をどのように処理するのだろうか? 私はオンラインでいくつかの例を見つけましたが、人々はすべてのレジスター、ビットなどを大きなヘッダーに熱心に入力しましたが、決定的なものは何もありませんでした...しかし、上記の2つのオプションはどちらも現時点ではあまりスマートではないようです:(

4

2 に答える 2

2

私の理想的な世界では、ハードウェア設計者は、C++、C、および ASM と互換性のあるヘッダー ファイルを提供します。実際のハードウェア レジスタに基づいて自動生成されたもの。#defines (ASM 用) と typedef 構造体 (C および C++ 用) の両方を介してすべてのレジスタとビット/フィールドを定義したもの。すべてのビットとフィールドのアクセス プロパティ (読み取り専用、書き込み専用、書き込みクリアなど) を示したもの。各レジスタとそのビット/フィールドの使用と目的を定義するコメントを含むもの。また、レジスターとビットフィールドが正しく順序付けられていることを確認するために、ターゲットのエンディアンとコンパイラーを考慮する必要があります。

私は前職でできる限りこの理想に近づきました。レジスタ記述ファイル (定義した形式) を解析し、完全なヘッダー (構造体と #defines) を自動生成するスクリプトと、デバッグ目的で読み取り可能なすべてのレジスタをダンプする関数を作成しました。他の会社でも同様のアプローチを見てきましたが、そこまで行ったものはありませんでした。

typedef 構造体を使用してレジスタ レイアウトを定義すると、定義内の大きなレジスタ ギャップを簡単に説明できることを指摘しておきます。たとえば、"reserved[80]" または "unused[94]" または "unimplemented[2044]" または "gap[42]" 配列要素を追加してギャップを定義します。とにかく、構造体定義をハードウェアベースアドレスへのポインターとして常に使用するため、メモリ内のどこでも構造体の実際のサイズを占有しません。

それが役立つことを願っています。

于 2012-11-20T20:43:28.560 に答える
2

警告: ここで説明する方法はビットフィールドを使用しており、メモリ内での配置は実装によって異なります。これを行う場合は、コンパイラがこの点でどのように機能するかを確認してください。

ご指摘のとおり、それぞれの方法には長所と短所があります。私はハイブリッドなアプローチが好きです。レジスタ オフセットを定義できますが、その内容には構造体を使用し、ビットまたはレジスタ全体を指定するには共用体を使用します。ユニオン内では、レジスタのサイズに正しいサイズ変数を使用します(前述のように、バイトアドレス指定できない場合があります)。それほど多くの定義は必要なく、ビット シフトを台無しにする可能性が低く、マスクは必要ありません。例えば:

#define unsigned char u8;
#define unsigned short u16;

#define CTL_REG_ADDR  0x1234
typedef union {
  struct { 
    u16 not_used:10; //top 10 bits ununsed
    u16 foo_bits:3;  //a multibit register
    u16 bar_bit:1;   //just one bit
    u16 baz_bits:2;  //2 more bits
  } fields;
  u16 raw;
} CTL_REG_DATA;

#define STATUS_REG_ADDR 0x58
typedef union {
  struct { 
    u8 bar_bits:4;  //upper nibble
    u8 baz_bits:4;  //lower nibble
  } fields;
  u8 raw;
} STATUS_REG_DATA;

//use them like the following
u16 readregister(u16);
void writeregister(u16,u16);

CTL_REG_DATA reg;
STATUS_REG_DATA rd;
rd = readregister(STATUS_REG_ADDR);
if (rd.fields.bar_bit) {
   reg.raw = 0xffff;        //set every bit
   reg.fields.bar_bit = 0;  //but clear this one bit
   writeregister(CTL_REG_ADDR, reg);
}
于 2012-11-20T16:22:48.953 に答える