OpenVINOTM,给你看得见的未来!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » VxWorks在EasyARM2200和SmartARM2200上的移植

共9条 1/1 1 跳转至

VxWorks在EasyARM2200和SmartARM2200上的移植

菜鸟
2007-04-30 02:16:00    评分

**********************************************
* VxWorks在EasyARM2200和SmartARM2200上的移植 *
**********************************************
------ 浅谈《ecos增值包》辅助开发VxWorks BSP
2007/04/07 asdjf@163.com www.armecos.com

随着《ecos增值包》用户群的增大,我们决定进一步增加对《ecos增值包》使用者的支持力度,如果能自行解决版权问题,那么希望下面的文章能对用户您开发VxWorks BSP有帮助。

目前,EasyARM2200和SmartARM2200开发板已经(增值)支持uCos、uCLinux、ecos、uITRON(ecos兼容层)、VxWorks等操作系统(Linux和WinCE需要带MMU的芯片);三款TCP/IP完整协议栈(openBSD、FreeBSD、lwip)、若干BootLoader(redboot、u-boot、bootrom、vivi、blob等)。有些用户用它快速开发交互式web server,有些用来作为辅助开发工具、有些用做快速原型验证......其实,《ecos增值包》是一个开发平台,它不仅限于ARM7,也适用于ARM15、ARM16等未来芯片,因为ecos支持对PowerPC、MIPS、X86、SH等不同体系架构的抽象,所以,您针对ecos平台开发的软件将很少/不用改动就可以平滑移植到新的体系架构上,保护您的投资利益。ecos平台的抽象机制(HAL层、设备文件、C++等)确保您将注意力集中在事物的核心本质上,而不必被细节干扰。例如:您可以针对ecos抽象出的万能中断模型编程,而不必关心具体CPU的中断体系;只对串口设备文件操作而不管UART寄存器的细节等。即使您没有很丰富的经验,也能轻松做项目,因为ecos平台将辅助您解决稳定性、效率、功耗、成本等棘手问题。ecos提供了FS、GUI、TCP/IP等全套功能部件,完全满足您对数据存储、显示、网络互联、控制等方面的需求,是完整的一揽子解决方案。

从ecos的观点看VxWorks BSP的开发,主要是平台和变种抽象层的移植,即我们要针对ARM7变种LPC2210和其承载平台EasyARM2200、SmartARM2200进行移植。ecos和VxWorks简直太象了(仅函数名不同):都使用组件概念(不知是谁抄谁的),都用GNU编译器编译(或diab),都实现差不多的功能。这为使用《ecos增值包》开发VxWorks BSP提供了便利。

-----------------------
| VxWorks BSP移植规划 |
-----------------------
1、实现硬件初始化
2、实现LPC2210中断体系到VxWorks中断体系的映射
3、实现心跳时钟、辅助时钟、时标
4、实现基于查询/中断的串口驱动
------------------------------------------------MileStone
5、实现NOR/NAND Flash驱动
6、实现多种字符设备、块设备、MUX网络设备驱动程序
7、实现TSFS/dosFS/TrueFFS文件系统、MiniGUI等
......

------------
| 移植难点 |
------------
1、工具使用:Makefile编写、反汇编、调试器使用、Tornado使用等
2、对LPC2210工作原理、硬件体系的理解
3、对BSP目录结构、工作流程、内存分布、ROM映像类型的理解等
4、正确的调试分析方法、完备的调试环境、有效的技术支持

---------------------------------------
| 《ecos增值包》针对BSP开发的解决方案 |
---------------------------------------
《ecos增值包》本身就提供GNU开发环境,提供step by step的指导,省去了自己搭建环境的苦恼,可以快速上手。文档中有专门章节讲解Makefile使用,中断体系、串口原理等等,这对于攻克难点有莫大的帮助。进行嵌入式开发不能光看书,任何书本都不能面面俱到,而且写出的书,信息量至少损失20%,理解起来又会损失20%,真正做还要再损失至少20%,所以,比较好的方法是靠“熏”。不断在一个已经搭建好的稳定平台上反复做实验,举一反三,天长日久,就会不知不觉熏陶出来。很多用户不懂得调试,遇到问题就问别人,其实首先应该问计算机,掌握调试方法就相当于掌握了打渔的方法,学会了点金术,发现问题,debug it,trace it,解决问题,It's hacker's solution。

-----------------------
| VxWorks BSP目录结构 |
-----------------------
BSP(板级支持包)就是指VxWorks的驱动程序。当然,其他一些操作系统也有自己的BSP驱动,还有些系统的驱动程序不叫BSP,总之,这里的BSP就是指驱动程序啦。
安装VxWorks和其BSP开发环境后,会出现c:\Tornado2.2目录,在target子目录下的内容就是BSP的相关内容。

------config BSP配置文件
| |
| |-----all 通用配置文件
| |_____BspName 板级支持包
|
target |---H 头文件
|---Lib 库文件
|---Man 说明文件
|---Src VxWorks特殊的源代码
|___Unsupport 工具及驱动

我们实际关心的只有target\config\all和target\config\BspName两个目录。target\config\BspName里面是我们要移植的真正的BSP程序。

-------------------
| VxWorks工作流程 |
-------------------
VxWorks分为bootrom和内核映像两部分,分别由不同文件完成功能。

-------------------------------------------
bootrom执行流程:
-------------------------------------------

romInit
(target\config\zlgarm\romInit.s)
|
romStart
(target\config\all\bootInit.c)
|
usrInit
(target\config\all\bootConfig.c)
|
bootCmdLoop
|
-----------------
| |
autoBoot |_bootLoad
|_bootLoad |_go
|_go

-------------------------------------------
VxWorks内核映像启动流程图:
-------------------------------------------
sysInit
(target\config\zlgarm\sysALib.s) 硬件相关
| ----------------
usrInit 通用启动过程
(target\config\zlgarm\sysALib.s)
|
-----------------
| | |
usrKernelInit | sysHwInit
| (target\config\zlgarm\sysLib.c)
kernelInit
|
Hooks-->usrRoot
|
|--------
|
sysClkConnect
|
|---------
|
sysHwInit2

bootrom首先执行硬件初始化romInit,它是用汇编写的,主要考虑到有些操作不能用C语言完成,例如开关中断,特权堆栈初始化等。然后使用C语言写的程序romStart拷贝搬移ROM映像,清RAM,解压代码(如果需要的话),这部分用C的好处是编程方便。usrInit是用来执行最小内核初始化的,然后进入命令循环,分两种情况,如果7秒钟(可设置)内无按键就自动执行预先烧好的程序,如果没有找到程序,就不断重复7秒等待过程。如果有按键按下,就进入命令行界面接收用户命令。

VxWorks内核被加载后首先执行硬件相关初始化,这部分初始化工作与bootrom中的相同。为什么要重复执行相同的硬件初始化呢?主要是考虑到VxWorks可能被其他bootloader加载,为了保证工作环境一致,所以这里再次重复相同的硬件初始化过程。接下来配置内核数据、初始化内核、启动usrRoot任务、连接启动时钟心跳,OK,现在系统就活了。虽然VxWorks一般不开源,但提供很多钩子(Hooks)函数,可以选择适当位置插入用户代码,完成特殊需求。

-------------------
| VxWorks内存布局 |
-------------------
内存布局是VxWorks移植中需要非常准确理解的内容,它关系到config.h的配置和Makefile的编写,如果处理不好或者概念搞错了,那就全乱了。

-------------- 0x00100000 = LOCAL_MEM_SIZE = sysMemTop()
| |
| RAM |
| 0 filled |
| |
|------------| = (romInit+ROM_COPY_SIZE) or binArrayStart
| ROM image |
|----------- | 0x00090000 = RAM_HIGH_ADRS
| STACK_SAVE |
|------------|
| | 0x00080000 = 0.5 Megabytes
| |
| |
| 0 filled |
| |
| | 0x00001000 = RAM_ADRS & RAM_LOW_ADRS
| |
| | exc vectors, bp anchor, exc msg, bootline
| |
| |
-------------- 0x00000000 = LOCAL_MEM_LOCAL_ADRS

--------------
| ROM |
| | 0xff8xxxxx = binArrayStart
| |
| | 0xff800008 = ROM_TEXT_ADRS
-------------- 0xff800000 = ROM_BASE_ADRS

上图是一个内存布局实例(1M字节RAM空间),地址值是自己编的,主要看宏定义。
ROM_BASE_ADRS是指ROM的起始地址,例如:0x80000000。
ROM_TEXT_ADRS是代码段的起始地址,有些CPU不能从0开始执行,所以需要一个偏移量,不同CPU的要求不同,一般和ROM_BASE_ADRS值相同。

LOCAL_MEM_LOCAL_ADRS是指可用RAM的起址,如:0x81000000。
RAM_LOW_ADRS是内核在内存中的加载位置。
RAM_HIGH_ADRS是需要拷贝到内存的ROM映像和堆栈的分界线,向下是堆栈,向上是ROM映像的起址。
未用的部分可以选择清零(用“0”填充)。
大体上就是这么一个分布,但是限于篇幅,还有很多细节没有谈到。比如:堆栈保护、解压缩代码空间、不同ROM映像类型的内存分布差异等。这些看代码可以详细了解,还是那句话,亲自调试就能了解全部细节,写出来的内容总会有信息量的损失,从调试中你能得到最准确全面的信息。

------------------------
| VxWorks的ROM映像类型 |
------------------------
VxWorks有三种ROM映像类型:
1、ROM_RESIDENT-----驻留ROM型。在ROM中运行,只有数据段放在RAM里。
2、ROM_COPY---------复制ROM型。把主要映像从ROM中拷贝到RAM里并跳到其RAM入口点执行。
3、ROM_COMPRESS-----解压ROM型。把主要映像从ROM中解压到RAM里并跳到其RAM入口点执行。
各种ROM映像类型的内存布局图如下:

ROM
--------------
| |
|------------|
| data |
|------------| 0xff8xxxxx = ROM_DATA_ADRS
| text |
| | 0xff800008 = ROM_TEXT_ADRS
-------------- 0xff800000 = ROM_BASE_ADRS

RAM
-------------- 0x00100000 = LOCAL_MEM_LOCAL_ADRS + LOCAL_MEM_SIZE
| |
| |
|------------| = RAM_DATA_ADRS + data segment size
| |
|data segment|
| |
|------------| 0x00001000 = RAM_DATA_ADRS
| initial sp |
|------------| = RAM_DATA_ADRS - STACK_SAVE
| |
| |
-------------- 0x00000000 = LOCAL_MEM_LOCAL_ADRS

上图是驻留ROM型映像的内存分布,可见,只有数据段拷贝到了RAM内存里,当然堆栈也在RAM内存中。

ROM
--------------
| |
|------------| 0xff8xxxxx = binArrayEnd
| subImage |
|------------| 0xff8xxxxx = binArrayStart
| data |
|------------| 0xff8xxxxx = ROM_DATA_ADRS
| text |
| | 0xff800008 = ROM_TEXT_ADRS
-------------- 0xff800000 = ROM_BASE_ADRS

RAM
-------------- 0x00100000 = LOCAL_MEM_LOCAL_ADRS + LOCAL_MEM_SIZE
| |
| |
|------------|
| temp data |
|------------| 0x00090000 = RAM_DATA_ADRS
| initial sp |
|------------| = RAM_DATA_ADRS - STACK_SAVE
| |
| |
|------------|
| |
| subimage |
| |
|------------| 0x00001000 = RAM_DST_ADRS (for non-resident images)
| |
| |
-------------- 0x00000000 = LOCAL_MEM_LOCAL_ADRS

