搜索
您的当前位置:首页正文

s3c2440嵌入式开发平台操作系统和驱动开发文档

来源:榕意旅游网


s3c2440嵌入式开发平台操作系统和

驱动开发文档

目 录

一 前言 .................................................................................................. 3 二 s3c2440嵌入式开发平台介绍 ........................................................ 3 2.1硬件配置: .................................................................................... 3 2.2 s3c2440嵌入式开发平台移植的系统软件 ............................... 4 2.3 s3c2440嵌入式开发平台特点: .................................................. 4 三 基于s3c2440的bootloader开发和应用 ........................................ 7 3.1 Bootloader及u-boot简介 .......................................................... 7 3.2 u-boot系统启动流程 ..................................................................... 7 3.3 s3c2440开发平台的u-boot移植 ................................................. 8 3.4 常用uboot命令介绍 .................................................................. 10 四 Wince.net 4.2操作系统简介 ........................................................... 12 4.1 嵌入式系统 ................................................................................. 12 4.2 嵌入式操作系统与Windows CE ............................................... 12 4.3 从操作系统角度看Windows CE.net的主要功能 .................... 13 4.4 从开发角度看Windows CE.net的主要功能 ............................ 13 4.5 Windows CE.net操作系统模型 .................................................. 14 五 开发平台Platform Builder 4.2简介 ............................................... 16 5.1 PB的安装步骤.......................................................................... 16 5.2 Plarform Builder文件夹结构 ...................................................... 20

1

六 S3C2440嵌入式开发平台的WINCE开发流程 ......................... 22 6.1 安装Platform Builder 4.2 ........................................................... 22 6.2 安装Embeded Visual C++ .......................................................... 22 6.3 选择BSP ..................................................................................... 22 6.4 PB下WINCE的开发、定制 ..................................................... 23 七 基于s3c2440的WINCE驱动开发 ................................................ 33 7.1 wince驱动模型 ......................................................................... 33 7.2 2440一个驱动程序示例:基于WinCE的I2C驱动程序设计 ............................................................................................................. 42 八 基于2440的嵌入式应用程序开发以及加载至内核 .................... 45 8.1 主流嵌入式开发软件介绍 ......................................................... 45 8.2 基于s3c2440嵌入式开发平台的EVC开发 ............................ 46 8.3 把应用程序加入到WINCE内核 .............................................. 49 8.4 一些EVC下实用的WIN32函数 ............................................. 50 8.5 EVC下的调试工具 ..................................................................... 52 九 实现永久保存注册表数据 .............................................................. 53 十 Windows CE 下应用程序自动启动 ............................................... 54 10.1建立快捷方式 ............................................................................ 54 10.2 修改shell.reg文件 .................................................................... 55 十一 开机后直接运行您的程序而不显示Windows CE桌面 ............. 56 附录一 flash.c .................................................................................. 57 附录二 dm9000.c ............................................................................. 69

2

附录三 smdk2440nand.h ................................................................. 74

一 前言

本开发文档是基于s3c2440嵌入式开发平台所制定,主要从s3c2440的bootloader开发、应用以及windows ce.net 4.2在该平台的移植、基于本平台的驱动开发介绍以及基于该平台的一些开发技巧。通过这个文档,用户可以详细的了解整个嵌入式的开发流程以及掌握其中关键的技术。 本文档的所有内容都是已经或者即将应用到s3c2440嵌入式平台上的,给出的程序以及方法也已经通过了测试,用户可以在这个基础上实现自己所需要的功能,当然,其中也难免会有一些错误,如果用户发现错误可以及时和我们联系。

二 s3c2440嵌入式开发平台介绍

S3C2440嵌入式开发平台主板采用当今流行的 ARM920T 内核的 SAMSUNG S3C2440 嵌入

式芯片。

2.1硬件配置:

●CPU:采用SAMSUNG S3C2440 ARM920t CPU,主频499MHZ,最高可达到533MHZ 集成有串口,SD卡控制器,USB Host 控制器,LCD控制器Nand Flash控制器,工业控制总线(CAN),camera控制器(数码摄像机接口),实时时钟等多种功能 ● 存储器: 64M SDRAM 32K FRAM(铁电存储器), 减少对FLASH频繁操作,延长FLASH寿命,同时防止掉电时丢失数据。

64M Nand Flash 用于存放操作系统(Windows CE和linux文件系统)以及应用程序 SD卡控制器

2M Nor Flash 用于存放bootloader 及kernel(linux) ●SM501 2D加速显卡:实现2D加速功能,显著提升显示效果,最高分辨率1024X768X32, 最高刷新率85HZ。有一个VGA、和二个TFT输出,支持触摸屏。 ●以太网控制器(10/100M):采以DM9000作为以太网控制器

●串行接口:采用S3C2440的五个串行口,其中两个接口为RS232电平,其它三个在总线插槽。

●三个USB Host V1.1接口,直接采用S3C2440的USB接口 ●一个音频输入输出接口(MIC, PHONE) ●两个CAN总线接口,可用于工业控制。

●电源失效信号输入接口,提供电源失效处理功能。 ●JTAG调试接口。

●电源输入12/5V, 功耗5W。

●一个camera 控制器,可接数码摄像头功能,进行视频采集 ●一个PCMCIA接口,可接无线网卡或调制解调器及其它外设

3

●一个96针总线插槽引出CPU的LOCAL BUS,可外接其它总线设备。

S3C2440嵌入式开发平台主板图:

2.2 s3c2440嵌入式开发平台移植的系统软件

●WINCE

WINCE.Net4.2 操作系统 。

开发的windows API 与桌面系统的windows API有较好的一致,这使得桌面应用软件可以方便快捷的移植到这款嵌入式WINCE 系统开发平台上。 ●LINUX

内核2.4.19,QT嵌入式GUI平台

●实现相应LINUX, WINCE平台下的硬件驱动程序 因具有2D显卡, 所以很好地实现了多媒体应用,实现全屏(1024×768×32)播放VCD、MPEG4等不同格式的媒体文件

2.3 s3c2440嵌入式开发平台特点:

s3c2440嵌入式开发平台是一台真正的工业控制电脑的主机系统,可以使用WINCE或LINUX操作系统。

1、 可以连接键盘,鼠标,USB移动硬盘,U盘,SD卡,音响, MIC, 有2D加速显卡,实现完美的多媒体应用(没有显卡加速的开发板即使是533MHZ的CPU也不适合作640X480高分辨率的多媒体应用)。 2、 终端功能:

4

a. 登陆WINDOWS Terminal Server(终端服务器),可以用WINCE.NET 42 的RDP 实现或者LINUX Xwindow实现 windows图形终端。

b. 以Xwindow 方式登陆LINUX 服务器,以ARM-9533 带linux 做X终端 3、 有通用电视遥控功能,实现设备的移动输入。

4、 配有铁电存储器, 可以更好的保护FLASH,延长FLASH寿命, 并且可以做到掉电不掉数据, 大大简化电源设计。

5、 实现Windows网上邻居功能,可以方便利用网络资源。

6、 连接Internet功能,WINCE42 自带的IE,可以通过局域网或直接通过串口联接MODEM 实现拨号上网。

以上功能, 本公司在WINCE4.2的硬件和软件上都已完整实现。而使用LINUX操作系统, 同其它开发板相比, 另外实现了2D加速功能。

ARM系统本身具有开放、集成度高、尺寸小、可扩展性强、低功耗等特点。此块开发板适用于无线应用、数字家电、车载设备、通信设备、网络终端等应用场合。并具有相当的价格优势,有很好的性价比。

S3C2440嵌入式开发平台WINCE下应用程序图(分辨率1024X768X32X70):

WINCE上网

5

S3C2440嵌入式开发平台LINUX下应用程序图(分辨率1024X768X32X70):

6

三 基于s3c2440的bootloader开发和应用

在s3c2440开发平台中我们使用了功能最强大的uboot作为它的bootloader。下面我们从本平台的uboot开发以及应用两个方面进行介绍。

3.1 Bootloader及u-boot简介

