EZ-USBには18本のI/Oポート、2つのシリアルインターフェイス(SIO)、タイマー、カウンター、I2Cインタフェイスなどが内蔵されており、MINI EZ-USBでもコネクタにこれらの信号が引き出されてます。
そこでこれらを簡単にPCから使用すること、EZ-USBの各I/Oインターフェイスの理解を目的に、簡単なFWとホストアプリケーションを作成します。

このページではEZ-USBのファームウエア(FW)をCypressの開発ツールに付属しているKEILのCコンパイラを使用して作成していきます。他のアプリケーション用のFWを開発する場合でも参考になるはずです。


<FWの仕様について>

今回のFWは汎用的に使用出来ることとEZ-USBの理解を目的にしておりますので、EZ-USBのCPU(8051)がアクセスできるレジスタを直接PCからアクセスする方式にします。そのため効率面では今ひとつですが、かなりの事が今回のFWを使用することで実現できるはずです。
このFWで基本的な動作を確認したうえで、専用のFWを作成するのも一つの方法でしょう。

EZ-USBのI/O(広い意味での)は、大きく2つに分類されます。

一つはEZ-USBのCPUである8051がもともと持っているSIO、タイマー、カウンターなどです。こちらはSFR(Special Function Registers)と呼ばれるエリアにマッピングされており、C言語では特殊なタイプ(sfr)を指定してアクセスします。

もう一つはEZ-USB専用に設けられたレジスター(EZRと呼びましょう)でUSBのエンドポイントバッファなどと同じエリアである外部データ空間にマッピングされています。こちらにはI2Cインターフェイス、I/Oポート関連のレジスターがあります。アドレスの指定は上位8ビットは0x7fに固定して0x7f00-0x7fffまでをアクセス対象にします。

OUT1のエンドポイントをホストからデバイスへのコマンド用、IN1のエンドポイントをデバイスからホストへの入力ポート読み出し用のエンドポイントとして使用します。
ホストが発行するコマンドは64バイト以下で以下のコマンドを組み合わせて使用します。

コマンド コード コマンド長 戻り長
SFR書き込み 0x00
SFR読み出し 0x01
EZR書き込み 0x02 2
EZR読み出し 0x03 2

戻り長が0でないコマンドを発行した場合に、IN1のエンドポイントにコマンドの結果が書き込まれますので、ホスト側のアプリケーションはこれを読み出すことになります。
詳しい使用方法はホストアプリケーションのページで説明します。


<KEILの統合開発環境>

Cypressの開発ツールに含まれる統合開発環境です。Cコンパイラ、リンカ、デバッガ(CPUエミュレータ含む)で構成されています。開発ツールに含まれるサンプルソースコードもこのKEILの統合環境でコンパイルすることが出来ます。

Cypressの開発ツールを標準でインストールすると、この統合環境はインストールされませんのでご注意ください。(詳しくはこのページの開発ツールのインストールを参照)

このKEILの統合環境はKEIL社の商品で、Cypressの開発ツールには評価版として付属しています。評価版としての制約は4Kbyte以下のオブジェクトコードまでしか扱えない点です。EZ-USBのプログラム領域は8Kbyteありますので、4Kbyteではちょっと物足りない気がしますが、普通のFWを開発するには十分です。特にMINI EZ-USBの場合はPCのアプリケーションと連動して動くことを想定していますので、FW側には最小限度の機能を実装すれば済むはずです。

それでは実際にKEILの統合環境を使用してみましょう。マイクロソフトのVCの統合環境とよく似ていますので、VCを使用したことがある人はすぐに使いこなすことが出来るでしょう。ではまずKeil uVison2を実行して、ProjectメニューからOpen Projectを選択してください。とりあえずサンプルのbulktestをコンパイルしてみることにしますので、C:\Cypress\USB\Examples\EzUsb\bulktest\targetにあるプロジェクトファイルを指定してください。



本来ならここで Project->Build target でコンパイル&リンクが行われOKなはずなのですが、残念ながらこのままのHEXファイルをCypressのコントロールパネルでダウンロード&RUNさせても正常に動作しません。
実はサンプルとして含まれているプロジェクトファイルは全て、Cypressの純正の開発ボード用に作成されていてそのままでは動きません。Cypressの開発ボードはAN2131QCと呼ばれるアドレスバスとデータバスが外部に出力されているチップを使用して外部にRAMを搭載しています。この外部RAMで動くコードが生成されるためMINI EZ-USBなどの内部RAMしか持たないEZ-USBでは動作しません。
そこで、Project -> Options for Target 'Target1' を選択して BL51Locate のタブを開いてください。



このCodeとXdataを上記のように設定します。
これはCodeのエリアをEZ-USBの内蔵RAMに設定し、外部データ領域(Xdata)を内蔵RAMの後半に設定しています。評価版のため4Kbyte以下のPGMしか生成できませんのでコードは0x1000以下を使用し、Xdataを0x1000から使用することでXdata領域に4Kbyte近くのワークエリアを確保することが出来ます。

これでOKです。再度 Project->Build target を実行してみましょう。今回作成されたbulktest.hexファイルはCypressのコントロールパネルで正しく実行できるはずです。

