正点原子的网络实验4 RAW_TCP客户端实验解读

有项目要用以太网,考虑到项目周期和目前广泛应用的TCPIP协议栈,我选择了lwip,根据以前公司

有的407的板子历程,我选择了网络实验4 RAW_TCP客户端实验历程作为入门学习,基于裸编环境,

现在把学习到的心得记录如下,希望能够帮助需要的人。至于lwip的细节部分,请看

《STM32F4 LWIP开发手册_V2.1.pdf》

《LwIP协议栈的设计与实现_中文译稿.pdf》

《LwIP协议栈源码详解.pdf》

rawapi.txt                              这篇入门重点看

《LwIP应用指南 V0.01.pdf》这篇入门重点看,介绍几个回调函数的意义

资料历程下载地址:lwipv1.41RAMTCP客户端学习资料+源码历程_if(es!=NULL)//连接处于空闲可以发送数据-网络基础代码类资源-CSDN下载

      Additionaly, memory (de-)allocation functions may be
      called from multiple threads (not ISR!) with NO_SYS=0
      since they are protected by SYS_LIGHTWEIGHT_PROT and/or
      semaphores.

此外,NO_SYS=0时候(这个时候有操作系统),释放内存函数

可以被多线程(不是ISR,即中断服务)调用,因为它们被

SYS_LIGHTWEIGHT_PROT和或semaphores保护。

      Only since 1.3.0, if SYS_LIGHTWEIGHT_PROT is set to 1
      and LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT is set to 1,
      pbuf_free() may also be called from another thread or
      an ISR (since only then, mem_free - for PBUF_RAM - may
      be called from an ISR: otherwise, the HEAP is only
      protected by semaphores).

仅从1.3.0开始,如果SYS_LIGHTWEIGHT_PROT设置为1
       和LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT设置为1,
       pbuf_free()也可以从另一个线程调用
       一个ISR(因为只有这样,mem_free  - 对于PBUF_RAM  - 可能
       从ISR调用:否则,HEAP只是
       受信号量保护)。

一、应用到的资源

详细的资源请看官方历程,这里只是介绍大概。

1、PYH芯片:LAN8720;

2、MCU:STM32F407ZGT6;

3、MCU资源:

     1)内部RAM:110K,初始化为内存池

//mem1内存参数设定,mem1完全处于内部SRAM里面
#define MEM1_BLOCK_SIZE	32  			//内存块大小为32字节
#define MEM1_MAX_SIZE		100*1024 	//最大管理内存 110k
#define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE/MEM1_BLOCK_SIZE  //内存表大小

     2)心跳定时器,历程在没有开启操作系统情况下,是提供精确软延时(看历程是死查询);

//延时nms 
//nms:0~65535
void delay_ms(u16 nms)
{	 	 
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;           //清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器
}

     3)定时器3,提供10ms的中断,作为lwip的心跳定时器;

TIM3_Int_Init(999,839); 	//100khz的频率,计数1000为10ms
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
	{
		lwip_localtime +=10; //加10
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //清除中断标志位
}

这里没必要单独占用一个timer中断,浪费。一些和时间先关的,都可以放到这个中断里面。

其他的按键、屏什么的,可以忽略。

4、lwip相关

     1)初始化:                   lwip_comm_init()

     2)lwip的poll回调函数:lwip_periodic_handle()

     3)TCP Client的demo:tcp_client_test()

     4)以太网的接受中断: lwip_pkt_handle()

二、初始化

初始化看具体的lwip_comm_init()函数,这里需要注意的是里面的初始化都是采用阻塞式的,

并且在不插网线情况下是初始化失败的。

如果要用裸编方式,即lwip的RAW编程模式,则必须解决阻塞问题。

看下阻塞的地方:

LAN8720_Init()的

    LAN8720_RST=0;                    //硬件复位LAN8720
    delay_ms(50);    
    LAN8720_RST=1;                     //复位结束

ETH_MACDMA_Config()的

while (ETH_GetSoftwareResetStatus() == SET);//等待软件重启网络完成 

ETH_Init()的

1)

  /* Delay to assure PHY reset */
  _eth_delay_(PHY_RESET_DELAY);

2)

就是如下的地方,一直在死等网线被插入,超时报错。

    /* We wait for linked status... */
    do
    {
      timeout++;
    } while (!(ETH_ReadPHYRegister(PHYAddress, PHY_BSR) & PHY_Linked_Status) && (timeout < PHY_READ_TO));

3)

    /* Wait until the auto-negotiation will be completed */
    do
    {
      timeout++;
    } while (!(ETH_ReadPHYRegister(PHYAddress, PHY_BSR) & PHY_AutoNego_Complete) && (timeout < (uint32_t)PHY_READ_TO)); 

4)

    /* Delay to assure PHY configuration */
    _eth_delay_(PHY_CONFIG_DELAY);

硬件底层的:

ETH_ReadPHYRegister()

  do
  {
    timeout++;
    tmpreg = ETH->MACMIIAR;
  } while ((tmpreg & ETH_MACMIIAR_MB) && (timeout < (uint32_t)PHY_READ_TO));

ETH_WritePHYRegister()

  /* Check for the Busy flag */
  do
  {
    timeout++;
    tmpreg = ETH->MACMIIAR;
  } while ((tmpreg & ETH_MACMIIAR_MB) && (timeout < (uint32_t)PHY_WRITE_TO));

我测试了下这里寄存器操作大概在timeout=380次左右,并且和网线插拔无关。这块我没有改

,因为改动起来,影响太大。所有调用的地方都需要改。因此除了ETH_ReadPHYRegister()和

ETH_WritePHYRegister(),其它所有函数都要改写为状态机。

三、lwip_periodic_handle()

这个是Lwip要求的周期性的调用函数,工程里面做的有点乱,其思路用该是放到main里面,然后

不停插延时标志,根据各个延时标志调用想用函数。

//LWIP轮询任务
void lwip_periodic_handle()
{
#if LWIP_TCP
	//每250ms调用一次tcp_tmr()函数
  if (lwip_localtime - TCPTimer >= TCP_TMR_INTERVAL)
  {
    TCPTimer =  lwip_localtime;
    tcp_tmr();
  }
#endif
  //ARP每5s周期性调用一次
  if ((lwip_localtime - ARPTimer) >= ARP_TMR_INTERVAL)
  {
    ARPTimer =  lwip_localtime;
    etharp_tmr();
  }

#if LWIP_DHCP //如果使用DHCP的话
  //每500ms调用一次dhcp_fine_tmr()
  if (lwip_localtime - DHCPfineTimer >= DHCP_FINE_TIMER_MSECS)
  {
    DHCPfineTimer =  lwip_localtime;
    dhcp_fine_tmr();
    if ((lwipdev.dhcpstatus != 2)&&(lwipdev.dhcpstatus != 0XFF))
    { 
      lwip_dhcp_process_handle();  //DHCP处理
    }
  }

  //每60s执行一次DHCP粗糙处理
  if (lwip_localtime - DHCPcoarseTimer >= DHCP_COARSE_TIMER_MSECS)
  {
    DHCPcoarseTimer =  lwip_localtime;
    dhcp_coarse_tmr();
  }  
#endif
}

模板的工程是到处调用(while(1)循环的地方都要调用),由于改为了FSM,我的工程只在一处调用

main()的while(1)里面:
 

    while(1){
#ifndef __DEBUG
        WDG_CLEAR();                        //Çå¹·
#endif

        SYS_CALL_SUB_FSM();                 //ÄÚºË

        user_tcpip_process();               //lwipÈÎÎñ
    }

然后:

void user_tcpip_process(void)
{
    tcpip_process();                        //TCPIP Á¬½Ó·þÎñÆ÷ÈÎÎñ
    if(CHECK_TCP_CLIENT_FLAG(TCP_CLIENT_FLAG_LWIP_INIT)){
        lwip_periodic_handle();             //lwip¶¨Ê±ÈÎÎñ
    }
}

这样思路就比较清晰了。

四、tcp_client_test()

这个函数是实现创建TCP客户端的核心,具体的实现看源码,我只讲个大概。

	tcppcb=tcp_new();	//创建一个新的pcb
	if(tcppcb)			//创建成功
	{
		IP4_ADDR(&rmtipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]); 
		tcp_connect(tcppcb,&rmtipaddr,TCP_CLIENT_PORT,tcp_client_connected);  //连接到目的地址的指定端口上,当连接成功后回调tcp_client_connected()函数
 	}else res=1;

这段就创建了一个TCP控制块,并且用它去连接一个服务器,这个函数是立即返回,并且不报告连接是否

成功,那么如何判断是否连接成功了呢?如果连接成功了,那么就会调用我们传入的回调函数tcp_client_connected

来看下它的实现:

//lwIP TCP连接建立后调用回调函数
err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
	struct tcp_client_struct *es=NULL;  
	if(err==ERR_OK)   
	{
		es=(struct tcp_client_struct*)mem_malloc(sizeof(struct tcp_client_struct));  //申请内存
		if(es) //内存申请成功
		{
 			es->state=ES_TCPCLIENT_CONNECTED;//状态为连接成功
			es->pcb=tpcb;  
			es->p=NULL; 
			tcp_arg(tpcb,es);        			//使用es更新tpcb的callback_arg
			tcp_recv(tpcb,tcp_client_recv);  	//初始化LwIP的tcp_recv回调功能   
			tcp_err(tpcb,tcp_client_error); 	//初始化tcp_err()回调函数
			tcp_sent(tpcb,tcp_client_sent);		//初始化LwIP的tcp_sent回调功能
			tcp_poll(tpcb,tcp_client_poll,1); 	//初始化LwIP的tcp_poll回调功能 
 			tcp_client_flag|=1<<5; 				//标记连接到服务器了
			err=ERR_OK;
		}else
		{ 
			tcp_client_connection_close(tpcb,es);//关闭连接
			err=ERR_MEM;	//返回内存分配错误
		}
	}else
	{
		tcp_client_connection_close(tpcb,0);//关闭连接
	}
	return err;
}

如果连接成功,那么就会初始化所有的callback函数,并且初始化了一个成功标志。这里看到有个申请内存的动作,

大小是一个应用层的tcp_client_struct大小,并且当做参数传递给了tcp块,

tcp_arg(tpcb,es);                    //使用es更新tpcb的callback_arg

这里一样要注意,close时候,如果已经连接了服务器,那么就要在close时候传入指针释放掉,否则内存泄漏;

如果没有连接成功,则close时候传入NULL。我感觉这样做很麻烦,一不小心就内存泄漏了,用一个static变量,

更合适。

再看看tcp_client_test()怎么处理的:

		delay_ms(2);
		t++;
		if(t==200)
		{
			if((tcp_client_flag&1<<5)==0)//未连接上,则尝试重连
			{ 
				tcp_client_connection_close(tcppcb,0);//关闭连接
				tcppcb=tcp_new();	//创建一个新的pcb
				if(tcppcb)			//创建成功
				{ 
					tcp_connect(tcppcb,&rmtipaddr,TCP_CLIENT_PORT,tcp_client_connected);//连接到目的地址的指定端口上,当连接成功后回调tcp_client_connected()函数
				}
			}
			t=0;
			LED0=!LED0;
		}

如果2*200=400ms没有连接上,则重现发起连接,但是连接前必须先调用

tcp_client_connection_close(tcppcb,0);//关闭连接

看看它的实现:

//关闭与服务器的连接
void tcp_client_connection_close(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
	//移除回调
	tcp_abort(tpcb);//终止连接,删除pcb控制块
	tcp_arg(tpcb,NULL);  
	tcp_recv(tpcb,NULL);
	tcp_sent(tpcb,NULL);
	tcp_err(tpcb,NULL);
	tcp_poll(tpcb,NULL,0);  
	if(es)mem_free(es); 
	tcp_client_flag&=~(1<<5);//标记连接断开了
}

es参数作用上面接过来,小心内存泄漏即可,(刚开始,我没仔细看,就内存泄漏了)。

这里清除了连接标志(直接清除,不管是否连接到服务器,省掉了判断)。

到此为止,客户端连接到服务器讲解完毕。

五、发送

demo程序是怎么发送的呢?看tcp_client_test(),有如下语句:

		if(key==KEY0_PRES)//KEY0按下了,发送数据
		{
			tcp_client_flag|=1<<7;//标记要发送数据
		}

来看看哪里检测这个标志了:

//lwIP tcp_poll的回调函数
err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb)
{
	err_t ret_err;
	struct tcp_client_struct *es; 
	es=(struct tcp_client_struct*)arg;
	if(es!=NULL)  //连接处于空闲可以发送数据
	{
		if(tcp_client_flag&(1<<7))	//判断是否有数据要发送 
		{
			es->p=pbuf_alloc(PBUF_TRANSPORT, strlen((char*)tcp_client_sendbuf),PBUF_POOL);	//申请内存 
			pbuf_take(es->p,(char*)tcp_client_sendbuf,strlen((char*)tcp_client_sendbuf));	//将tcp_client_sentbuf[]中的数据拷贝到es->p_tx中
			tcp_client_senddata(tpcb,es);//将tcp_client_sentbuf[]里面复制给pbuf的数据发送出去
			tcp_client_flag&=~(1<<7);	//清除数据发送标志
			if(es->p)pbuf_free(es->p);	//释放内存
		}else if(es->state==ES_TCPCLIENT_CLOSING)
		{ 
 			tcp_client_connection_close(tpcb,es);//关闭TCP连接
		} 
		ret_err=ERR_OK;
	}else
	{ 
		tcp_abort(tpcb);//终止连接,删除pcb控制块
		ret_err=ERR_ABRT;
	}
	return ret_err;
} 

在tcp_client_poll()里面查询了,OK,看看哪里调用了tcp_client_poll(),

在tcp_client_connected()里面tcp_poll(tpcb,tcp_client_poll,1);     //初始化LwIP的tcp_poll回调功能

设置为了lwip的poll函数,那好,看看tcp_poll()

void
tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval)
{
  LWIP_ASSERT("invalid socket state for poll", pcb->state != LISTEN);
#if LWIP_CALLBACK_API
  pcb->poll = poll;
#else /* LWIP_CALLBACK_API */  
  LWIP_UNUSED_ARG(poll);
#endif /* LWIP_CALLBACK_API */  
  pcb->pollinterval = interval;
}

再接着看:

#define TCP_EVENT_POLL(pcb,ret)                                \
  do {                                                         \
    if((pcb)->poll != NULL)                                    \
      (ret) = (pcb)->poll((pcb)->callback_arg,(pcb));          \
    else (ret) = ERR_OK;                                       \
  } while (0)

在tcp_slowtmr()里面:

      /* We check if we should poll the connection. */
      ++prev->polltmr;
      if (prev->polltmr >= prev->pollinterval) {
        prev->polltmr = 0;
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: polling application\n"));
        tcp_active_pcbs_changed = 0;
        TCP_EVENT_POLL(prev, err);
        if (tcp_active_pcbs_changed) {
          goto tcp_slowtmr_start;
        }
        /* if err == ERR_ABRT, 'prev' is already deallocated */
        if (err == ERR_OK) {
          tcp_output(prev);
        }
      }

再看:

void
tcp_tmr(void)
{
  /* Call tcp_fasttmr() every 250 ms */
  tcp_fasttmr();

  if (++tcp_timer & 1) {
    /* Call tcp_tmr() every 500 ms, i.e., every other timer
       tcp_tmr() is called. */
    tcp_slowtmr();
  }
}

再看,在void lwip_periodic_handle()里面

#if LWIP_TCP
	//每250ms调用一次tcp_tmr()函数
  if (lwip_localtime - TCPTimer >= TCP_TMR_INTERVAL)
  {
    TCPTimer =  lwip_localtime;
    tcp_tmr();
  }
#endif

至此我们总算理清了发送关系。这里采用250ms查询发送标志,然后发送数据,实际应用要改为立即调用发送数据,

增加实时性。

六、接收

先看接受的回调函数:
 

//lwIP tcp_recv()函数的回调函数
err_t tcp_client_recv(void *arg,struct tcp_pcb *tpcb,struct pbuf *p,err_t err)
{ 
	u32 data_len = 0;
	struct pbuf *q;
	struct tcp_client_struct *es;
	err_t ret_err; 
	LWIP_ASSERT("arg != NULL",arg != NULL);
	es=(struct tcp_client_struct *)arg; 
	if(p==NULL)//如果从服务器接收到空的数据帧就关闭连接
	{
		es->state=ES_TCPCLIENT_CLOSING;//需要关闭TCP 连接了 
 		es->p=p; 
		ret_err=ERR_OK;
	}else if(err!= ERR_OK)//当接收到一个非空的数据帧,但是err!=ERR_OK
	{ 
		if(p)pbuf_free(p);//释放接收pbuf
		ret_err=err;
	}else if(es->state==ES_TCPCLIENT_CONNECTED)	//当处于连接状态时
	{
		if(p!=NULL)//当处于连接状态并且接收到的数据不为空时
		{
			memset(tcp_client_recvbuf,0,TCP_CLIENT_RX_BUFSIZE);  //数据接收缓冲区清零
			for(q=p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
			{
				//判断要拷贝到TCP_CLIENT_RX_BUFSIZE中的数据是否大于TCP_CLIENT_RX_BUFSIZE的剩余空间,如果大于
				//的话就只拷贝TCP_CLIENT_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
				if(q->len > (TCP_CLIENT_RX_BUFSIZE-data_len)) memcpy(tcp_client_recvbuf+data_len,q->payload,(TCP_CLIENT_RX_BUFSIZE-data_len));//拷贝数据
				else memcpy(tcp_client_recvbuf+data_len,q->payload,q->len);
				data_len += q->len;  	
				if(data_len > TCP_CLIENT_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出	
			}
			tcp_client_flag|=1<<6;		//标记接收到数据了
 			tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
			pbuf_free(p);  	//释放内存
			ret_err=ERR_OK;
		}
	}else  //接收到数据但是连接已经关闭,
	{ 
		tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
		es->p=NULL;
		pbuf_free(p); //释放内存
		ret_err=ERR_OK;
	}
	return ret_err;
}

再看tcp_client_connected()的tcp_recv(tpcb,tcp_client_recv);      //初始化LwIP的tcp_recv回调功能

void
tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
{
  LWIP_ASSERT("invalid socket state for recv callback", pcb->state != LISTEN);
  pcb->recv = recv;
}

在tcp_impl.h中

#define TCP_EVENT_RECV(pcb,p,err,ret)                          \
  do {                                                         \
    if((pcb)->recv != NULL) {                                  \
      (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err));\
    } else {                                                   \
      (ret) = tcp_recv_null(NULL, (pcb), (p), (err));          \
    }                                                          \
  } while (0)

tcp_input()中

        if (recv_data != NULL) {
          LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);
          if (pcb->flags & TF_RXCLOSED) {
            /* received data although already closed -> abort (send RST) to
               notify the remote host that not all data has been processed */
            pbuf_free(recv_data);
            tcp_abort(pcb);
            goto aborted;
          }

          /* Notify application that data has been received. */
          TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
          if (err == ERR_ABRT) {
            goto aborted;
          }

          /* If the upper layer can't receive this data, store it */
          if (err != ERR_OK) {
            pcb->refused_data = recv_data;
            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
          }
        }

在ip_input(struct pbuf *p, struct netif *inp)

#if LWIP_RAW
  /* raw input did not eat the packet? */
  if (raw_input(p, inp) == 0)
