目次

課題説明ページ:2017b/Mission3

設計について

使用機:LEGO MINDSTORMS EV3

設計思想

当初は先の設計の自由度を上げるため、ミディアムモータ1つによる、キャッチ・昇降機能の実装したアームの完成を目指した。
この機構の設計は非常に難航し、多くの時間を費やしてしまった。これにより後のプログラミングの煮詰めが甘くなることになる。
しかし最終的にはアーム機構の設計に成功し、部品数にも余裕がでてきたため、2台体制での課題遂行を実現できた。
2台体制により時間短縮も狙えると思われたが、実際どうなったかは後述。

勿論今回も紙を動かすのは困難であった。

アーム開発

試作A型

試作アームA

先生に教えていただいた機構を参考に、歯車の組み合わせによってキャッチ・昇降をモータ1つで実現するアームを試作した。
運動の方向を変換する箇所にはベージュ色のベベルギア同士を噛み合わせている。

歴代の機構の中でも抜群の格好良さを誇るが、いくつか問題があった。

  • ベージュ色のベベルギア同士の噛み合わせが悪い。
  • 部品点数が多く、重量が重くなり、整備性も悪い。
  • 噛み合わせの悪さと重量問題が重なり、信頼性が低い。

ビジュアルを重視しすぎたのかもしれない。 これらの点から、再設計を試みた。

試作B型

試作アームB

A型の失敗からモータ1つによるキャッチ・昇降機能の実現に挫折し、ミディアムモータ2つを使い単純に繋げた。
時間的猶予に迫られて生まれた急造品である。
根元で支えるミディアムモータは辛そうだが、ホールド状態にすれば前部も支えることはできる。
単純な分安定した動作が期待できた。
この型の問題点は、

  • モータがホールド状態でなければ支えられないのは不便であり、欠陥である。
  • そもそも目標であったモータ1つによる設計が実現できていない。
  • 格好悪い。
  • 技術点も望めそうに無い。

A型アームの挫折から立ち直れていなかった。 これらの点から気を取り直して再設計を行った。

試作C型

試作アームC

ターンテーブル型ギアとベベルギアを活用し、ごくシンプルな形でキャッチ・昇降機能を実現した。
中心を通る軸を回すと、ベベルギアからターンテーブル型ギアを通じて運動方向を変換する。
ターンテーブル型ギア上部が回ると、伸びた白ビームが開閉し、コップを掴んでビームの動きが制限されると軸の回転にあわせて上へ持ち上がっていく。
狭い可動域を超えてモータを回してしまうと、すぐにあちこちのギアが外れ始める繊細な子である。
そのため、扱いには苦労させられた。
しかしそれ以外には問題無く、これを基に実戦機の開発に取り掛かった。
モータから軸にどうやって運動を伝えるかでまた苦悩するが、詳しくは下記にて。

車輌開発

試作儀

試作シャーシ1

試作アームA/B型に対応する車輌。
デフォルトの車体から前部を拡張し、モータや超音波センサを取り付ける空間を確保している。
アームの不良から廃案。
実はカラーセンサの取り付け位置にも不備がある。

試作況

試作機

試作アームC型に対応し、完成機の元になる車輌である。
写真の通り、ミディアムモータを横置きし、ギアをいくつか中継して軸へ運動を伝える。
最初はモータを縦置きし、軸の中心に運動を伝える方針だったが、スペース的な問題から諦めた。
アームの軸には繊細な運動を伝える必要があるのと、動作するアーム機構とモータの干渉を避けるためにできるだけギアを中継して離して配置した。
アームの軸の直下にはカラーセンサもあり、この狭いスペースに多機能を詰め込んだため、部品1つ交換するにも一苦労してしまう。
重心も低く抑えられたのはよかったが、繊細なC型アームと相まって、整備性は良いとは言えない。

また、超音波センサの取り付け位置に致命的なミスがあった。
アームの昇降に合わせて超音波センサも上下してしまうのである。
紙コップを掴んだら目隠し状態になるこの機体には、もう少し改良が必要だった。

完成機

完成機

試作況燭ら、超音波センサの取り付け位置とアームの形状を変更した。
超音波センサはアームの下部にシャーシと繋げて、アームと一緒に上を向く欠陥を改善した。
今まで超音波センサがコップを内側から支える役割を担ってたため、代わりに内側から支えるビームも取り付け。
また、アームは何度ものコップキャッチ実験を行い、安定してコップを掴める形状を模索し、最終的にこのような形状になった。
アームや各種センサによって重心が前部寄りになったが、EV3を後方に固定して安定させている。
整備性は悪いままだが、コンパクトに格好よくまとまったこの車輌は上出来だと思う。

