ChamberPlus System Level Studio

  首頁 | Contact to us

 

News
Products
FAQ
Technicality
Links
OldNews

互動區:

留下您的足跡

想討論嗎?

 

USB DIY 講座 (八) ---  Bulk Transfer 基礎

----------------------------------------------------------------------------------------------------------------------------------------

        在之前我介紹過有關 USB I/O 的東西,那些基本上的 I/O 可以用 Control pipe 作掉就可以了。也可用來傳輸一些少許的資料,但若是要以 Control Pipe 來傳一些比較多的資料時,就有點不太方便了。為什麼?!因為,Control pipe 是走 Endpoint 0 的路徑。雖然,也可以準確的傳輸資料但(當然速度上沒有像 Bulk 那麼快)。但他最不好用的部分應該是:他在Firmware 方面,很容易跟我們USB 的Housekeeping 的程式混在一起。在 Firmware 維護真的很不好用,當然,您用哪一家的解決方案,我是不清楚,但我至少寫過三家的方案,基本上都是一樣的。我是不知道大家有沒有K過一本 Intel 的USB firmware 的書,薄薄的一本,書名我忘了,只有一百多頁,應該是一位新加坡的老中寫的(因為封面的英文名字是姓:Tan∼不要誤會,就是閩南語的陳!)。我想那本書應該是寫USB  Firmware 的經典。(對不起,我當初也是跟別人借來看的,我想,目前所有以8051為主的 USB Controller 的起源應該就是這本了!)所以,在此我才會介紹用 Bulk Pipe 來傳一些比較大的資料,(什麼是比較大資料?我是覺得只要是KBytes級 都算是比較大的了!)。您會發現,只要Control pipe 搭配 Bulk Transfer pipe 在韌體與軟體就可以發揮不錯的傳輸性能了。也可以拿來作一些很不錯的東西了。至少,我認為在這兩方面(軟體與韌體)的維護上就比較輕鬆一點。因為這兩者在PC端的軟體有點類似,甚至,有些在DRIVER上是可以共用一個 Function Call 的。

        另外,因為一般 USB Controller 的data buffer 都不可能太大的,所以,勢必在Firmware 上必須作到一些記憶體的管理,以期達到USB 傳輸的Performance 表現。不過,若大家對USB 的感覺還不到的話,我還是建議先求穩吧。接下來,我就用一些範例來說明基本的 Bulk Transfer。然後,我再利用另外一個章節來作一些好玩又實際應用的東西。

----------------------------------------------------------------------------------------------------------------------------------------------

        在一些實際運用,我講的實際運用指的是DIY一些用來傳一些資料,像傳統的UART (Serial) 或 Parallel來說,就屬 Control 及Bulk 最常用。所以我才專門講這兩部分。首先先看一下這兩者之間的差異:以下是一個 Control Pipe 的 傳輸:

     這張圖是從Cypress 的一份資料抓下來的。再來考考或複習一下大家對USB的基本觀念,其實,這張圖舉的例子不好,不知大家有沒有注意到圖中有一個很奇怪或較不合理的地方?首先,您要會解釋上圖的意義。一般 Control pipe 包含了兩個方向(針對同一Endpoint,而Bulk 就只能定義一個方向而已!),看您是否能夠跟著我解讀上圖的所隱含的意義?上圖是一個 Control In Protocol ,就是Host 透過Control Pipe 跟DEVICE 要了一筆資料。很簡單,大家都會。而比較奇怪的地方是:當 Host 要完資料之後,會再發出一個 Out Token ,但 Device 竟然是回 NAK !!以  USB Procotol 來說,沒有錯!但以系統應用來說:真的沒意義。因為這個OUT Token 就只是一個 NULL OUT (不帶資料的),所以Device 的韌體根本不用理他,直接回ACK就可以了。好了,您以為您跟我的解釋已經解釋完了?!錯!這個故事只講一半,因為我說的,您很少會真的去看USB 真正的運作,尤其是 USB Housekeeping 的韌體。上圖若反過來看就對了,而且合情合理了。什麼?反過來看?!就是,上圖若是一 Control Out 一筆資料給Device,再結束一個 NULL In 就合理了 !是嗎?您可以解釋這個現象嗎?為什麼 ?因為一般USB Control In 一筆資料之後,Device 其實不用再處理其他有關跟 USB 的東西了。就算 Host 還要繼續要資料,USB Device 的 USB Housekeeping 韌體也不用急得回NAK,因為可以直接擋在這個  In Token 動作上。聽不懂?!要好好的體會喔!

    反過來,若是Host Out 了一筆資料給 Device 的話,當您USB Device 的USB Housekeeping 韌體回 Out Token 之ACK之後,可能還要處理這筆資料,他可能是一組命令或需要及時處理的資料,此時,您就得先搬資料,再回 USB Protocol 這個 USB Protocol 指的就是後面那個  NULL In 的ACK~就是說您要先用NAK把最後那個 NULL In 擋下來,讓HOST先等在這一組Setup Token 中),您不要以為您USB Controller 性能有多好,跟您說:PC 南橋的USB 比您還強,一旦您錯放,下一個Setup 再緊接的來,您USB Housekeeping 韌體就手忙腳亂。尤其當您碰到OHCI的南橋,包您USB 出狀況。我想這種Bug 您要抓也不是一天或一個月功力的人就抓得到的!!真的不騙您,書本或教科書才不會教您這個呢!!您學USB 學這麼久,您有真的去解析這樣的問題嗎?!為什麼人家 USB Controller 要設計成那個樣子?為什麼要用韌體來維護  USB Protocol 的東西?您真的有體會到嗎?說到這裡您就知道要用 Control pipe 去傳一些資料是蠻討厭的,我說的,這部分會跟您 USB Device 的 Housekeeping 韌體混在一起,又要  Parsing 所傳的資料內容,所以,一不小心韌體程式就容易長大。所以,我們才需要額外的 Bulk Transfer Pipe 來作簡單的資料傳輸。以下就是BULK Transfer Protocol :

    上圖就是分別是一個 Host 要資料(Bulk In),一個傳資料(Bulk Out),您應該知道哪一個是哪一個吧?從上圖我們可以體會得到,一般Bulk In 會比Bulk Out 有效率多了,因為不會有 Payload 出來暫住USB 的傳輸頻寬。所以,相對來說:若您 USB Device 的 Data Buffer 若不夠大的話,中間那個 NAK 會一直出現來搶您USB BUS的頻寬。又相對於Setup 的 Control Pipe 來說,至少 Bulk Transfer 沒有那些NULL IN 或NULL OUT 的東西來干擾,速度當然快多了。(更何況一對 SOF 之間可以塞進好幾個BULK In/Out!)但相對來說,因為在Bulk In/Out 過程中,基本上是沒有其他對應的Token 出現,所以,要傳資料的多寡,就要事先定義清楚的(您不要以為在所謂 MSDC--Mass Storage Device Class中的 BOT -- Bulk Only Transfer 就不用事先定義長度,他也是有的!幸好我也做過 !)。這樣又會牽涉到屬於您自己寫韌體時,所必須自己定義的東西了,說真的,您平常其他沒有寫過一些有關資料傳輸的韌體的話,對您來說,可能會有一些挑戰性。所以,您若沒有這方面的經驗的話,我建議您可以先利用一般的 8051 的 UART 做一些簡單的練習會比較有手感。瞭解?

        接下來,我就用一些實際的例子來說明:

        首先,我們先瞭解一下我的資料流的大綱,在USB DIY 講座( 七) 中知道我的USB Controller 中有 16 KBytes 的Data Buffer,那到底要切多少出來作應用,就看每個人的需求而定。當然,您可以把他一切為二,各為 8 KBytes + 8 KBytes 的 AB buffer 方式,這就是有名的 Ping-pong Buffer的作法。但在此我不這樣做,因為,許多真正DIY的應用來說,使用彈性遠比性能更重要,當然有越大的Data  Buffer  他所能 Gain 的好處就越多,不只是性能表現而已,而是更多的使用彈性,譬如,可切成不同的BANK 來作不同的應用與儲存空間。在此範例我就用 2KBytes 來作範例,我說的以KBytes 的大小來看,是比較有效率一點,因為 2KBytes約佔兩個  SOF ,所以,萬一有些資料要放棄的話,剛好也可以塞Setup Token來通知Device ,理想上,在Bulk 傳輸上,就得盡量作到像我USB DIY 講座( 二)所顯示的內容,幾乎是每64 Bytes 緊接著64Bytes 的一筆一比進來會比較好一點。但我說過,Buffer畢竟有限,但不求完美,也求盡善。若是像 Cypress 的架構那樣,每64 Bytes 就休息一下(因為有其他中斷程式要處理啊!當然他Endpoint 的 Buffer也定義這麼大了!),做起來還蠻沒效率的,重點還要韌體介入(關於這部分的原因,您可參考我以下的範例),相對來說會增加穩定性的風險。

        在作之前再強調一個USB 傳輸很重要的觀念:不管您要傳的資料大小,那怕是只有 64 Bytes (一個 Packet),USB在寫傳輸韌體的觀念:是用擋的,而非用寫的!我想大部分的 USB Controller都是一樣的。解釋一下:什麼是用擋的?而非用寫的。這點是跟 Serial 或 Parallel 或是在PC上寫程式迴然不同的觀念。先講為什麼?因為USB Bus 上,是隨時都有Protocol 訊號在跑,您用寫的根本來不及硬體Protocol 訊號還是強調一下:USB 不比Serial or Parallel,serial & Parallel 是有傳資料時,BUS 才有訊號的。什麼是用寫的?就像在PC上或在一般 8051 韌體上,像我們在8051 要傳一個Bytes 就是:

            mov    SBUF,  a ;

USB 的傳輸觀念是用擋的:先定義要傳的內容位置與傳的資料長度,然後像水閘門一樣放開(填USB Control Register ),硬體就把您的資料批哩啪啦放出去或抓進來。然後您才後知後覺的知道傳完了(中斷通知您)!我想,一般人開始作USB時都沒體會到,因為書本都只教您要填這個Register或清那個 Control bit 。渾然不知原因。這就上面那兩張 Bulk In/Out 圖所要彰顯的意思,看一下範例(在韌體方面程式):

InitialM2BufferToBulKIn:

        lcall ClrDMA3Addr
        EP1TxNak         ;; PC 請您等一下,我要準備資料!

        clr A
        mov iBulkLSB, A ;; USB Bulk In Tx Loop,
        mov iBulkMSB, A ;; mov iROMBank, A
        mov iPageCNTLSB, A ;; 1 page = 256 bytes, i.e. ignor the LSB of Address
        mov iPageCNTMSB, A ;; 1 page = 256 bytes, i.e. ignor the LSB of Address

        ;; REG_WR MBLHR, #FFh ;; Set Buffer Ready Size 64 Bytes
        REG_WR MBLHR, #00h ;; Set Image-Ready always ready
        REG_WR MBLLR, #00h

        ;;---USBLoopPageSize:
        REG_WR USBTxBc1, #20h ;; Set "Page size" Counter
        REG_WR USBTxBc2, #00h ;; DMA3 Byte Counter for USB

        REG_WR USBTxPk1A, #40h ;; for bulk transfer fix 64 bytes
        REG_WR USBTxPk2A, #00h ;; Set "Page Size" for 64 Bytes

        lcall ISP_BulkRead2KBytesUSingPAGE    ;; 別吵!我還在搬資料!


        clr A
        ACC2IO D3ARCR

        USBReadM2En
        ;; LED_G_ON
       EP1Enable         ;;PC 好了!您可以把資料取走了!

        ret

這是一個 Bulk In 的韌體程式,看到沒當我在 USB Controller 中準備資料時,我是先用NAK 把PC 端的要求先檔掉,再寫一個Call function 慢慢搬∼(呵∼不好意思讓您看到她是一個Atmel ISP的應用程式)。然後,最後放開∼讓USB 抓資料。您能體會嗎?!若反過來呢?是PC要將資料傳下來呢?!就是 Bulk Out :

InitialM2BufferFromBulKOut:
        lcall ClrDMA3Addr

        clr A
        mov iBulkLSB, A
        mov iBulkMSB, A
        mov iBulkOutCnt, A
        mov iROMBank, A
        mov iPageCNTLSB, A ;; 1 page = 256 bytes, i.e. ignor the LSB of Address
        mov iPageCNTMSB, A ;; 1 page = 256 bytes, i.e. ignor the LSB of Address

        ACC2IO USBRxLoByCnt
        ACC2IO USBRxHiByCnt

        REG_WR MBLHR, #00h ;; Set Image-Ready always ready
        REG_WR MBLLR, #40h

        REG_WR USBTxPk1A, #40h ;; for bulk transfer fix 64 bytes
        REG_WR USBTxPk2A, #00h ;; Set "Page Size" for 64 Bytes

        clr A
        REG_WR USBRxLoByCntIrq, #00h ;; for bulk transfer fix 64 bytes
        REG_WR USBRxHiByCntIrq, #40h ;; Set "Page Size" for 64 Bytes


        mov DPTR,#D3CR
        mov A,#11101000b ;;b4=0:M2 b3=1:Write b2=0:USB, inhibit the DMA3 BCount load!
        movx @DPTR,A

       EP2TxTimeOut        ;; PC 您可以把資料放進來了!

        ret

    有沒有?我先定義要收資料是要擺在哪?之後,才放開讓資料能收進來(放心好了,當PC的應用程式要傳資料給您時,在USB BUS 一定都塞滿 了PayLoad 外加您USB Controller 所回的 NAK ,就像上圖中所示!),哈∼哈∼ 您又被我給擺了一道了,看上面那段Bulk Out initailize 程式是沒有意義的!!Bulk Out 的學問不在Initial 時,而是『後知後覺』的收到資料後的中斷程式:

