目次
ピンポン玉または青と赤のボールを運搬して、所定の容器の中に入れる。
詳細はこちら。
とにかく安定性を重視した。移動はほぼすべてライントレースで行うため、貼り合わせたフィールドの境界線に線を引いた。ボールを持つ機構はアーム式とし、そのためにはモーターが最低4つ必要であったため、NXTを2台組み合わせた一体型とした。移動関連はマスターが、アーム関連はスレーブが担う。
↑側面。NXTが対称に配置されており、見えているNXTがスレーブ、反対側がマスターである。マスターにはタイヤのモーターと光センサー、スレーブにはアーム関連のモーターがつながっている。
↑斜め上。NXT本体やモーターのような大きなパーツをなるべく少ないパーツで組み合わせることで、頑丈になる。
↑背面。こちら側はキャスターが支えている。配線をしまう空間をあけて、見た目にも配慮した。(この画像のみアームの構造が旧型である。)
↑アームを取り払った前面。デフォルトのようにタイヤを回すモーターを寝かせると、重心の関係でアームの配置が難しくなった。そこで課題1のようにモーターを立てることで、重いNXT本体をアームの反対側に低く配置することができ、重心の問題を解決した。
↑底面。毎度のごとくタイヤにギアを組み込んでいる。今回は光センサーを2つ使用しており、センサー間は線の幅より少しだけ大きい。タイヤの軸とセンサーの距離は課題2と同様。
↑真ん中に挟まれたモーターはアームの上げ下げを担う。ここもギアを使用。
↑このようにアームが上下する。アームはそれなりの重量があり自然に落ちてきてしまうため、プログラムでカバーすることにした(後述)。アームの一部になっているモーターは、玉をつかむ部分の開閉を担う。
↑玉を2つ同時につかめる大きさとすることで、容器内の玉を効率よく運べる。画像の下の部分は、玉を持ち上げたときに落とさないようにするストッパーである。ギアで回転方向を変えるのが難しかった。
#define KOSATEN 37 //37以下は黒 #define completed 15 #define turn_left OnRev(OUT_A,70);OnFwd(OUT_B,70); //左に回転する #define go_left OnRev(OUT_A,85);Off(OUT_B); //左に進む #define go_straight OnRev(OUT_AB,70); //まっすぐ進む #define go_right OnRev(OUT_B,85);Off(OUT_A); //右に進む #define turn_right OnRev(OUT_B,100);OnFwd(OUT_A,100); //右に回転する #define short_break Off(OUT_AB);Wait(1000); #define brake Off(OUT_AB)
completed は通信に使う(後述)。 brake は「ブレーキ」を意味するが、break として捉えてほしい。
光センサーを2つ取り付けており、課題2のプログラムを流用できないため、新たに作り直した。左右の値の差を求め、差が大きければ曲がり、0に近ければ直進する仕組みとなっている。以下の画像では、赤い丸が光センサー、数字が光センサーの値、青い四角がタイヤを示している。
↑このとき、左右の値の差は0のため直進する。
↑軌道からずれてこのようになると、差が大きくなり軌道を修正する。
↑このように両側で黒と認識すると交差点と判断する。
↑このとき、まず右側が黒と認識するため、右に進む。
↑やがて左側も黒と認識するため、交差点と判断する。
sub follow_line() { while((SENSOR_1>KOSATEN)||(SENSOR_2>KOSATEN)){ int d=SENSOR_1-SENSOR_2; //センサー1が右側、センサー2が左側である if (d<-6){ turn_right; //右より左のほうが大きい(左が白、右が黒) }else if (d<-3){ go_right; //右より左のほうが少し大きい }else if (d<=3){ go_straight; //ほぼ同じ(どちらも白) }else if (d<=6){ go_left; //左より右のほうが少し大きい }else { turn_left; //左より右のほうが大きい(左が黒、右が白) } } PlaySound(SOUND_CLICK); brake;Wait(200); }while の条件について、「センサー1が黒でない」または「センサー2が黒でない」となっているが、これはド・モルガンの法則によるもので、『「センサー1が黒でない」または「センサー2が黒でない」』でなくなったとき、すなわち『「センサー1が黒である」かつ「センサー2が黒である」』となったときに while の外に出るようになっている。
sub follow_line2(int followtime) { long t0=CurrentTick(); while(CurrentTick()-t0<followtime){ int d=SENSOR_1-SENSOR_2; if (d<-6){ turn_right; }else if (d<-3){ go_right; }else if (d<=3){ go_straight; }else if (d<=6){ go_left; }else { turn_left; } } PlaySound(SOUND_CLICK); brake;Wait(200); }
ライントレースの性質上、T字路の行き止まりでない方向から進入すると分岐している方向に向いてしまうため、直進のプログラムは2通り作った。
sub chokusinL() //交差点を直進(右を向いているとき) { go_left;Wait(500); }
sub chokusinR() //交差点を直進(左を向いているとき) { go_right;Wait(500); }
sub sasetsu() //交差点を左折 { turn_left;Wait(500); go_left;Wait(500); }
sub usetsu(int migi) //交差点を右折 { turn_right;Wait(500); go_right;Wait(migi); }
sub turn_behind() //振り向く { turn_left;Wait(1000); while(SENSOR_1>KOSATEN){ turn_left; } //(線を認識するまで回転) Off(OUT_BC); }
sub backleft() //後退し右を向く { OnFwd(OUT_A,100);Off(OUT_B);Wait(1500); turn_right;Wait(600); Off(OUT_AB); }
sub backright() //後退し左を向く { OnFwd(OUT_B,100);Off(OUT_A);Wait(1400); turn_left;Wait(500); Off(OUT_AB); }
アーム関連はスレーブで制御するため、メッセージで指示を出し、completed と完了の合図が来るまで待つような仕組みとなっている。
sub ball_catch() //玉をつかむ { int msg; SendRemoteNumber(1,MAILBOX1,12); while(msg==completed){ ReceiveRemoteNumber(MAILBOX1,true,msg); } }
sub ball_release() //玉を放す { int msg; SendRemoteNumber(1,MAILBOX1,11); while(msg==completed){ ReceiveRemoteNumber(MAILBOX1,true,msg); } }
sub arm_up() //アームを上げる { int msg; SendRemoteNumber(1,MAILBOX1,13); while(msg==completed){ ReceiveRemoteNumber(MAILBOX1,true,msg); } }
sub arm_down() //アームを下げる { int msg; SendRemoteNumber(1,MAILBOX1,14); while(msg==completed){ ReceiveRemoteNumber(MAILBOX1,true,msg); } }
sub up_catch() //アームを上げながら閉じる { int msg; SendRemoteNumber(1,MAILBOX1,16); while(msg==completed){ ReceiveRemoteNumber(MAILBOX1,true,msg); } }
sub down_release() //アームを下げながら開く { int msg; SendRemoteNumber(1,MAILBOX1,17); while(msg==completed){ ReceiveRemoteNumber(MAILBOX1,true,msg); } }
移動の流れを下の画像に示す。この順番にしたがってサブルーチン等を組み合わせる。
task main() { SetSensorLight(S1); //右光センサー SetSensorLight(S2); //左光センサー arm_up(); short_break; ball_release(); go_straight; Wait(2000); //A→M follow_line(); //M→K chokusinR(); follow_line2(1500); short_break; //K→L arm_down(); short_break; ball_catch(); short_break; arm_up(); short_break; //玉をつかむ turn_behind(); follow_line(); //L→K chokusinL(); follow_line(); //K→M usetsu(500); follow_line(); sasetsu(); follow_line(); //M→B chokusinL(); follow_line(); //B→X usetsu(500); follow_line(); //X→X' sasetsu(); follow_line(); //X'→B' chokusinR(); follow_line(); //B'→M' sasetsu(); follow_line(); //M'→K' sasetsu(); follow_line2(1000); short_break; //K'→J' ball_release(); short_break; //玉を放す backleft(); //J'→K' follow_line2(3700); short_break; //K'→L' arm_down(); short_break; ball_catch(); short_break; arm_up(); short_break; //玉をつかむ turn_behind(); OnFwd(OUT_AB,30); Wait(100); follow_line(); //L'→K' usetsu(500); follow_line2(500); short_break; //K'→J' ball_release(); short_break; //玉を放す backright(); follow_line(); //J'→K' chokusinL(); follow_line(); //K'→M' usetsu(500); follow_line(); //M'→B' chokusinL(); follow_line(); //B'→X' usetsu(500); follow_line(); //X'→X sasetsu(); follow_line(); //X→B sasetsu(); follow_line(); //B→C usetsu(400); follow_line2(1650); short_break; //C→I arm_down(); short_break; ball_catch(); short_break; up_catch(); short_break; //玉をつかむ backleft(); //I→C follow_line(); //C→B usetsu(500); follow_line(); //B→X usetsu(500); follow_line(); //X→X' sasetsu(); follow_line(); //X'→B' sasetsu(); follow_line(); //B'→C' usetsu(700); follow_line2(900); short_break; //C'→I' down_release(); short_break; //玉を放す arm_up(); short_break; backright(); //I'→C' }
通信してアームを動かすだけなので、シンプルである。
すべてマスターと対応している。
#define ball_release 11 #define ball_catch 12 #define arm_up 13 #define arm_down 14 #define completed 15 #define up_catch 16 #define down_release 17
マスターの指示に従って動作し、完了したら completed とメッセージを送る。
task main() { int msg; while(true){ ReceiveRemoteNumber(MAILBOX1,true,msg); if(msg==ball_release){ OnFwd(OUT_A,50);Wait(500);Off(OUT_A);Wait(500); SendResponseNumber(MAILBOX1,completed); } //玉をつかむ if(msg==ball_catch){ OnRev(OUT_A,50);Wait(600);Off(OUT_A);Wait(500); SendResponseNumber(MAILBOX1,completed); } //玉を放す if(msg==arm_up){ OnFwd(OUT_B,50);Wait(600); OnFwd(OUT_B,5);Wait(500); SendResponseNumber(MAILBOX1,completed); } //アームを上げる if(msg==arm_down){ OnRev(OUT_B,30);Wait(600); OnFwd(OUT_B,5);Wait(500); SendResponseNumber(MAILBOX1,completed); } //アームを下げる if(msg==up_catch){ OnFwd(OUT_B,40);OnRev(OUT_A,15);Wait(300); SendResponseNumber(MAILBOX1,completed); } //アームを上げながら閉じる if(msg==down_release){ OnRev(OUT_B,10);OnFwd(OUT_A,20);Wait(150); SendResponseNumber(MAILBOX1,completed); } //アームを下げながら開く Wait(100); msg=0; } }
アームが重力で落ちてくるのを防ぐため、アームの上げ下げのプログラムは Off(OUT_B)から OnFwd(OUT_B,5) に置き換えている。すると、常にアームに上向きの力が働き、重力とつり合う。
アーム式というありきたりな仕組みではあるが、安定性を突き詰めたおかげで容器外の玉2つを確実に運ぶことができた。容器内の玉もあと少し調整を加えれば運べただろう。3人チームでもそれぞれ役割分担をして完成に近づけたことに大変達成感を感じている。