2007年1月25日 星期四

USB DIY -- USB Vendor Command



 

   我們來談一下關於在一般USB I/O 常用的 Vendor command 觀念。

上回有很簡單的提到關於USB I/O 簡單一個執行範例:

--

http://chamberplus.myweb.hinet.net/usb_diy5.htm

--

但是還是有許多讀者朋友還是不是很清楚關於這一部份的觀念,就打電話過來提問題。

雖然版主目前的工作目標是在全球各地大力推行關於FPPA單晶片多核心微控器。

但基於提攜後生晚輩的功德完滿的慈悲心,還是都很樂意的一一回答。

----

    為什麼這一部份會是很重要的觀念呢?!因為您只要弄清楚這一部份,您大概就不會想用一般所謂HID 的USB  Device 來開發一個簡單的純USB I/O  COntroller 的東西,而且這一部份就是USB 很基礎的東西,只要您搞懂之後,您大概USB的基本精髓就搞懂了,至少,我個人是這樣認為的。(但是,有時候,說起來很容易,但是要說到每位入門者都懂的話,真的要大家實際去演練一下!所謂:師傅引進門,修行在個人啊!)

   好,我們就開始簡單的說明一下:

    一般我們都希望透過USB介面來下命令來執行一些簡單的I/O 控制,譬如:點個LED燈啊 ;或是去控制一個步進馬達... 等等。其實,這一部份的答案就在版主的另一篇文章中: 

--

http://chamberplus.myweb.hinet.net/usb_diy2.htm

--

     但是可能許多人還是一時沒有悟到箇中的精髓,那版主就在此簡單的整理一下以上這兩篇文章的意義。

首先我們來看以下這兩張圖,並結合USB Controller 韌體來說明:

--

       一般在控制系統中,往往我們下完命令後,就想知道我們的控制系統有沒有收到我們的命令就是一個簡單的命令的通訊協定。只要控制系統可以準確的接到我們的命令,他當然就可以準確的執行我們要求的指令了!但是,在USB 中我們該如何完成這個動作呢?!

     以USB 的Control pipe 來說:是不能只用一組 Setup- token 來完成的。這是您所必須首先瞭解的。

我們PC端(主控機)該如何下命令呢?因為這一部份是屬於我們自製命令範圍,所以在USB 的規格中就幫我們預留了一個所謂Vendor Command 介面:在Chapter 9 中的那個 Table 9-2 Format of Setup Data (記住:當初版主學USB時,也是一手規格書,一手寫程式的!) --- Offset 0 他定義了什麼?就是 bmRequestType !! 

 Bit7  : 是定義一般命令的方向:是由PC 下給USB Device 的呢?還是USB Device 要回給 PC 的!?

Bit6..5 :就是說明上述所謂:是一般標準Command (一般來說就比較屬於Emuneration 用的)!或是Class Command 用的?(就是我們所謂 MSDC 或HID 的特殊命令,這一部份,您就要另外看相關Class 規格書!),剩下的就是我們所謂的Vendor Command    !! 所以,由此我們就知道:一般來說:Setup token 的第一個Byte 中我們的命令是不是Vendor Command 了:

   若是 0x40 的話:就是PC要下給 USB Device 的 Vendor  Command ; 0xc0 就是PC要求 USB Device 要回給 PC 的命令!(注意喔:還是PC 要求USB Device 的喔~因為USB的觀念還是主從觀念,PC 沒有要求USB Device 回命令,USB Device 還是不能主動回的喔!!)

    所以一個簡單的程式流程:

    PC 端的程式:

int i;
       for(i=0;i<8;i++) STICommandString[i]=0x00;
       STICommandString[0]=0x82; // SET USB Controller P1
       STICommandString[1]=(UCHAR)(USB_P1&0x00FF); // SET USB Controller P1
       USB_DEVICE_SEND_STI_COMMAND(STICommandString);

   就是要下一個Vendor Command !


  

      我們在 USB 的分析儀上就可以清楚的可以看到我們所下的命令:注意喔:我們在Setup Token 看到的是: 0x40 0x01 0x12 0x20 0x40 0x3F 0x40 0x00 !! 第一個所謂的 0x40 就是要告訴USB 驅動程式,我們這一個USB Command 是Vendor Command !

    所以,在相對的韌體中就可以找到所謂:

SetupToken:
                REG_RD  bmRequestType ;;讀取bmRequestType Register
                mov     B, A ;;避免破壞原有的值,先存放在 B Register中
                anl     A, #00011111b ;;為了以後要用,先把Bit0~4存到bmReqTypeCopy中
                mov     bmReqTypeRec, A
                mov     A, B
                anl     A, #01100000b   ;;因為只先判斷Bit5和6,所以把其它的Bit清掉

  DoRequestStandar:
                cjne    A, #00h, DoRequestClass;;比較是不是Standard Command
                ljmp    RequestStandard                 ;; 00 01 02 80 81 82

  DoRequestClass:
                
                cjne    A, #20h, DoRequestVendor        ;;比較是不是Vendor Command 
                ljmp    RequestClass

  DoRequestVendor:
                cjne    A, #40h, DoRequestReserved ;;如果不是上述兩種就表是有錯誤!!!
                             
                ljmp    RequestVendor                   

                
  DoRequestReserved:
                ljmp    RequestReserved                 ;; Other
   -----

    對不起,我們是老人家,還會用所謂組合語言,我們是比較落伍了。(但是我們學 C 語言    會很快呢!也會C 語言! 所以,我們活得比您快樂,還有競爭優勢:

void CTLSETUP_Packet(void) USING_1
/*++

Routine Description:

        process setup packet of control pipe

Arguments:

        none

Return Value:

        none

--*/
{
        UCHAR type;

        if (G_ucCtrlPhase != K_CommandPhase)
        {
                //phase transition error - setup packet should not appear
                //                         at current phase
                //Since hardware will reset the control pipe whenever
                //setup packet arrives, we just force the phase to command phase.
                G_ucCtrlPhase = K_CommandPhase;
        }

        CTLSETUP_GetSetupPacket();      //read setup packet

        G_usCtrlDataLength = G_pCtrlCommand->wLength;
        G_usCtrlDataIndex = 0x0000;

        //check Type of bmRequestType
        type = G_pCtrlCommand->bmRequestType & 0x60;
        if (type == 0x00)
        {
                CTLSETUP_StandardRequest();     //Standard Request
        }
        else if (type == 0x20)
        {
                CTLSETUP_ClassRequest();        //Class Request
        }

        else if (type == 0x40)
        {
                CTLSETUP_VendorRequest();       //Vendor Request
        }
        else //if (type == 0x60)
        {
                CTLSETUP_ReservedRequest();     //Reserved Request
        }

}

     怎樣?!版主不唬您吧!版主也寫過的哩~但在這一次用C 語言作USB 的東西,沒有第一次用組合語言寫 USB 程式快樂!

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

         然後我們的韌體就可以解 PC 端下給我們的命令內容了~就是上圖中那一大串的 64 Bytes 的內容,夠長了吧!看來還不只可以傳命令,還可以傳資料呢!!您覺得光是Setup Token  就可以讓我們用 USB 作多少DIY 的東西啊?!

      ;;-----------------------------------------------------------------------------
ScanSTIOutData:
                REG_RD  USBDsrr0 
                mov     Command, A
                mov R0, A
                lcall   STITxDataClear                         
                lcall   USBSTIAPI

                call    SetUSBSTITxData    ;;; Assign Status(Zero or Non-Zero) to Echo Cmd
                call    SetUSBSTITxData2
               
                ret

;;==============================================================================

     ....

...

    我們就可以解PC端的命令的!

USBSTIAPI:
MessageCmdInAPI:
  mov A, Command
;------------------------------------------------------------------------------
USB3Cmd41:    ;;Cancel_cmd
                cjne    A,#41h,USB3Cmd25
  ret
;------------------------------------------------------------------------------
USB3Cmd25:    ;; Test Command
                cjne    A,#25h,USB3Cmd35
....

   簡單吧!

   -----

    不過,這裡還有一個很重要的學問:您看到我上述解USB Vendor Command 時,還Call 其他副程式:

                call    SetUSBSTITxData    ;;; Assign Status(Zero or Non-Zero) to Echo Cmd
                call    SetUSBSTITxData2

   就是還要負責 回 USB Protocol 的東西,就是我在USB 簡介中所提到的 USB House-keeping 的管理程式。往往一般人會被兩者給搞亂的。其實,這些USB House-Keeping 成是就是拿來管USB 什麼時候開關USB Setup - Out- In 的那個 通訊協定。記住:USB的Protocol 都是很快的~我們是透韌體還控管的。其實,在一般USB Controller 中(像 Cypress這一種),就是去填一大堆的 USB Registers的!

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

       所以,當我們收完PC 端的命令後,往往PC還會下個確認命令,要我們USB Device Echo 我們所收到的命令,所以就會有另一組Setup Token : 0xc0 0x01 0x13 0x20 0x00 0x3F 0x40 0x00 !

這個0xc0 就是PC 端要跟USB Device 要一組 命令資料。(不過,這裡版主比較偷懶,所以用  8 bytes 回的 圖中為0x02 0x80 0x00 0x00 0x00 0x00 0x00 0x00 ~照一般的習慣是:64 Bytes 下,就用 64 Bytes 收會比較習慣一點!)

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

    所以,這個一來依往的命令就可讓我們的韌體聽從PC 下過來的命令,來執行PC 端所要求的指令了!

夠簡單吧 !

--------

   其實,這其中還要交代一個很重要的觀念:您可別以為這樣子,您PC 端就可以拼命的下命令,或是傳資料喔!因為在USB 的命令傳輸是很快的,而往往我們韌體在執行一些命令時(如:控制步進馬達等這種快不得的指令時!)是需要一點時間的,您如果拼命下命令,您的USB Controller 就必須常常被叫去解USB Command ,結果,哈~哈~ 不是馬達轉起來怪怪的,就是USB 介面不小心就給當掉了!...您的PC端 應用程式還死的不明不白呢!!....您總覺得為何USB 怎麼這麼奇怪?!那這一部份,說真的,版主因為不知道您真正的應用為何?所以,我也沒辦法幫上您什麼忙呢!!這不是版主不負責,真的,我也不知道!  就像版主現在在推多核心的微控器時,我也不知道客戶拿這麼一顆微控器要作什麼啊?!或許,您可以告訴我,我們可以研究研究一下。----所以您會有一種感覺說:怎麼版主您怎會懂這麼多東西?!因為我們不會藏私什麼?我們是以更寬廣的心,去迎接更寬廣的視野。我們所得到的資訊就越廣!您一直抓著您手上的東西,您怎麼可以還可以抓別的東西呢?!您不幫助別人解決問題,人家怎麼會跟您說他的想法呢?!為什麼我們會相信神明呢?!因為祂們一直在聆聽大家的說法,也看多人們的作法,所以,祂們是用更寬厚的慈悲心來看待我們!而我們自私的人們呢?!由衷的期望我們會有一個處處無私的社會自然就會有競爭力的國家!共勉之!

   ---

4 則留言:

  1. Chamber兄:
    我有一個問題, 你的範例是否是bulk mode的傳輸格式而且須Driver的應用?
    若是用HID的方式, 是否可以不用Driver而使用Vender command? 我試過HID只能用Set report方式送資料, 無法定Vender command.

    回覆刪除
  2. 賈老師的真老公2007年2月16日 晚上9:23

     
             我上述的範例是用Setup Token 為範例的!不過,同樣的道理對於Bulk Token 的也是適用的!因為一般所熟悉的MSDC 也是利用Bulk Token 傳命令的!
              當然我自己的Driver 就可以傳屬於我自己的Vendor Command 的~但若是標準驅動程式的話,像一些 MSDC 也是可以的!這一點我自己用過,是可以的!至於HID?!我沒做過,不過,我記得上回有人請我去看問題時,我好像也有看過類似的作法。
            其實,這種作法您必須去確認相關的Class Driver 規格書,看他裡面有沒有定義所謂的Vendor Command ?!像MSDC 所遵循的命令格式是SCSI 命令格式,而SCSI 命令格式中,就有所謂的Vendor Command 的定義方式。不過,這種作法還是會有風險的!因為,您會想得到,人家也會想得到,所以難免會有衝突的機會!---當然,用自己的Driver 就不會有這個風險,但是就是要自己寫一隻Driver ,是比較討厭的!若兩者要混得用~可不可以?!當然可以!就更複雜了!因為您要宣告為兩個Interface~然後掛兩枝Driver !!當然其中一隻就是標準的!結果還是一樣的!
     
          不知這樣的回答,您清不清楚?!若有問題,您還是可以提出討論!謝謝! 
     

    回覆刪除