目的とするUSBデバイス(ゲームPAD)が動作するシンプルなドライバを作成します。
USBホストCoreの初期設定
ゲームPADを接続してUSBホストCoreからバスリセットを行うと、USBデバイスはデフォルトステートに移行ます。また、USBホストCoreからはCPUに対して接続イベント(CONNECTION_EVENT)インタラプトが発生します。この状態で、USBデバイスのデフォルトアドレス0に対してアクセスが可能になります。

デフォルトステートまでの処理
USBデバイスとのデータ転送
USBホストCoreとUSBデバイスは、IN/OUT/SETUPのTransactionを用いてデータ転送を行います。
送信(OUT Transaction)
USBホストからUSBデバイスにデータを転送します。USBホストCoreのUSB_TX_FIFO_DATAに送信するデータを設定、HOST_TX_TRANS_REGレジスタのTRANSACTION_TYPEにOUTDATA0_TRANSまたはOUTDATA1_TRANSを設定した後、
HOST_TX_CONTROL_REGのTRANS_REQ_BITを1に設定すると、OUT Transactionを実行します。

OUT Transactionフロー
受信(IN Transaction)
USBデバイスからデータを受信します。HOST_TX_TRANS_REGレジスタのTRANSACTION_TYPEにIN_TRANSを設定後、HOST_TX_CONTROL_REGのTRANS_REQ_BITを1に設定すると、USBデバイスにIN Tokenが送信されます。USBデバイスからデータを受信するとUSB_INTERRUPT_STATUS_REGのTRANS_DONE_BITが1になります。USB_RX_FIFO_DATA_COUNT_LSBの値を読み出し、その回数だけUSB_RX_FIFO_DATAのデータをリードします。

IN Transactionフロー
SETUP Transaction
デバイスリクエスト用のデータをUSB_TX_FIFO_DATAに設定、HOST_TX_TRANS_REGレジスタのTRANSACTION_TYPEにSETUP_REANSを設定後、HOST_TX_CONTROL_REGのTRANS_REQ_BITを1に設定すると、SETUP Transactionを実行します。
ディスクリプタの獲得
どのようなデバイスがUSBバスに接続されているかは、そのデバイスの標準ディスクリプタを獲得する事で判断できます。
- デバイスディスクリプタ
- コンフィグレーションディスクリプタ
- インターフェースディスクリプタ
- HIDディスクリプタ
- エンドポイントディスクリプタ
各ディスクリプタは、コントロール転送でUSBデバイスから獲得します。HID(Human Interface Device)ディスクリプタは、HIDクラス独自のディスクリプタです。
各ディスクリプタの詳細はこちら
各ディスクリプタから、USBデバイス(ゲームPAD)についての以下の情報が得られます。
- コンフィグレーション数は1
- インターフェース数は1
- エンドポイント数は1(除くエンドポイント0)
- インターフェースクラスは3(HID)
- エンドポイントのアドレスは1
- エンドポイント1の転送はインタラプトIN転送
- エンドポイント1の最大パケットサイズは4
インタラプト転送
ディスクリプタ情報から、USBデバイス(ゲームPAD)のデータは、エンドポイント1へのインタラプト転送で獲得できることがわかりました。CPUのタイマー割り込みを用いて、一定間隔のインタラプト転送をUSBデバイスに対して発行し、その時のゲームPADの状態を獲得します。

