2015年4月24日金曜日

EZ-USB FX2LP を動かしてみる (26) 転送レート測定のソースコード

本連載の第一回は3月12日でした.

それ以前は、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 件のコメント:

コメントを投稿