[[2013b/Member]]

#contents

*メンバー [#we36f83d]

Tomo, Yuki, Naoki, Naoyoshi

*ルール [#ta2b9743]


*作戦 [#ie32c57a]

作戦が定まるまでは多くの試行錯誤を繰り返した。まずハードをどのような形態にするか、つまりどういった仕組みで缶を持ち上げ、塔を積み立てるかということを考え、いくつか試作機が作られた。中には我々を笑いの渦に引き込んで離さないほど滑稽なものもあったが、名誉のために言及しない。
その他ライントレース、走行プログラムの方向性なども考慮し、最終的に定まったのは以下のポイントである。

+ 二台のNXTによる一体型ロボットを作る
+ 缶をつかむアームの上下運動は、四角いフレームを折りたたんだり広げたりすることで行う
+ 一度掴んだ缶を次の缶の上に載せ、また持ち替える方法を取る
+ 交差点の判定には2つの光センサを用いる
+ 缶の位置の同定には超音波センサを用いる
+ 走行パターン、缶の塔の運搬・建造パターンの切り替えは、缶をつかむ動作の回数によって定める

*ハードウェア紹介 [#jd67d9d8]

*ソフトウェア紹介 [#v439f1f9]

走行用プログラムはマスター、缶を掴むプログラムはスレーブが担当した。

**マスター側のプログラム [#h6f3adb9]

***定義文〜サブルーチン群 [#zc5d9d42]

 #define RIGHT OUT_B
 #define LEFT OUT_C
 #define BOTH OUT_BC
 #define SPEED1 35
 #define SPEED2 SPEED1*2
 #define OnRL(speedR,speedL) OnRev(RIGHT,speedR);OnRev(LEFT,speedL); 	//ギアの都合で正転と逆転が反対
 #define go_forward OnRevSync(BOTH,SPEED2,0);	//左右のタイヤの移動距離をシンクロさせて前進
 #define go_back OnFwdSync(BOTH,SPEED2,0);	//後退
 #define turn_left1 OnFwdSync(BOTH,SPEED2,100);	//時計回りに旋回。3つ目の引数100は旋回率(右のモータと左のモータの回転角度が100%異なる)
 #define turn_left0 OnRL(SPEED2,0);	//左折
 #define turn_right0 OnRL(0,SPEED2);	//右折
 #define turn_right1 OnFwdSync(BOTH,SPEED2,-100);	//半時計回りに旋回
 #define u_turnCW OnRL(-SPEED1,SPEED1);	//時計回りに旋回。音波探知用にスピードを落としている。
 #define u_turnRCW OnRL(SPEED1,-SPEED1);	//半時計回りに旋回
 #define THRESHOLD 45
 #define RANGE1 7	//ライントレース中に缶をつかむ動作に移行するための距離
 #define RANGE2 19.8	//アームを伸ばした時の缶までの距離
 #define RANGE3 23	//回転しながら缶を検知するときの距離
 #define RANGE4 5	//前方に感知した缶をバンパーのホルダーに押しこむのに必要な距離
 #define STEP 1
 #define SR SENSOR_2
 #define SL SENSOR_1
 #define SS SensorUS(S3)
 #define CONN 1
 #define arm_down "arm1.rxe"	//アームを開き、つかむ準備をするプログラムを定義しなおした。特にアームをダウンさせるわけではない
 #define arm_up "arm2.rxe"	//アームの降下→握り→上昇の一連の動作を定義しなおした
 #define BUILD_RAG 1000	//ゴールの交差点に止まったあと、ゴール中央を向くための回転に必要な時間。
 #define DO 523	//音程C、E、Gを指定。hiCはCの二倍で対応
 #define MI 659
 #define SO 784
 #define SIGNALON 11	//Mail受信用
 #define SIGNALOFF 12
 #define LATIO 3        //走行駆動系のギア比(24:8)。距離の算出に必要

 int nFase,angleA,angleB,msg,nCross;

 float GetAngle(float r)	//引数r[cm]だけ進むのに必要な回転角を導くマクロ。ギア比考慮済み
	{
	const float diameter=5.45;
	const float pi=3.1415;
	float ang=r/(diameter*pi)*360.0*LATIO;
	return ang;
	}

 sub find_line()	//黒線を見つけるまで前進し、左右両方のセンサが線を検知するまで角度を調整するサブルーチン
	{
	until((SL<=THRESHOLD)&&(SR<=THRESHOLD))
		{
		if((SL>THRESHOLD)&&(SR>THRESHOLD))
			{
			go_forward;
			}
		else if((SL>THRESHOLD)&&(SR<=THRESHOLD))
			{
			turn_right0;
			}
		else
			{
			turn_left0;
			}
		Wait(STEP);
		}
	}

 sub start_traceR()	//左右の光センサが線に乗った状態から右方向にライントレースを開始するためのサブルーチン
	{
	until(SL>THRESHOLD)
		{
		turn_right0;
		}
	PlaySound(SOUND_CLICK);
	Off(BOTH);
	}

 sub start_traceL()	//左右の光センサが線に乗った状態から左方向にライントレースを開始するためのサブルーチン
	{
	until(SR>THRESHOLD)
		{
		turn_left0;
		}
	PlaySound(SOUND_CLICK);
	Off(BOTH);
	}

 sub traceR()	//右側の光センサを使ってライントレースを行うモード
	{
	if(SR<THRESHOLD-4) 
		{
      		turn_right1;
		}
	else if(SR<THRESHOLD) 
		{
		turn_right0;
		}
	else if(SR<THRESHOLD+4)
		{
		go_forward;
		}
	else if(SR<THRESHOLD+7)
		{
		turn_left0;
		}
	else
		{
		turn_left1; 
		}
	}

 sub traceL()	//左側の光センサを使ってライントレースを行うモード
	{
	if(SL<THRESHOLD-4) 
		{
      		turn_left1;
		}
	else if(SL<THRESHOLD) 
		{
		turn_left0;
		}
	else if(SL<THRESHOLD+4)
		{
		go_forward;
		}
	else if(SL<THRESHOLD+7)
		{
		turn_right0;
		}
	else
		{
		turn_right1; 
		}
	}

 sub search_crossR(int MAXCross)	//右側の光センサを使ってライントレースを行いつつ、引数MAXCrossの分だけ交差点を感知するサブルーチン。交差点の判定は左右の光センサが同時に閾値を下回ったことで行う。
	{
	nCross=0;
	while(nCross<MAXCross)
		{
		until((SL<=THRESHOLD)&&(SR<=THRESHOLD))
			{
			traceR();
			Wait(STEP);
			}
		nCross++;
		PlaySound(SOUND_UP);
		until((SL>THRESHOLD)&&(SR>THRESHOLD))
			{
			go_forward;
			Wait(STEP);
			}
		}
	}

 sub search_crossL(int MAXCross)
	//同じく左側の光センサによるライントレースと同時に交差点判定を行う
	{
	nCross=0;
	while(nCross<MAXCross)
		{
		until((SL<=THRESHOLD)&&(SR<=THRESHOLD))
			{
			traceL();
			Wait(STEP);
			}
		nCross++;
		PlaySound(SOUND_UP);
		until((SL>THRESHOLD)&&(SR>THRESHOLD))
			{
			go_forward;
			Wait(STEP);
			}
		}
	}

これらの交差点判定プログラムは机上の空論に終わった。交差点以外でもカーブで2つのセンサが反応してしまうことがあるし、仮に交差点を認識してもそこから両方のセンサを白の部分へ導けなかったりした。

 sub catch()	//持っている缶を新しい缶の上に載せ、掴み、また持ち上げる一連の動作。
	{
	angleB=GetAngle(RANGE4);
	RotateMotor(BOTH,SPEED1,-angleB);	//缶をホルダーの中に押しこむために少し前進する
	PlayTone(262,400);
	//ネット上で見つけたメロディー。特に意味はない。後述出典参照
	Wait(500);
	PlayTone(294,400);
	Wait(500);
	PlayTone(300,400);
	Wait(500);
	PlayTone(294,400);
	Wait(500);
	RemoteStartProgram(CONN,arm_down);
	//スレーブにアームを開かせる
	Wait(5000);
	ReceiveRemoteNumber(MAILBOX1,true,msg);
	if(msg==SIGNALON)
		{
		PlaySound(SOUND_CLICK);
		}
	else
		{
		PlaySound(SOUND_FAST_UP);
		}
	angleA=GetAngle(RANGE2);
	RotateMotor(BOTH,SPEED2,angleA);	//アームの伸びに合わせ後退する
	RemoteStartProgram(CONN,arm_up);
	//スレーブに缶を掴ませる
	PlayTone(DO,120);Wait(150);
	//達成感のあるメロディー。一回ごとに半音ずつ上昇させたかった
	PlayTone(MI,120);Wait(150);
	PlayTone(SO,120);Wait(150);
	PlayTone(DO*2,240);Wait(300);
	PlayTone(SO,120);Wait(150);
	PlayTone(DO*2,360);Wait(450);
	Wait(10000);
	if(msg==SIGNALON)
		{
		PlaySound(SOUND_CLICK);
		}
	else
		{
		PlaySound(SOUND_FAST_UP);
		}
	RotateMotor(BOTH,SPEED2,-angleA+angleB);
	//つかむ動作の開始点まで戻る。
	}

 sub build()	//ゴールで缶を載せるだけのプログラム。最後まで使われなかった
	{
	PlayTone(262,400);
	Wait(500);
	PlayTone(294,400);
	Wait(500);
	PlayTone(524,400);
	Wait(500);
	PlayTone(294,400);
	Wait(500);
	RemoteStartProgram(CONN,arm_down);
	Wait(10000);
	go_back;
	Wait(2000);
	Off(BOTH);
	nFase++;
	}

***メインタスク [#g141e4e0]

 task main()	//スタートからゴールまでの一連の流れを記述したプログラム
	{
	SetSensorLight(S1);
	SetSensorLight(S2);
	SetSensorLowspeed(S3);
	nFase=0;	//第0段階開始(ラインに乗るまで)
	msg=SIGNALOFF;
	until(BluetoothStatus(CONN)==NO_ERR);
	find_line();
	start_traceR();
	nFase++;	//第一段階開始	(一つ目の缶をつかむまで)
	while(SS>RANGE1)
		{
		traceR();
		Wait(STEP);
		}
	Off(BOTH);
	catch();
	nFase++;	//第二段階開始(2つ目の缶をつかむまで)
	while(nFase<3)
		{
		until(SS<RANGE3)	//缶を感知するまで回転し続ける
			{
			u_turnRCW;
			Wait(STEP);
			}
		while(SS>RANGE1)	//感知したらホルダーに格納されるまで前進
			{
			go_forward;
			Wait(STEP);
			}
		Off(BOTH);
		catch();
		nFase++;
		}
	while(nFase<4)	//第三段階開始(3つ目の缶を格納するまで)
		{
		until(SS<RANGE3+3)
			{
			u_turnRCW;
			Wait(STEP);
			}
		u_turnCW;
		Wait(200);
		Off(BOTH);
		while(SS>RANGE1)
			{
			go_forward;
			Wait(STEP);
			}
		Off(BOTH);	//3つ目の缶は掴まず、ホルダーで抱えたまま進み、ゴールで缶を2つ載せる
		nFase++;	//第四段階開始(缶をゴールに運び、塔を建てるまで)
		}
	until((SR<THRESHOLD)||(SL<THRESHOLD))	//反転し、ライントレースに戻る
		{
		u_turnRCW;
		}
	find_line();
	start_traceL();
	search_crossL(2);	//2つ目の交差点を過ぎたところで停止、缶を下ろす
	Off(BOTH);
	build();
	go_back;Wait(3000);
	u_turnRCW;Wait(BUILD_RAG);
	}

コース上の缶に番号をつけ、その順番でプログラムが進むよう設計した。具体的には、変数nFaseを設定し、缶を掴む動作が繰り返されるたびにnFaseに1を足していきフェイズが進行する仕組みである。
当初は4つの缶を積み上げる計画だったが、時間の都合で最終的に3つの缶を積み上げた塔を建てることを目指した。そういう意図でプログラムはできているが、一度も最後まで行けたことはない。理由として、缶から缶までの移動を周囲の影響を受けやすい超音波センサを用いたこと、ライントレースの制度が低くさらに交差点の判定がうまく機能しなかったことが挙げられる。いずれにせよプログラムの練り上げに圧倒的に時間が足りなかったことは否定しようのない事実である。

**スレーブ側のプログラム [#p1ac4f5e]

***アームを開く動作 [#e073615d]

arm1.rxe
 #define SIGNALON 11
 #define SIGNALOFF 12
 #define CONN 0
 task main()
	{
	until(BluetoothStatus(CONN)==NO_ERR);
	RotateMotor(OUT_A,20,-48);
	Wait(1000);
	PlaySound(SOUND_FAST_UP);
	Wait(2000);
	SendResponseNumber(MAILBOX1,SIGNALON);
	}

***アームの下降、握り、上昇の動作 [#z0ecb11d]

arm2.rxe
 #define SIGNALON 11
 #define SIGNALOFF 12
 #define CONN 0
 task main()
	{
	until(BluetoothStatus(CONN)==NO_ERR);
	RotateMotorEx(OUT_BC,15,450,0,true,true);
	Wait(1000);
	RotateMotor(OUT_A,30,48);
	Wait(1000);
	PlaySound(SOUND_FAST_UP);
	Wait(1000);
	RotateMotorEx(OUT_BC,40,-450,0,true,true);
	Wait(1000);
	SendResponseNumber(MAILBOX1,SIGNALON);
	}

アームの開閉角度48度と昇降角度450度の調整には最後まで時間を使ったところでもある。

**プログラム全体を通して反省 [#l8d912fd]

- 先述した通り、圧倒的に練り上げが足りなかった。原因として考えられるのは、無駄に複雑なプログラムを組もうとして一度に大量の書き換えを行い、閾値の調整や変数の設定などの基本的なことを怠ったことだ。
- 2つの光センサによる交差点の判定を試みたが、全くうまく行かなかった。理由は先述したとおりだ。今となっては前半のような、単一の光センサでも変数のカウントによって交差点を判別できるプログラムを構築したほうが信頼性が高いと思える。ハードに頼ってソフト開発を怠った典型だ。


*参考にした資料・ウェブサイト [#nf5e3cfd]

- SIGE-Lab 第7章 音楽を作る(音楽データはここから)
- 講義資料
- 『NXC を使った LEGO の NXT ロボットのプログラミング』, Daniele Benedettelli



トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS