LwIP學習筆記——STM32 ENC28J60移植與入門
0.前言
去年(2013年)的整理了LwIP相關代碼,并在STM32上“裸奔”成功。一直沒有時間深入整理,在這里借博文整理總結。LwIP的移植過程細節(jié)很多,博文也不可能一一詳解個別部分只能點到為止。
【本文要點】
【1】不帶操作系統(tǒng)的LwIP移植,LwIP版本為1.4.1。
【2】MCU為STM32F103VE,網卡為ENC28J60。
【3】移植過程重點描述ethernetif.c和LwIP宏配置等。
【4】一個簡單的TCP echo例子。
【5】力求簡單,沒有DHCP功能,甚至沒有用到網卡中斷。
【代碼倉庫】
代碼倉庫位于Bitbucket(要源代碼請點擊這里)。博文中不能把每個細節(jié)描述清楚,更多內容請參考代碼倉庫中的具體代碼。
【硬件說明】
測試平臺使用奮斗版,原理圖請參考代碼倉庫中的DOC目錄。
【參考博文】
學習嵌入式網絡是一個循序漸進的過程,從淺入深從簡單到復雜。
【1】ENC28J60學習筆記——學習網卡
【2】STM32NET學習筆記——索引——理解TCPIP協議棧
【3】uIP學習筆記——初次應用協議棧
【4】Yeelink平臺使用——遠程控制 RT Thread + LwIP+ STM32——更加實用的做法
1.ethernetif.c的相關修改
雖然LwIP移植過程比較復雜,但是只要結合網卡具體功能,耐心修改ethernetif.c即可。ethernetif.c重點實現網卡的三個功能,初始化,發(fā)送和接收。
為了更好的配合lwIP,修改了ENC28J60學習筆記中部分驅動函數。(換句話說,想要從0開始移植LwIP必須對操作網卡非常熟悉)
【1】初始化
staticvoid
low_level_init(structnetif*netif)
{
structethernetif*ethernetif=netif->state;
/*setMAChardwareaddresslength*/
netif->hwaddr_len=ETHARP_HWADDR_LEN;
/*setMAChardwareaddress*/
netif->hwaddr[0]='A';
netif->hwaddr[1]='R';
netif->hwaddr[2]='M';
netif->hwaddr[3]='N';
netif->hwaddr[4]='E';
netif->hwaddr[5]='T';
/*maximumtransferunit*/
netif->mtu=1500;
/*devicecapabilities*/
/*don'tsetNETIF_FLAG_ETHARPifthisdeviceisnotanethernetone*/
netif->flags=NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;
/*Dowhateverelseisneededtoinitializeinterface.*/
enc28j60_init(netif->hwaddr);//【1】
}
【說明】
【1】enc28j60_init(netif->hwaddr); low_level_init中指定了enc28j60中的網卡地址。
【2】發(fā)送
staticerr_t
low_level_output(structnetif*netif,structpbuf*p)
{
structethernetif*ethernetif=netif->state;
structpbuf*q;
enc28j60_init_send(p->tot_len);//【1】initiatetransfer();
#ifETH_PAD_SIZE
pbuf_header(p,-ETH_PAD_SIZE);/*dropthepaddingword*/
#endif
for(q=p;q!=NULL;q=q->next){
/*Sendthedatafromthepbuftotheinterface,onepbufata
time.Thesizeofthedataineachpbufiskeptinthe->len
variable.*/
enc28j60_writebuf(q->payload,q->len);//【2】senddatafrom(q->payload,q->len);
}
enc28j60_start_send();//【3】signalthatpacketshouldbesent();
#ifETH_PAD_SIZE
pbuf_header(p,ETH_PAD_SIZE);/*reclaimthepaddingword*/
#endif
LINK_STATS_INC(link.xmit);
returnERR_OK;
}
【說明】
【1】enc28j60_init_send(p->tot_len);初始化發(fā)送緩沖區(qū)大小,pbuf結構為一個鏈表,第一個pbuf結構體中的tot_len字段代表整個以太網數據包的大小。
【2】enc28j60_writebuf( q->payload, q->len ); 通過遍歷鏈表把內容填入ENC28J60的緩沖區(qū)中。
【3】enc28j60_start_send();啟動網卡發(fā)送。
【3】接收
staticstructpbuf*
low_level_input(structnetif*netif)
{
structethernetif*ethernetif=netif->state;
structpbuf*p,*q;
u16_tlen;
len=enc28j60_packet_getlen();//【1】
#ifETH_PAD_SIZE
len+=ETH_PAD_SIZE;/*allowroomforEthernetpadding*/
#endif
/*Weallocateapbufchainofpbufsfromthepool.*/
p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);
if(p!=NULL){
#ifETH_PAD_SIZE
pbuf_header(p,-ETH_PAD_SIZE);/*dropthepaddingword*/
#endif
for(q=p;q!=NULL;q=q->next){
enc28j60_readbuf(q->payload,q->len);//【2】readdatainto(q->payload,q->len);
}
enc28j60_finish_receive();//【3】acknowledgethatpackethasbeenread();
#ifETH_PAD_SIZE
pbuf_header(p,ETH_PAD_SIZE);/*reclaimthepaddingword*/
#endif
LINK_STATS_INC(link.recv);
}else{
enc28j60_finish_receive();//【4】droppacket();
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
}
returnp;
}
【說明】
【1】len = enc28j60_packet_getlen(); 獲得網卡中數據包的長度。
【2】enc28j60_readbuf (q->payload, q->len);把網卡中的內容復制到內存池中。
【3】enc28j60_finish_receive();接收完成,移動網卡中緩沖區(qū)指針。
【4】應用
【1】LwIP網卡硬件初始化調用ethernetif_init即可,該函數中調用了low_level_init,并指定了網卡輸出函數low_level_output。
【2】一旦網卡有數據進入,應立即代用ethernetif_input函數??梢允褂弥袛喾椒ɑ虿樵兎椒ā?/p>
2.lwipopt.h配置簡述
lwip中的配置選項非常的多,了解所有的配置非常不容易。本博文參考STM32官方的兩個例子總結得到。
#ifndef__LWIPOPTS_H__
#define__LWIPOPTS_H__
#defineSYS_LIGHTWEIGHT_PROT0
#defineNO_SYS1
#defineNO_SYS_NO_TIMERS1
/*----------Memoryoptions----------*/
/*MEM_ALIGNMENT:shouldbesettothealignmentoftheCPUforwhich
lwIPiscompiled.4bytealignment->defineMEM_ALIGNMENTto4,2
bytealignment->defineMEM_ALIGNMENTto2.*/
#defineMEM_ALIGNMENT4
/*MEM_SIZE:thesizeoftheheapmemory.Iftheapplicationwillsend
a lot of data that needs to be copied, this