<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 嵌入式系統 > 設計應用 > ARM硬件平臺上基于UCOS移植Lwip網(wǎng)絡(luò )協(xié)議棧

ARM硬件平臺上基于UCOS移植Lwip網(wǎng)絡(luò )協(xié)議棧

作者: 時(shí)間:2016-11-20 來(lái)源:網(wǎng)絡(luò ) 收藏
目錄

1硬件平臺 1

1.1硬件平臺簡(jiǎn)介 1
1.2 硬件設計電路原理圖 2

2. Keil 開(kāi)發(fā)工具及Keil工程簡(jiǎn)介 6

2.1 Keil開(kāi)發(fā)工具 6
2.2 Keil工程簡(jiǎn)介 6
2.3 鏈接文件、啟動(dòng)文件分析 6

3. UCOS移植 11

3.1 ucos簡(jiǎn)介 11
3.2 ucos移植總述 11
3.3 和移植UCOS有關(guān)的ARM芯片知識 11
3.4 系統堆棧和UCOS的任務(wù)堆棧 14
3.5 系統時(shí)鐘 14
3.6 任務(wù)級任務(wù)切換 14
3.7 中斷級任務(wù)切換 16

4.Lwip移植 18

4.1 lwip簡(jiǎn)介 18
4.2 lwip移植總述 18
4.3移植lwip操作系統模擬層 19
4.4 根據lwip提供的軟件架構編寫(xiě)相應的網(wǎng)卡芯片驅動(dòng) 27
4.5 移植完成后測試TCP/IP協(xié)議棧 35
4.6 設計并實(shí)現簡(jiǎn)單的WEB服務(wù)器 37

1.硬件平臺

1.1硬件平臺簡(jiǎn)介

為保證網(wǎng)絡(luò )協(xié)議棧的順利移植,選用了LPC2220作為主控芯片,RTL8019AS作為網(wǎng)卡芯片,使用HR901170A進(jìn)行電平轉換、濾波。

本文引用地址:http://dyxdggzs.com/article/201611/318803.htm

LPC2220是Philips公司推出的微處理器,片上有64K的RAM空間,通過(guò)總線(xiàn)很容易再擴展ROM和RAM。芯片還擁有豐富的IO接口以及多種中斷源,還集成了多種定時(shí)器、PWM等,另外,該芯片內部集成了很多串行通訊協(xié)議,如SPIUART等。

RTL8019AS是由臺灣Realtek公司生產(chǎn)的以太網(wǎng)控制器。他符合EthernetII與IEEE802.3標準,100腳的PQFP封裝,采用全雙工收發(fā)并可同時(shí)達到10Mb/s的速率,內置16kB的SRAM,支持8/16位數據總線(xiàn),8個(gè)中斷申請線(xiàn)以及16個(gè)I/O基地址選擇。

HR901170A是漢仁電子有限公司生產(chǎn)的RJ45接口連接器(帶網(wǎng)絡(luò )變壓器/濾波器),該連接器滿(mǎn)足IEEES02.3和IEEE902.3ab標準,能夠較好地抑制電磁干擾。通過(guò)HR901170A系統就可以連接到以太網(wǎng)上。

基于LPC2220和RTL8019AS的上述特點(diǎn),我們使用此款芯片可以設計出滿(mǎn)足移植Lwip網(wǎng)絡(luò )協(xié)議棧所需要的硬件運行環(huán)境。

1.2 硬件設計及電路原理圖

圖1.2-1硬件電路連接圖1

圖1.2-2硬件電路連接圖2

RTL8019AS芯片工作方式分為3種:①跳線(xiàn)方式,網(wǎng)卡的i/o和中斷由跳線(xiàn)決定。②即插即用方式,由軟件進(jìn)行自動(dòng)配置plug and play。③免跳線(xiàn)方式,網(wǎng)卡的i/o和中斷由外接的93c46里的內容決定。在嵌入式應用場(chǎng)合,為了節約成本,一般不使用93c46的,可以降低成本,同時(shí)又減少連線(xiàn)。我們選擇使用跳線(xiàn)模式,使用此模式的硬件設置方式為第65引腳(JP)接高電平,如圖1.2-2硬件電路連接圖2所示。

硬件復位引腳33(RSTDRV),此引腳為網(wǎng)卡芯片硬件復位引腳,RSTDRV為高電平有效,至少需要800ns的寬度。由硬件電路圖可知,此引腳連接到LPC2220的P0.8上。

中斷引腳(INT7-0)為97-100,1-4 共有8個(gè)中斷引腳,但使用時(shí)只是用一個(gè)中斷引腳,選擇哪個(gè)引腳作為中斷信號是根據[80-78][IRQS2-0]來(lái)決定的,根據電路圖可IRQS2-0這三個(gè)引腳懸空,RTL8019AS內部有下拉電阻,故IRQS2-0這三個(gè)引腳電平都為0,根據手冊可知,選擇的是INT0作為中斷源引腳,此引腳連接到LPC2220的P0.9引腳。

64腳(AUI),該引腳決定使用aui還是bnc接口。我們用的網(wǎng)卡的接口一般是bnc的,很少用aui。bnc接口方式支持8線(xiàn)雙絞或同軸電纜。高電平時(shí)使用aui接口,懸空為低電平,使用bnc接口。我們將該引腳懸空即可。

網(wǎng)絡(luò )接口類(lèi)型由74,77(PL0,PL1)引腳決定,我們使用第一種自動(dòng)檢測就可以了。會(huì )自動(dòng)檢測接口類(lèi)型然后進(jìn)行工作。自動(dòng)檢測是用同軸還是雙絞線(xiàn)。這兩個(gè)引腳內部存在下拉電阻,懸空即可。

