您的位置 首页 观点

ARM硬件平台上根据UCOS移植Lwip网络协议栈

目录1硬件平台11.1硬件平台简介11.2硬件设计及电路原理图22.Keil开发工具及Keil工程简介62.1Keil开发工具62.2Keil工程…

目录

1硬件渠道 1

1.1硬件渠道简介 1
1.2 硬件规划电路原理图 2

2. Keil 开发东西及Keil工程简介 6

2.1 Keil开发东西 6
2.2 Keil工程简介 6
2.3 链接文件、发动文件剖析 6

3. UCOS移植 11

3.1 ucos简介 11
3.2 ucos移植总述 11
3.3 和移植UCOS有关的ARM芯片常识 11
3.4 体系仓库和UCOS的使命仓库 14
3.5 体系时钟 14
3.6 使命级使命切换 14
3.7 间断级使命切换 16

4.Lwip移植 18

4.1 lwip简介 18
4.2 lwip移植总述 18
4.3移植lwip操作体系模仿层 19
4.4 依据lwip供给的软件架构编写相应的网卡芯片驱动 27
4.5 移植完结后测验TCP/IP协议栈 35
4.6 规划并完结简略的WEB服务器 37

1.硬件渠道

1.1硬件渠道简介

为确保网络协议栈的顺畅移植,选用了LPC2220作为主控芯片,RTL8019AS作为网卡芯片,运用HR901170A进行电平转化、滤波。

LPC2220是Philips公司推出的微处理器,片上有64K的RAM空间,经过总线很简略再扩展ROM和RAM。芯片还具有丰厚的IO接口以及多种间断源,还集成了多种守时器、PWM等,别的,该芯片内部集成了许多串行通讯协议,如SPIUART等。

RTL8019AS是由台湾Realtek公司出产的以太网操控器。他契合EthernetII与IEEE802.3规范,100脚的PQFP封装,选用全双工收发并可一起到达10Mb/s的速率,内置16kB的SRAM,支撑8/16位数据总线,8个间断恳求线以及16个I/O基地址挑选。

HR901170A是汉仁电子有限公司出产的RJ45接口衔接器(带网络变压器/滤波器),该衔接器分量IEEES02.3和IEEE902.3ab规范,能够较好地按捺电磁搅扰。经过HR901170A体系就能够衔接到以太网上。

依据LPC2220和RTL8019AS的上述特色,咱们运用此款芯片能够规划出分量移植Lwip网络协议栈所需求的硬件运转环境。

1.2 硬件规划及电路原理图

图1.2-1硬件电路衔接图1

图1.2-2硬件电路衔接图2

RTL8019AS芯片作业方法分为3种:①跳线方法,网卡的i/o和间断由跳线决议。②即插即用方法,由软件进行主动装备plug and play。③免跳线方法,网卡的i/o和间断由外接的93c46里的内容决议。在嵌入式运用场合,为了节省本钱,一般不运用93c46的,能够降低本钱,一起又削减连线。咱们挑选运用跳线方法,运用此方法的硬件设置方法为第65引脚(JP)接高电平,如图1.2-2硬件电路衔接图2所示。

硬件复位引脚33(RSTDRV),此引脚为网卡芯片硬件复位引脚,RSTDRV为高电平有用,至少需求800ns的宽度。由硬件电路图可知,此引脚衔接到LPC2220的P0.8上。

间断引脚(INT7-0)为97-100,1-4 共有8个间断引脚,但运用时仅仅用一个间断引脚,挑选哪个引脚作为间断信号是依据[80-78][IRQS2-0]来决议的,依据电路图可IRQS2-0这三个引脚悬空,RTL8019AS内部有下拉电阻,故IRQS2-0这三个引脚电平都为0,依据手册可知,挑选的是INT0作为间断源引脚,此引脚衔接到LPC2220的P0.9引脚。

64脚(AUI),该引脚决议运用aui仍是bnc接口。咱们用的网卡的接口一般是bnc的,很少用aui。bnc接口方法支撑8线双绞或同轴电缆。高电平时运用aui接口,悬空为低电平,运用bnc接口。咱们将该引脚悬空即可。

网络接口类型由74,77(PL0,PL1)引脚决议,咱们运用第一种主动检测就能够了。会主动检测接口类型然后进行作业。主动检测是用同轴仍是双绞线。这两个引脚内部存在下拉电阻,悬空即可。

芯片的brom地址由以下引脚72,71,69,68,67(BS4..BS0)决议,在嵌入式范畴一般都不用该brom。brom是bootrom的缩写。在电脑里用来做无盘作业站时分用到,能够从网卡进行引导,而不是从a盘,c盘等引导体系。故悬空即可。

RTL8019AS支撑3支可编程LED灯,电路衔接见原理图。

RTL8019AS与主控芯片间通讯的输入/输出地址共有32个,地址偏移量为00H-1FH。

RTL8019AS的IO基地址在跳线方法下由[85-84,82-81] [IOS3-0]这四个引脚状况决议,电路图中这四个引脚悬空,故这四个引脚状况都为0,依据数据手册可知RTL8019AS的IO基地址为300H,将300H化成二进制数值00110000 0000,很明显地址中第8、9为地址为1,第6、7位和10-19位悉数为0。咱们仅需求操控第0-4位地址,就能够产生00H-1FH这32个偏移量。电路原理图中SA8、SA9接+5v,SA10-SA19接的是地。

电路图中SA0-SA4别离接的是LPC2220的A1-A5引脚,而SA5接的是NET_nCS引脚。

图1.2-2硬件电路衔接图3

NET_nCS的信号是依据nCS3(BANK3的片选信号)和A22地址线信号产生的。

数据总线SD0-SD15衔接到LPC2220的D0-D15,组成16bit总线。

产生00H-1FH的偏移量需求NET_nCS信号为低。咱们总结一下,咱们的RTL8019AS需求的地址是300H-301FH,硬件连线决议了这个地址偏移量。咱们将RTL8019AS接到LPC2220的BANK3上。对LPC2220来说,只产生00H-1FH的偏移量就能够。LPC2220的BANK3开端地址是0X83000000,也便是说当拜访这个地址时才会产生nCS3为低的信号,假如BANK3只需求衔接网卡的话,咱们就能够直接运用nCS3信号作为选通网卡芯片的信号即可,但咱们硬件规划时将BANK3又分成了几个独立的拜访空间用于挂接不同的总线器材。咱们运用地址线A21、A22、A23将BANK3分为0X834000000、0x83100000、0x83800000这几个独立空间。咱们只剖析运用A22地址线信号和nCS3