インタラプト転送フロー
ゲームPADデータの獲得
ゲームPADのデータ(どのボタンが押されたか、どの方向キーが押されたか)は、インタラプト転送で獲得したデータに格納されています。データフォーマットは、HIDクラスのREPORTディスクリプタから得られます。今回使用したゲームPADは、4ByteのデータとしてPADの状態がホストに返されます。PADの状態は次のフォーマットで格納されています。
Byte | フィールド名 | 説明 |
---|---|---|
0 | x方向キー | 左: 0センター:0x7f, 右: 0xff |
1 | y方向キー | 上: 0センター:0x7f, 下: 0xff |
2 | ボタン0グループ | 各ビットに8個のボタンの状態をアサイン OFF:0, ON:1 |
3 | ボタン1グループ | ビット0と1に、2個のボタンの状態をアサイン OFF:0, ON:1 |
テストプログラム
簡単なテストプログラムで動作の確認を行いました。
- USBデバイスのアドレスは5に設定
- インタラプト転送は、CPUのタイマ割り込みを用いて1秒間隔で実行
- FPGAからのIRQ1インタラプトでUSBホストCoreのFIFOをリードし、ゲームPAD用の構造体にデータを格納
プログラム(抜粋)
メイン
int main(void){
int i;
char buf[64];
puts ("USB Test\n");
intr_disable();
int_config();
timer1_config(); // インタラプト転送用タイマー設定
intr_enable();
usb_init(); // USBホストCoreの初期化
while(1) {
//インタラプトハンドラでインタラプト転送を実行;
}
return 0;
}
タイマー設定
void timer1_config() {
// TMU1を使用して1秒間隔でインタラプトを発生
// pck = 33MHz = 30ns, pck/1024 = 32.5KHz = 30us
TMU.TSTR0.BYTE = 0x00; // stop timer
// set interrupt priority for TMU0(bit28:24),TMU1(bit20:16)
INTC.INT2PRI0.LONG = 0x0c0c0000; // bit 28-24 (0,1 = mask)
INTC.INT2MSKCR.LONG = 1; // TMU0-2
TMU1.TCOR = get_timer_count(1000000000); // timer constant (1s)
TMU1.TCNT = get_timer_count(1000000000);
TMU1.TCR.WORD = 0x0024; // timer control UNF=0,UNIE=1,CKEG=00 TPSC=100
TMU.TSTR0.BYTE = 0x02; // start tmu1
}
インタラプトハンドラ(タイマー割り込み)
void INT_TMU1_TUNI1() {
unsigned int x;
puts ("TMU1 INT\r\n");
// clear TCR0.UNF
TMU1.TCR.WORD = TMU1.TCR.WORD & ~0x0100;
x = 1;
while (x) x = INTC.INT2A0.LONG & 1; // bit0 : TMU0-2
// start usb interrupt transfer
if (usb_state == USB_NORMAL) interrupt_in_transfer_nb(5, 1); // アドレス5, エンドポイント1へのインタラプト転送
}
void interrupt_in_transfer_nb(int adrs, int endp) {
USB_TX_ADDR_REG.LONG = adrs;
USB_TX_ENDP_REG.LONG = endp;
USB_TX_TRANS_TYPE_REG.LONG = 1; // IN TRANS
int_trans_done = 0;
USB_CONTROL_REG.LONG = 1;
puts("Start INT_TRANS\r\n");
}
インタラプトハンドラ(FPGA IRQ1割り込み)
void INT_IRL_LEVEL11() {
// INTR IRQ1
unsigned int d;
char buf[64];
d = USB_INTERRUPT_STATUS_REG.LONG;
puts("INT detected\r\n");
if (d & 0x1) {
puts("TRANS_DONE_BIT\r\n");
USB_INTERRUPT_STATUS_REG.LONG = 1; // clear status
int_trans_done = 1;
if (usb_state == USB_NORMAL) interrupt_in_transfer_get(5, 1); // データ獲得
} else if (d & 0x2) {
puts("RESUME_INT_BIT\r\n");
USB_INTERRUPT_STATUS_REG.LONG = 2;
} else if (d & 0x4) {
puts("CONNECTION_EVENT_BIT\r\n");
USB_INTERRUPT_STATUS_REG.LONG = 4;
int_connect_done = 1;
} else if (d & 0x8){
puts("SOF_SENT_BIT\r\n");
USB_INTERRUPT_STATUS_REG.LONG = 8;
} else {
sprintf(buf, "UNKOWN INT %x \r\n", d);
puts(buf);
}
}
void interrupt_in_transfer_get(int adrs, int endp) {
int i, fnum;
char buf[64];
unsigned int status;
// buffer clear
for (i=0; i < 4; i++) joypad_status.PACKED[i] = 0;
while (!int_trans_done) ;
puts("interrupt in trans done\r\n");
status = USB_RX_STATUS_REG.LONG;
sprintf(buf, "INT RX STATUS %x \r\n", status);
puts(buf);
// get IN data
fnum = USB_RX_FIFO_DATA_COUNT_LSB.LONG;
sprintf(buf,"interrupt in length = %d\r\n",fnum);
puts(buf);
for (i = 0; i < fnum; i++) {
if (i <4) joypad_status.PACKED[i] = USB_RX_FIFO_DATA.LONG; // 受信データを構造体に格納
}
// status
sprintf(buf, "X = %x\r\n",joypad_status.BYTE.x);
puts(buf);
sprintf(buf, "Y = %x\r\n",joypad_status.BYTE.y);
puts(buf);
sprintf(buf, "B1 = %x, %d %d\r\n",joypad_status.BYTE.b1.BYTE,
joypad_status.BYTE.b1.BIT.button0,
joypad_status.BYTE.b1.BIT.button1);
puts(buf);
sprintf(buf, "B1 = %x, %d %d %d %d %d %d %d %d\r\n",joypad_status.BYTE.b0.BYTE,
joypad_status.BYTE.b0.BIT.button0,
joypad_status.BYTE.b0.BIT.button1,
joypad_status.BYTE.b0.BIT.button2,
joypad_status.BYTE.b0.BIT.button3,
joypad_status.BYTE.b0.BIT.button4,
joypad_status.BYTE.b0.BIT.button5,
joypad_status.BYTE.b0.BIT.button6,
joypad_status.BYTE.b0.BIT.button7);
puts(buf);
}
ゲームPAD用データ構造体
typedef union {
unsigned char PACKED[4];
struct {
unsigned char x; // left: 0, center:0x7f. right: 0xff
unsigned char y; // up: 0, center:0x7f. down: 0xff
union {
unsigned char BYTE;
struct {
unsigned char button0 : 1;
unsigned char button1 : 1;
unsigned char button2 : 1;
unsigned char button3 : 1;
unsigned char button4 : 1;
unsigned char button5 : 1;
unsigned char button6 : 1;
unsigned char button7 : 1;
} BIT;
} b0;
union {
unsigned char BYTE;
struct {
unsigned char button0 : 1;
unsigned char button1 : 1;
unsigned char : 6;
} BIT;
} b1;
} BYTE;
} st_joypad;