核心芯片使用STM32F407,以太网芯片使用LAN8720,开发环境基于LWIP无操作系统移植。代码参考正点原子。
1、主要函数:
//创建tcp控制块
struct tcp_pcb *tcp_new(void)
//连接到目的地址的指定端口
tcp_connect(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port,tcp_connected_fn connected)
//LWIP轮询任务
void lwip_periodic_handle()
//更新tpcb的callback_arg
void tcp_arg(struct tcp_pcb *pcb, void *arg)
//回调注册函数,数据发送成功后响应
void tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
//回调注册函数,tcp连接失败后响应
void tcp_err(struct tcp_pcb *pcb, tcp_err_fn err)
//回调注册函数,tcp发送数据响应
void tcp_sent(struct tcp_pcb *pcb, tcp_sent_fn sent)
//回调注册函数,tcp接受数据响应
void tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval)
//数据发送
err_t tcp_output(struct tcp_pcb *pcb)
//终止连接,删除pcb控制块
void tcp_abort(struct tcp_pcb *pcb)
2、头文件/全局变量
//需要包含的iwip头文件
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include "lwip/memp.h"
#include "lwip/mem.h"
#include "lwip_comm.h"
//其他头文件
#include "mysys.h"
#include "delay.h"
#include "key.h"
#include "malloc.h"
#include "stdio.h"
#include "string.h"
#include "net.h"
#define TCP_CLIENT_RX_BUFSIZE 1500 //定义tcp client最大接收数据长度
#define TCP_CLIENT_TX_BUFSIZE 200 //定义tcp client最大发送数据长度
#define TCP_CLIENT_PORT 8001 //定义tcp client要连接的远端端口
//tcp服务器连接状态
enum tcp_client_states
{
ES_TCPCLIENT_NONE = 0, //没有连接
ES_TCPCLIENT_CONNECTED, //连接到服务器了
ES_TCPCLIENT_CLOSING, //关闭连接
};
//LWIP回调函数使用的结构体
struct tcp_client_struct
{
u8 state; //当前连接状
struct tcp_pcb *pcb; //指向当前的pcb
struct pbuf *p; //指向接收/或传输的pbuf
};
//TCP Client 全局状态标记变量
//bit7:0,没有数据要发送;1,有数据要发送
//bit6:0,没有收到数据;1,收到数据了.
//bit5:0,没有连接上服务器;1,连接上服务器了.
//bit4~0:保留
u8 tcp_client_flag;
struct tcp_pcb *tcppcb; //定义一个TCP服务器控制块
//TCP Client接收数据缓冲区
u8 tcp_client_recvbuf[TCP_CLIENT_RX_BUFSIZE];
//TCP服务器发送数据内容
u8 tcp_client_sendbuf[50]="STM32F407 TCP Client send data\r\n";
3、初始化
void tcp_config(void)
{
struct ip_addr rmtipaddr; //远端ip地址
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);
}
}
远端IP在lwip_comm.c文件中定义。
tcp_connect()连接到目的地址的指定端口上,当连接成功后回调tcp_client_connected()函数,代码如下
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;
}
该函数主要注册了几个重要的回调函数,同时标记服务器已连接。若连接失败则回调tcp_client_error()函数,该函数没有做任何处理,为空函数。
4、收发
(1)接受回调函数
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;
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;
}
(2)数据发送
数据发送回调函数
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); //申请内存
//将tcp_client_sentbuf[]中的数据拷贝到es->p_tx中
pbuf_take(es->p,(char*)tcp_client_sendbuf,strlen((char*)tcp_client_sendbuf));
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;
}
该回调函数在无操作系统环境下需要轮询的方式进行调用,用到了一个重要的变量即在移植lwip的sys_arch.c文件时定义的lwip_localtime时钟变量。该变量在可由定时器中断1ms自加10。该回调函数将发送数据拷贝到缓冲空间中,并调用发送函数将数据发送出去
void tcp_client_senddata(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
struct pbuf *ptr;
err_t wr_err=ERR_OK;
while((wr_err==ERR_OK)&&es->p&&(es->p->len<=tcp_sndbuf(tpcb)))
{
ptr=es->p;
wr_err=tcp_write(tpcb,ptr->payload,ptr->len,1); //将要发送的数据加入到发送缓冲队列中
if(wr_err==ERR_OK)
{
es->p=ptr->next; //指向下一个pbuf
if(es->p)pbuf_ref(es->p); //pbuf的ref加一
pbuf_free(ptr); //释放ptr
}else if(wr_err==ERR_MEM)es->p=ptr;
tcp_output(tpcb); //将发送缓冲队列中的数据立即发送出去
}
}
数据发送完成后发送一次空数据响应发送成功(个人理解)
err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
struct tcp_client_struct *es;
LWIP_UNUSED_ARG(len);
es=(struct tcp_client_struct*)arg;
if(es->p)tcp_client_senddata(tpcb,es);//发送数据
return ERR_OK;
}
5、断开连接
void tcp_client_connection_close(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
tcp_abort(tpcb)
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);
}
删除控制块,注销tcp回调函数,标记连接断开