stm32f103 w5500 tcp server

背景

项目中需要使用网络,开始使用的stm32f4+lwip的方案,但是硬件成本有些高,更主要的是lwip不好用,老是断,可能是自己没有研究透它吧。经过长时间的调研论证,最终选择了w5500这款芯片。它把TCP/IP网络协议栈固化在了硬件芯片中,为用户留出应用层接口,简单稳定。

移植过程

首先,从https://w5500.com/上下载芯片手册和参考代码了解芯片的原理及基本用法,内容不是很复杂。项目中需要W5500作为TCP Server,会有三个客户端连接。参考相关代码,移植到项目中,具体代码如下。

配置

static void net_set_config(void)
{
    
    
    unsigned char mac[6] = {
    
    0x00, 0x08, 0xdc, 0x11, 0x11, 0x11};
    unsigned char ip[4]  = {
    
    192, 168, 1, 100};
    unsigned char sub[4] = {
    
    255, 255, 255, 0};
    unsigned char gw[4]  = {
    
    192, 168, 1, 1};
    unsigned char tx_size[8] = {
    
    2,2,2,2,2,2,2,2};
    unsigned char rx_size[8] = {
    
    2,2,2,2,2,2,2,2};

    /* 设置Mac地址 */
    set_mac_addr(mac);
    /* 设置IP */
    set_source_ip_addr(ip);
    /* 设置子网掩码 */
    set_subnet_mask(sub);
    /* 设置网关 */
    set_gateway(gw);

    /* 初始化8个socket */
    sys_init(tx_size, rx_size);
    /* 设置超时时间 */
    set_retrans_time(2000);
    /* 设置最大重新发送次数 */
    set_retrans_num(3);
	
    /* 打开心跳功能 */
    set_keepalive(SOCKET_ID_0);
	set_keepalive(SOCKET_ID_1);
	set_keepalive(SOCKET_ID_2);	
}

状态机

int net_process_socket(SOCKET sock, unsigned short port)
{
    
    
    int ret = -1;
    unsigned char state = SOCK_CLOSED;
    unsigned short len = 0;
    unsigned char data[MAX_MSG_LEN];

    state = get_sock_status(sock);
    switch (state) {
    
    
        case SOCK_INIT:
            listen(sock);
            break;
        case SOCK_ESTABLISHED:
            if (get_sock_interrupt_status(sock) & Sn_IR_CON) {
    
    
                set_sock_interrupt_status(sock, Sn_IR_CON);
            }
            len = get_sock_rx_free_buff_size(sock);
            if (len > 0) {
    
    
                recv(sock, data, MAX_MSG_LEN);
				/* process the recv data */
            }
            break;
        case SOCK_CLOSE_WAIT:
            disconnect(sock);
            break;
        case SOCK_CLOSED:
            ret = socket(sock, Sn_MR_TCP, port, Sn_MR_ND);
            break;
        default:
            break;
    }

    return 0;
}

运行

void net_task(void *arg)
{
    
        
    /* 配置网络信息 */
    net_set_config();

    while (1) {
    
     
        net_process_socket(SOCKET_ID_0, 12345);
        net_process_socket(SOCKET_ID_1, 12345);
        net_process_socket(SOCKET_ID_2, 12345);
		
		/* 延时20ms */
        vTaskDelay(100);
    }
}

w5500最多支持8个socket连接,由于项目中需要三个客户端,所以,配置了三个socket。然后,在单独的线程中依次运行每个socket对应的状态机。具体的基础知识不去细说,官网和博客中有非常多的描述。

问题

按说,按照官网的设置后,应该可以正常运行。确实可以运行了,三个客户端也都可以连接上,但是,设备长时间测试中发现,客户端和服务器之间的连接经常断开。一旦断了,有的客户端可以连上,有的客户端就再也连接不上了。解决的方式只能是重启设备,这样肯定是不行的啊。

客户端是有重连机制的,会周期地发送心跳包给服务器,超时后就重新连接服务器,整个机制都没有问题,问题就是始终连接不上服务器了。采用的有线连接,按说本身就不应该断开的。这个问题折腾了一个月多都没有头绪,设备出厂的日期也只能往后推了。

解决方案

首先,通过网络调试助手模拟客户端,长时间运行看看会不会断,没有断过。通过wireshark抓包,发现没有keepalive心跳包,不知道为什么服务器没有发出来。参考了同事和浩然电子杜工的建议,进行了如下修改,问题得到解决,目前运行十几天,设备没有断过一次。

  • 增加备用socket
    有三个客户端,那么就开六个客户端,每个客户端有一个正常使用的,一个备用的,一旦连接断掉,另一个立马启用,无缝切换。
  • 心跳处理
    自动发送心跳改成手动发送。
  • 增加延时
    官网上的资料包均是基于裸机的演示程序,而我们的项目是多线程的,所以,需要在死等的地方修改成延时。

