目次
#contents
*課題3:ボール運搬ロボット[#l3956274]
青と赤のボールを運搬して、それぞれ所定の350ml缶の上に乗せる。

&ref(2018b/Member/Jerry/Mission3/cource3.jpg,75%,課題3のコース);

-フィールドの説明 
--フィールドは課題2で使用した紙を使用する。
--350ml缶(中が入っていても空でよい)は逆さまにして使い、ロボットのスタート直後にサイコロを振って出た目の番号の位置に置く。
--その番号の次の番号のところにもダミーの缶を置くことができる。ただし6の目が出た場合は6の位置と25cm以上離れていない場所ならどこに置いてもよいものとする。距離は缶と缶のもっとも近い部分で測る。
--空き缶には色をつけたり文字や記号を書いてもよい。あるいは周囲に紙を張ってもよい。
--赤と青のボールは、図のように所定の場所に置いておく。その際、キットに含まれない小さな輪ゴムを使用するものとする。
**ルール [#vb163c4f]
-基本ルール
--競技時間は審判が続行不能と判断するまで、あるいはリタイアするまで。
--図のX地点または(および)Y地点からスタートする。ただし接地している部分はそれぞれの領域内に収まるものとする(線上はOK)。上空部分は領域からはみ出していてもよい。
--赤いボールを図のピンクのいずれかに置いた缶に、青いボールを図の水色のいずれかに置いた缶に、それぞれ乗せる。
--開始の合図から5秒以内にスタートボタンを押す作業を完了すること。
--競技が終了するまで、ロボットに触ったり人間が遠隔で操作してはならない。
--途中でうまく動かなくなった場合、1回限り再スタートすることができる(再スタートの際に別プログラムで起動してよい)。
-基本得点の計算方法
--ボールを一つ乗せればそれぞれ10点、二つとも乗せればボーナス点としてさらに10点。
--ダミーの缶を設置した上で、正しい缶に乗せれば、それぞれさらに6点加点する。
--ボールを目的の缶に当てることができれば、それぞれ4点。
--ボールを同じ領域内の間違った缶に乗せた場合は、それぞれ6点。
--ボールを同じ領域内の間違った缶に当てた場合は、それぞれ2点。
--ボールを違う領域内の缶に乗せた場合は、それぞれ2点。
--ボールを違う領域内の缶に当てた場合は、0点。
--目的の缶をもとの位置(直径7cmの円)から少し出してしまった場合は1点減点、半分以上出してしまった場合は2点または取得した得点の半分のいずれか少ないほうを減点、その缶を完全にだしてしまったときは点数を半分にする。
--ダミーの缶がもとの位置から移動しても減点はしない。

-技術点の計算方法

以下の動作の精度・スピード・確実性などを含めた技術的な工夫や芸術性について他の全てのチーム(5チーム)が20点満点で採点し、その平均点を求める。 得点の目安:
--ボール探し取りにいくまでの動作 (3点)
--ボール掴む動作 (3点)
--ボールを運ぶ動作 (2点)
--ボールを缶に置く動作 (2点)
--2台のNXT、EV3の連携の良さ(2点)
--自立型のロボットとしての形や動作の美しさ、斬新さ(2点)
--その他 (3点)
*今回のロボットについて [#e2ba4992]
**今回のロボットに至った経緯 [#gd80fdde]
&ref(2018b/Member/Jerry/Mission3/3.jpg,50%,ロボット);

今回のロボットは2台のNXTを通信してプログラムを制御するので、主に「2台のNXTを別々に制御する」または「2台のNXTを1体化させて大きなロボットを作る」という2つの方針が考えられるが私たちのグループでは、後者の方針を用いることにした。理由は、ライントレースを確実に行うことを重視し、さらには、ロボットを別々に制御する際にボールを缶の高さまで持ち上げることが難しいのではないかと考えたからである。
**ロボット作りで工夫した点 [#w6cbb8f6]

-光センサ

&ref(2018b/Member/Jerry/Mission3/5.jpg,40%,光センサ);

今回の課題では、光センサを2つ用いることができたので、私たちのグループではセンサ1(左側の光センサ)をライントレースのために使い、センサ4(右側の光センサ)を交差点認識のために使うことでライントレースと交差点認識を確実に行えるようにした。
-ボールを持ち上げる

今回のロボット作りで一番大変だったのが「ボールを持ち上げる」という動作であった。当初はベルトコンベアーのような部品を作り、ボールを缶の高さまで上昇させるというプランを思いついたが、部品を作ることが想像以上に難しく大変であったため断念した。

次に思いついた方針は、写真のようにボールを掴んでいるモータ自体を持ち上げるというプランである。「この方針なら大丈夫」と思っていたが、いざプログラムを作り動かしてみると、モータ自身の重みでアームが持ち上がらない、持ち上がってもすぐにアームが下がるという予想外の問題が発生した。

&ref(2018b/Member/Jerry/Mission3/4.jpg,40%,ボールを持ち上げる);

私たちのグループでは、この問題を解決するための方法として、NXT本体にアームを当てて、アームの高さを維持するという方法を思いつき、この方法でボールを持ち上げることにした。
-軽量化

「2台のNXTを1体化させて大きなロボットを作る」という方針を用いたため、必然的にロボットを動かすタイヤにはNXT2台分の重さという大きな負荷がかかってしまう。実際にロボットを動かしてみた時も、ロボット自体の重さのせいでタイヤが動かなくなったり、遠心力でロボットが倒れるという失敗も経験したので、必要最小限の部品で軽いロボットを作ることを目指した。
-コード

&ref(2018b/Member/Jerry/Mission3/2.jpg,40%,コード);

今までの課題と違い、光センサ2つ、超音波センサ、ボールを掴むためのモータ、ボールを持ち上げるためのモータ、タイヤの為に用いる2つのモータといった計7本のコードを使いながらも、小型のロボットを作ったためにコードが絡まりやすく、タイヤの動きやボールを持ち上げる動作を妨げることがあったので、2台のNXTの間にコードをしまうための空間を作り、コードがロボットの動きを妨げないようにした。、
*ロボットを動かすためのプログラム [#t28312fb]
**ライントレース [#xad04020]
今回も課題2の時と同様に「黒と白の境界線をライントレースする」というプログラムを制作した。ただ、今回の課題では、先ほども書いた通り光センサを2つ使っているため、より簡単にライントレースを行うことができた。今回は光センサを地面と近い位置に設置しているため
明るさの値が3段階でライントレースを行うことができた。
    #define go_forward OnFwd(OUT_A,40);OnFwd(OUT_C,40)  //直進,Aは一番右のモータ,Cは一番左のモータ
    #define turn_left1 OnFwd(OUT_A,35);OnFwd(OUT_C,-30)  //左旋回
    #define turn_right1 OnFwd(OUT_C,35);OnFwd(OUT_A,-30) //右旋回
白と黒の境界線の明るさの値が50だったので
    #define THRESHOLD 50
と定義して、下図のように左側のライントレースだけを行うようにしてライントレースプログラムを作成した。

&ref(2018b/Member/Jerry/Mission3/line_trace.jpg,75%,左側のライントレース);
***交差点を認識するライントレース [#g6bfc511]
 void Lfollow_line(long min_t) //min_tにいれた時間分だけラインの左側をライントレースする。
    {    
    long t0=CurrentTick(); //現在の時間を記録する。
    while(CurrentTick()-t0<=min_t) //現在の時間とt0の時間の差がmin_t以下の時、以下のプログラムを実行する。{
            if ((SENSOR_1<THRESHOLD-7)&&(SENSOR_4<THRESHOLD-11)){
              Off(OUT_AC);Wait(1000); //S1の明るさの値が43未満かつS4明るさの値が39未満の時、ロボットが停止する。
            }
            else if (SENSOR_1<THRESHOLD-7){
            turn_left1; //明るさの値が43未満の時、turn_left1の動きをする。
            } 
            else if (SENSOR_1<THRESHOLD+7){  
            go_forward; //明るさの値が43以上57未満の時、go_forwardの動きをする。
            } 
            else {
            turn_right1; //それ以外の時、turn_right1の動きをする。
            }  
    }
このプログラムは以下のように動く。

&ref(2018b/Member/Jerry/Mission3/stop.jpg,50%,交差点を認識するライントレース);
***交差点を横断するプログラム [#u785261f]
    #define follow_intersection ResetTachoCount(OUT_AC);RotateMotor(OUT_AC,30,100);
    #define follow_intersection2 ResetTachoCount(OUT_AC);RotateMotor(OUT_AC,30,300);
このプログラムは交差点を横断するために用いる。2種類存在するのはfollow_intersectionのプログラムを3回打つのを省略するために用いたので特に深い意味はない。
***交差点を認識した後後退するプログラム [#s832d5d2]
    #define Mfollow_intersection ResetTachoCount(OUT_AC);RotateMotor(OUT_AC,-30,210);
このプログラムは青のボールを掴むための距離を取るために用いる。
***交差点を認識しないライントレース [#qca2b6e4]
 void follow_line(long min_t) //min_tにいれた時間分だけラインの左側をライントレースする。
    {    
    long t0=CurrentTick(); //現在の時間を記録する。
    while(CurrentTick()-t0<=min_t) //現在の時間とt0の時間の差がmin_t以下の時、以下のプログラムを実行する。{
            if (SENSOR_1<THRESHOLD-7){
            turn_left1; //明るさの値が43未満の時、turn_left1の動きをする。
            }
            else if (SENSOR_1<THRESHOLD+7){  
            go_forward; //明るさの値が43以上57未満の時、go_forwardの動きをする。
            } 
            else {
            turn_right1; //それ以外の時、turn_right1の動きをする。
            }  
    }
このプログラムは以下のように動く。

&ref(2018b/Member/Jerry/Mission3/left.jpg,50%,交差点を認識しないライントレース);
**缶を認識し、缶に近づくためのプログラム(赤いボールをつかんだ時) [#qb69c2fe]
***缶を認識するためのプログラム [#ya1ed339]
   
   #define SPEED 50
   #define SPEED_SLOW 30
   #define SPEED_MSLOW -30
   const float diameter = 5.45; //タイヤの直径(cm)
   const float track = 10.35;   //タイヤのトレッド幅(cm)
   const float pi = 3.1415;     //円周率

 void turnAng(long ang) //角度 ang 度の時計回りの旋回
    {
    long angle;
    angle = track/diameter*ang;
    RotateMotorEx(OUT_AC, SPEED_SLOW, angle, 100, true, true);   
    }
 
 int searchDirection(long ang)// 現在の方向を中心にang度の範囲で探す。
                              // 障害物までの距離を返す。
    {
    long angle, tacho_min=0, tacho_corr;
    int d_min;
    d_min=300;
    angle = (track/diameter)*ang;// 旋回角度からタイヤの回転を計算する。
    turnAng(ang/2);              // 旋回角度からタイヤの回転を計算する。
    ResetTachoCount(OUT_AC);     // 角度計測をリセットする。
    OnFwdSync(OUT_AC,SPEED_SLOW,-100);
    while(MotorTachoCount(OUT_A)<=angle){
          if(SensorUS(S2)<d_min){
          d_min=SensorUS(S2);          // 仮の最小値を更新する。
          tacho_min=MotorTachoCount(OUT_A);
          }
    }
    OnFwdSyncEx(OUT_AC,SPEED_SLOW,100,RESET_NONE);
    until(MotorTachoCount(OUT_A)<=tacho_min||SensorUS(S2)<=d_min);
    Wait(14);                   //微調整
    Off(OUT_AC);Wait(500);
    return d_min;
    ResetTachoCount(OUT_AC);    // 角度計測をリセットする。
    }

&ref(2018b/Member/Jerry/Mission3/find_d.jpg,40%,空き缶を認識する仕組み(d_min));

***缶に近づくためのプログラム [#k02ee8ec]
 void fwdDist(float d) //距離 d cm 前進する。
    {
    long angle;
    angle = d/(diameter*pi)*360.0 ;   //角度を計算する。
    RotateMotorEx(OUT_AC,SPEED_SLOW,angle,0,true,true);
    }
このプログラムにより、さきほどのsearchDirection()という関数によって得られたd_min (cm) の値の分だけロボットが前進する。また、このプログラムのモータを逆回転し下記のようなプログラムをつくることでd_min (cm) だけロボットを後退させ、缶を探し始めた位置まで戻ることが可能となる。
 void MfwdDist(float d) //距離 d cm 後退する。
    {
    long angle;
    angle = d/(diameter*pi)*360.0 ;   ////角度を計算する。
    RotateMotorEx(OUT_AC,SPEED_MSLOW,angle,0,true,true);
    }
**缶を認識し、缶に近づくためのプログラム(青いボールをつかんだ時) [#v2bf3074]
***缶を認識するためのプログラム [#t550e9de]
 void turnAng2(long ang2) //角度 ang2 度の時計回りの旋回
    {
    long angle2;
    angle2 = track/diameter*ang2;
    RotateMotorEx(OUT_AC, SPEED_SLOW, angle2, 100, true, true);   
    }
 
 int e_searchDirection(long ang2) //現在の方向を中心にang2度の範囲で探す。
                                   // 障害物までの距離を返す。
    {
    long angle2, tacho_min2=0, tacho_corr2;
    int e_min;                     //仮の最小値
    e_min=300;
    angle2 = (track/diameter)*ang2; //旋回角度からタイヤの回転を計算する。
    turnAng2(ang2/2);               //指定された角度の半分を旋回する。
    ResetTachoCount(OUT_AC);        //角度計測をリセットする。
    OnFwdSync(OUT_AC,SPEED_SLOW,-100); //反時計回りに旋回する。
    while(MotorTachoCount(OUT_A)<=angle2){
           if(SensorUS(S2)<e_min){
           e_min=SensorUS(S2);             //仮の最小値を更新する。
           tacho_min2=MotorTachoCount(OUT_A);
           }
    }
    OnFwdSyncEx(OUT_AC,SPEED_SLOW,100,RESET_NONE);
    until(MotorTachoCount(OUT_A)<=tacho_min2||SensorUS(S2)<=e_min);
    Wait(14);                       微調整
    Off(OUT_AC);Wait(500);
    return e_min;
    ResetTachoCount(OUT_AC);        //角度計測をリセットする。
    }

&ref(2018b/Member/Jerry/Mission3/find_e.jpg,40%,空き缶を認識する仕組み(e_min));


***缶に近づくためのプログラム [#k000b718]
    void e_fwdDist(float e) //距離 e cm 前進する。
    {
    long angle2;
    angle2 = e/(diameter*pi)*360.0 ;   //角度を計算する。
    RotateMotorEx(OUT_AC,SPEED_SLOW,angle2,0,true,true);
    }
このプログラムにより、さきほどのe_searchDirection()という関数によって得られたe_min (cm) の値の分だけロボットが前進する。また、このプログラムのモータを逆回転し下記のようなプログラムをつくることでe_min (cm) だけロボットを後退させ、缶を探し始めた位置まで戻ることが可能となる。
 void e_MfwdDist(float e) //距離 e cm 後退する。
    {
    long angle2;
    angle2 = e/(diameter*pi)*360.0 ;   //角度を計算する。
    RotateMotorEx(OUT_AC,SPEED_MSLOW,angle2,0,true,true);
    }
**ボールを掴むためのプログラム(子機への命令) [#kdc79699]
 void Ball_catch()
    {
    RemoteStartProgram(1,"Last(S1).rxe"); //子機内にあるLast(S1)というプログラムを実行するように命令する。
    while(msg != 11) //子機から「11」というメッセージが来るまで待つ。{
    ReceiveRemoteNumber(MAILBOX1,true,msg); //メールボックス1を空にする。←子機からのメッセージが親機の卯木気を妨げるのを防ぐため。
    }
    Wait(3000);
    }
ボールを掴む動作自体は子機が行うため、このプログラムは子機に向かって親機が「ボールを掴め」と命令するようなプログラムとなっている。子機のプログラミングが終了したことを知らせるための合図としてmsgという変数を用いる。また、子機の接続番号は1と定義している。
    int msg;// 子機のプログラムが終了したことを知らせるための合図。
    #define CONN 1  // 子機の接続番号
**ボールを掴むためのプログラム(子機が動く) [#ba95a8da]
   Last(S1)
    #define Ball_catch ResetTachoCount(OUT_B);RotateMotor(OUT_B,-25,75); //電池残量によらず、常に一定の動きをする。
    task main()
    {
    Ball_catch;           //ボールを掴む。
    SendResponseNumber(MAILBOX1,11); //親機に「11」というメッセージを送る。
    Wait(1000);
    }

これらのプログラムは下図のように進行する。

&ref(2018b/Member/Jerry/Mission3/11.jpg,25%,ボールを掴む仕組み);

**ボールを放出するためのプログラム(子機への命令) [#s71877b2]
 void Ball_release()
    {
    RemoteStartProgram(1,"Last(S2).rxe"); //子機内にあるLast(S1)というプログラムを実行するように命令する。
    while(msg != 12) //子機から「12」というメッセージが来るまで待つ。{
    ReceiveRemoteNumber(MAILBOX1,true,msg);  //メールボックス1を空にする。←子機からのメッセージが親機の動きを妨げるのを防ぐため。
    }
    Wait(3000);
    }
ボールを放出する動作自体は子機が行うため、このプログラムは子機に向かって親機が「ボールを放出しろ」と命令するようなプログラムとなっている。子機のプログラミングが終了したことを知らせるための合図としてmsgという変数を用いる。また、子機の接続番号は1と定義している。
    int msg;// 子機のプログラムが終了したことを知らせるための合図。
    #define CONN 1  // 子機の接続番号
**ボールを放出するためのプログラム(子機が動く) [#b2fb030b]
 Last(S2)
    #define Ball_release ResetTachoCount(OUT_B);RotateMotor(OUT_B,25,75); //電池残量によらず、常に一定の動きをする。
    task main()
    {
    Ball_release; //ボール放出する。
    SendResponseNumber(MAILBOX1,12); //親機に「12」というメッセージを送る。
    Wait(1000);
    }

これらのプログラムは下図のように進行する。

&ref(2018b/Member/Jerry/Mission3/12.jpg,25%,ボールを放出する仕組み);


**アームを上げるためのプログラム [#j88e765a]
    #define Ball_up RotateMotor(OUT_B,-50,160);Wait(2000);
    #define Ball_up2 RotateMotor(OUT_B,-50,50);Wait(2000);
初めのプログラムはボールを掴んだ後にNXT本体にアームを当て、アームが下がってこない位置までボールを持ち上げるために使い、2つ目のプログラムは缶にボールを乗せた後に、アームをもとの位置まで戻すために用いられる。
**アームを下げるためのプログラム [#p47e43bd]
    #define Ball_down RotateMotor(OUT_B,30,60);Wait(2000);
    #define Ball_down2 RotateMotor(OUT_B,30,150);Wait(2000);
初めのプログラムは缶に近づいた後に、アームを下げてボールを缶に乗せるために使い、2つ目のプログラムは青いボールを掴むために用いられる。
*全体のプログラム [#h97cd118]
 task main()
 {
    SetSensorLight(S1); //左側のセンサ
    SetSensorLowspeed(S2); //超音波センサ
    SetSensorLight(S4); //右側のセンサ
    Wait(2000); //スタートボタンを押してから、離れるまでの時間。
    Ball_catch(); //子機にボールを掴ませる。
    follow_intersection; //スタートラインを横断する。
    Lfollow_line(3000); //次の交差点(G地点)まで進んで停止する。
    follow_intersection; //交差点を横断する。
    Ball_up; //アームをあげる。←このまま進行していくと青いボールにぶつかってしまう。
    Lfollow_line(18000); //次の交差点(E地点)まで進んで停止する。
    int d=searchDirection(120);//缶を探す。
    fwdDist(d-18.0); //缶に近づく。
    searchDirection(20); //缶にボールを乗せられるように微調整する。
    Ball_down; //アームを下げる。
    Ball_release(); //子機で缶にボールを乗せる。
    MfwdDist(d-17.0); //缶から離れる。←元の位置よりも1�儔爾�ることにより、E地点の交差点で直進することを防ぐ。
    Ball_up2; //ライントレースを行う際に、ボールや缶にぶつかるのを防ぐためにアームを上げる。
    follow_line(2000); //ライントレースに戻る。この時、E地点を左折する。
    Lfollow_line(3000); //次の交差点(I地点)まで進んで停止する。
    Mfollow_intersection; //青いボールを確実につかむことができるように後退する。
    Ball_down2; //アームを下げる。
    Ball_catch(); //子機にボールを掴ませる。
    Ball_up; //アームをあげる。
    follow_intersection2; //赤いボールを当てるための缶を超音波センサで感知しないように、円IHKJの中心まで前進する。
    int e=e_searchDirection(120); //缶を探す。
    e_fwdDist(e-18.0); //缶に近づく。
    e_searchDirection(20); //缶にボールを乗せられるように微調整する。
    Ball_down; //アームを下げる。
    Ball_release(); //子機で缶にボールを乗せる。
    e_MfwdDist(e-18.0); //缶から離れてもとの位置に戻る。
 }
*反省と考察 [#f94e2960]
今回の課題では、プログラム自体は簡単に作ることができたが、重量や可動域などがロボットの動きに大きな影響を与えてしまい思い通りに動かすことのできるロボットを作ることがとても大変だった。本番では2つのボールを掴むことはできたが、赤いボールは缶に当てることができたが、青いボールは缶を正確に認識することができず上手く缶に当てることができなかった。練習時には、1段階ではなく、2段階で缶を探すプログラムを作成してみたがどちらのプログラムでも毎回同じようには缶を探すことはできなかった。

&ref(2018b/Member/Jerry/Mission3/miss.jpg,25%,缶を正確に探すことができなかった原因);

私は、缶がうまく認識できないのは、缶の側面が曲面で超音波が変な方向に反射してしまうことが原因なのではないかと考えた。また、缶を探すときにもっとロボットの速度を遅くすることで缶を正確に認識することができたのではないかと考えた。

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