Mbed(lpc1768)でNintendoSwitchを操作する
Pokémon RNG Advent Calendar 2020 12月24日の記事となります。(大遅刻)
半年以上前にお蔵入りしていた物なので面白い記事では無いです。
(ここから半年前に書いてる部分)
お久しぶりでございます、ゲーム好きの和菓子です。
あつ森も発売されて久しい今、マイル稼ぎマクロが作られる等ポケモン発売時よりマクロ界隈が活発になっている気がする今日この頃です。
さて、そんなマクロに使われるマイコンはarduinoが大半です(androidやラズパイも存在は確認しています)が、マイコンの中でもswitchを操作するライブラリが無いものもあります。それが今回のお題であるMbedなわけです。
ということで家にあるmbedを有効活用するべく、プログラムの作成に励むことにしました。
Mbedとは何ぞ
mbed(エンベッド)はARM社のプロトタイピング用ワンボードマイコンおよびそのデバイスのプログラミング環境を指す。(Wikipediaより抜粋)
要約するとarduinoより高性能でCPUの違うマイコンに一種になります。
なんとフラッシュ保存容量は512KB!(arduinoは32KB)
このマイコンならより多くのプログラムを1つのマイコンに収めておく事ができそうです。そう、プログラムさえできれば...
ここから先は和菓子が奮闘した記録になります。
軽くネットサーフィン
まず「mbed usb joystick」で検索をかけてみると以下のサイトに行きつきます。
どうやらmbedをコントローラー化することに関しては先駆者がいたようですね。ありがたくプログラムを利用させてもらいましょう。
コードの書き換え
まずUSBJoystick.cppのUSBディスクリプタを見てみます
uint8_t * USBJoystick::reportDesc() {
static uint8_t reportDescriptor[] = {
USAGE_PAGE(1), 0x01, // Generic Desktop
LOGICAL_MINIMUM(1), 0x00, // Logical_Minimum (0)
USAGE(1), 0x04, // Usage (Joystick)
COLLECTION(1), 0x01, // Application
USAGE_PAGE(1), 0x02, // Simulation Controls
USAGE(1), 0xBB, // Throttle
USAGE(1), 0xBA, // Rudder
LOGICAL_MINIMUM(1), 0x81, // -127
LOGICAL_MAXIMUM(1), 0x7f, // 127
REPORT_SIZE(1), 0x08,
REPORT_COUNT(1), 0x02,
INPUT(1), 0x02, // Data, Variable, Absolute
USAGE_PAGE(1), 0x01, // Generic Desktop
USAGE(1), 0x01, // Usage (Pointer)
COLLECTION(1), 0x00, // Physical
USAGE(1), 0x30, // X
USAGE(1), 0x31, // Y
// 8 bit values
LOGICAL_MINIMUM(1), 0x81, // -127
LOGICAL_MAXIMUM(1), 0x7f, // 127
REPORT_SIZE(1), 0x08,
REPORT_COUNT(1), 0x02,
INPUT(1), 0x02, // Data, Variable, Absolute
// 16 bit values
// LOGICAL_MINIMUM(1), 0x00, // 0
// LOGICAL_MAXIMUM(2), 0xff, 0x7f, // 32767
// REPORT_SIZE(1), 0x10,
// REPORT_COUNT(1), 0x02,
// INPUT(1), 0x02, // Data, Variable, Absolute
END_COLLECTION(0),
#if (HAT4 == 1)
// 4 Position Hat Switch
USAGE(1), 0x39, // Usage (Hat switch)
LOGICAL_MINIMUM(1), 0x00, // 0
LOGICAL_MAXIMUM(1), 0x03, // 3
PHYSICAL_MINIMUM(1), 0x00, // Physical_Minimum (0)
PHYSICAL_MAXIMUM(2), 0x0E, 0x01, // Physical_Maximum (270)
UNIT(1), 0x14, // Unit (Eng Rot:Angular Pos)
REPORT_SIZE(1), 0x04,
REPORT_COUNT(1), 0x01,
INPUT(1), 0x02, // Data, Variable, Absolute
#endif
#if (HAT8 == 1)
// 8 Position Hat Switch
USAGE(1), 0x39, // Usage (Hat switch)
LOGICAL_MINIMUM(1), 0x00, // 0
LOGICAL_MAXIMUM(1), 0x07, // 7
PHYSICAL_MINIMUM(1), 0x00, // Physical_Minimum (0)
PHYSICAL_MAXIMUM(2), 0x3B, 0x01, // Physical_Maximum (315)
UNIT(1), 0x14, // Unit (Eng Rot:Angular Pos)
REPORT_SIZE(1), 0x04,
REPORT_COUNT(1), 0x01,
INPUT(1), 0x02, // Data, Variable, Absolute
#endif
// Padding 4 bits
REPORT_SIZE(1), 0x01,
REPORT_COUNT(1), 0x04,
INPUT(1), 0x01, // Constant
#if (BUTTONS4 == 1)
// 4 Buttons
USAGE_PAGE(1), 0x09, // Buttons
USAGE_MINIMUM(1), 0x01, // 1
USAGE_MAXIMUM(1), 0x04, // 4
LOGICAL_MINIMUM(1), 0x00, // 0
LOGICAL_MAXIMUM(1), 0x01, // 1
REPORT_SIZE(1), 0x01,
REPORT_COUNT(1), 0x04,
UNIT_EXPONENT(1), 0x00, // Unit_Exponent (0)
UNIT(1), 0x00, // Unit (None)
INPUT(1), 0x02, // Data, Variable, Absolute
// Padding 4 bits
REPORT_SIZE(1), 0x01,
REPORT_COUNT(1), 0x04,
INPUT(1), 0x01, // Constant
#endif
#if (BUTTONS8 == 1)
// 8 Buttons
USAGE_PAGE(1), 0x09, // Buttons
USAGE_MINIMUM(1), 0x01, // 1
USAGE_MAXIMUM(1), 0x08, // 8
LOGICAL_MINIMUM(1), 0x00, // 0
LOGICAL_MAXIMUM(1), 0x01, // 1
REPORT_SIZE(1), 0x01,
REPORT_COUNT(1), 0x08,
UNIT_EXPONENT(1), 0x00, // Unit_Exponent (0)
UNIT(1), 0x00, // Unit (None)
INPUT(1), 0x02, // Data, Variable, Absolute
#endif
#if (BUTTONS32 == 1)
// 32 Buttons
USAGE_PAGE(1), 0x09, // Buttons
USAGE_MINIMUM(1), 0x01, // 1
USAGE_MAXIMUM(1), 0x20, // 32
LOGICAL_MINIMUM(1), 0x00, // 0
LOGICAL_MAXIMUM(1), 0x01, // 1
REPORT_SIZE(1), 0x01,
REPORT_COUNT(1), 0x20,
UNIT_EXPONENT(1), 0x00, // Unit_Exponent (0)
UNIT(1), 0x00, // Unit (None)
INPUT(1), 0x02, // Data, Variable, Absolute
#endif
END_COLLECTION(0)
};
reportLength = sizeof(reportDescriptor);
return reportDescriptor;
}
当たり前ですがswitchの認識するディスクリプタではありませんね。下記に書き換えます(ギャグか?)
uint8_t * USBJoystick::reportDesc() {
static uint8_t reportDescriptor[] = {
USAGE_PAGE(1), 0x01, // Generic Desktop
USAGE(1), 0x05, // Usage (Joystick)
COLLECTION(1), 0x01, // Application
LOGICAL_MINIMUM(1), 0x00,
LOGICAL_MAXIMUM(1), 0x01,
PHYSICAL_MINIMUM(1), 0x00,
PHYSICAL_MAXIMUM(1), 0x01,
REPORT_SIZE(1), 0x01,
REPORT_COUNT(1), 0x10,
USAGE_PAGE(1), 0x09, // Buttons
USAGE_MINIMUM(1), 0x01, // 1
USAGE_MAXIMUM(1), 0x10, // 32
INPUT(1), 0x02, // Data, Variable, Absolute
USAGE_PAGE(1), 0x01,
LOGICAL_MAXIMUM(1), 0x07, // 7
PHYSICAL_MAXIMUM(2), 0x3B, 0x01, // Physical_Maximum (315)
REPORT_SIZE(1), 0x04,
REPORT_COUNT(1), 0x01,
UNIT(1), 0x14, // Unit (Eng Rot:Angular Pos)
USAGE(1), 0x39, // Usage (Hat switch)
INPUT(1), 0x42, // Data, Variable, Absolute
UNIT(1), 0x00,
REPORT_COUNT(1), 0x01,
INPUT(1), 0x01,
LOGICAL_MAXIMUM(2), 0xff, 0x00,
PHYSICAL_MAXIMUM(2), 0xff, 0x00,
USAGE(1), 0x30,
USAGE(1), 0x31,
USAGE(1), 0x32,
USAGE(1), 0x35,
REPORT_SIZE(1), 0x08,
REPORT_COUNT(1), 0x04,
INPUT(1), 0x02,
USAGE_PAGE(2), 0x00, 0xff,
USAGE(1), 0x20,
REPORT_COUNT(1), 0x01,
INPUT(1), 0x02,
USAGE2(0), 0x21, 0x26, // USAGE (9761)
REPORT_COUNT(1), 0x08, // REPORT_COUNT (8)
OUTPUT(1), 0x02, // OUTPUT (Data,Var,Abs)
END_COLLECTION(0)
};
reportLength = sizeof(reportDescriptor);
return reportDescriptor;
}
次にUSBJoystick.hのpid/vidを書き換えます
//これを
USBJoystick(uint16_t vendor_id = 0x1234, uint16_t product_id = 0x0600, uint16_t product_release = 0x0001, int waitForConnect = true): // 32 buttons, no padding on buttons
USBHID(0, 0, vendor_id, product_id, product_release, false) {
_init();
connect(waitForConnect);
};
↓
//こうじゃ
USBJoystick(uint16_t vendor_id = 0x0f0d, uint16_t product_id = 0x0092, uint16_t product_release = 0x0100, int waitForConnect = true): // 32 buttons, no padding on buttons
USBHID(0, 0, vendor_id, product_id, product_release, false) {
_init();
connect(waitForConnect);
};
更にUSBディスクリプタに合わせてデータの送信順を決めます
#if (BUTTONS32 == 1)
// Fill the report according to the Joystick Descriptor
report.data[0] = (_buttons >> 0) & 0xff;
report.data[1] = (_buttons >> 8) & 0xff;
report.data[2] = (_buttons >> 16) & 0xff;
report.data[7] = (_hat & 0x0f);
report.length = 8;
#endif
こうすればとりあえずボタン入力が効くようになりましたが(hatに関しては7以上の数値は入れられない仕様っぽいので僕はお手上げ)、スティックは未実装なので詳しい人はhat含め是非下にあるソースコード落として実装してみてください。
と、ここまでが半年前に書いていた内容でした。
改めて見たら間違いだらけで恥ずかしい。
せっかくなので今の自分の知識で書き直してみるか!とクリスマスイブに一念発起した結果、ちょうど一時間前くらいにボタン・スティック・十字キー全て動作させることができたので記しておきます。
改めて書き直した部分
内部構造を大きく変えましたが、一番重要だったのは先程も書いたデータの送信順でした。
bool USBJoystick::update() {
HID_REPORT report;
report.data[0] = _joystickInputData.Button & 0xFF;
report.data[1] = (_joystickInputData.Button >> 8) & 0xFF;
report.data[2] = _joystickInputData.Hat;
report.data[3] = _joystickInputData.LX;
report.data[4] = _joystickInputData.LY;
report.data[5] = _joystickInputData.RX;
report.data[6] = _joystickInputData.RY;
report.data[7] = _joystickInputData.VendorSpec;
report.length = sizeof(USB_JoystickReport_Input_t);
return send(&report);
}
これがその答えです。はい。以前書いた物がいかに見当違いだったかが判ると思います。
(以前の僕はSwitch-Fightstickのコードも読めなかったのか...)
他にもこまごまと書きかえた部分は存在しますが、特段解説するべきものでもないので割愛...
配線作業
ここまでプログラムの事ばっかりで肝心の配線に触れていませんでした。
リンク先に英語で書いてあります。
(Mbedのピンアサイン画像はスイッチサイエンスさんより拝借)
緑→MbedのD+ピン
白→D-ピン
赤→VINピン
黒→GNDピン
うーん単純。わかりやすい。
こういう基盤があれば簡単に実現できますが、僕は取り寄せるのが面倒だったのでUSBケーブル切って配線しました。
(配線間違えると当然うまく動かないので気を付けて)
まとめ
半年以上かかったけど、無事形にすることができました。
僕の真似をしてくれる人がいるのかはわからないけど、ここにプログラムのZIPアーカイブを載せて記事は終わりにしたいと思います。(記事が短い?書くのが面d...時間がなかったんです許して)
それでは、また記事を書くことがあったらお会いしましょう。
この記事が気に入ったらサポートをしてみませんか?