<カメレオンUSB FX2のアプリケーション>

「カメレオンUSB FX2」のアプリケーションを開発するためには

の3つが必要になります。

アプリケーションの流れとしては、MAX2でデータを取得して、PCに転送し、PCでデータを加工して利用する。(あるいはその逆)というのが一般的な「カメレオンUSB FX2」のアプリケーションの流れになると思います。

FX2LPのCPUである8051は8bitで、動作クロックも48MHzと貧弱ですので、数100Mbpsという高速データを取り扱うアプリケーションの場合は、FX2LPがデータ処理を行うことは現実的ではなく、転送データはCPUをスルーします。(逆に言えばファームウエアとして実装しなければならない機能はUSB制御としての基本的な機能のみですむはずです。)

また、ホストアプリケーションにしてもユーザが本当に行いたいことは、データを加工することです。USBの制御についてはあくまでも必要だから行うだけで、できるだけ手を抜きたいところではあります。

ということで、ファームウエアについては、高速転送を可能にするFX2LPのFIFOを使用する汎用的なものを用意しました。多くの場合、この標準的なファームウエアで対応できるはずです。ホストアプリケーションに関してはUSB制御にかかわる部分はライブラリ化して、ユーザが自分の行いたいデーター処理に専念できるようにしています。

MAX2のデザインに関しては、実際のアプリケーションのハード部分のインターフェイスに当たりますので、汎用化は難しくアプリ毎に開発してください。


<カメレオンUSB FX2クラスライブラリ>

「カメレオンUSB FX2」はCypressの提供するデバイスドライバCyUSB.sysによって制御されます。CypressではCyUSB.sysを制御するためのクラスライブラリとしてCyAPIを提供しています。今回作成した「カメレオンUSB FX2クラスライブラリ」も基本的にはこのCyAPIを呼び出す形で実装されています。

「カメレオンUSB FX2クラスライブラリ」はCyAPIを使って下記の機能を実装しています。

  1. PCアプリからFX2LPへのファームウエア(EEPROM書き込み用のiicフォーマット)のダウンロード
  2. 複数のFX2LPを識別する機能
  3. CyAPIの非同期転送関数を使用した高速データ転送の仕組み

「カメレオンUSB FX2」のアプリケーションはファームウエアは基板のEEPROMには書き込まず、PCアプリケーション側に持ちます。そして必要に応じてFX2LPに転送することになります。このためには、ファームウエアのダウンロード機能は外すことは出来ません。

「複数のFX2LPを識別する機能」については、かねてから多くの要望があり実装しました。具体的な用途としては、既に開発が完了した「カメレオンUSB FX2」の機器を使用しているPCで、別の新たなアプリケーションが開発できるようになります。

FX2LPには、EEPROMからベンダID、プロダクトID、デバイスID(CyConsoleのbcdDevice)だけを取得して、FWは後でPCからダウンロードできる機能があります。ベンダIDとプロダクトIDを変えてしまうと、デバイスドライバも変えなくてはならず、今回の目的には使用できなくなりますが、ベンダIDとプロダクトIDはFX2LPのままで、デバイスIDだけを個別に振り分けます。

CyAPIはデバイスIDを取得できますので、対象となるデバイスIDを持つ機器にFW(ファームウエア)をダウンロードすればOKです。ところが、問題があります。FWをダウンロードすると今度はFWが持つベンダID、プロダクトID、デバイスIDを使用するようになり、再度識別出来なくなってしまいます。この問題を回避するためにライブラリでは、FWをFX2LPにダウンロードする際に、デバイスIDを変更するようにしています。(このため複数のFX2LPを識別するFWは、必ずベンダID「04B4」、プロダクトID「1004」、デバイスID「0000」に設定してください。)(EEPROMにデバイスIDを書き込むにはFX2WRID.exeツールを使用します。)

「非同期転送を使用した高速データ転送の仕組み」ですが、高速データ転送は「カメレオンUSB FX2」の目玉でもありますので、この機能を使いやすい形で実装するのは重要です。

