交差点C, E, F, C', E', F'上に空き缶が設置されている。C, E, Fの空き缶にはボールが乗っており、C, E, F上のボールを運び、C', E', F'の空き缶の上に乗せるロボットを作成せよ。
私たちの班は、一度にすべてのボールを回収した後、順番に指定された空き缶の上に置いていくことにした。ボールを回収する順番、置く順番は図に示した番号の通りである。また、道順はオレンジの矢印の通りである。
私たちの班は、上での説明のように、一度にボールを回収、設置できるような構造のロボットを作成した。まず、赤外線センサーで、ボールにある程度の距離(約10cm)まで接近すると、赤矢印のようにバーを下ろし、缶をロボットに引き寄せて固定する。その後、ボールを回収する機構を回転させることでボールを回収する。この機構は、ボールを置くこともでき、黄矢印のように回転させると回収、紫矢印のように回転させることで置くことができるようになっている。
今回のロボットはNXT本体二つを一つのロボットに組み込んだ一体型である。マスター側では主にライントレースや缶との距離を計るといった役割がある。スレーブ側ではマスター側から送られてきた番号の通りの動作(ボールを置く・放す)をする。一体型にしたことによって、たくさんのモーターやセンサーを接続できるようになり、役割を分担させプログラミングしやすくなるといったメリットがある。
画像のように光センサーを二つ使い、ライントレースをしやすくした。1つでライントレースするときよりも、プログラミングがしやすくなり、精度が上がった。
しかし、一体型にしたことで、ロボットのバランスが不安定になったというデメリットもある。これは、本体二つを取り付ける位置を工夫することによって若干安定はしたが、依然として重いことには変わりが無いため、キャスターをつけると進路がぶれることが多々あった。そこで、キャスターではなく、ブロックによる支えを使った。
プログラム作成は相方が行ったので、詳しい説明ができないところもあるが、理解した部分の説明をする。
マスター側の役割は、ライントレース・缶への接近・スレーブ側へ信号を送ることである。
//master program //left sensor:S1 motor:C //right sensor:S2 motor:B #define CONN 1 //1のスレーブNXTと通信(マスターは0) #define grayS1 50 //左ライトセンサの基準 #define grayS2 45 //右ライトセンサの基準 #define blackS1 45 //左ライトセンサの基準 #define blackS2 40 //右ライトセンサの基準 int fast = 50; //モータのスピード(直線) int slow = 30; //モータのスピード(曲線)」
マスター側の定義部分である。光センサー二つの値をそれぞれ二段階に分けた。二つのセンサーの値には若干の誤差があったため、プログラムで修正した。ライントレースのスピードは、直線と曲線で変えて定義している。
void lineFollow(int breaksensor) //S1:0, S2:1, S1+S2:2, S4:3 { SetSensorLight(S1); SetSensorLight(S2); SetSensorLowspeed(S4); int lightL, lightR, dist = 100 while(true){ lightL = SENSOR_1; //左ライトセンサの値 lightR = SENSOR_2; //右ライトセンサの値 if((lightL>grayS1) && (lightR>grayS2)){ OnFwdSync(OUT_BC, fast, 0); //直線 }else if((lightL>grayS1) && (lightR<grayS2)){ OnFwdSync(OUT_BC, slow, 50); //右へ }else if((lightL<grayS1) && (lightR>grayS2)){ OnFwdSync(OUT_BC, slow, -50); //左へ } if((breaksensor == 0) && (lightL<blackS1)) break; //左線の交差点判断 if((breaksensor == 1) && (lightR<blackS2)) break; //右線の交差点判断 if((breaksensor == 2) && (lightL<grayS1) && (lightR<grayS2)) break; //左右線の交差点判断 if((breaksensor == 3) && (SensorUS(S4)<10)) break; //空き缶判断 } Off(OUT_BC); }
ライントレースをする関数である。この関数で、缶への接近も行う。breaksensorの値でどの種類の交差点を判断するか設定する。0の場合は左側の交差点、1は右側、2は十字路の判定を行う。また、4の場合は空き缶が約10cmの位置に来るまで接近する。
void turnLeft(int deg){ SetSensorLight(S1); SetSensorLight(S2); RotateMotorEx(OUT_BC, fast, -deg, 0, true, true); RotateMotor(OUT_B, fast, 400); until((SENSOR_1>grayS1) && (SENSOR_2>grayS2)){ OnFwd(OUT_B, fast); } Off(OUT_BC); } void turnRight(int deg){ SetSensorLight(S1); SetSensorLight(S2); RotateMotorEx(OUT_BC, fast, -deg, 0, true, true); RotateMotor(OUT_C, fast, 400); until((SENSOR_1>grayS1) && (SENSOR_2>grayS2)){ OnFwd(OUT_C, fast); } Off(OUT_BC); }
引数degの値だけ後退し、右左折をする関数である。
void sendMessage(int number){ int val = 0; SendRemoteNumber(CONN, MAILBOX1, number); until(val == number){ ReceiveRemoteNumber(MAILBOX2, true, val); } }
この関数を用いて、スレーブ側へ信号を送る。11でアームダウン、12でアームアップ、1〜6までの数字でボールの回収、設置を行う。また、スレーブ側から信号が送り返されてくるまで待つ。
task main(){ SetSensorLight(S1); SetSensorLight(S2); int val = 0; until(BluetoothStatus(CONN) == NO_ERR); //スレーブと接続できるまで待つ RemoteStartProgram(CONN, "lineFollow_s.rxe"); //スレーブプログラム開始 //ボール1回収 RotateMotorEx(OUT_BC, fast, 180, 0, true, true); lineFollow(3); sendMessage(11); RotateMotorEx(OUT_BC, fast, 400, 0, true, true); sendMessage(1); sendMessage(12); //ボール2回収 RotateMotorEx(OUT_BC, fast, -500, 0, true, true); lineFollow(1); turnRight(40); lineFollow(2); RotateMotorEx(OUT_BC, fast, 70, 0, true, true); lineFollow(3); sendMessage(11); sendMessage(2); sendMessage(12); //ボール3回収 turnLeft(270); lineFollow(2); turnRight(30); lineFollow(3); RotateMotorEx(OUT_BC, fast, -150, 0, true, true); OnFwdSync(OUT_BC, fast, 0); lineFollow(3) ; sendMessage(11); sendMessage(3); sendMessage(12); //ボール3置く turnRight(270); lineFollow(2); RotateMotorEx(OUT_BC, fast, 70, 0, true, true); lineFollow(2); turnLeft(40); lineFollow(1); turnRight(40); lineFollow(2); RotateMotorEx(OUT_BC, fast, 70, 0, true, true); lineFollow(2); turnRight(40); lineFollow(3); RotateMotorEx(OUT_BC, fast, -150, 0, true, true); OnFwdSync(OUT_BC, fast, 0); lineFollow(3); sendMessage(11); sendMessage(4); sendMessage(12); //ボール2置く turnRight(290); lineFollow(2); turnLeft(50); lineFollow(3); RotateMotorEx(OUT_BC, fast, -150, 0, true, true); OnFwdSync(OUT_BC, fast, 0); lineFollow(3); sendMessage(11); sendMessage(5); sendMessage(12); //ボール1置く RotateMotor(OUT_C, fast, -400); RotateMotor(OUT_B, fast, 400); until((SENSOR_1>grayS1) && (SENSOR_2>grayS2)){ OnFwd(OUT_B, fast); } Off(OUT_BC); lineFollow(3); RotateMotorEx(OUT_BC, fast, -200, 0, true, true); OnFwdSync(OUT_BC, fast, 0); lineFollow(3); sendMessage(11); sendMessage(6); sendMessage(12); RotateMotorEx(OUT_BC, fast, -100, 0, true, true); SendRemoteNumber(CONN, MAILBOX1, 21); }
main関数では、一番上の図のような順番でボールの回収、設置を行う。ロボットの進む道も図の通りである。
スレーブ側では、バーの操作やボールの回収、設置を行う。
//slave program #define CONN 0 #define barDown RotateMotor(OUT_C, 100, 500) //固定棒下げる #define barUp RotateMotor(OUT_C, 100, -500) //固定棒上げる
void ball(int count = 0){ if(count == 1) RotateMotor(OUT_B, 40, 430); //ボール回収 if(count == 2) RotateMotor(OUT_B, 40, 330); //ボール回収 if(count == 3) RotateMotor(OUT_B, 40, 460); //ボール回収 if(count == 4) RotateMotor(OUT_B, 40, -460); //ボール放す if(count == 5) RotateMotor(OUT_B, 40, -330); //ボール放す if(count == 6) RotateMotor(OUT_B, 40, -430); //ボール放す }
1〜6の数字でボールの回収、設置を行う関数である。1〜3までは回収、4〜6までが設置であり、回収の際、回収する機構の回転に微調整が必要なため数値をボールごとに変えてある。
task main(){ int val=0, oldval; while(true){ ReceiveRemoteNumber(MAILBOX1, true, val); if(val != oldval){ if((val>0) && (val<10)){ ball(val); }else if(val == 11){ barDown; }else if(val == 12){ barUp; }else if(val == 21){ break; } SendResponseNumber(MAILBOX2, val); } oldval = val; } }
スレーブ側では、処理が終わった際に処理の終了を知らせる信号をマスター側へ返す。マスター側はこの信号が送られてくるまではマスター側の動作をしない。また、21の信号が送られてくれば、whileループから抜け、プログラムが終了する。
本番ではボールの回収は行えたが、設置することはできなかった。リハーサルでは完璧に動作していたが、後ろの支えによる摩擦力などによって、思い通りに動作しなかったと考えられる。
今回の課題3は、非常に惜しい結果となった。少しの段差や摩擦でロボットの進路が変わったり、タイヤが地面から浮いてしまったりなど、環境による影響で、ロボットの動作に非常に大きなむらがあった。改善するとすれば、後輪をつけるか、ロボットの重心を中心部分に持って行くといったものが挙げられるだろう。いずれにしても、ロボットへのアプローチがもう少しあれば完璧に成功していたと思う。
三回の課題を通して、思い通りのロボット、プログラムを作成することは難しいということが分かった。特にオリジナルロボットの作成は思ったようにできても、プログラムするときに実際のロボットとかみ合わずに動作しないことが多々あった。それは、ロボットの重さや今回のような摩擦力などの物理現象による影響を考慮できていなかったということが考えられる。これらのことから、先生も言っていたように、どのような場面でも同じレベルの動作が保証されているようなロボットの作成が肝心であると思った。ハードウェアとソフトウェアの両方について考える機会はあまり無く、このゼミは自分にとって本当にいい勉強になったと思う。今後、組み込みシステムについて学ぶときには、これらの経験を活かしていきたい。