打印
[其他ST产品]

STM32F4+LWIP+交换机 TCP连接问题

[复制链接]
6112|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
申小林一号|  楼主 | 2015-10-28 17:08 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
最近做了一个东西,STM32 用交换机芯片做PHY,STM32端做服务器,每次拔掉网线之后再接上网线就不能连接了,请问是怎么回事呢?小弟才开始学习LWIP,不知道各位大侠有没有遇到过相关的问题呢?在这样的硬件条件下,如何判断网络是断开的呢?如何设置重连呢?
沙发
zhuotuzi| | 2015-10-28 17:13 | 只看该作者
LwIP是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。

使用特权

评论回复
板凳
zhuotuzi| | 2015-10-28 17:15 | 只看该作者
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】初始化
[cpp] view plaincopy


  • 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] = 'A';  
  •   netif->hwaddr[1] = 'R';  
  •   netif->hwaddr[2] = 'M';  
  •   netif->hwaddr[3] = 'N';  
  •   netif->hwaddr[4] = 'E';  
  •   netif->hwaddr[5] = 'T';  
  •    
  •   /* maximum transfer unit */  
  •   netif->mtu = 1500;  
  •    
  •   /* device capabilities */  
  •   /* don't 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. */  
  •   enc28j60_init(netif->hwaddr); // 【1】  
  • }  


    【说明】
        【1】 enc28j60_init(netif->hwaddr); low_level_init中指定了enc28j60中的网卡地址。

    【2】发送
[cpp] view plaincopy


  • static err_t  
  • low_level_output(struct netif *netif, struct pbuf *p)  
  • {  
  •   struct ethernetif *ethernetif = netif->state;  
  •   struct pbuf *q;  
  •   
  •   enc28j60_init_send(p->tot_len); //【1】initiate transfer();  
  •    
  • #if ETH_PAD_SIZE  
  •   pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */  
  • #endif  
  •   
  •   for(q = p; q != NULL; q = q->next) {  
  •     /* Send the data from the pbuf to the interface, one pbuf at a
  •        time. The size of the data in each pbuf is kept in the ->len
  •        variable. */  
  •     enc28j60_writebuf( q->payload, q->len ); //【2】send data from(q->payload, q->len);  
  •   }  
  •   
  •   enc28j60_start_send(); //【3】signal that packet should be sent();  
  •   
  • #if ETH_PAD_SIZE  
  •   pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */  
  • #endif  
  •    
  •   LINK_STATS_INC(link.xmit);  
  •   
  •   return ERR_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】接收
[cpp] view plaincopy


  • static struct pbuf *  
  • low_level_input(struct netif *netif)  
  • {  
  •   struct ethernetif *ethernetif = netif->state;  
  •   struct pbuf *p, *q;  
  •   u16_t len;  
  •   
  •   len = enc28j60_packet_getlen(); // 【1】  
  •   
  • #if ETH_PAD_SIZE  
  •   len += ETH_PAD_SIZE; /* allow room for Ethernet padding */  
  • #endif  
  •   
  •   /* We allocate a pbuf chain of pbufs from the pool. */  
  •   p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);  
  •    
  •   if (p != NULL) {  
  •   
  • #if ETH_PAD_SIZE  
  •     pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */  
  • #endif  
  •   
  •     for(q = p; q != NULL; q = q->next) {  
  •       enc28j60_readbuf (q->payload, q->len ); //【2】read data into(q->payload, q->len);  
  •     }  
  •     enc28j60_finish_receive(); //【3】acknowledge that packet has been read();  
  •   
  • #if ETH_PAD_SIZE  
  •     pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */  
  • #endif  
  •   
  •     LINK_STATS_INC(link.recv);  
  •   } else {  
  •     enc28j60_finish_receive(); //【4】drop packet();  
  •     LINK_STATS_INC(link.memerr);  
  •     LINK_STATS_INC(link.drop);  
  •   }  
  •   
  •   return p;  
  • }  


    【说明】
        【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函数。可以使用中断方法或查询方法。



使用特权

评论回复
地板
zhuotuzi| | 2015-10-28 17:15 | 只看该作者


2.lwipopt.h配置简述
    lwip中的配置选项非常的多,了解所有的配置非常不容易。本博文参考STM32官方的两个例子总结得到。
[cpp] view plaincopy


  • #ifndef __LWIPOPTS_H__  
  • #define __LWIPOPTS_H__  
  •   
  • #define SYS_LIGHTWEIGHT_PROT 0  
  •   
  • #define NO_SYS 1  
  •   
  • #define NO_SYS_NO_TIMERS 1  
  •   
  • /* ---------- Memory options ---------- */  
  • /* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
  •    lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
  •    byte alignment -> define MEM_ALIGNMENT to 2. */  
  • #define MEM_ALIGNMENT 4  
  •   
  • /* MEM_SIZE: the size of the heap memory. If the application will send
  • a lot of data that needs to be copied, this should be set high. */  
  • #define MEM_SIZE (5*1024)  
  •   
  • /* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
  •    sends a lot of data out of ROM (or other static memory), this
  •    should be set high. */  
  • #define MEMP_NUM_PBUF 10  
  • /* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
  •    per active UDP "connection". */  
  • #define MEMP_NUM_UDP_PCB 6  
  • /* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
  •    connections. */  
  • #define MEMP_NUM_TCP_PCB 10  
  • /* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
  •    connections. */  
  • #define MEMP_NUM_TCP_PCB_LISTEN 6  
  • /* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
  •    segments. */  
  • #define MEMP_NUM_TCP_SEG 12  
  • /* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
  •    timeouts. */  
  • #define MEMP_NUM_SYS_TIMEOUT 3  
  •   
  •   
  • /* ---------- Pbuf options ---------- */  
  • /* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */  
  • #define PBUF_POOL_SIZE 10  
  •   
  • /* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */  
  • #define PBUF_POOL_BUFSIZE 1500  
  •   
  •   
  • /* ---------- TCP options ---------- */  
  • #define LWIP_TCP 1  
  • #define TCP_TTL 255  
  •   
  • /* Controls if TCP should queue segments that arrive out of
  •    order. Define to 0 if y**ice is low on memory. */  
  • #define TCP_QUEUE_OOSEQ 0  
  •   
  • /* TCP Maximum segment size. */  
  • #define TCP_MSS (1500 - 40)  /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */  
  •   
  • /* TCP sender buffer space (bytes). */  
  • #define TCP_SND_BUF (2*TCP_MSS)  
  •   
  • /* TCP sender buffer space (pbufs). This must be at least = 2 *
  •    TCP_SND_BUF/TCP_MSS for things to work. */  
  • #define TCP_SND_QUEUELEN (6 * TCP_SND_BUF)/TCP_MSS  
  •   
  • /* TCP receive window. */  
  • #define TCP_WND (2*TCP_MSS)  
  •   
  •   
  • /* ---------- ICMP options ---------- */  
  • #define LWIP_ICMP 1  
  •   
  • /* ---------- DHCP options ---------- */  
  • /* Define LWIP_DHCP to 1 if you want DHCP configuration of
  •    interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
  •    turning this on does currently not work. */  
  • #define LWIP_DHCP 0  
  •   
  • /* ---------- UDP options ---------- */  
  • #define LWIP_UDP 1  
  • #define UDP_TTL 255  
  •   
  • /* ---------- Statistics options ---------- */  
  • #define LWIP_STATS 0  
  • #define LWIP_PROVIDE_ERRNO 1  
  •   
  • /**
  • * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
  • */  
  • #define LWIP_NETCONN 0  
  •   
  • /**
  • * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
  • */  
  • #define LWIP_SOCKET 0  
  •   
  • #endif /* __LWIPOPTS_H__ */  


    【具体说明和修改】
    【1】未使用操作系统,所有NO_SYS定义为1,LWIP_NETCONN定义为0(表示不使用),LWIP_SOCKET定义为0(表示不使用)。
    【2】NO_SYS_NO_TIMERS定义为1,该定义为LwIP1.4.0以上版本增加,具体可参考LwIP修改文档。
    【3】LWIP_DHCP被定义为0,关闭了DHCP功能以简化代码。
    【4】相比STM32官方例子,去除了校验码相关配置全部使用软件校验。STM32官方案例中使用了代码EMAC功能的MCU,该系列MCU中包括硬件校验功能,但是ENC28J60并没有此功能,所以只能开启LwIP中的软件校验功能。


使用特权

评论回复
5
zhuotuzi| | 2015-10-28 17:16 | 只看该作者

