ホーム

電子工作

腕ロボット

二足歩行ロボット

無線コントローラ

工作機械

電子工作動画

自作ソフト(C言語)

Webページ作成

Javascriptゲーム

FLASHとは!

自学自習のテキスト

その他

無線コントロール
無線コントロールに挑戦 2015年2月〜6月
 早速、吉野さんの著書に紹介されていたワイヤレスコントローラ「ロジクールのLPGC−60000」を 探しましたが2004年製造のものなのでどこにもありませんでした。
そこで、それと互換らしい製品をAmazonから購入しました。
左がそのコントローラでその上の小さいものがワイヤレスの受信機です。
本には「その受信機のコネクタ部分を中の線を露出させるように一部つぶしてその線にケーブルを直接 はんだ付けしてマイコンとつなぐ」とあったのですが、そうすると受信機を別のものには使えないように なってしまいます。
 ネットを「PS2の受信機コネクタ」で検索していると、受信機をつなぐものが売っていました(¥650)。
上の左がそれで、それに受信機をつないだところがその右の画像です。
これで、必要な時だけ受信機をこの器具につないで使えますので受信機をほかのものに随時使うことが出来ます。
マイコンは5V電源で、受信機コネクタは3V電源です。
 異なる電源系の間は電圧の違いを吸収するためにレベル変換を行う必要があります。
このためのレベル変換ICという便利なICがあります。その一つである「74VHCT540」を 使います。
左がその写真です。ただこのICの足の間隔は1.27mmで、一般の基盤の穴の間隔はこの倍の2.54mm なのです。このままでは基盤に取り付けることが出来ませんので、その仲立ちをする変換基盤を使います。
 D020と刻印されているのがそれです。裏の1.27mmのほうに1.27mmの足の間隔のVHCT540 を取り付けて一般の基盤につけます。
右の写真が変換基盤にVHCT540を付け、さらに変換基盤にヘッダーを付けて普通の基盤につけられるように したものです。
 左がその回路図です。
 ワイヤレスコントローラの受信機コネクタは3.3V電源なので、9Vバッテリーから3端子レギュレータを介して 作った3.3Vを受信機コネクタのVCC端子につなげます。 3端子レギュレータの前後にはコンデンサが必要です。

 受信機コネクタの端子は8個あります。機能は表示の通りですが8番端子は未使用です。
3番のmotor端子はPS2に受信機コネクタをつなげた時にPS2のCD−ROMドライブに供給する電源 となるもので、ここでは関係ありません。

 ワイヤレスコントローラを操作した結果は、受信機コネクタのCMD,SEL0,CLKにしかるべき 信号を入力すると、DATAとACKから出力信号が取れます。


 左が出来上がったものです。
 奥に見えている2行表示の液晶モニタはDATAコネクタからの信号を表示するために付けています。
 基盤の下にあるLEDはコントローラのボタンを押したときに点灯するようにしています。

 DATAとACKにはプルアップ抵抗を付ける必要がありますが、回路図には4.7kΩと書いてあります。 有線のコントローラで試すと思い通りの動作をしました。

 表示しているのはロジクール製のものです。


 というのは、最初に買ったサードパーティー製のワイヤレスコントローラでは動作しませんでした。
 そこで、テストのために有線のコントローラを購入して、動作確認すると思い通りの動作をしました。
 サードパーティー製のものにして、プルアップ抵抗の値をいろいろと変えてみようと思い、半固定抵抗にして いろいろと試しましたが、LEDは思い通りに点灯しません。
 ネットをいろいろ調べるとロジクール製のもので実験されている方がほとんどでしたので、ロジクール製 のものを購入して試しましたが、やはりだめでした。

 それで、吉野さんにヘルプを仰ぐと、「いろいろ設定を変えて試してください。」ということでしたので、 待ち時間を変えたり、待ち時間を入れたりするとロジクール製は完全にうまくいきました。
 実際には、ネットに次のような設定が載っていましたのでこの通りに設定するとロジクール製のものは うまく動作しました。
ロジクール製以外のものは、DATから信号が返ってきてるのですが、正常に読み取れません。
 とりあえず、ロジクール製のものはうまくいっているので「ロジクール製以外を調べるのは先送り」にして 先に進みます。
 