产生的NET_nCS信号,此信号线硬件上衔接到RTL8019AS的SA5上,A22地址线上信号为高电平而且nCS3产生低电平信号,这种状况下NET_nCS才是低电平,而只需NET_nCS为低电平时,才干产生RTL8019AS需求的300H-301FH地址偏移量。现在经过LPC2220拜访地址空间0x83400000,这个时分依据上面剖析NET_nCS为低电平,也即RTL8019AS的SA5为低电平,第四位地址线SA0-SA4衔接的是LPC2220的A1-A5,

拜访0x83400000、0x83400001对应的RTL8019AS地址即为300H,同理0x83400010、0x83400011对应的RTL8019AS地址即为301H。咱们拜访LPC2220的0x83400000-0x8340003F即拜访了RTL8019AS的32个偏移量。

2. Keil 开发东西及Keil工程简介

2.1Keil开发东西

Keil MDK供给了针对ARM系列芯片的汇编器、C/C++的编译器和一个能进行工程办理的IDE。一起,该软件还支撑JLink在线调试,是进行嵌入式软件开发十分优异的软件。

2.2 Keil工程简介

Keil MDK能够树立针对详细芯片的工程,依据选定的ARM芯片主动生成发动代码,担任硬件的根本初始化和C言语运转环境以及C言语库的初始化。供给工程文件办理,全体编译、链接、调试。Keil MDK工程还能够编制链接文件,链接器会依据编制的链接文件进行链接二进制文件,用来分量嵌入式开发的不同硬件渠道需求。

2.3 链接文件、发动文件剖析

ARM芯片运转方法和仓库相关常识都对了解UCOS的使命切换都有很大的协助,因而咱们首要应该了解芯片运转方法和仓库的概念。了解这些概念最好的方法是剖析一下体系发动代码。
在剖析发动代码之前,先了解一下Keil MDK 工程中Scf链接文件的相关常识。咱们知道源代码程序经过编译、链接后生成一个二进制文件,这个二进制文件是用来操控ARM芯片的。
这个二进制文件是直接下载到ARM处理器芯片的,这个二进制文件的格局如图2.4-1 ARM Image映像文件结构。

图2.4-1 ARM Image映像文件结构

ZI段总共初始化为0的变量区域,RW段总共现已初始化的变量区域,RO段总共代码区域。

因ZI段仅仅初始化为0的变量区域,所以在Image文件中并不占空间,映像文件中仅仅包含实践地址和巨细。咱们一般将image映像文件下载到ROM中,体系发动时从ROM中读取第一条需求碑文的指令,但RW段下载到了ROM中,ROM是不可写的。因而呈现了装载地址和运转地址不共同的状况。咱们要确保程序正常运转就有必要确保变量在拜访之前放到了正确的地址。一个简略的装载地址到运转地址的转化见图2.4-2 简略的涣散装载内存映像图。

图2.4-2 简略的涣散装载内存映像图

在KeilMDK工程中运用涣散装载文件scf文件来设置映像文件的转载地址和运转地址,当咱们设置的转载地址和运转地址不共同时,KeilMDK会主动产生转移代码,在运用RW、ZI段之前将代码转移到正确的地址。

咱们工程运用的涣散加载文件内容:

ROM_LOAD 0x80000000

{

ROM_EXEC 0x80000000

{

Startup.o (vectors, +First)

* (+RO)

}

IRAM 0x40000000

{

Startup.o (MyStacks)

}

STACKS_BOTTOM +0 UNINIT

{

Startup.o (StackBottom)

}

STACKS 0x40004000 UNINIT

{

Startup.o (Stacks)

}

ERAM 0x81000000

{

* (+RW,+ZI)

}

HEAP +0 UNINIT

{

Startup.o (Heap)

}

HEAP_BOTTOM 0x81080000 UNINIT

{

Startup.o (HeapTop)

}

}

此涣散加载文件只需一个装载域ROM_LOAD,装载地址是0x80000000,这个地址是ARM芯片外的一个NorFlash芯片的开端地址。存在ROM_EXEC、IRAM、STACKS_BOTTOM、STACKS、ERAM、HEAP、HEAP_BOTTOM共8个运转域,每个运转域都有自己的运转地址。其间ROM_EXEC运转域和装载域地址相同,此运转域包含体系的发动代码和一切RO段代码。剩下其他运转域的地址和装载域都不同,都需求依据涣散加载文件进行代码转移作业,这个作业是由KeilMDK东西主动完结。

体系发动代码首要完结的作业如下:

1. 间断向量表

2. 初始化总线频率和存储器体系

3. 初始化仓库

4. 呼叫主运用程序

间断向量表是当外部中或体系反常产生时刻断服务程序的进口地址或寄存间断服务程序的首地址。此工程中将间断向量表定位在0x80000000这个地址开端的当地。

AREA vectors,CODE,READONLY

ENTRY

;interrupt vectors

Reset

LDR PC, ResetAddr

LDR PC, UndefinedAddr

LDR PC, SWI_Addr

LDR PC, PrefetchAddr

LDR PC, DataAbortAddr

DCD 0xb9205f80

