0
0

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

Last updated at Posted at 2023-12-09

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アドレスの確認が可能

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とペアリングがされる。その後、シリアルモニタにアナログスティック、各スイッチの状態が表示される。

#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を宣言することでデバッグモードになり、1秒毎シリアルモニタ上に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

プログラム

 /*
  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接続をする。ラジコンづくり① 準備編

githubリポジトリ PS4-esp32

ESPRESSIF社公式 SPI Master Driver

githubリポジトリ spi_slave/sender

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0