1 概述
Windows CE是Microsoft公司专门针对嵌入式产品领域开发的嵌入式操作系统。该系统是一种紧凑、高效、可伸缩的32位操作系统,主要面向各种嵌入式系统和产品。它的模块化设计使嵌入式系统和应用程序开发者能够方便地加以定制,以适应一系列产品(例如消费类电子设备、专用工业控制器和嵌入式通信设备等)的需要。GPIO是端口的扩展器,当微控制器或者芯片没有足够的I/O端口,或者当系统需要用远端串行通信端口时,GPIO能够提供额外的控制和监视功能[1]。用户可以通过GPIO与硬件工作,例如点亮LED、控制蜂鸣器、控制LCD显示和键盘的输入等。所以GPIO的应用极为广泛,但是这些都需要驱动的支持。
2 GPIO流驱动原理
Windows CE的所有驱动程序都可以通过流接口驱动来实现,GPIO驱动程序开发也通过流接口驱动来实现。流接口驱动是最基本的一种驱动结构,它的接口是一组固定的流接口函数。流接口驱动可以把外设抽象成一个文件,过程是:应用程序调用操作系统的API函数,操作系统通过驱动接口调用驱动程序的硬件操作函数来完成对硬件的操作。
流接口函数包括XXX_Init、XXX_Deinit、XXX_Open、XXX_Close、XXX_Read、XXX_Write、XXX_PowerUp、XXX_PowerDown、XXX_IOControl、XXX_Seek、XXX_PreClose、XXX_PreDeinit。其中,XXX表示设备名的前缀,它非常重要,因为设备管理器在注册表中通过前缀来识别设备。流接口驱动是动态链接库,由设备管理程序的特殊应用程序加载、管理和卸载。在实际的开发中,用设备的前缀名来代替XXX。例如:GPIO设备的前缀名可以是GIO,相应地要实现的DLL接口为GIO_Init 、GIO_Read 、GIO_IOControl等。其中,XXX_Init用于初始化必要的资源、内存映射和IST(中断服务线程)。XXX_IOControl为设备的特定操作提供扩展接口,对于大多数的驱动,GPIO主要操作都在这里,它会被应用层的API函数调用来获得或者设置状态。应用程序通过调用XXX_IOControl函数并传入不同的操作码,实现GPIO的各种功能。而在应用程序里对应的文件API函数有ActivateDeviceEx、RegisterDevice、CreateFile、DeviceIoControl、 ReadFile、 WriteFile、CloseHandle、SetFilePointer等。
流接口驱动既可以是不分层的架构,也可以是分层架构[2]。同时,流接口驱动既可以管理内建设备,也可以管理安装设备;既可以在设备启动时加载,也可以在需要时动态加载。流接口驱动的架构如图1所示。
图1 流接口驱动的架构
3 GPIO驱动开发过程
Samsung公司基于ARM11内核的SC36410处理器包含了17个GP端口,从GPA到GPQ共187针。这些I/O大部分是可以复用的,可以作为输入口、输出口和特殊功能口,不同的功能通过各自的配置控制寄存器GPxCON选择来实现。配置好相应的控制寄存器后就可以选择相应数据寄存器GPxDAT进行读写数据。GPxDAT用来选择是否使用内部上拉电阻,当引脚被设置为输入时,读此寄存器可知引脚的状态是高电平还是低电平;当引脚被设置为输出时,写此寄存器可令该引脚为高电平或者低电平。以SC36410的GPM端口为例来介绍GPIO驱动的开发。通过查阅SC36410手册看到GPMCON的地址为0x7F008820,GPMDAT的地址为0x7F008824,GPMPUD的地址为0x7F008828。GPM端口包括6针(GPM[5:0]),每针的输入/输出由GPM0到GPM5寄存器分别确定。控制寄存器GPM0设置为0000时,表明GPM[0]引脚为输入脚;控制寄存器GPM0设置为0001时,表明GPM[0]引脚为输出脚。其他引脚操作完全类似。
3.1 地址映射
Windows CE有虚拟地址和物理地址之分,程序访问的地址都是虚拟地址。要访问硬件的物理地址,驱动程序必须将设备的物理地址映射到IST正在运行的地址空间,完成物理地址到虚拟地址的映射[3]。函数VirtualAlloc、VirtualCopy和VirtualFree是Windows CE的内核函数,具有同样功能的CEDDK函数MmMapIoSpace和UnMmMapIoSpace也可以完成地址映射。添加如下语句:static volatile S3C6410_GPIO_REG *v_pIOPregs
经过地址映射后,结构体变量指针v_pIOPregs就可以访问GPM的寄存器了。
3.2 编写驱动实现代码
Windows CE里设备驱动程序本质上是一些动态链接库(DLL),它们向内核提供一些入口函数,这样设备管理器就可以通过这些函数与具体的硬件设备进行通信。GPIO驱动的入口函数为DllMain。DllMain是一个可选的DLL入口方式,在进程和线程初始化或终止时以及调用LoadLibrary或FreeLibrary之后被系统调用,DllMain函数如下所示:
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
Windows CE加载设备驱动时,产生DLL_PROCESS_ATTACH调用DllMain,初始化设备,分配所需资源,并且开启ISR和IST;当卸载设备驱动时,产生DLL_PROCESS_DETACH,调用DllMain,关闭设备并释放它占用的各种资源[4]。
加载设备驱动的具体步骤如下:
① 新建GPM_Driver.cpp文件,添加一些头文件和完善接口函数的内容:在GPM_Init()函数中完成虚拟地址映射,在GPM_Deinit ()中释放虚拟地址空间和各种资源。代码如下:
DWORD GPM_Init(LPCTSTR pContext,LPCVOID lpvBusContext) {
v_pIOPregs=(volatile S3C6410_GPIO_REG*)DrvLib_MapIoSpace(S3C6410_BASE_REG_PA_GPIO,sizeof(S3C6410_GPIO_REG),FALSE);
return 1;
}
BOOL GPM _Deinit(DWORD hDeviceContext) {
VirtualFree((PVOID)v_pIOPregs,0,MEM_RELEASE);
v_pIOPregs=NULL;
return TRUE;
}
在GPM_IOControl ()函数中实现了GPIO的大部分操作,按照SC36410手册的说明和要求使I/O控制代码识别要完成的操作。GPMCON控制寄存器的输入/输出配置,GPMDAT负责数据寄存器的读写,部分代码如下:
switch (dwIoControlCode) {
case IOCTL_LED_LED1_OPEN:
v_pIOPregs>GPMCON&=(~(0x0f<<4));
v_pIOPregs>GPMCON|=0x1<<4;
v_pIOPregs>GPMDAT|=(0x01<<1);
break;
case IOCTL_LED_LED1_CLOSE:
v_pIOPregs>GPMCON&=(~(0x0f<<4));
v_pIOPregs>GPMCON|=0x1<<4;
v_pIOPregs>GPMDAT&=~(0x01<<1);
break;
}
② 创建GPM_Driver.DEF文件,提供驱动接口。
3.3 驱动程序配置[5]
配置驱动程序的具体步骤如下:
① 为使Windows CE生成内核映像时自动将GPM_Driver.dll加入到系统内核映像,在platform.bib文件中添加如下内容:
GPM_Driver.dll $(_FLATRELEASEDIR)\\GPM_Driver.dll NK SHK
② 在platform.reg文件中添加如下内容,当Windows CE启动时,就会自动加载该驱动了。
[HKEY_LOCAL_MACHINE\\Drivers\\BuiltIn\\GPM_Driver]
"Prefix"="GPM"
"Dll"="GPM_Driver.dll"
"Order"=dword:30
"Index"=dword:1
③ 新建Makefile文件,并添加内容如下:
!INCLUDE $(_MAKEENVROOT)\makefile.def
④ 配置sources文件。把其他驱动程序的sources文件复制过来,在文件某些位置替换为GPM_Driver的内容。
⑤ 在X:\\WINCE600\\PLATFORM\\SMDK6410\\SRC\\DRIVERS的dirs文件下,X代表Windows CE的安装盘符,按照文件格式添加\\GPM_Driver。
⑥ 对驱动程序进行编译,生成的GPM_Driver.dll会自动加入到操作系统内核映像,最终生成NK.bin文件,下载到目标板后即可被顶层的应用程序API函数调用,实现GPIO的输入/输出等功能。这样,GPM便可以实现控制LED发光、蜂鸣器发出声响等功能了。
结语
Windows CE已经成为广泛应用的嵌入式操作系统,本文以SC36410的GPM端口为例,主要介绍了Windows CE下GPIO驱动程序的开发方法,分析了GPIO流接口驱动程序的开发原理,主要包括建立DLL项目、编写一些输入/输出函数和寄存器,以及配置驱动程序等,对Windows CE下GPIO驱动编写具有一定的参考价值。