はじめに
USB HID (Human Interface Device) を利用した自作デバイスに関する情報は、キーボードやジョイスティックなどの入力デバイスに特化したものが多く、汎用的な用途のデバイスを扱う初心者向けの記事はあまり見かけません。
そもそもUSB汎用デバイスを必要とする場面が少ないことに加え、初心者にとっては「デバイス側とホスト側の両方で開発の難易度が高く、結果として手を出す機会が少ない」ことは確かです。
しかし、HIDデバイスはOSに標準で実装されているドライバで動作するため、専用ドライバのインストールが不要なデバイスを製作できるという大きな利点があります。
そこで本記事では、HIDデバイスの利点を活かし、できるだけ簡単に扱う手段に焦点を当ててみました。
最初は既製のデバイスを利用してHID通信を実際に体験し、最終的にはマイコンボードやマイコン単体で USB HID デバイスを作ってみます。
厄介なホスト側も開発環境は使用せず、Webブラウザで JavaScript + WebHID API を使ってホスト機能を実現します。 WebHID API を利用することでデバイスとの通信は驚くほど簡単になります。
今回は、HIDデバイスと意思疎通できるところまで進んでみます。
HID汎用デバイス操作の概要
HID汎用デバイス操作のおおまかな手順を示します。
手順① デバイスとの接続
ホストは複数存在するHIDデバイスの中から目的のデバイスを選択し、接続を確立します。
手順② ホストからデバイスに出力レポートを送信
HIDでは送受信するデータを「レポート」と表現します。
入出力の方向はホスト目線になりますので、デバイスに送信されるデータは出力レポートになります。
1回で送信できる出力レポートは64バイトまでになります。
手順③ デバイスはホストに入力レポートを送信
デバイスはホストからのデータを受信し、必要があればホストに64バイトまでのデータを送信します。
WebHIDは、入力レポートを「受信イベント」で処理するために、手順②の前にイベント処理の手続きを済ませます。
手順④ デバイスの解放
デバイスを解放し、必要な終了処理を行います。
ホストとデバイス
練習用ホストはWebブラウザ
繰り返しになりますが、ホスト機能はWebブラウザを使えば簡単に実現できます。
目的外使用ですが...(^_^;)
HIDデバイスとWebブラウザの組み合わせは環境を選ばないことが最大のメリットなので、上記のとおりChromebookでも動作を確認できました。
Webブラウザの条件は WebHID対応 であることですが、PC版の Chrome, Edgh, Opera が対応済みなので問題ないでしょう。(Firefox, Safari は未対応)
練習用デバイスはMCP2221A
まずは、既製のデバイスで練習します。
練習用デバイスとして採用する Microchip MCP2221A は次の機能を持つICです。
- HIDクラスを利用した I²C通信 / GPIO / ADC / DAC 機能
- CDCクラスによる USB-シリアル変換(※ 本記事では利用しません)
外付け部品はコンデンサ1個、形状は14ピンDIPで実験しやすく、入手も容易です。
(執筆時 秋月電子で550円)
準備の際はコンデンサ(詳細後述)もお忘れなく。
USB-シリアル変換は専用ドライバが不要なので、手元に1つあると便利なICです。
情報も多く、検索すれば活用例は多数見つかります。
MCP2221A実装済みの安価なボードも多数出回っているので、そちらを使ってもいいでしょう。
実験環境
以下の環境で動作確認しています。
<環境1>
- OS: Windows 11 Pro (25H2)
- Web Browser:
Google Chrome Ver. 141.0.7390.123 および
Microsoft Edge Ver. 141.0.3537.99
<環境2>
- OS: ChromeOS Ver. 141.0.7390.126
- Web Browser:
Google Chrome Ver. 141.0.7390.126
デバイスの準備
配線
配線は入念にチェックしてください。
PCとの接続は自己責任でお願いします。
ピン配置と最低限の配線例(写真)を示します。手持ちのUSBケーブル(USB Aコネクタ - バラ4P オス)を使ったので、簡単に配線できました。
外付け部品としてVUSB(11番ピン)とVSS(14番ピン)間に0.47μF(474)のコンデンサを接続します。なお、データシートには「0.22~0.47μF(セラミックコンデンサ)を使用する」との記載があります。
ホストからデバイスを確認
配線後はPCと接続して、デバイスマネージャ(Windowsの場合)で確認します。
表示 - デバイス(コンテナー別)を選択するとMCP2221が確認できます。
見つからない場合はすぐに外して配線チェックをしてください。
ホストの準備
ここでは、Chromeの例を示しますが、Edgeでも同様です。
ブラウザの起動
デバイスとの通信実験には、ブラウザのデベロッパーツールからコンソールを利用します。
REPL(対話環境)であることに加えてデバッグ環境も整っているので、今回の実験にはうってつけですが、次の留意点があります。
コンソールはタブに従属しており、ページのデバッグに利用するのが本来の役割です。よって、タブが開いているページから影響を受ける可能性があります。
なお、「土台」にするタブとして空のページ(about:blank)は使えません。
実験は、タブで開いているページからコンソールを「間借り」して行います。
本当はダミーのhtmlファイルを使ってコンソールの土台にするべきですが、神経質になる必要はありません。
当方は面倒くさがりですので、Googleのトップページ(https://www.google.com/)を間借りしています。
コンソールの起動
土台のタブがアクティブな状態で、ショートカットキー Ctrl + Shift + J からコンソールを起動します。
デバイスとの通信
準備が整ったらデバイスと通信してみます。
MCP2221Aはホストからのデータを「コマンド」として受け取り、「レスポンス」を返します。MCP2221Aに接続されたI²Cデバイスの操作も、ホストからのコマンドとレスポンスで処理します。
MCP2221Aのコマンドとレスポンスの詳細はデータシートに詳しく記載されています。
通信の開始
「HID汎用デバイス操作の概要」の手順に沿って進めます。
デバイス接続時には VID と PID でフィルタリングすることができます。
デバイスマネージャーからデバイスのハードウェアIDを確認しておくとよいでしょう。
手順① デバイスとの接続
次のコードをコンソールに入力します。
filters:...には上で確認した VID, PID を記述します。
const mcp2221a = (await navigator.hid.requestDevice({
filters: [{ vendorId: 0x04D8, productId: 0x00DD }],
}))[0];
コンソールを呼び出したタブに接続の許可を求めるダイアログが表示されるので、MCP2221を選択して接続をクリックします。フィルタをかけないと他のデバイスも列挙されます。
タブで開いているwww.google.comが接続要求したことになります。
続いて、デバイスを開きます。
await mcp2221a.open();
さらに受信データの受け皿と受信イベント処理を記述して接続時の処理は終了です。
let mcpResponse = undefined; // 受信データ(Uint8Array 64バイト)格納先
mcp2221a.addEventListener("inputreport", (event) => {
mcpResponse = new Uint8Array(event.data.buffer);
});
手順② ホストからデバイスに出力レポートを送信
await mcp2221a.sendReport(0, Uint8Array.from([0xB0, 0x02]));
sendReport()の第1引数(レポートID)は使用しないので0とします。
第2引数が送信データ(Uint8Array 最大64バイト)です。
先頭バイト0xB0は Read Flash Data コマンド、第2バイト0x02は Read USB Manufacturer Descriptor String (メーカー名文字列)の要求です。
手順③ デバイスはホストに入力レポートを送信
コマンドを受け取ったデバイスは、レスポンスを入力レポートとして返します。
入力レポートを受信するとinputreportイベントが発火します。
event(HIDInputReportEventインターフェースのインスタンス)からdata,bufferと辿れば受信データを取り出せます。
MCP2221Aは準備が整い次第、レスポンスを返しますから、通信が成功すれば受信データがmcpResponseに入っているはずです。
コンソールから確認します。
mcpResponse;
何やらデータが確認できます。
赤丸のアイコンをクリックすると16進ダンプ等の詳細を見ることができます。
データシートによれば、レスポンスのバイトインデックス2には (文字数 × 2 + 2) が入り、バイトインデックス4からutf-16LEでエンコードされた文字列データが格納されるようです。
せっかくですので文字列を取り出してみましょう。
const decoder = new TextDecoder('utf-16le');
decoder.decode(mcpResponse.subarray(4, 4 + mcpResponse[2] - 2));
正しく取得できています。
コマンド0xB0, 0x03は製品名を返します。
手順④ デバイスの解放
await mcp2221a.forget();
これで、タブのアイコンが消えればデバイスが解放されます。
本当は受信イベントリスナーも削除すべきですが、詳細は次回ということで。
コンソールとタブを閉じればとりあえず終了となります。
次回は、MCP2221Aを使ってI²Cデバイスや GPIO / ADC / DAC も利用してみます。