Bootloader代码是芯片复位后进入操作系统之前执行的一段代码,主要用于完成由硬件启动到操作系统启动的过渡,从而为操作系统提供基本的运行环境,如初始化CPU、 堆栈、存储器系统等。Bootloader 代码与CPU 芯片的内核结构、具体型号、应用系统的配置及使用的操作系统等因素有关,其功能类似于PC机的BIOS程序。由于Bootloader和CPU及电路板的配置情况有关,因此不可能有通用的bootloader ,开发时需要用户根据具体情况进行移植。嵌入式Linux系统中常用的bootloader有armboot、redboot、blob、u-boot等,其中u-boot是当前比较流行,功能比较强大的bootloader,可以支持多种体系结构,但相对也比较复杂。Bootloader的实现依赖于CPU的体系结构,大多数bootloader都分为stage 1和stage2两大部分。Bootloader的基本原理见参考文献。u-boot是sourceforge网站上的一个开放源代码的项目。它可对 PowerPC MPC5XX、MPC8XX、MPC82XX、 MPC7XX、MPC74XX、ARM(ARM7、ARM9、StrongARM、VxWorks、NetBSD、QNX、RTEMS、ARTOS、LynxOS等,主要用来开发嵌入式系统初始化代码bootloader。软件的主站点是http://sourceforge.net/projects/u-boot。u-boot 最初是由denx的PPC-boot发展而来的,它对PowerPC系列处理器的支持最完善,对Linux操作系统的支持最好。源代码开放的U-boot软件项目经常更新。

3.2 u-boot系统启动流程

大多数bootloader都分为stage1和stage2两大部分,u-boot也不例外。依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。 3.2.1 stage1 (start.s代码结构)

u-boot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:

(1) 定义入口。由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。 (2)设置异常向量。

(3)设置CPU的速度、时钟频率及中断控制寄存器。 (4)初始化内存控制器 。

(5)将ROM中的程序复制到RAM中。 (6)初始化堆栈 。

(7)转到RAM中执行,该工作可使用指令ldr pc来完成。 3.2.2 stage2 C语言代码部分

lib_arm/board.c中的start_armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个uboot(armboot)的主函数,该函数主要完成如下操作: (1)调用一系列的初始化函数。 (2)初始化Flash设备。

(3)初始化系统内存分配函数。

7

(4)如果目标系统拥有NAND设备,则初始化NAND设备。 (5)如果目标系统有显示设备,则初始化该类设备。 (6)初始化相关网络设备,填写IP、MAC地址等。 (7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。

3.3 s3c2440开发平台的u-boot移植

3.3.1 下载uboot

可以从http://sourceforge.net/projects/u-boot下载u-boot最新版本,在linux中利用tar zvfx 命令解压即可。 3.3.2 安装交叉编译器

把我们提供的交叉编译器cross-compiler在linux的/opt目录下解压即可。 3.3.3 配置自己的主板

(1)阅读u-boot下的Makefile文件,在Makefile文件中添加两行: s3c2440_config:unconfig

@./mkconfig $(@:_config=)arm arm920t s3c2440

其中arm是CPU的种类,arm920t是arm cpu对应的代码目录,s3c2440是自己主板对应的目录。

(2)在board目录中建立s3c2440目录,复制smdk2440目录中的内容(cp smdk2440/* s3c2440),因为一般来说我们都应该找一个和我们开发平台相近的平台,在这个平台上进行修改,这样能缩短开发时间。

(3)在include/configs/目录下复制smdk2440.h(cp smdk2440.h s3c2440.h),因为smdk2440这个平台用的也是s3c2440的cpu,所以很多内容我们可以在这个平台上进行修改。这个头文件主要定义了cpu的寄存器的设置,一般不需要做什么修改。 (4)在Makefile中修改ARM编译器的目录名及前缀

我们的ARM编译器的目录是在:/opt/host/armv4l/bin/armv4l-unknown-linux- 把CROSS-COMPILE = arm-linux- 改为实际目录:如:

CROSS-COMPILE = /opt/host/armv4l/bin/armv4l-unknown-linux- (5)完成之后,可以测试一下你的配置: #make s3c2440_config #make

(6)编译如果在processor.h中出错,则修改processor.h中: union debug_insn {

u32 arm; u16 thumb; }

修改成:

union debug_insn {

u32 arm_mode; u16 thumb_mode; }

(7)编译成功,编译好的结果应该和smdk2440一样。

8

注:如果第一次修改后编译没有在processor.h中出错,而在examples目录中出错,认真检查以上修改(修改的内容不正确),并且在examples目录中运行touch命令,再编译。 3.3.4 生成最基本的u-boot (1)修改程序连接地址

在borad/s3c2440中有个config.mk文件,用于设置程序连接的起始地址,在这里我们设置了TEXT_BASE = 0x33E80000。

(2)为了以后能在uboot的GO命令执行修改过的用loadb或tftp下载的u-boot:在/board/s3c2440的memsetup.S中标记符”0:”上加上五句: mov r3, pc

ldr r4, =0xFFFF0000 and r3, r3, r4 add r0, r0, r3 add r2, r2, r3

3.3.5 AM28LV160DB NOR FLASH操作功能

我们使用的NOR FLASH是AM28LV160DB,可以在其他的平台下找到这个NOR FLASH的驱动,把找到的flash.c拷贝到s3c2440的目录中。flash.c的内容见附录一。

3.3.6 实现网卡功能

s3c2440使用的网卡是DM9000,在uboot中没有相应的驱动,我们自己编写了网卡的驱动。驱动源代码见附录二。

(1)把驱动拷到drivers/dm9000.c

(2)在drivers/Makefile中加入dm9000.o

(3)在lib_arm/board.c中修改CS89000=>DM9000 (4)在include/configs/s3c2440.h中加入以下几句

#define CONFIG_DRIVER_DM9000 1 /* 我们使用dm9000网卡 */ #define DM9000_BASE 0x10000300 /*网卡的基址为0x10000300 */ #define CONFIG_ETHADDR 20:04:12:01:00:01/*网卡的MAC地址*/ #define CONFIG_NETMASK 255.255.255.0/*网卡的子网掩码*/ #define CONFIG_IPADDR 192.168.0.215/*平台的IP地址*/ #define CONFIG_SERVERIP 192.168.0.1/*主机的IP地址*/ #define CONFIG_GATEWAYIP 192.168.0.1/*平台的网关地址*/ 3.3.7 实现NAND FLASH功能

(1)在include/configs/s3c2440.h中加入以下一句 #define CFG_CMD_NAND

(2)同样在include/configs/s3c2440.h中加入以下一句 #if (CONFIG_COMMANDS & CFG_CMD_NAND) #include \"smdk2440nand.h\"

#endif /* CONFIG_COMMANDS & CFG_CMD_NAND */ (3)smdk2440nand.h内容见附录三。 3.3.8 编译u-boot

在linux下进入uboot目录:

#make s3c2440_config #make

9

就会在uboot目录下得到u-boot.bin,这个就是我们需要的uboot二进制文件。 3.3.9 下载uboot

用SJF软件通过jtag口下载编译的u-boot。 3.3.10 引导WINCE

(1)利用ftfp把nk.nb0下载到平台,命令如下:

tftp 30100000 nk.nb0 (2)固化WINCE,命令如下:

nand clean 2400000 1c00000; nand write.jffs2 30100000 2400000 1b00000 (3)启动flash中固化好的WINCE,命令如下:

mw.l 30000000 0 c00000;nand read.jffs2 30100000 2400000 1b00000;go 30101000 (4)也可以不固化,用tftp下载nk.nb0后直接在RAM里面运行WINCE,命令如下: go 30101000

3.4 常用uboot命令介绍

Printenv 打印环境变量。 Uboot> printenv baudrate=115200 ipaddr=192.168.1.1

ethaddr=12:34:56:78:9A:BC serverip=192.168.1.5

Environment size: 80/8188 bytes Setenv 设置新的变量

Uboot> setenv myboard AT91RM9200DK Uboot> printenv baudrate=115200 ipaddr=192.168.1.1

ethaddr=12:34:56:78:9A:BC serverip=192.168.1.5 myboard=AT91RM9200DK

Environment size: 102/8188 bytes Saveenv 保存变量

命令将当前定义的所有的变量及其值存入flash中。用来存储变量及其值的空间只有8k字节,应不要超过。

Loadb 通过串口Kermit协议下载二进制数据。 Tftp 通过网络下载程序,需要先设置好网络配置 Uboot> setenv ethaddr 12:34:56:78:9A:BC Uboot> setenv ipaddr 192.168.1.1

Uboot> setenv serverip 192.168.1.254 (tftp服务器的地址) 下载bin文件到地址0x20000000处。 Uboot> tftp 20000000 application.bin (application.bin应位于tftp服务程序的目录) Uboot> tftp 32000000 vmlinux

把server(IP=环境变量中设置的serverip)中/tftpdroot/ 下的vmlinux通过TFTP读入到物理内存32000000处。 Md 显示内存区的内容。

10

Mm 修改内存,地址自动递增。 Nm 修改内存,地址不自动递增。 Mw 用模型填充内存

mw 32000000 ff 10000(把内存0x32000000开始的0x10000字节设为0xFF) Cp 拷贝一块内存到另一块 Cmp 比较两块内存区

这些内存操作命令后都可加一个后缀表示操作数据的大小,比如cp.b表示按字节拷贝。 Protect 写保护操作

protect on 1:0-3(就是对第一块FLASH的0-3扇区进行保护) protect off 1:0-3取消写保护 Erase 擦除扇区。

erase: 删除FLASH的扇区

erase 1:0-2(就是对每一块FLASH的0-2扇区进行删除) 对DataFlash的操作

U-Boot在引导时如果发现NPCS0和NPCS3上连有DataFlash,就会分配虚拟的地址给它,具体为 :

0xC0000000---NPCS0 0xD0000000---NPCS3 run 执行设置好的脚本

Uboot> setenv flashit tftp 20000000 mycode.bin\\; erase 10020000 1002FFFF\\; cp.b 20000000 10020000 8000 Uboot> saveenv Uboot> run flashit

bootcmd 保留的环境变量,也是一种脚本

如果定义了该变量,在autoboot模式下,将会执行该脚本的内容。 Go 执行内存中的二进制代码,一个简单的跳转到指定地址 Bootm 执行内存中的二进制代码

要求二进制代码为制定格式的。通常为mkimage处理过的二进制文件。 起动UBOOT TOOLS制作的压缩LINUX内核, bootm 3200000 Bootp 通过网络启动,需要提前设置好硬件地址。 ? 得到所有命令列表

help help usb, 列出USB功能的使用说明 ping 注:只能开发板PING别的机器 usb

usb start: 起动usb 功能 usb info: 列出设备

usb scan: 扫描usb storage(u 盘)设备 kgo 起动没有压缩的linux内核 kgo 32000000

fatls 列出DOS FAT文件系统

fatls usb 0列出第一块U盘中的文件 fatload 读入FAT中的一个文件

fatload usb 0:0 32000000 aa.txt 把USB中的aa.txt 读到物理内存0x32000000处! flinfo 列出flash的信息

11

nfs

nfs 32000000 192.168.0.2:aa.txt

把192.168.0.2(LINUX 的NFS文件系统)中的NFS文件系统中的aa.txt 读入内存0x32000000处

四 Wince.net 4.2操作系统简介

4.1 嵌入式系统

嵌入式系统是不同于常见计算机系统的一种计算机系统,它不以独立设备的物理形态出现,即它没有一个统一的外观,它的部件根据主体设备以及应用的需要嵌入在设备的内部,发挥着运算、处理、存储以及控制作用。从体系结构上看,嵌入式系统主要由嵌入式处理器、支撑硬件和嵌入式软件组成。其中嵌入式处理器通常是单片机或微控制器;支撑硬件主要包括存储介质、通信部件和显示部件等;嵌入式软件则包括支撑硬件的驱动程序、操作系统、支撑软件以及应用中间件等。

可见,嵌入式系统是一个很大的概念,一旦嵌入式处理器和支撑硬件选定了,那么工作最多的就集中在嵌入式软件当中了。而嵌入式软件中的嵌入式操作系统部分和应用软件部分就成了重中之中。它们与通常说的操作系统与应用软件的概念是相似的,但也有区别。

4.2 嵌入式操作系统与Windows CE

嵌入式操作系统是与应用环境密切相关的,从应用范围角度来看,大致可以分为通用型的嵌入式操作系统如Windows CE、VxWorks、嵌入式Linux等和专用型的嵌入式操作系统如Palm OS、Symbian等。从实时性的角度看,大致可以分为实时嵌入式操作系统和一般嵌入式操作系统。从原理上说,嵌入式操作系统仍旧是一种操作系统,因此它同样具有操作系统在进程管理、存储管理、设备管理、处理器管理和输入输出管理几方面的基本功能,但是由于硬件平台和应用环境与一般操作系统的不同,那么它也有自身的特点,最大的特点就是可定制性,也就是它能够提供可配置或可剪裁的内核功能和其他功能,可以根据应用的需要有选择的提供或不提供某些功能以减少系统开销。

微软公司的Windows CE操作系统就是一种嵌入式操作系统,它1996年开始发布Windows CE 1.0版本,2004年7月发布了Windows CE .NET 5.0版本,目前用得最多的是Windows CE .NET 4.2版本,其发展速度也是很快的,功能上自不必描述,它的主要应用领域有PDA市场、Pcket PC、Smartphone、工业控制、医疗等。

现代的嵌入式操作系统同嵌入式操作系统的定制或配置工具紧密联系,构成了嵌入式操作系统的集成开发环境。就WinCE来讲,你无法买到WinCE这个操作系统,你买到的是Platform Builder for CE.NET 4.2的集成开发环境,我们也简称为PB,利用它你可以剪裁和定制出一个符合你自己需要的WinCE.NET 4.2的操作系统,因此,我们说的操作系统实际上完全是由自己定制出来的,这就是嵌入式操作系统最大的特点。

对于嵌入式的应用软件,通常就是指运行在嵌入式操作系统之上的软件了,这种软件由于不再针对常规的操作系统进行开发,因此很多如VB、VC++等开发工具就不方便使用了,那么就有专门的SDK或集成开发环境来提供这种开发需要。在WinCE操作系统上的应用软件开发,微软就提供了Embedded Visual Basic(简称EVB)、Embedded Visual C++(简称EVC)、Visual Studio.NET等工具,它们是专门针对CE操作系统的开发工具,EVB只支持到CE的3.0版本,目前用得最多的还是EVC,把你的CE操作系统中的SDK(软件开发包)导出然后

12

安装在EVC下,就可以变成专门针对你这种设备或系统的开发工具了。而VS.NET中的VB.NET和C#也提供了对以CE为操作系统的智能设备开发的支持,而且也很方便,但必须要求这些设备中提供了对微软的.NET Compact FrameWork的支持才行,如果使用的话就要看具体情况了。

以上说了这么多,还是举个例子吧。比如说我要做一台医疗仪器,那么我就要选择好嵌入式的硬件环境,然后定制出符合我需要的CE操作系统,利用这个系统导出SDK,然后利用EVC结合这个SDK来开发我的信号采集、处理和病情分析的应用程序,最后就形成了一台合适的利用嵌入式技术开发出的仪器了。

4.3 从操作系统角度看Windows CE.net的主要功能

从操作系统的角度看,Windows CE.net具有灵活的电源管理功能,包括睡眠/唤醒模式。在Windows CE.net中,还使用对象存储(Object Store)技术,包括文件系统、注册表以及数据库。它还具有很多高性能、高效率的操作系统特性,包括安需换页、共享存储、交叉处理同步、支持大容量堆(heap)等。

Windows CE.net具有良好的通信能力。它广泛支持各种通信硬件,也支持直接的局域网以及拨号连接,并提供与PC、内部网以及Internet的连接,包括用于应用级数据传输的设置至设备间的连接。在提供各种基本的通信基础结构的同时,Windows CE.net还提供与Windows 9x/NT的最佳集成和通信。

Windows CE.net的图形用户界面相当出色。它拥有基于Microsoft Internet Explorer 的Internet 浏览器,此外,还支持TrueType字体。开发人员可以利用丰富灵活的控件库在Windows CE.net环境下为嵌入式应用建立各种专门的图形用户界面。Windows CE.net甚至还能支持诸如手写体和声音识别、动态影像、3D图形等特殊应用。

Windows CE.net是一个多任务的操作系统,可以同时执行多个任务并在它们之间来回切换,这其实就是Windows的简化版本,可以通过我们熟悉的Windows操作方式来控制Windows CE.net,它也带有“我的文档”,也有很多软件如MediaPlayer、WordPad等。 Windows CE.net内置了很多多媒体功能,通过Windows Media Player可以播放音乐、视频。Windows CE.net具有可扩充的Compact Flash/SD 插槽,通过扩充卡可以实现多种功能,例如,网页浏览、无线接入或者增加更大的存储空间等。

4.4 从开发角度看Windows CE.net的主要功能

1)定制系统内核

Windows CE.net提供了一个工具Platform Builder,是Windows CE.net主要集成开发环境,通过这个工具可以很方便地根据不同的硬件,定制、裁剪出符合不同系统要求的Windows CE.net操作系统。 2)开发驱动程序

Windows CE.net本身提供了相当多的应用程序,以2410为例,Windows CE.net就提供了显卡、声卡以及基于x86系统CPU的支持代码等。但是对于一个完整的嵌入式系统,有的时候Windows CE.net自带的驱动程序并不能满足用户要求,或者没有所需的Windows CE.net下的驱动程序,这时就要用户自己编写相应的驱动程序。Windows CE.net开发工具Platform Builder就可以完成相应的驱动程序开发。 3)导出SDK

定制操作系统内核后,如果需要则可以通过PB生成自己的SDK,生成的SDK可以很容易安装到其他的开发环境中去(比如Embeded Visual C++)。这样就可以在新的开发环境中利用自己定制的SDK进行开发。

13

4)编写应用软件

Windows CE.net带的开发工具EVC以及PB均可以完成应用软件的开发工作,并且Windows CE.net提供了对COM/DCOM、Direct3D、DVD、Internet、IP电话等多种功能的支持,让应用软件的开发更加方便。 5)源代码的共享

Windows CE.net是一个有限开放源代码的软件,它对驱动程序、应用软件等均提供了一定数量的源代码,开发者可以利用这些代码来进行编程工作。

4.5 Windows CE.net操作系统模型

Windows CE.net操作系统的设计借鉴了Windows 2000/XP操作系统的设计,从体系结构上,它具有分层结构的特点,又具有微内核结构的特点。下面从分层结构和微内核结构的特点出发,分别介绍Windows CE.net的分层模型和体系结构组件模型,然后再分别介绍Windows CE.net的其他一些重要部分的结构模型。 1)分层模型

操作系统的分层模型主要特点就是将操作系统的功能按功能的调用次序分成若干层,各层之间只能单向依赖或者单向调用(在Windows系列操作系统中,一般只能相邻的上层才可以调用下层,但有时候也可以跨层访问)。这样就使功能模块之间的调用关系更加清晰。 Windows CE.net的分层模型不仅是考虑操作系统本身,而且是从一个嵌入式系统应用环境的角度来考虑,使系统具有更好的可扩展性和更清晰的结构,因为嵌入式操作系统的应用往往要经过定制,使操作系统适应特殊的应用环境。

Windows CE.net的分层设计,主要考虑了如下因素:

从接口的角度讲,Windows CE.net要具备面向应用开发和面向系统两个界面。这也是一般操作系统应该实现的两个层面。比如桌面的Windows操作系统,既有面向应用的SDK应用层界面,又有面向系统的DDK系统界面。应该有一个层次来实现硬件特点与操作系统本身特性的隔离,以便实现系统的移植。在以上两个层次之外,在底层是具体的硬件设备,在顶层就应该是具体的应用程序。

所以,根据以上原则,Windows CE.net操作系统的分层模型如下所示:

Internet Application MFC,ATL,COM/DCOM,.NET„ COMM,GWES,STORAGE„ CoreDLL,Schedule,Memory,Device„ BSPs,CSPs,Drivers„ Hardware 应用集成层 应用开发层 应用支持库 操作系统层 OEM适配层 硬件层

从上图中,具体来讲,操作系统的功能在中间两层,即在操作系统层和应用支持库层实现。应用支持库的上部和操作系统层的上部以及下部具有接口性质,他们构成了Windows CE.net的应用界面和系统界面。根据以上的理解,OAL层实现的应该是系统界面。同时它也集中了所有的硬件特性,使系统便于迁移。最底层使具体的硬件,最顶层是应用集成层。这里并没有把应用程序层单独列出来,是因为在嵌入式系统中,应用程序层有时是很模糊的,嵌入式系统的应用程序往往和硬件捆绑在一起销售,所以他们本身也是构成应用集成层的实体。

