SNSへはこちら

LPC11U35はUSBがラク!(4) - HIDで遊ぶ

前回最終回だといったが、忘れてくれ()
今回は HID をやっていきます。なぜかと言うと、マウスでもキーボードでもジョイスティックでもない HID がサンプルとして存在したからです。

HID レポートディスクリプタについても学んだことだし、色々と初経験なのでちょいと遊んでみましょう。

結局 HID って

何なんでしょうね。かなり抽象的なデバイスであることは確かです。抽象的ということは、かなり広い種類のインターフェイスデバイスをサポートするデバイスクラスであると言えるでしょう。今回のサンプルも、その汎用性の広さを利用したものだと考えられます。

プロジェクトのインポートと設定

毎回面倒くさいやつです。CDC の時にダウンロードしたアーカイブの中に USB_ROM_HID というのが入っているので、それをインポートしましょう。
前回 LPC11Uxx_Driver_Lib を入れていない人はついでに入れてください。

入れましたか?そうしたら以下を変えます。

  • MCU Settings は LPC11U35/501 に
  • C コンパイラのインクルード設定で "${workspace_loc:/LPC11Uxx_Driver_Lib/inc}" が一番上に来るように

ディスクリプタを覗いてみる

どうやらこのデバイス構成はマウスでもキーボードでもないようです。ではディスクリプタを覗いて、その全貌をなんとなく確認しましょう。

デバイスディスクリプタ

ここには 0x00 ということで、デバイスクラスやプロトコルに関して特に情報は記述されていません。ということはコンフィギュレーションディスクリプタやその後続に記載があるということですね。

