アプリケーションノート 4000

MAX3421Eの改版1と2のホストOUT転送


要約: このアプリケーションノートはホスト側のUSBコントローラとして働くMAX3421Eの改版1と2を述べたものです。MAX3421Eはダブルバッファの送信FIFOを用いて、USBペリフェラルにデータを送ります。このデバイスの改版1と2では、OUT伝送のプログラミングのためには特別な考慮が必要です。このアプリケーションは送信方式について説明します。シングルバッファとダブルバッファを用いたOUT伝送のプログラミング実例が提供されています。

はじめに

MAX3421EはUSBのホスト/ペリフェラルコントローラであり、ホストとして動作する場合は、データをUSBペリフェラルに送信するためにダブルバッファ型の送信FIFOを使用します。このペアのFIFOは2つのレジスタで制御されます。

R2:SNDFIFOであり、FIFOのデータレジスタです
R7:SNDBCであり、バイトカウントレジスタです。

マイクロコントローラは繰り返してSNDFIFOレジスタのR2に書き込み、FIFOに最大64データバイトをロードします。その後、マイクロコントローラはSNDBCレジスタに書き込みますが、これは次の3つのことからなります。
  1. MAX3421EのSIE (Serial Interface Engine)にFIFO内の送信バイト数を通知します。
  2. USB伝送のために、SNDFIFOおよびSNDBCレジスタをUSBロジックに接続します。
  3. SNDBAVIRQ割込みフラグをクリアします。2番目のFIFOがマイクロコントローラのローディングのために使用可能な場合は、SNDBAVIRQは即座に再アサートされます。


Figure 1. The SNDFIFO and SNDBC registers load a
図1. SNDFIFOおよびSNDBCレジスタは「ピンポン」ペアのFIFOとバイトカウントレジスタにロードします。

図1に示すように、第1のFIFOバイトは物理的なFIFOからは与えられず、第1のFIFOバイトは2つの内部クロックドメイン(1つはマイクロコントローラ用であり、もう1つはUSBロジック用です)を調停するために使われる同期レジスタから与えられます。

シングルバッファ型転送のプログラミング

帯域幅が重要ではない単純な転送の場合は、パケットを送信するファームウェアは比較的、簡単です。そのステップは次のようになります。
  1. SNDFIFOにロードします。
  2. SNDBCレジスタにロードします。
  3. HXFRレジスタにOUT PIDとエンドポイント数を書き込んで転送を開始します。
  4. HXFRDNIRQ (Host Transfer Done Interrupt Request)を待機します。
  5. HRSLレジスタから転送結果のコードを読み取ります。
    1. ACKの場合は、成功です。
    2. NAKの場合は、次のステップに進みます。
  6. 最初のFIFOバイトに再ロードして転送を再開します。
    1. ダミー値の0をSNDBCに書き込み、OUTデータを含んでいるFIFOをマイクロコントローラ制御に切り換えます。
    2. SNDFIFOレジスタに最初のFIFOバイトのみの再書込みを行います。このバイトは図1のSYNC REGISTER (同期レジスタ)に与えられます。
    3. 再送パケット用の正しいバイトカウントをSNDBCレジスタに再書込みします。このことによって、USBの再伝送のためにUSB側にFIFOバッファを切り換えてもとに戻します。
    4. ステップ3に行くことによってパケットを再開します。


ステップ4によって、この方法がシングルバッファ転送となります。このシーケンスはステップ4が完了するのを待機するため、最初のFIFOの制御がMAX3421E SNDFIFOからUSBペリフェラルに移動しても、マイクロコントローラは2番目のFIFOをロードしません。

ステップ6が必要であるのは、MAX3421Eの改版1では、同期用フリップフロップはUSB OUT転送を繰り返し行う場合は毎回初期化しなければならないからです。

ダブルバッファ型転送のプログラミング

ダブルバッファによって、複数の64バイトパケットからなる長いデータレコードがUSBホストからUSBペリフェラルに伝送する場合に性能が改善されます。性能が改善されるのは、1つのSNDFIFOがUSBに接続されてパケットを伝送している間に、マイクロコントローラが同時にもう1つのSNDFIFOに次の64バイトデータパケットをロードすることができるためです。しかし、プログラムステップはシングルバッファ転送よりも少し複雑です。ある所定時間にプログラムはデータの2つのバッファを追跡しなければなりません。その理由はFIFOを切り換え(すなわち、NAKに遭遇した場合です)、最初のバイトを再ロードし、元にもどすためにバイトカウントレジスタを再ロードしなければならないからです。図2は1つの可能なステップシーケンスを示しています。実例関数のSend_OUT_Record()は本ノートの最後に記載されており、図2のフローチャートを実現しています。

