tl; dr
- ネジコンをSeeeduino XIAOでゲームパッド化
- TinyUSBライブラリーは、バージョン0.10.x
- 出来上がったものがこちらになります
ネジコンを手に入れたので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 <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;
bool haveController = false;
void printInput(PsxController *psx)
{
String str;
auto protocol = psx->getProtocol();
switch(protocol)
{
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;
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
{
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 <PsxControllerBitBang.h>
#include <Adafruit_TinyUSB.h>
#include "tinyusb.h"
#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;
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_NEGCON:
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;
}
#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
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
{
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
を起動して入力を確認。
(十字キーはボタンとして認識させている)
とりあえず完成
github.com
あとは基板作って、この中(↓)に収めたいが、それは気が向いたときにでも・・・
続き
sikisya.hateblo.jp