配置

static void net_set_config(void)
{
    
    
    unsigned char mac[6] = {
    
    0x00, 0x08, 0xdc, 0x11, 0x11, 0x11};
    unsigned char ip[4]  = {
    
    192, 168, 1, 100};
    unsigned char sub[4] = {
    
    255, 255, 255, 0};
    unsigned char gw[4]  = {
    
    192, 168, 1, 1};
    unsigned char tx_size[8] = {
    
    2,2,2,2,2,2,2,2};
    unsigned char rx_size[8] = {
    
    2,2,2,2,2,2,2,2};

    /* 设置Mac地址 */
    set_mac_addr(mac);
    /* 设置IP */
    set_source_ip_addr(ip);
    /* 设置子网掩码 */
    set_subnet_mask(sub);
    /* 设置网关 */
    set_gateway(gw);

    /* 初始化8个socket */
    sys_init(tx_size, rx_size);
    /* 设置超时时间 */
    set_retrans_time(2000);
    /* 设置最大重新发送次数 */
    set_retrans_num(3);	
}

不去开启心跳自动发送机制。

状态机

int net_process_socket(SOCKET sock, unsigned short port)
{
    
    
    int ret = -1;
    unsigned char state = SOCK_CLOSED;
    unsigned short len = 0;
    unsigned char data[MAX_MSG_LEN];
    static unsigned char sock_est_flag[8] = {
    
    1, 1, 1, 1, 1, 1, 1, 1};
    SOCKET another_sock;

    state = get_sock_status(sock);
    switch (state) {
    
    
        case SOCK_INIT:
            listen(sock);
            sock_est_flag[sock] = 1;
            break;
        case SOCK_ESTABLISHED:
            if (sock_est_flag[sock] == 1) {
    
    
                sock_est_flag[sock] = 0;
                if ((sock % 2) == 0) {
    
    
                    another_sock = sock + 1;
                } else {
    
    
                    another_sock = sock - 1;
                }
                if (get_sock_status(another_sock) == SOCK_ESTABLISHED) {
    
    
                    close(another_sock);
                }
            }
            
            if (get_sock_interrupt_status(sock) & Sn_IR_CON) {
    
    
                set_sock_interrupt_status(sock, Sn_IR_CON);
            }
            len = get_sock_rx_free_buff_size(sock);
            if (len > 0) {
    
    
                recv(sock, data, MAX_MSG_LEN);
                /* process the recv data */
            }
            break;
        case SOCK_CLOSE_WAIT:
            disconnect(sock);
            break;
        case SOCK_CLOSED:
            ret = socket(sock, Sn_MR_TCP, port, Sn_MR_ND);
            break;
        default:
            break;
    }

    return 0;
}

增加备用socket。备用socket时刻处于listen状态。

运行

void net_task(void *arg)
{
    
        
	int i = 0;
	int counter = 0;

    /* 配置网络信息 */
    net_set_config();

    while (1) {
    
     
        net_process_socket(SOCKET_ID_0, 12345);
        net_process_socket(SOCKET_ID_1, 12345);
        net_process_socket(SOCKET_ID_2, 12346);
        net_process_socket(SOCKET_ID_3, 12346);
        net_process_socket(SOCKET_ID_4, 12347);
        net_process_socket(SOCKET_ID_5, 12347);

		if ((counter++ % 100) == 0) {
    
    
			for (i = 0 ; i < 6; i++) {
    
    
				iinchip_write_data(Sn_CR(i),Sn_CR_SEND_KEEP);
			}
		}
		
		/* 延时20ms */
        vTaskDelay(100);
    }
}

手动发送keepalive心跳包,每10s发送一次。

延时

另外, 非常重要的一点是,在socket的发送和接收函数中死等的地方增加操作系统的延时。

总结

遇到问题不要着急,总会有解决的方案,现在没有只是时候未到。说明目前掌握的线索还不足以破案,需要继续分析研究。黔驴技穷后多和同事请教讨论,很多时候会思维定式,别人的某句话可能就能给你提供有用的线索。

再次感谢浩然电子的杜工耐心的帮助。
再此记录下来艰辛历程,希望可以帮到其他人。

猜你喜欢

转载自blog.csdn.net/donglicaiju76152/article/details/95735091