#author("2020-01-10T18:31:55+09:00","yuu","yuu")
#author("2020-01-10T18:34:38+09:00","yuu","yuu")
目次
#contents
*課題2:ライントレース [#b5adab8b]
下の図のようなコースを各チームで作成し、「ミッション」を遂行するためのロボットを作成せよ。詳しいルールは[[2019b/Mission2]]を参照してください。
&ref(IMG_5730bcomp.png,nolink,90%, );

**コースについて [#lbeb3b1d]
1人目のコースを走ります。
+A地点から出発
+B
+C (直進)
+D (一時停止の後、直進)
+E, F 通過
+G (一時停止の後、左折)
+H (一時停止の後、左折)
+I (ボール or キューボイドをつかんでUターン)
+H (直進)
+J (一時停止)
+A地点に入る(ゴール)

線に沿って進めるか、交差点を判断できるか、ボールをつかめるか、が今回の課題のポイントになります。

*ロボットについて [#u97ed52b]
インストラクションに乗っていたロボット元にして、線を認識させるためのライトセンサ、ボールを認識させるための超音波センサ、ボールを回収するためのモータを取り付けました。
超音波センサは前にある物体との距離を測るセンサです。ボールを反応しやすいようセンサは斜めに取り付けました。(水平、垂直に取り付けた場合、正しく読み取ってくれないため)
モータにはボールを囲うためを枠がつけられています。ロボットがボールの前に来るとモータを回し、上げていた枠を下げ、そのままボールを転がして運びます。モータはNXT本体の下に入れ重心が前に行き過ぎないように本体を後ろのほうにもっていきました。枠を上げている状態と下げている状態の写真です。

&ref(IMG_5744bcomp.jpg,nolink,90%, );
&ref(IMG_E5690comp.jpg,nolink,81%, );

もともとボールを持ち上げて運ぶものを作ろうとしていたのですが、上手くいきませんでした。この時の試作はおまけで紹介します。

*プログラムについて [#rd8aea22]

&ref(IMG_E5747comp.jpg,nolink,90%, );
&ref(IMG_E5748comp.jpg,nolink,90%, );
**一本道の場合 [#s362ccf6]
線の左側をトレースする方法について説明します。ライトセンサを使い明るさを測定し、測定結果が白に近いならばロボットが左に寄っているから右に曲がる、黒に近いならばロボットが右に寄っているから左に曲がる、という動作を繰り返せば、ロボットは線の左側付近に沿って進んでくれます。(上、左側図参照)
**交差点の認識 [#jf8412e1]
「左に大きく曲がる」が続いた場合に交差点と判断します。具体的には、濃い黒を認識し続ける時間を計り、その時間が基準を超えた場合を交差点と見なしています。(上、右側図参照)
**ボールを拾う直前以外のライントレース サブルーチン [#na9ab930]
 void line_follow(long tmin=0, long tmax=0, int side=1)            //side:left=1, right=-1
 {
     SetSensorLight(S3);                                           //ライトセンサ使用
     int turn, bril;                                                          
     long time1 = CurrentTick();                                   //サブルーチン開始の時刻
     long time2 = CurrentTick();                                   //黒認識した時刻
     while((CurrentTick()-time2 < 230) && (CurrentTick()-time1 < tmax)){
         bril = SENSOR_3 - 50;                                     //ライトセンサの値-50
         turn = side*pow(bril*0.14, 5);                            //左右モータの回転比率
         if(abs(turn)>90){
             turn = 90*(abs(turn)/turn);                           //turnが±90を超えないように
          }
         OnFwdSync(OUT_BC, 20, turn);
         if((CurrentTick()-time1 < tmin)||(bril > -11)){
             time2 = CurrentTick();                                //time2 リセット
          }
         NumOut(0, 48, bril);                                      //画面に値を表示
         NumOut(0, 56, turn*side);
     }
     Off(OUT_BC);                                                  //停止
     Wait(1000);                                                   //1秒待つ
 }
ライントレースしてtmin[ms]経った後、交差点を見つけると1秒間停止して終了、見つけなくてもtmax[ms]経てば1秒間停止して終了します。tminの間は交差点かどうかを調べず、急なカーブでも線に沿って進ませます。引数のsideは線の左側をトレースさせるか右側をトレースさせるかを決めるものです。
>
 //line_followの8行目
 bril = SENSOR_3 - 50;                                    
 turn = side*pow(bril*0.14, 5);
 if(abs(turn)>90){
     turn = 90*(abs(turn)/turn);                           
 }

SENSOR_3はライトセンサの値で、明るい(白)ほど大きく、暗い(黒)ほど小さくなります。そのためbrilは白だと正、黒だと負の値になります。turnはbrilの5次関数です。白黒の境界線では、0に近い数になりますが、境界線から離れるとturnの絶対値は大きくなり、急に曲がります。turn>90ならturn=90、turn<-90ならturn=-90にすることで-90以上90以下の値になるように調節しています。

ちなみに、abs(A)はA絶対値、pow(A,B)はAのB乗を表します。
>
 //line_follow  7行目
 while((CurrentTick()-time2 < 230) && (CurrentTick()-time1 < tmax))
>
 //line_follow  14行目
 if((CurrentTick()-time1 < tmin)||(bril > -11)){
     time2 = CurrentTick(); 
 }

CurrentTick()-time1はサブルーチン開始から経過した時間で、これがtmaxより大きいとwhileループから抜けます。time2は濃い黒以外のところではリセットされるため、CurrentTick()-time2は濃い黒を認識し続けている時間を表します。この時間が230より大きいと濃い黒を認識し続けることなので、交差点と判断してwhileループを抜けます。

ちなみに、&&はAND(論理積)、||はOR(論理和)を表します。

**ボールを拾う直前(H→I)のライントレース ザブルーチン [#oe14a61a]
 void line_follow2(long tmin=0, long tmax=0, int side=1) 
 {
     SetSensorLight(S3);
     SetSensorLowspeed(S2);                                            //超音波センサ使用
     int turn,bril;
     long time1 = CurrentTick(); 
     long time2 = CurrentTick(); 
     while((CurrentTick()-time2 < 230) && (CurrentTick()-time1 < tmax) && (SensorUS(S2)>10)){
         bril = SENSOR_3 - 50;
         turn = side*pow(bril*0.14, 5);
         if(abs(turn)>90){
             turn = 90*(abs(turn)/turn); 
          }
         OnFwdSync(OUT_BC, 20, turn);
         if((CurrentTick()-time1 < tmin)||(bril > -11)){
             time2 = CurrentTick();
          }
         NumOut(0, 48, bril);
         NumOut(0, 56, turn*side);
     }
     Off(OUT_BC);
     Wait(1000);
 }
line_followとほとんど同じですが、8行目whileの条件式を追加し、超音波センサがボールを検知した時にもwhileループを抜けます。ボールを検知するかどうかの引数を入れて1つにまとめたかったのですがうまく動いてくれなかったので新しくサブルーチンを作ることにしました。

**G、H交差点での左回転 [#r994d37a]
 void turn_left(){
      SetSensorLight(S3);
      RotateMotorEx(OUT_BC, 30, 180, 0, true, true);
      OnFwdSync(OUT_BC, 30, -100);
      Wait(1000);                           //初めの線は無視
      while(SENSOR_3 > 60){}
      Wait(180);
      Off(OUT_BC);
 }
そのままライントレースを続けて交差点を曲がろうとすると、白黒の境界線から左右に大きくずれてしまい、次の交差点の判断ができなくなってしまう問題がありました。そのため、前に少し前に進み左旋回することで、ロボットが次の線の方向を向くようにしました。ライトセンサが線を見つけて少し時間が経つまで旋回するとセンサは線の位置に左側の位置に来るのでそのまま次のライントレースを続けられます。

&ref(IMG_E5732comp.jpg,nolink,90%, );
&ref(IMG_E5733comp.jpg,nolink,90%, );
&ref(IMG_E5734comp.jpg,nolink,90%, );

**I地点での右旋回 [#a9309a44]
 void turn_right(){
      SetSensorLight(S3);
      OnFwdSync(OUT_BC, 30, 100);
      Wait(1000);
      while(SENSOR_3 > 60){}
      Wait(180);
      Off(OUT_BC);
 }
ボールを拾った後のUターンです。Uターンした後、センサの位置はHを超えて(Hより左)いるため、H交差点を無視して進めます。(2回目のH交差点では停止する必要はない。)
**メインプログラム [#xcf574a1]
 task main()
 {    
      ClearScreen();
      Off(OUT_A);
      RotateMotorEx(OUT_BC, 20, 100, 0, true, true);     //A
      line_follow(3000, 100000, 1);                      //A,B,C,D
      TextOut(0, 0, "ABCD");
      RotateMotorEx(OUT_BC, 20, 100, 10, true, true);    //D
      line_follow(3000, 100000, 1);                      //E,F,G
      TextOut(0, 8, "EFG");
      turn_left();
      line_follow(1000, 100000, 1);                      //G,H
      TextOut(0, 16, "GH");
      turn_left();
      line_follow2(1000, 100000, 1);                     //H,I
      TextOut(0, 24, "HI");
      RotateMotorEx(OUT_BC, 30, 40, 0, true, true);
      RotateMotor(OUT_A, 30, -45);                       //掴む
      Off(OUT_A);                                                          
      turn_right();                                      //旋回
      TextOut(0, 32, "IH");
      line_follow(30000, 60000, -1);                     //H,J
      TextOut(0, 40, "IJ");
      RotateMotorEx(OUT_BC, 30, 500, 0, true, true);
 }
交差点を直進するときにはライトセンサを使うとややこしくなるので、RotateMotorExで進ませています。ボールをつかみUターンした後、半径5cmの急カーブがあるため、少しでも緩やかに曲がれるようにその部分だけ右側をトレースすることにしました。また、どこまでプログラムが動作したか知るために、画面に進んだ交差点の記号を表示させています。
*結果・感想 [#jf3ae9c7]
JからAに入るところで交差点を検知してくれず、ゴール付近まで進んだのですが完走できませんでした。左トレースと右トレースの違いが原因なのかどうかはわかりませんが、完走できなかったのは悔しいです。今回、左右の曲がり具合は5次関数で決めました。そのため、定数1つ変えればいいという面もありますが、調節しにくく、線から外れるとなかなか境界線に戻れず、境界線を左右に往復し続けてしまい、場合によっては交差点と誤認することもありました。また、電池を新しくすると速度が早くなり、すぐにコースアウトしてしまうことから、スピードは遅めにしています。速度調節
JからAに入るところで交差点を検知してくれず、ゴール付近まで進んだのですが完走できませんでした。左トレースと右トレースの違いが原因なのかどうかはわかりませんが、完走できなかったのは悔しいです。今回、左右の曲がり具合は5次関数で決めました。そのため、定数1つ変えればいいという面もありますが、調節しにくく、線から外れるとなかなか境界線に戻れず、境界線を左右に往復し続けてしまい、場合によっては交差点と誤認することもありました。また、電池を新しくすると速度が早くなり、すぐにコースアウトしてしまうことから、スピードは遅めにしています。もともと速度調節
機能をつけようとして、brilが0に近いところでは加速させていました。しかし、カーブでも加速しあまりなめらかに動かなかったのでやめました。brilの正負を記録し、「正負どちらか一方に偏っている場合はカーブと判断する」とするとうまくいくかもしれません。

*おまけ [#veb8571c]
プログラムの内容ばかりになったので、掴む機構の試作(失敗作)を軽く紹介します。モータ1つで持ち上げるところまでやろうとしたので複雑になっています。大きい白歯車を回転させることで、てこクランク機構のような動きでアームを上下を動かします。左端のすくう部分は重力によって反時計回りに回転しようとするのですが、大きい黒歯車に付けられた突起(3枚目の写真がわかりやすい)によって止められています。歯車の回転とともにすくう部分も回転しボールを回収します。

&ref(IMG_5682(1)comp.jpg,nolink,50%, );
&ref(IMG_5682(4)comp.jpg,nolink,50%, );
&ref(IMG_5682(5)comp.jpg,nolink,50%, );
&ref(IMG_5682(7)comp.jpg,nolink,50%, );

超音波センサの位置を調節しにくい、ボールが正面にないと回収できないことから、枠でボールを囲む機構に変えました。

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS