目次一覧
yoshi…このページの作成者。工学部機械システム工学科所属。マインドストームに関しては全くの初心者。
最近は専らバーチャルの戦場で銃を乱射することに勤しんでいる。ロボットの機体作成の難易度が高すぎて心が折れかけた模様。
機構学で学んだことをマインドストームに生かせないか模索中。
blackcat…相棒。とても親切な人柄で、金曜5限の疲れた心を癒してくれる。彼もマインドストームは初心者の模様。
レポートのページは2012b/Member/blackcat/Mission1。
図のようなコースを各自で作成した上で、次のような動作をするロボットを作成せよ。
NXT付属のテキストに載っているモデルでは、前後に長くなってしまうため障害物に接触してしまうと思い、なるべく突出した部分の無い小型な構造を目指しました。
小型化するためにまず、タイヤのモータを縦に設置することを考えました。
また、ポールを運ぶ機構を前方に設置すると前後に長くなるため、横から本体の内部(下部)へボールを出し入れするような構造を考えました。
そして本体はその上に設置することにしました。
全体の形としては、ほぼ直方体となっています。駆動させるタイヤは前方に配置し、光センサはそのさらに前に設置してあります。
また、後部にはキャスターを付けました。本体を上部で固定して、その下にボールの回収・射出機構を設けてあります。
なるべく小型にしようと努力しましたが、障害物を完全に避けることはできませんでした。
左の画像は左前上方から、右の画像は後方から撮影したものです。
ライントレースをするための部分を作ってからこちらの構造を考え始めたので、本体の下に空間にどのようにして納めるかをまず考えました。
モータの長手方向と機体の前後方向を合わせて、モータの回転方向を水平方向になるよう設置するのが一番空間の無駄が少なかったので、そのように設置することにしました。
そして、モータの運動を水平なバーに伝達することで、水平方向の円運動という簡単な動きでボールを回収・射出することを目指しました。
モータに直接挿してあるバーに、シュートの時ボールを押し出すためのバーを接続してあります。
また、モータに直接挿してあるバーには歯車が付いており、大きな歯車を間に1個介して、
ボールを回収、射出するためのバーの歯車にモータの回転を伝達するという仕組みになっています。
この部分を作る時、2つのバーの回転角度が丁度よくなるような歯車の組み合わせで組んだら、2つのバーの間に入るぴったりのサイズの歯車となったのは奇跡とも言えるでしょう。
左の画像は本体の下部、右の画像は左側面を撮影したものです。
このように構造的な欠陥が数多く存在する機体となってしまったため、次の課題からは作り方を根本的に変える必要があると思いました。
今回私はフィールドの左側からスタートするプログラムを担当しました。
私が作成したライントレースのプログラムでは、コース外へ延びる線との交差点においてうまく交差点だと認識してくれないという大事件が発生したので、
不確実な要素をなるべく排除するために交差点を避けて進むプログラムを作成することにしました。
プログラムの全体の流れとしては
といった様になります。
始めに定数及び動作を定義します。
#define SPEED_H 45 // 高速 #define SPEED_L 35 // 中速 #define SPEED_LL 20 // 低速 #define THRESHOULD 47 // 閾値
モーターの稼働スピード及び閾値を定義。 より正確にラインをトレースするためにスピードを3段階設けました。
また、コースの黒線が若干白っぽいので閾値は高めに設定しました。
#define OnRL(speedR,speedL) OnFwd(OUT_B,speedR);OnFwd(OUT_C,speedL); #define go_forward OnRL(SPEED_H,SPEED_H); // 直進 #define turn_left1 OnRL(SPEED_L,-SPEED_L); // 左旋回 #define turn_left0 OnRL(SPEED_L,-SPEED_LL); // 左折 #define turn_right0 OnRL(-SPEED_LL,SPEED_L); // 右折 #define turn_right1 OnRL(-SPEED_L,SPEED_L); // 右旋回 #define STEP 1 // 1回の判断で動作させる時間 #define nMAX 140 // 通常のカーブとして許容できる繰り返しの最大値 #define short_break Off(OUT_BC);Wait(1000); // 小休止 #define CROSS_TIME 400 // ライン横断に要する時間 #define cross_line OnRL(SPEED_H,0);Wait(CROSS_TIME); // ラインを横断 #define shoot OnRL(-SPEED_H,SPEED_H);Wait(1100);Off(OUT_BC);OnRL(SPEED_H,SPEED_H);Wait(700);Off(OUT_BC);OnFwd(OUT_A,100);Wait(250);Off(OUT_A); // シュート
動作等を定義。 ポートAはボールの回収・射出機構、ポートBは右タイヤ、ポートCは左タイヤのモータに接続されています。
#define E 329.5 // ミ #define Fs 370 // ファ# #define G 392 // ソ #define A 440 // ラ #define As 466 // ラ# #define B 494 // シ #define C 523.5 // ド #define D 587 // レ sub fanfare() { repeat(3) { PlayTone(D,100);Wait(140); } PlayTone(D,400);Wait(470); PlayTone(As,400);Wait(470); PlayTone(C,400);Wait(470); PlayTone(D,100);Wait(300); PlayTone(C,100);Wait(150); PlayTone(D,1000);Wait(1400); while (true) { PlayTone(A,400);Wait(450); PlayTone(G,400);Wait(450); PlayTone(A,400);Wait(450); PlayTone(G,100);Wait(230); PlayTone(C,400);Wait(450); PlayTone(C,100);Wait(230); PlayTone(B,400);Wait(450); PlayTone(C,100);Wait(230); PlayTone(B,400);Wait(450); PlayTone(B,100);Wait(230); PlayTone(A,400);Wait(450); PlayTone(G,400);Wait(450); PlayTone(Fs,400);Wait(450); PlayTone(G,180);Wait(230); PlayTone(E,1600);Wait(2000); PlayTone(A,400);Wait(450); PlayTone(G,400);Wait(450); PlayTone(A,400);Wait(450); PlayTone(G,100);Wait(230); PlayTone(C,400);Wait(450); PlayTone(C,100);Wait(230); PlayTone(B,400);Wait(450); PlayTone(C,100);Wait(230); PlayTone(B,400);Wait(450); PlayTone(B,100);Wait(230); PlayTone(A,400);Wait(450); PlayTone(G,400);Wait(450); PlayTone(A,400);Wait(450); PlayTone(C,180);Wait(230); PlayTone(D,1600);Wait(2000); } }
音階を定義し、ファンファーレのサブルーチンを用意。 楽曲は「ファイナルファンタジー」シリーズから「ファンファーレ(作曲:植松伸夫氏)」を使用させていただきました。
以下から、実際の動作の部分のプログラムとなります。
task main() {
SetSensorLight(S1); // センサのポート1に光センサを接続 int nOnline = 0; // ライン上にどれだけいたかをカウントする変数を用意 int nCurve = 0; // 左急カーブの回数をカウントする変数を用意 long t0 = CurrentTick(); // ライン横断までの時間を測るタイマーを用意
OnRL(-SPEED_L,SPEED_H); Wait(300); Off(OUT_BC); OnRev(OUT_A,30); Wait(500); Off(OUT_A); OnRL(2*SPEED_H,SPEED_H); Wait(250); Off(OUT_BC);
ここの一連のプログラムで、ピンポン球を回収してスタートゾーンの枠から脱出します。
回収の動きは、まず右に旋回してアームの可動域にボールを入れる、その後アームを閉じてボールを内部に取り込む、といった動作です。
while (CurrentTick() - t0 < 10000) { // スタートから10秒間は以下の動作を実行 if(SENSOR_1 < THRESHOULD - 13) { // 濃い黒の上では右旋回 turn_right1; } else { if (SENSOR_1 < THRESHOULD - 7) { // 薄い黒の上では右折 turn_right0; } else if (SENSOR_1 < THRESHOULD + 7) { // ラインとの境界では直進 go_forward; } else if (SENSOR_1 < THRESHOULD + 15) { // ラインから近い白の地点では左折 turn_left0; } else { turn_left1; // それ以上白い場合は左旋回 } Wait(STEP); } }
この部分はスタートから10秒間の、ラインの右端をトレースするためのプログラムです。 閾値からの差によって曲がり方を選択しながら進んで行きます。
if (CurrentTick() - t0 == 10000) { cross_line; // ラインを横断 t0 = CurrentTick();
スタートから10秒経過したら、ラインを横断して左側へ移ります。
タイマーをリセットしていますが、これ以降使わないのであまり関係ありません。
while (nCurve < 2) { if(SENSOR_1 < THRESHOULD - 13) { turn_left1; nOnline++; if (nOnline == nMAX) { // nOnlineがnMAXを超え、急カーブだと認識した場合は、以下の動作を実行 nCurve++; // 急カーブの回数をプラス nOnline = 0; // ライン上にどれだけいたかをカウントするカウンタをリセット PlaySound(SOUND_CLICK); Off(OUT_BC); Wait(300); OnRL(SPEED_H,-SPEED_H); // センサをライン上から外す Wait(500); } // ここまで } else { if (SENSOR_1 < THRESHOULD - 7) { turn_left0; } else if (SENSOR_1 < THRESHOULD + 7) { go_forward; } else if (SENSOR_1 < THRESHOULD + 15) { turn_right0; } else { turn_right1; } nOnline = 0; } Wait(STEP); } }
ラインの左側に移った後は、ラインの左端をトレースするように先ほどとは逆方向に曲がるようなプログラムにしています。
また、ラインの横断後左急カーブの回数が2回未満の時は、左急カーブに差し掛かったらカーブのカウンタを+1して左旋回し、センサをライン上から外します。
これは、1回のカーブでカウンタが2回以上プラスされるのを防ぐためです。
while (nCurve >= 2) { while (nOnline < nMAX) { if(SENSOR_1 < THRESHOULD - 13) { turn_left1; nOnline++; } else { if (SENSOR_1 < THRESHOULD - 7) { turn_left0; } else if (SENSOR_1 < THRESHOULD + 7) { go_forward; } else if (SENSOR_1 < THRESHOULD + 15) { turn_right0; } else { turn_right1; } nOnline = 0; } Wait(STEP); } short_break; PlaySound(SOUND_LOW_BEEP); shoot; PlaySound(SOUND_FAST_UP); Wait(1000); OnRL(SPEED_H,SPEED_H); Wait(15000); Off(OUT_BC); Wait(1000); fanfare(); } } }
左急カーブを2つ通り過ぎた後は、その後の3つ目の左急カーブでピンポン球をゴールにシュートします。
シュートの動きは、交差点を認識したらその場で旋回して、ゴールの方向に機体の左側を向ける、
その後少し前進してゴールの正面まで移動、そこでアームを回転させてシュート。といった動作です。
シュート後はゴールの枠内から離脱し、勝利のファンファーレを奏でます。
これらの問題を解決するために数値の設定を何度も見直し、調整しましたが、結局解決には至らず、成功率は6割程度にとどまりました。
プログラムから不確実な要素をもっと排除するべきだったと思います。