ARM硬件平臺上基於UCOS移植Lwip網絡協議棧

目錄

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(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"
};


版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章