Raspberry Piでサーボモーターを動かす/evdevでキーボード入力を受け取る【動画解説】

動画解説シリーズ第2弾です。

激安左手デバイスでプリンを操作する
www.youtube.com

トピックはこの3つかな。

  • 左手デバイスについて
  • Raspberry Piでサーボモーターを動かす方法
  • Raspberry Piでキーボード入力を受け取る方法(evdev)

左手デバイスについて

買ったのはこれです。

Macro Mechanical Keyboard 3 Keys 1 Knob USB RGB Mini Gaming Custom Programming Knob Keypads Switch Portable For Photoshop

実はでかいのも買いました。

Ammtoo Programming Macro Custom Keyboard 12 Key 3 Knobs RGB 18 Character One Key Password Copy Paste Mechanical Hotswap Keypad

無線のもありますがたぶん技適通ってないので欲しい人は有線を買いましょう。

あのあと結局あやしい設定アプリを入れて設定してみたのですが、ウィルスチェック等にも引っかからず普通に使えました。
ちなみに動画中に出てくる起動時警告が出るのは古いバージョンで、新しいのは出ないようです。商品ページの下の方にURLがありました。

ただキーボードを日本語配列で使ってると設定時に戸惑うことがあります。もしかしたら別エントリで解説するかも。
(追記:書きました→中国製 激安左手デバイスのキーアサインがうまくできない場合の設定方法(日本語キーボード設定のWindows) - nomolkのブログ

あと、いろいろレビューを見てると、デフォルトがぜんぶ同じキーで設定されてる場合もあるようです。
その場合は設定アプリ使わないと動画と同じことができないので、ご注意ください。

Raspberry Piでサーボモーターを動かす方法

定番ネタですが一応。まずはつなぎ方です。

こんな感じ。ポイントは

  • モーター用電源はRaspberry Piから引かずに別で取る
  • 別電源とRaspberry PiはGND同士をつなぐ
  • 使うGPIOは12と13

あたりでしょうか。
モーター用の電源は動画では5VのACアダプタを使っていますが、乾電池3~4本でもいいと思います。サーボ1台を負荷無しで動かしてみるくらいならRaspberryPiの5Vピンから取ってもいけると思うけど、複数台ある場合は別電源取りましょう。

あとサーボを動かすにはPWMという機能を使うので、使えるGPIOピンが決まっています。12と13を使いましょう。
※ハードウェアPWMを使わずにソフトウェアPWMを使えば別のGPIOでもいけると思うけど、精度が下がりそう。

続いてソフトウェア側です。
pigpioというライブラリを使います。読みはピグピオじゃなくてパイ・GPIOだと思います。ちなみにインストールしなくても最初から入ってました。

プログラムの実行前にサービスを起動しておく必要があります。

sudo pigpiod

細かい使い方は他のサイトでもよく解説されてるので割愛します。今回使ったコードを貼るので、似たようなことをしたい方は参考にしてください。

import pigpio
import evdev
import sys

# pigpioの初期化
pi = pigpio.pi()
if not pi.connected:
    print("pigpioデーモンが起動していません。'sudo pigpiod'を実行してください。")
    sys.exit()

# GPIOピン設定
SERVO1_GPIO = 12
SERVO2_GPIO = 13

# 周波数
PWM_FREQUENCY = 50  # 50Hz

# 角度をデューティサイクルに変換(0~1,000,000の範囲)
def angle_to_duty_cycle(angle):
    pulse_width = 500 + (angle * 2000 // 180)  # パルス幅(μs)
    duty_cycle = int((pulse_width / 20000) * 1000000)  # 20ms周期に基づく比率
    return duty_cycle

# 初期位置
servo1_angle = 90
servo2_angle = 90
pi.hardware_PWM(SERVO1_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo1_angle))
pi.hardware_PWM(SERVO2_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo2_angle))

# キーボードデバイスを直接指定
keyboard = evdev.InputDevice('/dev/input/event2')
print(f"{keyboard.name} のキー入力を監視しています...")

