GameTech社製プレイステーション用無線コントローラ(デュアルショック2互換品) をストロベリーリナックス社製H8/3694マイコンボードに接続する実験を行いました。 以下、試される場合は全て自己責任でお願いします。 なお、以下にはソースファイルの全文を含め"丸のまま"が書いてあります。 僕のように機械が専門でソフトに興味が無い方は良いとして、 「作る楽しみ」を奪われたくない方は読まないほうがいいかも知れません(^^;)。
まず、無線コントローラは3.3〜3.6V仕様なので、マイコン回路全体も3.3V系とする必要があります。 マイコンボード付属の5Vレギュレータの代わりにTA48033を用います。それ以外の回路変更等は特に不要です。 (註:本来3694は3V以上で動作可能ですが、クロック20MHzの場合は4V以上が必要とされます。 この使い方は動作保証外となりますが、今の所問題なく動いてます。)
図のようにマイコンのIOポートとVcc、グランドを無線コントローラの受信機に接続します。 IOポートのうち、Port20,1,2は変更不可です。Port52は余っている出力ポートなら何でもOKです。
以下長いですがソースファイルを……
//コントローラ操作コマンド群 //コンフィギュレーションモードエンター unsigned char CMD_config_mode_enter[] = {0x01,0x43,0x00,0x01, 0x00,0x00,0x00,0x00, 0x00}; //コンフィギュレーションモードイクジット unsigned char CMD_config_mode_exit[] = {0x01,0x43,0x00,0x00, 0x5a,0x5a,0x5a,0x5a, 0x5a}; //アナ⇔デジ遷移とそのロック unsigned char CMD_set_mode_and_lock[] = {0x01,0x44,0x00,0x01, 0x00,0x00,0x00,0x00, 0x00}; //コントローラの種別とモード取得 unsigned char CMD_query_model_and_mode[] = {0x01,0x45,0x00,0x5a, 0x5a,0x5a,0x5a,0x5a, 0x5a}; //バイブ機能許可 unsigned char CMD_vibration_enable[] = {0x01,0x4d,0x00,0x00, 0x01,0xff,0xff,0xff, 0xff}; //バイブ機能禁止 unsigned char CMD_vibration_disnable[] = {0x01,0x4d,0x00,0xff, 0xff,0xff,0xff,0xff, 0xff}; //Dual Shock 2である事の確認 unsigned char CMD_query_DS2_analog_mode[] = {0x01,0x41,0x00,0x5a, 0x5a,0x5a,0x5a,0x5a, 0x5a}; //DS2固有アナログモードへの遷移 unsigned char CMD_set_DS2_native_mode[] = {0x01,0x4f,0x00,0xff, 0xff,0x03,0x00,0x00, 0x00}; //キーデータ読み込み(DS2native以外) unsigned char CMD_read_data[] = {0x01,0x42,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00}; //キーデータ読み込み(DS2native) unsigned char CMD_read_data2[] = {0x01,0x42,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00}; //送信データの格納変数 unsigned char CMD_temp[21]; //受信データの格納変数 unsigned char DAT_temp[21]; #define DAT_ID DAT_temp[1] #define DAT_SS DAT_temp[2] |
まず、こんな感じでコントローラに渡すコマンドを配列として定義します。 コマンドの内容については永田さんの解説を参考にしました。 コンパイラに依るかもしれませんが、 初期値付きで宣言した変数はROM領域に書き込まれる為、プログラム中で値を書き換えることはできません。 よって、_set_mode_and_lockや_read_dataなど値を変える必要のあるコマンドは、_tempにコピーしてから使います(後述)。
union { unsigned char BYTE; struct{ unsigned char dig_left :1; unsigned char dig_down :1; unsigned char dig_right :1; unsigned char dig_upper :1; unsigned char dig_start :1; unsigned char dig_r3 :1; unsigned char dig_l3 :1; unsigned char dig_serect :1; } BIT; } DIG1; union { unsigned char BYTE; struct{ unsigned char dig_square :1; unsigned char dig_cross :1; unsigned char dig_circle :1; unsigned char dig_delta :1; unsigned char dig_r1 :1; unsigned char dig_l1 :1; unsigned char dig_r2 :1; unsigned char dig_l2 :1; } BIT; } DIG2; #define D_LEFT !DIG1.BIT.dig_left #define D_DOWN !DIG1.BIT.dig_down #define D_RIGHT !DIG1.BIT.dig_right #define D_UPPER !DIG1.BIT.dig_upper #define D_START !DIG1.BIT.dig_start #define D_R3 !DIG1.BIT.dig_r3 #define D_L3 !DIG1.BIT.dig_l3 #define D_SERECT !DIG1.BIT.dig_serect #define D_SQUARE !DIG2.BIT.dig_square #define D_CROSS !DIG2.BIT.dig_cross #define D_CIRCLE !DIG2.BIT.dig_circle #define D_DELTA !DIG2.BIT.dig_delta #define D_R1 !DIG2.BIT.dig_r1 #define D_L1 !DIG2.BIT.dig_l1 #define D_R2 !DIG2.BIT.dig_r2 #define D_L2 !DIG2.BIT.dig_l2 #define A_RH DAT_temp[5] #define A_RV DAT_temp[6] #define A_LH DAT_temp[7] #define A_LV DAT_temp[8] #define A_RIGHT DAT_temp[9] #define A_LEFT DAT_temp[10] #define A_UPPER DAT_temp[11] #define A_DOWN DAT_temp[12] #define A_DELTA DAT_temp[13] #define A_CIRCLE DAT_temp[14] #define A_CROSS DAT_temp[15] #define A_SQUARE DAT_temp[16] #define A_L1 DAT_temp[17] #define A_R1 DAT_temp[18] #define A_L2 DAT_temp[19] #define A_R2 DAT_temp[20] |
この部分は必須ではありません。プログラムからキー状態を参照する際見やすいように受信データに名前を付けています。 DIG1にはDAT_temp[3]、DIG2にはDAT_temp[4]の値を都度代入します。 デジタルデータ(押してる/押してない)は"押してないときHigh"という仕様なので、"押したら1"になるよう頭に"!"を付けています。
#define SEL IO.PDR5.BIT.B2 //1バイト送受信 int trade_char(unsigned char *cmd,unsigned char *data){ unsigned char stus; if (SCI3.SSR.BIT.TDRE) SCI3.TDR = *cmd ; stus = SCI3.SSR.BYTE; while((stus & 0x40) == 0){ //受信終了待ち stus = SCI3.SSR.BYTE; } if((stus & 0x20) != 0){ //オーバーランエラー処理 SCI3.SSR.BIT.OER &= 0; return 1; } else { *data = SCI3.RDR; return 0; } } //バイト列の送受信 void trade_st(unsigned char *cmd,unsigned char cmd_len, unsigned char *data){ unsigned char i; volatile char j; SCI3.SCR3.BYTE = 0x00; //0000 0000内部クロックを出力 SCI3.SMR.BYTE = 0x80; //1000 0000クロック同期式モード SCI3.BRR = 9; //500k bps for(j=0; j<2; j++); //1パルス分以上待機 IO.PMR1.BIT.TXD = 1; //Port22のTxD出力許可 SEL = 0; SCI3.SSR.BYTE &= 0xC7; SCI3.SCR3.BYTE |= 0x30; //TE,RE =1 for(i = 0; i < cmd_len;i++){ trade_char(cmd + i, data + i); } SEL = 1; } |
通信にはマイコンのシリアル・コミュニケーション・インターフェイス(SCI)モジュールを用います。 ハードウェアマニュアルの記載に従って順にレジスタを操作していきます。通信速度は500kbpsです。 SEL信号はプレステ本体がコントローラとメモリーカードを区別するためのもので、 コントローラとの通信時はLowとする仕様になっています。クロック同期式通信のアウトラインについては こちら。
void conf_DS2(void){ int i; // trade_st(CMD_read_data, sizeof(CMD_read_data), DAT_temp); trade_st(CMD_config_mode_enter, sizeof(CMD_config_mode_enter), DAT_temp); trade_st(CMD_query_model_and_mode, sizeof(CMD_query_model_and_mode), DAT_temp); for(i=0; i<21; i++){ CMD_temp[i] = CMD_set_mode_and_lock[i]; } if(DAT_temp[5] ==0x00) CMD_temp[3] = 0x00; //digital mode else CMD_temp[3] = 0x01; //analog mode trade_st(CMD_temp, sizeof(CMD_set_mode_and_lock), DAT_temp); // trade_st(CMD_query_DS2_analog_mode, sizeof(CMD_query_DS2_analog_mode), DAT_temp); trade_st(CMD_set_DS2_native_mode, sizeof(CMD_set_DS2_native_mode), DAT_temp); trade_st(CMD_vibration_enable, sizeof(CMD_vibration_enable), DAT_temp); trade_st(CMD_config_mode_exit, sizeof(CMD_config_mode_exit), DAT_temp); for(i=0; i<21; i++){ CMD_temp[i] = CMD_read_data2[i]; } } void com_DS2(void){ if(DAT_SS != 0x5a) conf_DS2(); if(DAT_ID == 0x79) trade_st(CMD_temp, sizeof(CMD_read_data2), DAT_temp); else trade_st(CMD_temp, sizeof(CMD_read_data), DAT_temp); DIG1.BYTE = DAT_temp[3]; DIG2.BYTE = DAT_temp[4]; } [EOF] |
最後にこんな感じの関数を定義して、com_DS2を20msecぐらいの周期で呼び出します。 コントローラのアナログ切り替えボタンを押したときや異常なデータが帰って来たときはconf_DS2が呼び出されます。 _set_mode_and_lockでは切り替えボタンの要求に応じてアナログモードとデジタルモードを切り替えます。 その際、前述の通りコマンドを一度コピーしてから該当部分を変更します。 また、conf_DS2から抜ける前に、_tempにread_data2を代入しておきます。バイブレーションを働かせる際は、 CMD_temp[3]、[4]の値を適宜書き換えます。