ラズパイピコでトレイン シミュレーター用コントローラーを作成する

マイコン

概要

Raspberry Pi Pico の USB 機能を利用し、JR 東日本トレインシミュレーター用の簡易コントローラーを作成します。

作成する機能

タクト スイッチを使用して、シンプルに次の 3 つの操作を行えるようにします。

  • 加速強め/ブレーキ弱め (P)
  • 加速弱め/ブレーキ強め (B)
  • ニュートラル (N)

作成は、手軽に試すことのできるブレッドボードを使用します。

ハードウェア

使用パーツ

使用するパーツを以下の表にまとめます。これらは、秋月電子通商などで購入できます。表中のパーツ名は、秋月電子通商の商品ページにリンクしています。

パーツ名型番数量備考
Raspberry Pi Pico1
ブレッドボードEIC-8011
タクト スイッチTS-06063基板取付用タイプ
細ピン ヘッダーPHA-1x20SG2シングル 20 ピン
ジャンパ ワイヤー165-012-000(EIC
-J-L)
適量単線タイプ (型番は一例)
USB ケーブル1A オス – マイクロ B オス
パーツ一覧

回路

回路図は以下の通りです。

回路図
回路図

部品配置

部品配置は以下の写真の通りです。

部品配置
部品配置

ファームウェア

TinyUSB のサンプルを流用

ファームウェアは、Raspberry Pi Pico 公式 SDK に付属する TinyUSB のサンプル コード dev_hid_composite を改造して作成します。Raspberry Pi Pico を HID デバイスとして動作させ、Windows からはキーボード デバイスとして認識させます。

サンプルの主な改造ポイントを以降で説明します。

ボタン入力ピン割り当て

ボタン入力を受け付けるため、GPIO ピンを次のように割り当てます。GP25 は基板上の LED を点滅させるために使用します。

GPIO機能
GP2加速強め/ブレーキ弱め
GP3ニュートラル
GP4加速弱め/ブレーキ強め
GPIO のアサイン
C
// GPIO ピン兼ボタン ID の定義
enum
{
    LED_PIN = 25,       // LED
    BUTTON_ACCEL = 2,   // 加速強め/ブレーキ弱め
    BUTTON_NEUTRAL = 3, // ニュートラル
    BUTTON_BRAKE = 4,   // 加速弱め/ブレーキ強め
};

GPIO の初期化

main 関数内で、ボタン用の GPIO を入力ピンとして設定し、さらにプルアップを有効にします。

C
int main()
{
    board_init();
    tusb_init();
    stdio_init_all();

    // GPIO を初期化します。
    gpio_init(LED_PIN);
    gpio_init(BUTTON_ACCEL);
    gpio_init(BUTTON_NEUTRAL);
    gpio_init(BUTTON_BRAKE);

    // GPIO の入出力方向を設定します。
    gpio_set_dir(LED_PIN, GPIO_OUT);
    gpio_set_dir(BUTTON_ACCEL, GPIO_IN);
    gpio_set_dir(BUTTON_NEUTRAL, GPIO_IN);
    gpio_set_dir(BUTTON_BRAKE, GPIO_IN);

    // 入力端子をプルアップします。
    gpio_pull_up(BUTTON_ACCEL);
    gpio_pull_up(BUTTON_NEUTRAL);
    gpio_pull_up(BUTTON_BRAKE);

    while (1)
    {
        tud_task(); // tinyusb device task
        led_blinking_task();
        hid_task();
    }

    return 0;
}

ボタン入力関数の作成

GetButtonStatus 関数は GPIO 入力をスキャンし、押されているボタンの ID を返します。

C
uint GetButtonStatus(void)
{
    const uint buttons[] =
        {
            BUTTON_NEUTRAL,
            BUTTON_BRAKE,
            BUTTON_ACCEL
        };

    // 入力ポートをスキャンします。
    for (int i = 0; i < count_of(buttons); i++)
    {
        if (!gpio_get(buttons[i]))
        {
            return buttons[i];
        }
    }

    return 0;
}