从各层提供者角度来讲,硬件层和OEM适配层由硬件厂商提供;操作系统层、应用支持

14

库、应用开发层由微软提供;应用集成层由软件开发商提供。当然这个划分并不是绝对的,很多时候是相互交叉的,比如有时作为开发人员也要对硬件厂商提供的驱动进行二次开发,或编写自己的驱动;为了要达到更好的效果,有时也要对应用支持库进行相应的修改等。 2)组件模型

Windows CE 是由许多离散模块构成的,每一模块都提供特定的功能。这些模块中的一部分被划分成组件。组件使 Windows CE 变得非常紧凑(只占不到 200 KB 的 RAM),因此只占用了运行设备所需的最小的ROM、RAM 以及其它硬件资源。 Windows CE 包含提供操作系统最关键功能的 4 个模块:内核模块;对象存储模块;图形、窗口和事件子系统 (GWES) 模块以及通信模块。Windows CE 还包含一些附加的可选择模块,这些模块可支持的任务有管理可安装设备驱动程序、支持 COM 等。

 内核

内核是 OS 的核心,通过 Coredll 模块表示。它提供在所有设备中都出现的基本操作系统功能。内核负责内存管理、进程管理以及特定文件管理等功能。它还管理虚拟内存、调度、多重任务处理以及例外处理等。

Windows CE 的任何配置都需要用到 Coredll 模块的大多数组件。有一些内核组件是可选的,只有在涉及系统功能操作时,才需要这些组件,例如电话技术、多媒体技术以及图形设备接口(GDI)技术等。

 对象存储

Filesys 模块支持Windows CE 对象存储 API 函数。对象存储所支持的永久性存储器的类型如下表所示。

存储器类型----------------------------------说明

文件系统-------------------------------------包含应用程序和数据文件

系统注册表----------------------------------存储应用程序必须快速访问的系统配置信息以及其它任何信息

Windows CE 数据库------------------------提供结构化存储 对象存储可将用户数据和应用程序数据存入文件或注册器。在操作系统构造进程(该进程中只包括那些必需选项)的过程中,对于这些不同的对象存储组件,可以选取,也可以忽略。

 GWES(图形、窗口、事件系统)

GWES 是用户、应用程序和 OS 之间的图形用户接口。GWES 通过处理键盘、笔针动作来接受用户输入,并选择传送到应用程序和OS 的信息。GWES 通过创建并管理在显示设备和打印机上显示的窗口、图形以及文本来处理输出。

GWES 的中心是窗口。所有应用程序都需要窗口以接收来自 OS 的消息,即使那些为缺少图形显示的设备创建的应用程序也是如此。GWES 提供控制器、菜单、对话框以及图形显示的设备资源,还提供 GDI 以控制文本与图形显示。

 通信

通信组件提供对下列通信硬件和数据协议的支持: · 串行 I/O 支持

· 远程访问服务(RAS)

· 传输控制协议/ Internet 协议 (TCP/IP) · 局域网 (LAN)

· 电话技术 API (TAPI) · Windows CE 的无线服务  可选组件

15

除上述主要模块之外,还可使用其它的操作系统模块。这些模块与组件主要有: · 设备管理器和设备驱动程序 · 多媒体(声音)支持模块 · COM 支持模块

· Windows CE 外壳模块

Windows CE 提供的每一模块或组件都支持一组可用的相关 API 函数。

五 开发平台Platform Builder 4.2简介

为了定制CE操作系统,微软公司为我们提供了Platform Builder(简称PB,但与Power Builder的数据库开发工具截然不同)的集成开发环境,下面我们就来认识一下这个开发环境并利用它生成一个CE操作系统。

先来说说PB的购买。PB是商业软件,用户需要向微软在中国的各代理机构联系购买,价格大约在10000~15000之间,可以向各代理机构查询。如果出于非商业目的可以到微软公司的网站上下载其评估版,这个版本好像只支持CEPC的体系结构,无法用在商业开发当中。我们所用的PB是从微软购买的正版软件,版本号是4.2,所以可能会和评估版有些差异,因本人没有使用过评估版,因此具体细节就不得而知了。

5.1 PB的安装步骤

PB的安装很简单,和通常Windows下的软件安装方法是一样的,只要插入安装光盘就会自动安装,或者手动执行光盘中的setup.exe安装程序即可。在安装过程中需要强调的有两点,一是要选择我们实际要用到的体系结构,CE支持x86、ARM、RISC等很多种体系结构的嵌入,你要根据你的需要来选择,不要多选,很占用硬盘空间,也不要少选或错选导致以后用到的时候要重新安装,在这里我们用的是ARMV4、ARMV4I和其模拟器;二是尽量把操作系统文件夹即WINCE420文件夹放在非引导分区,即如果你的硬盘的引导区是C盘,那么WINCE420最好放在其他空间较大的盘中,防止将来引导分区损坏重装系统时把你作的CE系统也给格式化掉,因为WINCE420文件夹下的东西就是CE系统的组件和你自己做的CE系统。 PB安装好后就可以运行它了,我们先来看一下它的窗口组成。

16

怎么样,是不是和VC++的样子差不多?没错,左侧是Workspace窗口,它将包含你自己的CE系统,中间是启动窗口,没什么具体用处,右侧是Catalog窗口,是PB提供的可供你选择使用的CE内核组件包,你只要把其中你需要的组件选到左侧的Workspace窗口然后编译,那么就可以得到你自己的CE系统了,就是这么简单直观。底部是Output窗口,是编译、调试、日志、查找的输出窗口,和VC++的一样。

接下来我们就先在模拟器上生成我们自己的CE系统。 我们已经看到了在屏幕中间有个“New Platform”按钮,也可以在“File”菜单下发现这一命令,我们就从单击这个按钮开始。 有人已经猜想出会出现什么了,没错,就是一个微软惯用的向导。 第一步,是一屏介绍,告诉你该向导可以帮你作什么,直接点击“Next”;

第二步,是让你选择BSP,所谓BSP即板支持包,是由主板厂家提供给你的CE组件,以x86为例,有三种选择,CEPC是在x86架构上以PC机的硬件为基础的BSP;EMULATOR是在x86架构上的模拟器,即在PC机上用软件模拟一个硬件平台,如果我们手中没有硬件平台那么就可以利用这个模拟器在自己的PC机上模拟一个平台,当然功能是有限的;NATIONAL GEODE是利用GEODE微处理器的x86结构,如果你使用的CPU是这种那么就应该先则此项BSP。此处我们就先选择模拟器吧。

第三步,配置你的平台,如下图所示:

17

可以在可用的配置中选择一项如数字多媒体设备、企业终端、移动手机等,也可以选择自定义配置,同时选择你的系统的存储路径和平台名称,这里我们就先选一个Internet Appliance,放在默认路径,平台名称就叫Hello吧。

第四步,配置你想包含在你系统中的应用程序和多媒体选项,例如是否包含对.NET Compact Frameworks的支持,是否包含IE浏览器,是否包含MP3播放或MPEG-4播放等等,此处取默认值。

第五步,像第四步一样配置网络通讯组件,可以配置局域网、个人网络、远程桌面连接等。

18

第六步,完成定制界面。

至此,我们自己的第一个CE操作系统就定制完成了,接下来要做的就是编译了,通过“Build”菜单下的“Build Platform”或“Rebuild Platform”命令来进行,注意在编译之前通过“Build”菜单下的“Set Active Configuration”来确定是编译Debug版还是Release版,编译的时间比较长,一般要10分钟以上,看你选择的组件的情况了。

好了,编译完成了,我们来看一下WINCE420\\Public\\Hello文件夹下的变化,在RelDir文件夹下分别有Debug和Release版本的文件夹,我们到Release文件夹下可以发现很多编译过程中和编译后的文件,找到NK.bin文件,它就是我们的CE系统打包后的文件了,也就是我们最后要的CE系统。

接下来我们来利用模拟器运行一下我们自己的系统吧。

我们首先执行“Target”菜单下的“Configure Remote Connection”命令,在弹出的对话框中配置如下:

19

然后运行“Target”菜单中的“Download/Initialize”命令把生成的操作系统下载到设备中去并初始化设备,在这里我们上图已经配置过了,就是下载到模拟器中去。在此期间弹出的对话框都可以选择“Yes”,最后我们就看到了如图所示的系统模拟器界面,这个就是我们自己定制的Hello的CE系统。

对于实际的嵌入式设备我们需要利用串口或者网口与之相连接,然后在上一步的配置中设定好传输协议,比如网口的话就需要设定好主机的ip等,这样可以通过Download/Initialize把定制好的NK.bin下载到目标板上面去,一般目标板上会接有VGA或者LCD,通过显示器或者LCD也可以看到如下的Windows CE.net启动画面。

如果运行完成,那么除了关闭模拟器窗口以外,还要执行“Target”菜单下的“Disconnect”命令断开与设备的连接。

5.2 Plarform Builder文件夹结构

这里所述的文件夹结构,不是WinCE系统的文件夹结构,而是集成开发环境Platform Builder的文件夹结构,该文件夹结构十分复杂,共有3万多个文件,2400多个子文件夹,如果不清楚的话在以后的开发过程当中将会带来很大的麻烦,也时甚至会发生找不到文件的现象。对于这个文件夹结构,我们可以大体上分成两个部分,一个是PB的安装文件夹,一个是CE文件夹,下面我们就分别来了解一下。

先来看PB的安装文件夹。该文件夹一般装在系统盘的Program Files\\Windows CE Platform Builder\\4.20文件夹下,文件夹结构如下图所示:

20

在这个文件夹结构中,cec文件夹是很重要的,它是包组件文件(.CEC文件)的安放位置,在PB安装以后这里面包含了很多标准的操作系统组件、设备驱动程序组件、板支持包组件、平台管理组件等,如果用户想要扩展组件的话,只需要把相应的CEC文件安放在这个文件夹中即可,因此它是系统组件的配置文件所在的文件夹。

Utilities文件夹中包含的是一个有用的工具,通过它可以生成系统的启动盘,从而可以引导我们自己定制的CE操作系统。

Wcetk文件夹中包含的是另一个有用的工具,通过它可以测试CE的性能。 接下来我们看一下CE的文件夹,其结构如下图所示:

其中PLATFORM文件夹下存放的是与具体平台相关的程序,当你修改某一平台的内核时就要到具体的平台所在的文件夹下去修改,比如EMULATOR平台即模拟器的KERNEL部分,那就要到EMULATOR的文件夹下改其KERNEL子文件夹下的源程序。

SDK文件夹包含了PB在编译时用到的如LINK.exe等程序,如果我们需要手工编译些什么东西那么可以到这个文件夹下来找相应的工具程序。

PUBLIC文件夹下是各平台要用到的公共的源程序,也是子文件夹最多的一个文件夹,它的结构如下:

21

其中大部分都是系统组件的源程序比如IE、SHELL,如果我们想要修改某个组件的行为就可以到相应的文件夹下去找。

用得最多的是其中的COMMON文件夹,在该文件夹下的SDK文件夹下的SAMPLES子文件夹中有一些示范样例程序,比如大键盘的输入法的源程序等,我们可以更改这些源程序。在该文件夹下的OAK文件夹中的CSP文件夹为CPU支持组件,里面的各子文件夹都是针对特定的CPU的内容,比如针对ARM、I486、SA11X1等,如果我们需要处理和特定CPU相关的部分就可以到此文件夹下来操作。在该文件夹下的DRIVERS文件夹为微软做好的各种典型设备的驱动程序的源程序,比如1394的驱动、网卡的驱动、串口的驱动等,如果我们想要修改驱动或重新驱动,都可以以这个文件夹下的源程序做参考。

说了这么多文件夹结构,那么如此复杂的文件夹结构在编译CE操作系统的时候是如何组织起来的呢,编译器是如何找到所需要的文件的,又是由谁来告诉编译器如何编译的呢?这些问题的答案就是DIRS文件和SOURCES文件,顾名思义,一个是负责连接各级文件夹的,一个是负责编译选项的。

DIRS文件是位于需要编译的文件夹中的一个特殊的文本文件,它指出了要编译的源程序所在的文件夹名。

SOURCES文件是位于源程序文件夹中的一个特殊的文本文件,它包含了一些宏定义,编译程序就是利用这些宏定义作为选项来决定如何编译和链接这些源程序的。

我们可以自己用记事本工具来打开这样的文件看看,如果想了解具体每个宏的含义请参阅PB的帮助文档。

六 S3C2440嵌入式开发平台的WINCE开发流程

6.1 安装Platform Builder 4.2

在进行WINCE开发前必须先安装WINCE的开发平台Platform Builder,我们使用的是PB4.2版本,这个版本也是现在最成熟的WINCE开发版本,开发出来的WINCE.NET能满足基本上所有的需求。在这个平台上可以定制WINCE,编写驱动代码以及应用程序,换句话说,利用PB4.2就可以完成所有的WINCE的开发。

6.2 安装Embeded Visual C++

在PB4.2中不支持MFC程序,所以如果你已经习惯了利用MFC进行软件开发,那么建议你安装EVC,这个软件和我们桌面WINDOWS软件开发工具VC十分类似,它编译出来的软件也能直接应用在你定制的内核上(当然,首先你得安装你定制的WINCE的SDK)。

6.3 选择BSP

一般来说嵌入式CPU生产厂商都会提供基于这一款CPU在WINDOWS CE.NET下的板级支持包(BSP),我们S3C2440嵌入式开发平台的WINCE BSP也是在三星公司提供的SMDK2440 BSP基础下进行开发的。下图就是我们使用的BSP目录。在进行基于S3C2440嵌入式开发平台的WINCE开发前需要把我们提供的BSP解压到WINCE安装路径下\\PLATFORM文件夹内。

22

其中DRIVERS目录里面就是大部分驱动的源代码,包括了背光灯控制驱动、摄像机驱动、CAN总线驱动、键盘驱动、Nandflash驱动、PCMCIA驱动、串口驱动、触摸屏驱动、USB驱动以及音频设备驱动等。

FILES目录下面是需要编译到WINCE内核中的文件,包括platform.reg(定义目标平台冷启动时所加载的注册表键值)、platform.bib(定义打包到OS镜像文件时所需要的文件(files)和模块(modules)),Platform.dat(定义目标平台冷启动时所加载的系统文件、目录和链接等);一些应用程序比如摄像头测试程序CameraTest.exe,注册表编辑器regedit.exe以及部分驱动的动态链接库比如网卡驱动dm9000.dll等。

