WINCE入门的第一个驱动程序

本文详细介绍了在Windows CE系统中开发一个简单驱动的过程,包括Makefile、source文件、SimpleDriver.def文件等内容,以及如何配置注册表和platform.bib文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WINCE的第一个驱动程序是什么呢?有的说是GPIO,有的说是LED,也有的说是SimpleDriver,我个人也是同意后者的。

因此这里就以SimpleDriver为例讲解如何开始自己的第一个驱动程序。

首先,要认识到一个驱动有哪些开发模型。这个相信大家都知道,比如本机驱动,分层驱动,流接口驱动等等,这些都闭着眼睛都知道,可是真正的实现呢?具体什么情况用什么模型呢?

关于分层驱动,介绍饿时最多的,尤其是网上和教科书还有一些所谓的论文,真正在产品中用这种方法的又有几个呢?不是分层驱动不好,只是要看具体情况,这个主要看个人喜好,个人能力还有就是在PUBLIC目录下是否有驱动的MDD。

因此,对于一个外设来说,尤其是一片子来说,个人认为单体驱动和流接口驱动用的更实际点,尤其涉及数据的传输,个人认为流接口驱动更容易一些。以上仅代表个人观点,欢迎拍砖。

其次,对于一个驱动我们要认清楚里面到底有哪些文件,他们的作用又是干什么的呢?下面以SimpleDriver为例,进行第一个简单流接口驱动的讲解。

 1.Makefile文件

这里的Makefile文件请不要和其他环境下(GCC,VS2005)的Makefile文件弄混,它是BSP里面的Makefile。Windows CE中的Makefile比较特别,它包含对所有项目都通用的配置信息。

其内容很简单,只有一句话:

!INCLUDE $(_MAKEENVROOT)\makefile.def

当build.exe查找dirs和source文件之后,它就会设置一个内部环境变量。这个环境变量可以被Nmake.exe传递给编译器、连接器或其他工具。

2.source文件

source也是一个文本文件,它为子目录中的源代码设置了不少宏定义。 

复制代码

 
TARGETNAME
=SimpleDriver
RELEASETYPE
=PLATFORM
TARGETTYPE
=DYNLINK

TARGETLIBS
=$(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    
   
DEFFILE
=$(TARGETNAME).def
DLLENTRY
=DllEntry

SOURCES
=SimpleDriver.c
复制代码

以上是simpledriver里面source的内容,具体解释如下:

 TARGETNAME=SimpleDriver; 指定生成最终生成的.exe,.lib,.dll文件的名称,这里是SimpleDriver.dll

 RELEASETYPE=PLATFORM;它设置两种旗标:RELEASEDIR和RELEASELIBDIR,用于指定编译生成二进制和库文件存放的目录。默认情况下,为目标生成的二进制和库文件存放在

                                       ;目录%_PROJECTROOT%\oak下,这里存放在D:\WINCE600\PLATFORM\Mini2440\target\ARMV4I\retail目录下。

 TARGETTYPE=DYNLINK;这个宏定义指定构建文件的最终类型,可以把TARGETTYPE类型设置为一下四种类型中的任意一种。

1)MANAGED_EXE;

2) MANAGED_DLL;

3) MANAGED_WINEXE;

4) MANAGED_MODULE; 