上图是复制ROM型和解压ROM型映像的内存分布,他们比驻留ROM型稍微有点复杂。复制ROM型和解压ROM型映像的主要差别的是subimage是否压缩,一般压缩率能超过55%,允许在ROM中烧写更大的系统,唯一的代价是增加了几秒钟的启动延迟,而复制ROM型映像没有解压过程,所以启动速度更快。subimage是由make产生的中间映像,并被make插在ROM映像文件中。代码段、初始化数据段、subimage在ROM中的排列顺序就如上图所示。

复制ROM型映像直接把binArrayStart处的subimage拷贝到RAM_DST_ADRS,没有上面的temp data部分。

对于解压ROM型映像,首先拷贝压缩的代码数据段到RAM_DATA_ADRS位置(即temp data部分),然后运行解压缩例程把解压后的subimage放置在RAM_DST_ADRS。

RAM_DST_ADRS和RAM_DATA_ADRS宏均来自定义于make的链接地址,缺省值分别为Makefile文件中的RAM_LOW_ADRS和RAM_HIGH_ADRS。关于如何改变链接地址的信息,参见“target/h/make/rules.bsp”文件。

---------------
| BSP移植详述 |
---------------
罗嗦了那么多,终于开始正式移植了。还是前面那句话,写出来的东西总是挂一漏万,无论我怎么组织语言,总有说不完的话,数不清的注意事项,虽然心里很明白,但如果每一点都指出来,那会让读者更晕,云里雾里的,没有实际做过,很难理解我说的细节,有些妙处真的是只可意会不可言传,我也不知道怎么表达出来,并非留一手。我希望帮你“熏”一下,能帮多少算多少,最重要的是你要亲自实践,光看游泳书是学不会游泳的。

==========
硬件初始化
==========
硬件初始化直接抄板子自带的汇编初始化源码,改进一下执行效率即可。

======
中断
======
中断需要提供:初始化、返回向量号、中断使能、中断禁止函数。
中断初始化真是妙不可言,因为LPC2210提供VIC映射,所以一步就可以得到向量号,不必查询,所以初始化做得好,中断响应效率可以很高。中断使能只要对LPC2XXX_VIC_INT_ENABLE寄存器对应位置1即可;中断禁止时对LPC2XXX_VIC_INT_ENABLE_CLR寄存器对应位置1。

在调试BSP时,运行后总是没有动静,通过内存打印技术,发现程序死在了excVecInit()函数处。见名知意,这个函数肯定和中断向量初始化有关,但它到底是如何工作的呢?虽然有源码,但那个是for X86的,对于ARM体系相关的函数部分没有指导意义啊,怎么办呢?此时,需要祭出战无不胜,攻无不克,见神杀神,见鬼杀鬼的利器---反汇编调试。

《ecos增值包》提供了GNU开发环境,使用“arm-elf-objdump -d vxWorks_romResident > 1.txt”就可以得到VxWorks的汇编列表文件1.txt,里面有地址和汇编信息,可用于和AXD汇编对照调试。