Figure 2. Flowchart illustrates double buffered OUT packets.
図2. フローチャートはダブルバッファ型OUTパケットを示しています。

右側のループ(ステップ1~4)はFIFOにUSBデータをロードします。ステップ5から始まる左側のループはUSBによってFIFOを発送して、NAKによるリトライを取り扱います。「First pass?」チェック(ステップ3)によってUSB転送はホストコントローラが最初のSNDFIFOをロードした後、すぐに行われることを確実にします。フローチャートは送信よりもFIFOのロードに優先権を与えるようにアレンジされているため、ダブルバッファの性能が維持されます。

フローチャートは次の3つのケースを考えると、最もよく理解されます。
  1. パケットを1つ送信する(合計1~64バイトのペイロード)。
  2. パケットを2つ送信する(合計65~128バイトのペイロード)。
  3. 3つ以上のパケットを送信する(合計129以上のペイロード)。


本アプリケーションノートの最後に記載されたSend_OUT_Record()関数を参照すると、呼出しプログラムはこの関数に4つのパラメタを受け渡します。
  1. ep、OUTパケットがそれに対してディスパッチされなければならないエンドポイント数。
  2. *pBUF、バイトデータのバッファフルを示すポインタ
  3. TBC、送信する総バイトカウント(バッファの中のバイト数)
  4. NAKLimit、断念してリターンするまでのペリフェラルからの連続するNAK応答の数


この関数はMAX3421E Host Result Code Register (HRSL)の値を返します。これは最後に送信されたパケットから読み取られたものです。この値は転送が成功だった場合は、「ACK」を示し、NAK限界を超えるか、またはその他の問題の終結の場合は「NAK」となります。

1個のOUTパケットの送信

ループは送信バイトが64以下の場合にステップ1に入ります。ステップ1の結果が真の場合は、SPIマスタがSNDFIFOを満たします。ペリフェラルがOUT転送を「NAK」する場合、ステップ10でBCとFBが必要な場合は、この関数はバイトカウンタ(BC)とSNDFIFOの最初のバイト(FB)をセーブします。これはレコードの最初の転送であるため、この関数はステップ4でOUT転送を発信します。ステップ1に返ると、送信するバイトが既に無いため、この関数はステップ5の転送完了を待機し、ステップ6でデバイスの応答をテストします。この点で、ACKが返された場合、ステップ7では送信データは無く、この関数はステップ11に返ります。しかし、NAKが返された場合は、NAKの限界がステップ9でテストされ、それがまだ限界内であれば、パケットはステップ10と4で再送されます。

2つのOUTパケットの送信

この関数は送信バイトが65~128バイトの場合にステップ1で再びエンターします。前と同じように、この関数は最初のSNDFIFOを満たし、そして直ぐにステップ2~4で転送を始めます。ステップ1に返って、この関数は更に送信するバイトを見つけ、再びステップ2でSNDFIFOを満たします。これは最初の転送でないため、この関数はステップ4では次のパケットを開始しようとはしません。もう一度、ステップ1に返って、最初のSNDFIFOはUSBによって最初のパケットを送信し、他方、2番目のパケットは2番目のSNDFIFOに留まって、送信を待機しています。ステップ2ではこの関数は実際には、2組のBC/FBデータ(バイトカウントとFIFOの最初のバイト)をセーブすることに注意してください。1組は現在送信しているSNDFIFOであり、もう1組は2番目のSNDFIFO内で待機しているペンディングデータです。送信するデータがすでに存在しない(そしてまた2つのSNDFIFOはデータでフルとなっている)ため、制御はステップ5に分岐します。

関数ステップ5で最初のパケットの転送を待機し、その後、ステップ6で転送結果をチェックします。前と同じように、パケットが「NAK」されたら、この関数がFIFOとステップ10のペンディングBC/FB値を備えたバイトカウントレジスタを再ロードし、ステップ4でパケットを再送信し、(ステップ1を通って)完了を待つために分岐してステップ5に戻ります。ステップ5-6-9-10-4-5のループはACKを受信するか、またはNAKリミットに達するまで、すべてのNAKに対し継続します。

