それ以前は、PCのインターフェースというと、COMポートを扱った経験しかなかったわたしでした.USBについてはどうだったかというと、書籍をチラ読みして、ディスクリプタとかエンドポイントとかFIFOとかpipeとかという用語は目にしていましたが、ディスクリプタを格納してるのがデバイスなのかPCなのかすら知らない人でありました.
それが今や、USBデバイスのFIFO越しにある回路の制御ならできますってところまで進化したのですから、思えば遠くまで来たものだと遠い目をしておこう.(HIDはまだ全然わからないけど)
今回は、前回やった転送レート測定システムのソースコード解説などです.
【全体構成】
データ流はIN方向ですから、PC←EZ-USB←FPGAの方向へ流れます.FPGAが発生したデータをPCにまで到達させるのが目的です.
ソースコードはホストアプリ、EZ-USB、FPGAの3つ存在するのですが、FPGAのソースなんか解説しても無駄なのでFPGAの解説は割愛します.
FPGAの機能
1) IFCLK発生
48MHzをEZ-USBのIFCLKピンへ供給する.IFCLKは、スレーブFIFOへのwriteでのみ使用され、それ以外の回路はEZ-USBが自力で発生した別の48MHzで動きます.48MHzにした理由は、EZ-USBの最高周波数だからです.
2) データ発生
32bitのインクリメンタルデータを発生し、FD[7:0]へ8bitに区切って出力します.WRパルスでFIFOへ1BYTE書き込みます.最大書き込み速度48MBYTE/Secです.FD[7:0]ではなくFD[15:0]にするコトもEZ-USBは出来ますが、今回の実験では配線が簡単なFD[7:0]にしました.
3) FIFO full(FF)の監視
FIFOはすぐに満杯になってしまいます.するとEZ-USBはFFを出力します.FPGAはFFを受信したらそれ以上FIFOへの書き込みをせず、FFが消えるまで待ちます.
EZ-USBの機能
・USB2.0 EP6 IN BULK転送
・FIFOは、512BYTEx4本 または 1024BYTEx2本 のいずれか
・スレーブFIFO関連外部ピン (FPGAへ配線するのは太字の信号のみ)
-IFCLKは、FPGAから48MHzをもらう
-FD[15:0]のうちFD[7:0]しかここでは使わない
-FFは、外部ピンCTL1=FLAGBへ出てくる (負論理)
-WRは、外部ピンRDY1=SLWRへ入力する (負論理)
-EP6 FIFOの指定は、FIFOADR[1:0]=2 で指定する (0=EP2,1=EP4,2=EP6,3=EP8である)
-SLOEはOUTの時に使うので、ひとまずここでは使わないで de-assertのまま (負論理)
-PKTENDは、ひとまずここでは使わないで de-assertのまま (負論理)
ホストアプリの機能
・EP6へ、IN要求を出す
・EP6からのデータを受信する
・受信したBYTE数を積算し、所定のBYTE数を超えたらEXITする (所要時間を表示する)
・HDDへのwriteはしない.CRCチェック等もしない. (ホスト処理能力要因を排除するため)
【EZ-USBのソースコード】
EZ-USB開発環境のフォルダはこちらです. FIFO512BYTE版 FIFO1024BYTE版
【512BYTE版】
以下ではまず、512BYTE版の説明をします.
このコードは、Cypressのサンプルの「bulkloop」というのをベースにしています.
dscr.a51
このファイルにはディスクリプタの中身が記述されています.(構造体宣言はFx2.hにある)
EP6 INを使うのが主目的ですが、EP2 OUTも活かしてあります.EP4,EP8は殺しました.
そのために少し変更しました.
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 ホストアプリで使う情報 04B4
dw 0410H ;; Product ID (Sample Device) ホストアプリで使う情報 1004
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
中略
;; Interface Descriptor インターフェースディスクリプタ
db DSCR_INTRFC_LEN ;; Descriptor length
db DSCR_INTRFC ;; Descriptor type
db 0 ;; Zero-based index of this interface
db 0 ;; Alternate setting
db 2 ;; Number of end points EPを2ヶにしました
db 0ffH ;; Interface class
db 00H ;; Interface sub class
db 00H ;; Interface sub sub class
db 0 ;; Interface descriptor string index
;; Endpoint Descriptor EP2のディスクリプタ
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT ;; Descriptor type
db 02H ;; Endpoint number, and direction EP2 OUTの意味
db ET_BULK ;; Endpoint type バルク転送
db 00H ;; Maximun packet size (LSB)
db 02H ;; Max packect size (MSB) 512バイト
db 00H ;; Polling interval
;; Endpoint Descriptor EP4はコメントアウトで使わなくする
; db DSCR_ENDPNT_LEN ;; Descriptor length
; db DSCR_ENDPNT ;; Descriptor type
; db 04H ;; 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
;; Endpoint Descriptor EP6のディスクリプタ
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT ;; Descriptor type
db 86H ;; Endpoint number, and direction EP6 INの意味
db ET_BULK ;; Endpoint type バルク転送
db 00H ;; Maximun packet size (LSB)
db 02H ;; Max packect size (MSB) 512バイト
db 00H ;; Polling interval
;; Endpoint Descriptor EP8はコメントアウトで使わなくする
; db DSCR_ENDPNT_LEN ;; Descriptor length
; db DSCR_ENDPNT ;; Descriptor type
; db 88H ;; 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
以下略
fw.c
このファイルにはmain()があります.bulkloopサンプルから何も変更する部分がありません.
main()の構造を単純化するとこうなっています.
void main(void)
{
TD_Init(); 様々な初期化 カスタマイズする必要がある
デバイスディスクリプタの先頭アドレスを採取しているところ
pDeviceDscr = (WORD)&DeviceDscr;
pDeviceQualDscr = (WORD)&DeviceQualDscr;
pHighSpeedConfigDscr = (WORD)&HighSpeedConfigDscr;
pFullSpeedConfigDscr = (WORD)&FullSpeedConfigDscr;
pStringDscr = (WORD)&StringDscr;
割り込みenable
EZUSB_IRQ_ENABLE(); // Enable USB interrupt (INT2)
EZUSB_ENABLE_RSMIRQ(); // Wake-up interrupt
EA = 1; // Enable 8051 interrupts
while(TRUE) メインループ
{
データ転送に関与する大事なお仕事をするルーチンですが、ここではこの関数の中身は空っぽです.USB転送にCPUは介在しないAUTOMODEであるため.
TD_Poll();
if(GotSUD) セットアップコマンドを受信したか?
{
SetupCommand(); セットアップ処理をする(ディスクリプタ送信など)
}
}
}
bulkloop.c
TD_Init()と、TD_poll() をカスタマイズする必要があります.
ここでのEZ-USBの使い方は、AUTOMODEといって、データ通信にCPUが何ら介在しないモードです.なので、初期化だけがCPUの作業と言っても過言ではありません.なので、TD_Init()をいろいろと変更しました.bulkloopサンプルの元のコードではEP2,4,6,8が全て活かされていたのですが、EP4,8を殺したのが大きな変更点です.
void TD_Init(void)
{
CPUCS = ((CPUCS & ~bmCLKSPD) | bmCLKSPD1) ; CPU clock 48MHz
IFCONFIG = 0x43; 1)IFCLK=外部入力 2)FIFO同期モード 3)スレーブFIFOモード
SYNCDELAY; お定まりの遅延時間
EP1の設定
EP1OUTCFG = 0xA0; OUT バルク転送モード
EP1INCFG = 0xA0; IN バルク転送モード
SYNCDELAY;
EP2の設定
EP2CFG = 0xA0; OUT バルク転送モード FIFO=512BYTE 4本
SYNCDELAY;
EP4の設定
EP4CFG = 0x00; 殺しておく
SYNCDELAY;
EP6の設定
EP6CFG = 0xE0; IN バルク転送モード FIFO=512BYTE 4本
SYNCDELAY;
EP8の設定
EP8CFG = 0x00; 殺しておく
SYNCDELAY;
FIFOをリセットする
FIFORESET = 0x80; 全FIFOリセット(たぶん)
SYNCDELAY;
FIFORESET = 0x82; EP2 FIFOリセット
SYNCDELAY;
FIFORESET = 0x84; EP4 FIFOリセット (死んでいるが念のため)
SYNCDELAY;
FIFORESET = 0x86; EP6 FIFOリセット
SYNCDELAY;
FIFORESET = 0x88; EP8 FIFOリセット (死んでいるが念のため)
SYNCDELAY;
FIFORESET = 0x00; FIFOリセット終了 (たぶん)
SYNCDELAY;
OUTPKTEND = 0x82; 何かよく判らんが、EP2 FIFO 4本を全て初期化するために4回やる
SYNCDELAY;
OUTPKTEND = 0x82;
SYNCDELAY;
OUTPKTEND = 0x82;
SYNCDELAY;
OUTPKTEND = 0x82;
SYNCDELAY;
EP2をAUTOMODEにする (及びFD[7:0]にも設定している)
EP2FIFOCFG = 0x10; // EP2 is AUTOOUT=1, AUTOIN=0, ZEROLEN=0, WORDWIDE=0
SYNCDELAY;
EP6をAUTOMODEにする (及びFD[7:0]にも設定している)
EP6FIFOCFG = 0x0C; // EP6 is AUTOOUT=0, AUTOIN=1, ZEROLEN=1, WORDWIDE=0
SYNCDELAY;
EP6 FIFOに512BYTE溜まったら送信する意味だと思う
EP6AUTOINLENH = 0x02; // Auto-commit 512-byte packets
SYNCDELAY;
EP6AUTOINLENL = 0x00;
SYNCDELAY;
EP2 AUTOMODEスタート FIFO4本なので4回やる
EP2BCL = 0x80;
SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
FIFOアクセスのためのオートポインタスタート (DMAに似た仕組み)
AUTOPTRSETUP |= 0x01;
}
次はTD_Poll()ですが、元のコードではいろいろと仕事をしていましたけれど、不要なので中身を全部削除しました.AUTOMODEだからデータ転送に逐一CPUが関与する必要が無いからです.
void TD_Poll(void){;}
【1024BYTE版】
つぎに、1024BYTE版にするために何処を変更したかを記します.
512BYTE版と1024BYTE版ではFIFOの構造が異なります.(EZ-USBの都合で)
<512BYTE版>
EP2 FIFO長512BYTE 4本
EP6 FIFO長512BYTE 4本
<1024BYTE版>
EP2 FIFO長1024BYTE 2本
EP6 FIFO長1024BYTE 2本
このコトをソースコードに反映させる必要があります.
dscr.a51
;; Endpoint Descriptor EP2のディスクリプタ
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 04H ;; Max packect size (MSB) 1024バイト
db 00H ;; Polling interval
;; Endpoint Descriptor EP6のディスクリプタ
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT ;; Descriptor type
db 86H ;; Endpoint number, and direction
db ET_BULK ;; Endpoint type
db 00H ;; Maximun packet size (LSB)
db 04H ;; Max packect size (MSB) 1024バイト
db 00H ;; Polling interval
bulkloop.c
void TD_Init(void)
{
EP2の設定
EP2CFG = 0xAA; OUT バルク転送モード FIFO=1024BYTE 2本
EP6の設定
EP6CFG = 0xEA; IN バルク転送モード FIFO=1024BYTE 2本
FIFOをリセットする
OUTPKTEND = 0x82
SYNCDELAY;
OUTPKTEND = 0x82;
SYNCDELAY;
// OUTPKTEND = 0x82;; EP2 FIFO 2本になってので初期化は2回でいいだろう
// SYNCDELAY;
// OUTPKTEND = 0x82;
// SYNCDELAY;
EP6 FIFOに1024BYTE溜まったら送信する意味だと思う
EP6AUTOINLENH = 0x04; // Auto-commit 1024-byte packets
SYNCDELAY;
EP6AUTOINLENL = 0x00;
SYNCDELAY;
EP2 AUTOMODEスタート FIFO2本なので2回でいいだろう
EP2BCL = 0x80;
SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
// EP2BCL = 0x80;
// SYNCDELAY;
// EP2BCL = 0x80;
// SYNCDELAY;
【Linuxホストのソースコード】
ソースコードはこちらです.libusb0.1が必要です.コンパイルするには、こんな感じでどうぞ.
gcc fx2e.c -lusb -lm -o fx2e
EZ-USBをLinuxマシンに接続してから、fx2eをrunさせます.コマンドオプションがあります.使い方
転送レートを測定するための最もフツーなコマンドはこうです.10^8乗=100MBをEP6から受信して、所要時間を表示します.
time fx2e 8
すると次のような表示が出ます.device serching
device found VID=04b4 PID=1004
EP6 BULK IN (completed 512B TTL134217728B REQ134217728B)
real 0m32.795s これが所要時間でよいと思う
user 0m0.744s
sys 0m5.996s
100MBは正確には134MBですから、転送レートは、134÷32.8=4MB/Sec と計算されます.
ちなみにここでは、512BYTE/FIFO、512BYTE/IN要求 で動かしました.
別の条件で転送レートを測定してみます.
time fx2e 7 n n 4096
こうすると、10^7=10MBを、4096BYTE/IN要求 でEP6から受信しました.結果は、1.539Secかかったコトになり、10MBは正確には16.8MBですから、16.8÷1.539=10.9MB/Secと計算されます.device serching
device found VID=04b4 PID=1004
EP6 BULK IN (completed 4096B TTL16777216B REQ16777216B)
real 0m1.539s
user 0m0.032s
sys 0m0.092s
なお、FIFO長はEZ-USBのプログラムで決まりますので、ホストからでは変更できません.(やればできるがやってない)
他の使い方では、vオプションをつけると、
fx2e 4 v
途中経過がたくさん表示されますが、表示動作の分転送レートは遅くなります.device serching
device found VID=04b4 PID=1004
EP6 BULK IN (512B TTL512B REQ16384B) 4E 48 E4 2F 4F 48 E4 2F
EP6 BULK IN (512B TTL1024B REQ16384B) CE 48 E4 2F CF 48 E4 2F
EP6 BULK IN (512B TTL1536B REQ16384B) 4E 48 E4 30 4F 48 E4 30
中略
EP6 BULK IN (512B TTL14848B REQ16384B) 4E 48 E4 3D 4F 48 E4 3D
EP6 BULK IN (512B TTL15360B REQ16384B) CE 48 E4 3D CF 48 E4 3D
EP6 BULK IN (512B TTL15872B REQ16384B) 4E 48 E4 3E 4F 48 E4 3E
EP6 BULK IN (completed 512B TTL16384B REQ16384B)
ソースコード
「USB2.0 インターフェース設計術 電波新聞社」のサンプルコードを流用させていただきました.
デバイスディスクリプタで確認したVIDとPIDをここで利用します.EZ-USBをサーチするキーとして使われますので必須な情報です.
#define USB_VENDOR 0x04B4 /* ベンダID(Cypress) */
#define USB_PRODUCT 0x1004 /* プロダクトID */
int main(int argc, char *argv[])
{
これはlibusbの初期化のお決まりの4行なので深く考えても仕方なし
usb_init(); /*内部構造のセットアップ処理 */
usb_find_busses(); /*本システム上の全てのバスを見つける*/
usb_find_devices(); /*各々のバスで全てのUSBデバイスを発見します。*/
bus=usb_get_busses(); /*USBバスのリストを取得する*/
USBデバイスのVID/PIDをサーチして、EZ-USBを見つける
printf("device serching\r\n");
for(bus=bus; bus; bus=bus->next){
for(dev=bus->devices; dev; dev=dev->next) {
if(d) print_dev_descr(&(dev->descriptor));
if( (dev->descriptor.idVendor==USB_VENDOR) && (dev->descriptor.idProduct == USB_PRODUCT) ){
find_dev= dev; VIDとPIDが合致したデバイスのコンテキストを保存
goto find_devicelabel;
}
}
}
中略 エラー処理
EZ-USBをopenし、ハンドルを入手する
h_dev=usb_open(find_dev);
中略 エラー処理
CONFIGURATION=1にセットする.これにどれだけ意味があるのかは謎.
if( usb_set_configuration(h_dev,1)<0 ){ 中略エラー処理 }
インターフェースを要求.たぶんだが、Linuxデバドラからlibusbに制御を奪っているのではないだろうか?
if( usb_claim_interface(h_dev,find_dev->config->interface->altsetting-> bInterfaceNumber)<0 ) { 中略エラー処理 }
fx2eの”w”コマンドオプションだったら、EP2へお試しOUTを1回だけやる (何度もやるとFIFOがoverflowしてしまうので何度もやらない)
if(w){
ret=usb_bulk_write(h_dev,2,dat,size,1000);
中略 エラー処理
}
usb_bulk_write(h_dev,2,dat,size,1000);は大切な関数なので引数を解説すると、
第1引数 デバイスへのハンドル、open()で得たもの
第2引数 EP番号、ここではEP2へOUTしている
第3引数 OUTするデータを格納した配列
第4引数 OUTするバイト数
第5引数 timeoutまでのmSec
fx2eのコマンドオプションで10^xが指定されたので、最寄りの2^xを探しておく.後で使う.
while(1){
if(pow(2,i)>pow(10,digit)) break;
else i++;
}
double reqbyte=pow(2,i); EP6から受信したい総BYTE数
double ttlbyte=0; EP6から受信した途中経過BYTE数
EP6 INループ
while(1){
EP6 BULK INデータの受信
ret=usb_bulk_read(h_dev,6,dat,size,1000); 引数は上記のusb_bulk_write()と同じ
中略 エラー処理
ttlbyte=ttlbyte+ret; 受信BYTE数加算
受信したい総BYTE数を超えたらexit
if(ttlbyte>=reqbyte) {
printf("EP6 BULK IN (completed %dB TTL%dB REQ%dB)\n",ret,(int)ttlbyte,(int)reqbyte);
return 0;
}
fx2eの”w”コマンドオプションだったら、INデータの詳細を表示する
if(v){
printf("EP6 BULK IN (%dB TTL%dB REQ%dB) ",ret,(int)ttlbyte,(int)reqbyte);
for(i=0;i<8;i++)printf("%02X ",dat[i]);
printf("\n");
}
}
EZ-USBをcloseする
if (usb_close(h_dev)<0) { 中略エラー処理 }
その25へ その27へ
かしこ
INDEXページへ
https://hirasakausb.blogspot.com/2019/03/ez-usb-fx2lp-index.html
0 件のコメント:
コメントを投稿