
自作 ハンコン(ESP32-S2/S3使用)
ESP32というマイコンボードが、arduinoより性能が高くて安いと知ったので、 あれこれと検索していたらハンドルコントローラをESP32で作った ページ があって、ダンボールを重ねてハンドルにしたり、とても楽しそうだったので作ってみました。
ESP32ボードをアルミケースに入れると、BluetoothでのPCとの接続状態が悪化するように思えたので、ESP32をUSB接続してジョイパッドとして動かせるのかを調べました。検索していたら、 こちらのページ が見つかりました。
ESP32-S2/S3であれば、ライブラリが揃っていました。
ここまでわかれば、なんとかなります。
ハンドル部の作成



木材と木材の接続はいつもは木工用ボンドを使うのですが、今回は試しに作ってみるだけなので木ネジを多用しています。





しばらくこの状態で遊んでいたら、机に固定する部分が ポッキリと折れてしまいました。木目と力の方向が合っていませんでした。


ESP32関係

ESP32-S3-DevKitCはAliexpressのこちらのショップから購入しました。
ロータリーエンコーダの電源は+5Vが必要です。しかしESP32-S3 devcitCは USBコネクタの右側の外から2ピン目に5Vの端子(上の写真の右下部)があるのですが、ここはデフォルトでは5Vが出ていないため、捺印12の上の IN-OUTのPADをショートさせる必要があります。PADのIN-OUT捺印側はレギュレータ AMS1117(BOOTスイッチの下の部品)の右側の端子(VINつまり5V)とつながっており、PADのUSBコネクタ側は、5V捺印の端子につながっています。IN-OUTのPADをショートさせることで、5V捺印の端子から取り出した5Vをロータリーエンコーダへの電源として供給できました。
ESP32のプログラムもこちらを参考にしました。
できたmain.cppを載せておきます。割り込み内での処理はフラグを設定するだけにして、mainの中でこのフラグを見てCWとCCWの処理を行っています。ハンドル以外にアクセルとブレーキもお試しで接続したときのコードなので、これらの部分は現物に合わせた補正計算が入っています。
#include <Joystick_ESP32S2.h>
#define ENCA GPIO_NUM_14
#define ENCB GPIO_NUM_13
#define AD1_3 GPIO_NUM_4
#define AD1_4 GPIO_NUM_5
#define SW1 GPIO_NUM_1
#define SW2 GPIO_NUM_2
#define SW3 GPIO_NUM_42
#define SW4 GPIO_NUM_41
#define encdelta 70
#define USECCOUNT 10000
#define J_BUTTON 4
#define J_HATSW 0
#define J_XAXIS true
#define J_YAXIS false
#define J_ZAXIS false
#define J_RXAXIS true
#define J_RYAXIS true
#define J_RZAXIS false
#define J_THROTTLE false
#define J_RUDDER false
#define J_ACCEL false
#define J_BRAKE false
#define J_STEERING false
volatile int Enc = 0;
volatile int SW1_changed = 0;
volatile int SW2_changed = 0;
volatile int SW3_changed = 0;
volatile int SW4_changed = 0;
volatile int Timerflag = 0;
int encdata = 0;
hw_timer_t * tm = NULL;
// Create Joystick
Joystick_ Joystick(
JOYSTICK_DEFAULT_REPORT_ID,
JOYSTICK_TYPE_GAMEPAD,
J_BUTTON, J_HATSW,
J_XAXIS, J_YAXIS, J_ZAXIS, J_RXAXIS, J_RYAXIS, J_RZAXIS,
J_RUDDER, J_THROTTLE, J_ACCEL, J_BRAKE, J_STEERING );
//--------------------------------------INTERRUPT
void IRAM_ATTR INT_ENCODER() {
if (digitalRead(ENCA) == 0) {
if (digitalRead(ENCB) == 0) {
Enc = 1;
}
else {
Enc = -1;
}
}
}
void IRAM_ATTR INT_SW1() {
SW1_changed = 1;
}
void IRAM_ATTR INT_SW2() {
SW2_changed = 1;
}
void IRAM_ATTR INT_SW3() {
SW3_changed = 1;
}
void IRAM_ATTR INT_SW4() {
SW4_changed = 1;
}
#if 1
void IRAM_ATTR INT_TIMER() {
Timerflag=1;
}
#endif
void setup() {
// USB
USB.PID(0x8211);
USB.VID(0x303b);
USB.productName("ESP32-USBPAD");
USB.manufacturerName("ky01");
USB.begin();
// wheel LeftThumb X
Joystick.setXAxisRange(-32767, 32767);
Joystick.setXAxis(0);
// accele LeftTrigger
Joystick.setRxAxisRange(0, 32767);
Joystick.setRxAxis(0);
// break RightTrigger
Joystick.setRyAxisRange(0, 32767);
Joystick.setRyAxis(0);
Joystick.begin();
// 入力ピン設定
pinMode(ENCA, INPUT_PULLUP);
pinMode(ENCB, INPUT_PULLUP);
pinMode(SW1, INPUT_PULLUP);
pinMode(SW2, INPUT_PULLUP);
pinMode(SW3, INPUT_PULLUP);
pinMode(SW4, INPUT_PULLUP);
pinMode(AD1_3, ANALOG);
pinMode(AD1_4, ANALOG);
// ロータリーエンコーダー割り込み設定
attachInterrupt(ENCA, INT_ENCODER, FALLING);
// GPIO割り込み設定
attachInterrupt(SW1, INT_SW1, CHANGE);
attachInterrupt(SW2, INT_SW2, CHANGE);
attachInterrupt(SW3, INT_SW3, CHANGE);
attachInterrupt(SW4, INT_SW4, CHANGE);
// Timer割り込み設定
tm = timerBegin(0,80,true); //80MHz / 80 -> 1usec
timerAttachInterrupt(tm, INT_TIMER, true);
timerAlarmWrite(tm, USECCOUNT, true); // 1usec x 10000 -> 10msec
timerAlarmEnable(tm);
// ADCの解像度を12bit(0~4095)に設定
analogSetAttenuation(ADC_11db); // ATT -11dB
Serial.begin(115200);
}
//----------------------------------------------
void loop() {
int16_t accelarator;
int16_t breaking;
int16_t numave=3;
long ad13Millivolt = 0;
long ad14Millivolt = 0;
long adave[numave];
//-----------------rotally encoder
if (Enc == 1) {
if (encdata > -32767 + encdelta) {
encdata -= encdelta;
Joystick.setXAxis(encdata);
}
Enc = 0;
}
if (Enc == -1) {
if (encdata < 32767 - encdelta) {
encdata += encdelta;
Joystick.setXAxis(encdata);
}
Enc = 0;
}
//-------------------- timer
if ( Timerflag == 1) {
//-----------------accel
ad13Millivolt = analogReadMilliVolts(AD1_3);
// 2270 500
if( ad13Millivolt > 2200 ) ad13Millivolt=2200;
accelarator = (int)(32767- (ad13Millivolt - 500) * 32767 / (2200 - 500)); // 3.3V PU A/D ValR GND
accelarator = int(accelarator / 300) * 300;
if( accelarator > 32767 ) accelarator = 32767;
if( accelarator < 0 ) accelarator = 32767;
Joystick.setRxAxis(accelarator);
//-----------------break
ad14Millivolt = analogReadMilliVolts(AD1_4);
// 3114 60
breaking = (int)(32767 - (ad14Millivolt - 50) * 32767 / (3114 - 50));
if( breaking > 32767 ) breaking = 32767;
Joystick.setRyAxis(breaking);
Timerflag = 0;
}
//-------------------------------SW1
if (SW1_changed == 1) {
if (digitalRead(SW1) == 0) {
Joystick.pressButton(0);
}
else {
Joystick.releaseButton(0);
}
SW1_changed=0;
}
//-------------------------------SW2
if (SW2_changed == 1) {
if (digitalRead(SW2) == 0) {
Joystick.pressButton(1);
}
else {
Joystick.releaseButton(1);
}
SW2_changed=0;
}
//-------------------------------SW3
if (SW3_changed == 1) {
if (digitalRead(SW3) == 0) {
Joystick.pressButton(2);
Joystick.setXAxis(0);
}
else {
Joystick.releaseButton(2);
}
SW3_changed=0;
}
//-------------------------------SW4
if (SW4_changed == 1) {
if (digitalRead(SW4) == 0) {
Joystick.pressButton(3);
}
else {
Joystick.releaseButton(3);
}
SW4_changed=0;
}
} // end of loop()
platformio.iniの中身はこのようにしました。
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
lib_deps = schnoog/Joystick_ESP32S2@^0.9.4
Windows上のgamepadのプロパティを見ながらハンドルを回すと目印が動くのですが、なんか遅い。ふと見ると、ハンドルを回すとVSCodeのログ画面に大量のメッセージが出てました。調べていくと、USBHID.cppの中にメッセージを出力するコードがありました。
USBHID.cppは
.platformio\packages\framework-arduinoespressif32\libraries\USB\src
にありました。
USBHID.cppの一部を引用。345~363行目の部分です。
if(!res){
log_e("not ready");
} else {
// The semaphore may be given if the last SendReport() timed out waiting for the report to
// be sent. Or, tud_hid_report_complete_cb() may be called an extra time, causing the
// semaphore to be given. In these cases, take the semaphore to clear its state so that
// we can wait for it to be given after calling tud_hid_n_report().
xSemaphoreTake(tinyusb_hid_device_input_sem, 0);
res = tud_hid_n_report(0, id, data, len);
if(!res){
log_e("report %u failed", id);
} else {
if(xSemaphoreTake(tinyusb_hid_device_input_sem, timeout_ms / portTICK_PERIOD_MS) != pdTRUE){
log_e("report %u wait failed", id);
res = false;
}
}
}
上記の中でメッセージを出力しているのは、
log_e("report %u wait failed", id);
の部分だったので、メッセージを出さないようにコメントアウトしたところ、メッセージは出なくなってgamepadのデバイスのプロパティの目印の動作速度が爆速になりました。何かしらの良くないことが起きているのかもしれませんが、コードの深読みができないので根本原因の解決は放置です。
もしかしたら私の環境だけで発生していたのかもしれませんが、何かしらの参考にでもなればと掲載しました。
ケース

使用感
このハンコンでRush Rallyを遊んでみましたが、実に楽しいです。試作のアクセルとブレーキは足での操作に耐えられずに壊れてしまったので遊べた期間は数日だったのですが、微妙に曲がっている高速ストレートもぎりぎりのイン側を攻められるし、タイトなカーブの出口での逆ハンもスムーズにできて、各コースで良いタイムを出せました。FFBはついて無いのでくるくる回るだけのハンドルですが、市販のハンコンでなくても十分に楽しめました。