摘要:該應用筆記對主機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); }
評論
查看更多