【WinCE 驱动】Win CE驱动程序快速入门

 应用程序调用驱动程序

1、应用程序使用CreateFile函数打开设备

2、然后用DeviceIoControl与驱动程序进行通信,发送命令,交换数据,向指定的设备驱动发送正确的控制码及数据

3、驱动中的IOControl根据传进来的控制码及数据执行swith。。case语句中代码,进而执行操作。

    应用程序可以通过API函数DeviceIoControl来实现对设备的访问—获取信息,发送命令,交换数据等。利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的。

    应用程序和驱动程序的通信过程是:应用程序使用CreateFile函数打开设备,然后用DeviceIoControl与驱动程序进行通信,包括读和写两种操作。还可以用ReadFile读数据用WriteFile写数据。操作完毕时用CloseHandle关闭设备。我们比较常用的就是用DeviceIoControl对设备进行读写操作。

 流式驱动程序的工作过程。

答:第一步:加载驱动程序,主要有两种加载方式:(1)当系统启动时,设备管理器搜寻注册表的HKEY_LOCAL_MACHINE\Drivers\BuiltIn键下面的子键,并逐一加载该子键下的每一个驱动,此过程称为BusEnum;(2)应用程序可调用ActivateDeviceEx()函数动态地加载驱动程序。

    第二步:设备管理器从注册表的dll键值中获取驱动程序所在的DLL文件名。

第三步:设备管理器调用LoadDriver()函数把该DLL加载到自己的虚拟地址空间中。

第四步:设备管理器在注册表的HKEY_LOCAL_MACHINE\Drivers\Active下,记录所有已经加载的驱动程序。

第五步:设备管理器调用驱动程序中的COM_Init函数,并把上一步中添加的注册表项的完整路径作为COM_Init函数的第1个参数传入驱动程序内。

第六步:在COM_Init中,通常须对硬件进行一些最基本的初始化操作。

通过以上的操作完成流式驱动程序的加载任务。

对驱动程序的操作:

第一步:应用程序使用该设备。首先,应用程序调用CreateFile(TEXT(“COM1”)….)打开设备。然后,文件系统判断打开的是文件还是设备。最后,如果打开的是设备,就将控制权交回设备管理器。

第二步:设备管理器调用驱动程序的COM_Open()函数打开设备。

第三步:COM_Open()函数把打开设备的结果返回给设备管理器。

第四步:设备管理器把SMP_open()的返回结果,返回给应用程序的CreateFile()函数调用。

通过第七步到第十步,设备已被成功打开,接下来可对设备进行读/写和控制操作。下面将以从设备中读取数据为例。

第一步:应用程序使用CreateFile()调用返回的句柄,调用函数ReadFile(),向设备发送读请求。

第二步:设备管理器调用驱动程序的COM_Read()函数,读取设备的数据信息。在此过程中,COM_Read()函数可与硬件交互,从硬件中读取必要的信息。然后返回给设备管理器,再返回给应用程序。

最后,当应用程序不再使用该设备时,它可调用CloseHandle()将设备关闭。

      入门前应该阅读一下这篇文章:http://blog.csdn.net/lan120576664/article/details/4942146

     有人也许和我一样比较心急,想尽快知道如何去写一个具体的驱动程序,这里,假设您对驱动程序已经有比较好的了解,告诉大家一个快速上路的方法。当然,如果您有足够的时间我建议在动手之前还是深入了解一下CE整个系统架构。

在CE中,最简单的一个驱动程序莫过于一个内置(Built-in)设备的流接口驱动。对于一个不支持热拔插的设备,最快捷的方法就是为其实现一个内置的流接口的驱动。

对于这样一类驱动程序,我们只需要按一种特定的规则实现一个动态库,其中实现对所有的硬件功能的调用,再将这个动态库加入系统中,然后设置相关的注册表项,使得在系统启动时设备管理器能识别并且加载这个设备即可。

(自我理解:驱动程序即完成硬件操作的动态链接库)

3.1               实现动态链接库
此动态链接库与应用程序层所用的库并不很大差别,源文件可以是C、C++、甚至汇编,,只是它要实现以下函数。

1       DllEntry(HINSTANCE DllInstance, INT Reason, LPVOID Reserved )  //动态库被加载和卸载时被调用

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

参数:

DllInstance:DLL的句柄,与一个EXE文件的句柄功能类似,一般可以通过它在得到DLL中的一些资源,例如对话框,除此之外一般没什么用处。

Reason:

这个是操作系自动统调用的,参数也是操作系统传入的,一般不用管这个DllEntry,可以加一些打印信息而已,按照模板写即可。

