〔パート1〕 〔パート2〕 〔パート3〕 〔パート4〕 〔パート6〕 〔マイコンのトップに戻る〕
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ではコピー動作)は
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]が押されたら
データは1バイトでビット並びは 7 6 5 4 3 2 1 0 [ 0 ] [ 0 ] [ 0 ] [ かな ] [Compose] [Scroll Lock] [Caps Lock] [Num Lock]※ レポートディスクリプタに”Feature”タグを追加すれば、EP0 OUTのコントロール転送で
上の図は、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)
データステージは、数トランザクションからなります、又はなしです。
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クラスはこの他にレポートディスクリプタの送信要求も行われます。
@ 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]は、[SETUP]パケットのステージ時にアドレス番号も伝えられますが、
実際にアドレス番号を設定するのは、ステータスステージが正常に終了した後になります。
[SET_CONFIGURATION]
この要求が来たら、通信する為のエンドポイント(EP1)の初期化やHDIクラス固有の初期化などを
行います。
これ以降が終了後、EP1によるインターラプト転送(キーコードの送信)が出来る様になります。
インターラプト転送
キーの[かな][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)に送られて来る
ので、SIEはBDT内容を見てデータをホストに送信します。
※ ホストから[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"モードを進めます
"v2016-11-07"用記事とリンク切れ見直し(*1) 2017/01/22
【きむ茶工房ガレージハウス】
Copyright (C) 2006-2020 Shigehiro Kimura All Rights Reserved.