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