課題

空き缶に乗ったボールを運搬し、ゴール地点にある別の空き缶の上に乗せる。

詳細

?plugin=ref&page=2019b%2FMission2&src=2019b-mission2.png

コースは課題2で作ったものをそのまま使用し、発表会にて得点形式で他のグループと争う。

ロボットの説明

ボールを空き缶と共に運ぶロボットと、そのボールを受け取りゴールの空き缶に置くロボットの2台を作製した。

1台目

IMG_71255.JPG

このロボットがボールの乗った空き缶全てを一度に運搬し、コース中央部まで運ぶ。
構造は単純で、本体前方の長いアームで缶を縦に3つ並べることができる。
このアームは右側のみが可変で、左側は動かない。空き缶を最短コースで取りに行く方針で、B,C,D,G点をなぞる。そのため、3つとも取り終わるまで左折は必要ない。取った後に右アームを下ろして固定する。
またアームの長さがD-E間より長くなっているため、D点で片側のタイヤを軸にして右折することによりE,Fをなぞる必要がないという利点がある。

2台目

IMG_71277.JPG

このロボットがボールを受け取りゴールまで運搬する。1台目より機構が複雑になっている。

受け取る機構

ボールを受け取るために超音波センサーと光センサーを使用する。
上記のセンサーで缶の位置をおおよそ把握し、本体上部についているプロペラの回転半径内にボールを配置してからプロペラを回すことで、缶の上に乗ったボールを引きずり上げるような形で受け取ることができる。回転角の調整で最大3つの保持が可能だ。

IMG_71244.JPG

ゴールに置く機構

ゴールの空き缶にボールを置く機構は完成したが、離れた位置にある缶を見つける機構が不完全だった。

IMG_71266.JPG

この機構は空き缶を押した時に効果を発揮し、缶を押したときに動いたブロックで光センサーを塞いで値を変えることができる。
ライントレースの黒い部分よりも明るさがはっきり低くなるようブロックの位置を調節し、缶にたどり着くまでに横切ったラインによって誤作動しないようにした。

IMG_7128 (2).JPG

缶によって押される部分は3回作動しなければ意味が無く、奥まで押されなければ作動しない。
ブロックを元の位置に戻すためのおもりを取り付け、押される部分との間を歯車でつなぎ、空き缶のような非常に軽い物体でも押すことができるようにした。

プログラムの説明

マスター側(1台目)

ライントレース

#define BLACK 30
#define THRESHOLD 43
#define WHITE_GRAY 51
#define WHITE 57
#define SPEED 20
#define go_forward OnFwdSync(OUT_BC,SPEED,0);
#define curve_left Off(OUT_C); OnFwd(OUT_B,SPEED);
#define curve_right Off(OUT_B); OnFwd(OUT_C,SPEED);
#define turn_left OnFwdSync(OUT_BC,SPEED,-100);
#define turn_right OnFwdSync(OUT_BC,SPEED,100);

まずはライントレースのために明るさの値と使われる動作を定義する。
明るさはできるだけ厳しく調整しないとトレースによるアームのブレが大きくなる。

void line_followleft()
{
   SetSensorLight(S4);
   long t0=CurrentTick();
   while(CurrentTick()-t0<150){   //最後の更新からの経過時間が0.15秒以内である限り
     if(SENSOR_4<BLACK){
       turn_left;          //黒線上で左回転する
     }else{
       if(SENSOR_4>WHITE){
         turn_right;         //白いところで右回転する
       }else if(SENSOR_4>WHITE_GRAY){
         curve_right;        //白寄りの境界で右に曲がる
       }else if(SENSOR_4>THRESHOLD-5){
         go_forward;         //境界線上で直進する
       }else{
         curve_left;         //他の値を取れば左に曲がる
       }
     t0=CurrentTick();        //黒以外のときはt0を更新する
     }
   }
   Off(OUT_BC);
   Wait(1000);
   turn_right;
   Wait(300);
   Off(OUT_BC);           //交差点を見つけたら止まって方向を修正する
   Wait(500);
}

