摘要:该应用笔记对主机USB控制器MAX3421E版本1和2进行了讨论。MAX3421E采用双缓冲发送FIFO向USB外设发送数据。在该器件的版本1和2中,需着重考虑OUT传输的编程。该应用笔记详细说明了发送机制。并给出了单缓冲和双缓冲OUT传输的例程。
R2:NDFIFO,FIFO数据寄存器
R7:SNDBC,字节计数寄存器
微控制器对SNDFIFO寄存器R2进行重复写操作,向FIFO写入多达64字节的数据。然后,微控制器再对SNDBC寄存器进行写操作,完成以下3项任务:
图1. SNDFIFO寄存器和SNDBC寄存器载入一对“ping-pong”FIFO和字节计数寄存器
如图1所示,第一个FIFO字节不是由物理FIFO产生,而是由用来调谐两个内部时钟使其同步的同步寄存器产生,其中一个时钟用于微控制器,另一个时钟用于USB逻辑。
若采用MAX3421E版本1,还需要执行第6步,因此,每次重复USB OUT传输时,同步触发器必须重新初始化。
图2. 双缓冲OUT数据包传输流程
右侧循环(步骤1至步骤4)将USB数据装载到FIFO;而从第5步开始左侧的循环则通过USB分配FIFO,并处理NAK重试。“First pass?”判断(步骤3)确保在主控制器载入第一个SNDFIFO后,USB传输立即开始。设计该流程图时使FIFO加载优先级高于发送优先级,因此,仍能保持双缓冲性能。
该流程最好从以下三个方面考虑:
函数在步骤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响应,则函数退出。
图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所示,Send_OUT_Record()函数以6.76Mbps速率传输521字节的数据。作为参考,与PC相连的USB全速USB thumb drive采用UHCI USB控制器(USB的唯一附件) 以5.94Mbps速率传输512字节的数据。
注: MAX3421E的未来版本将不会再涉及到FIFO的重新载入问题。只需重写HXFR寄存器,MAX3421E就可重新启动一个OUT数据包。这种改进不再需要Send_OUT_Record()函数。
引言
MAX3421E是一款USB主机/外设控制器,也就是说,当作为主机工作时,采用双缓冲发送FIFO将数据发送至USB外设,这一对儿FIFO由两个寄存器控制:R2:NDFIFO,FIFO数据寄存器
R7:SNDBC,字节计数寄存器
微控制器对SNDFIFO寄存器R2进行重复写操作,向FIFO写入多达64字节的数据。然后,微控制器再对SNDBC寄存器进行写操作,完成以下3项任务:
- 指明MAX3421E SIE (串行接口引擎) FIFO中需要发送的字节数。
- 连接SNDFIFO和SNDBC寄存器至USB逻辑,以进行USB通信。
- 清除SNDBAVIRQ中断标志。如果第二个FIFO可以用于µC装载的话,那么SNDBAVIRQ将立即重新触发。
图1. SNDFIFO寄存器和SNDBC寄存器载入一对“ping-pong”FIFO和字节计数寄存器
如图1所示,第一个FIFO字节不是由物理FIFO产生,而是由用来调谐两个内部时钟使其同步的同步寄存器产生,其中一个时钟用于微控制器,另一个时钟用于USB逻辑。
单缓冲传输编程
对于那些带宽、无严格要求的简易传输而言,固件发送一个数据包相对比较简单。步骤如下:- 将字节载入SNDFIFO。
- 将字节载入SNDBC寄存器。
- 向HXFR寄存器写入OUT PID和端点号,传输开始。
- 等待HXFRDNIRQ (主机发送完成中断请求)。
- 从HRSL寄存器中读取传输结果代码。
- 如果为ACK,表明传输已完成。
- 如果为NAK,表明将进入下一步。
- 再次装载第一个FIFO字节,重新开始传输:
- 写SNDBC = 0,在微控制器控制下返回一个虚拟值,以切换含有OUT数据的FIFO。
- 仅将第一个FIFO字节重新写入SNDFIFO寄存器,该字节进入图1中的SYNC寄存器。
- 再次将重新发送的数据包的字节数写入SNDBC寄存器,这样可切换FIFO返回至USB侧,重新进行USB通信。
- 进入第3步,重新启动数据包。
若采用MAX3421E版本1,还需要执行第6步,因此,每次重复USB OUT传输时,同步触发器必须重新初始化。
双缓冲传输编程
当由多个64字节数据包组成的长数据组从USB主机传输至USB外设时,利用双缓冲可以提高性能。当SNDFIFO连接至USB发送一个数据包时,微控制器同时把下一个64字节数据包载入其他SNDFIFO中,从而改善了系统性能。然而,其程序流程要比单缓冲传输稍微复杂。某些情况下,需要触发FIFO (如得到NAK信号时),重新装载第一个字节并重写字节计数寄存器,使其返回初始位置,这就要求程序随时跟踪数据的两个缓冲器,图2给出了双缓冲传输的一种可能流程。本应用笔记最后给出了Send_OUT_Record()函数,实现图2所示流程。图2. 双缓冲OUT数据包传输流程
右侧循环(步骤1至步骤4)将USB数据装载到FIFO;而从第5步开始左侧的循环则通过USB分配FIFO,并处理NAK重试。“First pass?”判断(步骤3)确保在主控制器载入第一个SNDFIFO后,USB传输立即开始。设计该流程图时使FIFO加载优先级高于发送优先级,因此,仍能保持双缓冲性能。
该流程最好从以下三个方面考虑:
- 发送一个数据包(1至64字节总有效载荷)。
- 发送两个数据包(65至128字节总有效载荷)。
- 发送三个或多个数据包(129或更多字节总有效载荷)。
- ep,为OUT数据包分配的终端号
- *pBUF,装满数据字节的缓冲器指针
- TBC发送总字节数(缓冲器中的字节数)
- NAKLimit,在停止和返回之前,从外设接收的连续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为所示为用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所示,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); }
评论
查看更多