LDR PC, [PC, #-0xff0]

LDR PC, FIQ_Addr

ResetAddr DCD ResetInit

UndefinedAddr DCD Undefined

SWI_Addr DCD SoftwareInterrupt

PrefetchAddr DCD PrefetchAbort

DataAbortAddr DCD DataAbort

Nouse DCD 0

IRQ_Addr DCD 0

FIQ_Addr DCD FIQ_Handler

初始化总线频率以分量各个BANK外接的设备正常运用,一个杂乱的体系或许存在多种存储器类型的接口,需求依据实践的体系规划对此加以正确装备。对同一种存储器类型来说,也咱们拜访速度的差异,需求不同的时序设置。工程中咱们运用的存储器包含NorFlash和SRAM,设置的拜访总线宽度都为16bit。

仓库空间是C言语正常运转所需求的根本环境,函数调用参数、回来值、函数调用联系都需求运用仓库。因而,需求设置ARM各个运转方法的仓库空间。

InitStack

MOV R0, LR

;Build the SVC stack

MSR CPSR_c, #0xd2

LDR SP, StackIrq

;Build the FIQ stack

MSR CPSR_c, #0xd1

LDR SP, StackFiq

;Build the DATAABORT stack

MSR CPSR_c, #0xd7

LDR SP, StackAbt

;Build the UDF stack

MSR CPSR_c, #0xdb

LDR SP, StackUnd

;Build the SYS stack

MSR CPSR_c, #0xdf

LDR SP, =StackUsr

BX R0

调用__main()函数,此函数首要作业流程如图2.4-3 __main 函数碑文流程。

图2.4-3 __main 函数碑文流程

  1. 调用__user_setup_stackheap()设置用户方法下的栈空间和堆空间。空间能够通进程序界说,也能够经过涣散加载文件拟定肯定地址空间。
  2. 调用__rt_lib_init()初始化库函数,在必要时为用户main函数设置argc、argv参数。调用__cpp_initialize__aeabi_初始化C++特性。
  3. Calls main(), the user-level root of the application.

From main(),your program might call, among other things, library functions.

调用用户main函数,在main函数里,你能够调用其他用户函数,也能够调用库函数。

  1. Calls exit() with the value returned by main().
  2. 当main函数回来时,调用exit函数整理资源。

3. UCOS移植

3.1 ucos简介

UCOS是一个可裁剪、支撑抢占式调度的实时嵌入式操作体系。供给根本的使命办理功用,支撑信号量、邮箱、行列等使命间同步、通讯机制。

3.2 ucos移植总述

Ucos移植首要是完结保存、康复ARM芯片碑文程序所需求的寄存器环境和完结体系时钟接口需求的硬件守时器的设置及发动。需求移植完结的首要有使命级使命切换、间断级使命切换、使命仓库初始化、体系时钟。

3.3 和移植UCOS有关的ARM芯片常识

C言语经过编译器编译、链接后生成的二进制指令是能在ARM芯片上直接碑文的指令代码。这些指令碑文是依赖于各种寄存器的,维护程序运转环境其实便是维护这些寄存器。

ARM芯片有7种运转方法:

1. 用户方法(user方法),运转运用的一般方法。

2. 快速间断方法(fiq方法),用于支撑数据传输或通道处理。

3. 间断方法(irq方法),用于一般间断处理。

4. 超级用户方法(svc方法),操作体系的维护方法。

5. 反常间断方法(abt方法),输入数据后登入或预取反常间断指令。

6. 体系方法(sys方法),是操作体系运用的一个有特权的用户方法。

7. 未界说方法(und方法),碑文了未界说指令时进入该方法。

外部间断,反常操作或软件操控都能够改动间断方法。大大都运用程序都时是在用户方法下运转。进入特权方法是为了处理间断或反常恳求或操作维护资源服务的。

些作业方法是芯片硬件供给的程序运转的不同环境,不同的方法有不同的硬件拜访权限,运用不同的寄存器。这就给不同的程序供给了不同的权限机制,你比方说你的操作体系代码运转在权限比较高的方法下,而你的运用程序运转在权限比较低的方法下。这样就起到了对操作体系代码的维护效果。

寄存器,各个方法下可见的寄存器以及各个寄存器的功用:

ARM共有37个32位的寄存器,其间31个是通用寄存器,6个是状况寄存器。但在同一时刻,对程序员来说并不是一切的寄存器都可见。在某一时刻存储器是否可见(可被拜访),是处理器当时的作业状况和作业方法决议的。其各个方法下的寄存器如图3.3-1 ARM各种运转方法:

图3.3-1 ARM各种运转方法

其间体系方法和用户方法所用的寄存器是相同的。画三角暗影的寄存器总共在不同方法下有不同的物理寄存器。

以下对其进行分类阐明。

通用寄存器:

ARM的通用寄存器包含R0~R15,其间R0~R7是归于未分组寄存器,各个方法下都运用相同的寄存器。R8~R14在FIQ方法下是有独立的物理寄存器,其意图是加速间断呼应速度,从硬件上保存程序碑文现场。R13和R14这两个寄存器在每种方法下都有自己的独立寄存器。R15只需一个,一切方法共用。

下对这些寄存器中的比较有特别功用的做一下介绍:

寄存器R13:在ARM指令中,常用R13做仓库指针用。每种运转方法都有自己独立的仓库,用于保存间断产生时的程序运转环境和C言语碑文时进行进程操控。

寄存器R14:专职持有回来点的地址,在体系碑文一条“跳转并链接(link)”(BL)指令

的时分,R14将收到一个R15的劳动。其他的时分,它能够用作一个通用寄存器。相应的它在其他方法下的私有寄存器R14_svc,R14_irq,R14_fiq,R14_abt和R14_und都相同用来保存在间断或反常产生时,或时在间断和反常中碑文了BL指令时,R15的回来值。

寄存器R15是程序计数器(PC)。在ARM状况下,R15的bits[1:0]为0,bits[31:2]保存了PC的值。在Thumb状况下,bits[0]为0一起bits[31:1]保存了PC值。

FIQ方法具有7个私有寄存器R8-14(R8_fiq-R14_fiq)。在ARM状况下,大都FIQ处理都不需求保存任何寄存器。用户、间断、反常间断,超级用户和未界说方法都具有2个私有寄存器,R13和R14。答应这些方法都可具有1个私有仓库指针和链接(link)寄存器。

程序状况寄存器。

ARM920T具有一个当时程序状况寄存器(CPSR),别的还有5个保存程序状况寄存器(SPSRs)用于反常间断处理。这些寄存器的功用有:

1. 保存最近完结的ALU操作的信息。

2. 操控间断的使能和制止。

3. 设置处理器的操作方法。

状况寄存器各位界说见图3.3-2 ARM状况寄存器:

图3.3-2 ARM状况寄存器

3.4 体系仓库和UCOS的使命仓库

当产生外部间断或许体系反常时,ARM会进入相应的方法,各种运转方法均有其独立的仓库空间。UCOS中的使命是调度的最小单元,每个使命都有自己独立的仓库空间,当使命运转时,它用来保存一些局部变量,当使命挂起时,它担任保存使命的运转现场,也便是CPU寄存器的值。

3.5 体系时钟

体系时钟是UCOS办理使命延时的根本依据,要求有一个周期性的守时器产生固定距离时刻。咱们运用LPC2220的守时器0产生固守时刻距离事情,时刻距离设置为10ms,守时时刻到产生间断。UCOS体系时钟处理函数是OSTimeTick(),时刻间断服务程序里调用此函数即可。

3.6 使命级使命切换

UCOS的用户调用一些体系服务时(比方,OSTimeDly、OSSemPend),就会产生使命级使命切换。其切换的本质是保存正在碑文使命的碑文现场,然后康复应该运转的使命的运转现场。
本工程中运用软间断的方法完结使命级使命切换的意图。
使命级切换函数的底层接口是运用的软间断技能,用__swi来声明一个不存在的函数,则调用这个函数就在调用这个函数的当地刺进一条SWI指令,而且能够指定功用号。界说如下:__swi(0x00) void OS_TASK_SW(void); /* 使命级使命切换函数 */
调用OS_TASK_SW()这个函数时就会产生一个软间断,产生软间断后碑文软间断服务程序。服务程序首要代码剖析如下:
SoftwareInterrupt
LDR SP, StackSvc ; 从头设置仓库指针
STMFD SP!, {R0-R3, R12, LR}
MOV R1, SP ; R1指向参数存储方位

MRS R3, SPSR
TST R3, #T_bit ; 间断前是否是Thumb状况
LDRNEH R0, [LR,#-2] ; 是: 获得Thumb状况SWI号
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 获得arm状况SWI号
BICEQ R0, R0, #0xFF000000
; r0 = SWI号,R1指向参数存储方位
CMP R0, #1
LDRLO PC, =OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次使命切换

BL SWI_Exception

LDMFD SP!, {R0-R3, R12, PC}^
代码难点剖析:
软间断指令使处理器进入办理方法,而用户程序处于体系/用户方法,其它反常也有自己的处理器方法,都有各自的仓库指针,不会咱们给仓库指针赋值而损坏其它处理器方法的仓库而影响其它程序的碑文。回来的地址现已存储在衔接寄存器LR中而不是存储在仓库中。咱们进人办理方法主动关间断,所以这段程序不会被其它程序一起调用。
咱们ARM处理器核具有两个指令集,在碑文Thumb指令的状况时不是一切寄存器都可见(参阅ARM的相关材料),而且使命又或许不在特权方法(不能改动CPSR)。为了兼容恣意一种方法,本移植运用软间断指令SWI使处理器进入办理方法和ARM指令状况,并运用功用0完结OS_TASK_SW()的功用。
因使命级使命切换运用的是软间断技能,咱们把osctxsw()与osintctxsw()合二为一了,共同选用osintctxsw()来完结。之所以这样搞的原因是使命进行切换的时分,都有必要进入软间断的状况,而关于软间断的反常呼应代码现已将使命的环境变量进行了保存,然后也不需求像osctxsw()晒干规则的那样对将环境变量进行保存。osintctxsw()函数的移植剖析见3.7间断级使命切换。

3.7 间断级使命切换

当体系使命延时时刻到或许在间断服务程序里抛出信号量、邮箱等能够产生体系调度的操作时,会碑文使命切换,但这种切换是在间断方法下进行的。但底层切换函数是共同的,只不过使命级使命切换时是在SVC方法下进行,间断级使命切换是在间断方法下进行。
下面咱们剖析间断级使命切换的首要流程和代码:
SUB LR, LR, #4 ; 核算回来地址
STMFD SP!, {R0-R3, R12, LR} ; 保存使命环境
MRS R3, SPSR ; 保存状况
STMFD SP, {R3,SP, LR}^; 保存用户状况的R3,SP,LR,留意不能回写
; 假如回写的是用户的SP,所以后边要调整SP
LDR R2, =OSIntNesting ; OSIntNesting++
LDRB R1, [R2]
ADD R1, R1, #1
STRB R1, [R2]

SUB SP, SP, #4*3

MSR CPSR_c, #(NoInt :OR: SYS32Mode) ; 切换到体系方法
CMP R1, #1
LDREQ SP, =StackUsr

BL $IRQ_Exception_Function ; 调用c言语的间断处理程序

MSR CPSR_c, #(NoInt :OR: SYS32Mode) ; 切换到体系方法
LDR R2, =OsEnterSum; OsEnterSum,使OSIntExit退出时刻断封闭
MOV R1, #1
STR R1, [R2]

BL OSIntExit

LDR R2, =OsEnterSum; 咱们间断服务程序要退出,
;所以OsEnterSum=0
MOV R1, #0
STR R1, [R2]

MSR CPSR_c, #(NoInt :OR: IRQ32Mode) ; 切换回irq方法
LDMFD SP, {R3,SP, LR }^ ; 康复用户状况的R3,SP,LR,
;留意不能回写
; 假如回写的是用户的SP,所以后边要调整SP
LDR R0, =OSTCBHighRdy
LDR R0, [R0]
LDR R1, =OSTCBCur
LDR R1, [R1]
CMP R0, R1

ADD SP, SP, #4*3 ;
MSR SPSR_cxsf, R3
LDMEQFD SP!, {R0-R3, R12, PC}^ ; 不进行使命切换
LDR PC, =OSIntCtxSw ; 进行使命切换

代码首要功用剖析:
完结在间断方法下保存体系方法下正在运转使命的各个寄存器到间断方法仓库,然后碑文相应的间断服务程序,间断退出时做使命切换。
下面咱们剖析完结使命切换的函数OSIntCtxSw。
OSIntCtxSw
;下面为保存使命环境
LDR R2, [SP, #20] ;获取PC
LDR R12, [SP, #16] ;获取R12
MRS R0, CPSR

MSR CPSR_c, #(NoInt :OR: SYS32Mode)
MOV R1, LR
STMFD SP!, {R1-R2} ;保存LR,PC
STMFD SP!, {R4-R12} ;保存R4-R12

MSR CPSR_c, R0
LDMFD SP!, {R4-R7} ;获取R0-R3
ADD SP, SP, #8 ;出栈R12,PC

MSR CPSR_c, #(NoInt :OR: SYS32Mode)
STMFD SP!, {R4-R7} ;保存R0-R3

LDR R1, =OsEnterSum ;获取OsEnterSum
LDR R2, [R1]
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum

;保存当时使命仓库指针到当时使命的TCB
LDR R1, =OSTCBCur
LDR R1, [R1]
STR SP, [R1]
BL OSTaskSwHook ;调用钩子函数
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4]
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
上述函数完结了保存上一个被间断使命运转时各个寄存器到使命的仓库空间里,然后将体系中优先级最高且安排妥当的使命仓库里保存的各个寄存器内容康复到体系方法的各个寄存器中,使使命正常运转。

4.Lwip移植

4.1 lwip简介

lwip是瑞典核算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。LwIP是Light Weight (轻型)IP协议,有无操作体系的支撑都能够运转。LwIP完结的重点是在坚持TCP协议首要功用的基础上削减对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就能够运转,这使LwIP协议栈适合在低端的嵌入式体系中运用。

4.2 lwip移植总述

Lwip有无操作体系的支撑都能够运转,咱们移植是依据UCOS的。
依据UCOS移植Lwip首要包含两个方面的作业:
1. 依据Lwip供给的操作体系模仿层接口编写依据UCOS的完结代码,以完结Lwip和UCOS的完美交融。
2. 依据Lwip供给的底层网卡驱动接口,结合RTL8019AS网卡datasheet编制网卡驱动程序。

4.3移植lwip操作体系模仿层

操作体系模仿层(sys_arch)存在的意图首要是为了便利 LwIP 的移植,它在底层操作体系和LwIP 之间供给了一个接口。这样,咱们在移植 LwIP 到一个新的方针体系时,只需修正这个接口即可。不过,不依赖底层操作体系的支撑也能够完结这个接口。
sys_arch需求为LwIP供给创立新线程功用,供给信号量 (semaphores) 和邮箱 (mailboxes) 两种进程间通讯方法 (IPC) 。
1. 模仿层需求增加的头文件 cc.h 阐明
Lwip运用的数据类型界说:
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned short u16_t;
typedef signed short s16_t;
typedef unsigned int u32_t;
typedef signed int s32_t;
typedef unsigned int sys_prot_t;
typedef unsigned int mem_ptr_t;
lwip运用的结构体对齐方法声明相关的宏界说:
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_END
为便利操作协议帧数据,lwip协议栈中结构体运用单字节对齐方法。
处理器方法:
#define BYTE_ORDER LITTLE_ENDIAN
咱们运用的LPC2220为小端方法处理器,故界说为小端方法。
其他内容首要和调试输出功用有关,这儿不进行逐个阐明。
2. 需求完结的操作体系模仿层函数
– void sys_init(void)

初始化lwip操作体系模仿层。

– sys_sem_t sys_sem_new(u8_t count)

创立一个信号量,count总共初始化后的信号量状况。

– void sys_sem_free(sys_sem_t sem)

删去指定的信号量。

– void sys_sem_signal(sys_sem_t sem)

发送一个信号量。

– u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
等候指定的信号并堵塞线程。timeout 参数为 0,线程会一向被堵塞至收到指定的信号;非 0,则线程仅被堵塞至指定的 timeout时刻(单位为毫秒)。在timeout 参数值非 0 的状况下,回来值为等候指定的信号所耗费的毫秒数。假如在指定的时刻内并没有收到信号,回来值为SYS_ARCH_TIMEOUT。假如线程不用再等候这个信号(也便是说,现已收到信号) ,回来值也能够为 0。留意,LwIP完结了一个称号与之类似的函数来调用这个函数,sys_sem_wait(),留意差异。
– sys_mbox_t sys_mbox_new(void)
创立一个空音讯邮箱。

– void sys_mbox_free(sys_mbox_t mbox)
开释一个邮箱。

– void sys_mbox_post(sys_mbox_t mbox, void *msg)
投递音讯“msg”到指定的邮箱“mbox” 。

– u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
堵塞线程直至邮箱收到至少一条音讯。最长堵塞时刻由 timeout 参数指定(与
sys_arch_sem_wait()函数类似) 。msg 是一个成果参数,用来保存邮箱中的音讯指针 (即*msg = ptr) ,它的值由这个函数设置。 “msg”参数有或许为空,这标明当时这条音讯应该被丢掉。 回来值与 sys_arch_sem_wait()函数相同:等候的毫秒数或许 SYS_ARCH_TIMEOUT――假如时刻溢出的话。LwIP完结的函数中,有一个称号与之类似的――sys_mbox_fetch(),留意委任。

– struct sys_timeouts *sys_arch_timeouts(void)
回来一个指向当时线程运用的 sys_timeouts 结构的指针。LwIP 中,每一个线程都有一个timeouts 链表,这个链表由 sys_timeout 结构组成,sys_timeouts 结构则保存了指向这个链表的指针。这个函数由 LwIP 的超时调度程序调用,而且不能回来一个空(NULL)值。 单线程 sys_arch 完结中,这个函数只需简略回来一个指针即可。这个指针指向保存在 sys_arch 模块中的 sys_timeouts 全局变量

– sys_thread_t sys_thread_new(void (* thread)(void *arg), void *arg, int prio)

创立一个新的线程。

完结sys_sem_t sys_sem_new(u8_t count)函数:
sys_sem_t sys_sem_new(u8_t count)
{
return OSSemCreate((u16_t)count);
}
这个函数完结比较简略,UCOS供给了信号量的操作函数,直接调用即可。
完结void sys_sem_free(sys_sem_t sem)函数:
void sys_sem_free(sys_sem_t sem)
{
u8_t Err;
OSSemDel(sem, OS_DEL_ALWAYS, &Err);
}
完结void sys_sem_signal(sys_sem_t sem)函数:
void sys_sem_signal(sys_sem_t sem)
{
OSSemPost(sem);
}
完结u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)函数:
u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
{
u8_t Err;
u32_t wait_ticks;

if (OSSemAccept(sem))/* 假如现已收到, 则回来0 */
{
return 0;
}

wait_ticks = 0;
if(timeout!=0){
wait_ticks = (timeout * OS_TICKS_PER_SEC)/1000;
if(wait_ticks < 1)
wait_ticks = 1;
else if(wait_ticks > 65535)
wait_ticks = 65535;
}

OSSemPend(sem, (u16_t)wait_ticks, &Err);

if (Err == OS_NO_ERR)
return timeout/2; //将等候时刻设置为timeout/2
else
return SYS_ARCH_TIMEOUT;
}
堵塞进程,等候一个信号量的到来。假如timeout不为0,则进程堵塞的时刻最多为相关的毫秒数,不然进程一向堵塞,直到收到信号量。
回来值:假如timeout不为0,则回来值为等候该信号量的毫秒数,假如函数在规则的时刻内没有比及信号量,则回来值为SYS_ARCH_TIMEOUT,假如信号量在调用函数时现已可用,则函数不会产生任何堵塞操作,回来值这时能够是0。
完结sys_mbox_t sys_mbox_new(int size)函数功用:
sys_mbox_t sys_mbox_new(int size)
{
u8_t Err;
sys_mbox_t pQDesc;

pQDesc = OSMemGet( MboxMem, &Err );
if( Err == OS_NO_ERR ) {
pQDesc->ucos_queue = OSQCreate( &(pQDesc->mbox_msg_entris[0]), MAX_MSG_IN_MBOX );
if( pQDesc->ucos_queue != NULL ) {
return pQDesc;
}
else{
OSMemPut(MboxMem,pQDesc);
}
}
return SYS_MBOX_NULL;
}
邮箱用于音讯传递,用户即能够将其完结为一个行列,答应多条音讯投递到这个邮箱,也能够每次只答应投递一个音讯,这两种方法 LwIP都能够正常运作。不过,前者愈加有用。这儿咱们运用音讯行列的方法,答应投递多条音讯。
完结void sys_mbox_free(sys_mbox_t mbox)函数:
void sys_mbox_free(sys_mbox_t mbox)
{
u8_t Err;

OSQFlush(mbox->ucos_queue);

OSQDel(mbox->ucos_queue, OS_DEL_ALWAYS, &Err);

OSMemPut( MboxMem, mbox );
}
完结void sys_mbox_post(sys_mbox_t mbox, void *msg)函数功用:
void sys_mbox_post(sys_mbox_t mbox, void *msg)
{
if (msg == NULL)
msg = (void*)&NullMessage;//处理空指针投递的问题
while (OSQPost(mbox->ucos_queue, msg) == OS_Q_FULL)
OSTimeDly(10);
}
完结u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)函数:
u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)
{
u8_t Err;
u32_t wait_ticks;
void *Data;

Data = OSQAccept(mbox->ucos_queue);
if (Data != NULL)
{
if (Data == (void*)&NullMessage)
{
*msg = NULL;
}
else
{
*msg = Data;
}
return 0;
}

wait_ticks = 0;
if(timeout!=0){
wait_ticks = (timeout * OS_TICKS_PER_SEC)/1000;
if(wait_ticks < 1)
wait_ticks = 1;
else if(wait_ticks > 65535)
wait_ticks = 65535;
}

Data = OSQPend(mbox->ucos_queue, (u16_t)wait_ticks, &Err);

if (Data != NULL)
{
if (Data == (void*)&NullMessage)
{
*msg = NULL;
}
else
{
*msg = Data;
}
}

if (Err == OS_NO_ERR)
return timeout/2; //将等候时刻设置为timeout/2
else
return SYS_ARCH_TIMEOUT;
}
完结struct sys_timeouts * sys_arch_timeouts(void)函数功用:
struct sys_timeouts * sys_arch_timeouts(void)
{
return &global_timeouts;
}
完结sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)函数功用:
sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)
{
static u32_t TaskCreateFlag=0;
u8_t i=0;
name=name;
stacksize=stacksize;

while((TaskCreateFlag>>i)&0x01){
if(ii++;
else return 0;
}
if(OSTaskCreate(thread,(void*)arg, &LWIP_STK_AREA[i][LWIP_STK_SIZE-1],prio)==OS_NO_ERR){
TaskCreateFlag |=(0x01<};

return prio;
}
新建一个进程,在整个体系中只会被调用一次。
移植操作体系模仿层完结。

4.4 依据lwip供给的软件架构编写相应的网卡芯片驱动

从用户编程视点看,针对RTL8019AS的操作实践上便是对RTL8019AS内部寄存器的操作,以完结网卡的初始化、数据发送、数据接纳等操作。发送数据时,主操控器将数据写入网卡的SRAM中,然后发送一个发送数据指令,网卡就会将数据封装成规范以太网物理层数据帧发送出去。同理,网卡接纳到以太网数据时,网卡会主动解析成高层运用的有用格局,放在内部SRAM中供主控芯片读取,咱们选用周期查询方法完结接纳数据的处理。
RTL8019AS与主控芯片间通讯的输入/输出地址共有32个,地址偏移量为00H-1FH。其间00-0F共16个地址,为内部寄存器地址,RTL8019AS的内部寄存器每个都是8位的,一切寄存器总共分为4页,每一页都同享这16个偏移量,当时那一页有用是由CR寄存器的值决议的。

要接纳和发送数据包都有必要读写网卡的内部的16k的ram,有必要经过DMA进行读和写.网卡的内部ram是一块双端口的16k字节的ram.所谓双端口便是说有两套总线衔接到该ram,一套总线A是网卡操控器读/写网卡上的ram,另一套总线B是主操控器读/写网卡上的ram.总线A又名Local DMA,总线B又名Remote DMA.
长途DMA地址包含10H~17H,都能够用来做长途DMA端口,只需用其间的一个就能够了。咱们运用10H。
复位端口包含18H~1FH共8个地址,功用相同,用于RTL8019AS复位。咱们运用18H。

Lwip供给了网卡驱动结构方法,咱们只需依据实践运用的网卡特性完善这些函数就能够了。
详细说咱们应该完结以5个函数的完结。
static void low_level_init(struct netif *netif)。
static err_t low_level_output(struct netif *netif, struct pbuf *p)
static struct pbuf *low_level_input(struct netif *netif)
err_t ethernetif_init(struct netif *netif)
static void ethernetif_input(struct netif *netif)
前3个函数与网卡驱动函数密切相关。low_level_init为网卡初始化函数,首要用来完结网卡的复位及参数初始化。low_level_output为网卡数据包发送函数。low_level_input为网卡数据包接纳函数。
ethernetif_input函数首要效果是调用网卡数据包接纳函数low_level_input从网卡SRAM中读取一个数据包,然后解析数据包类型,然后交付给上层运用程序。实践上,ethernetif_input现已是一个能够直接运用的函数,调用一次能够完结数据包的接纳和递送。咱们在运用层树立一个使命周期性调用该函数完结接纳数据包的功用。
ethernetif_init是上层运用在办理网络接口结构netif时调用的函数。该函数首要完结netif结构中的某些字段初始化,并终究调用low_level_init函数完结网卡的初始化。
low_level_init函数完结源代码:
static void
low_level_init(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state;

/* set MAC hardware address length */
netif->hwaddr_len = ETHARP_HWADDR_LEN;

/* set MAC hardware address */
netif->hwaddr[0] = MyMacID[0];
netif->hwaddr[1] = MyMacID[1];
netif->hwaddr[2] = MyMacID[2];
netif->hwaddr[3] = MyMacID[3];
netif->hwaddr[4] = MyMacID[4];
netif->hwaddr[5] = MyMacID[5];

/* maximum transfer unit */
netif->mtu = 1500;

/* device capabilities */
/* dont set NETIF_FLAG_ETHARP if this device is not an ethernet one */
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

/* Do whatever else is needed to initialize interface. */
board_eth_init();
}
netif结构体是协议栈内核对体系网络接口设备进行办理的重要数据结构,内核会为每个网络接口分配一个netif结构,用于描绘接口特点。上面函数初始化了hwaddr、mtu、flag等要害特点域,并最终调用board_eth_init函数。
board_eth_init函数源代码如下:
void board_eth_init(void)
{
unsigned char i;
unsigned char j;

IODIR=IODIR|RSTDRV;
IOCLR=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
IOSET=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}
IOCLR=RSTDRV;
for(i=0;i<200;i++)
{
for(j=0;j<200;j++);
}

NE_RESET = 0x12;
Delay(500);
NE_CR = ENCR_PAGE0 + ENCR_NODMA;
NE_DCR = NE_DCRVAL;
NE_RBCR0 = 0x00; /* MSB remote byte count reg */
NE_RBCR1 = 0x00; /* LSB remote byte count reg */
NE_TPSR = TX_START_PG;
NE_PSTART = RX_START_PG ; /* DMA START PAGE 46h */
NE_PSTOP = RX_STOP_PG; /* Ending page +1 of ring buffer */
NE_BNRY = RX_START_PG;/* Boundary page of ring buffer */
NE_RCR = ENRCR_RXCONFIG;
NE_TCR = ENTCR_TXCONFIG; /* xmit on. */
NE_ISR = 0xff; /* Individual bits are cleared by writing a “1” into it. */
NE_IMR = ENIMR_RX; // by Forrest..
NE_CR = ENCR_PAGE1 + ENCR_NODMA;
NE_PAR0 = MyMacID[0];
NE_PAR1 = MyMacID[1];
NE_PAR2 = MyMacID[2];
NE_PAR3 = MyMacID[3];
NE_PAR4 = MyMacID[4];
NE_PAR5 = MyMacID[5];
NE_MAR0 = 0xff;
NE_MAR1 = 0xff;
NE_MAR2 = 0xff;
NE_MAR3 = 0xff;
NE_MAR4 = 0xff;
NE_MAR5 = 0xff;
NE_MAR6 = 0xff;
NE_MAR7 = 0xff;
NE_CURR = RX_START_PG; /* RX_CURR_PG; Current memory page = RX_CURR_PG ? */

NE_CR = ENCR_PAGE0 + ENCR_NODMA + ENCR_START;
}
board_eth_init函数是确保网卡RTL8019AS正常作业的条件,它首要完结网卡的硬件复位,然后进行网卡的软件复位(往0X18端口写入恣意值使其软复位),接着初始化网卡装备中的发送、接纳缓冲区的页地址、装备了网卡发送装备寄存器、接纳寄存器,最终设置网卡本身的物理地址和多播过滤地址。
low_level_output函数,上层运用层数据需求封装成协议栈要求的pbuf数据格局,然后再操作网卡发送数据。其源代码如下:
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;

u8_t isr;
u8_t chain=0;
u8_t * tr_ptr;
u16_t tr_len, temp_dw;
u16_t padLength,packetLength;

/* Set up to transfer the packet contents to the NIC RAM. */
padLength = 0;
packetLength = p->tot_len;

/* packetLength muse >=64 (see 802.3) */
if ((p->tot_len) < 64)
{
padLength = 64 – (p->tot_len);
packetLength = 64;
}

/* dont close nic,just close receive interrupt */
NE_CR = ENCR_PAGE2 | ENCR_NODMA | ENCR_START;
isr = NE_IMR;
isr &= ~ENISR_RX;
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_START;
NE_IMR = isr;

NE_ISR = ENISR_RDC;

/* Amount to send */
NE_RBCR0 = packetLength & 0xff;
NE_RBCR1 = packetLength >> 8;

/* Address on N%&&&&&% to store */
NE_RSAR0 = 0x00;
NE_RSAR1 = NE_START_PG;

/* Write command to start */
NE_CR = ENCR_PAGE0 | ENCR_RWRITE | ENCR_START;

/* write packet to ring buffers. */
for(q = p, chain = 0; q != NULL; q = q->next)
{
if(chain == 1)
{
if(((q->len-1) & 0x01) && (q->next != NULL))
{
tr_len = q->len – 2;
tr_ptr = ((u8_t*)q->payload) + 1;

temp_dw = *(((u8_t *)q->payload) + q->len – 1);
temp_dw += *(u8_t *)(q->next->payload) << 8;

chain = 1;
}
else
{
tr_len = q->len – 1;
tr_ptr = ((u8_t*)q->payload) + 1;
chain = 0;
}
}
else
{
if((q->len & 0x01) && (q->next != NULL))
{
tr_len = q->len – 1;
tr_ptr = (u8_t*)q->payload;

temp_dw = *(((u8_t *)q->payload) + q->len – 1);
temp_dw += *(u8_t *)(q->next->payload) << 8;

chain = 1;
}
else
{
tr_len = q->len;
tr_ptr = (u8_t*)q->payload;

chain = 0;
}
}

ne2k_copyout(tr_len, tr_ptr);

if (chain == 1) NE_DATAW = temp_dw;

}
if(padLength>0)
ne2k_outpad(padLength);

/* Wait for remote dma to complete – ISR Bit 6 clear if busy */
while((u8_t)(NE_ISR & ENISR_RDC) == 0 );

/* clear RDC */
NE_ISR = ENISR_RDC;

/* Issue the transmit command.(start local dma) */
NE_TPSR = NE_START_PG;
NE_TBCR0 = packetLength & 0xff;
NE_TBCR1 = packetLength >> 8;

/* Start transmission (and shut off remote dma) */
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_TRANS | ENCR_START;
/* reopen receive interrupt */
NE_CR = ENCR_PAGE2 | ENCR_NODMA | ENCR_START;
isr = NE_IMR;
isr |= ENISR_RX;
NE_CR = ENCR_PAGE0 | ENCR_NODMA | ENCR_START;
NE_IMR = isr;

#ifdef LINK_STATS
lwip_stats.link.xmit++;
#endif /* LINK_STATS */

return ERR_OK;
}
low_level_input函数从网卡读取数据,封装成pbuf方法后传递给上层运用层。其源代码如下:
static struct pbuf *
low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t packetLength, len;
u8_t PDHeader[18]; /* Temp storage for ethernet headers */
u8_t * payload;

