ネジコンのUSB化
tl; dr
PCでネジコンが使いたい
ネジコンを手に入れたのでPCで使おうと手元のコンバーターを使ったが、Lトリガー(アナログ入力)を認識してくれない。 他のコンバーターを調べてみても、ネジコンに対応してないどころか終売になっているものだらけ。
Arduino互換機(Seeeduino XIAO
)を利用してコンバーターを作成する。
開発環境
名称 | バージョン |
---|---|
Seeed SAMD Boards | 1.8.2 |
PsxNewLib | 0.4.0 |
Adfruit TinyUSB Library | 0.10.5 |
ネジコン読み取り
とりあえずコネクターが欲しいので、Amazonで適当なPS2/3→USBコンバーターを購入。(約400円)
(もしかしてネジコン対応しているかなと試してみたが、やっぱり駄目だった)
はんだごてで取り外して、適当な基板に接続。使いやすいようにピンも付けた。
Seeeduino XIAO
と接続して入力を読み取る。ライブラリーはPsxNewLibを利用。(v0.3.0以上)
サンプルコードを参考に読み取り処理を実装。
読み取りコード
//#include <PsxControllerHwSpi.h> #include <PsxControllerBitBang.h> const byte PIN_PS2_DAT = 10; const byte PIN_PS2_CMD = 9; const byte PIN_PS2_CLK = 8; const byte PIN_PS2_ATT = 7; PsxControllerBitBang<PIN_PS2_ATT, PIN_PS2_CMD, PIN_PS2_DAT, PIN_PS2_CLK> psx; //PsxControllerHwSpi<PIN_PS2_ATT> psx; bool haveController = false; void printInput(PsxController *psx) { String str; auto protocol = psx->getProtocol(); switch(protocol) { //case PSPROTO_DIGITAL: //case PSPROTO_DUALSHOCK: //case PSPROTO_DUALSHOCK2: //case PSPROTO_FLIGHTSTICK: case PSPROTO_NEGCON: str = "NEGCON: "; str += (psx->buttonPressed(PSB_PAD_LEFT)) ? "←" : " "; str += (psx->buttonPressed(PSB_PAD_DOWN)) ? "↓" : " "; str += (psx->buttonPressed(PSB_PAD_UP)) ? "↑" : " "; str += (psx->buttonPressed(PSB_PAD_RIGHT)) ? "→" : " "; str += (psx->buttonPressed(PSB_TRIANGLE)) ? "△" : " "; str += (psx->buttonPressed(PSB_CIRCLE)) ? "○" : " "; str += (psx->buttonPressed(PSB_R1)) ? "R1" : " "; str += (psx->buttonPressed(PSB_START)) ? "ST" : " "; str += " L1:"; str += psx->getAnalogButton(PSAB_L1); str += " ×:"; str += psx->getAnalogButton(PSAB_CROSS); str += " □:"; str += psx->getAnalogButton(PSAB_SQUARE); byte lx, ly; psx->getLeftAnalog(lx, ly); str += " LX:"; str += lx; str += " LY:"; str += ly; break; //case PSPROTO_JOGCON: default: break; } Serial.println(str); } void setup() { Serial.begin(9600); Serial.println("setup"); } void loop() { if (!haveController) { if (psx.begin()) { Serial.println("Controller found!"); if (!psx.enterConfigMode ()) { Serial.println("Cannot enter config mode"); } else { // Try to enable analog sticks if (!psx.enableAnalogSticks ()) { Serial.println("Cannot enable analog sticks"); } if (!psx.exitConfigMode ()) { Serial.println("Cannot exit config mode"); } } haveController = true; } } else { if (!psx.read()) { Serial.println("controller lost."); haveController = false; } else { printInput(&psx); } } }
USB変換
XIAOをUSBデバイスとして扱うためにはTinyUSB
を利用するらしい。
設定から「USB Stack」を「TinyUSB」に変更。
しかし、公式のサンプルを試してもビルドが通らない。
cannot declare variable 'usb_hid' to be of abstract type 'Adfruit_USBD_HID'
調べると、どうもXIAOの場合はTinyUSBライブラリーが0.10.5までしか対応していないらしい。(2021年7月現在)
ただ、0.10.5を使用すると、ゲームパッド用のディスクリプターが色々と足りていないので、手動で追加してあげる必要がある。
こちらのページを参考に、最新版のコードからパーツを拝借。
GamePadの定義を追加
/* tinyusb.h */ /* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * This file is part of the TinyUSB stack. */ // https://github.com/adafruit/Adafruit_TinyUSB_Arduino/blob/e02de0e0e9e1bd6d46a9ed032048d22deb032ea4/src/class/hid/hid.h //--------------------------------------------------------------------+ // GAMEPAD //--------------------------------------------------------------------+ /// HID Gamepad Protocol Report. typedef struct TU_ATTR_PACKED { int8_t x; ///< Delta x movement of left analog-stick int8_t y; ///< Delta y movement of left analog-stick int8_t z; ///< Delta z movement of right analog-joystick int8_t rz; ///< Delta Rz movement of right analog-joystick int8_t rx; ///< Delta Rx movement of analog left trigger int8_t ry; ///< Delta Ry movement of analog right trigger uint8_t hat; ///< Buttons mask for currently pressed buttons in the DPad/hat uint32_t buttons; ///< Buttons mask for currently pressed buttons }hid_gamepad_report_t; /// Standard Gamepad Buttons Bitmap typedef enum { GAMEPAD_BUTTON_0 = TU_BIT(0), GAMEPAD_BUTTON_1 = TU_BIT(1), GAMEPAD_BUTTON_2 = TU_BIT(2), GAMEPAD_BUTTON_3 = TU_BIT(3), GAMEPAD_BUTTON_4 = TU_BIT(4), GAMEPAD_BUTTON_5 = TU_BIT(5), GAMEPAD_BUTTON_6 = TU_BIT(6), GAMEPAD_BUTTON_7 = TU_BIT(7), GAMEPAD_BUTTON_8 = TU_BIT(8), GAMEPAD_BUTTON_9 = TU_BIT(9), GAMEPAD_BUTTON_10 = TU_BIT(10), GAMEPAD_BUTTON_11 = TU_BIT(11), GAMEPAD_BUTTON_12 = TU_BIT(12), GAMEPAD_BUTTON_13 = TU_BIT(13), GAMEPAD_BUTTON_14 = TU_BIT(14), GAMEPAD_BUTTON_15 = TU_BIT(15), GAMEPAD_BUTTON_16 = TU_BIT(16), GAMEPAD_BUTTON_17 = TU_BIT(17), GAMEPAD_BUTTON_18 = TU_BIT(18), GAMEPAD_BUTTON_19 = TU_BIT(19), GAMEPAD_BUTTON_20 = TU_BIT(20), GAMEPAD_BUTTON_21 = TU_BIT(21), GAMEPAD_BUTTON_22 = TU_BIT(22), GAMEPAD_BUTTON_23 = TU_BIT(23), GAMEPAD_BUTTON_24 = TU_BIT(24), GAMEPAD_BUTTON_25 = TU_BIT(25), GAMEPAD_BUTTON_26 = TU_BIT(26), GAMEPAD_BUTTON_27 = TU_BIT(27), GAMEPAD_BUTTON_28 = TU_BIT(28), GAMEPAD_BUTTON_29 = TU_BIT(29), GAMEPAD_BUTTON_30 = TU_BIT(30), GAMEPAD_BUTTON_31 = TU_BIT(31), }hid_gamepad_button_bm_t; /// Standard Gamepad Buttons Naming from Linux input event codes /// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h #define GAMEPAD_BUTTON_A GAMEPAD_BUTTON_0 #define GAMEPAD_BUTTON_SOUTH GAMEPAD_BUTTON_0 #define GAMEPAD_BUTTON_B GAMEPAD_BUTTON_1 #define GAMEPAD_BUTTON_EAST GAMEPAD_BUTTON_1 #define GAMEPAD_BUTTON_C GAMEPAD_BUTTON_2 #define GAMEPAD_BUTTON_X GAMEPAD_BUTTON_3 #define GAMEPAD_BUTTON_NORTH GAMEPAD_BUTTON_3 #define GAMEPAD_BUTTON_Y GAMEPAD_BUTTON_4 #define GAMEPAD_BUTTON_WEST GAMEPAD_BUTTON_4 #define GAMEPAD_BUTTON_Z GAMEPAD_BUTTON_5 #define GAMEPAD_BUTTON_TL GAMEPAD_BUTTON_6 #define GAMEPAD_BUTTON_TR GAMEPAD_BUTTON_7 #define GAMEPAD_BUTTON_TL2 GAMEPAD_BUTTON_8 #define GAMEPAD_BUTTON_TR2 GAMEPAD_BUTTON_9 #define GAMEPAD_BUTTON_SELECT GAMEPAD_BUTTON_10 #define GAMEPAD_BUTTON_START GAMEPAD_BUTTON_11 #define GAMEPAD_BUTTON_MODE GAMEPAD_BUTTON_12 #define GAMEPAD_BUTTON_THUMBL GAMEPAD_BUTTON_13 #define GAMEPAD_BUTTON_THUMBR GAMEPAD_BUTTON_14 /// Standard Gamepad HAT/DPAD Buttons (from Linux input event codes) typedef enum { GAMEPAD_HAT_CENTERED = 0, ///< DPAD_CENTERED GAMEPAD_HAT_UP = 1, ///< DPAD_UP GAMEPAD_HAT_UP_RIGHT = 2, ///< DPAD_UP_RIGHT GAMEPAD_HAT_RIGHT = 3, ///< DPAD_RIGHT GAMEPAD_HAT_DOWN_RIGHT = 4, ///< DPAD_DOWN_RIGHT GAMEPAD_HAT_DOWN = 5, ///< DPAD_DOWN GAMEPAD_HAT_DOWN_LEFT = 6, ///< DPAD_DOWN_LEFT GAMEPAD_HAT_LEFT = 7, ///< DPAD_LEFT GAMEPAD_HAT_UP_LEFT = 8, ///< DPAD_UP_LEFT }hid_gamepad_hat_t; // https://github.com/adafruit/Adafruit_TinyUSB_Arduino/blob/3b4d46b717f554c66fa95638abd4c28c0e6f22d2/src/class/hid/hid_device.h // Gamepad Report Descriptor Template // with 16 buttons, 2 joysticks and 1 hat/dpad with following layout // | X | Y | Z | Rz | Rx | Ry (1 byte each) | hat/DPAD (1 byte) | Button Map (2 bytes) | #define TUD_HID_REPORT_DESC_GAMEPAD(...) \ HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_GAMEPAD ) ,\ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ /* Report ID if any */\ __VA_ARGS__ \ /* 8 bit X, Y, Z, Rz, Rx, Ry (min -127, max 127 ) */ \ HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_Z ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_RZ ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_RX ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_RY ) ,\ HID_LOGICAL_MIN ( 0x81 ) ,\ HID_LOGICAL_MAX ( 0x7f ) ,\ HID_REPORT_COUNT ( 6 ) ,\ HID_REPORT_SIZE ( 8 ) ,\ HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ /* 8 bit DPad/Hat Button Map */ \ HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_HAT_SWITCH ) ,\ HID_LOGICAL_MIN ( 1 ) ,\ HID_LOGICAL_MAX ( 8 ) ,\ HID_PHYSICAL_MIN ( 0 ) ,\ HID_PHYSICAL_MAX_N ( 315, 2 ) ,\ HID_REPORT_COUNT ( 1 ) ,\ HID_REPORT_SIZE ( 8 ) ,\ HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ /* 16 bit Button Map */ \ HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ HID_USAGE_MIN ( 1 ) ,\ HID_USAGE_MAX ( 32 ) ,\ HID_LOGICAL_MIN ( 0 ) ,\ HID_LOGICAL_MAX ( 1 ) ,\ HID_REPORT_COUNT ( 32 ) ,\ HID_REPORT_SIZE ( 1 ) ,\ HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ HID_COLLECTION_END \
あとは実行コードを書いてXIAOに書き込む。
スケッチファイル
//#include <PsxControllerHwSpi.h> #include <PsxControllerBitBang.h> #include <Adafruit_TinyUSB.h> // v0.10.x #include "tinyusb.h" // 十字キー(HATスイッチ)をボタン化したい場合はコメントアウト #define USE_GAMEPAD_HAT const byte PIN_PS2_DAT = 10; const byte PIN_PS2_CMD = 9; const byte PIN_PS2_CLK = 8; const byte PIN_PS2_ATT = 7; PsxControllerBitBang<PIN_PS2_ATT, PIN_PS2_CMD, PIN_PS2_DAT, PIN_PS2_CLK> psx; //PsxControllerHwSpi<PIN_PS2_ATT> psx; uint8_t const desc_hid_report[] = { TUD_HID_REPORT_DESC_GAMEPAD() }; Adafruit_USBD_HID usb_hid; hid_gamepad_report_t gp; bool haveController = false; void sendGamePadStatus(PsxController *psx) { auto protocol = psx->getProtocol(); switch(protocol) { //case PSPROTO_DIGITAL: //case PSPROTO_DUALSHOCK: //case PSPROTO_DUALSHOCK2: //case PSPROTO_FLIGHTSTICK: case PSPROTO_NEGCON: // set digital button if (psx->buttonPressed(PSB_CIRCLE)) { gp.buttons += GAMEPAD_BUTTON_0; } if (psx->buttonPressed(PSB_TRIANGLE)) { gp.buttons += GAMEPAD_BUTTON_1; } if (psx->buttonPressed(PSB_R1)) { gp.buttons += GAMEPAD_BUTTON_2; } if (psx->buttonPressed(PSB_START)) { gp.buttons += GAMEPAD_BUTTON_3; } // set hat #ifdef USE_GAMEPAD_HAT if (psx->buttonPressed(PSB_PAD_UP)) { gp.hat = GAMEPAD_HAT_UP; } if (psx->buttonPressed(PSB_PAD_RIGHT)) { gp.hat = GAMEPAD_HAT_RIGHT; } if (psx->buttonPressed(PSB_PAD_DOWN)) { gp.hat = GAMEPAD_HAT_DOWN; } if (psx->buttonPressed(PSB_PAD_LEFT)) { gp.hat = GAMEPAD_HAT_LEFT; } #else if (psx->buttonPressed(PSB_PAD_UP)) { gp.buttons += GAMEPAD_BUTTON_4; } if (psx->buttonPressed(PSB_PAD_RIGHT)) { gp.buttons += GAMEPAD_BUTTON_5; } if (psx->buttonPressed(PSB_PAD_DOWN)) { gp.buttons += GAMEPAD_BUTTON_6; } if (psx->buttonPressed(PSB_PAD_LEFT)) { gp.buttons += GAMEPAD_BUTTON_7; } #endif // set analog button byte lx, ly; psx->getLeftAnalog(lx, ly); gp.x = lx - 128; gp.y = ly - 128; gp.z = psx->getAnalogButton(PSAB_L1) - 128; gp.rx = psx->getAnalogButton(PSAB_CROSS) - 128; gp.ry = psx->getAnalogButton(PSAB_SQUARE) - 128; } usb_hid.sendReport(0, &gp, sizeof(gp)); } void resetGamePad() { gp.x = 0; gp.y = 0; gp.z = 0; gp.rx = 0; gp.ry = 0; gp.rz = 0; gp.hat = 0; gp.buttons = 0; } void setup() { Serial.begin(9600); Serial.println("setup"); usb_hid.setPollInterval(2); usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); usb_hid.begin(); while( !USBDevice.mounted() ) { delay(1); } } void loop() { if (!usb_hid.ready()) { return; } resetGamePad(); if (!haveController) { if (psx.begin()) { Serial.println("Controller found!"); if (!psx.enterConfigMode ()) { Serial.println("Cannot enter config mode"); } else { // Try to enable analog sticks if (!psx.enableAnalogSticks ()) { Serial.println("Cannot enable analog sticks"); } if (!psx.exitConfigMode ()) { Serial.println("Cannot exit config mode"); } } haveController = true; } } else { if (!psx.read()) { Serial.println("controller lost."); haveController = false; } else { sendGamePadStatus(&psx); } } }
動作確認
Win + R
からJOY.CPL
を起動して入力を確認。
(十字キーはボタンとして認識させている)
とりあえず完成
あとは基板作って、この中(↓)に収めたいが、それは気が向いたときにでも・・・