說明:
- 以下結論都是基于Windows XP系統所得出的,不保證在其他系統的适用性。
- 在此讨論的是HID自定義裝置,對于标準裝置,譬如USB滑鼠和鍵盤,由于作業系統對其獨占,許多操作未必能正确執行。
1. 所使用的典型Windows API
CreateFile
ReadFile
WriteFile
以下函數是DDK的内容:
HidD_SetFeature
HidD_GetFeature
HidD_SetOutputReport
HidD_GetInputReport
其中,CreateFile用于打開裝置;ReadFile、HidD_GetFeature、HidD_GetInputReport用于裝置到主機方向的資料通信;WriteFile、HidD_SetFeature、HidD_SetOutputReport用于主機到裝置方向的資料通信。鑒于實際應用,後文主要讨論CreateFile,WriteFile,ReadFile,HidD_SetFeature四個函數,明白了這四個函數,其它的可以類推之。
2. 幾個常見錯誤
當使用以上API時,如果操作失敗,調用GetLastError()會得到以下常見錯誤:
6: 句柄無效
23: 資料錯誤(循環備援碼檢查)
87: 參數錯誤
1784: 使用者提供的buffer無效
後文将會詳細說明這些錯誤情況。
3. 主機端裝置枚舉程式流程
4. 函數使用說明
CreateFile(devDetail->DevicePath, //裝置路徑
GENERIC_READ | GENERIC_WRITE, //通路方式
FILE_SHARE_READ | FILE_SHARE_WRITE, //共享模式
NULL,
OPEN_EXISTING, //檔案不存在時,傳回失敗
FILE_FLAG_OVERLAPPED, //以重疊(異步)模式打開
NULL);
在這裡,CreateFile用于打開HID裝置,其中裝置路徑通過函數SetupDiGetInterfaceDeviceDetail取得。CreateFile有以下幾點需要注意:
- 通路方式: 如果是系統獨占裝置,例如滑鼠、鍵盤等等,應将此參數設定為0,否則後續函數操作将失敗(譬如HidD_GetAttributes);也就是說,不能對獨占裝置進行除了查詢以外的任何操作,是以能夠使用的函數也是很有限的,下文的一些函數并不一定适合這些裝置。在此順便列出MSDN上關于此參數的說明:
If this parameter is zero, the application can query file and device attributes without accessing the device. This is useful if an application wants to determine the size of a floppy disk drive and the formats it supports without requiring a floppy in the drive. It can also be used to test for the file's or directory's existence without opening it for read or write access。
- 重疊(異步)模式:此參數并不會在此處表現出明顯的意義,它主要是對後續的WriteFile,ReadFile有影響。如果這裡設定為重疊(異步)模式,那麼在使用WriteFile,ReadFile時也應該使用重疊(異步)模式,反之亦然。這首先要求WriteFile,ReadFile的最後一個參數不能為空(NULL)。否則,便會傳回87(參數錯誤)錯誤号。當然,87号錯誤并不代表就是此參數不正确,更多的資訊将在具體講述這兩個函數時指出。此參數為0時,代表同步模式,即WriteFile,ReadFile操作會在資料處理完成之後才傳回,否則阻塞在函數内部。
ReadFile(hDev, //裝置句柄,即CreateFile的傳回值
recvBuffer, //用于接收資料的buffer
IN_REPORT_LEN, //要讀取資料的長度
&recvBytes, //實際收到的資料的位元組數
&ol); //異步模式
在這裡,ReadFile用于讀取HID裝置通過中斷IN傳輸發來的輸入報告。有以下幾點要注意:
1、ReadFile的調用不會引起裝置的任何反應,即HID裝置與主機之間的中斷IN傳輸不與ReadFile打交道。實際上主機會在最大間隔時間(由裝置的端點描述符來指定)内輪詢裝置,發出中斷IN傳輸的請求。“讀取”即意味着從某個buffer裡面取回資料,實際上這個buffer就是HID裝置驅動中的buffer。這個buffer的大小可以通過HidD_SetNumInputBuffers來改變。在XP上預設值是32(個報告)。
2、讀取的資料對象是輸入報告,也即通過中斷輸入管道傳入的資料。是以,如果裝置不支援中斷IN傳輸,那麼是無法使用此函數來得到預期結果的。實際上這種情況不可能在HID中出現,因為協定指明了至少要有一個中斷IN端點。
3、IN_REPORT_LEN代表要讀取的資料的長度(實際的資料正文+一個byte的報告ID),這裡是一個常數,主要是因為裝置固件的資訊我是完全知道的,當然知道要讀取多少資料(也就是報告的長度);不過也可以通過另外的函數(HidD_GetPreparsedData)來事先取得報告的長度,這裡不做詳細讨論。因為很難想象在不了解固件資訊的情況下來做自定義裝置的HID通信,在實際應用中一般來說就是固件與PC程式比對着來開發。此參數如果設定過大,不會有實質性的錯誤,在recvBytes參數中會輸出實際讀到的長度;如果設定過小,即小于報告的長度,會傳回1784号錯誤(使用者提供的buffer無效)。
4、關于異步模式。前面已經提過,此參數的設定必須與CreateFile時的設定相對應,否則會傳回87号錯誤(參數錯誤)。如果不需要異步模式,此參數需置為NULL。在這種情況下,ReadFile會一直等待直到資料讀取成功,是以會阻塞住程式的目前過程。
WriteFile(hDev, //裝置句柄,即CreateFile的傳回值
reportBuf, //存有待發送資料的buffer
OUT_REPORT_LEN, //待發送資料的長度
&sendBytes, //實際收到的資料的位元組數
&ol); //異步模式
在這裡,WriteFile用于傳輸一個輸出報告給HID裝置。有以下幾點要注意:
1、 與ReadFile不同,WriteFile函數被調用後,雖然也是經過驅動程式,但是最終會反映到裝置中。也就是說,調用WriteFile後,裝置會接收到輸出報告的請求。如果裝置使用了中斷OUT傳輸,則WriteFile會通過中斷OUT管道來進行傳輸;否則會使用SetReport請求通過控制管道來傳輸。
2、 OUT_REPORT_LEN代表要寫入的資料長度(實際的資料正文+一個byte的報告ID)。如果大于實際報告的長度,則使用實際報告長度;如果小于實際報告長度,會傳回1784号錯誤(使用者提供的buffer無效)。
3、 reportBuf[0]必須存有待發送報告的ID,并且此報告ID訓示的必須是輸出報告,否則會傳回87号錯誤(參數錯誤)。這種情況可能容易被程式員忽略,結果不知錯誤号所反映的是什麼,網上也經常有類似疑問的文章。順便指出,輸入報告、輸入報告、特征報告這些報告類型,是反映在HID裝置的報告描述符中。後文将做舉例讨論。
4、 關于異步模式。前面已經提過,此參數的設定必須與CreateFile時的設定相對應,否則會傳回87号錯誤(參數錯誤)。如果不需要異步模式,此參數需置為NULL。在這種情況下,WriteFile會一直等待直到資料讀取成功,是以會阻塞住程式的目前過程。
HidD_SetFeature(hDev, //裝置句柄,即CreateFile的傳回值
reportBuf, //存有待發送資料的buffer
FEATURE_REPORT_LEN); //buffer的長度
HidD_SetOutputReport(hDev, //裝置句柄,即CreateFile的傳回值
reportBuf, //存有待發送資料的buffer
OUT_REPORT_LEN); //buffer的長度
HidD_SetFeature發送一個特征報告給裝置,HidD_ SetOutputReport發送一個輸出報告給裝置。注意以下幾點:
1、 跟WriteFile類似,必須在reportBuf[0]中指明要發送的報告的ID,并且和各自适合的類型相對應。也就是說,HidD_SetFeature隻能發送特征報告,是以報告ID必須是特征報告的ID;HidD_SetOutputReport隻能發送輸出報告,是以報告ID隻能是輸出報告的ID。
2、 這兩個函數最常傳回的錯誤代碼是23(資料錯誤)。包括但不僅限于以下情況:
- 報告ID與固件描述的不符。
- 傳入的buffer長度少于固件描述的報告的長度。
據有關資料反映(非官方文檔),隻要是驅動程式對請求無反應,都會産生此錯誤。
5. 常見錯誤彙總
- HID ReadFile
- Error Code 6 (handle is invalid)
傳入的句柄無效
- Error Code 87 (參數錯誤)
很可能是createfile時聲明了異步方式,但是讀取時按同步讀取。
- Error Code 1784 (使用者提供的buffer無效):
傳參時傳入的“讀取buffer長度”與實際的報告長度不符。
- HID WriteFile
- Error Code 6 (handle is invalid)
傳入的句柄無效
- Error Code 87(參數錯誤)
- CreateFile時聲明的同步/異步方式與實際調用WriteFile時傳入的不同。
- 報告ID與固件中定義的不一緻(buffer的首位元組是報告ID)
- Error Code 1784 (使用者提供的buffer無效)
傳參時傳入的“寫入buffer長度”與實際的報告長度不符。
- HidD_SetFeature
- HidD_SetOutputReport
- Error Code 1 (incorrect function)
不支援此函數,很可能是裝置的報告描述符中未定義這樣的報告類型(輸入、輸出、特征)
- Error Code 6 (handle is invalid)
傳入的句柄無效
- Error Code 23(資料錯誤(循環備援碼檢查))
- 報告ID與固件中定義的不相符(buffer的首位元組是報告ID)
- 傳入的buffer長度少于固件定義的報告長度(報告正文+1byte, 1byte為報告ID)
- 據相關資料反映(非官方文檔),隻要是驅動程式不接受此請求(對請求無反應),都會産生此錯誤
6. 報告描述符及資料通信程式示例
報告描述符(由于是彙編代碼,是以不必留意其文法,僅需注意表中的每個資料都占1個位元組):
_ReportDescriptor: //報告描述符
.dw 0x06, 0x00, 0xff //用法頁
.dw 0x09, 0x01 //用法(供應商用法1)
.dw 0xa1, 0x01 //集合開始
.dw 0x85, 0x01 //報告ID(1)
.dw 0x09, 0x01 //用法(供應商用法1)
.dw 0x15, 0x00 //邏輯最小值(0)
.dw 0x26, 0xff, 0x0 //邏輯最大值(255)
.dw 0x75, 0x08 //報告大小(8)
.dw 0x95, 0x07 //報告計數(7)
.dw 0x81, 0x06 //輸入(資料,變量,相對值)
.dw 0x09, 0x01 //用法(供應商用法1)
.dw 0x85, 0x03 //報告ID(3)
.dw 0xb1, 0x06 //特征(資料,變量,相對值)
.dw 0x09, 0x01 //用法(供應商用法1)
.dw 0x85, 0x02 //報告ID(2)
.dw 0xb1, 0x06 //特征(資料,變量,相對值)
.dw 0x09, 0x01 //用法(供應商用法1)
.dw 0x85, 0x04 //報告ID(4)
.dw 0x91, 0x06 //輸出(資料,變量,相對值)
.dw 0xc0 //結合結束
_ReportDescriptor_End:
這個報告描述符,定義了 4 個不同的報告:輸入報告 1 ,特征報告 2 ,特征報告 3 ,輸出報告 4 (數字代表其報告 ID )。為了簡化,每個報告都是 7 個位元組(加上報告 ID 就是 8 個位元組)。下面用一個簡單的示例來描述 PC 端與 USB HID 裝置進行通信的一般方法。
|