1. 程式人生 > 實用技巧 >ARM硬體平臺上基於UCOS移植Lwip網路協議棧

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

LDRPC, ResetAddr

LDRPC, UndefinedAddr

LDR PC, SWI_Addr

LDRPC, PrefetchAddr

LDRPC, DataAbortAddr

DCD0xb9205f80

LDRPC, [PC, #-0xff0]

LDRPC, FIQ_Addr

ResetAddr DCD ResetInit

UndefinedAddr DCDUndefined

SWI_Addr DCD SoftwareInterrupt

PrefetchAddr DCDPrefetchAbort

DataAbortAddr DCD DataAbort

Nouse DCD 0

IRQ_Addr DCD0

FIQ_Addr DCDFIQ_Handler

初始化匯流排頻率以滿足各個BANK外接的裝置正常使用,一個複雜的系統可能存在多種儲存器型別的介面,需要根據實際的系統設計對此加以正確配置。對同一種儲存器型別來說,也因為訪問速度的差異,需要不同的時序設定。工程中我們使用的儲存器包括NorFlash和SRAM,設定的訪問匯流排寬度都為16bit。

堆疊空間是C語言正常執行所需要的基本環境,函式呼叫引數、返回值、函式呼叫關係都需要使用堆疊。因此,需要設定ARM各個執行模式的堆疊空間。

InitStack

MOVR0, LR

;Build the SVC stack

MSRCPSR_c, #0xd2

LDRSP, StackIrq

;Build the FIQ stack

MSRCPSR_c, #0xd1

LDRSP, StackFiq

;Build the DATAABORT stack

MSRCPSR_c, #0xd7

LDRSP, StackAbt

;Build the UDF stack

MSRCPSR_c, #0xdb

LDRSP, StackUnd

;Build the SYS stack

MSRCPSR_c, #0xdf

LDRSP, =StackUsr

BXR0

呼叫__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"
};


版權宣告:本文為博主原創文章,未經博主允許不得轉載。