#endif /* LWIP_RAW */
  {
    switch (IPH_PROTO(iphdr)) {
#if LWIP_UDP
    case IP_PROTO_UDP:
#if LWIP_UDPLITE
    case IP_PROTO_UDPLITE:
#endif /* LWIP_UDPLITE */
      snmp_inc_ipindelivers();
      udp_input(p, inp);
      break;
#endif /* LWIP_UDP */
#if LWIP_TCP
    case IP_PROTO_TCP:
      snmp_inc_ipindelivers();
      tcp_input(p, inp);
      break;
#endif /* LWIP_TCP */

在netif_init()

netif_add(&loop_netif, &loop_ipaddr, &loop_netmask, &loop_gw, NULL, netif_loopif_init, ip_input);

在lwip_init(void)有netif_init();lwip_init()被lwip_comm_init()调用。

咦,ip_input0哪里调用了?接着分析

netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask,
  ip_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input)

有netif->input = input;

在ethernetif_input()中有:

//网卡接收数据(lwip直接调用)
//netif:网卡结构体指针
//返回值:ERR_OK,发送正常
//       ERR_MEM,发送失败
err_t ethernetif_input(struct netif *netif)
{
	err_t err;
	struct pbuf *p;
	p=low_level_input(netif);
	if(p==NULL) return ERR_MEM;
	err=netif->input(p, netif);
	if(err!=ERR_OK)
	{
		LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
		pbuf_free(p);
		p = NULL;
	} 
	return err;
}
//当接收到数据后调用 
void lwip_pkt_handle(void)
{
  //从网络缓冲区中读取接收到的数据包并将其发送给LWIP处理 
 ethernetif_input(&lwip_netif);
}
//以太网中断服务函数
void ETH_IRQHandler(void)
{
	while(ETH_GetRxPktSize(DMARxDescToGet)!=0) 	//检测是否收到数据包
	{ 
		lwip_pkt_handle();		
	}
	ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
	ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}

好了,至此我们把接收逻辑也搞清楚了。网上有的帖子说接受回调是查询方式的,其实这个主要是看tcpip_input()的调用

方式,即lwip_pkt_handle()的调用方式决定,如果是查询的,那么考虑关键代码区就会少很多,如果是中断方式,那么APP

层就要考虑临界代码区保护了。

七、一些修改的心得

1、lwip_comm_init()初始化时候,如果初始化失败,那么可以隔一段时间再初始化,我设置的1分钟,没必要一直初始化,

可能网线没插呢;

2、在此强调tcp_client_connection_close时候,注意内存泄漏;

3、判断网线插拔

            if((!(ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status))){
                //TRACE_INFO("ÍøÏ߶Ͽª =%X \r\n",s_ptTcpPcb->callback_arg);
                tcp_client_connection_close(s_ptTcpPcb,s_ptTcpPcb->callback_arg);//¹Ø±ÕÁ¬½Ó
#if LWIP_DHCP
                s_tState = FSM_USER_TCPIP_DHCP;
#else
                s_tState = FSM_USER_TCPIP_CREATE_TCP_CLIENT;
#endif
                TRACE_INFO("网线断开......\r\n");
                break;
            }

也没必要每次否判断,只要比心跳时间短,就可以了;

4、lwip_periodic_handle()在初始化失败后,没必须调用,无意义;

5、裸编情况下,一定要改为非阻塞模式,否则严重影响实时性;

6、ping命令的时候不能丢包。

发送和接收时候的申请和释放内存分析,请看Lwip pbuf分析

<----------------------------------------------------------------------------------------------------------------------------------->

2019.09.20

今天在做lwip的功能模块是否启用,要灵活可配置。主要实现的思路如下:

1、Lwip的要重新初始化;

2、lwip的内核的内存怎么回收、释放;

3、接收的数据释放;

4、发送的内存释放;

以上几点要保证,否则内存泄漏。鉴于以上思路比较麻烦,如果你的lwip只有一个网卡,使用

比较简单,可以采用如下思路:

1、lwip用到的所有的内存全部是独立的,和其他逻辑不共用;

2、重新初始化所有的内存池;

上面这个思路实现就很简单了,但是在测试中发Lwip重启几次,就进入一个死循环,出不来了。

随后追踪到这个函数:

为什么?lwip如果只初始化一次,肯定没有问题,因为测试了很长时间了。那么还要从重启里面去找,也不是内存泄漏。我直觉是某个链表没有清掉,思路是把Lwip用到的全局变量也全部初始化,最后测试,问题解决。

重启lwip初始化过程:

(1)复位所有状态机,我是自己写了一个函数,判断是否所有状态机全部复位:

static bool lwip_service_is_stop(void)
{
    if(!tcpip_is_stop()){
        return false;
    }

    if(s_tAppLwip.tLwipAppReviceFrameProcessRunFlag){
        return false;
    }

    if(s_tAppLwip.tLwipAppSendProcessRunFlag){
        return false;
    }
    //全部停止后,清除标志位
    s_tAppLwip.tLwipStop = false;
    return true;
}

(2)关闭以太网中断,防止初始化过程中来了数据,导致内存错乱;

ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,DISABLE);  	//关闭以太网接收中断

