- 最終更新日
- 記事公開日
コントローラーのボタン長押しを検知。押しっぱなしの間はサーボモーターが動き、離すと停止(ラズパイZERO・Python)
今回、「ラズパイに繋いだコントローラーの長押しを検知して、ボタンを押している間はサーボモーターを動かす」という制御を行ったのですが、意外と大変だったので情報を残しておきます。
ボタン長押し中はプログラムが動かない
「jstest /dev/input/js0」コマンドでボタンの割り当てをチェックしてみると分かると思いますが、押している間は何のデータも送られていません。
データが送られているタイミングは、「ボタンを押した瞬間」と「ボタンを離した瞬間」だけです。
もしも、ボタンを押している間もデータを送ってくれる仕組みだったら、ループ文の途中に細かいパルスを出し続ければ、簡単にサーボモーターの制御ができたのですが・・・
しかも厄介なのが、コントローラーのコマンド入力待ちで「待機画面」になること。
コマンドが入力されない限り、プログラムが走らないので、何も処理を行えません。
ボタンを長押ししている間、何もできないのです。
ボタンを離したときに、やっとプログラムが走り出します。
つまり、「ボタンONでモーターを動かし、ボタンを離した瞬間にモーターを止める」という手法を使うには、少し工夫が必要になります。
そこで使用したのが、「threading」
「threading」でコマンド入力とモーター制御を同時に動かす
「threading」は、Python3.xでは標準ライブラリとして用意されおり、複数の関数を並列に同時処理できる仕組みです。
「コントローラのコマンド待ち画面」と「サーボモーターを動かす」をthreadingで並列処理することにより、ボタンの長押しに応じてサーボモーターを動かすことができました。
サーボモーターの動作は、一回のパルスで目的の位置を指定するのではなく、数百回に分け、ループで最終的な位置まで動くようにしています。
ボタンが押された瞬間に、サーボモーターのループ動作がスタート。
ボタンが離れた瞬間に、サーボモーターのループ動作ストップの指示をします。
#!/usr/bin/python
import time
import threading
import pigpio
import struct
# Device Input Directory
device_path = "/dev/input/js0"
# L(time) : unsigned long
# h(value) : unsigned short
# B(type) : unsigned char
# B(number) : unsigned char
EVENT_FORMAT = "LhBB";
EVENT_SIZE = struct.calcsize("LhBB")
# PWM Pin Set
gpio_pin0 = 12
gpio_pin1 = 13
# Make Instance : pigpio.pi
servo = pigpio.pi()
# GPIO12 : ALT0(PWM0)
servo.set_mode(gpio_pin0, pigpio.ALT0)
# GPIO13 : ALT0(PWM1)
servo.set_mode(gpio_pin1, pigpio.ALT0)
# Default Motor Status
flag = False
# Controller Input Func
def con_input():
# Device Path Open
with open(device_path, "rb") as device:
# Device Input Data Read (1st Time)
event = device.read(EVENT_SIZE)
# Exists [event] Data
while event:
# Fromat Convert
(con_time, con_val, con_type, con_num) = struct.unpack(EVENT_FORMAT, event)
print( "{0}, {1}, {2}, {3}".format(con_time, con_val, con_type, con_num ) )
global flag
# CROSS KEY Push
if con_type == 2:
# LEFT / RIGHT KEY
if con_num == 0:
# INITIAL
if con_val == 0:
flag = False
# LEFT (MAX:-32767)
elif con_val < 0:
flag = 1
# RIGHT (MAX:32767)
elif con_val > 0:
flag = 2
# Device Input Data Read (2nd Time)
event = device.read(EVENT_SIZE)
# Parallel Process Command
th = threading.Thread(target=con_input)
th.start()
# Default Motor Pulse
i = 80000
# Speed Adjust (Motor Step)
step = 200
# Speed Adjust (Wait Interval)
interval = 0.00001
# Motor Max Pulse
left_max = 40000
right_max = 107000
# Infinite loop
while True:
# Controlle BUTTON ON
if flag:
# LEFT KEY ON
if flag == 1:
i = i - step
if i < left_max:
i = left_max
# RIGHT KEY ON
elif flag == 2:
i = i + step
if i > right_max:
i = right_max
# Motor Pulse Set
servo.hardware_PWM(gpio_pin0, 50, i)
# Motor Pulse Data
print(i)
# Speed Adjust
time.sleep(interval)
# Parallel Process End
th.join()
exit()
サーボモーターを途中で停止させる処理について
サーボモーターの動作範囲は、はじめに送ったパルスで決まってしまいます。
しかし、ステップを付けて、一回の動作を細かく区切ったコードにすれば、途中で止めることができます。
例えば、通常のサーボモーターを動作させる場合のコード(Python)は以下の通りですが、これでは途中でモーターを止めることはできません。
servo.hardware_PWM(gpio_pin0, 50, 70000)
もし、サーボモーターに緊急停止機能や強制ストップ機能でも付いていれば、このコードでも問題ない可能性はありました。
しかし、1,000円ほどのMG996にはそんな機能は搭載されていません。
また、「途中で電流を遮断してしまえばいいのでは?」と思い試してみましたが、再度、電流を流した時に、遮断する前に送ったパルスの位置を初期値と認識して動いてしまいました。
ということで、以下のようにステップを付けて動かしています。
while True:
i = i + 200
servo.hardware_PWM(gpio_pin0, 50, i)
これなら、loop文の途中に差し込んだり、並行処理を使って、外部からのインプット信号を割り込ませれば、サーボモーターを途中で止めることが可能になります。
なお、ステップ値を細かくすればするほど、シームレスに動いてくれます。
サーボモーターの動作速度を調整する処理について
サーボモーターの動作速度を速くしたり遅くしたりと、スピード調整したい場合、一般的には、電圧の上げ下げで行うのかもしれません。
しかし、
- 電圧を下げると、トルクも下がってしまう
- ソフトウェアだけでは、電圧は制御できても、モーターを動かすために十分な電流を扱えないため、トランジスタを搭載した別回路が必要になる(ラズパイZEROのGPIOピンで扱える最大電流量は8mA)
以上の理由から、今回はコードだけで速度調整を行うことにしました。
速度調整のキモとなるのが、
step = 200
interval = 0.00001
の2項目です。
step
- ステップ数を小さく → シームレス感:UP、動作スピード:DOWN
- ステップ数を大きく→ シームレス感:DOWN、動作スピード:UP
interval
- パルス信号の間隔を小さく → 応答速度:UP、動作スピード:UP
- パルス信号の間隔を大きく → 応答速度:DOWN、動作スピード:DOWN
この2つの項目を調整しながら、目的に合わせて調整していきます。
「応答速度」は速ければ良いわけではなく、操作する人間が想像している感覚とマッチさせるため、やや調整が必要でした。
パルスを分けた分、動きが悪くなることも予想していましたが、予想に反して、一回のパルスを送ったときと変わらないくらいサクサク動いてくれました。
完成品
さらに詳しい情報は、下記のページに掲載しています↓