Wince 流驱动的实现原理及过程

1 Windows CE驱动介绍

    驱动程序是介于操作系统和设备之间的一个代码层,它的主要作用是为操作系统提供一个接口,以操作不同的硬件,包括物理的和虚拟的设备。虽然驱动程序有很多种,但从编程的角度来看,无非是往一个固定的框架中添加相应的代码。这里的框架指的是一个接口,面向操作系统。代码实现的宗旨是,在正确的时间往正确的寄存器中写正确的值。

    驱动程序的分类,从不同的角度有不同的分法。拿串口驱动来说,你可以说它是一个分层驱动,你也可以说它是一个流驱动,你还可以说它是开机时自动加载的驱动……这似乎有点乱。如果你也这么认为,那建议往下看。如果这些你都了如指掌,那就不浪费时间了,当然,您愿意找茬,我会很感谢!

    先说本地驱动(Native Drivers)和流驱动(Stream Drivers)。WinCE下的驱动都可以归类到这两个里面,二者必居其一。这是从驱动程序提供给操作系统的接口来区分的。流驱动为操作系统提供了流接口函数,如XXX_Init()、XXX_Open()、XXX_Read()、XXX_Write()、XXX_Close()等等。这一类的驱动由Device Manager来管理,它调用ActivateDeviceEx()函数来加载流驱动。ActivateDeviceEx()的参数是注册表中相应的键,用来设定加载流驱动的属性,如Index、Order、Prefix等等。流驱动的注册表配置信息一般存放在[HKEY_LOCAL_MACHINE\Drivers\BuiltIn]下。流驱动加载成功后,应用程序通过调用CreateFile()、ReadFile()、WirteFile()等来访问流驱动的设备。流驱动可以动态管理,驱动调试助手就是用来帮助调试这一类驱动的。

与流驱动相反,本地驱动提供给操作系统的不是标准的流接口,而是事先约定好的特定接口。不同的设备,接口也不一样。WinCE中,常见的本地驱动有LCD显示驱动、触摸屏驱动、鼠标和键盘驱动及打印机驱动等。可以看到,本地驱动主要是人机界面相关的驱动。它们由GWES管理,在系统启动时加载。他们在注册表中也有各自相应的配置信息。如键鼠的注册表配置如下:

[HKEY_LOCAL_MACHINE"System"CurrentControlSet"Control"Layouts"00000409]

"LayoutFile"="kbdmouse.dll"

"LayoutText"="US"

"PS2_AT"="kbdmouse.dll"

"Matrix"="kbdmouse.dll"

本地驱动由操作系统调用,应用程序不能访问。对于这类驱动,驱动调试助手是无能为力的,只能老老实实的编译、下载、验证。

WinCE驱动中经常会听到MDD(Model Device Driver)和PDD(Platform Dependent Driver)的概念,这是从驱动代码实现的结构来区分的。WinCE的驱动可以是单层的,也可以是PDD+MDD。这没有硬性规定,一个驱动程序可以采用分层结构,也可以采用单层结构。一般来说,单层结构的驱动执行效率更高,而分层结构的驱动方便代码维护和移植。拿串口驱动来说,完全可以采用单层结构。而把它分为PDD和MDD,作为一般的开发者,我们只需实现PDD层就可以了,MDD层由微软实现。这样,驱动开发的工作量少很多,而代码的可靠性则有了更好的保证。至于采用哪一种结构的驱动,主要看你的需求。

WinCE 6.0引入了内核态驱动和用户态驱动的概念。在WinCE5.0及先前的版本中,驱动工作在用户态。从代码方面看,内核态驱动和用户态驱动没太大差别。如果驱动中没有采用什么特别的技术,内核态驱动和用户态驱动甚至是二进制兼容的。内核态驱动被加载到内核空间,用户态驱动被加载到特定的用户进程空间中。从执行效率来看,内核态的驱动效率比用户态的驱动高。从稳定性方面考虑,用户态的驱动不会对系统产生致命影响,而内核态的驱动相对危险。同样,采用哪一种类型的驱动,也是看你的需求。

从驱动加载的时间来看,可分为两种:系统启动时加载和需要时加载。一般来说本地驱动都是在启动时加载的,所以这里说的主要是流驱动。如果想要驱动在系统启动时加载,只需将它的注册表配置信息放到[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\]下,如[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Battery],系统启动时,Device Manager会自动加载它。需要时加载,顾名思义,就是想加载就加载,想卸载就卸载,很灵活。这里很有必要说一下USB设备的驱动加载,如USB摄像头驱动,它也属于需要时加载的驱动。从驱动的接口来看,它属于流驱动,但相对普通的流驱动,它增加了几个函数:USBDeviceAttach()、USBInstallDriver()、USBUnInstallDriver()等。USB摄像头驱动的加载在USBDeviceAttach()中完成。所以,它无须,也不能,用驱动调试助手加载。需要时加载的驱动还有一个作用,在无法修改系统的情况下,应用程序中动态加载该驱动,以完成对硬件的操作。

综上所述,WinCE驱动的分类,主要有以下几种分法:

按驱动接口分,可分为本地驱动和流驱动;

按驱动结构分,可分为单层驱动和分层驱动;

按驱动加载的空间分,可分为内核态驱动和用户态驱动;

按驱动加载的时间分,可分为启动时加载和需要时加载两种。

此文档主要针对流接口驱动进行详细介绍,结合我在6410开发板上实现的PWM驱动来详细介绍如何在Windows CE中编写一个流接口驱动。

 

 

2 流接口驱动介绍

在计算机系统中,很多硬件设备都在不断的制造或使用二进制制数据,这些设备可以被抽象成流式设备。流式设备的最典型例子是串口。我们在使用串口的时候,二进制数据会像流水一样,从一台设备经过串口线流到另外一台设备上。应用程序使用文件 API 对设备进行访问,文件 API 都会被操作系统转发到FileSys.exe 进程中,FileSys.exe 发现是对设备的操作,然后就会把执行交给设备管理器处理。设备管理器会根据具体的请求,调用不同的流式接口驱动程序中暴露的接口。最终,驱动程序会负责与硬件交互。

图2 流式接口驱动

2.1 流式接口函数

通过前面的内容我们知道,应用程序通过操作系统对流式接口驱动程序进行访问。对于操作系统而言,最基本的文件操作原语有 open, close, read, write, seek 五个,分别用来对文件进行打开、关闭、读取、写入和移动文件指针操作。但是对于驱动程序,仅有这五个基本的操作原语显然是不够的,驱动程序还需要加载/卸载等附加操作。因此,Windows CE 中定义的流式接口函数有 12 个,有些函数是直接与某个文件操作 API 对应的,而有些函数是为了某些特殊的目的,例如电源管理函数。流式接口函数的列表如下:

表1.1  流接口函数描述

函数名称

描述

XXX_Colse

在驱动程序关闭时,应用程序通过CloseHandler()函数调用这个函数

XXX_Deinit

当设备管理器卸载一个驱动程序时调用这个函数

XXX_ Init

当设备管理器初始化一个具体的设备式调用这个函数

XXX_IOControl

应用程序通过DeviceControl()函数可以调用这个函数

XXX_Open

再打开一个设备驱动程序时,应用程序通过CreateFile()函数调用这个函数

XXX_PowerDown

在系统挂起前调用这个函数

XXX_PowerUp

在系统重新启动时调用这个函数

XXX_Read

在一个设备驱动程序处于打开状态时,由应用程序通过ReadFile()函数调用这个函数

XXX­_Seek

对设备的数据指针进行操作时,由应用程序通过SetFilePointer()函数调用这个函数

XXX_Write       

在一个设备驱动程序处于打开状态时,由应用程序通过WriteFile()调用这个函数

XXX_PreClose

通知驱动程序把打开的句柄设置为无效,而避免某些竞态。

XXX_PreDeinit

通知程序把设备句柄设置为无效,而避

免某些竞态。

注:红色字体为比较重要的函数,写应用程序要经常调用

其中 XXX 是驱动程序的设备名称,例如对于串口驱动程序,串口的设备名称是 COM,因此,XXX_Open 在串口当中就被替换成了 COM_Open。驱动程序的设备名称我们可以在注册表中指定。

 

2.2 流接口函数具体介绍

下面具体来介绍上面的接口函数

1、  XXX_Init XXX_Deinit

XXX_Init该函数在DllEntry后被调用. 但不是被应用程序调用,而是在一定的情况下被设备管理器调用. 那么"一定情况下"是指什么呢. 是指当用户开始用一个设备时,设备管理器调用该函数初始化设备. 当要实现多个流接口驱动的实例时(串口是个典型的例子, COMx就是COM的多个实例),XXX_Init应该被调用多次。该函数要返回设备句柄来为其它的接口函数能使用,XXX_Deinit是设备被卸载时调用。函数声明如下:

// 初始化设备,在设备被加载的时候调用,返回设备的上下文句柄

DWORD XXX_Init(

LPCTSTR pContext,// 字符串,指向注册表中记录活动驱动程序的键

LPCVOID lpvBusContext//ActivateDeviceEx 函数的第四个参数,VOID 指针

);

// 释放设备,在设备被卸载的时候调用,返回设备卸载是否成功

BOOL XXX_Deinit(

DWORD hDeviceContext//XXX_Init 函数返回的设备上下文

);

2、 XXX_Open XXX_Close

XXX_Open与 XXX_Close分别在用户调用CreateFile和CloseHandle时被调用. XXX_Open的参数hDeviceContext是由XXX_Init返回的值.是一个指向设备context的句柄. 另外, 用CreateFile打开设备时要用”XXX1:”的形式. 后面的数字根据注册表active下的键值确定。这两个函数的声明如下:

// 打开设备进行读写,返回设备的打开上下文

DWORD XXX_Open(

DWORD hDeviceContext,    // 设备上下文,由 XXX_Init 函数创建

DWORD AccessCode,        // 设备的访问模式,从 CreateFile 函数传入

DWORD ShareMode          // 设备的共享模式,从 CreateFile 函数传入

 

// 关闭设备,返回设备关闭是否成功

BOOL XXX_Close(

DWORD hOpenContext       //设备的打开上下文,由XXX_Open 函数返回

);

通常,在XXX_Open 函数中,需要为设备申请资源,一般是一些用于管理设备的数据结。而在 XXX_Close 函数中,这些数据结构可以被释放。

3、XXX_ReadXXX_WriteXXX_Seek

对于设备的主要操作都是通过 ReadFile(),WriteFile()与 SetFilePointer()函数进行的,他们负责对设备进行读、写和移动当前指针。在流式接口驱动层面,XXX_Read、XXX_Write与XXX_Seek 三个函数就提供了对这些操作的支持。这三个函数的声明如下所示:

// 从设备中读取数据,返回 0 表示文件结束,返回-1 表示失败,返回读取的字节数表示成功

DWORDXXX_Read(

DWORD hOpenContext,// XXX_Open 返回的设备打开上下文

LPVOID pBuffer, // 输出,缓冲区的指针,读取的数据会被放在该缓冲区内

DWORDCount// 要读取的字节数

);

// 向设备中写入数据,返回-1 表示失败,返回写入的字节数表示成功

DWORDXXX_Write(

DWORD hOpenContext,// XXX_Open 返回的设备打开上下文

LPCVOID pBuffer, // 输入,指向要写入设备的数据的缓冲

DWORDCount// 缓冲中的数据的字节数

);

// 移动设备中的数据指针,返回数据的新指针位置,-1 表示失败

DWORDXXX_Seek(

DWORD hOpenContext,// XXX_Open 返回的设备打开上下文

longAmount, // 要移动的距离,负数表示前移,正数表示后移

WORDType// 移动的相对位置,有FILE_BEGIN、FILE_CURRENT 和FILE_END

);

4、XXX_IOControl

XXX_IOControl是当用户用DeviceIOControl定义要完成的操作时, 系统用该函数. dwCode是操作码,特定于不同的设备.可通过头文件传给应用程序. 这两个参数是必须的, 其它几个可为NULL, pBufIn输入buffer, pBufOut是输出buffer. 输入输出是相对设备的, pdwActualOut是实际输出的字节数. 这个函数的功能比较强大,为什么这么说呢, 因为它的上层DeviceIOControl比较强大, 事实上,DeviceIOControl可以完成几乎所有的操作其中就包括读写操作,函数的原形如下:

// 向驱动程序发送控制命令

BOOL XXX_IOControl(

DWORD hOpenContext, // 由 XXX_Open 返回的设备打开上下文

DWORD dwCode, // 要发送的控制码,一个 32 位无符号数

PBYTE pBufIn, // 输入,指向输入缓冲区的指针

DWORD dwLenIn, // 输入缓冲区的长度

PBYTE pBufOut, // 输出,指向输出缓冲区的指针

DWORD dwLenOut, // 输出缓冲区的最大长度

PDWORD pdwActualOut// 输出,设备实际输出的字节数

);

对于 XXX_IOControl 函数,在流式接口中应用非常广泛,一些不适合于像文件一样进

行读写的设备,都可以通过 XXX_IOControl 函数来控制。

5、 XXX_PowerUp XXX_PowerDown

XXX_PowerDown与XXX_PowerUp这两个函数, 是在系统掉电与上电时执行的, 所以很显然,你不能调用它. 它是在内核模式下执行的. 为了避免死机, 这两个函数最好什么都不要做,尽快返回。

6、XXX_PreClose XXX_PreDeinit

这两个函数是在Windows CE 5.0 中新引入的函数,目的是为了防止多线程操作时可能引发的一些竞态(Race Condition)。这两个函数都是可选的,但是如果驱动实现了其中的一个,那么也必须实现另外一个,否则驱动就无法被加载。它们的函数原形如下:

BOOLXXX_PreClose(

DWORDhOpenContext// 设备的打开上下文

);

BOOLXXX_PreDeinit(

DWORDhDeviceContext // 设备的上下文

);

设备管理器在对设备进行管理的时候,对于对 XXX_Init、XXX_Deinit、XXX_Open 和

XXX_Close 的调用,设备管理器内部会维持一个全局的 Critical Section 来保证操作的原子性,但是出于效率的考虑,在调用 XXX_Read、XXX_Write、XXX_Seek 与 XXX_IOControl 的时候,并没有对这些调用加锁,这就导致了引发竞态的可能。

设想这样一种情况,系统中有多个线程同时访问某个驱动程序,当某个线程调用了XXX_Read 来读取打开的设备的时候,另外一个线程调用了 XXX_Close 关闭了打开的设备,因为正如前文所述,设备管理器对 XXX_Read 的调用是没有 Critical Section 的保护的,因此有可能调用 XXX_Read 的线程在没有执行完之前就被执行 XXX_Close 的线程抢占,并且关闭了设备,那么当 XXX_Read 的线程重新占有处理器的时候,它所要读取的设备已经被关闭了,这极有可能导致驱动程序崩溃。同样的问题也发生在设备卸载的时候。如图所示:

图2.2 设备驱动程序中的竞态

添加了这两个函数之后,就可以有效防止前面的情况发生。XXX_PreClose 在设备管理器调用 XXX_Close 之前被调用,在这个函数里,驱动程序需要唤醒在等待对设备进行操作的线程,并且释放申请的资源。这时,还会把 hOpenContext 设置为无效,因此后面的对设备的访问都会直接返回失败,而不会使整个驱动程序崩溃。同样 XXX_PreDeinit 在设备管理器调用 XXX_Deinit 之前被调用,可以防止应用程序打开一个已经被卸载的设备。

2.3 流接口函数调用示例

Windows CE里任何暴露了流式接口函数的驱动程序都可以被称作流式接口驱动程序(也就是在驱动程序的 DLL 中把这些函数作为 DLL的导出函数)。在流式接口驱动程序中,驱动程序负责把外设抽象成一个文件,而应用程序则可以使用操作系统提供的文件 API 对外设进行访问。举例来说,串口驱动程序是典型的流式驱动,如果应用程序希望打开串口,则可以通过如下的语句:

ANDLE hComm;                                             

hComm = CreateFile (TEXT("COM1:"), GENERIC_READ | GENERIC_WRITE,

0, NULL, OPEN_EXISTING, 0, NULL);           

if (hComm == INVALID_HANDLE_VALUE)                         

// error opening port; abort                                         

CreateFile()函数的第一个参数不再是文件名称,而是驱动程序的名字,在这个例子中为“COM1”。同样的道理如果需要向串口写入数据,只需要使用 WriteFile()函数,代码如下:

INT rc;

DWORD cBytes;

BYTE ch;

ch = TEXT ('a');

rc = WriteFile(hSer,&ch, 1, &cBytes, NULL);

当然如果想要读取串口的数据可以调用WriteFile()函数。

2.4 流驱动加载过程

下面是流驱动加载过程

(1)加载驱动。在当系统启动时,设备管理器搜寻注册HKEY_LOCAL_MACHINE\Driver\BuiltIn键下面的子键,并逐一加载子键下的每个驱动,此过程叫BusEnum。

(2)设备管理器从注册表的dll键值中获取驱动程序所在的DLL文件名。

(3)设备管理器调用LoadDriver()函数把DLL加载到自己的虚拟地址空间内。

(4)设备管理器在注册表的HKEY_LOCAL_MACHINE\Driver\Active下面,记录所有已经加载的驱动程序[2]。

(5)设备管理器调用驱动中的XXX_Init()函数。

(6)在XXX_Init()中,通常对硬件进行一些基本的初始化操作。通过以上6步,流接口驱动被成功加载。

(7)应用程序使用该设备。首先它调用CreateFile()打开设备。CreateFile()是在FileSys.exe中实现的。但是FileSys.exe只作简单判断,如果发现打开的设备驱动程序而不是一个文件,那么就重新把主动权交还给设备管理器。

(8)设备管理器调用驱动程序中的XXX_Open()函数打开设备。在XXX_Open()中,驱动程序可能会对硬件进行一些额外的初始化工作,使硬件进入工作状态。

(9)XXX_Open()函数把打开设备的结果返回给设备管理器。

(10)设备管理器把XXX_Open()返回的结果,再返回给应用程序的CreateFile()函数调用。通过7-10步,设备已被成功打开,至此就可以对设备进行读写和控制操作。

(11)应用程序使用第7步CreateFile()函数返回的句柄作为 ReadFile() / WriteFile()的第一个参数,向设备发送读请求。同样ReadFile()/ WriteFile()要经过FileSys.exe转发给设备管理器。

(12)设备管理器调用驱动程序中的XXX_Read() /XXX_Write() 函数,读取设备的数据信息或向设备写信息。

(13)在流驱动程序中,XXX_Read() / XXX_Write() 函数可与硬件交互,从硬件中读取必要的信息或向硬件写必要的信息。然后返回给设备管理器,再返回给应用程序。

当应用程序不再使用该设备时,它可调用CloseHandle()将设备关闭。当系统不再使用设备时,应用程序可调用DeactivateDevice()函数把该驱动程序卸载。

2.5 PWM流式接口驱动的实现

    至此,我以我在6410 开发板上实现的PWM驱动程序为例,介绍如何实现流式接口驱动程序。

实现流式接口驱动程序通常只需四个步骤:

1、为流式接口驱动程序选择一个前缀。

2、实现流式接口驱动 DLL 所必需的接口函数。

3、编写 DLL 的导出函数定义文件.DEF。

4、为驱动程序配置注册表

正如前文介绍,应用程序通常需要通过设备的名称来对驱动程序进行访问。这里我

们采用由三个大写的英文字母,然后加一个 0 到 9 之间的数字构成的传统方式命名。PWM驱动的前缀定义为“PWM”。下面就需要为步进电机编写代码了,这一步可以在 Platform Builder 或者 eMbeddedVisual C++或者Visual Studio 2005 中进行。Windows CE 的驱动程序就是一个用户态的 DLL,因此,任何可以编写Windows CE DLL 的工具都可以用来开发驱动程序。我们以使用 PlatformBuilder 为例,介绍开发驱动程序的过程。

1、我使用的是Windows CE 6.0的开发环境,首先要到我们进入选定的BSP的驱动文件夹中创建一个自己的驱动文件夹:

2、进入设备驱动的目录C:\WINCE600\PLATFORM\SMDK6410\SRC\DRIVERS,创建一个名为My_PWM的文件夹。

3、打开当前路径下的dirs文件,并添加我们刚创建的文件夹名称,这样在编译驱动的时候我们的驱动程序就可以编译到系统中去了,如图2.5所示:

图2.5

4、进入My_PWM文件夹,创建一个.cpp文件,命名为MyPWMDriver。此文件为驱动程序的源文件,主要用于实现标准的流接口函数。因为驱动的前缀被定义为“PWM”,因此在流式驱动程序中就必须实现一些 PWM打头的函数。首先在MyPWMDriver.cpp中添加必要的头文件:

#include<ceddk.h>

#include<nkintr.h>

#include<pm.h>

#include<DrvLib.h>

#include<s3c6410_gpio.h>

#include<s3c6410_base_regs.h>

#include<s3c6410_pwm.h>

#include"pmplatform.h"

#include"Pkfuncs.h"

#include"ioctl_cfg.h"

 

#defineStart 2;                          //宏定义,用于启动PWM输出

#defineStop  3;                       //宏定义,用于关闭PWM输出

然后,增加 GPIO 端口寄存器和PWM寄存器的声明:

staticvolatile S3C6410_GPIO_REG * g_pGPIOReg = NULL;//指向IO地址块的指针

staticvolatile S3C6410_PWM_REG * g_pPWMReg = NULL;//指向定时控制器块的指针

接下来要先实现驱动程序的入口函数DllEntry,这个函数是动态链接库的入口,每个动态链接库都需要输出这个函数,它只在动态库被加载和卸载时被调用,也就是设备管理器调用LoadLibrary而引起它被装入内存和调用UnloadLibrary将其从内存释放时被调用,因而它是每个动态链接库最早被调用的函数,一般用它做一些全局变量的初始化。函数实现如下:

BOOLWINAPI 

DllEntry(HANDLE   hinstDLL,

           DWORD dwReason,

           LPVOID /* lpvReserved */)

{

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       DEBUGREGISTER((HINSTANCE)hinstDLL);

       return TRUE;

    case DLL_THREAD_ATTACH:

       break;

    case DLL_THREAD_DETACH:

       break;

    case DLL_PROCESS_DETACH:

       break;

#ifdefUNDER_CE

    case DLL_PROCESS_EXITING:

       break;

    case DLL_SYSTEM_STARTED:

       break;

#endif

    }

 

    return TRUE;

}

 

下面要就要具体的实现几个流式接口了。首先,是 PWM_Init 和 PWM_Deinit 两个函数。这两个函数在驱动程序加载和卸载的时候被调用,在这两个函数中通常放置一些初始化工作代码,我们在这两个函数里面做一些相关的物理寄存器映射工作。

DWORDPWM_Init(DWORD dwContext)

{

   

    RETAILMSG(1,(TEXT("My_PWM_Init----\r\n")));

Virtual_Alloc(); //映射物理寄存器到虚拟空间,因为WinCE使用的是虚拟地址,

//此函数也是在当前文件实现

    PWMInit();       // 配置PWM端口

    return TRUE;

}

 

BOOLPWM_Deinit(DWORD hDeviceContext)

{

    BOOL bRet = TRUE;

    if (g_pGPIOReg) {

       DrvLib_UnmapIoSpace((PVOID)g_pGPIOReg);  //释放虚拟地址空间

    }

    if (g_pPWMReg) {

       DrvLib_UnmapIoSpace((PVOID)g_pPWMReg);  //释放虚拟地址空间

    }

 

    g_pGPIOReg = NULL;

    g_pPWMReg = NULL;

    RETAILMSG(1,(TEXT("USERPWM:PWM_Deinit\r\n")));

    return TRUE;

}

接下来是PWM_IOControl()函数,此函数也是本驱动的关键函数,由应用程序的DeviceIOControl()调用,实现指令和数据的传递:

BOOL(DWORD hOpenContext,

                DWORD dwCode,

                PBYTE pBufIn,

                DWORD dwLenIn,

                PBYTE pBufOut,

                DWORD dwLenOut,

                PDWORD pdwActualOut)

{

    unsigned char temp[4];                 //用于数据接受的临时数组

    DWORD Freq,dutyfactor;

    RETAILMSG(1,(TEXT("My_PWM_IOControl----\r\n")));

    temp[0] = pBufIn[0];                   //数据接收

    temp[1] = pBufIn[1];

    temp[2] = pBufIn[2];

    temp[3] = pBufIn[3];

    //memcpy(&temp, &pBufIn, dwLenIn);

    Freq = temp[3]*256 + temp[2];          //获取应用程序传递过来的频率值

    dutyfactor = temp[1]*256 + temp[0];    //获取应用程序传递过来的占空比的

                                          //值

    switch(dwCode)

    {

    case 2:

       {     

PWMSet(Freq,dutyfactor );     //此函数在本文件中实现,用于根据所获得

//的值设置相应的控制寄存器,以实现我们

//所需要的PWM输出

       break;

       }

    case 3:

       PWMStop();                      //关闭PWM输出 

       break;

    default:

       break;    

    }

    RETAILMSG(1,(TEXT("PWM:Ioctl code =0x%x\r\n"), dwCode));

    return TRUE;

}

剩余的几个流式接口函数不进行任何实质性的操作,只是实现了一个框架,仅仅输出一些调试信息,以便观查调用:

DWORDPWM_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_Open\r\n")));

    return TRUE;

}