SEL,CLK,CMKから上のようなパルスを送るとDATからパルス出力されます。
この画像は次のサイトから拝借したものです。
 http://homepage3.nifty.com/nisokuda-yahhoi/index.htm

下の表のようにCMDから01h、42h、00h、00h、00h、と5バイトのデータを送ると同時に DATから5バイトのデータが出力され、その4バイト目と5バイト目がワイヤレスコントローラ のどのボタンを押したかのキーデータとなります。
	ノーマルPAD       時間の流れ →

	 10000000 01000010 01011010 01234567 01234567
	|--------|--------|--------|--------|--------|
 CMD	|   01h  |  42h   |   00h  |   00h  |   00h  |
	|--------|--------|--------|--------|--------|
         xxxxxxxx 10000010 10100101 01234567 01234567
	|--------|--------|--------|--------|--------|
 DAT	|  ----  |  41h   |   5ah  |  SW.1  |  SW.2  |
	|--------|--------|--------|--------|--------|


 プログラム的にはそう簡単ではありません。
というのは、上の波形で分かるようにこれらの受信機コネクタの端子の入出力はすべて ビット単位です。しかし、データはバイト単位です。つまり、1バイトのデータが8個のパルス として送られてきます。たとえば01hは「10000000」のようにです。
僕もこのへんのことは全然知らなかったもので、吉野氏への質問とか前述のサイトとか次のサイトを 見て勉強した次第です。
 http://www.vector.co.jp/soft/data/game/se020797.html
次の表はDAT端子からの出力データです。4バイト目、5バイト目のキーデータは押されたキーの 箇所が0となります。
 例えば、十字ボタンの前を押すと11101111のデータなので、十進数では239となります。
	ノーマルパッド
	+----------+----+----+----+----+----+----+----+----+
	|  バイト  | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
	+==========+====+====+====+====+====+====+====+====+
	|1バイト目|               -----                   |  不定でよい
	+----------+---------------------------------------+
	|2バイト目|                0x41                   | 'A'
	+----------+---------------------------------------+
	|3バイト目|                0x5a                   | 'Z'
	+----------+----+----+----+----+----+----+----+----+
	|4バイト目| 左 | 後 | 右 | 前 |スタート|  1 |  1 |セレクト|
	+----------+----+----+----+----+----+----+----+----+
	|5バイト目| □ | × | ○ | △ | R1 | L1 | R2 | L2 |
	+----------+----+----+----+----+----+----+----+----+

 コントローラにはグリグリスティックがふたつあります。
 このグリグリスティックを使うにはアナログモードにする必要があります。
 アナログモードにすると、次のようにDATから次のように返ってきます。
 6バイト目からは右スティックキーの左右の、7バイト目からは上下の位置情報が、
 8バイト目からは左スティックの左右の位置情報、9バイト目からは 上下の位置情報を取得できます。
アナログジョイスティック(アナログモード)
	+----------+----+----+----+----+----+----+----+----+
	|  バイト  | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
	+==========+====+====+====+====+====+====+====+====+
	|1バイト目|               -----                   |  不定でよい
	+----------+---------------------------------------+
	|2バイト目|                0x53                   |
	+----------+---------------------------------------+
	|3バイト目|                0x5a                   | 'Z'
	+----------+----+----+----+----+----+----+----+----+
	|4バイト目| 左 | 下 | 右 | 上 |スタート|  1 |  1 |セレクト|
	+----------+----+----+----+----+----+----+----+----+
	|5バイト目| □ | × | ○ | △ | R1 | L1 | R2 | L2 |
	+----------+----+----+----+----+----+----+----+----+
	|6バイト目| 右ハンドル  左右  左:00h,中心:80h,右:FFh |
	+----------+---------------------------------------+
	|7バイト目| 右ハンドル  上下  上:00h,中心:80h,下:FFh |
	+----------+---------------------------------------+
	|8バイト目| 左ハンドル  左右  左:00h,中心:80h,右:FFh |
	+----------+---------------------------------------+
	|9バイト目| 左ハンドル  上下  上:00h,中心:80h,下:FFh |
	+----------+---------------------------------------+
	注)4バイト目の上下左右は、右ハンドルの方向キー

 アクリル板に固定して使いやすいようにしました。
 ワイヤレスコントローラのボタンをセロテープで固定してその状態を液晶モニタとLEDで 判別できるようにしています。

 液晶モニタには、1行目にはDATの4,5バイト目(十字□×○△キー)、2行目にはDATの6〜9 バイト目(グリグリスティック)の状態を表示しています。

