初涉學USB,初學者USB入門總結
一,概述
現在很多的主控上都帶有USB的功能,但是對于初學者來說,這方面應用還是比較棘手,因為usb的不但固件程序需要編寫,PC端的驅動也要編寫,而且驅動寫好了還要寫個上位機才能看出效果。這樣調試起來十分困難,建議從USB的鍵盤,鼠標開始做,了解清楚了,再做自己的協議就比較簡單了。
USB的概念歷史啥的這里就不說了。我們先不管具體的數據包格式,這一節先從整個包的層面上簡單的說,過程是這樣的,
---------------------------------------設備插入-------------------------------------------------------------
1)??? 主機會輪回查詢各個USB端口,主機檢測到D+與D-之間有電壓差,就認為有新的設置接入。主機等待100ms后發出復位請求。設備接到復位請求后將產生一個外部中斷信號。
---------------------------------------枚舉過程------------------------------------------------------------
2)??? 主機這時候只是知道有新的設備插入了,但是不知道插進來個什么東西,所以就開始詢問它是什么設備,怎么用,負荷能力怎么樣。這個時侯就進入了枚舉過程。因為剛剛插入的設備沒有分配地址,就用默認地址0,首先發送一個Get_descriptor(獲取設備描述符)指令包,設備接到包后就開始解析包(其實就是你在固件程序里判斷處理) ,然后按固定格式返回自己設備的設備描述符,這一步主要是主機知道你的USB設備的基礎屬性,比如支持的傳輸數據長度,電流負荷多少,支持那個USB版本,以及以后方便電腦找驅動的PID,VID。
3)??? 這時候主機知道你(你做的設備,簡稱你吧)的數據長度還有電流大小后,下一步就是給你分配一個屬于你的地址。
4)??? 給你一個地址后就開始詢問你的具體配置。首先發送一個試探性的設備配置請求Get_configuration(要求固定返回9個設備配置字),你接到后就開始發送9字節的設備配置字,其中包括你的配置字的總長度,這樣主機就知道你的配置到底有多長,然后再發一次設備配置請求,這時你就開始上傳所有的配置字。這個時侯主機就已經很明白你的工作方式就各種特性,然后就可以正常工作了
5)??? 如果你在前面的某些配置(以后章節詳細說明)要求要說明自己的名字什么的,這里還要上傳字符串描述符。
6)??? 如果是鼠標或者鍵盤還要上傳報告描述符
---------------------------------------正常數據階段------------------------------------------------------
7)??? 這個時侯你已經被主機正式接受并且注冊了,你可以通過自己寫測驅動或通用驅動與電腦進行通訊了。
以上是簡單的描述,詳細的后面章節再做介紹,學習一個東西關鍵是首先要知道這個東西是什么,簡單的工作原理。對于USB的工作我這里做個比方,
???? 主機好比一個公司,你就是USB設備,要進入公司首先要面試(枚舉),你到了面試現場(第一次插入設備),面試官首先了解到你的外表,性別已經你要應聘的崗位(設備描述符),然后給你一個號,以后就開始按號叫人,當你被叫到就開始問你的專業知識,性格等(配置描述符),如果你比較合適(通過了枚舉)你就會錄取了,并且注冊一個你的信息到公司(驅動安裝,并且寫入注冊表)。等你下次來公司,只要把工號(PID,VID)報上,就知道是你來了。
初涉USB,初學者USB入門總結(2) 設備固件程序
二,實際數據過程測試
(圖片顏色顯示不出,可以到我博客http://blog.csdn.net/arthur05611/archive/2009/02/23/3929778.aspx)
這節主要是對固件里的USB請求處理有個概念,還有就是調試的方法。大篇幅的程序配合,如果不關心這一塊的話可以跳過,呵呵。
為了更好的說明整個USB啟動過程,我們可以用串口實時的跟蹤各個USB中斷。不過這里先不用串口進行測試,只是簡單的用一組變量記錄過程。測試程序如下(以下會有程序的說明):
uchar test[100];//100長度的變量,記錄過程
uchar conters=0;//記錄計數值,
/*------------------------------------------------------------
??????? 高校電子聯盟--肖繼達
???? QQ:258347765???
-------------------------------------------------------------*/
void EXT_int(void)//USB中斷響應函數
{
??? /*------------------------------------------------------------
????????? Check interrupt status register to know interrupt
?????? source.
??? ------------------------------------------------------------*/
??? if (USB_BUSRESET_ASS_INT())
??? {?? /* USB bus reset */
??????? /*? for USB Rev.1.1
???????????????????? After USB bus reset released, 10msec recoverly time we have.
???????????????????? Follwing request must be processed normally.
??????? */
??????? CLR_BUS_RESET_STATE();? /* USB bus reset status clear */
??????? /*------------------------------------------------------------
?????????? Endpoint0 setting
??????? ------------------------------------------------------------*/
??????? /* Tx/Rx payload size setting */
??????? /* Rx payload is fixed as 8-byte or 32-byte, therefor the
?????????? setting has no meaninig */
??????? SET_PAYLOAD_EPn(EP0RX, device_descriptor.bMaxPacketSize0);
??????? SET_PAYLOAD_EPn(EP0TX, device_descriptor.bMaxPacketSize0);
??????? /* Stall bit, the value undefined after reset, cleared */
??????? CLR_STALL_EPn(EP0);
?????????????
??????? /*------------------------------------------------------------
?????????? Misceronous status variable initialization
??????? ------------------------------------------------------------*/
??????? usb_status.configuration = NULL;
??????? usb_status.remote_wakeup = 0;
??????? usb_status.address = 0;
??????? usb_status.dvcstate = DEFAULT_STATE;??? /* Device state :DEFAULT */
??????? usb_status.stall_req = 0;
?????????????? #ifdef Debug
???????????????????? test[conters]='!';
???????????????????? conters++;
???????????????????? #endif
??????? /*------------------------------------------------------------
?????????? Callback to application layer
??????? ------------------------------------------------------------*/
??????? (*usb_status.callback)();
??? }
??? else if (SUSPENDED_INT())
??? {?? /* suspended state */
??????? /* for USB Rev.1.1
???????????????????? Transit to suspended state after detect the USB line has kept idle over 3msec.
???????????????????? After resume detected, end suspend state in 3msec to be able to respond
???????????????????? the host request.
??????? */
????????????? CLR_SUSPENDED_STATE();
???????????????????? #ifdef Debug
???????????????????? test[conters]='@';
???????????????????? conters++;
???????????????????? #endif
??? }
??? else if (AWAKE_INT())
??? {?? /* Deveice awake state */
??????? /* AWAKE procedure */
??????? CLR_AWAKE_STATE();????????? /* Request clear */
???????????????????? #ifdef Debug
???????????????????? test[conters]='#';
???????????????????? conters++;
???????????????????? #endif
??? }
??? else if (USB_BUSRESET_DES_INT())
??? {?? /* USB bus reset deassert */
??????? /* Procedure for USB bus reset de-assert */
???????
??????? CLR_BUS_RESET_DES_STATE();? /* Request clear */
???????????????????? #ifdef Debug
???????????????????? test[conters]='$';
???????????????????? conters++;
???????????????????? #endif
??? }
??? else if (SOF_INT())
??? {?? /* SOF interrupt status */
??????? CLR_B_SOF_STATE();??
?????????????? #ifdef Debug
???????????????????? test[conters]='%';
???????????????????? conters++;
???????????????????? #endif
?????? /* SOF interrupt status clear */
??? }?? /* SOF interrupt status */
??? if (SETUP_RDY_INT())
??? {?? /* setup ready */
????????????????? #ifdef Debug
???????????????????? test[conters]='^';
???????????????????? conters++;
???????????????????? #endif
??????? read_Device_Requests();
??? }
??? else if(EP1_PKTRDY_INT())
??? {?? /* EP1 packet ready */????
??????? read_FIFO(EP1);
?????? }
??? else if (EP2_PKTRDY_INT())
??? {?? /* EP2 packet ready */
??????? write_FIFO(EP2);
??? }
??? else if (EP0_RXPKTRDY_INT())
??? {?? /* EP0 receive packet ready */
??????? read_FIFO(EP0RX);
??? }
??? else if (EP0_TXPKTRDY_INT())
??? {?? /* EP0 transmit packet ready */
??????? write_FIFO(EP0TX);
??? }
}
計錄的結果在變量查看中顯示如下:
首先我解釋一下,這段程序是我在做USB設備時的中斷函數。主控(就是你往里面寫固件程序的那個東西)會在要求設備進行操作時,產生一個相應的中斷(我們可以用中斷的方式,也可以用查詢的方式,中斷的方式的好處就是主機有需要操作的都會叫你,而用查詢你必須不斷的問主機“有事么”,這里采用中斷方式),比如主機給設備設置地址,主機會通過固定的通道(point0)發送一個‘設定地址’包,設備主控接到包后會產生中斷,并且把響應的狀態保存在相應的寄存器中,我們只要在中斷程序判斷各個寄存器就能完成主機的任務。
程序中藍色字是中斷類型的判斷,其對應的宏定義就不列出來了。如果是這個中斷就會執行相應的中斷操作。并且一次中斷只有一種中斷類型,我們在每個中斷響應中加一段紅色字的程序,是為了保存每次中斷的狀態,比如剛插上設備,來了一次BUSRESET總線復位中斷,就會進入相應的中斷操作,完了后記錄狀態test[conters]='!'; conters++;意思是進入了這個中斷就在這一組數的當前位置設成'!',并且位置記錄的變量加一,以便下一次記錄到下一個位置。這樣USB的過程我們就記錄了下來,
???? 下面看一下記錄結果(其中的數字和字母是響應標準請求時的程序產生的這里不羅列程序了)。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.jpg
可以看到,一開始是一次總線復位,然后USB bus reset de-assert,然后再掛起總線。重復了兩次,然后就是上一節的具體配置了。
這節主要是對固件里的USB請求處理有個概念,還有就是調試的方法。
?
初涉USB,初學者USB入門總結(3) 數據包闡述
對于USB傳輸大體有個概念,下一步就來看看到底USB上傳的什么東西,以什么格式傳數據,先不涉及端點的概念。
各種總線的數據傳輸都是以固定的層次協議進行的,USB當然也不例外。所謂的層次也只是個抽象的概念罷了,就是表達一種依附關系,上層要依賴與底層,上層以底層為基礎,上層只需要關心自己的東西就行了,如果你還不明白,那就繼續看,學習一個東西不可能一兩句話說的明白一個點,需要全面了解后才能清楚各個點。
要實現兩個機器(機器的范圍比較廣,可以是電腦,交換機,單片機)的通信總是要有一個載體才可以,對于機器當然是電平高低為載體,具體的說機器甲要告訴機器乙一件事情(比如說一條指令),那么機器甲可以通過一根線(串行數據總線)連到機器乙的一個IO口上,甲發送一個個的高低電平,乙固定時間檢測自己的這個IO口,然后逐個記錄下放到自己的緩沖里,這樣乙就收到甲送的數據了。上述就是一個簡單的數據鏈路層(計算機網絡里這么叫)的描述,這一層要保證的就是甲發的每一位數據,乙都可以正確及時的接受,并且對在傳輸過程中出錯的數據做出反應。其實比數據連路更底層的還有物理層,這就是真正的物理介質,對于機器就是電線了,數據就是電線上傳輸的電壓,USB是用的四線,兩個電源,兩個數據線。
這里也打個比方,比如人與人進行交流,我們當然是通過說話了,物理層就是空氣和傳輸的聲波,數據鏈路層就是我們說的每一個字,物理層就是空氣,負責把我們說的話轉換成聲波傳給對方,數據鏈路層負責讓對方能正確的聽到每個字,如果聽的不清可以告訴對方重新說一遍。
經過上述的兩個底層,就可以保證每一位數據可以正確的傳到對方那里去。下一步的工作當然是解析數據代表了什么,一般來說,數據都是以一串數為單位,一般稱為一個包,機器間傳輸都是以一個包為單位傳出,就像人們說話都是以一句話為單位輸出一樣。每一個包包含有許多位數據,這些數據又分段表示不同的意義,如圖一,這是一個USB令牌階段的包,Sync是同步數據(相當于說話時先打個招呼,告訴對方要跟他說話了),PID是包標示(告訴對方這個包是干什么用的),ADDR是對方的地址(叫對方的名字),ENDP是用端點幾通訊(先不介紹這個),CRC5是校驗位(判斷這個包是否在傳輸中出錯),EOP是包結束。
|--------------------------------------------------------|
|?? Sync? |? PID? |? ADDR? |? ENDP? |? CRC5? |? EOP????? |
|________________________________________________________|
????????????????????? 圖一
USB的數據包又分為三種,一個是令牌包,一個是數據包,另一個是握手包。每一次的USB通訊事務處理都是以令牌包開頭,告訴對方要跟誰說話,這句話是用來干嘛的。如果要求有數據傳輸,則下一步就是數據包,另外如果要求對方要有反饋,則會發出握手包。令牌包又簡單的包括OUT,IN,STEP三種類型,OUT是用于主機告訴設備主機要向USB設備發送數據,IN是用于主機告訴設備要上傳數據,而STEUP是用于主機向USB設備發送配置信息,在枚舉過程中會用到。另外數據包和握手包的具體格式什么的,可以參照詳細的協議。
可以看到在所以的通訊過程中,主機都是發起者,不管是主機發送數據到USB設備還是USB設備發送數據到主機,都必須收主機控制。圖二為一次事務的過程
??
??????? 令牌階段?? ——》??????? 數據階段???? ——》???? 握手階段
??????????????????????????????? 圖二
這個過程可以這樣描述,甲和乙對話,甲是老板,乙是職員。第一節已經講過了,乙面試就是枚舉,在這個過程中,甲多段的發送STEP令牌包給乙,乙收到后如果要反饋數據,就發數據包給甲,甲正確接收后,跟甲握握手,表示這次對話成功。
乙被正式錄取后,甲會分派任務(OUT),這時甲對乙說有任務給你(令牌階段),然后乙就開始聽,甲說你的任務就是記錄數據并且上報(這段話就是數據包),乙說好的(握手包)。
乙開始正式工作,并且記錄數據。過了一段時間,甲開始要求提交數據(IN),乙把數據報告給甲(數據階段),甲說好(握手成功)。這里乙不能主動的去向老板匯報,只能被動的干活。
上面已經講USB主機和設備間數據傳輸的過程,都是我個人理解,有不正確和不到位的大家提出,方便初學者理解,謝謝??
初涉USB,初學者USB入門總結(4)USB通訊設備快速開發
經過上述三節的描述,對USB應該已經有了初步的認識,其中具體的協議(比如各個描述符的定義什么的)這里不做描述了,網上一搜一大堆。下面我以一個實例來詳細說明快速開發USB設備的步驟,
一,設定規劃
凡事預則立,不預則費,所以開發一個小小的USB也要稍微規劃一下,比如想象要實現什么功能,傳輸的數據協議什么的。
二,固件編程,
固件編程說白了就是寫單片機程序,要實現USB一般可以使用帶USB功能的單片機,再個就是加一個專用的USB芯片。這里以內部集成USB功能單片機為例
固件的USB開發一般就是先使能USB,使能USB時鐘,使能各個USB控制中斷(掛起,復位,標準請求,寫入,寫出等)然后USB就能正常工作了,這時候不如不寫別的東西,電腦就可以檢測出有USB設備插入了,具體的反應是在設備管理器里會發現閃了一下說明發現了新的USB設備,接下來電腦會發送各種標準請求,因為這個時候你的程序還沒寫完整,對這些請求不會有反應,所以電腦不可能識別出是什么東西。
接下來的工作就是在中斷中響應電腦傳來的各種標準請求。當必要的請求都被正確的響應的話,這個時候如果電腦里有正確的驅動,電腦就會去加載這個驅動,如果是第一次插入這個設備,還要把驅動安裝一下,然后設備就進入正常工作了,電腦會顯示“這個USB已經成功安裝并可以應用了”。
這里捎帶著說一下端點(endpoint)的概念,一般一個USB設備都會有數個端點,端點就是一個數據緩沖控制區(FIFO),每個緩沖區相當于有一個出口一個進口的池子,數據通過進口進入到池子,然后你再在固件里去用這些數據。固件往電腦寫數據,也是把數據先放到池子里,然后打開出口,就可以干自己的事情,不用一個個的把數據發出了,池子的出口自動把數據流出。
一般的端口0是用來做標準請求響應用的,也就是在枚舉階段用到。我一般把端口1定義為出(OUT),端口2定義為入(IN)(注意,這個OUT和IN是相對與電腦的,也就是說OUT是數據從電腦出去到設備,IN是設備的數據進入電腦)。這些定義也是在標準請求中去告訴電腦的。
接下來就可以實現與電腦的通訊了,你把數據放到相應的池子里就行了。下面就可以自己定義通訊的數據格式了。比如控制開發板上的8個LED的第一個燈亮,那么上位機發送數據0x55,0x01,0x80,0xaa。我們就可以規定第一個數據是啟示位,遇到這個表明開始一次控制指令,0x01表示這個是控制燈亮暗的指令,0x80表示LED的控制數據,最高位是1,表示第一個亮,其他位是0,表示都暗。最后一個數據是0xaa,表示這是結束。其實所謂的數據協議不過就是自己定義的一套讓通訊雙方都能正確理解對方的數據格式。電腦比較是電腦,什么都要規定好了,它才能正確的工作。
三,驅動程序
??? 對于快速開發用Driverstudio就可以了,我先裝了VC6.0,然后裝了DDK2600,最后裝了Driverstudio,網上有說這個順序不容易出問題,我也沒時間去試別的順序會出怎么樣的特效,姑且不管他是否在忽悠,先這樣按了沒壞處。
??? 我一開始比較新潮的裝了DriverStudio3.2版本,然后按網上的方法破解了,生成了驅動是能打開設備,但是就是傳輸不了數據,搞了兩天還是不行,后來想到是不是3.2版本太新了?或者破解沒完整?然后卸載了3.2裝了3.1,果然可以了,真不知道是Compuware做了手腳故意玩我還是本人愚笨弄錯了哪里。
驅動生成的步驟可以在百度,Google里搜“10分鐘完成一個USB驅動程序”能出來一
大堆,要是你嫌搜索麻煩就直接點這個算了http://www.4oa.com/Article/html/6/33/482/2005/17317.html按那個步驟操作就可以了,根據向導操作完了以后,VC就會出來一個驅動程序框架了,如果你在這個時候編譯一下就可你會碰到很多問題,我的操作是這樣的。首先把DDK的庫編譯一下,操作網上有, 網上有云:
1.啟動Visual C++ 。
2.選擇菜單 File|Open Workspace。打開位于DriverStudio/DriverWorks/Source/vdwlibs.dsw的工作空間文件。
3.選擇菜單 Build|Batch Build,在彈出的對話框中選擇你想編譯的庫。
4.點擊Build編譯你選擇的庫。
然后在VC的Driverstudio的工具條點擊“change environment variables”,在第一個選型卡把DDK的路徑選上,我的是C:\WINDDK\2600。然后點OK,接下來點DriverStudio工具條的編譯,就可以了,如果你還是碰到問題,你可以把VC顯示的錯誤復制到百度。
評論
查看更多