2024年2月7日发(作者:)
WinCE第三方驱动安装之二——CAB安装包制作
Microsoft Windows CE是紧凑的,高效的操作系统,它被广泛的应用在从手持电脑到专门的工业控制器或消费用电子产品等各种嵌入工业产品中。英创公司ARM9系列工控主板预装了正版Windows CE5.0操作系统,并对板上所有硬件资源提供了完备的驱动支持。随着WinCE操作系统的广泛应用,越来越多的在PC上使用的硬件设备提供了对 WinCE系统的支持,如3G模块,Wi-Fi等。
第三方驱动一般以两种形式提供,一是动态链接库(*.dll)加对应的注册表文件(*.reg),另一种是可直接安装的CAB文件(*.cab)。与动态链接库加注册表文件形式的驱动相比,CAB文件安装十分方便,不需要了解繁杂的 WinCE INF文件格式或REG文件格式。本文介绍通过WinCE CAB Manager工具将以动态链接库和注册表文件形式提供的USB转串口驱动程序压缩为可直接在英创主板上安装的 CAB压缩包的方法。
1、打开WinCE CAB Manager,选择File->New,运行New Cabinet wizard(CAB新建向导),选择“next”直至完成如图1。
2、在CAB Information上点击右键,选择Properties(属性)选项(如图2),打开CAB包属性设置对话框,如图3。
3、在CAB Properties对话框中(如图3),填写Company Name(公司名称)和Application
Name(CAB包名称)。
4、切换至CAB Properties->Installation Directory对话框,设置CAB包默认安装路径,此处设置为NandFlashUSBDriverDll目录,如图4。
5、切换至CAB Properties->Cabinet对话框,设置处理器类型,Processor可直接选择为ALL/CEF,如图5。
6、切换至CAB Properties->Operating System对话框,设置操作系统版本,设置为支持CE4.0~CE5.0,如图6。设置完成后选择确认完成CAB包属性设置。
7、为CAB包增加DLL文件,如图7,在File标签上点击右键,选择Add…添加USB转串口驱动的动态链接库文件。
8、修改的安装路径,在图8中所示上点右键,选择Properties,在弹出的 Properties对话框中设置的安装路径,如图9,选择%InstallDir%表示使用在第4点中设置的默认安装路径,即将 安装到NandFlashUSBDriverDll目录。
9、选择File->Import->Import 导入USB转串口驱动程序对应的注册表文件。在导入注册表时需要注意,第三方驱动默认安装目录往往是windows目录,因此需要在注册表文件中将动态链接库安装目录修改为在第4点中指定的目录NandFlashUSBDriverDll 。图10是注册表修改前后的对比。
将上面的工作保存后,一个可在英创主板上安装的CAB包PL2303_就制作完成了,下面结合英创工控主板EM9161,介绍PL2303_的安装方法。
1、在EM9161的Nandflash目录下新建UsbDriverDll文件夹。
2、将制作好的CAB安装包PL2303_拷贝到EM9161 Nandflash,双击运行,如图11,点击OK完成驱动程序的安装。
对于英创公司其它没有WinCE标准显示界面的嵌入式主板EM9160、EM9260、EM9360可通过WinCE远程桌面实现CAB包安装,安装过程与EM9161一致。
/article/
posted @ 2011-07-25 17:06 Maintell 阅读(65) 评论(0) 编辑
wince下USB设备驱动程序
随着USB设备的不断增加,我们这些开发人员也就多了对USB设备进行驱动程序开发的工作。但是对于很多初学者来说,存在以下三个困难:
一是对WinCE的驱动程序结构了解得太少,没办法得心应手的专注于驱动程序的开发工作;
二是对WinCE自带的USB驱动程序的例子没有弄懂,看到一大堆文件夹结构和源程序思维混乱;
三是几乎没有什么中文的参考资料,不知如何下手。
第三条是很多开发人员都遇到的,我也一样,很多朋友问我有没有什么资料,我也只能说抱歉,因为我也同样有这个问题,一切都靠自己的黑暗中摸索,因此本文不谈第三条。
第一条是可以找到资料的,如《Windows CE .NET系统分析及实验教程》,因此本文也不打算在此花费大量笔墨。
这样,本文的着重点就在第二条上面了,通过本文,我希望能让更多的朋友理解Windows CE下对USB设备的驱动模型及样例程序中的实现过程,以样例代码为基础理顺USB设备驱动程序的开发思路。同样,本文的读者对象预期是入门者和准备着手 USB驱动开发的人员,驱动开发高手自然就当一笑吧。同时写本文的目的也是履行我半年前答应很多朋友的诺言,并向我的慵懒致歉。
好了,在看样例程序之前,我们还有些东西需要了解,我们就先来看下图:
在此图中,我们可以非常清晰的看到主机和物理外设之间的结构方式,在主机端,通过USBD模块和HCD模块使用默认的PIPE访问一个通用的逻辑设备,实际上就是说USBD和HCD是一组抽象出来的访问所有USB设备的逻辑接口,它们负责管理所有USB设备的连接、加载、移除、数据传输和通用的配置。其中 HCD是主机控制驱动,是为USBD提供底层的功能访问服务,USBD是USB总线驱动,位于HCD的上层,利用HCD的服务提供较高层次抽象的功能。
由于HCD和USBD都是面向的一致的逻辑设备接口,那么对于各种各样的物理设备,就需要有唯一对应的设备驱动程序,这就是上图中最上层的特殊的PIPE所连接的物理设备和USB设备驱动程序。
有了对这个结构的认识,我们可以明确的是我们要写的就是最上端的USB设备驱动程序,在WINCE的样例程序中也称为USB Client Driver,它是工作于USBD之上,所以实际上我们的工作就变成了利用USBD提供的接口针对特定的物理设备来完成USB设备驱动程序,而暂时与其他的部分无关。
好了,先到这,接下来就准备看一些具体的东西吧!
接下来,我们就来分析一下CE中的样例程序,我用的是4.2版本的,所以下面的内容是4.2版本中的程序。这里的程序是通过文件夹的形式组织在一起的,所以我们还是像以前学习CE的时候那样,先来了解与此相关的文件夹结构,如下图。
在USB文件夹下,分成了CLASS,CLIENTS,COMMON,HCD,INC,USBD几个文件夹,其中INC和COMMON里面有一个 lock.c的程序,这个程序很明显是将要被其他USB有关的驱动程序所使用的一个锁,代码很简单,只是一个类似临界区的封装体,可以保护多线程对同一内存区域的读写访问,可以先不去管它。CLIENTS文件夹可能最初微软的开发人员是用来放置设备驱动程序的,但是后来没有放,而发布的时候也没有删除,所以遗留了下来,里面是个空的文件夹,所以没用实际用处。USBD和HCD是前述的底层驱动,里面含有很多子文件夹和程序,由于我们只针对USB设备驱动,因此对这两部分不做分析,不兴趣的朋友可以自己去了解。
重点就在CLASS文件夹了,展开来看,里面又包含了COMMON、HID、PRINTER、STORAGE几个文件夹,同样,COMMON里面存放的源程序是为HID、PRINTER、STORAGE所共有的。HID是USB输入设备如键盘/鼠标的样例驱动程序,PRINTER是USB打印机的样例驱动程序,STORAGE是USB存储设备如U盘的样例程序。
我们此次以USB存储设备为例,所以再来展开STORAGE文件夹,其中的INC文件夹里面是头文件,CLASS是USB存储设备的驱动程序,DISK是磁盘驱动程序。这里为什
么有两个驱动程序呢,我来简要解释一下。
驱动程序工作在硬件与操作系统之间,它有两个功能,一个是 将操作系统转发来的操作以符合指定硬件设备的形式控制硬件设备,另一个是向操作系统提供这个访问接口。比如说U盘,一方面驱动程序要把操作系统对U盘的识别、读、写等操作转换成U盘的动作,另一方面又告诉操作系统这是个U盘,可以当成一个文件夹或文件系统来用,能够接受标准的文件操作命令。所以此处存在两个驱动。
另外还有一个文件夹,WINCE420PUBLICCOMMONDDKINC,这里面是与设备驱动有关的头文件,对于USB设备,相关的文件有 USB100.H, USBTYPES.H, USBDI.H,这里面前两个里面关于USB的定义是完全符合USB规范的,不是随便定义出来的,而USBDI.H文件里的内容就是USBD总线驱动程序向USB设备驱动程序提供的接口描述,在开发USB设备驱动时必须要包含此头文件,这样才可以得到USBD接口的原型。
此前,我们共同了解了USB驱动在CE中的位置结构,也了解了样例驱动程序的文件夹结构,接下来,我们就要了解一下USBD为我们提供了哪些接口来实现设备访问以及驱动程序管理的功能。找到USBDI.H,不要告诉我你找不到吧,不管你用什么编辑器,记事本也好,PB也好,VC/EVC或者VS都行,打开它,我们一起来了解一下USBD为我们提供了什么。
我们首先看到的一个大的结构体就是_USB_DRIVER_SETTINGS,注意这个结构体不是USB规范中的USB设备描述,而是为了CE设备管理器加载USB设备驱动程序方便而建立的。该结构体中对供应商描述、设备描述和Interface的描述是用来匹配注册表中对USB设备驱动的注册表键,当设备管理器发现你设备的这些值与注册表中的这些值相符时,就会加载你的驱动。也就是说它是与你的设备唯一对应的东西,是一种标识。该结构体的供应商部分的描述需要根据你的设备的供应商信息来填,设备描述的设备类、子类、协议等可以在USB规范中找到,在USB100.H头文件中也有一部分,在后面的样例程序中也定义了一部分。
在接下来有三个函数是必须由USB设备驱动程序实现的,这也就是我们在MSDN里或其他CE的文档里所看到的,这几个函数就是:
USBDeviceAttach:设备加载的时候由系统调用
USBInstallDriver:设备第一次加载的时候由系统调用,用来安装注册表配置以便搜索设备
USBUnInstallDriver:设备移除后清理由上一个函数写入的注册表配置
这样在我们的驱动程序中就一定要按照这三个函数的原型来实现,否则就不能为设备管理器所识别。其实除了这三个,个我觉得第四个也是必须的,这就是一个函数指针所指向的函数:
*LPDEVICE_NOTIFY_ROUTINE
这个指针所指向的函数是用来接收通知消息的,既然微软说任何USB设备必须实现USB_CLOSE_DEVICE消息的响应,那么这个指针所指向的函数自然也就是必须要实现的了。
继续向下看,是一组函数的原型,这些函数就是USBD向设备驱动程序提供的服务接口,有些函数是可以任意调用的,用来完成版本信息读取、注册表操作和设备驱动程序注册,这些函数有:
GetUSBDVersion RegisterClientDriverID UnRegisterClientDriverID RegisterClientSettings
UnRegisterClientSettings OpenClientRegistryKey
还有大量的函数是必须通过指针调用的,通常只允许在驱动程序中调用,为了方例使用,在这里给出一个_USB_FUNCS的结构体,每一个结构体成员对应了一个函数指针,这样在驱动程序中要想使用USBD函数只能通过一个结构体变量来进行了,在这里我们要记住这个结构体的名字,并且微软还对这个结构体变量进行了以下的类型定义:
typedef struct _USB_FUNCS USB_FUNCS, * PUSB_FUNCS, * LPUSB_FUNCS;
typedef struct _USB_FUNCS const * PCUSB_FUNCS;
typedef struct _USB_FUNCS const * LPCUSB_FUNCS;
好了,到此我们发现,大部分的USB工作都已经被USBD完成了,我们为了实现自己的设备驱动,只需要利用这些指针或函数,来实现四个我们自己的函数,然后在其中匹配上我们自己的设备就可以了。是不是忒简单,没错,总不能刚开始就把自己吓得不行,那样后面可就没法做了。
在上次了解了所有 USBD接口函数以后,我们已经有了很多基础知识了,回顾USB样例的文件夹结构,我们还能记得USBCLASSCOMMON这个文件夹下是存放所公共部分的源程序,它是微软专门抽象出来的能为大多数USB设备驱动程序服务的一些结构体以及函数的封装,我们这次再来概略的了解一下这里面的源程序。
这里面包含了三个程序,分别是:
remlock usbclient utils
下面我们分别来了解一下这三个程序的功能和接口,很显然,USB设备驱动程序肯定是会用到这其中的一部分函数的,因此我们不一定需要读懂这其中的每一行,但至少要对这些函数有个印象,不至于在读驱动程序时不知道函数的来源。
remlock程序是一个移除设备的锁,利用这个结构体
typedef struct _REMOVE_LOCK
{
BOOL Removed;
LONG IoCount;
HANDLE RemoveEvent;
} REMOVE_LOCK, *PREMOVE_LOCK
来实现在设备移除时进行的同步控制。其中Removed成员是对设备是否已经移除的标识,IoCount成员是对设备进行访问的数量,这也是驱动程序中常用的行为,就像此前我们看到的那个Lock程序一样,RemoveEvent是一个内核事件,熟悉WIN32编程的应该都很清楚,它是内核通知应用程序的一种方式,也是线程这间并发控制的一种手段,如果不熟悉,还是
像我在以前文章中提到的那样,一定要找WINDOWS高级编程之类的书把它学明白,否则就很难控制驱动程序了。
利用它实现的那几个函数就不说了,与临界区的用法是一样的。另外提一句,在此程序中有类似InterlockedIncrement这样的函数,这种函数是WIN32 API函数,专门用来提供多线程对同一变量的同步访问的,可以通过MSDN查到详细用法。
usbclient程序是对USBD进行包装以供USB设备驱动程序使用的函数接口,通过usbclient.h我们可以发现里面是关于数据传输、属性设置、状态描述和复位的一组函数原形的定义,我们再看usbclient.c文件,这些函数大部分都拥有一个LPCUSB_FUNCS类型的参数,回顾上次我们对USBD的了解可知,正是通过这一参数才能访问USBD提供的服务功能,浏览一下函数的实现发现,确实每个函数都是通过这个参数调用了USBD的函数,然后处理调用后的结果,所以这里只是多了一层封装,使得驱动程序的编写更加清晰易于维护。
另外,这里我们要留意一下IssueBulkTransfer()、IssueInterruptTransfer()、
IssueVendorTransfer() 这三个函数,它们实现了通用的Bulk传输、中断传输和自定义的传输方式,在驱动程序中要用得到。
utils程序很简单,是对注册表操作的封装,利用_REG_VALUE_DESCR这个结构体和GetSetKeyValues()函数可以方便的访问注册表,在驱动程序的安装中会用得较多。
又说了这么多东西,虽然没有看多少程序,但我们又离驱动程序近了一层,至少知道了很多函数是要在驱动程序中用到的,如果有兴趣,可以具体阅读每一个函数的实现方法,但我觉得这并不影响对驱动程序的开发。如果是我写驱动,在没有特别的情况下,我会把这些公用的源程序照搬过来,这可是能极大的缩短开发周期的事哦!
正如所料,接下来我们就进入到DRIVERSUSBCLASSSTORAGECLASS文件夹下,接触USB设备驱动程序。
我们先来了解两个头文件,分别是STORAGEINCusbmsc.h和STORAGECLASSusbmscp.h,其中前者是USB存储设备公用的头文件,后者是需要按
照自己的设备更改的头文件。我们先来看前者。
在usbmsc.h这个头文件中,前边定义了很多常量,包括子类和协议的常量,这是从哪里来的呢?前文我们已经提到过,这些量值是依据USB设备规范得来的,在规范上都作了定义,所以此处的值必须与USB规范中的相一致。再向下的命令块结构体和数据块结构体是用来与USB设备通讯用的,可以通过这两个结构体的实例与USB设备传输数据。 下面的函数原型就不说了,前文提到过,在这里只记得有这几个函数就行了。
再来看usbmscp.h这个头文件,这个头文件是要按照自己的需要和USB设备来进行修改的,比如DRIVER_NAME_SZ是驱动程序的名字,RESET_TIMEOUT 是一个超时的默认值。还有很重要的一个就是USBMSC_DRIVER_SETTINGS的设置,这个设置是与USBDI.H中的 USB_DRIVER_SETTINGS结构体一一对应的,为了符合我自己的设备,通常要把dwVendorId和dwProductId等设置成设备的对应值,比如我的U盘的VendorID是0x058F,ProductID是0x9321,那我就会把这两个值对应的写在相应的位置上。同时在系统注册表中也会利用这两个值修改注册表的键以便设备管理器可以顺利的找到我的设备驱动。
下面还有一个_USBMSC_DEVICE结构体,它是用来描述你自己的USB存储设备的,是封装了USBD函数表指针、磁盘设备指针、管道和配置项的最重要的数据结构,在驱动程序实现上此数据结构就是重点的参数,鉴于样例程序对每一个结构体元素都作了明确的注释,此处我就不一一描述了,它就像C++中的类一样,是最后把一些小类组合起来的可以最终使用的结构。
好了,对这两个头文件有所了解以后,我们就进入最关键的部分,源程序。我们接下来来看usbmsc.c这个文件。为什么要先看这个文件而不是同一文件夹下的其他几个文件呢?我来解释一下。在这个文件夹中有一个的文件,大家都知道它是定义了导出函数的,通常与它同名的程序文件都会含有 DllEntry的入口,既然入口在这,那我们自然就先来看这个文件了。如果用到了其他的文件,再看不迟。
这可是一个有1000多行的源程序,但不要害怕,我们只看最主要的,别的函数的实现你可以自己去研究。首先看到文件的DllEntry入口之前有5个函数原型的定义,从函数
名上就可以知道这个函数的功能了,很显然这几个函数是程序实现过程中被调用的,所以目前知道功能就行了,不用了解实现方法。忘了说一句,这个程序中包含了bot.h和cbit.h两个头文件,可见程序中是要用到它们的功能的,不过先不管它,继续往下看。
DllEntry入口函数的下面,就是USBInstallDriver()这个函数了,它的作用是进行与USB设备相关的注册表操作,主要的语句是:
bRc = RegisterClientDriverID( wsUsbDeviceID );
bRc = RegisterClientSettings( szDriverLibFile, wsUsbDeviceID, NULL,
&usbDriverSettings );
即先注册设备类别,然后是设备细节。 同样,USBUnInstallDriver()函数是以相反的顺序解除注册信息的。 这几个与注册有关的函数在前面我们提到过,是由USBD接口提供的,这里我们可以看到USBD对设备驱动程序的重要性。
在继续向下看,我们发现了USBDeviceAttach()函数,这可是最重要的地方了,当有USB设备插入插口以后,操作系统是如何识别它的呢,如何将其做为一个文件夹加以访问的呢?我们就来解开这里的谜团。
为了我们方便说明,我将此程序简化如下:
后面的程序将以此行号进行说明。
我们来看程序的第4行,这里有一个判断语句,它是在判断插入的设备是否是USBMSC_INTERFACE_CLASS类型的,这个常量是在usbmsc.h文件中定义的,也就是说如果设备不是USB存储设备,那么就结束这个函数,也就是此驱动只能处理USB存储设备。
当发现设备符合此驱动程序的要求后,就通过函数ParseUsbDescriptors()来解析这个设备,这个函数在下面的程序中将被实现,我们可以看一下该函数的函数体,很显然,它是在为设备进行各种配置,这就不多说它了。
再往下,分配内存,设置标志,从注册表中读取信息。注意,这里读取到的注册表信息是DriversUSBClientDrivers Mass_Storage_Class和bInterfaceSubClass变量组合成的注册表键下的值,具体可参阅源程序,这个注册表键下放置的内容是
[HKEY_LOCAL_MACHINEDriversUSBClientDriversMass_Storage_Class6]
"DLL"=""
"Prefix"="DSK"
"Folder"="USB Disk"
"IOCTL"=dword:4
"IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"
由此可以看出,通过此处的注册表读取,驱动程序可以知道这个设备将通过哪种形式以及哪个DLL向操作系统提供接口。同时也为后续的操作进行了准备。
最关键的部分就在接下来的LoadDriver()那句,加载了另一个驱动程序的DLL文件,就是上述注册表中的文件,计数器增一,取到该文件中UsbDiskAttach函数及UsbDiskDetach函数的地址,注册事件通知处理函数,然后调用了该DLL文件中的
UsbDiskAttach函数。
由此可见,USB设备驱动程序有两层功能,一方面是识别出指定的设备并进行配置,另一方面按照要求调用更高层的驱动程序来向操作系统提供接口。当调用了
后,操作系统就会按该文件中的程序以一个磁盘的形式或文件夹的形式进
行处理,通过文件系统的操作,就可以对其进行读写控制了。我们也可以看一下HID设备的这个函数,它也是通过这种方式让操作系统知道把USB设备识别成鼠标设备的。
前文我们说过还有一个通知消息的回调函数,我们在刚才的程序体中已经发现通过:
UsbFuncs->lpRegisterNotificationRoutine( hDevice, UsbDeviceNotify, pUsbDevice );
语句已经对这个函数进行了设置。我们再向下来看一下这个函数的函数体。这个函数很简单,只要对USB_CLOSE_DEVICE消息进行处理,既然是要关闭 USB设备,那么调用中的Detach函数是必须的,让上层的驱动程序进行释放,然后将引用计数减一,如果不再有设备引用此驱动程序,则FreeLibrary(),仅此而已。
其余的函数可以再仔细研究一下,在此就不详细描述了,接下来我们要弄明白的就是到底操作系统是如何通过抽象的DISK读写具体的设备呢?
带着上次留下的疑问,我们继续来学习操作系统如何通过USBDISK读写USB设备的。我们先看USBCLASSSTORAGEDISK 文件。在这个文件中可以看到,该DLL一共导出了14个函数,其中两个是上次内容当中被设备驱动程序调用的
UsbDiskAttach和UsbDiskDetach,余下的是一组以DSK开头的流驱动接口,易见,USBDISK是以流驱动的形式向操作系统提供服务的。
为了清晰起见,以下大量的程序我们并不学习,而只关心设备读写,因此我们来看DISK.C这个程序文件。找到DSK_Read和DSK_Write两个函数,令我们大失所望,因为这两个函数都是形如
UNREFERENCED_PARAMETER(pDevice);
UNREFERENCED_PARAMETER(pBuffer);
UNREFERENCED_PARAMETER(BufferLength);
DEBUGMSG(ZONE_ERR,(TEXT("DSK_Readn")));
SetLastError(ERROR_INVALID_FUNCTION);
return 0;
这样的实现,也就是说用户无法通过常规的ReadFile和WriteFile函数使用这个设备,那怎么办?是否意味着这个DISK无法读写呢?当然不是,我们应该马上想到DSK_IOControl()这个函数,当遇到某些设备无法用常规的文件操作函数操作时,我们有DeviceIoControl()用户函数可以使用,而这个函数就会调用到驱动程序中的DSK_IOControl函数。
在这个函数中,我们找到了对IOCTL_DISK_READ等命令的处理程序,其中最关键的一句就是ScsiRWSG(pDevice, pSgReq, pDevice->Lun, bRead),即调用了一个ScsiRWSG的函数。
在Scsi2.c这个程序中,我们找到了这个函数,其中SG指的是一种读写缓冲区的数据结构,实际上就是带有缓冲区及长度的一个结构体,是CE下磁盘设备通用的读写数据结构,可以在diskio.h中找到它的定义。在这个函数中我们发现它再次调用了ScsiReadWrite()这个函数进行读写操作,找到这个函数,里面有我们最重要的一行调用,即调用了UsbsDataTransfer()函数,还记得这个函数在哪见过吗?没错,就是在USB设备的驱动程序当中。
通过这一过程我们发现,那些Scsi的函数都只是在准备一些缓冲区、数据结构等,并没有对硬件进行操作,真正要操作硬件设备的还是由驱动程序来完成的,可见,设备驱动程序是有着很强层次结构的,下层是专门针对物理设备的,上层是针对操作系统的抽象设备的,下层是U盘等物理实体,上层是文件夹,二者通过一定的通信或调用机制完成了设备在操作系统下的正常工作。
回到usbmsc.c程序中来,找到UsbsDataTransfer函数,这个函数很简单,根据传输协议调用CBIT_DataTransfer()或BOT_DataTransfer() 即可。
/sea918/archive/2009/03/04/
posted @ 2011-07-25 17:05 Maintell 阅读(55) 评论(0) 编辑
利用EVC快速开发WINCE5.0的流驱动(转载)
WinCE5.0提供了一个标准的流驱动格式,大大方便了设备驱动程序的开发工作。但是传统的开发方式往往效率很低。方法如下:
1.在Platform Builder下建立一个流驱动的dll工程。
2.为流驱动增加注册表项。如:
[HKEY_LOCAL_MACHINEDriversBuiltinIRControl]
"Prefix"="IRC"
"Dll"=""
"Order"=dword:66
"Index"=dword:1
3.修改文件,将流驱动的dll文件打包到.
g,生成包含流驱动的.
整个过程烦琐,而且只要稍加修改,就要重先makeImg,大概要浪费3-5分钟.这种开发效率是很低的.
下面2种介绍在EVC下快速开发WINCE流驱动的方法,是我自己亲身经验,拿出来与大家分享,也希望和各高手相互学习和探讨.
WINCE 的驱动,不象桌面windows驱动开发那样烦琐,windows驱动开发分好几种,如wdm啦,VXD啦,等等,开发这些驱动需要专门的开发环境和工 具,给开发者带来比较高的技术门槛.所幸的是,WinCE的驱动开发,没有这么多的限制和门槛.而且应用程序可以直接和底层硬件打交道的(注:尽管这样,笔者还是不建议大家直接用应用程序访问底层硬件,这种做法是不
安全的),甚至中断初始化,中断线程都可以在用户级的应用程序完成.所以给驱动开发带来了不少的便利.
依我自己的经验来看,具体有以下2种方法.
1.利用EVC生成驱动的DLL.
1).新建一个evc的dll工程,将流驱动代码移植到evc代码中。
2).编写导出文件*.def,将流驱动的10个标准函数导出.并在Project--->Setting--->Link--->Project options里面标注,如:/def:"" .(这一步如果不做,生成的驱动将不可用)
3).编译代码,这个时候一般会出现一些错误,如找不到头文件或库文件等错误,请在wince的安装目录下找到相应的头文件和库文件,在 Tools--->options--->Directories中指明头文件和库文件的路径。在 Project--->Setting--->link中指定相应的*.obj文件(具体文件视源代码调用了哪些驱动和库,到wince的目 录下找到这些obj文件链接进来即可)
4).编写一个应用程序来调试驱动.利用RegisterDevice/DeRegisterDevice动态加载/卸载流驱动,模拟WinCE设备管理程序的功能.
以上这种方法,完全脱离了Platform Builder的开发环境,使驱动的开发和调试工作象开发应用程序一样轻松和快捷.可 以在驱动程序中利用RETAILMSG函数从UART口输出调试信息.
2.利用EVC将驱动程序写成exe,直接象调试应用程序一样调试驱动.
最后将调试好的源代码移植到驱动程序的dll工程中去.具 体方法和第一种方法类似,不同之处在于,前者是新建一个纯DLL工程(带导出函数),后者是新建一个MFC的exe工程而已.编译的头文件和库文件方法2 者雷同.这种方法的好处是可以直接编译成evc的debug工程,可以在源代码中进行单步跟踪调试和设置断点,大大的提高了驱动程序的调试效率.
/projectdevelop/archive/2009/03/31/
posted @ 2011-07-25 17:04 Maintell 阅读(19) 评论(0) 编辑
WinCE流驱动加载的控制 (转载)
前段时间整理了《WinCE下调试串口的动态复用》,基本实现了调试串口与普通功能串口之间的动态切换。其中实现的方法有点欠缺,在重新烧录或者升级系统后,导致系统无法正常启动。这算是个BUG。该功能加上才几天,就陆续有好几个同事碰到。本来想着使用方便的,没想到反而增添了一些麻烦。
这个问题在实现时曾考虑到,发布版本的日志里也加了说明,如果启用了调试串口,那么在烧录或者升级系统前,须禁用调试串口。说实在的,确实有点麻烦,不小心就忘了做这个工作。而问题的根源是实现机制不太合理。调试串口的配置被分散在两处,一处是存储在NAND
Flash的特定区域,另一处是注册表中。系统启动时,OAL根据Flash中保存的状态,确定是否启用调试串口,而驱动加载时又根据注册表的状态,确定是否加载调试串口的驱动。两处保存的状态不同步时,问题就出现了。问题分析清楚了,解决方法自然就有了,保证两处的状态一致即可。
调试串口的配置由存储在NAND Flash中的参数决定,系统启动时根据该值,动态修改调试串口对应的注册表配置,确保在启用调试串口时,不再加载它所对应的驱动,也就不会产生冲突,导致系统无法正常启动了。
在驱动注册表中,Flags是用于控制流驱动的加载行为的。其中DEVFLAGS_NOLOAD即表示不要加载该驱动。所以,在合适的地方添加如下代码,即可控制调试串口驱动的加载。
view sourceprint?
void
DisableDebugSerial(BOOL
bDisable)
{
HKEY
Key;
DWORD
Status;
DWORD
Disposition;
DWORD
Value;
DWORD
Flags;
Status = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
L"DriversBuiltInSerial3", 0, NULL, 0, 0, NULL, &Key, &Disposition);
if
(Status == ERROR_SUCCESS)
{
Value = bDisable ? DEVFLAGS_NONE : DEVFLAGS_NOLOAD;
RegSetValueEx(Key, DEVLOAD_FLAGS_VALNAME, 0,
DEVLOAD_FLAGS_VALTYPE, (PBYTE)&Value, sizeof(Value));
RegCloseKey(Key);
}
}
如果禁用了调试串口,则将Flag是设置为DEVFLAGS_NONE,设备管理器将正常加载驱动。如果没有禁用调试串口,则将Flags设置为DEVFLAGS_NOLOAD,设备管理器就不会加载该驱动。
通过以上方法的改进,调试串口的动态复用就更方便了。
/we-hjb/archive/2011/04/28/
posted @ 2011-07-25 16:35 Maintell 阅读(18) 评论(0) 编辑
WinCE6添加对MFC的支持 转载
WinCE6默认情况下不支持MFC,在PB里面也没有与其有关的组件可以添加。要想让WinCE6支持MFC,就要手动拷贝一些DLL,然后修改bib文件,把这些需要的DLL打包进NK。
需要的DLL在Program FilesMicrosoft Visual Studio 8VCceDllarmv4i下可以找到。下面是我打包进NK的DLL。
在bib文件中添加:
$(_FLATRELEASEDIR) NK SH
$(_FLATRELEASEDIR) NK SH
$(_FLATRELEASEDIR) NK SH
$(_FLATRELEASEDIR) NK SH
$(_FLATRELEASEDIR) NK SH
$(_FLATRELEASEDIR) NK SH
$(_FLATRELEASEDIR) NK SH
转自:/NorthCan/archive/2011/07/22/
posted @ 2011-07-25 16:30 Maintell 阅读(10) 评论(0) 编辑
2011年7月24日
Wince初级篇(转载)
一、初级问答
1、wince驱动它以什么形式存在呢?
dll
2、怎么把它加到wince内核中呢?
3、如何在开机时自动加载内核里面的驱动呢?
4、XIP是什么?
Execute in place (XIP),即本地执行。
本地执行区域中,程序可以在ROM中执行,而不用复制到RAM中,节省了系统资源。
5、访问寄存器
1、#define rRTCCON (*(volatile unsigned char *)0x57000043) //RTC control
这有点类似absacc.h中XBYTE的定义
2、#define XBYTE ((unsigned char volatile xdata *) 0)
其中volatile英文愿意为易变的,此处为易变量,是可能指由外部因素改变的变量,如外部设备的寄存器(volatile unsigned char *)0x57000043这个表达式是强制转换0x57000043为易变的无符号指针类型,将其作为地址来使用。
相当于volatile unsigned char *p;
p = 0x57000043;
然后对p进行引用*(volatile unsigned char *)0x57000043也就是指向了寄存器绝对地址为0x57000043,然后按照定义宏的习惯,将之用括号小心的包起来。然后就可以对寄存器进行读写啦。
二、中级问答
1、如何快速编译Wince系统呢?
命令行编译:
"build -c" 命令
"buildrel" 命令
"makeimg" 命令 or 点击build按钮
2、驱动相关
CEC特性目录集合文件---定义
定义要将哪些特性集成到开发环境中,并加入到操作系统镜像中CEC文件简单的说,就是把自己编写的应用程序、驱动程序、第三方驱动加载到特性目录中,使建立平台时可以像添加自带特性一样添加自己的特性。
而不是每建一个平台都要在,,中重复写很多东西。 BIB文件 二进制镜像编译文件---加入
定义哪些模块与文件,如何包含在操作系统的镜像中。 REG文件 注册文件
系统冷启动时建立注册键与变量(注册表)流接口驱动程序
流接口函数使得应用程序可以通过文件系统访问这些驱动程序。
流接口驱动程序几乎支持任何类型的可以连接到基于WINCE的平台的外部设备。
我理解为通过该驱动使得应用程序访问到任何ARM外的设备,比如一个IO,让灯亮。
驱动分为:
本机驱动:WINCE专用驱动(如电源和LED),由GWES管理
流接口驱动:一般的设备驱动程序,以DLL的形式出现单片驱动
分层驱动:
上层是MDD,模块设备驱动程序model device driver
下层是PDD,平台设备驱动程序Platform Dependent Driver
中断处理函数armint.c
中断使能cfw.c
包含硬件访问地址的注册表项
posted @ 2011-07-24 16:32 Maintell 阅读(41) 评论(0) 编辑
Windows CE的嵌入式系统的结构
要进行嵌入式系统的应用开发,必须先建立一个开发平台。一个基于Windows CE 的平台由Windows CE 操作系统核组件、OEM 适配层(OEM Adaptation Layer ,OAL) 和设备驱动程序以及组成系统的硬件设备组成,图1为基于Windows CE平台的层次结构。
从图1中可以看出,一个基于Windows CE 的嵌入式系统可分为四个层次,从底层到上层分别是硬件层、OEM层、操作系统层和应用程序层。
硬件层是系统的硬件,包括微处理器和各种周边设备。OEM层是一个硬件抽象层,它提供了硬件和操作系统之间的接口,操作系统要访问具体的硬件就可以通过OEM层提供的API 进行访问,而不必直接与硬件打交道。操作系统层中有Windows CE 的组件,用户可以根据自己的系统的需要进行定制,选择需要的组件,去掉不必要的组件,这样可以减小内存需求,使系统性能达到最佳。应用程序层是用户为特定的嵌入式系统开发的应用程序。
在操作系统层中,设备管理器提供对可安装设备的支持,允许在系统中安装诸如PC 卡存储器和调制解调器之类的设备已扩充功能,内核提供最基本的操作系统功能,例如进程调度、内存管理、进程通信等。图形、窗口事件处理模块将用户的击键、鼠标移动和控件选择转换为消息,传送给应用程序和操作系统来处理用户的输入。对象存储、文件系统、数据库和注理用户的输入。对象存储、文件系统、数据库和注册表提供存储数据的能力。附加技术模块指由CE操作系统提供的一些可选择的专用功能模块,如Java语言支持模块、手写体
输入识别模块等。用户界面模块为系统提供一个与PC 电脑上的视窗操作系统类似的图形化操作界面。对系统设计者而言,需要自行开发的是应用程序和直接与硬件有关的部分,包括硬件系统本身、OAL 、设备驱动程序。其中,OAL 是指建立在硬件设备与系统内核之间的一层代码,主要任务是为内核管理具体的硬件设备时钟、中断和实施电源管理提供支持。设备驱动程序负责支持操作系统对目标硬件的访问。OAL 和驱动程序都要针对具体的硬件设备编写。
一般硬件设备制造商会为设备提供驱动软件支持,如果有专门为CE 系统编写的驱动程序,只需将其加入到操作系统的相应模块中就可用了。如果没有,则应根据厂商提供的驱动程序开发包编写。通常这些开发包总已经包含了完成各种硬件操作的标准代码,开发者的工作只是将其与相应的CE 系统API函数对应起来。在CE 系统中,所有涉及硬件的操作都通过调用相应的API 接口函数来完成,而OAL 和驱动程序就是要为这些API 函数提供支持,将其翻译为直接对目标硬件进行底层操作。通过这种方式,CE 系统将应用程序与具体的硬件设备进行了隔离,应用程序只需调用API 函数就可以实现对硬件的访问。这样,程序员在开发应用程序时就没有必要编写任何直接针对硬件的代码,因此不必考虑具体的硬件特性,而应用程序也具有了不依赖于具体硬件设备的独立性。
posted @ 2011-07-24 16:30 Maintell 阅读(12) 评论(0) 编辑
Windows CE驱动分类
Windows CE提供了许多用于开发设备驱动的模型,这些驱动程序模型使得Windows CE
能适应大部分的内部和外围设备。因此,在深入探讨Windows CE矩阵键盘驱动程序之前,先了解在WinCE平台上使用的两种设备:内建设备和可安装设备。因此,从驱动加载方式来看WinCE可分为本机设备驱动(Built-In Driver)、可加载驱动(Loadable Driver)。
本机设备驱动即Native Device Drivers,WinCE设计成可直接使用内建设备,这些设备由本机驱动过程控制。本机驱动程序是与WinCE的核心组件紧密相连,这些驱动对应的设备通常在系统启动时,在GWES的进程空间内被加载,因此它们不是以独立的DLL形式存
在。可加载设备是指可与平台连接和分离的第三方接口设备,可由用户随时安装和卸载这些驱动,可以在系统启动时或者和启动后的任何时候由设备管理器动态加载。通常这类驱动是以DLL动态链接库的形式存在,系统加载后这些驱动程序是以用户态的角色运行,这种外围设备的驱动也被称为流驱动。
两者的差别在于它们提供的编程接口不同:本地设备驱动可以根据具体设备的需求提供本机的相应接口;而流接口驱动则是提供一组通用接口即流接口函数,应用程序可以通过流接口提供的接口函数来访问外围设备。
posted @ 2011-07-24 16:22 Maintell 阅读(39) 评论(0) 编辑
WinCE USB驱动开发的简介与内容
随着USB2.0设备的不断增加,USB设备驱动开发在嵌入式开发中变的越来越重要。Windows CE支持USB 2.0更是对这一波新技术浪潮产生巨大的推动。本文的作者分析了自己进行USE驱动开发的经历,希望可以让读者少走一些弯路。
随着USB2.0设备的不断增加,USB设备驱动开发在嵌入式开发中变的越来越重要。Windows CE支持USB 2.0更是对这一波新技术浪潮产生巨大的推动。近期我负责一个这样的项目,在WinCE下开发USB接口的外围设备驱动。当时做这个项目花费了我相当多的时间和精力,错走许多冤枉路使我精疲力尽。
项目需求是在已调好的ARM9板子上开发USB WiFi无线网卡的驱动程序,具体要求是驱动程序平台是WinCE,CPU类型支持ARM构架,要能比较方便地移植到X86;驱动接口类型是USB2.0 和Wlan 802.11b。后来因为连接效率一直有问题,就东改西改,最后改的是一塌糊涂。幸好老板比较宽容,给了我充裕的时间和支持,这里将关于USB驱动开发的点滴理解与大家分享。
1. 什么是WinCE设备驱动程序?
(1)从驱动加载方式来区分
在深入探讨Windows CE所支持的外围设备驱动程序之前,先了解在WinCE平台上使用的两种设备:内建设备和可安装设备。因此,从驱动加载方式来看WinCE可分为本机设备驱动(Built-In Driver)、可加载驱动(Loadable Driver)以及混合型驱动。
①本机设备驱动
本机设备驱动即Native Device Drivers。WinCE设计成可直接使用内建设备,这些设备由本机驱动过程控制,而本机驱动程序又与WinCE的核心组件紧密相连。这些驱动对应的设备通常在系统启动时,在GWES的进程空间内被加载,因此它们不是以独立的DLL形式存在,也因此要求每一个本机驱动程序都必须与称为设备驱动程序接口 (DDI)的特定接口一致。
本机设备是指整合进平台的设备,其中包括显示、触摸面板、音频、串行埠、LED、电池和PC卡插座等。如果没有这些本机设备整个系统就不能和用户信息交流,例如触摸面板和显示等。本机驱动程序一般设计为动态链接库,但有两个例外:电池和LED驱动程序由于小而设计为静态库(当建立CE图像时与GWES模块链接)。这些设备相应的驱动程序是在WinCE平台开发过程中由OEM开发的,它们储存在ROM或闪存内。通常只有OEM才会对本机设备驱动程序进行修改,其它自由设备生产商只提供附加的硬件设备,对本机设备驱动程序不会有过多涉及。
②可加载设备驱动
可加载设备是指可与平台连接和分离的第三方接口设备,可由用户随时安装和卸载。这种外围设备的驱动也被称为流驱动,这些驱动可以在系统启动时或者和启动后的任何时候由设备管理器动态加载。通常这类驱动是以DLL动态链接库的形式存在,系统加载后这些驱动程序也只是以用户态的角色运行。可加载驱动程序是通过文件操作API来从设备管理器和应用程序获得命令。在WinCE典型的可加载驱动有:PCMCIA driver()、Serial
driver()、ATAFLASH driver()、Ethernet driver(,)。
与本机驱动程序不同的是,所有可加载流驱动程序都共享一个公用接口。该接口由每个驱动程序内的10个功能或记录点组成,这些功能与应用程序所用的文件 API中的功能匹配。因此,控制可加载设备的流接口驱动程序一般由应用程序存取,流接口驱动程序由一个特殊文件来将设备功能展现给应用程序的,该文件可被打开、读取、写入和关闭。例如,用户将一个GPS设备与平台相连后,就可启动有GPS功能的应用程序来存取并使用该设备。WinCE是使用已有的API来让应用程序存取这些驱动程序,而不是建立新的API。
(2)从驱动程序层次上分类
一般可以分为独立驱动和层次型驱动两类。独立驱动程序是指将驱动程序编写成同时包含Model Device Driver(MDD)和Platform Dependent Driver(PDD)层的独立驱动。使用独立驱动的好处在于可以省去MDD和PDD层驱动之间的信息传递,这一点在实时处理中非常重要。独立驱动的代码包括中断服务例程和平台相关处理函数。另外,如果设备的操作和MDD驱动层的接口描述相吻合,用独立驱动程序可以提高处理性能。
层次型驱动是指分为两层,较上层的MDD和比较下层的PDD。MDD实现的是和平台无关的功能,它描述了一个通用的驱动程序框架;而PDD是和硬件以及平台相关的代码组成。MDD调用PDD中特定的接口来获取硬件相关的信息。当使用层次型驱动的时候,一般只需要基于相近的样列驱动程序,针对特定的硬件只修改PDD程序,MDD建立的框架可继续使用。但由于层次间接口的层层调用以及消息的传递,使得处理速度相对于独立驱动程序要慢。因此,在嵌入式实时要求苛刻的环境下,层次型驱动显得不是很适合。
简单的说,独立驱动是把PDD与MDD写在一起,没有做严格的区分,通常这种驱动比较简单,比如ATADISK。至于本机驱动和加载式流驱动是从驱动与系统其它模块(调用者)的接口形式上做的分类。所以,一个加载式驱动程序可以是独立的流式驱动,例如ATADISK;也可以是分层的流式驱动,例如 OHCI。也就是说,独立和分层是驱动实现方式上的分类,而本机和加载流式则是驱动模型上的分类。所谓本机驱动就是操作系统有保留专门的接口,而加载流式驱动是指编写DLL文件导出各种流式接口函数的接口。
2. USB加载式流接口驱动要点分析
为了支持不同类型的外围设备,WinCE平台提供了具有定制接口的流接口驱动程序模型。因为大部分USB外围设备由于功能性更适合流接口驱动的结构,所以一般都采用加载式流接口驱动程序模型来开发USB设备驱动程序。
(1)USB系统结构分析
WinCE下USB系统软件由两层组成:较高USB设备驱动程序层和较低的USB函数层。较低的USB函数层本身又由两部分组成:较高的通用串行总线驱动程序(USBD)模块和较低的主控制器驱动程序(HCD)模块。通过HCD模块功能和USBD模块实现高层的USBD接口函数,USB设备驱动程序就能与外围设备进行通讯。
在数据传输的过程中,操作流程通常按下列的次序进行:①USB设备驱动程序进行数据传输的初始化,即通过USBD接口函数给USBD模块发送数据传输的请求。②USBD模块将该请求分成一些单独的事务。③HCD模块排出事务次序。④主控制器硬件执行事务。这里需要提醒的是,所有的事务都是从主机发出的,外围设备完全是被动接受型的。
(2)USB设备驱动程序入口点函数
从结构分析我们可知,所有的USB设备驱动程序必须在它们的DLL库设置一定的入口点与USBD模块进行适当的交互。设置入口点函数有两个作用:一是使得 USBD 模块能与外部设备交互;二是使得驱动程序能创建和管理任何可能需要的注册键。
下面简要介绍相关函数的作用:USBDeviceAttach是当 USB 设备连接到主计算机时运行,USBD模块会调用这个函数初始化USB设备,取得USB设备信息和配置USB设备,并且申请必需的资源。 USBInstallDrive是在第一次加载USB设备驱动程序时首先被调用,它使得驱动程序能创建需要的注册键,用于将一个驱动程序所需的注册表信息写入到HKEY_LOCAL_MACHINEDriversUSBClientDrivers目录下,例如设备名称等。需要注意的是,USB设备驱动程序不使用标准的注册表函数,而是使用RegisterClientDriverID()、RegisterClientSettings()函数来注册相应的设备信息。
USBUninstallDriver是在用户删除USB设备驱动程序时调用,负责删除注册键并释放其它相关资源。它通过调用 UnRegisterClientSettings()和UnRegisterClientDriverID()函数来删除由驱动程序的 USBInstallDriver()函数创建的所有注册键。因此,我们在驱动程序中就需要严格按照这三个函数的原型来实现,否则就不能为设备管理器所识别。
3.USB设备流接口驱动的实现步骤
从WinCE USB设备驱动模型及结构分析中,我们可以清晰的看到主机和外设之间的实现方式。在主机端,通过USBD模块和HCD模块使用默认的PIPE访问一个通用的逻辑设备,实际上就是说USBD和HCD是一组访问所有USB设备的逻辑接口,它们负责管理所有USB设备的连接、加载、移除、数据传输和通用配置。其中HCD是主机控制驱动,是为USBD提供底层的功能访问服务,USBD是USB总线驱动,位于HCD的上层,利用HCD的服务提供较高层次的功能。因此,实现USB加载流驱动程序大致需要完成以下步骤:
(1)选择代表设备的文件名前缀。前缀非常重要,设备管理器在注册表中通过前缀来识别设备。同时,在流接口命名时也将这个前缀作为入口点函数的前缀,如果设备前缀为XXX,那么流接口对应为XXX_Close,XXX_Init等。
(2)设置驱动的各个入口点函数。所谓入口点是指提供给设备管理器的标准文件I/O接口。在生成一个DLL后,就用设备文件名前缀替换名字中的XXX。因此,每个加载式流接口驱动程序必须实现XXX_Init()、XXX_IOControl()以及XXX_PowerUp()等一组标准的函数,用来完成标准的文件I/O函数和电源管理等。
(3)建立.DEF文件。当设备管理器初始化USB设备编译出来的流接口函数后,还必须建立一个.def文件。DEF文件定义了DLL要导出的接口集,而且加载式流驱动大多是以DLL形式存在的,所以应将DLL和DEF的文件名统一起来。DEF文件告诉链接程序需要输出什么样的函数,最后将驱动程序编译到内核中去,这样这个USB设备流接口驱动程序就可以被应用程序调用。
(4)在注册表中为驱动程序建立表项。在注册表中建立驱动程序入口点,这样设备管理器才能识别和管理这个驱动。此外,注册表中还能存储额外的信息,这些信息可以在驱动运行之后被使用到。
在这次USB驱动开发过程中,错走许多冤枉路使我叫苦连天。我感受最深的是由于WinCE提供了通用串行总线驱动程序(USBD)模块、USBD接口函数全集、样本主机控制器驱动程序(HCD)模块。所以,我们只需要根据USB设备硬件特性,利用USBD提供的不同函数,实现流接口函数与外围设备的交互。在没有特别的情况下,我最大的收获经验是把这些公用的源程序照搬过来,能极大的缩短开发周期,从而能更快速地进行嵌入式开发。
转自:/hoys/archive/2010/08/20/
posted @ 2011-07-24 10:58 Maintell 阅读(71) 评论(1) 编辑
2011年7月22日
WinCE下Touch Panel驱动介绍
转自:/zhongnanjun_3/article/details/3274020
WinCE中的Touch Panel驱动是由GWES模块来管理的,Touch Panel驱动接收用户的触摸信息,并将其转换为屏幕上的坐标信息,传给GWES模块。在WinCE中,Touch Panel驱动是分层的,分为MDD层和PDD层,这和其他WinCE设备驱动是一样的。MDD层由微软提供,用户只需要实现MDD和PDD层间的DDSI函数就可以了。如图
WinCE中的GWES模块负责加载和管理Touch Panel驱动,Touch Panel的MDD层向上提供DDI接口,PDD层是针对硬件的实现,对MDD层提供DDSI接口。
WinCE中的GWES模块负责加载和管理Touch Panel驱动,Touch Panel的MDD层向上提供DDI接口,PDD层是针对硬件的实现,对MDD层提供DDSI接口。
1 Touch Panel驱动中的数据结构
(1) TOUCH_PANEL_SAMPLE_FLAGS
用于描述一个采样点的信息,这些信息被定义在一个枚举结构中:
enum enumTouchPanelSampleFlags {
TouchSampleValidFlag = 0x01,
TouchSampleDownFlag = 0x02,
TouchSampleIsCalibratedFlag = 0x04,
TouchSamplePreviousDownFlag = 0x08,
TouchSampleIgnore = 0x10,
TouchSampleMouse = 0x40000000
};
TouchSampleValidFlag:一个有效的采样值
TouchSampleDownFlag:第一次按触摸屏时,返回该flag
TouchSampleIsCalibratedFlag:采样的x和y坐标值不需要再被校验了
TouchSamplePreviousDownFlag:表示上一次采样状态是按在触摸屏上
TouchSampleIgnore:忽略这次采样值
TouchSampleMouse:预留
(2) TPDC_CALIBRATION_POINT
用于描述一个校验点的相关信息,结构如下:
struct TPDC_CALIBRATION_POINT {
INT PointNumber;
INT cDisplayWidth;
INT cDisplayHeight;
INT CalibrationX;
INT CalibrationY;
};
PointNumber:校验点索引值,用于描述校验点在LCD上的位置
0:中间
1:左上
2:左下
3:右下
4:右上
cDisplayWidth:显示的宽度
cDisplayHeight:显示的高度
CalibrationX:校验点的x坐标值
CalibrationY:校验点的y坐标值
(3) TPDC_CALIBRATION_POINT_COUNT
用于描述需要校验的点的个数,结构如下:
struct TPDC_CALIBRATION_POINT_COUNT {
DDI_TOUCH_PANEL_CALIBRATION_FLAGS flags;
INT cCalibrationPoints;
};
flags:一般为0
cCalibrationPoints:需要校验的点的个数,一般是5
(4) gIntrTouch和gIntrTouchChanged
这是两个被MDD层用到的中断,需要在PDD层中定义,如下:
DWORD gIntrTouch = SYSINTR_NOP;
DWORD gIntrTouchChanged = SYSINTR_NOP;
gIntrTouch用于描述触摸屏中断,要和硬件的触摸屏中断相关联。
gIntrTouchChanged用于在触摸屏按下后,每隔一段时间进行一次采样,应该和硬件的一个定时器中断相关联。
这两个值应该在DdsiTouchPanelEnable(..)函数中和硬件中断关联,并在函数DdsiTouchPanelGetPoint(..)中根据情况清除相应的中断。
2 MDD层API
MDD为上层导出所需的Touch Panel驱动接口函数,上层通过这些函数可以完成对Touch Panel的操作,下面会介绍这些函数的功能。
(1) BOOL TouchPanelEnable(PFN_TOUCH_PANEL_CALLBACK pfnCallback):
使能Touch Panel设备,用于初始化Touch Panel。
pfnCallback:指向处理Touch Panel事件的回调函数
(2) Void TouchPanelDisable(void):
禁用Touch Panel设备。
(3) BOOL TouchPanelGetDeviceCaps(INT iIndex, LPVOID lpOutput ):
获得Touch Panel设备的相关信息。
iIndex:索引值
TPDC_SAMPLE_RATE_ID:采样率信息
TPDC_CALIBRATION_POINT_COUNT_ID:采样点个数信息
TPDC_CALIBRATION_POINT_ID:采样点坐标信息
lpOutput:指向一个内存区域,用于存放获得的相关信息
(4) VOID TouchPanelCalibrateAPoint(INT32 UncalX, INT32 UncalY, INT32* pCalX,
INT32* pCalY):
将输入的未经过校验的坐标信息转换成校验后的坐标信息。
UncalX:输入的X坐标
UncalY:输入的Y坐标
pCalX:校验后的X坐标
pCalY: 校验后的Y坐标
(5) VOID TouchPanelPowerHandler(BOOL bOff):
Touch Panel的电源控制函数。
bOff:TRUE表示关闭电源,FALSE表示打开电源
(6) BOOL TouchPanelReadCalibrationPoint(INT* pRawX, INT* pRawY):
获得Touch Panel的坐标。
pRawX:触摸屏的X坐标
PRawY:触摸屏的Y坐标
(7) VOID TouchPanelReadCalibrationAbort(void):
终止当前的校验。
(8) VOID TouchPanelSetCalibration(INT32 cCalibrationPoints, INT32* pScreenXBuffer,
INT32* pScreenYBuffer, INT32* pUncalXBuffer, INT32* pUncalYBuffer):
校验函数。通过一组实际的触摸屏上采集的点坐标和相应的屏幕坐标计算校验系数。具体公式如下:
Sx = A1*Tx + B1*Ty + C1
Sy = A2*Tx + B2*Ty + C2
这里就是通过显示屏坐标和采样的触摸屏坐标计算A1,B1,C1,A2,B2,C2。
cCalibrationPoints:校验点的个数
pScreenXBuffer:一组显示屏上的X坐标
pScreenYBuffer:一组显示屏上的Y坐标
pUncalXBuffer:一组触摸屏上采样的X坐标
pUncalYBuffer:一组触摸屏上采样的Y坐标
(9) BOOL TouchPanelSetMode(INT iIndex, LPVOID lpInput):
设置Touch Panel的工作模式。
iIndex:索引模式
TPSM_SAMPLERATE_HIGH:设置高采样率
TPSM_SAMPLERATE_LOW:设置低采样率
TPSM_PRIORITY_HIGH_ID:设置触摸屏的IST为高优先级
TPSM_PRIORITY_NORMAL_ID:设置IST为正常优先级
lpInput:指向一块内存,其中包含相关信息。
3 PDD层API
(1) LONG DdsiTouchPanelAttach(void):
该函数在Touch Panel驱动的Dll被加载的时候调用
(2) LONG DdsiTouchPanelDettach(void):
该函数在Touch Panel驱动的Dll被卸载的时候调用
(3) BOOL DdsiTouchPanelEnable(void):
打开Touch Panel电源并做初始化。一般会在这里初始化一些信息,打开Touch Panel设备电源并做初始化。
(4) VOID DdsiTouchPanelDisable(void):
关闭Touch Panel设备。关闭Touch Panel电源并释放资源。
(5) BOOL DdsiTouchPanelGetDeviceCaps(ULONG iIndex, LPVOID lpOutput):
查询Touch Panel设备的相关信息
iIndex:查询的索引值
TPDC_SAMPLE_RATE_ID:查询采样率信息
TPDC_CALIBRATION_POINT_ID:查询需要校验的点的坐标
TPDC_CALIBRATION_POINT_COUNT_ID:查询用于校验的点的个数
lpOutput:根据iIndex值分别指向相关的信息
(6) void DdsiTouchPanelGetPoint(TOUCH_PANEL_SAMPLE_FLAGS pTipState, PLONG
pUnCalX, PLONG pUnCalY):
获得Touch Panel上被按下的点的状态和坐标。
pTipState:当前触摸点的状态,比如无效点,有效点,被按下的点等。
pUnCalX:触摸点的X坐标
pUnCalY:触摸点的Y坐标
(7) VOID DdsiTouchPanelPowerHandler(BOOL bOff):
设置Touch Panel的电源状态。
bOff:TRUE表示关闭电源,FALSE表示打开电源
(8) BOOL DdsiTouchPanelSetMode(ULONG iIndex, LPVOID lpInput):
设置Touch Panel工作模式。
iIndex:模式索引
TPSM_SAMPLERATE_HIGH_ID:高采样率
TPSM_SAMPLERATE_LOW_ID:低采样率
lpInput:指向包含相关信息的内存
4 注册表设置
对于Touch Panel驱动来说,有些注册表项是需要配置的。具体如下:
“InputConfig”:WinCE系统输入配置。
Bit0表示键盘输入
Bit1表示Touch Panel输入
Bit2表示硬件按键输入
“DeviceName”:Touch Panel驱动的名字。
“MaxCalError”:Touch Panel的精确度配置。
“CalibrationData”:Touch Panel的校验值。第一次启动WinCE后,需要通过WinCE的触摸屏校验程序对Touch Panel进行校验。校验完成后,校验值会被写入注册表里面。
下面是关于Touch Panel的注册表配置的例子:
[HKEY_LOCAL_MACHINE/ControlPanel]
"InputConfig"=dword:3 ;3 => keybd and touch screen
[HKEY_LOCAL_MACHINE/HARDWARE/DEVICEMAP/TOUCH]
"DriverName"=""
"MaxCalError"=dword:8
"CalibrationData"="446,671 36,191 38,1179 856,1161 862,169 "
个人觉得,要想更好的理解Touch Panel驱动,还是需要去读读代码,基于WinCE6.0下,可以参考"/WINCE600/PLATFORM/H4SAMPLE/SRC/DRIVERS/TOUCH"下面的驱动,基于这个驱动开发自己的驱动会比较方便。
Windows CE下触摸屏驱动实现的分析
Analysis of Touch Panel Driver Realization Based on Windows CE OS
刘林辉1 张 芬2
Liu,LinHui1 Zhang,Fen2
(1 长沙理工大学能源与动力工程学院,湖南 长沙,410077;2 华中科技大学机械科学与工程学院,湖北 武汉,430074)
摘要:本文介绍了Windows CE操作系统的触摸屏驱动程序模型,详细阐述嵌入式系统中电阻式触摸屏的Windows CE驱动程序的设计和实现方法。
关键词:触摸屏,Windows CE
中图分类号:TP316
文献标识码:A
Abstract:This article introduced touch panel driver model of Windows CE operating
system, and elaborated the driver design and the realization method of resistance-type
touch panel based on an embedded system which take Windows CE as operating system.
Keyword:Touch Panel,Windows CE
1. 前言
触摸屏是嵌入式设备中常用的计算机输入设备,它可使操作简单直观,人人都会使用,这一点无论是键盘还是鼠标都无法与其相比。在手机、PDA等手持产品及公共服务设备中大量采用触摸屏。触摸屏分为电阻式、电容式、表面声波式等多种,电阻式触摸屏是目前应用比较广泛的一种,有四线、五线、七线等几种。本文将分析Windows CE操作系统下的触摸屏驱动程序模型及实现方法。
2. Windows CE触摸屏驱动程序模型
在Windows CE操作系统中触摸屏驱动是一种分层驱动。其驱动模型如图1所示。上层是模型设备驱动程序(Model Device Driver, MDD),下层是依赖平台的驱动程序(Platform
Dependent Driver, PDD)。MDD通常无需修改直接使用,MDD链接PDD层并定义它希望调用的函数接口:设备驱动程序提供器接口(Device Driver Service Provider Interface,
DDSI)。同时MDD把不同的函数集提供给操作系统,这些函数叫做设备驱动程序接口(Device Driver Interface, DDI),这部分为也就是我们通常驱动需要实现的部分。
3 Windows CE的触摸屏驱动程序接口
Windows CE的触摸屏驱动链接了tch_和两个静态链接库。触摸屏驱动由GWES加载,GWES通过DDI调用驱动程序获取设备状态,设置驱动功能等,而驱动本身通过DDSI直接获得硬件信息来确定当前触摸屏的状态。
Windows CE触摸屏驱动要求的DDI接口包括:TouchPanelGetDeviceCaps、TouchPanelEnable、TouchPanelDisable、TouchPanelSetMode、TouchPanelReadCalibrationPoint、TouchPanelReadCalibrationAbort、TouchPanelSetCalibration、TouchPanelCalibrateAPoint、TouchPanelPowerHandler。
Windows CE触摸屏驱动要求的DDSI接口包括:DdsiTouchPanelAttach、DdsiTouchPanelDetach、DdsiTouchPanelDisable、DdsiTouchPanelEnable、DdsiTouchPanelGetDeviceCaps、DdsiTouchPanelGetPoint、DdsiTouchPanelPowerHandler。
4 Windows CE的触摸屏数据采集
Windows CE触摸屏驱动程序采用中断方式对触摸笔的按下状态进行检测,如果检测到触摸笔按下将产生中断并触发一个事件通知一个工作线程开始采集数据。同时,驱动将打开一个硬件定时器,只要检测到触摸笔仍然在按下状态将定时触发同一个事件通知工作线程采集数据,直到触摸笔抬起后关闭该定时器,并重新检测按下状态。驱动中采用了触摸屏中断以及定时器中断两个中断源,不仅可以监控触摸笔按下和抬起状态,而且可以检测触摸笔按下时的拖动轨迹。
触摸屏驱动在初始化过程调用TouchPanelEnable函数使能触摸屏。该函数调用的DDSI函数为:DdsiTouchPanelEnable和DdsiTouchPanelDisable。该函数实现如下 内容:
1) 创建事件hTouchPanelEvent和hCalibrationSampleAvailable。hTouchPanelEvent事件在正常状态下当有触摸笔按下或者按下后需要定时采集数据时被触发。而hCalibrationSampleAvailable事件在校准状态下当有校准数据输入时被触发;
2) 检查并初始化所需的中断gIntrTouch(触摸屏中断)和gIntrTouchChanged(定时器中断),并将中断gIntrTouch、gIntrTouchChanged关联到事件hTouchPanelEvent。当gIntrTouch,gIntrTouchChanged中断产生时将触发hTouchPanelEvent事件;
3) 创建一个ISR线程TouchPanelpISR。TouchPanelpISR用于等待和处理触摸屏事件hTouchPanelEvent,它是整个驱动程序中唯一的事件源。
TouchPanelpISR函数是实现触摸屏数据采集关键函数,它实现的内容为:
1) 等待循环,用于接收hTouchPanelEvent事件,并构成函数的主体;
2) 通过调用DdsiTouchPanelGetPoint函数获取当前触摸屏位置和状态信息;
3) 在获取有效数据且在校准状态下,收集并提交按下的位置信息;
4) 在正常状态下,校准数据,并检查校准后数据的有效性;
5) 最后调用由GWES传入的回调函数,提交位置信息和状态信息。
因此,在触摸屏驱动程序中DdsiTouchPanelEnable、DdsiTouchPanelDisable和DdsiTouchPanelGetPoint三个DDSI接口函数是驱动实现的关键所在。
在DdsiTouchPanelEnable和DdsiTouchPanelDisable函数中分别打开和关闭触摸屏硬件,这两个函数其实可以不真正操作硬件,而只是实现软件上的控制,但是为了降低功耗最好在DdsiTouchPanelDisable中将触摸屏控制器电源关闭并在DdsiTouchPanelEnable函数中打开。
在DdsiTouchPanelGetPoint函数中实现对触摸屏数据的采样。从上面的分析得知MDD通过检测hTouchPanelEvent和hCalibrationSampleAvailable事件控制采样,这两个事件被触发都将调用该函数。而这两个事件触发条件有两个:
1) 触摸笔按下时产生触摸屏中断gIntrTouch时触发;
2) 触摸笔按下后,定时器被打开,定时器将定时产生中断gIntrTouchChanged,并触发事件,直到触摸笔抬起为止。
因此该函数不仅需要对触摸屏数据采样,而且需要对触发条件进行状态控制,其流程如图2所示。图中定义了三个变量,它们分别为:
1) TouchIrq为静态变量或全局变量,且初始值为TRUE,该变量必须在触摸屏按下并产生触摸屏中断时设置为FALSE;
2) InterruptType为静态变量或全局变量,且初始值为SYSINTR_NOP,当在处理触摸屏中断时设置为SYSINTR_TOUCH,在处理定时器中断时设置为SYSINTR_TOUCH_CHANGED,其余设置为SYSINTR_NOP,且在处理完毕后必须将其作为参数传入InterruptDone函数以清除中断;
3) g_NextExpectedInterrupt为静态变量或全局变量,该变量表示下一个希望产生的中断,初始状态为PEN_DOWN,也就是触摸笔在抬起状态,因此希望下一个产生的中断为PEN_DOWN。当触摸屏中断产生以及定时器中断产生时该变量为PEN_UP_OR_TIMER,也就是下一个可能产生的状态为触摸笔抬起状态或者触摸笔按下但定时器中断产生。
DdsiTouchPanelGetPoint函数一开始从触摸笔抬起状态开始执行,此时TouchIrq等于TRUE。如果此时触摸笔按下,将设置TouchIrq为FALSE,表示本次采样是由于触摸屏中断产生并设置下一次调用由定时器产生。然后设置InterruptType状态为SYSINTR_TOUCH,接着开始采集数据并设置g_NextExpectedInterrupt变量为PEN_UP_OR_TIMER,表示下一次产生的中断为定时器中断。接着判断在触摸笔按下状态(g_NextExpectedInterrupt等于PEN_UP_OR_TIMER)下触摸笔是否抬起,如果抬起则设置g_NextExpectedInterrupt为PEN_DOWN恢复到抬起状态。最后通过将InterruptType作为参数传入InterruptDone函数以清除中断。当触摸笔按下,并产生定时器中断时,TouchIrq等于FALSE,此时
InterruptType被设置为SYSINTR_TOUCH_CHANGED,其余的动作基本和上面的流程一致。
5 Windows CE下的触摸屏校准
电阻触摸屏需要校准。应用程序需要一些参考值,以便将接收到的触摸屏坐标数据转换成高层软件所需的屏幕坐标。理想情况下校准程序只要在产品初次加电测试过程中运行一次就可以了,参考值被存储在非易失性存储器中。在理想情况下只需两组原始数据,即在屏幕对角读取的最小和最大值。而在实际应用中,因为许多电阻触摸屏存在显著的非线性,因此如果在最小和最大值之间简单的插入位置数值会导致驱动程序非常的不精确。
在Windows CE中通过在函数DdsiTouchPanelGetDeviceCaps 中设置校准点的个数,在TouchDriverCalibrationPointGet中获取每个校准点的屏幕坐标。常用的校准点数量为5。校准UI将在校准点坐标处相应显示一个十字叉,用户需要精确地在该十字叉位置按下触摸屏,驱动通过TouchPanelReadCalibrationPoint函数读取相应的触摸屏坐标值,然后开始下一个校准点。循环设定的次数后,将采集到的触摸屏坐标值和校准点屏幕坐标送到TouchPanelSetCalibration函数中进行处理。该函数将产生校准基准参数。
TouchPanelSetCalibration函数执行的动作是一套数学算法,具体内容为:
在触摸屏数据与其位置偏移关系且屏幕像素与其位置偏移关系同为线性关系假设情况下,触摸屏返回的位置信息与像素位置信息之间成2D坐标变换关系。则对于触摸屏按下点的触摸屏坐标(Tx,Ty)与其在显示设备位置关系上匹配的点的屏幕坐标(Sx,Sy)之间的转换关系,可以通过下述坐标变换表示:
Sx = A1*Tx + B1*Ty + C1
Sy = A2*Tx + B2*Ty + C2
TouchPanelSetCalibration的具体工作就是通过校准的动作获取的屏幕坐标和触摸屏坐标TouchCoordinate来确定A1,B1,C1和A2, B2, C2。
6. 结束语
本文作者的创新点:从分析嵌入式Windows CE操作系统中触摸屏驱动程序的模型及实现方法的角度深入剖析了Windows CE中触摸屏数据采集和校准的执行流程,对于类似系统的驱动开发具有一定的借鉴性。
参考文献:
[1] Paul Kovitz. 电阻式触摸屏结构和实现原理,夏普公司,2003


发布评论