大まかな内容は「2組の積んである缶を種類ごとに分けて,各々指定されたポイントへ運搬する(下図参照)」のを「ロボットコンテストの形式に沿いチームで発表&評価(基本点・技術点の換算)」を行う,ということである.ルールや形式等の詳細説明は非常に長くなるため,こちら←をご参照の上でこのページをご覧ください.
→積まなくても,指定区域内に入れればOK
課題内容が発表された際に,私達のグループではおおまかに以下の項目のような戦略を立てた.
これを分解すると… ⇒ 装置A + 装置B + ほかEV3本体
装置Aは,装置B及びEV3本体を積載して移動するためのものなので,大きいタイヤに加えて2つのキャリアボール←を取りつけて,4点で支える形式にした.
課題完遂のための重要部分となる垂直方向の昇降に関しては,EV3に無限軌道のためのパーツが付属していたためそれを用いた.アームの機構に2つの同じ無限軌道を挟むことで,垂直に保った体勢での昇降が可能となる.以下より,それぞれの説明をする.
無限軌道は二つあるが、偶数個のギアを用いて一つの駆動で両方とも動くシステムとなっている.
三段に積んである缶の一段目までアームが下がるような長さとなっている.アームが下がるにつれ、アームに接続したコードが六連ギアに重なり、巻き込むことがあったので、左図のようなパーツを上部に取り付けた.
また、必要な全長をもつ無限軌道の製作において、最初は車軸を巻くだけの長方形に近い形にしたが、巻かれたベルトの長さのゆとりが大きくアームの昇降が不安定になったので、上図のように中央部分にベルトを張るためのパーツを取り付けた.
缶をキャッチしての積み降ろし、運搬を担う黄金の腕ともいうべき機構である.モーターと二つのギアを用いて開閉を行い、缶を両脇より挟んでキャッチする形式となっている.
左図について、二つの缶のに対して縦方向にキャッチできるようになっている.先端の補助アームに固定用パーツは取り付けていないが、これは二つにまとめてからの積み上げの際に、アームをあげると手前の缶だけ上がり、適当な距離の直進とアームの下降だけで缶を積めるシステムになっている.
右図については、六連ギアを組んだ際に出来た幅に合うように、駆動部分にパーツを重ねて取り付け、つなぎとしたものが確認できる.また、青丸で囲まれた機構を挟むように取り付けたパーツについて、機構とベルトが接する面が広い(固定は一部)ことで、昇降時に左右の揺れを軽減する働きをもつ.
機能、構造及びデザイン性に富んでおり、揺るぎなく"fantastic"の名に相応しいと考えられる.
とは言ったものの,参考にした動画はありますが(こちら←でござい.まあ,あくまで参考ですが、)
二つの超音波センサーを上図のように取り付け,これを用いてフィールド内にある缶を探すことにした.正面,側面につけたのは,静止状態での缶の検知範囲を大きくしたかったためである.後述のプログラムでは,各々を us1 , us2 とおいている.(ちなみに us2 の使用回数は一回のみ)
サブルーチンを一個一個作っていく中で(lift_down_can3_can2()とか)「ただ缶を運んだだけではパッとしない」,「せっかく昇降機能がついているんだから...」などなど(向上心という名の)神の囁きによって大時化の海原へと思考をいざなってしまった(ということにしておいた).
そして,編み出したSUVAらしきストラテジーが以下の通り.
山ほどあるなかで,要旨を三つに絞ったものが以下の通り.
今回はEV3を扱っているため,プログラミングにTera TermというソフトからPythonという言語を用いる.
以下は定義までの導入である
#!/usr/bin/env python3 from ev3dev.ev3 import * #EV3を扱うモジュールをインポート from time import sleep #timeモジュールからsleep関数をインポート
us1 = UltrasonicSensor('in1') #1端子側の超音波センサー(正面)をus1とおく us2 = UltrasonicSensor('in3') #3端子側の超音波センサー(側面)をus2とおく mL = LargeMotor('outA') #A端子側の移動用モーターをmLとおく mR = LargeMotor('outB') #B端子側の移動用モーターをmRとおく mA = MediumMotor('outC') #C端子側のアーム開閉用モーターをmAとおく mLif = LargeMotor('outD') #D端子側の無限軌道制御用モーターをmLifとおく
以下は各位置での缶をアームでつかんでの積み降ろしをするために,アームを昇降するときに用いるmLifの回転角度の値を とりあえず おいたものである.
lif2 = 250 #ある缶から一個上の缶までアームが上昇するためのmLifの回転角度値 lif3 = 495 #ある缶から二個上の缶までアームが上昇するためのmLifの回転角度値 dow3 = -520 #ある缶から二個下の缶までアームが降下するためのmLifの回転角度値 dow2 = -280 #ある缶から一個下の缶までアームが降下するためのmLifの回転角度値
御覧の通り,値がバラバラである.
昇降の絶対値は各々で等しくしたほうが扱いやすいのではないかと考えるかもしれないが(ごもっとも),これには深い訳←があり,後述に説明がある.
def motor_init(): #各モーターをリセットする mL.reset() mR.reset() mLif.reset() def turn(t): #右回りを正とし,指定角度 t だけ移動用モータが回転,車体が旋回する mL.run_to_rel_pos(position_sp=t,speed_sp=60,stop_action='brake') mR.run_to_rel_pos(position_sp=-t,speed_sp=60,stop_action='brake') sleep(3) def run_for_back_pos(t): #指定角度 t だけ移動用モーターが回転,車体が前進または後退する motor_init() mL.run_to_rel_pos(position_sp=t,speed_sp=60,stop_action='hold') mR.run_to_rel_pos(position_sp=t,speed_sp=60,stop_action='hold') sleep(1) def run_adjust(t): #上述定義を用いて,前進または後退の調整 run_for_back_pos(t)
def arm_open(): #アームを開く※2 mA.reset() mA.run_to_rel_pos(position_sp=-20,speed_sp=20,stop_action='hold') sleep(2) def arm_close(): #アームを閉じる mA.reset() mA.run_to_rel_pos(position_sp=50,speed_sp=50,stop_action='hold') sleep(2) def arm_adjust(t): #アーム開閉時に調整のために用いる(mAの回転角度をtとおく) mA.reset() mA.run_to_rel_pos(position_sp=t,speed_sp=50,stop_action='hold') sleep(1)
※2 アームを開く時のモーターの速さはできるだけ小さくしなければならない.後に扱う,缶を降ろす作業の際に,アームのキャッチングの位置によっては最下点に下がっても缶が接地しない場合があり,リリース時で缶を落とさなければならない.したがって,落としても缶が倒れないようにするにはできるだけゆっくり開き,アームに付属したゴムの摩擦力を利用して静かにリリースする必要がある.
def lift_can2(): #アームが,ある缶から一個上の缶まで上昇する motor_init() mLif.run_to_rel_pos(position_sp=lif2,speed_sp=80,stop_action='hold') sleep(3) def lift_can3(): #ある缶から二個上の缶まで上昇する motor_init() mLif.run_to_rel_pos(position_sp=lif3,speed_sp=80,stop_action='hold') sleep(6) def down_can2(): #ある缶から一個下の缶まで降下する motor_init() mLif.run_to_rel_pos(position_sp=dow2,speed_sp=75,stop_action='hold') sleep(5) def down_can3(): #ある缶から二個下の缶目で降下する motor_init() mLif.run_to_rel_pos(position_sp=dow3,speed_sp=75,stop_action='hold') sleep(6)
def lift_adjust(t): #上述に定義した値以外で昇降を調整(mLifの回転角度を t とおく) motor_init() mLif.run_to_rel_pos(position_sp=t,speed_sp=60,stop_action='hold') sleep(3)
lif,dowの各々の値はこちら←
def can_approach(): #缶の積み降ろしの前に,缶を見つけ,缶と適切な距離を取ってつかむ motor_init() while us1.value() > 70: #缶との距離が一定値超の時,前進すして缶に接近 mL.run_forever(speed_sp=80) mR.run_forever(speed_sp=80) run_for_back_pos(80) #一定の距離まで近づいたら,そこから缶に適切な距離まで近づく sleep(2) def turn_can_search(le,s): #前方にある缶を探して旋回 ※1 motor_init() while us1.value() > le: mL.run_forever(speed_sp=50*s) mR.run_forever(speed_sp=-50*s) turn(20*s) #検知した後,正面を向くよう修正(調整中にこの過程はほぼ無意味だと判明) (↑でももう遅かった!)
※1 状況下に応じて超音波センサーによる検知可能範囲の長さを le (mm)とし,s = ±1 を代入して右回りか左回りかを調節する.缶を検知したとき,超音波センサーが缶の正面を向いているわけではないことに配慮してturn(|20|)を方向修正として挿入するという工夫をした...が,状況によって値が足りなかったりして,結局後から調整用のturn(t)を追加したりして,無意味だと考えたときには後戻りできるプログラム量ではなかった.
def start(): #スターティングポイントよりB地点に向かい,右へ方向転換 motor_init() run_for_back_pos(600) #↓で旋回して缶の正面になるよう,適当な位置まで前進 sleep(8) turn(165) #缶の正面を向くよう旋回 sleep(2) turn(20) #旋回して缶を見つける準備に入る turn_can_search(250,-1)#左回りに旋回して缶を探す turn(-5) #方向修正
プログラム作成時にすでに目印として'#'で(ナンカアヤシイ)コメント文を入れた箇所があるため,後付けのものは // で記す('#'の内容とは異なる)
def lift_down_can3_can2(): #三段目の缶と二段目の缶をアームでつかんで降ろす motor_init() lift_can3() //三段目の缶まで移動 arm_close() //アームを閉じる lift_adjust(45) //移動の際に下の缶を引っ掛けないように缶を少し持ち上げる run_adjust(-100) #7up / CClemon //後退 sleep(2) turn(-110) //左へ旋回 down_can3() //缶をとりあえず降ろす lift_adjust(-40) //最下点まで降ろすよう調整 arm_open() //アームを開いて缶をリリース run_for_back_pos(250) //左斜め前方まで押して移動 sleep(3.5) lift_can3() //移動の際に缶を引っ掛けないよう予め上げておく lift_adjust(45) run_for_back_pos(-250) //積まれた缶の前まで下がる ※※1 sleep(3.5) turn(110) //缶の正面へ旋回 arm_adjust(-10) //アームを少し開く(確実に缶がキャッチできるように) down_can2() //二段目の缶までアームを降ろす run_adjust(113) #CClemon / 7up //缶の手前まで接近 ※※1 sleep(2.5) arm_close() //アームを閉じて缶をキャッチ lift_adjust(45) //缶を少し持ち上げる run_for_back_pos(-90) //少し後退 ←この時二段目の缶はサークルから外れるように turn(110) //右へ旋回 ← down_can2() //缶を降ろす arm_open() //アームを開いて缶をリリース lift_can2() //アームを上げとく(↓collect_can1_can3()へ)
御覧の通り,この長いサブルーチン中にアーム昇降の調整に用いる lift_adjust(t) が多く表示され,t の代入値域も広い(調整用とは思えん)ことがわかる.「だったら最初から lift_can とか down_can とか使わずに lift_adjust(t)で統一すればいいではないか.」...と思うであろうが,実際,私も発表直前になっても未完成という切羽詰まった中で,主にそちらを使った.であるならば,なぜ昇降の値を調整用とで区別したかというと,プログラムを確認する上でコメント文の有無に関わらず,何の動作を行っているか分かり易くするためである(例えば,メインの昇降に用いる lift_can,down_can やキャッチ後の移動前に下の缶を引っ掛けないよう少し上げておく動作)
※※1 キャッチ前の缶との接近において,3段目と2段目に関しては確実に,しっかりキャッチできる缶との距離を取らなければならない.
def collect_can1_can3(): //三段目と左斜め前方に移した一段目(二つの同種の缶)をまとめる motor_init() turn(-115) //二段目を降ろしてアームを上げた後,三段目の正面を向く down_can2() //二段目にあったアームを三段目まで降ろす run_adjust(110) //三段目の缶がアームの中に入るよう前進 ※※1 sleep(1.5) arm_close() #7up / CClemon //アームを閉じて缶をキャッチ run_for_back_pos(-320) //後退 ←旋回しても,アームに缶が当たらないような位置に sleep(6) lift_can3() //缶をキャッチしたまま上げて,超音波センサーの視界を確保 turn_can_search(300,-1) //最初に移した缶を探し,左回りに旋回 down_can3() //缶を最下点まで降ろす arm_open() //アームを開いて缶をリリース arm_adjust(-10) //二つの缶が入るようにアームをある程度開いておく run_for_back_pos(500) //アームの中に二つの缶が入るまで適当な距離を前進 sleep(6) arm_adjust(20) //アームを閉じて缶をまとめる(緩めに閉じるので離脱可能)
↓lift_down_can3_can2() と collect_can1_can3() の大まかな様子(昇降の様子は省略).右側でも同じ過程を経る.
以下は,二つの同種の缶をキャッチした状態から,手前側の固定された缶を上げて固定されずに残った缶に積む (pile) ためのプログラムである.
def pile_can(): lift_can3() #手前側のキャッチして固定された缶を上げる run_for_back_pos(130) #残された缶に,積み降ろしに適当な距離まで接近 sleep(3) lift_adjust(40) #上げる過程で傾いた缶を慣性力で傾きを修正する(ほぼ無意味) lift_adjust(-180) #缶を積む arm_open() #アームを開いて積まれた缶をリリース
以下は,Y地点での缶の積み降ろしが終わった後,X地点の積み降ろしの準備に入るためのプログラムである.
def can_approach2(): motor_init() run_for_back_pos(-15) #C地点辺りで二つの缶を降ろした後,離脱準備に入る lift_can2() #周辺の缶を後の動作でアームで引っ掛けないよう,上げた状態に lift_adjust(65) #もう少し上げておく(これが後々に利いてくる...予定) sleep(2) run_for_back_pos(-275) #B地点辺りまで後退 sleep(6) turn(100) #旋回してX地点の方向に合わせる run_for_back_pos(730) #X地点の缶を目指してテキトーな距離まで移動 sleep(11.5) turn(-80) #旋回して,缶を見つける準備に入る turn_can_search(300,1) #右回りに旋回しながら缶を探す turn(20) #適当な位置に正面を戻す arm_adjust(-5) #缶を少し開いておく run_for_back_pos(100) #ある程度の距離まで缶に接近 sleep(3) down_can2() #アームを下げておく(lift_down_can3_can2()に入る準備) (↑ここで利いてくる.一つのルーチンでstart()の状態と同じに)
↓ can_approach2() の大まかな動きの様子(昇降の様子は省略).車幅が増大したのはご愛嬌.
以下はX地点での缶の積み降ろしと分別を終えた状態から,二つのCC●モンの缶をキャッチしたままY地点へ向かい,二段に積み上げてサークル内に入れるためのプログラムである.
def toward_circleY(): motor_init() arm_close() #アームをしっかり閉じる(閉めすぎたー,と考えたがもう遅い) arm_adjust(11) turn(-360) #Y地点へ向かうため後方へ方向転換 sleep(5) turn(25) #少し旋回※※2 turn(-25) #戻る pile_can() #二つの缶を二段に積み上げる down_can2() #積み上げてアームを開いた後,一段目までアームを下げる lift_adjust(-70) #最下点まで下げておく sleep(1) run_for_back_pos(20) #缶を確実につかむため接近 arm_close() #アームを閉じて缶をキャッチ lift_adjust(20) #缶を少し浮かせ,後の移動で引きずって缶が傾かないようにする run_for_back_pos(380) #サークル付近にある最初に降ろした缶に,ある程度接近 ※※3↓ sleep(7) arm_adjust(5) #缶をしっかりつかむ lift_adjust(340) #缶をキャッチしたまま上げて,正面の超音波センサーの視界を確保 sleep(6) #↓右回りに旋回し,左回りの検知の準備に入る mL.run_to_rel_pos(position_sp=45,speed_sp=40,stop_action='brake') mR.run_to_rel_pos(position_sp=-45,speed_sp=40,stop_action='brake') sleep(4) turn_can_search(300,-1)#左回りに旋回して缶を検知する lift_adjust(-410) #缶を地面まで降ろす sleep(7) arm_open() #アームを開き缶をリリース(can7up_catchへ) ※※3↑(ここまで)
※※2 turn(-360)の後に挿入したturn(±25)は特に意味がないような過程だと考えるかもしれないが,非常に重要である.turn(-360) によってアーム中の固定されてないほうの缶は,引きずられていったことによってアームの中で傾いてしまう.この傾きを turn(±25)によって修正できるのである.
※※3 缶を探す動作を挿入しなくても,いくつかの試行で,最初の方向転換で向いた方向がY地点の正面となっていることが確認されている.しかし,運搬の成功の可能性を大きくするために,必要であると考えた.また,turn_can_search(300,-1) の前の旋回は turn() を使わずに,speed を小さくして stop_action を 'hold’ ではなく 'brake' を用いたものにしている.これは,缶をキャッチしながらの移動に配慮して,缶に働く慣性力の影響をできるだけ小さくする必要があるためである.('hold' は制動の力が大きく,'brake' は比較的小さい.)
当初,サークル外の缶の検知した後,そのまま前進して pile_can() に移ってCC●レモンを三段積みにしてキャッチしたまま後退してサークル内にリリースしようとしていたが,Y地点辺りの路面状態が悪く(何やら歪んで凸凹している),積んだ後の後退時で傾いて崩れて全く成功しなかったので断念した.
以下は,CC●モンの缶の二段積みを終えて,移動し,7●Pの缶をキャッチするための(定義名のアヤシイ)プログラムである(お前もかっ.画像修正の努力と時間を返せっ).
def can7up_catch(): motor_init() lift_can3() #移動中,缶に引っ掛けないよう,適当な高さにアームを上げておく lift_adjust(70) run_for_back_pos(-250) #後退 sleep(4) turn(130) #旋回 ※※4↓ sleep(1.5) while us2.value() > 160: #側面にある超音波センサーが,二段積みの缶を探して旋回 mL.run_forever(speed_sp=60) mR.run_forever(speed_sp=-60) turn(40) #外周のラインに対してほぼ垂直になるように方向修正 run_for_back_pos(200) #車体が外周ラインに乗るまで前進 ※※4↑(ここまで) sleep(6) turn(-170) #ラインに平行になるよう旋回(C地点辺りに移した缶のキャッチへ) sleep(2) arm_adjust(-15) #二つの缶が確実にアームの中に入るよう,アームを開いておく sleep(1) down_can3() #アームを最下点まで下げておく lift_adjust(-25) while us1.value() > 70: #缶との距離が一定値超のとき,前進して缶に接近 mL.run_forever(speed_sp=60) mR.run_forever(speed_sp=60) run_for_back_pos(100) #缶との距離が一定値に達したら,適当な距離まで接近 arm_close() #アームを閉じて缶をキャッチ arm_open() #アームを開く(toward_circleX()へ) ※※5
※※4 外周と垂直に近い状態にするとき,サークル内にリリースした缶を目印にすることで,リリースの角度ごとで外周に対しての角度は異なるが,各々の誤差は小さくなる.最初の旋回は,X地点付近の缶を検知するのを防ぐため.
以下は二つの7●Pの缶をキャッチした状態より二段に缶を積む上げてX地点へ向かい,サークル付近にある最初に降ろした缶とを合わせて三段に積み上げてサークル内に入れるためのプログラムである.三つの課題を通して史上最長のサブルーチンとなった.
def toward_circleX(): motor_init() arm_close() #アームを閉じる pile_can() #缶を二段に積み上げる(最後はアームを開く) down_can2() #最下点まで下げておく lift_adjust(-110) sleep(1) run_for_back_pos(20) #下の缶を確実にキャッチできるよう接近 arm_close() #アームを閉じて缶をキャッチ arm_adjust(8) #缶をしっかりキャッチ lift_adjust(20) #缶を少し浮かせ,後の移動で引きずって缶が傾かないようにする run_for_back_pos(-110)#↓で旋回に入ったとき積んだ缶に引っ掛けない距離まで後退 mL.run_to_rel_pos(position_sp=-300,speed_sp=40,stop_action='brake') mR.run_to_rel_pos(position_sp=300,speed_sp=40,stop_action='brake') sleep(8) #↑X地点のほうへ旋回 ※※3 arm_adjust(8) #ここまでの過程でアームが緩む時があるので缶をしっかりキャッチ lift_adjust(320) #缶を上げて,正面の超音波センサーの視界を確保 sleep(5) turn_can_search(500,1)#右回りに旋回して,缶を探す ※※6 turn(20) #方向修正 lift_adjust(-320) #缶を降ろす sleep(6) run_for_back_pos(220) #X地点の側の缶にある程度近づく sleep(7) turn_can_search(300,-1)#左回りに旋回して,缶を探す(↑で lift を入れ忘れる超痛恨ミス) turn(-15) #方向修正 ※※6 while us1.value() > 80: #缶との距離が一定値超の時,前進して缶に接近 mL.run_forever(speed_sp=70) mR.run_forever(speed_sp=70) pile_can() #缶を積み,三段に積み上げる(最後,アームは開いた状態) down_can2() #最下点まで下げておく lift_adjust(-85) sleep(1) arm_close() #三段に積まれた缶をキャッチ mL.run_to_rel_pos(position_sp=-110,speed_sp=30,stop_action='brake') mR.run_to_rel_pos(position_sp=110,speed_sp=30,stop_action='brake') sleep(5) #↑三段に積まれた缶をサークルに入るよう,キャッチしたまま旋回 arm_open() #アームを開いて三段に積まれた缶をリリース
※※6 turn_can_search() が距離を変えて二回行われているが,これはできるだけ正確に缶の正面まで接近するためである.
↓ toward_circleY() から toward_circleX() までの大まかな様子である.
start() #スタート地点より,B地点まで前進,右折してY地点の缶に車体を向ける can_approach() #缶まで適当な距離だけ接近 lift_can3_can2() #三段目と二段目の缶を降ろす collect_can1_can3() #一段目と三段目の缶を指定ポイントまで運搬&リリース can_approach2() #X地点の缶へ向かう can_approach() #上述に同じ lift_down_can3_can2()# ↓ ←本番はここで缶を崩して終了 ((ノ∀`)アチャー(ノ∀`)アチャー) collect_can1_can3() # ↓ toward_circleY() #Y地点での缶の積み上げ,運搬&リリース can7up_catch() #最初に降ろしてまとめておいた缶のキャッチへ向かう toward_circleX() #X地点での缶の積み上げ,運搬&リリース
本番直前の度重なる微調整の成果もなく,最大の見せ場となる積み上げを披露する前に,私達の努力は儚く散っていた.can_approach2() で缶との正確な距離&位置を取れなかったことが原因と考えられる.つまり,turn_can_search() というプログラム中ではエース格のサブルーチンに対する微調整が見事なまでに不足していたということである 受け入れがたい事実デスガ...( ノД`).
作成したプログラミングを振り返ってみてみると,長すぎて美しくない,というのが第一印象である.ここまで長くするとどこを間違えたか把握しづらい,というのは分かってはいたものの時間的余裕がない状況下では抜本的な修正は不可能に近かった.しかし,本番でミスしたのはそれも要因の一つであり,タイムマネジメントが良かったとも決して言えなかった.ハードウェアにそれほど時間をかけず,ソフトウェアに残りを集中させる,という形はとれたものの,やはり微調整は本当の完成までに間に合わなかったという結末となった.
しかし,ロボコンの結果は,缶を運びきれずに基本点無得点ながらも、努力とスキルの髄を集めて洗練されたロボットの動きから技術点を9割近くマークし,13チーム中5位という謎の大健闘を果たした.
プログラミングはコンピューターを介すが,所詮はヒトが作ったもの.エラーは確実に起こらないとは言えない.むしろ,それにどう対応していくか.また、本気で最良のプログラムにするには相当なデバッグが要する.それらが身に染みた半年間であったとしみじみ.
追記:プログラミングは実践あるのみ.とりあえず何か打ち込んで修正を重ねる、「トライアルアンドエラー」が肝となる.
このページの訪問者数 :本日1 昨日2 合計&counter([total|today|yesterday]);