写真は、完成機を2台揃えて撮ったものである。
見分けやすいようにEV3の両端のパーツの色を変えてある。
急ピッチ作業でEV3との固定方法に違いが見られるが、気にするほどではない。

戦略方針

戦略図

それぞれの車輌はA、D地点を同時にスタートする。

1号車2号車
DをスタートAをスタート
.灰奪弉鷦(1個目).灰奪弉鷦
ピンポン玉投入(1回目)待機
コップ重ねて回収(2個目)待機
ぅ團鵐櫂鷆姪蠧(2回目)待機
ゥ灰奪廚Y地点に2個重ね置く待機
Dへ退避し、信号送信⊃号受信、行動開始
待機ピンポン玉投入
待機ぅ灰奪廚Y地点へ重ねる(理想)

(表組み自体を寄せる方法がわからず、画像の回り込みを利用して表の位置調整を行った。端末によってはレイアウトが崩壊しているかもしれない。)

どちらかになにかしらのトラブルがあっても、もう一方が成し遂げる、というリスク分散を図る狙いだ。
しかし、2台体制によるデメリットもあった。
EV3にとっての”動く障害物”を増やしてしまったのである。
2台の衝突、超音波センサによるコップ検知のミスを避けるために通信を行い、2台が同時に中央ゾーンに入らないように図った。

もう少し2台である利点を活かした戦略を採りたかったが、如何せん時間が足りなかった。

また、この通信戦略もロボコンまでに間に合わなかった。
通信プログラムのみのコーディングは一応終え、車輌に搭載せずEV3本体同士のみでのテストのみ行って成功している。
しかし実行プログラムに組み込んでのテストは間に合わず、一度も行えていない。
結果、ロボコン直前に急遽予定を変更し、2号車はピンポン玉投入まで行った後に退避している。
退避後、1号車が頑張る間にアームの上下を繰り返し応援を行おうとしたが、なぜか動かなかった。

後述の実行プログラムには通信プログラムを組み込んであるが、ロボコン時の通りコメントアウトしている。

プログラム

言語:python3

関数群

ライントレースも行うが、細かな定数を調整した他は課題2の関数と同じであるため、ライントレースを行うための関数部分は省略している。
詳しくはTracer_ver.1.7.3を参照。

import ev3dev.ev3 as ev3
import time
mr = ev3.LargeMotor('outC')
assert mr.connected, "Connect right large motor to port C"
ml = ev3.LargeMotor('outD')
assert ml.connected, "Connect left large motor to port A"
arm = ev3.MediumMotor('outB')
assert arm.connected, "Connect arm medium motor to port B"
cs = ev3.ColorSensor()
assert cs.connected, "Connect a color sensor to any sensor port"
cs.mode = 'COL-REFLECT'
us = ev3.UltrasonicSensor()
assert us.connected, "Connect a color sensor to any sensor port"
us.mode = 'US-DIST-CM'
position_sp_to_angle = 1.55 #position_spによる移動時、角度に変換するための定数
target_val = 50             #ライントレース時のカラーセンサの目標値
std_speed = 22              #標準の走行速度
kp = std_speed*0.04         #ラインの境界へ戻る強さ
position_sp_to_cm = 20.05   #position_spによる移動時、値をcmに変換するための定数
intersection_time = 0.23/1.8          #線上走行時、交差点検知と判断する時間
angle_stabilization_time = 0.3*1.8    #走行する境界を変更する際の移動時間
def init_arm():    #アームモータのリセット、紙コップを掴んだ位置で行う
   'pos_positive: to inside, pos_negative: to outside'
   arm.reset()  # del stop_action='hold'
   input('Press Enter to initialize closed arm position')
   arm.reset()
def arm_open():    #アームを開く、init_armの後に使用しなければならない
   arm.run_to_abs_pos(position_sp=200,
                      speed_sp=std_speed*5,
                      stop_action="hold")
   arm.wait_while("running")
def cup_catch():    #コップをキャッチする
   mr.run_forever(speed_sp=std_speed*5)
   ml.run_forever(speed_sp=std_speed*5)
   while us.value() > 50:
       pass
   for motor in mr, ml:
       motor.stop(stop_action='brake')
   move(5)
   arm.run_to_abs_pos(position_sp=0,
                      speed_sp=std_speed*5,
                      stop_action="hold")
   arm.wait_while("running")
   move(-5)
def cup_a_little_lift():     #アームをわずかに持ち上げる、ピンポン玉投入用。init_armの後に使用しなければならない。
   arm.run_to_abs_pos(position_sp=-42,
                      speed_sp=std_speed*5,
                      stop_action="hold")
   arm.wait_while("running")
