第二回目の課題は、ライントレース(&ボール運び)ロボット作成。課題内容はこちらを参照。
今回選択したコースはC→A。Cを出発、P・Q間のボールを確保、環状を一周、Aへ向かう、Aのゴールへシュート というルートになっている。
下の画像は、ルートを簡略に説明したgif画像。
下はロボットの全体写真。真横から撮影したものである。
前輪にモーター2つ、後輪にはキャスターを用いた。また、ボールの移動にアームを用いた。以下、詳細に説明する。
前輪のタイヤの間隔を出来る限り狭くするよう心掛けた。そのようにしたことで、私のコースではないがC直前のような急カーブでもスムーズに対応できるようになった。
また、モーターの間隔を狭くするにあたって、前後に余分な空間ができてしまったが、前方は光センサーの取り付け部分、後方はキャスターの補強部分となり、小型化する上で有用なものとなった。
光センサーは、床から高さ5mm程度に設定している。あまり距離が出来てしまうと、周囲から光の影響を受け易くなり、至近距離では明暗の差が減少してしまうと考えたからである。
また、前回同様、キャスターの高さ調節には苦労した。垂直にならなければキャスターとして機能しないからである。だが、班の中で意見を出し合えたおかげで比較的時間は掛からずに制作することが出来た。
本体とキャスターの取り付け部分に余裕をもたせているのは、方向転換の際の回転を滑らかに行わせる為である。
今回作成したアームは、モーターの回転によって上下するというシンプルな構造にしてある。
アームには、前にボールが転がらないようにするという役割を担わせる一方、進行を左右されないようにボールを持ち上げるようにした。
ピンポン玉のような軽いものでは大差はないかもしれないが、ボールで前に突っ張ってしまうような心配はなくなった。
アームが早く上下してしまうと、こちらの意図しない負荷が部品にかかってしまう恐れ(アームの上下動による部品の故障)があった。それを無くすためにもギア比調節による減速は不可欠であった。
今回用いたギア比は、
3本目の棒にアームがついている。
よって、ギア比は(1/5)*(3/5)*(3/5)=(9/125)となる。モーターが14回転すると、アームのついている棒が1回転する。つまり、モーターが4回転するとアームが約100°(アームが上下する幅)回転することになる。
プログラム全体を記載する。
#define THRESHOLD 47 //基準となるしきい値 #define HIPOWER 7 //直進時 #define LOWPOWER 4 //カーブトレース時 #define setpower_H SetPower(OUT_AC,HIPOWER); #define setpower_L SetPower(OUT_AC,LOWPOWER); #define go_forward setpower_H; OnFwd(OUT_AC); #define go_back(t) setpower_H; OnRev(OUT_AC); Wait(t); #define turn_left1 setpower_L; OnFwd(OUT_C); OnRev(OUT_A); #define turn_right1 setpower_L; OnFwd(OUT_A); OnRev(OUT_C); #define turn_left2 setpower_L; OnFwd(OUT_C); Off(OUT_A); #define turn_right2 setpower_L;OnFwd(OUT_A); Off(OUT_C); #define STEP 1 #define nMAX 7 //黒をカウントする最大値 #define short_break Off(OUT_AC); Wait(200); //交差点判断時停止 #define cross_time 30 #define armtime 50 //アームの上下時間 #define arm_up OnFwd(OUT_B); Wait(armtime); Off(OUT_B); #define arm_down OnRev(OUT_B); Wait(armtime); Off(OUT_B);
sub cross_line() { //交差点横断 OnFwd(OUT_AC); Wait(cross_time); Off(OUT_AC); } sub shoot() { //ゴールへシュート arm_up; go_forward; Wait(50); go_back(50); }
task main() { SetSensor(SENSOR_1, SENSOR_LIGHT); int lc=0; //交差点の数をカウント int nc=0; //黒に連続でなった回数をカウント while (lc < 5) { //ゴール手前交差点でループ解除 while (nc < nMAX) { if (SENSOR_1 < THRESHOLD -8) { //39未満は黒 turn_left1; //左折 nc++; } else { if (SENSOR_1 < THRESHOLD -4) { //39〜42は黒灰色 turn_left2; //左旋回 } else if (SENSOR_1 < THRESHOLD +4) { //43〜50は灰色 go_forward; //直進 } else if (SENSOR_1 < THRESHOLD +8) { //51〜54は白灰色 turn_right2; //右旋回 } else { //55以上は白 turn_right1; //右折 } nc=0; //カウンタをリセット } Wait(STEP); } short_break; //交差点時 turn_right1; Wait(nMAX + 14); cross_line(); nc=0; lc++; //通過した交差点をカウント if (lc == 2) { //2つ目の交差点での動作 arm_down; //ボールを固定 go_back(20); turn_left1; Wait(55); Off(OUT_AC); } if (lc == 3) { //3つ目の交差点での動作 go_back(20); turn_left1; Wait(40); Off(OUT_AC); } } go_back(50); //ゴール手前交差点での動作 Off(OUT_AC); shoot(); Off(OUT_AC); }
今回のプログラミングでは、3段階に分けてプログラムを組み立てていった。
最初に上記のプログラムにあるライントレースの部分だけを完成させた。ラインをトレースさせることだけで試走させ、形になったところで、交差点動作をループの中に追加させた。
スタートからゴール手前の交差点まで一連のトレースが可能になったとき、ボール確保とシュートのプログラムを実行するように組み込んだ。
連続で黒と判断された回数をカウントすることによって、交差点を判断させた。7回連続で黒となった場合は、カーブではなく交差点であるという指示である。
また、交差点横断だけでは不十分な部分を、交差点に差し掛かった回数をカウントすることによって、その交差点に見合った条件を付け加えるようにした。その交差点時動作に付随する形でアームの挙動を設定し、今回の課題(ボールをゴールへシュート)を遂行した。
スムーズなトレースをさせるために、黒と白の境界(灰色)を基準とした5段階判定(黒・黒灰色・灰色・白灰色・白)を用いた。内側に入りすぎ(黒)の時、または外側に出過ぎ(白)の時は、右左折する(直角に曲がる)ようにした。黒と灰色の中間(黒灰色)、白と灰色の中間(白灰色)は、右左旋回する(円を描くように緩やかに曲がる)ようにプログラミングを行った。
黒灰色・白灰色は共に光度差4を範囲としてあるが、灰色は光度差8を範囲としてある。直進(go_forward)の判定幅を多めにとることによって、速度の向上に繋げた。
setpowerを用いているのは、直進時とカーブ時に緩急をつけるためである。カーブ時のトレースがより正確になり、交差点付近等でのタイヤによる滑りが軽減される。ギア比での速度調整では、必然的に速度の遅い方に合わせる必要が出てくる。それでは直線時の移動速度に無駄が出来てしまう為、setpowerを用いる必要があった。また、前回の課題でsetpower3以下は好ましくないとわかっているため、低速時のsetpowerを4にしている。
上記のプログラムを用いて実際に動作させた時の動画。
ロボット本体製作・・・4時間
プログラミング(試走行含む)・・・5時間
今回は、前回の書道ロボットに比べ、かなりスムーズに完成したと思う。ロボットのイメージがより具体的になってきたということと、パーツを覚え始めたということが時間短縮に繋がっていったのだと考える。しかし、プログラミングの理解力をもっと高める必要があると感じる場面があった。ifの入れ子構造の繋がりを理解することに時間がかかった上、確認作業にも手間取った。取り敢えず作り始める、というスタンスではなく、プログラムのイメージを一度書き起こしてから、プログラミングに取り掛かっていくべきだったと感じた。最終課題では、課題1、課題2で得た知識や反省を活かしながら取り組めるよう心掛けたい。
※課題2・・・2015/07/16 (木) 最終更新
※課題2のページに追加更新という形をとる。
ロボティクス入門ゼミ最終週(2015年7月31日)にロボティクス入門ゼミの集大成となるロボットコンテストが行われた。以後文中表記の際はロボットコンテストをロボコンと略称を用いる。
ロボコンの詳細はこちらを参照。
ロボコンのロボット本体の基本構造は上記の課題2で説明した構造同様である。
ロボコン用のロボットは、AB共にアームと光センサーの位置を除くほぼ全てのパーツ配置を同じにしてある(不足パーツは他のものを代用している)。プログラムの際に同じような数値を入れるためである。そのような構造にすることで、プログラミングの時間短縮を図った。
課題2のロボットは上下に動くアームでボール持ち上げ移動させていたが、ロボコンでは、ボールの受け渡し動作があるため、タイムロスが大きい上にパスの安定性に欠ける。今回のロボコン内容に沿わせるため、アームの部分だけを変更し、左右に開くアームを用いることにした。
アームが左右に開く場合、同じ高さでは互いにぶつかり合うため、上手くパスが通らない。そこで、アームの位置を上下にずらすことによって、アーム同士の干渉自体を避けることにした。また、2台ともボールを左右から同時に挟むようになっているため、パスの際生じる多少の位置ずれを補えるようになっている。
また、AとBのロボットのアームパーツ(黄色のボールを挟んでいるパーツ)のつけ方を逆にした。Aは本体から少し遠めの位置にボールを、Bは本体から近い位置にボールを固定することによって、ABのアームが双方の本体に接触せずにボールの受け渡しが可能になった。
#define THRESHOLD 40 //基準となるしきい値 #define HIPOWER 7 #define LOWPOWER 4 #define setpower_H SetPower(OUT_AC,HIPOWER); #define setpower_L SetPower(OUT_AC,LOWPOWER); #define go_forward setpower_H; OnFwd(OUT_AC); #define go_back(t) setpower_H; OnRev(OUT_AC); Wait(t); #define turn_left1 setpower_L; OnFwd(OUT_C); OnRev(OUT_A); #define turn_right1 setpower_L; OnFwd(OUT_A); OnRev(OUT_C); #define turn_left2 setpower_L; OnFwd(OUT_C); Off(OUT_A); #define turn_right2 setpower_L; OnFwd(OUT_A); Off(OUT_C); #define STEP 1 #define nMAX 6 //黒をカウントする最大値 #define short_break Off(OUT_AC); Wait(200); #define cross_time 33 //交差点横断時間 #define armtime 15 //アーム開閉時間 #define arm_open setpower_L; OnFwd(OUT_B); Wait(armtime); Off(OUT_B); #define arm_close setpower_L; OnRev(OUT_B); Wait(armtime); Off(OUT_B); #define signal_go 25 #define first_ball 150
sub cross_line() { //交差点横断 OnFwd(OUT_AC); Wait(cross_time); Off(OUT_AC); }
task main() { SetSensor(SENSOR_1, SENSOR_LIGHT); int lc=0; int nc=0; ClearTimer(0); ClearMessage(); while(FastTimer(0) < first_ball) { //Aゾーンからボールを掴むまでのライントレース if (SENSOR_1 < THRESHOLD -7) { //ライン左側トレース turn_left1; } else { if (SENSOR_1 < THRESHOLD -3) { turn_left2; } else if (SENSOR_1 < THRESHOLD +3) { go_forward; } else if (SENSOR_1 < THRESHOLD +7) { turn_right2; } else { turn_right1; } } Wait(STEP); } arm_close(); while (lc < 3) { //Bゾーンまで向かうライントレース while (nc < nMAX) { if (SENSOR_1 < THRESHOLD -7) { //ライン左側トレース turn_left1; nc++; } else { if (SENSOR_1 < THRESHOLD -3) { turn_left2; } else if (SENSOR_1 < THRESHOLD +3) { go_forward; } else if (SENSOR_1 < THRESHOLD +7) { turn_right2; } else { turn_right1; } nc=0; } Wait(STEP); } PlaySound(SOUND_CLICK); //交差点識別時のサウンド lc++; if (lc == 1) { //交差点一回目の動作 turn_right1; Wait(nMAX + 15); cross_line(); nc=0; } if (lc == 2) { //交差点二回目の動作 turn_right1; Wait(nMAX + 22); cross_line(); turn_right1; Wait(10); Off(OUT_AC); nc=0; } } nc=0; turn_right1; //Bゾーン手前の直角 Wait(nMAX + 14); Off(OUT_AC); SendMessage(signal_go); //ロボットB始動命令 Wait(40); arm_open(); //ロボットBにボール受け渡し完了 go_back(65); //ロボットAがCへ向かう turn_right1; Wait(50); go_forward; Wait(110); Off(OUT_AC); go_forward; until (SENSOR_1 < THRESHOLD -7); //ラインまで直進 turn_right1; Wait(50); Off(OUT_AC); if (true) { //Cゾーンまでトレース while (nc < nMAX) { if (SENSOR_1 < THRESHOLD -7) { //ライン右側トレース turn_right1; nc++; } else { if (SENSOR_1 < THRESHOLD -3) { turn_right2; } else if (SENSOR_1 < THRESHOLD +3) { go_forward; } else if (SENSOR_1 < THRESHOLD +7) { turn_left2; } else { turn_left1; } nc=0; } Wait(STEP); } PlaySound(SOUND_CLICK); //交差点識別時のサウンド turn_left1; Wait(nMAX + 14); cross_line(); } nc=0; go_forward; //Cゾーンに入る Wait(75); turn_right1; //ボール受け取りのための位置修正 Wait(110); Off(OUT_AC); PlaySound(SOUND_DOWN); //ボール受け取り準備完了 until (Message() ==signal_go); //ロボットBより再始動命令 PlaySound(SOUND_UP); //ロボットA再始動 arm_close(); //ボール受け取り Wait(250); go_forward; //Cゾーンから離脱 Wait(120); int bc=0; while (bc < 3) { //CゾーンからAへ向かうライントレース while (nc < nMAX) { if (SENSOR_1 < THRESHOLD -7) { //ライン左側トレース turn_left1; nc++; } else { if (SENSOR_1 < THRESHOLD -3) { turn_left2; } else if (SENSOR_1 < THRESHOLD +3) { go_forward; } else if (SENSOR_1 < THRESHOLD +7) { turn_right2; } else { turn_right1; } nc=0; } Wait(STEP); } PlaySound(SOUND_CLICK); //交差点認識時のサウンド turn_right1; Wait(nMAX + 14); cross_line(); nc=0; bc++; if (bc == 2) { //交差点二回目の追加動作 turn_right1; Wait(60); Off(OUT_AC); } } go_forward; //Aゾーンに帰還 Wait(60); turn_right1; //静止の方向修正 Wait(130); Off(OUT_AC); PlaySound(SOUND_DOWN); //ロボットA、Mission遂行完了 }
#define THRESHOLD 44 //基準となるしきい値 #define HIPOWER 7 #define LOWPOWER 4 #define setpower_H SetPower(OUT_AC,HIPOWER); #define setpower_L SetPower(OUT_AC,LOWPOWER); #define go_forward setpower_H; OnFwd(OUT_AC); #define go_back(t) setpower_H; OnRev(OUT_AC); Wait(t); #define turn_left1 setpower_L; OnFwd(OUT_C); OnRev(OUT_A); #define turn_right1 setpower_L; OnFwd(OUT_A); OnRev(OUT_C); #define turn_left2 setpower_L; OnFwd(OUT_C); Off(OUT_A); #define turn_right2 setpower_L; OnFwd(OUT_A); Off(OUT_C); #define STEP 1 #define nMAX 5 //黒をカウントする最大値 #define short_break Off(OUT_AC); Wait(200); #define cross_time 40 //交差点横断時間 #define armtime 15 //アーム開閉時間 #define arm_open setpower_L; OnFwd(OUT_B); Wait(armtime); Off(OUT_B); #define arm_close setpower_L; OnRev(OUT_B); Wait(armtime); Off(OUT_B); #define signal_go 25
sub cross_line() { //交差点横断 OnFwd(OUT_AC); Wait(cross_time); Off(OUT_AC); }
task main() { SetSensor(SENSOR_1, SENSOR_LIGHT); int lc=0; int nc=0; ClearMessage(); until (Message() == signal_go); //ロボットAがBゾーンに来るまで待機 PlaySound(SOUND_UP); //ロボットB始動 arm_close; //ボール受け取り Wait(250); go_forward; //Bゾーン離脱 Wait(140); Off(OUT_AC); while (lc < 4) { //BゾーンからCゾーンへ向かうライントレース while (nc < nMAX) { if (SENSOR_1 < THRESHOLD -7) { //ライン右側トレース turn_right1; nc++; } else { if (SENSOR_1 < THRESHOLD -3) { turn_right2; } else if (SENSOR_1 < THRESHOLD +3) { go_forward; } else if (SENSOR_1 < THRESHOLD +7) { turn_left2; } else { turn_left1; } nc=0; } Wait(STEP); } PlaySound(SOUND_CLICK); //交差点認識時のサウンド lc++; if (lc == 1) { //交差点一回目の動作 turn_left1; Wait(nMAX + 14); cross_line(); nc=0; } if (lc == 2) { //交差点二回目の動作 turn_right2; Wait(25); turn_right1; Wait(35); Off(OUT_AC); nc=0; } if (lc ==3) { //交差点三回目の動作 turn_left1; Wait(nMAX + 14); cross_line(); nc=0; } } turn_left1; //Cゾーン手前の直角 Wait(nMAX + 20); Off(OUT_AC); SendMessage(signal_go); //ロボットA再始動命令 Wait(10); arm_open; //ボール受け渡し完了 go_back(80); //ライントレースのコース上から離脱 turn_right1; Wait(60); go_forward; Wait(200); Off(OUT_AC); PlaySound(SOUND_DOWN); //ロボットB、Mission遂行完了 }
プログラムのライントレース部分に関しては、課題2とほとんど変わらないため、コメント文を省略してある。必要な部分のみの記述となっている。
ロボコンに向けてのプログラミングを行うにあたって、AB共に8Vを切ってしまうような低電圧時にプログラミングを行った。ロボコン直前で電池を交換したため、90°回転など秒数で制御、調整していたものが全て無駄になるというアクシデントがあった。また、ライントレース精度にも支障をきたし、スムーズなトレースが出来なくなった。モーターの回転速度が格段に上昇したため、黒の連続回数カウントが上手くいかなくなったのである。
自分たちの班のロボコンがスタートするまでに、何とか改善しようと、焦りに焦り、今までにないスピードで必要最低限のプログラミング変更を施した。故に、ロボットAに入れるプログラムをBに記述したりと冷静さを欠いていたのは目に見えてわかった。
その時のプログラムの主な変更、追加点として、速度調節のためにギア比調節は無謀だと考えたため、急遽setpowerを組み込んだ。カーブ時に用い、直進動作では用いていない。カーブ動作時はモーターの出力誤差はあまり関係ないからである。直進動作はライントレース速度向上のため、不問にした。
始動命令(signal_go)を受け取った際に、受け取りましたという信号(SendMessage(signal_ok))を作り、最初は送っていたのだが、作業の遅延に繋がっていると感じたため、省くように変更した。
arm_open(); //ロボットBにボール受け渡し完了 go_back(65); //ロボットAがCへ向かう turn_right1; Wait(50); go_forward; Wait(110); Off(OUT_AC); go_forward; until (SENSOR_1 < THRESHOLD -7); //ラインまで直進 turn_right1; Wait(50); Off(OUT_AC);
上記のプログラムは、ロボットAのプログラムを一部抜粋したものである。
BゾーンからCゾーンへロボットAが向かう際に、ライントレースによって向かった方が、Cゾーンに入ったときにより確実な方向調節が可能になる。そのためにも、どこかでライン上に乗らなければならない。しかし、元来た道を辿ってしまうと、途中でBゾーンから出てきたロボットBと接触してしまう場所が出てくる。それを避けるために、Bゾーンから下へ直進し、Cゾーン付近のラインに乗るようプログラミングを行った。
以下が上記のプログラム動作を可視化した説明用gif画像である。
ロボット製作・・・5時間
プログラミング(試走行含む)・・・12時間
今回のロボコンでは、無事タイムを残すことができ、結果1位を獲得できた。テスト期間に行った作業が報われる形になり喜ばしく、また安堵した。
時間に追われて作業した結果、プログラムにはまだ改善の余地が見受けられる。速さと正確さ、簡略化を兼ね備えたプログラミングが出来るよう努力していきたい。
大学高年次なるにつれ、ロボット、プログラミングはますます複雑なものになっていく。RISは青少年向けキットであるが、それでもかなり翻弄された。私はこの講座で初めてプログラミングに触れたが、専門分野に入る前に少しでも触れることが出来たことは、大きな収穫であったと感じる。これからも自分なりの考え、方針をもってプログラミングが出来るよう精進したい。