PICに内蔵されているUSB機能の実験パート5
(HIDクラスでPCにキーボードデータを送受信してみる:XC8コンパイラ編)

〔パート1〕 〔パート2〕 〔パート3〕 〔パート4〕   〔パート6〕 〔マイコンのトップに戻る〕


前回のパート2パート4では、CDCクラスを利用してパソコンとデータを送受信して見ました。
このパート5では、HID(Human Interface Devices)クラスを利用して見ようと思います。
HIDクラスはパソコンを操作する為の入力デバイス(マウス・ジョイスティック等)を接続する為の物です。
今回はこの中からキーボードのみを接続した場合の模試実験を行って見ます。

今回使用するPICは18F14K50で秋月電子のUSB対応超小型マイコンボードです、
USBで利用する為の部品が全て実装されているので便利に使用出来ます。

"v2016-11-07"バージョン以降のUSBフレームワークではXC8に対応しました、
デモプログラムのプロジェクトフォルダーは、
"C:\microchip\mla\v2016_11_07\apps\usb\device\hid_keyboard\firmware\
low_pin_count_usb_development_kit_pic18f14k50.x"

有るのでMPLAB Xでプロジェクトを開きましょう。
このデモプログラム(デモボード用)と下記の改造プログラムとでは実行動作が異なるでしょう。
尚、PIC16F1459用は"low_pin_count_usb_development_kit_pic16f1459.x"に有ります。 *1)
ですのでぇ、"v2016-11-07"バージョンでぇ動作実験を行った方が良いかもですぅ....

《配線図》

配線図  左図は、HIDクラスでキーボードの模試実験を
 行う為の回路です。
 電源はUSBバスからの供給です。
 (外部からの電源供給は不可です)

 SW1はボードのJ1-1(PIC:RC0)端子に接続
 SW2はボードのJ1-2(PIC:RC1)端子に接続
 スイッチはプルアップ配線です。
 LEDは+側をボードのJ1-3(PIC:RC2)端子に接続

 マイコンボードの詳しい端子配列は、秋月電子の説明書を参照下さい。

《ダウンロードプログラム》

このダウンロードプログラムは、[microchip_solutions_v2013-02-15]ライブラリの中の
USB”フォルダー内に有る”Device - HID - Keyboard\Firmware\USB Device - HID - Keyboard
- C18 - Low Pin Count USB Development Kit.mcp
”のプロジェクトファイルを流用しました。
(パート4のプログラムと同様にXC8 C Compiler用に改造しています)

キーボードデバイスを製作する際の雛形プログラムとして使用して下さい。

@ ここからダウンロードし解凍して下さい。

A 解凍すると"USB"フォルダーが出来ます、このフォルダーごと全て、
   適当なフォルダー下に移動させて下さい。

B 後はMPLAB IDEで、
   プロジェクトファイル(USB\HID - Keyboard\Firmware\HID - Keyboard.mcp)を
   読み込みコンパイルし、18F14K50にプログラムを書き込みます。
   MPLAB IDE(V8.84)/MPLAB(R) XC8 C Compiler Version 1.00を使用しました。

   コンパイルすると"Warning [1311] "が出ますが気にしなくてOKです。

C 18F14K50をパソコンにUSBケーブルで接続します、キーボード ドライバが読込まれます。
   電源はUSBから供給し、消費電流は100mAまでとなっています。
   (最大500mAまで変更可能ですが、初期化が完了後提供されるので注意が必要です)

D メモ帖等のテキストエディターを起動させます、スイッチ2を押せばその間にデータが送信
   されます、データは文字の"a"〜"z""1"〜"0"が繰り返されます。

E スイッチ1を押して見て下さい、[Num Lock]キーと[*]キーのデータが送信されます。
   画面には"*"が表示され、LEDが点灯します、再びスイッチ1を押せばLEDは消えます。

