ESP32でDS4を使う方法(SPI Masterでデータ送信)

# ESP32でDualShock4を使いたい理由
自作ロボであったり、ラジコンを作る際にコントローラをどうするかというのは、めんどくさい問題であると思っている。
ZigbeeやTwelight等の無線マイコンをつかうのもいいが、コントローラの回路設計はめんどいし耐久性も低くなる。
したがって、既存のコントローラをいい感じに使いたいなと思った。

# 開発環境
|ツール|ツール名|
|---|---|
|IDE|Arduino IDE 2.2.1|
|開発ボード|ESP32-WROOM-32Eマイコンボード K-16108|
|コントローラ|DualShock4|
|USB-シリアル変換ボード|CH340E USBシリアル変換モジュール Type-C K-14745|
|Bluetooth MACアドレス書き換えソフト|SixaxisPairTool 0.3.1|
|ライブラリ|https://github.com/aed3/PS4-esp32|
|OS|windows 11|

# ESP32とDS4をつなぐ

## ESP32のMACアドレスを取得する

下記コードを実行することでMACアドレスの確認が可能

```c
void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(115200);

  uint8_t ESP_bt_mac[6] = {0};
  
  esp_read_mac(ESP_bt_mac,ESP_MAC_BT);
  
  Serial.printf("ESP_MAC_BT = %x:%x:%x:%x:%x:%x\n",
  ESP_bt_mac[0],
  ESP_bt_mac[1],
  ESP_bt_mac[2],
  ESP_bt_mac[3],
  ESP_bt_mac[4],
  ESP_bt_mac[5]); //esp32のマックアドレスがシリアルモニタ返される

}

```
確認したMACアドレスをメモ帳等にコピっておく。

## DS4のMACアドレスを書き換える
MACアドレスの書き換えツールである[SixaxisPairTool 0.3.1]をダウンロードする。これ以前のバージョンでは、当環境だと動作しなかった。
ソフトを実行し、[change master]の項に先ほど、確認したESP32のMACアドレスを入力する。その後、[update]を押すことで、DS4のMACアドレスが変更される。

## 接続確認
下記コードを実行し、DS4のPSボタンを押すとESP32とペアリングがされる。その後、シリアルモニタにアナログスティック、各スイッチの状態が表示される。

