[[2017a/Member]] #contents *Mission2 [#x2228c16] &ref(2017a/Mission2/2017a-mission2.png,100%,); 今回は光センサーを使って上のようなコースの線上を伝って、ロボットが動くようにする課題であった。 自分の作ったロボットは、「A→E→P→Q→R→T(一周して)→H→G→S→Q→F→E→A」のように動く。 詳しくは[[Mission2:http://yakushi.shinshu-u.ac.jp/robotics/?2017a%2FMission2]]に書いてあるので参照願いたい。 *機構説明 [#jea9c75d] &ref(2017a/Member/tsukuzin/Mission2/IMG_0308.jpg,15%); &ref(2017a/Member/tsukuzin/Mission2/IMG_0310.jpg,24%); &ref(2017a/Member/tsukuzin/Mission2/setumei.gif,30%); 4つではなく3つのタイヤを使用することで旋回しやすくした。 また後輪はできるだけ摩擦を小さくするためにゴムタイヤのゴムを取りホイールのみを取り付けた。 3つ目の画像は後輪のタイヤを示している。敢えてシャフトを固定しないことで、曲がった時の摩擦が小さくなり、スムーズに進めるような機構にした。 またMission1の反省点であるバランスに関しても今回はリモコンを本体の重心に重なるように設置したため問題はなかった。光センサーは前輪のすぐ手前の真ん中に設置した。最初はもっと前のほうに設置していて、少し回転しただけで線を見失ってしまったが、画像のように設置することでしっかり線を追うようになった。 *プログラムについて [#x923757a] **使用した定義 [#l75cae2e] #define hazime() mae_susumu; Wait(60); #define mae_susumu OnFwd(OUT_AC); //前進する #define senkai_migi() OnFwd(OUT_A); OnRev(OUT_C); //大きく右に曲がる #define kaiten_migi() OnFwd(OUT_A); //小さく右に曲がる #define senkai_hidari() OnFwd(OUT_C); OnRev(OUT_A);//大きく左に曲がる #define kaiten_hidari() OnFwd(OUT_C);//小さく左に曲がる #define teisi(t) Off(OUT_AC); Wait(t); //t秒だけ停止 #define handantime 25 //交差点、曲がり角かの判断の時間 #define end() mae_susumu; Wait(60); Mission1で使用した定義をそのまま使用したかったが、ライントレースをさせる際に「mae_susumu(t)」のように関数にしてしまうとうまく走行しないと考えたため今回は関数の部分を削除した。また最初は、光センサーの白と黒の閾値を #define sakai 45 のように定義していたが、場所、時間によって光センサーの値が変化ため、たとえば sakai+5 > SENSOR_2 のような不等式で動作するとき、値を変更するのが少し面倒であった。 確かに定義したほうが見やすいが、今回は修正のしやすさを優先するため今回はこの定義を使用するのをやめた。 **サブルーチンについて [#cad2dffb] ***ライントレース部分 [#mcb1497a] sub lineI() //最初の線の内側(右側)を走る { ClearTimer(0); while(FastTimer(0)<handantime) { if(SENSOR_2 >= 47) { senkai_hidari(); ClearTimer(0); } else if (SENSOR_2 >= 45) { mae_susumu; ClearTimer(0); } else if (SENSOR_2 >= 38) { senkai_migi(); } } } sub lineO() //最初の線の外側(左側)を走る { ClearTimer(0); while(FastTimer(0)<handantime) { if(SENSOR_2 >= 47) { senkai_migi(); ClearTimer(0); } else if (SENSOR_2 >= 45) { mae_susumu; Wait(2); ClearTimer(0); } else if (SENSOR_2 >= 38) { senkai_hidari(); } } } 「lineO」を例にとってフローチャートにすると次のようになる。 &ref(2017a/Member/tsukuzin/Mission2/フローチャート2.png,100%); 始めにタイマーをリセットする。次に「while(FastTimer(0)<handantime)」によってタイマーの計測時間がhandantime(25)を超えるまでwhileの中をループし続ける。while文の中にはif文、else文で構成されていて、光センサーの値によって処理が異なるようになっている。光センサーの値が45以上の時(黒以外の時)タイマーの値は常にリセットされ、ループは継続される。しかし光センサーの値が45より小さい時(黒の時)、タイマーはリセットされない。この状態が一定の時間続くことで、交差点であると判断し、次の文へ進む構造となっている。 ***交差点、曲がり角の判断 [#o6814c61] sub kousatenQ() //交差点Qを通るときに使用する { PlaySound(SOUND_UP); ClearTimer(1); } sub kousatenR() //交差点Rを通るときに使用する { PlaySound(SOUND_UP); senkai_hidari(); Wait(70); ClearTimer(1); } sub kousatenTORS() { teisi(100); PlaySound(SOUND_DOWN); mae_susumu; Wait(50); ClearTimer(1); } sub kakuEHGF() //曲がり角EHGFを通るときに使用する { PlaySound(SOUND_UP); ClearTimer(1); } sub kousatenPM() //交差点Pを通るとき(戻る)に使用する { teisi(100); PlaySound(SOUND_UP); mae_susumu; Wait(36); ClearTimer(1); } 交差点に差し掛かったら停止したり、音を鳴らしたりする。 最初はサブルーチンが8つまでしか使えないことを忘れていて、個々の交差点に1つずつサブルーチンを作る予定だったが、同じ動作でも判断できることがわかったため、サブルーチンの名前でそれらを区別した。 本来サブルーチンに交差点を通った回数を足していく予定だったが、今回はタスクメインのほうに書くことにした。「ClearTimer(1)」についてはタスクメインの欄で説明する。 **タスクメイン [#a7090820] task main () { SetSensor(SENSOR_2, SENSOR_LIGHT); SetPower(OUT_AC,1); ClearTimer(1); //走行時間の測定 int X=0; //交差点、T字路の回数を測定 hazime(); while(X=12) while(X<12) { lineI(); if(Timer(1)>50 && X==0) { X++; kakuEHGF(); //E地点を右折 } lineI(); if(Timer(1)>30 && X==1) { X++; kousatenPI();//P地点を左折 } lineO(); if(Timer(1)>30 && X==2 ) { X++; kousatenQ();//Q地点を直進 mae_susumu; Wait(50); } lineO(); if(Timer(1)>30 && X==3 ) { X++; kousatenR(); //R地点を左折 } lineO(); if(Timer(1)>30 && X==4 ) { X++; kousatenTORS(); //T地点を直進 } lineO(); if(Timer(1)>30 && X==5 ) { X++; kousatenTORS();//T地点を直進 } lineI(); if(Timer(1)>30 && X==6 ) { X++; kakuEHGF(); //直角Hを曲がる } lineI(); if(Timer(1)>30 && X==7 ) { X++; kakuEHGF(); //直角Gを曲がる } lineI(); if(Timer(1)>30 && X==8 ) { X++; kousatenTORS(); //地点Sを左折 } lineO(); if(Timer(1)>30 && X==9 ) { X++; kousatenPM(); //地点Pを直進 } lineO(); if(Timer(1)>30 && X==10 ) { X++; kousatenQ();// 地点Qを左折 senkai_hidari(); Wait(50); } lineO(); if(Timer(1)>30 && X==11 ) { X++; kakuEHGF(); } lineO(); } end(); } 大まかにはライントレース→交差点判断→ライントレース・・・・の繰り返しである。 最初に走行時間をTimer(1)で記録、交差点と曲がり角の回数を測定するために変数Xを定義した。 条件、「Timer(1)>○○ && X==○○」を満たさなければライントレースをし続ける。変数Xを用いる前は、交差点Pを交差点Qと誤認して素通りしてしまうことがあったため、論理積をとって条件を明確にするのは重要であることが分かった。 ***タスクメインについて気が付いたこと [#la1b392c] このプログラムで動かすと稀に「lineO」の動作(線の左側を走行する)から「lineI」の動作(線の右側を走行する)に突然変わってしまうことがあった。ロボットを動かしている際には気がつかなかったが、今考えてみるとその原因が分かったので記述する。 lineI(); if(Timer(1)>30 && X==1) { X++; kousatenPI();//P地点を左折 } lineO(); この誤作動がよく発生した「P地点左折」付近のプログラムのフローチャートは1つ目の画像のようになり、 if文の条件を満たさず、偽のルート通った際、そのまま「lineI」の動作へ入ってしまうため、2つ目の画像のように変える必要があった。 &ref(2017a/Member/tsukuzin/Mission2/フローチャート3.png,100%); &ref(2017a/Member/tsukuzin/Mission2/フローチャート4.png,100%); よって正しく動作させるためには、下のようにする必要があった。 lineI(); if(Timer(1)>30 && X==1) { X++; kousatenPI();//P地点を左折 } else { lineI(); } lineO(); ロボットを実際に動かしている際は「Timer(1)>30」の30の値を調整することで発生を抑えることが出来たが、完全ではなかった。if文やwhile文などの条件文は重なると処理が複雑になるので誤作動に気が付きにくいことが分かった。この講義に限らず、フローチャートを作って動作をわかりやすくしてからプログラミングをすることを心掛けたい。 *反省 [#taf1dd25] Mission1も電圧の変化によって書かれる線が全く異なってしまったが、今回も日光や場所によって光センサーが読み取る値が変化し、思った通りの動作をしないことがよくあった。また、電圧による動作の変化もモーターの出力を変えるだけでは対応できないこともあり、結局古い電池をそのまま使用し続ければならなかったりと調整が大変だった。 プログラミングもif文とwhile文を重ねて使用するので、実際にロボットがどの動作を行っているのかを正しく把握することが出来ないことがあり、誤作動を起こしたときの対処が大変だった。 通った交差点の判断として変数「X」を使用したが、出来ればこれを各サブルーチンに書き込んで、プログラム本体を見やすくしたかった。しかしながらサブルーチン内に変数「X」を書き込むと不明のコンパイルエラーが起きてしまい、作業が進まない状況になってしまった。今回はプログラム本体に直接書き込んだが、次回は今回の力不足を反省し、できるだけシンプルなプログラムにできるよう心掛けたい。 また、動作に関しても完璧とは言えず、最初は、光センサーは濃淡を5段階に分けて認識させていたが、3段階に変えて線の認識を優先したことから動作がかなり遅くなってしまった。Mission3では速さと正確さの両立ができるよう、チーム内で試行錯誤をしたい。全体的に今回の課題では自分の力不足を痛感するとともに、センサーを用いてアナログな情報をディジタルに変換し利用することがかなり大変であることが分かった。