芯片的brom地址由以下引腳72,71,69,68,67(BS4..BS0)決定,在嵌入式領(lǐng)域一般都不用該brom。brom是bootrom的縮寫(xiě)。在電腦里用來(lái)做無(wú)盤(pán)工作站時(shí)候用到,可以從網(wǎng)卡進(jìn)行引導,而不是從a盤(pán),c盤(pán)等引導系統。故懸空即可。

RTL8019AS支持3支可編程LED燈,電路連接見(jiàn)原理圖。

RTL8019AS與主控芯片間通訊的輸入/輸出地址共有32個(gè),地址偏移量為00H-1FH。

RTL8019AS的IO基地址在跳線(xiàn)模式下由[85-84,82-81] [IOS3-0]這四個(gè)引腳狀態(tài)決定,電路圖中這四個(gè)引腳懸空,故這四個(gè)引腳狀態(tài)都為0,根據數據手冊可知RTL8019AS的IO基地址為300H,將300H化成二進(jìn)制數值00110000 0000,很明顯地址中第8、9為地址為1,第6、7位和10-19位全部為0。我們僅需要控制第0-4位地址,就可以產(chǎn)生00H-1FH這32個(gè)偏移量。電路原理圖中SA8、SA9接+5v,SA10-SA19接的是地。

電路圖中SA0-SA4分別接的是LPC2220的A1-A5引腳,而SA5接的是NET_nCS引腳。

圖1.2-2硬件電路連接圖3

NET_nCS的信號是根據nCS3(BANK3的片選信號)和A22地址線(xiàn)信號產(chǎn)生的。

數據總線(xiàn)SD0-SD15連接到LPC2220的D0-D15,組成16bit總線(xiàn)。

產(chǎn)生00H-1FH的偏移量需要NET_nCS信號為低。我們總結一下,我們的RTL8019AS需要的地址是300H-301FH,硬件連線(xiàn)決定了這個(gè)地址偏移量。我們將RTL8019AS接到LPC2220的BANK3上。對LPC2220來(lái)說(shuō),只產(chǎn)生00H-1FH的偏移量就可以。LPC2220的BANK3起始地址是0X83000000,也就是說(shuō)當訪(fǎng)問(wèn)這個(gè)地址時(shí)才會(huì )產(chǎn)生nCS3為低的信號,如果BANK3只需要連接網(wǎng)卡的話(huà),我們就可以直接利用nCS3信號作為選通網(wǎng)卡芯片的信號即可,但我們硬件設計時(shí)將BANK3又分成了幾個(gè)獨立的訪(fǎng)問(wèn)空間用于掛接不同的總線(xiàn)器件。我們利用地址線(xiàn)A21、A22、A23將BANK3分為0X834000000、0x83100000、0x83800000這幾個(gè)獨立空間。我們只分析利用A22地址線(xiàn)信號和nCS3

產(chǎn)生的NET_nCS信號,此信號線(xiàn)硬件上連接到RTL8019AS的SA5上,A22地址線(xiàn)上信號為高電平并且nCS3產(chǎn)生低電平信號,這種情況下NET_nCS才是低電平,而只有NET_nCS為低電平時(shí),才能產(chǎn)生RTL8019AS需要的300H-301FH地址偏移量?,F在通過(guò)LPC2220訪(fǎng)問(wèn)地址空間0x83400000,這個(gè)時(shí)候根據上面分析NET_nCS為低電平,也即RTL8019AS的SA5為低電平,第四位地址線(xiàn)SA0-SA4連接的是LPC2220的A1-A5,

訪(fǎng)問(wèn)0x83400000、0x83400001對應的RTL8019AS地址即為300H,同理0x83400010、0x83400011對應的RTL8019AS地址即為301H。我們訪(fǎng)問(wèn)LPC2220的0x83400000-0x8340003F即訪(fǎng)問(wèn)了RTL8019AS的32個(gè)偏移量。

2. Keil 開(kāi)發(fā)工具及Keil工程簡(jiǎn)介

2.1Keil開(kāi)發(fā)工具

Keil MDK提供了針對ARM系列芯片的匯編器、C/C++的編譯器和一個(gè)能進(jìn)行工程管理的IDE。同時(shí),該軟件還支持JLink在線(xiàn)調試,是進(jìn)行嵌入式軟件開(kāi)發(fā)非常優(yōu)秀的軟件。

2.2 Keil工程簡(jiǎn)介

Keil MDK可以建立針對具體芯片的工程,根據選定的ARM芯片自動(dòng)生成啟動(dòng)代碼,負責硬件的基本初始化和C語(yǔ)言運行環(huán)境以及C語(yǔ)言庫的初始化。提供工程文件管理,整體編譯、鏈接、調試。Keil MDK工程還可以編制鏈接文件,鏈接器會(huì )根據編制的鏈接文件進(jìn)行鏈接二進(jìn)制文件,用來(lái)滿(mǎn)足嵌入式開(kāi)發(fā)的不同硬件平臺需求。

2.3 鏈接文件、啟動(dòng)文件分析

ARM芯片運行模式和堆棧相關(guān)知識都對理解UCOS的任務(wù)切換都有很大的幫助,因此我們首先應該理解芯片運行模式和堆棧的概念。理解這些概念最好的方式是分析一下系統啟動(dòng)代碼。
在分析啟動(dòng)代碼之前,先理解一下Keil MDK 工程中Scf鏈接文件的相關(guān)知識。我們知道源代碼程序經(jīng)過(guò)編譯、鏈接后生成一個(gè)二進(jìn)制文件,這個(gè)二進(jìn)制文件是用來(lái)控制ARM芯片的。
這個(gè)二進(jìn)制文件是直接下載到ARM處理器芯片的,這個(gè)二進(jìn)制文件的格式如圖2.4-1 ARM Image映像文件結構。