uint8_t USB_DeviceDescriptor[] =
{
  USB_DEVICE_DESC_SIZE,              /* bLength */
  USB_DEVICE_DESCRIPTOR_TYPE,        /* bDescriptorType */
  WBVAL(0x0200), /* 2.00 */          /* bcdUSB */
  0x00,                              /* bDeviceClass */
  0x00,                              /* bDeviceSubClass */
  0x00,                              /* bDeviceProtocol */
...

インターフェイスディスクリプタ

ソースコードにはマクロで書いてあるので、それを一部定数に展開したものを掲載します。

/* Interface 0, Alternate Setting 0, HID Class */
  USB_INTERFACE_DESC_SIZE,           /* bLength */
  USB_INTERFACE_DESCRIPTOR_TYPE,     /* bDescriptorType */
  0x00,                              /* bInterfaceNumber */
  0x00,                              /* bAlternateSetting */
  0x02,                              /* bNumEndpoints */
  0x03,  /* bInterfaceClass */
  0x00,                 /* bInterfaceSubClass */
  0x00,                 /* bInterfaceProtocol */
  0x04,                              /* iInterface */

bInterfaceClass で「HID だよ」と言っているようですが、肝心の bInterfaceProtocol がなんと「None」と言っています。マウスでもキーボードでも無く、None です。

しらべてみた

せっかくなので、お勉強です。HID の規格書によると....

A variety of protocols are supported HID devices. The bInterfaceProtocol member of an Interface descriptor only has meaning if the bInterfaceSubClass member declares that the device supports a boot interface, otherwise it is 0.

と書いてあります。あれれ、意外でした。ちなみに日本語訳すると概ねこんな感じ。

様々なプロトコルが HID でサポートされている。インターフェイスディスクリプタのメンバである bInterfaceProtocol は、 bInterfaceSubClass がブートデバイスをサポートするとした時にのみ意味を成す。ブートインターフェイスをサポートしないのであれば 0 にする。

つまりブートインターフェイスではないとディスクリプタに記述する場合(bInterfaceSubClass == 0)は、bInterfaceProtocol == 0 で良いんですね。なるほど。
つまり、結論から言うとここまでは HID デバイスでどのようなものか、は全く分かってきません。どんどん見ていきましょう。

レポートディスクリプタ

結局この先も全く情報がないので、こうなると HID の場合はレポートディスクリプタに頼るしか無いとなります。こちらもマクロでガチガチなので、展開して示します。

const uint8_t HID_ReportDescriptor[] = {
  0x06, 0x00, 0xFF,
  0x09, 0x01,
  0xA1, 0x01,
  0x15, 0x00,
  0x26, 0xFF, 0x00,
  0x75, 0x08,
  0x95, 0x01,
  0x09, 0x01,
  0x81, 0x02,
  0x95, 0x01,
  0x09, 0x01,
  0x91, 0x02,
  0x95, 0x01,
  0x09, 0x01,
  0xB1, 0x02
  0xC0
};

どれどれ、解析してみましょう。

  • Usage Page(Vendor-Defined)
    • Usage(Vendor-Usage 1)
    • Collection(Application) begin
    • Logical Minimum(0)
    • Logical Maximum(0xFF)
    • Report Size(8)
    • Report Count(1)
    • Usage(Vendor-Usage 1)
    • Input(Data, Variable, Absolute)
    • Report Count(1)
    • Usage(Vendor-Usage 1)
    • Output(Data, Variable, Absolute)
    • Report Count(1)
    • Usage(Vendor-Usage 1)
    • Feature(Data, Variable, Absolute)
    • End Collection

つまりこんな感じですかね。

  • ベンダー固有の HID デバイスである
  • 設定できる項目は1つのみ
  • 8バイトの 0x00 〜 0xFF のデータをホストから受信できる
  • 8バイトの 0x00 〜 0xFF のデータをホストに送信できる
  • Feature はよくわからん(デバイスコンフィギュレーションに関係があるっぽい)

更にまとめると、1バイトのデータを送受信できるというデバイスでした。なんとも単純。

HIDにデータを送信しよう

では、どのようにデータを送りましょうか?仮想 COM ポートとしてマウントされているわけではないのでね...

1つの方法は hidapi というライブラリを用いる方法です。このライブラリを用いると、かなり簡単にホストとデバイスでデータのやり取りを行えます。

では早速インストールです。

$ brew install hidapi

hidapiを使ったプログラム(PC側)

例によって Mac の場合ですが、コードを書いていきましょう。
僕の探し方が悪かったのか、詳細なドキュメンテーションが見つからなかったので、ヘッダを見ながらなんとな〜く解釈して書きました。
ちなみに、接続されているデバイスを get する hid_enumerate という関数があるのですが、これが正常に動かなかったので VID/PID は直打ちです。

#include <stdio.h>
#include <stdlib.h>
#include <hidapi.h>

unsigned char buf[2];

int main(int argc, char** argv) {
    if( argc != 2 ) {
        fprintf(stderr, "Bad Arguments.\n");
        return 1;
    }
    hid_init();

    hid_device* handle = hid_open(0x1fc9, 0x0107, NULL);
    if( handle == NULL ) {
        fprintf(stderr, "Failed to open device\n");
        return 1;
    }

    buf[0] = 0;
    buf[1] = atoi(argv[1]);
    int retcode = 0;
    if( hid_write(handle, buf, 2) == -1 ) {
        fprintf(stderr, "Failed to write.\n");
        retcode = 1;
    }

    hid_close(handle);
    hid_exit();


    return retcode;
}

このプログラムでは実行時のコマンドライン引数で送るデータを指定します。コンパイルして実行しましょう。

$ gcc $(pkg-config --cflags --libs hidapi) -o hid -Wall main.c
$ ./hid 1 # 1を送信

特にエラーが出なければ、デバイスにデータ送出が完了しています。

それではデバイス側のコードを書くとしましょうか。今回使っているマイコンボードは PIO0_7 に LED がつながっているので、初期化しておきます。

  LPC_GPIO->DIR[0] |= 1 << 7;

そして lpc11uxx_usbd_hid.c の HID_Ep_Hdlr 内にて次のように書きます。loopback_report はどうやら受信データを格納するバッファのようです。

  switch (event) {
    case USB_EVT_IN:
      pUsbApi->hw->WriteEP(hUsb, pHidCtrl->epin_adr, loopback_report, 1);
      break;
    case USB_EVT_OUT:
      pUsbApi->hw->ReadEP(hUsb, pHidCtrl->epout_adr, loopback_report);
      if( *loopback_report != 0 ) {
          LPC_GPIO->SET[0] = 1 << 7;
      }else{
          LPC_GPIO->CLR[0] = 1 << 7;
      }
      break;
  }

これで完成!0以外のデータを送ると LED が点灯し、0 を送ると消灯します。

$ ./hid 1 # 点灯
$ ./hid 0 # 消灯

なるほど〜〜こうやって自分でデバイスを定義できちゃうんですね。HID を使えばどんなデバイスでもできそうな気がしてきました。無限の可能性を感じます。