#contents *コース全体を見渡して問題を確認[#raising_questions] まずは、課題をクリアするのに何が必要かを検討してみる。 コースは、一つ目の課題で使ったコースを用いて作成するが、その向きはルール上自由である。よって、自分のチームが実際に使用したコースの見本を下に掲載する。 &ref(2012b/Member/ddnp/Mission2/course.jpg, 100%, ロボコンコース); また、詳しいルールはこちらを参照。[[2012年後期ロボコン>2012b/ロボコン#lcda8ca9]] とりあえず、ここから見えてくる大きな課題は3つ -真ん中の木をどうやって乗り越えるか -紙パックをどうやって掴むか -どうやって箱を入れ替えるか まずはこれらの解決に入ることにする。 *紙パックを運ぶ[#the_way_of_carrying_boxes] **木を乗り越える [#w08268df] まず、木を乗り越える方法について吟味してみる。 とりあえず思い当たる方法は二つ。 -箱だけ乗り越える -本体ごと乗り越える もしかしたら複数の小型ユニットが本体からの命令で紙パックを操作するなんてことも考えたが、そもそもパーツの数からして作るのは難しいと判断。というか本体の性能からして不可能に近いと思われる。 よってこの二つに絞ったが、それぞれの特色を吟味すると、以下の様なことが想定できる。 -箱だけ乗り越える場合 --箱を受け渡す機構が必要 --受け渡しのため、最低二台は必要になる --自由自在に動きまわらせるには、ロボットの回転が不可欠。よって、独立して動く動力が2つ必要なため、一台に二つ、合計4つ以上のモーターを動力に回さなければいけない --よって、箱を運ぶためのモーターが一台につき1つしか使えない --ただし、軽量化は可能 --小型化も楽 --箱の受け渡しにおいて確実性をあげるには、通信することも不可欠 --モーター数制限により、箱の積み上げはほぼ不可能だと思われる --コースを点対称に組み、それを利用すれば、1つの機体、1つのプログラムを完成させるだけで、それらをコピーして課題をこなすことが出来る -本体ごと乗り越える場合 --本体自体が木を乗り越えられる機構が必要 --大型化、重量化が懸念される --ただし、動力に必要なモーターは最低2つで済む --モーターが余れば、箱や本体の乗り越え、箱の積み上げ等、様々な部分で利用が可能。しかも最高4つのモーターが使える --モーターが多く使えるため、箱の積み上げが箱だけ乗り越えさせる場合より容易に可能 --RCX本体に接続できる接続コードは3つまでなので、4つ以上モーターを使う場合はRCXを2台導入する必要がある --センサーも同様 --これより、RCX同士が通信で処理の同期を取る必要性がある 箱を積み上げる気が無かった上、機体やプログラムを使いまわせる利点から、箱だけ乗り越えるロボットを作ることになった。 個人的には、本体ごと乗り越えさせる機構の構想が浮かばなかったことも、それに同意した理由となる。 乗り越えの方針は決まったため、次に問題となるのは箱の掴み方となった。 **紙パックを挟む [#ccf1b82c] 機体を動かすことについては[[課題1>2012b/Member/ddnp/Mission1]]にて十分に経験を積んでいるため、紙パックを運ぶ、というより紙パックを掴むことの方が課題となる。 では、どうやって掴むといいだろうか。 -横から挟み込む -上から吊る -下から持ち上げる とりあえず、人の手で行える動作を元に考えたので、自分で思いついたアイディアはこれくらい。 &ref(2012b/Member/ddnp/Mission2/how_to_raise.jpg, 100%, 紙パックの手での持ち上げ方); この3つの中から、消去法で「横から挟み込む」方法を取ることになった。 「下から持ち上げる」方法は、箱の下にロボットの部品を潜り込ませることが難しく、「上から吊る」方法は、紙パックの固定と紙パックへの狙いを定めるのが難しいと判断したからである。 後々になって、「押す」という動作があることを思いついたが、その時にはロボットの制作路線が決まっていたのでアイディアとしては却下。このロボットで「押し」を利用する場所も特になかった。 事実、他のチームは「挟み」と「押し」を利用していて、上手く行っているようだったので、安定した方法であったのではないかと思う。 **挟むためのパーツ[#parts_to_hold] 挟むためのパーツということで、クワガタのように挟み込む形に。 &ref(2012b/Member/ddnp/Mission2/arm_A.jpg, 100%, アーム); 写真下部に垂直に付いているギアを回すことで、写真中央のウォームギアが回転、その回転が隣のギアに伝わり、シャフトの回転に合わせてアームが開閉する。 &ref(2012b/Member/ddnp/Mission2/arm_B.jpg, 100%, 開くアームのギアの動き); これのギアを回してみると、とりあえず紙パックを掴むことは出来た。 単純に見えて掴めるのか怪しいけど、意外と紙パックにフィットするんだこれが。 紙パックを挟むこと自体は大して難しくなかった。しかしここから、大きな壁に直面する。 **紙パックを持ち上げる[#how_to_raise_up] 問題は紙パックの持ち上げである。 木の障害物がある以上、その障害を乗り越えられるだけの高さまで紙パックを挙げるか、もしくはそれに相当する何かが必要になる。 しかし、紙パックを木の障害物の高さにまで上げる動作は難しい。何故なら、アームを挟む動作にモーターを使っているためである。 機体のコピーを作ることを前提としている以上、ひとつのモーターで挟む、持ち上げるの動作を行うしか無い。 他の方法を考えようかと思っていたところ、天の一声(CV:担当教員の松本先生)がかかった。 「アームのギアに強い力をかければ歯車は重くなるから、その歯車の重みを利用してアームを持ち上げればいい。」 &ref(2012b/Member/ddnp/Mission2/way_of_armup_A.jpg, 100%, 通常のアームの動き(閉める時)); 普通、歯車の回転はアームを閉める働きをする。 &ref(2012b/Member/ddnp/Mission2/way_of_armup_B.jpg, 100%, アームを閉めきった時); アームを閉めていくと箱やアーム自身の締め付けが強くなり、締め付けをもっと強くしようとすればそれ相当の力が必要になる。水道の蛇口を必要以上に閉めようとする時に、大きな力が必要になるのと同じである。 最終的には、歯車自体が動かなくなるが、それを利用する。 &ref(2012b/Member/ddnp/Mission2/way_of_armup_C.jpg, 100%, アームを締め付ける時の力関係); 歯車が動かなくなるので、アーム自体にはそれ以上力が加わらない。しかし、動力を与え続けている限り、動かない歯車に力自体は伝わることになる。これが、アーム全体の重量(赤矢印)より大きくなった時、アーム全体を動かす力が働く。(図の上向きの矢印は一例。) &ref(2012b/Member/ddnp/Mission2/way_of_armup_D.jpg, 100%, アームを締め付ける時の力関係); そのアーム全体を動かす力を、上方向にかかるようにすると、アームはその力の向きに沿って持ち上がる。下げる時は、モーターの動力をオフにすれば、自重で下がる。 理論は出来上がったので、実際にアームの仕組みを作ることが課題となる。 そしてここからがとてつもなく大変であった。 **紙パックが持ち上がるまでに問題になった所[#arm_problems] ***アームとの歯車の組み合わせ [#g3226a72] いきなり、歯車同士を垂直に交らわせることが問題となった。 交らわせることの出来る歯車の大きさや、噛み合わせがなかなかうまく行かない。 普通の形の歯車を垂直に噛み合わせようとしても、回転している最中にかみ合わせが悪くなり空回りしてしまう。また、アームに接続する歯車の大きさによっては、アーム全体を持ち上げるだけのスペースが本体前面に確保できない上、モーター自体も大きいため、動力を伝えるのに邪魔になる。 モーターの邪魔にならないくらいに大きい歯車を使用し、かつその歯車とかみ合わせがうまくいく歯車の選別が必要になった。 そして、見事に垂直方向に噛み合わせてくださいと言わんばかりの歯車を発見。流石LEGO様分かっていらっしゃる。 とりあえず、わかりにくいとは思うが、下図のように歯車の歯が出っ張っているものと、LEGOの中で最も大きい歯車を使用することでこの問題を解決することが出来た。この大きい歯車は、後々の説明でよく出てくるので、ここで「中継歯車」と名付けておく。 &ref(2012b/Member/ddnp/Mission2/combi_gear.jpg, 100%, アームと動力の接続部分に使った歯車の組み合わせ); またその中継歯車は、モーターに直接接続していない。 アーム全体が上に持ち上がるといっても、歯車に接続している以上、その運動は回転するような持ち上げ方になる。よって、回転の中心になる軸が必要になるのだが、モーターの軸は短すぎて、軸として十分な役割を果たせなかった。 よって、中継歯車を長いシャフトに通して、これを実現した。ロボットの機体から、ユニットを置く基盤の部品が飛び出ていたので、これに固定することで安定感も抜群。 ここでなら分かると思うが、直接動力を与えられているわけではないので、中継歯車という名前にしてある。 &ref(2012b/Member/ddnp/Mission2/alpha_arm_parts.jpg, 100%, 大きい歯車と軸。またアーム部分の全体像); さて、これで持ち上がるかといったらそうでもない。まだまだアーム全体を持ち上げる力が不足している。そこで、アームの根本を輪ゴムで本体に接続し、持ち上げ動力の補助として使ってみた所、持ち上がった。 が、次の日、何故か突如として持ち上がらなくなる…。原因は輪ゴムの劣化か何かだったようだ。残念。だが持ち上げた瞬間に「この仕組みでいける!」と確信した自分のチームは、この機構を突き詰めて見ることにした。とりあえず、輪ゴムでは信頼性に欠けるので、パーツの方でどうにかしてみよう。 ***アームまわりの軽量化、補強 [#b262b93b] まずはアームの根本のギアを強化。 &ref(2012b/Member/ddnp/Mission2/new_arm_1.jpg, 100%, アーム内部構造の改変); -歯車を変えてサイズを一回り小さく -無駄なブロックが無くなり堅固性の向上につながった模様 続いてアーム部本体の軸回りを強化。 &ref(2012b/Member/ddnp/Mission2/new_arm_2.jpg, 100%, アーム部の軸回りを改変); -持ち上げる際にアーム部分との乖離を起こし安かったためこちらの改善を主に -軸とアーム部を固定する役目を、薄い一枚ブロックで行なっていたのだが、ちゃんとしてるパーツを見つけたのでそちらに変更 -中継歯車付近、アームの回転軸にあった輪ゴムの厚さほどの謎のスペースが消え、それによって歯車のかみ合わせがよくなった。どうやらLEGOの想定していないパーツの使い方だったらしい…そういう使い方をしたあたり、これはこれで嬉しい( ウォームギア(&ref(2012b/Member/ddnp/Mission2/warm_gear.gif, 100%, ウォームギアアイコン);)との組み合わせが出来る歯車でアーム全体を最も小さく出来そうな歯車を探して、それに合わせて必要最低限のパーツを組み合わせたので、恐らくコレ以上の軽量化は無理だと思われる。 だがしかし、これでもまだ持ち上がる時と持ち上がらない時が半々。紙パックを持ち上げようとしても、アームの歯車だけでなく、動力を伝えるものも含めて全体が動かなくなる現象が発生。一体何がいけないんだ…? ***モーター歯車の大きさ [#v5024b1e] ここで、持ち上がらない原因が、歯車全体が動かなくなったところに注意してみる。 アームに動力を伝えるのに、中継歯車を使った。そしてそれはアーム部分の回転軸に直接つながっていると説明した。では、その中継歯車にどうやって動力を伝えていたのか。それが問題の原点であった。伝わる動力が小さすぎたのだ。 その部分は、モーター自体に歯車をはめ、その歯車が直接中継歯車とつながっている構造となっている。 ここで、このモーターに接続している歯車を便宜上「モーター歯車」と名付ける。 &ref(2012b/Member/ddnp/Mission2/morter_gear.jpg, 100%, 中継歯車とモーター歯車); 参考に写真を貼ったが、これはこの問題発生時点の歯車ではなく、改良後。では、どこを改良したのかというと、モーター歯車の大きさである。 &ref(2012b/Member/ddnp/Mission2/gear_torque.jpg, 100%, ギアの大きさによる伝わる力の変化); 大きさの違う歯車には、てこの原理が働く。そのため同じ動力であるならば、大きな歯車から小さな歯車に力を伝える時より、大きな歯車から小さな歯車に力を伝える時の方が大きな力が働くのだが、そこの詳しい説明はどこを探しても見当たらなかった。なのでここからは自分の憶測なのだが、歯車に使われるシャフトは必ずしも歯車の中心一点で歯車を動かしているわけではない。十字型をした軸を使用しているため、この接点がモーターから歯車に力を伝える力点だと考えられる。そう考えると、歯車の中心に支点があり、そこからわずかにズレた場所に力点があり、歯車の噛みあう場所に作用点がある、というてこの原理が見えてくる。そのため、小さい歯車から大きい歯車に動力を伝える場合、伝わる動力は大きくなり、大きい歯車から小さい歯車に動力を伝える場合、伝わる動力は小さくなる。ちょうど、箸を使って豆を摘む時、摘む場所が豆に近いほうがつまみやすいのと同じである。 &ref(2012b/Member/ddnp/Mission2/raise_power_A.jpg, 100%, モーター歯車が大きい場合のアームを持ち上げる力); &ref(2012b/Member/ddnp/Mission2/raise_power_B.jpg, 100%, モーター歯車が小さい場合のアームを持ち上げる力); 中継歯車自体がまるでシーソーのような役目を果たしているため、中継歯車に伝わる動力はそのままアーム全体を持ち上げる力に成り代わる。そのためモーター歯車を可能な限り小さいものにすれば、モーターの動力を出来るだけ大きい状態でアームの持ち上げに使うことが出来る。 改良前の状態では、LEGOパーツ中で中くらいの大きさの歯車をモーター歯車として使っていたので、アーム全体を持ち上げるだけの力が出せなかったようだ。小さい歯車に置き換えた所、難なく持ち上げることが出来た。これにて輪ゴムを取っ払っても、確実にアームを上げることが出来る。しかし、このせいで思わぬ副作用が発生した。 ***歯車の空回り [#i769186d] &ref(2012b/Member/ddnp/Mission2/arm_powerup_B.jpg, 100%, モーター歯車が大きい場合のアームを締める力); &ref(2012b/Member/ddnp/Mission2/arm_powerup_A.jpg, 100%, モーター歯車が小さい場合のアームを締める力); 先ほど、中継歯車がシーソーのような役割をするといった。そのため、アームを持ち上げる力が大きくなったわけだが、それは同時にアームを締める力も大きくなったということも意味する。普通だったらそれでいいのだが、そのためにアームの根本で事件が起きた。 &ref(2012b/Member/ddnp/Mission2/arm_root.jpg, 100%, アーム根本の原因の場所); 図の青矢印部分で、歯車が空回りし始めたのだ。);ウォームギア&ref(2012b/Member/ddnp/Mission2/warm_gear.gif, 100%, ウォームギアアイコン);自体が大きな力を伝える歯車であるため、紙パックを掴むにしては少々能力が高すぎる。確実に掴めるという利点はあるものの、アーム全体を持ち上げるため、この歯車を止めるには、相当な負荷が必要となる。当然、ウォームギアが伝える力が大きいほど、それと同じ大きさの負荷が必要になるので、負荷は大きくなる。今回の問題点は、その負荷がかかりきる前に、アーム部分の歯車が耐えられなくなり、ウォームギアから外れて空回りするようになったといったものだ。これを改善するためには、 -モーター歯車を大きくする -アーム部分の歯車を大きくする -ウォームギアの部分を別のもっと大きい歯車で代用する のいずれかを行う必要がある。しかし、モーター歯車を大きくした場合、アーム全体が持ち上がらなくなると考えられるため、却下。 アーム部分の歯車を大きくする案も、LEGOのパーツに余裕がなかったため却下。結果、ウォームギア部分を改善するしか無いのだが、その方法をとうとう見つけることが出来なかった。パーツを付け替えた所、空回りしなくなったのでそれで良しとしたが、今思えば、ディファレンシャルギアが使えたのでは無いかと思う。とりあえずこの問題は保留となった。 ***全体的な接続の強化 [#s599454b] ここまで来ていろいろとやってみた挙句実感したが、この構造は非常に無理がかかる仕組みになっているようだ。 全体的な歯車のかみ合わせがすぐに不安定になり、空回りするようになってきた。モーター歯車と中継歯車、中継歯車とアーム部に動力を伝える垂直歯車、そしてウォームギアまわりの噛み合わせと、様々な場所で突如として空回りが起こるのだ。ウォームギア回りはしかたがないとしても、その他は改善のしようがあったため、可能な限り補強を実行。モーター自体をブロックでガチガチに固めたり、アーム部の歯車を輪ゴムで縛り付けたりしている。尚、この時までの輪ゴム系の処置は全て同チームのkuRo氏が行なっているため、自分の中で「ゴムのkuRo」の異名を与えたことに本人は気づいていない。 &ref(2012b/Member/ddnp/Mission2/gear_protect.jpg, 100%, 歯車をゴムで止めている様子); それにしてもこの輪ゴム、本当にどうやって止めてるのか分からない。謎。 &ref(2012b/Member/ddnp/Mission2/morter_protect.jpg, 100%, モーターをゴムで接続する様子); この一つ前の段階では、シャフトを使って堅固すぎる固定をしたりしていた。 恐らく、アーム回りで自分が関わった改良は大体このあたりで全てだと思われる。 ***アームテスト用プログラム [#jf39cf0b] ただ単純に、アーム動力のモーターへ2秒間出力するだけである。これを使って、掴み、持ち上げ、開放のテストを行なっていた。掴みと開放は、モーターに繋がるコードの接続方向を変えることで行うことができるので、プログラムはこれだけである。 task main () { // アーム出力調整 SetPower(OUT_B, 6); // 2秒間アームに出力 OnFwd(OUT_B); Wait(200); Off(OUT_B); } **紙パックを見つける [#t0611b16] 箱を掴んで持ち上げられるなら、後は紙パックを認知できれば良い。それなら、タッチセンサーの出番である。 ***タッチセンサーをアームにつける [#d81277b3] &ref(2012b/Member/ddnp/Mission2/touch_sensor.jpg, 100%, アームに付けてみたタッチセンサー); とりあえず、タッチセンサーをアームに付けてみた。そしてロボットを直進させて、感知できるかやってみることに。 task main { SetSensor(SENSOR_2, SONSOR_TOUCH); // 感知するまで前進 until(SENSOR_2) { OnFwd(OUT_AC); } // 停止 Off(OUT_AC); } だが、ただ単純につけただけでは感知しない模様。紙パックが軽すぎるのだ。そこで、次のような解決策を考えた。 -本体の速度を上げる -バンパーを用いて、てこの原理を使う 紙パックの軽さから考えて、感知させるには相当な速さが必要なはずだ。それほどまでに本体の速度を上げてしまっては、制御が難しくなる上、ライントレスも難しいということで、バンパーを使うことに。紙パックが変な当たり方をしても絶対に感知できる利点もある。 &ref(2012b/Member/ddnp/Mission2/bumper_module.gif, 100%, てこの原理を用いたバンパーの働き); バンパーはアームに取り付ける予定であったため、バンパー自体が大きくなりすぎると、アーム全体の重量が増加し、持ち上がらなくなる恐れがある。また、バンパーを本体に付けると、持ち上げの際に邪魔になり、アームの回転軸に取り付ける方法はそもそもアイディアに無かった。よって、可能な限り小さく無駄のないバンパーをアームに取り付ける必要があったが、どうしてもうまくいかない。回転軸に近い場所に取り付けて重量の負担を減らしてみたり、タッチセンサーに当たるバンパーの形を変えてみたりと色々とやった。最終的に、地面スレスレまでバンパーを伸ばしててこの原理を使おうとしたが、それもダメ。もしかしたらアームに取り付けられるようなバンパーでは絶対に反応しないのではないかという仮説に達し、別の場所に付けて見ることに。 ***タッチセンサーをロボット背面につける [#re2de7fd] 結果論からいくと、下の写真のようになった。 &ref(2012b/Member/ddnp/Mission2/back_bumper.jpg, 100%, ロボット背面のバンパー); &ref(2012b/Member/ddnp/Mission2/backbumper_touchsensor.jpg, 100%, ロボット背面に押し上げるように付いているタッチセンサー); &ref(2012b/Member/ddnp/Mission2/bumper_and_sensor.jpg, 100%, バンパーとタッチセンサー部分抜粋); ロボット全体の高さの中で、最も高い場所から地面スレスレまでバンパーを伸ばし、タッチセンサーは支点のほぼ真下にある。それに付け加え、バンパーの先に紙パックの軽さをカバーするためのおもりとなるタイヤをとりつけ、タッチセンサーの先端部がRCX本体の平らな面とほぼ点で接触するようにした。ここまでしてようやく、紙パックにタッチセンサーが反応するようになる。この時、アームにタッチセンサーを取り付けるのは構造上難しいことを悟り、このままでいくことにした。 ***紙パックを掴むプログラム [#v5f48545] タッチセンサーが背面に付いているため、タッチセンサーが反応したら、半回転して紙パックを掴む動作が必要になる。が、これはkuRo氏に任せたので割愛。kuRo氏の課題が出来たらここにリンク掲載予定。 **箱の入れ替え [#y2ba45e3] &ref(2012b/Member/ddnp/Mission2/how_to_exchange_2.jpg, 100%, 先に全ての箱を移動させる方法); 点対称なコースであるため、先にコースの半分を使って全ての箱を木の向こう側に送り込み、通信で箱の移動を確認。その後、箱の位置に近い場所をセンサーで探してゴールに置くという方法を提案した。これなら別に光センサーを使わずして箱が黒か白かは一目瞭然である。真ん中の繋ぎ目の左側と右側で、白箱と黒箱の入れ替えが行われるからだ。置く場所も、ある長さの直線を感知したら、白い場所を突っ切って木に当たるまで、などという風に工夫すれば、ブレが小さくてすむ。 &ref(2012b/Member/ddnp/Mission2/how_to_exchange_1.jpg, 100%, 1つずつ箱を移動させる方法); しかし、最終的に1つずつ箱を交換していく方法を選択。これならスタート以外の2回は全て同じプログラムで良いからだ。そしてこの勘違いが後のプログラム部分で思わぬ悲劇を生む。 *タイヤまわり [#ddea24d5] &ref(2012b/Member/ddnp/Mission2/wheel_gear.jpg, 100%, タイヤまわりの歯車); 車輪の方も、ライントレスをする以上、可能な限り遅くしたいと考えたため、本体のタイヤまわりの歯車が最終的に超メカっぽくなった。一番小さい歯車が、タイヤを回転させる歯車に接続するようになっている。というのも、回転数を考えたためだ。 &ref(2012b/Member/ddnp/Mission2/gear_speed.jpg, 100%, ギアの回転数); 小さい歯車の一回転は、大きい歯車の一回転に相当しない。そのため、回転速度に差が出てくる。これが回転数の差である。が、何故か素早い動きを行ったため、今考えると真ん中の歯車は関係ないのかもしれない…。前述のように、回転数でもシーソーのような役割をするのだろうか…。後々紙パックを持った時にスピードダウンすることがわかったので、これはそのまま放置することにした。 *全体の荷重 [#j7b243b3] ただ、それと同時に別の問題をクリアした。むしろこっちがメインだったかもしれない。実は、ロボット全体の荷重が後ろに掛かりすぎて、重さがタイヤに全く乗らず、タイヤが空回りする問題が発生していたのだ。 最も重いのはRCX本体。なのでこいつを動かせば、大抵の荷重問題は解決するだろうという直感から、RCX本体を動かすことに。モーターを後ろに回し、タイヤの上にRCXを支えるパーツを持ってくる。これにてRCXがロボットの前に移動され、RCXの重みが直にロボットに掛かるようになり、問題なく発進するようになった。 &ref(2012b/Member/ddnp/Mission2/over_wheel.jpg, 100%, タイヤ上のRCX位置); 水色の線が、タイヤの中心から垂直に伸ばした線。、改良前ではモーターがRCXの前面にあり、邪魔となっている。改良後では、RCXの乗る位置がタイヤの上に来ているのがわかると思う。 *最終的な本体 [#x3d5bfbd] ロボット全体とその他補足的な改良を説明する。 &ref(2012b/Member/ddnp/Mission2/itamae.jpg, 100%, 完成したロボット); ロボットの全体像はこんな感じ。紙パックの下の方を掴めると、モーメントの関係より持ち上げがやりやすいため、可能な限り地面に近い位置にアームが来ている。 &ref(2012b/Member/ddnp/Mission2/arm_gears.jpg, 100%, アームの歯車); 苦労したアームの歯車まわりは、こんな感じ。 &ref(2012b/Member/ddnp/Mission2/itamae_under.jpg, 100%, ロボット下面(三点目の支え)); このロボットも2輪のため、支えるためのパーツが下面に付いている。仕組みは、[[課題1>2012b/Member/ddnp/Mission1]]のロボットの使い回しだ。 &ref(2012b/Member/ddnp/Mission2/itamae_under2.jpg, 100%, ロボット下面2(光センサーの位置)); また、タッチセンサー側への進行が主になるので、光センサーも後ろ側に付いている。RCX本体を支える土台が前に来たのが幸いし、下部をくり抜いて光センサーを取り付けることが出来た。ところで、これでは一体どっちを前と呼べばいいか謎である。だいぶ個性が出たロボットとなった。 とりあえずここでハード面は一旦完成として、プログラムに移ることにする。この時点でロボコン1日前である。(やばい) *必要なプログラム [#d870ac88] 機体は出来たので、プログラムを考えることとなった。 1つずつ箱を運ぶプログラムを書くので、断片的な動作のプログラムを書いて、サブルーチンに変換。あとはそれらを組み合わせてやれば良い。その断片的な動作とは、この4つ。 -箱を感知し掴む -箱を持ち上げ、木を超えさせる(この中で箱を離すプログラムが作られるので、それをコピーして他の場所でも使う予定) -箱をゴール内まで運ぶ(3つ分) -通信を持ちいて箱を置いたことを伝え、また同じ内容を受信する。 こちらのプログラムを、他の三人にお願いした。これらを完成させれば、ライントレスをメインにした移動用のプログラムに組み合わせて、3つ全ての箱を運ぶことが出来る。ライントレスのプログラムは前回の課題で自分の作ったものを改良して使うことにしたので、移動用のプログラムは自分が担当することに。移動用プログラムは以下のとおり。 -スタート地点から出発し、一つ目に辿り着く -箱を受け渡すところまで進む -通信場所まで移動する -渡された箱のある場所まで移動する -渡された箱付近からゴール付近まで戻る 全体的な流れはこんなかんじを想定。 +一つ目の箱に辿り着く +箱を感知し掴む +箱を受け渡すところまで #contents *コース全体を見渡して問題を確認{#raising_questions} [#n5e80754] まずは、課題をクリアするのに何が必要かを検討してみる。 コースは、一つ目の課題で使ったコースを用いて作成するが、その向きはルール上自由である。よって、自分のチームが実際に使用したコースの見本を下に掲載する。 &ref(2012b/Member/ddnp/Mission2/course.jpg, 100%, ロボコンコース); また、詳しいルールはこちらを参照。[[2012年後期ロボコン>2012b/ロボコン#lcda8ca9]] とりあえず、ここから見えてくる大きな課題は3つ -真ん中の木をどうやって乗り越えるか -紙パックをどうやって掴むか -どうやって箱を入れ替えるか まずはこれらの解決に入ることにする。 *紙パックを運ぶ[#the_way_of_carrying_boxes] **木を乗り越える [#k5a29678] まず、木を乗り越える方法について吟味してみる。 とりあえず思い当たる方法は二つ。 -箱だけ乗り越える -本体ごと乗り越える もしかしたら複数の小型ユニットが本体からの命令で紙パックを操作するなんてことも考えたが、そもそもパーツの数からして作るのは難しいと判断。というか本体の性能からして不可能に近いと思われる。 よってこの二つに絞ったが、それぞれの特色を吟味すると、以下の様なことが想定できる。 -箱だけ乗り越える場合 --箱を受け渡す機構が必要 --受け渡しのため、最低二台は必要になる --自由自在に動きまわらせるには、ロボットの回転が不可欠。よって、独立して動く動力が2つ必要なため、一台に二つ、合計4つ以上のモーターを動力に回さなければいけない --よって、箱を運ぶためのモーターが一台につき1つしか使えない --ただし、軽量化は可能 --小型化も楽 --箱の受け渡しにおいて確実性をあげるには、通信することも不可欠 --モーター数制限により、箱の積み上げはほぼ不可能だと思われる --コースを点対称に組み、それを利用すれば、1つの機体、1つのプログラムを完成させるだけで、それらをコピーして課題をこなすことが出来る -本体ごと乗り越える場合 --本体自体が木を乗り越えられる機構が必要 --大型化、重量化が懸念される --ただし、動力に必要なモーターは最低2つで済む --モーターが余れば、箱や本体の乗り越え、箱の積み上げ等、様々な部分で利用が可能。しかも最高4つのモーターが使える --モーターが多く使えるため、箱の積み上げが箱だけ乗り越えさせる場合より容易に可能 --RCX本体に接続できる接続コードは3つまでなので、4つ以上モーターを使う場合はRCXを2台導入する必要がある --センサーも同様 --これより、RCX同士が通信で処理の同期を取る必要性がある 箱を積み上げる気が無かった上、機体やプログラムを使いまわせる利点から、箱だけ乗り越えるロボットを作ることになった。 個人的には、本体ごと乗り越えさせる機構の構想が浮かばなかったことも、それに同意した理由となる。 乗り越えの方針は決まったため、次に問題となるのは箱の掴み方となった。 **紙パックを挟む [#g5a10402] 機体を動かすことについては[[課題1>2012b/ddnp/Mission1]]にて十分に経験を積んでいるため、紙パックを運ぶ、というより紙パックを掴むことの方が課題となる。 では、どうやって掴むといいだろうか。 -横から挟み込む -上から吊る -下から持ち上げる とりあえず、人の手で行える動作を元に考えたので、自分で思いついたアイディアはこれくらい。 &ref(2012b/Member/ddnp/Mission2/how_to_raise.jpg, 100%, 紙パックの手での持ち上げ方); この3つの中から、消去法で「横から挟み込む」方法を取ることになった。 「下から持ち上げる」方法は、箱の下にロボットの部品を潜り込ませることが難しく、「上から吊る」方法は、紙パックの固定と紙パックへの狙いを定めるのが難しいと判断したからである。 後々になって、「押す」という動作があることを思いついたが、その時にはロボットの制作路線が決まっていたのでアイディアとしては却下。このロボットで「押し」を利用する場所も特になかった。 事実、他のチームは「挟み」と「押し」を利用していて、上手く行っているようだったので、安定した方法であったのではないかと思う。 **挟むためのパーツ[#parts_to_hold] 挟むためのパーツということで、クワガタのように挟み込む形に。製作はチームのkurokishi氏による。 &ref(2012b/Member/ddnp/Mission2/arm_A.jpg, 100%, アーム); 写真下部に垂直に付いているギアを回すことで、写真中央のウォームギアが回転、その回転が隣のギアに伝わり、シャフトの回転に合わせてアームが開閉する。 &ref(2012b/Member/ddnp/Mission2/arm_B.jpg, 100%, 開くアームのギアの動き); これのギアを回してみると、とりあえず紙パックを掴むことは出来た。 単純に見えて掴めるのか怪しいけど、意外と紙パックにフィットするんだこれが。 紙パックを挟むこと自体は大して難しくなかった。しかしここから、大きな壁に直面する。 **紙パックを持ち上げる[#how_to_raise_up] 問題は紙パックの持ち上げである。 木の障害物がある以上、その障害を乗り越えられるだけの高さまで紙パックを挙げるか、もしくはそれに相当する何かが必要になる。 しかし、紙パックを木の障害物の高さにまで上げる動作は難しい。何故なら、アームを挟む動作にモーターを使っているためである。 機体のコピーを作ることを前提としている以上、ひとつのモーターで挟む、持ち上げるの動作を行うしか無い。 他の方法を考えようかと思っていたところ、天の一声(CV:担当教員の松本先生)がかかった。 「アームのギアに強い力をかければ歯車は重くなるから、その歯車の重みを利用してアームを持ち上げればいい。」 &ref(2012b/Member/ddnp/Mission2/way_of_armup_A.jpg, 100%, 通常のアームの動き(閉める時)); 普通、歯車の回転はアームを閉める働きをする。 &ref(2012b/Member/ddnp/Mission2/way_of_armup_B.jpg, 100%, アームを閉めきった時); アームを閉めていくと箱やアーム自身の締め付けが強くなり、締め付けをもっと強くしようとすればそれ相当の力が必要になる。水道の蛇口を必要以上に閉めようとする時に、大きな力が必要になるのと同じである。 最終的には、歯車自体が動かなくなるが、それを利用する。 &ref(2012b/Member/ddnp/Mission2/way_of_armup_C.jpg, 100%, アームを締め付ける時の力関係); 歯車が動かなくなるので、アーム自体にはそれ以上力が加わらない。しかし、動力を与え続けている限り、動かない歯車に力自体は伝わることになる。これが、アーム全体の重量(赤矢印)より大きくなった時、アーム全体を動かす力が働く。(図の上向きの矢印は一例。) &ref(2012b/Member/ddnp/Mission2/way_of_armup_C.jpg, 100%, アームを締め付ける時の力関係); そのアーム全体を動かす力を、上方向にかかるようにすると、アームはその力の向きに沿って持ち上がる。下げる時は、モーターの動力をオフにすれば、自重で下がる。 理論は出来上がったので、実際にアームの仕組みを作ることが課題となる。 そしてここからがとてつもなく大変であった。 **紙パックが持ち上がるまでに問題になった所[#arm_problems] ***アームとの歯車の組み合わせ [#p8950519] いきなり、歯車同士を垂直に交らわせることが問題となった。 交らわせることの出来る歯車の大きさや、噛み合わせがなかなかうまく行かない。 普通の形の歯車を垂直に噛み合わせようとしても、回転している最中にかみ合わせが悪くなり空回りしてしまう。また、アームに接続する歯車の大きさによっては、アーム全体を持ち上げるだけのスペースが本体前面に確保できない上、モーター自体も大きいため、動力を伝えるのに邪魔になる。 モーターの邪魔にならないくらいに大きい歯車を使用し、かつその歯車とかみ合わせがうまくいく歯車の選別が必要になった。 そして、見事に垂直方向に噛み合わせてくださいと言わんばかりの歯車を発見。流石LEGO様分かっていらっしゃる。 とりあえず、わかりにくいとは思うが、下図のように歯車の歯が出っ張っているものと、LEGOの中で最も大きい歯車を使用することでこの問題を解決することが出来た。この大きい歯車は、後々の説明でよく出てくるので、ここで「中継歯車」と名付けておく。 &ref(2012b/Member/ddnp/Mission2/combi_gear.jpg, 100%, アームと動力の接続部分に使った歯車の組み合わせ); またその中継歯車は、モーターに直接接続していない。 アーム全体が上に持ち上がるといっても、歯車に接続している以上、その運動は回転するような持ち上げ方になる。よって、回転の中心になる軸が必要になるのだが、モーターの軸は短すぎて、軸として十分な役割を果たせなかった。 よって、中継歯車を長いシャフトに通して、これを実現した。ロボットの機体から、ユニットを置く基盤の部品が飛び出ていたので、これに固定することで安定感も抜群。 ここでなら分かると思うが、直接動力を与えられているわけではないので、中継歯車という名前にしてある。 &ref(2012b/Member/ddnp/Mission2/alpla_arm_parts.jpg, 100%, 大きい歯車と軸。またアーム部分の全体像); さて、これで持ち上がるかといったらそうでもない。まだまだアーム全体を持ち上げる力が不足している。そこで、アームの根本を輪ゴムで本体に接続し、持ち上げ動力の補助として使ってみた所、持ち上がった。 が、次の日、何故か突如として持ち上がらなくなる…。原因は輪ゴムの劣化か何かだったようだ。残念。だが持ち上げた瞬間に「この仕組みでいける!」と確信した自分のチームは、この機構を突き詰めて見ることにした。とりあえず、輪ゴムでは信頼性に欠けるので、パーツの方でどうにかしてみよう。 ***アームまわりの軽量化、補強 [#b0913fda] まずはアームの根本のギアを強化。 &ref(2012b/Member/ddnp/Mission2/new_arm_1.jpg, 100%, アーム内部構造の改変); -歯車を変えてサイズを一回り小さく -無駄なブロックが無くなり堅固性の向上につながった模様 続いてアーム部本体の軸回りを強化。 &ref(2012b/Member/ddnp/Mission2/new_arm_2.jpg, 100%, アーム部の軸回りを改変); -持ち上げる際にアーム部分との乖離を起こし安かったためこちらの改善を主に -軸とアーム部を固定する役目を、薄い一枚ブロックで行なっていたのだが、ちゃんとしてるパーツを見つけたのでそちらに変更 -中継歯車付近、アームの回転軸にあった輪ゴムの厚さほどの謎のスペースが消え、それによって歯車のかみ合わせがよくなった。どうやらLEGOの想定していないパーツの使い方だったらしい…そういう使い方をしたあたり、これはこれで嬉しい( ウォームギア(#ref(2012b/Member/ddnp/Mission2/warm_gear.gif, 100%, ウォームギアアイコン);)との組み合わせが出来る歯車でアーム全体を最も小さく出来そうな歯車を探して、それに合わせて必要最低限のパーツを組み合わせたので、恐らくコレ以上の軽量化は無理だと思われる。 だがしかし、これでもまだ持ち上がる時と持ち上がらない時が半々。紙パックを持ち上げようとしても、アームの歯車だけでなく、動力を伝えるものも含めて全体が動かなくなる現象が発生。一体何がいけないんだ…? ***モーター歯車の大きさ [#f8fd493f] ここで、持ち上がらない原因が、歯車全体が動かなくなったところに注意してみる。 アームに動力を伝えるのに、中継歯車を使った。そしてそれはアーム部分の回転軸に直接つながっていると説明した。では、その中継歯車にどうやって動力を伝えていたのか。それが問題の原点であった。伝わる動力が小さすぎたのだ。 その部分は、モーター自体に歯車をはめ、その歯車が直接中継歯車とつながっている構造となっている。 ここで、このモーターに接続している歯車を便宜上「モーター歯車」と名付ける。 &ref(2012b/Member/ddnp/Mission2/morter_gear.jpg, 100%, 中継歯車とモーター歯車); 参考に写真を貼ったが、これはこの問題発生時点の歯車ではなく、改良後。では、どこを改良したのかというと、モーター歯車の大きさである。 &ref(2012b/Member/ddnp/Mission2/gear_torque.jpg, 100%, ギアの大きさによる伝わる力の変化); 大きさの違う歯車には、てこの原理が働く。そのため同じ動力であるならば、大きな歯車から小さな歯車に力を伝える時より、大きな歯車から小さな歯車に力を伝える時の方が大きな力が働くのだが、そこの詳しい説明はどこを探しても見当たらなかった。なのでここからは自分の憶測なのだが、歯車に使われるシャフトは必ずしも歯車の中心一点で歯車を動かしているわけではない。十字型をした軸を使用しているため、この接点がモーターから歯車に力を伝える力点だと考えられる。そう考えると、歯車の中心に支点があり、そこからわずかにズレた場所に力点があり、歯車の噛みあう場所に作用点がある、というてこの原理が見えてくる。そのため、小さい歯車から大きい歯車に動力を伝える場合、伝わる動力は大きくなり、大きい歯車から小さい歯車に動力を伝える場合、伝わる動力は小さくなる。ちょうど、箸を使って豆を摘む時、摘む場所が豆に近いほうがつまみやすいのと同じである。 &ref(2012b/Member/ddnp/Mission2/raise_power_A.jpg, 100%, モーター歯車が大きい場合のアームを持ち上げる力); &ref(2012b/Member/ddnp/Mission2/raise_power_B.jpg, 100%, モーター歯車が小さい場合のアームを持ち上げる力); 中継歯車自体がまるでシーソーのような役目を果たしているため、中継歯車に伝わる動力はそのままアーム全体を持ち上げる力に成り代わる。そのためモーター歯車を可能な限り小さいものにすれば、モーターの動力を出来るだけ大きい状態でアームの持ち上げに使うことが出来る。 改良前の状態では、LEGOパーツ中で中くらいの大きさの歯車をモーター歯車として使っていたので、アーム全体を持ち上げるだけの力が出せなかったようだ。小さい歯車に置き換えた所、難なく持ち上げることが出来た。これにて輪ゴムを取っ払っても、確実にアームを上げることが出来る。しかし、このせいで思わぬ副作用が発生した。 ***歯車の空回り [#j5e0d914] &ref(2012b/Member/ddnp/Mission2/arm_powerup_B.jpg, 100%, モーター歯車が大きい場合のアームを締める力); &ref(2012b/Member/ddnp/Mission2/arm_powerup_A.jpg, 100%, モーター歯車が小さい場合のアームを締める力); 先ほど、中継歯車がシーソーのような役割をするといった。そのため、アームを持ち上げる力が大きくなったわけだが、それは同時にアームを締める力も大きくなったということも意味する。普通だったらそれでいいのだが、そのためにアームの根本で事件が起きた。 &ref(2012b/Member/ddnp/Mission2/arm_root.jpg, 100%, アーム根本の原因の場所); 図の青矢印部分で、歯車が空回りし始めたのだ。);ウォームギア#ref(2012b/Member/ddnp/Mission2/warm_gear.gif, 100%, ウォームギアアイコン);自体が大きな力を伝える歯車であるため、紙パックを掴むにしては少々能力が高すぎる。確実に掴めるという利点はあるものの、アーム全体を持ち上げるため、この歯車を止めるには、相当な負荷が必要となる。当然、ウォームギアが伝える力が大きいほど、それと同じ大きさの負荷が必要になるので、負荷は大きくなる。今回の問題点は、その負荷がかかりきる前に、アーム部分の歯車が耐えられなくなり、ウォームギアから外れて空回りするようになったといったものだ。これを改善するためには、 -モーター歯車を大きくする -アーム部分の歯車を大きくする -ウォームギアの部分を別のもっと大きい歯車で代用する のいずれかを行う必要がある。しかし、モーター歯車を大きくした場合、アーム全体が持ち上がらなくなると考えられるため、却下。 アーム部分の歯車を大きくする案も、LEGOのパーツに余裕がなかったため却下。結果、ウォームギア部分を改善するしか無いのだが、その方法をとうとう見つけることが出来なかった。パーツを付け替えた所、空回りしなくなったのでそれで良しとしたが、今思えば、ディファレンシャルギアが使えたのでは無いかと思う。とりあえずこの問題は保留となった。 ***全体的な接続の強化 [#q73f5619] ここまで来ていろいろとやってみた挙句実感したが、この構造は非常に無理がかかる仕組みになっているようだ。 全体的な歯車のかみ合わせがすぐに不安定になり、空回りするようになってきた。モーター歯車と中継歯車、中継歯車とアーム部に動力を伝える垂直歯車、そしてウォームギアまわりの噛み合わせと、様々な場所で突如として空回りが起こるのだ。ウォームギア回りはしかたがないとしても、その他は改善のしようがあったため、可能な限り補強を実行。モーター自体をブロックでガチガチに固めたり、アーム部の歯車を輪ゴムで縛り付けたりしている。尚、この時までの輪ゴム系の処置は全て同チームのkurokishi氏が行なっているため、自分の中で「ゴムのkurokishi」の異名を与えたことに本人は気づいていない。 &ref(2012b/Member/ddnp/Mission2/gear_protect.jpg, 100%, 歯車をゴムで止めている様子); それにしてもこの輪ゴム、本当にどうやって止めてるのか分からない。謎。 &ref(2012b/Member/ddnp/Mission2/morter_protect.jpg, 100%, モーターをゴムで接続する様子); この一つ前の段階では、シャフトを使って堅固すぎる固定をしたりしていた。 恐らく、アーム回りで自分が関わった改良は大体このあたりで全てだと思われる。 ***アームテスト用プログラム [#p7af4191] ただ単純に、アーム動力のモーターへ2秒間出力するだけである。これを使って、掴み、持ち上げ、開放のテストを行なっていた。掴みと開放は、モーターに繋がるコードの接続方向を変えることで行うことができるので、プログラムはこれだけである。 task main () { // アーム出力調整 SetPower(OUT_B, 6); // 2秒間アームに出力 OnFwd(OUT_B); Wait(200); Off(OUT_B); } **紙パックを見つける [#v37e3bc5] 箱を掴んで持ち上げられるなら、後は紙パックを認知できれば良い。それなら、タッチセンサーの出番である。 ***タッチセンサーをアームにつける [#yaed0152] &ref(2012b/Member/ddnp/Mission2/touch_sensor.jpg, 100%, アームに付けてみたタッチセンサー); とりあえず、タッチセンサーをアームに付けてみた。そしてロボットを直進させて、感知できるかやってみることに。 task main { SetSensor(SENSOR_2, SONSOR_TOUCH); // 感知するまで前進 until(SENSOR_2) { OnFwd(OUT_AC); } // 停止 Off(OUT_AC); } だが、ただ単純につけただけでは感知しない模様。紙パックが軽すぎるのだ。そこで、次のような解決策を考えた。 -本体の速度を上げる -バンパーを用いて、てこの原理を使う 紙パックの軽さから考えて、感知させるには相当な速さが必要なはずだ。それほどまでに本体の速度を上げてしまっては、制御が難しくなる上、ライントレスも難しいということで、バンパーを使うことに。紙パックが変な当たり方をしても絶対に感知できる利点もある。 &ref(2012b/Member/ddnp/Mission2/bumper_module.gif, 100%, てこの原理を用いたバンパーの働き); バンパーはアームに取り付ける予定であったため、バンパー自体が大きくなりすぎると、アーム全体の重量が増加し、持ち上がらなくなる恐れがある。また、バンパーを本体に付けると、持ち上げの際に邪魔になり、アームの回転軸に取り付ける方法はそもそもアイディアに無かった。よって、可能な限り小さく無駄のないバンパーをアームに取り付ける必要があったが、どうしてもうまくいかない。回転軸に近い場所に取り付けて重量の負担を減らしてみたり、タッチセンサーに当たるバンパーの形を変えてみたりと色々とやった。最終的に、地面スレスレまでバンパーを伸ばしててこの原理を使おうとしたが、それもダメ。もしかしたらアームに取り付けられるようなバンパーでは絶対に反応しないのではないかという仮説に達し、別の場所に付けて見ることに。 ***タッチセンサーをロボット背面につける [#ccbbd601] 結果論からいくと、下の写真のようになった。 &ref(2012b/Member/ddnp/Mission2/back_bumper.jpg, 100%, ロボット背面のバンパー); &ref(2012b/Member/ddnp/Mission2/backbumper_touchsensor.jpg, 100%, ロボット背面に押し上げるように付いているタッチセンサー); &ref(2012b/Member/ddnp/Mission2/bumper_and_sensor.jpg, 100%, バンパーとタッチセンサー部分抜粋); ロボット全体の高さの中で、最も高い場所から地面スレスレまでバンパーを伸ばし、タッチセンサーは支点のほぼ真下にある。それに付け加え、バンパーの先に紙パックの軽さをカバーするためのおもりとなるタイヤをとりつけ、タッチセンサーの先端部がRCX本体の平らな面とほぼ点で接触するようにした。ここまでしてようやく、紙パックにタッチセンサーが反応するようになる。この時、アームにタッチセンサーを取り付けるのは構造上難しいことを悟り、このままでいくことにした。 ***紙パックを掴むプログラム [#y749f890] タッチセンサーが背面に付いているため、タッチセンサーが反応したら、半回転して紙パックを掴む動作が必要になる。が、これはkurokishi氏に任せたので割愛。kurokishi氏の課題が出来たらここにリンク掲載予定。 **箱の入れ替え [#lc4d2601] &ref(2012b/Member/ddnp/Mission2/how_to_exchange_2.jpg, 100%, 先に全ての箱を移動させる方法); 点対称なコースであるため、先にコースの半分を使って全ての箱を木の向こう側に送り込み、通信で箱の移動を確認。その後、箱の位置に近い場所をセンサーで探してゴールに置くという方法を提案した。これなら別に光センサーを使わずして箱が黒か白かは一目瞭然である。真ん中の繋ぎ目の左側と右側で、白箱と黒箱の入れ替えが行われるからだ。置く場所も、ある長さの直線を感知したら、白い場所を突っ切って木に当たるまで、などという風に工夫すれば、ブレが小さくてすむ。 &ref(2012b/Member/ddnp/Mission2/how_to_exchange_1.jpg, 100%, 1つずつ箱を移動させる方法); しかし、最終的に1つずつ箱を交換していく方法を選択。これならスタート以外の2回は全て同じプログラムで良いからだ。そしてこの勘違いが後のプログラム部分で思わぬ悲劇を生む。 *タイヤまわり [#o4dfd4c8] &ref(2012b/Member/ddnp/Mission2/wheel_gear.jpg, 100%, タイヤまわりの歯車); 車輪の方も、ライントレスをする以上、可能な限り遅くしたいと考えたため、本体のタイヤまわりの歯車が最終的に超メカっぽくなった。一番小さい歯車が、タイヤを回転させる歯車に接続するようになっている。というのも、回転数を考えたためだ。 &ref(2012b/Member/ddnp/Mission2/gear_speed.jpg, 100%, ギアの回転数); 小さい歯車の一回転は、大きい歯車の一回転に相当しない。そのため、回転速度に差が出てくる。これが回転数の差である。が、何故か素早い動きを行ったため、今考えると真ん中の歯車は関係ないのかもしれない…。前述のように、回転数でもシーソーのような役割をするのだろうか…。後々紙パックを持った時にスピードダウンすることがわかったので、これはそのまま放置することにした。 *全体の荷重 [#ae77a4f7] ただ、それと同時に別の問題をクリアした。むしろこっちがメインだったかもしれない。実は、ロボット全体の荷重が後ろに掛かりすぎて、重さがタイヤに全く乗らず、タイヤが空回りする問題が発生していたのだ。 最も重いのはRCX本体。なのでこいつを動かせば、大抵の荷重問題は解決するだろうという直感から、RCX本体を動かすことに。モーターを後ろに回し、タイヤの上にRCXを支えるパーツを持ってくる。これにてRCXがロボットの前に移動され、RCXの重みが直にロボットに掛かるようになり、問題なく発進するようになった。 &ref(2012b/Member/ddnp/Mission2/over_wheel.jpg, 100%, タイヤ上のRCX位置); 水色の線が、タイヤの中心から垂直に伸ばした線。、改良前ではモーターがRCXの前面にあり、邪魔となっている。改良後では、RCXの乗る位置がタイヤの上に来ているのがわかると思う。 *最終的な本体 [#td3153eb] ロボット全体とその他補足的な改良を説明する。 &ref(2012b/Member/ddnp/Mission2/itamae.jpg, 100%, 完成したロボット); ロボットの全体像はこんな感じ。紙パックの下の方を掴めると、モーメントの関係より持ち上げがやりやすいため、可能な限り地面に近い位置にアームが来ている。 &ref(2012b/Member/ddnp/Mission2/arm_gears.jpg, 100%, アームの歯車); 苦労したアームの歯車まわりは、こんな感じ。 &ref(2012b/Member/ddnp/Mission2/itamae_under.jpg, 100%, ロボット下面(三点目の支え)); このロボットも2輪のため、支えるためのパーツが下面に付いている。仕組みは、[[課題1>2012b/ddnp/Mission1]]のロボットの使い回しだ。 &ref(2012b/Member/ddnp/Mission2/itamae_under2.jpg, 100%, ロボット下面2(光センサーの位置)); また、タッチセンサー側への進行が主になるので、光センサーも後ろ側に付いている。RCX本体を支える土台が前に来たのが幸いし、下部をくり抜いて光センサーを取り付けることが出来た。ところで、これでは一体どっちを前と呼べばいいか謎である。だいぶ個性が出たロボットとなった。 とりあえずここでハード面は一旦完成として、プログラムに移ることにする。この時点でロボコン1日前である。(やばい) *必要なプログラム [#g73cf94e] 機体は出来たので、プログラムを考えることとなった。 1つずつ箱を運ぶプログラムを書くので、断片的な動作のプログラムを書いて、それらを組み合わせてやれば良い。その断片的な動作とは、この4つ -箱を感知し掴む -箱を持ち上げ、木を超えさせる(この中で箱を離すプログラムが作られるので、それをコピーして他の場所でも使う予定) -箱をゴール内まで運ぶ(3つ分) -通信を持ちいて箱を置いたことを伝え、また同じ内容を受信する。 こちらのプログラムを、他の三人にお願いした。これらを完成させれば、ライントレスをメインにした移動用のプログラムに組み合わせて、3つ全ての箱を運ぶことが出来る。ライントレスのプログラムは前回の課題で自分の作ったものを改良して使うことにしたので、移動用のプログラムは自分が担当することに。移動用プログラムは以下のとおり。 -スタート地点から出発し、一つ目に辿り着く -箱を受け渡すところまで進む -通信場所まで移動する -渡された箱のある場所まで移動する -渡された箱付近からゴール付近まで戻る - 全体的な流れはこんなかんじを想定していた。 +一つ目の箱に辿り着く +箱を感知し掴む +箱を受け渡すところまで進む +通信場所まで移動 +通信 +渡された箱まで移動 +ゴール付近まで移動 +ゴール内に収める +次の箱に移動 +2〜8を繰り返す これにて、全体的な見通しが立ったため、各自プログラムを作ることに **ライントレスを改良 [#a50f952b] 箱の配置場所からして、境界線のライントレスが線の右側と左側を自由自在に切り替えられる方が良いため、それを制御する変数linesideを追加。また、プログラム全体をルーチン化して、様々な場所に組み込めるように変更。それに合わせて全ての関数をグローバル化し、右回転と左回転の命令をルーチンからマクロに変更。NQCでは、サブルーチンのネストは禁止されているようだ。 /* ############################ 定数定義 ############################ */ // トレス判定系 #define DEEPBLACK 42 // 濃い黒のしきい値 #define BLACK 46 // 黒のしきい値 #define WHITE 50 // 白のしきい値 #define DEEPWHITE 52 // 濃い白のしきい値 #define CORNER_WAIT 2500 // コーナー判定を無視する時間 #define CHANGE_LINE1 2000 // 一回目のライントレス方向変更時間 // 境界トレス用 #define CORNER_TIME 60 // 直角判定する時間 // 黒トレス用 #define SEESIGHT 120 // 黒トレス時のライン捜索時間 /* ############################# # 変数宣言 ############################# */ int clockwise = 3; // 黒トレス時の回転方向を記憶 奇数は時計回り int rad_increase = 1; // 黒トレス時の反転回数 倍角変数にもなる int corner_times = 0; // コーナーを曲がった回数 int backturn = 0; // シュート時の角度補正用 int corner_wait = -2300; // コーナーを曲がった時の時間 int lineside = 1; // 境界トレスの境界線を切り替え 奇数で右側トレス /* ############################# # マクロ ############################# */ #define RotateRight OnFwd(OUT_A); OnRev(OUT_C); // 時計回り #define RotateLeft OnFwd(OUT_C); OnRev(OUT_A); // 反時計回り /* ############################# # 境界トレス # ルーチン ############################# */ sub border_trace() { // 白側→黒側 if(SENSOR_1 > DEEPWHITE) { // 薄くなるまで回転 while(SENSOR_1 > WHITE) { if(lineside % 2) { // 左小回転 RotateLeft; } else { // 右小回転 RotateRight; } } } // 黒側→白側 else if(SENSOR_1 < DEEPBLACK) { ClearTimer(2); // 旋回時間を測定 // 薄くなるまで回転 while(SENSOR_1 < BLACK) { if(lineside % 2) { // 右小回転 RotateRight; } else { // 左小回転 RotateLeft; } } // コーナーを判定且つ以前コーナーに来てからしばらく経過している場合 if(FastTimer(2) >= CORNER_TIME && FastTimer(0) >= corner_wait+CORNER_WAIT) { backturn = FastTimer(2)+5; // 曲がるのにかかった時間を記憶 corner_times++; // コーナーの数を数える PlayTone(440, 30); // コーナーの数を数えたことを知らせる corner_wait = FastTimer(0); // コーナーを曲がったときの時間を記憶 } } else { OnFwd(OUT_AC); // 常に前進 } } /* ############################# # 黒トレス # ルーチン ############################# */ sub line_trace() { // 完全に白い場所を感知した時、方向修正 if(SENSOR_1 >= WHITE) { rad_increase = 1; // 倍角変数を初期化 // 黒ラインに入るまで処理。黒ラインが見当たらないとき一度処理を抜ける while(SENSOR_1 >= WHITE && rad_increase <= 3) { ClearTimer(1); // タイマーリセット // 黒ラインに入るまで、且つ旋回時間が過ぎるまで旋回 while((SENSOR_1 >= BLACK) && (FastTimer(1) <= SEESIGHT * rad_increase)) { // 時計回り if(clockwise % 2) { RotateRight; } // 反時計回り else if(!(clockwise % 2)) { RotateLeft; } } // 白い場所から脱出できなかった場合 if(SENSOR_1 >= BLACK) { clockwise ++; // 回転方向を変更 rad_increase += 1; // 回転時間を増加 } } } else { OnFwd(OUT_AC); // 常に前進 } } ロボットの速度に合わせて、感知する黒の領域も、41付近から43付近まで上げて細かいトレースを行うようにした。この時点では、境界トレス時、黒の直角を感知するプログラムは残してあるだけで、ほとんどいじってはいない。とりあえず感知を行うか実験した時の名残で、直角判定する時間CORNER_TIMEを増加させてあるだけだ。タイマーも同じくいじっていない。 とりあえずここまで完成させて、後の不具合は組み込みながら調整していくことにした。 **このあたりまで来て気づいた重要なこと:ソフト面 [#hfc46717] 他の人達がちらほらプログラムを完成させてきたところで、ある重要な点に気づいた。「1つずつ入れ替えていったら、ただ入れ替えるだけでは2箱目の入れ替えから既に入れ替えた箱が邪魔になるのでは…。」そう、自分が最初に3つ箱を渡した後、それをゴールに置いていく方法を提案したのは、これが理由だったのを本人が忘れていたというドジをやったのだ。これではプログラムを書き換えるしか無い。だが時間も残り僅かで、このまま開発していってもそもそも1つも運べない可能性すらあった。なのでとりあえず一つ運べてから考えることにして、次に進むことにした。 **このあたりまで来て気づいた重要なこと:ハード面 [#e3e5e86f] ここで、箱を感知してから半回転する動作は、回転時間を定数で与えて管理していたのだが、その時間で思わぬことが起こる。コースの回転する場所によって、一周の回転時間が10〜16秒ほど大きく変動するのだ。 &ref(2012b/Member/ddnp/Mission2/itamae_under.jpg, 100%, ロボット下面(三点目の支え)); この理由は、第三の支えとなっているパーツが原因であると思う。コースは紙で出来ているため、折り曲げて持ち歩いていた。そのせいで、コースに凹凸が出来、図の黒い円形の部分が折り目に引っかかっるようであった。しかも、紙パックを掴んで回転させてみると、紙パック自体もコースの凹凸に引っかかり、時には本体が動かなくなる自体が発生。 &ref(2012b/Member/ddnp/Mission2/course_register.jpg, 100%, コース上でライントレスの障害となる部分); さらにその上、本体が大きすぎてライントレスをしようとしても、真ん中の仕切り棒に当たって動かなくなってしまう区間が確認された。一応、三点目の支えを可能な限り車輪側に寄せ、バンパーをRCX側に寄せてみたが、それくらいの変化ではどちらも改善する気配なし。この時にはもう時間もアイディアもなかったので、結果プログラムでどうにかしようとして、泥沼にはまっていく。 **定数では不確実 [#s5da0e85] 上で述べたように、障害を全てプログラムを乗り越えようとしたため、どうにもならない事態に突入する。回転する秒数を1.2秒、2.0秒、4.0秒、モーターパワーを上げて2.0秒、1.6秒、1.2秒…と、定数と出力を変えてどんどん試してみる。だがやはり、場所によって回転するしないが出てきてしまうので毎回毎回調整する始末。回り過ぎる時もあれば、あまり回らない時もある。しかも、紙パックを持っている時でも値が変わってくるので、もうほとんど絶望状態に陥る。やはり、何もかも値を与えて動作させるには無理がある、という結論に達した。ハードで良いアイディアが出るか、ソフトでうまい方法を思いつくかを定数をいじりながら待っていたが、無念にもタイムアップした。ライントレスなどを使って、出来る限り精密性を確保しながら、個々の場合に応じて時間の値を与えるべきであったと感じる。もしくは、グローバル変数を用いてサブルーチンを作り、処理の段階に応じて異なる時間定数を代入していけば良かった。と、今頃になって改善策を思いつく始末である。 *最終的なプログラム [#h61895ed] 結局、一つ目の箱を仕切り棒の向こう側に渡すところまでしか完成しなかった。 //------------------------------------------------// // // Title : Box_exchanger // // Usage : ライントレスを行いながら // 真ん中のしきりをクリアして // 左右別々のコースにある // 白と黒の箱を交換する // //------------------------------------------------// /* ############################ 定数定義 ############################ */ // トレス判定系 #define DEEPBLACK 42 // 濃い黒のしきい値 #define BLACK 46 // 黒のしきい値 #define WHITE 50 // 白のしきい値 #define DEEPWHITE 52 // 濃い白のしきい値 #define CORNER_WAIT 2500 // コーナー判定を無視する時間 #define CHANGE_LINE1 2000 // 一回目のライントレス方向変更時間 // 境界トレス用 #define CORNER_TIME 60 // 直角判定する時間 // 黒トレス用 #define SEESIGHT 120 // 黒トレス時のライン捜索時間 // 箱をつかむ系 #define QUARTER_TURN_TIME 120 // 1/4回転 #define STAY_BACK_TIME 150 // 持ち上げる際に一度退く時間 #define APPROACH_TIME 80 // 箱に近づく時間 // 箱を渡す系 #define BOXED_TURN_TIME 270 // 箱をもっての回転時間 #define PUSH_TIME 60 // 箱を押す時間 // アーム処理系 #define OPEN_TIME 320 // アームを完全に開閉する時間 #define KEEP_TIME 80 // 箱を完全につまむまでの時間 #define RAISEUP_TIME 100 // アームを持ち上げる時間 /* ############################# # 変数宣言 ############################# */ int clockwise = 3; // 黒トレス時の回転方向を記憶 奇数は時計回り int rad_increase = 1; // 黒トレス時の反転回数 倍角変数にもなる int corner_times = 0; // コーナーを曲がった回数 int backturn = 0; // 角を曲がった度数補正用 int corner_wait = -2300; // コーナーを曲がった時の時間 int lineside = 1; // 境界トレスの側を切り替え 奇数で右側トレス /* ############################# # マクロ ############################# */ #define RotateRight OnFwd(OUT_A); OnRev(OUT_C); // 時計回り #define RotateLeft OnFwd(OUT_C); OnRev(OUT_A); // 反時計回り /* ############################# # 境界トレス # ルーチン ############################# */ sub border_trace() { // 白側→黒側 if(SENSOR_1 > DEEPWHITE) { // 薄くなるまで回転 while(SENSOR_1 > WHITE) { if(lineside % 2) { // 左小回転 RotateLeft; } else { // 右小回転 RotateRight; } } } // 黒側→白側 else if(SENSOR_1 < DEEPBLACK) { ClearTimer(2); // 旋回時間を測定 // 薄くなるまで回転 while(SENSOR_1 < BLACK) { if(lineside % 2) { // 右小回転 RotateRight; } else { // 左小回転 RotateLeft; } } // コーナーを判定且つ以前コーナーに来てからしばらく経過している場合 if(FastTimer(2) >= CORNER_TIME && FastTimer(0) >= corner_wait+CORNER_WAIT) { backturn = FastTimer(2)+5; // 曲がるのにかかった時間を記憶 corner_times++; // コーナーの数を数える PlayTone(440, 30); // コーナーの数を数えたことを知らせる corner_wait = FastTimer(0); // コーナーを曲がったときの時間を記憶 } } else { OnFwd(OUT_AC); // 常に前進 } } /* ############################# # 黒トレス # ルーチン ############################# */ sub line_trace() { // 完全に白い場所を感知した時、方向修正 if(SENSOR_1 >= WHITE) { rad_increase = 1; // 倍角変数を初期化 // 黒ラインに入るまで処理。黒ラインが見当たらないとき一度処理を抜ける while(SENSOR_1 >= WHITE && rad_increase <= 3) { ClearTimer(1); // タイマーリセット // 黒ラインに入るまで、且つ旋回時間が過ぎるまで旋回 while((SENSOR_1 >= BLACK) && (FastTimer(1) <= SEESIGHT * rad_increase)) { // 時計回り if(clockwise % 2) { RotateRight; } // 反時計回り else if(!(clockwise % 2)) { RotateLeft; } } // 白い場所から脱出できなかった場合 if(SENSOR_1 >= BLACK) { clockwise ++; // 回転方向を変更 rad_increase += 1; // 回転時間を増加 } } } else { OnFwd(OUT_AC); // 常に前進 } } /* ############################# # 箱掴みルーチン ############################# */ sub box_catch() { // 一時後退 OnRev(OUT_AC); Wait(STAY_BACK_TIME); // 半周回転 SetPower(OUT_AC, 3); // 回転時のモーター動力確保のため、動力を少々上げる OnRev(OUT_A); OnFwd(OUT_C); Wait(QUARTER_TURN_TIME*2); Off(OUT_AC); SetPower(OUT_AC, 2); // 動力戻す // アーム開 OnFwd(OUT_B); Wait(OPEN_TIME); // 手前引っ張り修正 OnRev(OUT_AC); Wait(STAY_BACK_TIME); Off(OUT_AC); // ここまで箱に向かう処理 OnRev(OUT_B); Wait(OPEN_TIME + KEEP_TIME); Off(OUT_B); // ここまで箱を軽く掴む OnFwd(OUT_AC); Wait(STAY_BACK_TIME); Off(OUT_AC); // 少し後ろに引っ張る OnFwd(OUT_B); Wait(OPEN_TIME); Off(OUT_B); // 引っ張る OnRev(OUT_AC); Wait(APPROACH_TIME); Off(OUT_AC); OnRev(OUT_B); Wait(OPEN_TIME + KEEP_TIME); Off(OUT_B); // 処理終了 Off(OUT_ABC); SetPower(OUT_AC, 2); SetPower(OUT_B, 7); } /* ############################# # 箱乗り上げルーチン ############################# */ sub box_throw() { // 動力UPのため一度全体のモーターを初期化 SetPower(OUT_AC, 3); SetPower(OUT_B, 7); // 一時後退 OnRev(OUT_AC); Wait(STAY_BACK_TIME); // 半周回転 SetPower(OUT_AC, 4); OnRev(OUT_C); OnFwd(OUT_A); Wait(BOXED_TURN_TIME); Off(OUT_AC); SetPower(OUT_AC, 3); // アーム持ち上げと同時に直進 OnRev(OUT_B); OnRev(OUT_AC); Wait(RAISEUP_TIME); Off(OUT_B); // さらに押す Wait(PUSH_TIME); Off(OUT_AC); // 箱を離す OnFwd(OUT_B); Wait(OPEN_TIME); // 二回押して確実に向こう側に渡してみる repeat(2) { // 退いて OnFwd(OUT_AC); Wait(PUSH_TIME); Off(OUT_AC); OnRev(OUT_B); Wait(OPEN_TIME); // 押す OnRev(OUT_AC); Wait(PUSH_TIME); Off(OUT_AC); OnFwd(OUT_B); Wait(OPEN_TIME); } // 処理終了 Off(OUT_ABC); SetPower(OUT_AC, 2); SetPower(OUT_B, 7); } /* ################################ # # メイン関数 # ################################ */ task main() { //-------------------------// // 初期化設定 //-------------------------// SetSensor(SENSOR_1, SENSOR_LIGHT); // センサー1に光センサーをセット SetSensor(SENSOR_2, SENSOR_TOUCH); // センサー2にタッチセンサーをセット SetPower(OUT_AC, 2); // 走行動力を設定 SetPower(OUT_B, 7); // アーム動力を設定 ClearTimer(0); // プログラムを走らせてからの時間を計測 //------------------------// // スタート // ↓ // 一つ目の箱 //------------------------// // 箱に向かって直進 until(SENSOR_2) { OnFwd(OUT_AC); } Off(OUT_AC); // 箱を取得 box_catch(); //------------------------// // 一つ目の箱 // ↓ // 板乗り越え //------------------------// // 黒いラインまで前進 until(SENSOR_1 < DEEPBLACK) { OnFwd(OUT_AC); } Off(OUT_AC); // 仕切り棒を感知するか、ライン右側に移動するまで黒ライントレス until(SENSOR_2 || !(clockwise % 2)) { line_trace(); } // 仕切り棒を感知するまでライン右側を境界トレス until(SENSOR_2) { border_trace(); } // 箱を仕切り棒の向こう側に置く box_throw(); // 動作を停止 Off(OUT_ABC); } 自分が作ったプログラムを実際にロボットに組み込んで試したいという要望から、製作中のプログラムを渡してみたら、各々が改良を始めてしまい、2台のロボットで使いまわすはずのプログラムが結果的に二つに分裂してしまった。チーム内でうまく連携が取れなかったのは少々痛いところがある。以下、このプログラムについての補足のコメント -全体が流れるような処理になったため、until文を使用して上から下に順番に処理が実行されるようにした。動作の流れが見やすいので、プログラムを追いやすい。組んでいて思ったが、RCXに関してはこういう書き方が標準なのかもしれない。 -箱を掴む動作のプログラムは、ただ単純にアームを閉じるだけだと他の箱にアームが当たる恐れがあったので、一度側面をつまんで引きずるよう改変 -同じく箱を乗り越えさせる処理も、二回ほど押しこむ動作を追加 -ライントレス中に、仕切り棒に当たって動かなくなる現象を逆利用。ライントレスをしていればほぼ垂直な状況で仕切り棒に接触できるので、後は半回転して落とせば良い様に変更 ところで、掲載のために見返しをしてみたらコメント文が酷いことになってた。だいぶ疲れてたらしい。記憶に無い。 #define PUSH_TIME 60 // 箱を♂時間 //-------------------------// // 初期化設定 //-------------------------// SetSensor(SENSOR_1, SENSOR_LIGHT); // ヒカリセンサー1起動!!! SetSensor(SENSOR_2, SENSOR_TOUCH); // タッチセンサー2初期化!!! SetPower(OUT_AC, 2); // モーター動力カクニン!!! SetPower(OUT_B, 7); // アームモーターエネルギー充填!! ClearTimer(0); // 3,2,1!ハッシン!!!(ドゥムゥッ...ッ.......(シュパーン)... *総括 [#sabe06f6] **反省点 [#t9b09147] 反省点としては、というか反省点しか無い気もするのだが、以下に纏める。 ***チームの連携 [#a20b633e] 今回の課題については、特にチームの連携が必要だったと実感した。まずは時間である。全体を通してチームが集まれる時間があまり取れなかった上、その時間帯に全員が揃うことも珍しかった。そのようなこともあり、制作は順調には進まなかった。ほとんどの場合、自分が一つロボットを持って帰っていたので、個人でもうすこし修正を入れてみるべきだったように思う。これはプログラムに関しても同じ事が言える。次に、役割分担がうまくいかなかった所である。作成してもらったプログラムに関しても、結局個人で修正を加えてしまうような無駄なことをしていた。それならば最初から自分で作ったほうが効率が良いわけである。先に、チーム全体で見通しを立てて、実装すべき箇所を十分に検討・理解すべきであった。その点から見ると、その次に悪かったところは話し合いが上手く取れなかったところにあるとも言える。 ***時間配分 [#m4d8a01d] 先生に再三「ハードでなくソフトに時間をかけてほしい」と言われていたのに、それについて全く理解していなかったように思う。そして、今回それを実感した。もう実践する機会も無いであろうが、時間配分は本当に重要だと思う。自分の性格上、私生活にも言えたりする。 **総括 [#g529d877] 結局ロボコンでは、箱すらも運べなかった。本番当日になってまでも、新しい問題が浮上する始末であった。既存の問題としては、最後になってウォームギアまわりの空回り現象が再発するようなロボットであり、LEGOに対しても無理がかかるし、自分たちにとっても少しむずかしいロボット作り出会ったように思う。最適解を見つけるには、複雑な方法でなくてもいいということを実感した。無理に複雑な方法を利用しなくても、それを達成できる方が重要だということが身にしみてわかったように思う。けれど、難しい課題であったために、アームの持ち上げ問題に対しては結構夢中になって取り組めた。個人的には、そこでの問題をこれだけ解決出来たことについてはとても充実感を感じる。最後のロボコンで、このロボットについた名前「板前」の由来を、全員の前で見せられなかったことが心残りである。ちなみにその由来は、箱を下ろす際に「バンッ」と勢いよく振り下ろす様子に合わせて、自分が「ヘイおまち!」と言い出したところに由来している。 &ref(2012b/Member/ddnp/Mission2/itamae.jpg, 100%, ロボット「板前」); ***ちなみに、本番当日になって発生した問題とは [#sa5e48df] 書くところがないため、ここにそれを纏めるが、開ききったアームが持ち上がってしまうというものである &ref(2012b/Member/ddnp/Mission2/arm_close.jpg, 100%, 閉まっているアーム); アームが閉まっている最中に持ち上げる時は問題がないのだが、 &ref(2012b/Member/ddnp/Mission2/arm_open.jpg, 100%, 開いているアーム); 図のようにアームが全開になっている時にアームを締めようとすると、モーメントの関係でアームを締める力よりアーム自体の自重の方が軽くなってしまう。そこで、開いたままアームが持ち上がり、紙パックを挟めないという問題であった。即席で飾りのおもりも付けてみたが、アーム先端部分だったため開いている時はそれも意味なし。付けてみるべきはアームの根本であったと今頃になって思う。 *おまけ [#idfb4a54] RCX本体からは、1音しか出すことが出来ない。以前、「RCXで複数音出せないか」と試みた際に作った楽曲再生プログラムが埋もれていたため、通信を習った直後に2台使って演奏させてみたことがある。そのプログラム自体を掲載すると、もしかしたら課題より長いかもしれないくらい気合が入っているので、このページに添付するだけにする。(bigbridgeA.nqc、bigbridgeB.nqc)結果的に、演奏パターンがメロディとベースに別れてから全く同期しなくなるので、命令一つ一つにラグが生じていることが分かった。これより、2台でミッションをこなすときも、それぞれのロボットに合わせて多少プログラムが変更されるので、絶対に同期をとったほうが良いと、私は思っている。 進む +通信場所まで移動 +通信 +渡された箱まで移動 +ゴール付近まで移動 +ゴール内に収める +次の箱に移動 +2〜8を繰り返す これにて、全体的な見通しが立ったため、各自プログラムを作ることに。 **ライントレスを改良 [#uc700fcd] 箱の配置場所からして、境界線のライントレスが線の右側と左側を自由自在に切り替えられる方が良いため、それを制御する変数linesideを追加。また、プログラム全体をルーチン化して、様々な場所に組み込めるように変更。それに合わせて全ての関数をグローバル化し、右回転と左回転の命令をルーチンからマクロに変更。NQCでは、サブルーチンのネストは禁止されているようだ。 /* ############################ 定数定義 ############################ */ // トレス判定系 #define DEEPBLACK 42 // 濃い黒のしきい値 #define BLACK 46 // 黒のしきい値 #define WHITE 50 // 白のしきい値 #define BLACK 46 // 黒のしきい値 #define WHITE 50 // 白のしきい値 #define DEEPWHITE 52 // 濃い白のしきい値 #define CORNER_WAIT 2500 // コーナー判定を無視する時間 #define CORNER_WAIT 2500 // コーナー判定を無視する時間 #define CHANGE_LINE1 2000 // 一回目のライントレス方向変更時間 // 境界トレス用 #define CORNER_TIME 60 // 直角判定する時間 // 黒トレス用 #define SEESIGHT 120 // 黒トレス時のライン捜索時間 #define SEESIGHT 120 // 黒トレス時のライン捜索時間 /* ############################# # 変数宣言 ############################# */ int clockwise = 3; // 黒トレス時の回転方向を記憶 奇数は時計回り int rad_increase = 1; // 黒トレス時の反転回数 倍角変数にもなる int corner_times = 0; // コーナーを曲がった回数 int backturn = 0; // シュート時の角度補正用 int corner_wait = -2300; // コーナーを曲がった時の時間 int lineside = 1; // 境界トレスの境界線を切り替え 奇数で右側トレス int clockwise = 3; // 黒トレス時の回転方向を記憶 奇数は時計回り int rad_increase = 1; // 黒トレス時の反転回数 倍角変数にもなる int corner_times = 0; // コーナーを曲がった回数 int backturn = 0; // シュート時の角度補正用 int corner_wait = -2300; // コーナーを曲がった時の時間 int lineside = 1; // 境界トレスの境界線を切り替え 奇数で右側トレス /* ############################# # マクロ ############################# */ #define RotateRight OnFwd(OUT_A); OnRev(OUT_C); // 時計回り #define RotateRight OnFwd(OUT_A); OnRev(OUT_C); // 時計回り #define RotateLeft OnFwd(OUT_C); OnRev(OUT_A); // 反時計回り /* ############################# # 境界トレス # ルーチン ############################# */ sub border_trace() { // 白側→黒側 if(SENSOR_1 > DEEPWHITE) { // 薄くなるまで回転 while(SENSOR_1 > WHITE) { if(lineside % 2) { // 左小回転 RotateLeft; } else { // 右小回転 RotateRight; } } } // 黒側→白側 else if(SENSOR_1 < DEEPBLACK) { ClearTimer(2); // 旋回時間を測定 // 薄くなるまで回転 while(SENSOR_1 < BLACK) { if(lineside % 2) { // 右小回転 RotateRight; } else { // 左小回転 RotateLeft; } } // コーナーを判定且つ以前コーナーに来てからしばらく経過している場合 if(FastTimer(2) >= CORNER_TIME && FastTimer(0) >= corner_wait+CORNER_WAIT) { backturn = FastTimer(2)+5; // 曲がるのにかかった時間を記憶 corner_times++; // コーナーの数を数える PlayTone(440, 30); // コーナーの数を数えたことを知らせる corner_wait = FastTimer(0); // コーナーを曲がったときの時間を記憶 } } else { OnFwd(OUT_AC); // 常に前進 } } /* ############################# # 黒トレス # ルーチン ############################# */ sub line_trace() { // 完全に白い場所を感知した時、方向修正 if(SENSOR_1 >= WHITE) { rad_increase = 1; // 倍角変数を初期化 // 黒ラインに入るまで処理。黒ラインが見当たらないとき一度処理を抜ける while(SENSOR_1 >= WHITE && rad_increase <= 3) { ClearTimer(1); // タイマーリセット // 黒ラインに入るまで、且つ旋回時間が過ぎるまで旋回 while((SENSOR_1 >= BLACK) && (FastTimer(1) <= SEESIGHT * rad_increase)) { // 時計回り if(clockwise % 2) { RotateRight; } // 反時計回り else if(!(clockwise % 2)) { RotateLeft; } } // 白い場所から脱出できなかった場合 if(SENSOR_1 >= BLACK) { clockwise ++; // 回転方向を変更 rad_increase += 1; // 回転時間を増加 } } } else { OnFwd(OUT_AC); // 常に前進 } } ロボットの速度に合わせて、感知する黒の領域も、41付近から43付近まで上げて細かいトレースを行うようにした。この時点では、境界トレス時、黒の直角を感知するプログラムは残してあるだけで、ほとんどいじってはいない。とりあえず感知を行うか実験した時の名残で、直角判定する時間CORNER_TIMEを40から60へ増加させてあるだけだ。タイマーも同じくいじっていない。 とりあえずここまで完成させて、後の不具合は組み込みながら調整していくことにした。 **このあたりまで来て気づいた重要なこと:ソフト面 [#l9356db2] 他の人達がちらほらプログラムを完成させてきたところで、ある重要な点に気づいた。「1つずつ入れ替えていったら、ただ入れ替えるだけでは2箱目の入れ替えから既に入れ替えた箱が邪魔になるのでは…。」そう、自分が最初に3つ箱を渡した後、それをゴールに置いていく方法を提案したのは、これが理由だったのを本人が忘れていたというドジをやったのだ。だが時間も残り僅かで、このまま開発していってもそもそも1つも運べない可能性すらあった。なのでとりあえず一つ運べてから考えることにして、次に進むことにした。 **このあたりまで来て気づいた重要なこと:ハード面 [#p5dbb2f7] ここで、箱を感知してから半回転する動作は、回転時間を定数で与えて管理していたのだが、その時間で思わぬことが起こる。コースの回転する場所によって、一周の回転時間が10〜16秒ほど大きく変動するのだ。 &ref(2012b/Member/ddnp/Mission2/itamae_under.jpg, 100%, ロボット下面(三点目の支え)); この理由は、第三の支えとなっているパーツが原因であると思う。コースは紙で出来ているため、折り曲げて持ち歩いていた。そのせいで、コースに凹凸が出来、図の黒い円形の部分が折り目に引っかかっるようであった。しかも、紙パックを掴んで回転させてみると、紙パック自体もコースの凹凸に引っかかり、時には本体が動かなくなる自体が発生。 &ref(2012b/Member/ddnp/Mission2/course_register.jpg, 100%, コース上でライントレスの障害となる部分); さらにその上、本体が大きすぎてライントレスをしようとしても、真ん中の仕切り棒に当たって動かなくなってしまう区間が確認された。一応、三点目の支えを可能な限り車輪側に寄せ、バンパーをRCX側に寄せてみたが、それくらいの変化ではどちらも改善する気配なし。この時にはもう時間もアイディアもなかったので、結果プログラムでどうにかしようとして、泥沼にはまっていく。 **定数では不確実 [#t1fb9051] 上で述べたように、障害を全てプログラムで乗り越えようとしたため、どうにもならない事態に突入する。回転する秒数を1.2秒、2.0秒、4.0秒、モーターパワーを上げて2.0秒、1.6秒、1.2秒…と、定数と出力を変えてどんどん試してみる。だがやはり、場所によって回転するしないが出てきてしまうので毎回毎回調整する始末。回り過ぎる時もあれば、あまり回らない時もある。しかも、紙パックを持っている条件下ではさらに値が変わってくるので、もうほとんど絶望的。やはり、何もかも値を与えて動作させるには無理がある、という結論に達した。ハードで良いアイディアが出るか、ソフトでうまい方法を思いつくかを定数をいじりながら待っていたが、無念にもタイムアップした。ライントレスなどを使って、出来る限り精密性を確保しながら、個々の場合に応じて時間の値を与えるべきであったと感じる。もしくは、グローバル変数を用いてサブルーチンを作り、処理の段階に応じて異なる時間定数を代入していけば良かった。と、今頃になって改善策を思いつく始末である。 *最終的なプログラム [#y243c171] 結局、一つ目の箱を仕切り棒の向こう側に渡すところまでしか完成しなかった。 //------------------------------------------------// // // Title : Box_exchanger // // Usage : ライントレスを行いながら // 真ん中のしきりをクリアして // 左右別々のコースにある // 白と黒の箱を交換する // //------------------------------------------------// /* ############################ 定数定義 ############################ */ // トレス判定系 #define DEEPBLACK 42 // 濃い黒のしきい値 #define DEEPBLACK 42 // 濃い黒のしきい値 #define BLACK 46 // 黒のしきい値 #define WHITE 50 // 白のしきい値 #define DEEPWHITE 52 // 濃い白のしきい値 #define DEEPWHITE 52 // 濃い白のしきい値 #define CORNER_WAIT 2500 // コーナー判定を無視する時間 #define CHANGE_LINE1 2000 // 一回目のライントレス方向変更時間 #define CHANGE_LINE1 2000 // 一回目のライントレス方向変更時間 // 境界トレス用 #define CORNER_TIME 60 // 直角判定する時間 // 黒トレス用 #define SEESIGHT 120 // 黒トレス時のライン捜索時間 #define SEESIGHT 120 // 黒トレス時のライン捜索時間 // 箱をつかむ系 #define QUARTER_TURN_TIME 120 // 1/4回転 #define STAY_BACK_TIME 150 // 持ち上げる際に一度退く時間 #define APPROACH_TIME 80 // 箱に近づく時間 #define QUARTER_TURN_TIME 120 // 1/4回転 #define STAY_BACK_TIME 150 // 持ち上げる際に一度退く時間 #define APPROACH_TIME 80 // 箱に近づく時間 // 箱を渡す系 #define BOXED_TURN_TIME 270 // 箱をもっての回転時間 #define PUSH_TIME 60 // 箱を押す時間 #define BOXED_TURN_TIME 270 // 箱をもっての回転時間 #define PUSH_TIME 60 // 箱を押す時間 // アーム処理系 #define OPEN_TIME 320 // アームを完全に開閉する時間 #define KEEP_TIME 80 // 箱を完全につまむまでの時間 #define RAISEUP_TIME 100 // アームを持ち上げる時間 #define OPEN_TIME 320 // アームを完全に開閉する時間 #define KEEP_TIME 80 // 箱を完全につまむまでの時間 #define RAISEUP_TIME 100 // アームを持ち上げる時間 /* ############################# # 変数宣言 ############################# */ int clockwise = 3; // 黒トレス時の回転方向を記憶 奇数は時計回り int rad_increase = 1; // 黒トレス時の反転回数 倍角変数にもなる int corner_times = 0; // コーナーを曲がった回数 int backturn = 0; // 角を曲がった度数補正用 int corner_wait = -2300; // コーナーを曲がった時の時間 int lineside = 1; // 境界トレスの側を切り替え 奇数で右側トレス int clockwise = 3; // 黒トレス時の回転方向を記憶 奇数は時計回り int rad_increase = 1; // 黒トレス時の反転回数 倍角変数にもなる int corner_times = 0; // コーナーを曲がった回数 int backturn = 0; // 角を曲がった度数補正用 int corner_wait = -2300; // コーナーを曲がった時の時間 int lineside = 1; // 境界トレスの側を切り替え 奇数で右側トレス /* ############################# # マクロ ############################# */ #define RotateRight OnFwd(OUT_A); OnRev(OUT_C); // 時計回り #define RotateRight OnFwd(OUT_A); OnRev(OUT_C); // 時計回り #define RotateLeft OnFwd(OUT_C); OnRev(OUT_A); // 反時計回り /* ############################# # 境界トレス # ルーチン ############################# */ sub border_trace() { // 白側→黒側 if(SENSOR_1 > DEEPWHITE) { // 薄くなるまで回転 while(SENSOR_1 > WHITE) { if(lineside % 2) { // 左小回転 RotateLeft; } else { // 右小回転 RotateRight; } } } // 黒側→白側 else if(SENSOR_1 < DEEPBLACK) { ClearTimer(2); // 旋回時間を測定 // 薄くなるまで回転 while(SENSOR_1 < BLACK) { if(lineside % 2) { // 右小回転 RotateRight; } else { // 左小回転 RotateLeft; } } // コーナーを判定且つ以前コーナーに来てからしばらく経過している場合 if(FastTimer(2) >= CORNER_TIME && FastTimer(0) >= corner_wait+CORNER_WAIT) { backturn = FastTimer(2)+5; // 曲がるのにかかった時間を記憶 corner_times++; // コーナーの数を数える PlayTone(440, 30); // コーナーの数を数えたことを知らせる PlayTone(440, 30); // コーナーの数を数えたことを知らせる corner_wait = FastTimer(0); // コーナーを曲がったときの時間を記憶 } } else { OnFwd(OUT_AC); // 常に前進 } } /* ############################# # 黒トレス # ルーチン ############################# */ sub line_trace() { // 完全に白い場所を感知した時、方向修正 if(SENSOR_1 >= WHITE) { rad_increase = 1; // 倍角変数を初期化 // 黒ラインに入るまで処理。黒ラインが見当たらないとき一度処理を抜ける while(SENSOR_1 >= WHITE && rad_increase <= 3) { ClearTimer(1); // タイマーリセット ClearTimer(1); // タイマーリセット // 黒ラインに入るまで、且つ旋回時間が過ぎるまで旋回 while((SENSOR_1 >= BLACK) && (FastTimer(1) <= SEESIGHT * rad_increase)) { // 時計回り if(clockwise % 2) { RotateRight; } // 反時計回り else if(!(clockwise % 2)) { RotateLeft; } } // 白い場所から脱出できなかった場合 if(SENSOR_1 >= BLACK) { clockwise ++; // 回転方向を変更 clockwise ++; // 回転方向を変更 rad_increase += 1; // 回転時間を増加 } } } else { OnFwd(OUT_AC); // 常に前進 OnFwd(OUT_AC); // 常に前進 } } /* ############################# # 箱掴みルーチン ############################# */ sub box_catch() { // 一時後退 OnRev(OUT_AC); Wait(STAY_BACK_TIME); // 半周回転 SetPower(OUT_AC, 3); // 回転時のモーター動力確保のため、動力を少々上げる OnRev(OUT_A); OnFwd(OUT_C); Wait(QUARTER_TURN_TIME*2); Off(OUT_AC); SetPower(OUT_AC, 2); // 動力戻す // アーム開 OnFwd(OUT_B); Wait(OPEN_TIME); // 手前引っ張り修正 OnRev(OUT_AC); Wait(STAY_BACK_TIME); Off(OUT_AC); // ここまで箱に向かう処理 OnRev(OUT_B); Wait(OPEN_TIME + KEEP_TIME); Off(OUT_B); // ここまで箱を軽く掴む Off(OUT_B); // ここまで箱を軽く掴む OnFwd(OUT_AC); Wait(STAY_BACK_TIME); Off(OUT_AC); // 少し後ろに引っ張る OnFwd(OUT_B); Wait(OPEN_TIME); Off(OUT_B); // 引っ張る OnRev(OUT_AC); Wait(APPROACH_TIME); Off(OUT_AC); OnRev(OUT_B); Wait(OPEN_TIME + KEEP_TIME); Off(OUT_B); // 処理終了 Off(OUT_ABC); SetPower(OUT_AC, 2); SetPower(OUT_B, 7); } /* ############################# # 箱乗り上げルーチン ############################# */ sub box_throw() { // 動力UPのため一度全体のモーターを初期化 SetPower(OUT_AC, 3); SetPower(OUT_B, 7); // 一時後退 OnRev(OUT_AC); Wait(STAY_BACK_TIME); // 半周回転 SetPower(OUT_AC, 4); OnRev(OUT_C); OnFwd(OUT_A); Wait(BOXED_TURN_TIME); Off(OUT_AC); SetPower(OUT_AC, 3); // アーム持ち上げと同時に直進 OnRev(OUT_B); OnRev(OUT_AC); Wait(RAISEUP_TIME); Off(OUT_B); // さらに押す Wait(PUSH_TIME); Off(OUT_AC); // 箱を離す OnFwd(OUT_B); Wait(OPEN_TIME); // 二回押して確実に向こう側に渡してみる repeat(2) { // 退いて OnFwd(OUT_AC); Wait(PUSH_TIME); Off(OUT_AC); OnRev(OUT_B); Wait(OPEN_TIME); // 押す OnRev(OUT_AC); Wait(PUSH_TIME); Off(OUT_AC); OnFwd(OUT_B); Wait(OPEN_TIME); } // 処理終了 Off(OUT_ABC); SetPower(OUT_AC, 2); SetPower(OUT_B, 7); } /* ################################ # # メイン関数 # ################################ */ task main() { //-------------------------// // 初期化設定 //-------------------------// SetSensor(SENSOR_1, SENSOR_LIGHT); // センサー1に光センサーをセット SetSensor(SENSOR_2, SENSOR_TOUCH); // センサー2にタッチセンサーをセット SetPower(OUT_AC, 2); // 走行動力を設定 SetPower(OUT_B, 7); // アーム動力を設定 SetPower(OUT_B, 7); // アーム動力を設定 ClearTimer(0); // プログラムを走らせてからの時間を計測 //------------------------// // スタート // ↓ // 一つ目の箱 //------------------------// // 箱に向かって直進 until(SENSOR_2) { OnFwd(OUT_AC); } Off(OUT_AC); // 箱を取得 box_catch(); //------------------------// // 一つ目の箱 // ↓ // 板乗り越え //------------------------// // 黒いラインまで前進 until(SENSOR_1 < DEEPBLACK) { OnFwd(OUT_AC); } Off(OUT_AC); // 仕切り棒を感知するか、ライン右側に移動するまで黒ライントレス until(SENSOR_2 || !(clockwise % 2)) { line_trace(); } // 仕切り棒を感知するまでライン右側を境界トレス until(SENSOR_2) { border_trace(); } // 箱を仕切り棒の向こう側に置く box_throw(); // 動作を停止 Off(OUT_ABC); } 自分が作ったプログラムを実際にロボットに組み込んで試したいという要望から、製作中のプログラムを渡してみたら、各々が改良を始めてしまい、2台のロボットで使いまわすはずのプログラムが結果的に二つに分裂してしまった。チーム内でうまく連携が取れなかったのは少々痛いところがある。以下、このプログラムについての補足のコメント -全体が流れるような処理になったため、until文を使用して上から下に順番に処理が実行されるようにした。動作の流れが見やすいので、プログラムを追いやすい。組んでいて思ったが、RCXに関してはこういう書き方が標準なのかもしれない。 -箱を掴む動作のプログラムは、ただ単純にアームを閉じるだけだと他の箱にアームが当たる恐れがあったので、一度側面をつまんで引きずるよう改変 -同じく箱を乗り越えさせる処理も、二回ほど押しこむ動作を追加 -ライントレス中に、仕切り棒に当たって動かなくなる現象を逆利用。ライントレスをしていればほぼ垂直な状況で仕切り棒に接触できるので、後は半回転して落とせば良い様に変更 ところで、掲載のために見返しをしてみたらコメント文が酷いことになってた。だいぶ疲れてたらしい。こんなの書いてた気もするが記憶が曖昧…。 #define PUSH_TIME 60 // 箱を♂時間 //-------------------------// // 初期化設定 //-------------------------// SetSensor(SENSOR_1, SENSOR_LIGHT); // ヒカリセンサー1起動!!! SetSensor(SENSOR_2, SENSOR_TOUCH); // タッチセンサー2初期化!!! SetPower(OUT_AC, 2); // モーター動力カクニン!!! SetPower(OUT_B, 7); // アームモーターエネルギー充填!! SetPower(OUT_B, 7); // アームモーターエネルギー充填!! ClearTimer(0); // 3,2,1!ハッシン!!!(ドゥムゥッ...ッ.......(シュパーン)... *総括 [#dff1dc6d] **反省点 [#n10d53c3] 反省点としては、というか反省点しか無い気もするのだが、以下に纏める。 ***チームの連携 [#h24e4616] 今回の課題については、特にチームの連携が必要だったと実感した。まずは時間である。全体を通してチームが集まれる時間があまり取れなかった上、その時間帯に全員が揃うことも珍しかった。そのようなこともあり、制作は順調には進まなかった。ほとんどの場合、自分が一つロボットを持って帰っていたので、個人でもうすこし修正を入れてみるべきだったように思う。これはプログラムに関しても同じ事が言える。次に、役割分担がうまくいかなかった所である。作成してもらったプログラムに関しても、結局個人で修正を加えてしまうような無駄なことをしていた。それならば最初から自分で作ったほうが効率が良いわけである。先に、チーム全体で見通しを立てて、実装すべき箇所を十分に検討・理解すべきであった。その点から見ると、原因は話し合いが上手く取れなかったところにあるとも言える。 ***時間配分 [#nd404fc5] 先生に再三「ハードでなくソフトに時間をかけてほしい」と言われていたのに、それについて全く理解していなかったように思う。そして、今回それを実感した。もう実践する機会も無いであろうが、時間配分は本当に重要だと思う。自分の性格上、私生活にも言えたりする。 **総括 [#y647a26e] 結局ロボコンでは、箱すらも運べなかった。本番当日になってまでも、新しい問題が浮上する始末であった。既存の問題としては、最後になってウォームギアまわりの空回り現象が再発するようなロボットであり、LEGOパーツに対しても無理がかかるし、自分たちにとっても少しむずかしいロボット作りであったように思う。最適解を見つけるには、複雑な方法でなくてもいいということを実感した。無理に複雑な方法を利用しなくても、それを達成できる方が重要だということが身にしみてわかったように思う。けれど、難しい課題であったために、アームの持ち上げ問題に対しては結構夢中になって取り組めた。個人的には、そこでの問題をこれだけ解決出来たことについてはとても充実感を感じる。最後のロボコンで、このロボットについた名前「板前」の由来を、全員の前で見せられなかったことが心残りである。ちなみにその由来は、箱を下ろす際に「バンッ」と勢いよく振り下ろす様子に合わせて、自分が「ヘイおまち!」と言い出したところに由来している。 &ref(2012b/Member/ddnp/Mission2/itamae.jpg, 100%, ロボット「板前」); ***ちなみに、本番当日になって発生した問題とは [#pb2d4707] 書くところがないため、ここにそれを纏めるが、開ききったアームが持ち上がってしまうというものである &ref(2012b/Member/ddnp/Mission2/arm_close.jpg, 100%, 閉まっているアーム); アームが閉まっている最中に持ち上げる時は問題がないのだが、 &ref(2012b/Member/ddnp/Mission2/arm_open.jpg, 100%, 開いているアーム); 図のようにアームが全開になっている時にアームを締めようとすると、モーメントの関係でアームを締める力よりアーム自体の自重の方が小さくなってしまう。そのため、開いたままアームが持ち上がり、紙パックを挟めないという問題であった。即席で飾りのおもりも付けてみたが、アーム先端部分だったため開いている時はそれも意味なし。付けてみるべきはアームの根本であったと今頃になって思う。 *おまけ [#d56f0789] RCX本体からは、1音しか出すことが出来ない。以前、「RCXで複数音出せないか」と試みた際に作った楽曲再生プログラムが埋もれていたため、通信を習った直後に2台使って演奏させてみたことがある。そのプログラム自体を掲載すると、もしかしたら課題より長いかもしれないくらい気合が入っているので、このページに添付するだけにする。(bigbridgeA.nqc、bigbridgeB.nqc)結果的に、演奏パターンがメロディとベースに別れてから全く同期しなくなるので、命令一つ一つにラグが生じていることが分かった。これより、2台でミッションをこなすときも、それぞれのロボットに合わせて多少プログラムが変更されるので、絶対に同期をとったほうが良いと、私は思っている。 RCX本体からは、1音しか出すことが出来ない。以前、「RCXで複数音出せないか」と試みた際に作った楽曲再生プログラムが埋もれていたため、通信を習った直後に2台使って演奏させてみたことがある。そのプログラム自体を掲載すると、もしかしたら課題より長いかもしれないくらい気合が入っているので、このページに添付するだけにする。割と力作。(bigbridgeA.nqc、bigbridgeB.nqc)結果的に、演奏パターンがメロディとベースに別れてから全く同期しなくなるので、命令一つ一つにラグが生じていることが分かった。これより、2台でミッションをこなすときも、それぞれのロボットに合わせて多少プログラムが変更されるので、絶対に同期をとったほうが良いと、私は思っている。