圖2.4-1 ARM Image映像文件結構

ZI段表示初始化為0的變量區域,RW段表示已經(jīng)初始化的變量區域,RO段表示代碼區域。

因ZI段只是初始化為0的變量區域,所以在Image文件中并不占空間,映像文件中只是包含實(shí)際地址和大小。我們一般將image映像文件下載到ROM中,系統啟動(dòng)時(shí)從ROM中讀取第一條需要執行的指令,但RW段下載到了ROM中,ROM是不可寫(xiě)的。因此出現了裝載地址和運行地址不一致的情況。我們要保證程序正常運行就必須保證變量在訪(fǎng)問(wèn)之前放到了正確的地址。一個(gè)簡(jiǎn)單的裝載地址到運行地址的轉換見(jiàn)圖2.4-2 簡(jiǎn)單的分散裝載內存映像圖。

圖2.4-2 簡(jiǎn)單的分散裝載內存映像圖

在KeilMDK工程中使用分散裝載文件scf文件來(lái)設置映像文件的轉載地址和運行地址,當我們設置的轉載地址和運行地址不一致時(shí),KeilMDK會(huì )自動(dòng)產(chǎn)生搬運代碼,在使用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)

}

}

此分散加載文件只有一個(gè)裝載域ROM_LOAD,裝載地址是0x80000000,這個(gè)地址是ARM芯片外的一個(gè)NorFlash芯片的起始地址。存在ROM_EXEC、IRAM、STACKS_BOTTOM、STACKS、ERAM、HEAP、HEAP_BOTTOM共8個(gè)運行域,每個(gè)運行域都有自己的運行地址。其中ROM_EXEC運行域和裝載域地址一樣,此運行域包含系統的啟動(dòng)代碼和所有RO段代碼。剩余其他運行域的地址和裝載域都不同,都需要根據分散加載文件進(jìn)行代碼搬運工作,這個(gè)工作是由KeilMDK工具自動(dòng)完成。

系統啟動(dòng)代碼主要完成的工作如下:

1. 中斷向量表

2. 初始化總線(xiàn)頻率和存儲器系統

3. 初始化堆棧

4. 呼叫主應用程序

中斷向量表是當外部中或系統異常發(fā)生時(shí)中斷服務(wù)程序的入口地址或存放中斷服務(wù)程序的首地址。此工程中將中斷向量表定位在0x80000000這個(gè)地址開(kāi)始的地方。

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

初始化總線(xiàn)頻率以滿(mǎn)足各個(gè)BANK外接的設備正常使用,一個(gè)復雜的系統可能存在多種存儲器類(lèi)型的接口,需要根據實(shí)際的系統設計對此加以正確配置。對同一種存儲器類(lèi)型來(lái)說(shuō),也因為訪(fǎng)問(wèn)速度的差異,需要不同的時(shí)序設置。工程中我們使用的存儲器包括NorFlash和SRAM,設置的訪(fǎng)問(wèn)總線(xiàn)寬度都為16bit。

堆??臻g是C語(yǔ)言正常運行所需要的基本環(huán)境,函數調用參數、返回值、函數調用關(guān)系都需要使用堆棧。因此,需要設置ARM各個(gè)運行模式的堆??臻g。

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()設置用戶(hù)模式下的??臻g和堆空間??臻g可以通過(guò)程序定義,也可以通過(guò)分散加載文件制定絕對地址空間。
  2. 調用__rt_lib_init()初始化庫函數,在必要時(shí)為用戶(hù)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.

調用用戶(hù)main函數,在main函數里,你可以調用其他用戶(hù)函數,也可以調用庫函數。

  1. Calls exit() with the value returned by main().
  2. 當main函數返回時(shí),調用exit函數清理資源。

3. UCOS移植

3.1 ucos簡(jiǎn)介

UCOS是一個(gè)可裁剪、支持搶占式調度的實(shí)時(shí)嵌入式操作系統。提供基本的任務(wù)管理功能,支持信號量、郵箱、隊列等任務(wù)間同步、通訊機制。

3.2 ucos移植總述

Ucos移植主要是實(shí)現保存、恢復ARM芯片執行程序所需要的寄存器環(huán)境和實(shí)現系統時(shí)鐘接口需要的硬件定時(shí)器的設置及啟動(dòng)。需要移植實(shí)現的主要有任務(wù)級任務(wù)切換、中斷級任務(wù)切換、任務(wù)堆棧初始化、系統時(shí)鐘。

3.3 和移植UCOS有關(guān)的ARM芯片知識

C語(yǔ)言經(jīng)過(guò)編譯器編譯、鏈接后生成的二進(jìn)制指令是能在A(yíng)RM芯片上直接執行的指令代碼。這些指令執行是依賴(lài)于各種寄存器的,保護程序運行環(huán)境其實(shí)就是保護這些寄存器。

ARM芯片有7種運行模式:

1. 用戶(hù)模式(user模式),運行應用的普通模式。

2. 快速中斷模式(fiq模式),用于支持數據傳輸或通道處理。

3. 中斷模式(irq模式),用于普通中斷處理。

4. 超級用戶(hù)模式(svc模式),操作系統的保護模式。

5. 異常中斷模式(abt模式),輸入數據后登入或預取異常中斷指令。

6. 系統模式(sys模式),是操作系統使用的一個(gè)有特權的用戶(hù)模式。