(3)初始化内存池

mymem_init(SRAMIN);		    //初始化内部内存池

(4)初始化lwip的业务层,根据自己需要初始化;

(5)初始化lwip协议栈

①主要利用lwip_comm_init()函数进行初始化,ETH_Mem_Malloc()、lwip_comm_mem_malloc()、LAN8720_Init()、lwip_init()、lwip_comm_default_ip_set(&lwipdev)、

netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,&ethernetif_init,&ethernet_input)等

②开始创建TCP Client,并设置超时时间

//TCP Client 测试
static struct tcp_pcb* tcp_client_create(void)
{
 	struct tcp_pcb *tcppcb = NULL;  	//定义一个TCP服务器控制块
	struct ip_addr rmtipaddr;  	        //远端ip地址
	
 
	TRACE_INFO("Local IP:%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);                         //本地IP 
	TRACE_INFO("Remote IP:%d.%d.%d.%d\r\n",lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);//远端IP 
	TRACE_INFO("Remotewo Port:%d\r\n",lwipdev.remoteport);                                                                  //服务器端口号

	tcppcb=tcp_new();	//创建一个新的pcb
	if(tcppcb)			//创建成功
	{
		IP4_ADDR(&rmtipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]); 
		tcp_connect(tcppcb,&rmtipaddr,lwipdev.remoteport,tcp_client_connected);  //连接到目的地址的指定端口上,当连接成功后回调tcp_client_connected()函数        
 	}
    return tcppcb;
}

③等待连接成功或者超时到,如果成功,则进入④;如果失败则进入②;

④查询服务器是否主动断开、是否心跳超时、是否网线断开,如果是则进入②;查询是否收到ACK信号,如果是,则发送数据(我没有在ACK的回调函数里面进行数据发送,仅仅设置了标志,减少中断时间);

⑤判断出错次是否超过阈值,如果是则进入(1),正lwip协议栈重启。

<-------------------------------------------------------问题总结---------------------------------------------------------> 

1、ETH_SoftwareReset 失败

       网口不通,查看log,提示ETH_SoftwareReset 失败,查看代码:ETH_GetSoftwareResetStatus()失败,肉眼检查没有发现焊接错误。于是在网上查找相似的问题,在正点原子的官方论坛上发现有好多人都是这个错误,楼下回复都是检查芯片的焊接。于是动用示波器,检测LAN8720的晶振,发现25M输入正常。检查nINT/REFCLKO 输出时钟,发现50M信号没有,查看lan8720的pdf,看到LED2/nINTSEL拉低到地,设置REFCLKO输出50M,没有发现问题,只能尝试重新焊接。我用烙铁重加焊锡后,再用风枪加热,保证焊盘的充分接触。动用风枪的原因是lan8720底部焊盘是GND。再次运行程序发现可以成功重启单片机网络。

2、ping时断时续

查看自己的电源是不是不稳定,或者电源噪声是不是干扰了lwip网口;基本是硬件问题。

3、断开之后不通了

今天测试发现,lwip断开了,再也不通了,ping也不通,现场没有发现过,没找到原因,原始添加lwip的重启机制,请看上面重启lwip初始化过程,目前测试稳定。

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值