```c
#include <PS4Controller.h>
#include <stdio.h>

typedef union{
			  int8_t signed_data;
		    uint8_t unsigned_data;
}analog_data; //符号付き、符号なしを同様に扱うための共用体

typedef union{
  struct{
      uint8_t bit_0 : 1; //right
      uint8_t bit_1 : 1; //down
      uint8_t bit_2 : 1; //up
      uint8_t bit_3 : 1; //left
      uint8_t bit_4 : 1; //square
      uint8_t bit_5 : 1; //Cross
      uint8_t bit_6 : 1; //Circle
      uint8_t bit_7 : 1; //Triangle
      uint8_t bit_8 : 1; //L1
      uint8_t bit_9 : 1; //R1
      uint8_t bit_A : 1; //L3
      uint8_t bit_B : 1; //R3
      uint8_t bit_C : 1; //Share
      uint8_t bit_D : 1; //Options
      uint8_t bit_E : 1; //none
      uint8_t bit_F : 1; //none
  };
	uint32_t all_data : 16;
}digital_data; //スイッチの数は14個なので14bitsで状態を表す

void setup() {
  // put your setup code here, to run once:

  /*
  ESPRESSIF社公式より
  Misxellaneous System APIs
  https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/misc_system_api.html#_CPPv414esp_mac_type_t
  */
  
  Serial.begin(115200);

  uint8_t ESP_bt_mac[6] = {0};
  char DS4_bt_mac[30];
  
  esp_read_mac(ESP_bt_mac,ESP_MAC_BT);
  
  /*
  Serial.printf("ESP_MAC_BT = %x:%x:%x:%x:%x:%x\n",
  ESP_bt_mac[0],
  ESP_bt_mac[1],
  ESP_bt_mac[2],
  ESP_bt_mac[3],
  ESP_bt_mac[4],
  ESP_bt_mac[5]); //esp32のマックアドレスがシリアルモニタ返される
  */

  sprintf(DS4_bt_mac,"%x:%x:%x:%x:%x:%x",
  ESP_bt_mac[0],
  ESP_bt_mac[1],
  ESP_bt_mac[2],
  ESP_bt_mac[3],
  ESP_bt_mac[4],
  ESP_bt_mac[5]); //esp32のマックアドレスを文字列に変換してる。

  PS4.begin(DS4_bt_mac);

}

void loop() {
  // put your main code here, to run repeatedly:

  digital_data DS4_switch_stat;
  analog_data DS4_analog_stat[6];

  DS4_switch_stat.all_data = 0;

  if (PS4.isConnected()) {

    DS4_switch_stat.bit_0 = PS4.Right();
    DS4_switch_stat.bit_1 = PS4.Down();
    DS4_switch_stat.bit_2 = PS4.Up();
    DS4_switch_stat.bit_3 = PS4.Left();

    DS4_switch_stat.bit_4 = PS4.Square();
    DS4_switch_stat.bit_5 = PS4.Cross();
    DS4_switch_stat.bit_6 = PS4.Circle();
    DS4_switch_stat.bit_7 = PS4.Triangle();

    DS4_switch_stat.bit_8 = PS4.L1();
    DS4_switch_stat.bit_9 = PS4.R1();

    DS4_switch_stat.bit_A = PS4.L3();
    DS4_switch_stat.bit_B = PS4.R3();

    DS4_switch_stat.bit_C = PS4.Share();
    DS4_switch_stat.bit_D = PS4.Options();

    DS4_analog_stat[0].unsigned_data = PS4.L2Value(); //符号なし1Byte
    DS4_analog_stat[1].unsigned_data = PS4.R2Value(); //符号なし1Byte
    DS4_analog_stat[2].signed_data = PS4.LStickX(); //符号あり1Byte
    DS4_analog_stat[3].signed_data = PS4.LStickY(); //符号あり1Byte
    DS4_analog_stat[4].signed_data = PS4.RStickX(); //符号あり1Byte
    DS4_analog_stat[5].signed_data = PS4.RStickY(); //符号あり1Byte

    Serial.printf("DS4_Switch_Status=%ld\n", DS4_switch_stat.all_data);
    
    Serial.printf(
      "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
      DS4_switch_stat.bit_0,
      DS4_switch_stat.bit_1,
      DS4_switch_stat.bit_2,
      DS4_switch_stat.bit_3,
      DS4_switch_stat.bit_4,
      DS4_switch_stat.bit_5,
      DS4_switch_stat.bit_6,
      DS4_switch_stat.bit_7,
      DS4_switch_stat.bit_8,
      DS4_switch_stat.bit_9,
      DS4_switch_stat.bit_A,
      DS4_switch_stat.bit_B,
      DS4_switch_stat.bit_C,
      DS4_switch_stat.bit_D
      );

    Serial.printf(
      "L2=%d\n,R2=%d\n,Lx=%d\n,Ly=%d\n,Rx=%d\n,Ry=%d\n",
      DS4_analog_stat[0].unsigned_data,
      DS4_analog_stat[1].unsigned_data,
      DS4_analog_stat[2].signed_data,
      DS4_analog_stat[3].signed_data,
      DS4_analog_stat[4].signed_data,
      DS4_analog_stat[5].signed_data
      );

    Serial.println(); //区切り
    // This delay is to make the output more human readable
    // Remove it when you're not trying to see the output
    delay(1000);
  }

}
```

別のマイコンにSPI等のシリアル通信で状態を送ることを想定しているため、共用体で符号付き、符号なしを同様に扱えるようにしている。また、一つの変数でスイッチの状態を表せるように、共用体とビットフィールドを用いている。

# ESP32からSPI通信でDS4のデータを送る
ESP32をSPI Masterとして、MCUと通信を行いDS4のデータを送信するプログラムを作成した。
コード内で`#define debug_mode`を宣言することでデバッグモードになり、１秒毎シリアルモニタ上にDS4の状態が表示される。

https://github.com/hayate2718/ESP32_DS4

## 仕様

|ESP32|SPI_Master|
|---|---|
|||
|pin_num|func|
|12|GPIO_MOSI|
|13|GPIO_MISO|
|15|GPIO_SCLK|
|14|GPIO_CS|

|||
|---|---|
|通信速度|1Mbps(1MHz)|
|Data_size|64bits|


|DS4_stat|bit_num|
|---|---|
|十字キー右|bit0|
|十字キー下|bit1|
|十字キー上|bit2|
|十字キー左|bit3|
|四角|bit4|
|バツ|bit5|
|マル|bit6|
|三角|bit7|
|L1|bit8|
|R1|bit9|
|L3|bit10|
|R3|bit11|
|Share|bit12|
|Option|bit13|
|None(Read = 0)|bit14|
|None(Read = 0)|bit15|
|L2(unsigned)|bit16 ~ bit23|
|R2(unsigned)|bit24 ~ bit31|
|L_Stick_X(signed)|bit32 ~ bit39|
|L_Stick_Y(signed)|bit40 ~ bit47|
|R_Stick_X(signed)|bit48 ~ bit55|
|R_Stick_Y(signed)|bit56 ~ bit63|