キーデータを送る場合
     unsigned char hid_report_in[8] ;
     static unsigned char key = 4 ;

     hid_report_in[0] = 0;         // bite[0][右Alt][右Shift][右Ctrl][0][左Alt][左Shift][左Ctrl]
     hid_report_in[1] = 0;         // 0x00
     hid_report_in[2] = key++;     // 最初に押されたキーのデータ
     hid_report_in[3] = 0;         // 2番目に押されたキーのデータ
     hid_report_in[4] = 0;         // 3番目に押されたキーのデータ
     hid_report_in[5] = 0;         // 4番目に押されたキーのデータ
     hid_report_in[6] = 0;         // 5番目に押されたキーのデータ
     hid_report_in[7] = 0;         // 6番目に押されたキーのデータ
     // データの送信
     lastINTransmission = HIDTxPacket(HID_EP, (BYTE*)hid_report_in, 8);

     if(key == 40) key = 4;  // キーデータは4"a"から39"0"を繰り返す
  例えば、左側の[Ctrl]キーと[c]キーが同時に押された場合(Windowsではコピー動作)は
   hid_report_in[0] = 0x01;
   hid_report_in[2] = 6;
  と設定する。

  キーデータのキーコード変換一覧は、「Universal Serial Bus HID Usage Tables(1.12)」の
  53ページ"10 Keyboard/Keypad Page(0x07)"を参照下さい。 *1)

データを受信する場合
     unsigned char hid_report_out[1] ;

     if(!HIDRxHandleBusy(lastOUTTransmission)) {
          if(hid_report_out[0] & 0x01) // LEDを点灯(Num Lock)
          else                         // LEDを消灯(Num Lock)
          // 次が受信出来る様に準備する
          lastOUTTransmission = HIDRxPacket(HID_EP,(BYTE*)&hid_report_out,1);
     }
  通常のキーボードは、キーの[かな][Compose][Scroll Lock][Caps Lock][Num Lock]が押されたら
  LED表示器がONする。
  なので、このキーデータを送ればそのタイミングでデータが返って来るので内容をチェックし該当の
  LEDを表示させる事が出来ます。
  データは1バイトでビット並びは
     7       6       5       4         3          2            1           0
  [  0  ] [  0  ] [  0  ] [ かな ] [Compose] [Scroll Lock] [Caps Lock] [Num Lock]
  ※ レポートディスクリプタに”Feature”タグを追加すれば、EP0 OUTのコントロール転送で
    この情報を受け取る事も可能な様です、USBHIDCBSetReportHandler( )関数を参照。

《メモ》

今回USB規格内のHIDクラス(パソコンに入力を行うUSB機器の事)について少し調べたので
覚書のメモとして残しておきます。

ディスクリプタ

これは全てのUSB機器が必ず持っていないとならない情報で、機器のクラス情報やベンダー情報に
通信する為の情報などです、接続時にホストから問い合わせ(GET_DESCRIPTOR)が有ります。

ここのプログラムでディスクリプタを記述してあるファイルは、"usb_descriptors.c"です。
内容については、「SyncHackのUSB/Descriptor」や こちらのPDF「4.USBデバイスの論理的構造とディスクリプタ 9頁」を見て下さい。
また、キーボードやマウス関連のディスクリプタは、「人と電子工房」さんのこちらも参照下さい。

ディスクリプタ構成図

上の図は、HIDクラス(キーボード)のディスクリプタ構成図です。
新しい機能(例 マウス)を追加したい場合は、インターフェイスディスクリプタを追加する事になります、

尚、エンドポイントEP0は全てのUSB機器が実装している必要がある機能なので、
ディスクリプタには記述しません。

[レポートディスクリプタ]

レポートディスクリプタは、ホストとデバイス間で送受信するデータのフォーマットを定義する為の
ディスクリプタです。
内容は、こちらのPDF「21頁」又は、こちらのPDF「6頁」を参照下さい。 *1)
(検索サイトが開きますが、下に有るリンクをクリックすればPDFが開きます)
又は、「人と電子工房」さんのこちらも参照下さい。

例えば、EP1 OUT で受信するデータは下記の1バイトでPCから送信されます。

     7       6       5       4         3          2            1           0
  [  0  ] [  0  ] [  0  ] [ かな ] [Compose] [Scroll Lock] [Caps Lock] [Num Lock]

なので、
    0x95, 0x05,          //   REPORT_COUNT (5)         ;データは5個
    0x75, 0x01,          //   REPORT_SIZE (1)          ;データは1ビット
    0x05, 0x08,          //   USAGE_PAGE (LEDs)        ;参照するデータが記述してあるページ
    0x19, 0x01,          //   USAGE_MINIMUM (Num Lock) ;データの最小値
    0x29, 0x05,          //   USAGE_MAXIMUM (Kana)     ;データの最大値
    0x91, 0x02,          //   OUTPUT (Data,Var,Abs)    ;データの型式
    0x95, 0x01,          //   REPORT_COUNT (1)
    0x75, 0x03,          //   REPORT_SIZE (3)
    0x91, 0x03,          //   OUTPUT (Cnst,Var,Abs)
