作者:董宇新 張東來 來源:微電腦資訊
1. 引言
硬體驅動開發是嵌入式開發的基礎,而隨著USB裝置的普及,USB裝置驅動開發在嵌入式開發中變的越來越重要。
為了支援不同類型的可以串連到基於Windows CE的平台外圍裝置,微軟提供了具有定製介面的流介面驅動程式模型。而大部分USB外圍裝置由於功能性更適合流介面驅動的結構,都可以採用流介面驅動程式模型來開發自己的驅動程式。本文就是基於流介面驅動程式,對Windows CE平台下的USB外圍裝置的驅動開發進行了詳細的分析和設計。
2. Windows CE下USB(Universal Serial Bus)系統分析
Windows CE下USB系統軟體由兩層組成:較高USB裝置驅動程式層和較低的USB函數層。較低的USB函數層本身又由兩部分組成—較高的通用序列匯流排驅動程式(USBD)模組和較低的主控制器驅動程式(HCD)模組。依據HCD模組提供的功能,USBD模組實現高層的USBD介面函數。USB裝置驅動程式使用USBD介面函數與它們的外圍裝置進行通訊。
在資料轉送的過程中,操作流程通常按下列的次序進行:
1.USB裝置驅動程式進行資料轉送的初始化,即通過使用USBD介面函數給USBD模組發送資料轉送的請求。
2.基於有關匯流排的情況與匯流排相連的USB裝置的特性,USBD模組將該請求分成一些單獨的事務。
3.HCD模組排好事務通過匯流排的次序。
4.主控制器硬體執行或完成這些事務。
所有的匯流排上的事務都是從主機一側發出的,外圍裝置完全是依賴的。
3. USB裝置驅動程式的進入點函數
所有的USB裝置驅動程式必須在它們的DLL庫呈現一定的進入點與USBD模組進行適當的互動。這些進入點函數不僅使USBD模組能夠與外圍裝置的驅動程式串連,也使得驅動程式能夠建立和管理任何可能需要的註冊鍵:
1.USBDeviceAttach
當USB裝置串連到主要電腦時,USBD模組調用這個函數。這個函數主要用於初始化USB裝置,取得USB裝置資訊,配置USB裝置,並且申請必需的資源。在裝置管理員初始化裝置的時候,可以將裝置驅動控制代碼等資訊作為ActivateDevice()函數的第二個參數寫入到註冊表HKLM/Drivers/Active/xx目錄下,並且將註冊表地址作為參數傳給XXX_Init()函數以供應用程式使用。
2.USBInstallDriver
該函數在第一次載入USB裝置驅動程式時首先被調用,主要用於將一個驅動程式所需的註冊表資訊,例如裝置名稱等寫入到HKEY_LOCAL_MACHINE/Drivers/USB/ClientDrivers目錄下。需要注意的是,USB裝置驅動程式不使用標準的註冊表函數,而是使用RegisterClientDriverID()、RegisterClientSettings()函數來註冊相應的裝置資訊,方法如下:
BOOL
USBInstallDriver( LPCWSTR szDriverLibFile )
{
// 設定USB裝置描述資訊,如果某資訊為空白則設為USB_NO_INFO
USB_DRIVER_SETTINGS usbDriverSettings = { DRIVER_SETTINGS };
// 寫註冊表
bRc = RegisterClientDriverID( wsUsbDeviceID );
if ( !bRc )
{
return FALSE;
}
bRc = RegisterClientSettings( szDriverLibFile,
wsUsbDeviceID,
NULL,
&usbDriverSettings );
if ( !bRc )
{
return FALSE;
}
……………………………
}
3.USBUninstallDriver
當使用者從基於Windows CE的平台中刪除驅動程式時調用該函數,它負責調用UnRegisterClientSettings()和UnRegisterClientDriverID()函數刪除由驅動程式的USBInstallDriver()函數建立的所有註冊鍵。
4. 針對USB裝置的流介面驅動程式的設計
USB裝置驅動程式的存在使得外圍裝置可以為應用程式提供服務,表示了流介面驅動程式和USBD模組以及Windows CE其它系統組件的相互關係。
圖1 流介面驅動程式的系統結構
Fig.1 The frame of Stream Interface drivers
每個流介面驅動程式必須實現XXX_Init()、XXX_IOControl()以及XXX_PowerUp()等一組標準的函數,用來完成標準的檔案I/O函數和電源管理等,而在產生一個DLL後,就用裝置檔案名稱首碼替換下列名字中的XXX。
下面以XXX_Init()作為例子簡單介紹這幾個函數的使用。
XXX_Init()這個函數並不是由應用程式直接調用的,而是通過裝置管理員提供的ActiveDeviceEx()函數來調用的。由於在裝置管理員初始化裝置的時候我們已經使用ActivateDevice()函數將裝置控制代碼等資訊寫入Drivers/Active下面,所以在應用程式初始化時我們只需要將註冊表的地址作為Context參數傳給XXX_Init(),然後利用RegOpenKeyEx()、RegQueryValueEx()等函數執行開啟和讀寫註冊表操作,執行成功後則返回USB裝置的控制代碼等資訊。
下面是USB網路攝影機驅動程式CAM_Ini()函數中的部分原碼:
CAM_Init(PVOID dwContext)
{
…………….
LPTSTR ActivePath = (LPTSTR) dwContext; // HKLM/Drivers/Active/xx
HKEY hKey;
long lStatus;
…………….
// 開啟註冊表
lStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
ActivePath,
0,
0,
&hKey);
// 擷取註冊表中的資料
lStatus = RegQueryValueEx( hKey,
TEXT("ClientInfo"),
NULL,
&dwType,
(LPBYTE)(&dwVal),
&dwValLen);
……………..
pUsbPrn = (PUSBCAM_CONTEXT)dwVal;
…………….
return (pUsbPrn );
}
在對裝置進行讀操作之前,首先要先通過執行CreatFile()函數來調用XXX_Open()開啟裝置,XXX_Open()所需的第一個參數是應用程式初始化時由XXX_Init()返回的裝置控制代碼。而XXX_Read()需要的第一個參數是CreatFile()執行成功後返回的驅動引用執行個體控制代碼,第二個和第三個參數分別是用於從驅動中讀資料的緩衝區地址和長度。應用程式通過ReadFile()函數來調用XXX_Read(),而在XXX_Read()中通過調用USBD模組提供的不同的傳輸函數來與不同的USB外圍裝置進行通訊,例如在印表機裝置中需要批量傳輸函數IssueBulkTransfer()。
XXX_Write()函數的使用方法和XXX_Read()大致相同,其餘的流介面函數的功能如下表所示。
表1 流介面驅動程式進入點函數
Table 1 The entry function of Stream Interface drivers
函數名稱 |
描述 |
XXX_Init |
當裝置管理員初始化一個USB裝置的時候調用這個函數 |
XXX_Deinit |
當裝置管理員卸載一個USB驅動程式的時候調用這個函數 |
XXX_Open |
在開啟一個USB裝置驅動的時候應用程式通過CreatFile()函數調用這個函數 |
XXX_Close |
在USB驅動程式關閉的時候應用程式通過CloseHandle()函數調用這個函數 |
XXX_IOControl |
上層的軟體通過DeviceIoControl()函數可以調用這個函數 |
XXX_Read |
USB裝置驅動程式處於開啟狀態的時候由應用程式通過ReadFile()函數調用 |
XXX_Seek |
對USB裝置的資料指標進行操作,由應用程式通過SetFilePointer()函數調用 |
XXX_Write |
在一個USB裝置驅動程式處於開啟狀態時由應用程式通過WriteFile()調用 |
XXX_PowerUp |
在系統重新啟動前調用這個函數 |
XXX_PowerDown |
在系統掛起前調用這個函數 |
得到這些編譯出來的流介面函數後還必須建立一個.def檔案,告訴連結程式需要輸出什麼樣的函數,最後將驅動程式編譯到核心中去,這樣這個USB裝置流介面驅動程式就可以被應用程式調用。
5. 結論
在Windows CE下,由於微軟提供了通用序列匯流排驅動程式(USBD)模組、USBD介面函數全集、樣本主機控制器驅動程式(HCD)模組,所以我們只需要根據USB裝置硬體特性,利用USBD提供的不同函數,實現流介面函數與外圍裝置的互動。這大大節省了開發時間,從而能更快速地進行嵌入式開發。