彭 偉
PENG Wei
(武漢城市職業學院 電子信息工程學院,武漢 430064)
每年有超過20億的新安裝器件,可連接多種類型的設備,能夠自動進行配置,連接簡單且支持熱插撥,多數時候不需要提供獨立的電源,具有高傳輸速率,高可靠性等,上述諸多優點使USB成為了最成功的PC機接口。微軟為大量USB類設備提供類驅動程序,且一直在改進和增加類驅動程序的數量,對于USB設備獨立硬件供應商(IHV),Windows驅動程序基礎(Windows Driver Foundation,WDF)已成為USB驅動程序開發的首選模型,它提供3個選項來訪問USB設備:1)使用UMDF實現用戶模式驅動程序;2)使用KMDF實現內核模式驅動程序;3)將Winusb.sys作為設備功能驅動程序安裝,并提供WinUSB API供應用程序訪問設備。本文將基于WinUSB通用驅動程序及Microchip公司USB接口系統固件框架,以PIC微控制器與SHT75傳感器為核心,上位機選用C#開發平臺,開發實現基于WinUSB的溫濕度數據采集與控制系統并進行仿真。
Windows驅動程序包括總線驅動程序(bus driver)、功能驅動程序(function driver)、過濾驅動程序(filter driver),圖1給出了Windows USB驅動程序堆棧體系結構框圖,圖中出現了微軟推出的USB設備通用驅動程序WinUSB,它基于Windows Driver Frameworks (WDF)開發,由內核模式驅動程序(Winusb.sys)與用戶模式DLL(winusb.dll)共兩個基本組件構成。WinUSB(Winusb.sys)可作為單一USB設備或復合設備功能驅動程序安裝在設備內核模式堆棧中。用戶程序通過調用WinUSB用戶模式動態鏈接庫winusb.dll公開的函數與USB設備通信。圖1展示了包含3個Winusb.sys實例的USB驅動程序堆棧,實例1注冊設備接口A,支持用戶模式驅動程序(Usboem.dll),實例2注冊設備接口B,支持通過系統服務(SVCHOST)與winusb.dll進行通信的掃描儀用戶模式程序(Usbscan.exe),實例3注冊設備接口C,支持固件升級程序(Usbfw.exe)。實例2、3基于USB通用父驅動程序usbccgp.sys,為復合設備提供服務。所有用戶程序均通過加載winusb.dll,調用DLL所提供的函數與USB驅動程序棧通訊。
通過WinUSB驅動程序與設備通訊,用戶程序不需要構造設備I/O控制請求來執行標準的USB操作(如配置設備、發送控制請求以及與設備互傳數據)等,用戶程序調用winusb.dll提供的函數時,winusb.dll將使用應用程序提供的數據構建適當的IOCTL調用DeviceIoControl,將請求發送至Winusb.sys處理,請求完成時WinUSB將Winusb.sys返回的信息回傳到調用進程,調用失敗或掛起時將返回零值,調用GetLastError可獲取詳細的錯誤消息。