7. 未定義模式(und模式),執行了未定義指令時(shí)進(jìn)入該模式。

外部中斷,異常操作或軟件控制都可以改變中斷模式。大多數應用程序都時(shí)是在用戶(hù)模式下運行。進(jìn)入特權模式是為了處理中斷或異常請求或操作保護資源服務(wù)的。

些工作模式是芯片硬件提供的程序運行的不同環(huán)境,不同的模式有不同的硬件訪(fǎng)問(wèn)權限,使用不同的寄存器。這就給不同的程序提供了不同的權限機制,你比如說(shuō)你的操作系統代碼運行在權限比較高的模式下,而你的應用程序運行在權限比較低的模式下。這樣就起到了對操作系統代碼的保護作用。

寄存器,各個(gè)模式下可見(jiàn)的寄存器以及各個(gè)寄存器的功能:

ARM共有37個(gè)32位的寄存器,其中31個(gè)是通用寄存器,6個(gè)是狀態(tài)寄存器。但在同一時(shí)間,對程序員來(lái)說(shuō)并不是所有的寄存器都可見(jiàn)。在某一時(shí)刻存儲器是否可見(jiàn)(可被訪(fǎng)問(wèn)),是處理器當前的工作狀態(tài)和工作模式?jīng)Q定的。其各個(gè)模式下的寄存器如圖3.3-1 ARM各種運行模式:

圖3.3-1 ARM各種運行模式

其中系統模式和用戶(hù)模式所用的寄存器是一樣的。畫(huà)三角陰影的寄存器表示在不同模式下有不同的物理寄存器。

以下對其進(jìn)行分類(lèi)說(shuō)明。

通用寄存器:

ARM的通用寄存器包括R0~R15,其中R0~R7是屬于未分組寄存器,各個(gè)模式下都使用同樣的寄存器。R8~R14在FIQ模式下是有獨立的物理寄存器,其目的是加快中斷響應速度,從硬件上保存程序執行現場(chǎng)。R13和R14這兩個(gè)寄存器在每種模式下都有自己的獨立寄存器。R15只有一個(gè),所有模式公用。

下對這些寄存器中的比較有特殊功能的做一下介紹:

寄存器R13:在A(yíng)RM指令中,常用R13做堆棧指針用。每種運行模式都有自己獨立的堆棧,用于保存中斷發(fā)生時(shí)的程序運行環(huán)境和C語(yǔ)言執行時(shí)進(jìn)行過(guò)程控制。

寄存器R14:專(zhuān)職持有返回點(diǎn)的地址,在系統執行一條“跳轉并鏈接(link)”(BL)指令

的時(shí)候,R14將收到一個(gè)R15的拷貝。其他的時(shí)候,它可以用作一個(gè)通用寄存器。相應的它在其他模式下的私有寄存器R14_svc,R14_irq,R14_fiq,R14_abt和R14_und都同樣用來(lái)保存在中斷或異常發(fā)生時(shí),或時(shí)在中斷和異常中執行了BL指令時(shí),R15的返回值。

寄存器R15是程序計數器(PC)。在A(yíng)RM狀態(tài)下,R15的bits[1:0]為0,bits[31:2]保存了PC的值。在Thumb狀態(tài)下,bits[0]為0同時(shí)bits[31:1]保存了PC值。

FIQ模式擁有7個(gè)私有寄存器R8-14(R8_fiq-R14_fiq)。在A(yíng)RM狀態(tài)下,多數FIQ處理都不需要保存任何寄存器。用戶(hù)、中斷、異常中止,超級用戶(hù)和未定義模式都擁有2個(gè)私有寄存器,R13和R14。允許這些模式都可擁有1個(gè)私有堆棧指針和鏈接(link)寄存器。

程序狀態(tài)寄存器。

ARM920T具有一個(gè)當前程序狀態(tài)寄存器(CPSR),另外還有5個(gè)保存程序狀態(tài)寄存器(SPSRs)用于異常中斷處理。這些寄存器的功能有:

1. 保留最近完成的ALU操作的信息。

2. 控制中斷的使能和禁止。

3. 設置處理器的操作模式。

狀態(tài)寄存器各位定義見(jiàn)圖3.3-2 ARM狀態(tài)寄存器:

圖3.3-2 ARM狀態(tài)寄存器

3.4 系統堆棧和UCOS的任務(wù)堆棧

當產(chǎn)生外部中斷或者系統異常時(shí),ARM會(huì )進(jìn)入相應的模式,各種運行模式均有其獨立的堆??臻g。UCOS中的任務(wù)是調度的最小單元,每個(gè)任務(wù)都有自己獨立的堆??臻g,當任務(wù)運行時(shí),它用來(lái)保存一些局部變量,當任務(wù)掛起時(shí),它負責保存任務(wù)的運行現場(chǎng),也就是CPU寄存器的值。

3.5 系統時(shí)鐘

系統時(shí)鐘是UCOS管理任務(wù)延時(shí)的基本依據,要求有一個(gè)周期性的定時(shí)器產(chǎn)生固定間隔時(shí)間。我們使用LPC2220的定時(shí)器0產(chǎn)生固定時(shí)間間隔事件,時(shí)間間隔設置為10ms,定時(shí)時(shí)間到產(chǎn)生中斷。UCOS系統時(shí)鐘處理函數是OSTimeTick(),時(shí)間中斷服務(wù)程序里調用此函數即可。

3.6 任務(wù)級任務(wù)切換

