PS4コントローラのスティックでarduinoを制御する

# 目的
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 基本情報まとめ](https://qiita.com/NaoyaOura/items/1081884068fe3ea79570)を参考にセットアップしました。

arduinoIDEをダウンロードしてセットアップする場合は、https://www.arduino.cc/ にアクセスしてメニューから[SOFTWARE]を選び、好きなファイル形式を選びます。
![arduinoide.png](https://qiita-image-store.s3.amazonaws.com/0/201344/031cfa3f-0eb7-21cc-9cff-d20564afe1bc.png)

[Just Download]をクリック※気に入ったら寄付も歓迎とのこと
![arduinoidedownload.png](https://qiita-image-store.s3.amazonaws.com/0/201344/61af6034-8d07-e5d6-d11c-bc679ff55147.png)

USB Host sheild 2.0は、[Gitページ](https://github.com/felis/USB_Host_Shield_2.0)にアクセスして、[Clone or Download]-[Download]をクリックしてダウンロードします。![usbhostsheild2zip.png](https://qiita-image-store.s3.amazonaws.com/0/201344/09a80ddb-48ab-2bb4-d5cf-31605a7a82fe.png)

次に[スケッチ]-[ライブラリをインクルード]-[.ZIP形式のライブラリをインストール]を選んで、先ほどDownloadしたUSB Host sheild 2.0のzipファイルを選ぶとライブラリに組み込まれます。
![arduinozip.png](https://qiita-image-store.s3.amazonaws.com/0/201344/72e72ed3-9b8c-69f7-ca46-af8a501d7c64.png)


# 関連リンク
### 購入サイト
 - [amazon サインスマート(SainSmart) USBホストシールド 2.0](https://www.amazon.co.jp/SainSmart-USB%E3%83%9B%E3%82%B9%E3%83%88%E3%82%B7%E3%83%BC%E3%83%AB%E3%83%89-Mega2560-Duemilanove-ADK%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%A7%E3%81%8D/dp/B006J4G000/ref=pd_sbs_328_7?_encoding=UTF8&pd_rd_i=B006J4G000&pd_rd_r=a667f9c3-c20f-11e8-a931-fb5aba217938&pd_rd_w=GAVvQ&pd_rd_wg=ktDEe&pf_rd_i=desktop-dp-sims&pf_rd_m=AN1VRQENFRJN5&pf_rd_p=cda7018a-662b-401f-9c16-bd4ec317039e&pf_rd_r=Z43A4GGV8F0Q4R7ZKDKX&pf_rd_s=desktop-dp-sims&pf_rd_t=40701&psc=1&refRID=Z43A4GGV8F0Q4R7ZKDKX)
 - [amazon サインスマート UNO R3 ATmega328P 互換ボード](https://www.amazon.co.jp/%E3%82%B5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88-ATmega328P-%E4%BA%92%E6%8F%9B%E3%83%9C%E3%83%BC%E3%83%89-USB%E3%82%B1%E3%83%BC%E3%83%96%E3%83%AB%EF%BC%88Arduino-%E3%81%AB%E5%AF%BE%E5%BF%9C%EF%BC%89%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%8A%E3%83%AB%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3/dp/B00E5WJSHK/ref=sr_1_2?s=industrial&ie=UTF8&qid=1538023628&sr=1-2&keywords=arduino+uno+%E3%82%B5%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88)
 - [amazon BLEドングル BT-Micro4](https://www.amazon.co.jp/dp/B0071TE1G2/ref=as_li_ss_tl?ie=UTF8&linkCode=sl1&tag=deko0f-22&linkId=5aa88893490e5fc87615aaf241a3f509)

※サインスマートのUSBホストシールドは高いですがハンダする必要が無いのでおすすめです。

### ドライバ(arduinoUno互換ボードのUSBシリアルドライバ)
 - [Sparkfun CH340](https://cdn.sparkfun.com/assets/learn_tutorials/5/9/7/Windows-CH340-Driver.zip)

### ドキュメント
 - [USB Host sheildライブラリ](http://felis.github.io/USB_Host_Shield_2.0/)
 - [Arduino 日本語リファレンス](http://www.musashinodenpa.com/arduino/ref/)

### USB Host sheild参考
 - [DEKOのあやしいお部屋。](https://ht-deko.com/arduino/shield_usbhost.html)

# PS4コントローラのスケッチ
PS4コントローラとarduinoをBLEでつなぐ場合は、
[ファイル]-[スケッチ例]-[USB Host Shield Library 2.0]-[Bluetooth]-[PS4BT]を使用します。
![psblesample2.png](https://qiita-image-store.s3.amazonaws.com/0/201344/ab25b756-2721-2b82-01c6-bbbdd588ae7a.png)
修正後のスケッチは[Git](https://github.com/igapon50/arduino-ESP-THETA/blob/master/Hands-on/3rd_period/sample01/PS4BT/PS4BT.ino)から取得してください。

# コードの説明
## 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](http://felis.github.io/USB_Host_Shield_2.0/class_p_s4_parser.html#a9b11c5b27f9c9501fdb4f616cb3b194c)()によって、スティックの傾き(0~255)が取得できます。
多少のブレはありますが、スティックのセンターは127です。
![ps4stick.jpg](https://qiita-image-store.s3.amazonaws.com/0/201344/63f854a6-863e-660c-4c35-123316b71eea.jpeg)
矢印の方向に値が大きくなるので、左=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](http://felis.github.io/USB_Host_Shield_2.0/class_p_s4_parser.html#a9b11c5b27f9c9501fdb4f616cb3b194c)の範囲 | 取りうる値の数  |   補正して[map](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2743)で変換後の値 |   変換後の文字  |
|:----------------|:------:|:------------:|:------------:|
| 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]を使用します。
![slave_receiversample.png](https://qiita-image-store.s3.amazonaws.com/0/201344/a33f0e97-a434-3b36-b73a-78fae885f938.png)
修正後のスケッチは[Git](https://github.com/igapon50/arduino-ESP-THETA/blob/master/Hands-on/3rd_period/sample02/slave_receiver/slave_receiver.ino)から取得してください。
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)をそれぞれ繋ぎます。
![IMG_E1722.JPG](https://qiita-image-store.s3.amazonaws.com/0/201344/63a8a4ac-e5cc-1ff0-49d3-6133797fc660.jpeg)
左がi2c送信側、右がi2c受信側です。上手く受け取れたようです。
![psbletera.png](https://qiita-image-store.s3.amazonaws.com/0/201344/64786771-56ae-b433-6bde-d195ca9c5fff.png)

# おわりに
今回はPS4コントローラを使用しましたが、PS3コントローラやXboxコントローラも、ヘッダファイルとクラスを変えれば、同様に使用できると思います。