CyAPIではデータ転送の仕組みは、同期方式と非同期方式の2種類が提供されています。同期方法は受信するバッファを指定して、CyAPIの関数を呼び出します。この方法は扱いが楽でよいのですが、データの取りこぼしが発生します。FX2LPのFIFOのバッファは最大で2Kbyteまで設定することが出来るので、2Kbyteが溢れるまでに、PC側がデータを取り込んでくれれば、途切れることなくデータを送信出来ます。ところが同期方法を使用してデータを転送すると、PC側の処理が追いつかずに取りこぼしが起きます。(想像ですが同期方式は新たな転送バッファを用意する際に必ず、ユーザーモードとカーネルモードの切り替えが必要になるためと思われます。)

取りこぼしを防ぐには「カメレオンUSB FX2」側により大きなバッファメモリを用意して、データを保管し、PC側の要求に応じて転送することで回避することも可能ですが、あまり現実的ではありません。

そこで登場するのが非同期転送方式です。この方法のメリットは、アプリケーション側で用意した複数個のバッファを事前に、デバイスドライバに登録出来る点です。十分な容量(64Kbyte以上)を持つバッファを複数個(8個以上)登録しておくことで、デバイスドライバは潤滑にデータを取り込むことが可能になります。

非同期方法の問題点は、用意したバッファにデータが届いているかをアプリ側で個別に調べたり、決められた手順でバッファを開放したりと面倒なところが難点です。今回「カメレオンUSB FX2クラスライブラリ」では、バッファにデータが届いた際に呼び出されるコールバック関数を導入することにより、面倒な作業は全てライブラリに任せることが出来ます。

ファームウエアの使用方法についてはサンプルアプリケーションのページで解説しています。


<コンソールアプリケーション>

説明が長くなってしましましたが、習うより慣れろです。まずは、シンプルなコンソールアプリを例に「カメレオンUSB FX2クラスライブラリ」の使い方を説明します。このサンプルで使用しているFWはサイプレスの開発キットにある「bulkloop」ですので、「カメレオンUSB FX2」だけではなく「mini EZ-USB FX2」でも動作します。(C++は最近使い始めたので、おかしなところもあるかもしれませんがご容赦ください)

コンソールアプリケーションアーカイブ(VisualStudio 2005用)

ファイル構成
cusb2.h FX2クラスライブラリヘッダ
(クラス・型・関数などの宣言)
CyAPI.h Cypress提供のCyAPIヘッダ
CyAPI.lib Cypress提供のCyAPIライブラリ
cusb2.cpp FX2クラスライブラリ本体
test_cons.cpp コンソールアプリケーション本体
fw_bulkloop.cpp bulkloopファームウエア

もっとも簡単な同期転送を使用した例になります。
流れとしては、ファームのロード -> エンドポイントの確保 -> 実際のデータ転送です。

  1. u8 fw_bulkloop[];
  2. int _tmain(int argc, _TCHAR* argv[])
  3. {
  4.   u8 buf[1024];
  5.   read_func(buf, 1024); //テストデータ作成
  6.   cusb2 *usb = new cusb2(NULL);
  7.   if(usb->fwload(0, fw_bulkloop, NULL))
  8.   {
  9.     CCyUSBEndPoint *inep = usb->get_endpoint(0x86);
  10.     CCyUSBEndPoint *outep = usb->get_endpoint(0x02);
  11.     LONG len = 1024;
  12.     usb->xfer(outep, buf, len);
  13.     printf("out(PC->FX2) trans %d.\n",len);
  14.     usb->xfer(inep, buf, len);
  15.     printf("in(FX2->PC) trans %d.\n",len);
  16.     if(usb->USBDevice->bHighSpeed)
  17.     printf("Device is HighSpeed.\n");
  18.   }
  19.   else
  20.   {
  21.     printf("not found target FX2.\n");
  22.   }
  23.   delete usb;
  24.   return 0;
  25. }
1行目のfw_bulkloop[ ]はバルク転送ループバックを行うCypressの開発キットについているファームです。

7行目はbuf[]にテストデータを作成するために関数を呼び出しています。

8行目でcusb2クラスのオブジェクトusbを作成します。コンソールアプリですので引数がNULLになっています。

