【電子工作】ゲームパッドの入力信号をBluetoothでラズパイに飛ばしてモーターを駆動させる
今回の電子工作は「PC接続したゲームパッドのスティック入力をBluetoothでラズパイに飛ばしてDCモーターおよびサーボモーターを駆動させる」というものです。
やりたいこと
そもそものきっかけは、「脳波コントロールできるラジコンカーを作りたい!」でした。でも、いきなりそこに行くのはかなり大変そうだ。そこでまずはゲームパッドでコントロールできるラジコンカーを作ることにしました。ただし、単純にラズパイにゲームパッドを接続して動かすのではそのあとの開発が大変そうだ、と考え、こんな構成を考えました。
ゲームパッドや脳波などからの入力信号はPCで処理します。ゲームパッドの場合、左スティックの上下をDCモーターの回転に、右スティックの左右をサーボモーターの駆動に割り当てます(詳しくはコードのパートで)。処理された信号をBluetoothでラズパイに送信し、モーター類の駆動のためのデータとします。
こうすることで、入力信号の処理系と、モーター類の駆動系を別々にできるので、入力デバイスを変えたいと思ったときにはPC側の処理系だけを変更すれば良い、ということになるわけです。
さあ、工作しよう
というわけで、工作します。
使うもの
後ははんだごて、割りばし、瞬間接着剤、カッターを使います。
ジャンパワイヤはこんなに必要ないです。
オス-メスピン 8本
- DCモーター用 2本(DCモーターのケーブルにはんだづけ)
- サーボモーター用 3本
- モータードライバ用 3本
- ブレッドボード-ラズパイ接続用 2本
オス-オスピン 1本
- ブレッドボード上で使用
それ以外はブレッドボードキットに付属しているホチキス針みたいなやつがあればOK。ゲームパッドはXBoxのやつを使いました。
ワニ口クリップのやつは電池をつなぐのに使います。はんだ付けしちゃっても良い場合はワニ口じゃなくても大丈夫です。
車体にはタミヤのバギーを使います。ステアリングとモーターボックスが別になっており、加工が簡単なためです。ただし、このままでは使えない箇所もあるので、一部ちょこっとだけ加工が必要です。
なお、ラズパイは3B+を使っていますが、バージョンはこれでなくても大丈夫だとは思います。また、今回はラズパイを電源に挿したままの状態で使用しているので、ラズパイ用のバッテリーは購入していません。本格的にラジコンにする場合はバッテリーも必要ですので、購入しておいてください。
バギー本体をくみ上げる
タミヤのバギーにはDCモーターが付属しているので、基本的に説明書に沿って作っちゃってOKです。ただし、電池ボックスの箇所にサーボモーターを配置するため、電池ボックスはつけないでください。
こんな感じになります。
二つほど工夫があります。まずはモーターから。
モーターの電極部分にコンデンサをはんだ付けしています。これをしておくと電流が安定し、ラズパイ本体を傷つけにくくなるとのこと。
もう一つはサーボモーター。
いろいろと模索してみた結果、割りばしを裁断して積み上げる形式におちつきました。今回は5本、積み上げています。使う割りばしの太さによって積み上げる数は変わると思います。百均で売っている瞬間接着剤で割りばし同士および割りばしとサーボモーターを接着しています。ねじうちするとねじの太さによっては割りばしが壊れてしまうので、細い釘で補強するなりすると動作が安定するでしょう。
また、プロペラ部は穴同士をつなげています。ステアリングを左右に振ったときに位置が前後するため、プロペラの隙間を動けるようにするためです。これは実際に自分で動かしてみると、こうする理由がはっきりすると思います。
あとは、ステアリングバーの中心にプロペラの穴に入るくらいの太さの針金をはんだ付けしています。これでステアリング部分は完了。
なお、注意としてここまでくみ上げる前にサーボモーターの位置を中心にしておいてください。ここまで組んでしまうと取り外しできない仕様になっているためです。
配線する
ものすごくわかりにくくて恐縮です。
DCモーター、モータードライバ、ラズパイの接続
DCモーターとモータードライバの接続は付属の説明書に記載があるので、そちらを確認してください。この配線では、モータードライバのin1を20に、in2を21に、pwmを16につないでいます。
サーボモーターとラズパイの接続
サーボモーターは茶、赤、黄のラインが出ています。以下のような接続にします。今回はラズパイと直接接続していますが、間にドライバを挟んでも良いかもしれないです。
- 茶:GND
- 赤:電源
- 黄:3ピン
配線後
配線が全部完了するとこんな感じ。眼鏡ケースに乗せて動作確認しています。
このまま走らせると内〇ぶちまけながら走ってるみたいな感じになっちゃいますので要注意。
コードを書こう
コードはpython3系を使用します。pygame, serial, RPi.GPIOの三つのライブラリを使います。そんなに難しいことはしていません。ポイントはPC側で取得したデータをいったんテキストにしてBluetoothのシリアル通信に乗せ、ラズパイ側で受信したらそれを数値に復元する、というところでしょうか。
PC側、ラズパイ側のいずれも入力信号を確認するためのウィンドウをpygameの関数を使って表示させています。これはあってもなくてもかまいません。あった方がどんな数値をやりとりしているのかがわかりやすくなる、くらいです。
PC側のコード
まずはPC側のコードです。細かいところはコードを解読してください。
# pygameでPCにUSB接続されたコントローラーのスティック入力を検知し数値を返す # 取得したスティック入力をBluetoothでRaspberryPiに送信する import pygame import serial s = serial.Serial('COM3', 9600) # 名称はBluetooth接続したときの名称にする BLACK = (0, 0, 0) WHITE = (255,255, 255) WIDTH = 320 HEIGHT = 240 def main(): # 初期化 pygame.init() surface = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.joystick.init() print('get_count:{}'.format(str(pygame.joystick.get_count()))) # コントローラーの情報を出力 joystick_id = 0 joystick = pygame.joystick.Joystick(joystick_id) joystick.init() print('get_name:{}'.format(joystick.get_name())) print('get_numaxes:{}'.format(str(joystick.get_numaxes()))) print('get_numbuttons:{}'.format(str(joystick.get_numbuttons()))) print('get_numhats:{}'.format(str(joystick.get_numhats()))) # 画面表示用の設定 myfont = pygame.font.Font(None, 30) myclock = pygame.time.Clock() endflag = 0 while endflag == 0: # ウィンドウが閉じられるまでwhileループを実行する for event in pygame.event.get(): if event.type == pygame.QUIT: endflag = 1 # スティックの入力方向を数値で取得する ax_0 = round(joystick.get_axis(1), 3) # 左スティックの縦 ax_1 = round(joystick.get_axis(2), 3) # 右スティックの横 # そのままだとわずかにスティックが倒れていると検知されるので、閾値を設けて0にする処理が必要 ax_0 = 0 if abs(ax_0) < 0.1 else ax_0 ax_1 = 0 if abs(ax_1) < 0.1 else ax_1 m = str(ax_0) + ',' + str(ax_1) + '\r\n' s.write(m.encode('utf-8')) # 取得した入力値を画面表示 bitmaptext_0 = myfont.render('axis_0:{}'.format(str(ax_0)), True, BLACK) bitmaptext_1 = myfont.render('axis_1:{}'.format(str(ax_1)), True, BLACK) surface.fill(WHITE) surface.blit(bitmaptext_0, (50, 100)) surface.blit(bitmaptext_1, (50, 150)) pygame.display.update() myclock.tick(60) pygame.quit() if __name__ == '__main__': main()
ラズパイ側のコード
次にラズパイ側のコードです。同じく細かいところは解読してください。
import serial import pygame import RPi.GPIO as GPIO # シリアル通信開始 s = serial.Serial('/dev/rfcomm0', 9600) flg = 0 # コントローラー入力表示ウィンドウ BLACK = (0,0,0) WHITE = (255, 255, 255) WIDTH = 320 HEIGHT = 240 # GPIOセット GPIO.setmode(GPIO.BCM) hertz = 50 # モータードライバ初期設定 in1 = 20 in2 = 21 pwm = 16 standbyPin = 12 GPIO.setup(in1, GPIO.OUT) GPIO.setup(in2, GPIO.OUT) GPIO.setup(pwm, GPIO.OUT) GPIO.setup(standbyPin, GPIO.OUT) GPIO.output(standbyPin, GPIO.HIGH) p_motor= GPIO.PWM(pwm, hertz) p_motor.start(0) duty = [0, 0] # サーボモーター初期設定 GPIO.setmode(GPIO.BCM) servo = 3 GPIO.setup(servo, GPIO.OUT) # 3ピンを制御用とする p_servo = GPIO.PWM(servo, hertz) p_servo.start(0) # モーター駆動用関数 def motor_drive(speed): dutyCycle = speed if(speed < 0): dutyCycle = dutyCycle * -1 if(speed > 10): GPIO.output(in1, GPIO.HIGH) GPIO.output(in2, GPIO.LOW) elif(speed < -10): GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.HIGH) else: GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.LOW) p_motor.ChangeDutyCycle(dutyCycle) # サーボモーター駆動用関数 def servo_drive(angle): p_servo.ChangeDutyCycle(angle) # 実行用関数 def main(): # データ表示用 pygame.init() surface = pygame.display.set_mode((WIDTH, HEIGHT)) myfont = pygame.font.Font(None, 30) myclock = pygame.time.Clock() stick_data = [0, 7] while flg == 0: for event in pygame.event.get(): if event.type == pygame.QUIT: end == 1 # 受信したデータを数値に変換する m = s.readline().decode('utf-8').replace('\r\n','').split(',') m = [float(m[0]), float(m[1])] #print(float(m[0]), float(m[1])) # DCモーター駆動 motor_drive(stick_data[0]) # サーボモーター駆動 servo_drive(stick_data[1]) stick_data = [0 if abs(m[0])<0.1 else m[0]*50, # 右スティック入力が0.1未満の場合は駆動しない (-m[1]*1.5)+7] # サーボモーターは最小5.5, 最大8.5、中央7とする print(stick_data) surface.fill(WHITE) bitmaptext_0 = myfont.render('axis0:{}'.format(float(m[0])), True, BLACK) bitmaptext_1 = myfont.render('axis1:{}'.format(float(m[1])), True, BLACK) surface.blit(bitmaptext_0, (50, 50)) surface.blit(bitmaptext_1, (50, 100)) pygame.display.update() pygame.quit() if __name__ == '__main__': main() GPIO.cleanup()
PCとラズパイをBluetooth接続しよう
こちらのリンク先に詳細があるのでこちらを参照ください。
qiita.com
Bluetoothのペアリングが終わったら、以下のコマンドをラズパイ側で実行するのを忘れずに。
sudo sdptool add --channel=22 SP sudo rfcomm listen /dev/rfcomm0 22
これでラズパイ側で受信可能な状態になったはずです。
課題と今後
課題点
比較的単純な構成で作れるBluetooth接続としました。しかし、Bluetoothは近距離通信なため、本格的な遠隔操作、例えば東京のPCから大阪のロボットを遠隔操作する、みたいな用途には向きません。その場合はSSH接続するのが良いでしょう。SSH接続の場合、Bluetoothのシリアル通信のような単純なデータの送信だけではだめで、Webインターフェイスなりなんなりを作る必要がありそうです。そうした場合にどうするのか、はまだ考えていません。