UCOS的用戶(hù)調用一些系統服務(wù)時(shí)(比如,OSTimeDly、OSSemPend),就會(huì )產(chǎn)生任務(wù)級任務(wù)切換。其切換的實(shí)質(zhì)是保存正在執行任務(wù)的執行現場(chǎng),然后恢復應該運行的任務(wù)的運行現場(chǎng)。
本工程中使用軟中斷的方式實(shí)現任務(wù)級任務(wù)切換的目的。
任務(wù)級切換函數的底層接口是使用的軟中斷技術(shù),用__swi來(lái)聲明一個(gè)不存在的函數,則調用這個(gè)函數就在調用這個(gè)函數的地方插入一條SWI指令,并且可以指定功能號。定義如下:__swi(0x00) void OS_TASK_SW(void); /* 任務(wù)級任務(wù)切換函數 */
調用OS_TASK_SW()這個(gè)函數時(shí)就會(huì )產(chǎn)生一個(gè)軟中斷,產(chǎn)生軟中斷后執行軟中斷服務(wù)程序。服務(wù)程序主要代碼分析如下:
SoftwareInterrupt
LDR SP, StackSvc ; 重新設置堆棧指針
STMFD SP!, {R0-R3, R12, LR}
MOV R1, SP ; R1指向參數存儲位置

MRS R3, SPSR
TST R3, #T_bit ; 中斷前是否是Thumb狀態(tài)
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb狀態(tài)SWI號
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm狀態(tài)SWI號
BICEQ R0, R0, #0xFF000000
; r0 = SWI號,R1指向參數存儲位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01為第一次任務(wù)切換

BL SWI_Exception

LDMFD SP!, {R0-R3, R12, PC}^
代碼難點(diǎn)分析:
軟中斷指令使處理器進(jìn)入管理模式,而用戶(hù)程序處于系統/用戶(hù)模式,其它異常也有自己的處理器模式,都有各自的堆棧指針,不會(huì )因為給堆棧指針賦值而破壞其它處理器模式的堆棧而影響其它程序的執行。返回的地址已經(jīng)存儲在連接寄存器LR中而不是存儲在堆棧中。由于進(jìn)人管理模式自動(dòng)關(guān)中斷,所以這段程序不會(huì )被其它程序同時(shí)調用。
因為ARM處理器核具有兩個(gè)指令集,在執行Thumb指令的狀態(tài)時(shí)不是所有寄存器都可見(jiàn)(參考ARM的相關(guān)資料),而且任務(wù)又可能不在特權模式(不能改變CPSR)。為了兼容任意一種模式,本移植使用軟中斷指令SWI使處理器進(jìn)入管理模式和ARM指令狀態(tài),并使用功能0實(shí)現OS_TASK_SW()的功能。
因任務(wù)級任務(wù)切換使用的是軟中斷技術(shù),我們把osctxsw()與osintctxsw()合二為一了,統一采用osintctxsw()來(lái)實(shí)現。之所以這樣搞的原因是任務(wù)進(jìn)行切換的時(shí)候,都必須進(jìn)入軟中斷的狀態(tài),而對于軟中斷的異常響應代碼已經(jīng)將任務(wù)的環(huán)境變量進(jìn)行了保存,從而也不需要像osctxsw()里面規定的那樣對將環(huán)境變量進(jìn)行保存。osintctxsw()函數的移植分析見(jiàn)3.7中斷級任務(wù)切換。

3.7 中斷級任務(wù)切換

當系統任務(wù)延時(shí)時(shí)間到或者在中斷服務(wù)程序里拋出信號量、郵箱等可以產(chǎn)生系統調度的操作時(shí),會(huì )執行任務(wù)切換,但這種切換是在中斷模式下進(jìn)行的。但底層切換函數是一致的,只不過(guò)任務(wù)級任務(wù)切換時(shí)是在SVC模式下進(jìn)行,中斷級任務(wù)切換是在中斷模式下進(jìn)行。
下面我們分析中斷級任務(wù)切換的主要流程和代碼:
SUB LR, LR, #4 ; 計算返回地址
STMFD SP!, {R0-R3, R12, LR} ; 保存任務(wù)環(huán)境
MRS R3, SPSR ; 保存狀態(tài)
STMFD SP, {R3,SP, LR}^; 保存用戶(hù)狀態(tài)的R3,SP,LR,注意不能回寫(xiě)
; 如果回寫(xiě)的是用戶(hù)的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語(yǔ)言的中斷處理程序

MSR CPSR_c, #(NoInt :OR: SYS32Mode) ; 切換到系統模式
LDR R2, =OsEnterSum; OsEnterSum,使OSIntExit退出時(shí)中斷關(guān)閉
MOV R1, #1
STR R1, [R2]

BL OSIntExit

LDR R2, =OsEnterSum; 因為中斷服務(wù)程序要退出,
;所以OsEnterSum=0
MOV R1, #0
STR R1, [R2]

MSR CPSR_c, #(NoInt :OR: IRQ32Mode) ; 切換回irq模式
LDMFD SP, {R3,SP, LR }^ ; 恢復用戶(hù)狀態(tài)的R3,SP,LR,
;注意不能回寫(xiě)
; 如果回寫(xiě)的是用戶(hù)的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}^ ; 不進(jìn)行任務(wù)切換
LDR PC, =OSIntCtxSw ; 進(jìn)行任務(wù)切換

代碼主要功能分析:
實(shí)現在中斷模式下保存系統模式下正在運行任務(wù)的各個(gè)寄存器到中斷模式堆棧,然后執行相應的中斷服務(wù)程序,中斷退出時(shí)做任務(wù)切換。
下面我們分析實(shí)現任務(wù)切換的函數OSIntCtxSw。
OSIntCtxSw
;下面為保存任務(wù)環(huán)境
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

;保存當前任務(wù)堆棧指針到當前任務(wù)的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]
上述函數實(shí)現了保存上一個(gè)被中斷任務(wù)運行時(shí)各個(gè)寄存器到任務(wù)的堆??臻g里,然后將系統中優(yōu)先級最高且就緒的任務(wù)堆棧里保存的各個(gè)寄存器內容恢復到系統模式的各個(gè)寄存器中,使任務(wù)正常運行。

4.Lwip移植

4.1 lwip簡(jiǎn)介

lwip是瑞典計算機科學(xué)院(SICS)的Adam Dunkels 開(kāi)發(fā)的一個(gè)小型開(kāi)源的TCP/IP協(xié)議棧。LwIP是Light Weight (輕型)IP協(xié)議,有無(wú)操作系統的支持都可以運行。LwIP實(shí)現的重點(diǎn)是在保持TCP協(xié)議主要功能的基礎上減少對RAM 的占用,它只需十幾KB的RAM和40K左右的ROM就可以運行,這使LwIP協(xié)議棧適合在低端的嵌入式系統中使用。

4.2 lwip移植總述

Lwip有無(wú)操作系統的支持都可以運行,我們移植是基于UCOS的。
基于UCOS移植Lwip主要包含兩個(gè)方面的工作:
1. 根據Lwip提供的操作系統模擬層接口編寫(xiě)基于UCOS的實(shí)現代碼,以實(shí)現Lwip和UCOS的完美融合。
2. 根據Lwip提供的底層網(wǎng)卡驅動(dòng)接口,結合RTL8019AS網(wǎng)卡datasheet編制網(wǎng)卡驅動(dòng)程序。

4.3移植lwip操作系統模擬層

操作系統模擬層(sys_arch)存在的目的主要是為了方便 LwIP 的移植,它在底層操作系統和LwIP 之間提供了一個(gè)接口。這樣,我們在移植 LwIP 到一個(gè)新的目標系統時(shí),只需修改這個(gè)接口即可。不過(guò),不依賴(lài)底層操作系統的支持也可以實(shí)現這個(gè)接口。
sys_arch需要為L(cháng)wIP提供創(chuàng )建新線(xiàn)程功能,提供信號量 (semaphores) 和郵箱 (mailboxes) 兩種進(jìn)程間通訊方式 (IPC) 。
1. 模擬層需要添加的頭文件 cc.h 說(shuō)明
Lwip使用的數據類(lèi)型定義:
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使用的結構體對齊方式聲明相關(guān)的宏定義:
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_END
為方便操作協(xié)議幀數據,lwip協(xié)議棧中結構體使用單字節對齊方式。
處理器模式:
#define BYTE_ORDER LITTLE_ENDIAN
我們使用的LPC2220為小端模式處理器,故定義為小端模式。
其他內容主要和調試輸出功能有關(guān),這里不進(jìn)行一一說(shuō)明。
2. 需要實(shí)現的操作系統模擬層函數
- void sys_init(void)

初始化lwip操作系統模擬層。

- sys_sem_t sys_sem_new(u8_t count)

創(chuàng )建一個(gè)信號量,count表示初始化后的信號量狀態(tài)。

- void sys_sem_free(sys_sem_t sem)

刪除指定的信號量。

- void sys_sem_signal(sys_sem_t sem)

發(fā)送一個(gè)信號量。

- u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
等待指定的信號并阻塞線(xiàn)程。timeout 參數為 0,線(xiàn)程會(huì )一直被阻塞至收到指定的信號;非 0,則線(xiàn)程僅被阻塞至指定的 timeout時(shí)間(單位為毫秒)。在timeout 參數值非 0 的情況下,返回值為等待指定的信號所消耗的毫秒數。如果在指定的時(shí)間內并沒(méi)有收到信號,返回值為SYS_ARCH_TIMEOUT。如果線(xiàn)程不必再等待這個(gè)信號(也就是說(shuō),已經(jīng)收到信號) ,返回值也可以為 0。注意,LwIP實(shí)現了一個(gè)名稱(chēng)與之相似的函數來(lái)調用這個(gè)函數,sys_sem_wait(),注意區別。
- sys_mbox_t sys_mbox_new(void)
創(chuàng )建一個(gè)空消息郵箱。

- void sys_mbox_free(sys_mbox_t mbox)
釋放一個(gè)郵箱。

- 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)
阻塞線(xiàn)程直至郵箱收到至少一條消息。最長(cháng)阻塞時(shí)間由 timeout 參數指定(與
sys_arch_sem_wait()函數類(lèi)似) 。msg 是一個(gè)結果參數,用來(lái)保存郵箱中的消息指針 (即*msg = ptr) ,它的值由這個(gè)函數設置。 “msg”參數有可能為空,這表明當前這條消息應該被丟棄。 返回值與 sys_arch_sem_wait()函數相同:等待的毫秒數或者 SYS_ARCH_TIMEOUT――如果時(shí)間溢出的話(huà)。LwIP實(shí)現的函數中,有一個(gè)名稱(chēng)與之相似的――sys_mbox_fetch(),注意區分。

- struct sys_timeouts *sys_arch_timeouts(void)
返回一個(gè)指向當前線(xiàn)程使用的 sys_timeouts 結構的指針。LwIP 中,每一個(gè)線(xiàn)程都有一個(gè)timeouts 鏈表,這個(gè)鏈表由 sys_timeout 結構組成,sys_timeouts 結構則保存了指向這個(gè)鏈表的指針。這個(gè)函數由 LwIP 的超時(shí)調度程序調用,并且不能返回一個(gè)空(NULL)值。 單線(xiàn)程 sys_arch 實(shí)現中,這個(gè)函數只需簡(jiǎn)單返回一個(gè)指針即可。這個(gè)指針指向保存在 sys_arch 模塊中的 sys_timeouts 全局變量。

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

創(chuàng )建一個(gè)新的線(xiàn)程。

實(shí)現sys_sem_t sys_sem_new(u8_t count)函數:
sys_sem_t sys_sem_new(u8_t count)
{
return OSSemCreate((u16_t)count);
}
這個(gè)函數實(shí)現比較簡(jiǎn)單,UCOS提供了信號量的操作函數,直接調用即可。
實(shí)現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);
}
實(shí)現void sys_sem_signal(sys_sem_t sem)函數:
void sys_sem_signal(sys_sem_t sem)
{
OSSemPost(sem);
}
實(shí)現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))/* 如果已經(jīng)收到, 則返回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; //將等待時(shí)間設置為timeout/2
else
return SYS_ARCH_TIMEOUT;
}
阻塞進(jìn)程,等待一個(gè)信號量的到來(lái)。如果timeout不為0,則進(jìn)程阻塞的時(shí)間最多為相關(guān)的毫秒數,否則進(jìn)程一直阻塞,直到收到信號量。
返回值:如果timeout不為0,則返回值為等待該信號量的毫秒數,如果函數在規定的時(shí)間內沒(méi)有等到信號量,則返回值為SYS_ARCH_TIMEOUT,如果信號量在調用函數時(shí)已經(jīng)可用,則函數不會(huì )發(fā)生任何阻塞操作,返回值這時(shí)可以是0。
實(shí)現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;
}
郵箱用于消息傳遞,用戶(hù)即可以將其實(shí)現為一個(gè)隊列,允許多條消息投遞到這個(gè)郵箱,也可以每次只允許投遞一個(gè)消息,這兩種方式 LwIP都可以正常運作。不過(guò),前者更加有效。這里我們使用消息隊列的方式,允許投遞多條消息。
實(shí)現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 );
}
實(shí)現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;//解決空指針投遞的問(wèn)題
while (OSQPost(mbox->ucos_queue, msg) == OS_Q_FULL)
OSTimeDly(10);
}
實(shí)現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; //將等待時(shí)間設置為timeout/2
else
return SYS_ARCH_TIMEOUT;
}
實(shí)現struct sys_timeouts * sys_arch_timeouts(void)函數功能:
struct sys_timeouts * sys_arch_timeouts(void)
{
return &global_timeouts;
}
實(shí)現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;
}
新建一個(gè)進(jìn)程,在整個(gè)系統中只會(huì )被調用一次。
移植操作系統模擬層完成。

4.4 根據lwip提供的軟件架構編寫(xiě)相應的網(wǎng)卡芯片驅動(dòng)

從用戶(hù)編程角度看,針對RTL8019AS的操作實(shí)際上就是對RTL8019AS內部寄存器的操作,以實(shí)現網(wǎng)卡的初始化、數據發(fā)送、數據接收等操作。發(fā)送數據時(shí),主控制器將數據寫(xiě)入網(wǎng)卡的SRAM中,然后發(fā)送一個(gè)發(fā)送數據指令,網(wǎng)卡就會(huì )將數據封裝成標準以太網(wǎng)物理層數據幀發(fā)送出去。同理,網(wǎng)卡接收到以太網(wǎng)數據時(shí),網(wǎng)卡會(huì )自動(dòng)解析成高層使用的有效格式,放在內部SRAM中供主控芯片讀取,我們采用周期查詢(xún)方式實(shí)現接收數據的處理。
RTL8019AS與主控芯片間通訊的輸入/輸出地址共有32個(gè),地址偏移量為00H-1FH。其中00-0F共16個(gè)地址,為內部寄存器地址,RTL8019AS的內部寄存器每個(gè)都是8位的,所有寄存器一共分為4頁(yè),每一頁(yè)都共享這16個(gè)偏移量,當前那一頁(yè)有效是由CR寄存器的值決定的。

要接收和發(fā)送數據包都必須讀寫(xiě)網(wǎng)卡的內部的16k的ram,必須通過(guò)DMA進(jìn)行讀和寫(xiě).網(wǎng)卡的內部ram是一塊雙端口的16k字節的ram.所謂雙端口就是說(shuō)有兩套總線(xiàn)連結到該ram,一套總線(xiàn)A是網(wǎng)卡控制器讀/寫(xiě)網(wǎng)卡上的ram,另一套總線(xiàn)B是主控制器讀/寫(xiě)網(wǎng)卡上的ram.總線(xiàn)A又叫Local DMA,總線(xiàn)B又叫Remote DMA.
遠程DMA地址包括10H~17H,都可以用來(lái)做遠程DMA端口,只要用其中的一個(gè)就可以了。我們使用10H。
復位端口包括18H~1FH共8個(gè)地址,功能一樣,用于RTL8019AS復位。我們使用18H。

Lwip提供了網(wǎng)卡驅動(dòng)框架形式,我們只要根據實(shí)際使用的網(wǎng)卡特性完善這些函數就可以了。
具體說(shuō)我們應該實(shí)現以5個(gè)函數的實(shí)現。
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個(gè)函數與網(wǎng)卡驅動(dòng)函數密切相關(guān)。low_level_init為網(wǎng)卡初始化函數,主要用來(lái)完成網(wǎng)卡的復位及參數初始化。low_level_output為網(wǎng)卡數據包發(fā)送函數。low_level_input為網(wǎng)卡數據包接收函數。
ethernetif_input函數主要作用是調用網(wǎng)卡數據包接收函數low_level_input從網(wǎng)卡SRAM中讀取一個(gè)數據包,然后解析數據包類(lèi)型,然后交付給上層應用程序。實(shí)際上,ethernetif_input已經(jīng)是一個(gè)可以直接使用的函數,調用一次可以完成數據包的接收和遞交。我們在應用層建立一個(gè)任務(wù)周期性調用該函數實(shí)現接收數據包的功能。
ethernetif_init是上層應用在管理網(wǎng)絡(luò )接口結構netif時(shí)調用的函數。該函數主要完成netif結構中的某些字段初始化,并最終調用low_level_init函數完成網(wǎng)卡的初始化。
low_level_init函數實(shí)現源代碼:
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結構體是協(xié)議棧內核對系統網(wǎng)絡(luò )接口設備進(jìn)行管理的重要數據結構,內核會(huì )為每個(gè)網(wǎng)絡(luò )接口分配一個(gè)netif結構,用于描述接口屬性。上面函數初始化了hwaddr、mtu、flag等關(guān)鍵屬性域,并最后調用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函數是保證網(wǎng)卡RTL8019AS正常工作的前提,它首先完成網(wǎng)卡的硬件復位,然后進(jìn)行網(wǎng)卡的軟件復位(往0X18端口寫(xiě)入任意值使其軟復位),接著(zhù)初始化網(wǎng)卡配置中的發(fā)送、接收緩沖區的頁(yè)地址、配置了網(wǎng)卡發(fā)送配置寄存器、接收寄存器,最后設置網(wǎng)卡自身的物理地址和多播過(guò)濾地址。
low_level_output函數,上層應用層數據需要封裝成協(xié)議棧要求的pbuf數據格式,然后再操作網(wǎng)卡發(fā)送數據。其源代碼如下:
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 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函數從網(wǎng)卡讀取數據,封裝成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要求的協(xié)議棧底層操作網(wǎng)卡的函數編寫(xiě)完畢。

4.5 移植完成后測試TCP/IP協(xié)議棧

我們使用查詢(xún)方式讀取網(wǎng)卡數據包,具體方案是建一個(gè)查詢(xún)任務(wù),周期性調用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協(xié)議棧前,我們需要初始化。初始化代碼:
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 設計并實(shí)現簡(jiǎn)單的WEB服務(wù)器

HTTP是一個(gè)基于TCP/IP,屬于應用層的面向對象的協(xié)議,由于其簡(jiǎn)捷、快速的方式,適用于分布式超媒體信息系統。
通過(guò)瀏覽器訪(fǎng)問(wèn)一個(gè)WEB服務(wù)器時(shí),其實(shí)就是利用HTTP 協(xié)議向服務(wù)器發(fā)送web頁(yè)面請求,WEB服務(wù)器接收到該請求后,返回應答信息和瀏覽器請求的網(wǎng)頁(yè)內容。
我們以一個(gè)最簡(jiǎn)單的例子說(shuō)明一下HTTP協(xié)議:
瀏覽器發(fā)送的標準請求是這樣的:
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. 說(shuō)明我需要index.html這個(gè)網(wǎng)頁(yè)內容,使用的HTTP協(xié)議版本是1.1
2. 我可以接收的文件類(lèi)型是text/html
3. 我可以接收的語(yǔ)言是中文
4. 瀏覽器的型號和版本號
5. 需要保持長(cháng)連接。
服務(wù)器的回復信息是這樣的:
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
服務(wù)器回復的信息含義:
1. 服務(wù)器返回瀏覽器訪(fǎng)問(wèn)的頁(yè)面存在。
2. 該響應頭表明服務(wù)器支持Range請求,以及服務(wù)器所支持的單位是字節(這也是唯一可用的單位)。
3. 關(guān)閉連接
4. 服務(wù)器返回的文件類(lèi)型為text/html,文件編碼為gb2312。
基于上述HTTP協(xié)議原理,我們可以設計一個(gè)簡(jiǎn)單的WEB服務(wù)器,有瀏覽器訪(fǎng)問(wèn)時(shí)WEB服務(wù)器返回固定的頁(yè)面。
在瀏覽器中輸入開(kāi)發(fā)板的IP地址:192.168.0.174
頁(yè)面顯示如圖4.6-1 簡(jiǎn)單WEB服務(wù)器:

瀏覽器默認訪(fǎng)問(wèn)端口是80,我們開(kāi)發(fā)板使用lwip提供的socket編程接口編程實(shí)現監聽(tīng)80端口,有瀏覽器訪(fǎng)問(wèn)開(kāi)發(fā)板的80端口,開(kāi)發(fā)板向瀏覽器返回指定WEB頁(yè)面。
實(shí)現代碼如下:
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);
}
}
}

網(wǎng)頁(yè)內容:
const unsigned char htmldata[]={
"HTTP/1.1 200 OKrn"
"Date: Sat, 4 Apr 2015 18:54:17 GMTrn"
"Server: microHttp/1.0 Zlgmcu Corporationrn"
"Accept-Ranges: bytesrn"
"Connection: Keep-Closern"
"Content-Type: text/html; charset=gb2312rn"
"rn"
"rn"
"rn"
"this is Lwip testrn"
"rn"
"

HELLO WELCOME TO LWIP WEB sever

rn"
"

硬件平臺:ARM

rn"
"

軟件平臺:UCOS Lwip

rn"
"

Design by ***

rn"
" 国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
rn"
"rn"
};




評論


技術(shù)專(zhuān)區

關(guān)閉