一般我们只关心两个值:DLL_PROCESS_ATTACH与DLL_PROCESS_DETACH,Reason等于前者是动态库被加载,等于后者是动态库被释放。

所以,我们可以在Reason等于前者是初始化一些资源,等于后者时将其释放。

      DWORD XXX_Init( LPCTSTR pContext, LPCVOID lpvBusContext);        它是驱动程序的动态库被成功装载以后第一个被调用的函数。其调用时间仅次与DllEntry,而且,当一个库用来生成多于一个的驱动程序实例时仅调用一次DllEntry,而xxx_Init会被调用多次。驱动程序应当在这个函数中初始化硬件,如果初始化成功,就分配一个自已的内存空间(通常用结构体表示),将自已的状态保存起来,并且将此内存块的地址做为一个DWORD值返回给上层。设备管理器就会用在调用XXX_Open时将此句柄传回,我们就能访问自已的状态。如果初始化失败,则返回0以通知这个驱动程序没有成功加载,先前所分配的系统资源应该全部释放,此程序的生命即告终至当这个函数成功返回,设备管理器对这个程序就不做进一步处理,除非它设置了更多的特性。至此一个各为XXX的设备就已经加载成功,当用户程序调用CreateFile来打开这个设备时,设备管理器就会调XXX_Open函数。 

自己简化理解:XXX_Init()分配自己一个空间保证状态,返回地址给 XXX_Open ,作为XXX_Open的传入参数,用户程序调用CreateFile时调用XXX_Open,就可以通过那个参数了解XXX_Init()的情况。

参数:pContext:系统传入的注册表键,通过它可以讲到我们在注册表中设置的配置信息。lpvBusContext:一般不用,在这先不做讲解实际上,很多程序中将这个函数写成了DWORD XXX_Init( DWORD pContext )我们只需要将pContext转化成LPCTSTR即可。 3      DWORD XXX_Open(DWORD hDeviceContext,DWORD dwAccess, DWORD dwShareMode);当用户程序调用CreateFile打开这个设备时,设备管理器就会调用此驱动程序的XXX_Open函数。参数:hDeviceContext XXX_Init 返回给上层的值,也就是我们在XXX_Init中分配的用来记录驱动程序信息的那个结构体的指针,我们可以在这个函数中直接将其转化成所定义的结构,从而获取驱动程序的信息dwAccess 上层所要求的访问方式,可以是读或者写,或者是0,即不读也不写dwShareMode 上层程序所请求的共享模式,可以是共享读、共享写这两个值的逻辑或,或者是0,即独占式访问。系统层对设备文件的存取权限及共享方法已经做了处理,所以在驱动程序中对这两个参数一般可以不用理会。这个函数一般不用做太多处理,可以直接返回hDeviceContext表示成功,对于一个不支持多个用户的硬件,在设备已经打开后,应该总是返回0以至失败,则CreateFile调用不成功。 4   DWORD XXX_Close(  DWORD hDeviceContext );      当用户程序调用CloseHandle关闭这个设备句柄时,这个函数就会被设备管理器调用。参数:       hDeviceContext 为XXX_Open返回给上层的那个值。       这个函数应该做与XXX_Open相反的事情,具体包括:释放XXX_Open分配的内存,将驱动程序被打开的记数减少等。 5        DWORD XXX_Deinit( DWORD hDeviceContext );        这个函数在设备被卸载时被调用,它应该实现与XXX_Init相反的操作,主要为释放前者占用的所有系统资源。参数:        hDeviceContext XXX_Init函数返回给上层的那个句柄6       void XXX_PowerUp(  DWORD hDeviceContext );
l         void XXX_PowerDown(DWORD hDeviceContext );
       正如其名称中体现的那样,这两个函数在系统PowerUp与PowerDown时被调用,这两个函数中不能使用任何可能引起线程切换的函数,否则会引起系统死机。所以,在这两个函数中,实际上几乎是什么做不了,一般在PowerDown时做一个标志,让驱动程序知道自已曾经被Power Down过。在Power Down/On的过程中硬件可能会掉电,所以,尽管Power On以后,原来的IO操作仍然会从接着执行,但可能会失败。这时,当我们发现一次IO操作失败是因为程序曾经进入过Power Down状态,就重新初始化一次硬件,再做同样的IO操作。l         BOOL XXX_IOControl(                      DWORD hDeviceContext,                      DWORD dwCode,                      PBYTE pBufIn,                      DWORD dwLenIn,                      PBYTE pBufOut,                      DWORD dwLenOut,                      PDWORD pdwActualOut                  );几乎可以说一个驱动程序的所有功能都可以在这个函数中实现。对于一类CE自身已经支持的设备,它们已经被定义了一套IO操作定,我们只需按照各类设备已经定义的内容去实现所有的IO操作。但当我们实现一个自定义的设备时,我们就可以随心所欲定义我们自已的IO操作。参数:hDeviceContext XXX_Open返回给上层的那个句柄,即我们自已定义的,用来存放程序所有信息的一个结构。dwCode      IO操作码,如果是CE已经支持的设备类,就用它已经定义好码值,否则就可以自已定义。pBufIn 传入的Buffer,每个IO操作码都会定义自已的Buffer结构dwLenIn pBufIn以字节记的大小pBufOut,dwLenOut分别为传出的Buffer,及其以字节记的大小pdwActualOut    驱动程序实际在pBufOut中填入的数据以字节记的大小 其中,前两个参数是必须的,其它的任何一个都有可能是NULL或0。所以,当给pdwActualOut赋值时应该先判断它是否为一个有效的地址 3.2注册表设置l         在注册表中添加如下项目。(一般放在Platform.reg)

