ゆるエンジニアはいろいろ遊びたい

FAエンジニアが週末にいろいろ遊ぶブログです

RaspberryPi Zero2Wでモーターをゲームパッドで操作してみた

倒立振子を作るためにFEETECHのFM90を買ったのですが、4個セットだったので2個余っていました。
ラズパイゼロ2Wでも使ってみたかったので、モータードライバL298Nを購入し、ゲームパッドで動かしてみました。

アマゾンで2個750円くらい。安いのですがアマプラに加入していないと送料を取られます。
L298Nは最大電圧12V、駆動電流2Aで、入力の電源により出力電圧が変わるようです。
今回はドライバの電源は単3電池4本の6Vでモーターを動かします。ラズパイの電源は別途USBから給電しています。
配線図は以下

ラズパイ5ではハードウェアPWMを使うためにconfig.txtをいじる必要がありましたが、ゼロ2wではpigpioが使えるのでいらないみたいです。
今回はPS4のコントローラをBluetoothで接続して動かしてみました。
まずは必要モジュールのインストールから。ラズパイのターミナルで実行します。

sudo apt update
sudo apt install pigpio
sudo systemctl enable pigpiod
sudo systemctl start pigpiod
sudo apt install python3-evdev

pigpioはすでにインストールされてました。
evdevはコントローラを使うために必要みたいです。
PS4コントローラとの接続はラズパイのデスクトップ上でBluetoothのアイコンからデバイスを追加することでできました。
バイスの番号を調べるには、

ls /dev/input

でわかります。event1がパッドのジャイロ、event2がパッドの操作です。
プログラムは以下の通り

from gpiozero import Motor, PWMOutputDevice
from gpiozero.pins.pigpio import PiGPIOFactory
from evdev import InputDevice, ecodes
from threading import Thread
import time

# ---------- pigpio ----------
factory = PiGPIOFactory()

# ---------- GPIO設定 ----------
LEFT_EN  = 18
LEFT_IN1 = 23
LEFT_IN2 = 24

RIGHT_EN  = 19
RIGHT_IN1 = 27
RIGHT_IN2 = 22

# PWMは低め(重要)
left_pwm  = PWMOutputDevice(LEFT_EN,  frequency=200, pin_factory=factory)
right_pwm = PWMOutputDevice(RIGHT_EN, frequency=200, pin_factory=factory)

left_motor  = Motor(forward=LEFT_IN1,  backward=LEFT_IN2,  pin_factory=factory)
right_motor = Motor(forward=RIGHT_IN1, backward=RIGHT_IN2, pin_factory=factory)

# ---------- PS4コントローラ ----------
gamepad = InputDevice('/dev/input/event2')  # 必要なら変更
print(gamepad)

# ---------- 状態 ----------
LEFT_GAIN  = 1.0
RIGHT_GAIN = 0.69

left_val  = 0.0
right_val = 0.0
running   = True

DEADZONE = 10
MAX_PWM  = 0.7   # 最大速度制限(安全)

# ---------- ユーティリティ ----------
def clamp(v, min_v=0.0, max_v=1.0):
    return max(min(v, max_v), min_v)

def stick_to_power(value):
    if value is None:
        return 0.0

    center = 128
    diff = value - center

    if abs(diff) < DEADZONE:
        return 0.0

    power = diff / 127.0
    power = max(min(power, 1.0), -1.0)

    # 前倒しで前進にする
    return -power

def drive(left, right):
    # 左
    if left >= 0:
        left_motor.forward()
        left_pwm.value = clamp(left * MAX_PWM)
    else:
        left_motor.backward()
        left_pwm.value = clamp(-left * MAX_PWM)

    # 右
    if right >= 0:
        right_motor.forward()
        right_pwm.value = clamp(right * MAX_PWM)
    else:
        right_motor.backward()
        right_pwm.value = clamp(-right * MAX_PWM)

def stop():
    left_pwm.value = 0
    right_pwm.value = 0
    left_motor.stop()
    right_motor.stop()

# ---------- モーター制御スレッド(50Hz) ----------
def motor_loop():
    while running:
        drive(left_val, right_val)
        time.sleep(0.02)   # 50Hz

motor_thread = Thread(target=motor_loop, daemon=True)
motor_thread.start()

# ---------- メイン(入力処理のみ) ----------
try:
    print("PS4 Tank Drive Start")
    
    for event in gamepad.read_loop():
        
        if event.type == ecodes.EV_ABS:
            if event.code == ecodes.ABS_Y:
                power = stick_to_power(event.value)
                left_val  = power * LEFT_GAIN
            elif event.code == ecodes.ABS_RY:
                power = stick_to_power(event.value)
                right_val = power * RIGHT_GAIN
        '''左右1スティック動作
        if event.code == ecodes.ABS_Y:
            power = stick_to_power(event.value)
            left_val  = power * LEFT_GAIN
            right_val = -power * RIGHT_GAIN
        '''  


except KeyboardInterrupt:
    print("Exit")

finally:
    running = False
    stop()
    left_pwm.close()
    right_pwm.close()
    left_motor.close()
    right_motor.close()

実行するとPS4コントローラで動かせました。
左右のモーターで出力差があったため、遅い方のモーターに合わせて出力調整をしています。


やっぱり精密な制御の場合はエンコーダがあるモーターじゃないと無理っぽいです。
今までFA機器を使っていてここまで個体差がある部品を使った事がなかったので、やはり産業用機器は精度が全然違うんだなぁ。と思いました。
もう少し慣れてきたらエンコーダ付きのモーターを使ってみたいです。
前回ブラウザでカメラモジュールのストリーミングができるようになったので、これでモニター付きラジコンカーを作れるようになりました。