LwIP学习笔记——STM32 ENC28J60移植与入门
扫描二维码
随时随地手机看文章
0.前言
去年(2013年)的整理了LwIP相关代码,并在STM32上“裸奔”成功。一直没有时间深入整理,在这里借博文整理总结。LwIP的移植过程细节很多,博文也不可能一一详解个别部分只能点到为止。
【本文要点】
【1】不带操作系统的LwIP移植,LwIP版本为1.4.1。
【2】MCU为STM32F103VE,网卡为ENC28J60。
【3】移植过程重点描述ethernetif.c和LwIP宏配置等。
【4】一个简单的TCP echo例子。
【5】力求简单,没有DHCP功能,甚至没有用到网卡中断。
【代码仓库】
代码仓库位于Bitbucket(要源代码请点击这里)。博文中不能把每个细节描述清楚,更多内容请参考代码仓库中的具体代码。
【硬件说明】
测试平台使用奋斗版,原理图请参考代码仓库中的DOC目录。
【参考博文】
学习嵌入式网络是一个循序渐进的过程,从浅入深从简单到复杂。
【1】ENC28J60学习笔记——学习网卡
【2】STM32NET学习笔记——索引——理解TCPIP协议栈
【3】uIP学习笔记——初次应用协议栈
【4】Yeelink平台使用——远程控制 RT Thread + LwIP+ STM32——更加实用的做法
1.ethernetif.c的相关修改
虽然LwIP移植过程比较复杂,但是只要结合网卡具体功能,耐心修改ethernetif.c即可。ethernetif.c重点实现网卡的三个功能,初始化,发送和接收。
为了更好的配合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】发送
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);初始化发送缓冲区大小,pbuf结构为一个链表,第一个pbuf结构体中的tot_len字段代表整个以太网数据包的大小。
【2】enc28j60_writebuf( q->payload, q->len ); 通过遍历链表把内容填入ENC28J60的缓冲区中。
【3】enc28j60_start_send();启动网卡发送。
【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();接收完成,移动网卡中缓冲区指针。
【4】应用
【1】LwIP网卡硬件初始化调用ethernetif_init即可,该函数中调用了low_level_init,并指定了网卡输出函数low_level_output。
【2】一旦网卡有数据进入,应立即代用ethernetif_input函数。可以使用中断方法或查询方法。
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