今回は、私一人のみでなく、二人で課題に取り組んだ。ここに参加メンバーを記しておく。
・kiyomizu(私) ・suiden
まずは、何をすればよいのか課題1の内容(ルール)を確認したいと思う。
1.スタート地点から出発し、黒い線にそって動く。 2.一つ目の十字の交差点と二つめの十字の交差点の間に置かれたピンポン玉をつかむ。 3.さらに黒線に沿って動いてゴール地点を目指す。 4.ゴールに向けてピンポン玉をシュートする。
(注1*なお、この課題は二人で同一のロボットを使い、お互いに逆の経路をたどるという課題である。ちなみに、私はスタートからゴール担当。)
(注2*私達はピンポン玉ではなく、備え付けのボールを使用することになっていた。扱う玉の大きさが大きさがロボットの構造に影響を大きく与えたのでここに記しておく。)
これを見て、やるべきことを考えたところ、大雑把に2段階の作業が必要であることがわかった。
1つ目、今回の課題をクリアするための能力を持ったロボットを作る。
2つ目、それを要求通りに動かすプログラムを作る。以下、それらについて書きたいと思う。
課題1をクリアするための能力とは何であろうか。3つの能力がこのロボットには必要であると私は考えた。
(プログラムで身につけられる能力は除外し、最初から備え付けにしておく必要のある能力のみここに挙げた。)
・ボールを保持する能力 ・ボールをシュートする能力 ・黒い線に沿って進む能力
<ボールを保持する能力>…開発当初は前の空いたコの字型の"囲い"をロボットの前につけておき、
ロボットが進みながら、その中へボールを入れゴールへ持っていくという構造(図1)が検討されたが、
返しをつけるなど工夫をしたところでボールを保持したままゴールへ行く確率が低いことに加え、
ボールを入れる際に囲い自身にボールが当たってはじかれてしまいボールをそもそも保持できない可能性がそこそこあるなどの理由で却下された。
この欠点を解決し得る構造(それもシュートする機能も兼ねた素晴らしいもの)ができたのでそれを採用した。
ボールをシュートする能力と説明がかぶるのでそちらで説明する。
<ボールをシュートする能力>…初期では、囲いを使うという構造に合わせそれを妨害せず、
まっすぐ前にボールを飛ばすことのできるもの(図2)が考えられ、
ブロックの組み合わせでできた"足"をモーターで前に回し、シュートする構造が考案された。
(ちなみに、この構造は横から見るとサッカー選手の足の動作に見えなくもないもので、私のお気に入りであった。)
しかし、これも囲いの構造のとりやめに伴い没になった。
では、どのようにしたのか。シュートするための構造は、どのようにボールを保持しているのかによってある程度の制約を受ける。
そこで、できるだけ高確率でボールを保持する構造とそれに適したシュートをする構造をつくりあげる必要性が出てきた。
これらの条件をたった1つの構造でクリアしているのが今回私達が採用した"二重バー構造"(図3)である。
(注3)二重バー構造とは何か。一言でいうと、前後に二つの棒(バー)を配置した"閉じた枠"を、先に述べた足へと取り付けた構造である。
ボールを捕まえるときは、これを振り下ろしボールを枠の中に閉じ込める。
この時、バーがボールにあたりボールが若干はねてしまうが絶妙な高さで調整された前後のバーと横への取りこぼし防止バンパーがボールの脱出を許さない。
このバンパーとバーの位置の調整が優れており、ほぼ100%でボールを捕獲する。
(今回のこの調整はセット付属のボール専用チューンなのでピンポン玉では多少成功率が落ちることになる。先ほど"注2"でボールの種類について特に指定したのはこのことによる。)
シュートをするときには、この足を上へと振り上げ枠の後部についたバーがボールをはじき前へととばす。
この時、かちんと小気味よい音がなり、見ている方も大変気持ちがよい。
さらに、先ほどの"足"構造と同じく、シュート動作がサッカー選手の足に心なしか見えるという、ビジュアル面にも優れた素晴らしい構造である。
このような素晴らしい構造の案をだしてくれたsuiden氏にこの場を借りてお礼を申し上げたい。
(注3*今回のこの構造は私ではなく相方のsuiden氏が考案したものであり、私に命名権があるわけではないが便宜的にこのように名づけた。各人留意されたし。)
(図1、ボールを囲いながらゴールへと向かう機構。図2とセットで使うことを目的に開発された。)
(図2、初期のボールを前にとばす機構。)
(図3、二重バー構造。)
<黒い線に沿って進む能力>…この課題の直前に"ライントレース"という機能を持ったロボットを作っていたのでそれを踏襲した。
ライントレースとは、ある線に沿って進むという機能のことで、光センサーを用い、検知した明るさによって進む方向を決めるというものである。
(今回は、センサーが黒い線の左側の境界をたどるようにしたのだが、詳しくはプログラムの制作で説明する。)
したがって、光センサーを取り付ける必要がある。が、問題はどこに取り付けるのかということである。
センサーと車輪の位置が離れすぎているとセンサーが明るさを読み取ってから車輪が回ったとき大幅に黒い線からずれてしまうことがありえるし、
課題1のコースは急カーブが多いのであまり大柄なロボットを作ってしまうとターンが難しいなど条件があり、
ほどほどに車輪に近い位置に取り付けることが必要だった。
また、以前のものをできるだけ流用するため、つまりなるべくプログラムを変更することがないようにすることが必要で、
光センサーの高さ、角度もあまり変えないように配置した。
そんなこんなでできたのがこのロボットである。
題名の通り、課題1をクリアするために必要なプログラムを組む。
具体的には、先のロボット製作の段階で盛り込んだ機能を使えるようにプログラムを組んでいく。
よって、必要なプログラムは先ほどと同じ3つである。
・ボールを保持するプログラム ・ボールをシュートするプログラム ・黒い線に沿って進むプログラム
<ボールを保持するプログラム>…今回の私たちのロボットでは、
ボールを保持することは枠の中へ入れることですでに達成していることになるので、
ボールを枠の中へ入れることをプログラミングすればよいのである。
有り体にいうと、二重バー構造のついた足を下ろせばよい。
よって、モーターを前回転させるというプログラムを組むことにした。
その際、ただの前回転ではなくモーターの回転角を決めることのできるものを使うことにした。
またその下ろすタイミングは時間でとることにした。
<ボールをシュートするプログラム>…ボールを保持するプログラムの真逆である。
なので、上の逆、つまりモ−ターを後ろ回転させることにした。
上げるタイミングは交差点でカウントし何個目かで測ることにした。
(最後のゴール地点はT字であるが、このプログラミングだと交差点の判定をするため。交差点の判定の詳細は下にて。)
<黒い線に沿って進むプログラム>…今回の一番の鬼門である。
センサーが黒い線の左側の境界をたどるようにした。
具体的にどうしたのかを説明すると、あらかじめロボットについている明るさの度合いを調べる機能で黒い線の上、
紙の白い部分、それらの境界の明るさを測定し、
黒い線の上の数値が出たら進路を左へ、紙の白い部分だったら進路を右へ、
紙の白い部分だったら進路はそのままとなるようにプログラムした。
(図4)が、その判定をする値を決めるのが難しく、ちょうどよい値を模索しなくてはならない。
また、モーターの出力が高いと軌道修正する前にはるかかなたへ行ってしまうのでそのような調整もしなくてはならなかった。
加えて、この課題のコースには交差点が存在しそのままライントレースしていると、
悲しいかな正規ルートから抜け出て行ってしまうので、交差点の判別も必要である。
この問題はロボットの判定回数を利用することで解決した。
どういうことかというと、ある一定の数を設定しておきロボットが異常な数(最初に設定したある数)以上に横に曲がる判定をロボットがした場合、
一時的にライントレースを中断し直進させ、その後またライントレースを開始するという方法である。
これらをもとに実際に組んだプログラムがこれである。
ざっと流れを説明すると、始めに今回使う様々な数などの定義
、次に使う関数の定義(kiyomizuオリジナルの関数)、続いて開始数秒の動作、それからボールをとるまでの動作、
それからゴールにつくまでの動作、シュートを打つ動作となっている。
#define THRESHOLD 50 //しきい値(明るさ判定でつかう値)// #define SPEED 75 //シュート動作などでのモーターの出力(%)// #define SPEED_T 40 //ターン時のモーターの出力(%)// #define SPEED_S 30 //直進時のモーターの出力(%)// #define STEP 1 //明るさ判定の頻度 (x回/1000s)// #define CROSS_TIME 200 //交差点を直進する際にかける時間// #define SHOOT_TIME 1000 //シュート動作にかける時間// #define nMAX 300 //交差点判定での異常な数の目安// #define START_DASH 1000 //スタート直後の直進する動作にかける時間// #define TRACE_TIME 8000 //スタート直後の直進の後のライントレース(最初の交差点の手前まで)にかける時間// #define short_break Off(OUT_BC); Wait(1000); //判定の後、交差点をわたるまでの微妙な時間調整// #define CATCH_TIMING 30000 //動作開始からボールをつかむまでにかける時間//
sub start_go() //スタート直後の直進する動作の関数の定義// { OnFwd(OUT_BC,SPEED_S); Wait(START_DASH); Off(OUT_BC); } sub go_forward() //前に進む動作の定義// { OnFwd(OUT_BC,SPEED_S); } sub turn_left0() //左に曲がる動作の定義// { OnFwd(OUT_C,-SPEED_T); } sub turn_left1() //その場で左に旋回する動作の定義// { OnFwd(OUT_B,SPEED_T); OnRev(OUT_C,SPEED_T); } sub turn_right0() //右に曲がる動作の定義// { OnFwd(OUT_B,-SPEED_T); } sub turn_right1() //その場で右に旋回する動作の定義// { OnFwd(OUT_C,SPEED_T); OnRev(OUT_B,SPEED_T); } sub cross_line() //交差点を突っ切る動作の定義// { PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_BC,SPEED_S); Wait(CROSS_TIME); short_break; } sub pre_shoot() //ボールを捕獲する前に足を振り上げる動作(ボール捕獲前の予備動作)の定義// { RotateMotor(OUT_A,SPEED,-200); } sub Messi_Shoot() //まるでメッシのようなシュートを打つ動作の定義// { RotateMotor(OUT_B,SPEED,45); RotateMotor(OUT_C,SPEED,45); RotateMotor(OUT_A,SPEED,200); Wait(SHOOT_TIME); Off(OUT_A); } sub shoot_performance() //シュート後にするパフォーマンスをする動作の定義// { PlaySound(SOUND_DOUBLE_BEEP); RotateMotor(OUT_B,SPEED,90); RotateMotor(OUT_C,SPEED,90); RotateMotor(OUT_B,SPEED,90); RotateMotor(OUT_C,SPEED,90); }
これから下が実際に行う動作のプログラムになる。 プログラムの順は下記のようである。
ミッションスタート →少し直進(黒いスタートの枠から外に出る) →最初の交差点手前までライントレース(この過程はいらなかったのではないかと今更判明。) →ライントレース(交差点判定あり、時限式ボール捕獲あり) →交差点カウンター式メッシのようなシュート。
task main() { SetSensorLight(S1); //光センサーの接続先指定// int nOnline=0; //後々使う交差点判定のときの数を定義+現在その値は0と指定// int cross_line_times=0; //通過した交差点の数カウンターの定義+現在その値は0と指定// long t0 = CurrentTick(); //開始直後の時間を記録//
pre_shoot(); //ボール捕獲のための予備動作兼威嚇// start_go(); //直進(黒いスタートの枠から外に出るため)//
while (CurrentTick()-t0 <= TRACE_TIME){ //開始から過ぎた時間がある値より小さければずっと以下(”→}”の印まで)の関数を実行// if (SENSOR_1 < THRESHOLD-35){ //もしセンサーの値がある値より小さければ左へ旋回しろ// turn_left1(); } else { //でない状況で// if (SENSOR_1 < THRESHOLD-15){ //もしセンサーの値がある値より小さければ左へ曲がれ// turn_left0(); } else if (SENSOR_1 < THRESHOLD+15){ //でない状況で、もしセンサーの値がある値より小さければまっすぐ進め// go_forward(); } else if (SENSOR_1 < THRESHOLD+35){ //でない状況で、もしセンサーの値がある値より小さければ右へ曲がれ// turn_right0(); } else { //でない状況で、もしセンサーの値がある値より小さければ右へ旋回しろ// turn_right1(); } Wait(STEP); //判断時間と黒い線に沿うための動作時間の合計の指定// ”→}” PlaySound(SOUND_UP); //区切りでの警笛//
while (true) { long t1 = CurrentTick(); //開始から測った現在の時間を記録// while (nOnline < nMAX){ //横に曲がる判定の数が異常ではない場合、以下の関数を実行しろ// if (SENSOR_1 < THRESHOLD-35){ //ここから// turn_left1(); nOnline++; } else { if (SENSOR_1 < THRESHOLD+15){ turn_left0(); } else if (SENSOR_1 < THRESHOLD+15){ go_forward(); } else if (SENSOR_1 < THRESHOLD+35){ turn_right0(); } else { turn_right1(); } nOnline=0; } Wait(STEP); } //ここまで $最初の交差点手前までライントレース$と同じ// if(t1-t0 == CATCH_TIMING){ RotateMotor(OUT_A,SPEED,200);}//もし開始から今までの経過時間がある値と同じならば、足を振り下ろせ(=ボールを捕まえろ)// } } (以下、交差点を渡る+シュートを打つ動作) short_break; //時間調整のための小休止// turn_right1(); //交差点を直進する際の入射角度の調整// Wait(nMAX*STEP); cross_line_times++; //通過した交差点の数カウンターの値に+1// if (cross_line_times==3){Messi_Shoot();shoot_performance();}//もし、交差点の数を数えるカウンターがある値と等しければ、足を振り上げろ(=シュートを打て)そして、パフォーマンスをしろ// else{cross_line();} //でない状況で、交差点を直進しろ// nOnline=0; //横に曲がる判定の回数のカウンターをリセット// } }
このようにプログラムを構築した。
関数while,if,elseなどの使い方がキーになっている。
かっこの有効範囲、関数の順序なども関わっておりプログラミングの素人の私には、
それはそれは難しかった。(あまりにも難しかったためノートに書き下し、マーカーを入れるなどするはめになった。)
と、これまでさも複雑そうなプログラムを構築し、なんとかロボットを動かすことができた体で話をしてきたが、
実際にはうまく動くことはなかった!
以上のことを加味した結果、複雑で解読が困難になりにくく、
モーターの出力調整によって、確実に正しく動くプログラムを組むことにした。
以下は、課題1専用プログラム2である。
…前回のプログラムの反省点は複雑化しすぎたことにより、
どこの調整をすればうまくいくのかが解らなかったことにあると考えたため、
今回のコンセプトは複雑になり過ぎないこととし、極端に簡単、軽量なプログラムを目指した。
あまりにも簡素化を目指したため、今回のプログラムは先のプログラムとは別に、白紙から構築してある。
ライントレース、交差点判定は自力で調整(人がモータの回転数、動作時間を調整)し、黒い線の上を通るようにした。
したがって、センサーを使わず黒い線の上を通ることになり、
完全にモーターの回転数、動作時間に依存して動かすため、
電池の残存電力が最大の時にうまくいくようにプログラム内の値を調整してある。
当然そもそも光センサーを使っていないので交差点で惑わされることはない。
このため自身で決める定義は一切使わずにすみ、
単純にモーターの動作の関数とその稼働時間の指定をする関数、
区切りで鳴らす警笛の関数ぐらいしか使わずに組むことができた。
task main () { OnFwd(OUT_A,-70); Wait(500);Off(OUT_A);//黒い枠からでるための最初の前進// PlaySound(SOUND_FAST_UP); //区切りの警笛(今回は各カーブで鳴るように設定されている。毎度毎度説明すると鬱陶しいので次回から省略することにする。)// OnFwd(OUT_BC,50); Wait(500);Off(OUT_BC);//コースの始めの前進// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_B,50); Wait(900);Off(OUT_BC);//最初の左旋回その1// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_BC,50); Wait(300);Off(OUT_BC);//位置調整の直進その1// OnFwd(OUT_C,50);OnFwd(OUT_B,15); Wait(1500);Off(OUT_BC);//左への回転その1// PlaySound(SOUND_LOW_BEEP); //ここで第一交差点通過// OnFwd(OUT_C,40);OnFwd(OUT_B,10); Wait(1000);Off(OUT_BC);//左への回転その2// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_A,80); Wait(500);Off(OUT_A); PlaySound(SOUND_FAST_UP);//足を下ろせ(=ボールを捕まえろ)// OnFwd(OUT_C,40);OnFwd(OUT_B,10); Wait(1000);Off(OUT_BC);//左への回転その3// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_C,40);OnFwd(OUT_B,10); Wait(1000);Off(OUT_BC);//左への回転その4// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_C,40);OnFwd(OUT_B,10); Wait(500);Off(OUT_BC);//左への回転その5// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_A,60); Wait(500);Off(OUT_A); OnFwd(OUT_A,60); Wait(500);Off(OUT_A); //ここで中央交差点を通過// PlaySound(SOUND_DOUBLE_BEEP); OnFwd(OUT_BC,50); Wait(500);Off(OUT_BC);//位置調整の直進その2// OnFwd(OUT_C,10);OnFwd(OUT_B,25);Wait(2400);Off(OUT_BC);//右への回転その1// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_BC,50); Wait(300);Off(OUT_BC);//コース上の直進// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_B,50);Wait(1000);Off(OUT_B);//左への直角回転// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_C,50);OnFwd(OUT_B,45); Wait(1000);Off(OUT_BC);//コース上の直進その2// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_B,50); Wait(1600);Off(OUT_BC);//最後の左への回転その1// PlaySound(SOUND_LOW_BEEP); OnFwd(OUT_C,50); Wait(1900);Off(OUT_BC);//最後の左への回転その2// PlaySound(SOUND_LOW_BEEP); OnRev(OUT_C,30);OnRev(OUT_B,25);Wait(500);Off(OUT_BC);//シュートを打つ前に少し後ろに下がれ(=シュートの予備動作)// OnFwd(OUT_A,-90); Wait(400);Off(OUT_A);//足を振り上げろ(シュートを打て)// PlaySound(SOUND_FAST_UP); }
やけにカーブの関数が多いのは、一回一回自分で進む量、モーターの出力などを手で調整したためで
プログラムの複雑さを回避した途端、関数自身は簡単なのに恐ろしく構築(大半がモーターの出力調整である)に時間を食うはめになった。
言わば、手動ライントレースなので当然このコースでしか使えず、
光センサーを使う方と比べると正確性も落ちてしまうが一応そこそこのシュート成功率を誇る。
無い知恵を絞り素人がプログラミングをしてみたところ(あまりにうまくいかずコンピュータ室で発狂しかけたこともあった)
一応エラーはでないものの期待通りにロボットが動くことはないという悲しい結末であった。
反省点をあげてみると先にも述べた通り、しきい値やモーターの出力の調整に問題があったと思う。
タイマーを使い任意の区間ごとにしきい値を変えたり、
これは専用プログラム2を作った後に気づいたのだが、手動ライントレースの要所で光センサーを使ったりすれば良かったのではないかと思った。
さて、専用プログラム2についてであるが、
最初に組んだプログラムがまともに動かなかったことに対し、
意地でもなんとか課題を成功さてやるという決意のもとに急遽制作されたものである。
したがって、格別に素晴らしい技巧が凝らしてあるということもない。
しかし、デメリット(確実性が落ちること、組むのにそこそこ時間がかかることなど……)もさることながら、
メリットもある(構築難易度が低いため大抵の人がすぐさまプログラム内容を理解できることや
光センサーあり型とは違いジグザグ進まずすっすっと進むので見ていて気持ちがよいことなど……)ことに
注目していただければ幸いである。
制作者として心残りがあるとすれば、
先ほど言及した手動ライントレースの要所で光センサーを使う方式のプログラムを組んでみればきっと面白かっただろうと思うくらいである。
今後は、最初に組んだプログラムのようなややこしいものでも動かせるように腕前を鍛えていこうと思う。
以上、課題1レポートでした。
長文お読みいただきありがとうございました。