ビットフィールドの使用方法
組み込み現場ではフラグ処理を多用するかと思います。イベント発行などでフラグ処理を使っている場合はフラグではなくキューでのイベント発行を心掛けた方がよいかと思いますが、それでもフラグ処理が有効な場合は多々あるかと思います。
ここでは一例としてデバイス制御のコマンドに関して考えていきたいと思います。他のデバイスを制御する際、その制御コマンドがビットで定義されている事があります。そういった時どうやって簡単なインターフェースを実現するでしょうか?
たとえばマクロを使用してコマンドの一つ一つを定義するというのがあるかと思います。
#define DfBit1 0x01 #define DfBit2 0x02 #define DfBit3 0x04 #define DfBit4 0x08
この場合はビット操作 and や or を使ってコマンドを変更しますが、ビット操作というのは他のプログラマには大変理解しにくいものです。
他にビット操作を簡単に実現するものとしてビットフィールドがあります。しかしビットフィールドだと毎回そのtypeで定義しなければならず、これが面倒となります。そこで次のようなキャストを使用したマクロを使用します。
typedef struct BIT_TYPE1_{ unsigned btCom0:1; unsigned btCom1:1; unsigned btCom2:1; unsigned btCom3:1; unsigned btDummy:4; } BIT_TYPE1; #define DfBitType1(X) (*( (BIT_TYPE1*)(&X) ) )
次のように使用します。
unsigned char cCom; cCom = 0; DfBitType1(cCom).btCom1=1; std::cout << "cCom =" << (int)cCom << std::endl;
このプログラムの出力結果は1となります。他のビットを操作したいときは構造体と同様に名前を指定するだけで中身のビット列を意識することなく使用できます。ただしこの方法にはいくつか問題があります。まずdefineを使ってしまうという事です。スコープの制御ができない為、基本的に非公開にしたいコマンドをすべてのファイルで無条件で見ることが出来てしまいます。また定義が増えるとその分defineが増えて選択ミスを引き起こすかもしれません。そしてキャストは基本的に右辺のみに使うべきです。そこで次のようにunionで定義する方法が汎用性が高く有効です。
typedef union UnBitCom_{ unsigned char BYTE; struct BIT_TYPE1_{ unsigned btCom0:1; unsigned btCom1:1; unsigned btCom2:1; unsigned btCom3:1; unsigned btDummy:4; } BIT_TYPE1; struct BIT_TYPE2_{ unsigned btCom0:1; unsigned btCom1:1; unsigned btCom2:1; unsigned btDummy:4; unsigned btCom7:1; } BIT_TYPE2; struct BIT_TYPE3_{ unsigned btCom0:1; unsigned btCom1:1; unsigned btCom2:1; unsigned btCom3:1; unsigned btDummy:4; } BIT_TYPE3; } UnBitCom ;
次のように使用します。
UnBitCom unCom; unCom.BYTE = 0; unCom.BIT_TYPE2.btCom7=1; std::cout << "unCom =" << (int)unCom.BYTE << std::endl;
BYTEアクセスしたい場合はBYTEを指定すれば直値を入れる事が出来ます。またビットフィールドを複数定義した場合もそれを取捨選択できます。VisualStuidoで編集すればインテリセンスで指定するstructのtypeをSuggestionしてくれます。またdefineを使わないで済みますので(unionで変数宣言する必要はありますが)コマンドの隠蔽にも便利です。