圖1 Windows USB驅動程序堆棧體系結構及含多個Winusb.sys實例的USB驅動程序堆棧
Winusb.sys還是UMDF功能驅動程序與關聯的設備之間鏈接的關鍵部分,Winusb.sys作為上層過濾驅動程序安裝在設備的內核模式堆棧中,應用程序通過與設備的UMDF用戶模式驅動程序通信來發送讀、寫或設備I/O控制請求,驅動程序則與框架交互,后者將請求傳遞到Winusb.sys,Winusb.sys處理該請求并將其傳遞到協議驅動程序,最后傳遞到設備,任何響應均通過反向路徑返回。
WinUSB作為設備功能驅動程序安裝時,其驅動程序包共有4項內容:1)WinUSB輔助安裝程序(Co-installer)Winusbcoinstaller.dll;2)KMDF輔助安裝程序WdfcoinstallerXXX.dll;3)將Winusb.sys安裝為設備功能驅動程序的INF文件;4)簽名的數據包目錄文件(Signed Catalog File)。KMDF和WinUSB輔助安裝程序須從同一版本的WDK獲取,WDF輔助安裝程序文件須從最新版本的WDK安裝目錄獲取,以便該驅動程序支持所有最新的Windows版本。
WinUSB驅動包中的INF文件將Winusb.sys安裝為設備的功能驅動程序,為操作系統匹配驅動程序與設備提供重要信息。INF文件中與上、下位機USB接口軟件開發關系最為密切的信息包括:1)USB固件內設備描述符定義的VID/PID。例如本文INF文件中[Manufacturer]區有:%USBMyDevice.DeviceDesc% = USB_Install,USBVID_00AA&PID_00BB,它將設備VID/PID設為0x00AA/00BB。2)設備接口類(Device Interface Class)GUID。上位機用戶程序使用該GUID枚舉WinUSB設備接口,新項目中的GUID可由guidgen.exe生成,INF文件[USB_Install.HW]區通過AddReg指示符在注冊表中的DeviceInterfaceGUIDs鍵下存儲一個或多個設備接口類GUID,例如本文所編寫的INF文件中有:HKR,,DeviceInterfaceGUIDs,0x10000,"{B6B014A8-8CDE-4112-9474-F15486E401BE}"。Winusb.sys每次加載時都會根據注冊表中的DeviceInterfaceGUIDs項指定的設備接口類注冊一個設備接口實例。INF文件[Version]區還有一個ClassGUID條目,例如本文中該條目定義為:ClassGUID={36FC9E60-C465-11CF-8056-444553540000},它是設備安裝類(Device Setup Class)GUID,INF文件的ClassInstall32指示符將在注冊表中創建:…CurrentControlSetControlClass{36FC9E60-C465-11CF-8056-444553540000},并導入INF文件內的相關子鍵值,用于標識該設備類安裝程序。
Winusb.sys安裝為內核功能驅動程序時使用[DDInstall.Services]的AddService=WinUSB,…實現,ServiceType設為SERVICE_KERNEL_DRIVER,ServiceBinary設為Winusb.sys。安裝為設備上層過濾驅動程時,需通過[DDInstall.HW]的AddReg指示符在注冊表中創建上層過濾驅動條目“UpperFilters”。
Windows 8之前的操作系統將Winusb.sys作為功能驅動程序加載時需提供定制的INF文件,該文件指定了設備特定的硬件ID,同時包括內置Winusb.inf的部分,這些部分是實例化服務、復制內置二進制文件以及注冊設備接口GUID所必需的。
為探測下位機USB設備是否接入主機,可調用RegisterDeviceNotification注冊設備通知消息,在設備移除或接入時通知窗體WndProc進程,該進程檢測到有設備接入主機時進一步獲取“設備路徑”。
Winusb.sys被加載時,它根據注冊表中DeviceInterfaceGUIDs鍵下該設備接口類注冊一個設備接口。用戶模式程序調用SetupDiGetClassDevs可枚舉該設備接口類設備并返回該類的“設備信息集”句柄,循環調用SetupDiEnumDeviceInterfaces可逐一枚舉設備信息集中的設備接口,提取索引指定的設備接口詳細信息時可調用SetupDiGetDeviceInterfaceDetail,所獲取的信息通過SP_DEVICE_INTERFACE_DETAIL_DATA結構返回。由于該結構大小無法提前獲取,故需連續兩次調用該函數,第二次調用時接口詳細信息將填充到根據第一次調用返回值所確定大小的該緩沖區,通過緩沖內該結構的DevicePath成員中可獲得“設備路徑”。本文有:DevicePath="\\?\usb#vid_00aa&pid_0 0bb#2&2260b228&0&e1#{b6b014a8-8cde-4112-9474-f15486e401be}”。當檢測到設備路徑中含有“Vid_00AA&Pid_00BB”時即表示搜索成功,否則繼續枚舉下一接口。
Windows API提供的CreateFile函數可創建、打開文件、目錄、物理盤、卷、COM端口、設備(包括USB設備)、服務、通訊資源、命名管道或控制臺等,返回用于訪問相應對象的句柄。CreateFile的第一個參數lpFileName是將要創建或打開的對象名稱,Unicode允許該參數最多為32000個字符,該類型長字符串所使用的前綴標識為“\?”,C語言表示的DevicePath中長串標識符“\\?\”后面分別是USB設備實例“USBVid_00AA&Pid_00BB2&2260b228&0&E1”和設備接口GUID。將設備路徑DevicePath賦給CreateFile函數的lpFileName參數即可獲取設備句柄,所獲取的設備句柄傳給WinUSB函數WinUsb_Initialize,可獲取WinUSB句柄。有了WinUSB句柄,即可進一步調用Winusb.dll提供的其他大量WinUSB API實現對USB設備的訪問。
C#中調用WinUSB函數,需要通過DllImport引入DLL文件并聲明函數,例如:
[DllImport("winusb.dll",SetLastError = true)]
internal static extern Boolean WinUsb_Initialize(…);
所設計的系統中,讀傳感器溫濕度數據及控制繼電器開關的自定義命令字節分別為0x81、0x82,向USB設備發送命令字節的函數為WinUsb_WritePipe(寫管道),該函數前兩個參數分別為WinUSB接口句柄InterfaceHandle和管道號PipeID,其中PipeID由1個方向位+7個地址位構成,對應于下位機USB固件中端點描述符內的bEndpointAddress字段,寫管道函數的其他參數分別為:數據緩沖區指針、緩沖區大小、實際傳輸字節數及可選的Overlapped參數。本文將Overlapped設為IntPtr.Zero,使寫管道函數向PipeID(0x01)管道完成一字節控制命令輸出并返回后,才開始讀取USB設備返回的傳感器數據,調用WinUSB讀管道數據函數WinUsb_ReadPipe“同步讀取”USB設備返回的溫濕度數據時,上位機用戶程序可能因USB接口斷開、返回數據量大等原因導致程序被阻塞。為避免這一問題,可使用Delegate代理實現對USB設備所返回數據的“異步讀取”,定義的代理對應于讀管道函數WinUsb_ReadPipe,調用BeginInvoke方法時除了給出讀管道函數所需要必須的參數以外,另附加的兩個參數為:1)new AsyncCallback(GetBulkDat);2)ReadDelegate。前者指明代理調用WinUsb_ReadPipe返回時的異步回調函數為GetBulkDat,后者為當前代理對象實例,當GetBulkDat被回調時,ReadDelegate作為IAsyncResult類型的參數傳給該回調函數,AsyncState屬性獲得代理對象,并執行EndInvoke等待BeginInvoke調用的方法結束。如果IsCompleted屬性值為真則表示從USB設備通過批量(Bulk)傳輸讀取的數據已裝填到指定緩沖。
瑞士SENSIRION傳感器公司推出的數字式溫濕度傳感器SHT75實現了數字式輸出、免調試、免標定、免外圍電路及全互換功能。為方便用戶開發應用溫濕度傳感器SHT75,SENSIRION提供了SHT75的8051 C程序范例,可以非常方便的移植為PIC微控制器C程序,PIC從傳感器讀取4字節數據后,根據函數:Calc_STH75與Calc_Dew_point可分別計算溫濕度及露點值。由于這兩個函數內浮點計算量大,本文將它們移至上位機C#程序中,PIC微控制器則僅負責從傳感器讀取四字節溫濕度原始數據,通過USB接口直接傳輸給上位機,上位機C#程序再通過這四字節原始數據調用上述兩個函數計算出相應結果,這樣處理可提高傳輸速度并減輕下位機微控制器負荷。C#函數GetBulkDat通過緩沖中的四字節數據完成浮點計算后,仍然需要通過代理在主窗體中刷新顯示,因為該函數與主窗體不在同一線程運行。
回調函數讀取數據管道結束并通過顯示代理完成窗體信息刷新顯示后,接著再次通過寫管道函數WinUsb_WritePipe發出讀取SHT75傳感器數據的命令字節0x81,實現對溫濕度數據的實時連續采集。繼電器控制與之類似,唯一差別是刷新顯示下位機返回的繼電器狀態后并不繼續調用WinUsb_WritePipe發出控制繼電器的命令字節0x82,而是等待上位機用戶的下一個操作。
為便于工程技術人員開發應用PIC USB微控制器,Microchip公司提供了PIC微控制器USB協議棧固件框架,可處理大量USB通訊任務,所提供的面向不同客戶群的演示項目均采用協作式多任務環境(禁止出現阻塞),內置USB模塊的PIC18F4550微控制器具有獨立的中斷邏輯結構,協議棧框架核心文件usb_device.c提供主狀態機函數USBDeviceTasks,對USB中斷進行檢測并進入相應的處理程序。
固件框架同時提供了處理用戶I/O操作的演示函數ProcessIO,由固件程序內的主函數調用。本文程序設計的ProcessIO函數負責讀取上位機命令,根據所接收到的上位機命令字節0x81與0x82分別實時回傳SHT75傳感器溫濕度數據(4字節)及控制繼電器開關并同時返回繼電器狀態。
固件框架中的描述符文件usb_descriptors.c給出了VID/PID定義,描述符中設置設備類編碼為0x0000(bDeviceClass/bDeviceSubClass),表示一個配置內的每個接口均獨立指定了其自身的類信息及各種接口操作,接口類編碼0xFFFF(bInterfaceClass/bInterfaceSubClass)則表示接口類由廠商指定。端點描述符中的端點屬性(EndpointAttributes)全部設為_BULK(批量傳輸端點)。PIC固件程序分別定義的數據收/發緩沖為INPacket與OutPacket,與上位機C#程序定義的緩沖OUTBuffer與INBuffer對應。在指定端點非忙時,固件可通過USBGenWrite、USBGenRead分別發送與接收數據包,二者最終均調用USBTransfer OnePacket(ep,dir,data,len),該函數為協議棧提供的通用單包傳輸函數。
Labcenter公司的實物電路仿真軟件Proteus具有對微控制器及其外圍電路組成的綜合應用系統的交互仿真功能,仿真USB接口需要安裝虛擬USB驅動程序(Virtual USB Drivers)并添加USBCONN組件。圖2給出了以PIC18F4550微控制器及溫濕度傳感器SHT75為核心的USB接口應用系統仿真電路。