同じ方法で他のサンプルプログラムもコンパイル&実行が可能になります。


<8051エミュレータを使用してデバッグ>

KEILの統合環境にはEZ-USBのCPUである8051のエミュレータも内蔵されていますので、これを使ってデバッグも可能です。I/Oポートやタイマー、カウンターのハードウエア状況もエミュレーションされていて表示することが可能です。(さすがにUSB関連のハードはエミュレーションできませんが、、。)

8051エミュレータを有効にするにはProject -> Options for Target 'Target1' を選択して Debug タブを開いてください。


Use SimulatorのチェックをONにすれば完了です。
実際にデバッグを行うにはメニューの Debug -> Start/Stop Debug Session でデバッグが開始されます。後はステップトレースや変数のウォッチ、アセンブラ表示などを使用してデバッグが可能です。



<FWのスケルトン>

ツールの使用方法をマスターしましたので実際にFWの開発に取り掛かります。
はっきり言ってEZ-USBのFWを一から書くのはかなりの知識と時間と経験が必要になります。
(私もとても出来ません。)

しかしあきらめてはいけません。知識と時間と経験を補ってくれるのがサンプルソースコードです。
EZ-USBの開発者が作ってくれたサンプルコードを使用すれば簡単に(EZに)FWを開発することが可能です。

サンプルソースコードもそのように考えて作られており、基本的に fw.c と periph.c の2つのソースコードに分割されています。このうちFW毎に修正が必要なのは基本的にはperiph.cです。

FW作成用のスケルトンも開発ツールの中に用意されているのですが、今回はさらに手を抜いてbulktestのFWを改造することにより空のスケルトンを作成しました。以下の点を改造しています。

改造済みの空のスケルトンFWをこちらに置いておきますので、自分でFWを作られる方は雛形にしてください。

この空のFWに必要な処理を書き足していくことで目的のFWを完成させることが可能です。書き足すといってもどのような仕組みでスケルトンが構成されているかを理解しないことには改造できません。そこでFWのスケルトンの仕組みについて解説します。

ユーザが意識する必要があるFWの関数は大きく4つに分類されます。これらの関数を理解すれば十分にFWの開発を行うことが出来ます。

初期化関数:TD_Init()

EZ-USBのFWの初期化処理用の関数です。現在はIN、OUTの7個づつのエンドポイントを有効にして、エンドポイントに対する割り込みを有効にする処理を行っています。
これ以外の初期化に必要な処理(例えばI/Oポートの設定など)を追加で記述します。

ポーリング関数:TD_Poll()

この関数はFWがアイドル(無処理)の状態で呼び出される関数です。現在は空になっています。
この関数内に無限ループのような処理を組み込むことは出来ません。
USBに関係ない処理や、エンドポイントの割り込みの中で処理するには時間がかかりすぎるような処理を記述することで、効率的なFWを作成できます。

OUTエンドポイント割り込み関数:ISR_Ep1out() 〜 ISR_Ep7out()

OUTエンドポイントにデータが書き込まれたとき(PCからのデータがEZ-USBのエンドポイントに届いたとき)割り込み処理の中で起動されます。現在この関数は受信バイト数のレジスタを0にして、割り込みを許可する処理が記述されており、データはそのまま捨てられています。

ユーザの処理は現在記述されている処理の上に追加します。簡単な例として、エンドポイントのデータをそのままINエンドポイントに書き出せば、ループバック処理となります。(bulktestと同等)

INエンドポイント割り込み関数:ISR_Ep1in() 〜 ISR_Ep7in()

INエンドポイントが空になったとき(EZ-USBからPCへのデータがPCに届いたとき)に割り込み処理で呼ばれる関数。現在この関数は次回の割り込みを許可するだけの処理を行っています。

ユーザの処理は現在記述されている処理の上に追加します。簡単な例として、64バイト(エンドポイントのサイズ)を超えるデータをPCに渡すときに、この関数の中で次の64バイト分をエンドポイントにセットすることが出来ます。

<FWのインプリメンテーション>

FWの仕様はOUT1のエンドポイントでPCからのコマンドを受け取り、結果をIN1のエンドポイントからPCに返すようになっています。
従って今回はISR_Ep1out()関数内で、コマンド解析、レジスタへの書き込み(読み込み)、読み込み結果をIN1のエンドポイントに書き出すだけですみます。

  1. void ISR_Ep1out(void) interrupt 0
  2. {
  3.   u8 i,r;
  4.   u8 xdata *ezrp;
  5.   r=0;
  6.   for(i=0;i<OUT1BC;){
  7.     switch(OUT1BUF[i]){
  8.     case SFR_WRITE:
  9.       sfr_write(OUT1BUF[i+1],OUT1BUF[i+2]);
  10.       i+=3;
  11.       break;
  12.     case SFR_READ:
  13.       IN1BUF[r++]=sfr_read(OUT1BUF[i+1]);
  14.       i+=2;
  15.       break;
  16.     case EZR_WRITE:
  17.       ezrp=(u8 *)(0x7f00+OUT1BUF[i+1]);
  18.       *ezrp=OUT1BUF[i+2];
  19.       i+=3;
  20.       break;
  21.     case EZR_READ:
  22.       ezrp=(u8 *)(0x7f00+OUT1BUF[i+1]);
  23.       IN1BUF[r++]=*ezrp;
  24.       i+=2;
  25.       break;
  26.     default:
  27.       i++;
  28.       break;
  29.     }
  30.   }
  31.   if(r!=0){
  32.     IN1BC=r;
  33.   }
  34.   OUT1BC = 0;
  35.   EZUSB_IRQ_CLEAR();
  36.   OUT07IRQ = bmEP1;
  37. }