NE_ISR = ENISR_RDC;
// NE_RBCR1 = 0x0f; /* See controller manual , use send packet command */
NE_CR = ENCR_PAGE0 | ENCR_RREAD | ENCR_RWRITE | ENCR_START;
// NE_CR = ENCR_PAGE0 | ENCR_RREAD | ENCR_START;
/* get the first 18 bytes from nic */
ne2k_copyin(18,PDHeader);

/* Store real length, set len to packet length – header */
packetLength = ((unsigned) PDHeader[2] | (PDHeader[3] << 8 ));

/* verify if the packet is an IP packet or ARP packet */
if((PDHeader[3]>0x06)||(PDHeader[16] != 8)||(PDHeader[17] != 0 && PDHeader[17] != 6))
{
ne2k_discard(packetLength-14);
return NULL;
}

/* We allocate a pbuf chain of pbufs from the pool. */
p = pbuf_alloc(PBUF_RAW, packetLength, PBUF_POOL);

if (p != NULL) {
/* We iterate over the pbuf chain until we have read the entire
packet into the pbuf. */

/* This assumes a minimum pbuf size of 14 … a good assumption */
memcpy(p->payload, PDHeader + 4, 14);

for(q = p; q != NULL; q = q->next) {
/* Read enough bytes to fill this pbuf in the chain. The
available data in the pbuf is given by the q->len
variable. */
payload = q->payload;
len = q->len;
if (q == p) {
payload += 14;
len -=14;
}

ne2k_copyin(len,payload);
}

#ifdef LINK_STATS
lwip_stats.link.recv++;
#endif /* LINK_STATS */
} else {
/* no more PBUF resource, Discard packet in buffer. */
ne2k_discard(packetLength-14);
#ifdef LINK_STATS
lwip_stats.link.memerr++;
lwip_stats.link.drop++;
#endif /* LINK_STATS */
}