ペリフェラルがOUT転送にACKを応答すれば、この関数はステップ7で終結の試験をします。完了していない場合は、この関数はステップ8に分岐して、2番目のSNDFIFO内で待機している、次のパケットを送信します。ステップ8では、この関数は幾つかの仕事をします。これらの値がステップ10で必要である場合に備えて、「ペンディング」中のBC/FC値を「現在」のBC/FC変数にコピーします。現在のバイトカウントをローディングしてUSBに2番目のSNDFIFOをスイッチします。そして、ステップ4でOUTパケットを送信します。その後、前と同じようにNAKのたびにループを続けます。ACK応答を受信すると、この関数はステップ7でステップ11へ抜け出ます。

3個以上のOUTパケットの送信

この関数は基本的には、2つのパケットに対しては、1つのパケットが送信途中で、もう1つがペンディングになるまで、前と同じ手順に従います(2番目のSNDFIFOを待機)。その後、この関数がステップ4でもう1つのパケットを送信するたびに、ステップ1でSNDFIFOのロードするデータがないかをチェックします。プログラムがステップ2に進むためには、送信するデータがさらにあり、しかもFIFOが利用可能でなければなりません。ステップ1~3の「load SNDFIFO」ループがステップ5…4の「send FIFO」ループよりも優先されるために、データがUSBで送信されているのと同時に、データはSNDFIFOに常にロードされ、したがって、ダブルバッファリングが達成されます。

性能

Figure 3. Scope traces show the MAX3421E loading and sending the USB packets.
図3. オシロスコープのトレースはMAX3421EがUSBパケットをロードして送信しているのを示しています。

図3はオシロスコープによる捕捉を示しており、Send_OUT_Record()関数を使う64バイトのOUTパケットのローディングと送信のオーバラップを示しています。レコードサイズは512バイトであり、8個の64バイトパケットから構成されています。このトレースでは、MAX3421Eに取り付けられたデバイスはNAKを生成せず、最大の転送帯域幅を測定することができ、全転送が1つのスコープによる捕捉に適合しています。

上の2つのトレースはSPIマスタ(SPIハードウェア内蔵のARM7)がMAX3421E SNDFIFOに64バイトデータをロードしているときのSPIポートの活動を示しています。各SNDFIFOのロードに対して、SS# (スレーブ選択)ラインはローとなり、SCK (シリアルクロック)は65 x 8回パルス出力します。1度コマンドをロードし、その後、64バイトデータを8回繰り返します。最初のSNDFIFOのロードはこのトレースが開始される前に開始されるため、図3はSNDFIFOが7回行われるのが示されています。

3番目のトレースはUSB D+信号であり、64バイトのパケットがUSBを使ってMAX3421Eホストからペリフェラルへ移動する様子が示されています。一番下のトレースは転送を開始するためにARM7がMAX3421E HXFRレジスタのローディングを示すパルスです。

USBパケットが開始した後は、バス上をUSBパケットが移動するのと同時に、ARM7は2番目のSNDFIFOのローディングを開始します。このダブルバッファリングによって転送帯域幅が改善されます。

帯域幅の測定

Figure 4. CATC (LeCroy) trace and bus analysis for the Figure 3 transfer.
図4. 図3の転送に対するCATC (LeCroy)のトレースとバス解析。

図4が示すように、Send_OUT_Record()関数は6.76Mbpsの速度で512バイトレコードを転送しました。参考として、PCに付属しているUSBフルスピードのUSBのサムドライブがUHCI USBコントローラ(USBの唯一の付属品)を使って5.94Mbpsで512バイトのレコードを転送しました。

実装ノート

以下に示した実例はMaxim USB Laboratoryを使用して作成され、試験されました。Maxim USB Laboratoryの詳細はアプリケーションノート3936 「Maxim USB Laboratory」にあります。このキットはMAX3421EとMAX3420E USBペリフェラルコントローラの両方を含んでいます。ファームウェアをテストするために、MAX3421EホストはUSBケーブルを使ってMAX3420Eペリフェラルに接続され、MAX3420Eは「*1*」というコメント出力ステートメントにより各OUT転送を受け取るようにコマンドを与えられました(NAKなし)。

注:MAX3421Eのこれから先の改版ではFIFOを再ロードする問題はありません。MAX3421EはHXFRレジスタを単に再ロードすることによってOUTパケットを再発行します。この変更によって、Send_OUT_Record()関数が不要になります。

Send_OUT_Record()の実例関数

// *******************************************************************************
// Send an OUT record to end point 'ep'.
// pBuf points to the byte buffer; TBC is total byte count.
// NAKLimit is the number of NAKs to accept before returning.
//
// Returns HRSL code (0 for success, 4 for NAK limit exceeded, HRSL for problems)
// *******************************************************************************
//
BYTE Send_OUT_Record(BYTE ep, BYTE *pBuf, WORD TBC, WORD NAKLimit)
{
static WORD NAKct,rb;           // Buf index, NAK counter, remaining bytes to send
WORD bytes2send;                // temp
BYTE Available_Buffers;         // Remaining buffers count (0-2)
BYTE FI_FB;                     // Temporary FIFO first byte
static BYTE CurrentBC;          // Byte count for currently-sending FIFO
static BYTE CurrentFB;          // First FIFO byte for currently-sending FIFO
static BYTE PendingBC;          // Byte count for next 64 byte packet scheduled for sending
static BYTE PendingFB;          // First FIFO byte for next 64 byte packet scheduled for sending
BYTE dum;
BYTE Transfer_In_Progress,FirstPass;    // flags
//
NAKct=0;
Available_Buffers = 2;
rb = TBC;                       // initial remaining bytes = total byte count
FirstPass = 1;
//
do
        {
        while((rb!=0)&&(Available_Buffers!=0))  
        // WHILE there are more bytes to load and a buffer is available
                {
                // Pwreg(rEPIRQ,bmOUT1DAVIRQ);// *1* enable the 3420 for another OUT transfer
                FI_FB = *pBuf;                // Save the first byte of the 64 byte packet
                bytes2send = (rb >= 64) ? 64: rb;         // Lower of 64 bytes and remaining bytes
                rb -= bytes2send;                         // Adjust 'remaining bytes'
                Hwritebytes(rSNDFIFO,64,pBuf);
                pBuf += 64;                   // Advance the buffer pointer to the next 64-byte chunk
                Available_Buffers -= 1        // One fewer buffer is now available
//
                if(Available_Buffers==1)      // Only one has been loaded
                        {
                        CurrentBC = bytes2send;
                        CurrentFB = FI_FB;
                        }
                else                          // Available_Buffers must be 0, both loaded.
                        {
                        PendingBC = bytes2send;
                        PendingFB = FI_FB;
                        }
//
                if(FirstPass)
                        {
                        FirstPass = 0;
                        Hwreg(rSNDBC,CurrentBC);        // Load the byte count
                        L7_ON                           // Light 7 is used as scope pulse
                        Hwreg(rHIRQ,bmHXFRDNIRQ);       // Clear the IRQ
                        Hwreg(rHXFR,(tokOUT | ep));     // Launch an OUT1 transfer
                        L7_OFF
                        }
                }       // While there are bytes to load and there is space for them
//
        do // While a transfer is in progress (not yet ACK'd)
        {
//              while((Hrreg(rHRSL) & 0x0F) == hrBUSY) ;        // Hang here until current packet completes
                while((Hrreg(rHIRQ) & bmHXFRDNIRQ) != bmHXFRDNIRQ) ;
                dum = Hrreg(rHRSL) & 0x0F;                      // Get transfer result
                if (dum == hrNAK)
                        {
                        Transfer_In_Progress = 1;
                        NAKct += 1;
                                if (NAKct == NAKLimit)
                                        return(hrNAK);
                                else
                                        {
                                        Hwreg(rSNDBC,0);                // Flip FIFOs
                                        Hwreg(rSNDFIFO,CurrentFB);
                                        Hwreg(rSNDBC,CurrentBC);        // Flip FIFOs back
                                        L7_ON                           // Scope pulse
                                        Hwreg(rHIRQ,bmHXFRDNIRQ);       // Clear the IRQ
                                        Hwreg(rHXFR,(tokOUT | ep));     // Launch an OUT1 transfer
                                        L7_OFF
                                        }
                        }
                else if (dum == hrACK)
                        {
                        Available_Buffers += 1;
                        NAKct = 0;
                        Transfer_In_Progress = 0;               // Finished this transfer
                        if (Available_Buffers != 2)             // Still some data to send
                                {
                                CurrentBC = PendingBC;
                                CurrentFB = PendingFB;
                                Hwreg(rSNDBC,CurrentBC);
                                L7_ON                           // Scope pulse
                                Hwreg(rHIRQ,bmHXFRDNIRQ);       // Clear the IRQ
                                Hwreg(rHXFR,(tokOUT | ep));     // Launch an OUT1 transfer
                                L7_OFF
                                }
                        }
                else return(dum);
        } 
        while(Transfer_In_Progress);
}
while(Available_Buffers!=2);            // Go until both buffers are available (have been sent)
return(0);
}