//-------------------------------------------------------------------------

BOOLPWM_Close(DWORD hOpenContext)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_Close\r\n")));

 

    return TRUE;

}

//-------------------------------------------------------------------------

voidPWM_PowerDown(DWORD hDeviceContext)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_PowerDown\r\n")));

}

 

//-------------------------------------------------------------------------voidPWM_PowerUp(DWORD hDeviceContext)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_PowerUp\r\n")));

 

}

//-------------------------------------------------------------------------

DWORDPWM_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_Read\r\n")));

    return TRUE;

}

 

//-------------------------------------------------------------------------

DWORDPWM_Seek(DWORD hOpenContext, long Amount, DWORD Type)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_Seek\r\n")));

    return 0;

}

//-------------------------------------------------------------------------

DWORDPWM_Write(DWORD hOpenContext, LPCVOID pSourceBytes, DWORD NumberOfBytes)

{

    RETAILMSG(1,(TEXT("USERPWM:PWM_Write\r\n")));

    return 0;

}

这样,所有流式接口驱动程序的导出函数就实现完毕了。但是现在还不能进行编译。我们知道,如果要在 DLL 中导出一个函数,有两种方法,一种是使用编译器扩展关键字__declspec(dllexport),如果采用这种关键字,还要注意 C++编译器会对函数名称进行修饰,因此还要加上 extern “C”。另外一种更为简便的方式是使用.DEF 文件。

