Tomo, Yuki, Naoki, Naoyoshi
上図の黄色の缶をできるだけ多くGOALと書かれているエリアに置くと高得点が得られる。この際、n段目詰まれた缶の得点は5n点となる。
まずどのようなロボットで、どんなプログラムで缶を積むかを考えた。ロボットの形態は何回か紆余曲折、試行錯誤を経て変わったが、最終的に固まった方針は以下のとおりである。
最初期の段階ではフィールド上の9つの缶全てを使い、3×3の塔を建設するつもりだった。缶を拾いに行く順番は下記のとおりである。(アラビア数字→アルファベット→ローマ数字)
初期理想案
しかしプログラム面で時間が足りない見通しとなり、最終的にSTART近傍の3つの缶を使って塔を建てることになった。その手順が下図である
最終案
プログラミングの方向としては、段階をカウントするための変数(nFase)を設定し、その値に応じてラインのどちらをトレースするか、缶を探知した時にどうするかなどを変えていくことにした。(プログラムの項で後述)
今回ハードウェア作成は主に他のメンバーたちがやってくれた。
このロボットの最大の特徴は、このアームの形状である。下部に設置されたプレートが缶の下のくびれた部分をすくい取り、左右から缶をホールドするだけでなく、下から缶を支えられるためどんなに激しく動いてもずり落ちたり飛び出したりすることはない。設計上は缶を3つ積んだ状態で持ち続けることができる。 弱点としては、アームが重くなってしまったことで水平を保ちにくくなってしまったことがある。その点は黄色の輪ゴムで張力を発生させ、傾きを最小限に抑えることができた。
二台のNXT本体をL字に配置し、極力空間の無駄をなくすとともにボディ全体の剛性が増した。また、アームが伸びた時でも重心が安定しているのもこのレイアウトの利点と言える。
その他の点で大まかに工夫されている点は以下のとおり
走行用プログラムはマスター、缶を掴むプログラムはスレーブが担当した。
#define RIGHT OUT_B #define LEFT OUT_C #define BOTH OUT_BC #define SPEED1 35 #define SPEED2 SPEED1*2 #define OnRL(speedR,speedL) OnRev(RIGHT,speedR);OnRev(LEFT,speedL); //ギアの都合で正転と逆転が反対 #define go_forward OnRevSync(BOTH,SPEED2,0); //左右のタイヤの移動距離をシンクロさせて前進 #define go_back OnFwdSync(BOTH,SPEED2,0); //後退 #define turn_left1 OnFwdSync(BOTH,SPEED2,100); //時計回りに旋回。3つ目の引数100は旋回率(右のモータと左のモータの回転角度が100%異なる) #define turn_left0 OnRL(SPEED2,0); //左折 #define turn_right0 OnRL(0,SPEED2); //右折 #define turn_right1 OnFwdSync(BOTH,SPEED2,-100); //半時計回りに旋回 #define u_turnCW OnRL(-SPEED1,SPEED1); //時計回りに旋回。音波探知用にスピードを落としている。 #define u_turnRCW OnRL(SPEED1,-SPEED1); //半時計回りに旋回 #define THRESHOLD 45 #define RANGE1 7 //ライントレース中に缶をつかむ動作に移行するための距離 #define RANGE2 19.8 //アームを伸ばした時の缶までの距離 #define RANGE3 23 //回転しながら缶を検知するときの距離 #define RANGE4 5 //前方に感知した缶をバンパーのホルダーに押しこむのに必要な距離 #define STEP 1 #define SR SENSOR_2 #define SL SENSOR_1 #define SS SensorUS(S3) #define CONN 1 #define arm_down "arm1.rxe" //アームを開き、つかむ準備をするプログラムを定義しなおした。特にアームをダウンさせるわけではない #define arm_up "arm2.rxe" //アームの降下→握り→上昇の一連の動作を定義しなおした #define BUILD_RAG 1000 //ゴールの交差点に止まったあと、ゴール中央を向くための回転に必要な時間。 #define DO 523 //音程C、E、Gを指定。hiCはCの二倍で対応 #define MI 659 #define SO 784 #define SIGNALON 11 //Mail受信用 #define SIGNALOFF 12 #define LATIO 3 //走行駆動系のギア比(24:8)。距離の算出に必要
int nFase,angleA,angleB,msg,nCross;
float GetAngle(float r) //引数r[cm]だけ進むのに必要な回転角を導くマクロ。ギア比考慮済み { const float diameter=5.45; const float pi=3.1415; float ang=r/(diameter*pi)*360.0*LATIO; return ang; }
sub find_line() //黒線を見つけるまで前進し、左右両方のセンサが線を検知するまで角度を調整するサブルーチン { until((SL<=THRESHOLD)&&(SR<=THRESHOLD)) { if((SL>THRESHOLD)&&(SR>THRESHOLD)) { go_forward; } else if((SL>THRESHOLD)&&(SR<=THRESHOLD)) { turn_right0; } else { turn_left0; } Wait(STEP); } }
sub start_traceR() //左右の光センサが線に乗った状態から右方向にライントレースを開始するためのサブルーチン { until(SL>THRESHOLD) { turn_right0; } PlaySound(SOUND_CLICK); Off(BOTH); }
sub start_traceL() //左右の光センサが線に乗った状態から左方向にライントレースを開始するためのサブルーチン { until(SR>THRESHOLD) { turn_left0; } PlaySound(SOUND_CLICK); Off(BOTH); }
sub traceR() //右側の光センサを使ってライントレースを行うモード { if(SR<THRESHOLD-4) { turn_right1; } else if(SR<THRESHOLD) { turn_right0; } else if(SR<THRESHOLD+4) { go_forward; } else if(SR<THRESHOLD+7) { turn_left0; } else { turn_left1; } }
sub traceL() //左側の光センサを使ってライントレースを行うモード { if(SL<THRESHOLD-4) { turn_left1; } else if(SL<THRESHOLD) { turn_left0; } else if(SL<THRESHOLD+4) { go_forward; } else if(SL<THRESHOLD+7) { turn_right0; } else { turn_right1; } }
sub search_crossR(int MAXCross) //右側の光センサを使ってライントレースを行いつつ、引数MAXCrossの分だけ交差点を感知するサブルーチン。交差点の判定は左右の光センサが同時に閾値を下回ったことで行う。 { nCross=0; while(nCross<MAXCross) { until((SL<=THRESHOLD)&&(SR<=THRESHOLD)) { traceR(); Wait(STEP); } nCross++; PlaySound(SOUND_UP); until((SL>THRESHOLD)&&(SR>THRESHOLD)) { go_forward; Wait(STEP); } } }
sub search_crossL(int MAXCross) //同じく左側の光センサによるライントレースと同時に交差点判定を行う { nCross=0; while(nCross<MAXCross) { until((SL<=THRESHOLD)&&(SR<=THRESHOLD)) { traceL(); Wait(STEP); } nCross++; PlaySound(SOUND_UP); until((SL>THRESHOLD)&&(SR>THRESHOLD)) { go_forward; Wait(STEP); } } }
上記二つの交差点判定プログラムは机上の空論に終わった。原因としては、交差点以外でもカーブで2つのセンサが反応してしまうこと、仮に交差点を認識してもそこから両方のセンサを白の部分へ導けないことがあること、などが挙げられる。
sub catch() //持っている缶を新しい缶の上に載せ、掴み、また持ち上げる一連の動作。 { angleB=GetAngle(RANGE4); RotateMotor(BOTH,SPEED1,-angleB); //缶をホルダーの中に押しこむために少し前進する PlayTone(262,400); //ネット上で見つけたメロディー。特に意味はない。後述出典参照 Wait(500); PlayTone(294,400); Wait(500); PlayTone(300,400); Wait(500); PlayTone(294,400); Wait(500); RemoteStartProgram(CONN,arm_down); //スレーブにアームを開かせる Wait(5000); ReceiveRemoteNumber(MAILBOX1,true,msg); if(msg==SIGNALON) { PlaySound(SOUND_CLICK); } else { PlaySound(SOUND_FAST_UP); } angleA=GetAngle(RANGE2); RotateMotor(BOTH,SPEED2,angleA); //アームの伸びに合わせ後退する RemoteStartProgram(CONN,arm_up); //スレーブに缶を掴ませる PlayTone(DO,120);Wait(150); //達成感のあるメロディー。一回ごとに半音ずつ上昇させたかった PlayTone(MI,120);Wait(150); PlayTone(SO,120);Wait(150); PlayTone(DO*2,240);Wait(300); PlayTone(SO,120);Wait(150); PlayTone(DO*2,360);Wait(450); Wait(10000); if(msg==SIGNALON) { PlaySound(SOUND_CLICK); } else { PlaySound(SOUND_FAST_UP); } RotateMotor(BOTH,SPEED2,-angleA+angleB); //つかむ動作の開始点まで戻る。 }
sub build() //ゴールで缶を載せるだけのプログラム。最後まで使われなかった { PlayTone(262,400); Wait(500); PlayTone(294,400); Wait(500); PlayTone(524,400); Wait(500); PlayTone(294,400); Wait(500); RemoteStartProgram(CONN,arm_down); Wait(10000); go_back; Wait(2000); Off(BOTH); nFase++; }
今一度、缶を掴む順番を載せておく。
task main() //スタートからゴールまでの一連の流れを記述したプログラム { SetSensorLight(S1); SetSensorLight(S2); SetSensorLowspeed(S3); nFase=0; //第0段階開始(ラインに乗るまで) msg=SIGNALOFF; until(BluetoothStatus(CONN)==NO_ERR); find_line(); start_traceR(); nFase++; //第一段階開始 (一つ目の缶をつかむまで) while(SS>RANGE1) { traceR(); Wait(STEP); } Off(BOTH); catch(); nFase++; //第二段階開始(2つ目の缶をつかむまで) while(nFase<3) { until(SS<RANGE3) //缶を感知するまで回転し続ける { u_turnRCW; Wait(STEP); } while(SS>RANGE1) //感知したらホルダーに格納されるまで前進 { go_forward; Wait(STEP); } Off(BOTH); catch(); nFase++; } while(nFase<4) //第三段階開始(3つ目の缶を格納するまで) { until(SS<RANGE3+3) { u_turnRCW; Wait(STEP); } u_turnCW; Wait(200); Off(BOTH); while(SS>RANGE1) { go_forward; Wait(STEP); } Off(BOTH); //3つ目の缶は掴まず、ホルダーで抱えたまま進み、ゴールで缶を2つ載せる nFase++; //第四段階開始(缶をゴールに運び、塔を建てるまで) } until((SR<THRESHOLD)||(SL<THRESHOLD)) //反転し、ライントレースに戻る { u_turnRCW; } find_line(); start_traceL(); search_crossL(2); //2つ目の交差点を過ぎたところで停止、缶を下ろす Off(BOTH); build(); go_back;Wait(3000); u_turnRCW;Wait(BUILD_RAG); }
コース上の缶に番号をつけ、その順番でプログラムが進むよう設計した。具体的には、変数nFaseを設定し、缶を掴む動作が繰り返されるたびにnFaseに1を足していきフェイズが進行する仕組みである。便宜上whileをつかってnFaseの値の真偽を判断しているが、繰り返しを前提にしているものではない。
当初は4つの缶を積み上げる計画だったが、時間の都合で最終的に3つの缶を積み上げた塔を建てることを目指した。そういう意図でプログラムはできているが、一度も最後まで行けたことはない。理由として、缶から缶までの移動を周囲の影響を受けやすい超音波センサを用いたこと、ライントレースの制度が低くさらに交差点の判定がうまく機能しなかったことが挙げられる。いずれにせよプログラムの練り上げに圧倒的に時間が足りなかったことは否定しようのない事実である。
arm1.rxe
#define SIGNALON 11 #define SIGNALOFF 12 #define CONN 0 task main() { until(BluetoothStatus(CONN)==NO_ERR); RotateMotor(OUT_A,20,-48); Wait(1000); PlaySound(SOUND_FAST_UP); Wait(2000); SendResponseNumber(MAILBOX1,SIGNALON); }
arm2.rxe
#define SIGNALON 11 #define SIGNALOFF 12 #define CONN 0 task main() { until(BluetoothStatus(CONN)==NO_ERR); RotateMotorEx(OUT_BC,15,450,0,true,true); Wait(1000); RotateMotor(OUT_A,30,48); Wait(1000); PlaySound(SOUND_FAST_UP); Wait(1000); RotateMotorEx(OUT_BC,40,-450,0,true,true); Wait(1000); SendResponseNumber(MAILBOX1,SIGNALON); }
アームの開閉角度48度と昇降角度450度の調整には最後まで時間を使ったところでもある。
今一度メンバーに感謝したい。ありがとうございました。