USBのバルク転送に限って言えば、FX2LPではCPUが何ら関与せずともハードウエアの自助努力でデータ転送できるようになっています.もちろん、USBに接続した最初の初期設定の時はFX2LPのCPU(8051)がいろいろと立ち回らなくちゃいけません.しかし、初期設定さえ済んでしまえば、CPUは黙ってればOKという意味です.(データパケット毎にCPUに仕事させる使い方ももちろん可能です)
したがって、FX2LPでバルク転送できりゃOKOKとミニマムに割り切るとするならば、仕組みを勉強するべき対象は、初期設定がどうなっているの?ってところだけを解析すればひとまずOKOKってコトになる、、、、それが今わたしが置かれた状況であります.
ちなみに、初期設定以外にはCPUは何も仕事してないじゃん、というのはFX2LPの内部状態をモニタして確認済みです.バルク転送時には割り込みなんか全く発生しませんから.(FIFO overflowにでもなれば割り込みが生じるかもですが、それはさておくとして)
【USBデバイスをPCに挿した時に起こるコトの概要】
PCにUSBデバイスを挿した時、PCはプラグインプレイでUSBデバイスにマッチした①デバイスドライバを選んで、そのデバドラを利用してUSBデバイスの②素性を知り、USBデバイスに「③お前はデバイスアドレスx番な」と名付け、、、、といった作業をします.その後データ伝送を始めます.
ここで、①②③をPCが実施するには、USBデバイスの素性をPCが知る必要があります.「USBデバイスの素性」は誰が持っているのかというと、「USBデバイス」が持っています.だからPCはUSBデバイスに問います.
「お主は何物だ?」
【USBデバイスが持っている「素性情報」=ディスクリプタ】
ディスクリプタは10種類ぐらいあるんですけど、FX2LPのディスクリプタの主なのはこんなです.(なんでアセンブラコードなんだ?という疑問には後で答えます)
1.デバイスディスクリプタ 全部で18BYTE
接続時に最初にPCが知りたがる「素性」がこれ.エントリはいろいろあるけど、とくに重要なのは VendorIDとProductIDです.このVID/PIDをPCが知ると、PCはVID/PIDを検索キーにしてデバイスドライバを探索します.PCのデバドラに***.infというファイルがあり、infファイルの中にVID/PIDが書かれていて、一致したinfファイルから芋づる式にPCはデバドラを読み込みます.
DeviceDscr:
db DSCR_DEVICE_LEN ;; Descriptor length
db DSCR_DEVICE ;; Decriptor type
dw 0002H ;; Specification Version (BCD)
db 00H ;; Device class
db 00H ;; Device sub-class
db 00H ;; Device sub-sub-class
db 64 ;; Maximum packet size
dw 0B404H ;; Vendor ID
dw 0410H ;; Product ID (Sample Device)
dw 0000H ;; Product version ID
db 1 ;; Manufacturer string index
db 2 ;; Product string index
db 0 ;; Serial number string index
db 1 ;; Number of configurations
2.コンフィグディスクリプタ 全部で9BYTE
これはUSB2.0接続時のHIGH SPEEDモードの場合.末尾には「要求電流値」がある.
HighSpeedConfigDscr:
db DSCR_CONFIG_LEN ;; Descriptor length
db DSCR_CONFIG ;; Descriptor type
db TotalLength(LSB) ;; エンドポイントディスクリプタ末尾までのBYTE数
db TotalLength(USB) ;; エンドポイントディスクリプタ末尾までのBYTE数
db 1 ;; Number of interfaces
db 1 ;; Configuration number
db 0 ;; Configuration string
db 10000000b ;; Attributes
db 50 ;; Power requirement (div 2 ma)
3.ストリングディスクリプタ 任意の長さ
PCのデバイスマネージャに表示されるデバイス名文字列が記述されています.
StringDscr1:
db StringDscr1End-StringDscr1 ;; String desc. length
db DSCR_STRING
db 'C',00
db 'y',00
db 'p',00
db 'r',00
db 'e',00
db 's',00
db 's',00
4.エンドポイントディスクリプタ EPの素性が記述されています
Endpoint Descriptor
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT ;; Descriptor type
db 02H ;; Endpoint number, and direction
db ET_BULK ;; Endpoint type
db 00H ;; Maximun packet size (LSB)
db 02H ;; Max packect size (MSB)
db 00H ;; Polling interval
ディスクリプタは他にもあります.
ここで疑問
Q: デバイスディスクリプタのVID/PIDさえPCが知れれば、他のディスクリプタは不要ではないか? なぜなら、PCのデバドラを記述する人はEZ-USB FX2LPの素性を完璧に知っているはずだから
A: これ、EZ-USB FX2LPの内部状態をモニタした結果、ある程度言えてます.
訂正:
ホストはエンドポイントディスクリプタも読んでいると判りました.ただし、その読み方に癖があります.エンドポインタディスクリプタを明示的に読んでません.コンフィグディスクリプタを読むときに、後続のインターフェースディスクリプタと、エンドポイントディスクリプタを一括してホストは読んでいます.そのために何BYTE読めばよいかは、コンフィグディスクリプタの、TotalLengthというフィールドに記述されています.
ちなみに、FX2LPのプログラムを読むと、インターフェースディスクリプタの送信要求に答えるルーチンを装備していません.エンドポイントディスクリプタに対しても同様です.癖があるなぁ.
【USBのデータ流れ】
ホストがデータ統制権限を持っています.つまりこういうコト.
1) ホスト→デバイスへのデータ流(OUT) = ホストが自由に発言できる
2) デバイス→ホストへのデータ流(IN) = ホストの命令によってデバイスが発言できる
1でホストが発言する時の文脈はこうです.
「デバイスアドレスx番の者よ、以下のデータを受信し、何番のFIFO(EPy)へ積め」
2でホストがデバイスの発言を許すときの文脈はこうです.
「デバイスアドレスx番の者よ、何番のFIFO(EPy)についてZZZバイト喋りなさい」
USBケーブルがこういった命令専用の信号線を持っているわけではなく、パケット通信によって、ユーザーデータなのか、制御命令なのかを区別しています.
ここで疑問
Q: ZZZバイト喋れ、と命じるPCは、何の根拠でZZZバイトと定めるのだろうか? ストリングディスクリプタは何バイトあるかわからんじゃないですか?
A: デバイスディスクリプタ=18バイト.コンフィギュディスクリプタ=9バイトと決まっているので、こいつらは決め打ちできますので無問題.でもストリングディスクリプタのバイト数は既知でない.解答はずーっと下の方で書きますが、最初にレングスを調査してますね.
【ディスクリプタのコード実装はどうなっているか?】
まずはFX2.hが重要です.各種ディスクリプタが構造体として定義されています.
typedef struct // Device Descriptor
{
BYTE length; // Descriptor length ( = sizeof(DEVICEDSCR) )
BYTE type; // Decriptor type (Device = 1)
中略
WORD vendor_id; // Vendor ID
WORD product_id; // Product ID
WORD version_id; // Product version ID
中略
}DEVICEDSCR;
FX2.hには、ディスクリプタ構造体の実体がメモリに確保されてもいます.「code」というのはコード領域に(決め打ちで)領域確保せよという意味です.
extern code DEVICEDSCR DeviceDscr;
extern code CONFIGDSCR HighSpeedConfigDscr;
extern code STRINGDSCR StringDscr;
ここで疑問
Q: 領域(メモリ)は確保されたディスクリプタ構造体ですが初期化はどこでやってるのか?
A: 述べたアセンブラコードがそれだったのです.dscr.a51というファイルに記述されています.
FX2LPソースコードへのディスクリプタの記述は以上のとおりでおしまい.
【初期化時にPCとEZ-USB FX2LPがやっているコトの概要】
そろそろ核心の話題です.
がっ、その前に予備知識を.
パケットには、データパケットもあれば、制御信号のパケットもあります.
制御信号パケットの一種であるSETUPパケットには、8バイトの制御情報が付随します.PCからデバイス(ここではFX2LP)へ8バイトが送付されます.FX2LPではその8バイトをSETUPDAT[0]~[7]というレジスタへ自動的に格納します.SETUPDAT[0]~[7]によって、ディスクリプタを含む様々な制御操作を行います.
ここで疑問
Q: SETUPパケットにデータエラーがあったらどうなるの?
A: FX2LPのハードウエアの責任で、再送リクエストなどをやってくれるとRef. manualに書かれています.
FX2LPは、SETUPパケットを受信したらSUDAV割り込みを発生させるように作られています.(SUDAV=SetUp Data AVailableの略)
SUDAV割り込みがかかったら、SETUPDAT[1]の数値によって、下表のように制御内容によって分岐するようソースコードがプログラミングされてます.
わたしが作った内部状態モニタは、SUDAV割り込みに罠を仕掛けていると思えばだいたい合っています.上表にはたくさんの行がありますけど、実機をモニタして登場したのは次の3種類だけでした.
0x06 Get Descriptor
0x00 Get Status
0x09 Set Cofiguration
「06 Get Descriptor」は、各種のディスクリプタを送信せよとPCが言ってます.
「00 Get Status」は、デバイス(FX2LP)のステータスを送信せよとPCが言ってます.具体的には、Remote Wake UpとSelf powerについてのenable/disable状況を送信するソースコードになっていますが、その理由はよくわかりません.
「09 Set Cofiguration」は、HIGH Speed mode(USB2.0)に設定しています.
しかしながら、「0x05 Set Address」 を受け取っていないのは信じ難いです.そこで上表をよ~く見ると、0x05だけはSUDAV割り込みがかからないので、モニタできなかったと推測します.なので、FX2LP初期化時に登場するのは、00,05,06,09の4種類なんだと思っておくことにします.
ここで疑問
Q: 何種類ものディスクリプタを問われる「06 Get Descriptor」ではどうやって何種類ものディスクリプタを区別・指定しているのだろうか?
A: SETUPDAT[3]を参照してどのディスクリプタかを決めています.下表がその番号対応表.
Q: いくつものディスクリプタ構造体へのアドレッシングはどうしてるの?
A: main()の最初で、ポインタに構造体アドレスを代入している.そのポインタを利用している.特に奇抜なコトはやってません.
pDeviceDscr = (WORD)&DeviceDscr;
pDeviceQualDscr = (WORD)&DeviceQualDscr;
pHighSpeedConfigDscr = (WORD)&HighSpeedConfigDscr;
pFullSpeedConfigDscr = (WORD)&FullSpeedConfigDscr;
pStringDscr = (WORD)&StringDscr;
さらに疑問
Q: FX2LPは、ディスクリプタをFIFOにコピーしてからPCへ送信しているの?
A: ちがいます.デバイスディスクリプタをPCへ送信するソースコードはこのたった2行だけです.
SUDPTRH = MSB(pDeviceDscr);
SUDPTRL = LSB(pDeviceDscr);
この2行はなにをやっているのか?
1) SUDPTRH/Lは、FX2LPのレジスタです.このレジスタにデバイスディスクリプタ構造体のアドレスを書きます.以後はFX2LPのハードウエアにお任せです.
2) FX2LPのハードウエアは、SUDPTRLにwriteされたら、PCへのデータ転送を開始する
3) データ転送するバイト数は、SETUPDAT[7]~[8]で決める. (ここでディスクリプタ長がSETUPDAT[7:8]未満なら短い方を採用するとRef. manualに書かれている)
4) データエラー処理もFX2LPのハードウエアが実行する.
さらに疑問
Q: 「00 Get Status」でも同様の送信をしているのか?
A: Get Statusでは別の方法です.というかFIFOに積んで送信するという基本動作で処理しています.ソースコードは鬼のように簡単でたったの4行です.
EP0BUF[0] = ((BYTE)Rwuen << 1) | (BYTE)Selfpwr;
EP0BUF[1] = 0;
EP0BCH = 0;
EP0BCL = 2;
この4行は何をやっているのか?
1) PCへ送信したいステータス情報は2バイトのみである
2) その2バイトをエンドポイント0(EP0)のFIFOに積む (EP0BUFがFIFO)
3) 送信バイト数をEP0BCH/Lへ書き込む (EP0BCH/Lはレジスタ)
4) EP0BCLに書き込まれたらFX2LPのHWが自動的に2バイトをPCへ送信する
5) データエラー処理もFX2LPのハードウエアが実行する
【初期化時にPCとFX2LPがやっているコトのモニタ結果】
↓自作の内部状態モニタの出力結果がこれです.ただし、上で述べた通り、セットアドレスは見逃しています! orz
high speed(2.0) initialized
ISR_Ures
ISR_Susp
ISR_Ures
ISR_Highspeed
SETUPDAT 80 06 00 01 00 00 40 00 ===> Get Device Descriptor 64byte
ISR_Ures
ISR_Highspeed
SETUPDAT 80 06 00 01 00 00 12 00 ===> Get Device Descriptor 18byte
SETUPDAT 80 06 00 02 00 00 ff 00 ===> Get Config Descriptor 255byte
SETUPDAT 80 06 00 03 00 00 ff 00 ===> Get String Descriptor 255byte (Index0)
SETUPDAT 80 06 02 03 09 04 ff 00 ===> Get String Descriptor 255byte (Index2)
SETUPDAT 80 06 00 01 00 00 12 00 ===> Get Device Descriptor 18byte
SETUPDAT 80 06 00 02 00 00 09 00 ===> Get Config Descriptor 9byte
SETUPDAT 80 06 00 02 00 00 20 00 ===> Get Config Descriptor 32byte
SETUPDAT 80 00 00 00 00 00 02 00 ===> Get Device Status 2byte
SETUPDAT 00 09 01 00 00 00 00 00 ===> SC_SET_CONFIGURATION
SETUPDAT 80 06 00 01 00 00 12 00 ===> Get Device Descriptor 18byte
SETUPDAT 80 06 00 03 00 00 02 00 ===> Get String Descriptor 2byte (Index0)
SETUPDAT 80 06 00 03 00 00 04 00 ===> Get String Descriptor 4byte (Index0)
SETUPDAT 80 06 01 03 09 04 02 00 ===> Get String Descriptor 2byte (Index1)
SETUPDAT 80 06 01 03 09 04 10 00 ===> Get String Descriptor 16byte (Index1)
SETUPDAT 80 06 02 03 09 04 02 00 ===> Get String Descriptor 2byte (Index2)
SETUPDAT 80 06 02 03 09 04 0e 00 ===> Get String Descriptor 14byte (Index2)
SETUPDAT 80 06 02 03 09 04 02 00 ===> Get String Descriptor 2byte (Index2)
SETUPDAT 80 06 02 03 09 04 0e 00 ===> Get String Descriptor 14byte (Index2)
これを一行づつチェックしたのが以下です.
↓FX2LPが、自分でUSB2.0に初期化しています.ふ~んそれでも良いんだ? 最初はUSB1.1で起動するのかと思っていたんですが...
USB high speed(2.0) initialized
↓割り込みが4つ連続しています.
ISR_Ures USB reset
ISR_Susp USB suspend
ISR_Ures USB reset
ISR_Highspeed High Speed (USB2.0に設定してるんだろう)
↓デバイスディスクリプタを64バイト送信しろとPCは無茶言ってますが、デバイスディスクリプタは18バイトなので、FX2LPが送信するのは18バイトになるはず.この短縮動作については上述しました.
SETUPDAT 80 06 00 01 00 00 40 00 ===> Get Device Descriptor 64byte
↓なぜか、resetとHigh Speedをまたやってます.なんでだろ?
ISR_Ures USB reset
ISR_Highspeed High Speed
↓デバイスディスクリプタを18バイト送信しろとPCは言ってます.どうして2度目? どうして今度はキチッと18バイト?
SETUPDAT 80 06 00 01 00 00 12 00 ===> Get Device Descriptor 18byte
↓コンフィギュディスクリプタを256バイト送信しろとPCは言ってます.コンフィギュディスクリプタは9バイトと判っていると思うんですけどね.FX2LPは9バイトに短縮して送信します.
SETUPDAT 80 06 00 02 00 00 ff 00 ===> Get Config Descriptor 255byte
↓ストリングディスクリプタ(1つ目)を256バイト送信しろとPCは言ってます.
SETUPDAT 80 06 00 03 00 00 ff 00 ===> Get String Descriptor 255byte (Index0)
↓ストリングディスクリプタ(3つ目)を256バイト送信しろとPCは言ってます.0409は英語指定の意味がありますが、英語指定されたからといってFX2LPのプログラムは何もしてません.
SETUPDAT 80 06 02 03 09 04 ff 00 ===> Get String Descriptor 255byte (Index2)
↓デバイスディスクリプタを18バイト送信しろとPCは言ってます.どうして3度目?
SETUPDAT 80 06 00 01 00 00 12 00 ===> Get Device Descriptor 18byte
↓コンフィギュデスクリプタを9バイト送信しろとPCは言ってます.なぜか2度目.
SETUPDAT 80 06 00 02 00 00 09 00 ===> Get Config Descriptor 9byte
↓コンフィギュデスクリプタを9バイト送信しろとPCは言ってます.なぜか3度目.
SETUPDAT 80 06 00 02 00 00 20 00 ===> Get Config Descriptor 32byte
↓デバイスステータスを2バイト送信しろとPCは言ってます.
SETUPDAT 80 00 00 00 00 00 02 00 ===> Get Device Status 2byte
↓ソースコードではHighSpeedに設定しているのですが、SETUPDAT[2]=1の意味は不明です.
SETUPDAT 00 09 01 00 00 00 00 00 ===> Set Configuration
↓デバイスディスクリプタを18バイト送信しろとPCは言ってます.どうして4度目?
SETUPDAT 80 06 00 01 00 00 12 00 ===> Get Device Descriptor 18byte
↓ストリングディスクリプタ(1つ目)を2バイト送信しろとPCは言ってます.この2バイトをチェックすると、1つ目のストリングディスクリプタの長さを知るコトができます.
SETUPDAT 80 06 00 03 00 00 02 00 ===> Get String Descriptor 2byte (Index0)
↓1つ目のストリングディスクリプタの長さ=4だと知ったPCは、今度は4バイト送信しろと言ってます.
SETUPDAT 80 06 00 03 00 00 04 00 ===> Get String Descriptor 4byte (Index0)
ちなみに1つ目のストリングディスクリプタはこんなです.確かに4バイトです.
StringDscr0:
db StringDscr0End-StringDscr0 ;; String descriptor length
db DSCR_STRING
db 09H,04H
SETUPDAT 80 06 01 03 09 04 02 00 ===> Get String Descriptor 2byte (Index1)
SETUPDAT 80 06 01 03 09 04 10 00 ===> Get String Descriptor 16byte (Index1)
SETUPDAT 80 06 02 03 09 04 02 00 ===> Get String Descriptor 2byte (Index2)
SETUPDAT 80 06 02 03 09 04 0e 00 ===> Get String Descriptor 14byte (Index2)
SETUPDAT 80 06 02 03 09 04 02 00 ===> Get String Descriptor 2byte (Index2)
SETUPDAT 80 06 02 03 09 04 0e 00 ===> Get String Descriptor 14byte (Index2)
2つ目のストリングディスクリプタはこうなっていて、確かに16バイトです.
StringDscr1: db StringDscr1End-StringDscr1 ;; String descriptor length
db DSCR_STRING
db 'C',00
db 'y',00
db 'p',00
db 'r',00
db 'e',00
db 's',00
db 's',00
3つ目のストリングディスクリプタはこうなっていて、確かに14バイトです.
StringDscr2: db StringDscr2End-StringDscr2 ;; Descriptor length
db DSCR_STRING
db 'E',00
db 'Z',00
db '-',00
db 'U',00
db 'S',00
db 'B',00
以上で、初期化時にやっているコトはだいたいわかりました.
#PCのソフトを作れないとこの先へ進むのは苦しい.visual studio2008を引っぱり出すかな?
その15へ その17へ
かしこ
INDEXページへ
https://hirasakausb.blogspot.com/2019/03/ez-usb-fx2lp-index.html
そういえば VID PID で検索したところにも書いてありました。
返信削除www.cypress.com/?docID=43116