と記述されています。
REPORT_COUNT (5)
REPORT_SIZE (1)
 1ビットのデータが5個有ると言う事です。(受信データの0ビット〜4ビットを意味します)

USAGE_PAGE (LEDs)
 「Universal Serial Bus HID Usage Tables(1.12)」の14ページ"3 Usage Pages"を見ると、
 [LEDs]はPageID=08でSection or Document=11なので11項"11 LED Page (0x08)"を見ます
 と言う事です。 *1)

USAGE_MINIMUM (Num Lock)
USAGE_MAXIMUM (Kana)
 利用するデータの範囲を表します、データの範囲は0x01〜0x05なので、
 11項"11 LED Page (0x08)"を見ると(Num Lock)〜(Kana)が割付けて有り、
 "11.1 Keyboard Indicators"となっています。

OUTPUT (Data,Var,Abs)
 上記の内容は、データで変数で絶対値と言う事で、OUTで送りますです。

REPORT_COUNT (1)
REPORT_SIZE (3)
 3ビットのデータが1個有ると言う事です。(受信データの5ビット〜7ビットを意味します)

OUTPUT (Cnst,Var,Abs)
 上記の内容は、定数(0だから)で変数で絶対値と言う事で、OUTで送りますです。

USBのプロトコル

USBにて通信する際のデータフレーム構造やパケットの種類・論理プロトコルの話は、
[picfun(電子工作の実験室)]さんのこちら「USBの通信プロトコル」を見て下さい。

また、ホストとデバイス間のアプリレベルで通信される論理的な通信はエンドポイントパイプの概念で
行われます。この概念の話は、こちらを見て下さい。

下記の方に記載「コントロール転送時のプロトコル」での通信図の構成としては、

通信図の構成

データステージは、数トランザクションからなります、又はなしです。

USBの基本動作

USB機器をパソコンに接続した場合に、デバイスとホスト間でやり取りされる基本の動作
(Enumeration:エニュメレーション)です、これは全てのUSB機器で行われます、
こちら「USBのプラグ&プレイ」をまず読みましょう。
この通信はエンドポイント0(EP0 IN/OUT)を使い"コントロール転送"モードで行われる。

[USBデバイスのステート]

通常(USB規格)は@〜Eまでの状態がエニュメレーション状況に応じて推移する。
ここの"usb_device.c"ではPICが起動し初期化を行った状態時、或いはUSBバスに接続されていない
状態(プルアップ接続は無し)の時を"DETACHED_STATE"としている。

また、[SET_ADDRESS]要求パケットが来た時点では"ADR_PENDING_STATE"となっていて、
アドレスを設定した時点で"ADDRESS_STATE"へ推移します。

@ Attachedステート(USBバスに接続状態:ATTACHED_STATE)
A Poweredステート(電源投入状態:POWERED_STATE)
B Defaultステート(EP0 受信準備完了状態:DEFAULT_STATE)
C Addressステート(指定アドレス設定状態:ADDRESS_STATE)
D Configuredステート(コンフィグレーション完了状態:CONFIGURED_STATE)
E Suspendステート(サスペンド状態:省電力モードへ移行時)

Suspendステートが"usb_device.c"には無いが、
サスペンド状態の管理は"USBBusIsSuspended"フラグ変数で行っている様です。

標準デバイスリクエスト

ホストからのリクエストは、上記USBの基本動作時や設定の変更/読込み時などに、
"コントロール転送"によって行われます。

リクエストの種類は、「おなかすいたさんのwikiHP」やこちらの「5.USBシステムの動作概要12頁」
を見て下さい、今回はこの中から[GET_DESCRIPTOR] [SET_ADDRESS] [SET_CONFIGURATION]
[SET_FEATURE] [CLEAR_FEATURE]のみ実装しています。 *1)

HIDクラス固有のリクエスト

標準デバイスリクエスト以外にHIDクラス固有のリクエストが有ります。 内容はこちらのPDF16頁を参照下さい。

コントロール転送時のプロトコル

上記USBの基本動作時に行われるプロトコルの概要を記載します。(但し、PICでの内容)
コントロール転送は、エンドポイント0(EP0のIN/OUT)に対して行う事になっています。
コントロール転送のデータは8バイトづつの転送(1トランザクションあたり)となります。

こちらの「AzusaTekunoさんのHP」に、USB機器とホストの接続時の通信内容ダンプが有ります、
参考になるので一見しましょう。
 *1)

[GET_DESCRIPTOR]