return p;
}
Lwip要求的协议栈底层操作网卡的函数编写结束。

4.5 移植完结后测验TCP/IP协议栈

咱们运用查询方法读取网卡数据包,详细计划是建一个查询使命,周期性调用GetPacket()函数,函数源代码:
void GetPacket(void)
{
u8_t isr,curr,bnry;

NE_CR = ENCR_PAGE0 | ENCR_NODMA;
isr = NE_ISR;

/* got packet with no errors */
if (isr & ENISR_RX) {

NE_ISR = ENISR_RX;

NE_CR = ENCR_PAGE1 | ENCR_NODMA;
curr = NE_CURR;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
bnry = NE_BNRY;
/* get more than one packet until receive buffer is empty */
while(curr != bnry) {
ethernetif_input(&rtl8019_netif);
NE_CR = ENCR_PAGE1 | ENCR_NODMA;
curr = NE_CURR;
NE_CR = ENCR_PAGE0 | ENCR_NODMA;
bnry = NE_BNRY;
}
// rBNRY = NE_BNRY;
}
else {
NE_ISR = 0xFF;
};
}
在测验lwip协议栈前,咱们需求初始化。初始化代码:
struct netif rtl8019_netif;
struct netif loop_netif;
extern err_t ethernetif_init(struct netif *netif);