[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SampleDev]

   "Prefix"="XXX"

   "Dll"="MyDev.Dll"

   "Order"=dword:1


l         在BIB文件中添加项目,将所用到的文件加入BIN文件(一般放在Platform.bib)。

MyDev.dll      $(_FLATRELEASEDIR)/MyDev.dll       NK SH

l         注:

SampleDev为任意与其它项目不重名的字符串.

每个函数名的前缀XXX可以是任意大写的字符串,只要保证与注册表中Prefix后面的值相同就行。

3.3编译程序
       现在,已经知道了需要实现哪些东西,一定想知道如何去实现它。一个最直接的方法就是在platform/BSP/drivers 下新建一个目录,然后在drivers目录中的dirs文件中加入以你刚新建的目录名。

       在刚新建的目录下,新建你的C源代码文件,在其中实现上面所述的函数,及其功能。新建名称分别为sources, makefile, mydev.def的文件。其内容如下:

       makefile: 只需要这样一行

!INCLUDE $(_MAKEENVROOT)/makefile.def


mydriver.def文件定义需要输出的函数,这些函数能够被其它代码用动态加载的方法调用。格式:


LIBRARY       MyDev(这个字符串要和将要生成的动态库的文件名一样)


EXPORTS

       XXX_Init

       XXX_Deinit

       XXX_Open

       XXX_Close

       XXX_PowerOff

       XXX_Power_Down

       XXX_IOControl

Sources:这个文件很重要,内容也多,最基本的一个文件该有如下内容。


TARGETNAME= MyDev(指定要生成的动态库的名称)

TARGETTYPE=DYNLINK(指定要生成的是一个动态库)

(下面两项指定需要与哪些动态库链接,一般要第一项就足够了)

TARGETLIBS=$(_COMMONSDKROOT)/lib/$(_CPUINDPATH)/coredll.lib

SOURCELIBS= $(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/ceddk.lib /


DEFFILE=MyDev.def (指定def文件)


DLLENTRY=DllEntry(指定动态库的入口函数)


SOURCES=(请在这写上你所有源文件的名字,它们将会被编译)

       好了,现在万事俱备,只剩编译啦。


3.4用一个Project文件来编译出驱动程序库文件
如果您在用CE5.0,那用一个Project来构造一个驱动程序将是一个不错的选择。即在新建一个Project时设置其类型为DLL,其它设置根据提示即可。并且可以将注册表设置放在Project所在文件夹。

3.5基本调试方法
一般驱动程序可以用DEBUG版来调试,也可以用输出调试信息的方法。我们一般用这两个函数输出调试信息:RETAILMSG和DEBUGMS,后者只能在DEBUG版中输出,而前者在RELEASE和DEBUG版中都可以输出,而且,可以在系统运行时刻根据Debug Zone选择让DEBUGMSG输出哪些调试信息。

驱动程序的调试一般可以分为以下几步:

1.看驱动程序的DllEntry是否被调用。如果这个函数被调用,说明驱动程序的文件已经在CE的image中,而且与注册表中设置的文件名相同。

2.看Init 函数是否被调用。如果它被调用,刚说明注册表设置正确。如果它没有被调用,一般是因为注册表中的Prefix设置与Init函数前面那三个字符不相同。或者def文件中没有定义Init函数。如果这个函数能够被调用,但驱动程序还是不能正确加载,请详细检查代码。

原文:http://blog.csdn.net/cy757/article/details/2785195

Windows CE.5.0系统、pxa270平台为例:

一.中断添加
1
.要使用中断必须先注册中断 
      D:\WINCE500\PLATFORM\MAINSTONEIII\src\inc\bsp_cfg.h
参照wince自带的中断注册即可。注册值不能大于SYSINTR_FIRMWARE+23
2
.将硬件中断变为系统中断
      D:\WINCE500\PLATFORM\MAINSTONEIII\src\kernel\oal\intr.c
其中函数OALIntrStaticTranslate是实现硬件中断转换为系统中断的函数。可以参照别的中断产生的方法写你自己的中断。

3.中断设置与初始化
      D:\WINCE500\PUBLIC\csp_pxa27x\oak\oal\common\intr\common\oem.c
中的OEMInterruptEnableOEMInterruptDisable函数,参照别的中断设置写自己的中断。 可以在OEMInitInterrupts添加对中断的初始化,也可以在后面的中断处理线程(IST)中添加。

二. 中断处理线程     
1
.创建线程CreateThread   
2
.创建事件CreateEvent    
3
.中断与事件绑定InterruptInitialize
4
while()
      1
)等待中断事件发生  WaitForSingleObject   
      2
)确认有一个来自操作系统的事件
      3
)在尽可能短的时间内处理中断
      4
)调用InterruptDone