これは黒線の左側の境界線上をトレースし、交差点を見つけた場合に停止するプログラムである。

メイン

float GetAngle(float d)       //旋回したい角度を未知数とする
{
   const float diameter = 5.45;
   const float length = 12.0;
   float ang = d*length*2/diameter;
   return ang;
}

先に、片方のタイヤを軸にして回転する際のタイヤの回転角を算出する式を作る。

#define CONN 1
#define SIGNALON 11
#define few_go(t) OnFwdSync(OUT_BC,SPEED,0); Wait(t); Off(OUT_BC); Wait(1000);
#define raise_hand OnRev(OUT_A,SPEED); Wait(450); Off(OUT_A);
#define hold_can OnFwd(OUT_A,SPEED); Wait(400); Off(OUT_A);
#define back(t) OnRev(OUT_BC,SPEED); Wait(t); Off(OUT_BC);

こちらはマスター側なのでスレーブの接続番号は1。
few_go(t)は交差点を越えるための動作である。使用する場所によって調整が必要なため、tの関数にしている。

task main()
{
   int angle = GetAngle(90.0);
   int msg;

   raise_hand;            //右側のアームを上げる
   line_followleft();
   RotateMotor(OUT_C,SPEED,angle);  //ライントレースした後、D点で右に90度旋回する
   hold_can;             //缶を固定するために右アームを下げる
   line_followleft();
   few_go(800);            //G点の交差点を越える
   line_followleft();
   RotateMotor(OUT_B,SPEED,angle);   //H点で左に90度旋回する
   back(1000);            //位置調整
   raise_hand;
   back(5000);
   SendRemoteNumber(CONN,MAILBOX1,SIGNALON); 
                    //缶を離した後にスレーブに命令を送信する
}

こちらの動作は主にライントレースを行うのみとなっている。
H点で旋回した後はライン上に缶が3つ並んだ状態となる。

スレーブ側(2台目)

ライントレース

#define BLACK 34
#define THRESHOLD 43
#define WHITE_GRAY 48
#define WHITE 53
#define SPEED 30
#define go_forward OnFwdSync(OUT_BC,SPEED,0);
#define curve_left Off(OUT_C); OnFwd(OUT_B,SPEED);
#define curve_right Off(OUT_B); OnFwd(OUT_C,SPEED);
#define turn_left OnFwdSync(OUT_BC,SPEED,-100);
#define turn_right OnFwdSync(OUT_BC,SPEED,100);

まずはマスターと同じように定義する。

void line_followleft()
{
   long t0=CurrentTick();
   while(CurrentTick()-t0<160){    //交差点を判別するために要する時間が0.16秒
     if(SENSOR_2<BLACK){
       turn_left;
     }else{
       if(SENSOR_2>WHITE){
         turn_right;
       }else if(SENSOR_2>WHITE_GRAY){
         curve_right;
       }else if(SENSOR_2>THRESHOLD-3){
         go_forward;
       }else{
         curve_left;
       }
     t0=CurrentTick();
     }
   }
   Off(OUT_BC);
   Wait(1000);
   turn_right;
   Wait(200);
   Off(OUT_BC);
   Wait(500);
}

左の境界線上のライントレースはマスター側とほとんど同じである。
トレースするラインがマスターと違うため、交差点の判別に必要な値を調節している。

void line_followright()
{
   long t0=CurrentTick();
   while(CurrentTick()-t0<200){     //最後の更新から0.2秒経たない限り
     if(SENSOR_2<BLACK){
       turn_right;            //黒線上で右回転する
     }else{
       if(SENSOR_2>WHITE){
         turn_left;           //白いところで左回転する
       }else if(SENSOR_2>WHITE_GRAY){
         curve_left;              //白寄りの境界で左に曲がる
       }else if(SENSOR_2>THRESHOLD-5){
         go_forward;           //境界線上で直進する
       }else{
         curve_right;          //他の値を取れば右に曲がる
       }
     t0=CurrentTick();          //黒以外のときはt0を更新する
     }
   }
   Off(OUT_BC);
   Wait(1000);
   turn_left;
   Wait(200);
   Off(OUT_BC);             //交差点を見つけたとき停止して方向を調整する
   Wait(500);
}