LEDの数を増やしました。左のスティックキーを回すとその位置に対応したLEDが点灯します。
右のスティックキーを動かすと内側のLEDが光るようにします。

 キーをセロテープで固定してLEDを光らせています。

液晶モニタに表示するのは文字列だけなので数値を文字列に変換する関数を作って使っています。
待ち時間を作るために、ms単位の関数、μs単位の関数を使っています。
プログラム上でパルスを作ってクロックとしています。

このプログラムの核であるワイヤレスコントローラへの通信の部分は、吉野さんが作ったものをベースにしています。
いまさらながら吉野さんの力には脱帽です。

/***************************************/
/* ワイヤレスコントローラ wcontroll.c  */
/***************************************/
void itu0_wait_init(void);
void itu1WaitMsec(int msec);
void itu1WaitMicro(int kkk);
#include "3048f.h"
#include "lcd1.h"
int remocon_mode1(void);
/*関数宣言*/
void rc_read(unsigned char *cmdData,unsigned char *swData);
void rc_select(unsigned char status);
void rc_io(unsigned char out_data, unsigned char *in_data);
char *numToHexa(int num,char *ss);
char *numToString(int su,char *s);
char *numToBinary(int su,char *s);
int DomainInBig(int domain,int k1,int k2,int x,int y);
int DomainInSmall(int domain,int k1,int x,int y);
/*数値をバイナリ文字列に変換する関数*/
char *numToBinary(int su,char *s)
{
	int i,k,ret,num,num1,num2,len;
	char s1[25];
	for(i=1;i<=20;i++)	s1[i-1] = 0x20;
	s1[20]=0;
	k=1;
	num = su;
	while(1){
		num1 = num % 2 +'0';
		s1[20-k]=num1;
		num1 = num /2;
		num = num1;
		if(num1<1)	break;
		k++;
	}
	strcpy(s,s1+20-k);
	return (s);
}
char *numToHexa(int num,char *ss)
{
	int i,k,num1,num2;
	char s1[10],s2[10];

	num1 = num/16;
	num2 = num % 16;
	strcpy(s1," ");
	strcpy(s2," ");
	if(num1>=10) num1 = num1-10+'A';
	else num1 = num1+'0';
	s1[0] = num1;
	if(num2>=10)	num2=num2-10+'A';
	else num2=num2+'0';
	s2[0] = num2;
	strcpy(ss,"0x");
	strcat(ss,s1);
	strcat(ss,s2);
	return ss;
}
char *numToString(int su,char *s)
{	
	int i,k,ret,num,num1,num2,len;
	char s1[25];
	for(i=1;i<=20;i++)	s1[i-1] = 0x20;
	s1[20]=0;
	k=1;
	num = su;
	while(1){
		num1 = num % 10 +'0';
		s1[20-k]=num1;
		num1 = num /10;
		num = num1;
		if(num1<1)	break;
		k++;
	}
	strcpy(s,s1+20-k);
	return (s);
}
/* 待ち時間発生初期化 *********************************/
void itu0_wait_init(void){
	/* ITU0、1のクロックを8分週 */
	/* 3125:1ms、4687.5:1.5ms、6250:2ms、62500:20ms*/
	ITU0.TCR.BYTE = 0x03;	/* ITU0、TCNTクリア禁止 clock 1/8 */
	ITU1.TCR.BYTE = 0x03;
	/* ITU1、TCNTクリア禁止 clock 1/8 */
	ITU.TSTR.BIT.STR0 = 0;	/* ITU0 TCNTカウント停止 */
	ITU.TSTR.BIT.STR1 = 0;	/* ITU1 TCNTカウント停止 */
	return;
}
/* 待ち時間発生 引数に必要なミリ秒を指定する*************/
void itu1WaitMsec(int msec){
	ITU.TSTR.BIT.STR1 = 1;	/* ITU1 TCNTカウント開始 */
	ITU1.TCNT = 0;
    	while(ITU1.TCNT <3125*msec);/*msec second経過するまで待つ*/
	ITU.TSTR.BIT.STR1 = 0;	/* ITU1 TCNTカウント停止 */
	return;
}
/* 待ち時間発生 μ秒指定*************/
void itu1WaitMicro(int kkk){
    double k;
	k = ((double)kkk)*3.125;
	ITU.TSTR.BIT.STR1 = 1;	/* ITU1 TCNTカウント開始 */
	ITU1.TCNT = 0;
    	while(ITU1.TCNT < (int)k);/*kkk μ秒経過するまで待つ*/
	ITU.TSTR.BIT.STR1 = 0;	/* ITU1 TCNTカウント停止 */
	return;
}
/********************************************************/
/*  リモコンコントローラのボタン等の操作状態を読み取る  */
/* 引数:ACTION構造体へのポインタ                       */
/* 返値:なし                                           */
/********************************************************/
void rc_read(unsigned char *cmdData,unsigned char *swData)
{
unsigned char	i, j;

	/*CMD Pin リセット*/
	P5.DR.BYTE= P5.DR.BYTE & 0xfe;

	/*選択*/
	rc_select(1);
	itu1WaitMicro(500); /*500μ秒待つ*/

	/*ボタンの状態読み取り*/
	for(i = 0; i < 5; i++)
	{
		rc_io(cmdData[i], &swData[i]);
		itu1WaitMicro(10); /*10μ秒待つ*/

/*		j = rc_ack();*/
	}

	/*ジョイスティックの状態読み取り(アナログモード時のみ)*/
	if(swData[1] == 115)
	{
		for(i = 5; i < 9; i++)
		{
			rc_io(cmdData[i], &swData[i]);
		itu1WaitMicro(10); /*10μ秒待つ*/
/*			j = rc_ack();*/
		}
	}
	itu1WaitMsec(10); /*10m秒待つ*/
	/*非選択*/
	rc_select(0);

	/*CMD Pin リセット*/
	P5.DR.BYTE = P5.DR.BYTE & 0xfe;
}