圖2 USB接口溫濕度檢測與控制系統仿真測試電路
PROTEUS提供的USB虛擬分析器可跟蹤設備與上位機用戶軟件及驅動程序的所有交互信息,包括IRP請求(包括IOCTL與MJ_PNP)、USB事務(包括SETUP、IN、OUT)、USB寄存器(包括UCON、UIR、USTAT、BDnSTAT、BDnCNT、BDnADRL/H等)。測試運行USB接口應用系統時,借助USB虛擬分析器可觀察到主機給USB設備上電、主機復位設備(RESET)、USB總線驅動程序通過URB(USB請求包)請求USB設備描述符前8字節、主機再次復位設備(RESET)、USB總線驅動程序通過URB執行控制傳輸,將USB默認地址0x00重設為操作系統分配的地址,及USB總線驅動程序通過URB連續發送提取描述符請求,依次取得完整的設備描述符、配置描述符(包括接口描述符、端點描述符)、字符串描述符。PnP事件觸發即插即用管理器(PnP Manager)向WDM驅動程序發送主功能(Major Function)碼IRP_MJ_PNP,進入相應的派遣函數,查詢設備ID、執行設備啟動等。
打開上位機C#軟件時將自動搜索WinUSB設備,成功搜索到USB設備后,每當C#調用WinUSB的讀、寫管道函數與USB設備交換數據時,將分別訪問對應的端點(EP1_IN或EP1_OUT)緩沖,運行過程中均可通過USB虛擬分析器觀察到:IOCTL: BULK_OR_INTERRUPT_TRANSFER,展開該層時可觀察到相應的IN事務或OUT事務。