3.LwIP相关初始化
[cpp] view plaincopy


  • void LwIP_Config (void)  
  • {  
  •     struct ip_addr ipaddr;  
  •     struct ip_addr netmask;  
  •     struct ip_addr gw;  
  •      
  •     // 调用LWIP初始化函数  
  •     lwip_init();  
  •      
  •     IP4_ADDR(&ipaddr, 192, 168, 1, 16); // 设置网络接口的ip地址  
  •     IP4_ADDR(&netmask, 255, 255, 255, 0);    // 子网掩码  
  •     IP4_ADDR(&gw, 192, 168, 1, 1);   // 网关  
  •      
  •     // 初始化enc28j60与LWIP的接口,参数为网络接口结构体、ip地址、  
  •     // 子网掩码、网关、网卡信息指针、初始化函数、输入函数  
  •     netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);  
  •      
  •     // 把enc28j60设置为默认网卡  
  •     netif_set_default(&enc28j60);  
  •      
  •     netif_set_up(&enc28j60);  
  • }  


    【说明】
        【1】通过netif_add初始化网卡IP地址,子网掩码和网关地址。此处使用静态IP地址。
        【2】netif_add需要传入两个函数指针,分别是网卡初始化函数和接收内容处理函数。ethernetif_init位于ethernetif.c而ethernet_input并不位于ethernetif.c,此处也不能使用ethernetif_input,其实ethernet_input在函数ethernetif_input被调用,但是ethernet_input变了一个样子:
        netif->input(p, netif)!=ERR_OK
        【3】在带操作系统的移植中,最后一个参数使用tcpip_input。

4.while(1)部分
[cpp] view plaincopy


  • timer_typedef tcp_timer, arp_timer;  
  •   
  • /* 设定查询定时器 ARP定时器 */  
  • timer_set(&tcp_timer, CLOCK_SECOND / 10); // tcp处理定时器 100ms  
  • timer_set(&arp_timer, CLOCK_SECOND * 5); // arp处理定时器 5s  
  • while (1) {  
  •      
  •     if (enc28j60_packet_getcount() != 0) {  
  •         ethernetif_input(&enc28j60);  
  •     }  
  •      
  •     // TCP 定时处理  
  •     if (timer_expired(&tcp_timer)) {  
  •         timer_set(&tcp_timer, CLOCK_SECOND / 4);  
  •         tcp_tmr();  
  •     }  
  •      
  •     // ARP 定时处理  
  •     if (timer_expired(&arp_timer)) {  
  •         timer_set(&arp_timer, CLOCK_SECOND * 5);  
  •         etharp_tmr();  
  •     }  
  • }  


    【说明】
    while(1)循环包括3个主要功能
    【1】一旦接受到数据包,立刻调用 ethernetif_input。此处使用查询法而不是中断法(中断法效果相似)
    【2】定期处理TCP链接,定时时间为100ms,可根据情况适当缩小时间间隔。
    【3】定期更新ARP缓冲,可根据情况适当扩大时间间隔。
    【4】此处的timer通过systick实现,具体实现请参考代码仓库。

4.基本测试
    【1】ping实验
    此时网卡的静态IP地址为192.168.1.16,通过ping指令发送16个数据包
    ping 192.168.1.16 -n 16
file:///C:/Users/dell/AppData/Local/YNote/Data/xukai19871105@126.com/79584133fb6b486eaaadbf2272e96ceb/clipboard.png

图1 ping实验

    【2】TCP Echo例子
    LwIP提供很多示例,TCP Echo示例位于contrib-1.4.1的apps文件夹中,文件夹名为tcpecho_raw)。修改TCP侦听端口为10086。
    err = tcp_bind(echo_pcb, IP_ADDR_ANY, 10086);
file:///C:/Users/dell/AppData/Local/YNote/Data/xukai19871105@126.com/18259b4ebe074ebaad5bbadb661b0de0/clipboard.png



图2 TCP Echo例子


5.总结
    【1】移植和应用LwIP一定要耐心细致。
    【2】一旦网卡接收到数据,应调用ethernetif_input函数,调用该函数让数据进入LwIP协议栈。
    【3】 netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);最后一个参数为ethernet_input,千万必要写成ethernetif_input。

6.参考资料
    更多细节内容请参考图书资料
    【1】《嵌入式网络系统设计——基于Atmel ARM7系列》
    【2】《STM32嵌入式系统开发实战指南——FreeRTOS与LwIP联合移植》

使用特权

评论回复
6
zhuotuzi| | 2015-10-28 17:17 | 只看该作者
2.lwipopt.h配置简述
    lwip中的配置选项非常的多,了解所有的配置非常不容易。本博文参考STM32官方的两个例子总结得到。
