#author("2020-02-15T23:56:36+09:00","yuu","yuu")
#author("2020-02-20T20:00:41+09:00","yuu","yuu")
[[2019b/Member]]
目次
#contents
*課題3:ボール運搬ロボット [#w5bac6c7]
交差点C, E, F, C', E', F'に空き缶が置かれていて、そのうち、C, E, Fの空き缶の上にボールが乗っています。C, E, Fの空き缶の上のボールを運び、C',  E', F'に空き缶の上に乗せるロボットを作ります。詳しくは[[2019b/Mission3]]を参照してください。
**ルールの一部 [#d7b41439]

''基本ルール''
--図のA地点または(および)A'地点からスタートする。ただし接地している部分はそれぞれの領域内に収まるものとする(線上はOK)。上空部分は領域からはみ出していてもよい。
--競技が終了するまで、ロボットに触ったり人間が遠隔で操作してはならない。
--競技終了後、ロボットが、空き缶に触れていてはいけない。

''基本得点の計算方法''
--ボールを1個運べば8点。3つとも運べば24点。
--競技終了後、ゴールの空き缶が半分以上円からはみ出しまった場合、3点減点。
--競技終了後、ゴールの空き缶が完全に円から出しまった場合、4点減点。
--競技終了後、空き缶にロボットが触れていれば、もとの基本点の3減点。

*ロボットについて [#uae2a7dd]
2台のNXT一体型のロボットです。ライントレース機構とボールを掴む放す機構からなります。
**ライントレース機構 [#f2528dfe]
線を見つけるためのライトセンサ2つを前に左右並べて付け、前にある空き缶との距離を測る超音波センサを前向きにつけています。これらのセンサーの値を元に2つのモータで移動します。これらのセンサやモータはマスター側に接続します。ロボットの前面です。

&ref(IMG_5847comp.jpg,nolink,80%, );

**ボールを掴む、放す機構 [#qa6f0068]
もともとアームで左右からボールを掴む形を作っていたのですが、握力がないと途中でボールを落としてしまい運べませんでした。そこでロボットの一部に空き缶と同じ高さの台を付け、ボールを手前に寄せればボールを落とすことなく拾えると考えました。3つのボールを同時に運べるように改良した結果、写真のロボットになりました。
3つの棒がついた回転部分を回すだけで順回転でボールをレールの上に乗せて回収し、レールには傾斜があるので、逆回転させるとボールは転がり、1つずつ置けます。さらに空き缶を動かないように固定する棒をつけることでボールを確実に拾えるようにしました。モーターは重いので本体の上に持って行き、歯車で回転部分と固定棒まで伝達しています。(ギア比は特に意味なし) モータはスレーブ側に接続します。ロボットの側面(左)とボールを回収している様子(右)です。

&ref(IMG_5848comp.jpg,nolink,80%, );
&ref(IMG_5849comp.jpg,nolink,80%, );
 
*プログラムについて [#x7118ba6]
**マスター側プログラム [#a848c1f8]
***マクロについて [#d02d7d6c]
 //master program
 //left  sensor:S1    motor:C
 //right sensor:S2    motor:B
 #define CONN 1    //1のスレーブNXTと通信(マスターは0)
 #define grayS1 50    //左ライトセンサの基準
 #define grayS2 45    //右ライトセンサの基準
 #define blackS1 45    //左ライトセンサの基準
 #define blackS2 40    //右ライトセンサの基準
 int fast = 50;    //モータのスピード(直線)
 int slow = 30;    //モータのスピード(曲線)
grayはライントレース時と左右両方線がある交差点の判断、blackは左右のうち一方だけ線がある交差点の判断に使います。
***自作関数    lineFollow() [#r81cb8c0]
 void lineFollow(int breaksensor)    //S1:0, S2:1, S1+S2:2, S4:3
 {
    SetSensorLight(S1);
    SetSensorLight(S2);
    SetSensorLowspeed(S4);
    int lightL, lightR, dist = 100
    while(true){
        lightL = SENSOR_1;    //左ライトセンサの値
        lightR = SENSOR_2;    //右ライトセンサの値
        if((lightL>grayS1) && (lightR>grayS2)){
            OnFwdSync(OUT_BC, fast, 0);    //直線
        }else if((lightL>grayS1) && (lightR<grayS2)){
            OnFwdSync(OUT_BC, slow, 50);    //右へ
        }else if((lightL<grayS1) && (lightR>grayS2)){
            OnFwdSync(OUT_BC, slow, -50);    //左へ
         }
        if((breaksensor == 0) && (lightL<blackS1)) break;    //左線の交差点判断
        if((breaksensor == 1) && (lightR<blackS2)) break;    //右線の交差点判断
        if((breaksensor == 2) && (lightL<grayS1) && (lightR<grayS2)) break;    //左右線の交差点判断
        if((breaksensor == 3) && (SensorUS(S4)<10)) break;    //空き缶判断
    }
    Off(OUT_BC);
 }
ライトセンサ2つ使ったライントレースです。左センサ白、右センサ白なら直進、左センサ黒、右センサなら左カーブ、左センサ白、右センサ黒なら、右カーブします。

whileループから抜けるためにどのセンサを使うかの引数breaksensorを使います。左線の交差点なら0、右線の交差点なら1、左右線の交差点なら2、空き缶なら3とします。例えば十字路の交差点を認識させる場合は、lineFollow(2);  というプログラムにすれば、左センサの値がgrayS1より小さくかつ右センサの値がgrayS2より小さい、つまり両方黒と認識する場合に止まります。

***自作関数    turnLeft()、turnRight() [#x61ab8b8]
 void turnLeft(int deg){
    SetSensorLight(S1);
    SetSensorLight(S2);
    RotateMotorEx(OUT_BC, fast, -deg, 0, true, true);
    RotateMotor(OUT_B, fast, 400);
    until((SENSOR_1>grayS1) && (SENSOR_2>grayS2)){
        OnFwd(OUT_B, fast);
    }
    Off(OUT_BC);
 }

 void turnRight(int deg){
    SetSensorLight(S1);
    SetSensorLight(S2);
    RotateMotorEx(OUT_BC, fast, -deg, 0, true, true);
    RotateMotor(OUT_C, fast, 400);
    until((SENSOR_1>grayS1) && (SENSOR_2>grayS2)){
        OnFwd(OUT_C, fast);
    }
    Off(OUT_BC);
 }

引数degだけ下がった後、左または右のモータ1つだけを回しカーブします。このカーブの時にタイヤが滑ってしまうことが多く、滑ってしまった時のためにライトセンサの値も利用し、左右白認識になる(2つのセンサの間に線が来る)まで、カーブを続けます。しかし、場合によっては線が全くないところまで回ることがあり、迷子になるので、これを避けるためにdegを調節しなければなりませんでした。
引数degだけ下がった後、左または右のモータ1つだけを回しカーブします。このカーブの時にタイヤが滑ってしまうことが多く、滑ってしまった時のためにライトセンサの値も利用し、左右白認識になる(2つのセンサの間に線が来る)まで、曲がり続けます。しかし、場合によっては線が全くないところまで回ることがあり、迷子になるので、これを避けるためにdegを調節しなければなりませんでした。


***自作関数    sendMessage() [#c6b18d6c]
 void sendMessage(int number){
    int val = 0;    //スレーブから送られてくる数、メインでint valとしていたので、ここのintは不要かも
    SendRemoteNumber(CONN, MAILBOX1, number);
    until(val == number){
        ReceiveRemoteNumber(MAILBOX2, true, val);
    }
 }
ボールを回収したり、置く機構はスレーブ側なのでマスターからスレーブに伝えます。引数numberをスレーブに送ったあと、同じ数が送り返されるまで(スレーブが動作し終わるまで)待ちます。

***メインプログラム [#w5e36fb4]
 task main(){
    SetSensorLight(S1);
    SetSensorLight(S2);
    int val = 0;
    until(BluetoothStatus(CONN) == NO_ERR);    //スレーブと接続できるまで待つ
    RemoteStartProgram(CONN, "lineFollow_s.rxe");    //スレーブプログラム開始
 //ボール1回収
    RotateMotorEx(OUT_BC, fast, 180, 0, true, true);    //スタートゾーンの線を無視して進む
    lineFollow(3);
    sendMessage(11);
    RotateMotorEx(OUT_BC, fast, 400, 0, true, true);    //空き缶を前にずらす
    sendMessage(1);
    sendMessage(12);
 //ボール2回収
    RotateMotorEx(OUT_BC, fast, -500, 0, true, true);
    lineFollow(1);
    turnRight(40);
    lineFollow(2);
    RotateMotorEx(OUT_BC, fast, 70, 0, true, true);    //交差点直進
    lineFollow(3);
    sendMessage(11);    //固定棒下げる
    sendMessage(2);     //回収
    sendMessage(12);    //固定棒上げる
 //ボール3回収
    turnLeft(270);    //RotateMotorEx(OUT_BC, fast, -300, true, true);
                      //lineFollow(2);
                      //turnLeft(30);    このように変えたほうがいいかも
    lineFollow(2);
    turnRight(30);
    lineFollow(3);
    RotateMotorEx(OUT_BC, fast, -150, 0, true, true);
    OnFwdSync(OUT_BC, fast, 0);    //交差点認識で止まらないように
    lineFollow(3);
    sendMessage(11);
    sendMessage(3);
    sendMessage(12);
 //ボール3置く
    turnRight(270);
    lineFollow(2);
    RotateMotorEx(OUT_BC, fast, 70, 0, true, true);
    lineFollow(2);
    turnLeft(40);
    lineFollow(1);
  
    turnRight(40);
    lineFollow(2);
    RotateMotorEx(OUT_BC, fast, 70, 0, true, true);
    lineFollow(2);
    turnRight(40);
    lineFollow(3);
    RotateMotorEx(OUT_BC, fast, -150, 0, true, true);
    OnFwdSync(OUT_BC, fast, 0);
    lineFollow(3);
    sendMessage(11);
    sendMessage(4);    //置く
    sendMessage(12);
 //ボール2置く
    turnRight(290);
    lineFollow(2);
    turnLeft(50);
    lineFollow(3);
    RotateMotorEx(OUT_BC, fast, -150, 0, true, true);
    OnFwdSync(OUT_BC, fast, 0);
    lineFollow(3);
    sendMessage(11);
    sendMessage(5);
    sendMessage(12);
 //ボール1置く
    RotateMotor(OUT_C, fast, -400);
    RotateMotor(OUT_B, fast, 400);
    until((SENSOR_1>grayS1) && (SENSOR_2>grayS2)){
        OnFwd(OUT_B, fast);    //タイヤが滑ってしまった時のためにライトセンサで向きを調整
    }
    Off(OUT_BC);
    lineFollow(3);
    RotateMotorEx(OUT_BC, fast, -200, 0, true, true);
    OnFwdSync(OUT_BC, fast, 0);
    lineFollow(3);
    sendMessage(11);
    sendMessage(6);
    sendMessage(12);
    RotateMotorEx(OUT_BC, fast, -100, 0, true, true);
    SendRemoteNumber(CONN, MAILBOX1, 21);
 }

下にルートを載せておきます。緑の円はボールを回収する空き缶、青の円はボールを置く空き缶です。アルファベットはプログラムと対応しています。~
 F…lineFollow~
 M…RotateMotorEx~
 R…turnRight~
 L…turnLeft~
 S…sendMessage

 
&ref(IMG_5730_1comp.gif,nolink,100%,ボール1回収);
&ref(IMG_5730_2comp.gif,nolink,100%,ボール2回収);
&ref(IMG_5730_3comp.gif,nolink,100%,ボール3回収);


&ref(IMG_5730_4comp.gif,nolink,100%,ボール3置く);
&ref(IMG_5730_5comp.gif,nolink,100%,ボール2置く);
&ref(IMG_5730_6comp.gif,nolink,100%,ボール1置く);

**スレーブ側プログラム [#p6c499b2]
***マクロについて [#ib2f0793]
 //slave program
 #define CONN 0
 #define barDown RotateMotor(OUT_C, 100, 500)    //固定棒下げる
 #define barUp RotateMotor(OUT_C, 100, -500)    //固定棒上げる

***自作関数    ball() [#a44afcbc]
 void ball(int count = 0){
    if(count == 1) RotateMotor(OUT_B, 40, 430);    //ボール回収
    if(count == 2) RotateMotor(OUT_B, 40, 330);    //ボール回収
    if(count == 3) RotateMotor(OUT_B, 40, 460);    //ボール回収
    if(count == 4) RotateMotor(OUT_B, 40, -460);    //ボール放す
    if(count == 5) RotateMotor(OUT_B, 40, -330);    //ボール放す
    if(count == 6) RotateMotor(OUT_B, 40, -430);    //ボール放す
 }
回転部分を回し、ボールを回収したり、置いたりするプログラムです。回転棒の間隔が違うので、モータを回転させる角度を使い分けています。
***メインプログラム [#j33a5702]
 task main(){
    int val=0, oldval;
    while(true){
        ReceiveRemoteNumber(MAILBOX1, true, val);
        if(val != oldval){
            if((val>0) && (val<10)){
                ball(val);
            }else if(val == 11){
                barDown;
            }else if(val == 12){
                barUp;
            }else if(val == 21){
                break;
            }
            SendResponseNumber(MAILBOX2, val);
        }
        oldval = val;
    } 
 }
MAILBOX1で送られてきたvalの値によってする動作を決めます。valが1〜6の時は回転部分の上下、valが11、12の時は固定棒の上下、valが21の時はbreakでwhileループから抜けてプログラムを終了します。プログラム終了以外の動作の後はマスター側へvalを送り、動作が終わったことを伝えます。同じメッセージを2度受け取らないように前に受け取った値と異なる場合のみ動作します。

*結果 [#d48fd802]
H交差点左折時にタイヤが滑ってしまったり、途中で線を見失ったりして、ボールの回収はできても置くところまでたどり着きませんでした。基本点は0点です。

*反省 [#ld7d4b74]
二台のNXT一体型のためロボットが重く、重心の位置が後ろだったため、後ろのブロックと床との間に大きな摩擦力が働いたのがタイヤが滑った原因の可能性が高いです。後ろにキャスターをつけた時はキャスターの向きによってバランスを崩したり、バックする時にまっすぐに下がれないことから後ろの支えはブロックだけにしました。重心をタイヤの位置に近づけると後ろの負担が減り、いいそうです。

2つ目、3つ目のボールを回収したあと、空き缶を検知した場所を基準に右左折していたのですが、空き缶を見つけるのが遅くれ空き缶を前に押し出してしまうと位置がずれてしまいます。その部分も一度大きくバックした後lineFollowで前進し交差点を見つける、または線に沿ってバックするプログラムを作り交差点を見つけて、交差点の場所を基準に右左折したほうが確実でした。

課題2の時はモータとライトセンサが離れていたので、左右のモータを逆回転させて右左折してもライトセンサで線を検知できました。(図1)しかし今回、モータとセンサが近かったので旋回するとライトセンサは黒認識し続けてしまいます。(図2)そのため、片方のモータだけを回して右左折することで、センサの位置の回転半径を大きくして線を認識しやすくしています。(図3)しかし、右左折し始める位置がずれると迷子になってしまいます。

この問題のロボットでの解決方法は前輪のモータをより後ろに付けることです。そうすれば、センサとの距離が大きくなるので、旋回しても線を認識しやすくなりそうです。また、重心と前輪の位置も近く後ろのブロックの負担も減らせ、タイヤが滑る問題も解決できるかもしれません。
プログラムでの解決方法は右左折する時にセンサ1つにすることです。右折なら右のライトセンサで線の右端をたどれば、線を見失うことはなくなりそうです。


&ref(IMG_5867comp.gif,nolink,80%,図1);
&ref(IMG_5868comp.gif,nolink,80%,図2);
&ref(IMG_5869comp.gif,nolink,80%,図3);
//&ref(IMG_5818_Moment(7).jpg,80%, );
//#attach();

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