目的とする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を実行します。
受信(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のデータをリードします。
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;