hid_task 関数の改造

hid_task 関数内で、board_button_read 関数の代わりに GetButtonStatus 関数を呼び出すことにより、ボタンが押されたときにキー イベントを送信できるようにします。

C
void hid_task(void)
{
    // Poll every 10ms
    const uint32_t interval_ms = 10;
    static uint32_t start_ms = 0;

    if (board_millis() - start_ms < interval_ms)
        return; // not enough time
    start_ms += interval_ms;

    //uint32_t const btn = board_button_read();
    uint32_t const btn = GetButtonStatus();

    // Remote wakeup
    if (tud_suspended() && btn)
    {
        // Wake up host if we are in suspend mode
        // and REMOTE_WAKEUP feature is enabled by host
        tud_remote_wakeup();
    }
    else
    {
        // Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
        send_hid_report(REPORT_ID_KEYBOARD, btn);
    }
}

tud_hid_report_complete_cb 関数の改造

サンプルでは、キーボード イベント以外にマウス イベント等も送信していますが、今回はキーボード イベントのみでよいため、不要なコードを削除します。

C
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const *report, uint16_t len)
{
    (void)instance;
    (void)len;
    (void)report;
}

send_hid_report 関数の改造

キーボード イベントのみ送信するため、その他のコードを削除します。また、それぞれのボタンに対応したキー コードを送信するようにコードを書き換えます。

ここで、JR 東日本トレインシミュレーターのキーボード ショートカットは以下の通りです。

操作ショートカット キー
加速強め/ブレーキ弱めz
ニュートラルs
加速弱め/ブレーキ強めq
使用するショートカット キー
C
static void send_hid_report(uint8_t report_id, uint32_t btn)
{
    // skip if hid is not ready yet
    if (!tud_hid_ready())
        return;

    switch (report_id)
    {
    case REPORT_ID_KEYBOARD:
    {
        // キーボードに対して複数の連続したゼロ レポートが送信されるのを避けるために使用します
        static bool has_keyboard_key = false;
        uint8_t keycode[6] = {0};

        switch (btn)
        {
        case BUTTON_ACCEL:
            keycode[0] = HID_KEY_Z;
            break;

        case BUTTON_NEUTRAL:
            keycode[0] = HID_KEY_S;
            break;

        case BUTTON_BRAKE:
            keycode[0] = HID_KEY_Q;
            break;

        default:
            break;
        }

        if (btn)
        {
            tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
            has_keyboard_key = true;
        }
        else
        {
            // 以前にキーが押された場合は空のキーのレポートを送信する
            if (has_keyboard_key)
                tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
            has_keyboard_key = false;
        }
    }
    break;


    default:
        break;
    }
}

デバッグ

ビルドしたファームウェア (.uf2) をインストールすると、Raspberry Pi Pico 上の LED が点滅を始め、PC にドライバーが自動的にインストールされます。そして、Raspberry Pi Pico がキーボード デバイスとして認識されるようになります。

メモ帳を立ち上げ、ウィンドウをアクティブにした状態でボタンを押します。押したボタンに応じて「z」、「s」、「q」という文字が入力されれば OK です。

うまくいったら、今度は JR 東日本トレインシミュレーターを立ち上げて、コントロールができることを確認します。

実行例

以下の動画は、作成したコントローラーで JR 東日本トレインシミュレーターをプレイしている様子です。

デモ動画

ソース全体について

ソース ファイル一式 (ZIP ファイル) は、下記リンクからダウンロードできます。

まとめ

Raspberry Pi Pico SDK には USB ライブラリが標準装備されているため、USB のプロトコルに関する専門的な知識がなくても、このようにオリジナルの USB 機器を簡単に作成することができました。便利な時代になりましたね。

皆さんも、オリジナル USB 機器の作成に挑戦してみてはいかがでしょうか?

コメント