新たに追加するコードはこれだけです。

6行目でOUT1BCというのが出てきますが、これはEZ−USBに内蔵されるレジスターのことで C:\Cypress\USB\Target\IncにあるEZRegs.hの中で定義されています。通常EZ−USBのFWはこのEZRegs.hとezusb.hをインクルードします。
このヘッダファイルのおかげでFWの開発者はEZ−USBのレジスタの番地やアクセス方法などを意識せずダイレクトにC言語の中で使用することが出来ます。

話を戻しますが、このOUT1BCにはOUTエンドポイント1に受信されたバイト数が格納されています。他のエンドポイントにも同様なレジスタが存在します。
そして7行目のswitch文でコマンドの解析を行い、対応するcase文で各処理を記述します。OUT1BUF[i]のように受信したエンドポイントのバッファに直接アクセスすることが出来ます。

9行目、13行目でsfr_write()とsfr_read()という関数を呼び出していますが、これはSFRのレジスタをアクセスする関数を別に作成しています。SFRへのアクセスは8051の特殊な空間にマッピングされるためポインターなどを使って汎用的にアクセスできないのでこのようにしています。

これに対して、I/OポートやUSB用のレジスタへのアクセスは4行目のようにxdata属性をつけてポインタを宣言して、ポインターに対象のアドレスをセットしてアクセスします。このxdata属性は8051の外部メモリー空間へのアクセスを明示するものです。I/OポートやUSB用のレジスターはチップの内部に存在はするのですが、8051CPUコアから見れば外部ということになるのでxdataが必要になります。

18行目でこのポインターを使用してエンドポイントに格納されたデータをレジスターに書き込んでいます。

23行目ではIN1BUF[r++]=*ezrpのようにINエンドポイント1のバッファに直接レジスタの値を書き込みます。

31−33行目の処理は、実際にINエンドポイント1に書き込まれたデータがある場合にIN1BCのレジスタに書き込んだバイト数をセットしています。このようにINエンドポイントのバイトカウントレジスタ(IN1BC)に値を書き込むことにより実際の転送が開始されます。

今回の処理ではPCがコマンド書き込んだ直後に、必ずPCがINエンドポイントを読むことが保障されているので、何も気にせずにIN1BUFやIN1BCのレジスタをアクセスしています。ところが処理の方法によっては、INエンドポイントをPCが読み込んでいることを保障できないことがあります。(例えば連続して大量のデータをPCに送る場合など。)
このような場合は

if (EPIO[IN1BUF_ID].cntrl & bmEPBUSY){
  //CPUがエンドポイントにアクセス可能
}else{
  //EZ−USBがエンドポイントを制御中。(CPUアクセス不可)
}

のようにIN1エンドポイントが使用可能か調べる必要があります。

35-37行目の処理はスケルトンのFWに予め記述してあった部分で、OUT1BCに0をセットすることでEZ−USBに対してエンドポインタのデータを受け取ったことを明示します。これによりEZ−USBは新たなデータをエンドポイントに受け取ることが出来ます。36、37行目は次回データを受け取ったときに割り込みがかかるようにしています。


<FW識別用文字列の追加>

dscr.a51というアセンブラのファイルも一部修正します。

この修正は次のページで解説するホストアプリケーションで使用するカメレオンUSBライブラリのためのものです。

MINI EZ-USB(カメレオンUSBも)ではFWをPCからEZ−USBに転送して使用します。これはMINI EZ-USBをPCに接続して、最初にホストアプリケーションを実行するときに行う必要があります。USBケーブルを抜かずに、2回目以降ホストアプリケーションを起動したときには、既に前回ロードしたFWがEZ−USBに残っているはずです。このことをホストアプリケーションから確認するために、EZ−USBにロードされているFWの名前とバージョンを、USBのストリングディスクリプタに持たすことにしています。(これは単にカメレオンUSBライブラリの仕様です)

StringDscr1に名前("RGIO")、StringDscr2にバージョン("V100")を格納します。

StringDscr1:
    db StringDscr1End-StringDscr1 ;; String descriptor length
    db DSCR_STRING
    db 'R',00
    db 'G',00
    db 'I',00
    db 'O',00
StringDscr1End:

StringDscr2:
    db StringDscr2End-StringDscr2 ;; Descriptor length
    db DSCR_STRING
    db 'V',00
    db '1',00
    db '0',00
    db '0',00
StringDscr2End:

どうでしょうか? FWの開発もそれほど難しくないことがおわかりいただけたでしょうか? 今回作成したFWはここに置いておきますので自由に改造して使用してみてください。