def cup_lift():      #アームを完全に持ち上げる。コップ重ね用。init_arm後に使用しなければならない。
   arm.run_to_abs_pos(position_sp=-230,
                      speed_sp=std_speed*5,
                      stop_action="hold")
   arm.wait_while("running")
def search_min_d(search_time=4.5, i=1):          #超音波センサによるコップサーチ。
   if i == 1:                                    ##引数search_timeにはコップサーチを行う時間(s)、iで左旋回、右旋回どちらでサーチを始めるか指定。
       sign = [1, -1]                            #i=1で左周り、i=-1で右周り
   elif i == -1:                                 #与えられた時間・周回で計測した中で、距離が最短だった方向へ向く。
       sign = [-1, 1]
   min_d = 500                 #コップまでの距離「min_d」の初期値を500とし、それ以下を検知次第代入していく
   start_time = time.time()
   mr.run_forever(speed_sp=sign[0]*std_speed*5) #1巡目、ここでmin_dを決定
   ml.run_forever(speed_sp=sign[1]*std_speed*5)
   while time.time()-start_time < search_time:
       if min_d > us.value():
           min_d = us.value()
   for motor in mr, ml:
       motor.stop(stop_action='brake')
   start_time = time.time()
   mr.run_forever(speed_sp=sign[1]*std_speed*5) #2巡目、min_d以下の距離を検知したら停止
   ml.run_forever(speed_sp=sign[0]*std_speed*5)
   while time.time()-start_time < search_time:
       if us.value() <= min_d:
           for motor in mr, ml:
               motor.stop(stop_action='brake')
           return
   for motor in mr, ml:
       motor.stop(stop_action='brake')
   search_min_d(search_time)                   #2巡目で停止できなかった場合、再帰呼び出し
def line_adjust_by_edge(edge):                       #交差点誤検知の回避用、引数にはトレースする境界をleft_edgeかright_edgeで与える。
   for motor in mr, ml:
       motor.run_direct()
   while abs(cs.value()-target_val) > 10:
       line_following(edge)                          #line_following関数については[[Tracer_ver.1.7.3>2017b/Member/ibu/Mission2#ibdb1e7a]]へ
   for motor in mr, ml:
       motor.stop(stop_action='brake')
def line_adjust_by_time(edge, stab_time=angle_stabilization_time):   #交差点誤検知の回避用
   for motor in mr, ml:                                              #こちらは交差点と判断するまでの線上走行の時間を指定できる
       motor.run_direct()
   start_time = time.time()
   while time.time()-start_time < stab_time:
       line_following(edge)
   for motor in mr, ml:
       motor.stop(stop_action='brake')
def run_until_line():    #線を見つけるまで走行する
   for motor in mr, ml:
       motor.run_forever(speed_sp=std_speed*5)
   while abs(cs.value()-target_val) > 10:
       pass
   for motor in mr, ml:
       motor.stop(stop_action='brake')

1号車実行プログラム

D地点をスタートする車輌のプログラム。
前述の通り、通信部分はコメントアウトしている。

#!/usr/bin/env python3
from def_red_v016 import *
#import paho.mqtt.client as mqtt  # pahoのライブラリをインポート
#client = mqtt.Client()                  # MQTTのクライアントを生成
def arm_set():            #アーム位置を調整するための機能
   a = input('w, s')
   while a in {'w', 's'}:
       if a == 'w':
           arm.run_timed(time_sp=0.1*1000, speed_sp=200, stop_action='brake')
           arm.wait_while('running')
       elif a == 's':
           arm.run_timed(time_sp=0.1*1000, speed_sp=-200, stop_action='brake')
           arm.wait_while('running')
       a = input('w, s')
'''
def mqtt_send(x): #値を送信
   position = x    #位置情報をアルファベットで送信
   client.publish("position", position)  # "position"というトピックでブローカに送る
   print("send_position_"+x)
'''
def main():
   #client.connect("10.60.2.156",1883, 60) # ブローカに接続、(60秒以上接続がないと切断)
   line_adjust_by_time('left_edge', 1.3)
   run_until_intersection('left_edge')
   intersection_turn_left()
   change_following_edge()
   run_until_intersection('right_edge')
   intersection_turn_right()
   line_adjust_by_time('right_edge', 1)
   run_until_intersection('right_edge')
   circle(145) # intersection,change direction
   arm_open()
   move(-4)
   search_min_d(1)
   cup_catch()
   circle(-105)
   move(1)
   line_adjust_by_time('left_edge', 1)
   circle(-20)
   run_until_intersection('left_edge')
   cup_a_little_lift()
   move(4)
   circle(35)
   move(5)
   cup_a_little_lift()
   move(10)
   cup_lift()
   circle(-30)
   move(-5)
   search_min_d(1, -1)
   cup_catch()
   arm_open()
   cup_a_little_lift()
   circle(60)
   cup_lift()
   circle(-140)
   arm_open()
   move(-4)
   circle(-90)
   #mqtt_send(1)
   move(50)
   #client.disconnect() # 切断
