下の図のようなコースを各チームで作成し、「ミッション」を遂行するためのロボットを作成する。
今回で扱うロボットは課題1と同様にEV3である.以下の写真のようなロボットを製作した.
アームを開くとこんな感じ→
缶のキャッチングのためのアームは,両脇より挟む様式を用いたものにした.当初,缶が収まるような大きさの枠を用いて,引っ掛けるようにしてキャッチさせようとしたが,この場合には枠の真正面に缶が位置しなければ確実にキャッチできないことが分かったため変更した.変更後のものは,缶がアームを広げた範囲にあれば確実に回収できる(下図参照)それに外見がカッコイイ.なお,アームは本体左後方に取り付けたモーターで動かす.
今回の課題完遂のための最重要部分である.
なるべくタイヤの前方につけることで,色を検知してジグザグ走行をするときに車体の振れ幅が小さくなり,正確なライントレースができる.
缶のキャッチやリリースの際に使用する.
本体の真正面についていないことに注意する.
今回の課題で扱うロボットは最初の写真から分かる通り重装備で,特にアームも大きいため縦に長くなっている.これによって缶のキャッチングの前やリリーシングの後のロボットの動きで,車体やアームが缶を引っ掛ける可能性がある.したがって,これに十分に配慮したライントレースのプログラムを作成する必要がある.
ライントレースでは車体のスピードと切り返しの角度が重要な点になる.例えば,スピードが速い場合は交差点での実際の検知継続時間が通常のライントレース時の検知継続時間と等しいまたは下回る可能性がある.この時,交差点検知は困難になる.また,スピードが速くてカーブを曲がる際にタイヤの切り返しが小さい場合に,ライン上にカラーセンサーが留まり続けられず,ライントレースが出来なくなる可能性がある.さらに,交差点進入時の車体の角度によっては「黒」の検知継続時間が基準よりも短くなる可能性もある.したがって,左右のタイヤのスピードはいくつかの実験を通して,最適値を導き出す必要がある.
今回はEV3を扱っているため,プログラミングにTera TermというソフトからPythonという言語を用いる.
以下は定義までの導入である
1#!/usr/bin/env python3 from ev3dev.ev3 import * #EV3を扱うモジュールをインポート from time import #timeモジュールをインポート
mL = LargeMotor('outA') #A端子側の移動用モーターをmLとおく mR = LargeMotor('outB') #B端子側の移動用モーターをmRとおく mA = MediumMotor('outC') #C端子側のアーム開閉用モーターをmAとおく cs = ColorSensor('in1') #1端子側のカラーセンサーをcsとおく us = UltrasonicSensor('in2') #2端子側の超音波センサーをusとおく
def motor_init(): #各モーターをリセットする mL.reset() mR.reset() mA.reset() def turn(r): #その場で角度 r だけ旋回する(右向きを正とする) mL.run_to_rel_pos(position_sp=r,speed_sp=100,stop_action='hold') mR.run_to_rel_pos(position_sp=-r,speed_sp=100,stop_action='hold') sleep(1) def line_follow_fast(t): #指定時間 t だけ速度を大きくしたライントレースする motor_init() tS = time.time() while time.time() - tS < t: if cs.value() <= 30: mL.run_forever(speed_sp=130) mR.run_forever(speed_sp=40) if cs.value() >= 30: mL.run_forever(speed_sp=40) mR.run_forever(speed_sp=130) if time.time() - tS > t: mL.stop() mR.stop() sleep(1)
以下は交差点検知用トレースプログラムである.なお,カラーセンサーによる「黒」と「白」の識別用基準値は 30 とし,検知継続時間の基準値は 0.47 秒とする.交差点を検知した後のターンの方向によって,トレースの位置(右側or左側)を変更させる必要があるため予め二種類のプログラムを作成しておく.
def inter_find1(): #ラインの左側をトレースするとき交差点検知した後,一時停止する motor_init() tS = time.time() #検知開始時刻を tS とおく(time.time()はその時の時刻) while time.time() - tS < 0.47: #検知継続時間が0.4秒未満のときトレースを続ける if cs.value() <= 30: #「白」っぽい色を検知したら右に曲がりラインを探す mL.run_forever(speed_sp=100) mR.run_forever(speed_sp=-40) if cs.value() >= 30: #「黒」っぽい色を検知したら左に曲がりラインを出る mL.run_forever(speed_sp=-30) mR.run_forever(speed_sp=70) #正確に検知継続時間を計測するため速度は小さめに tS = time.time() #タイムカウント開始 if time.time() - tS > 0.47: #検知継続時間が0.4秒を超えたときwhileを抜け停止する mL.stop() mR.stop() sleep(1)
def inter_find2(): #ラインの右側をトレースするとき交差点検知した後,一時停止する motor_init() tS = time.time() while time.time() - tS < 0.47: if cs.value() <= 30: mL.run_forever(speed_sp=-40) mR.run_forever(speed_sp=100) if cs.value() >= 30: mL.run_forever(speed_sp=70) mR.run_forever(speed_sp=-30) tS = time.time() if time.time()- tS > 0.47: mL.stop() mR.stop() sleep(1)
以下は各交差点を検知するためのプログラムである.
def inter1_straight(): #第一交差点(C地点) inter_find2() sing_go() def inter4_left(): #第四交差点(G地点) inter_find1() sing_go() def inter5_right(): #第五交差点(H地点) inter_find2() sing_go() def inter6_right(): #第六交差点(I地点) inter_find2() sing_go() turn(60) sleep(1)
B地点の交差点での検知については工夫した点があるので後述で説明する.
def can_catch(): #アームを内側へ開く(キャッチングの時には開いた状態から) motor_init() mA.run_to_rel_pos(position_sp=46,speed_sp=40,stop_action='hold') sleep(1)
def can_release(): #アームを外側へ開く(アームを閉めた状態から) mA.reset mA.run_to_rel_pos(position_sp=-46,speed_sp=50,stop_action='hold') sleep(1)
今回は交差点を検知したときとスタートまでのカウントダウンに音を出すことにした.
def sing_start(): #3秒間のカウント for i in [1,2,3]: #後述のコマンドを3回繰り返す Sound.tone(600, 300).wait() #600ヘルツを0.3秒間発声 sleep(0.7)
def sing_go(): #高音域を発声 Sound.tone(1200, 400).wait() #1200ヘルツを0.4秒間発声
def sound_start(): #スタート3秒前カウント(イメージは「ド,ド,ド,レー↑」) sing_start() sing_go()
基本的にトレースの行程に沿って説明する.
トレース序盤
def curve_fast(): #第一カーブのトレース line_follow_fast(11) def straight_fast_inter1(): #第一交差点通過後の直線のトレース line_follow_fast(10)
以下は第二カーブから缶のキャッチ,G地点の交差点検知にかけて
def curve_follow_catch(): #第二カーブをトレース,D地点で停止するよう超音波センサーで motor_init() 缶を検知(なお,値の単位は mm(ミリメートル) である) while us.value() >= 55: #缶との距離が55mm超の場合,トレースを続けて缶にアプローチ if cs.value() <= 30: mL.run_forever(speed_sp=-40) mR.run_forever(speed_sp=90) if cs.value() >= 30: mL.run_forever(speed_sp=90) mR.run_forever(speed_sp=-40) if us.value() <= 55: #55mmに達したらwhileを抜けて一時停止,キャッチのために缶との距離を詰める mL.stop() mR.stop() sing_go() sleep(1) mL.run_to_rel_pos(position_sp=80,speed_sp=80,stop_action='hold') mR.run_to_rel_pos(position_sp=80,speed_sp=80,stop_action='hold') def inter_find_through1(): #キャッチした後にサークルから離脱,G地点の交差点までトレース motor_init() mL.run_to_rel_pos(position_sp=-80,speed_sp=80,stop_action='hold') mR.run_to_rel_pos(position_sp=-80,speed_sp=80,stop_action='hold') sleep(0.5) turn(-90) inter4_left()
↓curve_follow_catch() から inter_find_through1() までの様子
以下はG地点からH地点までの連続カーブをトレースするためのプログラムである. 第四カーブ通過後すぐの直線を速度の大きいトレースに切り替えたいと考えたが,そのための条件として,左側をトレースし続けて第四カーブで曲がるときにカラーセンサーがラインの外側にあることを利用する.つまり,「白」の検知継続時間に注目したバージョンのものを作成した. これはライントレースと言える...のだろうか?
def zigzag_follow(): #G地点からH地点までの連続する第三,第四カーブをトレース motor_init() tS = time.time() while time.time() - tS < 2.5: if cs.value() <= 30: mL.run_forever(speed_sp=-40) mR.run_forever(speed_sp=90) tS = time.time() if cs.value() >= 30: mL.run_forever(speed_sp=90) mR.run_forever(speed_sp=-40) if time.time() - tS > 2.5: turn(100) #H地点手前で交差点検知に移るため,ラインの左側から右側へシフト sleep(0.5) line_follow_fast(5) #第四カーブ通過後の直線を速度が速いトレースで通過する sleep(0.5)
def inter_find_through2(): #L,K,J地点を交差点として検知して直進する for i in [1,2,3]: #L,K,Jの3地点で後述のコマンドを繰り返す inter_find2() #↓交差点を検知した後,トレースを続行させるために体勢を整える sleep(1) mL.run_to_rel_pos(position_sp=40,speed_sp=40,stop_action='hold') mR.run_to_rel_pos(position_sp=160,speed_sp=120,stop_action='hold') sleep(1.2) turn(-20) sleep(0.5)
実は全くの偶然だが,inter_find_through2でのトレースの仕方は inter_find_through1 と同一にも関わらず,交差点を検知する(これは,交差点を検知せずにトレースするinter_find_through1に対しても同じ).サークルのトレースに入る車体の角度によるものと考えられる.
なお,次の地点までのトレース前に体勢を整えていなければ,ラインの右側をトレースしている場合に,交差点検知後は右折をしてそのままトレースし続けてしまう.したがって,今回は交差点検知後に,各地点の右へ延びる直線をいったん跨ぐようにして,次の地点へのトレースに復帰するようにした.(下図参照)
以下は缶のリリースと方向転換の過程である.
def follow_release(): inter_find_through2() #I地点からJ地点までサークルをトレース sing_go() turn(-200) #Y地点に缶をリリースする角度に旋回(直線BJに対して少し斜め) sleep(0.5) can_release() #缶をリリース sleep(0.5) mL.run_to_rel_pos(position_sp=-200,speed_sp=80,stop_action='hold') #後退※ mR.run_to_rel_pos(position_sp=-200,speed_sp=80,stop_action='hold') sleep(1)
※このとき,旋回しても車体が缶に当たらない距離まで下がるための準備に入る.これをしないと悲劇が...→
車体の方向を転換してB地点へ向かう.
def leave_circle(): motor_init() while us.value() <= 120: #超音波センサーで,旋回しても缶に当たらない距離まで下がる. mL.run_forever(speed_sp=-100) mR.run_forever(speed_sp=-100) if us.value() >= 120: mL.stop() mR.stop() sleep(0.1) turn(90) #少し車体の角度を変える while cs.value() >= 30: #ラインを検知するまで進む mL.run_forever(speed_sp=80) mR.run_forever(speed_sp=80) if cs.value() <= 30: mL.stop() #ラインを検知したら一時停止 mR.stop() sleep(0.1) #↓ラインを跨ぐ mL.run_to_rel_pos(position_sp=90,speed_sp=80,stop_action='hold') mR.run_to_rel_pos(position_sp=90,speed_sp=80,stop_action='hold') sleep(1)
def inter10_find_left(): motor_init() while cs.value() >= 30: #ラインを検知するまで(検知しなかったら)旋回 mL.run_forever(speed_sp=80) mR.run_forever(speed_sp=-80) if cs.value() <= 30: mL.stop() mR.stop() sleep(0.1) inter_find2() #ラインを検知したら,B地点の交差点検知に入る sing_go() mL.run_to_rel_pos(position_sp=80,speed_sp=80,stop_action='hold') #↓※※ mR.run_to_rel_pos(position_sp=80,speed_sp=80,stop_action='hold') sleep(0.5) turn(-58)
以下の図は inter10_find_left の動作の様子である.
※※このとき,検知後に曲がってラインから外れるだけでは次トレースの体勢は整わない.トレースが始まり,ラインに入るときに誤検知してしまう角度で入るためである.(下図参照)
×←ラインに対する車体の角度が大きいと「黒」の検知が長くなる
〇←通常のトレースに近い状態
以下はスクエアを探して後方より入庫するまで
def find_square(): inter_find1() #突き当りを交差点として検知 sleep(1) turn(-360) #方向転換 sleep(0.3) def enter_square(): #後方より入庫 motor_init() mL.run_to_rel_pos(position_sp=-250,speed_sp=80,stop_action='hold') mR.run_to_rel_pos(position_sp=-250,speed_sp=80,stop_action='hold') sleep(2) can_catch()
sound_start() #スタート合図 curve_fast() #緩やかな第一カーブを速くトレース inter1_straight() #第一交差点を検知&停止→直進 straight_fast_inter1() #検知後の直線を速くトレース turn(-40) #ラインの右側トレースから左側トレースに転換 ※※※ can_release() #アームを開いてキャッチングの準備(releaseはまだです) curve_follow_catch() #急な第二カーブを通過後,第二交差点で停止,缶をキャッチ inter_find_through1() #最初のサークルを辿り,第四交差点を検知&停止→左折 zigzag_follow() #急カーブが連続する第三カーブを通過 inter5_right() #第五交差点を検知&停止→右折 inter6_right() #第六交差点を検知&停止→右折 follow_release() #サークルを辿り,第九交差点で停止&缶をリリース leave_circle() #缶のリリース後,サークルより離れて第十交差点に向かう inter10_find_left() #第十交差点を検知&停止→左折 find_square() #ベーススクエアまで戻る enter_square() #T字路で検知&停止からの方向転換して後方より入庫
※※※キャッチに入る前になぜトレースの位置を変更するかというと,超音波センサーが缶より見て正面左側についており,交差点に進入してすぐに缶を検知して停止してほしいため,なるべく缶の正面のほうを向きやすくなるようにする必要があったためである.
今回はハードウエアに対して比較的時間をかけなかったので,円滑にライントレースのプログラムを作成することができ,発表会にも間に合わせることができた.結果としては最後の最後で,後方入庫という最大の見せ場で失敗してしまった.これは,最後の交差点からのトレースのために施した工夫が仇となったためである.具体的には,スクエアのT字路に入る車体の角度がずれてしまったことだと考えられる.ある場面での調整は後々の影響が大きいということを痛感した.
本日1昨日0合計&counter([total|today|yesterday]);