2020年5月16日土曜日

Nordic SDKのサンプルコードにないHIDサービスを実装する

Nordic nRF5 SDKには、HIDの実装例としてキーボードとマウスが公開されています。HIDはそれ以外の使われ方も定義されていますが、サンプルは公開されていないうえインターネットでも実装例は少ないです。Gamepadの実装をやってみたところいろいろな苦労があったので、参考のためにまとめておきます。

マウスを参考にして、USBのHIDを載せる。

フォーラムに書いてあります。基本的に、次の流れで調整すると動きます。

  1. レポートディスクリプタの中身を整える (コード内ではrep_map_data[]に定義)
  2. レポートディスクリプタに記載したルールに従ってバイト列を作る
  3. データを送信する (コード内ではble_hids_inp_rep_send()関数で送信)

レポートディスクリプタの中身を整える

レポートディスクリプタは、送信したビット列のデータをどのように解釈するかを定義したものです。なので、中身は自分で必要なものをきちんと定義する必要があります。Gamepadの場合はどのように指定すればいいのかは、下記のリンクが参考になりました。

今回は下記のように指定しています。ゲームボーイのキー操作を入力としたいので、8ビットの入力を4つのボタンと1つの方向キーとして扱います。

static uint8_t rep_map_data[] ={
    0x05, 0x01,             // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,             // USAGE (Gamepad)

    0xa1, 0x01,             // COLLECTION (Application)

    0x05, 0x01,             // USAGE_PAGE (Generic Desktop)
    0x09, 0x39,             // USAGE (Hat switch)     // 十字キー
    0x15, 0x00,             // LOGICAL MAXIMUM (0)    // このデータは0〜7の範囲
    0x25, 0x07,             // LOGICAL MAXIMUM (7)    
    0x35, 0x00,             // PHYSICAL MINIMUM (0)   // 0〜7が
    0x46, 0x3b, 0x01,       // PHYSICAL MAXIMUM (315) // 0〜315に対応
    0x75, 0x04,             // REPORT SIZE (4)        // SIZEとCOUNTで
    0x95, 0x01,             // REPORT COUNT (1)       // 4ビットが1個の意味
    0x65, 0x14,             // UNIT (Degrees)         // 単位は角度
    0x81, 0x42,             // INPUT (DATA, VARIABLE, ABS, NULL)

    0x05, 0x09,             // USAGE_PAGE (Button)    // ボタン単体
    0x15, 0x00,             // LOGICAL MINIMUM (0)    // このデータは0〜1の範囲
    0x25, 0x01,             // LOGICAL MAXIMUM (1)
    0x19, 0x01,             // USAGE MINIMUM (1)
    0x29, 0x04,             // USAGE MAXIMUM (4)
    0x75, 0x01,             // REPORT SIZE (1)        // SIZEとCOUNTで
    0x95, 0x04,             // REPORT COUNT (4)       // 1ビットが4個の意味
    0x81, 0x02,             // INPUT (DATA, VARIABLE, ABS)

    0xc0,                   // END_COLLECTION
};

ルールに従ってバイト列を作る

レポートディスクリプタで指定したルールに従った値を並べます。レポートディスクリプタに記載した順序がそのままバイト内のビット列になるので、下位4ビットが十字キー・上位4ビットがボタン単体となるよう組み立てます。

データを送信する

組み立てたデータは、関数ひとつで送信できます。

err_code = ble_hids_inp_rep_send(&m_hids,
                                 INPUT_REP_BUTTONS_INDEX,
                                 INPUT_REP_BUTTONS_LEN,
                                 buffer);

  • INPUT_REP_BUTTONS_INDEX: マウスのサンプルのように、レポートディスクリプタのなかに複数のIDがある場合はここで指定します。今回の例ではひとつしかないので、0になります。
  • INPUT_REP_BUTTONS_LEN: 送信するデータのバイト数です。今回は1バイトなので1です。
  • buffer: データ本体のポインタ。

動作例

データの受信側で確認する方法

HTML5 Gamepad Testerを使っています。ペアリングし、レポートディスクリプタの指定もうまくいっている場合は、キー操作に反応します。

ペアリングできていてもキー操作に反応しない場合、レポートディスクリプタの記載内容がきちんと合っていない可能性があります。バイト列が送信されているかどうかは、Macを使っていると確認が楽です。Appleの開発ツールとして公開されているPacket Loggerを使うと、どのデバイスからどのデータが飛んでいるかを確認できます。バイト列の中身をPacket Loggerで確認できれば、ペアリングと通信はうまくいっているので、おかしいのはディスクリプタだと推測できます。