記事の内容
PCに外部キーボードデバイスとして認識されるアーケードタイプのコントローラーの制作方法のついて記載します。
例として方向キーとEnterキー入力のできる仕様になります。

制作の動機
Unity などのゲームエンジンでシンプルなゲーム(移動と単一アクション程度)を制作する際、ゲームコントローラーの入力設定を行うのが手間に感じることがあります。
また、ビルド後に他の PC でもコントローラーなしですぐ動作させたいという理由から、キーボード入力として扱えるコントローラーがあると便利だと思い、制作しました。
作成するもの
使用したもの
-
Mac Book Pro
開発に使用します。
-
Arduino IDE
スケッチの作成とマイコンへの書き込みに使用します。
-
Seeed XIAO RP2040
マイコンとして使用し、キーボードデバイスとして振る舞うスケッチを書き込みます。
https://akizukidenshi.com/catalog/g/g116132/
同じ RP2040 デバイス(例:Raspberry Pi Pico など)でも代用可能ですが、XIAO は小型で取り付けやすいという利点があります。
-
3Dプリンター
コントローラーの筐体作成に使用します。
ケースであればあんでも良いので詳細は割愛します。
工程
スケッチの用意
Arduino IDEでスケッチを作成します。
ライブラリにTinyUSBを使用します。
ライブラリマネージャーから「Adafruit TinyUSB Library」を追加してください。
#include "Adafruit_TinyUSB.h"
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_KEYBOARD()
};
Adafruit_USBD_HID usb_hid;
//------------- Input Pins -------------//
uint8_t pins[] = { D3, D4, D5, D6, D7 };
uint8_t pincount = sizeof(pins) / sizeof(pins[0]);
uint8_t hidcode[] = {HID_KEY_ARROW_UP, HID_KEY_ARROW_DOWN, HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_LEFT, HID_KEY_ENTER };
#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) || defined(ARDUINO_FUNHOUSE_ESP32S2)
bool activeState = true;
#else
bool activeState = false;
#endif
void setup() {
if (!TinyUSBDevice.isInitialized()) {
TinyUSBDevice.begin(0);
}
// Setup HID
usb_hid.setBootProtocol(HID_ITF_PROTOCOL_KEYBOARD);
usb_hid.setPollInterval(2);
usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
usb_hid.setStringDescriptor("TinyUSB Keyboard");
usb_hid.setReportCallback(NULL, hid_report_callback);
usb_hid.begin();
if (TinyUSBDevice.mounted()) {
TinyUSBDevice.detach();
delay(10);
TinyUSBDevice.attach();
}
for (uint8_t i = 0; i < pincount; i++) {
pinMode(pins[i], activeState ? INPUT_PULLDOWN : INPUT_PULLUP);
}
}
void process_hid() {
static bool keyPressedPreviously = false;
uint8_t count = 0;
uint8_t keycode[6] = {0};
// scan normal key and send report
for (uint8_t i = 0; i < pincount; i++) {
if (activeState == digitalRead(pins[i])) {
// if pin is active (low), add its hid code to key report
keycode[count++] = hidcode[i];
// 6 is max keycode per report
if (count == 6) break;
}
}
if (TinyUSBDevice.suspended() && count) {
TinyUSBDevice.remoteWakeup();
}
if (!usb_hid.ready()) return;
if (count) {
uint8_t const report_id = 0;
uint8_t const modifier = 0;
keyPressedPreviously = true;
usb_hid.keyboardReport(report_id, modifier, keycode);
} else {
if (keyPressedPreviously) {
keyPressedPreviously = false;
usb_hid.keyboardRelease(0);
}
}
}
void loop() {
#ifdef TINYUSB_NEED_POLLING_TASK
TinyUSBDevice.task();
#endif
if (!TinyUSBDevice.mounted()) {
return;
}
static uint32_t ms = 0;
if (millis() - ms > 2) {
ms = millis();
process_hid();
}
}
// Output report callback for LED indicator such as Caplocks
void hid_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {
(void) report_id;
(void) bufsize;
if (report_type != HID_REPORT_TYPE_OUTPUT) return;
uint8_t ledIndicator = buffer[0];
}
以下の部分で、スイッチを接続するピン番号を指定します。
//------------- Input Pins -------------//
uint8_t pins[] = { D3, D4, D5, D6, D7 };
以下の部分で、各ピンに対応するキーコードを指定します。
uint8_t hidcode[] = {HID_KEY_ARROW_UP, HID_KEY_ARROW_DOWN, HID_KEY_ARROW_RIGHT, HID_KEY_ARROW_LEFT, HID_KEY_ENTER };
XIAOをPCに繋ぎ、スケッチを書き込みます。
書き込み後、キーボードデバイスとして認識されるようになります。
パーツの結線
スケッチで指定したデジタル入力ピンと GND にスイッチパーツを結線します。
キーを押すと方向キーと Enter キーが入力され、長押しした場合は連続入力が行われます。

キーを入力すると方向キーとEnterキーの入力ができます。
長押しすると連続入力になります。
パーツの組み込み
完成
すべて組み上げれば完成です。
XIAO を採用すると USB-C でそのまま接続でき、USB-C to C ケーブルで動作する点も地味に便利です。