这里设置的最终类型为dll。

 TARGETLIBS=$(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    ;它指定了额外的库文件(.lib)和目标文件(.Obj)链接为目标可执行文件(.exe或.dll).

                                                                                                     ;这里将 coredll.lib 链接生成最终的目标文件SimpleDriver.dll

 DEFFILE=$(TARGETNAME).def;它指定模块定义文件(.def)的名称,这里指定了模块定义文件的名称为SimpleDriver.def

 DLLENTRY=DllEntry;它为一个DLL文件指定DLL的入口函数,此时TARGETTYPE被设置为DYNLINK。如果 DLLENTRY对应的值没有被设置时,那么_DllMainCRTStartUp是DLL的C程序

                              ;运行入口点。这里 DLLENTRY的入口函数被指定为DllEntry,因而,上面的 TARGETTYPE被设置成DYNLINK;

 SOURCES=SimpleDriver.c;它包含编译过程的文件列表,这些列表中包含汇编文件和源文件,这些文件的类型有.cxx,.cpp,.c,.asm,.s,.src,.rc,.obj,.ire,.res,.odl,.tlb,.i,.cs,.resx等。

                                    ;这些文件编译之后可能是静态库文件(.lib),也有可能是动态库文件(.dll)。这里编译过程中需要用到的源文件有SimpleDriver.c,

                                   ;编译之后的生成SimpleDriver.dll的动态链接库文件。

 3.SimpleDriver.def文件

.def文件定义了DLL的导出函数列表。
 这里包括的内容如下:

复制代码
LIBRARY SimpleDriver

EXPORTS 
    SPL_Init
    SPL_Deinit
    SPL_Open
    SPL_Close
    SPL_Read
    SPL_Write
    SPL_Seek
    SPL_IOControl
    SPL_PowerDown
    SPL_PowerUp
复制代码

主要是针对当前流接口函数,将相应的函数导出。

4. SimpleDriver.h

这个而就不用多介绍了吧,主要是一些头文件的声明,定义等等 

 5.SimpleDriver.c

下面给出基本的代码,有些函数给出了空定义,方便以后实现,同时方便理解。

复制代码

#include 
<windows.h>
#include 
<types.h>


static BYTE g_Tmp = 0;                        /* 暂存数据变量 */
static DWORD g_OpenCount = 0;                /* 驱动打开计数器 */ 

/*******************************************************************************************
函数名称: DllEntry
描    述: 驱动程序动态库入口
输入参数:     
输出参数:
返    回: 
******************************************************************************************
*/
BOOL WINAPI DllEntry(HANDLE hInstDll, DWORD dwReason, LPVOID lpvReserved)
{
    
switch ( dwReason ) 
    {
        
case DLL_PROCESS_ATTACH:
            RETAILMSG(
1, (TEXT("SPL: DLL_PROCESS_ATTACH.\r\n")));    /* 提示动态库加载 */
            DisableThreadLibraryCalls((HMODULE) hInstDll);            
            
break;

        
case DLL_PROCESS_DETACH:
            RETAILMSG(
1, (TEXT("SPL: DLL_PROCESS_DETACH.\r\n")));    /* 提示动态库卸载 */
            
break;
    }
    
    
return (TRUE);
}


/*******************************************************************************************
函数名称: SPL_Init
描    述: 驱动程序初始化函数
输入参数: DWORD dwContext: 设备管理器传递给本驱动的参数, 通常为流接口驱动在注册表内的位置     
输出参数: 无
返    回: 驱动程序句柄
******************************************************************************************
*/
DWORD SPL_Init(DWORD dwContext)
{
    RETAILMSG(
1, (TEXT("::: SPL_Init.\r\n")));        /* 提示驱动加载 */

    g_Tmp 
= 0;                                        /* 初始化全局变量的值 */
    g_OpenCount 
= 0

    
return 1;                                        /* 返回一个不为零的数表示成功 */
}


/*******************************************************************************************
函数名称: SPL_Deinit
描    述: 驱动程序卸载函数
输入参数: DWORD dwContext: 驱动程序句柄
输出参数: 无
返    回: FALSE: 失败    TRUE: 成功
******************************************************************************************
*/
BOOL SPL_Deinit(DWORD dwContext)
{
    RETAILMSG(
1, (TEXT("::: SPL_Deinit.\r\n")));    /* 提示驱动卸载 */
    
    g_Tmp 
= 0;                                        /* 恢复全局变量的值 */
    g_OpenCount 
= 0

    
return TRUE;
}


/*******************************************************************************************
函数名称: SPL_Open
描    述: 打开驱动程序
输入参数: DWORD hDeviceContext: 设备驱动程序引用实例句柄
          DWORD AccessCode    : 访问请求代码,是读和写的组合
          DWORD ShareMode      : 共享模式  
输出参数:
返    回: 驱动程序引用事例句柄
******************************************************************************************
*/
DWORD SPL_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)
{
    RETAILMSG(
1, (TEXT("::: SPL_Open.\r\n")));         /* 提示驱动打开 */
    
    
// 不允许多个应用程序打开本驱动
    if (g_OpenCount != 0)
    {
        RETAILMSG(
1, (TEXT("SPL Open failed.\r\n")));/* 提示驱动打开失败 */
        
return 0;
    }
    g_OpenCount
++;                                     /* 驱动打开计数器加1 */

    
return g_OpenCount;                                 /* 必须返回一个不为空的句柄 */
}


