ARM硬件平臺上基于UCOS移植Lwip網(wǎng)絡(luò)協(xié)議棧
1硬件平臺 1
1.1硬件平臺簡介 1
1.2 硬件設(shè)計及電路原理圖 2
2. Keil 開發(fā)工具及Keil工程簡介 6
2.1 Keil開發(fā)工具 6
2.2 Keil工程簡介 6
2.3 鏈接文件、啟動文件分析 6
3. UCOS移植 11
3.1 ucos簡介 11
3.2 ucos移植總述 11
3.3 和移植UCOS有關(guān)的ARM芯片知識 11
3.4 系統(tǒng)堆棧和UCOS的任務(wù)堆棧 14
3.5 系統(tǒng)時鐘 14
3.6 任務(wù)級任務(wù)切換 14
3.7 中斷級任務(wù)切換 16
4.Lwip移植 18
4.1 lwip簡介 18
4.2 lwip移植總述 18
4.3移植lwip操作系統(tǒng)模擬層 19
4.4 根據(jù)lwip提供的軟件架構(gòu)編寫相應(yīng)的網(wǎng)卡芯片驅(qū)動 27
4.5 移植完成后測試TCP/IP協(xié)議棧 35
4.6 設(shè)計并實現(xiàn)簡單的WEB服務(wù)器 37
1.硬件平臺
1.1硬件平臺簡介
為保證網(wǎng)絡(luò)協(xié)議棧的順利移植,選用了LPC2220作為主控芯片,RTL8019AS作為網(wǎng)卡芯片,使用HR901170A進行電平轉(zhuǎn)換、濾波。
本文引用地址:http://www.ex-cimer.com/article/201611/318803.htmLPC2220是Philips公司推出的微處理器,片上有64K的RAM空間,通過總線很容易再擴展ROM和RAM。芯片還擁有豐富的IO接口以及多種中斷源,還集成了多種定時器、PWM等,另外,該芯片內(nèi)部集成了很多串行通訊協(xié)議,如SPIUART等。
RTL8019AS是由臺灣Realtek公司生產(chǎn)的以太網(wǎng)控制器。他符合EthernetII與IEEE802.3標準,100腳的PQFP封裝,采用全雙工收發(fā)并可同時達到10Mb/s的速率,內(nèi)置16kB的SRAM,支持8/16位數(shù)據(jù)總線,8個中斷申請線以及16個I/O基地址選擇。
HR901170A是漢仁電子有限公司生產(chǎn)的RJ45接口連接器(帶網(wǎng)絡(luò)變壓器/濾波器),該連接器滿足IEEES02.3和IEEE902.3ab標準,能夠較好地抑制電磁干擾。通過HR901170A系統(tǒng)就可以連接到以太網(wǎng)上。
基于LPC2220和RTL8019AS的上述特點,我們使用此款芯片可以設(shè)計出滿足移植Lwip網(wǎng)絡(luò)協(xié)議棧所需要的硬件運行環(huán)境。
1.2 硬件設(shè)計及電路原理圖
圖1.2-1硬件電路連接圖1
圖1.2-2硬件電路連接圖2
RTL8019AS芯片工作方式分為3種:①跳線方式,網(wǎng)卡的i/o和中斷由跳線決定。②即插即用方式,由軟件進行自動配置plug and play。③免跳線方式,網(wǎng)卡的i/o和中斷由外接的93c46里的內(nèi)容決定。在嵌入式應(yīng)用場合,為了節(jié)約成本,一般不使用93c46的,可以降低成本,同時又減少連線。我們選擇使用跳線模式,使用此模式的硬件設(shè)置方式為第65引腳(JP)接高電平,如圖1.2-2硬件電路連接圖2所示。
硬件復(fù)位引腳33(RSTDRV),此引腳為網(wǎng)卡芯片硬件復(fù)位引腳,RSTDRV為高電平有效,至少需要800ns的寬度。由硬件電路圖可知,此引腳連接到LPC2220的P0.8上。
中斷引腳(INT7-0)為97-100,1-4 共有8個中斷引腳,但使用時只是用一個中斷引腳,選擇哪個引腳作為中斷信號是根據(jù)[80-78][IRQS2-0]來決定的,根據(jù)電路圖可IRQS2-0這三個引腳懸空,RTL8019AS內(nèi)部有下拉電阻,故IRQS2-0這三個引腳電平都為0,根據(jù)手冊可知,選擇的是INT0作為中斷源引腳,此引腳連接到LPC2220的P0.9引腳。
64腳(AUI),該引腳決定使用aui還是bnc接口。我們用的網(wǎng)卡的接口一般是bnc的,很少用aui。bnc接口方式支持8線雙絞或同軸電纜。高電平時使用aui接口,懸空為低電平,使用bnc接口。我們將該引腳懸空即可。
網(wǎng)絡(luò)接口類型由74,77(PL0,PL1)引腳決定,我們使用第一種自動檢測就可以了。會自動檢測接口類型然后進行工作。自動檢測是用同軸還是雙絞線。這兩個引腳內(nèi)部存在下拉電阻,懸空即可。
芯片的brom地址由以下引腳72,71,69,68,67(BS4..BS0)決定,在嵌入式領(lǐng)域一般都不用該brom。brom是bootrom的縮寫。在電腦里用來做無盤工作站時候用到,可以從網(wǎng)卡進行引導(dǎo),而不是從a盤,c盤等引導(dǎo)系統(tǒng)。故懸空即可。
RTL8019AS支持3支可編程LED燈,電路連接見原理圖。
RTL8019AS與主控芯片間通訊的輸入/輸出地址共有32個,地址偏移量為00H-1FH。
RTL8019AS的IO基地址在跳線模式下由[85-84,82-81] [IOS3-0]這四個引腳狀態(tài)決定,電路圖中這四個引腳懸空,故這四個引腳狀態(tài)都為0,根據(jù)數(shù)據(jù)手冊可知RTL8019AS的IO基地址為300H,將300H化成二進制數(shù)值00110000 0000,很明顯地址中第8、9為地址為1,第6、7位和10-19位全部為0。我們僅需要控制第0-4位地址,就可以產(chǎn)生00H-1FH這32個偏移量。電路原理圖中SA8、SA9接+5v,SA10-SA19接的是地。
電路圖中SA0-SA4分別接的是LPC2220的A1-A5引腳,而SA5接的是NET_nCS引腳。
圖1.2-2硬件電路連接圖3
NET_nCS的信號是根據(jù)nCS3(BANK3的片選信號)和A22地址線信號產(chǎn)生的。
數(shù)據(jù)總線SD0-SD15連接到LPC2220的D0-D15,組成16bit總線。
產(chǎn)生00H-1FH的偏移量需要NET_nCS信號為低。我們總結(jié)一下,我們的RTL8019AS需要的地址是300H-301FH,硬件連線決定了這個地址偏移量。我們將RTL8019AS接到LPC2220的BANK3上。對LPC2220來說,只產(chǎn)生00H-1FH的偏移量就可以。LPC2220的BANK3起始地址是0X83000000,也就是說當訪問這個地址時才會產(chǎn)生nCS3為低的信號,如果BANK3只需要連接網(wǎng)卡的話,我們就可以直接利用nCS3信號作為選通網(wǎng)卡芯片的信號即可,但我們硬件設(shè)計時將BANK3又分成了幾個獨立的訪問空間用于掛接不同的總線器件。我們利用地址線A21、A22、A23將BANK3分為0X834000000、0x83100000、0x83800000這幾個獨立空間。我們只分析利用A22地址線信號和nCS3
產(chǎn)生的NET_nCS信號,此信號線硬件上連接到RTL8019AS的SA5上,A22地址線上信號為高電平并且nCS3產(chǎn)生低電平信號,這種情況下NET_nCS才是低電平,而只有NET_nCS為低電平時,才能產(chǎn)生RTL8019AS需要的300H-301FH地址偏移量。現(xiàn)在通過LPC2220訪問地址空間0x83400000,這個時候根據(jù)上面分析NET_nCS為低電平,也即RTL8019AS的SA5為低電平,第四位地址線SA0-SA4連接的是LPC2220的A1-A5,
訪問0x83400000、0x83400001對應(yīng)的RTL8019AS地址即為300H,同理0x83400010、0x83400011對應(yīng)的RTL8019AS地址即為301H。我們訪問LPC2220的0x83400000-0x8340003F即訪問了RTL8019AS的32個偏移量。
2. Keil 開發(fā)工具及Keil工程簡介
2.1Keil開發(fā)工具
2.2 Keil工程簡介
2.3 鏈接文件、啟動文件分析
在分析啟動代碼之前,先理解一下Keil MDK 工程中Scf鏈接文件的相關(guān)知識。我們知道源代碼程序經(jīng)過編譯、鏈接后生成一個二進制文件,這個二進制文件是用來控制ARM芯片的。
這個二進制文件是直接下載到ARM處理器芯片的,這個二進制文件的格式如圖2.4-1 ARM Image映像文件結(jié)構(gòu)。
圖2.4-1 ARM Image映像文件結(jié)構(gòu)
ZI段表示初始化為0的變量區(qū)域,RW段表示已經(jīng)初始化的變量區(qū)域,RO段表示代碼區(qū)域。
因ZI段只是初始化為0的變量區(qū)域,所以在Image文件中并不占空間,映像文件中只是包含實際地址和大小。我們一般將image映像文件下載到ROM中,系統(tǒng)啟動時從ROM中讀取第一條需要執(zhí)行的指令,但RW段下載到了ROM中,ROM是不可寫的。因此出現(xiàn)了裝載地址和運行地址不一致的情況。我們要保證程序正常運行就必須保證變量在訪問之前放到了正確的地址。一個簡單的裝載地址到運行地址的轉(zhuǎn)換見圖2.4-2 簡單的分散裝載內(nèi)存映像圖。
圖2.4-2 簡單的分散裝載內(nèi)存映像圖
在KeilMDK工程中使用分散裝載文件scf文件來設(shè)置映像文件的轉(zhuǎn)載地址和運行地址,當我們設(shè)置的轉(zhuǎn)載地址和運行地址不一致時,KeilMDK會自動產(chǎn)生搬運代碼,在使用RW、ZI段之前將代碼搬運到正確的地址。
我們工程使用的分散加載文件內(nèi)容:
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運行域和裝載域地址一樣,此運行域包含系統(tǒng)的啟動代碼和所有RO段代碼。剩余其他運行域的地址和裝載域都不同,都需要根據(jù)分散加載文件進行代碼搬運工作,這個工作是由KeilMDK工具自動完成。
系統(tǒng)啟動代碼主要完成的工作如下:
1. 中斷向量表
2. 初始化總線頻率和存儲器系統(tǒng)
3. 初始化堆棧
4. 呼叫主應(yīng)用程序
中斷向量表是當外部中或系統(tǒng)異常發(fā)生時中斷服務(wù)程序的入口地址或存放中斷服務(wù)程序的首地址。此工程中將中斷向量表定位在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外接的設(shè)備正常使用,一個復(fù)雜的系統(tǒng)可能存在多種存儲器類型的接口,需要根據(jù)實際的系統(tǒng)設(shè)計對此加以正確配置。對同一種存儲器類型來說,也因為訪問速度的差異,需要不同的時序設(shè)置。工程中我們使用的存儲器包括NorFlash和SRAM,設(shè)置的訪問總線寬度都為16bit。
堆??臻g是C語言正常運行所需要的基本環(huán)境,函數(shù)調(diào)用參數(shù)、返回值、函數(shù)調(diào)用關(guān)系都需要使用堆棧。因此,需要設(shè)置ARM各個運行模式的堆??臻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
調(diào)用__main()函數(shù),此函數(shù)主要工作流程如圖2.4-3 __main 函數(shù)執(zhí)行流程。
圖2.4-3 __main 函數(shù)執(zhí)行流程
- 調(diào)用__user_setup_stackheap()設(shè)置用戶模式下的??臻g和堆空間??臻g可以通過程序定義,也可以通過分散加載文件制定絕對地址空間。
- 調(diào)用__rt_lib_init()初始化庫函數(shù),在必要時為用戶main函數(shù)設(shè)置argc、argv參數(shù)。調(diào)用__cpp_initialize__aeabi_初始化C++特性。
- Calls main(), the user-level root of the application.
From main(),your program might call, among other things, library functions.
調(diào)用用戶main函數(shù),在main函數(shù)里,你可以調(diào)用其他用戶函數(shù),也可以調(diào)用庫函數(shù)。
- Calls exit() with the value returned by main().
- 當main函數(shù)返回時,調(diào)用exit函數(shù)清理資源。
3. UCOS移植
3.1 ucos簡介
UCOS是一個可裁剪、支持搶占式調(diào)度的實時嵌入式操作系統(tǒng)。提供基本的任務(wù)管理功能,支持信號量、郵箱、隊列等任務(wù)間同步、通訊機制。
3.2 ucos移植總述
Ucos移植主要是實現(xiàn)保存、恢復(fù)ARM芯片執(zhí)行程序所需要的寄存器環(huán)境和實現(xiàn)系統(tǒng)時鐘接口需要的硬件定時器的設(shè)置及啟動。需要移植實現(xiàn)的主要有任務(wù)級任務(wù)切換、中斷級任務(wù)切換、任務(wù)堆棧初始化、系統(tǒng)時鐘。
3.3 和移植UCOS有關(guān)的ARM芯片知識
C語言經(jīng)過編譯器編譯、鏈接后生成的二進制指令是能在ARM芯片上直接執(zhí)行的指令代碼。這些指令執(zhí)行是依賴于各種寄存器的,保護程序運行環(huán)境其實就是保護這些寄存器。
ARM芯片有7種運行模式:
1. 用戶模式(user模式),運行應(yīng)用的普通模式。
2. 快速中斷模式(fiq模式),用于支持數(shù)據(jù)傳輸或通道處理。
3. 中斷模式(irq模式),用于普通中斷處理。
4. 超級用戶模式(svc模式),操作系統(tǒng)的保護模式。
5. 異常中斷模式(abt模式),輸入數(shù)據(jù)后登入或預(yù)取異常中斷指令。
6. 系統(tǒng)模式(sys模式),是操作系統(tǒng)使用的一個有特權(quán)的用戶模式。
7. 未定義模式(und模式),執(zhí)行了未定義指令時進入該模式。
外部中斷,異常操作或軟件控制都可以改變中斷模式。大多數(shù)應(yīng)用程序都時是在用戶模式下運行。進入特權(quán)模式是為了處理中斷或異常請求或操作保護資源服務(wù)的。
些工作模式是芯片硬件提供的程序運行的不同環(huán)境,不同的模式有不同的硬件訪問權(quán)限,使用不同的寄存器。這就給不同的程序提供了不同的權(quán)限機制,你比如說你的操作系統(tǒng)代碼運行在權(quán)限比較高的模式下,而你的應(yīng)用程序運行在權(quán)限比較低的模式下。這樣就起到了對操作系統(tǒng)代碼的保護作用。
寄存器,各個模式下可見的寄存器以及各個寄存器的功能:
ARM共有37個32位的寄存器,其中31個是通用寄存器,6個是狀態(tài)寄存器。但在同一時間,對程序員來說并不是所有的寄存器都可見。在某一時刻存儲器是否可見(可被訪問),是處理器當前的工作狀態(tài)和工作模式?jīng)Q定的。其各個模式下的寄存器如圖3.3-1 ARM各種運行模式:
圖3.3-1 ARM各種運行模式
其中系統(tǒng)模式和用戶模式所用的寄存器是一樣的。畫三角陰影的寄存器表示在不同模式下有不同的物理寄存器。
以下對其進行分類說明。
通用寄存器:
ARM的通用寄存器包括R0~R15,其中R0~R7是屬于未分組寄存器,各個模式下都使用同樣的寄存器。R8~R14在FIQ模式下是有獨立的物理寄存器,其目的是加快中斷響應(yīng)速度,從硬件上保存程序執(zhí)行現(xiàn)場。R13和R14這兩個寄存器在每種模式下都有自己的獨立寄存器。R15只有一個,所有模式公用。
下對這些寄存器中的比較有特殊功能的做一下介紹:
寄存器R13:在ARM指令中,常用R13做堆棧指針用。每種運行模式都有自己獨立的堆棧,用于保存中斷發(fā)生時的程序運行環(huán)境和C語言執(zhí)行時進行過程控制。
寄存器R14:專職持有返回點的地址,在系統(tǒng)執(zhí)行一條“跳轉(zhuǎn)并鏈接(link)”(BL)指令
的時候,R14將收到一個R15的拷貝。其他的時候,它可以用作一個通用寄存器。相應(yīng)的它在其他模式下的私有寄存器R14_svc,R14_irq,R14_fiq,R14_abt和R14_und都同樣用來保存在中斷或異常發(fā)生時,或時在中斷和異常中執(zhí)行了BL指令時,R15的返回值。
寄存器R15是程序計數(shù)器(PC)。在ARM狀態(tài)下,R15的bits[1:0]為0,bits[31:2]保存了PC的值。在Thumb狀態(tài)下,bits[0]為0同時bits[31:1]保存了PC值。
FIQ模式擁有7個私有寄存器R8-14(R8_fiq-R14_fiq)。在ARM狀態(tài)下,多數(shù)FIQ處理都不需要保存任何寄存器。用戶、中斷、異常中止,超級用戶和未定義模式都擁有2個私有寄存器,R13和R14。允許這些模式都可擁有1個私有堆棧指針和鏈接(link)寄存器。
程序狀態(tài)寄存器。
ARM920T具有一個當前程序狀態(tài)寄存器(CPSR),另外還有5個保存程序狀態(tài)寄存器(SPSRs)用于異常中斷處理。這些寄存器的功能有:
1. 保留最近完成的ALU操作的信息。
2. 控制中斷的使能和禁止。
3. 設(shè)置處理器的操作模式。
狀態(tài)寄存器各位定義見圖3.3-2 ARM狀態(tài)寄存器:
圖3.3-2 ARM狀態(tài)寄存器
3.4 系統(tǒng)堆棧和UCOS的任務(wù)堆棧
當產(chǎn)生外部中斷或者系統(tǒng)異常時,ARM會進入相應(yīng)的模式,各種運行模式均有其獨立的堆??臻g。UCOS中的任務(wù)是調(diào)度的最小單元,每個任務(wù)都有自己獨立的堆棧空間,當任務(wù)運行時,它用來保存一些局部變量,當任務(wù)掛起時,它負責保存任務(wù)的運行現(xiàn)場,也就是CPU寄存器的值。
3.5 系統(tǒng)時鐘
系統(tǒng)時鐘是UCOS管理任務(wù)延時的基本依據(jù),要求有一個周期性的定時器產(chǎn)生固定間隔時間。我們使用LPC2220的定時器0產(chǎn)生固定時間間隔事件,時間間隔設(shè)置為10ms,定時時間到產(chǎn)生中斷。UCOS系統(tǒng)時鐘處理函數(shù)是OSTimeTick(),時間中斷服務(wù)程序里調(diào)用此函數(shù)即可。
3.6 任務(wù)級任務(wù)切換
UCOS的用戶調(diào)用一些系統(tǒng)服務(wù)時(比如,OSTimeDly、OSSemPend),就會產(chǎn)生任務(wù)級任務(wù)切換。其切換的實質(zhì)是保存正在執(zhí)行任務(wù)的執(zhí)行現(xiàn)場,然后恢復(fù)應(yīng)該運行的任務(wù)的運行現(xiàn)場。
本工程中使用軟中斷的方式實現(xiàn)任務(wù)級任務(wù)切換的目的。
任務(wù)級切換函數(shù)的底層接口是使用的軟中斷技術(shù),用__swi來聲明一個不存在的函數(shù),則調(diào)用這個函數(shù)就在調(diào)用這個函數(shù)的地方插入一條SWI指令,并且可以指定功能號。定義如下:__swi(0x00) void OS_TASK_SW(void); /* 任務(wù)級任務(wù)切換函數(shù) */
調(diào)用OS_TASK_SW()這個函數(shù)時就會產(chǎn)生一個軟中斷,產(chǎn)生軟中斷后執(zhí)行軟中斷服務(wù)程序。服務(wù)程序主要代碼分析如下:
SoftwareInterrupt
LDR SP, StackSvc ; 重新設(shè)置堆棧指針
STMFD SP!, {R0-R3, R12, LR}
MOV R1, SP ; R1指向參數(shù)存儲位置
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指向參數(shù)存儲位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01為第一次任務(wù)切換
BL SWI_Exception
LDMFD SP!, {R0-R3, R12, PC}^
代碼難點分析:
軟中斷指令使處理器進入管理模式,而用戶程序處于系統(tǒng)/用戶模式,其它異常也有自己的處理器模式,都有各自的堆棧指針,不會因為給堆棧指針賦值而破壞其它處理器模式的堆棧而影響其它程序的執(zhí)行。返回的地址已經(jīng)存儲在連接寄存器LR中而不是存儲在堆棧中。由于進人管理模式自動關(guān)中斷,所以這段程序不會被其它程序同時調(diào)用。
因為ARM處理器核具有兩個指令集,在執(zhí)行Thumb指令的狀態(tài)時不是所有寄存器都可見(參考ARM的相關(guān)資料),而且任務(wù)又可能不在特權(quán)模式(不能改變CPSR)。為了兼容任意一種模式,本移植使用軟中斷指令SWI使處理器進入管理模式和ARM指令狀態(tài),并使用功能0實現(xiàn)OS_TASK_SW()的功能。
因任務(wù)級任務(wù)切換使用的是軟中斷技術(shù),我們把osctxsw()與osintctxsw()合二為一了,統(tǒng)一采用osintctxsw()來實現(xiàn)。之所以這樣搞的原因是任務(wù)進行切換的時候,都必須進入軟中斷的狀態(tài),而對于軟中斷的異常響應(yīng)代碼已經(jīng)將任務(wù)的環(huán)境變量進行了保存,從而也不需要像osctxsw()里面規(guī)定的那樣對將環(huán)境變量進行保存。osintctxsw()函數(shù)的移植分析見3.7中斷級任務(wù)切換。
3.7 中斷級任務(wù)切換
當系統(tǒng)任務(wù)延時時間到或者在中斷服務(wù)程序里拋出信號量、郵箱等可以產(chǎn)生系統(tǒng)調(diào)度的操作時,會執(zhí)行任務(wù)切換,但這種切換是在中斷模式下進行的。但底層切換函數(shù)是一致的,只不過任務(wù)級任務(wù)切換時是在SVC模式下進行,中斷級任務(wù)切換是在中斷模式下進行。
下面我們分析中斷級任務(wù)切換的主要流程和代碼:
SUB LR, LR, #4 ; 計算返回地址
STMFD SP!, {R0-R3, R12, LR} ; 保存任務(wù)環(huán)境
MRS R3, SPSR ; 保存狀態(tài)
STMFD SP, {R3,SP, LR}^; 保存用戶狀態(tài)的R3,SP,LR,注意不能回寫
; 如果回寫的是用戶的SP,所以后面要調(diào)整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) ; 切換到系統(tǒng)模式
CMP R1, #1
LDREQ SP, =StackUsr
BL $IRQ_Exception_Function ; 調(diào)用c語言的中斷處理程序
MSR CPSR_c, #(NoInt :OR: SYS32Mode) ; 切換到系統(tǒng)模式
LDR R2, =OsEnterSum; OsEnterSum,使OSIntExit退出時中斷關(guān)閉
MOV R1, #1
STR R1, [R2]
BL OSIntExit
LDR R2, =OsEnterSum; 因為中斷服務(wù)程序要退出,
;所以O(shè)sEnterSum=0
MOV R1, #0
STR R1, [R2]
MSR CPSR_c, #(NoInt :OR: IRQ32Mode) ; 切換回irq模式
LDMFD SP, {R3,SP, LR }^ ; 恢復(fù)用戶狀態(tài)的R3,SP,LR,
;注意不能回寫
; 如果回寫的是用戶的SP,所以后面要調(diào)整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}^ ; 不進行任務(wù)切換
LDR PC, =OSIntCtxSw ; 進行任務(wù)切換
代碼主要功能分析:
實現(xiàn)在中斷模式下保存系統(tǒng)模式下正在運行任務(wù)的各個寄存器到中斷模式堆棧,然后執(zhí)行相應(yīng)的中斷服務(wù)程序,中斷退出時做任務(wù)切換。
下面我們分析實現(xiàn)任務(wù)切換的函數(shù)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 ;調(diào)用鉤子函數(shù)
;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ù)實現(xiàn)了保存上一個被中斷任務(wù)運行時各個寄存器到任務(wù)的堆棧空間里,然后將系統(tǒng)中優(yōu)先級最高且就緒的任務(wù)堆棧里保存的各個寄存器內(nèi)容恢復(fù)到系統(tǒng)模式的各個寄存器中,使任務(wù)正常運行。
4.Lwip移植
4.1 lwip簡介
lwip是瑞典計算機科學院(SICS)的Adam Dunkels 開發(fā)的一個小型開源的TCP/IP協(xié)議棧。LwIP是Light Weight (輕型)IP協(xié)議,有無操作系統(tǒng)的支持都可以運行。LwIP實現(xiàn)的重點是在保持TCP協(xié)議主要功能的基礎(chǔ)上減少對RAM 的占用,它只需十幾KB的RAM和40K左右的ROM就可以運行,這使LwIP協(xié)議棧適合在低端的嵌入式系統(tǒng)中使用。
4.2 lwip移植總述
Lwip有無操作系統(tǒng)的支持都可以運行,我們移植是基于UCOS的。
基于UCOS移植Lwip主要包含兩個方面的工作:
1. 根據(jù)Lwip提供的操作系統(tǒng)模擬層接口編寫基于UCOS的實現(xiàn)代碼,以實現(xiàn)Lwip和UCOS的完美融合。
2. 根據(jù)Lwip提供的底層網(wǎng)卡驅(qū)動接口,結(jié)合RTL8019AS網(wǎng)卡datasheet編制網(wǎng)卡驅(qū)動程序。
4.3移植lwip操作系統(tǒng)模擬層
操作系統(tǒng)模擬層(sys_arch)存在的目的主要是為了方便 LwIP 的移植,它在底層操作系統(tǒng)和LwIP 之間提供了一個接口。這樣,我們在移植 LwIP 到一個新的目標系統(tǒng)時,只需修改這個接口即可。不過,不依賴底層操作系統(tǒng)的支持也可以實現(xiàn)這個接口。
sys_arch需要為LwIP提供創(chuàng)建新線程功能,提供信號量 (semaphores) 和郵箱 (mailboxes) 兩種進程間通訊方式 (IPC) 。
1. 模擬層需要添加的頭文件 cc.h 說明
Lwip使用的數(shù)據(jù)類型定義:
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使用的結(jié)構(gòu)體對齊方式聲明相關(guān)的宏定義:
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_END
為方便操作協(xié)議幀數(shù)據(jù),lwip協(xié)議棧中結(jié)構(gòu)體使用單字節(jié)對齊方式。
處理器模式:
#define BYTE_ORDER LITTLE_ENDIAN
我們使用的LPC2220為小端模式處理器,故定義為小端模式。
其他內(nèi)容主要和調(diào)試輸出功能有關(guān),這里不進行一一說明。
2. 需要實現(xiàn)的操作系統(tǒng)模擬層函數(shù)
- void sys_init(void)
初始化lwip操作系統(tǒng)模擬層。
- sys_sem_t sys_sem_new(u8_t count)
創(chuàng)建一個信號量,count表示初始化后的信號量狀態(tài)。
- void sys_sem_free(sys_sem_t sem)
刪除指定的信號量。
- void sys_sem_signal(sys_sem_t sem)
發(fā)送一個信號量。
- u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
等待指定的信號并阻塞線程。timeout 參數(shù)為 0,線程會一直被阻塞至收到指定的信號;非 0,則線程僅被阻塞至指定的 timeout時間(單位為毫秒)。在timeout 參數(shù)值非 0 的情況下,返回值為等待指定的信號所消耗的毫秒數(shù)。如果在指定的時間內(nèi)并沒有收到信號,返回值為SYS_ARCH_TIMEOUT。如果線程不必再等待這個信號(也就是說,已經(jīng)收到信號) ,返回值也可以為 0。注意,LwIP實現(xiàn)了一個名稱與之相似的函數(shù)來調(diào)用這個函數(shù),sys_sem_wait(),注意區(qū)別。
- sys_mbox_t sys_mbox_new(void)
創(chuàng)建一個空消息郵箱。
- 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 參數(shù)指定(與
sys_arch_sem_wait()函數(shù)類似) 。msg 是一個結(jié)果參數(shù),用來保存郵箱中的消息指針 (即*msg = ptr) ,它的值由這個函數(shù)設(shè)置。 “msg”參數(shù)有可能為空,這表明當前這條消息應(yīng)該被丟棄。 返回值與 sys_arch_sem_wait()函數(shù)相同:等待的毫秒數(shù)或者 SYS_ARCH_TIMEOUT――如果時間溢出的話。LwIP實現(xiàn)的函數(shù)中,有一個名稱與之相似的――sys_mbox_fetch(),注意區(qū)分。
- struct sys_timeouts *sys_arch_timeouts(void)
返回一個指向當前線程使用的 sys_timeouts 結(jié)構(gòu)的指針。LwIP 中,每一個線程都有一個timeouts 鏈表,這個鏈表由 sys_timeout 結(jié)構(gòu)組成,sys_timeouts 結(jié)構(gòu)則保存了指向這個鏈表的指針。這個函數(shù)由 LwIP 的超時調(diào)度程序調(diào)用,并且不能返回一個空(NULL)值。 單線程 sys_arch 實現(xiàn)中,這個函數(shù)只需簡單返回一個指針即可。這個指針指向保存在 sys_arch 模塊中的 sys_timeouts 全局變量。
- sys_thread_t sys_thread_new(void (* thread)(void *arg), void *arg, int prio)
創(chuàng)建一個新的線程。
實現(xiàn)sys_sem_t sys_sem_new(u8_t count)函數(shù):
sys_sem_t sys_sem_new(u8_t count)
{
return OSSemCreate((u16_t)count);
}
這個函數(shù)實現(xiàn)比較簡單,UCOS提供了信號量的操作函數(shù),直接調(diào)用即可。
實現(xiàn)void sys_sem_free(sys_sem_t sem)函數(shù):
void sys_sem_free(sys_sem_t sem)
{
u8_t Err;
OSSemDel(sem, OS_DEL_ALWAYS, &Err);
}
實現(xiàn)void sys_sem_signal(sys_sem_t sem)函數(shù):
void sys_sem_signal(sys_sem_t sem)
{
OSSemPost(sem);
}
實現(xiàn)u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)函數(shù):
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;
}
阻塞進程,等待一個信號量的到來。如果timeout不為0,則進程阻塞的時間最多為相關(guān)的毫秒數(shù),否則進程一直阻塞,直到收到信號量。
返回值:如果timeout不為0,則返回值為等待該信號量的毫秒數(shù),如果函數(shù)在規(guī)定的時間內(nèi)沒有等到信號量,則返回值為SYS_ARCH_TIMEOUT,如果信號量在調(diào)用函數(shù)時已經(jīng)可用,則函數(shù)不會發(fā)生任何阻塞操作,返回值這時可以是0。
實現(xiàn)sys_mbox_t sys_mbox_new(int size)函數(shù)功能:
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;
}
郵箱用于消息傳遞,用戶即可以將其實現(xiàn)為一個隊列,允許多條消息投遞到這個郵箱,也可以每次只允許投遞一個消息,這兩種方式 LwIP都可以正常運作。不過,前者更加有效。這里我們使用消息隊列的方式,允許投遞多條消息。
實現(xiàn)void sys_mbox_free(sys_mbox_t mbox)函數(shù):
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 );
}
實現(xiàn)void sys_mbox_post(sys_mbox_t mbox, void *msg)函數(shù)功能:
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);
}
實現(xiàn)u32_t
sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, u32_t timeout)函數(shù):
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;
}
實現(xiàn)struct sys_timeouts * sys_arch_timeouts(void)函數(shù)功能:
struct sys_timeouts * sys_arch_timeouts(void)
{
return &global_timeouts;
}
實現(xiàn)sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)函數(shù)功能:
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
else return 0;
}
if(OSTaskCreate(thread,(void*)arg, &LWIP_STK_AREA[i][LWIP_STK_SIZE-1],prio)==OS_NO_ERR){
TaskCreateFlag |=(0x01<};
return prio;
}
新建一個進程,在整個系統(tǒng)中只會被調(diào)用一次。
移植操作系統(tǒng)模擬層完成。
4.4 根據(jù)lwip提供的軟件架構(gòu)編寫相應(yīng)的網(wǎng)卡芯片驅(qū)動
從用戶編程角度看,針對RTL8019AS的操作實際上就是對RTL8019AS內(nèi)部寄存器的操作,以實現(xiàn)網(wǎng)卡的初始化、數(shù)據(jù)發(fā)送、數(shù)據(jù)接收等操作。發(fā)送數(shù)據(jù)時,主控制器將數(shù)據(jù)寫入網(wǎng)卡的SRAM中,然后發(fā)送一個發(fā)送數(shù)據(jù)指令,網(wǎng)卡就會將數(shù)據(jù)封裝成標準以太網(wǎng)物理層數(shù)據(jù)幀發(fā)送出去。同理,網(wǎng)卡接收到以太網(wǎng)數(shù)據(jù)時,網(wǎng)卡會自動解析成高層使用的有效格式,放在內(nèi)部SRAM中供主控芯片讀取,我們采用周期查詢方式實現(xiàn)接收數(shù)據(jù)的處理。
RTL8019AS與主控芯片間通訊的輸入/輸出地址共有32個,地址偏移量為00H-1FH。其中00-0F共16個地址,為內(nèi)部寄存器地址,RTL8019AS的內(nèi)部寄存器每個都是8位的,所有寄存器一共分為4頁,每一頁都共享這16個偏移量,當前那一頁有效是由CR寄存器的值決定的。
要接收和發(fā)送數(shù)據(jù)包都必須讀寫網(wǎng)卡的內(nèi)部的16k的ram,必須通過DMA進行讀和寫.網(wǎng)卡的內(nèi)部ram是一塊雙端口的16k字節(jié)的ram.所謂雙端口就是說有兩套總線連結(jié)到該ram,一套總線A是網(wǎng)卡控制器讀/寫網(wǎng)卡上的ram,另一套總線B是主控制器讀/寫網(wǎng)卡上的ram.總線A又叫Local DMA,總線B又叫Remote DMA.
遠程DMA地址包括10H~17H,都可以用來做遠程DMA端口,只要用其中的一個就可以了。我們使用10H。
復(fù)位端口包括18H~1FH共8個地址,功能一樣,用于RTL8019AS復(fù)位。我們使用18H。
Lwip提供了網(wǎng)卡驅(qū)動框架形式,我們只要根據(jù)實際使用的網(wǎng)卡特性完善這些函數(shù)就可以了。
具體說我們應(yīng)該實現(xiàn)以5個函數(shù)的實現(xiàn)。
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個函數(shù)與網(wǎng)卡驅(qū)動函數(shù)密切相關(guān)。low_level_init為網(wǎng)卡初始化函數(shù),主要用來完成網(wǎng)卡的復(fù)位及參數(shù)初始化。low_level_output為網(wǎng)卡數(shù)據(jù)包發(fā)送函數(shù)。low_level_input為網(wǎng)卡數(shù)據(jù)包接收函數(shù)。
ethernetif_input函數(shù)主要作用是調(diào)用網(wǎng)卡數(shù)據(jù)包接收函數(shù)low_level_input從網(wǎng)卡SRAM中讀取一個數(shù)據(jù)包,然后解析數(shù)據(jù)包類型,然后交付給上層應(yīng)用程序。實際上,ethernetif_input已經(jīng)是一個可以直接使用的函數(shù),調(diào)用一次可以完成數(shù)據(jù)包的接收和遞交。我們在應(yīng)用層建立一個任務(wù)周期性調(diào)用該函數(shù)實現(xiàn)接收數(shù)據(jù)包的功能。
ethernetif_init是上層應(yīng)用在管理網(wǎng)絡(luò)接口結(jié)構(gòu)netif時調(diào)用的函數(shù)。該函數(shù)主要完成netif結(jié)構(gòu)中的某些字段初始化,并最終調(diào)用low_level_init函數(shù)完成網(wǎng)卡的初始化。
low_level_init函數(shù)實現(xiàn)源代碼:
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結(jié)構(gòu)體是協(xié)議棧內(nèi)核對系統(tǒng)網(wǎng)絡(luò)接口設(shè)備進行管理的重要數(shù)據(jù)結(jié)構(gòu),內(nèi)核會為每個網(wǎng)絡(luò)接口分配一個netif結(jié)構(gòu),用于描述接口屬性。上面函數(shù)初始化了hwaddr、mtu、flag等關(guān)鍵屬性域,并最后調(diào)用board_eth_init函數(shù)。
board_eth_init函數(shù)源代碼如下:
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函數(shù)是保證網(wǎng)卡RTL8019AS正常工作的前提,它首先完成網(wǎng)卡的硬件復(fù)位,然后進行網(wǎng)卡的軟件復(fù)位(往0X18端口寫入任意值使其軟復(fù)位),接著初始化網(wǎng)卡配置中的發(fā)送、接收緩沖區(qū)的頁地址、配置了網(wǎng)卡發(fā)送配置寄存器、接收寄存器,最后設(shè)置網(wǎng)卡自身的物理地址和多播過濾地址。
low_level_output函數(shù),上層應(yīng)用層數(shù)據(jù)需要封裝成協(xié)議棧要求的pbuf數(shù)據(jù)格式,然后再操作網(wǎng)卡發(fā)送數(shù)據(jù)。其源代碼如下:
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函數(shù)從網(wǎng)卡讀取數(shù)據(jù),封裝成pbuf形式后傳遞給上層應(yīng)用層。其源代碼如下:
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)卡的函數(shù)編寫完畢。
4.5 移植完成后測試TCP/IP協(xié)議棧
我們使用查詢方式讀取網(wǎng)卡數(shù)據(jù)包,具體方案是建一個查詢?nèi)蝿?wù),周期性調(diào)用GetPacket()函數(shù),函數(shù)源代碼:
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);
}
系統(tǒng)ping測試成功如圖4.5-1 ping測試:
圖4.5-1 ping測試
4.6 設(shè)計并實現(xiàn)簡單的WEB服務(wù)器
HTTP是一個基于TCP/IP,屬于應(yīng)用層的面向?qū)ο蟮膮f(xié)議,由于其簡捷、快速的方式,適用于分布式超媒體信息系統(tǒng)。
通過瀏覽器訪問一個WEB服務(wù)器時,其實就是利用HTTP 協(xié)議向服務(wù)器發(fā)送web頁面請求,WEB服務(wù)器接收到該請求后,返回應(yīng)答信息和瀏覽器請求的網(wǎng)頁內(nèi)容。
我們以一個最簡單的例子說明一下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. 說明我需要index.html這個網(wǎng)頁內(nèi)容,使用的HTTP協(xié)議版本是1.1
2. 我可以接收的文件類型是text/html
3. 我可以接收的語言是中文
4. 瀏覽器的型號和版本號
5. 需要保持長連接。
服務(wù)器的回復(fù)信息是這樣的:
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ù)器回復(fù)的信息含義:
1. 服務(wù)器返回瀏覽器訪問的頁面存在。
2. 該響應(yīng)頭表明服務(wù)器支持Range請求,以及服務(wù)器所支持的單位是字節(jié)(這也是唯一可用的單位)。
3. 關(guān)閉連接
4. 服務(wù)器返回的文件類型為text/html,文件編碼為gb2312。
基于上述HTTP協(xié)議原理,我們可以設(shè)計一個簡單的WEB服務(wù)器,有瀏覽器訪問時WEB服務(wù)器返回固定的頁面。
在瀏覽器中輸入開發(fā)板的IP地址:192.168.0.174
頁面顯示如圖4.6-1 簡單WEB服務(wù)器:
瀏覽器默認訪問端口是80,我們開發(fā)板使用lwip提供的socket編程接口編程實現(xiàn)監(jiān)聽80端口,有瀏覽器訪問開發(fā)板的80端口,開發(fā)板向瀏覽器返回指定WEB頁面。
實現(xiàn)代碼如下:
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)頁內(nèi)容:
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"
"
HELLO WELCOME TO LWIP WEB sever
rn""
硬件平臺:ARM
rn""
軟件平臺:UCOS Lwip
rn""
Design by ***
rn""rn"
"rn"
};
評論