目次
今回の課題は、紙に書かれた黒い線の上をなぞって進み、途中置かれている紙コップをつかみ指定の場所まで移動させゴールするという課題である。
このコースを使ってライントレースする。3つの経路のうち私は第1コースとなった。
第1コース
また、コースを作りで線を黒く塗るときに真ん中が一番黒くなるようにした。
今回は、前回の課題1のときより動く距離が長いためできるだけ小さくする方がよいと考えた。なので、動作に問題がなければ細かいところでしっかり固定したりはしないようにした。
最初センサーの値の計測をしたときに少し角度がつくようにして計測したためそのままセンサーは地面に対して少し角度がつくように取り付けた。試しに地面に垂直に取り付けてみたが、値が角度をつけたときと大きく変わって低くなり、変化する幅も小さくなったため難しそうだと判断した。(センサーと地面が近すぎだけかもしれない)
紙コップを取るには紙コップを取れる位置まで移動しないといけないが、その調整が大変そうだったので紙コップの方を移動させることにした。
コースの色の大体の値は一番黒いところが30%、その次に黒いところが40%、白いところが70%である。
基本的に40%のところをまっすぐ走るようにし、黒いところ、白いところに行くと右側を走っているか、左側を走っているかによるが、それに応じて右に曲がる、左に曲がるプログラムにしいている。今回作ったライントレースのプログラムはカウンター方式で、一番黒いところ、次に黒いところ、白いところの3つ、あるいはそれぞれの場所の値の間にもう1ヶ所増やし5ヶ所を判別するプログラムを作り、それぞれの場所でする行動(前進、右に曲がる、左に大きく曲がる、など...)に付け加えてカウンターを増やすプログラムとカウンターをリセットするプログラムを追加する。そのカウンターに上限の値を設定してき、値を超えるとプログラムを終了するようにしている。このプログラムは交差点だけ反応してほしいのに緩やかなカーブでも反応してしまうときなどは、上限の値を変えるもしくは、モーターのパワーを小さくし遅くすることで誤反応をなくすことができる。
今からの説明は右側を走っている場合である。
交差点に行くと一番黒い線にぶつかる。
プログラムによって右のタイヤを支点として右に曲がるが、しばらくは黒いところが続くのでカウンターがたまっていく。
すると、カウンターが設定していた値を超え実行していたプログラムが終了し次のプログラムに移行する。
次のプログラムは、交差点から抜け出すもので光センサーが白いところを読み取るまで左タイヤを支点に左に曲がるというものである。
白いところまで行くとカウンターをカウントし始める。この時のカウンターの設定値を小さくしておくことで一瞬でも読み取るとプログラムが終わるようになっている。
交差点を抜けたら、最初に説明したプログラムを実行する。そうすると、黒いところまで左に曲がりライントレースを再開する。この時、プログラムのカウンターは白いところにあるので0となりプログラムが途中で終了することはない。
カーブに入ってくる直進用のプログラムも少し変えている。
まだ緩やかなところでは、
ライントレースを続ける。
しかし、カーブがきつくなると白いところから抜け出すのが遅くなる。なのでカウンターを白いところでカウントするようにしておき値を調整すると、図のようなところでカウンターが設定した値となり、プログラムが終了する。
そして、カーブ用のプログラムが実行される。このプログラムは直進用とほとんど同じで、違うのは白い所に出たときに起動する左に曲がるプログラムを左のタイヤを止めて曲げるのではなく、左は逆回転して曲がるものにする。そうすると急なカーブを曲がることができる。
また、このカーブのプログラムにもカウンターをつけており、2番目に黒いところでカウンターがカウントされるようにしておく。そうすることで、直進になった時にカウントが設定した値になり、うろグラムが終了するようになる。(このとき、誤作動しないように設定する値は少し大きめにしておく。)
#define speedA 28 //モーターAの出力 #define speedB 26 //モーターBの出力 #define Cpower 20 //モーターCの出力 #define THRESHOLD 50 //しきい値 #define STEP 1 //1回の判定で進む時間 #define sA2 speedA/2 //Aの出力1/2 #define sB2 speedB/2 //Bの出力1/2 #define sA3 speedA/3 //Aの出力1/3 #define sB3 speedB/3 //Bの出力1/3 #define sA4 speedA/4 //Aの出力1/4 #define sB4 speedB/4 //Bの出力1/4
パワーが大きいと動きが速くなり反応しないことがあったので少し小さめにした
sub go_forward(int x,int y) //前進 { OnFwd(OUT_A,speedA-x);OnFwd(OUT_B,speedB-y); } sub go_back(int x,int y) //後進 { OnFwd(OUT_A,-speedA-x);OnFwd(OUT_B,-speedB-y); } sub go_right1(int x,int y) //右の車輪を軸に右へ { OnFwd(OUT_B,speedB-y);Off(OUT_A); } sub go_left1(int x,int y) //左の車輪を軸に左へ { OnFwd(OUT_A,speedA-x);Off(OUT_B); } sub go_right2(int x,int y) //その場で右へ { OnFwd(OUT_A,-speedA-x);OnFwd(OUT_B,speedB-y); } sub go_left2(int x,int y) //その場で左へ { OnFwd(OUT_A,speedA-x);OnFwd(OUT_B,-speedB-y); }
ロボットの基本的な動きである前進、後進、右回転、左回転を関数にしている。関数の引数であるx,yに数値を入れるとその分だけスピードを遅くすることができるようにしてある。
sub gogo_b(int x,int y) //黒い線まで前進 { while (SENSOR_1 > THRESHOLD -9){ go_forward(x,y); Wait(STEP); } PlayTone(523,50); //音が鳴る } sub gogo_w(int x,int y) //白い線まで前進 { while (SENSOR_1 < THRESHOLD -9){ go_forward(x,y); Wait(STEP); } PlayTone(523,50); //音が鳴る } sub lele(int x,int y) //黒い線まで左回転 { while (SENSOR_1 < THRESHOLD -9){ go_left1(x,y); Wait(STEP); } PlayTone(523,50); //音が鳴る }
センサーが指定された値を読み取るまで動くようにすることで黒いラインまで、また、白いラインまで動くようにした。また、動作が終わると音が鳴る。(この音は動作の確認のものである。)
sub r_left(int x,int y) //右側で左カーブ { int n = 0; while (n<400){ if (SENSOR_1 < THRESHOLD -13) { go_right2(x,y);n=n+1; } else if (SENSOR_1 < THRESHOLD -9) { go_forward(x,y);n=n+1; } else if (SENSOR_1 < THRESHOLD +10) { go_left1(x,y); } else { go_left2(x,y);n=0; } Wait(STEP); } PlayTone(587,50); //音が鳴る } sub l_right(int x,int y) //左側で右カーブ { int n = 0; while (n<300){ if (SENSOR_1 < THRESHOLD -15) { go_left2(x,y);n=0; } else if (SENSOR_1 < THRESHOLD -13) { go_left1(x,y); } else if (SENSOR_1 < THRESHOLD -9) { go_forward(x,y);n=n+1; } else if (SENSOR_1 < THRESHOLD +10) { go_right1(x,y); } else { go_right2(x,y);n=0; } Wait(STEP); } PlayTone(587,50); //音が鳴る } sub r_right(int x,int y) //右側で右カーブ { int n = 0; while (n < 300){ if (SENSOR_1 < THRESHOLD -15) { go_right2(x,y); n=0; } else if (SENSOR_1 < THRESHOLD -13) { go_right1(x,y); } else if (SENSOR_1 < THRESHOLD -9) { go_forward(x,y); n=n+1; } else if (SENSOR_1 < THRESHOLD +10) { go_left1(x,y); } else { go_left2(x,y); n=0; } Wait(STEP); } PlayTone(587,50); //音が鳴る }
カーブのプログラムで、行う動作に付け加えてカウンターを増やすものと、リセットするプログラムを付け加えている。この付け加えている場所は、なるべく少なくしており、すべてにつけるともっと正確になるかもしれないが、塗っている線の色の濃さがすべて均一ではないので、センサーの読み取る値が変わってしまうことを考えた。そうすることで、プログラムを作る時も簡単に考えることができた。実際に動かしてみて、問題がなかったものはそのままつかい、うまくいかなかったものはプログラムを付け加えたり、速さを変えたりした。
sub l_straight1(int x,int y) //左側で直進1 { int n = 0; while (n<300){ if (SENSOR_1 < THRESHOLD -15) { go_left1(x,y);n=0; } else if (SENSOR_1 < THRESHOLD) { go_forward(x,y); } else { go_right1(x,y);n=n+1; } Wait(STEP); } PlayTone(523,50); //音が鳴る } sub l_straight2(int x,int y) //左側で直進2 { int n = 0; while (n<300){ if (SENSOR_1 < THRESHOLD -15) { go_left1(x,y);n=n+1; } else if (SENSOR_1 < THRESHOLD) { go_forward(x,y); } else { go_right2(x,y);n=0; } Wait(STEP); } PlayTone(523,50); //音が鳴る } sub l_straight3(int x,int y) //左側で直進3 { int n = 0; while (n < 250){ if (SENSOR_1 < THRESHOLD -15) { go_left1(x,y);n=n+1; } else if (SENSOR_1 < THRESHOLD -9) { go_forward(x,y); } else { go_right1(x,y);n=0; } Wait(STEP); } PlayTone(523,50); //音が鳴る } sub l_straight4(int x,int y) //左側で直進4 { int n = 0; while (n<300){ if (SENSOR_1 < THRESHOLD -15) { go_left1(x,y);n=0; } else if (SENSOR_1 < THRESHOLD - 9) { go_forward(x,y); } else { go_right2(x,y);n=n+1; } Wait(STEP); } PlayTone(523,50); //音が鳴る }
左側を直進するプログラムで、4つつくった。これらの違いとしては、黒い部分にいったときに行われる右に曲がるプログラムが、片方のタイヤを回すものと、両方回すものとで違いさらに、カウンターを増やすものとリセットするものでもわけており4つとなった。また、これらは違う場所で使われるのでその場所で調整し設定したカウンターの上限値となっている。
sub r_straight1(int x,int y) //右側で直進1 { int n = 0; while (n < 300){ if (SENSOR_1 < THRESHOLD -13) { go_right2(0,0); n=n+1; } else if (SENSOR_1 < THRESHOLD) { go_forward(x,y); } else { go_left1(x,y); n=0; } Wait(STEP); } PlayTone(523,50); //音が鳴る } sub r_straight2(int x,int y) //右側で直進2 { int n = 0; while (n < 300){ if (SENSOR_1 < THRESHOLD -13) { go_right2(0,0); n=0; } else if (SENSOR_1 < THRESHOLD) { go_forward(x,y); } else { go_left1(x,y); n=n+1; } Wait(STEP); } PlayTone(523,50); //音が鳴る } sub r_straight3(int x,int y) //右側で直進3 { int n = 0; while (n < 300){ if (SENSOR_1 < THRESHOLD -13) { go_right1(0,0); n=0; } else if (SENSOR_1 < THRESHOLD) { go_forward(x,y); } else { go_left1(x,y); n=n+1; } Wait(STEP); } PlayTone(523,50); //音が鳴る } sub r_straight4(int x,int y) //右側で直進4 { int n = 0; while (n < 250){ if (SENSOR_1 < THRESHOLD -13) { go_right2(0,0); n=n+1; } else if (SENSOR_1 < THRESHOLD) { go_forward(x,y); } else { go_left1(x,y); n=0; } Wait(STEP); } PlayTone(523,50); //音が鳴る }
右側を直進するプログラムで、基本左側と同じ理由で分けられている。しかし、1と4はカウンターの上限値が違うだけである。これは、引数を一つ増やして、1つにまとめることもできたが、作成中は気が付かなかった。
sub l_w() //白くなるまで左へ1 { int n = 0; while (n<50){ if (SENSOR_1 < THRESHOLD +15) { go_left1(sA4,sB4); } else { go_left1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る } sub l_w2() //白くなるまで左へ2 { int n = 0; while (n<50){ if (SENSOR_1 < THRESHOLD +15) { go_left2(sA4,sB4); } else { go_left1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る } sub l_w3() //白くなるまで左へ3 { int n = 0; while (n<150){ if (SENSOR_1 < THRESHOLD +15) { go_left1(sA4,sB4); } else { go_left1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る }
これは、ずっと左に回転し、白い部分の値を読み取るとカウンターを増やし上限値になると止まるプログラムである。これは、左への回転の仕方が違うものと、カウンターの上限値が違うものでわけてあり、これも、引数を使うと1つ減らすことができた。カウンターを50にしているのは、白と黒の境界線上ではなく白い値を読み取るところで止まってほしかったからである。カウンターの上限値を変えたのは、50にしても、次のプログラムに安定して行けない箇所があったからである。また、速度は遅めにして値をしっかり読めるようにしている。
sub r_w() //白くなるまで右へ1 { int n = 0; while (n<50){ if (SENSOR_1 < THRESHOLD +15) { go_right1(sA4,sB4); } else { go_right1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る } sub r_w2() //白くなるまで右へ2 { int n = 0; while (n<50){ if (SENSOR_1 < THRESHOLD +15) { go_right2(sA4,sB4); } else { go_right1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る }
これは、白くなるまでずっと右回転するバージョンである。これは、黒い部分での回転の違いで分けてある。
sub r_b() //黒くなるまで右へ1 { int n = 0; while (n<50){ if (SENSOR_1 > THRESHOLD - 9) { go_right1(sA4,sB4); } else { go_right1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る } sub r_b2() //黒くなるまで右へ2 { int n = 0; while (n<50){ if (SENSOR_1 > THRESHOLD - 9) { go_right2(sA4,sB4); } else { go_right1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る }
上のプログラムを黒くなるまで右にしたものである。これも、白い部分での回転方法の違いで分けている。
sub l_b() //黒くなるまで左へ1 { int n = 0; while (n<50){ if (SENSOR_1 > THRESHOLD - 9) { go_left1(sA4,sB4); } else { go_left1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る } sub l_b2() //黒くなるまで左へ2 { int n = 0; while (n<50){ if (SENSOR_1 > THRESHOLD - 9) { go_left2(sA4,sB4); } else { go_left1(sA4,sB4);n=n+1; } Wait(STEP); } PlayTone(659,50); //音が鳴る }
同じように、黒くなるまで左に行くプログラムである。
sub c_ca() //紙コップをつかむ { OnFwd(OUT_C,Cpower);Wait(1100); Off(OUT_C); } sub c_re() //紙コップを放す { OnFwd(OUT_C,-Cpower);Wait(1100); Off(OUT_C); }
紙コップをつかむのと放すプログラムである。時間で調整したため電池の消耗で誤差が大きく出てしまった。ここは、角度指定の方が向いていた。
今回プログラムを作成するのに、ベースとしてはカウンターを使う方法で作成したが、途中でうまくいかないときに改良したサブルーチンをどんどん追加していっていると数がとても多くなった。引数を入力するだけでうまくいくようなサブルーチンを1つ作れたらよかったが、いいアイデアが思い浮かばず、また、時間があまりなかった。そうすると、もうできているプログラムで進めるしかなくなってしまった。しかし、うまくいかないところは直せばしっかり動くので簡単なプログラムではあったと思う。
最初は枠の内側の右寄りに置いてスタートする。
task main() { SetSensorLight(S1); //端子1に光センサー gogo_b(0,0); //最初の枠から出る gogo_w(0,0); lele(0,0); r_straight1(0,0); //Bまで l_w(); //進路修正 r_straight1(0,0); //Cまで r_w(); r_straight1(0,0); //Fまで l_w(); //進路修正
↑枠からでた後のトレースは右側を通っていきBのまがり角を認識するとプログラムが終わり、もとに戻るプログラムが始まり、コースに戻るとまたトレースを再開する。
r_straight2(0,0); r_left(sA3,sB3);//カーブ1 l_w();//車線変更 l_straight4(0,0); PlayTone(523,50); l_right(sA3,sB3);//カーブ2 long t0 = CurrentTick(); while (CurrentTick() - t0 < 10000){ if (SENSOR_1 < THRESHOLD -15) { go_left1(0,0); } else if (SENSOR_1 < THRESHOLD -9) { go_forward(0,0); } else { go_right2(0,0); } Wait(STEP); } PlayTone(880,100); //音が鳴る l_straight2(sA2,sB2); Off(OUT_AB);//Rまで Wait(1000); l_w(); //進路修正 l_straight2(0,0); r_w();//Pまで r_b();
↑カーブは外側のラインをトレースしたかったので1つ目のカーブを曲がったあとに左側に車線変更している。(車線変更は反対側の白いところまで行くプログラムを使っている。)この左に曲がるプログラムは40%のところをまっすぐ走っているときにカウンターが増えるようにしている。また、Eの角を曲がるのにサブルーチンを何個も使わないといけなかったためわかりやすくするためにwhile文で一定の時間はライントレースさせ続けるようにした。
r_w2(); r_b2(); Off(OUT_AB);Wait(300); go_forward(0,0);Wait(500);Off(OUT_AB); c_ca(); //紙コップを取る Off(OUT_AB);Wait(300); go_back(0,0);Wait(450);Off(OUT_AB); go_left2(0,0);Wait(1500); //コースに戻る
↑これは、紙コップを回収するプログラムで、機体を回転させ棒で紙コップをよせてから回収している。また、安定して紙コップを取るために期待を停止させてから回収している。
l_straight2(0,0); PlayTone(880,100); l_w3();//Qまで long t1 = CurrentTick(); while (CurrentTick() - t1 < 2000){ if (SENSOR_1 < THRESHOLD -15) { go_left1(0,0); } else if (SENSOR_1 < THRESHOLD -9) { go_forward(0,0); } else { go_right1(0,0); } Wait(STEP); } PlayTone(880,100); l_straight3(0,0); Off(OUT_AB);Wait(1000);//Sまで r_w(); //進路変更 r_straight3(0,0); r_straight4(0,0);
↑途中のwhile文はそこの部分の色の塗り方の問題でうまく反応しなかったため一定の時間トレースするようにしている。また、カーブをトレースするプログラムがうまくいかなかったので直進用に作ったプログラムを入れてみるとうまくいったのでそのまま使っている。
r_w2(); r_b2(); r_w2(); go_right2(0,0);Wait(800); Off(OUT_AB);Wait(300); c_re(); //紙コップを放す Off(OUT_AB);Wait(300); l_b2(); go_forward(0,0);Wait(1200); //コースに戻る go_left2(0,0);Wait(1800);
↑紙コップを放すプログラム。棒を取り付けた影響で紙コップを置いた後決まった方にしか回ることができなかったため、余分な移動が増えてしまった。
r_straight4(0,0); Off(OUT_AB);Wait(1000);//Sまで l_w(); //進路変更 l_straight3(sA4,sB4); Off(OUT_AB);Wait(1000);//Fまで l_w(); //進路変更 r_straight1(0,0); Off(OUT_AB);Wait(1000);//Cまで gogo_w(0,0); l_straight2(0,0); gogo_w(0,0); go_right2(0,0);Wait(750); //枠の中に入れる go_forward(0,0);Wait(1200); Off(OUT_AB); PlayTone(880,200); //ゴール }
↑最後に枠の中にゴールするまでのプログラム。ここではライントレースをしてゴールするだけなのでこれまでと違ったところはなかったが、1ヶ所安定しないところがあったので速度を遅くした。また、最後に枠の中に入れるところは時間で動かした。
本番では、電池の消耗でプログラムがうまく動かず最初はダメだったが2回目ではうまく動いてくれた。電池が消耗しても速さを変えることで完璧にする予定だったが直前でうまく調整できなかったのが残念だった。
今回の課題ではカーブを曲がるところが難しく自分たちで作ったコースの色の塗り方が雑だったためカーブを上手く曲がれないことがあり苦戦した。もっと丁寧にコースを作るべきだったと後悔した。トレースのプログラム自体を途中で変えたので無駄な作業時間が多くとても時間がかかった。しかし、作業をしているときはそれほど時間を感じず楽しくできたし、達成感も大きかった。次の課題は期間が短くまた忙しい時期ではあるけれど、最後までしっかりやりきりたいと思う。