[[2019a/Member]] #contents *課題の解説 [#md50f4e0] **内容 [#pe98ebdd] A地点を出発し、下図の赤い矢印にそって動くロボットを作成する 。 &ref(001.png); **コースの概要 [#y5275963] A地点から出発 → M → K(直進) → L(ピンポン玉をつかむ) → K(右折) → J(一時停止の後、左折) → I(直進) → H(直進) → G(左折) → F → E → D(一時停止の後、直進) → C(直進) → B(一時停止) → シュート→ A地点に入る(ゴール) **注意点 [#d825c545] 交差点では1秒間停止し、丁字路では直角方向に進入する時のみ一時停止すること。 *ロボット作成の方針 [#tc268f06] **ライントレース [#l8ceb661] ライントレースをするためにカラーセンサーを使用する。このカラーセンサーは対象に向かって光を出し、反射した光の量を測って対象の明るさを測定している。黒い部分を測ると反射量が少なくなり値が小さくなる。白い部分を測ると反射量が多くなり値が大きくなる。 &ref(003.png); ライントレースをするにはカラーセンサーの値が常に一定になるように進めばよい。今回は紙とラインの境目をトレースする方法を利用することにした。つまり白い領域に入ったら黒い領域に向かうようにし、黒い領域に入ったら白い領域に向かうようにすることで常に黒と白の中間を辿るようにすることにした。 ラインを滑らかにトレースするためにはラインに対して本体が入る角度をできるだけ浅くするために小さく曲がる必要がある。一方で急なカーブを曲がるときには大きく曲がらなければいけない。スムーズな移動をするためには状況によって曲がる量を変える必要がある。このセンサーの値は連続的に変化するので黒と白に加えて黒よりの灰色や灰色、白よりの灰色と分けることができる。今回はこの五つに分けて大きく右に曲がる、小さく右に曲がる、直進する、小さく左に曲がる、大きく左に曲がるようにした。 **交差点認識、判別 [#l47d0d15] 基本はライントレースをしているので交差点に入ったことをロボットに認識させる必要がある。ラインの左側をトレースさせる場合交差点に入るとカラーセンサーの値は黒色を示すので左に転回してしまう。ここで普通のカーブと異なる点は黒色の領域にいる時間がカーブの時より長いことである。この違いを利用して交差点を判別することにする。 &ref(004.png); 普通はtime関数を使って黒色を示す時間を計測して判別するところだが、ここではあえて黒色を示した”回数”を利用した。このプログラムではwhileを使って0.001秒ごとにカラーセンサーの値を読み取り、ifを使ってその値に合わせた5段階の動き方をするように作っている(0.001秒ごとの周期に設定しているが実際に0.001秒ごとにやってくれているのかはわからない)。ここでロボットがラインに大きく入り込んだとすると、ロボットはラインから出るように動くはずである。この動作は白い部分に出るまで連続で続く。ここで変数(ここではiと置いている)を使って黒に入ったときにiに数を足していきiが一定の値を超えた時にこのトレースが止まるようにすれば交差点を認識できる。もちろん黒い領域にいる時間が最も長くなるのは交差点(直角カーブでも止まるが交差点を左折する動きをすれば問題ない)であるから交差点でのみ止まるように値を調節する必要がある。またこの設定では小刻みにラインに入っても止まるので灰色より明るい場所に入った場合にはiの値を0、すなわちリセットしている。 **ボールキャッチ・シュート [#p72435c8] ボールをつかみ、シュートするという複雑な動きをたった一つのモーターで行わなければけないというのはかなり無理のあることである。しかし、我々はこの難題を解決する方法を編み出した。まずシュートする方法を考えなければならないが、チームの一人がカタパルト式がいいという熱い要望があったのでカタパルト式を採用することになった。次にボールをつかむ必要がある。とはいってもカタパルトでやる以上水平に対して垂直な円運動の動作しかできない。またボールは地面に置いてあるのでボールを持ち上げるには下から上へ一方通行となる仕組みを作る必要があった。そこで考えたのが下図である。ボールを入れるかごはできるだけ固定せず外側へ開くようにしてアームをボールに押し付けるとアームが押し広げられ、ボールが上へと移動する。上に行ったボールはその自重で落ちることはないのでそのままゴールまでもっていき180°回転してシュートするという算段である。 &ref(005.png); &ref(007.png); *実際に作成されたロボット [#e3377703] **全体像 [#v414259a] できるだけシンプルな構造になるように意識した。特に重要だったのはカラーセンサーの位置でロボットの中心で床との間隔がちょうどよくなるようにすることはもちろん、本体の少し前方につけることでライントレースがより正確に行うことができるようになった。前回よりもコードを多く使ったので絡まらないように配線に気を付けた。 &ref(006.JPG); **アーム部分 [#u9d1e749] カタパルト式をとっているためアームは少し長めである。 ボールを乗せた状態 &ref(008.JPG); *プログラム [#s92d962a] **導入部分 [#ja2bc69b] #!/usr/bin/env python3 from ev3dev.ev3 import from time import sleep #スリープを使うには必要。 mL = LargeMotor('outA') #進行方向を向いて右側のタイヤを動かすモーターをoutAに接続。 mR = LargeMotor('outB') #進行方向を向いて左側のタイヤを動かすモーターをoutBに接続。 mM = MediumMotor('outC') #アームを動かすモーターをoutCに接続。 cs = ColorSensor('in1') #カラーセンサーをin1に接続。 **定義部分 [#p0e0256a] プログラム作成を円滑にし、かつ第三者に見やすくするためによく使う動作を定義しておく。 ***基本的な動作 [#ib9e3d2f] 特によく使うものをまとめた。関数の中でも使っている。 def rs(): #resetの略。各モーターをまとめてリセットする関数。 mL.reset() mR.reset() mM.reset() def stop():ロボットの移動を停止をする関数。 mL.stop() mR.stop() def turn_left(llsp,lrsp): #。カーブを左に曲がるための関数。llspは左曲がり"left"の左車輪"left"のスピード"speed"を意味。 mR.run_forever(speed_sp=lrsp, stop_action='brake') mL.run_forever(speed_sp=llsp, stop_action='brake') def turn_right(rrsp,rlsp): #カーブを右に曲がるための関数。turn_left()と同じ関数だが見た感じ分かりやすくするために分けて作った。 mL.run_forever(speed_sp=rlsp, stop_action='brake') mR.run_forever(speed_sp=rrsp, stop_action='brake') def st(): #直進する関数。 mL.run_forever(speed_sp=200, stop_action='brake') mR.run_forever(speed_sp=200, stop_action='brake') ***ライントレース部分 [#n801bcde] 4つ目の分岐でiに0.1足しているのは気分である。ここは足さなくても問題はないと思う。 upper_limitの部分は交差点やカーブの有無によって変動するので、変えられるようにしておいた。 def follow_line_left(upper_limit): #ラインの左側をトレースする関数。upper_limitには自由に数字を入れられる。 i = 0 #変数iを定義 rs() while i < upper_limit: #iがupper_limitになるまでトレースを続行。 if cs.value() >= 20: #csの値が20以上なら大きく右折。 turn_right(200,-20) i = 0 #iをリセット。 elif 20 > cs.value() >= 17: #csの値が17以上20未満なら小さく右折。 turn_right(200,100) i = 0 #iをリセット。 elif 17 > cs.value() >= 14: #csの値が14以上17未満なら直進。 st() i = 0 #iをリセット。 elif 14 > cs.value() >= 11: #csの値が11以上14未満なら小さく左折。 turn_left(200,100) i += 0.1 #iに0.1足す。 else: #csの値がそれ以外、つまり11未満であれば大きく左折。 turn_left(200,-100) i += 1.0 #iに1.0足す。 sleep(0.001) #csの条件判断を0.001秒ごと行う。 stop() ***交差点部分 [#f9ca592e] 今回は右折する場面がないので右折は省略。一応aの値をマイナスにすれば右折する。 def cross_left(a): #交差点で左折する関数。 while cs.value() < 20: #カラーセンサー(以後cs)の値が20になるまで左へ転回することを意味する。 mL.run_forever(speed_sp=a, stop_action='brake') mR.run_forever(speed_sp=-1*a, stop_action='brake') stop() 交差点に入って少し左に曲がってから止まるので向きを調整する必要があった。少し進んで右に曲がるとちょうどいい位置で止まってくれた。 def cross_st(): #交差点で直進する関数。 st() sleep(0.2) #0.2秒だけ直進。 stop() while cs.value() < 15: #csの値が15になるまで右に転回。 mL.run_forever(speed_sp=-70, stop_action='brake') mR.run_forever(speed_sp=70, stop_action='brake') stop() ***アーム部分 [#a65696fc] def catch_ball(): #ボールをつかむ関数。 mM.run_forever(speed_sp=300, stop_action='brake') #リフトを下げる。 sleep(1) mM.stop() def throw_ball(): #ボールをシュートする関数。 mM.run_to_rel_pos(speed_sp=1000, position_sp=-80, stop_action='brake') #約80°リフトを上げる。勢いよく。 sleep(0.5) mM.stop() **実際に動作するプログラム [#q11d195c] #の後ろの記述は説明のために書いてあるので、実際に書く必要はない。微調整をしたのでわかりずらい部分は---で囲んである。囲まれた部分を一つの動作としてみてほしい。 follow_line_left(10.0) #MK間をトレース。 turn_left(90,-90) #------------------------- sleep(1) #ボールにアームの向きを合わせる。↕ stop() #------------------------------------ catch_ball() #ボールをつかむ。 turn_left(100,-100) #--------------- sleep(1) #Jの方向に向きを合わせる。↕ stop() #---------------------------- follow_line_left(10.0) #KJ間を移動。 turn_left(70,-70) #------------------ sleep(1) #Jで左折。 ↕ stop() #----------------------------- follow_line_left(7.0) #JI間を移動。 sleep(1) #Iで一時停止。 cross_st() #Iを直進 follow_line_left(4.0) #IH間を移動。 sleep(1) #Hで一時停止。 cross_st() #Hを直進。 follow_line_left(4.0) #HG間を移動。 cross_left(100) #Gを左折。 follow_line_left(7.0) #GF間を移動。 cross_left(100) #Fはカーブであるが実質的な左折として扱う。 follow_line_left(10.0) #FE間を移動。 cross_left(100) #Eはカーブであるが実質的な左折として扱う。 follow_line_left(10.0) #ED間を移動。 sleep(1) #Dで一時停止。 cross_st() #Dを直進。 follow_line_left(8.0) #DC間を移動。 sleep(1) #Cで一時停止。 cross_st() #Cを直進。 follow_line_left(7.0) #CB間を直進。 sleep(1) #Bで一時停止。 turn_left(100,-100) #---------------------- sleep(3) #180°回転して、シュートの準備。↕ stop() #----------------------------------- srow_ball() #Aに向かってシュート。 mL.run_forever(speed_sp=-150, stop_action='brake') #------------------- mR.run_forever(speed_sp=-150, stop_action='brake') #バックでAに格納。 ↕ sleep(2) #st()のスピード部分を変数にして使ってもよかったかもしれない。↕ stop() #--------------------------------------------------------------- *感想 [#y10f67bc] 定義の甘さが目立った。ただ cross_left()を使って左折しようとしても思ったような角度まで曲がらなかったり、曲がったりして、場所によって手を加えていった結果、当初よりはプログラムが長くなってしまった。しかも内容を変えずとも、成功したり、しなかったり、と不安定さも残ってしまった。問題を解決するためにプログラムをつけ足してしまうのは悪い癖かもしれない。定義の部分で安定したものをつくれればそこまで複雑にしなくて済んだことも事実なので、定義の作成をより大切にしようと思った。このプログラムとは関係ないが毎回プログラムを動かす前にカラーセンサーの値を確認するようにしていた。値のずれでプログラムがうまくいかなくなることが多々あったので、確認しておいてよかったと思った。