/*******************************************************************************************
函数名称: SPL_Close
描    述: 驱动程序关闭函数
输入参数: DWORD hOpenContext:驱动程序引用事例句柄
输出参数: 无
返    回: FALSE: 失败    TRUE: 成功 
******************************************************************************************
*/
BOOL SPL_Close(DWORD hOpenContext)
{
    RETAILMSG(
1, (TEXT("::: SPL_Close.\r\n")));             /* 提示驱动关闭 */
    
    
if (g_OpenCount != 0
        g_OpenCount
--;                                     /* 驱动打开计数减1 */
        
    
return TRUE;
}


/*******************************************************************************************
函数名称: SPL_IOControl
描    述: 驱动程序 I/O 请求
输入参数: 
输出参数:
返    回: TRUE: 成功   FALSE: 失败
******************************************************************************************
*/
BOOL SPL_IOControl(DWORD hOpenContext,
                   DWORD dwCode,
                   PBYTE pBufIn,
                   DWORD dwLenIn,
                   PBYTE pBufOut,
                   DWORD dwLenOut,
                   PDWORD pdwActualOut)
{
    RETAILMSG(
1, (TEXT("::: SPL_IOControl.\r\n")));         /* 提示I/O请求函数执行 */
    
    
return TRUE;
}


/*******************************************************************************************
函数名称: SPL_Read
描    述: 从本驱动读取数据
输入参数: DWORD hOpenContext: 驱动程序引用事例句柄
          DWORD Count        : 要读的字节数
输出参数: LPVOID pBuffer    : 接收缓冲区
返    回: 实际读到的字节数
******************************************************************************************
*/
DWORD SPL_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count)
{
    uchar 
*pReadBuffer;
    
    RETAILMSG(
1, (TEXT("::: SPL_Read.\r\n")));                  /* 提示执行读函数 */
    
if ((pBuffer == NULL) || (Count <= 0))
    {                                                          
/* 读函数入口参数错误 */
        RETAILMSG(
1, (TEXT("::: SPL_Read() parameter is error.\r\n")));            
        
return 0;
    }
    
    
// 映射地址空间
    pReadBuffer = MapPtrToProcess(pBuffer, GetCallerProcess());
    
*pReadBuffer = g_Tmp;                                      /* 返回数据 */
    
    
return 1;                                                /* 返回读取的字节数 */
}


/*******************************************************************************************
函数名称: SPL_Write
描    述: 向本驱动写入数据
输入参数: DWORD hOpenContext: 驱动程序引用事例句柄
          LPVOID pBuffer    : 发送缓冲区
          DWORD Count        : 要写入的字节数
输出参数: 无
返    回: 实际写入的字节数
******************************************************************************************
*/
DWORD SPL_Write(DWORD hOpenContext, LPCVOID pBuffer, DWORD Count)
{
    uchar 
*pWriteBuffer;

    RETAILMSG(
1, (TEXT("::: SPL_Write.\r\n")));                  /* 提示执行写函数 */
    
if ((pBuffer == NULL) || (Count <= 0))
    {                                                          
/* 写函数入口参数错误 */
        RETAILMSG(
1, (TEXT("::: SPL_Write() parameter is error.\r\n")));            
        
return 0;
    }
    
    
// 获取应用程序地址空间数据指针
    pWriteBuffer = MapPtrToProcess((LPVOID)pBuffer, GetCallerProcess());
    g_Tmp 
= *pWriteBuffer;                                    /* 保存数据 */

    
return 1;
}


/*******************************************************************************************
函数名称: SPL_Seek
描    述: 对设备的数据指针进行操作,本驱动不支持该函数
输入参数: 
输出参数:
返    回:
******************************************************************************************
*/
DWORD SPL_Seek(DWORD hOpenContext, 
long Amount, DWORD Type)
{
    RETAILMSG(
1, (TEXT("::: SPL_Seek.\r\n")));                  /* 提示执行本函数 */

    
return 0;
}