9行目でファームウエアのロードを行います。
1番目の引数が、FX2のID番号。2番目の引数がファーム(iic形式)へのポインタ。3番目の引数が文字列へのポインタで、既にロード済みのファームが持つ文字列と比較されます。一致すれば、ファームのロードは行いません。NULLを指定すると無条件にファームのリーロードを行います。(既に必要とされるファームがロードされている際に、ファームのロードを行わないで、処理を早くするための機能)

11行目はデータを転送するためのエンドポイントを作成しています。ここではアドレスで、IN(FX2 -> PC)方向のエンドポイントになります。

12行目もエンドポイントです。0x02はOUT(PC -> FX2)方向のエンドポイントになります。

inepとoutepはファームウエア内で結合されていて、outepに送ったデータが、inepに戻ってくる(ループバック)ようになっています。

14行目でxfer( )関数を使用して、エンドポイントoutepにデータを転送します。転送する長さはlenで指定します。lenは参照渡しで、実際に転送された長さが戻ります。xfer( )関数は同期転送で、転送が終了するまで待ちます。

16行目もxfer( )関数を使用して、inepからデータを受け取ります。エンドポイントの向き(IN or OUT)によって、データの送信・受信が決まることに留意してください。

18行目はcusb2クラス内のUSBDeviceオブジェクトを使用してUSBがHiSpeed(480Mbps)で接続されているか調べています。cusb2はCyAPIを使用して構築されていますので、このようにCyAPIが持つ様々な機能を使用することも出来ます。CyAPIの詳細についてはCyAPIのヘルプファイル(C:\Cypress\USB\Drivers\CyAPI\CyAPI.chm)が参考になります。

25行目で不要になったusbオブジェクトを削除します。

次の例は同じbulkloopのファームウエアで、非同期転送(コールバック関数)を使用した例になります。
非同期転送では、エンドポイントごとに転送をコントロールするスレッドを作成します。そしてそのスレッド内部から、データ転送状況に応じて登録したコールバック関数が呼ばれることになります。ユーザアプリでは、コールバック関数内で受け取った(あるいは送信する)データを処理します。

  1. #define BUF_LEN 1024*64
  2. #define QUE_NUM 16
  3. bool write_func(u8 *buf, u32 len){
  4.   static s32 cnt=0;
  5.   fwrite(buf,1,len,wfp);
  6.   if(cnt++ < max_cnt) return true;
  7.     return false;
  8. }
  9. bool read_func(u8 *buf, u32 len){
  10.   static u32 seed = 0;
  11.   static s32 cnt=0;
  12.   u32 i, *ptr32;
  13.   //テストデータ作成
  14.   ptr32 = (u32 *)buf;
  15.   for(i=0; i< len/4; i++) ptr32[i] = seed++;
  16.   if(cnt++ < max_cnt + QUE_NUM) return true;
  17.   return false;
  18. }
  19. int _tmain(int argc, _TCHAR* argv[])
  20. {
  21.   cusb2 *usb = new cusb2(NULL);
  22.   if(usb->fwload(0, fw_bulkloop, NULL))
  23.   {
  24.     if(fopen_s(&wfp, "test.bin", "wb")){
  25.       printf("file open err.\n");
  26.     }
  27.     max_cnt = 100;
  28.     cusb2_tcb *tcb1=
         usb->start_thread(0x02, BUF_LEN, QUE_NUM, read_func);
  29.     cusb2_tcb *tcb2=
         usb->start_thread(0x86, BUF_LEN, QUE_NUM, write_func);
  30.     usb->delete_thread(tcb1);
  31.     usb->delete_thread(tcb2);
  32.   }
  33.   delete usb;
  34.   return 0;
  35. }
1行目は非同期転送用キュー(バッファ)の長さを定義しています。
2行目は非同期転送用キューの数を定義しています。
キューの長さと数は大きいほど安定してデータを転送出来ますが、長さ*キューの数分のメモリーが消費されます。

4-10行目はデータを受信した際に呼び出されるコールバック関数です。ポインタbufにデータのポインタ、lenにデータの長さがセットされて呼ばれます。この例では単純にファイルに書きためています。コールバック関数は戻り値が重要で、引き続きデータを受信(送信)しつづける際にはtrueを返し、送受信を終了する際にはfalseを返すようにします。