[cpp] view plaincopy


  • #ifndef __LWIPOPTS_H__  
  • #define __LWIPOPTS_H__  
  •   
  • #define SYS_LIGHTWEIGHT_PROT 0  
  •   
  • #define NO_SYS 1  
  •   
  • #define NO_SYS_NO_TIMERS 1  
  •   
  • /* ---------- Memory options ---------- */  
  • /* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
  •    lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
  •    byte alignment -> define MEM_ALIGNMENT to 2. */  
  • #define MEM_ALIGNMENT 4  
  •   
  • /* MEM_SIZE: the size of the heap memory. If the application will send
  • a lot of data that needs to be copied, this should be set high. */  
  • #define MEM_SIZE (5*1024)  
  •   
  • /* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
  •    sends a lot of data out of ROM (or other static memory), this
  •    should be set high. */  
  • #define MEMP_NUM_PBUF 10  
  • /* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
  •    per active UDP "connection". */  
  • #define MEMP_NUM_UDP_PCB 6  
  • /* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
  •    connections. */  
  • #define MEMP_NUM_TCP_PCB 10  
  • /* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
  •    connections. */  
  • #define MEMP_NUM_TCP_PCB_LISTEN 6  
  • /* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
  •    segments. */  
  • #define MEMP_NUM_TCP_SEG 12  
  • /* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
  •    timeouts. */  
  • #define MEMP_NUM_SYS_TIMEOUT 3  
  •   
  •   
  • /* ---------- Pbuf options ---------- */  
  • /* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */  
  • #define PBUF_POOL_SIZE 10  
  •   
  • /* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */  
  • #define PBUF_POOL_BUFSIZE 1500  
  •   
  •   
  • /* ---------- TCP options ---------- */  
  • #define LWIP_TCP 1  
  • #define TCP_TTL 255  
  •   
  • /* Controls if TCP should queue segments that arrive out of
  •    order. Define to 0 if y**ice is low on memory. */  
  • #define TCP_QUEUE_OOSEQ 0  
  •   
  • /* TCP Maximum segment size. */  
  • #define TCP_MSS (1500 - 40)  /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */  
  •   
  • /* TCP sender buffer space (bytes). */  
  • #define TCP_SND_BUF (2*TCP_MSS)  
  •   
  • /* TCP sender buffer space (pbufs). This must be at least = 2 *
  •    TCP_SND_BUF/TCP_MSS for things to work. */  
  • #define TCP_SND_QUEUELEN (6 * TCP_SND_BUF)/TCP_MSS  
  •   
  • /* TCP receive window. */  
  • #define TCP_WND (2*TCP_MSS)  
  •   
  •   
  • /* ---------- ICMP options ---------- */  
  • #define LWIP_ICMP 1  
  •   
  • /* ---------- DHCP options ---------- */  
  • /* Define LWIP_DHCP to 1 if you want DHCP configuration of
  •    interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
  •    turning this on does currently not work. */  
  • #define LWIP_DHCP 0  
  •   
  • /* ---------- UDP options ---------- */  
  • #define LWIP_UDP 1  
  • #define UDP_TTL 255  
  •   
  • /* ---------- Statistics options ---------- */  
  • #define LWIP_STATS 0  
  • #define LWIP_PROVIDE_ERRNO 1  
  •   
  • /**
  • * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
  • */  
  • #define LWIP_NETCONN 0  
  •   
  • /**
  • * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
  • */  
  • #define LWIP_SOCKET 0  
  •   
  • #endif /* __LWIPOPTS_H__ */  


    【具体说明和修改】
    【1】未使用操作系统,所有NO_SYS定义为1,LWIP_NETCONN定义为0(表示不使用),LWIP_SOCKET定义为0(表示不使用)。
    【2】NO_SYS_NO_TIMERS定义为1,该定义为LwIP1.4.0以上版本增加,具体可参考LwIP修改文档。
    【3】LWIP_DHCP被定义为0,关闭了DHCP功能以简化代码。
    【4】相比STM32官方例子,去除了校验码相关配置全部使用软件校验。STM32官方案例中使用了代码EMAC功能的MCU,该系列MCU中包括硬件校验功能,但是ENC28J60并没有此功能,所以只能开启LwIP中的软件校验功能。

使用特权

评论回复
7
qq280572| | 2015-10-28 17:26 | 只看该作者
网卡拔插没处理好。看这颗芯片支不支持;
使用协议栈自带的keep alive功能,自动检测网络是否掉线,来释放资源。利用lwip的回调机制,每次建立连接成功的时候回调用acept函数,在里面加一个flag来判断就好。--转至网络。

使用特权

评论回复
8
lefeng| | 2015-10-29 10:28 | 只看该作者
LwIP学习笔记——STM32 ENC28J60移植与入门

3楼怎么把链接做成文字的

使用特权

评论回复
9
泰山特曲123| | 2015-10-29 13:09 | 只看该作者
利用网卡的热插拔中断,重新初始化网卡

使用特权

评论回复
10
september7| | 2015-10-29 19:48 | 只看该作者
不错,谢谢楼主的分享了,以太网没搞过,赞一个。

使用特权

评论回复
11
可可球| | 2015-10-29 20:19 | 只看该作者
一旦网卡接收到数据,应调用ethernetif_input函数,调用该函数让数据进入LwIP协议栈

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

26

主题

471

帖子

2

粉丝