EP2_Rx:
        EP2NAK        ;;請PC您等一下!
        ;;--- 2K Full--
        ;; cpl P1.1
        ;;==== LED Control Routine===    ;; 閃一下燈代表還再傳!
        cpl LEDR_                        ;; 既然都叫PC等了∼利用一下前一章節I/O 控制 !
        jnb LEDR_, LED_R_Blanking        ;; Firmware 自己控制I/O !
            LED_G_ON
        sjmp LED_R_Blanking_END
LED_R_Blanking:
            LED_G_OFF
LED_R_Blanking_END:
;;---
;;----- Move the internal Buffer to target buffer
;;---
        lcall ISP_BulkWrite2KBytesUSingByte    ;;慢慢搬資料!

        lcall ClrDMA3Addr

        inc iBulkLSB
        mov A, iBulkLSB
        jnz BulkOutCNT01
        ;;--
        inc iBulkMSB
BulkOutCNT01:
        mov A, iBulkLSB
        cjne A,BulkLength_LSB,ContinueousBulkOut
        mov A, iBulkMSB
        cjne A,BulkLength_MSB,ContinueousBulkOut
        ;;--- End of Host request --
        LED_G_OFF

        ret        ;; 搬完了!
ContinueousBulkOut:

        EP2TxTimeOut    ;; 沒搬完,放開閘門讓資料流進來,繼續搬!!
        ret

寫完了,簡單吧!

            還沒完∼ USB 又不是只作 USB Device 端,還得寫PC 端程式耶!先看Bulk In 的程式:

       hRead = open_file(inPipe);
       if(hRead==INVALID_HANDLE_VALUE)
       {
            ///----
            MessageBox("Open in pipe Error",NULL,MB_OK);
            return ;
        }
        BOOL success;
        UCHAR *fwbufBK;
        fwbufBK = fwbuf; // backup the pointer
        for (int j = 0; j < ROMMax; j++)
        {
            success = ReadFile(hRead,
                                fwbuf,
                                64*32,
                                &nBytesRead,
                                NULL);

            fwbuf += 64*32; // move buffer point to next Bank !!
        }
        CloseHandle(hRead);

            所以,我可以以 2 KBytes 為單位,愛傳多少就多少,對上層AP應用程式也只能管到這裡而已,至於如何丟封包,那是底層Driver 跟微軟的USB 硬體Driver 的事了。 相對來說: Bulk Out 的程式:

        // Open Bulk Out pipe
        hWrite = open_file(myoutpipe);
        if(hWrite==INVALID_HANDLE_VALUE)
        {
            MessageBox("Open Out Pipe Error",NULL,MB_OK);
            return ;
        }
        //---
        //--- Loop of Download routine ---
        //==============================================================
        for(k=0; k<ROMMax;k++)
        {
            //--- Bulk Out 2KBytes
            success = WriteFile(hWrite,
                                fwbuf,
                                64*32,
                                &nBytesRead,
                                NULL);

            fwbuf += 64*32; // move buffer point to next Bank !!
        }
        //--------------------------------
        //--- End of the Download Loop                                                 

        CloseHandle(hWrite);

