24.1 DM9000概述
DM9000是一款完全集成的、性價比高、引腳數少、帶有通用處理器接口的單芯片快速以太網控制器。一個10/100MPHY和4K雙字的SRAM,它是出于低功耗和高性能目的設計的,其IO端口支持3.3V與5V電壓。DM9000為適應各種處理器提供了8位、16位數據接口訪問內部存儲器。DM9000協議層接口完全支持使用10Mbps下3類、4類、5類非屏蔽雙絞線和100Mbps下5類非屏蔽雙絞線,這是完全遵照IEEE802.3u標準。它的自動協商功能將自動完成DM9000配置以使其發揮出最佳性能,它還支持IEEE802.3x全雙工流量控制,DM9000的特性如下:
(1)支持處理器接口:I/O口的字節或字命令對內部存儲器進行讀寫操作
(2)集成自適應(AUTO-MDIX)10/100M收發器
(3)半雙工模式流量控制的背壓模式
(4)IEEE802.3x全雙工模式的流量控制
(5)支持喚醒幀,鏈路狀態改變和遠程喚醒
(6)內置16K字節SRAM
(7)內置3.3V至2.5V的調節器
(8)支持IP/TCP/UDP的校驗和生成以及校驗支持MAC接口
(9)支持自動加載EEPROM里面生產商ID和產品ID
(10)可選EEPROM配置
(11)超低功耗模式
A.功率降低模式(電纜偵測)
B.掉電模式
C.可選擇1:1或1.25:1變壓比例降低額外功率
(12)兼容3.3V和5.0V輸入輸出電壓
DM9000的結構框圖如下圖所示。
1、DM9000中斷引腳電平設置
DM9000的INT引腳為中斷輸出引腳,默認情況下該引腳高電平有效。可以通過設置DM9000的EECK引腳來改變INT的有效電平,當EECK拉高以后,INT低電平有效,否則的話INT是高電平有效的。
2、DM9000數據位寬設置
DM9000支持8位和16位兩種數據位寬,可以通過DM9000的EECS引腳設置其數據位寬,當EECS上拉的時候DM9000選擇8位數據位寬,否則的話選擇16位數據位寬。一般設置默認位寬為16位。
3、DM9000直接內存訪問控制DMAC
DM9000支持DMA方式以簡化對內部存儲器的訪問。在我們編程寫好內部存儲器地址后,就可以用一個讀/寫命令偽指令把當前數據加載到內部數據緩沖區,這樣,內部存儲器指定位置就可以被讀/寫命令寄存器訪問。存儲器地址將會自動增加,增加的大小與當前總線操作模式相同(8-bit或16-bit),接著下一個地址數據將會自動加載到內部數據緩沖區。內部存儲器空間大小為16K字節。前3K字節單元用作發送包的緩沖區,其他13K字節用作接收包的緩沖區。所以在寫存儲器操作時,如果地址越界(即超出3K空間),在IMR寄存器bit7置位的情況下,地址指針將會返回到存儲器0地址處。同樣,在讀存儲器操作時,如果地址越界(即超出16K空間),在IMR寄存器bit7置位的情況下,地址指針將會返回到存儲器0C00H地址處。
4、DM9000數據包發送
DM9000有兩個發送數據包:index1和index2,同時存儲在TXSRAM中。發送控制寄存器控制循環冗余校驗碼和填充的插入,其狀態分別記錄在發送狀態寄存器1和發送狀態寄存器2中。發送器的起始地址為00H,在軟件或硬件復位后,默認的數據發送包為index1。首先,將數據寫入TXSRAM中,然后,在發送數據包長度寄存器中把數據字節數寫入字節計數寄存器。置位發送控制寄存器(02H)的bit0位,則DM9000開始發送index1數據包。在index1數據包發送結束之前,數據發送包index2被移入TXSRAM中。在index1數據包發送結束后,將index2數據字節數寫入字節計數寄存器中,然后,置位發送控制寄存器(02H)的bit0位,則index2數據包開始發送。以此類推,后面的數據包都以此方式進行發送。
5、DM9000數據包接收
RXSRAM是一個環形數據結構。在軟件或硬件復位后,RXSRAM的起始地址為C00H。每個接收數據包都包含有CRC校驗域,數據域,以及緊跟其后的4字節包頭域。4字節包頭格式為:01H、狀態、BYTE_COUNT低、BYTE_COUNT高。請注意:每個接收包的起始地址處在適當的地址邊界,這取決于當前總線操作模式(8bit或者16bit)。DM9000是通過16位數據總線,掛在STM32的FSMC上面,DM9000的片選由FSMC_NE2控制,CMD則由FSMC_A7控制。這個連接方法,類似于TFTLCD顯示,總共用到了22個IO口。
24.2 TCP/IP協議概述
TCP/IP中文名為傳輸控制協議/因特網互聯協議,又名網絡通訊協議,是Internet最基本的協議、Internet國際互聯網絡的基礎,由網絡層的IP協議和傳輸層的TCP協議組成。TCP/IP定義了電子設備如何連入因特網,以及數據如何在它們之間傳輸的標準。協議采用了4層的層級結構,每一層都呼叫它的下一層所提供的協議來完成自己的需求。通俗而言:TCP負責發現傳輸的問題,一有問題就發出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地。而IP是給因特網的每一臺聯網設備規定一個地址。
TCP/IP協議不是TCP和IP這兩個協議的合稱,而是指因特網整個TCP/IP協議族。從協議分層模型方面來講,TCP/IP由四個層次組成:網絡接口層、網絡層、傳輸層、應用層。OSI是傳統的開放式系統互連參考模型,該模型將TCP/IP分為七層:物理層、數據鏈路層(網絡接口層)、網絡層(網絡層)、傳輸層(傳輸層)、會話層、表示層和應用層(應用層)。TCP/IP模型與OSI模型對比如下表所示。
層級 | OSI模型 | TCP/IP模型 |
---|---|---|
1 | 應用層 | 應用層 |
2 | 表示層 | |
3 | 會話層 | |
4 | 傳輸層 | 傳輸層 |
5 | 網絡層 | 互聯層 |
6 | 數據鏈路層 | 鏈路層 |
7 | 物理層 |
我們剛才介紹的DM9000相當于鏈路層,而LWIP提供的就是網絡層的功能,應用層則需要用戶自己編寫代碼去實現。
24.3 LWIP概述
LWIP是瑞典計算機科學院(SICS)的AdamDunkels等開發的一個小型開源的TCP/IP協議棧。LWIP是輕量級IP協議,有無操作系統的支持都可以運行,LWIP實現的重點是在保持TCP協議主要功能的基礎上減少對RAM的占用,它只需十幾KB的RAM和40K左右的ROM就可以運行,這使LWIP協議棧適合在低端的嵌入式系統中使用。目前LWIP的最新版本是2.1.2。在這里采用比較常用的1.4.1版本進行介紹。
LWIP的主要特性如下:
(1)ARP協議:以太網地址解析協議
(2)IP協議:包括IPv4和IPv6,支持IP分片與重裝,支持多網絡接口下數據轉發
(3)ICMP協議:用于網絡調試與維護
(4)IGMP協議:用于網絡組管理,可以實現多播數據的接收
(5)UDP協議:用戶數據報協議
(6)TCP協議:支持TCP擁塞控制,RTT估計,快速恢復與重傳等
(7)提供三種用戶編程接口方式:raw/callbackAPI、sequentialAPI、BSD-stylesocketAPI
(8)DNS:域名解析
(9)SNMP:簡單網絡管理協議
(10)DHCP:動態主機配置協議
(11)AUTOIP:IP地址自動配置
(12)PPP:點對點協議,支持PPPoE
24.4 DM9000驅動編寫
24.4.1 DM9000寄存器介紹
(1) 網絡控制寄存器 :NCR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
- | WAKEEN | - | FCTOL | FDX | LBK | RST |
Bit 6:置位時將啟用喚醒功能。清除該位還將清除所有喚醒事件狀態,軟件復位后,該位將不受影響
0:開啟
1:關閉
Bit 4:強制沖突模式,用于檢測
Bit 3:內部PHY全雙工模式
Bit 2~Bit 1:回環模式
00:正常
01:MAC內部回環
10:內部PHY100M模式數字回環
11:保留
Bit 0:軟件復位,寫1啟動復位,10us后自動清零
(2) 網絡狀態寄存器 :NSR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
SPEED | LINKST | WAKEST | - | TXD2END | TX1END | RXOV | - |
Bit 7:網絡速度,在使用內部PHY的情況下,0表示100Mbps,1表示10Mbps,當LINKST=0時,該位無意義
Bit 6:連接狀態
0:連接失敗
1:連接成功
Bit 5:喚醒狀態,讀或者對該位寫1清0,復位后該位不受影響
0:產生喚醒事件
1:沒有喚醒事件
Bit 3:發送數據包2完成標志,讀或者對該位寫1清0
Bit 2:發送數據包1完成標志,讀或者對該位寫1清0
Bit 1:接收緩存溢出標志
(3) 發送控制寄存器 :TCR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
- | TJDIS | EXCECM | PAD_DIS2 | CRC_DIS2 | PAD_DIS1 | CRC_DIS1 | TXREQ |
Bit 6:Jabber傳輸禁止
0:進制Jabber傳輸定時器(2K字節)
1:使能
Bit 5:嚴重沖突模式檢測
0:當沖突計數多于15則終止本次數據包
1:始終嘗試發送本次數據包
Bit 4:禁止為數據包2添加填充
Bit 3:禁止為數據包2添加CRC校驗
Bit 2:禁止為數據包1添加填充
Bit 1:禁止為數據包1添加CRC校驗
Bit 0:發送請求,發送完成后自動清除該位
(4) 接收控制寄存器 :RCR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
- | WTDIS | DIS_LONG | DIS_CRC | ALL | RUNT | PRMSC | RXEN |
Bit 6:看門狗定時器進制
0:使能
1:禁止
Bit 5:丟棄包長度超過1522字節的數據包
Bit 4:丟棄CRC校驗錯誤的數據包
Bit 3:允許廣播
Bit 2:允許小于最小長度的數據包
Bit 1:各種模式
Bit 0:接收使能
(5) 流控制閾值寄存器 :FCTR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
HWOT | LWOT |
Bit 7~Bit 4:接收緩存高位溢出門限
當接收SRAM空閑空間小于該門限值則發送一個暫停時間為FFFF H的暫停包,若該值為0,則沒有接收條件,若該值為1,則默認值為3K字節的空閑空間
Bit 3~Bit 0:接收緩存低位溢出門限
當接收SRAM空閑空間大于該門限值則發送一個暫停時間為0000 H的暫停包,當溢出門限最高值的暫停包發送之后,溢出門限值最低值的暫停包才有效,默認值為8K
(6) 背壓閾值寄存器 :BPTR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
BPHW | JPT |
Bit 7~Bit 4:背壓閾值最高值
當接收SRAM空閑空間低于該閾值,則MAC將產生一個擁擠狀態,默認值3K字節空閑空間
Bit 3~Bit 0:擁擠狀態時間,默認為200us
JPT值 | 擁擠狀態時間(us) | JPT值 | 擁擠狀態時間(us) |
---|---|---|---|
0000 | 5 | 1000 | 250 |
0001 | 10 | 1001 | 300 |
0010 | 15 | 1010 | 350 |
0011 | 25 | 1011 | 400 |
0100 | 50 | 1100 | 450 |
0101 | 100 | 1101 | 500 |
0110 | 150 | 1110 | 550 |
0111 | 200 | 1111 | 600 |
(7) 發送控制寄存器2 :TCR2
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
LED | RLCP | DTU | ONEPM | IFGS |
Bit 7:LED模式
0:設置LED引腳為模式0或者根據EEPROM的設定
1:設置LED引腳為模式1
Bit 6:重試沖突延遲數據包
Bit 5:禁止重新發送“underruned”數據包
Bit 4:單包模式
0:發送完成前發送最多兩個數據包的命令能被執行
1:發送完成前發送一個數據包的命令能被執行
Bit 3~Bit 0:幀間隔設置
0xxx:96 bit
1000:64 bit
1001:72 bit
1010:80 bit
1011:88 bit
1100:96 bit
1101:104 bit
1110:112 bit
1111:120 bit
(8) 中斷狀態寄存器 :ISR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
IOMODE | - | LNKCHG | UNRUN | ROO | ROS | PT | PR |
Bit 7:數據寬度選擇
0:16位模式
1:8位模式
Bit 5:連接狀態改變
Bit 4:發送“underrun”
Bit 3:接收計數器溢出
Bit 2:接收溢出
Bit 1:數據包發送
Bit 0:數據包接收
(9) 中斷狀態寄存器 :IMR
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|
PAR | - | LNKCHGI | UDRUNI | ROOI | ROSI | PTI | PRI |
Bit 7:使能SRAM讀寫指針在指針地址超過SRAM的大小時自動跳回起始位置,需要驅動程序設置該位,若設置該位,REG_F5將自動設置為0x H
Bit 5:使能連接狀態改變中斷
Bit 4:使能發送“underrun”中斷
Bit 3:使能接收計數器溢出中斷
Bit 2:使能接收溢出中斷
Bit 1:使能數據包發送中斷
Bit 0:使能數據包接收中斷
24.4.2 DM9000驅動
(1)初始化DM9000外設與寄存器配置
u8 DM9000_Init()
{
u32 temp;
//初始化和DM9000連接的IO和FSMC
RCC->AHBENR |= 1<<8 ; //使能FSMC時鐘
RCC->APB2ENR |= 1<<5 ; //使能PORTD時鐘
RCC->APB2ENR |= 1<<6 ; //使能PORTE時鐘
RCC->APB2ENR |= 1<<7 ; //使能PORTF時鐘
RCC->APB2ENR |= 1<<8 ; //使能PORTG時鐘
//PD7->DM9000_RST
GPIOD->CRH &= 0x00FFF000 ;
GPIOD->CRH |= 0xBB000BBB ;
GPIOD->CRL &= 0x0F00FF00 ;
GPIOD->CRL |= 0x30BB00BB ;
//PE
GPIOE->CRH &= 0x00000000 ;
GPIOE->CRH |= 0xBBBBBBBB ;
GPIOE->CRL &= 0x0FFFFFFF ;
GPIOE->CRL |= 0xB0000000 ;
//PF13-->FSMC_A7
GPIOF->CRH &= 0xFF0FFFFF ;
GPIOF->CRH |= 0x00B00000 ;
//PG9->NE2
GPIOG->CRH &= 0xFFFFFF0F ;
GPIOG->CRH |= 0x000000B0 ;
GPIOG->CRL &= 0xF0FFFFFF ;
GPIOG->CRL |= 0x08000000 ;
GPIOG->ODR |= 1<<6 ;
EXIT_Config( 6, 6, 1 ) ; //下降沿觸發
NVIC_Init( 0, 0, EXTI9_5_IRQn, 2 ) ; //優先級最高
//寄存器清零
FSMC_Bank1->BTCR[ 2 ] = 0x00000000 ;
FSMC_Bank1->BTCR[ 3 ] = 0x00000000 ;
FSMC_Bank1E->BWTR[ 2 ] = 0x00000000 ;
FSMC_Bank1->BTCR[ 2 ] |= 1<<12 ; //存儲器寫使能
FSMC_Bank1->BTCR[ 2 ] |= 1<<4 ; //存儲器數據寬度為16bit
//操作BTR寄存器
FSMC_Bank1->BTCR[ 3 ] |= 3<<8 ; //數據保持時間(DATAST)為3個HCLK 4/72M=55ns
FSMC_Bank1->BTCR[ 3 ] |= 0<<4 ; //地址保持時間(ADDHLD)未用到
FSMC_Bank1->BTCR[ 3 ] |= 1<<0 ; //地址建立時間(ADDSET)為2個HCLK 2/72M=27ns
FSMC_Bank1E->BWTR[ 2 ] = 0x0FFFFFFF ; //閃存寫時序寄存器
FSMC_Bank1->BTCR[ 2 ] |= 1<<0 ; //使能BANK1區域2
temp = *( vu32* )( 0x1FFFF7E8 ) ; //獲取STM32的唯一ID的前24位作為MAC地址后三字節
dm9000cfg.mode = DM9000_AUTO ;
dm9000cfg.queue_packet_len = 0 ;
//DM9000的SRAM的發送和接收指針自動返回到開始地址,并開啟接收中斷
dm9000cfg.imr_all = IMR_PAR|IMR_PRI ;
//初始化MAC地址
dm9000cfg.mac_addr[ 0 ] = 2 ;
dm9000cfg.mac_addr[ 1 ] = 0 ;
dm9000cfg.mac_addr[ 2 ] = 0 ;
dm9000cfg.mac_addr[ 3 ] = ( temp>>16 )&0xFF ; //低三字節用STM32的唯一ID
dm9000cfg.mac_addr[ 4 ] = ( temp>>8 )&0xFFF ;
dm9000cfg.mac_addr[ 5 ] = temp&0xFF ;
//初始化組播地址
dm9000cfg.multicase_addr[ 0 ] = 0xFF ;
dm9000cfg.multicase_addr[ 1 ] = 0xFF ;
dm9000cfg.multicase_addr[ 2 ] = 0xFF ;
dm9000cfg.multicase_addr[ 3 ] = 0xFF ;
dm9000cfg.multicase_addr[ 4 ] = 0xFF ;
dm9000cfg.multicase_addr[ 5 ] = 0xFF ;
dm9000cfg.multicase_addr[ 6 ] = 0xFF ;
dm9000cfg.multicase_addr[ 7 ] = 0xFF ;
DM9000_Reset() ; //復位DM9000
delay_ms( 100 ) ;
//獲取DM9000ID
temp = DM9000_ReadReg( DM9000_VIDL ) ;
temp |= DM9000_ReadReg( DM9000_VIDH )<<8 ;
temp |= DM9000_ReadReg( DM9000_PIDL )<<16 ;
temp |= DM9000_ReadReg( DM9000_PIDH )<<24 ;
//讀取ID錯誤
if( temp!=DM9000_ID )
return 1 ;
DM9000_Set_PHYMode( dm9000cfg.mode ) ; //設置PHY工作模式
DM9000_WriteReg( DM9000_NCR, 0x00 ) ;
DM9000_WriteReg( DM9000_TCR, 0x00 ) ; //發送控制寄存器清零
DM9000_WriteReg( DM9000_BPTR, 0x3F ) ;
DM9000_WriteReg( DM9000_FCTR, 0x38 ) ;
DM9000_WriteReg( DM9000_FCR, 0x00 ) ;
DM9000_WriteReg( DM9000_SMCR, 0x00 ) ; //特殊模式
DM9000_WriteReg( DM9000_NSR, NSR_WAKEST|NSR_TX2END|NSR_TX1END ) ; //清除發送狀態
DM9000_WriteReg( DM9000_ISR, 0x0F ) ; //清除中斷狀態
DM9000_WriteReg( DM9000_TCR2, 0x80 ) ; //切換LED到mode1
//設置MAC地址和組播地址
DM9000_Set_MACAddress( dm9000cfg.mac_addr ) ;//設置MAC地址
DM9000_Set_Multicast( dm9000cfg.multicase_addr ) ; //設置組播地址
DM9000_WriteReg( DM9000_RCR, RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN ) ;
DM9000_WriteReg( DM9000_IMR, IMR_PAR ) ;
DM9000_Get_SpeedAndDuplex() ; //獲取DM9000的連接速度和雙工狀態
DM9000_WriteReg( DM9000_IMR, dm9000cfg.imr_all ) ; //設置中斷
return 0 ;
}
注:函數中用到了未定義的數據類型,需要在sys.h中添加該類型的定義typedef volatile uint32_t vu32。
(2)DM9000內部寄存器讀寫函數
u8 DM9000_Init()
{
u32 temp;
//初始化和DM9000連接的IO和FSMC
RCC->AHBENR |= 1<<8 ; //使能FSMC時鐘
RCC->APB2ENR |= 1<<5 ; //使能PORTD時鐘
RCC->APB2ENR |= 1<<6 ; //使能PORTE時鐘
RCC->APB2ENR |= 1<<7 ; //使能PORTF時鐘
RCC->APB2ENR |= 1<<8 ; //使能PORTG時鐘
//PD7->DM9000_RST
GPIOD->CRH &= 0x00FFF000 ;
GPIOD->CRH |= 0xBB000BBB ;
GPIOD->CRL &= 0x0F00FF00 ;
GPIOD->CRL |= 0x30BB00BB ;
//PE
GPIOE->CRH &= 0x00000000 ;
GPIOE->CRH |= 0xBBBBBBBB ;
GPIOE->CRL &= 0x0FFFFFFF ;
GPIOE->CRL |= 0xB0000000 ;
//PF13-->FSMC_A7
GPIOF->CRH &= 0xFF0FFFFF ;
GPIOF->CRH |= 0x00B00000 ;
//PG9->NE2
GPIOG->CRH &= 0xFFFFFF0F ;
GPIOG->CRH |= 0x000000B0 ;
GPIOG->CRL &= 0xF0FFFFFF ;
GPIOG->CRL |= 0x08000000 ;
GPIOG->ODR |= 1<<6 ;
EXIT_Config( 6, 6, 1 ) ; //下降沿觸發
NVIC_Init( 0, 0, EXTI9_5_IRQn, 2 ) ; //優先級最高
//寄存器清零
FSMC_Bank1->BTCR[ 2 ] = 0x00000000 ;
FSMC_Bank1->BTCR[ 3 ] = 0x00000000 ;
FSMC_Bank1E->BWTR[ 2 ] = 0x00000000 ;
FSMC_Bank1->BTCR[ 2 ] |= 1<<12 ; //存儲器寫使能
FSMC_Bank1->BTCR[ 2 ] |= 1<<4 ; //存儲器數據寬度為16bit
//操作BTR寄存器
FSMC_Bank1->BTCR[ 3 ] |= 3<<8 ; //數據保持時間(DATAST)為3個HCLK 4/72M=55ns
FSMC_Bank1->BTCR[ 3 ] |= 0<<4 ; //地址保持時間(ADDHLD)未用到
FSMC_Bank1->BTCR[ 3 ] |= 1<<0 ; //地址建立時間(ADDSET)為2個HCLK 2/72M=27ns
FSMC_Bank1E->BWTR[ 2 ] = 0x0FFFFFFF ; //閃存寫時序寄存器
FSMC_Bank1->BTCR[ 2 ] |= 1<<0 ; //使能BANK1區域2
temp = *( vu32* )( 0x1FFFF7E8 ) ; //獲取STM32的唯一ID的前24位作為MAC地址后三字節
dm9000cfg.mode = DM9000_AUTO ;
dm9000cfg.queue_packet_len = 0 ;
//DM9000的SRAM的發送和接收指針自動返回到開始地址,并開啟接收中斷
dm9000cfg.imr_all = IMR_PAR|IMR_PRI ;
//初始化MAC地址
dm9000cfg.mac_addr[ 0 ] = 2 ;
dm9000cfg.mac_addr[ 1 ] = 0 ;
dm9000cfg.mac_addr[ 2 ] = 0 ;
dm9000cfg.mac_addr[ 3 ] = ( temp>>16 )&0xFF ; //低三字節用STM32的唯一ID
dm9000cfg.mac_addr[ 4 ] = ( temp>>8 )&0xFFF ;
dm9000cfg.mac_addr[ 5 ] = temp&0xFF ;
//初始化組播地址
dm9000cfg.multicase_addr[ 0 ] = 0xFF ;
dm9000cfg.multicase_addr[ 1 ] = 0xFF ;
dm9000cfg.multicase_addr[ 2 ] = 0xFF ;
dm9000cfg.multicase_addr[ 3 ] = 0xFF ;
dm9000cfg.multicase_addr[ 4 ] = 0xFF ;
dm9000cfg.multicase_addr[ 5 ] = 0xFF ;
dm9000cfg.multicase_addr[ 6 ] = 0xFF ;
dm9000cfg.multicase_addr[ 7 ] = 0xFF ;
DM9000_Reset() ; //復位DM9000
delay_ms( 100 ) ;
//獲取DM9000ID
temp = DM9000_ReadReg( DM9000_VIDL ) ;
temp |= DM9000_ReadReg( DM9000_VIDH )<<8 ;
temp |= DM9000_ReadReg( DM9000_PIDL )<<16 ;
temp |= DM9000_ReadReg( DM9000_PIDH )<<24 ;
//讀取ID錯誤
if( temp!=DM9000_ID )
return 1 ;
DM9000_Set_PHYMode( dm9000cfg.mode ) ; //設置PHY工作模式
DM9000_WriteReg( DM9000_NCR, 0x00 ) ;
DM9000_WriteReg( DM9000_TCR, 0x00 ) ; //發送控制寄存器清零
DM9000_WriteReg( DM9000_BPTR, 0x3F ) ;
DM9000_WriteReg( DM9000_FCTR, 0x38 ) ;
DM9000_WriteReg( DM9000_FCR, 0x00 ) ;
DM9000_WriteReg( DM9000_SMCR, 0x00 ) ; //特殊模式
DM9000_WriteReg( DM9000_NSR, NSR_WAKEST|NSR_TX2END|NSR_TX1END ) ; //清除發送狀態
DM9000_WriteReg( DM9000_ISR, 0x0F ) ; //清除中斷狀態
DM9000_WriteReg( DM9000_TCR2, 0x80 ) ; //切換LED到mode1
//設置MAC地址和組播地址
DM9000_Set_MACAddress( dm9000cfg.mac_addr ) ;//設置MAC地址
DM9000_Set_Multicast( dm9000cfg.multicase_addr ) ; //設置組播地址
DM9000_WriteReg( DM9000_RCR, RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN ) ;
DM9000_WriteReg( DM9000_IMR, IMR_PAR ) ;
DM9000_Get_SpeedAndDuplex() ; //獲取DM9000的連接速度和雙工狀態
DM9000_WriteReg( DM9000_IMR, dm9000cfg.imr_all ) ; //設置中斷
return 0 ;
}
(3)設置MAC地址與組播地址
void DM9000_Set_MACAddress( u8 *macaddr )
{
u8 i ;
for( i=0; i<6; i++ )
DM9000_WriteReg( DM9000_PAR+i, macaddr[ i ] ) ;
}
void DM9000_Set_Multicast( u8 *multicastaddr )
{
u8 i;
for( i=0; i<8; i++ )
DM9000_WriteReg( DM9000_MAR+i, multicastaddr[ i ] ) ;
}
(4)獲取與設置PHY工作模式
void DM9000_Set_MACAddress( u8 *macaddr )
{
u8 i ;
for( i=0; i<6; i++ )
DM9000_WriteReg( DM9000_PAR+i, macaddr[ i ] ) ;
}
void DM9000_Set_Multicast( u8 *multicastaddr )
{
u8 i;
for( i=0; i<8; i++ )
DM9000_WriteReg( DM9000_MAR+i, multicastaddr[ i ] ) ;
}
(5)復位
void DM9000_Reset()
{
DM9000_RST = 0 ; //DM9000硬件復位
delay_ms( 10 ) ;
DM9000_RST = 1 ; //DM9000硬件復位結束
delay_ms( 100 ) ; //等待DM9000準備就緒
DM9000_WriteReg( DM9000_GPCR, 0x01 ) ; //第1步:設置GPCR寄存器的bit0為1
DM9000_WriteReg( DM9000_GPR, 0 ) ; //第2步:設置GPR寄存器的bit1為0
DM9000_WriteReg( DM9000_NCR, 0x02|NCR_RST ) ; //第3步:軟件復位DM9000
//等待DM9000軟復位完成
do
{
delay_ms( 25 ) ;
}while( DM9000_ReadReg( DM9000_NCR )&0x01 ) ;
DM9000_WriteReg( DM9000_NCR, 0 ) ;
DM9000_WriteReg( DM9000_NCR, 0x02|NCR_RST ) ; //DM9000第2次軟復位
do
{
delay_ms( 25 ) ;
}while( DM9000_ReadReg( DM9000_NCR )&0x01 ) ;
}
(6)發送
void DM9000_SendPacket( struct pbuf *p )
{
struct pbuf *q ;
u16 pbuf_index=0 ;
u8 word[ 2 ], word_index=0 ;
DM9000_WriteReg( DM9000_IMR,IMR_PAR ) ; //關閉網卡中斷
DM9000->REG = DM9000_MWCMD ; //發送的數據搬到DM9000 TX SRAM中
q = p ;
//向DM9000的TX SRAM中寫入數據,一次寫入兩個字節數據
//當要發送的數據長度為奇數的時候,需要將最后一個字節單獨寫入DM9000的TX SRAM中
while( q )
{
if( pbuf_index
(7)接收
struct pbuf *DM9000_Receive_Packet()
{
struct pbuf *p ;
struct pbuf *q ;
u32 rxbyte ;
vu16 rx_status, rx_length ;
u16 *data ;
u16 dummy ;
int len ;
p = NULL ;
__error_retry:
DM9000_ReadReg( DM9000_MRCMDX ) ; //假讀
rxbyte = ( u8 )DM9000->DATA ; //進行第二次讀取
//接收到數據
if( rxbyte )
{
//rxbyte大于1,接收到的數據錯誤,掛了
if( rxbyte>1 )
{
DM9000_WriteReg( DM9000_RCR, 0x00 ) ;
DM9000_WriteReg( DM9000_ISR, 0x80 ) ;
return ( struct pbuf* )p ;
}
DM9000->REG = DM9000_MRCMD ;
rx_status = DM9000->DATA ;
rx_length = DM9000->DATA ;
p = pbuf_alloc( PBUF_RAW, rx_length, PBUF_POOL ) ; //pbufs內存池分配pbuf
//內存申請成功
if( p!=NULL )
{
for( q=p; q!=NULL; q=q->next )
{
data = ( u16* )q->payload ;
len = q->len ;
while( len>0 )
{
*data = DM9000->DATA ;
data ++ ;
len -= 2 ;
}
}
}
//內存申請失敗
else
{
data = &dummy ;
len = rx_length ;
while( len )
{
*data = DM9000->DATA ;
len -= 2 ;
}
}
//根據rx_status判斷接收數據是否出現錯誤,如果有任何一個出現的話丟棄該數據幀,
//當rx_length小于64或者大于最大數據長度的時候也丟棄該數據幀
if( ( rx_status&0xBF00 )||( rx_length<64 )||( rx_length>DM9000_PKT_MAX ) )
{
if( rx_length>DM9000_PKT_MAX )
{
DM9000_WriteReg( DM9000_NCR, NCR_RST ) ; //復位DM9000
delay_ms( 5 ) ;
}
//釋放內存
if( p!=NULL )
pbuf_free( ( struct pbuf* )p ) ;
p = NULL ;
goto __error_retry ;
}
}
else
{
DM9000_WriteReg( DM9000_ISR, ISR_PTS ) ; //清除所有中斷標志位
dm9000cfg.imr_all = IMR_PAR|IMR_PRI ; //重新接收中斷
DM9000_WriteReg( DM9000_IMR, dm9000cfg.imr_all ) ;
}
return ( struct pbuf* )p ;
}
24.5 LWIP協議移植
24.5.1 源碼下載
可以從官網進行源碼下載,LWIP官網:http://savannah.nongnu.org/projects/lwip/
24.5.2 源碼結構
打開從官網上下載下來的源碼其中包括doc,src和test三個文件夾和5個其他文件。doc文件夾下包含了幾個與協議棧使用相關的文本文檔,doc文件夾里面有兩個比較重要的文檔:rawapi.txt和sys_arch.txt。rawapi.txt告訴讀者怎么使用raw/callbackAPI進行編程,sys_arch.txt包含了移植說明,在移植的時候會用到。src文件夾是我們的重點,里面包含了LWIP的源碼。test是LWIP提供的一些測試程序。打開src源碼文件夾,src文件夾由4個文件夾組成:api、core、include、netif四個文件夾。api文件夾里面是LWIP的sequentialAPI(Netconn)和socketAPI兩種接口函數的源碼,要使用這兩種API需要操作系統支持。core文件夾是LWIP內核源碼,include文件夾里面是LWIP使用到的頭文件,netif文件夾里面是與網絡底層接口有關的文件。
24.5.3 添加LWIP源代碼文件
(1)將lwip-2.1.2\\src目錄下的5個文件夾復制到工程文件夾中新建的LWIP文件夾中,刪除apps文件夾核include/lwip/apps文件夾。
(2)在LWIP文件夾中新建app和arch兩個文件夾。
(3)在app文件夾中新建comm,tcp_client,tcp_server和udp_demo文件夾,最終文件結構如下圖所示。
(4)在app/comm目錄下創建comm.c,comm.h,lwipopts.h三個文件。
(5)在arch目錄下創建cc.h,cpu.h,perf.h,sys_arch.c和sys_arch.h五個文件。
(6)在include/netif目錄下創建ethernetif.h文件。
(7)將之前的DM9000驅動,LCD驅動和內存管理驅動程序導入工程中。
(8)將下列文件添加到項目LWIP-NETIF,LWIP-CORE,LWIP-API,LWIP-APP,LWIP-ARCH中。
項目目錄 | 文件 |
---|---|
LWIP-NETIF | etharp.c |
LWIP-CORE | autoip.c |
ip.c | ip_addr.c |
dns.c | init.c |
netif.c | pbuf.c |
tcp_in.c | tcp_out.c |
LWIP-API | api_lib.c |
netifapi.c | sockets.c |
LWIP-APP | comm.c |
LWIP-ARCH | sys_arch.c |
注:將lwip/core目錄下的sys.c文件與lwip/include/lwip目錄下的sys.h重命名為lwip_sys.c和lwip_sys.h,以免與SYSTEM目錄下的sys.c重名,產生編譯錯誤。
24.5.4 arch目錄下源碼文件的修改
(1)arch/cc.h文件代碼(該文件主要完成協議使用的數據類型的定義)
#ifndef _CC_H_
#define _CC_H_
#include "cpu.h"
#include "stdio.h"
//定義與平臺無關的數據類型
typedef unsigned char u8_t; //無符號8位整數
typedef signed char s8_t; //有符號8位整數
typedef unsigned short u16_t; //無符號16位整數
typedef signed short s16_t; //有符號16位整數
typedef unsigned long u32_t; //無符號32位整數
typedef signed long s32_t; //有符號32位整數
typedef u32_t mem_ptr_t ; //內存地址型數據
typedef int sys_prot_t ; //臨界保護型數據
#if OS_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev) CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev) CPU_INT_EN()
#endif
//method 3 is used in this port
#if OS_CRITICAL_METHOD == 3
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev) OS_CPU_SR_Restore(lev)
#endif
//根據不同的編譯器定義一些符號
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
//LWIP用printf調試時使用的一些數據類型
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
//宏定義
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \\
do \\
{ printf("Assertion \"%s\" failed at line %d in %s\\r\\n", x, __LINE__, __FILE__); \\
} while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
#endif
(2)arch/cpu.h文件代碼(負責定義CPU的大端小端模式)
#ifndef _CPU_H_
#define _CPU_H_
#define BYTE_ORDER LITTLE_ENDIAN //小端模式
#endif
(3)arch/perf.h文件代碼(用于系統測量與統計)
#ifndef _PERF_H_
#define _PERF_H_
#define PERF_START //空定義
#define PERF_STOP(x) //空定義
#endif
(4)arch/sys_arch.h文件代碼(為了與操作系統共存使用的獲取時間的函數,用于為LWIP提供時鐘)
#ifndef _ARCH_SYS_ARCH_H_
#define _ARCH_SYS_ARCH_H_
#include "cc.h"
u32_t sys_now( void ) ;
#endif
(5)arch/sys_arch.c文件代碼
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/lwip_sys.h"
#include "lwip/mem.h"
#include "tim.h"
//為LWIP提供計時
extern uint32_t lwip_localtime;//lwip本地時間計數器,單位:ms
u32_t sys_now()
{
return lwip_localtime ;
}
24.5.5 app/comm目錄下源碼文件的修改
(1)app/comm.c文件代碼
#include "lwip/tcpip.h"
#include "malloc.h"
#include "delay.h"
#include "usart1.h"
__lwip_dev lwipdev ; //lwip控制結構體
struct netif lwip_netif ; //定義一個全局的網絡接口
extern u32 memp_get_memorysize( void ) ; //在memp.c里面定義
extern u8_t *memp_memory ; //在memp.c里面定義
extern u8_t *ram_heap ; //在mem.c里面定義
u32 TCPTimer=0 ; //TCP查詢計時器
u32 ARPTimer=0 ; //ARP查詢計時器
u32 lwip_localtime ; //lwip本地時間計數器,單位:ms
#if LWIP_DHCP
u32 DHCPfineTimer=0 ; //DHCP精細處理計時器
u32 DHCPcoarseTimer=0 ; //DHCP粗糙處理計時器
#endif
u8 lwip_comm_mem_malloc()
{
u32 mempsize ;
u32 ramheapsize ;
mempsize = memp_get_memorysize() ; //得到memp_memory數組大小
memp_memory = mymalloc( SRAMIN, mempsize ) ; //為memp_memory申請內存
//得到ram heap大小
ramheapsize = LWIP_MEM_ALIGN_SIZE( MEM_SIZE )+2*LWIP_MEM_ALIGN_SIZE( 4*3 )+MEM_ALIGNMENT ;
ram_heap = mymalloc( SRAMIN, ramheapsize ) ; //為ram_heap申請內存
//有申請失敗的
if( !memp_memory||!ram_heap )
{
lwip_comm_mem_free() ;
return 1 ;
}
return 0 ;
}
void lwip_comm_mem_free()
{
myfree( SRAMIN, memp_memory ) ;
myfree( SRAMIN, ram_heap ) ;
}
void lwip_comm_default_ip_set( __lwip_dev *lwipx )
{
//默認遠端IP為:192.168.1.100
lwipx->remoteip[ 0 ] = 192 ;
lwipx->remoteip[ 1 ] = 168 ;
lwipx->remoteip[ 2 ] = 1 ;
lwipx->remoteip[ 3 ] = 104 ;
//MAC地址設置(高三字節固定為:2.0.0,低三字節用STM32唯一ID)
lwipx->mac[ 0 ] = dm9000cfg.mac_addr[ 0 ] ;
lwipx->mac[ 1 ] = dm9000cfg.mac_addr[ 1 ] ;
lwipx->mac[ 2 ] = dm9000cfg.mac_addr[ 2 ] ;
lwipx->mac[ 3 ] = dm9000cfg.mac_addr[ 3 ] ;
lwipx->mac[ 4 ] = dm9000cfg.mac_addr[ 4 ] ;
lwipx->mac[ 5 ] = dm9000cfg.mac_addr[ 5 ] ;
//默認本地IP為:192.168.1.30
lwipx->ip[ 0 ] = 192 ;
lwipx->ip[ 1 ] = 168 ;
lwipx->ip[ 2 ] = 1 ;
lwipx->ip[ 3 ] = 30 ;
//默認子網掩碼:255.255.255.0
lwipx->netmask[ 0 ] = 255 ;
lwipx->netmask[ 1 ] = 255 ;
lwipx->netmask[ 2 ] = 255 ;
lwipx->netmask[ 3 ] = 0 ;
//默認網關:192.168.1.1
lwipx->gateway[ 0 ] = 192 ;
lwipx->gateway[ 1 ] = 168 ;
lwipx->gateway[ 2 ] = 1 ;
lwipx->gateway[ 3 ] = 1 ;
lwipx->dhcpstatus = 0 ; //沒有DHCP
}
u8 lwip_comm_init()
{
struct netif *Netif_Init_Flag ; //調用netif_add()函數時的返回值,用于判斷網絡初始化是否成功
struct ip_addr ipaddr ; //ip地址
struct ip_addr netmask ; //子網掩碼
struct ip_addr gw ; //默認網關
//內存申請失敗
if( lwip_comm_mem_malloc() )
return 1 ;
//初始化DM9000AEP
if( DM9000_Init() )
return 2 ;
lwip_init() ; //初始化LWIP內核
lwip_comm_default_ip_set( &lwipdev ) ; //設置默認IP等信息
//使用動態IP
#if LWIP_DHCP
ipaddr.addr = 0 ;
netmask.addr = 0 ;
gw.addr = 0 ;
//使用靜態IP
#else
IP4_ADDR( &ipaddr, lwipdev.ip[ 0 ], lwipdev.ip[ 1 ], lwipdev.ip[ 2 ], lwipdev.ip[ 3 ] ) ;
IP4_ADDR( &netmask, lwipdev.netmask[ 0 ], lwipdev.netmask[1] , lwipdev.netmask[ 2 ], lwipdev.netmask[ 3 ] ) ;
IP4_ADDR( &gw, lwipdev.gateway[ 0 ], lwipdev.gateway[ 1 ], lwipdev.gateway[ 2 ], lwipdev.gateway[ 3 ] );
#endif
//向網卡列表中添加一個網口
Netif_Init_Flag = netif_add( &lwip_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input ) ;
//如果使用DHCP的話
#if LWIP_DHCP
lwipdev.dhcpstatus = 0 ; //DHCP標記為0
dhcp_start( &lwip_netif ) ; //開啟DHCP服務
#endif
//網卡添加失敗
if( Netif_Init_Flag==NULL )
return 3 ;
//網口添加成功后,設置netif為默認值,并且打開netif網口
else
{
netif_set_default( &lwip_netif ) ; //設置netif為默認網口
netif_set_up( &lwip_netif ) ; //打開netif網口
}
return 0 ; //操作OK
}
void lwip_pkt_handle()
{
ethernetif_input( &lwip_netif ) ; //從網絡緩沖區中讀取接收到的數據包并將其發送給LWIP處理
}
void lwip_periodic_handle()
{
#if LWIP_TCP
//每250ms調用一次tcp_tmr()函數
if( lwip_localtime-TCPTimer>=TCP_TMR_INTERVAL )
{
TCPTimer = lwip_localtime ;
tcp_tmr() ;
}
#endif
//ARP每5s周期性調用一次
if( ( lwip_localtime-ARPTimer )>=ARP_TMR_INTERVAL )
{
ARPTimer = lwip_localtime ;
etharp_tmr() ;
}
//如果使用DHCP的話
#if LWIP_DHCP
//每500ms調用一次dhcp_fine_tmr()
if( lwip_localtime-DHCPfineTimer>=DHCP_FINE_TIMER_MSECS )
{
DHCPfineTimer = lwip_localtime ;
dhcp_fine_tmr() ;
if( ( lwipdev.dhcpstatus!=2 )&&( lwipdev.dhcpstatus!=0xFF ) )
lwip_dhcp_process_handle() ; //DHCP處理
}
//每60s執行一次DHCP粗糙處理
if( lwip_localtime-DHCPcoarseTimer>=DHCP_COARSE_TIMER_MSECS )
{
DHCPcoarseTimer = lwip_localtime ;
dhcp_coarse_tmr() ;
}
#endif
}
//如果使能了DHCP
#if LWIP_DHCP
void lwip_dhcp_process_handle()
{
u32 ip=0, netmask=0, gw=0 ;
switch( lwipdev.dhcpstatus )
{
//開啟DHCP
case 0:
dhcp_start( &lwip_netif ) ;
lwipdev.dhcpstatus = 1 ; //等待通過DHCP獲取到的地址
break ;
//等待獲取到IP地址
case 1:
{
ip = lwip_netif.ip_addr.addr ; //讀取新IP地址
netmask = lwip_netif.netmask.addr ; //讀取子網掩碼
gw = lwip_netif.gw.addr ; //讀取默認網關
//正確獲取到IP地址的時候
if( ip!=0 )
{
lwipdev.dhcpstatus = 2 ; //DHCP成功
//解析出通過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 ) ;
//解析通過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 ) ;
//解析出通過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 ) ;
}
//通過DHCP服務獲取IP地址失敗,且超過最大嘗試次數
else if( lwip_netif.dhcp->tries>LWIP_MAX_DHCP_TRIES )
{
lwipdev.dhcpstatus = 0xFF ; //DHCP超時失敗
//使用靜態IP地址
IP4_ADDR( &( lwip_netif.ip_addr ), lwipdev.ip[ 0 ], lwipdev.ip[ 1 ], lwipdev.ip[ 2 ], lwipdev.ip[ 3 ] ) ;
IP4_ADDR( &( lwip_netif.netmask ), lwipdev.netmask[ 0 ], lwipdev.netmask[ 1 ], lwipdev.netmask[ 2 ], lwipdev.netmask[ 3 ] ) ;
IP4_ADDR( &( lwip_netif.gw ), lwipdev.gateway[ 0 ], lwipdev.gateway[ 1 ], lwipdev.gateway[ 2 ], lwipdev.gateway[ 3 ] ) ;
}
}
break;
default :
break;
}
}
#endif
(2)app/comm.h文件代碼
#ifndef _COMM_H_
#define _COMM_H_
#include "dm9000.h"
#define LWIP_MAX_DHCP_TRIES 4 //DHCP服務器最大重試次數
//lwip控制結構體
typedef struct
{
u8 mac[ 6 ] ; //MAC地址
u8 remoteip[ 4 ] ; //遠端主機IP地址
u8 ip[ 4 ] ; //本機IP地址
u8 netmask[ 4 ] ; //子網掩碼
u8 gateway[ 4 ] ; //默認網關的IP地址
vu8 dhcpstatus ; //dhcp狀態
//0,未獲取DHCP地址
//1,進入DHCP獲取狀態
//2,成功獲取DHCP地址
//0XFF,獲取失敗
}__lwip_dev ;
extern __lwip_dev lwipdev ; //lwip控制結構體
void lwip_pkt_handle( void ) ;
void lwip_periodic_handle( void ) ;
void lwip_comm_default_ip_set( __lwip_dev *lwipx ) ;
u8 lwip_comm_mem_malloc( void ) ;
void lwip_comm_mem_free( void ) ;
u8 lwip_comm_init( void ) ;
void lwip_dhcp_process_handle( void ) ;
#endif
注:函數中用到了未定義的數據類型,需要在sys.h中添加該類型的定義typedef volatile uint8_t vu8。
(3)app/lwipopts文件代碼
#ifndef _LWIPOPTS_H_
#define _LWIPOPTS_H_
#define SYS_LIGHTWEIGHT_PROT 0
//NO_SYS==1:不使用操作系統
#define NO_SYS 1 //不使用UCOS操作系統
#define MEM_ALIGNMENT 4 //使用4字節對齊模式
//heap內存的大小,如果在應用中有大量數據發送的話這個值最好設置大一點
#define MEM_SIZE 10*1024 //內存堆大小
//memp結構的pbuf數量,如果應用從ROM或者靜態存儲區發送大量數據時,這個值應該設置大一點
#define MEMP_NUM_PBUF 10
#define MEMP_NUM_UDP_PCB 6 //UDP協議控制塊(PCB)數量.每個活動UDP"連接"需要一個PCB
#define MEMP_NUM_TCP_PCB 10 //同時建立激活的TCP數量
#define MEMP_NUM_TCP_PCB_LISTEN 6 //能夠監聽的TCP連接數量
#define MEMP_NUM_TCP_SEG 20 //最多同時在隊列中的TCP段數量
#define MEMP_NUM_SYS_TIMEOUT 5 //能夠同時激活的timeout個數
//Pbuf選項
//PBUF_POOL_SIZE:pbuf內存池個數
#define PBUF_POOL_SIZE 10
//PBUF_POOL_BUFSIZE:每個pbuf內存池大小
#define PBUF_POOL_BUFSIZE 1500
//TCP選項
#define LWIP_TCP 1 //為1是使用TCP
#define TCP_TTL 255 //生存時間
//當TCP的數據段超出隊列時的控制位,當設備的內存過小的時候此項應為0
#define TCP_QUEUE_OOSEQ 0
//最大TCP分段
#define TCP_MSS ( 1500-40 ) //TCP_MSS = (MTU - IP報頭大小 - TCP報頭大小
//TCP發送緩沖區大小(bytes)
#define TCP_SND_BUF ( 4*TCP_MSS )
//TCP_SND_QUEUELEN: TCP發送緩沖區大小(pbuf).這個值最小為(2 * TCP_SND_BUF/TCP_MSS)
#define TCP_SND_QUEUELEN ( 4* TCP_SND_BUF/TCP_MSS )
//TCP發送窗口
#define TCP_WND ( 2*TCP_MSS )
//ICMP選項
#define LWIP_ICMP 1 //使用ICMP協議
//DHCP選項
//當使用DHCP時此位應該為1,LwIP 0.5.1版本中沒有DHCP服務
#define LWIP_DHCP 1
//UDP選項
#define LWIP_UDP 1 //使用UDP服務
#define UDP_TTL 255 //UDP數據包生存時間
//靜態選項
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
//SequentialAPI選項
//LWIP_NETCONN==1:使能NETCON函數(要求使用api_lib.c)
#define LWIP_NETCONN 0
//Socket API選項
//LWIP_SOCKET==1:使能Socket API(要求使用sockets.c)
#define LWIP_SOCKET 0
#define LWIP_COMPAT_MUTEX 1
#define LWIP_SO_RCVTIMEO 1 //通過定義可以避免阻塞線程
//Lwip調試選項
//#define LWIP_DEBUG 1 //開啟DEBUG選項
#define ICMP_DEBUG LWIP_DBG_OFF //開啟/關閉ICMPdebug
#endif
24.5.6 include/netif/ethernetif.h文件修改
#ifndef _ETHERNETIF_H_
#define _ETHERNETIF_H_
#include "lwip/err.h"
#include "lwip/netif.h"
//網卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
err_t ethernetif_init( struct netif *netif ) ;
err_t ethernetif_input( struct netif *netif ) ;
#endif
24.5.7 netif/ethernetif.c文件修改
#include "netif/ethernetif.h"
#include "dm9000.h"
#include "comm.h"
#include "malloc.h"
#include "netif/etharp.h"
#include "string.h"
static err_t low_level_init( struct netif *netif )
{
netif->hwaddr_len = ETHARP_HWADDR_LEN ; //設置MAC地址長度,為6個字節
//初始化MAC地址,不能與網絡中其他設備MAC地址重復
netif->hwaddr[ 0 ] = lwipdev.mac[ 0 ] ;
netif->hwaddr[ 1 ] = lwipdev.mac[ 1 ] ;
netif->hwaddr[ 2 ] = lwipdev.mac[ 2 ] ;
netif->hwaddr[ 3 ] = lwipdev.mac[ 3 ] ;
netif->hwaddr[ 4 ] = lwipdev.mac[ 4 ] ;
netif->hwaddr[ 5 ] = lwipdev.mac[ 5 ] ;
netif->mtu = 1500 ; //最大允許傳輸單元,允許該網卡廣播和ARP功能
netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP ;
return ERR_OK ;
}
static err_t low_level_output( struct netif *netif, struct pbuf *p )
{
DM9000_SendPacket( p ) ;
return ERR_OK ;
}
static struct pbuf *low_level_input( struct netif *netif )
{
struct pbuf *p ;
p = DM9000_Receive_Packet() ;
return p ;
}
err_t ethernetif_input( struct netif *netif )
{
err_t err ;
struct pbuf *p ;
p = low_level_input( netif ) ; //調用low_level_input函數接收數據
if( p==NULL )
return ERR_MEM ;
err = netif->input( p, netif ); //調用netif結構體中的input字段(一個函數)來處理數據包
if( err!=ERR_OK )
{
LWIP_DEBUGF( NETIF_DEBUG, ( "ethernetif_input: IP input error\\n" ) ) ;
pbuf_free( p ) ;
p = NULL ;
}
return err ;
}
err_t ethernetif_init( struct netif *netif )
{
LWIP_ASSERT( "netif!=NULL", ( netif!=NULL ) ) ;
//LWIP_NETIF_HOSTNAME
#if LWIP_NETIF_HOSTNAME
netif->hostname = "lwip" ; //初始化名稱
#endif
netif->name[ 0 ] = IFNAME0 ; //初始化變量netif的name字段
netif->name[ 1 ] = IFNAME1 ; //在文件外定義這里不用關心具體值
netif->output = etharp_output ; //IP層發送數據包函數
netif->linkoutput = low_level_output ; //ARP模塊發送數據包函數
low_level_init( netif ) ; //底層硬件初始化函數
return ERR_OK ;
}
24.5.8 其他文件修改
(1)頭文件修改:主要是將lwip/sys.h修改為lwip/lwip_sys.h,因為我們在移植的時候將lwip/core/sys.c和lwip/include/lwip目錄下的一個文件名稱從sys改為了lwip_sys,所以導致程序引用的頭文件也需要修改,需要修改的頭文件有:timers.c,init.c,lwip_sys.c,mem.c,pbuf.c和memp.c。
(2)memp.c文件修改
①修改memp_memory的定義,之前的定義是位于170行的這么幾行代碼。
static u8_t memp_memory[MEM_ALIGNMENT - 1
#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )
#include "lwip/memp_std.h"
];
我們將這段代碼屏蔽掉,重新添加定義u8_t *memp_memory;
②添加memp_get_memorysize函數
在333行插入以下函數代碼
u32_t memp_get_memorysize()
{
u32_t length=0;
length=(
MEM_ALIGNMENT-1 //全局型數組 為所有POOL分配的內存空間
//MEMP_SIZE表示需要在每個POOL頭部預留的空間 MEMP_SIZE = 0
#define LWIP_MEMPOOL(name,num,size,desc)+((num)*(MEMP_SIZE+MEMP_ALIGN_SIZE(size)))
#include "lwip/memp_std.h"
);
return length;
}
24.6 主函數編寫
經過上面的步驟,我們已經成功移植了LWIP 1.4.1版本,現在我們通過編寫主函數來初始化LWIP,讓LWIP跑起來。
(1)添加定時器驅動,我們這里采用通用定時器3來完成LWIP的定時功能。
在tim.c文件中添加以下代碼
#include "tim.h"
extern u32 lwip_localtime; //lwip本地時間計數器,單位:ms
void TIM3_IRQHandler()
{
//溢出中斷
if( TIM3->SR&0x0001 )
lwip_localtime +=10 ; //加10
TIM3->SR &= ~( 1<<0 ) ; //清除中斷標志位
}
void TIM3_Init( u16 arr, u16 psc )
{
RCC->APB1ENR |= 1<<1 ; //TIM3時鐘使能
TIM3->ARR = arr ; //設定計數器自動重裝值//剛好1ms
TIM3->PSC = psc ; //預分頻器7200,得到10Khz的計數時鐘
TIM3->DIER |= 1<<0 ; //允許更新中斷
TIM3->CR1 |= 0x01 ; //使能定時器3
NVIC_Init( 1, 3, TIM3_IRQn, 2 ) ; //組2
}
在tim.h文件中添加以下代碼
#ifndef _TIM_H_
#define _TIM_H_
#include "sys.h"
void TIM3_Init( u16 arr, u16 psc ) ; //定時器3初始化
#endif
(2)主函數添加以下代碼
#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "tim.h"
#include "lcd.h"
#include "malloc.h"
#include "dm9000.h"
#include "lwip/netif.h"
#include "comm.h"
#include "lwipopts.h"
int main()
{
u8 buf[ 30 ];
STM32_Clock_Init( 9 ) ; //系統時鐘設置
SysTick_Init( 72 ) ; //延時初始化
USART1_Init( 72, 115200 ) ; //串口初始化為115200
LCD_Init() ; //初始化LCD
TIM3_Init( 1000, 719 ) ; //定時器3頻率為100hz
my_mem_init( SRAMIN ) ; //初始化內部內存池
while( lwip_comm_init() ) ; //lwip初始化
//等待DHCP獲取成功/超時溢出
while( ( lwipdev.dhcpstatus!=2 )&&( lwipdev.dhcpstatus!=0xFF ) )
{
lwip_periodic_handle() ; //LWIP內核需要定時處理的函數
lwip_pkt_handle() ;
}
POINT_COLOR=RED;
LCD_ShowString( 30, 110, "LWIP Init Successed" ) ;
//打印動態IP地址
if( lwipdev.dhcpstatus==2 )
sprintf( ( char* )buf, "DHCP IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
//打印靜態IP地址
else
sprintf( ( char* )buf, "Static IP:%d.%d.%d.%d", lwipdev.ip[0], lwipdev.ip[1], lwipdev.ip[2], lwipdev.ip[3] ) ;
LCD_ShowString( 30, 130, buf ) ;
//得到網速
if( ( DM9000_Get_SpeedAndDuplex()&0x02 )==0x02 )
LCD_ShowString( 30, 150, "Ethernet Speed:10M" ) ;
else
LCD_ShowString( 30, 150, "Ethernet Speed:100M" ) ;
while( 1 )
{
lwip_periodic_handle() ;
lwip_pkt_handle() ;
delay_ms( 2 ) ;
}
}
-
存儲器
+關注
關注
38文章
7624瀏覽量
166223 -
DM9000
+關注
關注
0文章
24瀏覽量
17065 -
以太網控制器
+關注
關注
0文章
39瀏覽量
12923
發布評論請先 登錄
無線網絡通信新手求助!
stm32+lwip網絡通信
為什么網絡通信實驗用TCP客戶端模式時連接不上?
如何才能讓網絡通信實驗支持10個UDP鏈接?
為什么網絡通信實驗在網絡助手上接受到的先是456個字節?
請問探索者網絡通信實驗的控制網頁該怎么寫呢?
stm32f767網絡通信實驗移植iar少了幾個頭文件
阿波羅STM32F767網絡通信實驗直接用網線連接電腦通信不成功
基于原子STM32F4的攝像頭與網絡通信實驗
智多晶LWIP網絡通信系統介紹

評論