/*******************************************************************************************
函数名称: SPL_PowerUp
描    述: 电源上电驱动处理函数
输入参数: 
输出参数:
返    回: 无
******************************************************************************************
*/
void SPL_PowerUp(void)
{
    RETAILMSG(
1, (TEXT("::: SPL_PowerUp.\r\n")));             /* 提示执行本函数 */
}


/*******************************************************************************************
函数名称: SPL_PowerDown
描    述: 电源下电驱动处理函数
输入参数: 
输出参数:
返    回: 无
******************************************************************************************
*/
void SPL_PowerDown(void)
{
    RETAILMSG(
1, (TEXT("::: SPL_PowerDown.\r\n")));             /* 提示执行本函数 */
}
复制代码

OK!

到这里点击编译就可以了。这个新驱动程序的动态链接库将会被编译进内核。当调用CreateFile()函数(第一个参数是SPL)时就搜索注册表,设备管理器可以找到这个驱动,驱动的入口点可以用到其他程序。


上文介绍了WINCE一个简单驱动的编写,这节我们接着介绍一些配置文件的编写。涉及到的文件有.platform.bib,platform.reg,dirs,source,SimpleDriver.def,其中后面两个文件在前文已有介绍,这里就以前两个配置文件为主。

首先,从注册表说起,先简单的介绍一下注册表:Makeimg.exe使用.reg文件来为CE镜像建立注册表并添加默认的键值。也就是说,在.reg中写入的注册表的键值会被默认地放入CE镜像的初始化注册表中。其中Platform.reg定义了目标设备硬件的注册表设置,Project.reg定义基于Windows CE项目工程的注册表设置。注册表键值的类型如下:

1.       REG_SZ表示一个字符串类型,如reg_sz:”my string”

2.       REG_DWORD表示一个双字节类型,如dword:12345678(十六进制数)

3.       REG_MULTI_SZ表示多字符串类型,如multi_sz:“my string,my string”。

4.       REG_BINARY 二进制类型。

在实际应用中可以使用IF/ENDIF关键字来引入一个注册表设置块,通过设置一个环境变量或一个特殊的值来达到这个目的。

为了包含一个注册表设置块,当一个环境变量没有被设置或者没有等于一个特定的值的时候,引入的注册表设置块的行尾应使用一个空格和“!”。

这里比较好找,一目了然,在目录D:\WINCE600\PLATFORM\SMDKXXXX\Files\platform.reg下添加:

 

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SPL]
   
"Prefix"="SPL"
   
"Dll"="SimplDriver.Dll"
   
"FriendlyName"="Simple Driver"
   
"Order"=dword:0
   
"Index"=dword:0

这样在WINCE启动时,就自动加载该驱动了。

接着我们介绍platform.bib文件,关于bib文件,这里做一个简要的介绍:二进制镜像文件构建文件(.bib)定义了哪个模块或者文件被包含到运行时的CE镜像中。在编译期间,makeimg.exe使若干个.bib文件合并成ce.bib文件,然后,romimage.exe使用ce.bib决定哪些文件应该被打包进运行时CE镜像中,它同时还使用ce.bib来决定如何加载模块和文件到CE镜像(下载到目标设备的CE镜像)所在的内存中。

按照功能划分,.bib文件可以分为如下几种类型。

(1)                 Platform.bibPlatform.bib位于目录D:\WINCE600\PLATFORM\SMDK6410\Files,它包含硬件平台相关的模块和文件,如目标设备的驱动文件。这些模块和文件是CE镜像的入口,比如.exe文件,如波形音频文件(.wav)等。

(2)                 Project.bibProject.bib weiyumulu D:\WINCE600\PUBLIC\CEBASE\OAK\FILES,如果我们新建一个工程项目(OSDesign1)则这个工程位于目录:D:\WINCE600\OSDesigns\OSDesign1\OSDesign1\Wince600\SMDK6410_ARMV4I\OAK\files下。Project.bib文件定义与创建CE镜像的工程相关的模块,如果在OSDesign1中创建了一个自己的模块或者应用程序,那么就要把它们添加到Project.bib文件中的MODILES部分。

