プレステ無線コントローラとH8/3694の接続

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]の値を適宜書き換えます。

 

使用上の注意

 

○参考資料○

秋月3664基板でDS2無線コントローラを製作する(NONSAYAさん)
プレイステーション・PAD/メモリ・インターフェースの解析 (藤田さん)
デュアルショック(SCPH-1200)の解析(寺川さん)
DUALSHOCK2の信号解析まとめ (永田さん)