この要求の1回目は、デバイスディスクリプタ[DEVICE]の送信要求が行われます、
しかしこの時はデバイスディスクリプタの最初のデータ8バイトのみしかホストは受け取らず、
8バイト読んだらステータスステージに移ってしまいます。

2回目の要求時は、
デバイスディスクリプタ[DEVICE]とコンフィギュレーションディスクリプタ[CONFIGURATION]と
ストリングディスクリプタ[STRING]の3回にわたり全てのデータを受け取ります。
但し、ストリングディスクリプタはいらない場合が有るようです、また、要求されたストリング番号の
データのみ送る事になります。
HIDクラスはこの他にレポートディスクリプタの送信要求も行われます。

GET_DESCRIPTOR通信図

@ PICはハンドシェークパケットの送受信完了時に割り込みが発生します、
  割り込みが発生するとUSTATレジスタにどこのエンドポイントで発生したか書かれているので
  それを読み取り該当するBDT(EP0 OUT)よりパケットのデータを得ます。
  (USTATレジスタは4バッファ有るので、4個とも参照する)

A パケットのデータが[SETUP]パケットで[GET_DESCRIPTOR][DEVICE]なら
  デバイスディスクリプタのデータ情報をBDT(EP0 IN)にセットします。

B ホストから[IN]パケットが来ればSIEが自動的にBDTの情報でデバイスディスクリプタのデータを
  送信します。

C ホストから[ACK]パケットが来た時点で次のデバイスディスクリプタのデータ8バイトをBDTにセット
  します、以降は繰り返し。(但し途中でステータスステージが来たら終了処理に移ります)

データを送信する場合は、トランザクションごとに[DATA0] [DATA1] [DATA0]と交互に送る事に
なっているが、これはデータの欠落防止の為です。

例えばホストに30バイト送信する場合は、[8byte][8byte][8byte][6byte]と4回に分けて送信されるが、
24バイト等の場合は、[8byte][8byte][8byte]と丁度3パケットになる。
然し、この丁度の時は最後にデータ0の空パケット([SYNC][DATA0/1][CRC])を送信する事に
USB仕様がなっている様である、だから[8byte][8byte][8byte][0byte]です。

[SET_ADDRESS]

この要求はUSBデバイス機器に対してホストからアドレスが割り振られます。
この要求が来るまではアドレス番号0(ADR0)で通信していて、これ以降は割り振られた
アドレス(例えばADR2)で通信する事になります。

また、この要求にはデータステージは有りません。

SET_ADDRESS通信図


[SET_ADDRESS]は、[SETUP]パケットのステージ時にアドレス番号も伝えられますが、
実際にアドレス番号を設定するのは、ステータスステージが正常に終了した後になります。

[SET_CONFIGURATION]

この要求が来たら、通信する為のエンドポイント(EP1)の初期化やHDIクラス固有の初期化などを
行います。
これ以降が終了後、EP1によるインターラプト転送(キーコードの送信)が出来る様になります。

SET_CONFIGURATION通信図


インターラプト転送

キーの[かな][Compose][Scroll Lock][Caps Lock][Num Lock]が押された時(キーコドを送信した時)に
1バイトでデータが送られてきます。

受信するアプリは、BDT(EP1 OUT)のSTATレジスタのUOWNビットを常に監視します、
で、SIEは受信されればBDTに情報をセットして、UOWNビットをCPU側(0)にするのでアプリ側は
受信したと判断します。
アプリは受信データを取り出し(HIDRxPacket関数で)、次の受信の為にBDT(EP1 OUT)を準備し、
UOWNビットをSIE側(1)にして置きます。

尚、この受信機能が必要なければレポートディスクリプタの記述を消せばOKと思いますがぁ....

インターラプト転送(受信)通信図


データを送信する場合は、通常は8バイト全てデータ内容0を送信して置きます、
でぇ、キーが押された時にキーコードをHIDTxPacket( )関数で通知します。
これを行うと関数はBDT(EP1 IN)に送信情報をセットし、UOWNビットをSIE側(1)にして置きます。
後は、ホストから[IN]パケットが送られて来ればSIEが自動的にBDT情報でデータを送信します、
送信後はUOWNビットをCPU側(0)にします。

[IN]パケットは”通知するデータは有りますかぁ?”ってな感じでホストから定期的(1ms)に送られて来る
ので、SIEBDT内容を見てデータをホストに送信します。

