目次
今回の課題は、ライントレースを行い途中にある紙コップを決められた場所に移動させた後、目的地に向かうというものである。
図1:コースの全体図
自分は第2コースを選んだ。第2コースの経路は
D ⇒ Cを直進 ⇒ Bを左折 ⇒ Pを左折(1秒停止) ⇒ Qを直進 ⇒ Rを左折 ⇒ Fを左折 ⇒ Sを直進(1秒停止) ⇒ Y地点の紙コップを取る ⇒ Sを直進(1秒停止) ⇒ Qを左折(1秒停止) ⇒ Rを直進 ⇒ X地点に紙コップを置く ⇒ Pを左折 ⇒ Bを左折(1秒停止) ⇒ A
である。大まかに言うと、図1において赤矢印方向にスタート(D地点)から進み、緑丸でY地点にあるコップを手に入れ、青矢印方向に進み紫丸でコップをX地点に置き、ゴール(A地点)へ向かう、ということである。
写真1:ロボット全体
今回は二輪のロボットにした。後部の補助輪をなくしたことで摩擦が減り、動きがよくなることを期待した。また、部品は少なくコンパクトにまとまった。
写真2:ロボット本体 写真3:光センサー
使用部品はなるべく減らし軽量化した。特に光センサーの位置に気をつけ、なるべく機体に近づけることで、交差点での判断や移動をしやすくした。
写真4:回転前のアーム 写真5:回転後のアーム
紙コップを取るための腕は回転させてコップを取り込むようにした。写真4の矢印の方向にモーターを回し腕を下ろすと写真5のようになる。しかし、本体との取り付け方がやや甘く、繰り返し使うと外れたりした(ライントレース時に外れるということはなかった)。
今回のプログラムでは、アームを下ろす方をロボットの前方として、モーターA,B,Cは以下のように対応している。
OUT_A //右側のタイヤ OUT_B //左側のタイヤ OUT_C //アーム
プログラムは、ライントレース用とコップをとる用のサブルーチン、それらからなるメインルーチンで構成した。
ライントレースのための光の強さの基準値やロボットを動かす速さなどの定義である。
#define BLACK 30 //この数値以下を黒とする #define B_GRAY 37 //この数値を黒よりのしきい値とする #define W_GRAY 44 //この数値を白よりのしきい値とする #define WHITE 50 //この数値以上を白とする #define FAST 35 //モーターの強さ(強) #define SLOW 22 //モーターの強さ(弱) #define MOVE_ARM 25 //モーターの強さ
以下はロボットの動きの定義である。これがライントレースの基本となる。
#define On_Motor(vR,vL)OnFwd(OUT_A,vR);OnFwd(OUT_B,vL); //モーターA,BをそれぞれvR、vLの強さで動かす。 #define go_forward On_Motor(FAST,FAST); //前進 #define turn_sharp_left On_Motor(SLOW,-SLOW); //左旋回 #define turn_gently_left On_Motor(SLOW,0); //左折 #define turn_sharp_right On_Motor(-SLOW,SLOW); //右旋回 #define turn_gently_right On_Motor(0,SLOW); //右折
下記の3つの定義は交差点で使われるものである。
#define INTERSECTION_TIME 150 //交差点判断の時間 #define CURVE_TIME 50 //円環上の経路での交差点判断の時間 #define FORWARD_TIME 300 //前進させる時間
ライントレースについてはNXTの資料のp22~p28を参考にした。今回は資料p24にある、ロボットを黒線と白の部分の境界上で走らせる方法でプログラムを作成した。
図2:ライントレース
図2は作成したロボットのように光センサーが車体の前方かつタイヤ間の中心にあるロボットで考えている。図2のように光センサーが黒を認識すると左折、白を認識すると右折、黒と白の中間の値を認識すると直進するプログラムが基本的なライントレースである(左折と右折だけでも進むことはできる)。これに黒線上での左旋回と、白の部分での右旋回を加えた5段階のライントレースが今回の基本形である。さらに交差点判断を加えたものが私が作ったライントレースのプログラムである。
交差点について説明する。
図3:交差点判断
図3のように、通常のライントレースの場合と異なり、交差点では光センサーが連続して黒を認識するのでその時間が長い。それを利用して、ライントレース中に連続で黒を認識した時間を測定し、それが一定時間を越えたときを交差点だと認識させれば、ロボットに交差点を見つけさせることができる。
図4:円環状の経路への出入り
また、図4のような円環上の経路への出入りも考える必要があった。これは、基本的には交差点判断と同じだが、円環部分に侵入するときは交差点よりも続けて黒を認識する時間が短いので、交差点判断と同じ基準にするとそのままライントレースを続けてしまい、何度か必要になる1秒間停止ができないからである。また、図4の右側のような状況で円環を抜けずに走らせたい場合、交差点判断と同じ時間で判断させようとすると、連続して黒を認識する時間が短いので、そのままライントレースを続けて円環から抜けてしまい失敗する。したがって、円環状の経路への出入りでは判断基準の時間を短くする必要があった。また、図1のEF間にあるヘアピンカーブでは、誤検知したりしなかったりしたので、1度認識させることにした。
以下に今回使用したプログラムを示す。このプログラムでは判断基準の時間は引数(blacktime)となっている。また、他の引数は、stoptimeは交差点での一時停止の時間を、fwdtimeは交差点を抜けるための前進の時間を、sharplefttimeは交差点で曲がるときの左旋回の時間を表している。
sub line_trace(int blacktime,int stoptime,int fwdtime,int sharplefttime) //ライントレースのサブルーチン { SetSensorLight(S1); //光センサーを設定 long t0=CurrentTick(); //時間を測定 while(CurrentTick()-t0<blacktime){ //測定時間が(blacktime/1000)秒未満の間繰り返す if(SENSOR_1<BLACK){ //光センサーが得た値が黒未満のとき turn_sharp_left; //左旋回(この動作を続けている間時間を測定) } else{ //光センサーが得た値が黒以外 t0=CurrentTick(); //測定時間を初期化 if(SENSOR_1<B_GRAY){ //光センサーが得た値が黒以上黒寄りのしきい値未満のとき turn_gently_left; //左折 } else if(SENSOR_1<W_GRAY){ //光センサーが得た値が黒寄りのしきい値以上白寄りのしきい 値未満のとき go_forward; //前進 } else if(SENSOR_1<WHITE){ //光センサーが得た値が白寄りのしきい値以上白未満のとき turn_gently_right; //右折 } else{ //光センサーが得た値が白以上のとき turn_sharp_right; //右旋回 } } Wait(1); //0.001秒続ける } Off(OUT_AB); Wait(stoptime); //(stoptime/1000)秒間ロボットを停止 PlaySound(SOUND_CLICK); //音を鳴らす(交差点判断の合図) go_forward; Wait(fwdtime); //(fwdtime/1000)秒間前進 turn_sharp_left; Wait(sharplefttime); //(sharplefttime/1000)秒間左旋回 t0=CurrentTick(); //測定時間を初期化 }
このプログラムは、ロボットが連続して黒線の上で左旋回しているときの経過時間を測定し、他の動作に移るときは測定時間を初期化するようになっている。こうすることで、光センサーが黒を続けて認識した時間が交差点判断の基準の時間(blacktime/1000)を越えないときは、測定時間が初期化されwhile文の中を繰り返すので、ロボットはライントレースすることができる。逆に、基準時間を越える、すなわち交差点に差し掛かったときは、while文の繰り返しを抜けてプログラム下部の動作に移ることができる。
4つの引数を用いてこのプログラム1つでほぼ全ての状況に対応できるようにしたが、メイン関数が少し見づらくなった。また、途中にある"PlaySound"は交差点を判断できたときに音を鳴らすためのものである。 私が選んだ第2コースでは黒のラインの左側を走るライントレースだけでゴールまで行くことができるので、黒線の右側を走るライントレースは作っていないが、プログラムの黒を認識した時の動きと白を認識した時の動きを入れ替えることで可能なはずである。
コップの取得と配置そのものには特別なことはなにもない。ロボットにコップの方向を向かせることと、腕を振り下ろしたときにコップにぶつからないようにすることに注意するだけで良い。図1の緑丸の位置でY地点にあるコップをとり、紫丸の位置でコップをX地点に置くだけである。コップの配置のときに少し問題があったが、それはプログラム全体の最後に記述しておく。
下記のプログラムがコップの取得と配置のためのプログラムである。armpowerは腕を回転させる強さを、turnrighttime、turnlefttimeはそれぞれロボットを右または左に旋回させる時間を表している。
sub cop(int armpower,int turnrighttime,int turnlefttime) //コップの取得と配置のサブルーチン { turn_sharp_right; Wait(turnrighttime); //(turnrighttime/1000)秒間右旋回 On_Motor(-FAST,-FAST); Wait(150); //モーターの強さ35で0.15秒間後退 OnFwd(OUT_C,armpower); Wait(400); //モーターの強さarmpowerで0.4秒間腕を回す On_Motor(FAST,FAST); Wait(160); //モーターの強さ35で0.16秒間前進 turn_sharp_left; Wait(turnlefttime); //(turnlefttime/1000)秒間左旋回 PlaySound(SOUND_FAST_UP); //音を鳴らす(コップの取得または配置の合図) }
右旋回はコップを取るときや置くときにロボットの方向を調整する動作である。左旋回はロボットが元の進行方向を向くための動作である。
#define BLACK 30 #define B_GRAY 37 #define W_GRAY 44 #define FAST 35 #define SLOW 22 #define MOVE_ARM 25 #define On_Motor(vR,vL)OnFwd(OUT_A,vR);OnFwd(OUT_B,vL); #define go_forward On_Motor(FAST,FAST); #define turn_sharp_left On_Motor(SLOW,-SLOW); #define turn_gently_left On_Motor(SLOW,0); #define turn_sharp_right On_Motor(-SLOW,SLOW); #define turn_gently_right On_Motor(0,SLOW); #define INTERSECTION_TIME 150 #define CURVE_TIME 50 #define FORWARD_TIME 300 sub line_trace(int blacktime,int stoptime,int fwdtime,int sharplefttime) //ライントレース { SetSensorLight(S1); long t0=CurrentTick(); while(CurrentTick()-t0<blacktime){ if(SENSOR_1<BLACK){ turn_sharp_left; } else{ t0=CurrentTick(); if(SENSOR_1<B_GRAY){ turn_gently_left; } else if(SENSOR_1<W_GRAY){ go_forward; } else if(SENSOR_1<WHITE){ turn_gently_right; } else{ turn_sharp_right; } } Wait(1); } Off(OUT_AB); Wait(stoptime); PlaySound(SOUND_CLICK); go_forward; Wait(fwdtime); turn_sharp_left; Wait(sharplefttime); t0=CurrentTick(); } sub cop(int armpower,int turnrighttime,int turnlefttime) //コップの取得と配置 { turn_sharp_right; Wait(turnrighttime); On_Motor(-FAST,-FAST); Wait(150); OnFwd(OUT_C,armpower); Wait(400); On_Motor(FAST,FAST); Wait(160); turn_sharp_left; Wait(turnlefttime); PlaySound(SOUND_FAST_UP); } task main() { SetSensorLight(S1); //光センサーを設定 long t0=CurrentTick(); //時間を測定 PlaySound(SOUND_UP); //音を鳴らす(開始の合図) while(SENSOR_1>B_GRAY){ go_forward; //ラインを検知するまで前進 } line_trace(INTERSECTION_TIME,0,FORWARD_TIME,0); //(D地点)枠の部分で0.3秒間前進 line_trace(INTERSECTION_TIME,0,FORWARD_TIME,0); //(C地点)交差点で0.3秒間前進 line_trace(INTERSECTION_TIME,0,200,1600); //(B地点)交差点で0.2秒間前進後1.6秒間左旋回 line_trace(CURVE_TIME,1000,FORWARD_TIME,1600); //(P地点)円形コースへの侵入前に1秒間止まり、0.3秒間前進した後1.6秒間左旋回 line_trace(CURVE_TIME,0,FORWARD_TIME,0); //(Q地点)交差点で0.2秒間前進 line_trace(CURVE_TIME,0,FORWARD_TIME,1600); //(R地点)交差点で0.3秒間前進後1.6秒間左旋回 line_trace(INTERSECTION_TIME,0,FORWARD_TIME,1600); //(E地点)交差点で0.3秒間前進後1.6秒間左旋回 line_trace(CURVE_TIME,0,0,300); //1度目のヘアピンカーブで0.3秒間左旋回 line_trace(INTERSECTION_TIME,0,FORWARD_TIME,1600); //(F地点)交差点で0.3秒間前進後1.6秒間左旋回 line_trace(INTERSECTION_TIME,1000,FORWARD_TIME,0); //(S地点)交差点で1秒間止まり、0.3秒間前進 cop(MOVE_ARM,1000,1100); //1秒間右旋回後前進し、モーターの強さ25で腕を下ろした後、後退して1.8秒間左旋回 line_trace(INTERSECTION_TIME,1000,FORWARD_TIME,0); //(S地点)交差点で1秒間止まり、0.2秒間前進後1.6秒間左旋回 line_trace(CURVE_TIME,,1000,FORWARD_TIME,1800); //(Q地点)円形コースへの侵入前に1秒間止まり、0.2秒間前進後1.6秒間左旋回 line_trace(CURVE_TIME,0,FORWARD_TIME,0); //(R地点)交差点で0.3秒間前進 line_trace(CURVE_TIME,0,FORWARD_TIME,1400); //(P地点)交差点で0.3秒間前進後1.4秒間左旋回 cop(-MOVE_ARM,3400,1800); //3.4秒間右旋回後前進し、モーターの強さ25で腕を上げた後、後退して1.8秒間左旋回 line_trace(INTERSECTION_TIME,0,200,1800); //※前の旋回後、0.2秒間前進し1.7秒間左旋回してコースに復帰 line_trace(INTERSECTION_TIME,0,200,1700); //(B地点)交差点で1秒間止まり、0.2秒間前進後1.7秒間左旋回 line_trace(INTERSECTION_TIME,0,0,0); //(A地点)枠で停止 turn_sharp_right; Wait(200); //0.2秒間右旋回 go_forward; Wait(800); //0.8秒間前進 PlaySound(SOUND_FAST_UP); //音を鳴らす(ゴールの合図) }
前述した問題についてここで述べる。それは、コップを配置した後にロボットの向きを進行方向に戻す必要があるのだが、コップを置く前の右旋回の角度と置いた後のロボットの後退の度合いによっては、進行方向にロボットを向けようとすると、黒線を認識できずに白の部分で回転し続けてしまう場合があったことだ。そのため、コップを置いた後に90度程度回転させ、光センサーが黒線上に来た後、ライントレースのサブルーチンを入れて交差点判断をさせて元に戻すようにした(上記プログラムの※)。しかし、黒線上に来た後は、一定時間の間ライントレースを続けるプログラムをはさむべきだったと考える。これならば確実に戻れるだけでなく、コースの他の部分(ヘアピンカーブや交差点判断後の誤検知防止)にも使えるはずなので、1つのサブルーチンで全てをこなすよりわかりやすかったように考える。
ライントレースや交差点判断は全て上手くいった。しかし、紙コップを取るときにわずかにずれが出て引っかかった。その後はなんとか腕の中にコップが入り事なきを得て、最終的には、紙コップを置くことにも成功し無事ゴール地点にたどり着くことができた。
ロボットについては、紙コップを取る腕は振り下ろす形式よりも横から挟むようにした方が、機体や紙コップの位置をあまり気にせずにできてより安定したと考える。また、比較していないので当然だが、二輪にしたことが良かったのか悪かったのかはわからなかった。プログラムについては、全ての交差点や円形の経路への進入、ヘアピンカーブなどを全て一つのサブルーチンで書いたが、引数を増やしすぎるという前回の課題のときと同じ失敗をしてしまった。他の人のプログラムを見るとサブルーチンやマクロを多用しており、その分のプログラムの増加があっても見やすいメイン関数になっていると思った。また、失敗を恐れるなら、コースを離れてしまった場合のプログラムを考えるべきだった。次回はサブルーチンやマクロを活用したプログラムを作れるように考えたい。