/******************************************************/
/*  セレクトピンの状態を操作する                      */
/* 引数:status 選択/非選択状態                      */
/* 返値:なし                                         */
/******************************************************/
void rc_select(unsigned char status)
{
	if(status == 1)	P5.DR.BYTE = P5.DR.BYTE | 0x02;/*パッドに Low  送信 ・・・ 選択*/
	else			P5.DR.BYTE = P5.DR.BYTE & 0xfd;/*パッドに High 送信 ・・・ 解除*/
}
/******************************************************/
/*  ACKピンの状態を確認する                        */
/* 引数:なし                                         */
/* 返値:ACKピンの状態                             */
/******************************************************/
unsigned char rc_ack(void)
{
	ITU1.TCNT = 0;
	while(1)
	{
		if((P6.DR.BYTE & 0x40) != 0)	return(1);	/*ACK 受信*/
		if(ITU1.TCNT > 400)		return(0);	/*タイムアウト*/
	}
}

/******************************************************/
/* シリアルに送受するするデータを1バイトにまとめる   */
/*レベル変換IC、74vhct540はインバータなので全データをインバート */
/* 引数:out_data CMDデータ                        */
/*    in_data SWデータ                          */
/* 返値:なし                                         */
/******************************************************/
void rc_io(unsigned char out_data, unsigned char *in_data)
{
unsigned char	i, j, mask;

	mask = 1;
	*in_data = 0;

	for(i = 0; i < 8; i++)
	{
		/*CMD bit*/
		if( (mask & out_data) == 0) P5.DR.BYTE = P5.DR.BYTE | 0x01;
		else	P5.DR.BYTE = P5.DR.BYTE & 0xfe;

		/*Clock ↓*/
		P5.DR.BYTE = P5.DR.BYTE | 0x04;
		/*wait*/
		for(j = 0; j < 6; j++);
		/*Clock ↑*/
		P5.DR.BYTE = P5.DR.BYTE & 0xfb;
		/*wait*/
		for(j = 0; j < 6; j++);

		/*Check DAT*/
		if((P5.DR.BYTE & 0x08) == 0) *in_data = *in_data + mask;

		mask = mask * 2;
	}

	return;
}
/*サーボ番号をピン番号に割り当てる*/
void srvWari(int srvNo, int x)
{
	switch(srvNo){
		case 0:	P3.DR.BIT.B0 = x;break;
		case 1:	P3.DR.BIT.B1 = x;break;
	 	case 2:	P3.DR.BIT.B2 = x;break;
	  	case 3:	P3.DR.BIT.B3 = x;break;
		case 4:	P3.DR.BIT.B4 = x;break;
		case 5:	P3.DR.BIT.B5 = x;break;
		case 6:	P3.DR.BIT.B6 = x;break;
		case 7:	P3.DR.BIT.B7 = x;break;
		case 8: P1.DR.BIT.B0 = x;break;
		case 9:	P1.DR.BIT.B1 = x;break;
		case 10:P1.DR.BIT.B2 = x;break;
		case 11:P1.DR.BIT.B3 = x;break;
		case 12:P1.DR.BIT.B4 = x;break;
		case 13:P1.DR.BIT.B5 = x;break;
		case 14:P1.DR.BIT.B6 = x;break;
		case 15:P1.DR.BIT.B7 = x;break;
		case 16:P2.DR.BIT.B0 = x;break;
		case 17:P2.DR.BIT.B1 = x;break;
	 	case 18:P2.DR.BIT.B2 = x;break;
	  	case 19:P2.DR.BIT.B3 = x;break;
		case 20:P2.DR.BIT.B4 = x;break;
  	  		}
}
/*右スティックによるLED点灯*/
int DomainInSmall(int d,int k3,int x,int y)
{
	int r2,x1,y1;
	x1 = x-128; y1 = y-128; 
	r2 = x1*x1 + y1*y1;
	if( r2 < d*d )	return -1;
	if(y1<-k3)	{
		if(x1<-k3)	return 11;
		else if(x1<0)	return 9;
		else if(x1< k3)	return 8;
		else return 10;
	}
	else if(y1<0)	{
		if(x1<0)	return 2;
		else return 6;
	}
	else if(y1< k3)	{
		if(x1<0)	return 3;
		else return 4;
	}
	else {
		if(x1<0)	return 20;
		else return 18;
	}
}
/*左スティックによるLED点灯*/
int DomainInBig(int d,int k1,int k2,int x,int y)
{
	int r2,x1,y1;
	x1 = x-128; y1 = y-128; 
	r2 = x1*x1 + y1*y1;
	if( r2 < d*d )	return -1;
	if(y1<-2*k2)	{
		if(x1<-k1)	return 15;
		else if(x1< k1)	return 17;
		else return 19;
	}
	else if(y1<-k2)	{
		if(x1<-64)	return 13;
		else if(x1<0)	return 9;
		else if(x1<64)  return 8;
		else return 12;
	}
	else if(y1<0)	{
		if(x1<0)	return 11;
		else return 10;
	}
	else if(y1< k2){
		if(x1<0)	return 2;
		else return 6;
	}
	else if(y1<2*k2){
		if(x1<-80)	return 0;
		else if(x1<-40)	return 3;
		else if(x1<0)	return 20;
		else if(x1<40)	return 18;
		else if(x1<80)	return 4;
		else return 7;
	}
	else {
		if(x1<-64)	return 1;
		else if(x1<0)	return 14;
		else if(x1<64)	return 16;
		else return 5;
	}
}
/*メイン関数*/
void main(void){
	unsigned char num;
	int ret1,ret2,domain,k1,k2,k3;
	unsigned char cmdData[32],swData[32];
	char s[20],s1[20];
	int case_number,i,su3,su4,su5,su6,su7,su8;
	domain = 30;k1=40;k2=40;k3=64;
	P1.DDR = 0xff;		/*PORT3を出力に設定*/ 
	P3.DDR = 0xff;		/*PORT3を出力に設定*/
 	P2.DDR = 0xff;		/*PORT2を出力に設定*/
	P5.DDR = 0x07;		/*port5*/
	itu0_wait_init(); 	/* 待機タイマーの初期化 */
	lcd_init();		/* lcd液晶表示器の初期化 */
	rc_select(0);		/*コントローラ初期化*/
	cmdData[0] = 0x01;cmdData[1] = 0x42;
	for(i = 2; i < 32; i++) cmdData[i] = 0x00;	
 while(1){
	for(i=0;i<=20;i++)	swData[i]=0;
	rc_read(cmdData,swData);/*リモコンコントローラ状態読み取り*/
	for(i=0;i<=20;i++)	srvWari(i, 0);/*LED全消灯*/
	su3 = swData[3];su4 = swData[4];
	su5 = (int)swData[5];su6 = (int)swData[6];/*右スティックのデータx,y*/
	su7 = (int)swData[7];su8 = (int)swData[8];/*左スティックのデータx、y*/
	/*ボタン状態デコード*************************/
	case_number = 0;
	if(su3 == 239 && su4 == 255) {case_number = 1;srvWari(2,1);}/* 十字前*/
	if(su3 == 191 && su4 == 255) {case_number = 2;srvWari(1,1);}/* 十字後*/
	if(su3 == 127 && su4 == 255) {case_number = 3;srvWari(0,1);}/* 十字左*/
	if(su3 == 223 && su4 == 255) {case_number = 4;srvWari(3,1);}/* 十字右*/
	if(su3 == 255 && su4 == 239) {case_number = 5;srvWari(6,1);}/*△*/
	if(su3 == 255 && su4 == 127) {case_number = 6;srvWari(4,1);}/*□*/
	if(su3 == 255 && su4 == 191) {case_number = 7;srvWari(5,1);}/*×*/
	if(su3 == 255 && su4 == 223) {case_number = 8;srvWari(7,1);}/*○*/
		
	if(su3 == 255 && su4 == 247) {case_number = 9;srvWari(10,1);}/* R1*/
	if(su3 == 255 && su4 == 251) {case_number = 10;srvWari(11,1);}/* L1*/
	if(su3 == 255 && su4 == 253) {case_number = 11;srvWari(12,1);}/* R2*/
	if(su3 == 255 && su4 == 254) {case_number = 12;srvWari(13,1);}/* L2*/	
	if(su3 == 247 && su4 == 255) {case_number = 13;srvWari(8,1);}/*スタート*/
	
	if(swData[1] == 115){/*アナログモード時*/
		ret1 = DomainInSmall(domain,k3, su5, su6);/*右スティックによるLED点灯*/
		srvWari(ret1,1);
		ret2 = DomainInBig(domain,k1,k2, su7, su8);/*左スティックによるLED点灯*/
		srvWari(ret2,1);
	}
	numToString(su3,s);
	lcd_locate(0,0);/* カーソル位置の指定*/
	lcd_print(s);			/* 文字列の表示 */
	numToString(su4,s);
	lcd_locate(4,0);/* カーソル位置の指定*/
	lcd_print(s);			/* 文字列の表示 */

	numToString(su5,s);
	if(su5<100 && su5 >=10)	 strcat(s," ");
	if(su5<10)	strcat(s,"  ");
	lcd_locate(8,1);/* カーソル位置の指定*/
	lcd_print(s);			/* 文字列の表示 */

	numToString(su6,s);
	if(su6<100 && su6 >=10)	 strcat(s," ");
	if(su6<10)	strcat(s,"  ");
	lcd_locate(12,1);/* カーソル位置の指定*/
	lcd_print(s);			/* 文字列の表示 */
	
	numToString(su7,s);
	if(su7<100 && su7 >=10)	 strcat(s," ");
	if(su7<10)	strcat(s,"  ");
	lcd_locate(0,1);/* カーソル位置の指定*/
	lcd_print(s);			/* 文字列の表示 */

	numToString(su8,s);
	if(su8<100 && su8 >=10)	 strcat(s," ");
	if(su8<10)	strcat(s,"  ");	
	lcd_locate(4,1	);/* カーソル位置の指定*/
	lcd_print(s);			/* 文字列の表示 */
	
 }
}