INC目录下面是一些WINCE编译,以及编写驱动时需要使用到的头文件,我们比较关心的是s2440.h,这个头文件里面定义了s3c2440的寄存器以及网卡、NAND等设备的地址,我们在编写驱动的时候需要参考这些地址。oalintr.h这个头文件里定义了系统的中断号,这也是我们写驱动时经常要参考和修改的文件。

KERNEL文件夹里面就是系统启动时候首先要执行的初始化程序。包括ARM汇编程序以及C程序,系统工作模式的配置都是在这里进行的。其中fw.s这个汇编文件是cpu启动后第一个执行的程序,在这里设置了CPU的工作方式、配置了IO的模式等等。

BSP根目录下的smdk2440.cec这个文件是我们在PB下定制WINCE时候要用到的特征文件,这个文件决定了系统所有可用的特性。在PB下我们首先要把它导入才能进行下一步的工作。

6.4 PB下WINCE的开发、定制

下面就是在PB4.2下的WINCE的定制流程: 1.打开Platform Builder 4.2

23

2.选择File / Manage Catalog Features

3.点击Import 把WINCE安装路径/PLATFORM我们提供的BSP下的smdk2440.cec文件导入。

24

4.导入完了就能在manage catalog features里面看到我们的smdk2440.cec文件了,如下图最后一行所示:

有了这个cec文件,我们才可以定制基于我们2440开发平台的WINCE操作系统了。 5.选择File/New Platform新建一个平台,如下图所示:

6.选择Next,在Board Support Packages(BSPs)里面选择我们刚才导入的BSP-SAMSUNG SMDK2440:ARMV4I,如下图所示:

25

7.选择Next,在Platform Configuration 中选择Enterprise Terminal,在Platform name中给自己的开发平台起一个名字。

8.选择Next,在Application & Media下除了默认选择外在选择上Standard SDK for Windows CE.net、Windows Media Audio/MP3以及Windows Media Video/MPEG-4 Video,这样我们编译的WINCE内核就能支持标准的SDK以及多媒体功能。

26

9.之后一路选择Next,接受默认选择,点击Finish完成新建平台的过程,如下图所示:

10.经过以上过程,我们就得到了初步的一个基于2440开发平台的WINCE平台,下面我们就要把一些我们硬件和软件的特性添加到这个WINCE平台中,以便实现我们提供的平台下的所有功能。首先是硬件方面的特性,在PB界面的右边是平台可用的特征目录。展开BSP/Samsung smdk2440:ARMV4I,下面就是我们2440开发平台所支持的硬件特性。可以通过

27

在每个硬件特性上右击鼠标,选择Add to Platform(如果该硬件没有被添加到平台的话)把该硬件特性添加到我们定制的平台中。

11.右击PB界面左边如下图所示的我们定制的2440平台SJPG2440 features/ Samsung smdk2440:ARMV4I,选择Resolve Feature(s),进行硬件的一些选择配置。

12.选择Resolve Features下的Resolved标签,可以看到左边每一项硬件所对应右边的驱动程序。

28

1)在Display中选择SMI Voyager Family(Binary Dll Only),我们使用的是SM501显卡。

2)其他都选择默认的即可。

13)软件特性方面需要添加Hive-based Registry

14.添加FAT file system

29

15.分别添加Excel,PDF,PowerPoint,Word浏览器,如下图所示,当然如果你想使你的WINCE内核精简些的话,或者你的WINCE就不需要这些功能,你也可以不选择。

16.把USB Host Support下的特性都添加到平台都去。如下所示:

30

17.选择菜单栏的Build/Set Active Platform Configuration,在Platform configurations:下选择Release版本,如下图所示:

经过以上的配置以后就可以通过选择菜单栏的Build/Build Platform编译平台了,如下图所示:

31

编译完了后(编译的时候要看你选择的部件的数量来决定)就可以在你的PB工程下的RelDir下看到NK.nb0,这就是我们最后通过网络下载到2440平台的WINCE内核文件。

注:如果编译出现了溢出的错误,则需要删除一些非必要的软件特性文件,比如文件浏览器等

32

七 基于s3c2440的WINCE驱动开发

7.1 wince驱动模型

与其他操作系统一样,Windows CE.net也提供了驱动软件,这些软件的目的是驱动内部和外围的硬件设备,或者为它们提供接口。驱动程序将操作系统连接起来,使得操作系统能够识别设备并为应用程序提供设备服务。下图就是驱动程序在操作系统中的位置。

目前基于Windows CE.net的两种模型是本机设备驱动程序和流接口驱动程序。当然在有的地方还可以把Windows CE.net驱动分为Built-in Driver驱动程序和Installable Drivers驱动程序。

正如它们的名字所表明的,本机设备驱动适于集成到基于Windows CE.net平台的设备。通用LED驱动和电源驱动就是这样的例子,这些驱动是GWES的一部分,所以它们不表现在一个DLL的动态链接库上面。通用LED驱动由nleddrv.lib连接,电源驱动由battery.lib连接。还有一些样本本机驱动程序,例如显示驱动程序和键盘驱动程序,他们表现在一个DLL的文件上由GWES统一管理和加载。因为本机设备驱动程序通常与基于Windows CE.net的平台有着紧密的连接。而每种本机设备驱动程序都有精确的和特殊的目的,微软提供了定制接口的方式来支持内部设备驱动程序,也就是说绝大多数的开发人员不需要编写本机设备驱动程序。但是把Windows CE.net定制到新平台的原始设备制造商除外,他们可以创建自己的本机设备驱动程序,也可以将微软的本机设备驱动程序的例子移植到他们基于Windows CE.net的平台上。本机设备驱动程序总是在基于Windows CE.net的平台启动时加载。

另外一种驱动模型是具有定制接口的流接口驱动程序,它是一般类型的设备驱动程序,流接口驱动程序表现在用户一级的动态链接库DLL,用来实现一组固定的函数称为流接口函数,这些流接口函数使得应用程序可以通过文件系统访问这些驱动程序。流接口驱动几乎支持任何类型的可以连接到Windows CE.net的平台外部设备,其中包括寻呼机、打印机、调制解调器等。同时用户还可以定制出不支持任何外围设备的流接口驱动程序。下面我们通过一个简单的例子说明如何编写驱动程序。

在CE中,最简单的一个驱动程序莫过于一个内置(Built-in)设备的流接口驱动。对

33

于一个不支持热拔插的设备,最快捷的方法就是为其实现一个内置的流接口的驱动。

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

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

DllEntry(HINSTANCE DllInstance, INT Reason, LPVOID Reserved )

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

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

Reason: 一般我们只关心两个值: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函数。 参数:

pContext:系统传入的注册表键,通过它可以讲到我们在注册表中设置的配置信息。 lpvBusContext:一般不用,在这先不做讲解

实际上,很多程序中将这个函数写成了DWORD XXX_Init( DWORD pContext ) 我们只需要将pContext转化成LPCTSTR即可。

DWORD XXX_Open(DWORD hDeviceContext,DWORD dwAccess, DWORD dwShareMode);

当用户程序调用CreateFile打开这个设备时,设备管理器就会调用此驱动程序的XXX_Open函数。 参数:

hDeviceContext XXX_Init 返回给上层的值,也就是我们在XXX_Init中分配的用来记录驱动程序信息的那个结构体的指针,我们可以在这个函数中直接将其转化成所定义的结构,从而获取驱动程序的信息。

dwAccess 上层所要求的访问方式,可以是读或者写,或者是0,即不读也不写

dwShareMode 上层程序所请求的共享模式,可以是共享读、共享写这两个值的逻辑或,或者

34

是0,即独占式访问。

系统层对设备文件的存取权限及共享方法已经做了处理,所以在驱动程序中对这两个参数一般可以不用理会。

这个函数一般不用做太多处理,可以直接返回hDeviceContext表示成功,对于一个不支持多个用户的硬件,在设备已经打开后,应该总是返回0以至失败,则CreateFile调用不成功。 DWORD XXX_Close(DWORD hDeviceContext );

当用户程序调用CloseHandle关闭这个设备句柄时,这个函数就会被设备管理器调用。 参数:

hDeviceContext 为XXX_Open返回给上层的那个值。

这个函数应该做与XXX_Open相反的事情,具体包括:释放XXX_Open分配的内存,将驱动程序被打开的记数减少等。

DWORD XXX_Deinit (DWORD hDeviceContext );

这个函数在设备被卸载时被调用,它应该实现与XXX_Init相反的操作,主要为释放前者占用的所有系统资源。 参数:

hDeviceContext XXX_Init函数返回给上层的那个句柄 void XXX_PowerUp( DWORD hDeviceContext ); void XXX_PowerDown(DWORD hDeviceContext ); 正如其名称中体现的那样,这两个函数在系统PowerUp与PowerDown时被调用,这两个函数中不能使用任何可能引起线程切换的函数,否则会引起系统死机。所以,在这两个函数中,实际上几乎是什么做不了,一般在PowerDown时做一个标志,让驱动程序知道自已曾经被Power Down过。在Power Down/On的过程中硬件可能会掉电,所以,尽管Power On以后,原来的IO操作仍然会从接着执行,但可能会失败。这时,当我们发现一次IO操作失败是因为程序曾经进入过Power Down状态,就重新初始化一次硬件,再做同样的IO操作。 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结构

35

dwLenIn pBufIn以字节记的大小

pBufOut,dwLenOut分别为传出的Buffer,及其以字节记的大小

pdwActualOut 驱动程序实际在pBufOut中填入的数据以字节记的大小

其中,前两个参数是必须的,其它的任何一个都有可能是NULL或0。 所以,当给pdwActualOut赋值时应该先判断它是否为一个有效的地址

下面我就介绍一个简单的流驱动开发过程,通过这个过程,您将了解到流驱动的开发方法,今后也可以在这个模板上进行自己的驱动开发。

一般来说我们自己开发的驱动程序都是以动态链接库的形式给WINCE调用,所以下面这个模板也是标准的动态链接库形式:

1) 在D:\\WINCE420\\PLATFORM\\smdk2440\\DRIVERS下新建一个String目录(目录的名字可以和你的设备名字一样,这里是我这个模板驱动的名字)。在D:\\WINCE420\\PLATFORM\\smdk2440\\DRIVERS下的dirs文件中把这个新建的目录添加上。然后在String目录中新建一个String.c文件,文件内容如下所示:

//驱动程序部分(一定这个设备的名字尾STR,定义流接口名字必须是大写的三个字母,比如COM,CAN等):

#define _WIN32_WINNT 0x0400 #include #include

HANDLE g_hInstance; #define BUFSIZE 256 WCHAR achBuffer[BUFSIZE];

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

//----------------------------------------------------------------------------- BOOL WINAPI

DllEntryPoint(HANDLE hinstDLL, DWORD dwReason,

LPVOID /* lpvReserved */)

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

