主要使用LWIP实现TCP的数据传输,和主机建立通讯。
主要参考https://bestfpga.blog.csdn.net/article/details/88775286
SDK程序设计
按照前文方法,新建工程后启用lwIP 1.4.1库,其余配置都保持默认即可(使用RAW API)。使用lwIP需要启动中断系统,sys_intr.h和sys_intr.c文件代码与UDP实例中的相同。
整个代码可以借用lwip TCP Perf Client作为模板创建
此外lwIP要求每250ms调用一次tcp_tmr()函数(具体原因后面小节分析)因此我们要添加定时器资源。定时器使用方法参考本系列第10篇,这里不再讲述。
timer_intr.h文件代码如下:
#include <stdio.h>
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xscutimer.h"
extern volatile int TcpTmrFlag;
#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR
void Timer_start(XScuTimer *TimerPtr);
void Timer_Setup_Intr_System(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId);
int Timer_init(XScuTimer *TimerPtr,u32 Load_Value,u32 DeviceId);
timer_intr.c文件的代码如下:
#include "timer_intr.h"
volatile int TcpTmrFlag;
//---------------------------------------------------------
// 定时器中断处理函数
//---------------------------------------------------------
static void TimerIntrHandler(void *CallBackRef)
{
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
TcpTmrFlag = 1;
}
//---------------------------------------------------------
// 启动定时器函数
//---------------------------------------------------------
void Timer_start(XScuTimer *TimerPtr)
{
XScuTimer_Start(TimerPtr);
}
//---------------------------------------------------------
// 定时器中断设置函数
//---------------------------------------------------------
void Timer_Setup_Intr_System(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId)
{
XScuGic_Connect(GicInstancePtr, TimerIntrId,
(Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);
XScuGic_Enable(GicInstancePtr, TimerIntrId);
XScuTimer_EnableInterrupt(TimerInstancePtr);
}
//---------------------------------------------------------
// 定时器初始化函数
//---------------------------------------------------------
int Timer_init(XScuTimer *TimerPtr,u32 Load_Value,u32 DeviceId)
{
XScuTimer_Config *TMRConfigPtr;
TMRConfigPtr = XScuTimer_LookupConfig(DeviceId);
XScuTimer_CfgInitialize(TimerPtr,TMRConfigPtr,TMRConfigPtr->BaseAddr);
XScuTimer_LoadTimer(TimerPtr, Load_Value);
XScuTimer_EnableAutoReload(TimerPtr);
return 1;
}
mian.c文件中的代码
#include "timer_intr.h"
#include "sys_intr.h"
#include "user_tcp.h"
#include "sleep.h"
#define TIMER_LOAD_VALUE XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8 //0.25S
static XScuGic Intc; //GIC
static XScuTimer Timer;//timer
extern volatile unsigned tcp_client_connected;
extern int tcp_trans_cnt;
//--------------------------------------------------
// 中断与定时器初始化
//--------------------------------------------------
void System_Init()
{
Timer_init(&Timer,TIMER_LOAD_VALUE,TIMER_DEVICE_ID);
Init_Intr_System(&Intc); // initial DMA interrupt system
Setup_Intr_Exception(&Intc);
Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR);
Timer_start(&Timer);
TcpTmrFlag = 0;
}
//--------------------------------------------------
// 主程序
//--------------------------------------------------
int main(void)
{
struct netif *netif, server_netif; //用于lwIP网络接口的通用数据结构
struct ip_addr ipaddr, netmask, gw; //unsigned int
//开发板的MAC地址
unsigned char mac_ethernet_address[] = {0x00,0x0a,0x35,0x00,0x01,0x02};
System_Init();
netif = &server_netif;
//将4byte结构的IP地址转换为unsigned int
IP4_ADDR(&ipaddr, 192, 168, 1, 10); //IP地址(开发板)
IP4_ADDR(&netmask, 255, 255, 255, 0); //网络掩码
IP4_ADDR(&gw, 192, 168, 1, 1); //网关
lwip_init(); //初始化lwIP
//将网络接口添加到netif_list中
if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR)) {
xil_printf("Error adding N/W interface\r\n");
return -1;
}
netif_set_default(netif); //设置默认网络接口
netif_set_up(netif); //启动网络接口
tcp_send_init(); //初始化TCP PCB
while(1) {
/* call tcp timer every 250ms */
if(TcpTmrFlag)
{
tcp_tmr();
TcpTmrFlag = 0;
}
xemacif_input(netif); //将MAC队列中的packets传输到lwIP栈中
if (tcp_client_connected) { //连接成功则发送数据
sleep(1);
send_data();
xil_printf("tran_cnt:%d\n\r", tcp_trans_cnt);
}
}
}
与UDP相同的lwIP配置流程代码中给出了详细注释。最大区别在于下面这部分:
while(1) {
/* call tcp timer every 250ms */
if(TcpTmrFlag)
{
tcp_tmr();
TcpTmrFlag = 0;
}
user_function(); //用户功能
}
别小瞧这个函数,tcp_tmr对TCP的稳定使用至关重要,可查看本文后面小节的测试。
user_tcp.h文件代码如下(为了避免混淆,尽量不要取名为lwIP库中已用过的udp.h/c和tcp.h/c):
#include <stdio.h>
#include <string.h>
#include "lwip/err.h"
#include "lwip/tcp.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "lwip/init.h"
#include "lwip/tcp_impl.h"
#include "xil_printf.h"
extern volatile unsigned tcp_client_connected;
extern int tcp_trans_cnt;
int tcp_send_init();
void send_data(void);
#include "user_tcp.h"
#define SEND_SIZE 12
static struct tcp_pcb *connected_pcb = NULL;
volatile unsigned tcp_client_connected = 0;
int tcp_trans_cnt = 0;
char sendBuffer[12]="Hello World!";
//--------------------------------------------------
// TCP数据发送成功的回调函数
//--------------------------------------------------
static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
tcp_trans_cnt++; //统计发送数据的次数
xil_printf("send int");
return ERR_OK;
}
//--------------------------------------------------
// TCP连接成功的回调函数
//--------------------------------------------------
static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
{
xil_printf("txperf: Connected to iperf server\r\n");
connected_pcb = tpcb; //存储连接的TCP状态
tcp_nagle_disable(connected_pcb);
tcp_arg(tpcb, NULL); //指定应该传递回调函数的参数
//设置当TCP数据成功传递到远程主机时调用回调函数tcp_sent_callback
tcp_sent(tpcb, tcp_sent_callback);
tcp_client_connected = 1; //置1表示连接已建立
xil_printf("Connect Success.\r\n");
return ERR_OK;
}
//--------------------------------------------------
// TCP PCB初始化函数
//--------------------------------------------------
int tcp_send_init()
{
struct tcp_pcb *pcb;
struct ip_addr ipaddr;
err_t err;
u16_t port;
pcb = tcp_new(); //创建新的TCP PCB
if (!pcb) {
xil_printf("txperf: Error creating PCB. Out of Memory\r\n");
return -1;
}
IP4_ADDR(&ipaddr, 192, 168, 1, 100); //服务器的IP地址
port = 7; //服务器的默认端口
tcp_client_connected = 0;
//连接主机,连接建立后调用回调函数tcp_connected_callback
err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback);
if (err != ERR_OK) {
xil_printf("txperf: tcp_connect returned error: %d\r\n", err);
return err;
}
xil_printf("%d\r\n",err);
return 0;
}
//--------------------------------------------------
// TCP数据发送函数
//--------------------------------------------------
void send_data(void)
{
err_t err;
struct tcp_pcb *tpcb = connected_pcb;
if (!connected_pcb)
return;
err = tcp_write(tpcb, sendBuffer, SEND_SIZE, 3);
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_write: %d\r\n", err);
connected_pcb = NULL;
return;
}
err = tcp_output(tpcb);
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_output: %d\r\n",err);
return;
}
}
本设计的TCP工作在客户端模式,将远程主机当作服务器,主动请求连接。TCP client和TCP server在lwIP中的连接流程和区别可参考本系列前面与lwIP相关的文章。
请注意上面是以Vivado2017的某个版本来设计的;我这边使用了Vivado2019.1,结果上面的程序需要基础修改
1. LWIP1.4.1---LWIP2.x需要进行一定的修改
2. 移植完发现还是跑不起来,原因在查找