(3)                 Common.bibCommon.bib位于目录D:\WINCE600\PUBLIC\COMMON\OAK\FILES下,它定义了CE镜像文件包含的通用显示驱动和核心系统模块。

(4)                 Config.bibConfig.bib位于目录D:\WINCE600\PLATFORM\SMDK6410\Files下,它定义了ROMRAM的配置信息。它同样包含了CE镜像文件的MEMORYCONFIG部分。Config.bibMEMORY部分定义了运行时CE镜像内存分配表,指定了名称、地址、大小和MEMORY区域的类型。

.bib文件可以被分为四个部分,分别是MEMORYCONFIGMODULESFILES。下面将分别说明这四个部分各自的含义。

(1)       MEMORY。定义可用的物理内存,包括起始地址、大小和内存类型。

(2)       CONFIG。定义romimage.exe输出的配置选项。默认情况下,这个区域是在config.bib文件中。不过,也并不是.bib文件中必须要包含CONFIG部分。

(3)       FILES。指定放在CE镜像中的文件列表。

(4)       指定放在CE镜像总的模块列表,包括EXEDLL文件,与FILES的区别是放在MODULES中的文件通常是代码文件,并且构建系统时不会压缩这些文件。

这里我们主要介绍Modules部分。

Modules部分指定了哪些基于Windows CE的模块包含到CE镜像中,以及如何给加载到config.bib文件中的MEMORY部分建立内存表。这个部分可以包含200个模块,这些模块有源代码和数据两个部分组成。

MODULES的语法格式如下:

;   Name    Path      Memory block    Section override    Memory Type

;   ---------   --------     ----------------       -----------        -------------

各参数之间用空格分隔。

NAME:这个参数指定了MODULES模块的名称。通常情况下,它就像被路径引用的文件名称一样。

PATH:指定要打包进CE镜像的MODULES模块的完整路径。

MEMORY BLOCK:这个参数指定romimage.exe加载目标模块到内存区域的ramimage部分。这个内存位于config.bib文件中memory部分指定的某一段内存。

SECTION OVERRIDE:这个参数的设置时可选的,它可以为modulesfiles或者空。如果设置了这个参数,那么构建系统就会根据它来决定这一项是modules还是files

TYPE:这个参数指定文件的类型,主要有以下几种类型,在实际的使用中可以选用其中的一种或者多种组合。

1.       S:定义一个系统文件

2.       H:定义一个隐藏文件

3.       R:压缩资源,只应用于MODULES部分

4.       C:如果应用于一个模块,则压缩全部内容

5.       D:运行时不允许调试。

6.       N:标记一个模块为不可信任的,只应用MODULES部分。

7.       K:指定romimage.exe必须修正模块到一个内核地址。在这个过程中,romimage.exe分配一个固定的虚拟地址给DLL,设置了此标志的模块只可以被loadKernelLibrary()函数加载。

8.       P:指定romimage.exe禁止在头文件中检查指定的CPU类型。这个旗标只用于资源dll,可以在一种CPU伤编译,在不同CPU上使用。

9.       M:表示对此页禁止按序调页。

10.   U:表示不压缩此文件。

在目录D:\WINCE600\PLATFORM\SMDKXXXX\Files\platform.bib文件中有如下定义:

;   Name                          Path                                    Memory Type

;   --------------         ----------------------------------              -----------

;-------------

;--------------Simple Driver (caichang714@hotmail.com) ------------------------------------------------------

       simpledriver.dll                                   $(_FLATRELEASEDIR)\simpledriver.dll           NK                 SHK

; @CESYSGEN ENDIF CE_MODULES_DEVICE

;---------------------------------------------------------------------------------------

其中,_$(_FLATRELEASEDIR)\ 指的是D:\WINCE600\OSDesigns\SMDKXXXX\SMDKXXXX\RelDir

结合上面的说明,我们可以知道,上面的语句的意思是将编译生成的simpledriver.dll模块加载到CE运行时镜像NK中。它的文件属性是系统文件,隐藏文件和内核模块,由romimage.exe分配一个固定的虚拟地址给simpledriver.DLL

接着在D:\WINCE600\PLATFORM\SMDKXXXX\Src\Drivers目录下找到dirs文件,在里面添加:SimpleDriver\

 最后点击build或者在dos环境下build -c


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值