[[2016b/Member]] *目次 [#p78c52c6] #contents *課題概要 [#lac1e0b9] #ref(2016b/Mission3/2016b-mission3.png,50%,コース) ピッチャーロボットはAを出てQまで到達し、M内に入る。バッターロボットはCを出てBに入る。ピッチャーロボットが打ち出した球をバッターロボットが打つことで、コース内各所に設置された目標へ球を入れる。 ピッチャーロボットはAを出てM内に入る。バッターロボットはCを出てBに入る。ピッチャーロボットが打ち出した球をバッターロボットが打つことで、コース内各所に設置された目標へ球を入れる。 *ピッチャーロボットについて [#q2e08f0b] **車体 [#u9079446] #ref(2016b/Member/Megukyo/Mission3/pittyarzentai.jpg,50%,車体) 車体はこのようになっており、塔のような部分にボールを搭載し、車体中央部の打ち出し機構を通って発射される。ほかのチームにも似たタイプのバッターロボットがあるが、こちらは打ち出し機構にもモーターの動力を使うことで斜面を滑って発射される球よりも速度が高く、安定した弾道を実現している。 #ref(2016b/Member/Megukyo/Mission3/pittyarsensar.jpg,50%,車体) センサーは光、超音波センサを使用、車体フレームの外側に付けることで分解性と調整のしやすさを上げている。超音波センサは色々試した結果、なるべく高めに設置したほうがより上手くバッターロボットを検出出来ることが分かり、この高さに落ち着いた。 #ref(2016b/Member/Megukyo/Mission3/pittyarkikousaibu.jpg,50%,車体) 前方 #ref(2016b/Member/Megukyo/Mission3/utidasikukou.jpg,50%,車体) 後方~ 打ち出し機構部分は一個のモーターで駆動しており、前方の打ち出し用の車輪と後方の給弾部分にギアで動力が伝達される。後方の給弾機構は一度に90度回転するようになっており、一発ずつ確実に打ち出せるようになっている。後方から前方へはわずかに斜度がついており、球は重力によって自然と運ばれる。 #ref(2016b/Member/Megukyo/Mission3/utidaksikikou.jpg,50%,車体) 横から見るとこのようになっている。 **プログラム[#sd1c2f32] プログラムは、大まかには、まずピッチャーロボットがMまで到達→バッターロボットと通信→(バッターロボットがBまで到達)→(バッターロボットがピッチャーロボットに通信)→ピッチャーロボットがバッターロボットを検知して向きを調整→バッターロボットに通信→(ピッチャーロボットがバッターロボットを検知して向き、位置を調整)→(バッターロボットがピッチャーロボットに通信)→球を打ち出す、という流れになっている。 #define moter_conect_move OUT_AB //駆動輪モーターの接続コネクタ(ABC順) #define moter_conect_option OUT_C //駆動輪以外のモーターの接続コネクタ(ABC順) #define moter_conect_move_L OUT_A //左駆動輪モーターの接続コネクタ #define moter_conect_move_R OUT_B //右駆動輪モーターの接続コネクタ #define SPEED 55 //通常モーター速度 #define SPEED_FAST 60 //通常モーター速度(高速) #define SPEED_SLOW 40 //通常モーター速度(低速) #define SPEED_line_trace 30 //ライントレース時の速度(機体の重量によって調節する) #define SPEED_FAST_line_trace 40 //ライントレース時の速度(高速) #define SPEED_SLOW_line_trace 30 //ライントレース時の速度(低速) #define THRESHOLD 40 //白と黒の中間の反射率 const float diameter = 5.45; //タイヤの直径(cm) const float track = 11.38; //タイヤのトレッド幅(cm) const float pi=3.1415; //円周率 //関数エリア void stop_move() //すべての出力モーターの停止(すべてのサブルーチン内に同じこと書くと、見づらいため) { Off(OUT_ABC); Wait(1); } void fwdDist(float d) //距離 d cm 前進 { stop_move(); //モーターのリセット float angle = d/(pi*diameter)*360.0; //前進に必要な角度を計算する RotateMotorEx(moter_conect_move, SPEED_SLOW, angle, 0, true, true); //計算した角度 動かす } void turnAng_L(float ang) //左に ang (°) 回転 { stop_move(); //モーターのリセット float angle = track/diameter * ang; //左回転に必要な角度の計算 RotateMotorEx(moter_conect_move, SPEED_SLOW, angle, 100, true, true); //計算した角度動かす } void turnAng_R(float ang) //右に ang (°) 回転 { stop_move(); //モーターのリセット float angle = track/diameter * ang; //右回転に必要な角度の計算 RotateMotorEx(moter_conect_move, SPEED_SLOW, angle, -100, true, true); //計算した角度動かす } int searchDirection(long ang) //一番近い距離を探して、距離(cm)を返す(アルミ缶の検出プログラムと殆ど同じ) { stop_move(); //モーターのリセット long tacho_min ; int d_min = 300 ; long angle = (track/diameter)*ang; turnAng_L(ang/2); ResetTachoCount(moter_conect_move); OnFwdSync(moter_conect_move,SPEED_SLOW,-100); while(MotorTachoCount(moter_conect_move_L)<=angle) { int US_average = SensorUS(S4) ; //3回ほど超音波センサーの値を取得し、平均値を出すことで、突発的なセンサーの値のズレの影響を少なくする US_average = US_average+SensorUS(S4); // US_average = US_average+SensorUS(S4); // US_average = US_average / 3 ; // if (US_average<d_min) { d_min = SensorUS(S4); tacho_min = MotorTachoCount(moter_conect_move_L); } } OnFwdSyncEx(moter_conect_move,SPEED_SLOW,100,RESET_NONE); until(MotorTachoCount(moter_conect_move_L)<=tacho_min || SensorUS(S4)<=d_min); Wait(360); Off(moter_conect_move);Wait(500); return d_min; } void line_trace(int sensor_light_lv) //ライントレースのプロセス { if (sensor_light_lv < THRESHOLD-8) { OnFwd(moter_conect_move_R,SPEED_FAST_line_trace); OnFwd(moter_conect_move_L,-SPEED_FAST_line_trace); } else if (sensor_light_lv < THRESHOLD-5) { OnFwd(moter_conect_move_L,SPEED_SLOW_line_trace); } else if (sensor_light_lv < THRESHOLD +5) { OnFwdSync(moter_conect_move,SPEED_line_trace,0); } else if (sensor_light_lv < THRESHOLD+8) { OnFwd(moter_conect_move_R,SPEED_SLOW_line_trace); } else { OnFwd(moter_conect_move_L,SPEED_FAST_line_trace); OnFwd(moter_conect_move_R,-SPEED_FAST_line_trace); } } long intersection_judgment(long t0,int threshold)//Sensorの値がThreshold(任意の反射率)以上のとき、引数に現在の時刻を代入し返す。それ以外は変更せず引数を返す。 { if (SENSOR_1 > threshold) //現在のセンサーの値が、Threshold以上のとき { t0 = CurrentTick(); //現在の値をt0に代入 } return t0 ; //t0の値を返す } //関数(task_mainで呼び出す)エリア void set_sensor_all() { SetSensorLight(S1); // ライトセンサーの定義 SetSensorLowspeed(S4); //超音波センサーの定義 } void firing_ball() //ボールの発射関数 { stop_move(); //モーターのリセット(関数が連続する際、Offを挟まないと5行目以降でバグが起きる) RotateMotor(moter_conect_option,85,537); //optionモーター(ボール発射装置)のモーターの回転 } void line_tracing_in_seconds(float seconds) //seconds(秒)間、ライントレースをする { stop_move(); long t0 =CurrentTick(); float temp=1000*seconds; //whileループが成立する間、1000*secondsを毎回計算するのが、演算的に無駄であるため、tempに入れる while(CurrentTick()-t0 < temp) //temp秒間ループ { line_trace(SENSOR_1); //ライントレースをする } } void line_trace_and_intersection_judgment(int threshold,int judgment_time) //交差点を判断(Thresholdの値で判断)するまで、ライントレースをする { stop_move(); //モーターリセット long t0 = CurrentTick(); //現在の値をt0に取得 while(CurrentTick()-t0 < judgment_time) //judgment_timeより短い時間の黒の検出は無視 { line_trace(SENSOR_1); //ライントレースをする t0=intersection_judgment(t0,threshold); //現在がthresholdの値より大きいとき、t0に現在の値を代入 } stop_move(); //モーターの停止 Wait(1000); //交差点で一時停止 PlaySound(SOUND_UP); //交差点検出で音を鳴らす } void move_A_to_inside_of_M() //スタート地点から発射地点までの移動 { fwdDist(5.0); //5cm前進 line_tracing_in_seconds(1.2); //1.2秒間ライントレース turnAng_R(70.0); //右に70°転 fwdDist(30.0); //30cm前進 line_tracing_in_seconds(6.0); //6秒間ライントレース line_trace_and_intersection_judgment(31,100); //交差点を検出するまでライントレース line_tracing_in_seconds(7.2); //7.2秒間ライントレース turnAng_R(90.0); //右に90°回転 fwdDist(13.0); //13cm前進 } void mukiawase() //ロボットの向きを合わせる { turnAng_L(10.0); //弁当のゴールを検出しないようにする為の振り幅の調節 int a =searchDirection(60); //一番近い物体に向きを合わせる turnAng_R(3.0); //バッターの打ち返す機構に向きを合わせる微調整 } void send_action_finish() //バッターに動作の完了を送る { SendRemoteNumber(0,MAILBOX1,1); } int receive_action_signal() //バッターから命令を受け取る { int temp = 0; //メッセージの受取変数 while(temp==0) //メッセージが初期値から変わるまでループ { ReceiveRemoteNumber(MAILBOX2,true,temp); //メッセージの取得 } PlaySound(SOUND_UP); //メッセージを受け取ったら音を鳴らす return temp; //受け取った値を返す } //taskエリア task main() { set_sensor_all(); //センサーの初期化 int action_number=0; //命令コードの記憶変数 while(true) { action_number = receive_action_signal();//命令の受信をし、変数に代入(受信するまでこの行で停止) if (action_number == 1) //命令コードが1のとき { move_A_to_inside_of_M(); //ピッチャーの位置まで移動 action_number = 0; //命令コードのリセット } else if (action_number == 2) //命令コードが2のとき { mukiawase(); //ロボットの向きを合わせる action_number = 0; //命令コードのリセット } else if (action_number == 3) //命令コードが3のとき { firing_ball(); //ボールの発射 action_number = 0; //命令コードのリセット } send_action_finish(); //動作の完了を通知 PlaySound(SOUND_UP); //動作完了の音 } } *バッターロボットについて [#ud4e2ee7] **車体 [#aea2177c] #ref(2016b/Member/Megukyo/Mission3/battarzentai2.jpg,50%,車体) #ref(2016b/Member/Megukyo/Mission3/battarzentai.jpg,50%,車体) バッターロボットは基本的なライントレースマシンのフレームをより高めにしてフレーム内側にアーム駆動用モーターを取り付けることで、バッターロボットとしては比較的大きく複雑なアーム構造を持ちながらコンパクトに仕上がっている。~ また、バッターロボットは普段の教室では起きなかった現象だが、タイヤが滑るという現象が起きたため、急きょウエイトとして使用していなかったタイヤを取り付けた(恐らく床の摩擦が弱かったこととアームにより若干重心が偏っていたことが重なった原因) #ref(2016b/Member/Megukyo/Mission3/battarzentai3.jpg,50%,車体) センサーはアーム付け根上部に超音波センサ、前方下部に光センサが設置されている。やはりこれもなるべく上に超音波センサが来るよう、また直接球を扱うアームに近ければ超音波センサとバッターロボットとの距離とアーム部の位置のずれを補正せずに済むのでこの配置となった。なお超音波センサ使用時にはアームを下すことで超音波センサがアームを誤検知してしまうことはないようになっている。 #ref(2016b/Member/Megukyo/Mission3/armzoom2.jpg,50%,車体) 初期位置 #ref(2016b/Member/Megukyo/Mission3/armzoom3.jpg,50%,車体) 超音波センサ使用時~ アームの先端はこのように遊動式となっており、球の衝撃を多少なりとも吸収して、はじきにくくなっている。またこれは本体のサイズ制限に対する一つの解決策となっている。(初期位置ではアーム先端を本体上部に傾けて載せてある) #ref(2016b/Member/Megukyo/Mission3/armzoom.jpg,50%,車体) 球をキャッチする待機位置~ このロボットがボールを打つ一連の動作はまず、遊動式になっているアーム先端が球をとらえると、その自重により球はアームの奥に押し込まれ、固定される。次にアームの奥の板状部分によって正確に前方へ打ち出される。という流れである。打ち出す向きは本体の向きを変えることで制御している。 #ref(2016b/Member/Megukyo/Mission3/armzoom4.jpg,50%,車体) アームのみで撮影 **プログラム [#y8320fae] プログラムは、大まかには、(まずピッチャーロボットがMまで到達)→(ピッチャーロボットからバッターロボットへ通信)→バッターロボットがBまで到達→ピッチャーロボットに通信→(ピッチャーロボットがバッターロボットを検知して向きを調整)→(ピッチャーロボットからバッターロボットに通信)→バッターロボットを検知して向き、位置を調整→ピッチャーロボットに通信→ピッチャーロボットから打ち出された球を打つ、という流れになっている。 #define moter_conect_move OUT_BC //駆動輪モーターの接続コネクタ #define moter_conect_option OUT_A //ハンドルのモーターの接続コネクタ #define moter_conect_move_L OUT_B //左駆動輪モーターの接続コネクタ #define moter_conect_move_R OUT_C //右駆動輪モーターの接続コネクタ #define SPEED 45 //通常モーター速度 #define SPEED_FAST 60 //通常モーター速度(高速) #define SPEED_SLOW 40 //通常モーター速度(低速) #define SPEED_line_trace 30 //ライントレース時の速度(機体によって調節) #define SPEED_FAST_line_trace 40 #define SPEED_SLOW_line_trace 30 #define THRESHOLD 44 //白と黒の中間の反射率 const float diameter = 5.45; //タイヤの直径(cm) const float track = 15; //タイヤのトレッド幅(cm) const float pi=3.1415; //円周率 /****************** ここから, 長谷部くんのコードを引用した. *******************/ //すべての出力モーターの停止 void stop_move() { Off(OUT_ABC); Wait(1); } //左に ang (°) 回転 void turnAng_L(float ang) { stop_move(); //左回転に必要な角度の計算 float angle = track / diameter * ang; //計算した角度動かす RotateMotorEx(moter_conect_move, SPEED_SLOW, angle, 100, true, true); } //ライントレース void line_trace(int sensor_light_lv) { if (sensor_light_lv < THRESHOLD - 8) { OnFwd(moter_conect_move_R, SPEED_FAST_line_trace); OnFwd(moter_conect_move_L, -SPEED_FAST_line_trace); } else if (sensor_light_lv < THRESHOLD - 5) { OnFwd(moter_conect_move_L, SPEED_SLOW_line_trace); } else if (sensor_light_lv < THRESHOLD + 5) { OnFwdSync(moter_conect_move, SPEED_line_trace,0); } else if (sensor_light_lv < THRESHOLD + 8) { OnFwd(moter_conect_move_R, SPEED_SLOW_line_trace); } else { OnFwd(moter_conect_move_L, SPEED_FAST_line_trace); OnFwd(moter_conect_move_R, -SPEED_FAST_line_trace); } } //Sensorの値がThreshold(任意の反射率)以上のとき, 引数に現在の時刻を代入し返す. //それ以外は変更せず引数を返す long intersection_judgment(long t0, int threshold) { if (SENSOR_4 > threshold) { t0 = CurrentTick(); } return t0; } //seconds(秒)間、ライントレースをする void line_tracing_in_seconds(float seconds) { stop_move(); long t0 =CurrentTick(); float temp = 1000 * seconds; while(CurrentTick() - t0 < temp) { line_trace(SENSOR_4); } } //交差点を判断(Thresholdの値で判断)するまで、ライントレースをする void line_trace_and_intersection_judgment(int threshold, int judgment_time) { stop_move(); long t0 = CurrentTick(); while(CurrentTick()-t0 < judgment_time) { line_trace(SENSOR_4); t0=intersection_judgment(t0,threshold); } stop_move(); Wait(1000); PlaySound(SOUND_UP); } /****************** ここまで, 長谷部くんのコードを引用した. *******************/ //ピッチャーが移動中, 位置合わせ中の時に使用する. ピッチャーが"1"というメッセー //を返すまで次の行動に移らない. sub waitpartner() { int msg = 0; while(msg == 0) { ReceiveRemoteNumber(MAILBOX1, true, msg); } } //バッター側のNXTが位置合わせを行わないほうがうまくいくようなので, あえて位置合 //わせを行わない. (コメントアウト) int searchDirection(long ang) { long tacho_min; int d_min = 300; /*long angle = (track / diameter) * ang; turnAng_L(ang / 2); ResetTachoCount(OUT_BC); OnFwdSync(OUT_BC, SPEED, -100); while(MotorTachoCount(OUT_B) <= angle) { if (SensorUS(S1) < d_min) { d_min = SensorUS(S1); tacho_min = MotorTachoCount(OUT_B); } } OnFwdSyncEx(OUT_BC, SPEED, 100, RESET_NONE); until(MotorTachoCount(OUT_B) <= tacho_min || SensorUS(S1) <= d_min); Wait(150); */ Off(OUT_BC); Wait(500); return d_min; } sub ajustRobot() { int tmp = searchDirection(40); Wait(500); } //そのままではボールを打つハンドルが下がってきてしまうので, 上方向にある程度回転 //させて, 固定する sub handlehold() { OnFwd(OUT_A, 10); } sub ball_catch() { //ボール発車前に毎回位置合わせをする場合, コメントを外す. //ajustRobot(); SendRemoteNumber(1, MAILBOX2, 2); waitpartner(); handlehold(); Wait(1000); //ボール発射の司令 SendRemoteNumber(1, MAILBOX2, 3); handlehold(); Wait(1550); RotateMotor(OUT_A, 50, -50); OnRev(OUT_A, 50); Wait(500); } sub ball_hit(int drctn) { if (drctn == 40) //4点(右) { RotateMotor(OUT_B, SPEED, 210); Wait(500); RotateMotor(OUT_A, 100, 100); Wait(1000); handlehold(); RotateMotor(OUT_B, SPEED, -190); Wait(500); } else if (drctn == 41) //4点(左) { RotateMotor(OUT_B, SPEED, -240); Wait(500); RotateMotor(OUT_A, 100, 100); Wait(1000); handlehold(); RotateMotor(OUT_B, SPEED, 250); Wait(500); } else if (drctn == 60) //6点(右) { RotateMotor(OUT_B, SPEED, 100); Wait(500); RotateMotor(OUT_A, 100, 100); Wait(1000); handlehold(); RotateMotor(OUT_B, SPEED, -100); Wait(500); } else if (drctn == 61) //6点(左) { RotateMotor(OUT_B, SPEED, -190); Wait(500); RotateMotor(OUT_A, 100, 100); Wait(1000); handlehold(); RotateMotor(OUT_B, SPEED, 190); Wait(500); } else if (drctn == 10) //10点 { RotateMotor(OUT_B, SPEED, -100); Wait(500); RotateMotor(OUT_A, 100, 100); Wait(1000); handlehold(); RotateMotor(OUT_B, SPEED, 100); Wait(500); } else if (drctn == 00) //4点(右) { RotateMotor(OUT_B, SPEED, 30); Wait(500); } } //スタート地点から規定の位置まで進む sub start1() { RotateMotor(OUT_BC, SPEED, 40); RotateMotor(OUT_C , SPEED, 355); RotateMotor(OUT_BC, SPEED, 900); //3秒間無条件でライントレース(丁字路判別を行わない) line_tracing_in_seconds(3); //ライントレース, 丁字路判別 line_trace_and_intersection_judgment(33,90); RotateMotor(OUT_BC, SPEED, 300); RotateMotor(OUT_B , SPEED, -100); PlaySound(SOUND_LOW_BEEP); } task main() { SetSensorLowspeed(S1); SetSensorLight(S4); handlehold(); start1(); SendRemoteNumber(1, MAILBOX2, 1); waitpartner(); ajustRobot(); SendRemoteNumber(1, MAILBOX2, 2); Wait(2000); waitpartner(); handlehold(); //4点(右)を狙う ball_catch(); ball_hit(40); //4点(左)を狙う ball_catch(); ball_hit(41); //6点(左)を狙う ball_catch(); ball_hit(60); //6点(左)を狙う ball_catch(); ball_hit(61); //6点(左)を狙う ball_catch(); ball_hit(61); } *結果・まとめ [#d201919e] テスト段階では比較的うまくいっていたが、本番ではピッチャーロボット、バッターロボットがうまく向き合わず全然ボールを打つことができなかった。本体同士を向き合わせて打つ動作だけのテストではかなり正確に目標に当てれていたので残念な結果であった。おそらくはロボットが凹凸や空洞部分が多く全くもって一様な物体でないため、超音波が当たる角度、部位によって超音波センサの感知している値が大きく変動してしまっているためと考えられる。超音波センサをうまく相手のロボットに向かせるために角度制御である程度向かせ合っていたが、その段階で相当正確に向き合っていたので、超音波センサで向かい合わせずに、完全に角度制御で向かい合わせてもうまくいったかもしれない。 *感想 [#p89490b7] このロボティクス入門ゼミを通して今まで全く触れてこなかったプログラミングについて、多少なりとも身に付けられたことは非常に自分の今後に役立つと思う。しかしながら、課題3において、車体を組む面ではともかく、知識としてはわかっていても、実際にプログラミングする段階において、チームの二人にはついていくことができずにいたことが申し訳なかったとともに歯がゆい思いであった。繊維学部においても三年次以降、プログラミングの授業があるそうなので、今回のゼミで身につけた知識を多少なりとも生かせるようにしたい。