目次
幅20mmの黒いラインに沿って動き、途中で缶にボールを当てるロボットを制作すること。 今回のコースは以下の通りである。
1.ロボットを長方形X内におき、Aをスタート
2.Bを右折
3.Kで一時停止して左折
4.Jを直進
5.Iを直進
6.Hを左折
7.Gで一時停止して左折
8.Eで一時停止して右折
9.Lを経て正方形Y内に入って停止
上が今回使用したロボットの全体図である。
今回はロボットはシンプルな機構のものを採用して、プログラミングに集中できるようにした。
緑枠の部分はカラーセンサである。カラーセンサはセンサで検出した値に誤差が出ないように、地面と近づけて設置するようにした。^
赤枠で囲った部分が超音波センサである。発射の際のボールの位置と超音波センサの位置のずれを抑制するために、この二つは同一直線状にあるようにロボットを設計した。
緑枠の部分はカラーセンサである。カラーセンサはセンサで検出した値に誤差が出ないように、地面と近づけて設置するようにした。
青枠の部分は球の発射機構である。矢印の方向にフックのような形をしたパーツが動くことでボールを前に弾き出す。またボールの足元部分には黒くて細い棒状のパーツを、ボールの左右にはボールを挟み込む形でパーツを設置することでロボットの移動中にロボットがあらぬ方向へと飛んでいかないよう工夫した。
それぞれのセンサやモータを以下のように定義する。
#!/usr/bin/env python3 from ev3dev.ev3 import * import time from time import sleep mR=LargeMotor('outC') mL=LargeMotor('outB') mM=MediumMotor('outD') cs=ColorSensor('in3') us=UltrasonicSensor('in1')
超音波センサのついてる方を正面として右にあるLargeMotorをmRとして、左にあるLargeMotorをmLとして定義した。
また発射機構の部分についているのがMediumMotorで、mMとして定義することにした。
def traceR(): t0=time.time() while time.time()-t0<0.35: if cs.value()<35: #カラーセンサの値が35より小さいときその場で右に回る turnR2() if 35<=cs.value()<40: #カラーセンサの値が35以上で40より小さいとき前に進みながら右にカーブする turnR1() t0=time.time() if 40<=cs.value()<=50: #カラーセンサの値が40以上で50以下のとき真っすぐ走る run() t0=time.time() if 50<cs.value()<=60: #カラーセンサの値が50より大きくて60以下のとき前に進みながら左にカーブする turnL1() t0=time.time() if cs.value()>60: #カラーセンサの値が60より大きいときその場で左に回る turnL2() t0=time.time() mR.stop() #黒を一定の時間以上認識したらwhile構文から抜け出し、停止する mL.stop()
今回の一番基本となるライントレースのプログラムはこれである。
それぞれのif構文の中で使われたturnR1()などの関数は以下のものである。
def turnR1(): #前に進みながら右にカーブする命令を送り続ける mL.run_forever(speed_sp=100,stop_action='brake') mR.stop()
def turnL1(): #前に進みながら左にカーブする命令を送り続ける mR.run_forever(speed_sp=100,stop_action='brake') mL.stop()
def run(): #真っすぐ走る命令を送り続ける mR.run_forever(speed_sp=100,stop_action='brake') mL.run_forever(speed_sp=100,stop_action='brake')
def turnR2(): #その場で右に回る命令を送り続ける mL.run_forever(speed_sp=100,stop_action='brake') mR.run_forever(speed_sp=-70,stop_action='brake')
def turnL2(): #その場で左に回る命令を送り続ける mR.run_forever(speed_sp=100,stop_action='brake') mL.run_forever(speed_sp=-70,stop_action='brake')
検証した結果カラーセンサの値は35,40,50,60をそれぞれの命令の区別に使うことにした。
また交差点で止まるために一定の時間以上黒(今回はカラーセンサの値が35より小さいとき)を検知したらそこを交差点と判断して止まるようにした。この一定の時間を何秒にするかも検証をした結果0.35秒にすることにした。
このライントレースのプログラムはラインの右側をトレースするものだが、円の部分を走るとき(今回の課題ではK→J→I→Hの移動)は右側トレースだと延々と円の内部を回り続けてしまうので円の部分を走るときのみ、左側のライントレースをすることにした。
def traceL(): t0=time.time() while time.time()-t0<0.23: if cs.value()<35: #カラーセンサの値が35より小さいときその場で左に回る turnL2() if 35<=cs.value()<40: #カラーセンサの値が35以上で40より小さいとき前に進みながら左にカーブする turnL1() t0=time.time() if 40<=cs.value()<=50: #カラーセンサの値が40以上で50以下のとき真っすぐ走る run() t0=time.time() if 50<cs.value()<=60: #カラーセンサの値が50より大きくて60以下のとき前に進みながら右にカーブする turnR1() t0=time.time() if cs.value()>60: #カラーセンサの値が60より大きいときその場で右に回る turnR2() t0=time.time() mR.stop() #黒を一定の時間以上認識したらwhile構文から抜け出し、停止する mL.stop()
この左側トレースは基本的に右側トレースのものの命令をそれぞれの値で逆にしただけなのだが、交差点を判断するための黒を認識する時間が右側トレースと比べてかなり少ない(0.23秒)。これも検証を繰り返した結果、円に接する交差点は曲がりやすいということが分かりこうなった。その理由として円に接する交差点の角度は、他の交差点は直角に曲がったりするのに比べて大きめの角度で曲がるので黒と認識する時間が少ないのではないだろうかと考察をした。
def traceRa(t): t1=time.time() while time.time()-t1<t: if cs.value()<35: turnR2() if 35<=cs.value()<40: turnR1() if 40<=cs.value()<=50: run() if 50<cs.value()<=60: turnL1() if cs.value()>60: turnL2() mR.stop() mL.stop()
これは引数tの時間分止まらずにライントレースをし続けるプログラムである。
カラーセンサの値に伴うロボットの動作は普通のtraceR()関数と一緒なので省略するが、このプログラムは交差点を右左折したのちにまたすぐに同じ交差点を認識して止まるということがないようにしたいときに使う。また急カーブでは一回そこをわざと交差点として誤認識させてからこのプログラムで突破するという手法を用いた。
def turnR3(t): #ラインを横断せずに右折する mL.run_timed(time_sp=t,speed_sp=100,stop_action='brake') mR.run_timed(time_sp=t,speed_sp=-100,stop_action='brake') sleep(t/1000) mR.stop() mL.stop()
def turnL3(t): #ラインを横断せずに左折する mR.run_timed(time_sp=t,speed_sp=100,stop_action='brake') mL.run_timed(time_sp=t,speed_sp=-100,stop_action='brake') sleep(t/1000) mR.stop() mL.stop()
def turnR4(t): #ラインを横断して右折する mL.run_timed(time_sp=t,speed_sp=100,stop_action='brake') sleep(t/1000) mR.stop() mL.stop()
def turnL4(t): #ラインを横断して左折する mR.run_timed(time_sp=t,speed_sp=100,stop_action='brake') sleep(t/1000) mR.stop() mL.stop()
ラインを横断するというのは下のオレンジ色の線のような動作をさせたいときに使う。右側トレースから左側トレースに変えたり、その逆をさせたかったためこのプログラムを作成した。 また横断しないというのは下の水色の線のような動作をさせたいときに使う。これは横断させるプログラムとは逆に、交差点でトレースの左右を変えないようにするために作成した。
これらの右左折のプログラムでは引数tの値を変えることで各交差点を突破するようにした。
どこで何秒指定したかは最後のまとめで記述する。
まずは探査のプログラムについて
def search(): d_min=5000 #最初に仮の最小値を定める t1=time.time() while time.time()-t1 < 15: #15秒間回り続ける turnR() if us.value()<d_min: #超音波センサで近くのものとの距離を測る d_min=us.value() #もしも仮の最小値より小さければその値を記録する t2=time.time() #その最小値を測った時間を記録する mM.reset() mR.stop() mL.stop() t3=time.time() sleep(1) mR.run_timed(time_sp=1000*(t3-t2), speed_sp=50, stop_action='brake') #最小値を測ったときの時間を利用して逆回転をする mL.run_timed(time_sp=1000*(t3-t2), speed_sp=-50, stop_action='brake') sleep(5)
次にボールを発射するプログラム
def fire(): #モータを回転させてボールにフック状のパーツを当てて発射する mM.run_to_abs_pos(position_sp=150,speed_sp=500,stop_action='coast')
ロボットを何秒回転させれば360度回転して缶を探査できるかというのは少しずつ時間をずらして回してみて、一番適切な結果を用いた結果15秒となった。
def straight(): mR.run_timed(time_sp=1000, speed_sp=100, stop_action='brake') #少しだけ前に進ませ、カラーセンサをラインから外す mL.run_timed(time_sp=1000, speed_sp=100, stop_action='brake') sleep(1) while 70<=cs.value(): #カラーセンサがラインを認識するまで前進する run() mR.stop() mL.stop()
def AtoB(): traceR() turnR3(1200) def BtoK(): traceRa(2) traceR() sleep(1) turnL4(4500) def KtoJ(): traceL() turnR4(1500) def JtoI(): traceL() search() #I地点で缶の探査を開始する fire() sleep(1) turnR4(2000) def ItoH(): traceL() turnL4(1600) def HtoG(): traceRa(2) traceR() sleep(1) turnL4(1200) def GtoE(): traceRa(2) traceR() traceRa(2) traceR() sleep(1) turnR3(1200) def EtoLtoY(): traceRa(2) traceR() straight()
それぞれの関数名はどの地点からどの地点へ移動するときに使うプログラムかを示している。
AtoB() BtoK() KtoJ() JtoI() ItoH() HtoG() GtoE() EtoLtoY()
よって最終的にロボットを動かすためのプログラムは以上のものとなる。
最終的な成功率としては約70%ほどだった。
失敗する要因は曲がってほしい交差点で曲がってくれなかったり、その逆をしたりしたことが一番多い。
缶の探査と発射はほぼ確実に成功したのでライントレースのプログラムをもっと詰めるべきだったと思う。