ARM硬件平臺上基於UCOS移植Lwip網絡協議棧 分類: 嵌入式開發學習 2015-06-14 10:33 55人閱讀 評論(1) 收藏

目錄

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進行電平轉換、濾波。html

      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基地址選擇。web

     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引腳。app

 

      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,很明顯地址中第八、9爲地址爲1,第六、7位和10-19位所有爲0。咱們僅須要控制第0-4位地址,就能夠產生00H-1FH這32個偏移量。電路原理圖中SA八、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又分紅了幾個獨立的訪問空間用於掛接不一樣的總線器件。咱們利用地址線A2一、A2二、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,同理0x834000十、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(i<LWIP_MAX_TASKS&&i<32)
          i++;
    else return 0;
  }
  if(OSTaskCreate(thread,(void*)arg, &LWIP_STK_AREA[i][LWIP_STK_SIZE-1],prio)==OS_NO_ERR){
       TaskCreateFlag |=(0x01<<i); 
  };


  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 */
  /* don't 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;
  }
   
  /* don't 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 NIC 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服務器:


圖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"     "<HTML>\r\n"  "<HEAD>\r\n"  "<TITLE>this is Lwip test</TITLE>\r\n"  "<BODY>\r\n"  "<H1>HELLO WELCOME TO LWIP WEB sever</H1>\r\n" "<P>硬件平臺:ARM</P>\r\n" "<P>軟件平臺:UCOS Lwip</P>\r\n" "<P>Design by ***</P>\r\n" "</BODY>\r\n"     "</HTML>\r\n" };
相關文章
相關標籤/搜索