背景
項目中需要使用網絡,開始使用的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的發送和接收函數中死等的地方增加操作系統的延時。
總結
遇到問題不要着急,總會有解決的方案,現在沒有隻是時候未到。說明目前掌握的線索還不足以破案,需要繼續分析研究。黔驢技窮後多和同事請教討論,很多時候會思維定式,別人的某句話可能就能給你提供有用的線索。
再次感謝浩然電子的杜工耐心的幫助。
再此記錄下來艱辛歷程,希望可以幫到其他人。
COMMENTS