右のライントレースは左のものと全く逆の動作を入れてある。

旋回・回転の角度

   const float diameter = 5.45;
   const float length = 12.0;

計算式を2つ使用するので、同一の値は外に出しておく。

float GetAngle1(float d)      //回転したい角度を未知数にする
{
   float ang = d*length*2/diameter;
   return ang;
}

1つ目はその場で回転する際のタイヤの回転角を算出するものである。

float GetAngle2(float k)      //旋回したい角度を未知数にする
{
   float ang = length*k/diameter;
   return ang;
}

2つ目は片方のタイヤを軸にして旋回する際のタイヤの回転角を算出するものである。

ボールを受け取る

void fetch_ball()
{
   int angle = GetAngle1(60.0);

 repeat(3){
   while(SensorUS(S3)>7){        //超音波センサーが7cmより大きい値である限り
     if(SENSOR_2<BLACK){
       turn_left;
       }else if(SENSOR_2>WHITE){
         turn_right;
       }else if(SENSOR_2>WHITE_GRAY){
         curve_right;
       }else if(SENSOR_2>THRESHOLD-5){
         go_forward;
       }else{
         curve_left;
       }
   }                   //左の境界をライントレースする
   Off(OUT_BC);
     OnFwd(OUT_BC,SPEED);
     Wait(600);
     Off(OUT_BC);             //超音波センサーが他の値を取ったとき停止し、そのまま前進する
     RotateMotor(OUT_A,SPEED,92.0);    //プロペラを約90度回転させ、ボールを取る
     RotateMotor(OUT_B,SPEED,-angle);   //もうボールが乗っていない空き缶をどける
     RotateMotor(OUT_B,SPEED,angle);   //方向を戻す
   }                   //以上を3回繰り返し、ボールを3つ取る
}

ボールを受け取る動作には超音波センサーと光センサーを使用し、超音波センサーがボールを捉えるまでライントレースするようになっている。

ボールを置く

void put_ball()
{
   long t0=CurrentTick();
   long t_start,t_last;
   t_start = CurrentTick();
   while(SENSOR_2>21&&CurrentTick()-t0<5000){   //光センサーの値が21より大きく、かつ最後の更新から5秒経たない限り
     OnFwdSync(OUT_BC,20,0);            //前進し続ける
   }
   Off(OUT_BC);                  //光センサーが21以下を示すあるいは前進して5秒経過したとき停止する
   t_last = CurrentTick()-t_start;         //前進し始めてから光センサーが21以下を示すまでの時間を記録する
   RotateMotor(OUT_A,SPEED,90.0);         //プロペラを回転させてボールを置く
   OnRevSync(OUT_BC,20,0);
   Wait(t_last);
   Off(OUT_BC);                   //先程記録した時間のぶん後進する
}

まず、これは未完成である。 このプログラムはロボットの説明で紹介したギミックを使用している。
缶によってギミックが押されるまで前進し、ボールを置いて同じ場所まで戻ってくるというものだ。
whileの条件によって、缶を見つけなくても5秒以上経つとボールを捨てて戻ってきてしまうという欠点があり、うまく使用できなかった。

ゴールの缶を探す

この動作については先生に配られた紙に書いてあるものを参考にした。

#define SPEED_SLOW 20
void turnAng(long ang)
{
   long angle;
   angle = length/diameter*ang;
   RotateMotorEx(OUT_BC,SPEED_SLOW,angle,100,true,true);
}

これは指定した角度まで回転するというもの。