# キー入力ループ
try:
    for event in keyboard.read_loop():
        if event.type == evdev.ecodes.EV_KEY:
            key_event = evdev.categorize(event)
            if key_event.keystate == evdev.KeyEvent.key_down:
                key = key_event.keycode

                if key == 'KEY_C':
                    servo1_angle = 80
                    pi.hardware_PWM(SERVO1_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo1_angle))
                    print(f"サーボ①を{servo1_angle}度に設定しました。")
                elif key == 'KEY_B':
                    servo1_angle = 90
                    pi.hardware_PWM(SERVO1_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo1_angle))
                    print(f"サーボ①を{servo1_angle}度に設定しました。")
                elif key == 'KEY_A':
                    servo1_angle = 100
                    pi.hardware_PWM(SERVO1_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo1_angle))
                    print(f"サーボ①を{servo1_angle}度に設定しました。")
                elif key == 'KEY_D':
                    servo2_angle = max(0, servo2_angle - 5)
                    pi.hardware_PWM(SERVO2_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo2_angle))
                    print(f"サーボ②を{servo2_angle}度に設定しました。")
                elif key == 'KEY_E':
                    servo2_angle = 90
                    pi.hardware_PWM(SERVO2_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo2_angle))
                    print(f"サーボ②を{servo2_angle}度に設定しました。")
                elif key == 'KEY_F':
                    servo2_angle = min(180, servo2_angle + 5)
                    pi.hardware_PWM(SERVO2_GPIO, PWM_FREQUENCY, angle_to_duty_cycle(servo2_angle))
                    print(f"サーボ②を{servo2_angle}度に設定しました。")
                elif key == 'KEY_Q':
                    break
                else:
                    print(f"キー {key} は無効です。")

except KeyboardInterrupt:
    print("終了します。")

finally:
    pi.hardware_PWM(SERVO1_GPIO, 0, 0)  # PWM停止
    pi.hardware_PWM(SERVO2_GPIO, 0, 0)
    pi.stop()

こんなかんじ。

Raspberry Piでキーボード入力を受け取る方法(evdev)

「python キー入力」とかで検索するとinput()関数が出てきますが、SSH経由でいじってる場合にPC側の入力を受け取ることになるし、そもそもEnter押さないと値を受け取ってくれないので不適です。
Raspberry Piにつながったキーボードを直接監視するには、evdevというライブラリを使います。

pip install evdev

なんですけど、上記コマンドを実行すると「× This environment is externally managed」とかいうエラーが出ます。
最近のRaspbian(Raspberry Piの標準OS)ってpip installを普通に使えないらしいです。どうもシステムに直接ライブラリをインストールするんじゃなくて、プロジェクトごとに仮想環境を作ってそこに入れて管理してね、ということらしいです。
しかしRaspberry PiのOSなんてほとんど使い捨てみたいな使い方しかしてないので、それにあんまり意義を見出せません。そういう場合は「--break-system-packages」をつけるとエラーを無視してインストールできます。

pip install evdev --break-system-packages

入力を受け取るとき、どのデバイスの入力を待ち受けるかプログラム中で指定してやる必要があります。
下記はそれ用のテストプログラムです。
実行するとデバイスの一覧が表示され、そこから選ぶとキー入力状況を表示できます。

import evdev

# 接続されている入力デバイスを表示
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
for device in devices:
    print(f"デバイス名: {device.name}, パス: {device.path}")

# モニターするキーボードデバイスを指定(適切なパスに置き換える)
keyboard_path = input("キーボードのパスを入力してください(例: /dev/input/event0):")
keyboard = evdev.InputDevice(keyboard_path)

print(f"{keyboard.name} のキー入力を監視しています...")

# イベントをループで監視
try:
    for event in keyboard.read_loop():
        if event.type == evdev.ecodes.EV_KEY:
            key_event = evdev.categorize(event)
            if key_event.keystate == evdev.KeyEvent.key_down:
                print(f"キー押下: {key_event.keycode}")
            elif key_event.keystate == evdev.KeyEvent.key_up:
                print(f"キー解放: {key_event.keycode}")
except KeyboardInterrupt:
    print("キー入力の監視を終了します。")

これはsudoを使って管理者権限で実行する必要があります。
実行するとデバイスのリストが出るので、一つ選んで、例えば「/dev/input/event0」とか入力してEnterを押します。
実際にキー入力してみて入力状態が表示されれば当たり、違ったら別のデバイスでやり直しましょう。

ここでデバイスが特定できたら、上のコード(サーボモーターのところに貼った長いやつ)の

# キーボードデバイスを直接指定
keyboard = evdev.InputDevice('/dev/input/event2')

のところを書き換えればキー入力を待ち受けることができます。

以上、ざっくり解説でした。
参考になったらチャンネル登録と高評価お願いします。またね。

www.youtube.com