目次
#contents
*[[課題の説明>2014a/Mission1]] [#ofbcff9d]
*メンバー [#tbb423e0]
-bgpat: スタート→ゴール
-[[只の某>2014a/Member/tadanobo/Mission1]]: ゴール→スタート
*コースの作成 [#b0a98824]
&ref(2014a/Member/bgpat/Mission1/course.png);
&ref(2014a/Member/bgpat/Mission1/course.png,50%);

このような画像ファイルを作成して何枚かのA4用紙に分けて印刷し、台紙に貼り付けました。
薄い部分は黒いペンで塗りつぶしました。
正確なコースを作り、作業時間を短縮するために、
このような画像ファイルを作成しました。

セロハンテープを使って貼り付けたため、テープの部分が少しだけ明るくなってしまいましたが、トレースには問題ありませんでした。
これを何枚かのA4用紙に分けて印刷して台紙に貼り付け、
インクの薄い部分は黒いペンで塗りました。

しかし、貼り付ける作業に時間がかかってしまい、
あまいいい方法とは言えませんでした。

また、セロハンテープを使って貼り付けたため、テープの部分が少しだけ明るくなってしまいました。
しきい値の調整は難しかったですが、トレースにはほとんど問題ありませんでした。

*ロボット本体 [#i40141cf]
**ドライブベース [#i6612d7b]
説明書の見本をもとに組み立てました。

光センサーを近づけることで小回りが効くようにしています。
&ref(2014a/Member/bgpat/Mission1/body.JPG);
見本よりもタイヤの幅を狭くしたことで小回りが効き、
光センサーを前輪に近づけることでトレースがしやすくなりました。

パーツがたくさん余ったので補強をしっかりしておきました。

&ref(2014a/Member/bgpat/Mission1/body.JPG,100%);

**アーム [#i7d72672]
アームを上から下ろすことでボールを確保し、ボールを坂に押し上げます。
アームはNXT本体の右、車体の右肩にあたる部分に取り付けました。
モーターが車体からはみ出してしまうため、多めの補強を施しました。

坂のおかげでアームを上げるだけでシュートができます。
アームを上から下ろすことでピンポン玉を確保し、坂に押し上げます。
このとき、ピンポン玉の位置がずれても確保できるように坂を3箇所作りました。

坂は3箇所あり、位置がずれてもボールを取れる仕組みになっています。
&ref(2014a/Member/bgpat/Mission1/DSC01846.jpg);
ピンポン玉を確保したままゴールまで運びます。
シュートするときは、アームを上げるとピンポン玉が坂を転がります。

&ref(2014a/Member/bgpat/Mission1/DSC01846.jpg,20%);

*プログラム [#xe5408e4]

**定数 [#i0ee2036]
車体の構成に関する部分です

 // 入出力系
 #define LEFT_MOTER OUT_C
 #define RIGHT_MOTER OUT_A
 #define LIGHT_SENSOR S3
 
 // スピード
 #define SPEED 40
 
 // 回転センサーの係数
 #define MOTER_ROTATE (1 / 14.55)
 
 // 車体の幅
 #define BODY_WIDTH 12.2

**マクロ [#sf63adb2]
  // 回転センサーの値を取得する
  #define LeftRotation() MotorRotationCount(OUT_C)
  #define RightRotation() MotorRotationCount(OUT_A)
コードを簡略化するためのマクロです

 // 回転センサーの値を取得する
 #define LeftRotation() MotorRotationCount(OUT_C)
 #define RightRotation() MotorRotationCount(OUT_A)

**移動用モーター制御用関数 [#t3845988]
OnFwdとOnRevとOffをまとめて、スピードによって動作が変わるようになっています。  
OnFwdとOnRevとOffをまとめた関数です。
スピードによって動作が変わるようになっています。

  /*
   * モーターを回転させる
   * スピードが負のときは逆回転
   * 0のときは停止
  */
 void On(int leftSpeed, int rightSpeed){
     if(leftSpeed > 0){
         OnFwd(LEFT_MOTER, leftSpeed);
     }else if(leftSpeed){
         OnRev(LEFT_MOTER, -leftSpeed);
     }else{
         Off(LEFT_MOTER);
     }
     if(rightSpeed > 0){
         OnFwd(RIGHT_MOTER, rightSpeed);
     }else if(rightSpeed){
         OnRev(RIGHT_MOTER, -rightSpeed);
     }else{
         Off(RIGHT_MOTER);
     }
 }
 **明るさ判定

**明るさ判定関数 [#eb86e092]
ライントレースに使います。

授業では触れていませんが、[["switch"という構文>http://bricxcc.sourceforge.net/nbc/nxcdoc/nxcapi/switch.html]]を使うことを想定しています。
光センサーのしきい値はここに記入します。

 /*
  * 明るさを 0 〜 4 で返す
  * 暗い < 0 1 2 3 4 > 明るい
  */
 bool Light(){
     if(SENSOR_3 < 40){
         return 0;
     }else if(SENSOR_3 < 45){
         return 1;
     }else if(SENSOR_3 < 50){
         return 2;
     }else if(SENSOR_3 < 55){
         return 3;
     }else{
         return 4;
     }
 }
**メイン関数 [#t10751d9]

**メイン関数のコード [#sa2e140e]

[[解説はこちら>#k0f87237]]

コード全体が見たい人以外は飛ばしてください。
解説には部分ごとにコードを載せています。

 task main(){
     //角の回数を数えるための変数
     //通過ポイントを数えるための変数
     int corner = 0;
     
     // 車体の向き
     float rotation = 0;
     
     // 光センサー初期化
     SetSensorLight(LIGHT_SENSOR);
     
     // スタートから出る
     On(SPEED, SPEED);
     until(Light() == 0);
     until(Light() == 4);
     
     // トレース開始
     while(corner < 8){
         Wait(1);
         switch(Light()){
             case 0:  //左回転
                 On(-SPEED, SPEED);
                 break;
             case 1:  //左折
                 On(0, SPEED);
                 break;
             case 2:  //直進
                 On(SPEED, SPEED);
                 break;
             case 3:  //右折
                 On(SPEED, 0);
                 break;
             case 4:  //右回転
                 On(SPEED, -SPEED);
                 break;
         }
         
         // 回転センサーの値を取得する
         int _tl = LeftRotation();
         int _tr = RightRotation();
         
         // 差から角度を求める
         rotation += 360 * (_tr - tr - _tl + tl) * MOTER_ROTATE / (2 * BODY_WIDTH * PI);
         tl = _tl;
         tr = _tr;
         
         // 通過ポイント
         switch(corner){
             case 0:  // 1回目のヘアピンカーブ
                 if(rotation > 90){
                     PlayTone(TONE_C4, 100);
                     corner++;
                 }
                 break;
             case 1:  // 1回目の交差点
                 if(rotation < 20){
                     PlayTone(TONE_D4, 100);
                     On(SPEED, SPEED);
                     until(Light() == 0);
                     until(Light() >= 2);
                     corner++;
                 }
                 break;
             case 2:  // ボールを取る位置
             case 2:  // ピンポン玉を取る位置
                 if(rotation < -40){
                     PlayTone(TONE_E4, 100);
                     Wait(500);
                     On(SPEED, SPEED);
                     RotateMotor(OUT_B, 10, 90);
                     On(-SPEED, -SPEED);
                     Wait(500);
                     On(SPEED, 0);
                     until(Light() < 4);
                     corner++;
                 }
                 break;
             case 3:  // 大きいカーブ終了
                 if(rotation < -180){
                     PlayTone(TONE_F4, 100);
                     corner++;
                 }
                 break;
             case 4:  // 2回目の交差点
                 if(rotation > -180){
                     PlayTone(TONE_G4, 100);
                     On(SPEED, 0);
                     until(Light() == 4);
                     On(SPEED, SPEED);
                     until(Light() == 3);
                     corner++;
                 }
                 break;
             case 5:  // 3回目の交差点
                 if(rotation > 10){
                     PlayTone(TONE_A4, 100);
                     On(SPEED, 0);
                     until(Light() >= 3);
                     corner++;
                 }
                 break;
             case 6:  // 2回目のヘアピンカーブ
                 if(rotation > 90){
                     PlayTone(TONE_B4, 100);
                     corner++;
                 }
                 break;
             case 7:  // ゴール
                 if(rotation < -15){
                     PlayTone(TONE_C5, 100);
                     On(0, 0);
                     RotateMotor(OUT_B, 100, -90);
                     corner++;
                 }
                 break;
         }
    }
    Wait(2000);
 }
**解説 [#k0f87237]
***トレース [#f99fefa4]
明るさを0から4の5段階に分けて、0と4のときはその場で回転、1と3のときは曲がりながら進む、2のときは直進するようします。
**メイン関数の解説 [#k0f87237]

***変数とセンサーの初期化 [#k6bd95c1]

corner変数には通過ポイントの番号を、
rotation変数には車体の向きを格納します。

トレースに使用する光センサーを初期化します。

 //通過ポイントを数えるための変数
 int corner = 0;
 
 // 車体の向き
 float rotation = 0;
 
 // 光センサー初期化
 SetSensorLight(LIGHT_SENSOR);

***スタートから出る [#j7b5760d]
黒い線を跨ぐまで直進してスタートエリアから出ます。

 On(SPEED, SPEED);
 until(Light() == 0);
 until(Light() == 4);

***ライントレースのループ処理 [#j5421e60]

スタートエリアから出るとライントレースを開始します。
ゴールに到達してシュートするまで繰り返すループ関数です。

1ミリ秒で1ステップ進みます。

シュートをする前にプログラムが終了しないようにするために
ループのあとにWaitを入れました。

 while(corner < 8){
     Wait(1);
 
     /*
      * ライントレース
      */
 
     /*
      * 車体の向きを求める
      */
 
     /*
      * 通過ポイントごとの処理(交差点を渡るなど)
      */
 
 }
 Wait(2000);

***ライントレース [#f99fefa4]

明るさを0から4の5段階に分けて、0と4のときはその場で回転、1と3のときは曲がりながら進む、2のときは直進します。

[[Light関数についてはこちら>#eb86e092]]をご覧ください。

 switch(Light()){
     case 0:  //左回転
         On(-SPEED, SPEED);
         break;
     case 1:  //左折
         On(0, SPEED);
         break;
     case 2:  //直進
         On(SPEED, SPEED);
         break;
     case 3:  //右折
         On(SPEED, 0);
         break;
     case 4:  //右回転
         On(SPEED, -SPEED);
         break;
 }

***車体の向きを求める [#s4d5e350]

モーターの回転センサーを使って車体の向きを計算します。
"内側と外側の回転数の差"を"車体と幅から求めた車輪の円周"と"モーターが1回転したときの回転センサーの値"で割ることで計算しています。
正確な計算ではありませんが、だいたいあっていたのでこの方法を使いました。
通過ポイントを設定して、コースのどのあたりにいるかを求めています。
&ref(2014a/Member/bgpat/Mission1/point.png);
***ボール [#ec9fadc9]
1つ目の交差点を過ぎると角度を調整してアームを下ろします、このときボールが坂をのぼります。
0°は車体を置いた向きとなります。

ゴールにたどり着くとシュートはアームを上げるとボールが坂を転がります。
*感想 [#n5075af2]
なかなか思っている通りに動かず苦労しました。特に値の調整が大変でした。
 // 回転センサーの値を取得する
 int _tl = LeftRotation();
 int _tr = RightRotation();
 
 // 差から角度を求める
 rotation += 360 * (_tr - tr - _tl + tl) * MOTER_ROTATE / (2 * BODY_WIDTH * PI);
 tl = _tl;
 tr = _tr;

***通過ポイントごとの処理 [#nc87e345]

通過ポイントを設定して、車体の向きからコースのどのあたりにいるかを求めています。

デバッグ用に音を鳴らしています。

詳細は後述します。

&ref(2014a/Member/bgpat/Mission1/point.png,50%);

 switch(corner){
     case 0:
         /*
          * あとで解説
          */
         break;
 }

----

+1回目のヘアピンカーブ
~
~車体の向きが90°を超えると通過ポイント1を超えたことにします。
 if(rotation > 90){
     PlayTone(TONE_C4, 100);
     corner++;
 }
 break;
~
+1回目の交差点
~
~車体の向きが20°未満になると通過ポイント2を超えたことにして
~交差点を超えます。
~
~スタートと同じように線を超えるまで直進して交差点を超えます。
~
 if(rotation < 20){
     PlayTone(TONE_D4, 100);
     On(SPEED, SPEED);
     until(Light() == 0);
     until(Light() >= 2);
         corner++;
     }
 }
 break;
~
+ピンポン玉を取る位置
~
~1回目の交差点を超えて
~車体の向きが-40°(320°)より小さくなるとると
~ピンポン玉を拾います。
~
~直進しながらアームを下げ、
~ボールを確保すると元の位置までバックします。
~右に進みながらライントレースに復帰します。
~
 if(rotation < -40){
     PlayTone(TONE_E4, 100);
     Wait(500);
     On(SPEED, SPEED);
     RotateMotor(OUT_B, 10, 90);
     On(-SPEED, -SPEED);
     Wait(500);
     On(SPEED, 0);
     until(Light() < 4);
     corner++;
 }
 break;
~
+大きいカーブ終了
~
~車体の向きがスタートしたときと反対になると大きいカーブが終了したとみなします。
~
 if(rotation < -180){
     PlayTone(TONE_F4, 100);
     corner++;
 }
 break;
~
+2回目の交差点
~
~大きいカーブが終了して、車体が左に向くと2回目の交差点を超えます。
~
~進み方や光センサーの条件を変えていますが、1回目の交差点を超えるときとほとんど同じです。
~
 if(rotation > -180){
     PlayTone(TONE_G4, 100);
     On(SPEED, 0);
     until(Light() == 4);
     On(SPEED, SPEED);
     until(Light() == 3);
     corner++;
 }
 break;
~

*執筆中 [#e41a8e39]
時間が欲しい・・・

*コメント [#e006bb1b]
#comment


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS