应用笔记 4000

MAX3421E版本1和2主机OUT传输


摘要 : 该应用笔记对主机USB控制器MAX3421E版本1和2进行了讨论。MAX3421E采用双缓冲发送FIFO向USB外设发送数据。在该器件的版本1和2中,需着重考虑OUT传输的编程。该应用笔记详细说明了发送机制。并给出了单缓冲和双缓冲OUT传输的例程。

引言

MAX3421E是一款USB主机/外设控制器,也就是说,当作为主机工作时,采用双缓冲发送FIFO将数据发送至USB外设,这一对儿FIFO由两个寄存器控制:

R2:SNDFIFO,FIFO数据寄存器
R7:SNDBC,字节计数寄存器

微控制器对SNDFIFO寄存器R2进行重复写操作,向FIFO写入多达64字节的数据。然后,微控制器再对SNDBC寄存器进行写操作,完成以下3项任务:
  1. 指明MAX3421E SIE (串行接口引擎) FIFO中需要发送的字节数。
  2. 连接SNDFIFO和SNDBC寄存器至USB逻辑,以进行USB通信。
  3. 清除SNDBAVIRQ中断标志。如果第二个FIFO可以用于µC装载的话,那么SNDBAVIRQ将立即重新触发。
图1. SNDFIFO寄存器和SNDBC寄存器载入一对“ping-pong” FIFO和字节计数寄存器
图1. SNDFIFO寄存器和SNDBC寄存器载入一对“ping-pong” FIFO和字节计数寄存器

图1所示,第一个FIFO字节不是由物理FIFO产生,而是由用来调谐两个内部时钟使其同步的同步寄存器产生,其中一个时钟用于微控制器,另一个时钟用于USB逻辑。

单缓冲传输编程

对于那些带宽、无严格要求的简易传输而言,固件发送一个数据包相对比较简单。步骤如下:
  1. 将字节载入SNDFIFO。
  2. 将字节载入SNDBC寄存器。
  3. 向HXFR寄存器写入OUT PID和端点号,传输开始。
  4. 等待HXFRDNIRQ (主机发送完成中断请求)。
  5. 从HRSL寄存器中读取传输结果代码。
    1. 如果为ACK,表明传输已完成。
    2. 如果为NAK,表明将进入下一步。
  6. 再次装载第一个FIFO字节,重新开始传输:
    1. 写SNDBC = 0,在微控制器控制下返回一个虚拟值,以切换含有OUT数据的FIFO。
    2. 仅将第一个FIFO字节重新写入SNDFIFO寄存器,该字节进入图1中的SYNC寄存器。
    3. 再次将重新发送的数据包的字节数写入SNDBC寄存器,这样可切换FIFO返回至USB侧,重新进行USB通信。
    4. 进入第3步,重新启动数据包。
由第4步可以看出该过程属于单缓冲传输。由于需要等待完成第4步操作,故当第一个FIFO从MAX3421E SNDFIFO移向USB外设时微控制器无需载入第二个FIFO。

若采用MAX3421E版本1,还需要执行第6步,因此,每次重复USB OUT传输时,同步触发器必须重新初始化。

双缓冲传输编程

当由多个64字节数据包组成的长数据组从USB主机传输至USB外设时,利用双缓冲可以提高性能。当SNDFIFO连接至USB发送一个数据包时,微控制器同时把下一个64字节数据包载入其他SNDFIFO中,从而改善了系统性能。然而,其程序流程要比单缓冲传输稍微复杂。某些情况下,需要触发FIFO (如得到NAK信号时),重新装载第一个字节并重写字节计数寄存器,使其返回初始位置,这就要求程序随时跟踪数据的两个缓冲器,图2给出了双缓冲传输的一种可能流程。本应用笔记最后给出了Send_OUT_Record()函数,实现图2所示流程。

图2. 双缓冲OUT数据包传输流程
图2. 双缓冲OUT数据包传输流程

右侧循环(步骤1至步骤4)将USB数据装载到FIFO;而从第5步开始左侧的循环则通过USB分配FIFO,并处理NAK重试。“First pass?”判断(步骤3)确保在主控制器载入第一个SNDFIFO后,USB传输立即开始。设计该流程图时使FIFO加载优先级高于发送优先级,因此,仍能保持双缓冲性能。

该流程最好从以下三个方面考虑:
  1. 发送一个数据包(1至64字节总有效载荷)。
  2. 发送两个数据包(65至128字节总有效载荷)。
  3. 发送三个或多个数据包(129或更多字节总有效载荷)。
请参考本应用笔记最后给出的Send_OUT_Record()函数例程,调用程序需向该函数传送4个参数:
  1. ep,为OUT数据包分配的终端号
  2. *pBUF,装满数据字节的缓冲器指针
  3. TBC发送总字节数(缓冲器中的字节数)
  4. NAKLimit,在停止和返回之前,从外设接收的连续NAK响应个数
该函数返回发送最后一个数据包时读取的MAX3421E主机计算结果代码寄存器(HRSL)的数值。如果传输成功,数值将为“ACK”,如果超出NAK极限值,或因其它问题终止产生错误代码,则数值为“NAK”。

发送一个OUT数据包

当发送的字节数等于或小于64字节时,从步骤1开始循环。若步骤1的结果为真,则SPI主机写SNDFIFO。如果在第10步中需要用到BC和FB,也就是说,外设以“NAK”响应OUT传输时,函数保存字节数(BC)和SNDFIFO第一个字节(FB)。由于这是第一次数据传输,函数从第4步开始进行OUT传输。直至没有字节需要发送时再返回步骤1,然后函数在第5步等待传输完成,并在第6步测试设备响应。在该点上如果得到ACK,步骤7检测到没有数据需要发送,则函数将在步骤11返回。然而,如果该点得到NAK,则在步骤9测试NAK极限值,如果超出该极限值,则在步骤10和步骤4中重新发送数据包。

发送两个OUT数据包

若发送的字节数介于65和128之间,则函数从步骤1处再次开始循环。如上所述,函数先写满第一个SNDFIFO,从步骤2开始立即进行传输直至步骤4。然后函数返回至步骤1,发现还有数据等待发送,于是在步骤2中再次写满SNDFIFO。由于这不是第一次传输,因此,在步骤4函数不能开始传输下一个数据包。再次返回至步骤1时,第一个SNDFIFO通过USB传输第一个数据包,与此同时,第二个数据包仍处于第二个SNDFIFO中,做好传输准备。需要注意的是,在步骤2中,函数实际保存了两组BC/FB数据(字节计数和FIFO第一个字节),一组为当前正在传输的SNDFIFO,另一组为在第二个SNDFIFO中等待的即将传输的数据。若没有数据需要发送(并且两个SNDFIFO都装满了数据),则控制流程进入步骤5。

函数在步骤5中等待传输第一个数据包,然后在步骤6判断传输结果。如上所述,如果数据包为“NAK'd”,函数在第10步将未决的BC/FB数值重新载入FIFO和字节计数寄存器中,在步骤4重新发送数据包,流程分支返回至步骤5 (通过步骤1),等待传输完成。每产生一个NAK,按步骤5-6-9-10-4-5循环往复,直至接收到ACK或达到NAK极限值为止。

如果外设应答(ACK) OUT传输,函数在步骤7测试流程是否终止。如果没有完成,则函数流程进入步骤8,发送正在第二个SNDFIFO中等待的下一个数据包。在步骤8中,函数执行多个任务:如果步骤10中需要载入“未决的” BC/FC数值,将这些数值复制到“当前” BC/FC变量;通过加载当前字节数,切换第二个SNDFIFO至USB;在步骤4中开始传输OUT数据。每接收到一个NAK信号,便如上所述循环操作。若在步骤7至步骤11过程中接收到ACK响应,则函数退出。

发送三个或更多个OUT数据包

在一个数据包正在发送,而另一个数据包将要开始发送(等待第二个SNDFIFO)之前,该函数基本上是按照先前发送两个数据包的流程进行的。每次函数均在第4步开始启动另一个数据包,在步骤1判断是否还有数据需载入SNDFIFO中。必须在还有数据需要发送,同时程序流程中FIFO有效的情况下,才能进入步骤2。因为步骤1至步骤3中“写SNDFIFO”循环的优先级高于步骤5...4的“发送FIFO”循环,因此数据通过USB传输时总是被写入SNDFIFO,从而实现双缓冲。

性能

图3. MAX3421E载入和发送USB数据包的示波器曲线
图3. MAX3421E载入和发送USB数据包的示波器曲线

图3为所示为用Send_OUT_Record()函数载入和发送64字节OUT数据包时叠加的示波器曲线。数据的大小为512个字节,由8个64字节数据包组成。由图中可以看出,与MAX3421E相连的外设不产生NAK信号,因此可对最大传输带宽进行测量,示波器一次就可捕获整个传输过程。

最上面的两条曲线为SPI主机(SPI硬件具有ARM7)通过SPI端口向MAX3421E SNDFIFO载入64字节数据的曲线。每次写SNDFIFO时,SS# (从机选择)线路为低电平,SCK (串行时钟)脉冲为65 x 8,每当载入命令字节时,向64字节数据提供8个时钟脉冲。在该曲线开始之前,第一次SNDFIFO加载结束,因此,图3给出了7次SNDFIFO载入时的情况。

第三条曲线是USB D+信号,表示64字节OUT数据包正通过USB从主机MAX3421E向外设传输。最下面一条曲线是脉冲,表示ARM7载入MAX3421E HXFR寄存器,开始传输。

需要注意的是:在USB数据包开始不久,ARM7开始载入第二个SNDFIFO,同时USB数据包在总线上进行传输。这一双缓冲过程可大大改善传输带宽。

带宽测试

图4. 图3传输过程的CATC (LeCroy)和总线分析结果
图4. 图3传输过程的CATC (LeCroy)和总线分析结果

图4所示,Send_OUT_Record()函数以6.76Mbps速率传输521字节的数据。作为参考,与PC相连的USB全速USB thumb drive采用UHCI USB控制器(USB的唯一附件) 以5.94Mbps速率传输512字节的数据。

方案实现中的注意事项

以下提供的代码实例是采用Maxim USB函数库进行编写和测试的,Maxim USB函数库的详细说明请参见应用笔记3936,"Maxim USB库"。该函数库工具包含MAX3421E 和MAX3420E USB外设控制器。若要对固件进行测试的话,采用USB电缆将MAX3421E主机连接至MAX3420E外设,通过将标为"*1*"的语句注释掉,使MAX3420E接受每个OUT传输(无NAK)。

注: MAX3421E的未来版本将不会再涉及到FIFO的重新载入问题。只需重写HXFR寄存器,MAX3421E就可重新启动一个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);
}