今回のロボティクス入門の課題はライントレースです。具体的に説明すると作ったマシーンで道中にある玉を拾い、ゴールのかごに玉をシュートするというものです。
今回は地点Cから十字路をまっすぐ進み、道中で玉を拾いながらその先の丁字路を右に曲がり地点Aにボールを運び入れるルートを選び課題に取り組みました。
※最新のレポートは"結果2"以降です。お急ぎの方は"結果2"からご覧ください。
モーターCでアームを動かし、モーターA,Bで左タイヤ、右タイヤを動かします。また光センサーはS3に装着しました。右の写真を見てもらうと分かっていただけると思いますが、二つの白に近い灰色の長い棒がアームの役割を担っています。これを開閉することによりボールを保持します。
ロボット組立:三時間
プログラミング:八時間
レポート:二時間
タイヤのある位置まで制御することが出来ました。
その過程を説明すると次のようになります。「交差点で一時停止→ボール回収→丁字路で一時停止→方向転換」
#define Threshold 55 //閾値
今回は定義をあまり必要としなかったので閾値のみ定義することとなりました。
sub aida(){ //交差点と丁字路の間を制御する ResetTachoCount(OUT_AB); RotateMotor(OUT_AB,30,240); Off(OUT_AB); Wait(2000); ResetTachoCount(OUT_B); RotateMotor(OUT_B,30,190); }
これは交差点と丁字路の間を機械の判断に任せるには大変高度な技術が必要になるだろうと判断し、時間制御によりコントロールしました。また、角度で設定することにより電池の残量に依らず一定の距離進むことを可能にしました。
sub tsukamu(){ //ボールを掴む ResetTachoCount(OUT_C); RotateMotor(OUT_C,20,100); }
これも角度を指定することにより安定して同じ距離動かすことを可能にしました。
sub shot(){ //ボールを相手のゴールにシュウウウン! ResetTachoCount(OUT_C); RotateMotor(OUT_C,40,-100); OnFwdSync(OUT_AB,40,0); Wait(200); Off(OUT_ABC); }
一瞬だけ動かしてボールを前に押し出しました。
ここからはコメント文を用いて説明させていただきます。
task main(){ SetSensorLight(S3);//光センサーの使用 int kousatenn=0;//交差点を判断するための値 int last=0;//二回目の交差点に行くための値 int syuuryou=0;//終わるための値 while(kousatenn < 400 && syuuryou == 0){ //交差点カウンターが貯まるまで道に沿って動く OnFwdSync(OUT_AB,30,(SENSOR_3-Threshold)*23/10);//ラインに沿って動くように調整したもの if(40>SENSOR_3){ //交差点カウンターを貯める kousatenn++; }else { kousatenn = 0; } Wait(1);//検知の間隔 } while(kousatenn >= 400 && last == 0){ //交差点カウンターが溜まった一回目の動き Off(OUT_ABC); Wait(2000); tsukamu(); aida(); last = 1; //2つの交差点終了 kousatenn = 0;//交差点カウンターのリセット } while(kousatenn >= 400 && last == 1){ //交差点カウンターが溜まった二回目の動き Off(OUT_ABC); Wait(2000); shot(); kousatenn = 0;//交差点カウンターのリセット syuuryou = 1;//終了の合図 } }
今回は悩むことが少なかった。その代わりにとても越えられない壁があった。
ロボット作りでは光センサーの位置を決めるのに時間がかかった。プログラム作成中も手直しするほど気を遣う、繊細なものでした。
ロボティクス入門のホームページにある「サーボ・モータの機能をもっと使おう」という項目を発見し、プログラムの幅が広がった。他のページも見返すことで「&&」や「||」など色々な演算子を覚えたり、思い出したりした。
所々プログラムにミスが見つかるようになったが直すことが出来るものが大半になって来た。一回目の交差点を通るためのプログラムが渡った後にもう一度来るようになった。グループのメンバーに聞くが解決することがなく、色々と変えてはみたがループすることは変わらず朝を迎えた。
一度詰まってしまうと新しい解決策を見つけるには大変な労力が必要であると分かった。また、解決策が分かったらもう一度チャレンジしてみたいと思いました。
ルートはそのままC〜Aに、マシン、プログラム、情熱、その他もろもろをさらに良いものへと変更してきました。
説明を始める前に前回の反省点を考えていきたいと思います。
前回、"while"で囲まれた三つのプログラムが二つ目の"while"の動作を終えた後動かなくなってしまった。原因は単純に三つの"while"で囲まれたプログラムを一つの大きな"while"で囲んでいなかったというものでした。
説明書に書かれていた方法で作られた360度どの方向にも回る便利なタイヤですが、左右に揺れやすいこのロボットとは相性が悪く、マシン全体の動きが悪くなっていました。
これはライントレース中のプログラムが原因だと考えられます。次のプログラムを見てください。
OnFwdSync(OUT_AB,30,(SENSOR_3-Threshold)*23/10);//ラインに沿って動くように調整したもの
この三つめの値を複雑に書いてしまったため分かりにくいプログラムになってしまいましたが、三つめの面倒な計算式は「タイヤの同期率」を表すものだということは分かっていただけていると思います。"SENSOR_3"は光センサーの値、"Threshold"はライン左側の境界線上の値です。三つめの値が大体+30を上回ると右のタイヤが逆の方向に回り始め、−30を下回ると左のタイヤが逆の方向に回り始めるという性質を利用したプログラムですが、"SENSOR_3"の値が"Threshold"から大きくずれると急な旋回を始めてしまいます。それによりマシンが左右に大きく揺られ滑らかに走ることが出来ないという問題がありました。
全くスマートじゃないと思います。
無事走り切りました。
先ほど問題点の解説で説明した後輪についてです。左の画像を見てください。後輪ではなくなりました。後球です。これにより左右に揺れても後輪後球による動きの誤差がかなり減少しました。
中央の画像はアームとライトセンサーの位置関係を示すものです。ライトセンサーは通常タイヤからある程度離れた位置に置くことが好ましいです。それはライトセンサーの値の変化大きくするためです。
しかし、僕はタイヤとの距離を出来るだけ近いものにしました。それは僕のプログラムが急な値の変化を嫌うからです。ゆっくりと変わることにより同期率も徐々に変化し、ラインに沿った動きを可能にすると考えたからです。
右の画像はロボット全体が分かりやすく見えるように撮ったものです。縦の長さが長いというところが欠点だと思います。ちなみに音を感知するセンサーが付いていますがこれは動作を確認するために用いたものなので今から説明するプログラムには関係していないので気にしないでください。
#define max 61 #define min 31 #define LEVEL (max-min)/8 //9段階に分けるための値
ライトセンサーの最大の値と最小の値を調べて定義しました。LEVELで定義したのは後々、交差点を判断するために用いたりライントレースをするときにも用います。
int kousatenn=0; //交差点を判断するための値 int keizoku=0; //プログラムをどの段階まで進んだか認識させるための値 int syuuryou=0; //プログラムを終わらせるための値 int goukaku=0; //交差点であるか確かめるための値 int hanntenn=0; //プログラムを反転させるための値
コメント文で説明している通りです。
void tsukamu(){ //ボールを掴む RotateMotor(OUT_C,50,-100); }
回転角度を指定することで毎回同じ動作を続けることが出来る。前回書いていて無駄だと分かった"ResetTachoCount(OUT_AB);"を省くことで簡単にまとまりました。
void juujiro(){ //十字路突入時の動作 if(goukaku==1){ Off(OUT_AB); Wait(2000); tsukamu(); RotateMotor(OUT_AB,50,180); RotateMotor(OUT_B,50,200); RotateMotor(OUT_A,50,-90); Off(OUT_AB); Wait(2000); keizoku++; hanntenn++; }else { Off(OUT_AB); } }
"goukaku"という変数値は次の「交差点であるかの確認」というプログラムで交差点であると決定されたときに1になります。"keizoku","hanntenn"が共に1増えることにより前者は二回目の交差点を認知したときに別のプログラムに移動させ、後者は次のラインの左側から右側を沿うように移動するようになります。このプログラムで十字路〜丁字路を移動します。
void kakuninn(){ //交差点であるかの確認 Off(OUT_AB); Wait(500); RotateMotor(OUT_B,50,60); Off(OUT_B); if(SENSOR_3 <= min+LEVEL*2){ goukaku++; RotateMotor(OUT_B,50,-60); Off(OUT_B); }else { RotateMotor(OUT_B,50,-60); } }
マシーンの左のタイヤだけ前進させることで、C〜十字路を移動する際の左に曲がっているときに間違えて交差点と認識した場合でもそのまま全身することが出来ます。
void shot(){ //ボールを相手のゴールにシュート RotateMotor(OUT_C,50,100); OnFwdSync(OUT_AB,70,0); Wait(300); Off(OUT_ABC); syuuryou++; }
ボールを相手のゴールにシュートします。"syuuryou"という変数値は"while"のループから抜け出すための値です。
inline void sennjou(int douki){ //線の上に来た時のプログラム OnFwdSync(OUT_AB,40,douki/5); } inline void turn_left(){ //左に曲がるプログラム OnFwdSync(OUT_AB,30+hanntenn*10,-40); } inline void turn_right(){ //右に曲がるプログラム OnFwdSync(OUT_AB,40-hanntenn*10,40); } inline void Line_Trace(){ //ライントレースをするプログラム if(hanntenn==0){ //C〜十字路を移動中のライントレース if(SENSOR_3<=min+10){ turn_left(); if(SENSOR_3<=min+7){ kousatenn+=1; } }else if (SENSOR_3>=max-10){ turn_right(); kousatenn=0; }else { sennjou(SENSOR_3-(max+min)/2); //滑らかに動くための同期率操作 } } if(hanntenn==1){ //丁字路〜Aを移動中のライントレース if(SENSOR_3>=max-10){ turn_left(); kousatenn=0; }else if (SENSOR_3<=min+10){ turn_right(); if(SENSOR_3<=min+7){ kousatenn++; } }else { sennjou(SENSOR_3-(max+min)/2); } } Wait(1); Float(OUT_AB); }
"kousatenn"という変数値でマシーンが曲がりきる前に交差点だと判断させる。動作を反転させる前後で左に行く力と右に行く力を変えたのはカーブの内側から外側に行くとき勢いがつき線からはみ出すということを防ぐためです。
task main(){ SetSensorLight(S3); RotateMotor(OUT_C,50,100); //閉じた状態からスタート出来る while(syuuryou==0){ Line_Trace(); if(kousatenn>=200 && keizoku==0){ kakuninn(); juujiro(); kousatenn=0; } if(kousatenn>=100 && keizoku==1){ shot(); kousatenn=0; } } }
二回目ということもありプログラムの作成にはあまり時間が掛からなかったと思います。ロボコンが一段落したあとなので、以前まで分からなかったプログラムの間違いにも気づくことが出来たと思います。まだ、マシーンが滑らかに動くということが完全には出来ていなかったので、"OnFwdSync"の同期率によるモーターの変化を詳しく調べたいなと思いました。
また、マシーンが縦に長くなってしまったことにより左右に振れるようになった可能性も考えられるので、出来るだけコンパクトになるように組み立てることがプルグラムを組み立てる基礎になるのだと分かりました。