本体を上から撮影。
この機体は主に
大なり小なりそれぞれの部分で工夫があるので、順を追って説明していく。
正面から撮影。
このコップを掴む機構はゼミにて松本先生がお手本として皆にお披露目した物をそのまま真似ている。つまりは大枠の仕組みには手を加えずに、それを補助する多少の工夫を付け加えたのが私たちがしたお仕事である。具体的には、先ずコップに触れて摩擦で固定するタイヤのアームからの高さを弄っている。タイヤの位置が低いと重心の位置からしてコップが回ってしまうからだ。本番で生かされることはなかったが、持ち上げたコップの縁が被せられるコップに当たり角度を垂直にしつつ被さるという仕組みもあった。
さらに画像の下部をご覧いただくと、青い光センサーの傍から左右に伸びる棒が目に入る。これは紙コップに機体が接近する際に、紙コップを光センサー及びアームの正面に誘導するの為の物だ。長すぎると紙コップに予期しない場所で当たってしまい、逆に短すぎると機能を果たさないので多少の調節を行った。
それらとは別に正面の光センサーの位置も調整している。この調整というのが何処にどの様につけるのかという内容なのだが、アームにつけずにドライブベース本体から伸ばした棒につけている。その理由としては、NXTと違い超音波センサーが無いRCXでは離れた位置にある紙コップを認識する為に、光センサーを使うしかないと判断した為だ。アームに光センサーを付けると紙コップを持ち上げる際に光センサーも持ち上がってしまい前方の光を認識する機能を果たさなくなる。そうすると紙コップを持ち上げるまではいいのだが、その紙コップを被せる紙コップを探すの方法がライントレースと時間指定しか使えなくなってしまう。
モーターとアームを繋ぐギアのちょっとした工夫。ゼミ中にも紹介されていたことだが、ギアを回す時間の指定を間違い、力が掛かりすぎてパーツが破損するのを防ぐ白いギアが噛ませてある。このギアは一定以上の力が掛かると空回りするようになっているのだ。そしてモーターに取り付けられている螺旋状の黒いギア、ウォームギア。このウォームギアに普通のギアを噛ませると、一回転でギアの歯を一つ送り出すようになる。これによって紙コップを掴む機構がゆっくりと、尚且つ力強く動くこととなる。
正面からの画像でも見えていたプーリー。この部分は機体の動き自体にはなんら影響しないのだが、人間が機体に手を加える時とても役立つ。その状況なのだが、まずこの機体のコップを掴む機構がモーター一つで動いていることが関係する。通常時はギアが動けば先端のアームが開閉するのだが、そのアームが閉じきるとギアが固定され、回転をそのままに伝えて機構が上に跳ね上がるのだ。その結果、先ずそもそもがウォームギアを取り付けているのもあって、アーム側を動かすとギアを回すのに多大な力が掛かりパーツが破損する。そしてモーター側を回そうにも跳ね上がったアームが邪魔で解体するしかないという状況になる。そこで、モーターとウォームギアの同一軸上にプーリーを取り付けた。このプーリーの役割は跳ね上がったアームが完全にモーター近辺にくっついてしまうのを防ぎ、モーターの軸を回すのに掛かる力を軽減することだ。フィジカル・コンピューティングをするにおいてトライ&エラーは必要不可欠であり、現に私がこの機体を作るときもそうであった。失敗の度に元に戻すのを楽にするこのプーリーは小さい工夫でありながら大きな効果があったことは間違いない。
キャスターの写真。機体の後部、RCX本体の下に取り付けられ重さを支えていたパーツだ。何故普通のタイヤではなくキャスターなのかと言うと、摩擦を出来るだけ少なくする為なのだ。前部にある一対のタイヤでこの機体は動いており、汚い書き方だが後部はケツを振るような構造になっている。前部のタイヤが動くに際して後部は左右に振られることとなるのだが、後部を支えるパーツがキャスターではなくタイヤであった場合、地面との間に強い摩擦が生まれることとなる。この摩擦はパーツに負荷を掛ける上に動きの滑らかさを阻害するので、極力排除したいものなのだ。実際に機械を動かす以上、人は摩擦と振動に頭を悩ませなければならない。
下から撮影。モーター二つが接触している辺りドライブベースがRCXのお手本となる形と違うことが目につく。改造をした理由としては極力機体を小さくして小回りを良くし、紙コップとの無駄な接触を防ぐことがある。そして次にコップを持ち上げる機構の方に伸びる、光センサーのついた棒。この棒の長さ・太さの調整が容易かったのもある。長さは紙コップの判別をする光センサーの位置に影響し、太さはライントレースに使う二つの光センサーが機能するかどうかに影響するので重要だ。
サブ。こちらの機体は、機体本体の構造にもプログラムにも触れていなかったので私に説明できることはない。私の親愛なる班メンバー三人のページを見ていただければ幸いである。
赤色の矢印が私が作ったメイン機体、青色の矢印がサブ機体の移動経路であるとして上の図を見て頂きたい。メイン機体がコースを横に走る一本のラインを中心に動き紙コップをはめていき、サブ機が紙コップB・Cをメイン機が通るライン上に揃えていくというのが大まかな流れだ。順序立てて説明すると
ただこれだけである。肝としては、メイン機体が紙コップの色で数字を判別の後、紙コップ2近くの交差点でルートを数字に応じて変更することだろうか。
全般に同じ内容を繰り返し過ぎで頭の悪いプログラム。ライントレースをサブルーチンにするのではなく、インライン関数にして他のサブルーチンに引き込めば相当小さくなるのではなかろうか。打ち込む時点でRCXから見てマクロとインライン関数に大差がないということを知っていたならば、まだマシなプログラムを作られただろう。
#define set_power_L SetPower(OUT_A,1);SetPower(OUT_C,1);//タイヤの速度を落とすのに使う #define THRESHOLD 45//閾値 #define go_forward OnFwd(OUT_AC);//前進 #define turn_right1 OnFwd(OUT_A);Off(OUT_C);//右折 #define turn_left1 Off(OUT_A);OnFwd(OUT_C);//左折 #define STEP 1 #define U_turn OnFwd(OUT_A);OnRev(OUT_C);Wait(180);Off(OUT_AC);
今回は光センサー二つでラインを挟むようにしてライントレースし、尚且つ交差点判別が容易になったので右旋回と左旋回を削除した。しかし、結局右左90度に旋回することが多々あったのでそれぞれ作るべきだったのではないかと思う。
/*コップを持ち上げる、下ろす動きをマクロ化*/ #define cup_up OnFwd(OUT_B);Wait(130);Off(OUT_B); #define cup_down OnRev(OUT_B);Wait(130);Off(OUT_B); /*グローバル変数。コップの色に応じて1〜3の値をとり、点Aからのパターン分岐を決める。*/ int number=0;
グローバル変数は全てのタスクに作用するので、あまり勧められた物ではないということを聞いてはいたが、今回は変数を幾つも使う訳でもなく長いプログラムでもない、そして何よりサブルーチンを使ったことで多少なり変数を使えるタスクが複雑になることが予想されたので採用した。
/*論理積を使ったライントレースのサブルーチン*/ sub line(){ if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){//センサーが両方白ならば go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){//右側が黒 turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){//左側が黒 turn_left1; Wait(STEP); }else{go_forward;//両方黒 Wait(STEP); } }
今回のプログラムの失敗、一番の間違いがこのライントレースのサブルーチン化である。サブルーチンはメインタスクとは別に起動してある物で、その都度メインタスクから繋いで使う為容量を節約できるのだが、ライントレースは0.01秒毎に行うので何度もメインタスクがアクセスをすることとなる。そして何よりもサブルーチンの中にサブルーチンを含むことは出来ないので、このライントレースは単体で使うしかない。これをインライン関数にしておけば他のサブルーチンに組み込んで綺麗な見た目に出来たし、容量の減少によって作業効率も良くなった筈だ。
プログラムの内容としては、論理積を使って直進、右折、左折を使い分けてライン上を動く平平凡凡のライントレースだ。
ここからは紙コップを掴んだ後に行うパターン分けされた運搬作業のプログラムだ。他の部分に変数"number"に数が与えられるプログラムがあるので分かりづらいかもしれないが、それぞれ変数の値は紙コップの色で与えられていて、色によって運搬するプログラムが変わる物と考えて頂ければ差支えない。
イメージがしやすいように、「この機体で狙っていた動き」で使った図を何度か挟み込むこととする。
/*紙コップを掴んだ後に紙コップAがあった場所に辿り着いてから行う操作のサブルーチン*/ sub carry_cup(){ if(number==1){//紙コップ1へと向かうプログラム while((SENSOR_2<47)&&(SENSOR_2>35)){ if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } }
上のライントレースに関するサブルーチンの箇所でも書いたが、ライントレースをインライン関数にしなかった結果、このようなライントレースのプログラムがいたる所に散らばっている。何度も同じ説明はしないで以降は飛ばすので、ご了承を。
ライントレースを括っている"while"の条件を日本語に起こすと「前方を向いている光センサーが平時と違う値を読み取ったらライントレースを終えなさい」といったところか。つまりは前方に光を反射するしないに関わらず物体が何かあったらライントレースを止めるようにしていて、これによって紙コップがあることを認識させる。この部分が全体のプログラムでも一番の肝で、ロボコンにて私が失敗した一番の原因でもある。
go_forward; Wait(10);//紙コップに完全に接触するまで前進。そこからの位置調整も要調査 Off(OUT_AC); cup_down; U_turn;
紙コップを被せて再び次のコップをとる為に方向転換をしてライントレースを始める。
while((SENSOR_2<47)&&(SENSOR_2>35)){//紙コップBかCを認識するまで前進させる if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } }
}else if(number==2){ while((SENSOR_1>THRESHOLD)||(SENSOR_3>THRESHOLD)){//交差点を認識するまで前進させる if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } }
紙コップ2から一番近い交差点までを論理和でもってしてライントレースさせている。
turn_right0; Wait(75);//コップ2の逆方向を向かせる go_forward; Wait(100); U_turn;//交差点でそのまま左折するとコップ2を押してしまう為
交差点から紙コップ2へ向かうのだが、車体の大きさに対して交差点から紙コップまでの距離が近いので、一先ず逆方向に進ませて距離を離してからUターンして紙コップに向かわせている。
while((SENSOR_2<47)&&(SENSOR_2>35)){//紙コップ2を認識するまで前進させる if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } go_forward; Wait(20);//紙コップに完全に接触するまで前進。そこからの位置調整も要調査 Off(OUT_AC); cup_down; OnRev(OUT_AC); Wait(130);
紙コップを被せた後に紙コップが揃えられる軸に戻る。
while((SENSOR_1>THRESHOLD)||(SENSOR_3>THRESHOLD)){//交差点を認識するまで前進させる if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } turn_left0; Wait(75);//軸にそって紙コップBかCに向かせる while((SENSOR_2>35)&&(SENSOR_2<47)){//紙コップを認識するまで if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } }
}else{ while((SENSOR_1>THRESHOLD)||(SENSOR_3)>THRESHOLD){//交差点を認識するまで前進させる if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } go_forward; Wait(10); while((SENSOR_1>THRESHOLD)||(SENSOR_3)>THRESHOLD){//交差点を認識するまで前進させる go_forward; }
ここでは一つの用紙中央にある交差点まで向かわせてから紙コップ3へ向かわせようとしている。後に出てくるプログラムの話ではあるが、タイマーを使った方がプログラムが綺麗に、尚且つ確実なライントレースが出来ると分かってしまっている。本来ならここも直すべきなのだが、閾値の設定にてこずり手が回らなかった。
turn_right0; Wait(75); while((SENSOR_2>35)&&(SENSOR_2<47)){//紙コップを認識するまで go_forward; } go_forward; Wait(25); Off(OUT_AC); cup_down;
紙コップ3を認識するまで前進し紙コップを被せる。一枚の用紙中央の交差点まで向かったのは右方向に90度旋回し前進するだけで紙コップ3に辿り着けるため。
OnRev(OUT_AC); Wait(110); while((SENSOR_1>THRESHOLD)||(SENSOR_3)>THRESHOLD){//交差点を認識するまで前進させる OnRev(OUT_AC); } turn_right0; Wait(75); }
コップを被せた後は交差点まで後退して、ラインを認識後に右90度旋回をして紙コップを再び探し始める。
while((SENSOR_2<47)&&(SENSOR_2>35)){//紙コップを認識するまで if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } } /*開始直後の紙コップAを掴み振り向くまでの動きをインライン関数化*/ void start_dash(){ go_forward; Wait(120);//スタート地点からでる為の動き while((SENSOR_1>THRESHOLD)||(SENSOR_3)>THRESHOLD){//交差点を認識するまで前進 go_forward; } turn_right0; Wait(75); while((SENSOR_2<47)&&(SENSOR_2>35)){//紙コップを認識するまで if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } go_forward; Wait(100);//コップに接触するまで Off(OUT_AC); if(SENSOR_2>47){//白 number=1; }else if((SENSOR_2<47)&&(SENSOR_2>37)){ number=2; }else{ number=3; }//センサーの値に応じてnumberに値を入れる cup_up; U_turn; }
開始からライントレースを経て紙コップを掴む、色を読み取るまでをインライン関数化したもの。そう何度も使う訳ではないので、インライン関数にせずにメインタスクに入れてしまえばよかった。
task main(){ SetSensor(SENSOR_1,SENSOR_LIGHT); SetSensor(SENSOR_2,SENSOR_LIGHT); SetSensor(SENSOR_3,SENSOR_LIGHT);//それぞれセンサーを光センサーであると宣言 set_power_L;//タイヤの速度を最低に start_dash();//コップAを掴む carry_cup(); go_forward; Wait(50);//コップBに接触するまで Off(OUT_AC); if(SENSOR_2>47){ number=1; }else if((SENSOR_2>35)&&(SENSOR_2<47)){ number=2; }else{ number=3; }//センサーの値に応じてnumberに値を入れる cup_up; U_turn; ClearTimer(0);//タイマー設置
"carry_cup()"が紙コップAがあった場所を超えたところからのプログラムなので、そこまで辿り着き尚且つそこが紙コップAがかつてあった付近であると認識できるプログラムが必要になる。交差点判別回数に頼るとラインを挟んでセンサーが両方黒と判定したら交差点と認識するプログラムの都合、ラインへの角度などによって安定しなかったので、時間を使うこととした。
while(Timer(0)<40){ if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } carry_cup(); go_forward; Wait(50);//コップCに接触するまで Off(OUT_AC); if(SENSOR_2>47){ number=1; }else if((SENSOR_2>35)&&(SENSOR_2<47)){ number=2; }else{ number=3; }//センサーの値に応じてnumberに値を入れる cup_up; U_turn; ClearTimer(0);//タイマーを初期化 while(Timer(0)<20){ if((SENSOR_1>THRESHOLD)&&(SENSOR_3>THRESHOLD)){ go_forward; Wait(STEP); }else if((SENSOR_1>THRESHOLD)&&(SENSOR_3<THRESHOLD)){ turn_right1; Wait(STEP); }else if((SENSOR_1<THRESHOLD)&&(SENSOR_3>THRESHOLD)){ turn_left1; Wait(STEP); }else{ go_forward; Wait(STEP); } } carry_cup(); Off(OUT_AC); }
今見直してハッキリと分かることだが、全般に雑である。先ずプログラムの説明にもあったようにライントレースのサブルーチン化、インライン関数化が相当お粗末でプログラム全体の容量が膨れ上がっている。プログラムの説明文が尻すぼみに量が減っていっているが、これは手を抜いているのではなくライントレースが多すぎるのと同じ内容を説明しなかっただけなのだ。これだけでプログラムがダメダメなのは自明なのだが、それだけに留まらず紙コップの識別も酷い。紙コップを白黒青にぬり分けて光の反射を変えたのだが、それの光センサーによる値の範囲が
と言ったところか。これを見る分には十分に識別できそうだが、実際はここに紙コップがない通常時の値が入る。ただ色の判別をさせるだけならば兎も角、紙コップが前方に有るかどうかすら光センサーの値から判別させるのが祟って、青い紙コップを認識させずらかった。平時の光センサーが得る値と青紙コップを捉えた時の値が近かった為だ。そしてこの光センサーによる判別は場所に成否を大きく左右される。値の調節をしきれずに大失敗するとロボコン本番のようになってしまうのだ。ここまで来るとプログラムの設計ミスとしか言いようがない。先生曰く日除けをつけてセンサーの値を安定させるべきだったようだが、それ以前に完全に固定の動きをさせて、紙コップの色を識別することのみに光センサーを使うべきだったのではないかと後悔している。悔やんでも悔やみきれない。