黒線で書かれた道を指示通りにライントレースし、紙コップを指定の位置から取り、別の場所に置くことができるロボットを作成すること。下はコース全体の図(これを図αとする)
私は第ニコースに決まったため、次のようなコースでライントレースするロボットを作った。
Dスタート→Cを直進→Bを左折→Pを左折(一時停止)→Qを直進→Rを左折→Fを左折
→Sを直進(一時停止)→Y地点の紙コップを取得してコースに戻る→Sを直進(一時停止)
→Qを左折(一時停止)→Rを直進→X地点に紙コップを置いてコースに戻る→Pを左折
→B を左折(一時停止)→A地点へ(ゴール)
完成したロボットは付属の説明書に書いてあるライントレーサーとだいたいの形は同じだ。下がロボット全体を写した写真。前輪として左右にタイヤがあり、後輪として後方にキャスターがある。ロボットの上にあるのはカップをホールドするための機構。
モーターは全部で3つ使った。2つは移動のための左右のタイヤの駆動に使った。もう1つはカップを取ったり置いたりする機構に使った。
はじめは付属の説明書に書いてあるライントレーサーをそのまま作った。このロボットはコースのほとんどの部分を正しくライントレースすることができた。しかし、E-F間(図α参照)の急カーブで内側をライントレースするときに、曲がり切ることができず、コースアウトしてしまう課題があった。このE-F間では黒線の右をライントレースしても左をライントレースしても一回は急カーブの内側をライントレースしなければならない。コースアウトする原因は、センサーと前輪の位置関係にあった。ロボットはセンサーが前輪より前に出ている構造になっているが、この距離が長すぎるため車体の向きが変わりにくくなっていたことが原因だった。パートナーがこれを改造し、センサーを前輪により近い位置に付けてくれたおかげでこのカーブを曲がることができるようになった。
途中の段階で、センサーの紙面からの高さが低いためにロボットが誤作動をするようになっていた。センサーが紙面に近いとその凹凸を読み取ってしまうことが原因だったと思う。その後、センサーを数cm高い位置に付け直したことで正確なライントレースができるようになった。高すぎると読み取りが粗くなるので、センサーの高さは正確なライントレースのためには重要な要素になる。下の写真は完成時のセンサーの高さを示すために撮ったもの。
下の写真のようにカップをホールドする装置が動く。(上がホールドする前、下がホールド中。)
私が担当した第二コースは黒線の左側をトレースするとロボットが交差点を見つけやすかった。そのため、ロボットには終始黒線の左側をトレースさせた。
黒線の周りの明るさを測ると完全に白い場所がだいたい58〜60、黒線の中央付近がだいたい27〜30くらいだった。この明るさの範囲を7等分し、7つのパターンのプログラムを使い分けるという方法でライントレースさせた。下のように動くようなプログラムにした。
(明るさの条件;ロボットの動作)
閾値+10より大きい;右に旋回
閾値+6より大きい ;右に曲がる
閾値より大きい ;少しだけ(*)右に曲がる
閾値 ;直進
閾値-5より大きい ;少しだけ(*)左に曲がる
閾値-10より大きい;左に曲がる
閾値-10以下 ;左に旋回
*直進と旋回以外のときはその場の明るさと閾値との差を取って、比例制御となるようにしてある。上で「少しだけ」と表記したところは比例の係数を変えて速度を調節した。
閾値-10以下の値を観測する時間が一定値を超えた時に交差点と判断して止まるようにした。止まるのに時間が少し必要なため車体が左方向に曲がる。例えば図αでCからBへ来た時に交差点と判別して止まると車体が若干Pの方向に向いた状態で止まる。また、センサーは黒線の中央付近で止まる。
このため、交差点を直進するときは判別用のプログラムの後に、少しだけ右に曲がりつつ前進し、黒線を超えてからライントレースさせる必要がある。(それを行うのが「syusei」という名前のサブルーチン。)
交差点を左折(このコースでは右折しない)するときは単純に明るくなるまで左に旋回し続け、黒線の左側に移動させるプログラム(「escape」という名前のサブルーチン)を使った。
sub syusei() //(交差点を認識した後で)少し右に曲がりつつ前進して黒線を超える →ライントレースできる { go_forward; //黒線を超える Wait(150); while(sensor<th){ //次の黒線まで右旋回し、車体の向きを整える r_turn; } PlayTone(800,50); Wait(100); short_break2; } sub escape() //黒部分から白部分に移動する(交差点を認識し、左折するときのため) { short_break2; while(sensor<th+7){ //白くなるまで左旋回 l_rotation2; } PlayTone(800,50); Wait(100); short_break2; }
次のようなときに道の途中で交差点と判断して止まることがあった。
1.急カーブの内側をライントレースしているとき
2.交差点を左折した直後(特に車体の向きが完全に道と平行になるまでの間)
これを解決するために交差点だと判断する条件を次のように厳しくした(厳密にはライントレースを続けられる条件を広げた)
一定時間、閾値-10以下の値を観測する
↓
一定時間、閾値-10以下の値を観測する 「且つ」 ライントレースし始めてからある一定時間が経っている
こうすることで、次の交差点にたどり着くまでのだいたいの時間さえ分かっていれば、実際の交差点より手前で止まってしまうことを防げる。ここまで記述してきたことを組み合わせて、ライントレースを担うサブルーチンを書いた。(マクロなどの定義は項目「メインプログラムなど」に掲載)
sub trace(long t,int f,int x) //ライントレース ・fの時間、閾値-10以下の値を認識し続けると止まる ・開始後t秒間は黒をfの時間認識し続けても止まらない //ある交差点で上手く交差点判別ができなかった(黒色の濃さの微妙な違いが原因だと思う)ためxで明るさの条件を調節できるようにした。 { SetSensorLight(S2); long t0=CurrentTick(); long t1=CurrentTick(); while((CurrentTick()-t0<f)||(CurrentTick()-t1<t*1000)){ //ライントレースを続けるための条件なので「又は」で繋いだ //↑交差点を誤認しないための仕組み if(sensor>th+10){ //明るい、閾値+10より大 r_rotation; t0=CurrentTick(); }else if(sensor>th+6){ //少し明るい、閾値+6より大 OnFwd(OUT_A,A_speed-(sensor-th)*4); OnFwd(OUT_B,B_speed-(sensor-th)); t0=CurrentTick(); }else if(sensor>th){ //ほんの少し明るい、閾値より大 OnFwd(OUT_A,A_speed-(sensor-th)*2); OnFwd(OUT_B,B_speed-(sensor-th)); t0=CurrentTick(); }else if(sensor==th){ //明るさが閾値と等しいとき go_forward; t0=CurrentTick(); }else if(sensor>th-5+x){ //ほんの少し暗い、閾値-5より大 OnFwd(OUT_A,A_speed-(th-sensor)); OnFwd(OUT_B,B_speed-(th-sensor)*2); t0=CurrentTick(); }else if(sensor>th-10+x){ //少し暗い、閾値-10より大 OnFwd(OUT_A,A_speed-(th-sensor)); OnFwd(OUT_B,B_speed-(th-sensor)*4); t0=CurrentTick(); }else { //暗い、閾値-10以下 l_rotation } } PlayTone(200,200); }
右タイヤの駆動モーターをAに、左タイヤの駆動モーターをBに接続した。カップを取る機構のモーターはCに接続した。
#define B_speed 45 //Bの速度の基準 #define A_speed 43 //Aの速度の基準 #define short_break Off(OUT_AB);Wait(step); //一時停止 #define short_break2 Off(OUT_AB);Wait(100); //一時停止(2) #define th 44 //閾値 #define step 1000 //ストップ時間 #define r_turn OnFwd(OUT_B,B_speed-10);Off(OUT_A); //右折 #define l_turn OnFwd(OUT_A,A_speed-10);Off(OUT_B); //左折 #define r_rotation OnFwd(OUT_A,-30);OnFwd(OUT_B,30); //その場で右回転 #define l_rotation OnFwd(OUT_A,30);OnFwd(OUT_B,-30); //その場で左回転 #define l_rotation2 OnFwd(OUT_A,25);OnFwd(OUT_B,-30);//その場で左回転(ゆっくり動く) #define go_forward OnFwd(OUT_B,30);OnFwd(OUT_A,30); //直進 #define hantei 200 //交差点と判断するまでの時間(直角用) #define hanteicurve 140 //交差点と判断するまでの時間(カーブ用) #define sensor SENSOR_2
sub syuseicurve() //交差点を認識したときに直進するためのプログラム(カーブの途中にある交差点用) //カーブ途中の交差点ではsyuseiサブルーチンだと上手くいかなかったので、少し別の動きを加えてある { syusei(); //センサーを白の位置まで出す(前進+右に曲がる) while(sensor>th){ r_rotation; } PlayTone(800,50); short_break2; } sub capture() //カップをホールドする(カップの方向に車体を向けるプログラムなどもここに入れてある。) { r_rotation; Wait(750); Off(OUT_AB); go_forward; Wait(200); Off(OUT_AB); OnFwd(OUT_C,-40); Wait(1800); Off(OUT_C); l_rotation; Wait(800); Off(OUT_AB); } sub release() //カップを放す(車体の向きを変えるプログラムも入れてある。) { r_rotation; Wait(1500); Off(OUT_AB); OnFwd(OUT_C,40); Wait(1600); Off(OUT_C); l_rotation; Wait(1600); Off(OUT_AB); }
↓コースを5つに分けてサブルーチンにした。
sub DCBP() { trace(0,hantei,0); //DC syusei(); trace(0,hantei,0); //CB escape(); trace(1,hantei,0); //BP 左折 short_break; escape(); } sub PQR() { trace(1.5,hanteicurve,0); //PQ syuseicurve(); trace(0,hanteicurve,0); //QR escape(); } sub REFS() { trace(1,hantei,0); //RE escape(); trace(12,hantei,0); //EF escape(); trace(1,hantei,0); //FS short_break; syusei(); } sub YSQR() { trace(1,hantei,0); //YS short_break; syusei(); trace(0,hantei,0); //SQ short_break; escape(); trace(1,hanteicurve,0); //QR syuseicurve(); } sub XPBA() { trace(0.8,hanteicurve,4); //XP この交差点だけ黒を認識しなかったので黒の条件を甘くした escape(); trace(1,hantei,0); //PB short_break; escape(); trace(1,hantei,0); //BA short_break; r_turn; Wait(200); go_forward; Wait(2500); Off(OUT_AB); PlaySound(SOUND_UP); }
task main() { DCBP(); PQR(); REFS(); capture(); YSQR(); release(); XPBA(); }
上手くいかなかったときに闇雲にプログラムを変えて何回も失敗することがあった。
次の課題の時は原因をなるべくよく探ってから試行したい。