8101a644 <excVecInit>:
8101a644: e92d4800 stmdb sp!, {fp, lr}
8101a648: e24dd008 sub sp, sp, #8 ; 0x8
8101a64c: eb002d4e bl 81025b8c <armInitExceptionModes>
8101a650: e59fb4d4 ldr fp, [pc, #1236] ; 8101ab2c <$d> fp=81301524 excEnterTbl
8101a654: e3a01005 mov r1, #5 ; 0x5
8101a658: e59f04d0 ldr r0, [pc, #1232] ; 8101ab30 <$d+0x4> r0 = e59ff0f4
8101a65c: e24bb008 sub fp, fp, #8 ; 0x8

8101a660: e59b3008 ldr r3, [fp, #8]
8101a664: e2511001 subs r1, r1, #1 ; 0x1
8101a668: e5830000 str r0, [r3]
8101a66c: e59bc008 ldr ip, [fp, #8]
8101a670: e59b300c ldr r3, [fp, #12]
8101a674: e28bb008 add fp, fp, #8 ; 0x8
8101a678: e58c30fc str r3, [ip, #252]
8101a67c: 1afffff7 bne 8101a660 <excVecInit+0x1c> 8101a660
8101a680: e3a0c000 mov ip, #0 ; 0x0
8101a684: e59f34a8 ldr r3, [pc, #1192] ; 8101ab34 <$d+0x8> r3 = e7fddefe
8101a688: e58c3000 str r3, [ip]
8101a68c: e59fc4a4 ldr ip, [pc, #1188] ; 8101ab38 <$d+0xc> ip = 813087C8
8101a690: e59cb01c ldr fp, [ip, #28] fp = [813087E4]
8101a694: e35b0000 cmp fp, #0 ; 0x0
8101a698: 0a000003 beq 8101a6ac <excVecInit+0x68> 8101a6ac
8101a69c: e3a00000 mov r0, #0 ; 0x0
8101a6a0: e3a0101c mov r1, #28 ; 0x1c
8101a6a4: e1a0e00f mov lr, pc
8101a6a8: e1a0f00b mov pc, fp

8101a6ac: e59f3488 ldr r3, [pc, #1160] ; 8101ab3c <$d+0x10> r3 = 8101a6dc
8101a6b0: e59fc488 ldr ip, [pc, #1160] ; 8101ab40 <$d+0x14> ip = 813102a4 _func_armIrqHandler
8101a6b4: e58c3000 str r3, [ip] _func_armIrqHandler = 8101a6dc excIntHandle
8101a6b8: e3a00000 mov r0, #0 ; 0x0
8101a6bc: e28dd008 add sp, sp, #8 ; 0x8
8101a6c0: e8bd8800 ldmia sp!, {fp, pc}

8101b494 <intVecBaseSet>:
8101b494: e1a0f00e mov pc, lr

通过反复分析,intVecBaseSet在ARM体系上屁用也没有,是空的,根本不能指望通过它改变中断向量基址VEC_BASE_ADRS。这段汇编的大概意思是:在0地址开始处填写中断向量表跳转语句,在100H处写跳转地址,没有中断服务子程序的入口填写0xE59FF9F4(未定义指令,用于引发异常)。怪不得死机,LPC2210重映射到0地址的RAM空间只有64字节,向100H只读地址写数据会引发异常(44B0向ROM里写数据不会引发异常,顶多写不进去就是了,看来LPC2210在地址空间防护上做了一些工作,能识别出向只读空间里写数据的错误。虽然是好事,但给我们移植BSP带来了困难,怎么办呢?)。VxWorks考虑得真是周到,这部分是用源码提供的,那就咔嚓了excVecInit(),换成自己的myExcVecInit(),齐活。

VxWorks提供的中断处理函数不能动,因为要使用VxWorks的中断体系,由它来调用我们提供的处理函数,这样,就把LPC2210的中断体系映射到了VxWorks上。

既然可以替换成自己的代码,那就不用仿照VxWorks向量表原来的构造了,把它推翻,换个和LPC2210匹配更好的结构。我用LPC2210内部IRAM保存向量表和ISR服务程序入口地址,因为内部IRAM快,还可节省一些外部XRAM空间,然后把它映射到0地址即可(真是绝配啊!)。汇编源程序如下:(target\config\zlgarm\sysALib.s)

.globl FUNC(myExcVecInit) /* own code for armInitExceptionModes()---zk */

.extern FUNC(excEnterUndef)
.extern FUNC(excEnterSwi)
.extern FUNC(excEnterPrefetchAbort)
.extern FUNC(excEnterDataAbort)
.extern FUNC(intEnt)

.extern FUNC(armInitExceptionModes)

.extern FUNC(_func_armIrqHandler)
.extern FUNC(excIntHandle)

_ARM_FUNCTION(myExcVecInit)

stmfd sp!, {r0-r10,lr}
bl FUNC(armInitExceptionModes)

copy_vector:
adr r0, real_vectors
add r2, r0, #64
ldr r1, =0x40000000 /*前面的初始化程序已经把此RAM的前64字节重映射到了0地址*/
/*add r1, r1, #0x08*/
vector_copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble vector_copy_loop

/*反汇编的程序在此处判断了一个内存中的变量,但我没找到该变量名,没判断变量是否为0就直接赋值了。*/
/*看效果没有任何不良影响。*/
ldr r0, L$__func_armIrqHandler
ldr r1, L$_excIntHandle
str r1, [r0]

nop
ldmfd sp!, {r0-r10,pc}


/*************************************************/
/* interrupt vectors */
/*************************************************/
real_vectors:
ldr pc,.reset //0x00
ldr pc,.undefined_instruction //0x04
ldr pc,.software_interrupt //0x08
ldr pc,.prefetch_abort //0x0C
ldr pc,.data_abort //0x10
ldr pc,.not_used //0x14
ldr pc,.irq //0x18
ldr pc,.fiq //0x1C

/*************************************************/

.reset: .word 0xE59FF9F4
.undefined_instruction: .word FUNC(excEnterUndef)
.software_interrupt: .word FUNC(excEnterSwi)
.prefetch_abort: .word FUNC(excEnterPrefetchAbort)
.data_abort: .word FUNC(excEnterDataAbort)
.not_used: .word 0xE59FF9F4 /* not use */
.irq: .word FUNC(intEnt)
.fiq: .word 0xE59FF9F4 /* fiq */

注意噢:子程序要压栈保存所有改变的寄存器(除非明确需要改变,如传参数值,才不需要保存恢复),不然不要怪我没有提醒你ARM编译器会优化程序,使用寄存器传值,如果你的子程序内部改变了寄存器值又没有恢复原先的值,那么插入你自编的函数,会发生很多奇妙的事哦!
stmfd sp!, {r0-r10,lr}
ldmfd sp!, {r0-r10,pc}

============================
调试器、内存打印、点灯的异同
============================
这三者都是很好的调试方法,尤其在调试BSP类程序时很有用,此时,串口还没有工作,但又想看到信息以便进行分析,怎么办?凉拌。
点灯比较简单,看看灯的亮灭和组合就知道程序现在跑到哪了,适合问题定位。
内存打印可以表达更多信息,但需要bootloader支持,通过命令行查看内存。
调试器功能最强,有些支持源码调试,相当方便。打印语句调试是让被调程序牵着鼻子走,调试器是牵着被调程序鼻子走(可以任意修改走向),想牵谁的鼻子自己决定。
《ecos增值包》提供的调试环境适合粗调,尽管需要被被调程序牵着鼻子走,配合AXD调试(不适合粗调,容易陷在细节中,最好先粗调定位问题,再对问题点用调试器细调),还是很快的。

========
时钟驱动
========
时钟驱动需要实现:系统时钟和辅助时钟中断、连接、禁止、使能、读、写,时标中断、连接、禁止、使能、读周期、读频率、读tick值、锁中断读tick值。
需要注意的是,每次时钟中断ISR处理时都别忘了清时钟中断,不然,CPU一直陷入中断,就不能做别的事情了。具体代码可以参照其他BSP模板(在target\config\目录下就是各种ARM板子的BSP移植源码)并结合LPC2210开发板的时钟驱动范例自行写出。

========
串口驱动
========
VxWorks的串口驱动比较特别,与其他驱动不同。
VxWorks串口驱动同时支持基于查询和中断的驱动,可以在运行时通过IOCTL配置运行模式和属性。
查询方式很简单,这里主要说下基于中断的驱动。

LPC2210的UART收发中断共用同一个中断号,我们在中断服务程序里先收后发,通过状态寄存器判断是收中断还是发中断亦或是超时、错误中断。

(void) intConnect(INUM_TO_IVEC(devParas[i].vector), zlgarmInt, (int)&zlgarmChan[i] );
intEnable(INUM_TO_IVEC(devParas[i].vector));
把串口中断ISR服务程序挂在相应中断号devParas[i].vector上并使能中断。这样每次UART中断都会调用zlgarmInt函数。

void zlgarmInt(ZLGARM_CHAN *pChan)
{
zlgarmIntRcv(pChan);
zlgarmIntTx(pChan);
}
在zlgarmInt里先调用收函数,再调用发函数,实现收发操作共用同一个中断号的目的。

LOCAL int zlgarmTxStartup
(
SIO_CHAN * pSioChan /* channel to start */
)
{
ZLGARM_CHAN * pChan = (ZLGARM_CHAN *)pSioChan;
unsigned int stat;

unsigned char * base = (unsigned char *)pChan->regs;

zlgarmIntTx(pChan);

HAL_READ_UINT8(base+LPC2XXX_UART_IER, stat);
stat = stat | LPC2XXX_UART_IER_TXE;
HAL_WRITE_UINT8(base+LPC2XXX_UART_IER, stat);

return (OK);
}
每次发送前,VxWorks先调用zlgarmTxStartup激活中断,使后续发送自动化。

void zlgarmIntTx
(
ZLGARM_CHAN * pChan /* channel generating the interrupt */
)
{
unsigned int stat;
char outChar;
unsigned char * base = (unsigned char *)pChan->regs;

HAL_READ_UINT8(base + LPC2XXX_UART_LSR, stat);
if((stat & LPC2XXX_UART_STAT_TXE) == 0)
return;
if ((*pChan->getTxChar) (pChan->getTxArg, &outChar) != ERROR)
HAL_WRITE_UINT8(base + LPC2XXX_UART_THR, outChar);
else
{
HAL_READ_UINT8(base+LPC2XXX_UART_IER, stat);
stat = stat & (~LPC2XXX_UART_IER_TXE);
HAL_WRITE_UINT8(base+LPC2XXX_UART_IER, stat);
}
}
在发送时先判断是否正在发送,如果发送FIFO不为空,那么,肯定正在发送数据,程序退出,等到发送结束,发送中断会自动调用这个程序再次发送的。如果发送FIFO为空,说明已发送完毕,此时上层回调函数getTxChar从VxWorks管理的循环队列中取出一个字节数据继续发送,只要字符在发送状态,发送中断就会消失,一旦发送完毕会再次触发。如果循环队列中已无字符,一定要记住关闭发送中断使能,不然,由于发送FIFO为空,发送中断总是有效,CPU会反复陷入发送中断。

void zlgarmIntRcv
(
ZLGARM_CHAN * pChan /* channel generating the interrupt */
)
{
unsigned int stat;
unsigned int c;
unsigned char * base = (unsigned char *)pChan->regs;

HAL_READ_UINT8(base + LPC2XXX_UART_LSR, stat);
if ((stat & LPC2XXX_UART_STAT_RDR) != 0)
{
HAL_READ_UINT8(base+LPC2XXX_UART_RBR, c);
(*pChan->putRcvChar) (pChan->putRcvArg, c);
}
}
接收中断比较简单,只要判断接收状态即可,调用上层回调函数putRcvChar把收到的字符存入VxWorks管理的循环队列。

总体感觉VxWorks串口架构设计得非常合理和灵活,获益良多!妙不可言!

=========
flash驱动
=========


===========
MUX网络驱动
===========


=====================
Makefile和onfig.h配置
=====================
见《VxWorks内存布局》节

========
编译方法
========
---------------
bootrom编译方法
---------------
拷贝c:\Tornado2.2\host\x86-win32\bin\torVars.bat到c:\Tornado2.2\target\config\zlgarm目录并改名为zlgarmmake.bat。增加语句,最终内容如下:

rem Command line build environments
set WIND_HOST_TYPE=x86-win32
set WIND_BASE=C:\Tornado2.2
set PATH=%WIND_BASE%\host\%WIND_HOST_TYPE%\bin;%PATH%

rem Diab Toolchain additions
set DIABLIB=%WIND_BASE%\host\diab
set PATH=%DIABLIB%\WIN32\bin;%PATH%

make clean
make bootrom_res.bin

以后只要双击该文件就可以编译驻留ROM型bootrom映像。

---------------
vxworks编译方法
---------------
打开Tornado编译器,选择build标签,右键设定default_romResident为激活编译类型。以后只要点击编译图标即可。
注意:
每次修改Makefile和config.h后都要重新生成项目,因为Tornado使用的是上一次配置的项目,本次修改配置无效。
注释使用“/* */”,不要使用“//”。

========
使用方法
========
把bootrom或vxworks程序烧写到flash里,启动后就可以看到logo启动界面。enjoy it! ^_^
有了BSP,就可以专心开发VxWorks应用程序了,只要在生成项目时指定BSP即可。


《ecos增值包》用户(需提供识别码)对VxWorks BSP在EasyARM2200和SmartARM2200上移植有任何疑问均可来信询问(asdjf@163.com),不提供源码,提供文档支持。




关键词: VxWorks     EasyARM2200     Smart    

菜鸟
2007-04-30 02:17:00    评分
2楼

***********************************
* VxWorks for LPC2210应用编程实战 *
***********************************
------ 《ecos增值包》选配之VxWorks应用编程实战
2007/04/22 asdjf@163.com www.armecos.com

VxWorks for SmartARM2200试用版vxWorks_romResident.bin[url href=http://bbs.21ic.com/upfiles/img/20074/2007423222128169.zip]下载[/url] 226K
本试用程序需《Redboot for smartarm2200正式版》支持
[url href=http://www.armecos.com/purchase/order.asp?id=3]现在购买[/url][b][#ff0000]¥10[/#][/b]

《ecos增值包》用户可以选配VxWorks for LPC2210,以便在EASYARM2200或SMARTARM2200上进行VxWorks应用程序开发。该选件不单独出售,不含BSP源码,需另行购买开发环境。如过您满足这些要求,那么希望下面的文章能对您开发VxWorks应用程序有所帮助。

VxWorks和ecos一样,都是多任务操作系统,都用GNU工具链开发,只是API函数名略有不同,因此,我们分别用ecos中介绍过的两个demo程序对照讲解VxWorks应用编程的开发方法。

与ucos51和ecos相似,第一次使用操作系统,先弄个多任务跑跑,demo1演示了多任务并发运行(注:本站也出售ucos51软件包,51工程师也可以跑OS)。3个线程任务同时打印信息,A线程每秒打印一次,B线程每3秒打印一次,C线程每6秒打印一次。对比这些程序,其实就是API函数名字不同啦,思路完全相同,这说明OS抽象了硬件和时间流,使我们更接近应用的实质。下面是demo1源码:

#define STACK_SIZE 2000

void taska(int arg);
void taskb(int arg);
void taskc(int arg);

void demo1(void)
{
printf("Demo1 Start......\n");

taskSpawn("taska", 90, 0, STACK_SIZE, taska, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);

taskSpawn("taskb", 90, 0, STACK_SIZE, taskb, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0);

taskSpawn("taskc", 90, 0, STACK_SIZE, taskc, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}

void taska(int arg)
{
for(;;)
{
printf("\tAAAAAA111111 is active.thread data is %d.\n", arg);
taskDelay(100);
}
}

void taskb(int arg)
{
for(;;)
{
printf("\tBBBBBB333333 is active.thread data is %d.\n", arg);
taskDelay(300);
}
}

void taskc(int arg)
{
for(;;)
{
printf("\tCCCCCC666666 is active.thread data is %d.\n", arg);
taskDelay(600);
}
}

由上可见,VxWorks中创建任务使用taskSpawn,延时使用taskDelay。下表对比了创建任务和任务延时在ucos51、ecos、VxWorks中的名称。
------------------------------------------------------
| | 创建任务 | 任务延时 |
------------------------------------------------------
| ucos51 | OSTaskCreate | OSTimeDly |
------------------------------------------------------
| ecos | cyg_thread_create | cyg_thread_delay |
------------------------------------------------------
| VxWorks | taskSpawn | taskDelay |
------------------------------------------------------

VxWorks函数语法:

任务号 = taskSpawn(名称,优先级,选项,堆栈大小,任务函数名,参数1,...参数10);
taskDelay(延时tick数);

测试程序运行结果,Demo1程序创建了3个任务A、B、C,优先级全为10,A每秒显示一次,B每3秒显示一次,C每6秒显示一次。从显示结果看,显示3个A后显示1个B,显示6个A和2个B后显示1个C,结果显然正确。

(demo1运行效果图)

下面的demo2例子是控制GPIO让蜂鸣器发声的应用程序,响1秒停1秒,周而复始。

#define HAL_WRITE_UINT32( _register_, _value_ ) (*((volatile unsigned int *)(_register_)) = (_value_))

#define LPC2XXX_GPIO_IO0SET 0xE0028004
#define LPC2XXX_GPIO_IO0DIR 0xE0028008
#define LPC2XXX_GPIO_IO0CLR 0xE002800C

#define STACK_SIZE 2000
#define BEEPCON 0x0000080

void task(void);

void demo2(void)
{
printf("Demo2 Start......\n");

taskSpawn("task", 90, 0, STACK_SIZE, task, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}

void task(void)
{
HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0DIR,BEEPCON);

for(;;)
{
HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0SET,BEEPCON);
taskDelay(100);
HAL_WRITE_UINT32(LPC2XXX_GPIO_IO0CLR,BEEPCON);
taskDelay(100);
}
}

由上可见,VxWorks支持直接寄存器操作,使用平板内存模式,和ecos操作硬件I/O的方法一样。

注意:应用程序使用/**/做注释,如果使用"//"注释,需要去除-ansi编译选项。

下面图示说明使用方法:

首先将vxWorks_romResident.bin文件烧写在板子的flash里,设置好跳线。上电后将看到控制台shell界面,

打开Tornado,新建downloadable项目,

(新建项目图)1、2、3
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101550549.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101617722.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101639274.jpg[/img]

增加demo文件并编译,会提示生成依赖文件,选OK。

4、5
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101659764.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101716328.jpg[/img]

设置Target Server(配置文件设置),使用串口通信,9600波特率,好象最高速率为38400,太高就不容易连接成功,用网络的话就比较快。注意:Core File and Symbols里的File文件要与烧写在flash里的VxWorks一致,即带调试信息的ELF文件。连接成功会显示成功信息,托盘里出现一个象靶子一样的图标。

6、7、8、9
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101736432.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101751982.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/200742310187477.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101821792.jpg[/img]

下载.out文件

10
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101848188.jpg[/img]

启动目标调试器,点那个象蚂蚁一样的图标。打开源文件设置断点什么的,然后点那个奔跑小人图标,就可以调试了,支持单步、全速等各种调试手段。开始时会询问运行任务函数名称和参数,如图设置即可。

13、14、11
[img]http://bbs.21ic.com/upfiles/img/20074/200742310197112.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101921536.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101937197.jpg[/img]

串口0控制台运行效果

12
[img]http://bbs.21ic.com/upfiles/img/20074/2007423101956852.jpg[/img]

点击->i图标可以启动主机shell,输入i可以查看任务运行情况。刚开始有4个任务,执行任务、日志任务、shell终端、wdb调试驻留任务。当运行demo1后再输入i查询,可以看到多出了3个新任务:taska、taskb、taskc,正是我们在demo1里创建的3个任务。可以清楚看到任务优先级和延迟剩余时间及状态等信息。

15、16
[img]http://bbs.21ic.com/upfiles/img/20074/2007423102015154.jpg[/img]
[img]http://bbs.21ic.com/upfiles/img/20074/2007423102030305.jpg[/img]


菜鸟
2007-05-02 23:33:00    评分
3楼
好东西,正在移植这个

菜鸟
2007-05-12 00:07:00    评分
4楼

***********************************************************
* VxWorks之MUX网络驱动在EasyARM2200和SmartARM2200上的实现 *
***********************************************************
------ 浅谈《ecos增值包》辅助开发VxWorks网络驱动
2007/05/11 asdjf@163.com www.armecos.com

又有一些网友购买《ecos增值包》了,下面进一步谈谈VxWorks下网络驱动程序的开发,目标板为EasyARM2200和SmartARM2200(网卡芯片为RTL8019AS),调试环境是redboot,依靠打印输出调试。

--------------------
| 网络驱动程序概述 |
--------------------
在VxWorks中,网卡驱动程序分为END(Enhanced Network Driver)和BSD两种。END驱动程序基于MUX模式,是目前在VxWorks操作系统上应用最广泛的一种网络驱动程序。在该模式下,网络驱动程序被划分为协议组件和硬件组件,它们之间没有内部交换数据,只通过MUX间接相互作用。MUX接口的作用是分解协议和硬件网络驱动程序,从而使它们几乎独立,这种独立使添加新的驱动程序和协议变得简单。例如:添加一个新的网络驱动,则所有现有基于MUX的协议均可使用新的驱动程序;同样,添加一个新的基于MUX的协议,则任何现有的网络驱动均可通过MUX接口来访问新的协议。如下图所示:

协议A 协议B ......
| | |
-----------------------
|
MUX
|
-----------------------
| | |
设备1 设备2 ......

通过MUX接口,协议和硬件设备相互独立,任何协议都可以通过MUX统一接口访问任何硬件设备,任何硬件设备也可以通过MUX接口支持所有协议。

VxWorks MUX驱动调用关系如下图所示:

------------------------ --------------------- ---------------------
| |------>| muxBind() | | |
| stackTxShutdownRtn() |<------| muxUnbind() | | |
| | | | | |
| | | muxDevLoad() |------>| endLoad() |
| | | muxDevUnload() |------>| endUnload() |
| | | | | |
| stackRcvRtn() |<------| muxReceive() |<------| |
| stackError() |<------| muxError() |<------| |
| | | | | |
| |------>| muxSend() |------>| endSend() |
| stackTxRestartRtn() |<------| muxTxRestartRtn() |<------| endTxRestartRtn() |
| |------>| muxMCastAddrGet() |------>| endMCastAddrGet() |
| |------>| muxMCastAddrDel() |------>| endMCastAddrDel() |
| |------>| muxMCastAddrAdd() |------>| endMCastAddrAdd() |
| |------>| muxPollSend() |------>| endPollSend() |
| |------>| muxPollReceive() |------>| endPollReceive() |
| |------>| muxIoctl() |------>| endIoctl() |
| | | | | |
| | | |------>| endStart() |
| | | |------>| endStop() |
------------------------ --------------------- ---------------------
协议层接口 MUX接口 END接口

VxWorks启动时执行任务tUsrRoot来完成驱动程序的安装,它产生tNetTask任务来处理网络任务工作队列中的条目,调用muxDevLoad()来加载网络驱动程序,调用muxDevStart()来启动驱动程序。协议栈注册了4个回调函数,由MUX接口回调。

基本过程就是:
1、muxDevLoad()调用endLoad()
endLoad()初始化任何特殊设备,填写END_OBJ和NET_FUNCS结构并返回。MUX把返回的END_OBJ添加到END_OBJ结构的链表中,此链表列出了当前系统所运行的所有网络设备的信息,此时,用户驱动程序装载完毕以备用。

2、MUX调用endStart()启动网卡,注册中断服务程序并使能中断。

综上,VxWorks的网络驱动只要在endLoad()中填写END_OBJ结构和实现endXXX()函数即可。当然,内存管理也是一个很重要的方面,VxWorks的内存池API实现了网络数据“零拷贝”,各协议层仅传递指针而不重复拷贝数据。endXXX()函数主要实现开始、结束、发、收、IOCTL、多播地址插入/删除/读取等。其中收发既可以基于中断也可以基于查询方式。

-------------------------------------------------
| EasyARM2200和SmartARM2200上网络驱动程序的实现 |
-------------------------------------------------
要在EasyARM2200和SmartARM2200上实现END类型的网卡驱动,需要在BSP目录中增加几个文件:
BSP目录为c:\Tornado2.2\target\config\zlgarm
增加rtl8019end.c、rtl8019end.h、configNet.h文件,并修改config.h、Makefile配置。

-----------
configNet.h
-----------
#define ZLGARM7_LOAD_FUNC_0 rtl8019EndLoad

#define ZLGARM7_LOAD_STRING_0 "83400000:17"
IMPORT END_OBJ * ZLGARM7_LOAD_FUNC_0 (char *, void *);

END_TBL_ENTRY endDevTbl [] =
{
#ifdef INCLUDE_RTL8019_END
{ 0, ZLGARM7_LOAD_FUNC_0, ZLGARM7_LOAD_STRING_0, 1 , NULL, FALSE},
#endif /* INCLUDE_RTL8019_END */
{ 0, END_TBL_END, NULL, 0, NULL, FALSE},
};

在configNet.h配置文件中,主要涉及到一个表格(END_TBL_ENTRY)的填写。这个表格的每一项对应一个网卡的加载信息。
END_TBL_ENTRY结构中各项的含义分别为:
{ 设备编号,加载函数,初始化资源字串,缓冲类型,BSP内部指针,处理完成与否的标志 }
END_TBL_ENTRY使用END_TBL_END结束表格填写。
由此可见,上面的语句注册了一个网卡驱动rtl8019EndLoad。

资源字串的含义是:网卡基地址:网卡中断号。
这个字串是自行定义的,各项间用冒号分隔,只要能正确分解出各项内容即可,没有统一规定。例如这里定义资源字串的两项分别为网卡基地址和中断号。

---------
config.h
---------
#define DEFAULT_BOOT_LINE "rtl(0,0)host:vxWorks " \
"h=192.168.0.6 " \
"e=192.168.0.2 " \
"g=192.168.0.1 " \
"u=target " \
"tn=targetname"

#define INCLUDE_NETWORK
#define INCLUDE_END

#ifdef INCLUDE_END
#define INCLUDE_RTL8019_END /* Include Ethernet driver */
#endif /* INCLUDE_END */

#undef WDB_COMM_TYPE /* default WDB agent communication path is END */
#define WDB_COMM_TYPE WDB_COMM_END

这里设置目标机IP地址(e),使能END驱动,增加8019驱动程序,使用网络连通WDB agent。

--------
Makefile
--------
在MACH_EXTRA=后面添加rtl8019end.o,以便编译8019驱动。

MACH_EXTRA = rtl8019end.o

------------
rtl8019end.h
------------
这个文件里定义各种与8019有关的宏定义。

typedef struct end_device
{
END_OBJ end; /* The class we inherit from. */
ULONG base; /* base address */
int ivec; /* rtl8019 interrupt vector */
int offset; /* offset */
long flags; /* Our local flags. */
UCHAR enetAddr[6]; /* ethernet address */
char packetBuf[2048]; /* packet buffer */
......
}END_DEVICE;

# define DP_IN(_b_, _o_, _d_) HAL_READ_UINT8 ((_b_)->base + 2*(_o_), (_d_))
# define DP_OUT(_b_, _o_, _d_) HAL_WRITE_UINT8((_b_)->base + 2*(_o_), (_d_))

这里面的END_DEVICE是自定义结构,其中必须包含一个END_OBJ结构,其他内容根据实际情况自行定义,例如:这里面自行定义了8019网卡驱动程序用到的变量:包缓冲区、标志、MAC地址、基址、中断号等。各种驱动函数都会传递这个结构体的指针。
DP_IN和DP_OUT定义了8019输入输出函数,其中偏移要乘2,因为A0接到了A1上。

------------
rtl8019end.c
------------
LOCAL NET_FUNCS rtl8019EndFuncTable =
{
rtl8019EndStart,
rtl8019EndStop,
rtl8019EndUnload,
rtl8019EndIoctl,
rtl8019EndSend,
rtl8019EndMCastAdd,
rtl8019EndMCastDel,
rtl8019EndMCastGet,
rtl8019EndPollSend,
rtl8019EndPollRcv,
endEtherAddressForm,
endEtherPacketDataGet,
endEtherPacketAddrGet
};

rtl8019EndFuncTable结构体中填写了8019网卡的所有操作函数,rtl8019end.c中主要实现这些函数和rtl8019EndLoad、rtl8019EndUnload、rtl8019EndParse、rtl8019EndMemInit、rtl8019EndInt、rtl8019EndConfig、rtl8019EndReset等。

首先说下内存管理。VxWorks采用一种复杂灵活的内存管理机制来有效减少数据复制。这种机制能满足数据包头尾添加/删除,可变长内存分配,尽量少的数据复制(“零拷贝”)等要求。

要实现这种机制,首先在rtl8019EndMemInit中设置内存池的参数,然后申请内存空间;再使用netPoolInit来形成具体的内存池。
在网卡驱动程序的其他地方,如果需要进行内存分配,则都在这个内存池中申请。申请步骤为:
1、调用系统函数netClusterGet()预定一块簇缓冲区;
2、调用系统函数netClBlkGet()预定一个clBlk结构;
3、调用系统函数netMblkGet()预定一个mBlk结构;
4、调用系统函数netClBlkJoin()把簇添加到clBlk结构中;
5、调用系统函数netMblkClJoin()把clBlk结构添加到mBlk结构中。
最后将mBlk作为参数传递给处理函数。这种管理方法有很大好处,因为一个clBlk可与多个mBlk关联,这就使得在不同协议间传递数据变得容易,只须传递指针,而无须拷贝其中的数据。

当网络设备产生中断时,VxWorks调用驱动程序先前注册的中断服务程序。中断服务程序应尽可能地短,减少中断阻塞时间。它不应包含耗时的处理工作。为了将一些数据包处理工作放置在任务级,中断服务程序必须调用netJobAdd()将相应的处理程序作为输入来生成一个任务级的数据处理任务。例如:netJobAdd ((FUNCPTR)rtl8019EndRcvInt, (int)pDrvCtrl, 0, 0, 0, 0);。

关于8019的详细工作原理,可以通过购买本站(www.armecos.com)的“51+8019资料”获得。

---------------------------------------------
| 《ecos增值包》辅助开发VxWorks网络驱动程序 |
---------------------------------------------
为了调试方便,我们使用“驻留ROM”型编译模式,这样就需要把VxWorks烧写到flash里才能调试,不过,有了redboot,我们可以把RAM虚拟成ROM,让VxWorks在RAM里运行,这样就可以减少flash擦写次数,加快调试速度。此时,需要修改conig.h中的ROM_XXX_XXX宏定义,把地址定位到RAM上。
使用:lo -b 0x81010000 -r -h 192.168.0.1 a.bin下载VxWorks程序,
使用 go 0x81010000运行VxWorks程序。
通过打印输出调试信息,有些不适合打印的地方可以采用内存打印技术(在VxWorks里往内存打印数据,然后通过redboot查看内存),如中断程序里的打印。

----------------------------------------------------------------------------
8019复位
----------------------------------------------------------------------------

LOCAL void rtl8019EndReset
(
END_DEVICE* pDrvCtrl /* device to be reset */
)
{
unsigned int i;
unsigned char tmp;

if(pDrvCtrl->unit != 0)
return;

for(i = 0; i < 250; i++); /* 延时一段时间 */
DP_IN(pDrvCtrl, 0x1F, tmp);
DP_OUT(pDrvCtrl, 0x1F, tmp);
}

----------------------------------------------------------------------------
内存池初始化
----------------------------------------------------------------------------

LOCAL STATUS rtl8019EndMemInit
(
END_DEVICE * pDrvCtrl /* device to be initialized */
)
{
DBG_PRINTF ("InitMem\n");

/*
* Set up an END netPool using netBufLib(1).
*/

endMclConfig.mBlkNum = END_MBLK_NUM;
endClDescTbl[0].clNum = END_CL_NUM;
endMclConfig.clBlkNum = endClDescTbl[0].clNum;

/* Calculate the total memory for all the M-Blks and CL-Blks. */
endMclConfig.memSize = (endMclConfig.mBlkNum * (MSIZE + sizeof (long))) +
(endMclConfig.clBlkNum * (CL_BLK_SZ + sizeof(long)));

if ((endMclConfig.memArea = (char *) memalign (sizeof(long),
endMclConfig.memSize))
== NULL)
return (ERROR);

/* Calculate the memory size of all the clusters. */
endClDescTbl[0].memSize = (endClDescTbl[0].clNum *
(endClDescTbl[0].clSize + 8))
+ sizeof(int); /* +8 is for proper alignment */

/* Allocate the memory for the clusters */
endClDescTbl[0].memArea =
(char *) cacheDmaMalloc (endClDescTbl[0].memSize);

if (endClDescTbl[0].memArea == NULL)
{
DBG_PRINTF ("system memory unavailable\n");
return (ERROR);
}

if ((pDrvCtrl->end.pNetPool = (NET_POOL_ID) malloc (sizeof(NET_POOL)))
== NULL)
return (ERROR);

/* Initialize the memory pool. */
if (netPoolInit(pDrvCtrl->end.pNetPool, &endMclConfig,
&endClDescTbl[0], endClDescTblNumEnt, NULL) == ERROR)
{
return (ERROR);
}

DBG_PRINTF ("InitMem OK!\n");
return OK;
}

----------------------------------------------------------------------------
接收函数
----------------------------------------------------------------------------
LOCAL STATUS rtl8019EndRecv
(
END_DEVICE *pDrvCtrl /* device structure */
)
{
M_BLK_ID pMblk = NULL;
CL_BLK_ID pClBlk = NULL;
UINT32 len;
char * pBuf = NULL;

if (pDrvCtrl->end.pNetPool == NULL)
{
DBG_PRINTF ("rtl8019EndRecv: Illegal pNetPool on entry!\n");
END_ERR_ADD (&pDrvCtrl->end, MIB2_IN_ERRS, +1);
goto cleanRXD;
}

if ((pMblk = mBlkGet (pDrvCtrl->end.pNetPool, M_DONTWAIT, MT_DATA))
== NULL)
{
DBG_PRINTF ("rtl8019EndRecv: Out of M Blocks!\n");
END_ERR_ADD (&pDrvCtrl->end, MIB2_IN_ERRS, +1);
goto cleanRXD;
}

pBuf = netClusterGet (pDrvCtrl->end.pNetPool,
pDrvCtrl->end.pNetPool->clTbl[0]);

if (pBuf == NULL)
{
DBG_PRINTF ("rtl8019EndRecv: Out of clusters!\n");
pDrvCtrl->lastError.errCode = END_ERR_NO_BUF;
muxError(&pDrvCtrl->end, &pDrvCtrl->lastError);
goto cleanRXD;
}

/* 读取数据 */
len = rtl8019PacketGet(pDrvCtrl, pBuf);

if ((pClBlk = netClBlkGet (pDrvCtrl->end.pNetPool, M_DONTWAIT)) == NULL)
{
DBG_PRINTF ("rtl8019EndRecv: Out of Cluster Blocks!\n");
pDrvCtrl->lastError.errCode = END_ERR_NO_BUF;
muxError(&pDrvCtrl->end, &pDrvCtrl->lastError);
goto cleanRXD;
}

if (netClBlkJoin (pClBlk, pBuf, len, NULL, 0, 0, 0) == NULL)
{
DBG_PRINTF ("rtl8019EndRecv: netClBlkJoin failed\n");
pDrvCtrl->lastError.errCode = END_ERR_NO_BUF;
muxError(&pDrvCtrl->end, &pDrvCtrl->lastError);
goto cleanRXD;
}

if (netMblkClJoin (pMblk, pClBlk) == NULL)
{
DBG_PRINTF ("rtl8019EndRecv: netMblkClJoin failed\n");
pDrvCtrl->lastError.errCode = END_ERR_NO_BUF;
muxError(&pDrvCtrl->end, &pDrvCtrl->lastError);
goto cleanRXD;
}


pMblk->mBlkHdr.mFlags |= M_PKTHDR;
pMblk->mBlkHdr.mLen = len;
pMblk->mBlkPktHdr.len = len;
pMblk->mBlkHdr.mData += pDrvCtrl->offset;

END_ERR_ADD (&pDrvCtrl->end, MIB2_IN_UCAST, +1);

END_RCV_RTN_CALL(&pDrvCtrl->end, pMblk);

return (OK);

cleanRXD:

if (pClBlk != NULL)
{
netClBlkFree (pDrvCtrl->end.pNetPool, pClBlk);
}

if (pBuf != NULL)
{
netClFree (pDrvCtrl->end.pNetPool, pBuf);
pBuf = NULL;
}

if (pMblk != NULL)
{
netMblkFree (pDrvCtrl->end.pNetPool, pMblk);
}

END_ERR_ADD (&pDrvCtrl->end, MIB2_IN_ERRS, +1);


return (ERROR);
}

----------------------------------------------------------------------------
发送函数
----------------------------------------------------------------------------
LOCAL STATUS rtl8019EndSend
(
END_DEVICE *pDrvCtrl, /* device ptr */
M_BLK_ID pNBuff /* data to send */
)
{
int len;
int i;

if (pDrvCtrl->resetting)
{
return ERROR;
}

END_TX_SEM_TAKE (&pDrvCtrl->end, WAIT_FOREVER);

len = netMblkToBufCopy(pNBuff,
(void *)pDrvCtrl->packetBuf, NULL) ;
len = max(len, ETHERSMALL);

END_TX_SEM_GIVE (&pDrvCtrl->end);

page(pDrvCtrl, 0);
DP_OUT(pDrvCtrl, 0x09, 0x40); /* send buffer start address */
DP_OUT(pDrvCtrl, 0x08, 0x00);
DP_OUT(pDrvCtrl, 0x0B, len >> 8); /* send packet len */
DP_OUT(pDrvCtrl, 0x0A, len & 0xFF);
DP_OUT(pDrvCtrl, 0x00, 0x12); /* write dma, page0 */

for(i = 0; i < len; i++){ /* 发送数据到双口RAM */
DP_OUT(pDrvCtrl, 0x10, pDrvCtrl->packetBuf[i]);
printf("%x ", pDrvCtrl->packetBuf[i]);
}
printf("\n");

DP_OUT(pDrvCtrl, 0x04, 0x40); /* 发送缓冲区首址*/
DP_OUT(pDrvCtrl, 0x06, len >> 8); /* 发送包长度 */
DP_OUT(pDrvCtrl, 0x05, len & 0xFF);
DP_OUT(pDrvCtrl, 0x00, 0x3E); /* 发送 */

END_ERR_ADD (&pDrvCtrl->end, MIB2_OUT_UCAST, +1);

netMblkClChainFree (pNBuff);

return (OK);
}


菜鸟
2007-05-17 03:20:00    评分
5楼
怎么没人顶你,我也在学习VXWORKS的移植,想了解该从什么地方着手,谢了

菜鸟
2007-05-18 18:59:00    评分
6楼

从调试开始。

最好有个现成开发环境,边调试边学。

www.armecos.com


菜鸟
2007-05-18 19:27:00    评分
7楼

****************************************************
* u-boot(uboot)在EasyARM2200和SmartARM2200上的移植 *
****************************************************
------ 浅谈《ecos增值包》在辅助开发方面的用途
2007/03/25 asdjf@163.com www.armecos.com

现在想学习GNU/Linux的网友越来越多,但是很多人找不到切入点,如何才能利用现有的资源轻松进入呢?如果你手头有EasyARM2200和/或SmartARM2200开发板,那么,下面文章介绍的方法或许对你有用......
--------------------
| 学习u-boot的好处 |
--------------------
好处可多啦:
(1)学习GNU开发工具链的使用,如:makefile写法、常用shell命令、gcc等;
(2)学习的副产品---u-boot可以用来引导uCLinux/Linux、WinCE等操作系统;
(3)学习程序编写,看专家写的程序本身就是一种美的享受;
(4)借鉴(抄)其中的设备驱动,u-boot支持的CPU和外围硬件很丰富;
......
总之,最好研究一下u-boot,不只是为了得到一个bootloader,更重要的是为学习GNU/Linux打基础。

--------------
| 初学者难点 |
--------------
经常在网上看到类似的提问:cygwin怎么工作不正常?gcc命令为什么找不到?编译怎么一大堆错误?我是不是要装Linux环境?那得花一天时间啊!如何装虚拟机?如何在虚拟机和windows间互传文件?......
初学者遇到的最大难点是环境搭建。听起来好笑,但很多网友就是由于未能搭建好开发环境或者开发环境出问题,导致迟迟不能迈出第一步,耽误了宝贵的时间。其实最难的是第一步,走过这一步,后面就是一马平川了。

--------------------------
| 《ecos增值包》解决方案 |
--------------------------
开发平台采用“cygwin + gcc for arm + redboot”。

redboot和u-boot一样都是bootloader,支持串口/网口下载、烧录flash、查看内存等功能,利用ecos中已有的redboot来调试u-boot可以避免反复烧写flash,尽管flash可烧写十万次,但在RAM里调试程序仍是好习惯。

GNU开发工具链支持很多种体系架构,没有版权限制,大部分开源软件都是用GNU编译器开发。光盘中提供编译好的GNUTOOLS for ARM,当然,如果你感兴趣,可以自己从源码制作工具(binutils、gcc、gdb等),这样可以自行升级工具版本,《ecos增值包》提供step by step的指导。

cygwin是虚拟机,可以在windows上虚拟类UNIX的shell界面,相当于windows应用程序。使用cygwin的好处是简单方便,只要5-15分钟就可以搭建起环境。大部分人使用的是windows系统,尤其是初学者,纯Linux系统环境并不是最佳选择。如果装VmWare虚拟机,情况会好些,但在windows和虚拟机间交换文件(FTP、Samba等)也不容易。使用cygwin是最简单的方式,用户可以直接在windows下使用UltraEdit、VC6、文件搜索、文件比较等工具,考虑到大多数人的实际情况,采用cygwin开发比较现实。也许有人热衷于纯Linux环境,也许有人对cygwin存有偏见,但绝大部分情况是,cygwin没有问题,而是使用cygwin的人出了差错。其实,cygwin非常可靠易用,从原理上讲,它和纯Linux环境的执行效果是完全一样的。cygwin经常遭人诟病的是其执行效率低,这是事实,但对于编译100多K大小u-boot程序的初学者来说,此速度损失可忽略不计。而且make只会编译受改变影响的相关文件,加上使用已编译的库,即使编译大程序,执行速度也可以接受。

顺便说下,《ecos增值包》可不仅仅只是做这个用途的,这只是它的一个很小的应用。它还提供web server、ftp server、lwip、GUI、TCP/IP、FS、视频监控、税控机、行驶记录仪、自动绣花纺织机、远程监控、通信设备等应用,以后会陆续给出例子。

------------------
| u-boot移植规划 |
------------------
u-boot的功能很强大,决定先做以下驱动:
(1)串口驱动。用于串口下载程序和命令行调试界面;
(2)Flash驱动。保存参数,存储文件;
(3)网络驱动。实现TFTP和NFS。

暂时没有实现的是:(主要是因为EasyARM2200上没有这些设备)
USB host、NAND Flash、PCMCIA(CF)、RTC、reset等。

------------------
| u-boot结构分析 |
------------------
从ecos的观点(学习ecos就是好啊,看问题都抽象了^_^),u-boot包含了三个抽象层:
(1)体系结构抽象层
EasyARM2200和SmartARM2200使用LPC2210芯片,属于ARM7体系架构,u-boot已经提供了(或抽象了)通用的ARM驱动程序,即lib_arm目录。
(2)变种抽象层
很多公司都生产ARM(7)芯片,LPC2210是NXP公司设计的,基于ARM7的变种。其他如s3c44b0、pxa、ixp、arm946等都是不同芯片公司生产的ARM体系的变种。在cpu/目录下,就是各种变种的驱动目录,在此,我们增加cpu/lpc2xxx/目录。主要完成程序搬移、串口驱动、中断处理。

(3)平台抽象层
很多制造商都使用LPC2210芯片生产设备,EasyARM2200和SmartARM2200是zlg Co.生产的两款不同的硬件平台,虽然都使用LPC2210,但地址分配不同,外围设备不同(“价格差别也很大”)。在board/目录下就是各种不同硬件平台的驱动,在此,我们加入board/easyarm2200或board/smartarm2200目录。主要完成板子上电初始化、TEXT_BASE初始化、链接脚本lds、flash初始化。

除了这3个目录及文件,还有一些与移植有关:

include/configs/ZLGARM2200.h 这个头文件用于配置开发板平台,如各种参数、地址范围、功能选择等。实际使用时将ZLGARM2200.h换成相应的EasyARM2200.h或SmartARM2200.h

include/configs/asm-arm/arch-lpc2xxx/hardware.h 这个头文件定义开发板的各种寄存器地址宏,操作宏、初始化宏。

Makefile 这个文件里要增加开发板的配置选项,ZLGARM2200_config(实际使用时将ZLGARM2200_config换成相应的EasyARM2200_config或SmartARM2200_config)。还有要把交叉编译前缀“#CROSS_COMPILE = arm-linux-”换成“CROSS_COMPILE = arm-elf-”

还有一些需要改动的文件:
drivers/rtl8019.h 主要是适应8019的8位数据接口。

为了清晰起见,将要修改的目录文件总结如下:(小技巧:把这些目录或文件做成快捷方式放在桌面上)
board/easyarm2200/
cpu/lpc2xxx/
include/configs/asm-arm/arch-lpc2xxx/hardware.h
include/configs/EASYARM2200.h
drivers/rtl8019.h
Makefile

------------------------
| u-boot各部分移植详述 |
------------------------
u-boot不支持LPC2210,所以需要新增加变种和平台抽象层目录board/easyarm2200/和cpu/lpc2xxx/;以及配置文件hardware.h和EASYARM2200.h;同时,有些不适应的文件需要修改,如rtl8019.h和Makefile。

Makefile要修改的是:
---------------------------------------------------------------------------
#CROSS_COMPILE = arm-linux-
CROSS_COMPILE = arm-elf-

#########################################################################
## EASYARM2200 Systems
#########################################################################

EASYARM2200_config : unconfig
@$(MKCONFIG) $(@:_config=) arm lpc2xxx EASYARM2200 easyarm2200

---------------------------------------------------------------------------

drivers/rtl8019.h要修改的是:
---------------------------------------------------------------------------
#define RTL8019_REG_00 (RTL8019_BASE + 0x00 * 2)
......
---------------------------------------------------------------------------

include/configs/asm-arm/arch-lpc2xxx/hardware.h
---------------------------------------------------------------------------
// UART

#define LPC2XXX_UART0_BASE 0xE000C000
#define LPC2XXX_UART1_BASE 0xE0010000

#define LPC2XXX_UART_RBR 0x00
#define LPC2XXX_UART_THR 0x00
#define LPC2XXX_UART_IER 0x04
#define LPC2XXX_UART_IIR 0x08
#define LPC2XXX_UART_FCR 0x08
......
---------------------------------------------------------------------------

include/configs/EASYARM2200.h
---------------------------------------------------------------------------
#define DEBUG 10

#define CONFIG_COMMANDS ( CONFIG_CMD_DFL | \
CFG_CMD_DATE | \
CFG_CMD_ELF | \
CFG_CMD_NET | \
CFG_CMD_ENV | CFG_CMD_FLASH )

/*
* Physical Memory Map
*/
#define CONFIG_NR_DRAM_BANKS 1 /* we have 1 banks of DRAM */
#define PHYS_SDRAM_1 0x81000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x00800000 /* 8 MB */

#define PHYS_FLASH_1 0x80000000 /* Flash Bank #1 */
#define PHYS_FLASH_SIZE 0x00200000 /* 2 MB */

#define CFG_FLASH_BASE PHYS_FLASH_1
......
---------------------------------------------------------------------------

cpu/lpc2xxx/
---------------------------------------------------------------------------
========
serial.c
========
static int serial_flush_input(void)
static int serial_flush_output(void)
void serial_putc (const char c)
int serial_tstc (void)

void serial_setbrg (void)
{
unsigned char * base = (unsigned char *)LPC2XXX_UART0_BASE;

HAL_WRITE_UINT8(base + LPC2XXX_UART_LCR,LPC2XXX_UART_LCR_DLAB);

HAL_WRITE_UINT8(base+LPC2XXX_UART_DLM, SIO_BRDDIV / 256);
HAL_WRITE_UINT8(base+LPC2XXX_UART_DLL, SIO_BRDDIV % 256);

// 8-1-no parity.
HAL_WRITE_UINT8(base + LPC2XXX_UART_LCR,
LPC2XXX_UART_LCR_8_DBITS | LPC2XXX_UART_LCR_1_SBITS | LPC2XXX_UART_LCR_NO_PARITY);
}
int serial_getc (void)
{
int rv;
unsigned int c;
unsigned char ch;

for(;;) {
rv = serial_tstc();

if(rv > 0){
HAL_READ_UINT8(LPC2XXX_UART0_BASE+LPC2XXX_UART_RBR, c);
ch = (unsigned char)(c & 0xFF);
return ch;
}
}
}

=======
start.S
=======
如果从ROM里启动,那么开始部分的reset代码地址为80000030。用arm-elf-objdump -d u-boot > 1.txt得知reset偏址。
.globl _start
_start:
ldr pc, =0x80000030
加载位置TEXT_BASE在easyarm2200中是0x81060000,在smartarm2200中是0x817c0000。

/*************************************************/
/* interrupt vectors */
/*************************************************/
real_vectors:
ldr pc,.reset //0x00
ldr pc,.undefined_instruction //0x04
ldr pc,.software_interrupt //0x08
ldr pc,.prefetch_abort //0x0C
ldr pc,.data_abort //0x10
ldr pc,.not_used //0x14
ldr pc,.irq //0x18
ldr pc,.fiq //0x1C

/*************************************************/

.reset: .word reset
.undefined_instruction: .word undefined_instruction
.software_interrupt: .word software_interrupt
.prefetch_abort: .word prefetch_abort
.data_abort: .word data_abort
.not_used: .word not_used
.irq: .word irq
.fiq: .word fiq
......

============
interrupts.c
============
void udelay (unsigned long usec)
int interrupt_init (void)
---------------------------------------------------------------------------

board/easyarm2200/
---------------------------------------------------------------------------
=====================
EASYARM2200/config.mk
=====================
TEXT_BASE = 0x817C0000
PLATFORM_CPPFLAGS += -Uarm

===========================
EASYARM2200/lowlevel_init.S
===========================
#include <asm/hardware.h>

.globl lowlevel_init
lowlevel_init:

PLATFORM_SETUP1

mov pc, lr

==============
common/flash.c
==============
static ulong flash_get_size (vu_long *addr, flash_info_t *info)
......
case (CFG_FLASH_WORD_SIZE)SST_ID_xF1601:
case (CFG_FLASH_WORD_SIZE)SST_ID_xF160A:
info->flash_id += FLASH_SST160A;
info->sector_count = 32;
info->size = 0x00200000;
break; /* => 2 MB */
......
---------------------------------------------------------------------------

------------------
| u-boot移植难点 |
------------------
board/easyarm2200/EASYARM2200/config.mk中的TEXT_BASE决定u-boot在RAM中的加载位置,board/easyarm2200/EASYARM2200/u-boot.lds中虽然使用“. = 0x00000000;”,但config.mk中的LDFLAGS标志“LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)”却将text段指定在了$(TEXT_BASE),所以,最终text段的位置由TEXT_BASE决定。
cpu/lpc2xxx/start.S中判断程序是否位于指定加载位置,如果不是,就把代码搬移到TEXT_BASE指定位置,注意:“adr r0, _start”是把当前代码位置赋给r0,可不是“mov r0, _start”。
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq copy_vector
内存搬移
copy_vector:

cpu/lpc2xxx/start.S中的中断向量表要按照给出的方式定义,否则,中断可能无法长跳转到中断服务子程序,导致uClinux运行不正常(在开中断cli后,跳不到中断服务子程序)。

u-boot的平台配置文件include/configs/EASYARM2200.h需要仔细设置,注意不要引起地址冲突,u-boot自身需要占用一部分flash和内存空间,注意确保这些空间不被使用。

u-boot上电后会自动枚举flash,通过flash ID判断加载适当驱动,u-boot已经支持开发板的flash驱动了,出问题的话主要是没有找到对应ID,加上即可。

8019驱动u-boot也支持,主要是寄存器访问要改。

LPC2210没有cache和SDRAM,简化了移植工作。

------------------
| u-boot编译方法 |
------------------
cd /u-boot-1.2.0 进入u-boot目录。小技巧:输入cd/u-,然后按TAB键,shell会自动补全路径,很方便。
make clean 清除垃圾,只第一次需要,以后可不用此步骤。有时编译不正确,清一下垃圾就好了。
make EASYARM2200_config 配置EASYARM2200开发板编译环境,生成config.h和指定体系结构、平台、变种、配置文件名等。只第一次需要。
make 编译

------------------
| u-boot使用方法 |
------------------
u-boot功能强大,命令很多,详细使用方法可以在网上查找到,这里只给出一些范例。
help 帮助命令,可用“?”代替,可以用“help 指令”形式获得更详细说明。
printenv 打印环境变量

setenv ethaddr 12:34:56:78:9A:BC
setenv ipaddr 192.168.0.6
setenv serverip 192.168.0.1 (tftp服务器的地址)
tftp 81200000 a.bin 把server(IP=环境变量中设置的serverip)中/tftproot/下的a.bin通过TFTP读入到内存0x81200000处。

saveenv 保存环境变量到flash等不挥发的存储设备上。

nfs 32000000 192.168.0.1:aa.txt 把192.168.0.1(LINUX的NFS文件系统)上的NFS文件系统中的aa.txt 读入内存0x32000000处。

----------------------------------
| 《ecos增值软件包》辅助开发方法 |
----------------------------------
开发调试u-boot非常困难,因为串口还没有正常工作,如果出现问题,很难调试,而大多数初学者又没有仿真器,所以,这一状况更显严峻。当然不反对使用仿真器,不过使用一根串口线配合redboot也可以做到,调试效率不是很低,没有办法的办法。

每次编译完u-boot,就用redboot下载到内存调试运行:
lo -b 0x817c0000 -r -m xmodem
go 0x817c0000

程序可能出错,使用内存打印技术发现问题:
汇编中内存打印
ldr r1, =0x81040000
add r1, r1, #2
mov r0, #'a'
strb r0,[r1]
add r1,r1,#1
mov r0, #'2'
strb r0,[r1]
C程序中内存打印
unsigned char *yybuf = 0x81030000;

*(unsigned char *)yybuf++ = 'y';
*(unsigned char *)yybuf++ = '1';

把这些语句加到合适的地方,出错后,使用:
du -b 0x81040000 -l 300
就可以看到打印语句的输出(十六进制和ASCII码显示),通过判断打印语句的情况,就可以了解程序运行情况。
当然,在使用显存前要先清零:
mfill -b 0x81040000 -l 300 -p 0

在RAM里调试的好处是不必反复烧写flash,一旦调试正确,就可以固化。

由此可见,有了《ecos增值软件包》辅助开发,u-boot移植再也不是什么难事了,调试手段也够用,cygwin环境很方便,gcc编译器适用于各种体系结构,编译调试u-boot可以为使用Linux打基础,还能借鉴许多现成代码。

使用EasyARM2200和SmartARM2200的初学者现在就可以开始动手玩u-boot了,一般5-15分钟就可以搭建好开发环境,三天之内怎么也能跑起来了。


菜鸟
2007-05-18 19:29:00    评分
8楼

********************************************
* PPPOE在EasyARM2200和SmartARM2200上的实现 *
********************************************
------ 漫谈《ecos增值包》网络整体解决方案
2007/04/29 asdjf@163.com www.armecos.com

PPPOE是PPP over Ethernet的缩写,可以简单理解成以太网上跑的PPP协议,而PPP是点到点协议,完成点对点的通信连接。如今,IP分组网络大有一统天下的趋势,各种业务应用,如:语音、视频、数据、控制信息等都趋向于融合到IP网络中,即Anything over IP。而IP网络又可以基于各种介质,即IP over Anything。从而我们能够实现Anything over Anything。

然而,宽带接入一直困扰着IP网络的发展,也就是人们常说的最后一公里问题。现在,通信市场上存在着五花八门的宽带接入方式:LAN、ADSL、HFC Cable Modem、WLAN等各自占据一定的市场份额。其中,ADSL因为采用已有的广泛铺设的电话铜线,成本优势明显,所以成为主流宽带接入方式。它能够提供最低512 kHz, 最高8 MHz 的带宽。用户独享,不会因为上网人数增加而导致上网速率的降低。ADSL价格低廉, 能为广大的普通消费者所接受。用户一般通过ADSL Modem连接服务商的宽带接入服务器进而接入网络。作为《ecos增值包》产品线的一部分,EcosNet PPPOE允许PPP会话承载在以太网上,它从两方面简化了宽带互联网接入解决方案的开发难度。首先是PPPOE服务提供者可以使用现有电话网和计费方法;其次,PPPOE服务提供者可以让客户使用类似于目前流行的用户友好的拨号上网方式安装和访问网络。

尽管以太网是广播方式,但仍有必要实现点到点协议,主要是因为大量实际应用中要满足用户认证的需要。比如:

1、计费---目前,服务商的计费方法主要有两种:按时长计费(包月不限时、包月限时、计时、分时段);按流量计费。每种计费方法都不一定十全十美,一般根据实际情况灵活促销,比如:套餐、节假日优惠、午夜降价什么的,这导致计费公式不断变化,需要数据库记录交易情况,因此,必须提供用户识别能力。另外,流量管理也是一种特殊的计费,虽然不需要实际收费,但可以模拟成计费。

2、用户分级管理---比如:酒店里VOD点播,可以用PPPOE实现用户认证。

=================
| PPPOE协议分析 |
=================
PPPOE是如何工作的呢?
PPPOE协议分为发现阶段和会话阶段。发现阶段主要完成访问集中器选择,确定会话标识ID;会话阶段执行标准的PPP过程。

发现阶段:
第一步 主机向网络上所有的AC(访问集中器)广播,要求其中一个AC提供服务;
第二步 所有满足条件的AC返回应答,应答帧源地址为服务器MAC地址;
第三步 主机从收到的多个应答中根据集中器名称和能提供的服务标签选择一个合适的访问集中器,并向其确认应答。
第四步 访问集中器收到确认后再向主机回一个确认,主机收到确认后双方进入会话阶段。

在会话阶段,任一方都可以终止会话。

会话阶段传输的就是正常的PPP帧了,完成LCP、PAP/CHAP、NCP,最后进入IP传输阶段。


===============
| PPPOE帧格式 |
===============
PPPOE帧是直接承载在以太网上的,802.3协议为此特别指定了8863和8864作为PPPPOE的类型识别编码。802.3的帧结构如下所示:

------------------------------------------------------------------------------------------------------------------
| 前导位PR | 帧起始位SD | 目的MAC地址DA | 源MAC地址SA | 类型TYPE/长度LEN | 数据域DATA | 填充PAD | 校验FCS |
------------------------------------------------------------------------------------------------------------------
64 bit 2 bit 48 bit 48 bit 16 bit <=1500字节 DATA小于46字节补0 32 bit

类型TYPE说明:0800 IP包;0806 ARP包;814C SNMP包;8137 IPX/SPX;8863 PPPOE发现阶段;8864 PPPOE会话阶段
小于0600H值用于IEEE802帧,表示数据包长度。
DATA说明:DA + SA + TYPE = 14字节 + DATA = 1514字节,即最大传输包长度<=1514字节。
PAD说明:最小包长度不小于60字节,最小DATA长度46字节,不够的补0。

由上可知,网络协议栈可以在以太网帧一级识别出IP、ARP、PPPOE等帧。更多以太网帧数据结构详见《NE2000网卡芯片驱动程序》。

PPPOE帧的数据域中承载着PPPOE数据报文,报文格式如下:

-------------------------------------------------------
| 版本0x1 | 类型0x1 | 编码 | 会话ID | 长度 | 载荷 |
-------------------------------------------------------
4 bit 4 bit 1字节 2字节 2字节 0-1500字节

各字段含义如下:
(1)版本字段标志协议版本信息,目前规定其值为1;
(2)类型字段规定为1;
(3)编码字段在不同阶段具有不同取值;
(4)会话ID字段,在发现阶段取值为0x0000,在后续PPPOE会话过程中取值为发现阶段所获得的由AC(访问集中器Access Concentration)分配的唯一值;
(5)长度字段可以取值0-1500字节;
(6)净载荷字段存放PPPOE协议帧所承载的数据。在发现阶段承载零个或多个TAG结构,在会话阶段承载PPP协议数据。注意不是简单的PPP封包,因为并不需要PPP协议中的起始位标志、地址位、控制位和结束标志,也不需要PPP协议中规定的数据转译和CRC校验。

TAG标签结构:

-----------------------------
| TAG类型 | TAG长度 | TAG值 |
-----------------------------
2字节 2字节

===========================
| EcosNet网络整体解决方案 |
===========================
如何让EasyARM2200和SmartARM2200支持多种网络互联呢?EcosNet提供的网络整体解决方案可以比较容易地实现这一点。

EcosNet网络架构提供了接口API函数和钩子API函数,能灵活方便地增加各种功能和适应各种网络介质。

接口API函数
netif_add(netif, &ipaddr, netmask, &gw, state, init, input);

---------------------------------------------------------------------------------
struct netif *
netif_add(struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask,
struct ip_addr *gw,
void *state,
err_t (* init)(struct netif *netif),
err_t (* input)(struct pbuf *p, struct netif *netif))
---------------------------------------------------------------------------------

netif 预分配的网络接口数据结构
ipaddr 新网络接口的IP地址
netmask 新网络接口的子网掩码
gw 新网络接口的缺省网关IP地址
state 传递到新网络接口的不透明数据
init 初始化接口的回调函数
input 被传输调用的回调函数
入口包传递到上层协议栈
返回netif结构,如果失败返回NULL。

例如:netif_add(n2k_netif, &ipaddr, &netmask, &gw, NULL, n2k_init,tcpip_input);

接口API函数netif_add把网络设备驱动程序增加到IP路由队列里,由ip_router程序根据路由协议选择发送接口。此函数参数包括网络接口数据结构、IP地址、子网掩码、网关地址、传递数据、初始化回调、传输回调。各种网络接口设备都可以使用这个函数加入到IP路由队列中,例如:LOOP环回、SLIP、Ethernet、PPP等,而且可以在路由队列链表中注册很多接口驱动。这样各种网络设备都能够很方便地接入到系统中来。

钩子API函数
etherInputHookAdd(myhook);

myhook 自己的以太网钩子处理函数。

PPPoE通过采用以太网发送函数etherOutput发送数据包, 接收方向主要是调用EcosNet提供的增加钩子函数etherInputHookAdd添加自己的以太网钩子函数对以太网帧类型为0x8863和0x8864的包进行拦截处理。

以太网帧中同时传输IP、ARP、PPPOE等帧,通过钩子函数拦截不同类型的帧到相应处理入口。如:IP帧直接发到IP路由队列;ARP分发到ARP处理部分;PPPOE拦截到PPPOE处理部分。钩子函数主要完成过滤处理,有三种处理方式:

1、透传---不改变原有包的数据,直接透传上去。例如串口监听,把收到的数据记录下来同时向上透传;
2、拦截---拦截特定的数据,改变传递路径;
3、篡改---修改数据并向上传递。

PPPOE采用拦截方式,改变以太网帧传输路径到PPPOE处理部分。

钩子函数可以堆叠串联,因此存在一个堆叠顺序问题。例如:

------------
| PPPOE |
------------
| firewall |
------------
| VPN |
------------

最底层钩子挂接VPN,上面挂防火墙,再上面挂PPPOE拦截,顺序可以调整,越靠近底层,功能越强,不过消耗CPU资源也越多。
VPN可以在公网上实现专网效果,主要工作是加解密,需要商密局的加密芯片(就是一个EEPROM,很贵的),还有入网证。
防火墙通过过滤IP包完成一系列控管规则。
PPPOE帧被拦截到PPPOE处理部分。

在EasyARM2200和SmartARM2200上的实现PPPOE的简单方法就是利用EcosNet提供的接口API和钩子API,伪程序如下:

//增加PPPOE拦截钩子函数
etherInputHookAdd(pppoe_hook);

//创建PPP线程
sys_create_new(pppmain, para, PPP_THREAD_PRIO);

//注册PPP网络接口
netif_add(ppp_netif, &ipaddr, &netmask, &gw, NULL, ppp_init,tcpip_input);

这样,以太网上传输的PPPOE帧被拦截到PPPOE处理程序,然后被发送到PPP协议,如果ppp协商成功,IP数据包将被传到上层协议栈。PPPOE发送程序直接调用以太网发送函数etherOutput,发送封装之后的PPPOE帧。


菜鸟
2007-05-18 19:33:00    评分
9楼

**************************
* 第九讲 图形用户接口GUI *
**************************
2007/02/25 asdjf@163.com www.armecos.com

有了操作系统、TCP/IP协议栈、文件系统,再加上GUI就比较全面了。本节讲述MiniGUI在ecos上的移植。

MiniGUI是一款开源的GUI中间件,它将鼠标、键盘等设备驱动抽象为输入抽象层IAL引擎,将各种显示设备驱动抽象成图形抽象层GAL引擎,用户基于MiniGUI接口开发的程序可以跨平台使用。本来,最新版本的MiniGUI已经支持ecos了,但我使用的是MiniGUI学习版软件(MiniGUI-STR),它是MiniGUI的精简版本,只支持Linux和uClinux操作系统,此外还缺少许多高级特性。我用的开发板是SMARTARM2200,LCD驱动可以直接拿来使用,但我要玩扫雷,需要自己写鼠标和键盘驱动,为此,在移植上下了一番功夫。

MiniGUI属于中间件,它屏蔽了各种操作系统平台的差异,目前可以在Linux、uClinux、ecos、VxWorks、ThreadX、uC/OS-II等操作系统上运行,可以配置成三种运行模式:
1、MiniGUI-Threads 程序可以在不同线程中建立多个窗口,但所有窗口在同一个地址空间中运行。ecos、uC/OS-II、VxWorks就适合使用这种模式,根据MiniGUI作者的实际应用经验,ecos操作系统在一定程度上可以用来替代传统实时嵌入式操作系统。

2、MiniGUI-Lite C/S结构,每个程序是单独的进程,每个进程也可以建立多个窗口,适合于具有完整UNIX特性的嵌入式操作系统,例如:Linux。MiniGUI作者特别指出,要清楚嵌入式Linux/uClinux的方案并不是万能的。由于Linux/uClinux在实时性等方面的缺陷,使得它还不能用来开发对实时性、性能、成本等因素非常敏感的嵌入式产品。

3、MiniGUI-Standlone 不需要多线程/多进程的支持,独立运行,有些操作系统,例如uClinux,因某种原因缺少线程库支持,或者发现其线程库存在缺陷时适用这种模式。(MiniGUI作者就为uClinux的库缺陷头疼不已,某些调用就是不能正确运行,没办法,想出这么一招解决问题,呵呵。)

现在,我们已经知道ecos使用MiniGUI-Threads运行模式,下面就可以动手移植了,不过在移植前很有必要了解一下MiniGUI的工程组织方式。MiniGUI工程(MiniGUI库本身及示例程序包)是通过GNU的Automake/Autoconf脚本组织的。现在的各种自由软件,如:Apache、MySQL等都是利用Automake/Autoconf实现自动配置和编译的。用户只需要./configure、make、install这三条命令就可以把程序或函数库编译并安装到系统中。其中,configure是自包含的,也就是说用户不需要安装Automake/Autoconf环境就可以直接运行,就象用rar生成的自解压文件解压时不需要rar软件一样。configure是用脚本语言写的,可以独立运行,只有开发者生成configure文件时才需要Automake/Autoconf环境。

也许上面的论述令你感到复杂,其实核心思想就是要得到各个目录下的Makefile文件和一个config.h配置头文件。Makefile文件描述了各个文件之间的依赖关系、编译规则、编译选项和配置参数。config.h使用宏定义的方法给出了具体的配置参数。知道这么多足够了,就是这么简单,如果你对工程组织方式不感兴趣,现在就可以跳过这一段。因为MiniGUI要运行在各种平台上,所以不可能写一个固定的Makefile和配置文件,为了能自动探测平台环境,自动生成Makefile和config.h,需要编写configure脚本,但是手工写configure脚本太累了,有人就发明了自动生成工具Autoconf。使用者只要书写config.h.in和Makefile.in就可以自动生成configure脚本,工作量减轻了不少。但是手工写.in文件还是嫌累,那么可以用Autoscan从源文件中抽取与函数调用和头文件有关的信息,生成configure.scan,以此为框架手写configure.in,避免疏漏,然后用Autoheader读入configure.in生成config.h.in。编写Makefile.am,使用Automake生成Makefile.in。通过这么一系列的自动化过程,开发人员只要写很少的代码就可以生成复杂的Makefile和自动化配置软件。综上,复杂化的原因是软件自动化配置引起的,好在我们只是针对SMARTARM2200板子上的ecos做MiniGUI移植,环境是固定的,第一次configure配置生成Makefile和config.h后只需关心这几个文件就行了。

首先,我们要准备ecos库,选择net模板(其支持posix兼容层),添加ROMFS和RAMFS包,为后面的编译工作准备*.h文件。

从学习版光盘中拷贝或者从www.minigui.com网站下载以下文件:
libminigui-str-1.6.2.tar 源程序库
minigui-res-str-1.6.tar 资源文件:图标、光标、BMP图片、字体库、输入法
mg-samples-str-1.6.2.tar 示例程序
mde-str-1.6.2.tar 小游戏:扫雷等

将以上文件解压缩到同一个目录下,如:/t。修改各个configure配置选项,为了避免每次重复输入,可以把命令写入build文件。

#!/bin/sh

rm config.cache config.status -f

ECOS_DIR=/tmp/untitled_install

CC=arm-elf-gcc \
CFLAGS="-g -D__ecos__ -DEMBED -I ${ECOS_DIR}/include -I /tmp/minigui_lib/include" \
LDFLAGS="-L ${ECOS_DIR}/lib -Ttarget.ld -nostdlib -L /tmp/minigui_lib/lib" \
./configure \
--prefix=/tmp/minigui_lib \
--build=i386-elf \
--host=arm-elf \
--target=arm-elf \
--with-osname=ecos


配置相应的头文件位置、库文件位置、编译前缀等。操作系统选择ecos。
然后运行make,根据出错情况修改源文件,主要是头文件没找到,函数不存在等错误。
如果头文件不存在就注释掉对应的include,没有getpwuid函数,直接用“/”目录替代,没有random和srandom函数就用下面的方法替代:
#define srandom srand
#define random rand
总之就是调试,然后发现问题逐一解决。没有想象中的复杂,MiniGUI组织得很好,基本上只是一些小改动,或者删除,或者调整配置,本来MiniGUI就支持ecos嘛!

然后加入GAL和IAL,刚开始调试程序先把图像显示出来再说,IAL先不移植,直接使用dummy驱动。其实也不用移植,SmartARM板子的LCD驱动已经写好了,拿来就能用。不过不符合ecos规范,还可以进一步优化,基于ecos的线程效率高些,命名规则和移植性好,多少改改吧。

为了运行MiniGUI应用程序,我们还需要准备文件系统和运行时配置文件以及资源文件。
一般,需要将这些文件存放到ROMFS文件系统里。我们配置的ecos支持romfs,ROMFS文件系统有两种实现方法:1、用程序头文件实现,作成数组放到H文件里,由编译器决定地址;2、用ROM映像实现,使用固定的flash地址。ecos提供了三个ROM文件系统制作工具:
mk_romfs.exe 将romfs目录转化成BIN
file2c.tcl 将BIN转化成H文件
gen 批处理,从目录直接生成H文件

先准备好文件目录:
/etc
MiniGUI.cfg MiniGUI运行时配置文件
/res
app MiniGUI应用程序资源
bmp BMP图片
cursor 光标图形
font 字体库
icon 图标
imetab 输入法

res资源文件直接由minigui-res-str-1.6安装,app应用程序资源由用户指定,MiniGUI.cfg中要配置相关项:

[system]
# GAL engine
gal_engine=commlcd 使用commlcd的LCD驱动图形引擎

# IAL engine
#ial_engine=dummy 使用dummy输入引擎,即无输入设备
ial_engine=pcsim 使用pcsim键盘鼠标仿真输入引擎

[rawbitmapfonts] 指定字体资源路径
fontfile0=/res/font/8x16-iso8859-1.bin

[varbitmapfonts]
fontfile0=/res/font/Courier-rr-10-15.vbf

[cursorinfo] 指定光标资源路径
cursorpath=/res/cursor/

[iconinfo] 指定图标资源路径
iconpath=/res/icon/

[bitmapinfo] 指定BMP图形资源路径
bitmappath=/res/bmp/

[imeinfo] 指定输入法资源路径
imetabpath=/res/imetab/

[appinfo] 指定应用程序资源路径
apprespath=/res/app

其他没有写出的项使用缺省值或进行类似设置。这样MiniGUI就可以知道各种资源被保存到了什么地方,使用什么输入输出引擎等等信息,此信息是运行时才确定的。

在cygwin中使用$ mk_romfs -v ./romfs minigui.bin将romfs目录制作成ROMFS文件系统映像minigui.bin,
在redboot中用lo -b 0x81010000 -r -h 192.168.0.1 minigui.bin下载映像到RAM中,
在redboot中用fis create -b 0x81010000 -l 0x160000 minigui将RAM中的映像烧写到flash中,并命名为minigui,
用fis list查看到redboot把此映像自动分配到了0x80020000地址。
在程序中定义CYGNUM_FS_ROM_BASE_ADDRESS为0x80020000,就可以使用这个ROMFS了。

如果想用头文件的方式实现,只要用file2c.tcl就可以转换为C头文件,如下:
sh file2c.tcl minigui.bin miniguifs.h(或者直接用gen从目录生成H文件)
把这个头文件包含在C应用程序里,并将ROMFS挂装在这个数组上即可。不过这样每次更改目录/文件都要重新编译程序。

现在可以编译调试bomb程序了,可以看到液晶屏上显示出扫雷的图片,不过速度比较慢,刷屏的过程看得很清楚,可以看到一列一列摆放地雷的过程,不过这对于了解程序运行过程有好处。

图像显示正确后,就可以加入鼠标和键盘驱动了。一种方法是编写USB键盘和USB鼠标驱动;另一种是用PC机串口模拟。两种方法都支持,本讲只介绍后一种,另一个在USB host章节再说。

上位机
用PC机串口模拟鼠标键盘输入,上位机模拟程序用VC编写,在窗口内移动鼠标、按下/抬起按键或者有键盘输入时,模拟程序会从串口发送数据,格式为:
鼠标: 1LMRxxyy 0xxxxxxx 0yyyyyyy
键盘: 11111111 0000000k 0kkkkkkk

因为屏幕是240*320大小,所以用9位数就可以表示x、y坐标,L、M、R分别表示左中右按键情况,对应位为1表示按下,为0表示抬起,第一字节最高位为1,其他字节最高位为0,以便识别开始。键值用8位表示,第一字节为0xFF。
为了防止鼠标移出窗口,使用ClipCursor(&Rect)将鼠标限制在窗口内。
在CMainFrame::PreCreateWindow内修改窗口属性,使其显示240*320大小。
在OnLButtonDown/OnLButtonUp/OnRButtonDown/OnRButtonUp/OnMouseMove内记录鼠标位置和鼠标按键状态。
在OnCreate内打开串口,设置波特率等属性,创建一个50ms定时器。
在OnChar里判断输入键值。用GetKeyState函数查看指定虚拟键的状态,该状态指定此键是UP状态,DOWN状态,还是被触发的(开关每次按下此键时进行切换)。
在OnTimer里定时向串口发送经过编码的鼠标和/或键盘信息。

下位机
MiniGUI等待鼠标键盘事件(中断),当发生事件时更新坐标值和/或读入键值。在ecos中使用select原语提供多个事件同时等待,select支持阻塞等待,等待期间不浪费系统资源。read原语使用非阻塞调用方式(non-blocking),因此ecos配置时要选:
serial device drivers
support non-blocking read and write calls
注意:因为占用了串口0,所以printf在后面就不能使用了,超级终端也要关闭,以免冲突。
使用open ("/dev/ser0", O_RDONLY|O_NONBLOCK)打开串口,read读串口,select等待事件,解码出x、y坐标,LMR状态和键盘键值,就能够实现MiniGUI输入引擎。因为窗口大小是240*320的,而且鼠标被锁定在窗口内,所以,可以不用看PC屏幕,直接参照LCD屏幕鼠标指针位置移动鼠标/键盘输入,实验证明,鼠标指针的反映是及时的,只是在初始化时有比较明显的延时。
执行示例程序painter,可以用鼠标画线,反映稍微有一点延迟,换个高速CPU就能解决问题。

所有的C文件都能编译通过后,就可以开始加到ecos组件仓库里了。去掉所有多余文件,只保留源文件、Makefile文件、config.h头文件,做个简单的CDL脚本,一个minigui模板,就可以开始编译库了。这样,MiniGUI就成了我们系统的一个组件,用的时候只要配置裁减一下就可以了,再也不必关心编译问题了。

MiniGUI模仿了Windows的消息驱动机制,对于理解Windows工作原理很有好处。MiniGUI的体积在嵌入式里有点偏大(有点偏向桌面系统的迹象),但经过裁减和在ecos上的移植,还是实用的轻量级GUI。加上BUG的不断减少,更多软件的加入(如浏览器等),MiniGUI还是值得使用的。


共9条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]