if __name__ == '__main__':
   arm_set()
   init_arm()
   main()

2号車実行プログラム

A地点をスタートする車輌のプログラム。
通信プログラムが間に合わなかったため、ピンポン玉を投入した後に下がり、以降をコメントアウトしている。

#!/usr/bin/env python3
from def_green_v016 import *
'''
import paho.mqtt.client as mqtt
client = mqtt.Client()            # MQTTのクライアントを生成
position = 0
def on_connect(client, userdata, flags, rc):  # ブローカに接続した時に実行される関数(コールバック関数)を定義しておく
   print("Connected with result code "+str(rc))
   client.subscribe("position")                # "position"というトピックスを購読
def on_message(client, userdata, msg):        # メッセージを受け取った時に実行される関数(コールバック関数)を定義しておく
   position = int(msg.payload)               # payloadという変数にメッセージが入る                   
   print(position)
'''
def arm_set():              #アーム位置を調整するための機能
   a = input('w, s')
   while a in {'w', 's'}:
       if a == 'w':
           arm.run_timed(time_sp=0.25*1000, speed_sp=200, stop_action='brake')
           arm.wait_while('running')
       elif a == 's':
           arm.run_timed(time_sp=0.25*1000, speed_sp=-200, stop_action='brake')
           arm.wait_while('running')
       a = input('w, s')
def main():
   '''
   client.on_connect = on_connect    # 上で定義したコールバック関数を渡す
   client.on_message = on_message    # 上で定義したコールバック関数を渡す
   client.connect("localhost",1883, 60)  # ブローカ(自分自身なのでlocalhost)の1883番ポートに接続 (Keep Alive は60秒)
   client.loop_start()               # メッセージ受信ループの開始
   '''
   run_until_line()
   move(4)
   line_adjust_by_edge('right_edge')
   run_until_intersection('right_edge')
   circle(33)
   arm_open()
   search_min_d(0.8)
   cup_catch()
   circle(-160)
   line_adjust_by_edge('left_edge')
   run_until_intersection('left_edge')
   intersection_turn_left()
   line_adjust_by_time('left_edge', 1.3)
   cup_a_little_lift()
   move(5)
   cup_lift()
   move(-50)
   '''
   line_adjust_by_time('left_edge', 5)
   run_until_intersection('left_edge')
   while position != 1:
       pass
   move(15)
   circle(35*1.2)
   line_adjust_by_edge('left_edge')
   line_adjust_by_time('left_edge', 3)
   run_until_intersection('left_edge')
   intersection_turn_left()
   run_until_intersection('left_edge')
   circle(-27)
   arm_open()
   client.loop_stop()        # 受信ループを停止
   client.disconnect()       # 接続を切断
   '''
   
if __name__ == '__main__':
   arm_set()
   init_arm()
   main()    

結果・感想

プログラムがギリギリで間に合わず、方針通りに実行させることすらできなかった。
しかし、丹念な調整のおかげでポイントはある程度稼ぐ事ができ、ロボコンは2位で終えることができた。

もしあと1日余裕があり、方針通りのプログラムを実行できていれば、もう少し良い結果になっただろう。
この原因はロボット設計、特にアーム開発で時間をかけ過ぎたことでもある。
事前に見通しを立て、ロボ制作自体の計画を立てておけばよいのだが、それに無理に合わせた結果生まれたのがB型アームである。
これを設計し直して時間をかけた判断は間違っていないと信じたい。

結果はどうであれ、ロボティクス入門ゼミを通じて多くの経験を積むことができ、同時に楽しむことができた。
特にプログラミング経験、チームプロジェクトでの共同作業経験は将来に役立ってくれるはずだ。


来客数カウンター 595


意見や感想などあればどうぞ。



添付ファイル: filetactics.jpg 52件 [詳細] filemachine_proto4.jpg 48件 [詳細] filechassis_proto1.jpg 52件 [詳細] filefin_machines.jpg 77件 [詳細] fileproto_armC.jpg 71件 [詳細] fileproto_armB-2.jpg 61件 [詳細] fileproto_armA-1.jpg 108件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-02-13 (火) 12:08:03