项目中需要使用网络,开始使用的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心跳包,不知道为什么服务器没有发出来。参考了同事和浩然电子杜工的建议,进行了如下修改,问题得到解决,目前运行十几天,设备没有断过一次。
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的发送和接收函数中死等的地方增加操作系统的延时。
遇到问题不要着急,总会有解决的方案,现在没有只是时候未到。说明目前掌握的线索还不足以破案,需要继续分析研究。黔驴技穷后多和同事请教讨论,很多时候会思维定式,别人的某句话可能就能给你提供有用的线索。
再次感谢浩然电子的杜工耐心的帮助。
再此记录下来艰辛历程,希望可以帮到其他人。