microchipが提供しているPICマイコン向けのサンプルコードを見ると、自分が普段使わない宣言がいくつかあったのでメモっておく。
該当するPICのBDTの宣言
PICのUSBホスト処理で、「BDT(Buffer Descriptor Table)の構造体の先頭アドレスが512バイト境界に合わせる」云々という記述がある。該当するデータのヘッダなど見ていると、以下のようになっている。(PIC24Fの場合)
typedef union _BD_STAT
{
struct{
unsigned :2; //Byte count
unsigned BSTALL :1; //Buffer Stall Enable
unsigned DTSEN :1; //Data Toggle Synch Enable
unsigned :2; //Reserved - write as 00
unsigned DTS :1; //Data Toggle Synch Value
unsigned UOWN :1; //USB Ownership
};
struct{
unsigned :2;
unsigned PID0 :1;
unsigned PID1 :1;
unsigned PID2 :1;
unsigned PID3 :1;
};
struct{
unsigned :2;
unsigned PID :4; // Packet Identifier
};
uint8_t Val;
} BD_STAT;//Buffer Descriptor Status Register
// BDT Entry Layout
typedef union __BDT{
union
{
struct
{
uint8_t CNT;
BD_STAT STAT __attribute__ ((packed));
};
struct
{
//test
uint16_t count:10;
uint8_t :6;
//Buffer Address
uint16_t ADR;
};
};
uint32_t Val;
uint16_t v[2];
} BDT_ENTRY;
static BDT_ENTRY __attribute__ ((aligned(512))) BDT[2];
全く分からない。。とりあえず一つずつ確認する。
attribute ((packed)), attribute ((aligned(x)))
構造体の詰め物を抑制する attribute ((packed))
使っているCPUプロセッサの種類によって変わってくるのか、構造体のメンバの型のサイズとsizeof
したサイズが合わないことがしばしばある。普通にドットやアロー使ってアクセスする分には気にしなくても良いが、キャストしたりすると問題になる。サイズが合わなくなるのは、パフォーマンスを考えて、アクセスしやすいように、構造体メンバの間に詰め物を自動的に差し込まれるのが原因である。
struct _pk{
char a;
short b;
int c;
}pk;
pk.a = 1;
pk.b = 2;
pk.c = 3;
メンバのサイズを合計すると、1+2+4=7bytes
になる(VisualStudioの場合)が、実際には8bytesになる。代入後のメモリを1バイト区切りで見ると、
01 cc 02 00 03 00 00 00
となり、aとbの間に詰め物が1バイト分入っているのが分かる。もちろん特にintのサイズがプラットフォームごとに変わるので、どうなるかは実際に試す必要がある。
この詰め物を抑制する宣言が、gccコンパイラだと__attribute__ ((packed))
になる。(VisualStudioだと#pragma pack(1)
)
struct _pk{
char a;
short b;
int c;
} __attribute__ ((packed)) pk;
pk.a = 1;
pk.b = 2;
pk.c = 3;
この宣言を追加して、もう一度実行すると、sizeof(pk) = 7
となって
01 02 00 03 00 00 00
となって詰め物がなくなった。CPUの効率は悪くなるらしいので、むやみにやることは避けたい。
先頭アドレスを指定サイズごとにそろえる attribute ((align))
うまく言えないが、変数のアドレス(配列なら先頭アドレス)を指定したバイトの倍数になるように位置を合わせることもできる。(指定サイズは2の乗数だったはずだし、最大値もある)
PIC24FのBDT関連の変数宣言を確認すると、以下になっている(ピンポンバッファOFFの場合)
static BDT_ENTRY __attribute__ ((aligned(512))) BDT[2];
USBホストの場合、エンドポイント0に相当する部分しか利用しないらしいので、BDT_ENTRYが2個分の配列になっている。この先頭アドレスが512バイトごとに区切った位置に、揃うようなアロケーションになる。データシートの記述に準拠した実装になっている。
配列でビットフィールドを表現する
2つの構造体があり、unsigned short(2バイト、16ビット)のメンバが3つ定義されている。どちらもドットやアロー演算子でアクセスすると違いがない。
struct _st{
unsigned short a;
unsigned short b;
unsigned short c;
}test;
test.a = 2;
test.b = 31;
test.c = 0;
struct _st2{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
} test2;
test2.a = 2;
test2.b = 31;
test2.c = 0;
が、sizeof
でサイズを確認すると違いが出る。
sizeof(test) = 6 // 2byte x 3
sizeof(test2) = 2 // ???
testは2byteデータが3つなので6bytes(詰め物がない場合)になるが、test2はコロンで区切って数字を指定すると、ビットサイズを指定できるようになる。つまり、aが4bit、bが5bit、cは7bitのデータが連続するようになる。合計16bitなので、2バイトになる。
ちなみに以下のように1bitだけのデータの場合は、その変数で定義されている最小サイズは確保されるので、2バイトになる。
struct _st3{
unsigned short a : 1;
} test3;
test3.a = 1;
// sizeof(test3) == 2
また、指定したビットサイズを超える値を代入しても無視されるみたいだ。
test3.a = 4 // 1ビットなので、0か1しか代入しても無視される
あと、PICのレジスタによくあるが、未使用のビットが有効ビットの間に入るような並びを表現する場合は、メンバ名を省略できる。それと並び順は、宣言順に下位ビットに割り当てられる。
struct _st2{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
} test2;
// 15---- 8 7------0
// cccccccb bbbbaaaa
// 00000001 11110010
test2.a = 2; // 0x02 b00000010
test2.b = 31; // 0x1f b00011111
test2.c = 0; // 0x00 b00000000
データ定義をもう一度確認する
BD_STAT
まず、unionを使ったBD_STATのデータ定義について、最初のメンバ(1つ目の構造体)をベースとして、3つのデータが重なったようなデータ構造になる。BDT_ENTRYで使われるときは、__attribute__((packed))
でパディングされずに1バイトのサイズになる。
typedef union _BD_STAT
{
struct{
unsigned :2; //Byte count
unsigned BSTALL :1; //Buffer Stall Enable
unsigned DTSEN :1; //Data Toggle Synch Enable
unsigned :2; //Reserved - write as 00
unsigned DTS :1; //Data Toggle Synch Value
unsigned UOWN :1; //USB Ownership
};
struct{
unsigned :2;
unsigned PID0 :1;
unsigned PID1 :1;
unsigned PID2 :1;
unsigned PID3 :1;
};
struct{
unsigned :2;
unsigned PID :4; // Packet Identifier
};
uint8_t Val;
} BD_STAT;//Buffer Descriptor Status Register
Valに値を代入すると、各構造体のビットに反映されるような使い勝手だと思われる。実際に定義して値を代入すると、以下のような感じになる。
BD_ENTRY
さらにバッファのアドレスやら、カウントなどが積み重なってBDT(Buffer Descriptor Table)が定義されている。
// BDT Entry Layout
typedef union __BDT{
union
{
struct
{
uint8_t CNT;
BD_STAT STAT __attribute__ ((packed));
};
struct
{
uint16_t count:10;
uint8_t :6;
uint16_t ADR; //Buffer Address
};
};
uint32_t Val;
uint16_t v[2];
} BDT_ENTRY;
BDTの定義
ピンポンバッファの設定に応じて、BDTの配列のサイズが変わる。ホストモードの場合、EP0しか使わないので、EP1~EP15用のBDエントリが存在しない。
// ピンポンバッファOFF
static BDT_ENTRY __attribute__ ((aligned(512))) BDT[2];
#define BDT_IN (&BDT[0]) // EP0 IN Buffer Descriptor
#define BDT_OUT (&BDT[1]) // EP0 OUT Buffer Descriptor
// EP0のOUTだけバッファON
static BDT_ENTRY __attribute__ ((aligned(512))) BDT[3];
#define BDT_IN (&BDT[0]) // EP0 IN Buffer Descriptor
#define BDT_OUT (&BDT[1]) // EP0 OUT Even Buffer Descriptor
#define BDT_OUT_ODD (&BDT[2]) // EP0 OUT Odd Buffer Descriptor
// バッファON
static BDT_ENTRY __attribute__ ((aligned(512))) BDT[4];
#define BDT_IN (&BDT[0]) // EP0 IN Even Buffer Descriptor
#define BDT_IN_ODD (&BDT[1]) // EP0 IN Odd Buffer Descriptor
#define BDT_OUT (&BDT[2]) // EP0 OUT Even Buffer Descriptor
#define BDT_OUT_ODD (&BDT[3]) // EP0 OUT Odd Buffer Descriptor