void lwip_init_task(void)
{
struct ip_addr ipaddr, netmask, gw;

tcpip_init(NULL,NULL);
IP4_ADDR(&gw, 192,168,0,1);
IP4_ADDR(&ipaddr, 192,168,0,174);
IP4_ADDR(&netmask, 255,255,255,0);

netif_add(&rtl8019_netif,&ipaddr,&netmask,&gw,NULL,ethernetif_init,tcpip_input);
netif_set_default(&rtl8019_netif);
netif_set_up(&rtl8019_netif);
}
体系ping测验成功如图4.5-1 ping测验:

图4.5-1 ping测验

4.6 规划并完结简略的WEB服务器

HTTP是一个依据TCP/IP,归于运用层的面向对象的协议,咱们其简捷、快速的方法,适用于分布式超媒体信息体系。
经过浏览器拜访一个WEB服务器时,其实便是运用HTTP 协议向服务器发送web页面恳求,WEB服务器接纳到该恳求后,回来应对信息和浏览器恳求的网页内容。
咱们以一个最简略的比如阐明一下HTTP协议:
浏览器发送的规范恳求是这样的:
1. GET /index.html HTTP/1.1
2. Accept: text/html
3. Accept-Language: zh-cn
4. User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
5. Connection: Keep-Alive
上面的恳求意义:
1. 阐明我需求index.html这个网页内容,运用的HTTP协议版本是1.1
2. 我能够接纳的文件类型是text/html
3. 我能够接纳的言语是中文
4. 浏览器的类型和版本号
5. 需求坚持长衔接。
服务器的回复信息是这样的:
1. HTTP/1.1 200 OK
2. Date: Sat, 4 Apr 2015 18:54:17 GMT
3. Server: microHttp/1.0 Zlgmcu Corporation
4. Accept-Ranges: bytes
5. Connection: Keep-Close
6. Content-Type: text/html; charset=gb2312
服务器回复的信息意义:
1. 服务器回来浏览器拜访的页面存在。
2. 该呼应头标明服务器支撑Range恳求,以及服务器所支撑的单位是字节(这也是仅有可用的单位)。
3. 封闭衔接
4. 服务器回来的文件类型为text/html,文件编码为gb2312。
依据上述HTTP协议原理,咱们能够规划一个简略的WEB服务器,有浏览器拜访时WEB服务器回来固定的页面。
在浏览器中输入开发板的IP地址:192.168.0.174
页面显现如图4.6-1 简略WEB服务器:

浏览器默许拜访端口是80,咱们开发板运用lwip供给的socket编程接口编程完结监听80端口,有浏览器拜访开发板的80端口,开发板向浏览器回来指定WEB页面。
完结代码如下:
void lwip_demo(void *pdata)
{
struct netconn *conn,*newconn;
lwip_init_task();

conn=netconn_new(NETCONN_TCP);
netconn_bind(conn,NULL,80);
netconn_listen(conn);

while(1)
{
newconn=netconn_accept(conn);
if(newconn!=NULL)
{
struct netbuf *inbuf;
char *dataptr;
u16_t size;
inbuf = netconn_recv(newconn);
if(inbuf!=NULL)
{
//测验事例
netbuf_data(inbuf,(void **)&dataptr,&size);
netconn_write(newconn,htmldata,sizeof(htmldata), NETCONN_NOCOPY);
netbuf_delete(inbuf);
}
netconn_close(newconn);
netconn_delete(newconn);
}
}
}

网页内容:
const unsigned char htmldata[]={
“HTTP/1.1 200 OK\r\n”
“Date: Sat, 4 Apr 2015 18:54: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; charset=gb2312\r\n”
“\r\n”
“\r\n”
“\r\n”
this is Lwip test\r\n”
“\r\n”

HELLO WELCOME TO LWIP WEB sever

\r\n”

硬件渠道:ARM

\r\n”

软件渠道:UCOS Lwip

\r\n”

Design by ***

\r\n”
“\r\n”
“\r\n”
};

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/guandian/264144.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部