1、浅谈 WinCE 平台 USB 摄像头驱动开发流程 由于良好的性能、低廉的价格和灵活方便的特性,USB 摄像头正被广泛的集成到嵌入式系统中。例如,通过 USB 摄像头 WinCE 系统可以很方便地得到实时图像,这对某些要求实时图象监控的嵌入式系统是一个很不错的选择。但是由于嵌入式硬件平台的多样性,以及 WinCE 对 USB 设备驱动开发只提供了底层支持,再加上许多摄像头厂商尚未提供WinCE 下的 USB 摄像头驱动,这对初级开发人员在开发 WinCE USB 摄像头程序时是一个难点。前段时间,公司委派我负责一个嵌入式项目,项目要求是在 WinCE 平台上集成 USB 摄像头驱动和视频采集程
2、序。这个项目的关键是要集成 USB 摄像头驱动,并高效的把摄像头设备进行初始化以取得一幅完整的图像。幸好我以前开发过 WinCE USB 的主从设备的驱动程序。但虽然如此,我还是花了一些时间来调整系统的稳定性和可靠性。在这里我分享在这次项目实践中得到的经验和教训,希望大家能少走弯路。一. 什么是 USB 设备驱动程序开发?随着 USB 设备的普及,USB 设备驱动开发在嵌入式系统变得越来越重要了。为了支持不同类型的硬件可以连接到 WinCE 平台上,微软提供了具有定制接口的流接口驱动程序模型。WinCE 的 USB 外围设备一般是使用流接口驱动程序。流接口驱动程序是指通过系统提供的文件系统 A
3、PI 与应用程序交互 ;WinCE 内核系统会通过设备管理器来完成对流接口驱动程序的加载、卸载等管理工作;而流接口驱动程序则会通过调用 USBD 模块提供的接口函数实现与底层 USB 设备通信。因此,在进行 USB 设备驱动程序开发之前,我们必须先了解 USB 设备驱动的结构和分类。(1)主机与 USB 摄像头的通讯结构USB 摄像头驱动程序主要是利用系统提供的底层接口配置设备和摄像头设备进行通讯。因此,WinCE 的 USB 摄像头驱动分为两层:USB Client 设备驱动程序和底层的 WinCE函数实现层。而底层的函数层本身又由两部分组成,即通用串行总线驱动程序(USBD) 模块和较低层
4、的主控制器驱动程序(HCD)模块。HCD 负责最底层的处理,USBD 模块实现较高的 USBD 函数接口。因此,USB 摄像头驱动主要是利用 USBD 接口函数和外围 USB 摄像头打交道。一般来说,主机和 USB 外设之间的通讯是由在主机端通过 USBD 模块和 HCD 模块使用的 PIPE 访问一个通用的逻辑设备来完成。也就是说,USBD 和 HCD 是一组抽象出来用于访问 USB 设备的逻辑接口,它们主要是负责管理 USB 外设的连接、加载、移除、数据传输和通用的配置。其中 HCD 是由主机控制和驱动的,是为 USBD 提供底层的功能访问服务。 而 USBD 则是由 USB 总线驱动的,
5、位于 HCD 的上层,是利用 HCD 的服务提供较高层次抽象的功能。由于 HCD 和 USBD 都是面向一致的逻辑设备接口,因此如果嵌入式系统中拥有多种USB 物理外设的话,那么就需要有唯一对应的外设驱动程序,也就是要有最上层的 PIPE所连接的物理设备和 USB 设备驱动程序。有了对这个结构的认识,那么我们在进行 USB设备驱动程序开发时首先要写的就是最上端的 USB 摄像头客户端驱动程序,在 WinCE 的样例程序中它也被称为 USB Client Driver。它是工作于 USBD 之上,所以实际上我们的工作就变成了利用 USBD 提供的接口针对特定的物理设备来完成 USB 设备驱动程序
6、。(见图)(2)流驱动程序的分类和函数结构WinCE 驱动程序是介于内核系统和物理设备之间的一个代码层,它的主要作用是为内核系统提供一个接口用来操作不同的外围设备,包括物理设备和虚拟设备。驱动程序提供给内核系统的接口一般可以分为:本地驱动(Native Drivers)和流驱动(Stream Drivers)。我从这次项目实践中得到的经验是,WinCE 下的所有驱动都可以归类到这两个里面,二者必居其一。流驱动是指通过为内核系统提供流接口函数来实现驱动外围设备,如 XXX_Init()、XXX_Open()、XXX_Read()、XXX_Write()、XXX_Close() 等。这一类的驱动由
7、 Device Manager 来管理,它是通过调用 ActivateDeviceEx()函数来实现加载流驱动的。ActivateDeviceEx()的参数是注册表中相应的键,用来设定加载流驱动的属性,如Index、Order、Prefix 等等。流驱动加载成功后,应用程序就可以通过调用 CreateFile()、ReadFile()、WirteFile() 等函数来访问流驱动设备了。而与流驱动相反,本地驱动提供给内核系统的不是标准的流接口,而是事先约定好的特定接口。因此不同的本地驱动设备,接口也是不一样的。在 WinCE 中,常见的本地驱动有 LCD 显示驱动、触摸屏驱动、鼠标和键盘驱动及打
8、印机驱动等。从这里可以看出,本地驱动主要是涉及与人机界面相关的驱动。它们是由 GWES 来管理的,由于他们在注册表中有各自相应的配置信息,因此它们会在系统启动时自动加载。简单的说,就是本地驱动是由内核系统操作和调用的,一般的应用程序是不能访问和调用的。在上一段描述中我们提到自动加载的概念,这是从驱动加载时间来区分的。主要分为两种:一是系统启动时自动加载;二是需要时才加载。一般来说,本地驱动都是在启动时自动加载的。而需要时才加载的方式,顾名思义就是想加载时才加载、想卸载时就可卸载。USB 设备的驱动加载都是属于需要时才加载的驱动,例如 USB 摄像头的驱动程序。而从驱动的接口来看,USB 摄像头
9、驱动又属于流驱动接口。但相对于普通的流驱动接口,它增加了几个特有的接口函数:如 USBDeviceAttach()、USBInstallDriver() 、USBUnInstallDriver()等。在这次项目的调试中,我们发现需要时才加载的驱动程序有一个非常实用的好处,就是能在不修改嵌入式内核系统的情况下,应用程序可以动态加载该驱动以完成对硬件的操作,而操作完成后又可卸载其驱动程序以节省有限的内存。(3)USB 设备驱动程序入口点函数分析大部分 USB 外围设备由于功能性的原因会更适合使用流接口驱动结构,所以一般都会采用加载式流接口驱动程序模型来开发 USB 设备驱动程序。流驱动是指通过流接
10、口函数来实现驱动外围设备的。因此,编写流驱动程序实际上就是对各种流函数进行调用。又由于 USB 摄像头驱动程序主要是和 USBD 打交道,所以我们必须详细的了解 USBD提供的函数。让人感到幸运的是在 WinCE 下微软已经提供了通用串行总线驱动程序(USBD)模块、USBD 接口函数全集、样本主机控制器驱动程序(HCD)模块。所以,我们只需要根据 USB 摄像头的硬件特性,利用 USBD 提供的不同函数就能实现流接口函数与外围摄像头设备的交互。就能大大的节省开发时间,从而能更快速地进行嵌入式开发。二.USB 摄像头流驱动的实现过程WinCE 系统下的 USB 摄像头驱动程序的编写不同于在 W
11、indows 系统下的编写,因为在 WinCE 中对 USB 设备驱动开发只提供了底层支持。所以,在 WinCE 系统下必须要根据所选择的 USB 摄像头的硬件特性自行编写驱动程序。根据我在这次项目中得到的实践经验,具体可以分为以下三个步骤:(1)创建 USBD 函数控制模块从上述的 WinCE USB 设备驱动模型及结构分析图中,我们可以清晰的看到主机和USB 外设之间的实现方式。因此,我们首先需要编写 USB Client Driver。也就是说,我们首先需要利用 USBD 提供的接口针对特定的物理设备来完成 USB 摄像头客户端驱动程序。虽然 WinCE 没有提供 USBD 的标准机制,
12、但是编写 USBD 可供采用的方法有:是使用流接口函数;是使用现有的 WinCE 应用程序编程接口(API); 是创建用户指定的API。根据在这个项目的多次实践经验,我在编写 USB 摄像头驱动时采用了流接口驱动模式,该驱动程序的位置是位于 USBD 协议栈层上,属于控制具体设备功能的客户端驱动程序。然后,我把流接口驱动程序的流接口函数设计为匹配系统的文件系统 API 函数形式。通过这种机制方式,USB 摄像头就可在流接口的管理下通过文件系统 API 暴露给应用层,这样应用层就可把 USB 摄像头作为一种特殊的文件进行操作,从而达到对 USB 摄像头的控制。(2)创建控制 USB 摄像头的各种
13、流接口函数从结构分析我们可知,所有的 USB 设备驱动程序必须在它们的 DLL 库设置一定的入口点函数与 USBD 模块进行适当的交互。设置入口点函数有两个作用:一是使得 USBD 模块能与外部设备交互;二是使得驱动程序能创建和管理任何可能需要的注册键。因此,在编写 USB 摄像头驱动程序时有一个重要的步骤,就是要创建和实现三个入口函数 USBDeviceAttach(),USBInstallDriver(),USBUninstallDriver()。实现这三个入口函数的主要目的是为了使客户端驱动与系统的 USBD 协议栈进行联系。因为在 USB 摄像头接到主机后,USBD 模块会调用这个函数
14、来初始化 USB 设备,取得 USB 设备信息和配置USB 设备,并且申请必需的资源。USBInstallDrive 是在第一次加载 USB 设备驱动程序时首先被调用,它使得驱动程序能创建需要的注册键。但需要值得注意的是,USB 设备驱动程序不是使用标准的注册表函数,而是使用 RegisterClientDriverID()、RegisterClientSettings()函数来注册相应的设备信息。USBUninstallDriver 则是在用户删除USB 设备驱动程序时调用,负责删除注册键并释放其它相关资源。同样,它是通过调用UnRegisterClientSettings()和 UnReg
15、isterClientDriverID()函数来删除由驱动程序的USBInstallDriver()函数创建的所有注册键。因此,我们在驱动程序中需要严格按照这三个函数的原型来实现,否则就不能为设备管理器所识别。(3)在注册表中配置 USB 摄像头驱动信息USB 摄像头一般是使用需要时才加载的方式来加载的,因此在设备加载时会先检查设备的相关信息。在 WinCE 系统中,这些相关的设备配置信息都是存储在系统注册表中的。所以,内核系统会先访问注册表以获得必要的相关信息。例如,USBD 模块会使用一组跟踪驱动程序和设备的注册键来定位正确的驱动程序。如果注册表信息与 USB 设备信息符合,USBD 就会
16、加载此驱动程序,否则 USBD 就不会加载此程序。因此,编写 USB 摄像头驱动程序的最后一个关键步骤,就是要正确的在注册表中配置相关的 USB 摄像头驱动信息。WINCE 下 USB 接口摄像头驱动的加载最近和另一名伙伴在搞个公交车自动报站与监控的嵌入式项目。开发环境:WINCE5.0 操作系统、北京杨创 2440 开发板、普通摄像头一个(中星微芯片)、EVC4.0 为了在 ARM9 系统中能正常使用中星微摄像头进行图片采集,必须首先在 WINCE5.0 上加载其驱动程序。 1、ARM 嵌入式开发中流驱动程序说明 流接口驱动程序的主要任务就是把外设的使用传递给应用程序,这是通过把设备表示为文
17、件系统的一个特殊文件实现的。对于串口 1 在用户级别的程序里面通常被描述为COM1。WINCE 的设备文件通常保存在固定的路径/windows 目录下,通过 注册表机制 来完成特殊的命名惯例。 2、驱动程序选择 基于采购硬件设备的性价比、视频捕捉的流畅性及视频处理的稳定性等因素的考虑,我选择了杭州赛安软件工作室推出的 ZC030X 免费试用版作为视频采集驱动。 3、ZC030X 加载过程 (1)定制好自己的 WINCE 系统,并编译。 (2)编译成功后。将驱动所在目录,/BIN/ARMV4i/下所有文件放到:project 所在目录 /RelDir/ smdk2440_ARMV4I_Relea
18、se/下。 (3)然后修改 project.reg 系统文件。在该文件的结尾添加: HKEY_LOCAL_MACHINE/Drivers/USB/LoadClients/2760/Default/Default/ZC030X“DLL“=“ZC030X.DLL“HKEY_LOCAL_MACHINE/Drivers/USB/ClientDrivers/ZC030X“Prefix“=“CAM“Dll“=“ZC030X.DLL“ (4)在 project.bib 文件 module 下添加: zc030.dll $(_FLATRELEASEDIR)zc030x.dll NK SH (5)保存后重新编译系统,则系统成功加载了驱动 ZC030X。 然后自己就可以根据 ZC030X 提供的函数接口,写一个简单的应用程序检验一下系统是否成功加载了驱动。