阅读:2713回复:3
VxWorks在EasyARM2200和SmartARM2200上的移植
**********************************************
* 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.vector), zlgarmInt, (int)&zlgarmChan ); intEnable(INUM_TO_IVEC(devParas.vector)); 把串口中断ISR服务程序挂在相应中断号devParas.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),不提供源码,提供文档支持。 |
|
沙发#
发布于:2007-07-14 07:51
VxWorks for LPC2210应用编程实战
************************************ VxWorks for LPC2210应用编程实战 * *********************************** ------ 《ecos增值包》选配之VxWorks应用编程实战 2007/04/22 asdjf@163.com www.armecos.com vxWorks_romResident.bin试用版下载 483K 《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 增加demo文件并编译,会提示生成依赖文件,选OK。 4、5 设置Target Server(配置文件设置),使用串口通信,9600波特率,好象最高速率为38400,太高就不容易连接成功,用网络的话就比较快。注意:Core File and Symbols里的File文件要与烧写在flash里的VxWorks一致,即带调试信息的ELF文件。连接成功会显示成功信息,托盘里出现一个象靶子一样的图标。 6、7、8、9 下载.out文件 10 启动目标调试器,点那个象蚂蚁一样的图标。打开源文件设置断点什么的,然后点那个奔跑小人图标,就可以调试了,支持单步、全速等各种调试手段。开始时会询问运行任务函数名称和参数,如图设置即可。 13、14、11 串口0控制台运行效果 12 点击->i图标可以启动主机shell,输入i可以查看任务运行情况。刚开始有4个任务,执行任务、日志任务、shell终端、wdb调试驻留任务。当运行demo1后再输入i查询,可以看到多出了3个新任务:taska、taskb、taskc,正是我们在demo1里创建的3个任务。可以清楚看到任务优先级和延迟剩余时间及状态等信息。 15、16 |
|
板凳#
发布于:2007-07-14 07:51
VxWorks之MUX网络驱动在EasyARM2200和SmartARM2200上的实现
************************************************************ 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); printf("%x ", pDrvCtrl->packetBuf); } 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-07-14 07:52
VxWorks for LPC2210网络应用编程实战
**************************************** VxWorks for LPC2210网络应用编程实战 * *************************************** ------ 《ecos增值包》选配之VxWorks网络应用编程实战 2007/06/06 asdjf@163.com www.armecos.com VxWorks对网络支持得相当好,稳定且高效,内存池的巧妙运用实现了“零内存拷贝”,netJobAdd函数的使用减少了中断延迟时间,用户只需要实现网络驱动就可以运行完整的TCP/IP协议栈。通过网络下载和调试程序比串口快很多,我们已经移植好了EasyARM2200和SmartARM2200上的VxWorks网络驱动BSP,下面通过一个例子demo3.c来讲一下VxWorks for LPC2210网络应用编程。 这个例子是开发板上网页演示程序在VxWorks上的移植,通过实际测试(在SmartARM2200上),响应速度非常快(平均为0.09秒),ping测试(74字节包)的平均响应时间为3ms,ARP响应时间为3ms。源码如下: #include "sockLib.h" #include "inetLib.h" #define STACK_SIZE 2000 void webserver_test(int arg); unsigned char httpweb[]={ "HTTP/1.0 200 OK\r\n" "Date: Mon, 24 Nov 2003 01:24:17 GMT\r\n" "Server: microHttp/1.0 Zlgmcu Corporation\r\n" "Accept-Ranges: bytes\r\n" "Connection: Keep-Close\r\n" "Content-Type: text/html\r\n" "\r\n" }; unsigned char web[]={ "<HTML>\r\n" "<HEAD>\r\n" "<TITLE>ARM_NET演示网页(周立功单片机)</TITLE>\r\n" "<BODY aLink=green background=/100.bmp bgColor=#f1f1dd link=red\r\n" "vLink=#321afd>\r\n" "<H1>HELLO WELCOME TO EasyArm WEBSERVER</H1>\r\n" "<UL>\r\n" "<LI> <A HREF=\"http://www.zlgmcu.com/\">周立功单片机网站 </A>\r\n" "<LI> <A HREF=\"http://www.zlg.cn/\">周立功单片机内部BBS </A>\r\n" "<LI> <A HREF=\"http://www.zlgmcu.cn/\">周立功单片机 </A>\r\n" "</UL>\r\n" "</BODY>\r\n" "</HTML>\r\n" }; unsigned char httpgif[]={ "HTTP/1.0 200 OK\r\n" "Date: Mon, 24 Nov 2003 01:24:17 GMT\r\n" "Server: microHttp/1.0 Zlgmcu Corporation\r\n" "Accept-Ranges: bytes\r\n" "Connection: Keep-Close\r\n" "Content-Type: image/bmp\r\n" "\r\n" }; unsigned char bmp[442]={ 0x42,0x4d,0xb6,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x00,0x00,0x00,0x28,0x00, 0x00,0x00,0x1a,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x01,0x00,0x04,0x00,0x00,0x00, 0x00,0x00,0x40,0x01,0x00,0x00,0xc4,0x0e,0x00,0x00,0xc4,0x0e,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x80, 0x00,0x00,0x00,0x80,0x80,0x00,0x80,0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x80, 0x00,0x00,0xc0,0xc0,0xc0,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0xff,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0xff,0xff, 0x00,0x00,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf6,0x66,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x6f, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0x66,0x66,0x6f,0x6f,0xff,0x66,0x66, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xf6,0xff,0xff,0x6f,0xf6,0xff,0xf6, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x6f,0xff,0x6f,0xf6,0xff,0xf6, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xf6,0xff,0xf6,0xf6,0xff,0xff, 0x6f,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x6f,0xf6,0xff,0x6f,0xff, 0x6f,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xf6,0x66,0x66,0xf6,0xff,0xf6,0x66, 0x6f,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x6f,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x6f,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x6f,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0x00,0x00,0x00 }; void demo3(void) { printf("Demo3 Start......\n"); taskSpawn("webserver_test", 90, 0, STACK_SIZE, webserver_test, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0); } void webserver_test(int arg) { struct sockaddr_in host,client; int s,sa,e_source,len, sockAddrSize; unsigned char buf[400]; s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { return; } /* Set up host address */ sockAddrSize = sizeof(struct sockaddr_in); host.sin_family = AF_INET; host.sin_len = sizeof(host); host.sin_addr.s_addr = INADDR_ANY; host.sin_port = ntohs(80); if(bind(s, (struct sockaddr *) &host, sizeof(host)) < 0) { return; } listen(s, SOMAXCONN); while(1){ memset(buf, 0, sizeof(buf)); if ((sa = accept(s, (struct sockaddr *)&client, &sockAddrSize)) < 0) { printf("Accept ERROR!\n"); continue; } printf("SERVER : HTTP request arrived from %s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); len = read(sa, buf, sizeof(buf)); if(buf[5] == ' '){ len = write(sa, httpweb, sizeof(httpweb)-1); len = write(sa, web, sizeof(web)); } else if(buf[5] == '1'){ len = write(sa, httpgif, sizeof(httpgif)-1); len = write(sa, bmp,sizeof(bmp)); } close(sa); } } 下面图示说明使用方法: 和前面《VxWorks for LPC2210应用编程实战》里介绍的使用方法类似,唯一不同点是Target Server要设置成wdbrpc。 下面只介绍不同部分的设置。 设置Target Server,使用网络RPC通信,目标机IP地址为192.168.0.2,超时和重试次数根据需要设置,调试时如果网络速度比较慢的话(因为加入了调试信息),使用缺省值可能导致连接失败,正常工作时网络速度很快,不存在此问题。注意:Core File and Symbols里的File文件要与烧写在flash里的VxWorks一致,即带调试信息的ELF文件(vxWorks_romResident)。连接成功会显示成功信息,托盘里出现一个象靶子一样的图标。 其他操作与《VxWorks for LPC2210应用编程实战》里介绍的使用方法相同,不再赘述。 超级终端上显示的运行信息。 用IE访问192.168.0.2就可以浏览内置网页,响应速度相当快(0.09秒)。 基于网络RPC的调试shell比基于串口的快多了,输入“i”后,几乎是一下子蹦出任务状态列表信息。 这是ping测试的结果,通过sniffer抓包分析,ping测试(74字节包)的平均响应时间为3ms,ARP响应时间为3ms。看来VxWorks真的不赖。 |
|