硬件驱动开发是嵌入式开发的基础,而随着USB设备的普及,USB设备驱动开发在嵌入式开发中变的越来越重要。
为了支持不同类型的可以连接到基于Windows CE的平台外围设备,微软提供了具有定制接口的流接口驱动程序模型。而大部分USB外围设备由于功能性更适合流接口驱动的结构,都可以采用流接口驱动程序模型来开发自己的驱动程序。本文就是基于流接口驱动程序,对Windows CE平台下的USB外围设备的驱动开发进行了详细的分析和设计。
Windows CE下USB系统软件由两层组成:较高USB设备驱动程序层和较低的USB函数层。较低的USB函数层本身又由两部分组成—较高的通用串行总线驱动程序(USBD)模块和较低的主控制器驱动程序(HCD)模块。依据HCD模块提供的功能,USBD模块实现高层的USBD接口函数。USB设备驱动程序使用USBD接口函数与它们的外围设备进行通讯。
在数据传输的过程中,操作流程通常按下列的次序进行:
1.USB设备驱动程序进行数据传输的初始化,即通过使用USBD接口函数给USBD模块发送数据传输的请求。
2.基于有关总线的情况与总线相连的USB设备的特性,USBD模块将该请求分成一些单独的事务。
3.HCD模块排好事务通过总线的次序。
4.主控制器硬件执行或完成这些事务。
所有的总线上的事务都是从主机一侧发出的,外围设备完全是依赖的。
所有的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()函数创建的所有注册键。
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设备流接口驱动程序就可以被应用程序调用。
在Windows CE下,由于微软提供了通用串行总线驱动程序(USBD)模块、USBD接口函数全集、样本主机控制器驱动程序(HCD)模块,所以我们只需要根据USB设备硬件特性,利用USBD提供的不同函数,实现流接口函数与外围设备的交互。这大大节省了开发时间,从而能更快速地进行嵌入式开发。