目的
PS4コントローラのスティックでarduinoを制御する。
背景
イベント向けに、PS4コントローラのスティックで操作するラジコンを作りました。当初は一つのarduinoで制御しようとしていたのですが、センサーやモーターを追加するとすぐにPINが重複して、構成の再検討が必要になりました。
少し複雑なことをしようと思うと、途端に一つのarduinoで実現するのが難しくなります。
結局、PS4コントローラからの入力を一つ目のarduinoでi2cに送信して、二つ目のarduinoでi2cから受信してモーターを制御する様な構成にしました。
特にPS4コントローラからの入力をarduinoでi2cに送信する部分は、また使えると思ったので忘れる前にまとめます。
使用機材
Windows10パソコン
arduinoUno 2個
USB Host sheild 2.0
BLEドングル
PS4コントローラ
ブレッドボードジャンパーワイヤー 3本
(PCとarduinoを接続するための)USBケーブル 2本
環境
chocolateyを使っているならarduinoIDEのインストールは、管理者権限でプロンプト(cmd)を起動して以下のコマンドを実行します。
choco install arduino
バージョンは以下のコマンドで確認できます。
choco list arduino
Chocolatey v0.10.8
arduino 1.8.5 [Approved]
arduinoide 1.6.1 [Approved]
chocolateyのインストールは、chocolatey 基本情報まとめを参考にセットアップしました。
arduinoIDEをダウンロードしてセットアップする場合は、https://www.arduino.cc/ にアクセスしてメニューから[SOFTWARE]を選び、好きなファイル形式を選びます。
[Just Download]をクリック※気に入ったら寄付も歓迎とのこと
USB Host sheild 2.0は、Gitページにアクセスして、[Clone or Download]-[Download]をクリックしてダウンロードします。
次に[スケッチ]-[ライブラリをインクルード]-[.ZIP形式のライブラリをインストール]を選んで、先ほどDownloadしたUSB Host sheild 2.0のzipファイルを選ぶとライブラリに組み込まれます。
関連リンク
購入サイト
- amazon サインスマート(SainSmart) USBホストシールド 2.0
- amazon サインスマート UNO R3 ATmega328P 互換ボード
- amazon BLEドングル BT-Micro4
※サインスマートのUSBホストシールドは高いですがハンダする必要が無いのでおすすめです。
ドライバ(arduinoUno互換ボードのUSBシリアルドライバ)
ドキュメント
USB Host sheild参考
PS4コントローラのスケッチ
PS4コントローラとarduinoをBLEでつなぐ場合は、
[ファイル]-[スケッチ例]-[USB Host Shield Library 2.0]-[Bluetooth]-[PS4BT]を使用します。
修正後のスケッチはGitから取得してください。
コードの説明
USBの初期化
setup()のUsb.Init()でUSBの初期化を行います。
成功すればシリアルに「PS4 Bluetooth Library Started」と表示されます。
「OSC did not start」が表示されるようならUSB Host Sheildが上手く動いていないと思われるので、arduinoUnoとの接続など再確認してください。
void setup() {
Serial.begin(115200);
if (Usb.Init() == -1) {
Serial.print(F("OSC did not start\r\n"));
while (1); // Halt
}
Serial.print(F("PS4 Bluetooth Library Started\r\n"));
}
PS4コントローラのペアリングと接続
loop()のUsb.Task()でUSBのシーケンスを進めてBLE接続を行います。
PS4コントローラのshareボタンとPSボタンの同時長押しを行うと、コントローラのLEDが青色点滅となってペアリングが開始されます。ペアリングが開始されるとシーケンスが進み、その後接続完了まで進むとLEDが青色点灯となります。
接続完了するとPS4.connected()が真を返すようになります。
void loop() {
Usb.Task();
if (PS4.connected()) {
// 接続すると、この節に入る
}
}
繋がった後の処理は「if (PS4.connected()) {」の節に記述します。
スティックの傾きを取得する
以下の通りPS4コントローラのインスタンスPS4を作ります。
#include <PS4BT.h>
USB Usb;
BTD Btd(&Usb);
PS4BT PS4(&Btd, PAIR);
そしてメソッドgetAnalogHat()によって、スティックの傾き(0~255)が取得できます。
多少のブレはありますが、スティックのセンターは127です。
矢印の方向に値が大きくなるので、左=0、右=255、上=0、下=255となります。
uint8_t L_X = PS4.getAnalogHat(LeftHatX);
uint8_t L_Y = PS4.getAnalogHat(LeftHatY);
uint8_t R_X = PS4.getAnalogHat(RightHatX);
uint8_t R_Y = PS4.getAnalogHat(RightHatY);
左スティックの左右はLeftHatX、左スティックの上下はLeftHatY、右スティックの左右はRightHatX、右スティックの上下はRightHatY となります。
データの変換
ところで、スティックの傾きは256分割ですが、その精度を使う場面はあまり思いつかなかったので、15分割程度に丸めて使用することにしました。
ついでに、表示する際にわかりやすいので、0~255をA~Oの文字に変換します。
ただし、そのままmapで変換すると偏ってしまうので、センターより大きい値の時は補正します。
char newer_L_X = 'A' + map(128 < L_X ? --L_X : L_X,0,255,0,15);
char newer_L_Y = 'A' + map(128 < L_Y ? --L_Y : L_Y,0,255,0,15);
char newer_R_X = 'A' + map(128 < R_X ? --R_X : R_X,0,255,0,15);
char newer_R_Y = 'A' + map(128 < R_Y ? --R_Y : R_Y,0,255,0,15);
getAnalogHatの範囲 | 取りうる値の数 | 補正してmapで変換後の値 | 変換後の文字 |
---|---|---|---|
0-16 | 17 | 0 | A |
17-33 | 17 | 1 | B |
34-50 | 17 | 2 | C |
51-67 | 17 | 3 | D |
68-84 | 17 | 4 | E |
85-101 | 17 | 5 | F |
102-118 | 17 | 6 | G |
119-136 センター | 18 | 7 | H |
137-153 | 17 | 8 | I |
154-170 | 17 | 9 | J |
171-187 | 17 | 10 | K |
188-204 | 17 | 11 | L |
205-221 | 17 | 12 | M |
222-238 | 17 | 13 | N |
239-255 | 17 | 14 | O |
変換後の文字が変わらない間は処理しないようにします。
static char older_L_X = 'A';
static char older_L_Y = 'A';
static char older_R_X = 'A';
static char older_R_Y = 'A';
if(older_L_X != newer_L_X || older_L_Y != newer_L_Y || older_R_X != newer_R_X || older_R_Y != newer_R_Y){
older_L_X = newer_L_X;
older_L_Y = newer_L_Y;
older_R_X = newer_R_X;
older_R_Y = newer_R_Y;
i2c送信
二つ目のarduinoに送信します。
Wire.beginTransmission(8); //transmit to device #8
Wire.write(newer_L_X);
Wire.write(newer_L_Y);
Wire.write(newer_R_X);
Wire.write(newer_R_Y);
Wire.endTransmission(); //stop transmitting
送信するデータをシリアルにも出して、比較できるようにしておきます。
Serial.print(newer_L_X);
Serial.print(newer_L_Y);
Serial.print(newer_R_X);
Serial.print(newer_R_Y);
Serial.print(F("\r\n"));
i2c受信のスケッチ
二つ目のarduinoで受信します。
i2cスレーブのベースコードは[ファイル]-[スケッチ例]-[Wire]-[slave_receiver]を使用します。
修正後のスケッチはGitから取得してください。
i2cから受信する毎に4byteのデータをシリアルに出力する処理に変更しました。
#include <Wire.h>
char newer_L_X;
char newer_L_Y;
char newer_R_X;
char newer_R_Y;
void setup() {
Wire.begin(8); // join i2c bus with address #8
Wire.onReceive(receiveEvent); // register event
Serial.begin(115200);
}
void loop(){
//...
delay(100);
}
void receiveEvent(int howMany) {
String str = "";
while (Wire.available()) {
char c = Wire.read();
str += String(c);
Serial.print(c);
}
Serial.print(F("\r\n"));
newer_L_X = str.charAt(0);
newer_L_Y = str.charAt(1);
newer_R_X = str.charAt(2);
newer_R_Y = str.charAt(3);
}
i2cのつなぎ方
i2cは3本のPIN(GND,A4,A5)をそれぞれ繋ぎます。
左がi2c送信側、右がi2c受信側です。上手く受け取れたようです。
おわりに
今回はPS4コントローラを使用しましたが、PS3コントローラやXboxコントローラも、ヘッダファイルとクラスを変えれば、同様に使用できると思います。