switch(dwReason) {

case DLL_PROCESS_ATTACH: g_hInstance = hinstDLL;

RETAILMSG(1,(TEXT(\"STRINGS: DLL_PROCESS_ATTACH\\n\")));/*RETAILMSG是我们在调试驱动时候经常要用到的用串口向终端输出调试信息的*/ return TRUE;

case DLL_THREAD_ATTACH:

RETAILMSG(1,(TEXT(\"STRINGS: DLL_THREAD_ATTACH\\n\"))); break;

case DLL_THREAD_DETACH:

36

RETAILMSG(1,(TEXT(\"STRINGS: DLL_THREAD_DETACH\\n\"))); break;

case DLL_PROCESS_DETACH:

RETAILMSG(1,(TEXT(\"STRINGS: DLL_PROCESS_DETACH\\n\"))); break;

#ifdef UNDER_CE

case DLL_PROCESS_EXITING:

RETAILMSG(1,(TEXT(\"STRINGS: DLL_PROCESS_EXITING\\n\"))); break;

case DLL_SYSTEM_STARTED:

RETAILMSG(1,(TEXT(\"STRINGS: DLL_SYSTEM_STARTED\\n\"))); break; #endif }

return TRUE; }

//----------------------------------------------------------------------------- //----------------------------------------------------------------------------- BOOL STR_Close(DWORD hOpenContext)

{/*当用户程序调用CloseHandle关闭这个设备句柄时,这个函数就会被设备管理器调用,在这个模板里我们只是向终端输出一句字符串,实际应用中这里是对应设备关闭时所需要做的一些工作。*/ BOOL bRet = TRUE;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Close\\n\"))); return bRet; }

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

//----------------------------------------------------------------------------- BOOL STR_Deinit(DWORD hDeviceContext) {/*这个函数在设备被卸载时被调用,它应该实现与XXX_Init相反的操作,主要为释放前者占用的所有系统资源。*/ BOOL bRet = TRUE;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Deinit\\n\"))); return bRet; }

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

//----------------------------------------------------------------------------- DWORD STR_Init(DWORD dwContext)

{/*它是驱动程序的动态库被成功装载以后第一个被调用的函数。其调用时间仅次与DllEntry,而且,当一个库用来生成多于一个的驱动程序实例时仅调用一次DllEntry,而xxx_Init会被调用多次。驱动程序应当在这个函数中初始化硬件,如果初始化成功,就分

37

配一个自已的内存空间(通常用结构体表示),将自已的状态保存起来,并且将此内存块的地址做为一个DWORD值返回给上层。设备管理器就会用在调用XXX_Open时将此句柄传回,我们就能访问自已的状态。如果初始化失败,则返回0以通知这个驱动程序没有成功加载,先前所分配的系统资源应该全部释放,此程序的生命即告终至。

当这个函数成功返回,设备管理器对这个程序就不做进一步处理,除非它设置了更多的特性。至此一个各为XXX的设备就已经加载成功,当用户程序调用CreateFile来打开这个设备时,设备管理器就会调XXX_Open函数。*/

DWORD dwRet = 0;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Init\\n\")));

// Initialize buffer to zero.

memset (achBuffer, 0, BUFSIZE * sizeof(WCHAR));

// Set return value to non-zero. dwRet = 1; return dwRet; }

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

//----------------------------------------------------------------------------- BOOL STR_IOControl(DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)

{/*几乎可以说一个驱动程序的所有功能都可以在这个函数中实现。对于一类CE自身已经支持的设备,它们已经被定义了一套IO操作定,我们只需按照各类设备已经定义的内容去实现所有的IO操作。但当我们实现一个自定义的设备时,我们就可以随心所欲定义我们自已的IO操作*/

BOOL bRet = TRUE;

RETAILMSG(1,(TEXT(\"STRINGS: STR_IOControl\\n\"))); return bRet; }

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

//----------------------------------------------------------------------------- DWORD STR_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)

{/*当用户程序调用CreateFile打开这个设备时,设备管理器就会调用此驱动程序的XXX_Open函数*/ DWORD dwRet = 0;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Open\\n\")));

dwRet = 1; // Have to make non-zero for this call to succeed. return dwRet;

38

}

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

/*正如其名称中体现的那样,这两个函数在系统PowerUp与PowerDown时被调用,这两个函数中不能使用任何可能引起线程切换的函数,否则会引起系统死机。所以,在这两个函数中,实际上几乎是什么做不了,一般在PowerDown时做一个标志,让驱动程序知道自已曾经被Power Down过。在Power Down/On的过程中硬件可能会掉电,所以,尽管Power On以后,原来的IO操作仍然会从接着执行,但可能会失败。这时,当我们发现一次IO操作失败是因为程序曾经进入过Power Down状态,就重新初始化一次硬件,再做同样的IO操作*/ //----------------------------------------------------------------------------- void STR_PowerDown(DWORD hDeviceContext) {

RETAILMSG(1,(TEXT(\"STRINGS: STR_PowerDown\\n\"))); }

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

//----------------------------------------------------------------------------- void STR_PowerUp(DWORD hDeviceContext) {

RETAILMSG(1,(TEXT(\"STRINGS: STR_PowerUp\\n\"))); }

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

//----------------------------------------------------------------------------- DWORD STR_Read(DWORD hOpenContext, LPVOID pBuffer, DWORD Count) {//系统调用ReadFile函数时调用的驱动 DWORD dwRet = 0;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Read\\n\")));

// Copy the smaller of buffer size or string size. DWORD cbBuffer = wcslen(achBuffer); dwRet = min(cbBuffer, Count);

wcsncpy((LPWSTR)pBuffer, achBuffer, dwRet);

// Return number of bytes read. return dwRet; }

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

//----------------------------------------------------------------------------- DWORD STR_Seek(DWORD hOpenContext, long Amount, DWORD Type) {//系统调用seek时使用的驱动 DWORD dwRet = 0;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Seek\\n\"))); return dwRet; }

39

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

//----------------------------------------------------------------------------- DWORD STR_Write(DWORD hOpenContext, LPCVOID pSourceBytes, DWORD NumberOfBytes) {//系统调用WriteFile调用的驱动 DWORD dwRet = 0;

RETAILMSG(1,(TEXT(\"STRINGS: STR_Write\\n\")));

// Copy the smaller of buffer size or number of bytes they send us. dwRet = min(BUFSIZE, NumberOfBytes);

wcsncpy(achBuffer, (LPWSTR)pSourceBytes, dwRet);

// Return number of bytes written. return dwRet; }

2) 在同一个文件夹内新建一个String.def文件,文件内容就是该动态链接库提供的接口:

LIBRARY String EXPORTS STR_Init STR_Deinit STR_Open STR_Close STR_Read STR_Write STR_Seek

STR_IOControl STR _PowerDown STR _PowerUp

3) 同样在同一个目录下新建一个sources文件,内容为: TARGETNAME=String /*驱动的名字*/ RELEASETYPE=PLATFORM

TARGETTYPE=DYNLINK /*说明该驱动为动态链接库形式*/ WINCEOEM=1

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

DEFFILE=String.def /*驱动的定义文件,必须在同一个目录下,否则要指定路径*/ PREPROCESSDEFFILE=1

DLLENTRY=DllEntry/*动态链接库的入口*/ INCLUDES=..\\..\\inc

SOURCES= String.c/*驱动源文件*/

4)在同一个目录夏新建makefile: 只需要这样一行 !INCLUDE $(_MAKEENVROOT)\\makefile.def

5)驱动程序注册表部分,可以直接在Platform.reg中添加: [HKEY_LOCAL_MACHINE\\Drivers\\BuiltIn\\STRINGS] \"Index\"=dword:1

40

\"Prefix\"=\"STR\" 这是设备使用时的前缀名

\"Dll\"=\"String.dll\" 这是生成的动态链接库的名字 \"Order\"=dword:0

6)在BIB文件中添加项目,将所用到的文件加入BIN文件(一般放在Platform.bib)。 String.dll $(_FLATRELEASEDIR)\\ String.dll NK SH

7)调用这个驱动程序可以利用EVC写一个测试程序,程序代码如下,编译好这个程序后把可执行文件加入到WINCE内核中,就可以在WINCE启动后调用这个程序去测试驱动。

#include #include \"StdAfx.h\"

#define BUFFER_SIZE 256 // The buffer size is the same as the driver's buffer size

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {

// Open the STRINGS driver with READ and WRITE access

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

HANDLE hStr = CreateFile(TEXT(\"STR1:\"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);//打开STR设备1 if (INVALID_HANDLE_value == hStr) {

MessageBox(NULL, _T(\"Cannot open STR1:\"), _T(\"StringApp\"), MB_OK); return 0; }

// Write a string to the driver.

// --------------------------------------------------------------------- DWORD dwWritten = 0; WCHAR* pString = TEXT(\"This is a test of the String Driver. This is o­nly a test\"); WriteFile(hStr, pString, (_tcslen(pString)+1), &dwWritten, NULL);//往这个设备中写入数据

// Read string from driver.

// --------------------------------------------------------------------- WCHAR wch[BUFFER_SIZE];

DWORD dwBytesRead = BUFFER_SIZE;

memset(&wch, '\\0', BUFFER_SIZE * sizeof(WCHAR));

ReadFile(hStr, wch, sizeof(wch), &dwBytesRead, NULL);//从这个设备读取数据

MessageBox(NULL, wch, TEXT(\"StringApp\"), MB_OK); // Disconnect from driver.

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

41

CloseHandle(hStr);//关闭这个设备

return 0; }

7.2 2440一个驱动程序示例:基于WinCE的I2C驱动程序设计

上一节介绍了一个通用的驱动模板,下面针对S3C2440开发平台进行分析,介绍在Windows CE.net系统下进行底层设备驱动开发的方法并提供I2C通信的实例(实际的I2C驱动要用户根据我们提供的S3C2440平台搭建相应的接口板,此项功能我们也已经实现)。 7.2.1 I2C通信协议及S3C2440芯片介绍

I2C(Inter Integrated Circuit)总线是1980年由Philips公司推出的。 I2C总线用两条线(SDA和SCL)在总线和装置之间传递信息,在微控制器和外部设备之间进行串行通信或在主设备和从设备之间进行双向数据传送。两条通信线通过上拉电阻被拉升至+5 V。在控制系统中的每个集成电路可以通过一个CMOS缓冲器来读每一条线路,也可以通过一个栅极开路的FET管将每一条线的电平下拉。因此,对每个芯片来说,每条线既是输入线,又是输出线。

I2C总线遵从同步串行传输协议,即各位串行(一位接一位)发送,由时钟(clock)线指示读数据(data)线的时刻。每个数据包前有一个地址,以指示由哪个器件来接收该数据。

S3C2440是一款基于ARM920T的16/32位RISC微处理器,主要用于手持设备,拥有高性价比,低功耗等特点,也是目前市面上出现较多的嵌入式开发板的处理器之一。芯片拥有16 KB的指令和数据缓存器,有存储管理单元(MMU)、LCD控制器、3个串口、4路DMA、4个时钟定时器、8路10位的A/D转换;支持I2C、I2S、SPI、主从USB等接口以及SD/MMC卡。

S3C2440微处理器的I2C总线可以处于下面4种模式下:主接收模式、主发送模式、从接收模式和从发送模式。处理器对I2C进行的操作,主要是对下面的几个寄存器进行读/写:

◇ IIC控制寄存器,IICCON(物理地址0X54000000,内存映射后的虚拟地址); ◇ IIC控制/状态寄存器,IICSTAT(物理地址0X54000004); ◇ IIC数据寄存器,IICDS(物理地址0X54000008); ◇ IIC地址寄存器,IICADD(物理地址0X5400000C)。 7.2.2 Windows CE系统驱动特点

Windows CE.net驱动有两种模型:本机设备驱动程序和流接口驱动程序。本机设备驱动适于集成到基于Windows CE.net平台的设备。这些设备驱动程序是一些硬件所必需的,是由原始设备制造商创建的,用以驱动如键盘、触摸屏、音频设备等,往往在设备售出后就不会再更换,如通用LED驱动、电源驱动、键盘驱动和显示驱动等都是本机设备驱动。对于本机设备驱动程序,Platform Builder提供了一些驱动程序样本,目的是为了方便开发人员快速开发出自己的驱动程序。当Win CE系统启动时,本地设备驱动程序将被加载到系统的内存中。本地驱动程序的开发分为分层驱动和单片驱动程序。分层驱动要利用微软提供的与应用程序通信的上层,称为模块驱动程序层MDD(Model Device Driver)。MDD层通过设备驱动程序接口DDI(Device Driver Interface)与应用程序通信,开发驱动程序通常不修改MDD层,主要关心与具体硬件相关的下层,依赖平台的设备驱动程序层PDD(Platform Dependent Driver), PDD层通过设备驱动服务接口(Device Driver Service Provider Interface)直接管理硬件。流接口设备驱动程序(指可安装的启动程序)可以由第三方生产商提供,以支持添加到系统中的设备。Windows CE下的设备驱动程序在与应用程序相同的保护级上工作。当系统启动时,大多数驱动程序是由设备管理进程(DEVICE.EXE)加载的,

42

所有这些驱动程序将共享同一个进程地址空间。 7.2.3 I2C总线底层驱动设计

I2C总线驱动是放在Windows CE操作系统的内核下层,位于OEM Adaptation Layer(OAL)层的一个真正的驱动。

7.2.3.1 初始化I2C中断和编写ISR例程

I2C的通信是通过操作I2C的寄存器进行的。在I2C通信中主要对上面介绍的4个寄存器进行读写。通过读写这些寄存器中的命令状态字可以检测和控制I2C总线的行为。在Windows CE.net下,首先要在文件oalintr.h添加I2C的中断号的宏定义:

#defineSYSINTR_I2C(SYSINTR_FIRMWARE+19)

然后在文件cfw.c的文件中添加I2C中断的初始化,禁止和复位。具体代码如下: 在OEMInterruptEnable函数中加入 case SYSINTR_IIC:

s2410INT->rSRCPND=BIT_IIC;

if (s2410INT->rINTPND & BIT_IIC) s2410INT->rINTPND = BIT_IIC; s2410INT->rINTMSK&= ~BIT_IIC; break;

在OEMInterruptDisable函数中加入 case SYSINTR_IIC:

s2410INT->rINTMSK|= BIT_IIC; break;

在armint.c文件中添加ISR程序,处理中断发生后返回定义的中断号。具体代码如下: 在OEMInterruptHandler函数中添加 else if (IntPendVal == INTSRC_IIC) {

s2410INT->rSRCPND= BIT_IIC; /* 清除中断 */

if (s2410INT->rINTPND & BIT_IIC) s2410INT->rINTPND= BIT_IIC; s2410INT->rINTMSK|= BIT_IIC; /* I2C中断禁止 */ return (SYSINTR_RTC_ALARM); }

7.2.3.2 编写流驱动程序

I2C总线驱动程序采用的是Win CE流驱动的标准形式。在IIC_Init的函数中,首先通过函数VirtualAlloc()和VirtualCopy(),把芯片中针对I2C的物理地址和操作系统的虚存空间联系起来,对虚拟地址空间的操作就相当于对芯片的物理地址进行操作。地址映射的代码如下:

reg = (PVOID)VirtualAlloc(0, sz, MEM_RESERVE, PAGE_NOACCESS); if (reg) {

if (!VirtualCopy(reg, addr, sz, PAGE_READWRITE | PAGE_NOCACHE )) { RETAILMSG( DEBUGMODE,( TEXT( \"Initializing interrupt \\\\n\\\\r\" ) ) ); VirtualFree(reg, sz, MEM_RELEASE); reg = NULL; } }

其中sz是申请的长度,addr是申请虚拟地址空间的实际物理地址在Win CE中的映射地址。

然后对申请到的虚拟地址进行操作,安装Windows中的流驱动的模型进行驱动的编写,

43

主要包括下面函数的编写。

IIC_Init()

在函数中,主要是对I2C的初始化,主要语句如下: v_pIICregs = ( volatile IICreg *)IIC_RegAlloc((PVOID)IIC_BASE, sizeof(IICreg)); v_pIOPregs = ( volatile IOPreg *)IOP_RegAlloc((PVOID)IOP_BASE, sizeof(IOPreg)); v_pIOPregs->rGPEUP|= 0xc000; v_pIOPregs->rGPECON |= 0xa00000;

v_pIICregs->rIICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); v_pIICregs->rIICADD= 0x10; v_pIICregs->rIICSTAT = 0x10;

VirtualFree( ( PVOID )v_pIOPregs,sizeof( IOPreg ),MEM_RELEASE ); v_pIOPregs = NULL;

if ( !StartDispatchThread( pIIcHead) )

{ IIC_Deinit( pIIcHead );return ( NULL );}在StartDispatchThread()函数中,主要是创建线程、关联事件和中断,主要语句如下:

InterruptInitialize( 36,pIicHead->hIicEvent,NULL,0 );//关联时间和中断

CreateThread( NULL,0,IicDispatchThread,pIicHead,0,NULL );//创建线程等待时间

在IicDispatchThread()函数中,主要是等待中断的产生,然后去执行:WaitReturn = WaitForSingleObject( pIicHead->hIicEvent,INFINITE ); IicEventHandler( pIicHead );//事件处理函数 InterruptDone( 36 );

最后,在函数IIC_Open、IIC_Read、IIC_Write中,对各个寄存器进行操作,进行数据的赋值,得到I2C读取的数据和发送数据。

7.2.4 I2C驱动的封装和添加到Windows CE中

通过上面的工作,能编译一个DLL函数,但这还不能叫流接口驱动程序。因为它的接口函数还没有导出,还需要告诉链接程序需要输出什么样的函数,为此要建立一个自己的def文件,可以用记事本建一个,取名mydrive.Def:

LIBRARY MyDriver EXPORTS IIC_Close IIC_Deinit IIC_Init

IIC_IOControl IIC_Open

IIC_PowerDown IIC_PowerUp IIC_Read IIC_Seek IIC_Write

然后同样用记事本编写一个注册表文件,取名为mydrive.reg: [HKEY_LOCAL_MACHINE\\Drivers\\BuiltIn\\STRINGS] \"Index\"=dword:1 \"Prefix\"=\"IIC\"

\"Dll\"=\"MyDriver.dll\"

44

\"Order\"=dword:0

最后编写自己的CEC文件(也可以通过上一节介绍的方法把驱动加到内核中),主要是添加一个Build Method,任务是复制注册表到Win CE的系统目录下面。加一个Bib File,其主要功能是把编译的mydrive.dll文件添加到系统内核中去。保存写好的CEC文件。打开Platform Builder,打开“File”菜单,添加刚刚编写的CEC特征到系统选项中去。生成系统的时候,添加自己的CEC特性,就可以包含刚刚编写的I2C驱动了。

八 基于2440的嵌入式应用程序开发以及加载至内核

8.1 主流嵌入式开发软件介绍

当我们把windows ce.net成功移植以后,实际上针对wince的应用程序开发和我们开发桌面应用程序已经十分相像了,在windows下我们使用Visual C++来开发应用程序,而在windows ce.net下我们可以使用embed visual C++, VS2005以及CF net来开发。

无论是Win32平台还是WinCE平台,Visual C++都是一个强大的开发工具。而EVC也是WinCE上的主流开发工具。在为Windows CE.net设计的软件开发工具中,Embeded Visual C++(简称EVC)是最受欢迎、也最适合开发CE下软件的开发工具。EVC支持MFC类库的子集,可以给开发者提供最强大的支持,也使Win32平台上的VC程序员可以很容易地迁移到WinCE平台上。但由于MFC类库需要一个DLL,所以对某些存储空间有限的嵌入式设备来说,这是个很大的负担,所以SmartPhone就不支持MFC。

EVC和VC集成开发环境在大多数方面非常相似,而不同之处主要有下列几点,在实际的开发过程中需要注意。

 EVC编译器支持多种嵌入式CPU,支持多种指令集。VC只支持X86指令集。  为了能在开发机上调试,EVC包含一个模拟器,而VC不具备,也不需要。

 EVC包含远程调试工具,用于调试在实际设备上运行的程序,而VC只包含本地调

试工具。

 随EVC安装附带的SDK内容非常少,远远不够软件使用,必须安装Windows CE.net 才能得到全部的SDK。随VC附带的SDK则包含全部内容。

VS2005庞大豪华无比,其实真正针对的是.net开发,虽然也可以开发VC,相对EVC写代码时顺手多了,但就是没有开发.net更方便的功能,另外VS2005给人最大的印象就是无比冷酷的内存杀手,512M的机器可能是刚够塞VS2005和SmartDevice Emulator的牙缝,要想流畅一点,1GB内存是值得推荐的,VS2005提供了一个智能设备模拟器,和EVC最大的区别在于这个模拟器可以真实地模拟物理机器代码,也就是你可以把普通的PPC或SP软件装在这个模拟器上运行(没有真机器的人可以过过瘾),以前在EVC上开发的软件最痛苦莫过于有时在模拟器上执行得好好的程序到了真机器就有些莫名其妙的错误,现在有了一致的开发环境,这种情况我想一般不会出现了吧。用MFC来开发当然方便多了,但是速度就大打折扣了,用系统生成的Hello World在wince上运行,Win32版本的马上就显示出来,而MFC版本的则要迟钝1秒左右的时间才显示出界面。

顺便说说CF net,虽然CF net开发起来简单如VB,(我不说象Delphi的原因就是在.net方方面面上几乎都可以找到Delphi的身影),但是CF net天生的代码容易被反编译的不安全特点可能限制了它作为商业软件的首选语言,就是用混淆器也顶多让反编译者花多半小时功夫,而且用.net cf 2.0来开发WINCE软件似乎也太过分些,一只嵌入式系统的空间才多大,cf 2.0的驱动就要用了2M多。

总的来说EVC还是开发wince软件的主流,VS2005也是一种不错的选择,至少开发CF NET

45

是个不错的选择。不过提醒一下用什么开发就最好坚持下去,不要先用EVC开发再升级到VS2005上,或者相反,因为首先是一些系统调用在EVC和VS2005运行后就可能有不同的结果,在EVC下执行得好好的程序移植到VS2005下的运行结果可能不是你想象中的那样,其次就是VS2005打开EVC的项目问题会很友好地提醒你要升级,瞎忙了一阵却无法编译升级后的项目文件,所以想在两个开发平台间迁移项目,我看还是不要自找苦吃。

8.2 基于s3c2440嵌入式开发平台的EVC开发

从上面的分析我们可以知道,EVC是最适合我们进行嵌入式开发的软件,下面我们将介绍基于我们s3c2440嵌入式开发平台的EVC开发流程,通过一个简单的Hello World!例子,您将了解到最基本的应用程序开发方法。 (1)安装EVC

现在microsoft公司已经把EVC开放给开发者免费使用了,您可以从微软的网站上免费下载正版的EVC,安装过程和安装Visual C++基本相似,只是要选择你所支持的硬件平台,由于我们的2440是基于ARMV4的,所以在安装的时候一定要选上ARMV4和ARMV4I两个硬件平台的支持,同时为了方便应用程序的调试,也应该把emulator模拟器装上,这样我们有些程序可以不下载到开发板上,而直接在自己机器上模拟,等真正完成后再转换为ARMV4I版本的程序下载到开发板上使用。 (2)安装Standard SDK Standard SDK提供了WINCE下标准的API函数,只有安装它,我们的程序才能使用WINCE下的API,当然我们也可以通过PB倒出自己平台的SDK,安装这个SDK可以开发针对我们特定平台的应用程序。Standard SDK的安装非常简单,同样要求选择ARMV4和ARMV4I两个硬件平台的支持即可。

(3)利用EVC新建一个MFC工程

我们这里介绍一个Hello world基于MFC的WINCE程序,首先打开EVC,选择新建,如下图所示,选择WCE MFC AppWizard(exe)(是不是和VC一样?^_^),注意在CPUs上选择我们的平台ARMV4I,给我们的工程起一个名字HelloWorld。

(4)选择应用程序类型

46

这里我们选择建立一个基于对话框的应用程序,如下图所示:

(5)一路next,最后选择finish

如下图所示,这样我们就生成了一个最简单的基于我们开发平台的WINCE MFC程序。

47

(6)EVC开发界面

EVC的开发界面如下图所示,和VC基本上一样,在对话框里面我们把文字改成Hello World!选择编译程序。

(7)选择编译程序,我们就能在Microsoft eMbedded C++ 4.0安装路径\\Common\\EVC\\MyProjects\\HelloWorld\\ARMV4IRel下得到我们的应用程序,HelloWorld.exe。如下图所示:

48

8.3 把应用程序加入到WINCE内核

我们得到的应用程序有集中方法可以下载到内核中运行: (1)用U盘拷贝到WINCE中

由于我们的WINCE操作系统支持USB,所以我们可以利用U盘把应用程序拷贝到启动后的WINCE中,然后运行,这种方式优点是可以编译好应用程序后马上可以拷贝到操作系统中去测试,缺点是系统断电后应用程序就没了(当然我们也可以把应用程序拷贝到我们的永久存储目录下,这样调电也可以保存),这种方式比较适合于应用程序测试的时候。 (2)把应用程序打包到内核中

应用这种方法可以让应用程序加入到操作系统内核中,这样应用程序将会永久驻留在内核中,这种方式适合于应用程序调试成功,做最终产品时候使用。下面我就介绍一下这种方法。

a) 将应用程序(比如我们刚才生成的HelloWorld.exe)拷贝到WINCE安装目录

\\PUBLIC\\Term2440I\\RelDir\\SAMSUNG_SMDK2440_ARMV4IRelease下。如下图所示:

b) 在WINCE安装目录\\PUBLIC\\Term2440I\\RelDir\\SAMSUNG_SMDK2440_ARMV4IRelease下修改platform.bib文件,增加如下一行:

HelloWorld.exe $(_FLATRELEASEDIR)\\regedit.exe NK U 其中HelloWorld.exe是我们应用程序的名字,$(_FLATRELEASEDIR)表示的是WINCE安装目录\\PUBLIC\\Term2440I\\RelDir\\SAMSUNG_SMDK2440_ARMV4IRelease路径,NK表示把这个可执行文件打包到内核中,U表示这是一个用户程序。

c) 在PB中选择make image,这样在新生成的NK.nb0中就把应用程序加载进去了。把这个内核下载到s3c2440中,在windows目录下就会找到我们的HelloWorld.exe应用程序。在WINCE下运行HelloWorld.exe,如下图所示:

49

8.4 一些EVC下实用的WIN32函数

上面就简单的介绍了基于我们s3c2440嵌入式开发平台的应用程序开发流程,当然这只是一个最简单的程序,但是通过这个流程您也可以编写基于您产品需要的复杂应用程序,这和在VC下编写应用程序是基本一致的,下面介绍一些实用的WIN32函数: (1) 输出函数

 MessageBox – 显示消息窗口函数

– Example:

MessageBox(NULL, _T(\"Hello\"), _T(\"Title\"), 0);  NKDbgPrintfW –格式化&调试输出

– Example:

NKDbgPrintfW(_T(\"Value is %d\\n\\r\"), iVal);

(2) 字符集和字符串

现在的程序中一共有三种字符集,ANSI、Unicode、Bi-Modal,对于我们的嵌入式开发来说这三种都可以使用,但是如果我们的字符串是中文,那么一定要使用Unicode或者Bi-Modal字符集,如果中文中也使用ANSI字符集的话将会出现乱码,这点一定要切记。  ANSI 字符集和字符串:

‘H’

“Hello Unicode”

==> 以‘char’ or ‘unsigned char’存储  Unicode字符集和字符串:

L’H’

L”Hello Unicode”

==> 以“short’ or ‘unsigned short’存储

50

 Bi-Modal (_UNICODE) 两种字符兼容

TEXT(‘H’)

TEXT(“Hello Unicode”)

(3) UNICODE字符类型

 ANSI

CHAR – 解释为 char LPSTR - 解释为 char *  Unicode

WCHAR - 解释为 unsigned short LPWSTR - 解释为 unsigned short *  Bi-Modal (_UNICODE)

TCHAR - 解释为 char or unsigned short

LPTSTR - 解释为 char * or unsigned short *

(4)UNICODE字符操作函数

 ANSI

strlen() – 求字符串长度 strcpy() – 字符串拷贝 strcat() – 字符串连接  Unicode

wcslen() - 求字符串长度 wcscpy() - 字符串拷贝 wcscat() - 字符串连接  Bi-Modal (_UNICODE)

_tcslen() - 求字符串长度 _tcscpy() - 字符串拷贝 _tcscat() - 字符串连接

(5)字符集转换函数

 ANSI转换为 Unicode

mbstowcs(

wchar_t *wcstr, // Output string. const char *mbstr, // Input string. size_t count ); // Character count.  Unicode转换为ANSI

wcstombs(

char *mbstr, // Output string. const wchar_t *wcstr, // Input string. size_t count ); // Character count.

(6)创建进程函数

CreateProcess(

LPCTSTR lpszImageName, // EXE file name LPCTSTR lpszCmdLine, // Parameters

LPSECURITY_ATTRIBUTES lpsaProcess, // = NULL LPSECURITY_ATTRIBUTES lpsaThread, // = NULL

BOOL fInheritHandles, // Windows CE requires FALSE

51

DWORD dwFlags, // See docs for details LPVOID lpEnvironment, // = NULL

LPTSTR lpszCurDir, // Startup directory // = NULL LPSTARTUPINFO lpStart, // = NULL

LPPROCESS_INFORMATION lppi); // Return handles

(7)线程API

 创建线程:

CreateThread (Win32)  挂起线程:

SuspendThread (Win32) ResumeThread (Win32)  销毁线程:

ExitThread (Win32)

TerminateThread (Win32)

(8)结构化异常处理函数

__try {

// Protected block. }

__except (/* filter */ ) {

// Exception handler. }

8.5 EVC下的调试工具

对于EVC开发的应用程序,我们有丰富的调试工具供开发者调试程序,下面就是EVC的所有调试工具:

 模拟器

 远程调用评测程序  远程文件浏览程序  远程堆查看程序  远程内核跟踪程序  远程性能监视程序  远程进程浏览程序  远程注册表编辑器  远程消息监视程序  远程系统信息  远程截屏程序

对于EVC程序的调试,调试方法和基于VC的调试方法基本一致,可以通过设置断点,跟踪内存等方法进行调试,这里我们就不在详细介绍。

52

九 实现永久保存注册表数据

注册表类型分为基于对象存储的注册表和基于HIVE的注册表,在定制内核的时候只能选择其中一种。从理论上讲这两种注册表都能够实现永久保存注册表数据,但是采用不同的类型会影响CE的启动顺序和启动速度,还会影响内存的使用量。我还是趋向于采用基于HIVE的注册表来实现永久保存注册表数据,这也是一个发展趋势。在讲解之前先简单描述如果CE采用基于HIVE的注册表,那么在启动时如何加载已经保存的注册表数据: 1、nk.exe执行,启动filesys.exe。

2、filesys.exe加载引导HIVE,此时引导HIVE位于nk.bin解压之后的文件中。 3、filesys.exe启动device.exe,之后处于等待状态,等待device.exe将包含系统HIVE的文件系统和存储设备的驱动程序加载完毕。而这个文件系统和存储设备的驱动程序存在于引导HIVE中。 4、device.exe加载上述所说的文件系统驱动程序和存储设备驱动程序,使之开始工作。之后device.exe处于等待状态。

5、filesys.exe被唤醒,加载并且安装系统HIVE。之后filesys.exe处于等待状态。 6、nk.exe按照系统HIVE的信息开始执行初始化工作。其中包括加载驱动程序和启动一些应用程序。其中加载驱动程序一般由device.exe执行,而启动应用程序由filesys.exe执行。这时device.exe和filesys.exe已经被唤醒。 因为引导HIVE和系统HIVE肯定有重复的地方,所以可能出现重复加载了驱动程序或者重复启动了应用程序。为此,CE允许在描述驱动程序的注册表信息中加入防止重复的标志,而应用程序可以采用事件对象来防止重复启动,如device.exe。

下面讲述如何设置基于HIVE的注册表(假如保存系统HIVE的是FAT文件系统): 1、在PB中加入\"Hive-based Registry\" 2、打开platform.reg,找到如下信息:

; HIVE BOOT SECTION

[HKEY_LOCAL_MACHINE\\init\\BootVars]

\"SYSTEMHIVE\"=\"Documents and Settings\\\\system.hv\" \"PROFILEDIR\"=\"Documents and Settings\" \"Start DevMgr\"=dword:0 IF BSP_ENABLE_FSREGHIVE \"Start DevMgr\"=dword:1 ENDIF

; END HIVE BOOT SECTION

\"SYSTEMHIVE\"的值为系统HIVE文件的路径。\"Start DevMgr\"是一个布尔值,指示是否开始就执行设备管理器device.exe,按照CE帮助文档的说法,只有想把系统HIVE存储在对象存储中才在此设置为0,所以一般都要设置为1。

3、如果是多用户,可以在上述的注册表位置下输入\"DefaultUser\"=\"\",指定默认的用户名。如果是单用户系统,可以不设置。 4、保证将包含系统HIVE的文件系统驱动程序的注册表信息和存储设备的驱动程序的注册表信息被包含在“; HIVE BOOT SECTION”和“; END HIVE BOOT SECTION”之间,在这两个语句之间的注册表数据全部属于引导HIVE。假如我们将系统HIVE文件system.hv存放在硬盘上,并采用FAT文件系统。那么就要将

[HKEY_LOCAL_MACHINE\\System\\StorageManager\\FATFS]和

[HKEY_LOCAL_MACHINE\\System\\StorageManager\\Profiles\\HDProfile]移动到“; HIVE BOOT

53

SECTION”下。

5、在“; HIVE BOOT SECTION”和“; END HIVE BOOT SECTION”之间的所有驱动程序的注册表信息中都加入下列一个标志:

\"Flags\"=dword:1000

这个标志是一个位掩码,它可以和其它已经存在的\"Flags\"或运算。值1000表示此驱动程序只加载一次,这样device.exe就不会把当前驱动程序加载两次了。

6、在包含系统HIVE的存储设备的驱动程序的注册表信息中,加入如下标志(假设是硬盘):

[HKEY_LOCAL_MACHINE\\System\\StorageManager\\Profiles\\HDProfile] \"MountFlags\"=dword:2

这个标志表示这个存储设备包含系统HIVE文件。

按照如上所述设置后的内核就能实现永久存储注册表数据了。对于保存注册表数据的执行动作在此必须阐述清楚:

正常情况下,CE能够保证重要的注册表数据能够从内存刷到(Flush)永久存储器上。但是这并不能完全保证所有数据都能完整地保存而不丢失,所以要保证万无一失,应该主动地调用RegFlushKey函数强制将内存中的数据刷到永久存储器上。这个函数的参数只有一个,就是注册表分支。CE还增加一个注册表项(如下所示),它的作用是每当函数RegCloseKey被调用时都自动调用RegFlushKey函数。

[HKEY_LOCAL_MACHINE\\init\\BootVars] \"RegistryFlags\"=dword:1

如果CE在启动过程中发现系统HIVE出现错误,它会自动删除文件并创建一个默认的系统HIVE文件,如果出现下面的注册表项,说明发生了这种事情。

[HKEY_LOCAL_MACHINE] \"RegPersisted\"=dword:1

十 Windows CE 下应用程序自动启动

Windows CE 下应用程序自动启动有两种方法两种方法:

10.1建立快捷方式

1) 假定Windows CE.NET目标工程目录为D:\\WINCE420\\PUBLIC\\SJPG2440,并且工程已经Build成功,假定Windows CE.NET的应用为MyApp.exe;

2) 将应用程序MyApp.exe复制到SJPG2440工程目录

D:\\WINCE420\\PUBLIC\\SJPG2440\\RelDir\\SAMSUNG_SMDK2440_ARMV4IRelease 下 3) 修改SJPG2440工程的project.bib文件,在FILES Section添加如下内容: MyApp.exe $(_FLATRELEASEDIR)\\MyApp.exe NK H 4) 创建快捷方式文件MyApp.lnk,文件内容如下:

10#\\Windows\\MyApp.exe

将MyApp.lnk文件也放入到SJPG2440工程目录

D:\\WINCE420\\PUBLIC\\SJPG2440\\RelDir\\SAMSUNG_SMDK2440_ARMV4IRelease下 5) 修改SJPG2440工程的project.bib文件,在FILES Section添加如下内容:

54

MyApp.lnk $(_FLATRELEASEDIR)\\MyApp.lnk NK H

6) 修改MyWinCE工程的project.dat文件,添加如下内容:

Directory(\"\\Windows\\Startup\"):-File(\"MyApp.lnk\7) 修改MyWinCE工程的platform.bib文件,在FILES Section添加如下内容:

MyApp.exe $(_FLATRELEASEDIR)\\MyApp.exe NK H MyApp.lnk $(_FLATRELEASEDIR)\\MyApp.lnk NK H 8) Platform Builder IDE:【Build】->【Make Image】(记得千万不要Build或者Rebuild) 9) 成功后,得到的NK.bin(或NK.nb0)就包含了应用程序MyApp.exe和MyApp.lnk,当把相应的内核烧入开发板MyApp程序就会在系统启动时自动运行。

10.2 修改shell.reg文件

1) 首先新建工程,把你的应用程序放到内核里面去(前面介绍了如何做); 2) 编译平台; 3)修改

D:\\WINCE420\\PUBLIC\\SJPG2440\\RelDir\\SAMSUNG_SMDK2440_ARMV4IRelease下的shell.reg的文件 :

在文件中有这样一行:

[HKEY_LOCAL_MACHINE\\init] \"Launch50\"=\"explorer.exe\" \"Depend50\"=hex:14,00, 1e,00 修改如下:

[HKEY_LOCAL_MACHINE\\init] \"Launch40\"=\" MyApp.exe\" \"Depend40\"=hex:14,00, 1e,00 \"Launch50\"=\"explorer.exe\" \"Depend50\"=hex:14,00, 1e,00 4) Platform Builder IDE:【Build】->【Make Image】(记得千万不要Build或者Rebuild) 5) 成功后,得到的NK.bin(或NK.nb0)就包含了应用程序MyApp.exe和MyApp.lnk,当把相应的内核烧入开发板MyApp程序就会在系统启动时自动运行。

55

十一 开机后直接运行您的程序而不显示Windows CE桌面

Windows CE.NET的桌面确实漂亮,但是如果我们的嵌入式系统使用微软的桌面,总让我感觉不伦不类。因此,要想个办法,让我们的程序开机启动,把微软的那个桌面给替换掉。

其中使用startup的快捷方式再加上隐藏任务栏的方法,效果非常的好,但是每次启动的时候,总是微软的桌面先出来,再启动我们的程序,感觉还是不爽。

使用下面的方法可以不出现微软的桌面而先启动我们的程序。

1)首先新建工程,把你的应用程序放到内核里面去(前面介绍了如何做); 2) 编译平台;

3) 修改shell.reg的文件 : 在文件中有这样一行:

[HKEY_LOCAL_MACHINE\\init] \"Launch50\"=\"explorer.exe\" \"Depend50\"=hex:14,00, 1e,00

把这个explorer.exe改成你的应用程序(比如:MyApp.exe)就可以了; 4) Platform Builder IDE:【Build】->【Make Image】(记得千万不要Build或者Rebuild)

5) 成功后,得到的NK.bin(或NK.nb0)就包含了应用程序MyApp.exe和MyApp.lnk,当把相应的内核烧入开发板MyApp程序就会在系统启动时自动运行,而不显示微软的桌面。

56

附录一 flash.c

#include

flash_info_t flash_info[CFG_MAX_FLASH_BANKS]; /* info for FLASH chips */

/*----------------------------------------------------------------------- * Functions */

static ulong flash_get_size ( short manu, short dev_id, flash_info_t *info); static int write_word (flash_info_t *info, ulong dest, ulong data);

static void flash_get_offsets (ulong base, flash_info_t *info, int two_chips); static void flash_get_id_word( void *ptr, short *ptr_manuf, short *ptr_dev_id); static void flash_get_id_long( void *ptr, short *ptr_manuf, short *ptr_dev_id);

/*----------------------------------------------------------------------- */

unsigned long flash_init (void) {

unsigned long size_b0; short manu, dev_id; int i;

/* Init: no FLASHes known */

for (i=0; i/* Do sizing to get full correct info */

flash_get_id_word((void*)CFG_FLASH_BASE,&manu,&dev_id); size_b0 = flash_get_size(manu, dev_id, &flash_info[0]); flash_get_offsets (CFG_FLASH_BASE, &flash_info[0],0); flash_info[0].size = size_b0;

#if CFG_MONITOR_BASE >= CFG_FLASH_BASE flash_protect (FLAG_PROTECT_SET, CFG_MONITOR_BASE,

CFG_MONITOR_BASE + monitor_flash_len - 1, &flash_info[0]); #endif

#if (CFG_ENV_IS_IN_FLASH == 1) && defined(CFG_ENV_ADDR) flash_protect (FLAG_PROTECT_SET,

57

CFG_ENV_ADDR,

CFG_ENV_ADDR + CFG_ENV_SIZE - 1, &flash_info[0]); #endif

return (size_b0); }

/*----------------------------------------------------------------------- */

static void flash_get_offsets (ulong base, flash_info_t *info, int two_chips) {

int i, addr_shift;

vu_short *addr = (vu_short*)base;

addr[0x555] = 0x00AA ; addr[0x2AA] = 0x0055 ; addr[0x555] = 0x0090 ;

addr_shift = (two_chips ? 2 : 1 );

/* set up sector start address table */ if (info->flash_id & FLASH_BTYPE) {

/* set sector offsets for bottom boot block type */ info->start[0] = base + (0x00000000<start[1] = base + (0x00002000<start[2] = base + (0x00003000<start[3] = base + (0x00004000<sector_count; i++) {

info->start[i] = base + ((i-3) * (0x00008000</* set sector offsets for top boot block type */ i = info->sector_count - 1;

info->start[i--] = base + info->size - (0x00002000<start[i--] = base + info->size - (0x00003000<start[i--] = base + info->size - (0x00004000<= 0; i--) {

info->start[i] = base + i * (0x00008000</* check for protected sectors */

for (i = 0; i < info->sector_count; i++) {

/* read sector protection at sector address, (A7 .. A0) = 0x02 */

58

/* D0 = 1 if protected */

addr = (vu_short *)(info->start[i]);

info->protect[i] = addr[1<addr = (vu_short *)info->start[0]; *addr = 0xF0F0; /* reset bank */ }

/*----------------------------------------------------------------------- */

void flash_print_info (flash_info_t *info) {

int i;

if (info->flash_id == FLASH_UNKNOWN) {

printf (\"missing or unknown FLASH type\\n\"); return; }

switch (info->flash_id & FLASH_VENDMASK) {

case FLASH_MAN_AMD: printf (\"AMD \"); break; case FLASH_MAN_FUJ: printf (\"FUJITSU \"); break; case FLASH_MAN_TOSH: printf (\"TOSHIBA \"); break; default: printf (\"Unknown Vendor \"); break; }

switch (info->flash_id & FLASH_TYPEMASK) {

case FLASH_AM400B: printf (\"AM29LV400B (4 Mbit, bottom boot sect)\\n\"); break;

case FLASH_AM400T: printf (\"AM29LV400T (4 Mbit, top boot sector)\\n\"); break;

case FLASH_AM800B: printf (\"AM29LV800B (8 Mbit, bottom boot sect)\\n\"); break;

case FLASH_AM800T: printf (\"AM29LV800T (8 Mbit, top boot sector)\\n\"); break;

case FLASH_AM160B: printf (\"AM29LV160B (16 Mbit, bottom boot sect)\\n\"); break;

case FLASH_AM160T: printf (\"AM29LV160T (16 Mbit, top boot sector)\\n\"); break;

case FLASH_AM320B: printf (\"AM29LV320B (32 Mbit, bottom boot sect)\\n\"); break;

case FLASH_AM320T: printf (\"AM29LV320T (32 Mbit, top boot sector)\\n\"); break;

59

default: printf (\"Unknown Chip Type\\n\"); break; }

printf (\" Size: %ld MB in %d Sectors\\n\ info->size >> 20, info->sector_count);

printf (\" Sector Start Addresses:\"); for (i=0; isector_count; ++i) { if ((i % 5) == 0) printf (\"\\n \"); printf (\" %08lX%s\ info->start[i],

info->protect[i] ? \" (RO)\" : \" \" ); }

printf (\"\\n\"); }

/*----------------------------------------------------------------------- */

/*----------------------------------------------------------------------- */ /*

* The following code cannot be run from FLASH! */

static void flash_get_id_word( void *ptr, short *ptr_manuf, short *ptr_dev_id) {

vu_short *addr = (vu_short*)ptr;

addr[0x555] = 0x00AA ; addr[0x2AA] = 0x0055 ; addr[0x555] = 0x0090 ;

*ptr_manuf = addr[0]; *ptr_dev_id = addr[1];

addr[0] = 0xf0f0; /* return to normal */ }

60

static void flash_get_id_long( void *ptr, short *ptr_manuf, short *ptr_dev_id) {

vu_short *addr = (vu_short*)ptr; vu_short *addr1, *addr2, *addr3;

addr1 = (vu_short*) ( ((int)ptr) + (0x5555<<2) ); addr2 = (vu_short*) ( ((int)ptr) + (0x2AAA<<2) ); addr3 = (vu_short*) ( ((int)ptr) + (0x5555<<2) );

*addr1 = 0xAAAA; *addr2 = 0x5555; *addr3 = 0x9090;

*ptr_manuf = addr[0]; *ptr_dev_id = addr[2];

addr[0] = 0xf0f0; /* return to normal */ }

static ulong flash_get_size ( short manu, short dev_id, flash_info_t *info) {

switch (manu) {

case ((short)AMD_MANUFACT):

info->flash_id = FLASH_MAN_AMD; break;

case ((short)FUJ_MANUFACT):

info->flash_id = FLASH_MAN_FUJ; break;

case ((short)TOSH_MANUFACT):

info->flash_id = FLASH_MAN_TOSH; break; default:

info->flash_id = FLASH_UNKNOWN; info->sector_count = 0; info->size = 0;

return (0); /* no or unknown flash */ }

switch (dev_id) {

case ((short)TOSH_ID_FVT160):

info->flash_id += FLASH_AM160T; info->sector_count = 35; info->size = 0x00200000;

61

break; /* => 1 MB */

case ((short)TOSH_ID_FVB160):

info->flash_id += FLASH_AM160B; info->sector_count = 35; info->size = 0x00200000;

break; /* => 1 MB case ((short)AMD_ID_LV400T):

info->flash_id += FLASH_AM400T; info->sector_count = 11; info->size = 0x00100000;

break; /* => 1 MB case ((short)AMD_ID_LV400B):

info->flash_id += FLASH_AM400B; info->sector_count = 11; info->size = 0x00100000;

break; /* => 1 MB case ((short)AMD_ID_LV800T):

info->flash_id += FLASH_AM800T; info->sector_count = 19; info->size = 0x00200000;

break; /* => 2 MB

*/

*/

*/

*/

case ((short)AMD_ID_LV800B):

info->flash_id += FLASH_AM800B; info->sector_count = 19;

info->size = 0x00400000; /*%%% Size doubled by yooth */ break; /* => 4 MB */ case ((short)AMD_ID_LV160T):

info->flash_id += FLASH_AM160T; info->sector_count = 35; info->size = 0x00200000;

break; /* => 4 MB case ((short)AMD_ID_LV160B):

info->flash_id += FLASH_AM160B; info->sector_count = 35; info->size = 0x00200000;

break; /* => 4 MB default:

*/

*/

62

info->flash_id = FLASH_UNKNOWN;

return (0); /* => no or unknown flash */

}

return(info->size); }

/*----------------------------------------------------------------------- */

int flash_erase (flash_info_t *info, int s_first, int s_last) {

vu_short *addr = (vu_short*)(info->start[0]); int flag, prot, sect, l_sect; ulong start, now, last;

if ((s_first < 0) || (s_first > s_last)) { if (info->flash_id == FLASH_UNKNOWN) { printf (\"- missing\\n\"); } else {

printf (\"- no sectors to erase\\n\"); }

return 1; }

if ((info->flash_id == FLASH_UNKNOWN) || (info->flash_id > FLASH_AMD_COMP)) {

printf (\"Can't erase unknown flash type %08lx - aborted\\n\ info->flash_id); return 1; }

prot = 0;

for (sect=s_first; sect<=s_last; ++sect) { if (info->protect[sect]) { prot++; } }

if (prot) {

printf (\"- Warning: %d protected sectors will not be erased!\\n\ prot);

63

} else {

printf (\"\\n\"); }

l_sect = -1;

/* Disable interrupts which might cause a timeout here */ flag = disable_interrupts();

/* Start erase on unprotected sectors */ for (sect = s_first; sect<=s_last; sect++) {

addr[0x555] = (vu_short)0xAAAA; addr[0x2AA] = (vu_short)0x5555; addr[0x555] = (vu_short)0x8080; addr[0x555] = (vu_short)0xAAAA; addr[0x2AA] = (vu_short)0x5555; if (info->protect[sect] == 0) { /* not protected */

addr = (vu_short *)(info->start[sect]) ; addr[0] = (vu_short)0x3030 ; l_sect = sect; }

/* re-enable interrupts if necessary */ if (flag)

enable_interrupts();

/* wait at least 80us - let's wait 1 ms */ udelay (2000);

start = get_timer (0); last = start;

addr = (vu_short *)(info->start[l_sect]); while ((addr[0] & 0x8080) != 0x8080) {

if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) { printf (\"Timeout %d %d %d\\n\ return 1; }

/* show that we're waiting */

if ((now - last) > 100000) { /* every second */

64

putc ('.'); last = now; } } }

DONE:

/* reset to read mode */

addr = (vu_short *)info->start[0];

addr[0] = (vu_short)0xF0F0; /* reset bank */

printf (\"\\ndone\\n\"); return 0; }

/*----------------------------------------------------------------------- * Copy memory to flash, returns: * 0 - OK

* 1 - write timeout * 2 - Flash not erased */

int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt) {

ulong cp, wp, data; int i, l, rc;

wp = (addr & ~3); /* get lower word aligned address */

/*

* handle unaligned start bytes */

if ((l = addr - wp) != 0) { data = 0;

for (i=0, cp=wp; idata = (data << 8) | (*(uchar *)cp); }

for (; i<4 && cnt>0; ++i) {

data = (data << 8) | *src++; --cnt; ++cp; }

for (; cnt==0 && i<4; ++i, ++cp) {

data = (data << 8) | (*(uchar *)cp);

65

}

if ((rc = write_word(info, wp, data)) != 0) { return (rc); }

wp += 4; }

/*

* handle word aligned part */

while (cnt >= 4) { data = 0;

for (i=0; i<4; ++i) {

data = (data << 8) | *src++; }

if ((rc = write_word(info, wp, data)) != 0) { return (rc); }

wp += 4; cnt -= 4; }

if (cnt == 0) { return (0); }

/*

* handle unaligned tail bytes */

data = 0;

for (i=0, cp=wp; i<4 && cnt>0; ++i, ++cp) { data = (data << 8) | *src++; --cnt; }

for (; i<4; ++i, ++cp) {

data = (data << 8) | (*(uchar *)cp); }

return (write_word(info, wp, data)); }

/*----------------------------------------------------------------------- * Write a word to Flash, returns:

66

* 0 - OK

* 1 - write timeout * 2 - Flash not erased */

static int write_word (flash_info_t *info, ulong dest, ulong data) {

vu_short *addr = (vu_short *)(info->start[0]); vu_short sdata;

ulong start; int flag;

/* Check if Flash is (sufficiently) erased */ if ((*((vu_long *)dest) & data) != data) { return (2); }

/* First write upper 16 bits */ sdata = (short)(data>>16);

sdata = (sdata >> 8) | (sdata << 8);

/* Disable interrupts which might cause a timeout here */ flag = disable_interrupts();

addr[0x555] = 0xAAAA; addr[0x2AA] = 0x5555; addr[0x555] = 0xA0A0;

*((vu_short *)dest) = sdata;

/* re-enable interrupts if necessary */ if (flag)

enable_interrupts();

/* data polling for D7 */ start = get_timer (0);

while ((*((vu_short *)dest) & 0x8080) != (sdata & 0x8080)) { if (get_timer(start) > CFG_FLASH_WRITE_TOUT) { return (1); } }

/* Now write lower 16 bits */ sdata = (short)(data&0xffff);

67

sdata = (sdata >> 8) | (sdata << 8);

/* Disable interrupts which might cause a timeout here */ flag = disable_interrupts();

addr[0x555] = 0xAAAA; addr[0x2AA] = 0x5555; addr[0x555] = 0xA0A0;

*((vu_short *)dest + 1) = sdata;

/* re-enable interrupts if necessary */ if (flag)

enable_interrupts();

/* data polling for D7 */ start = get_timer (0);

while ((*((vu_short *)dest + 1) & 0x8080) != (sdata & 0x8080)) { if (get_timer(start) > CFG_FLASH_WRITE_TOUT) { return (1); } }

return (0); }

int flash_real_protect (flash_info_t * info, long sector, int prot) {

info->protect[sector] = prot; return prot; }

68

附录二 dm9000.c

#include #include #include

#ifdef CONFIG_DRIVER_DM9000

#if (CONFIG_COMMANDS & CFG_CMD_NET) #define DM9000_VID_L 0x28 #define DM9000_VID_H 0x29 #define DM9000_PID_L 0x2A #define DM9000_PID_H 0x2B #define DM9000_ID 0x90000A46 #define DM9000_INT_MII 0x00

#define DM9000_PPTR *(volatile u16 *)(DM9000_BASE) #define DM9000_PDATA *(volatile u16 *)(DM9000_BASE + 4) static unsigned char ior(int reg);

/* packet page register access functions */ static u32 GetDM9000ID(void) {

u32 id_val;

DM9000_PPTR = DM9000_PID_H;

id_val = (DM9000_PDATA & 0xff) << 8; DM9000_PPTR = DM9000_PID_L; id_val+= (DM9000_PDATA & 0xff); id_val = id_val << 16;

DM9000_PPTR = DM9000_VID_H;

id_val += (DM9000_PDATA & 0xff) << 8; DM9000_PPTR = DM9000_VID_L;

id_val += (DM9000_PDATA & 0xff);

return id_val; }

static unsigned short get_reg (int regno) {

DM9000_PPTR = regno;

return (unsigned short) DM9000_PDATA;

69

}

static void put_reg (int regno, unsigned short val) {

DM9000_PPTR = regno; DM9000_PDATA = val; }

static void iow(int reg, u8 value) {

DM9000_PPTR = reg;

DM9000_PDATA = value & 0xff; }

static unsigned char ior(int reg) {

DM9000_PPTR = reg;

return DM9000_PDATA & 0xff; }

static void eth_reset (void) {

int IoMode; u8 tmp;

iow(0, 1); /* register 0 set 1 in order to reset, auto clean after 10us*/ udelay(50); /* delay 100us */

IoMode = ior(0xfe) >> 6; /* ISR bit7:6 keeps I/O mode */ if(!IoMode)

printf(\"DM9000 work in 16 bus width\\n\"); else if(IoMode == 2)

printf(\"DM9000 work in 8 bus width\\n\"); else if(IoMode == 1)

printf(\"DM9000 work in 32 bus width\\n\"); else

printf(\"DM9000 work in wrong bus width, error\\n\");

iow(0x1e, 0x01); /* Let GPIO0 output */

iow(0x1f, 0x00); /* Enable PHY , Let GPIO0 output value = 0*/

iow(0xff, 0x80); /* disable interrupt and sram read/write point auto return*/

iow(0x01, 0xc); /* clear TX status */

iow(0x5, 0x33); /* enable rx fuction, note: must set promiscuous mode */

70

ior(0x6);

iow(0x2, 1); /* enable tx fuction */ IoMode = ior(0x01); if(IoMode & 0x40)

printf(\"Link on ethernet at:%d Mbps\\n\ else

printf(\"Not link of ethernet\\n\"); }

void DM9000_get_enetaddr (uchar * addr) {

int i, oft;

unsigned char env_enetaddr[6]; char *tmp = getenv (\"ethaddr\"); char *tmp1 = tmp; char *end; u32 ID;

for (i=0; i<6; i++) {

env_enetaddr[i] = tmp ? simple_strtoul(tmp, &end, 16) : 0; if (tmp)

tmp = (*end) ? end+1 : end; }

memcpy (addr, env_enetaddr, 6); ID = GetDM9000ID(); if ( ID != DM9000_ID) return;

printf(\"Found DM9000 ID:%x at address %x, ethaddr = %s!\\nmp1);

eth_reset ();

for (i = 0, oft = 0x10; i < 6; i++, oft++) iow(oft, addr[i]); return; }

void eth_halt (void) {

GetDM9000ID(); }

int eth_init (bd_t * bd) {

return 0;

71

}

/* Get a data block via Ethernet */ extern int eth_rx (void) {

int i;

unsigned short rxlen; unsigned short *addr; unsigned short status; u8 RxRead; u8 *tmp;

RxRead = ior(0xf0);

RxRead = (DM9000_PDATA) & 0xff; RxRead = (DM9000_PDATA) & 0xff;

if (RxRead != 1) /* no data */ return 0;

DM9000_PPTR = 0xf2; /* set read ptr ++ */

status = DM9000_PDATA; /* get stat */ rxlen = DM9000_PDATA; /* get len */

if (rxlen > PKTSIZE_ALIGN + PKTALIGN)

printf (\"packet too big! %d %d\\n\

for (addr = (unsigned short *) NetRxPackets[0], i = rxlen >> 1; i > 0; i--) *addr++ = DM9000_PDATA; if (rxlen & 1)

*addr = DM9000_PDATA;

/* Pass the packet up to the protocol layers. */ NetReceive (NetRxPackets[0], rxlen);

return rxlen; }

/* Send a data block via Ethernet. */

extern int eth_send (volatile void *packet, int length) {

volatile unsigned short *addr; int tmo;

u8 TxStatus;

72

int length1 = length; int IoMode;

retry:

TxStatus = ior(0x01);

TxStatus = TxStatus & 0xc;

/* Test to see if the chip has allocated memory for the packet */ if (!TxStatus) {

printf (\"unable to send packet; retrying... %d\\n\ for (tmo = get_timer (0) + CFG_HZ; get_timer (0) < tmo;); /*NOP*/;

IoMode = ior(0x01); if(IoMode & 0x40) eth_reset (); goto retry; }

DM9000_PPTR = 0xf8; /* data copy ready set */ /* copy data */

for (addr = packet; length > 0; length -= 2) {

/* printf(\"[%02d][%02d]\ DM9000_PDATA = *addr++; }

iow(0xfd, (length1 >> 8) & 0xff); /*set transmit leng */ iow(0xfc, length1 & 0xff); /* start transmit */ iow(0x02, 1);

return 0; }

#endif /* COMMANDS & CFG_NET */

#endif /* CONFIG_DRIVER_DM9000 */

73

附录三 smdk2440nand.h

#ifndef __SMDK2440_NAND_H #define __SMDK2440_NAND_H

#define CFG_NAND_BASE 0x4E000000

#define CFG_MAX_NAND_DEVICE 1 /* Max number of NAND devices */ #define SECTORSIZE 512

#define ADDR_COLUMN 1 #define ADDR_PAGE 2

#define ADDR_COLUMN_PAGE 3

#define NAND_ChipID_UNKNOWN 0x00 #define NAND_MAX_FLOORS 1 #define NAND_MAX_CHIPS 1

#define NAND_WAIT_READY(nand) {while(!(rNFSTAT&(1<<0)));}

// find in samsung uboot

#define NAND_DISABLE_CE(nand) {rNFCONT |= (1<<1);} #define NAND_ENABLE_CE(nand) {rNFCONT &= ~(1<<1);} #define NF_SOFT_UnLock() {NFCONT&=~(1<<12);} // end find

#define WRITE_NAND_COMMAND(d) {rNFCMD=d;} #define WRITE_NAND_ADDRESS(d) {rNFADDR=d;}

#define WRITE_NAND(d) {NF_SOFT_UnLock();rNFDATA8=d;} #define READ_NAND() (rNFDATA8)

/* the following functions are NOP's because S3C24X0 handles this in hardware */ #define NAND_CTL_CLRALE() #define NAND_CTL_SETALE() #define NAND_CTL_CLRCLE() #define NAND_CTL_SETCLE()

#define CONFIG_MTD_NAND_VERIFY_WRITE 1 #define CONFIG_MTD_NAND_ECC_JFFS2 1

#endif

74

因篇幅问题不能全部显示,请点此查看更多更全内容

Top