---------------------------------------------------------------------------------------------
DWORD   WINAPI   ThreadIST( LPVOID lpvParam )
{

    DWORD   dwStatus;
    //
检查运行标志
    while( g_fRun )
    {
        dwStatus   = WaitForSingleObject(g_hevInterrupt, INFINITE);
        if( dwStatus == WAIT_OBJECT_0 )
        {
            //
在此处理中断
            //
            g_dwInterruptCount ++;
            //
完成中断
            //
            InterruptDone( g_dwSysInt );
        }
    }
    return 0;
}

---------------------------------------------------------------------------------------------三. 驱动文件DLLPlatform Builder 新建一个Dll工程,在程序中添加流接口的一些标准函数:
      XXX.Init
() 
      XXX_Deinit
()
      XXX_IOControl
()
      XXX_Open
()
      XXX_Close
()
      XXX_PowerDown
()
      XXX_PowerUp
()
      XXX_Read
()
      XXX_Seek
()
      XXX_Write
()

XXX.Init里完成初始化,包括IST的创建等,此函数由系统自动调用。其它函数根据实际需要添加内容。

然后添加一个.def文件用来配置dll所要导出的函数。

  LIBRARY dllname
            EXPORTS
            XXX_Init
            XXX_Deinit
            XXX_IOControl
            XXX_Open 
            XXX_Close
            XXX_PowerDown
            XXX_PowerUp
            XXX_Read
            XXX_Seek
            XXX_Write

      最后将它们编译成DLL文件。

四. 添加驱动到系统 将驱动dll拷贝D:\WINCE500\PBWorkspaces\PXA270\RelDir\MAINSTONEIII_ARMV4I_Release目录下。然后在D:\WINCE500\PLATFORM\MAINSTONEIII\files\Preject.bib中添加:
      MODULES
      ;  Name            Path                        Memory Type
      ;  --------------  ---------------------------------------------  -----------
      dllname.dll $(_FLATRELEASEDIR)\dllname.dll  NK SH

      其主要功能是在make image的时候把dllname.dll文件添加到系统内核中去。 接下来还要在projiect.reg中添加:
      [HKEY_LOCAL_MACHINE\Drivers\BuiltIn\dllname]
       "Prefix"="XXX"
       "Dll"="dllname.dll"
使系统在启动时加载驱动。当然也可以直接建立一个bib文件和reg文件,只要将它们放到platformrelease目录下就行了。 上述也可以通过编写cec文件来实现。

打开驱动程序GenDriver的示例代码如下:

HWND hWnd = hMain;

    DWORD dwwritten;

   int count;

   int ret;

   //加载驱动

    HANDLE HandleDriver = INVALID_HANDLE_VALUE; //驱动程序句柄

    HANDLE hFile = INVALID_HANDLE_VALUE; //设备文件操作句柄

    HKEY hDrvKey = NULL; //注册表操作句柄

    HandleDriver = ActivateDeviceEx(L"Drivers\\BuiltIn\\GEN", NULL, 0, NULL);   

   if (HandleDriver == INVALID_HANDLE_VALUE)

        Add2List(hWnd,TEXT("加载SimpleDriver失败."));

   else

        Add2List(hWnd,TEXT("加载SimpleDriver成功."));

    HANDLE hDrv = CreateFile(TEXT("GEN0:"),GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

//以打开文件的方式打开流式驱动程序

   if(INVALID_HANDLE_VALUE == hDrv)

    {

        OutputDebugString(L"Failed to open driver...\r\n");

        Add2List (hWnd, TEXT("Failed to open driver."));

       return 0;

    }

   else

    {

        OutputDebugString(L"succeed to open driver...\r\n");

        Add2List (hWnd, TEXT("File sent successfully."));

    }

与驱动有关的函数:

1:VirtualAlloc和VirtualCopy  VirtualFree

二者关系: 

      VirtualAlloc申请(预留)一块虚拟内存给寄存器(如:v_pIOPregs) 然后用 VirtualCopy直接把寄存器的物理地址映射到刚才申请(预留)的虚拟地址空间 。

    在Windows里内存管理是分为两部份,全局内存是系统管理的内存,因而所有进程都可以访问的内存,而每一个进程又有自己的内存空间,这就是虚拟内存空间了,而虚拟内存的空间比较大,当物理内存不足时,系统会把虚拟内存的数据保存到硬盘里,这样只要硬盘的空间足够大,每个进程就可以使用3G的内存。虚拟内存分配可以作为程序里分配内存的主要方式,比如大量的数据缓冲区,动态分配内存的空间。使用VirtualAlloc函数来分配内存的速度要比全局内存要快。
函数 VirtualAlloc 声明如下:
 LPVOID VirtualAlloc(     LPVOID lpAddress,  // 分配虚拟地址的起始指针     DWORD dwSize,     // 大小,以字节为单位     DWORD flAllocationType, // 类型, flAllocationType 是分配内存的类型。 设为MEM_RESERVE  
  DWORD flProtect    //   flProtect 是访问这块分配内存的权限。 存取保护,设为PAGE_NOACCESS  
);  
如果调用成功,返回分配的首地址, 调用失败,返回NULL 你可以通过GetLastError函数来获取错误信息
    VirtualAlloc用来在进程的虚拟地址空间中保留(reserve)或者提交(commit)页。在保留时以64KB为粒度,即保留空间以64K为单位。而提交虚拟地址时,则以页(典型大小为4KB)为单位。 在保留时以64KB为粒度,即保留空间以64K为单位。而提交虚拟地址时,则以页(典型大小为4KB)为单位  回复  
  BOOL VirtualCopy(     LPVOID lpvDest,         // 虚拟目的地址指针,接受VirtualAlloc的返回值    LPVOID lpvSrc,         // 源物理地址指针    DWORD cbSize,          // 大小必须与虚拟地址相同    DWORD fdwProtect  // 存取保护类型  );  
    这里需要注意的是 fdwProtect 参数。如果是驱动程序访问,需要设置为 PAGE_NOCACHE ,以访问无缓存段虚拟地址。如果映射的 物理地址 范围在 0x1FFFFFFF 之上,必须使用 PAGE_PHYSICAL , 此时必须把 lpvSrc 右移八位,实现地址对齐。 (这是由内核中 VirtualCopy 的实现决定的,在那个函数中会判断如果是 PAGE_PHYSICAL 就将 PHYSADDR 左移 8 位移回来,源代码private/winceos/coreos/nk/kernel 目录下的 virtmem.c中的DoVirtualCopy ) 
BOOL VirtualFree(

  );

说明:

该函数的功能是取消或者释放调用进程的虚地址空间页的一个区域,如果想释放一块调用VirtualAllocEx函数分配的内存(包括虚拟内存),就使用VirtualFreeEx函数.

参数说明:

LPVOID lpAddress, 要释放的页的区域的地址,如果dwFreeType指定为MEM_RELEASE且这个区域是被保留的话,那么这个地址就要指定为分配区域的首地址

  SIZE_T dwSize, 要释放页的大小,如果dwFreeType类型中包含了MEN_RELEASE(应该是MEM_RELEASE),则dwSize必须为0

  DWORD dwFreeType 类型说明

  MEM_DECOMMIT 取消VirtualAlloc提交的页

  MEM_RELEASE 释放指定页,如果指定了这个类型而dwSize为设置为0,则函数会调用失败

返回值:

如果调用成功,返回一个非0值
调用失败,返回0

Wince6下的 SH 和SHK
platform.bib中的 SH 和 SHK的区别
S System file
H Hidden file
K Module needs to be prepared for execution in the kernel address space (to map
the address).
SH文件时系统隐藏文件,SHK是系统内核隐藏文件。加上K标志后,就是该文件被加载到系统的内核地址空间中。
wince5没这一说
那是因为WINCE5的设计思想是并不把驱动作为内核的一部分,而WINCE6.0现在是把很多驱动都放到内核中了,而一些驱动(比如输入法这些应用方面的驱动)就没有放到内核中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值