圖3 溫濕度數據采集與控制系統上位機C#程序
由于WinUSB的通用性與易用性,大量廠商開始基于WinUSB開發USB接口程序,盡管當前版本的WinUSB驅動尚存在若干限制,例如一次僅允許一個應用程序與設備通信且不支持在同步端點之間傳輸流數據。本文通過研究微軟WinUSB通用驅動程序框架及WinUSB驅動包構成及其INF文件關鍵條目、Microchip USB接口系統固件框架,基于C#軟件開發平臺設計了以PIC18F455微控制器及SHT75傳感器為核心的溫濕度數據采集與控制系統,通過了仿真運行及實物測試,為基于WinUSB的接口系統開發提供了重要參考。為更快速的開發WinUSB的上位機用戶程序,基于微軟VS2012+WDK8.0的開發環境除了提供USB KMDF與UMDF驅動程序開發模板以外,還提供了基于WinUSB的應用程序開發模板,通過該模板能夠自動生成通用代碼框架,包括自動生成INF文件。另外,微軟的Windows 8已經不再要求為WinUSB提供定制的INF文件,這無疑將使得基于WinUSB的系統開發變得更加便捷高效。
[1] 曾希強.Linux下基于libusb的USB設備驅動程序設計與實現[J].信息與電腦,2009(7):101-103.
[2] 彭定軍,陳安,高健.嵌入式Linux下基于libusb的USB驅動開發[J].技術與市場,2008(11):4-5.
[3] 安愛美,徐建建.基于WDF的USB驅動程序設計[J].自動化與儀表,2012(1):57-60.
[4] 錢宇紅. USB 數據傳輸卡WDF 驅動程序開發[J].計算機應用與軟件,2012(6):225-227.
[5] 鄒敬軒,蔡皖東.基于WDF過濾驅動的USB存儲設備監控系統[J].計算機工程與科學,2010(3):42-44,71.
[6] 朱誠,左輝.利用KMDF 驅動程序實現USB 設備的功耗控制[J].計算機應用與軟件,2012(12):252-254,321.
[7] 牛繼來,王海霞.基于WDM 的USB驅動程序研究與設計[J].計算機與數字工程,2007(12):127-129,139.
[8] 張智邦,鮑蘇蘇,金敏.基于WDM 模型USB 驅動程序的設計與研究[J].計算機系統應用,2011(11):185-188.
[9] 謝晶石.PIC單片機USB接口應用設計[J].數字技術與應用,2011(8):146-148.
[10] 覃冬華.基于Visual C# 的USB 接口通信程序設[J].數字技術與應用,2010(8):90-91.
[11] 胡鍵勛.基于Visual C++編寫USB接口溫度采集設計[J].現代計算機,2010(4):148-150.
[12] 李偉,張東亮,楊麗麗.利用Visual C++實現USB設備與PC機通信[J].儀器儀表用戶,2007(6):87-89.
[13] Microchip Libraries for Applications [OL].[201211]:http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2680&dDocName=en547784.
[14] USB Driver Stack Architecture[OL].[201303]:http://msdn.microsoft.com/zh-cn/library/windows/hardware/hh406256#usb_2.0_driver_stack.