2.6 编写 DLL 的导出函数定义文件.DEF

.DEF 文件定义了 DLL 的导出函数列表。在My_PWM文件夹中插入一个文本文件,命

名为MyPWMDriver.def,然后在该文件中输入如下内容:

LIBRARYpwm

EXPORTS

    PWM_Close

    PWM_Deinit

    PWM_Init

    PWM_IOControl

    PWM_Open

    PWM_PowerDown

    PWM_PowerUp

    PWM_Read

    PWM_Seek

    PWM_Write

添加Makefile文件

    在此文件夹下新建一个记事本文件添加如下内容:

!INCLUDE$(_MAKEENVROOT)\makefile.def

然后重命名为makefile即可。

添加Source文件

    在此文件夹下新建一个记事本文件并添加如下内容:

SYNCHRONIZE_DRAIN=1

RELEASETYPE=PLATFORM

TARGETNAME=MyPWMDriver

TARGETTYPE=DYNLINK

 

SOURCES=MyPWMDriver.cpp

 

TARGETLIBS=\

   $(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    \

   $(_TARGETPLATROOT)\lib\$(_CPUINDPATH)\DriverLib.lib

INCLUDES_PATH=$(_TARGETPLATROOT)\src\drivers\MyPWMDriver

    然后另存为source即可。Source文件将会告诉编译器要编译的源文件有哪些,要连接的库文件有哪些,以及生成的文件类型和文件名。

    至此,我们的驱动程序编写完毕,接下来我们还需要修改一下配置文件和注册表,让系统加载我们的驱动程序。

修改配置文件和注册表

1、修改配置文件

打开platform.bib文件,在文件中加入如下代码:

$(_FLATRELEASEDIR)\           NK    SH

这一步的工作是把前面生成的dll加进wince的系统内核中。

2、修改注册表

打开platform.reg文件,在文件中加入如下代码:

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\MyPWMDriver]

   "Prefix"="PWM"

   "Dll"="MyPWMDriver.dll"

   "Order"="200"

然后要做的工作就是编译我们的驱动程序到内核中,并验证我们的驱动是否有效,接下来就是一系列的调试工作。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值