int searchDirection(long ang)
{
   long angle,tacho_min=0,tacho_corr;
   int d_min;

   d_min=300;                                   //仮の最小値を決める

   angle = (length/diameter)*ang;
   turnAng(ang/2);               //指定されている角度の半分の角度で回転する
   ResetTachoCount(OUT_BC);           //角度のカウントをリセットする

   OnFwdSync(OUT_BC,SPEED_SLOW,-100);
   while(MotorTachoCount(OUT_B)<=angle){    //回転角が指定より小さい限り
     if(SensorUS(S3)<d_min&&SensorUS(S3)>10){
       d_min=SensorUS(S3);           //仮の最小値より小さく、かつ10より大きい値を取ったときに仮の最小値を更新する
       tacho_min=MotorTachoCount(OUT_B);    //またその際の角度も更新する
     }
   }
   OnFwdSyncEx(OUT_BC,SPEED_SLOW,100,RESET_NONE);
   until(MotorTachoCount(OUT_B)<=tacho_min);  //記録された角度に達するまで回転する

   Wait(14);                  //微調整
   Off(OUT_BC);
   Wait(500);
   return d_min;
}

これもうまく作動しなかった。
このプログラムは最も近くにある缶の方向を見つけるというもの。
仮の最小値を更新する条件に「10cmより大きい」というものが加わっているのは、時々4などの非常に小さい値を示す状況が見られたためである。
またこの動作はH点で使用するので、缶との最小距離が10cmを下回ることはないだろうという見立てもあった。

メイン

#define SIGNALON 11
#define few_go(t) OnFwdSync(OUT_BC,SPEED,0); Wait(t); Off(OUT_BC); Wait(1000);
#define back(t) OnRev(OUT_BC,SPEED); Wait(t); Off(OUT_BC);

タスクメインで使用する他の動作を定義する。

task main()
{
   int msg;
   int angle1 = GetAngle1(60.0);
   int angle2 = GetAngle2(90.0);
   int angle3 = GetAngle1(90.0);               //使用する角度をすべて定義する。

   SetSensorLight(S2);
   SetSensorLowspeed(S3);           //センサーも同じく設定する

   line_followleft();
   back(1200);                 //ライントレースして位置を調整する
   RotateMotor(OUT_C,SPEED,angle3);      //旋回して黒線の上に本体が来るように調節する
   few_go(600);
   while(msg != SIGNALON){           //マスターから命令が来るまで受信を続ける
     ReceiveRemoteNumber(MAILBOX1,false,msg);
   }
   fetch_ball();
   RotateMotorEx(OUT_BC,SPEED,angle2,-100,true,true);  //ボールを取った後、線の左側の白い部分に光センサーを配置する
   line_followright();                  //右のライントレースをする
   few_go(700);
   RotateMotorEx(OUT_BC,SPEED,angle2,100,true,true);
   back(700);                      //方向を調整し、少し後ろに下がる
   repeat(2){
     searchDirection(100);
     Wait(1000);
     put_ball();
   }
}

repeatの中がうまく作動しなかった。
最初の左側ライントレースをした後に後ろに下がると入力しているのは、本体がカーブを曲がりきれずH点で止まらないからである。
このプログラムでは本体がライントレース後G点で停止するため、後進してから方向調整するといった流れになっている。

まとめ・反省

今回はロボット製作とプログラム作成のどちらにも非常に時間がかかった。特に時間を要したのはスレーブ側である。光センサーを隠すことで缶の位置を正確に見つけることができる仕掛けを思いついたのがプログラム作成に取り掛かっていたときだったため、プログラムを最後まで書き上げることができなかった。
また以前の課題と変わらず、ロボットが大きくなってしまったことが最大の反省点である。もう少しコンパクトなロボットを作れたら良かった。


添付ファイル: fileIMG_71244.JPG 16件 [詳細] fileIMG_71277.JPG 19件 [詳細] fileIMG_71266.JPG 21件 [詳細] fileIMG_71255.JPG 21件 [詳細] fileIMG_7128 (2).JPG 14件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2020-02-13 (木) 13:35:46