## プログラム
```c++
 /*
  ESPRESSIF社公式より
  Misxellaneous System APIs
  https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/misc_system_api.html#_CPPv414esp_mac_type_t
  */

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_log.h"
#include "driver/spi_slave.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include <Arduino.h>
#include <SPI.h>
#include <PS4Controller.h>
#include <stdio.h>

//define

#define GPIO_MOSI           12
#define GPIO_MISO           13
#define GPIO_SCLK           15
#define GPIO_CS             14

#define GPIO_MOSI GPIO_NUM_12
#define GPIO_MISO GPIO_NUM_13
#define GPIO_SCLK GPIO_NUM_15
#define GPIO_CS GPIO_NUM_14

//global variables declaration

uint8_t txdata[8] = {0};

spi_device_handle_t handle;

typedef union{
			  int8_t signed_data;
		    uint8_t unsigned_data;
}analog_data; //符号付き、符号なしを同様に扱うための共用体

typedef union{
  struct{
      uint8_t bit_0 : 1; //right
      uint8_t bit_1 : 1; //down
      uint8_t bit_2 : 1; //up
      uint8_t bit_3 : 1; //left
      uint8_t bit_4 : 1; //square
      uint8_t bit_5 : 1; //Cross
      uint8_t bit_6 : 1; //Circle
      uint8_t bit_7 : 1; //Triangle
      uint8_t bit_8 : 1; //L1
      uint8_t bit_9 : 1; //R1
      uint8_t bit_A : 1; //L3
      uint8_t bit_B : 1; //R3
      uint8_t bit_C : 1; //Share
      uint8_t bit_D : 1; //Options
      uint8_t bit_E : 1; //none
      uint8_t bit_F : 1; //none
  };
	uint16_t all_data : 16;
}digital_data; //スイッチの数は14個なので14bitsで状態を表す

typedef union{
  struct{
      uint8_t bit_0 : 1;
      uint8_t bit_1 : 1;
      uint8_t bit_2 : 1;
      uint8_t bit_3 : 1;
      uint8_t bit_4 : 1;
      uint8_t bit_5 : 1;
      uint8_t bit_6 : 1;
      uint8_t bit_7 : 1;
  };
	uint8_t all_data : 8;
}data_8bits;

//function declaration

void SPI_Send_Data(void* _txdata,uint8_t length){ //SPI send data function
  spi_transaction_t t;
  spi_transaction_t* _t;

  memset(&t,0,sizeof(t));

  t.length = 8 * length; //length specify Byte unit (max 255 Bytes)
  t.tx_buffer = _txdata;

  assert(spi_device_queue_trans(handle, &t,portMAX_DELAY) == ESP_OK);
  assert(spi_device_get_trans_result(handle, &_t, portMAX_DELAY) == ESP_OK);

}

void DS4_Recive_Data(void * _Data ){//64bits以上のメモリが必要
  digital_data DS4_switch_stat;
  analog_data DS4_analog_stat[6];

  DS4_switch_stat.all_data = 0;

  if (PS4.isConnected()) {

    DS4_switch_stat.bit_0 = PS4.Right();
    DS4_switch_stat.bit_1 = PS4.Down();
    DS4_switch_stat.bit_2 = PS4.Up();
    DS4_switch_stat.bit_3 = PS4.Left();

    DS4_switch_stat.bit_4 = PS4.Square();
    DS4_switch_stat.bit_5 = PS4.Cross();
    DS4_switch_stat.bit_6 = PS4.Circle();
    DS4_switch_stat.bit_7 = PS4.Triangle();

    DS4_switch_stat.bit_8 = PS4.L1();
    DS4_switch_stat.bit_9 = PS4.R1();

    DS4_switch_stat.bit_A = PS4.L3();
    DS4_switch_stat.bit_B = PS4.R3();

    DS4_switch_stat.bit_C = PS4.Share();
    DS4_switch_stat.bit_D = PS4.Options();

    DS4_analog_stat[0].unsigned_data = PS4.L2Value(); //符号なし1Byte
    DS4_analog_stat[1].unsigned_data = PS4.R2Value(); //符号なし1Byte
    DS4_analog_stat[2].signed_data = PS4.LStickX(); //符号あり1Byte
    DS4_analog_stat[3].signed_data = PS4.LStickY(); //符号あり1Byte
    DS4_analog_stat[4].signed_data = PS4.RStickX(); //符号あり1Byte
    DS4_analog_stat[5].signed_data = PS4.RStickY(); //符号あり1Byte

    memcpy(_Data,&DS4_switch_stat,sizeof(DS4_switch_stat));
    memcpy((uint8_t*)_Data+sizeof(DS4_switch_stat),&DS4_analog_stat,sizeof(DS4_analog_stat));

  }

}

// setup function

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  
  uint8_t ESP_bt_mac[6] = {0};
  char DS4_bt_mac[30];

  esp_read_mac(ESP_bt_mac,ESP_MAC_BT);

  Serial.printf("ESP_MAC_BT = %x:%x:%x:%x:%x:%x\n",
  ESP_bt_mac[0],
  ESP_bt_mac[1],
  ESP_bt_mac[2],
  ESP_bt_mac[3],
  ESP_bt_mac[4],
  ESP_bt_mac[5]); //esp32のマックアドレスがシリアルモニタ返される

  sprintf(DS4_bt_mac,"%x:%x:%x:%x:%x:%x",
  ESP_bt_mac[0],
  ESP_bt_mac[1],
  ESP_bt_mac[2],
  ESP_bt_mac[3],
  ESP_bt_mac[4],
  ESP_bt_mac[5]); //esp32のマックアドレスを文字列に変換してる。

  PS4.begin(DS4_bt_mac);
  
  int ret = 0; //診断用バッファ
  

  spi_bus_config_t buscfg = {
        .mosi_io_num = GPIO_MOSI,
        .miso_io_num = GPIO_MISO,
        .sclk_io_num = GPIO_SCLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };

    spi_device_interface_config_t spicfg = {
      .command_bits = 0,
      .address_bits = 0,
      .dummy_bits = 0,
      .mode = 0,
      .duty_cycle_pos = 128,
      .clock_speed_hz = 1000000,
      .input_delay_ns = 0,
      .spics_io_num = GPIO_CS,
      .queue_size = 3,
    };

    buscfg.max_transfer_sz = 128;

    ret = spi_bus_initialize(SPI2_HOST, &buscfg, 1);
    assert(ret == ESP_OK);
    ret = spi_bus_add_device(SPI2_HOST, &spicfg,&handle);
    assert(ret == ESP_OK);

    Serial.printf("DS4_Ready\n");

}

//main routine

void loop() {
  // put your main code here, to run repeatedly:

  uint8_t flag = 0;

    while(PS4.isConnected()){

      if(!flag){
        Serial.printf("DS4_Start\n");
        flag = 1;
      }

      DS4_Recive_Data(txdata);
      SPI_Send_Data(txdata,sizeof(txdata));

      //#define debug_mode

      #ifdef debug_mode

      data_8bits switch_stat[2];

      switch_stat[0].all_data=txdata[0];
      switch_stat[1].all_data=txdata[1];

      Serial.printf("txdata size = %d\n", sizeof(txdata));

      Serial.printf("right=%d\n,down=%d\n,up=%d\n,left=%d\n,square=%d\n,closs=%d\n,circle=%d\n,triangle=%d\n,L1=%d\n,r1=%d\n,L3=%d\n,r3=%d\n,share=%d\n,option=%d\n,none=%d\n,none=%d\n"
      ,switch_stat[0].bit_0
      ,switch_stat[0].bit_1
      ,switch_stat[0].bit_2
      ,switch_stat[0].bit_3
      ,switch_stat[0].bit_4
      ,switch_stat[0].bit_5
      ,switch_stat[0].bit_6
      ,switch_stat[1].bit_7
      ,switch_stat[1].bit_0
      ,switch_stat[1].bit_1
      ,switch_stat[1].bit_2
      ,switch_stat[1].bit_3
      ,switch_stat[1].bit_4
      ,switch_stat[1].bit_5
      ,switch_stat[1].bit_6
      ,switch_stat[1].bit_7);

      Serial.printf("L2=%d\n,R2=%d\n,Lx=%d\n,Ly=%d\n,Rx=%d\n,Ry=%d\n",
      txdata[2],
      txdata[3],
      (signed char)txdata[4],
      (signed char)txdata[5],
      (signed char)txdata[6],
      (signed char)txdata[7]
      );

      delay(1000);

      #else

      delay(20);

      #endif

    }
}
```
# 参考文献
[ESP32を使ってDualshock4とBluetooth接続をする。ラジコンづくり①　準備編](https://ameblo.jp/mitsuhide-19960828/entry-12774324925.html)

[githubリポジトリ PS4-esp32](https://github.com/aed3/PS4-esp32)

[ESPRESSIF社公式 SPI Master Driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html)

[githubリポジトリ spi_slave/sender](https://github.com/espressif/esp-idf/blob/cb66d6f6faca7091bd8cc5578f54628aa88054fc/examples/peripherals/spi_slave/sender/main/app_main.c)