見出し画像

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」で検索をかけてみると以下のサイトに行きつきます。

USB Joystick Device

画像1

どうやら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のコードも読めなかったのか...)
他にもこまごまと書きかえた部分は存在しますが、特段解説するべきものでもないので割愛...

配線作業

ここまでプログラムの事ばっかりで肝心の配線に触れていませんでした。
リンク先に英語で書いてあります。

画像2

画像3

(Mbedのピンアサイン画像はスイッチサイエンスさんより拝借)
緑→MbedのD+ピン
白→D-ピン
赤→VINピン
黒→GNDピン

うーん単純。わかりやすい。
こういう基盤があれば簡単に実現できますが、僕は取り寄せるのが面倒だったのでUSBケーブル切って配線しました。
(配線間違えると当然うまく動かないので気を付けて)

まとめ

半年以上かかったけど、無事形にすることができました。
僕の真似をしてくれる人がいるのかはわからないけど、ここにプログラムのZIPアーカイブを載せて記事は終わりにしたいと思います。(記事が短い?書くのが面d...時間がなかったんです許して)

それでは、また記事を書くことがあったらお会いしましょう。

この記事が気に入ったらサポートをしてみませんか?