真的寫完了∼哇∼PC程式更簡單!誰說USB 很難?!

            我這個人就是喜歡務實,口說無憑,就讓一些圖像結果來說明上述的實驗:

        我是故意讓韌體產生一個固定的資料型態,讓我們容易除錯,畫面上只秀出前面的 256 Bytes而已。而且是要求Device 傳了 4KBytes 上來!也就是 2* 2KBytes (就上述PC軟體程式中 ROMMax = 2),這代表韌體還 得準備第二組 2KBytes 資料來測試韌體的反應:

        從以上的兩張圖,代表我沒有騙您,我真的是USB Device 抓資料上來的!而且當我韌體知道PC端是要跟我Device 端要資料時(就是利用一般 Setup Control pipe來下命令的∼而MSDC的BOT 就是一直以 Bulk 傳命令的),我就開始用NAK 擋掉PC的需求,就是圖上 Packet#5072 一直到 Packet #6313,都是硬體幫我擋掉的。一旦我準備好資料。我韌體一放開水閘門∼資料就以飛快不間斷的傳出!直到下張圖所示:

        就是因為PC端要了 4 KBytes 資料,所以,PC 端的Driver 會『死咬』我的Bulk-in 要資料∼這時您就要一定回資料,否則您PC端的USB底層的Driver 會一直Lock 住,不是當機(此時,您的應用程式也沒輒了,我想連您的USB Driver 也是一樣的,這時是歸微軟USB作業系統的驅動程式在管的),而是您韌體要加油了∼這是做 Bulk 跟 Control不一樣的地方。也是很容易玩到PC當機的地方(剛開始寫PC軟體,尤其是寫Driver 的人,您會常看他們常重新開機PC就是這樣子來的!這樣的Protocol 不是很危險嗎?您又沒好好K USB規格了,規格跟您講,此時您可回Bulk Stall 來解套的哩啦∼不過您也不要太高興,當您回個 Stall 之後,作業系統會馬上來問您:為什麼?!結果您的韌體還是得回他為什麼的哩啦!!哈∼哈∼您都作不完的啦!)。

        所以,上圖中 Packet #6410 到 Packet #19224 之間,又是硬體幫我擋掉,韌體能夠慢慢的準備資料(對不起,在這個韌體程式內我是沒準備,我是故意delay讓PC等,然後再傳不同的資料以示不同的資料區塊!)∼ 講完Bulk Transfer的基本操作流程了。清楚了嗎?!

        再來利用上面的例子來討論 USB Performance 的問題:從 Packet #6313 到 Packet #6410 為 6313-6410 = # 97,而每三個的Protocol就傳一筆 payload,所以是約 97/3 = 32 個Payload = 32*64 = 2048 bytes!果然是一點耽誤都沒有,果然是性能優越(不是我愛吹牛,這就是不只要懂USB,還得悟到USB的精髓的)。但是從Packet #6410 到 Packet #19224 之間 就耽誤了很久∼不是沒傳,而真的被耽誤了∼所以也驗證了我所說的∼一來Device 內部的  Buffer 不夠大,不能一氣呵成的傳資料會造成傳輸瓶頸;二來就是Device 的微控器(Such as 8051)速度不夠快,讓USB 的頻寬在浪費!當然,IC內部的Buffer 總不能無限的擴充,所以瓶頸一定會存在,但就看您如何去調配∼就像我這顆USB Controller 我可以全部 16 KBytes 都用來支援 Bulk In ,那速度至少也要拉個四倍~當然您還可用我上面所說的 Ping-pong Buffer 的作法∼但是Ping-pong 的作法也是需要微控器去設定控制的暫存器(Registers),此時又會牽扯到微控器的性能問題。因為一般IC設計的暫存器都會擺在對8051 說,就是外部記憶體的位置,而8051 對外部記憶體存取方式就是最長的 12T (Movx + DPTR) 。所以,要怎麼作,我想我已經點出了所有做USB常碰到的問題了。說真的,這些問題都是作USB Controller IC 的人要去想的。而對我們USB DIY 一族來說,能夠適度調配我們記憶體的配置,發揮有效傳輸功能就夠了。您同意嗎?

        當然,對一般使用USB DIY來說,您大概也不會留意到這些細節,但您有沒有體會過:當您解工程問題時,您只要沒把握,心中存著那一點疑問時,那不久的將來肯定會出問題。USB 真的不像傳統的Serial & Parallel那般的簡單,但的確從傳輸的定義與設計觀念來說:USB真的優異多了,也就是人家可以很容易取代傳統Serial & Parallel的原因。

        其實,講到這裡,我想一般USB 的應用就已經可以作到很好了,甚至相對 USB 1.1 與 2.0 來說,兩者的精髓是一樣的,差別也只是在傳輸過程中,傳送資料載波的Clock 速度而已,但是否過來說:您真的抓到我上述討論的重點了嗎?還是您真的用USB 2.0 傳輸Clock ,反而一直在浪費USB的頻寬呢?

        我想:經由這一系列的講解USB的基本觀念與實驗操作,我相信利用 USB Control + Bulk  就可以做出許許多多 DIY的東西了。未來我會利用這些觀念,來作一些好玩又有趣的東西。敬請期待,也歡迎您們提出您們的看法,一起研究。謝謝!

 

首頁 | News | Products | FAQ | Technicality | Links | OldNews

Telephone : 886-3-5439918    FAX : 886-3-5437632

Copyright(C) 2005 . ChamberPlus System Level Studio All rights reserved.  Last Update: 2008年01月18日。