12-23行目は送信データを作成するためのコールバック関数です。OUTエンドポイント(PC -> FX2方向)の場合は、事前に送信用のデータをキューの数だけ作成することになります。ですのでこの関数が呼ばれる回数は、実際に送信したキューの数+作成したキューの数になる点にご注意ください。

34行目でOUTエンドポイント用の非同期転送用スレッドを作成しています。cusb2_tcbは構造体で、スレッドを管理するための情報が格納されています。関数の引数は、エンドポイントのアドレス、キュー(バッファ)の長さ、キューの数になります。

35行目はINエンドポイント用の非同期転送用スレッドを作成しています。

36行目と37行目はスレッドが終了するのを待って、スレッドの後始末をする関数です。コールバック関数がfalseを返さないと永久にこの処理で待ちになる点にご注意ください。

<GUI(MFC)アプリケーション>

コンソールアプリケーションだけではさびしいですので、簡単なGUI(MFC)アプリケーションのサンプルも用意しました。

サンプルの処理内容は上記の同期転送と同じものになっています。(ショボすぎですみません)

GUI(MFC)アプリケーションアーカイブ(VisualStudio 2005用)

GUIアプリケーションの場合、「カメレオンUSB FX2」がPCに挿入されたとき、自動的に認識する(PnP)処理は落とせないと思います。PnPの処理に必要な部分だけ簡単に説明しますので、あとはソースコードを参照してください。

mfcDlg.cpp(94行目〜) 初期化処理
  1. // TODO: 初期化をここに追加します。
  2. usb = new cusb2(m_hWnd);
  3. Fx2Check();
cusb2クラスのオブジェクトusbを作成します。コンソールアプリの場合は引数がNULLでしたが、GUIアプリの場合は。MFCが使用するウインドウハンドルを指定します。
デバイスドライバ(CyUSB.sys)が扱うデバイスが挿入(抜き)されたとき、このハンドルに対してPnPメッセージが送られます。

3行めのFx2Check( )関数はファームのロード行う関数です。アプリ起動時から「カメレオンUSB FX2」がPCに接続されている可能性もありますので、ここでも呼んでおきます。


mfcDlg.cpp(33行目〜) メッセージマップ
  1. BEGIN_MESSAGE_MAP(CmfcDlg, CDialog)
  2.   ON_WM_PAINT()
  3.   ON_WM_QUERYDRAGICON()
  4.   //}}AFX_MSG_MAP
  5.   ON_MESSAGE(WM_DEVICECHANGE,OnPnpEvent)
  6.   ON_BN_CLICKED(IDC_BUTTON1, &CmfcDlg::OnBnClickedButton1)
  7.   ON_WM_CLOSE()
  8. END_MESSAGE_MAP()
デバイスドライバから送られてくるPnPメッセージとイベント関数(OnPnpEvent( ))との対応を取ります。

5行目は手作業で追加してください。(他の行は通常VisualStudioが自動的に生成してくれます)


mfcDlg.cpp(58行目〜)
  1. LRESULT CmfcDlg::OnPnpEvent(WPARAM wParam, LPARAM lParam)
  2. {
  3.   if(usb->PnpEvent(wParam, lParam))
  4.   {
  5.     Fx2Check();
  6.   }
  7.   return 0;
  8. }
  9. void CmfcDlg::Fx2Check()
  10. {
  11.   button1.SetWindowText("");
  12.   if(usb->fwload(0, fw_bulkloop, NULL))
  13.   {//対象FX2が使用可能
  14.     button1.SetWindowText("転送可能");
  15.     inep = usb->get_endpoint(0x86);
  16.     outep = usb->get_endpoint(0x02);
  17.   }
  18.   else
  19.   {//対象FX2は使用不可
  20.     button1.SetWindowText("転送不可");
  21.     inep = outep = NULL;
  22.   }
  23. }
1−8行目まではPnPメッセージを処理する関数です。
3行目でライブラリ側のメッセージ処理関数を呼び出します。この関数の戻り値によって、実際にファームのロードや、デバイスが存在しない場合の処理を行うFx2Check( )関数を呼び出します。

10−24行目ファームのロードを行います。
ファームがロードできない場合は、デバイスが無い状態として判断します。