Windows CE嵌入式导航系统研究(内核相关)
1.1 嵌入式车载导航系统的软件设计流程
嵌入式车载系统软件主要包括系统内核、驱动程序、应用程序三部分。设计的过程当中,我们采用瀑布模型进行设计,首先制定Windows CE5.0系统内核,再次编写相关设备驱动,最后编写或移植应用程序。
1.2 Windows CE5.0系统内核制定
Windows CE5.0内核制定的过程,是针对特定的硬件平台和应用,选择和配置各种组件,开发自定义组件,修改系统的配置文件,甚至对某些源码进行修改的过程。所以我们需要知道各种文件的存放位置,以便于迅速准确的定位,缩短开发时间,同时需要熟悉各种组件的作用和功能,以便将它们添加到系统中完成相应的功能。接下来我们来展示Windows CE5.0目录组织、内核组件等方面的内容。
1.2.1 Windows CE5.0目录组织
目录 |
内容说明 |
CRC |
存放Platfrom Builder5.0安装时候用到的校验文件CRC.INI |
PBWorkspaces |
_PLATFORMROOT环境变量标识Platform目录存放所有的BSP,一般来说BSP的名字与开发板的名字一致。 |
OTHERS |
包含WINCE中一些模块和二进制库文件和代码。如果在OS Design中选择某些组件,那么这些二进制代码就会被包含到最终的操作系统映像中。 |
PLATFORM |
存放了和硬件平台相关的BSP和驱动代码和文件 |
PRIVATE |
_PRIVATEROOT环境变量标识Private目录存放WINCE操作系统私有源代码。WINCE核心模块代码都放在此目录下。 |
PUBLIC |
包含硬件无关的Windows CE相关文件目录 |
SDK |
存放构建系统用到的编译器与其他一些辅助工具,在构建系统用_SDKROOT环境变量标识SDK目录。在/SDK/BIN/I386下存放构建系统可能用到的工具。而4个子目录ARM,MIPS,SH和X86分别针对WINCE所支持的4个平台的C/C++语言与汇编语言的编译器。 |
关于更详细的介绍,可以参考Platform Builder5.0的帮助文档。接下来详细介绍相关重要目录。
目录 |
内容说明 |
CEPC |
在PC机上运行Windows CE所需的BSP |
GEODE |
x86结构的AMD GEODE CPU开发板的BSP |
MAINSTONEII |
Intel MainstoneII硬件平台开发板的BSP |
SMDK2410 |
三星2410 ARM结构CPU的开发板的BSP |
COMMON |
多个BSP用到的公用代码 |
EMULATOR |
运行Windows CE模拟器所需的BSP |
MC9328MX1 |
Motorola开发板的BSP |
目录 |
内容说明 |
SERVERS |
Windows CE服务的源代码 |
SHELL |
Windows CE Shell组件 |
TEST |
一些Windows CE的测试代码 |
WCESHELLFE |
空目录 |
WINCEOS |
Windows CE操作系统的部分代码 |
Public目录包含操作系统中与硬件无关的组件和文件,包括各种工具,驱动和所有平台公共的组件,如表 5所示;
目录 |
内容说明 |
CEBASE |
微软提供的一些设计模版,头文件,批处理文件,用来构建内核映像的时候使用 |
Common |
微软提供的与平台无关的通用模块,包括驱动程序,构建用的批处理与一些组件 |
DATASYNC |
在桌面Windows和Windows CE之间同步用的组件 |
DIRECTX |
DirectX相关的组件 |
GDIEX |
图像处理相关的组件,例如GIF,JPG等 |
IE |
IE浏览器相关的组件,有部分源代码。 |
NETCF |
.NET Compact Framework相关的组件 |
RDP |
远程桌面连接RDP相关的组件 |
SCRIPT |
Jscript和VBScript脚本引擎相关的组件 |
SERVERS |
网络相关的服务 |
SHELL |
Windows CE的Shell组件 |
SHELLSDK |
支持Pocket PC界面AygShell的库 |
SPEECH |
语音识别和朗读的SAPI组件 |
SQLCE |
SQL Server CE 2.0的二进制文件 |
VIEWERS |
微软的文件浏览器组件,包括PDF,Word,Excel等等的二进制文件,没有源代码 |
VOIP |
基于SIP标准的VoIP模块 |
WCEAPPSFE |
Windows CE的应用程序模块,包括WordPad,收件箱等 |
WCESHELLFE |
Windows CE的Shell应用模块:包括Dr Watson,任务管理器等 |
Public目录下的Common目录中含有非常多的内容。大致包含一些与平台无关的代码和工具,Common目录下有几个子目录值得我们关注[15]。
%_PUBLICROOT%/Common/OAK/Catalog目录
这个目录存放了与Platform Builder CEC相关的内容。Platform Builder中的CEC文件基本都存放在该目录下。
%_PUBLICROOT%/Common/OAK/Drivers目录
这个目录是所有的微软提供的外设驱动程序代码。代码是按照外设的种类存放的。在编写驱动程序的时候,这个目录的代码非常有参考价值。
%_PUBLICROOT%/Common/OAK/MISC目录
这个目录存放了在构建的时候用到的一系列批处理文件和其他工具。
%_PUBLICROOT%/Common/OAK/CSP目录
这个目录是CPU Support Package的存放目录,与某个CPU相关的通用代码都存放在该目录下。
Others目录包含Windows CE中一些模块的二进制库文件和代码。如果在PBWorkspaces中选择了某些组件,那么这些二进制代码就会被包含到最终的操作系统映像中,详细内容如表 6所示。
目录 |
内容说明 |
WCETK |
Windows CE Test Kit的客户端(运行在CE上)程序 |
ATL |
ATL的头文件,库文件和源代码 |
MFC |
MFC的头文件,库文件和源代码 |
PLATMAN |
Platform Manager的客户端(运行在CE上)程序 |
SAMPLES |
MFC和ATL的示例代码 |
SQLCE20 |
Windows CE上支持SQLCE的库文件 |
EDB |
Windows CE上支持EDB的库文件 |
1.2.2 Windows CE5.0内核组件
组件 |
内容说明 |
Applications-End User |
微软提供给用户的应用程序,如游戏、Word、Execl、PDF文件查看工具等 |
Applications and Services Development |
Windows CE5.0中可用来开发应用程序和服务的库和系统功能,如.NET Compact Framework2.0、C语言运行库、DCOM库 |
Communictions Services and NetWorking |
网络相关的特性,包括WAN、LAN、PAN上的一些协议的实现 |
Core OS Services |
操作系统的核心特性,包括电池驱动、电源管理、调试工具、消息队列、USB口支持、串口支持等 |
Devices Management |
设备管理特性,包括SNMP和设备管理客户端 |
File System and Data Store |
支持文件特性和数据存储选项,包括注册表、存储管理器等 |
Fonts |
各种可选的字体 |
Graphics and Multimedia technologies |
图形和多媒体支持,包括各种音频、视频组件 |
International |
国际化支持,包括各种语言输入法和MUI图形界面 |
Internet Client Services |
访问因特网组件,包括IE6、JS5.5和VBS5.5脚本等 |
Security |
安全性支持,各种用来认证、授权和加密的组件 |
Shell and User interface |
图形界面组件,例如各种风格的菜单和XP风格的皮肤 |
Voices over IP Phone Services |
VOIP相关组件,包括RTC协议的实现等 |
Windows CE Error Reporting |
Windows CE的错误报告组件 |
1.2.3 WinCE5.0内核制定一般流程
(1) 打开Platform Builder5.0软件,新建相关项目,例如我们将项目命名为GPS2440A,如下图解。
在这一步骤中,大家可以根据需要选择相应的项目模板,随后一直按照默认设计即可,最后完成创建步骤,进入Platform Builder5.0工作环境界面,如图 5.8所示。
(2) 项目配置:新建项目完成之后,在编译之前我们需要对项目进行配置,如下图解。
在弹出的“Platform Setting”页面中更改工程的属性。首先修改配置属性,将属性配置为All confirgurations,如图 5.10所示。
(3) 添加组件:根据需要选择需要添加的组件,下面以添加Cab 安装包支持组件为例子显示,如图 5.13所示。
图 5.14 添加SimSun & NSimSun (Subset 2_90)
图 5.15 添加GB18030 Data Converter
经过这四个步骤,在“/PBWorkspaces/GPS2440A/RelDir/TQ2440_ARMV4I_
Release”目录下可以找到NK.BIN、EBOOT.nb0和STEPLDR.nb1文件。如下将介绍如下将系统下在到系统板子当中。
1.2.4 WinCE5.0内核下载
(3) 将F_SEL设置为启动模型,启动终端,在sscom32.exe软件,始终按住“SPACE”键,出现如图 5.19所示,在选择U,通过DNW软件下载NK.BIN文件。
1.2.5 S3C2440A BootLoader结构
S3C2440A BootLoader的软件框架主要可以分为如下5个部分
(5) BootPart支持库:向BootLoader提供存储分区,底层读写,擦除,格式化等存储设备操作。
1.2.6 BootLoader中Startup函数
Startup函数首先有两条重要的地址定义,定义ram空间的物理基地址和页表的基地址,这是startup函数主要操作的物理地址空间,如程序清单 5‑1所示。
PHYBASE EQU 0x30000000 ; physical start
PTs EQU 0x30010000 ; 1st level page table address (PHYBASE + 0x10000)
; save room for interrupt vectors.
在进入入口函数以后首先通过对协处理器的操作来清空TLB、指令Cache和数据Cache如程序清单 5‑2所示。
程序清单 5‑2 清空TLB、指令Cache和数据Cache
mcr p15, 0, r0, c8, c7, 0 ; flush both TLB
mcr p15, 0, r0, c7, c5, 0 ; invalidate instruction cache
mcr p15, 0, r0, c7, c6, 0 ; invalidate data cache
然后向I/O端口F控制寄存器写入0x5555,使GPIO0到GPIO8全部设置为输出口,如程序清单 5‑3所示。
ldr r0, = WTCON ; disable watch dog
ldr r1, = 0xffffffff ; disable all interrupts
ldr r1, = 0x7fff ; disable all sub interrupt
mov r1, #0x0 ; set all interrupt as IRQ
ldr r1, = 0x7 ; 0x0 = 1:1:1, 0x1 = 1:1:2, 0x2 = 1:2:2, 0x3 = 1:2:4,
; ldr r1, = 0x5 ; 0x0 = 1:1:1, 0x1 = 1:1:2, 0x2 = 1:2:2, 0x3 = 1:2:4,
ldr r0, = MPLLCON ; Configure MPLL
ldr r0, = UPLLCON ; Fin=12MHz, Fout=48MHz
; ldr r1, = ((0x3c << 12) + (0x4 << 4) + 0x2) ; 16Mhz
ldr r1, = ((0x38 << 12) + (0x2 << 4) + 0x2) ; 12Mhz
add r0, pc, #MEMCTRLTAB - (. + 8)
ldr r1, = BWSCON ; BWSCON Address
add r2, r0, #52 ; End address of MEMCTRLTAB
EBOOT 8c038000 00040000 RAMIMAGE//指定的Eboot在Ram中的虚拟地址,
DCD 0x8C000000, 0x30000000, 32 ; 32 MB DRAM BANK 6
ldr r0, = 0x38000 ; offset into the RAM
add r0, r0, #PHYBASE ; add physical base
mov r1, r0 ; (r1) copy destination
ldr r2, =0x0 ; (r2) flash started at physical address 0
ldr r3, =0x10000 ; counter (0x40000/4)
完成拷贝之后就要开始运行BootLoader了,方法很简单,即将拷贝的物理地址赋值给pc。
接下重新运行BootLoader,首先获取内存映射表的地址和页表要存储的地址,如程序清单 5‑10所示;
20 add r11, pc, #g_oalAddressTable - (. + 8)
ldr r10, =PTs ; (r10) = 1st level page table
; (r10) = 1st level page table
; (r11) = ptr to MemoryMap array
add r10, r10, #0x2000 ; (r10) = ptr to 1st PTE for "unmapped space"
mov r0, #0x0E ; (r0) = PTE for 0: 1MB cachable bufferable
设置cachable位,每一个页表内容的高12(因为页表是以1MB为单位构建的)位是虚拟内存对应的物理内存段的起始地址,低20位用来存放相应虚拟及物理内存段的读写权限及可缓冲信息等属性信息。
orr r0, r0, #0x400 ; set kernel r/w permission
25 mov r1, r11 ; (r1) = ptr to MemoryMap array
30 ldr r2, [r1], #4 ; (r2) = virtual address to map Bank at
ldr r3, [r1], #4 ; (r3) = physical address to map from
ldr r4, [r1], #4 ; (r4) = num MB to map
and r2, r2, r5 ; VA needs 512MB, 1MB aligned.
and r3, r3, r5 ; PA needs 4GB, 1MB aligned.
接下来,开始构造uncachable页表,对应的虚拟地址均是cachable虚拟地址+20000000,物理地址不变,如程序清单 5‑15所示。
bic r0, r0, #0x0C ; clear cachable & bufferable bits in PTE
add r10, r10, #0x0800 ; (r10) = ptr to 1st PTE for "unmapped uncached space"
bne %b25 ; go setup PTEs for uncached space
sub r10, r10, #0x3000 ; (r10) = restore address of 1st level page table
; Setup mmu to map (VA == 0) to (PA == 0x30000000).
ldr r0, =PTs ; PTE entry for VA = 0
ldr r1, =0x3000040E ; uncache/unbuffer/rw, PA base == 0x30000000
add r0, r0, #0x0800 ; PTE entry for VA = 0x0200.0000 , uncached
ldr r1, =0x30000402 ; uncache/unbuffer/rw, base == 0x30000000
; The following loop is to direct map RAM VA == PA. i.e.
; VA == 0x30XXXXXX => PA == 0x30XXXXXX for S3C2400
; Fill in 8 entries to have a direct mapping for DRAM
接下来开始,建立虚拟地址到相等的物理地址的映射,如程序清单 5‑16所示。
ldr r10, =PTs ; restore address of 1st level page table
add r10, r10, #(0x3000 / 4) ; (r10) = ptr to 1st PTE for 0x30000000
add r0, r0, #0x1E ; 1MB cachable bufferable
orr r0, r0, #0x400 ; set kernel r/w permission
45 mov r2, r1 ; (r2) = virtual address to map Bank at
cmp r2, #0x20000000:SHR:BANK_SHIFT
add r2, r10, r2, LSL #BANK_SHIFT-18
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page
ldr r10, =PTs ; (r10) = restore address of 1st level page table
; The page tables and exception vectors are setup.
; Initialize the MMU and turn it on.
mcr p15, 0, r1, c3, c0, 0 ; setup access to domain 0
mcr p15, 0, r0, c8, c7, 0 ; flush I+D TLBs
orr r1, r1, #0x0004 ; Enable the cache
cmp r0, #0 ; make sure no stall on "mov pc,r0" below
mov pc, r0 ; & jump to new virtual address
; (r10) = physcial address of 1st level page table
add sp, sp, #0x30000 ; arbitrary initial super-page stack pointer
跳转到BootLoader的main函数,并结束StartUp结束,如程序清单 5‑17所示。
1.2.7 Windows CE5.0启动过程
SleepState_WakeAddr EQU (SleepState_Data_Start )
SleepState_MMUCTL EQU (SleepState_WakeAddr + WORD_SIZE )
SleepState_MMUTTB EQU (SleepState_MMUCTL + WORD_SIZE )
SleepState_MMUDOMAIN EQU (SleepState_MMUTTB + WORD_SIZE )
SleepState_SVC_SP EQU (SleepState_MMUDOMAIN + WORD_SIZE )
SleepState_SVC_SPSR EQU (SleepState_SVC_SP + WORD_SIZE )
SleepState_FIQ_SPSR EQU (SleepState_SVC_SPSR + WORD_SIZE )
SleepState_FIQ_R8 EQU (SleepState_FIQ_SPSR + WORD_SIZE )
SleepState_FIQ_R9 EQU (SleepState_FIQ_R8 + WORD_SIZE )
SleepState_FIQ_R10 EQU (SleepState_FIQ_R9 + WORD_SIZE )
SleepState_FIQ_R11 EQU (SleepState_FIQ_R10 + WORD_SIZE )
SleepState_FIQ_R12 EQU (SleepState_FIQ_R11 + WORD_SIZE )
SleepState_FIQ_SP EQU (SleepState_FIQ_R12 + WORD_SIZE )
SleepState_FIQ_LR EQU (SleepState_FIQ_SP + WORD_SIZE )
SleepState_ABT_SPSR EQU (SleepState_FIQ_LR + WORD_SIZE )
SleepState_ABT_SP EQU (SleepState_ABT_SPSR + WORD_SIZE )
SleepState_ABT_LR EQU (SleepState_ABT_SP + WORD_SIZE )
SleepState_IRQ_SPSR EQU (SleepState_ABT_LR + WORD_SIZE )
SleepState_IRQ_SP EQU (SleepState_IRQ_SPSR + WORD_SIZE )
SleepState_IRQ_LR EQU (SleepState_IRQ_SP + WORD_SIZE )
SleepState_UND_SPSR EQU (SleepState_IRQ_LR + WORD_SIZE )
SleepState_UND_SP EQU (SleepState_UND_SPSR + WORD_SIZE )
SleepState_UND_LR EQU (SleepState_UND_SP + WORD_SIZE )
SleepState_SYS_SP EQU (SleepState_UND_LR + WORD_SIZE )
SleepState_SYS_LR EQU (SleepState_SYS_SP + WORD_SIZE )
SleepState_Data_End EQU (SleepState_SYS_LR + WORD_SIZE )
SLEEPDATA_SIZE EQU ((SleepState_Data_End - SleepState_Data_Start) / 4)
该函数主要完成以下工作,详细代码可以参考$(_PRIVATEROOT)WINCEOS/COREOS/NK/LDR/ARM/armstart.s所示。
l 完成OEMAddressTable表中的物理地址到虚拟地址和虚拟地址到物理地址之间的映射。
l 对存储器页表和内核参数区存储空间(RAM或DRAM)进行清零处理。
l 读出CPU的ID号,内核需要根据该ID决定ARM的MMU处理,因为ARMV6和ARMV6之前的ARM处理器的MMU处理过程有所区别。
l 设置并开启MMU和Cache,因为在Startup函数关闭MMU和Cache。
l 调用ARMInit函数重新定位Windows CE内核参数pTOC和初始化OEMInitGlobals全局变量。
l 利用mov pc, r12指令跳转到kernel.dll的入口位置,即NKStartup函数中。
该函数主要完成在启动第一个线程前对内核进行初始化,主要包括API函数集初始化、堆的初始化、初始化内存池、进程初始化、线程初始化和文件映射初始化等操作,如程序清单 5‑19所示。
g_pNKGlobal->pfnWriteDebugString (TEXT("Windows CE KernelInit/r/n"));
APICallInit ();// setup API set
HeapInit ();// setup kernel heap
InitMemoryPool ();// setup physical memory
PROCInit ();// initialize process
VMInit (g_pprcNK);// setup VM for kernel
THRDInit ();// initialize threadsv
g_pNKGlobal->pfnWriteDebugString (TEXT("Scheduling the first thread./r/n"));
1.3 WinCE5.0设备驱动开发
1.3.1 WinCE5.0驱动模型
1.3.2 WinCE5.0驱动体系结构
在Windows CE5.0驱动程序中,包含两种类型:分层驱动程序和单体驱动程序。有时候又把分层驱动程序的扩展驱动程序称之为另一种类型——混合驱动程序。
1. 分层驱动程序
l 与PDD代码链接,定义PDD层必须实现的DDSI函数,并在代码中调用这些函数。
2. 单体驱动程序
因为单体驱动程序在代码上是一个整体,结构相对紧凑,对与一些效率较高的场合,选用单体驱动程序有可能提高驱动程序的性能。在以下情况下可以悬着单体驱动程序结构。
(1) Windows CE没有提供分层驱动程序的实现,这种情况下开发者可以自己按照分层驱动程序的特性来开发,以方便驱动程序移植。
(2) 硬件设备上已经完成部分MDD功能,为了充分利用硬件的性能,可以采用单体驱动程序结构。
相对分层驱动程序,单体驱动程序开发起来较为复杂,可重用性大大降低,所以为软件开发人员在开发驱动程序时尽量选用分层驱动程序,只有在性能特别重要或者分层的驱动不能发挥硬件的性能时才会选用单体驱动程序。
3. 混合驱动程序
混合驱动程序第三种驱动程序结构,它往往并不在微软的帮助文档中出现,一个最要的原因是微软不推荐这样去做。这里进行简单的介绍主要是因为这种驱动程序结构在一些特殊场合确实有用武之地。
混合驱动程序结构是在扩展现有分层驱动程序的一个方法,但这种扩展是在牺牲驱动程序可移植性基础上获得,除非很有必要,一般不推荐使用此结构。
1.3.3 流接口驱动程序开发
流接口驱动程序是驱动开发模型中的一种,通过前面的介绍,相信大家对其已经有一定的了解。先介绍本系统其他关键驱动,如USB驱动、串口驱动之前,有必要介绍一下流驱动程序的开发方法。
1. 流接口驱动程序的体系结构
2. 标准流接口函数
正如上述,流接口驱动程序是从系统导出函数的,系统规定流接口驱动程序必须实现表 8所示函数[4]。
流接口函数 |
描述 |
XXX_Init |
当设备管理器初始化一个具体的设备时调用这个函数 |
XXX_Deinit |
当设备管理器卸载一个驱动程序时调用这个函数 |
XXX_PreDeinit |
释放设备,在设备被卸载时调用 |
XXX_Open |
打开设备驱动程序 |
XXX_Close |
关闭设备驱动程序 |
XXX_PreClose |
通知设备管理器把它打开的句柄设置为无效,并唤醒任何正在休眠的线程 |
XXX_Read |
从设备中读取数据 |
XXX_Write |
向设备中写入数据 |
XXX_Seek |
该函数移动设备的数据指针 |
XXX_PowerUp |
该函数恢复对设备供电 |
XXX_PowerDown |
该函数停止对设备供电 |
XXX_IOControl |
该函数发出命令给客户 |
(1) DWORD XXX_Init(LPCTSTR pContext,LPCVOID lpvBusContext);
l pContext:系统传入的注册表键,通过它可以讲到我们在注册表中设置的配置信息
(2) DWORD XXX_Open(DWORD hDeviceContext,DWORD dwAccess, DWORD dwShareMode);
当用户程序调用CreateFile打开这个设备时,设备管理器就会调用此驱动程序的XXX_Open函数,该函数参数如下;
l dwAccess:上层所要求的访问方式,可以是读或者写,或者是0,即不读也不写
l dwShareMode:上层程序所请求的共享模式,可以是共享读、共享写这两个值的逻辑或,或者是0,即独占式访问。
(3) DWORD XXX_Close(DWORD hDeviceContext);
当用户程序调用CloseHandle关闭这个设备句柄时,这个函数就会被设备管理器调用,该函数参数如下;
l hDeviceContext:为XXX_Open返回给上层的那个值。
这个函数应该做与XXX_Open相反的事情,具体包括:释放XXX_Open分配的内存,将驱动程序被打开的记数减少等。
(4) DWORD XXX_Deinit(DWORD hDeviceContext);
这个函数在设备被卸载时被调用,它应该实现与XXX_Init相反的操作,主要为释放前者占用的所有系统资源,该参数如下;
l hDeviceContext XXX_Init函数返回给上层的那个句柄
(5) void XXX_PowerUp( DWORD hDeviceContext );
(6) void XXX_PowerDown(DWORD hDeviceContext );
(7) BOOL XXX_IOControl(DWORD hDeviceContext,
l hDeviceContext:XXX_Open返回给上层的那个句柄,即我们自已定义的,用来存放程序所有信息的一个结构。
l dwCode: IO操作码,如果是CE已经支持的设备类,就用它已经定义好码值,否则就可以自已定义。
l pBufIn:传入的Buffer,每个IO操作码都会定义自已的Buffer结构
l pBufOut,dwLenOut分别为传出的Buffer,及其以字节记的大小。
l pdwActualOut:驱动程序实际在pBufOut中填入的数据以字节记的大小。
其中,前两个参数是必须的,其它的任何一个都有可能是NULL或0。所以,当给pdwActualOut赋值时应该先判断它是否为一个有效的地址
3. 虚拟地址映射
MMU启动之后,CPU就不能够直接访问物理地址了。这时需要一个物理地址到虚拟地址的映射。虚拟地址的映射包括两种:静态映射和动态映射。
; OEMAddressTable defines the mapping between Physical and Virtual Address
; o MUST be in a READONLY Section
; o First Entry MUST be RAM, mapping from 0x80000000 -> 0x00000000
; o each entry is of the format ( VA, PA, cbSize )
; o cbSize must be multiple of 4M
; o last entry must be (0, 0, 0)
; o must have at least one non-zero entry
; RAM 0x80000000 -> 0x00000000, size 64M
; FLASH and other memory, if any
; dd FlashVA, FlashPA, FlashSize
“dd 80000000h, 0, 04000000h”,将64MB(04000000h)的物理地址空间映射到从0x80000000开始的虚拟地址空间。物理地址从0开始。
VirtualCopy函数作用是将物理地址绑定到由VirtualAlloc申请的虚拟地址空间,其原型如程序清单 5‑21所示。
VirtualFree作用是取消虚拟地址与物理地址的映射,同时释放VirtualAlloc所申请的虚拟地址空间,其原型如程序清单 5‑22所示。
1.3.4 驱动的加载和卸载
在Windows CE 5.0中,流驱动的加载有三种方式,其中两种涉及设备管理器,一种涉及应用程序。相应的流驱动的卸载有就存在两种方式,分别针对设备管理器和应用程序。
1. 流驱动程序的加载
在Windows CE 5.0中,第二种加载流驱动的方式就是在设备管理器自动检测外围设备与系统连接的时候进行的。比如PC卡的自动检测等。
2. 流驱动程序的卸载
在Windows CE 5.0中,我们可以根据设备管理器和驱动程序两种加载方式来决定流驱动程序的卸载方式。
l 应用程序加载:当流驱动程序是应用程序加载时,应用程序就必须通过调用DeactiveDevice函数来卸载该驱动。
1.4 GPIO驱动程序设计
1.4.1 S3C2440A_IOPORT_REG结构体
S3C2440A_IOPORT_REG结构体客观的定义了S3C2440A芯片GPIO结构,只需要对其赋予初始地址即可访问GPIO地址中的相关数据,并控制GPIO,该结构的原型如程序清单 5‑23所示。
程序清单 5‑23 S3C2440A_IOPORT_REG结构体
UINT32 GPACON; // Port A - offset 0
UINT32 GPBCON; // Port B - offset 0x10
UINT32 GPBUP; // Pull-up disable
} S3C2440A_IOPORT_REG, *PS3C2440A_IOPORT_REG;
1.4.2 GIO_Init函数
DWORD GIO_Init(DWORD dwContext)
RETAILMSG(1,(TEXT("GPIO Initialize ...")));
v_pIOPregs->GPBCON = (v_pIOPregs->GPBCON &~(3 << 10)) | (1<< 10); // GPB5 == OUTPUT.
v_pIOPregs->GPBCON = (v_pIOPregs->GPBCON &~(3 << 12)) | (1<< 12); // GPB6 == OUTPUT.
v_pIOPregs->GPBCON = (v_pIOPregs->GPBCON &~(3 << 14)) | (1<< 14); // GPB7 == OUTPUT.
v_pIOPregs->GPBCON = (v_pIOPregs->GPBCON &~(3 << 16)) | (1<< 16); // GPB8 == OUTPUT.
RETAILMSG(1,(TEXT("OK !!!/n")));
bool InitializeAddresses(VOID)
VirtualFree((PVOID) v_pIOPregs, 0, MEM_RELEASE);
1.4.3 GIO_IOControl函数
真正对GPIO进行实质性的操作就在GIO_IOControl这个函数里面发生,操作命令如程序清单 5‑26所示,具体实现代码如程序清单 5‑27所示。
#define IO_CTL_GPIO_1_ON 0x01 // 将B对GPIO1口设置为输入
#define IO_CTL_GPIO_2_ON 0x02 // 将B对GPIO2口设置为输入
#define IO_CTL_GPIO_3_ON 0x03 // 将B对GPIO3口设置为输入
#define IO_CTL_GPIO_4_ON 0x04 // 将B对GPIO4口设置为输入
#define IO_CTL_GPIO_ALL_ON 0x05 // 将B对所有GPIO口设置为1
#define IO_CTL_GPIO_1_OFF 0x06 // 将B对GPIO1口设置0
#define IO_CTL_GPIO_2_OFF 0x07 // 将B对GPIO2口设置0
#define IO_CTL_GPIO_3_OFF 0x08 // 将B对GPIO3口设置0
#define IO_CTL_GPIO_4_OFF 0x09 // 将B对GPIO4口设置0
#define IO_CTL_GPIO_ALL_OFF 0x0a // 将B对所有GPIO口设置0
BOOL GIO_IOControl(DWORD hOpenContext,
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT&~(0x1<<5);
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT&~(0x1<<6);
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT&~(0x1<<7);
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT&~(0xF<<5);
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT|(0x1<<5);
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT|(0x1<<6);
v_pIOPregs->GPBDAT=v_pIOPregs->GPBDAT|(0xF<<5);
RETAILMSG(1,(TEXT("GPIO_Control:Ioctl code = 0x%x/r/n"), dwCode));
1.5 USB驱动程序设计
相比GPIO驱动,USB驱动程序的设计过程就比较复杂了,不管要了解USB协议还需要连接USB驱动程序框架,接下我简要介绍下本系统中USB驱动的设计方法和经验总结。
1.5.1 USB驱动概要设计
1.5.2 USB相关技术
1. USB拓扑结构
2. USB数据流模式
USB 设备连接到HOST 时,HOST 必须通过默认的控制管道对其进行枚举,完成获得其设备描述、进行地址分配、获得其配置描述、进行配置等操作方可正常使用。USB 设备的即插即用特性即依赖于此。
l 控制传输:主要用于在设备连接时对设备进行枚举以及其他因设备而已的特定操作。
l 中断传输:用于对延迟要求严格、小量数据的可靠传输,如键盘、游戏手柄等。
l 批量传输:用于对延迟要求宽松,大量数据的可靠传输,如U 盘等。
l 同步传输:用于对可靠性要求不高的实时数据传输,如摄像头、USB音响等。
3. USB设备请求
所有的USB设备都会在默认控制管道对主机发送过来的请求作出响应。这些请求利用控制传输产生。请求及其参数是通过SETUP包发送到设备。主机负责设置如下信息。
偏移量 |
场 |
大小 |
值 |
描述 |
0 |
bmRequestType |
1 |
位映像 |
请求的类型: D7 数据传输方向 0:主机到设备 1:设备到主机 D6~5 类型 0:标准 1:类型 2:厂商 3:保留 D4~0 接收器 0:设备 1:接口 2:端点 3:其他 4~31:保留 |
1 |
bRequest |
1 |
值 |
专用请求 |
2 |
wValue |
2 |
值 |
根据请求变化、长度大小为一个字 |
4 |
wIndex |
2 |
偏移量或索引 |
根据请求变化。典型的应用是用于传递索引或偏移量 |
6 |
wLength |
2 |
计数 |
如果有数据阶段,在数据阶段传输的字节数 |
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据 |
|
00000000B |
CLEAR_FEATURE(1) |
特性选择符 |
接口端点0 |
0 |
无 |
|
00000001B |
||||||
00000010B |
||||||
10000000B |
GET_CONFIGURATION(8) |
0 |
0 |
1 |
配置值 |
|
10000000B |
GET_DESCRIPTOR(7) |
描述符类型及其索引 |
0或者语言ID |
描述符长度 |
描述符 |
|
10000001B |
GET_INTERFACE(11) |
0 |
接口 |
1 |
备用设置 |
|
10000000B |
GET_STATUS(0) |
0 |
接口端点0 |
2 |
设备、接口或端点状态 |
|
10000001B |
||||||
10000010B |
||||||
00000000B |
SET_ADDRESS(5) |
设备地址 |
0 |
0 |
无 |
|
00000000B |
SET_CONFIGURATION(9) |
配置值 |
0 |
0 |
无 |
|
00000000B |
SET_DESCRIPTOR(6) |
描述符类型及其索引 |
0或者语言ID |
描述符长度 |
描述符 |
|
00000000B |
SET_FEATURE(3) |
特性选择符 |
接口端点0 |
测试选择符 |
测试选择符 |
0 |
00000001B |
SET_INTERFACE(11) |
备用设置 |
接口 |
0 |
无 |
|
00000010B |
SYNCH_FRAME(12) |
0 |
端点 |
2 |
帧号码 |
|
描述符类型 |
值 |
DEVICE |
1 |
CONFIGURATION |
2 |
STRING |
3 |
INTERFACE |
4 |
ENDPOINT |
5 |
DEVICE_QUALIFIER |
6 |
OTHER_SPEED_COFIGURATION |
7 |
INTERFACE_POWER |
8 |
OTG |
9 |
DEBUG |
10 |
INTERFACE_ASSOCIATION |
11 |
主机按照控制传输的约定,将SETUP包及其参数和数据发送到设备,并等待设备的握手信息。
4. 标准USB描述符
设备描述符说明了USB设备的通用信息。它包括应用到全部设备和所有设备配置的信息。USB设备只有一个设备描述符。其格式如表 12所示。
偏移 |
场 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
按字节算的描述符大小 |
1 |
bDescriptorType |
1 |
常数 |
设备描述符类型 |
2 |
bcdUSB |
2 |
BCD码 |
以BCD编码的USB规范版本号 210H即2.10 |
4 |
bDeviceClass |
1 |
类型 |
类型码(由USB-IF分配) 如果值为0,配置中的每个接口指定了自己的类型信息,而且接口独立操作 如果值在1~FFH之间设备支持不同接口有不同的类型规范,而且接口不独立操作 如果值为FFH,设备类型是厂商提供的 |
5 |
bDeviceSubClass |
1 |
子类型 |
子类型码(由USB-IF分配),这些代码受bDeviceClass场的限制。 |
6 |
bDeviceProtocol |
1 |
协议 |
协议码(USB-IF分配) |
7 |
bMaxPacketSize0 |
1 |
数字 |
端点0的最大包大小(只能是8,16,32或64) |
8 |
idVendor |
2 |
ID |
厂商ID(USB-IF分配) |
10 |
idProduct |
2 |
ID |
商品ID(由生产商分配) |
12 |
bcdDevice |
2 |
BCD码 |
BCD编码表式的设备发布号 |
14 |
iManufacturer |
1 |
索引 |
描述厂商的字符串描述符的索引 |
15 |
iProduct |
1 |
索引 |
描述产品的字符串描述符的索引 |
16 |
iSerialNumber |
1 |
索引 |
描述设备序列号的字符串描述符的索引 |
17 |
bNumConfiguration |
1 |
数字 |
可能的配置数 |
能进行高速操作的设备与全速和低速设备的配置信息不一样,因此还要有设备限制符。
偏移 |
场 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
描述符的大小 |
1 |
bDescriptorType |
1 |
常数 |
设备限定符的类型 |
2 |
bcdUSB |
2 |
BCD |
USB规范的发布号 |
4 |
bDeviceClass |
1 |
类型 |
类型码 |
5 |
bDeviceSubClass |
1 |
子类型 |
子类型码 |
6 |
bDeviceProtocol |
1 |
协议 |
协议码 |
7 |
bMaxPacketSize0 |
1 |
数字 |
其他速度的最大包大小 |
8 |
bNumConfigurations |
1 |
数字 |
其他速度配置的数据 |
9 |
bReserved |
1 |
0 |
保留到以后使用,必须为0 |
偏移 |
场 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
按字节计算的描述符大小 |
1 |
bDescriptorType |
1 |
常数 |
配置描述符类型 |
2 |
wTotalLength |
2 |
数字 |
该配置返回的数据总长度,包括该配置返回的所有描述符(配置、接口、端点号和专用类型描述符) |
4 |
bNumInterfaces |
1 |
数字 |
这个配置支持的接口数 |
5 |
bConfigurationValues |
1 |
数字 |
用于SetConfiguration() |
6 |
iConfiguration |
1 |
索引 |
描述这个配置的字符串描述符索引 |
7 |
bmAttributes |
1 |
位映像 |
配置特性: D7 保留(设为1) D6 自供电 D5 远程唤醒 D4~0 保留(设为0) |
8 |
bMaxPower |
1 |
mA |
当设备运行时特定配置的USB设备从总线获得的最大功耗。 |
偏移 |
场 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
描述符的大小 |
1 |
bDescriptorType |
1 |
常数 |
描述符的类型 |
2 |
wTotalLength |
2 |
数字 |
返回的数据总长度 |
4 |
bInterfaces |
1 |
数字 |
支持的接口数 |
5 |
bConfigurationValue |
1 |
数字 |
用于选择配置的值 |
6 |
iConfiguration |
1 |
索引 |
字符串描述符的索引 |
7 |
bmAttributes |
1 |
位映像 |
与配置描述符符相同 |
8 |
bMaxPower |
1 |
mA |
与配置描述符相同 |
偏移 |
场 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
按字节的描述符大小 |
1 |
bDescriptorType |
1 |
常数 |
描述符类型 |
2 |
bInterfaceNumber |
1 |
数字 |
接口的序列号 |
3 |
bAlternateSetting |
1 |
数字 |
选者备用配置时用的值 |
4 |
bNumEndpoints |
1 |
数字 |
接口的端点数(不包括端点0) |
5 |
bInterfaceClass |
1 |
类型 |
类型码(USB-IF分配) |
6 |
bInterfaceSubClass |
1 |
子类型 |
子类型码(USB-IF分配) |
7 |
bInterfaceProtocoal |
1 |
协议 |
协议码(USB-IF分配) |
8 |
iInterface |
1 |
索引 |
描述这个接口的字符串的索引值 |
偏移 |
场 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
按字节的描述符大小 |
1 |
bDescriptorType |
1 |
常数 |
描述符类型 |
2 |
bEndpointAddress |
1 |
端点 |
USB设备端点地址。 [3:0]端点号 [6:4]保留,设为0 [7]方向 |
3 |
bmAttributes |
1 |
位映像 |
[1:0] 传输类型 00 = 控制 01 = 同步 10 = 批量 11 = 中断 [3:2] 同步类型 00 = 非同步 01 = 异步 10 = 自适应 11 = 同步 [5:4] 使用类型 00 = 数据端点 01 = 反馈端点 10 = 隐式反馈数据端点 11 = 保留 |
4 |
wMaxPacketSize |
2 |
数字 |
端点能接受或发送的最大的包大小 |
6 |
bInterval |
1 |
数量 |
查询端点进行数据传输的间隔 |
1.5.3 Windows CE下USB初始化
1. 主机初始化
USB主机的初始化包括根集线器和主机控制器的初始化。初始化的任务是将主机控制器和根集线器设置为特定的状态,初始化内存。下面从主机控制器上电开始对初始化过程简单介绍。
[HKEY_LOCAL_MACHINE/Drivers/BuitIn]
设备管理器加载ohci2.dll,并且调用ohci2.dll中的HCD_Init函数。该函数完成对主机控制器的所有初始化。HCD_Init函数的实现如程序清单 5‑29所示。
extern "C" DWORD HCD_Init (DWORD dwContext )
HKEY ActiveKey; /* 指向打开的注册表键 */
WCHAR RegKeyPath[DEVKEY_LEN]; /* 保存设备在BuiltIn下的键值 */
DWORD status; /* 注册表操作函数的返回值 */
status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCWSTR)dwContext,
0, 0, &ActiveKey); /* 打开设备注册表键值 */
if (status != ERROR_SUCCESS) { /* 打开失败打印提示信息,返NULL */
DEBUGMSG(ZONE_INIT|ZONE_ERROR,
(TEXT("EHCD!HCD_Init RegOpenKeyEx(%s) returned %d./r/n"),
status = RegQueryValueEx(ActiveKey, DEVLOAD_DEVKEY_VALNAME,
NULL, &ValType, (PUCHAR)RegKeyPath,&ValLen);
if (status != ERROR_SUCCESS) { /* 查找失败,关闭注册表,返回NULL */
DEBUGMSG(ZONE_INIT|ZONE_ERROR,
(TEXT("EHCD!HCD_Init RegQueryValueEx(%s//%s) returned %d/r/n"),
(LPCWSTR)dwContext, DEVLOAD_DEVKEY_VALNAME, status));
g_IstThreadPriority = GetInterruptThreadPriority(
(LPTSTR)dwContext); /* 得到中断优先级 */
RegKeyPath[DEVKEY_LEN-1] = 0 ;
EnterCriticalSection( &g_CSection );
DWORD dwReturn =HcdPdd_Init((DWORD)RegKeyPath); /* 调用PDD层初始化函数 */
LeaveCriticalSection( &g_CSection );
dwSysIntr = MapIrq2SysIntr(dwIRQ); /* 映射中断 */
pobMem = HcdMdd_CreateMemoryObject(gcTotalAvailablePhysicalMemory,
gcHighPriorityPhysicalMemory, pvUsbHcca,
(LPVOID)(dwIoPortBase+HD64465_EMBEDED_SDRAM_OFFSET));
pobOhcd = HcdMdd_CreateHcdObject(pPddObject, pobMem,
szDriverRegKey, pvUsbRegister, dwSysIntr);
fResult = pobOhcd ? TRUE : FALSE;
fResult = FALSE; /* 创建失败,返回FALSE */
HcdMdd_CreateMemoryObject函数创建内存对象,HcdMdd_CreateHcdObject函数创建COhcd,其关键代码如程序清单 5‑31所示。
程序清单 5‑31 HcdMdd_CreateObject的关键代码
COhcd * pobOhcd = new COhcd(lpvOhcdPddObject, pobMem, szRegKey,
ioPortBase, dwSysIntr); /* 创建COhcd类 */
if (pobOhcd && (pobOhcd->DeviceInitialize() == FALSE)) {
COhcd::Initialize失败,删除Cohcd */
2. 设备的初始化
l 建立控制管道,如果是高速设备,还要为其分配处理转化器(TT)。
l 根据具体情况选择一个合适的配置描述符,并用这个配置描述符配置设备。
l 创建管理设备的类,若新插入的设备是集线器,则创建CExternHub类;若是功能设备,则创建CFunction类。
HcdDeviceAttach函数的关键代码如程序清单 5‑32所示。
fRet = LoadDeviceDrivers(pDev, &fLoaded); /* 根据注册表加载驱动 */
if (fRet && !fLoaded) { /* 加载驱动失败,提示用户输入驱动 */
if (!GetClientDriverName(szDllName, USB_MAX_LOAD_STRING))
else if (!InstallClientDriver(szDllName)) /* 调用驱动中USBInstallDriver函数
CallNetMsgBox(NULL,NMB_FL_OK|NMB_FL_TITLEUSB,
NETUI_GETNETSTR_USB_INSTALL_FAILURE,szDllName);
具体怎么根据注册表来查找合适的驱动,即驱动搜索算法,会在后面详细讲述。现在先了解一下USBInstallDriver函数。
程序清单 5‑33 USB_DRIVER_SETTINGS结构体
typedef struct _USB_DRIVER_SETTINGS
DWORD dwVendorId; /* 设备描述符中的厂商ID */
DWORD dwProductId; /* 设备描述符中的产品ID */
DWORD dwReleaseNumber; /* 设备描述符中的产品发布号 */
DWORD dwDeviceClass; /* 设备描述符中的设备类型 */
DWORD dwDeviceSubClass; /* 设备描述符中的设备子类型 */
DWORD dwDeviceProtocol; /* 设备描述符中的设备协议类型 */
DWORD dwInterfaceClass; /* 设备描述符中的接口类型 */
DWORD dwInterfaceSubClass; /* 设备描述符中的接口子类型 */
DWORD dwInterfaceProtocol; /* 设备描述符中的接口协议类型 */
} USB_DRIVER_SETTINGS, * PUSB_DRIVER_SETTINGS, * LPUSB_DRIVER_SETTINGS;
程序清单 5‑34 HID USB_DRIVER_SETTINGS示例
DriverSettings->dwVendorId = USB_NO_INFO;
DriverSettings->dwProductId = USB_NO_INFO;
DriverSettings->dwReleaseNumber = USB_NO_INFO;
DriverSettings->dwDeviceClass = USB_NO_INFO;
DriverSettings->dwDeviceSubClass = USB_NO_INFO;
DriverSettings->dwDeviceProtocol = USB_NO_INFO;
DriverSettings->dwInterfaceClass = USB_DEVICE_CLASS_HUMAN_INTERFACE;
DriverSettings->dwInterfaceSubClass = USB_NO_INFO;
DriverSettings->dwInterfaceProtocol = USB_NO_INFO;
综上所述,以下是USBInstallDriver函数需要完成的操作:
l 通过RegisterClientID函数,注册一个系统唯一的驱动表示字符串。
l 通过RegisterClientSettings函数,正确设置LoadClient下的子键。
l 通过OpenClientRegisterKey函数,设置驱动所需要的额外的信息。这一步不是必须的。
1.5.4 主机与设备间的通信
当设备驱动加载成功后,主机和设备就可以相互通信。客户驱动程序可以调用USBD层的接口完成各种通信任务。如表 18所示为USBD层提供的部分接口。
接口名 |
描述 |
GetTransferError |
返回与指定参数相关的错误信息 |
lpAbortPipeTransfer |
取消管道上的所有传输 |
lpAbortTransfer |
取消特定的传输 |
lpClearFeature |
向USB设备发送CLEAE_FEATURE请求 |
lpClosePipe |
关闭特定的管道 |
lpCloseTransfer |
关闭特定的传输 |
lpDeviceNotifyRoutine |
处理设备通知 |
lpDisableDevice |
禁能设备 |
lpFindInterface |
寻找特定的接口 |
lpGetDescriptor |
向设备发送GET_DESCRIPTOR请求 |
lpGetDeviceInfo |
获取设备所有的描述符 |
lpGetFrameLength |
获得帧长度 |
lpGetFrameNumber |
获得当前帧号码 |
lpGetIsochResults |
获取同步传输结果 |
lpGetStatus |
向设备发送GET_STATUS请求 |
lpGetTransferStatus |
获取特定传输状态 |
lpGetUSBDVersion |
获得设备USB版本号 |
lpIsDefaultPipeHalted |
检测默认管道是否挂起 |
lpIsPipeHalted |
检测特定管道是否挂起 |
lpIsTransferComplete |
检测特定传输是否完成 |
lpIssueBulkTransfer |
发送批量传输 |
lpIssueInterruptTransfer |
发送中断传输 |
lpIssueIsochTransfer |
发送同步传输 |
lpIssueVendorTransfer |
向设备发送厂商特定的传输 |
lpLoadGenericInterfaceDriver |
加载设备接口驱动 |
lpOpenClientRegisterKey |
打开设备驱动注册表键 |
lpOpenPipe |
打开管道 |
lpRegisterClientDriverID |
注册设备ID |
lpRegisterDriverSettings |
注册设备驱动 |
lpRegisterNotifyRoutine |
注册处理设备通知的函数 |
lpReleaseFrameLengthControl |
释放对帧长度的控制权 |
lpResetDefaultPipe |
恢复默认管道 |
lpResetPipe |
恢复特定管道 |
1.5.5 编写USB功能驱动程序
1. USB设备驱动程序入口点函数
2. USB设备流接口驱动的实现步骤
l 在注册表中为驱动程序建立表项。在注册表中建立驱动程序入口点,这样设备管理器才能识别和管理这个驱动。此外,注册表中还能存储额外的信息,这些信息可以在驱动运行之后被使用到。
3. 驱动通信的实现
l IOCTL_INTERRUPT_IN 发送中断输入传输。
l IOCTL_INTERRUPT_OUT 发送中断输出传输。
l IOCTL_OPEN_INTER_IN_PIPE 打开中断输入管道。
l IOCTL_CLOSE_INTER_IN_PIPE 关闭中断输入管道。
l IOCTL_OPEN_INTER_OUT_PIPE 打开中断输出管道。
l IOCTL_CLOSE_INTER_OUT_PIPE 关闭中断输出管道。
l IOCTL_OPEN_BULK_IN_PIPE 打开批量输入管道。
l IOCTL_CLOSE_BULK_IN_PIPE 关闭批量输入管道。
l IOCTL_OPEN_BULK_OUT_PIPE 打开批量输出管道。
l IOCTL_CLOSE_BULK_OUT_PIPE 关闭批量输出管道。
l IOCLT_GET_DESCRIPTOR 获取设备描述符的字符串表示。
RETAILMSG(1, (TEXT("interrupt-in transfer/n"))); /* 打印调试信息 */
if (!pBufOut ||! pdwActualOut) { /* 检查输入参数的有效性 */
RETAILMSG(1, (TEXT("Interrupt In Transfer Parameter ERROR/n")));
SetLastError(ERROR_INVALID_PARAMETER);
if (!pUSBD12->InterruptIn.bOpen) { /* 检查输入管道是否打开 */
RETAILMSG(1, (TEXT("Please Open Pipe First/n")));
EnterCriticalSection(&pUSBD12->InterruptIn.csPipeLock); /* 进入临界区,开始通信 */
* 调用USBD层IssueInterruptTransfer接口,USB_IN_TRANSFER表明是输入传输。pBufOut是缓冲
usbTransfer = (*pUSBD12->usbFuncs->lpIssueInterruptTransfer)(pUSBD12->InterruptIn.Pipe,
if (NULL == usbTransfer) { /* 检查传输是否成功建立 */
RETAILMSG(1, (TEXT("Issue Interrupt transfer ERROR : %d/n") , GetLastError()));
LeaveCriticalSection(&pUSBD12->InterruptIn.csPipeLock);
(*pUSBD12->usbFuncs->lpGetTransferStatus)(usbTransfer, pdwActualOut, dwErr);
if (ERROR_SUCCESS != dwErr) { /* 检查是否正确无误的完成传输 */
RETAILMSG(1, (TEXT("Transfer Issue Status NOT ERROR_SUCCESS/n")));
(*pUSBD12->usbFuncs->lpCloseTransfer)(usbTransfer);
LeaveCriticalSection(&pUSBD12->InterruptIn.csPipeLock);
(*pUSBD12->usbFuncs->lpCloseTransfer)(usbTransfer); /* 关闭传输 */
LeaveCriticalSection(&pUSBD12->InterruptIn.csPipeLock); /* 离开临界区 */
RETAILMSG(1, (TEXT("Interrupt In Transfer OK/n")));
4. 驱动USBInstallDriver和USBUninstallDriver函数
前面已经介绍过USBInstallDriver和USBUninstallDriver函数的作用,下面简单分析一下驱动中这两个函数的实现,其代码如程序清单 5‑35所示。
程序清单 5‑36 USBInstallDriver和USBUninstallDriver
BOOL USBInstallDriver (LPCWSTR szDriverLibFile)
BOOL bRet = FALSE; /* 函数返回值 */
USB_DRIVER_SETTINGS usbDriverSettings = {USBD12_DRIVER_SETTING};
RETAILMSG(1, (TEXT("USBInstallDriver/n"))); /* 打印调试信息 */
bRet = RegisterClientDriverID(USBD12_DRIVERID); /* 向系统注册设备ID */
if (!bRet) { /* 注册失败,返回FALSE */
RETAILMSG(1, (TEXT("RegisterClientDriverID error:%d/n"), GetLastError()));
bRet = RegisterClientSettings(szDriverLibFile, USBD12_DRIVERID,
0, &usbDriverSettings); /* 在注册表中注册驱动 */
RETAILMSG(1, (TEXT(" RegisterClientSettings Error : %d/n") , GetLastError()));
BOOL USBUnInstallDriver( void )
USB_DRIVER_SETTINGS usbDriverSettings = { USBD12_DRIVER_SETTING };
RETAILMSG(1, (TEXT("USBUNINSTALLDRIVER /n")));
bRet = UnRegisterClientDriverID(USBD12_DRIVERID); /* 删除设备ID */
RETAILMSG(1, (TEXT("UnReigsterClientID ERROR : %d/n "), GetLastError()));
bRet = UnRegisterClientSettings(USBD12_DRIVERID, NULL, &usbDriverSettings);
if(!bRet) { /* 删除失败,返回FALSE */
RETAILMSG( 1 , (TEXT( " UnRegisterClientSettings ERROR : %d/n ") , GetLastError()) );
5. 驱动USBDeviceAttach函数
在设备插入时,系统会调用驱动的USBDeviceAttach函数,该函数实现对设备插入的一些处理。这里主要是为设备分配索引号,注册该设备,创建设备相关的数据结构等。
ULONGLONG HighPart; /* 高64位 */
typedef struct __usb_ta __usb_TAG;
typedef struct __usb_tag *PUSB_TAG;
下一步,要为设备创建注册表。驱动会在KEY_LOCAL_MACHINE/Drivers
/USB/ ClientDrivers/XXX键下以设备的索引号为名创建一个子键,这个子键包括设备驱动的DLL名称,驱动的前缀Prefix,驱动的顺序Order和驱动的索引Index。
最后为设备注册一个设备拔出回调函数。该回调函数会在设备拔出时由系统调用。到这里基本上的工作都完成了。
1.6 串口驱动程序设计
1.6.1 串口驱动程序概要设计
对于本系统而言,需要实现两个虚拟串口用来接收GPS接收器的数据和驱动3G Modern上网。
1.6.2 Windows CE串口框架
1.6.3 HWOBJ结构体
ULONG BindFlags; // Flags controlling MDD behaviour. Se above.
DWORD dwIntID; // Interrupt Identifier used if THREAD_AT_INIT or THREAD_AT_OPEN
1.6.4 HW_VTBL结构体
PVOID (*HWInit)(ULONG Identifier, PVOID pMDDContext, PHWOBJ pHWObj);
BOOL (*HWPostInit)(PVOID pHead);
ULONG (*HWDeinit)(PVOID pHead);
ULONG (*HWClose)(PVOID pHead);
INTERRUPT_TYPE (*HWGetIntrType)(PVOID pHead);
ULONG (*HWRxIntrHandler)(PVOID pHead, PUCHAR pTarget, PULONG pBytes); copyright sql163.com
VOID (*HWTxIntrHandler)(PVOID pHead, PUCHAR pSrc, PULONG pBytes);
VOID (*HWModemIntrHandler)(PVOID pHead);
VOID (*HWLineIntrHandler)(PVOID pHead);
ULONG (*HWGetRxBufferSize)(PVOID pHead);
BOOL (*HWPowerOff)(PVOID pHead);
BOOL (*HWPowerOn)(PVOID pHead);
VOID (*HWClearDTR)(PVOID pHead);
VOID (*HWSetDTR)(PVOID pHead);
VOID (*HWClearRTS)(PVOID pHead); copyright sql163.com
VOID (*HWSetRTS)(PVOID pHead);
BOOL (*HWEnableIR)(PVOID pHead, ULONG BaudRate);
BOOL (*HWDisableIR)(PVOID pHead);
VOID (*HWClearBreak)(PVOID pHead);
VOID (*HWSetBreak)(PVOID pHead);
BOOL (*HWXmitComChar)(PVOID pHead, UCHAR ComChar);
ULONG (*HWGetStatus)(PVOID pHead, LPCOMSTAT lpStat);
VOID (*HWGetModemStatus)(PVOID pHead, PULONG pModemStatus);
VOID (*HWGetCommProperties)(PVOID pHead, LPCOMMPROP pCommProp);
VOID (*HWPurgeComm)(PVOID pHead, DWORD fdwAction);
BOOL (*HWSetDCB)(PVOID pHead, LPDCB pDCB);
BOOL (*HWSetCommTimeouts)(PVOID pHead, LPCOMMTIMEOUTS lpCommTO);
BOOL (*HWIoctl)(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
为保障对系统架构的清晰认识,我们将MDD的代码和PDD的代码分开进行分析。
1.6.5 串口MDD部分
1. COM_Init
2. COM_Deinit
3. COM_Open
typedef struct __HW_OPEN_INFO {
PHW_INDEP_INFO pSerialHead; // @field Pointer back to our HW_INDEP_INFO
DWORD AccessCode; // @field What permissions was this opened with sql163.com
DWORD ShareMode; // @field What Share Mode was this opened with
DWORD StructUsers; // @field Count of threads currently using struct.
COMM_EVENTS CommEvents; // @field Contains all in…. handling
LIST_ENTRY llist; // @field Linked list of OPEN_INFOs
} HW_OPEN_INFO, *P HW_OPEN_INFO;
事实上这里主要是对所需的数据结构进行处理,对于硬件的具体操作都留给PDD去做了,MDD所维护的仅仅是一个架构性的代码。Open操作完成后,驱动就进入了工作状态这个时候。
4. COM_Close
5. StartDispatchThread/StopDispatchThread
6. SerialDispatchThread/ SerialEventHandler
typedef struct __RX_BUFFER_INFO {
ULONG Read; /* @field Current Read index. */
ULONG Write; /* @field Current Write index. */
ULONG Length; /* @field Length of buffer */
BOOL DataAvail; /* @field BOOL reflecting existence of data. */
PUCHAR RxCharBuffer; /* @field Start of buffer */
CRITICAL_SECTION CS; /* @field Critical section */
} RX_BUFFER_INFO, *PRX_BUFFER_INFO;
typedef struct __TX_BUFFER_INFO {
DWORD Permissions; /* @field Current permissions */
ULONG Read; /* @field Current Read index. */
ULONG Length; /* @field Length of buffer */
PUCHAR TxCharBuffer; /* @field Start of buffer */
CRITICAL_SECTION CS; /* @field Critical section */
} TX_BUFFER_INFO, *PTX_BUFFER_INFO;
7. COM_Read
8. COM_Write
9. COM_PowerUp/ COM_PowerDown
这两个函数的调用都由CE的电源事件来引发,MDD并没有对这两个函数进行处理,仅仅是将其传递给PDD。
10. COM_IOControl
该函数用于实现向设备发送命令的功能。由于代码本身没有什么流程或逻辑性可言,全都是单独的实现,下面就用列表部分命令字及其说明。
l IOCTL_SERIAL_SET_BREAK_ON:中断(暂停)serial当前的发送或是接收,具体实现在PDD中。
l IOCTL_SERIAL_SET_BREAK_OFF:从中断(暂停)状态恢复,具体实现在PDD中。
l IOCTL_SERIAL_SET_DTR:将DTR引线拉高,直接调用PDD实现。
l IOCTL_SERIAL_CLR_DTR:将DTR引线拉低,直接调用PDD实现。
l IOCTL_SERIAL_SET_RTS:将RTS引线拉高,直接调用PDD实现。
l IOCTL_SERIAL_CLR_RTS:将RTS引线拉低,直接调用PDD实现。
l IOCTL_SERIAL_SET_XOFF:软件流模式下中止数据发送(Xflow控制)。
l IOCTL_SERIAL_SET_XON:软件流模式下启动数据发送(XFlow控制)。
l IOCTL_SERIAL_GET_WAIT_MASK:获取当前的事件对象。
l IOCTL_SERIAL_SET_WAIT_MASK:等待与提供的事件相同的事件发生。
1.6.6 串口驱动PDD部分
1. MDD到PDD变换层
在HW_VTBL结构体中,共有32个函数指针,分别用来指向串口驱动PDD层的物理设备的32个函数。由此可见编写串口PPD层驱动程序实际上就是需要实现HW_VTBL结构体中的32个函数。
如何构建MDD层这样的HW_VTBL对象了,如程序清单 5‑42代码所示,其中SerInit、SerPostInit、SerDeinit等变量都是函数已经实现的函数指针。
获得HW_VTBL结构体只是HWOBJ的一部分,接下需要构建HWOBJ整体,如程序清单 5‑43代码所示;
pSerObj=(PHWOBJ)LocalAlloc( LPTR ,sizeof(HWOBJ) );
pSerObj->BindFlags = THREAD_IN_PDD; // PDD create thread when device is first attached.
pSerObj->pFuncTbl = (HW_VTBL *) &IoVTbl; // Return pointer to appropriate function
ULONG Identifier, // @parm Device identifier.
PVOID pMddHead, // @parm First argument to mdd callbacks.
PHWOBJ pHWObj // @parm Pointer to our own HW OBJ for this device
DEBUGMSG (ZONE_CLOSE,(TEXT("+SerInit, 0x%X/r/n"), Identifier));
CSerialPDD * pSerialPDD = NULL;
DWORD dwIndex= pHWObj->dwIntID;
pSerialPDD = CreateSerialObject((LPTSTR)Identifier,pMddHead, pHWObj,dwIndex);
DEBUGMSG (ZONE_CLOSE,(TEXT("-SerInit, 0x%X/r/n"), pSerialPDD));
接下来看看MDD层调用SerOpen()函数时,最终怎样调用PDD层类中的Open方法,如程序清单 5‑45所示;
PVOID pHead /*@parm PVOID returned by Serinit. */
DEBUGMSG (ZONE_OPEN, (TEXT("SerOpen (%X)/r/n"),pHead));
CSerialPDD * pSerialPDD = (CSerialPDD *)pHead;
DEBUGMSG (ZONE_OPEN, (TEXT("-SerOpen(%X) return %d /r/n"),pSerialPDD,bReturn));
2. 串口PDD类
class CPdd16550 : public CSerialPDD, public CMiniThread {
CPdd16550 (LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj);
virtual BOOL CreateHardwareAccess();
// Power Manager Required Function.
virtual void SerialRegisterBackup() { m_pReg16550->Backup(); };
virtual void SerialRegisterRestore() { m_pReg16550->Restore(); };
// Implement CPddSerial Function.
virtual DWORD ThreadRun(); // IST
virtual BOOL InitXmit(BOOL bInit);
virtual void XmitInterruptHandler(PUCHAR pTxBuffer, ULONG *pBuffLen);
virtual void XmitComChar(UCHAR ComChar);
virtual BOOL EnableXmitInterrupt(BOOL bEnable);
virtual DWORD GetWriteableSize();
virtual BOOL InitReceive(BOOL bInit);
virtual ULONG ReceiveInterruptHandler(PUCHAR pRxBuffer,ULONG *pBufflen);
virtual ULONG CancelReceive();
virtual BYTE GetWaterMarkBit();
virtual BOOL InitModem(BOOL bInit);
virtual void ModemInterruptHandler() { GetModemStatus();};
virtual ULONG GetModemStatus();
virtual void SetDTR(BOOL bSet);
virtual void SetRTS(BOOL bSet);
virtual BOOL InitLine(BOOL bInit) ;
virtual void LineInterruptHandler() { GetLineStatus();};
virtual void SetBreak(BOOL bSet) ;
virtual BOOL SetBaudRate(ULONG BaudRate,BOOL bIrModule) ;
virtual BOOL SetByteSize(ULONG ByteSize);
virtual BOOL SetParity(ULONG Parity);
virtual BOOL SetStopBits(ULONG StopBits);
1.6.7 S3C2440A 串口驱动程序实现
程序清单 5‑47 CReg2440Uart类和CPdd2440Uart定义
CReg2440Uart(PULONG pRegAddr);
virtual ~CReg2440Uart() { ; };
// We do not virtual Read & Write data because of Performance Concern.
void Write_ULCON(ULONG uData) { WRITE_REGISTER_ULONG( m_pReg, (uData)); };
ULONG Read_ULCON() { return (READ_REGISTER_ULONG(m_pReg)); } ;
void Write_UBRDIV(ULONG uData) { WRITE_REGISTER_ULONG( m_pReg + 10, uData );};
ULONG Read_UBRDIV() { return READ_REGISTER_ULONG(m_pReg + 10); };
virtual BOOL Write_BaudRate(ULONG uData);
PULONG GetRegisterVirtualAddr() { return m_pReg; };
class CPdd2440Uart: public CSerialPDD, public CMiniThread {
CPdd2440Uart (LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj);
virtual BOOL CreateHardwareAccess();
// Power Manager Required Function.
virtual void SerialRegisterBackup() { m_pReg2440Uart->Backup(); };
virtual void SerialRegisterRestore() { m_pReg2440Uart->Restore(); };
// Implement CPddSerial Function.
virtual DWORD ThreadRun(); // IST
virtual BOOL InitXmit(BOOL bInit);
virtual DWORD GetWriteableSize();
virtual BOOL InitReceive(BOOL bInit);
virtual void SetDTR(BOOL bSet) {;};
virtual void SetRTS(BOOL bSet);
virtual BOOL InitLine(BOOL bInit) ;
virtual void SetBreak(BOOL bSet) ;
virtual BOOL SetBaudRate(ULONG BaudRate,BOOL bIrModule) ;
CReg2440Uart * m_pReg2440Uart;
volatile S3C2440A_INTR_REG * m_pINTregs;
void ClearInterrupt(DWORD dwInt) {
m_pINTregs->SUBSRCPND = (dwInt<
1.7 GPS驱动程序设计
1.7.1 GPS驱动框架
if (m_lpUsbClientDevice && CSerialPDD::Init()) {
LPCUSB_INTERFACE lpTargetInterface = m_lpUsbClientDevice->GetTargetInterface();
while (dwEndpoints && lpEndpoint!=NULL) {
if (USB_ENDPOINT_DIRECTION_IN(lpEndpoint->Descriptor.bEndpointAddress)) {
if ((lpEndpoint->Descriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_INTERRUPT &&
USB_ENDPOINT_DIRECTION_IN(lpEndpoint->Descriptor.bEndpointAddress)) {
return(m_lpSerialDataIn!=NULL && m_lpSerialDataOut!=NULL);
USB初始化完成之后,创建了相应的虚拟串口,在本系统中该串口采用“COM1“命名,下图 5.31是整个GPS驱动的工作流程,也是GPS驱动的实现过程。
1.7.2 GPS驱动加载过程
编译获得USBSER.DLL驱动文件之后,加载到系统中即可使用,如下介绍具体的加载过程。
第一、添加注册表到platform.reg文件当中,该注册表信息如程序清单 5‑48所示;
[HKEY_LOCAL_MACHINE/Drivers/USB/LoadClients/Default/0_0_0/255_0_0/PL_USBSER_Driver]
[HKEY_LOCAL_MACHINE/Drivers/Active/33]
[HKEY_LOCAL_MACHINE/Drivers/USBSER]
"DeviceArrayIndex"=dword:00000001
第二、将USBSER.DLL文件拷贝到BSP目录下Files文件夹中,并将如程序清单 5‑49代码加入到platform.bib文件当中。
USBSER.dll $(_FLATRELEASEDIR)/USBSER.dll NK SH
Windows CE嵌入式导航系统研究(内核相关)相关推荐
- Windows CE嵌入式系统程序开发
<Windows CE嵌入式系统程序开发> 基本信息 作者: 胡文 冯剑 姜海涛 胡玥 丛书名: 单片机与嵌入式丛书 出版社:机械工业出版社 ISBN:978711 ...
- 《Windows CE嵌入式开发入门——基于Xscale架构》 第9章 Windows CE BSP及驱动程序结构分析
9.1 Windows CE驱动程序结构概述 Windows CE的驱动程序可以从多种角度进行区分. 1.从加载以及接口方式来区分 可以分为本机设备驱动(Built-In Driver).可加载驱动 ...
- 《Windows CE嵌入式开发入门——基于Xscale架构》 第7章 Windows CE体系结构
7.1 层次式架构 1.系统分层模型 操作系统(包括应用环境.操作环境等)一般具有分层的结构特征,典型的就是UNIX系统的同心环,最里面是硬件,从里向外依次是kernel.共享函数库.应用程序3个层 ...
- 《Windows CE嵌入式开发入门——基于Xscale架构》第4章 外设控制器
PXA255具有丰富的外设接口,如LCD控制器.I2S控制器和UART控制器等,可以实现丰富的人机接口以及数据输入输出. 4.1 LCD控制器 LCD控制器的功能是产生显示驱动信号,驱动LCD显示器 ...
- 《Windows CE嵌入式开发入门——基于Xscale架构》第2章 系统时钟
电子系统中,时钟是一个关键的要素,尤其在手持设备中,时钟系统的设计与系统的性能和功耗有直接关系.PXA255提供了丰富的时钟系统的控制能力,能有效地实现性能和功耗的平衡. 2.1 实时时钟RTC 在 ...
- Windows CE在嵌入式工业中的应用思考
随着应用对象的扩大和微电子技术.软件技术的发展,嵌入式系统逐渐从单片机发展到高性能嵌入式微处理器和嵌入式操作系统.本文详细分析Windows CE 3.0的实时性.通用性.模块化.Win32兼容等性能 ...
- Windows CE在嵌入式工业控制系统中的应用思考
随着应用对象的扩大和微电子技术.软件技术的发展,嵌入式系统逐渐从单片机发展到高性能嵌入式微处理器和嵌入式操作系统.本文详细分析Windows CE 3.0的实时性.通用性.模块化.Win32兼容等性能 ...
- Windows CE的学习路线
Q:什么是嵌入式系统? 国际电气和电子工程师协会(IEEE)对嵌入式系统的定义是这样的:嵌入式系统是"控制.监视或者辅助设备.机器和车间运行的装置"(Devices used ...
- Windows CE
WindowsCE是微软公司嵌入式.移动计算平台的基础,它是一个开放的.可升级的32位嵌入式操作系统,是基于掌上型电脑类的电子设备操作系统,它是精简的Windows 95,Windows CE的图形用 ...
最新文章
- 华为的研发给我们什么启示?
- SpringSecurity案例之oauth2认证所需资源说明
- 想要设计自己的微服务?看这篇文章就对了 1
- 传奇霸业维护服务器,37传奇霸业8月18日部分区服维护计划
- Weblogic(4)—— Linux环境Weblogic12c配置节点管理(nodemanage.properties)来开启应用服务器(server)及线程池配置...
- 某安全服务商发布会总结.md
- 简述数学建模的过程_数学建模研究过程指导(精编版) Part IV
- magento smtp设置
- 译 - Cassandra 数据建模的基本规则
- 基于树莓派的Data Matrix decode
- 2014年11月份工作日志 模板样例
- 东芝打印机共享怎么设置_东芝 e-studio181怎么设置网络打印机
- php 输入经纬度查询位置,php 根据实际地址获取对应的经纬度
- 互联网晚报 | 9月9日 星期四 | 8个电竞项目入选杭州亚运会;联想TruScale正式发布;国民养老保险公司获批筹建...
- web前段设计之痛:手机浏览器和pc浏览器的width:100%的自适应问题
- MFC中得到2个SYSTEMTIME时间差的函数
- 三点布光材质连接,做旧
- Android Studio 3.6 发布啦,快来围观
- 用MSN Cartoon做的卡通自画像
- 关于散列表的大小设定