ホストから[IN]パケットSOFトークンを定期的(1ms毎)に送ってきますがぁ、
  エニュメレーション終了以降はアプリケーション側はこれに応える必要はないと思われます、
  USBモジュールのハード(SIE)が行ってくれます。
  なのでぇ、アプリは他の仕事もこなせると思いますがぁ...キーの反応がその分遅れそう。

インターラプト転送(送信)通信図

BDT(バッファディスクリプタテーブル)

受信する場合は、EP(0/1) OUTに該当するBDTエリアへ受信データの格納先アドレスやデータサイズに
ステータス情報(UOWNビットはSIE側にする)をセットして置く必要が有ります。

送信する場合は、EP(0/1) INの該当するBDTエリアへ送信するデータの格納先アドレスやデータサイズ
ステータス情報(UOWNビットはSIE側にする)をセットして置く必要が有ります。

サスペンド

UIRbits.IDLEIFの割り込みレジスタビットがONすればホストがアイドル状態に移行したと判断します。
アイドル状態は、ホストからの何らかの通信がバス上で3ms以上途絶えた場合に発生します、
と言う事はホストからは定期的に何らかのパケットを送り続けると言う事です。
HIDクラスでは EP1 IN(1ms)にパケットが定期的に送られてきます。

IDLEIFの割り込み発生で、UCONbits.SUSPNDをONにするので、PICはUSBモジュールと
PIC内部周辺回路を省電力モードにします。
アイドル状態に移行した場合はUSB規格で0.5mA(100mA時)に消費電流を抑える必要が有る為、
バスパワーで動作しているユーザ回路は考慮が必要でしょう。
回路等を制御する場合は、USBCBSuspend( )関数に記述しましょう。

ホストをアイドル状態から復帰させる場合は、USBCBSendResume( )関数をコールします、
今回のプログラムでは何処からもコールしていない、ユーザが任意に利用する。

UIRbits.ACTVIFの割込みレジスタビットがONすればホストがアイドル状態から復帰したと判断します。
割込み発生でUCONbits.SUSPNDをOFFにしてUSBCBWakeFromSuspend( )関数が
コールされるので、ユーザ回路の復帰制御等を行う場合はこの関数に記述しましょう。

USBCBSendResume( )関数を利用するには、"USBGetRemoteWakeupStatus( ) == TRUE"で
  ないとダメで、"TRUE"にするにはホストから[SET_FEATURE]パケットを受信した時の様である。
  では、[SET_FEATURE]パケットを送るタイミングは?、
  まず、PCのデバイスドライバのプロパティの「電源管理」タブ "このデバイスで、コンピュータの
  スタンバイ状態を元に戻す事が出来る様にする
" チェックボックスはチェックされている事です。
  その後、PCがスリープ状態に移行する時に[SET_FEATURE]パケットを送って来る。
  尚、"_RWU"が指定してあれば、スリープ状態から復帰する時に[CLEAR_FEATURE]パケット
  送って来ます。
  だかぁ、、"usb_config.h"ファイルの中に"#define USB_POLLING"の記述が有りこのモードで実行
  させると、PCがウエイクアップした時になぜだか”キーボードドライバ”がお亡くなりになっている。
  原因は不明だが、マウス等からウエイクアップさせた場合は大丈夫なのですがぁ.....
  まあ、USBCBSendResume( )関数を利用する場合、"#define USB_INTERRUPT"モードを進めます

《その他》

配線図  左写真は実験風景です。

 HIDクラスでのマウスとジョイスティック機器については
 ここのキーボード記述と対して変わらないので、
 実験は行っていませんが異なる部分のメモだけサクっと
 次回〔パート6〕に記述して置きます。


USB電源を利用する場合、初期は100mAの電力負荷です、コンフィギュレーションディスクリプタにて
最大500mAまで変更可能ですが [GET_DESCRIPTOR][CONFIGURATION]を問い合わせ後にホストは
提供します。なのでぇ、USBを挿した直後は100mAで有る事に注意が必要かもね。

その他の参考先HPとして、「HEROさんのPDFでUSB概説」も参考になります、見ておきましょう。

USB仕様はUSBインプリメンターズ・フォーラム(USB-IF)のこちらを参照下さい。
各デバイスクラスのドキュメントはUSB−IFのこちらを参照下さい。 *1)

18F14K50のデータシートはこちらです。

これで、FPSゲーム専用のキーボードとか出来そうな気がしてきたぞぉ!!



"v2016-11-07"用記事とリンク切れ見直し(*1) 2017/01/22


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2017 Shigehiro Kimura All Rights Reserved.