天天看點

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

一、環境介紹

MCU: STM32F103ZET6

代碼開發工具: Keil5

TCP/IP協定棧: LWIP

網卡: DM9000

本篇文章主要講解如何在STM32F103工程裡添加移植LWIP協定,最終完成TCP伺服器、TCP用戶端的通信測試。 網卡采用的是DM9000,工程代碼中,采用STM32的FSMC接口來驅動DM900網卡,DM9000是并口網卡,引腳多,但是速度快,也可以采用其他網卡,SPI協定的、UART協定的等。 比如:ENC28J60。 因為主要是講LWIP協定棧的移植,是以網卡相關的代碼就沒有細說(需要準備一個網卡可以正常通信的工程,再移植)。

工程源碼、LWIP資料包下載下傳位址:

https://download.csdn.net/download/xiaolong1126626497/19907087 資料包裡的内容如下:
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

二、D9000網卡

2.1 DM9000簡介

   DM9000 是一款完全內建的、成本效益高、引腳數少、帶有通用處理器接口的單晶片快速以太網控制器。 自帶一個 10/100M PHY 和 4K 雙字的 SRAM ,DM9000A 為适應各種處理器提供了8位、16 位資料接口通路内部存儲器,DM9000擁有自動協商功能,DM9000特性如下:

1、內建自适應10/100M收發器。

2、内置16k位元組的SRAM。

3、支援硬體幀校驗。

4、相容3.3V和5.0V輸入輸出電壓。

DM9000 有多種型号,有 100 引腳和 48 引腳的, 開發闆選擇的是 48 引腳的 DM9000,型号為 DM9000CEP。

2.2 DM9000 中斷引腳電平設定

DM9000的34(INT)引腳為中斷輸出引腳,預設情況下該引腳高電平有效。可以通過設定DM9000 的 20(EECK)引腳來改變 INT 的有效電平,當 EECK 拉高以後, INT 低電平有效,否則的話 INT 是高電平有效的。開發闆上 R66 電阻為 EECK 的上拉電阻,是以開發闆上 DM9000 的 INT 引腳是低電平有效的。

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

2.3 DM9000 資料位寬設定

前面我們提了一下 DM9000 支援 8 位和 16 位兩種資料位寬,可以通過 DM9000 的 21(EECS)引腳設定其資料位寬,當 EECS 上拉的時候 DM9000 選擇 8 位資料位寬,否則的話選擇 16 位資料位寬。開發闆上的 R65 電阻為 EECS 的上拉電阻,但是此電阻并未焊接! DM9000 晶片的資料位寬為 16 位。

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
2.4 DM9000寄存器表
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

2.5 DM9000常用寄存器介紹

NCR、 NSR、 TCR、 RCR、 FCTR、 BPTR、 TCR2、 ISR、 IMR。

NCR(網絡控制寄存器)寄存器

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

FCOL:強制沖突模式,用于檢測。

FDX:内部 PHY 全雙工模式。

LBK:回環模式(LoopBack)

00 正常;

01 MAC 内部回環;

10 内部 PHY100M 模式數字回環;

11 保留;

RST:置 1 軟體複位, 10us 後自動清零。

NSR 寄存器(網絡狀态寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

SPEED:網絡速度,在使用内部 PHY 情況下,0 表示 100Mbps,1 表示 100Mbps,當 LINKST=0時,此位無意義。

LINKST:連接配接狀态, 0 為連接配接失敗, 1 位已連接配接。

TX2END: TX(發送)資料包 2 完成标志,讀取或寫 1 将清零該位。

TX1END: TX(發送)資料包 1 完成标志,讀取或寫 1 将清零該位。

RXOV: RX(接收)FIFO 溢出标志。

TCR 寄存器(發送控制寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

TJDIS: Jabber 傳輸禁止。

1,禁止 Jabber 傳輸定時器(2048 位元組)。

0,使能。

EXCECM:嚴重沖突模式控制

0,當沖突計數多于 15 則終止本次資料包。

1,始終嘗試發送本次資料包。

PAD_DIS2:禁止為資料包 II 添加填充。

CRC_DIS2:禁止為資料包 II 添加 CRC 校驗。

PAD_DIS1:禁止為資料包 I 添加填充。

CRC_DIS1:禁止為資料包 I 添加 CRC 校驗。

TXREQ: TX(發送)請求,發送完成後自動清零該位

RCR 寄存器(發送控制寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

WTDIS:看門狗定時器(2048 位元組)禁止。

1,進制

0,使能

DIS_LONG:丢棄長資料包, 1,丢棄資料包長度超過 1522 位元組的資料包。

DIS_CRC:丢棄 CRC 校驗錯誤資料包。

ALL:允許廣播。

RUNT:允許小于最小長度的資料包。

PRMSC:各種模式。

RXEN:接收使能。

FCTR 寄存器(流控制門檻值寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

HWOT:RX FIFO 緩存高位溢出門限

當 RX SRAM 空閑空間小于該門限值時則發送一個暫停時間為 FFFFH 的暫停包,若該值為 0,則無接收控件。 1=1k 位元組,預設值為 3H,即 3K 位元組空閑空間,不要超過 S RAM 大小。

LWOT:RX FIFO 緩存低位溢出門限當 RX SRAM 空閑空間大于該門限值時則發送一個暫停時間為 0000H 的暫停包。

當溢出門限最高值的暫停包發送之後,溢出門限最低值的暫停包才有效,預設值為 8K,不要超過 SRAM 大小。

BPTR 寄存器(背壓門檻值寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

BPHW:背壓門檻值最高值當接收 SRAM 空閑空間低于該門檻值,則 MAC 将産生一個擁擠狀态, 1=1k 位元組。預設值為 3H,即 3K 位元組空閑空間,不要超過 SRAM 大小。

JPT:擁擠狀态時間,模式為 200us, JPT 值與其對應的擁擠狀态時間表

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
TCR2 寄存器(發送控制寄存器 2)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

LED: LED 模式

1,設定 LED 引腳為模式 1

0,設定 LED 引腳為模式 0 或根據 EEPROM 的設定。

RLCP:重試沖突延時資料包, 1 重新發送有沖突延遲的資料包。

DTU: 1 禁止重新發送“underruned”資料包。

ONEPM:單包模式。

1,發送完成前發送一個資料包的指令能被執行。

0,發送完成前發送最多兩個資料包的指令能被執行。

IFGS:幀間間隔設定。

0XXX 為 96bit, 1000 為 64bit, 1001 為 72bit

1010 為 80bit, 1011 為 88bit, 1100 為 96bit

1101 為 104bit, 1110 位 112bit, 1111 為 120bit

ISR 寄存器(中斷狀态寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

IOMODE: 0,16 位模式; 1,8 位模式。

LNKCHG:連接配接狀态改變。

UDRUN:發送“Underrun”

ROO:接收溢出計數器溢出

ROS:接收溢出。

PT:資料包發送。

PR:資料包接收。

IMR 寄存器(中斷狀态寄存器)

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

PAR:使能 SRAM 的讀/寫指針在指針位址超過 SRAM 的大小時自動跳回起始位置。需要驅動程式設定該位,若設定該位, REG_F5 将自動置為 0XH。

LNKCHGI:使能連接配接狀态改變中斷。

UDRUNI:使能發送“Underrun”中斷。

ROOI:使能接收溢出計數器溢出中斷。

ROI:使能接收溢出中斷。

PTI:使能資料包發送中斷。

PRI:使能資料包接收中斷。

2.6 DM9000 直接記憶體通路控制(DMAC)

DM9000 直接記憶體通路控制(DMAC)

DM9000 支援 DMA 方式簡化對内部存儲器的通路。在我們程式設計寫好内部存儲器位址後,就可以用一個讀/寫指令僞指令把目前資料加載到内部資料緩沖區,這樣,内部存儲器指定位置就可以被讀/寫指令寄存器通路。存儲器位址将會自動增加,增加的大小與目前總線操作模式相同(比如:8-bit、 16-bit 或 32-bit),接着下一個位址資料将會自動加載到内部資料緩沖區。要注意的是在連續突發式第一次通路的資料應該被忽略,因為,這個資料是最後一次讀寫指令的内容。内部存儲器空間大小 16K 位元組。前 3K 位元組單元用作發送包的緩沖區,其他 13K 位元組用作接收包的緩沖區。是以在寫存儲器操作時,如果位址越界(即超出 3K 空間),在 IMR 寄存器 bit7 置位的情況下,位址指針将會傳回到存儲器 0 位址處。同樣,在讀存儲器操作時,如果位址越界(即超出 16K 空間),在 IMR 寄存器 bit7 置位的情況下,位址指針将會傳回到存儲器 0x0C00 位址處。

DM9000 資料包發送

DM9000 有兩個發送資料包: index1 和 index2,同時存儲在 TX SRAM 中。發送控制寄存器(02h)控制循環備援校驗碼(CRC)和填充(pads)的插入,其狀态分别記錄在發送狀态寄存器I(03H)和發送狀态寄存器 II(04H)中。發送器的起始位址為 0x00H,在軟體或硬體複位後,預設的資料發送包為 index1。首先,使用 DMA 端口将資料寫 TX SRAM 中,然後,在發送資料包長度寄存器中把資料位元組數寫入位元組計數寄存器。置位發送控制寄存器的 bit0 位,則 DM9000 開始發送 index1 資料包。在 index1資料包發送結束之前,資料發送包 index2 被移入 TX SRAM 中。在 index1 資料包發送結束後,将 index2 資料位元組數寫入位元組計數寄存器中,然後,置位發送控制寄存器的 bit0 位,則 index2資料包開始發送。以此類推,後面的資料包都以此方式進行發送。

DM9000 資料包接收

RX SRAM 是一個環形資料結構。在軟體或硬體複位後, RX SRAM 的起始位址為 0X0C00。每個接收資料包都包含有 CRC 校驗域,資料域,以及緊跟其後的 4 位元組標頭域。 4 位元組標頭格式為: 01h、狀态、 BYTE_COUNT 低、 BYTE_COUNT 高。請注意:每個接收包的起始位址處在适當的位址邊界,這取決于目前總線操作模式(8bit 或者 16bit)

2.7 DM9000原理圖介紹

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

各信号線描述如下:

PWRST: DM9000 複位信号。

CS: DM9000 的片選信号。

WR(IOW): 處理器寫指令。

RD(IOR): 處理器讀指令。

CMD: 指令/資料标志, 0,讀寫指令; 1,讀寫資料。

SD0~SD15: 16 位雙向資料線。

信号線對應的GPIO口對應關系

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
FSMC接口框圖
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

DM9000網卡接在FSMC的第2塊上,資料線位址: 0x64000000

PA7位址線作為指令與資料線切換引腳。

外接16位寬度存儲器:HADDR[25:1]  FSMC_A[24:0]

外接8位寬度存儲器: HADDR[25:0]  FSMC_A[25:0]

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

2.8 DM9000時序圖介紹

IOR和IOW是DM9000的讀寫選擇引腳,低電平有效,即低電平時進行讀(IOR)寫(IOW)操作;AEN是晶片選通引腳,低電平有效,該引腳為低時才能進行讀寫操作;CMD的指令/資料切換引腳,低電平時讀寫指令操作,高電平時讀寫資料操作。

讀時序:

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
寫時序:
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

三、LWIP(TCP/IP)網絡協定棧介紹

根據以太網幀頭攜帶的上層協定類型值傳遞資料。

以太網幀格式定義:

目的MAC位址 源MAC位址           類型/長度                  資料                     校驗

  6位元組          6位元組               2位元組                   46-1500位元組              4位元組

ip:0x0800

ARP:0x0806

最大幀長1518位元組 最小位元組64位元組

3.1 LWIP介紹

lwip是瑞典計算機科學院網絡嵌入式系統小組(SICS)的Adam Dunkels(亞當·鄧克爾) 開發的一個小型開源的TCP/IP協定棧。實作的重點是在保持TCP協定主要功能的基礎上減少對RAM 的占用。

LwIP是Light Weight (輕型)IP協定,有無作業系統的支援都可以運作。LwIP實作的重點是在保持TCP協定主要功能的基礎上減少對RAM 的占用,它隻需十幾KB的RAM和40K左右的ROM就可以運作,這使LwIP協定棧适合在低端的嵌入式系統中使用。lwip提供三種API:1)RAW API  2)(NETCONN)lwip API  3)BSD API。

RAW 程式設計接口使得程式效率高,但是需要對 LWIP 有深入的了解,而且不适合大資料量等場合。 NETCONN 程式設計接口,使用 NETCONN API 時需要有作業系統的支援。

RAW API把協定棧和應用程式放到一個程序裡邊,該接口基于函數回調技術,使用該接口的應用程式可以不用進行連續操作。不過,這會使應用程式編寫難度加大且代 碼不易被了解。為了接收資料,應用程式會向協定棧注冊一個回調函數。該回調函數與特定的連接配接相關聯,當該關聯的連接配接到達一個資訊包,該回調函數就會被協定 棧調用。這既有優點也有缺點。優點是既然應用程式和TCP/IP協定棧駐留在同一個程序中,那麼發送和接收資料就不再産生程序切換。主要缺點是應用程式不 能使自己陷入長期的連續運算中,這樣會導緻通訊性能下降,原因是TCP/IP處理與連續運算是不能并行發生的。這個缺點可以通過把應用程式分為兩部分來克 服,一部分處理通訊,一部分處理運算。

lwip API把接收與處理放在一個線程裡面。這樣隻要處理流程稍微被延遲,接收就會被阻塞,直接造成頻繁丢包、響應不及時等嚴重問題。是以,接收與協定處理必須 分開。LwIP的作者顯然已經考慮到了這一點,他為我們提供了 tcpip_input() 函數來處理這個問題, 雖然他并沒有在 rawapi 一文中說明。 講到這裡,讀者應該知道tcpip_input()函數投遞的消息從哪裡來的答案了吧,沒錯,它們來自于由底層網絡驅動組成的接收線程。我們在編寫網絡驅動時, 其接收部分以任務的形式建立。 資料包到達後, 去掉以太網標頭得到IP包, 然後直接調用tcpip_input()函數将其 投遞到mbox郵箱。投遞結束,接收任務繼續下一個資料包的接收,而被投遞得IP包将由TCPIP線程繼續處理。這樣,即使某個IP包的處理時間過長也不 會造成頻繁丢包現象的發生。這就是lwip API。

BSD API提供了基于open-read-write-close模型的UNIX标準API,它的最大特點是使應用程式移植到其它系統時比較容易,但用在嵌入式系統中效率比較低,占用資源多。這對于我們的嵌入式應用有時是不能容忍的

lwIP協定棧主要關注的是怎麼樣減少記憶體的使用和代碼的大小,這樣就可以讓lwIP适用于資源有限的小型平台例如嵌入式系統。為了簡化處理過程和記憶體要求,lwIP對API進行了裁減,可以不需要複制一些資料。

其主要特性如下:

(1)支援多網絡接口下的IP轉發;

(2)支援ICMP協定;

(3)包括實驗性擴充的UDP(使用者資料報協定);

(4)包括阻塞控制、RTT 估算、快速恢複和快速轉發的TCP(傳輸控制協定);

(5)提供專門的内部回調接口(Raw API),用于提高應用程式性能;

(6)可選擇的Berkeley接口API (在多線程情況下使用) 。

(7)在最新的版本中支援ppp

(8) 新版本中增加了的IP fragment(IP分片)的支援.

(9) 支援DHCP協定,動态配置設定ip位址.

3.2 幾種開源TCPIP協定概述

1、BSD TCP/IP協定棧

   BSD棧曆史上是商業棧的起點,大多數專業TCP/IP棧(VxWorks内嵌的TCP/IP棧)是BSD棧派生的。這是因為BSD棧在BSD許可協定下提供了這些專業棧的雛形,BSD許用證允許BSD棧以修改或未修改的形式結合這些專業棧的代碼而無須向建立者付版稅。同時,BSD也是許多TCP/IP協定中的創新(如廣域網中餓擁塞控制和避免)的點。

2、uC/IP

   uC/IP是由Guy Lancaster編寫的一套基于uC/OS且開放源碼的TCP/IP協定棧,亦可移植到作業系統,是一套完全免費的、可供研究的TCP/IP協定棧,uC/IP大部分源碼是從公開源碼BSD釋出站點和KA9Q(一個基于DOS單任務環境運作的TCP/IP協定棧)移植過來。uC/IP具有如下一些特點:帶身份驗證和報頭壓縮支援的PPP協定,優化的單一請求/回複互動過程,支援IP/TCP/UDP協定,可實作的網絡功能較為強大,并可裁減。UCIP協定棧被為一個帶最小化使用者接口及可應用串行鍊路網絡子產品。根據采用CPU、編譯器和系統所需實作協定的多少,協定棧需要的代碼容量空間在30-60KB之間。

http://ucip.sourceforge.net

3、LwIP

    LwIP是瑞士計算機科學院(Swedish Institute of Computer Science)的Adam Dunkels等開發的一套用于嵌入式系統的開放源代碼TCP/IP協定棧。LwIP的含義是Light Weight(輕型)IP協定,相對于uip。LwIP可以移植到作業系統上,也可以在無作業系統的情況下獨立運作。LwIP TCP/IP實作的重點是在保持TCP協定主要功能的基礎上減少對RAM的占用,一般它隻需要幾十K的RAM和40K左右的ROM就可以運作,這使LwIP協定棧适合在低端嵌入式系統中使用。LwIP的特性如下:支援多網絡接口下的IP轉發,支援ICMP協定 ,包括實驗性擴充的的UDP(使用者資料報協定),包括阻塞控制,RTT估算和快速恢複和快速轉發的TCP(傳輸控制協定),提供專門的内部回調接口(Raw API)用于提高應用程式性能,并提供了可選擇的Berkeley接口API。

http://www.sics.se/~adam/lwip/ http://savannah.nongnu.org/projects/lwip/

4、uIP

   uIP是專門為8位和16位控制器設計的一個非常小的TCP/IP棧。完全用C編寫,是以可移植到各種不同的結構和作業系統上,一個編譯過的棧可以在幾KB ROM或幾百位元組RAM中運作。uIP中還包括一個HTTP伺服器作為服務内容。許可:BSD許用證

http://www.sics.se/~adam/uip/

uIP是一個完全由C語言編寫的開源軟體, 它的文檔和源代碼可用于商業和非商業用途, 它已經移植到了大部分的8位微控制器, 而且已在很多的嵌入式産品和項目中使用.

5、TinyTcp

  TinyTcp 棧是TCP/IP的一個非常小和簡單的實作,它包括一個FTP客戶。TinyTcp是為了燒入ROM設計的并且現在開始對大端結構似乎是有用的(初始目标是68000晶片)。TinyTcp也包括一個簡單的以太網驅動器用于3COM多總線卡

http://ftp.ecs.soton.ac.uk/pub/elks/utils/tiny-tcp.txt

選擇一個開源協定棧可以從四個方面來考慮:

 是否提供易用的底層硬體API,即與硬體平台的無關性;

協定棧需要調用的系統函數接口是否容易構造,另一個對于應用支援程度。

最關鍵的是占用的系統資源是否在可接受範圍内,有裁減優化的空間否? 其中,

BSD 棧可完整實作TCP/IP協定,但代碼龐大,70KB-150KB之間,裁減優化有難度,

uIP和TinyTcp代碼容量小巧,實作功能精簡,限制了在一些較高要求場合下的應用,如可靠性與大容量資料傳輸。

LwIP和uC/IP是同量級别的兩個開源協定棧,兩者代碼容量和實作功能相似,LwIP沒有作業系統針對性,它将協定棧與平台相關的代碼抽象出來,使用者如果要移植到自己的系統,需要完成該部分代碼的封裝,并為網絡應用支援提供了API接口的可選性。

uC/IP協定最初是針對uC/OS設計,為友善使用者移植實作,同樣也抽象了協定棧與平台相關代碼,但是協定棧所需調用的系統函數大多參照uC/OS核心函數原型設計,并提供了協定棧的函數,友善使用者參考,其不足在于該協定棧對網絡應用支援不足。

根據以上分析,從應用和開發的角度看,似乎LWIP更得到了網上很多朋友使用的青睐;uC/IP在文檔支援與軟體更新管理上有很多不足,但是它最初是針對UC/OS而設計,如果選用UC/OS作為軟體基礎的話,在系統函數構造方面有優勢。當然你選擇其他作業系統的話,可參照OS_NULL檔案夾下的檔案修改。 以上的這些開源協定棧也并非免費,拿來就可以用,據我所知,UC/OS的母公司推出UC/OS-TCP/IP花了6人*2年的工作量,國内某公司使用LWIP作為移植的參照,花了4-5人*2年的工作量來測試與優化協定,使用商用TCP/IP棧的高費用就不足為奇了。 作為廣大的愛好者學習而言,如果隻是跑跑原型,實驗一下效果,以上的幾種開源協定棧都提供了測試的例子,應該是不錯的選擇。

終上所述:LWIP可優先考慮,參考的資料較多

四、LWIP協定棧移植

4.1  LWIP源碼下載下傳

源碼下載下傳位址:

http://ftp.yzu.edu.tw/nongnu/lwip/
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
下載下傳LWIP1.4.1版本、并下載下傳contrib-1.4.1版本。
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
4.2 将LWIP源碼加入到工程目錄
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
4.3 配置lwipopts.h檔案
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

4.4 修改ethernetif.c檔案

ethernetif.c檔案預設是不編譯的,該檔案是網卡底層接口的模闆檔案,需要根據修改網卡發送接口和接收接口。

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

4.5 修改sys_arch.c檔案

修改sys_arch.c隻是留下sys_now()函數,其他代碼全部删除掉。删除windows.h頭檔案。

sys_now()函數用于傳回一個32位的系統時鐘,機關是ms。沒有作業系統的情況下,使用定時器提供時間即可。

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

4.6 建立lwip_config.c檔案

在LWIP/app目錄下建立一個lwip_config.c/lwip_config.h檔案。用于編寫動态IP位址配置設定處理代碼,和LWIP事物輪詢、初始化代碼。

編寫一個LWIP初始化配置函數,向LWIP協定棧添加一個新的網卡裝置

1./*
函數功能: LWIP協定棧初始化
*/
void lwip_config_init(void)
{
    ip_addr_t ipaddr;   //IP位址
    ip_addr_t netmask;  //子網路遮罩
    ip_addr_t gw;       //網關
    
    //全部初始化為0  -因為使用了動态IP位址配置設定
    ipaddr.addr=0;
    netmask.addr=0;
    gw.addr=0;
    
    /*1. 初始化LWIP核心*/
    lwip_init();
    /*2. 向網卡清單中添加一個網絡裝置*/
    netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&ethernet_input);
    /*3. 開啟DHCP服務 */
    dhcp_start(&lwip_netif);
    /*4. 設定netif為預設網口*/
    netif_set_default(&lwip_netif);
    /*5. 打開netif網口*/
    netif_set_up(&lwip_netif);  
}      

編寫LWIP事物輪詢函數與DHCP處理函數

u32 TCPTimer=0;             //TCP查詢計時器
u32 ARPTimer=0;             //ARP查詢計時器
u32 DHCPfineTimer=0;        //DHCP精細處理計時器
u32 DHCPcoarseTimer=0;      //DHCP粗糙處理計時器
u32 DHCP_State=1;          //儲存DHCP狀态 1表示沒有配置設定成功 0表示配置設定成功
 
/*
函數功能:  LWIP輪詢任務
*/
void lwip_periodic_handle()
{
    //每250ms調用一次tcp_tmr()函數
    if(TCPTimer >= TCP_TMR_INTERVAL)
    {
        TCPTimer=0;
        tcp_tmr();  //處理TCP協定請求
    }
    
    //ARP每5s周期性調用一次
    if(ARPTimer >= ARP_TMR_INTERVAL)
    {
        ARPTimer=0;
        etharp_tmr();
    }
    //每500ms調用一次dhcp_fine_tmr()
    if(DHCPfineTimer >= DHCP_FINE_TIMER_MSECS)
    {
        DHCPfineTimer=0;
        dhcp_fine_tmr(); //動态IP位址配置設定的事物處理
        if(DHCP_State)lwip_dhcp_process_handle();  //DHCP處理
    }
 
    //每60s執行一次DHCP粗糙處理
    if(DHCPcoarseTimer >= DHCP_COARSE_TIMER_MSECS)
    {
        DHCPcoarseTimer=0;
        dhcp_coarse_tmr();
    }  
}
 
//lwip控制結構體
typedef struct  
{
    u8 remoteip[4]; //伺服器主機IP位址 
    u8 ip[4];        //本機IP位址
    u8 netmask[4];  //子網路遮罩
    u8 gateway[4];  //預設網關的IP位址
}__lwip_dev;
 
extern __lwip_dev lwipdev;  //lwip資訊結構體
 
__lwip_dev lwipdev; //lwip資訊結構體
 
 
/*
函數功能: DHCP處理任務
*/
void lwip_dhcp_process_handle(void)
{
    u32 ip=0,netmask=0,gw=0;
    ip=lwip_netif.ip_addr.addr;         //讀取新IP位址
    netmask=lwip_netif.netmask.addr;   //讀取子網路遮罩
    gw=lwip_netif.gw.addr;           //讀取預設網關
    
    if(ip!=0)           //正确擷取到IP位址的時候
    {
        DHCP_State=0; //表示配置設定成功
      //解析出通過DHCP擷取到的IP位址
        lwipdev.ip[3]=(uint8_t)(ip>>24); 
        lwipdev.ip[2]=(uint8_t)(ip>>16);
        lwipdev.ip[1]=(uint8_t)(ip>>8);
        lwipdev.ip[0]=(uint8_t)(ip);
        printf("動态配置設定
IP:..............%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
        //解析通過DHCP擷取到的子網路遮罩位址
        lwipdev.netmask[3]=(uint8_t)(netmask>>24);
        lwipdev.netmask[2]=(uint8_t)(netmask>>16);
        lwipdev.netmask[1]=(uint8_t)(netmask>>8);
        lwipdev.netmask[0]=(uint8_t)(netmask);
        printf("子網掩
碼............%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
        //解析出通過DHCP擷取到的預設網關
        lwipdev.gateway[3]=(uint8_t)(gw>>24);
        lwipdev.gateway[2]=(uint8_t)(gw>>16);
        lwipdev.gateway[1]=(uint8_t)(gw>>8);
        lwipdev.gateway[0]=(uint8_t)(gw);
        printf("網
關.........%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
    }
}      

4.7 配置一個定時器提供時間基準

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

4.8 初始化lwip動态擷取IP位址

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

4.9 LWIP記憶體配置選擇

LWIP可以選擇使用系統庫自帶的函數malloc/free進行管理空間,也可以使用lwip自己的記憶體管理函數進行管理,源碼預設就是使用lwip自己的記憶體管理方法,就是在初始化記憶體的時候定義一個數組,數組的大小在lwipopts.h檔案MEM_SIZE宏定義的。

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)

五、LWIP函數使用(RAW程式設計接口)

5.1  LWIP初始化配置

STM32入門開發: LWIP網絡協定棧移植(網卡采用DM9000)
ip_addr_t ipaddr;   //IP位址
ip_addr_t netmask;  //子網路遮罩
ip_addr_t gw;       //網關
 
//全部初始化為0  -因為使用了動态IP位址配置設定
ipaddr.addr=0;
netmask.addr=0;
gw.addr=0;
 
/*1. 初始化LWIP核心*/
lwip_init();
/*2. 向網卡清單中添加一個網絡裝置*/
netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&ethernet_input);
/*3. 開啟DHCP服務 */
dhcp_start(&lwip_netif);
/*4. 設定netif為預設網口*/
netif_set_default(&lwip_netif);
/*5. 打開netif網口*/
netif_set_up(&lwip_netif);        

5.2 LWIP輪詢函數處理

LWIP輪詢期間:

1. 推薦每250ms周期性調用一次tcp_tmr()函數,處理TCP協定請求。

   逾時時間LWIP使用TCP_TMR_INTERVAL宏進行了定義。

2. 推薦每5s周期性調用一次etharp_tmr()函數,清除ARP表中過期的資料。

   逾時時間LWIP使用ARP_TMR_INTERVAL宏進行了定義。

3. (如果開啟了動态IP配置設定功能)推薦每500ms周期性調用一次dhcp_fine_tmr()函數,處理DHCP動态IP位址配置設定請求。  如果IP位址擷取成功,将會放在初始化時注冊的網絡裝置結構體裡(struct netif)。

   逾時時間LWIP使用DHCP_FINE_TIMER_MSECS宏進行了定義。

4. (如果開啟了動态IP配置設定功能)推薦每60s調用一次dhcp_coarse_tmr()函數,用于檢查DHCP租約時間,并進行重新綁定。

    逾時時間LWIP使用DHCP_COARSE_TIMER_MSECS宏進行了定義。

5. 在LWIP運作期間,當網卡收到資料時,還需要調用ethernetif_input函數讀取網卡資料。

在函數ethernetif_input()主要完成兩個工作

1、調用low_level_input(); 讀取網卡實際資料。

2、調用netif->input();

是以,為了能夠實時的讀取資料,需要最快的速度輪詢調用ethernetif_input函數。

5.3  LWIP程式設計RAW接口函數

tcp_new() 建立一個 TCP 的 PCB 控制塊
tcp_bind() 為 TCP 的 PCB 控制塊綁定一個本地 IP 位址和端口号
tcp_listen() 開始 TCP 的 PCB 監聽
tcp_accept() 控制塊 accept字段注冊的回調函數,偵聽到連接配接時被調用
tcp_accepted() 通知 LWIP 協定棧一個 TCP 連接配接被接受了
tcp_conect() 連接配接遠端主機
tcp_write() 構造一個封包并放到控制塊的發送緩沖隊列中
tcp_sent() 控制塊 sent 字段注冊的回調函數,資料發送成功後被回調
tcp_output() 将發送緩沖隊列中的資料發送出去
tcp_recv()控制塊 recv 字段注冊的回調函數,當接收到新資料時被調用
tcp_recved()當程式處理完資料後一定要調用這個函數,通知核心更新接收視窗
tcp_poll() 控制塊 poll 字段注冊的回調函數,該函數周期性調用
tcp_close() 關閉一個 TCP 連接配接
tcp_err() 控制塊 err 字段注冊的回調函數,遇到錯誤時被調用
tcp_abort() 中斷 TCP 連接配接      

5.4 建立TCP伺服器示例

下面示範了TCP伺服器建立步驟,測試伺服器是否正常。

u8 TCP_Create(u16_t port)
{
    struct tcp_pcb *pcb=NULL;
    pcb=tcp_new(); //建立套接字
    if(pcb==NULL)return 1;
    if(tcp_bind(pcb,IP_ADDR_ANY,port)!=ERR_OK)return 2; //綁定端口号
    pcb=tcp_listen(pcb); //開始監聽
    tcp_accept(pcb,TCP_accept);//等待連接配接
    return 0;
}
 
err_t TCP_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    u8 addr[4];
    //tcp_setprio(newpcb, TCP_PRIO_MIN);  設定優先級
    printf("有新的用戶端連接配接!\n");
    addr[3]=(newpcb->remote_ip.addr>>24)&0xFF;
    addr[2]=(newpcb->remote_ip.addr>>16)&0xFF;
    addr[1]=(newpcb->remote_ip.addr>>8)&0xFF;
    addr[0]=(newpcb->remote_ip.addr>>0)&0xFF;
    printf("ip位址:%d.%d.%d.%d\n",addr[0],addr[1],addr[2],addr[3]);
    printf("端口号:%d\n",newpcb->remote_port);
    printf("目前隊列剩餘位元組:%d\n",tcp_sndbuf(newpcb));
    tcp_write(newpcb,"1234567890",10,1); //将要發送的資料送出到發送隊列(不會立即發送)
    tcp_output(newpcb);                                  //提示系統現在,發送資料
    tcp_sent(newpcb,TCP_sent);                   //發送成功的回調函數
    tcp_recv(newpcb,TCP_recv);
    return ERR_OK;
}
 
err_t TCP_sent(void *arg, struct tcp_pcb *tpcb,u16_t len)
{
    printf("成功發送:%d位元組\n",len);
    //tcp_close(tpcb); //關閉用戶端連接配接
    return ERR_OK;
}
 
u8 rx_buff[1024];
err_t TCP_recv(void *arg, struct tcp_pcb *tpcb,struct pbuf *p, err_t err)
{
    u32 rx_cnt=0;
    struct pbuf *q;
    memset(rx_buff,0,sizeof(rx_buff));
    if(p==NULL)
    {
      printf("用戶端已經斷開連接配接!\n");
    }
    else
    {
      for(q=p;q!=NULL;q=q->next)
      {
            memcpy(rx_buff+rx_cnt,q->payload,q->len);
            rx_cnt+=q->len;
      }
       pbuf_free(p); //釋放PUFF
        printf("成功接收:%d位元組\n",rx_cnt);
        printf("收到的資料=%s\n",rx_buff);
    }
    return ERR_OK;
}      

5.5 建立TCP用戶端示例

u8 TCP_Create(u16_t port)
{
    struct tcp_pcb *pcb=NULL;
    pcb=tcp_new(); //建立套接字
    ip_addr_t ipaddr;
    if(pcb==NULL)return 1;
    IP4_ADDR(&ipaddr,192,168,31,54); //在ip_addr.h裡定義
    tcp_connect(pcb,&ipaddr,port,TCP_connected);
    return 0;
}
 
 
err_t TCP_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
    u8 addr[4];
    //tcp_setprio(newpcb, TCP_PRIO_MIN);  設定優先級
    printf("伺服器連接配接成功!\n");
    addr[3]=(tpcb->remote_ip.addr>>24)&0xFF;
    addr[2]=(tpcb->remote_ip.addr>>16)&0xFF;
    addr[1]=(tpcb->remote_ip.addr>>8)&0xFF;
    addr[0]=(tpcb->remote_ip.addr>>0)&0xFF;
    printf("伺服器ip位址:%d.%d.%d.%d\n",addr[0],addr[1],addr[2],addr[3]);
    printf("伺服器端口号:%d\n",tpcb->remote_port);
    printf("目前隊列剩餘位元組:%d\n",tcp_sndbuf(tpcb));
    tcp_write(tpcb,"1234567890",10,1); //将要發送的資料送出到發送隊列(不會立即發送)
    tcp_output(tpcb); //提示系統現在,發送資料
    tcp_sent(tpcb,TCP_sent); //發送成功的回調函數
    tcp_recv(tpcb,TCP_recv);
    return ERR_OK;
}
 
 
err_t TCP_sent(void *arg, struct tcp_pcb *tpcb,u16_t len)
{
    printf("成功發送:%d位元組\n",len);
    //tcp_close(tpcb); //關閉用戶端連接配接
    return ERR_OK;
}
 
 
u8 rx_buff[1024];
err_t TCP_recv(void *arg, struct tcp_pcb *tpcb,struct pbuf *p, err_t err)
{
    u32 rx_cnt=0;
    struct pbuf *q;
    memset(rx_buff,0,sizeof(rx_buff));
    if(p==NULL)
    {
        printf("伺服器已經斷開連接配接!\n");
    }
    else
    {
        for(q=p;q!=NULL;q=q->next)
        {
              memcpy(rx_buff+rx_cnt,q->payload,q->len);
              rx_cnt+=q->len;
        }
        printf("成功接收:%d位元組\n",rx_cnt);
        printf("收到的資料=%s\n",rx_buff);
    }
    return ERR_OK;
}      

繼續閱讀