添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

standalone的BSP,提供了LWIP141的源代码。对于APP来说,只需要关注LWIP的API函数即可。
NEWAPP中,LWIP ECHO SERVER这个工程样例,给我们搭建了使用LWIP的TCP服务的基本框架。我们基于这个框架进行情景分析。
整个工程基于RAW API来编写,所以是单进程的,但是仍然需要中断系统的支持,因为接收数据包,需要外设中断,然后在中断ISR中进行处理。
当外设接收到网络数据包时,发送一个中断,然后CPU进入中断处理,并调用LWIP的Callback来进行协议分解,数据提取,然后调用用户的Callback,进行数据处理。
当CPU需要向外发送网络数据包时,会调用LWIP的API进行数据封包,层层封包之后,LWIP会发送给外设,由外设把网络数据包发送出去。
也就是说,中断ISR主要负责接收网络包的处理,而用户程序负责发送网络数据包的处理。
接收和发送并不能同时进行,因为Callback处理接收时,不能同时处理发送封包。
这也是单进程的缺点。

整个LWIP,是基于SOD设计思想的。所以,我们在应用LWIP时,也要遵循SOD设计思想。
struct ip_addr,这个结构体,描述了IP地址的数据成员。
struct netif,这个结构体,描述了网络接口的硬件Resource。它包含了一个NETIF所需要的全部硬件属性,资源链接,以及操作函数入口。包括IPADDR,NETMASK,GW,HWADDR,MTU等硬件配置信息,以及netif_input()和netif_output()等函数指针。它还包含一些关联资源指针,用来关联其他的资源结构体,例如struct dhcp,struct pbuf等。
struct pbuf,这个结构体,用来描述缓冲包的硬件信息。包含了LEN,FLAG,TYPE等描述缓冲包的资源属性的信息,以及payload,这个关联资源指针,payload被定义为(void*),也就是被视为裸内存。
struct tcp_pcb,这个结构体,用来描述TCP数据包的硬件信息。
这些都是LWIP定义的,用户程序无需修改。

SI5324和SFP是ZCU706需要用到外设,在zedboard上并不需要。所以,IIC_access和IIC_phyreset也是不需要的。
zedboard需要GIC和Interrupt的支持,当然,还有Timer,这在init_platform()中体现出来。
echo这个文件,将APP需要的ECHOSERVER 的操作函数,集合在了一起。
这样,在Main中,调用所需要的ECHO函数,就可以实现基于LWIP的ECHOSERVER的功能。
main中,需要做的就是按照流程,配置和启动ECHOSERVER。

首先,要对LWIP协议栈进行初始化。这是LWIP协议栈中的init.c的函数。

struct ip_addr ipaddr, netmask, gw;
IP4_ADDR(&ipaddr,  192, 168,   1, 10);
	IP4_ADDR(&netmask, 255, 255, 255,  0);
	IP4_ADDR(&gw,      192, 168,   1,  1);
echo_netif = &server_netif;
lwip_init();

然后,要对zedboard的硬件外设进行配置。xemac_add(),这是xadaptor.c中的函数。zynq使用的是PS的MACPHY,所以这些函数在LWIP代码的contrib文件夹下面,xilinx对应的文件夹中。

if (!xemac_add(echo_netif, &ipaddr, &netmask,
						&gw, mac_ethernet_address,
						PLATFORM_EMAC_BASEADDR)) {
		xil_printf("Error adding N/W interface\n\r");
		return -1;

然后,设置默认网络接口。

netif_set_default(echo_netif);

然后,使能中断系统,这样,网口外设可以接收数据包。这个函数在platform_zynq.c文件中。

platform_enable_interrupts();

然后,建立网口外设,让网口和交换机进行通信,使网口被交换机登记。

netif_set_up(echo_netif);

之后,就可以启动APP了。即ECHOSERVER。这个函数在echo.c中。按流程完成了如下工作,
新建一个tcp_pcb,绑定一个TCPPORT到这个TCP_PCB。设置TCP_PCB的参数。监听TCP_PCB,这是一个listen_point,监听成功后,LWIP会返回一个新的建立好连接的TCP_PCB。这是一个connect_point。为这个connect_point关联Callback。

start_application();

之后,用户程序进入后台业务工作流程内。xemacif_input()是协议中用来接收数据包的函数,将网口外设接收的数据包处理后,加入到pbuf中。

while (1) {
		if (TcpFastTmrFlag) {
			tcp_fasttmr();
			TcpFastTmrFlag = 0;
		if (TcpSlowTmrFlag) {
			tcp_slowtmr();
			TcpSlowTmrFlag = 0;
		xemacif_input(echo_netif);
		transfer_data();

整体代码如下:

extern volatile int TcpFastTmrFlag;
extern volatile int TcpSlowTmrFlag;
static struct netif server_netif;
struct netif *echo_netif;
int main()
	struct ip_addr ipaddr, netmask, gw;
	/* the mac address of the board. this should be unique per board */
	unsigned char mac_ethernet_address[] =
	{ 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
	echo_netif = &server_netif;
/* Define this board specific macro in order perform PHY reset on ZCU102 */
#ifdef XPS_BOARD_ZCU102
	IicPhyReset();
#endif
	init_platform();
#if LWIP_DHCP==1
    ipaddr.addr = 0;
	gw.addr = 0;
	netmask.addr = 0;
#else
	/* initliaze IP addresses to be used */
	IP4_ADDR(&ipaddr,  192, 168,   1, 10);
	IP4_ADDR(&netmask, 255, 255, 255,  0);
	IP4_ADDR(&gw,      192, 168,   1,  1);
#endif	
	print_app_header();
	lwip_init();
  	/* Add network interface to the netif_list, and set it as default */
	if (!xemac_add(echo_netif, &ipaddr, &netmask,
						&gw, mac_ethernet_address,
						PLATFORM_EMAC_BASEADDR)) {
		xil_printf("Error adding N/W interface\n\r");
		return -1;
	netif_set_default(echo_netif);
	/* now enable interrupts */
	platform_enable_interrupts();
	/* specify that the network if is up */
	netif_set_up(echo_netif);
#if (LWIP_DHCP==1)
	/* Create a new DHCP client for this interface.
	 * Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
	 * the predefined regular intervals after starting the client.
	dhcp_start(echo_netif);
	dhcp_timoutcntr = 24;
	while(((echo_netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0))
		xemacif_input(echo_netif);
	if (dhcp_timoutcntr <= 0) {
		if ((echo_netif->ip_addr.addr) == 0) {
			xil_printf("DHCP Timeout\r\n");
			xil_printf("Configuring default IP of 192.168.1.10\r\n");
			IP4_ADDR(&(echo_netif->ip_addr),  192, 168,   1, 10);
			IP4_ADDR(&(echo_netif->netmask), 255, 255, 255,  0);
			IP4_ADDR(&(echo_netif->gw),      192, 168,   1,  1);
	ipaddr.addr = echo_netif->ip_addr.addr;
	gw.addr = echo_netif->gw.addr;
	netmask.addr = echo_netif->netmask.addr;
#endif
	print_ip_settings(&ipaddr, &netmask, &gw);
	/* start the application (web server, rxtest, txtest, etc..) */
	start_application();
	/* receive and process packets */
	while (1) {
		if (TcpFastTmrFlag) {
			tcp_fasttmr();
			TcpFastTmrFlag = 0;
		if (TcpSlowTmrFlag) {
			tcp_slowtmr();
			TcpSlowTmrFlag = 0;
		xemacif_input(echo_netif);
		transfer_data();
	/* never reached */
	cleanup_platform();
	return 0;

来看看start_application这个函数具体怎么做的。

int start_application()
	struct tcp_pcb *pcb;
	err_t err;
	unsigned port = 7;
	/* create new TCP PCB structure */
	pcb = tcp_new();
	if (!pcb) {
		xil_printf("Error creating PCB. Out of Memory\n\r");
		return -1;
	/* bind to specified @port */
	err = tcp_bind(pcb, IP_ADDR_ANY, port);
	if (err != ERR_OK) {
		xil_printf("Unable to bind to port %d: err = %d\n\r", port, err);
		return -2;
	/* we do not need any arguments to callback functions */
	tcp_arg(pcb, NULL);
	/* listen for connections */
	pcb = tcp_listen(pcb);
	if (!pcb) {
		xil_printf("Out of memory while tcp_listen\n\r");
		return -3;
	/* specify callback to use for incoming connections */
	tcp_accept(pcb, accept_callback);
	xil_printf("TCP echo server started @ port %d\n\r", port);
	return 0;

注意,这里最关键的就是tcp_accept,它绑定了Callback,它在echo.c文件中。

err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
	static int connection = 1;
	/* set the receive callback for this connection */
	tcp_recv(newpcb, recv_callback);
	/* just use an integer number indicating the connection id as the
	   callback argument */
	tcp_arg(newpcb, (void*)(UINTPTR)connection);
	/* increment for subsequent accepted connections */
	connection++;
	return ERR_OK;

注意这里的编程技巧,这是C语言提供的一个灵活性,函数内的static变量。无论何处声明的static变量,都会被放到静态区中,所以虽然在函数内声明变量,也不是放在stack frame中。区别在于,函数内的static变量,编译时,会被自动添加函数标号作为前缀。由于有函数标号作为前缀,这也表明的变量的访问范围,所以即使这个变量放在静态区,也只能被这个函数访问,而不能被其他函数访问。
这是局部静态变量和全局静态变量的最大区别。
关键在于,为TCP_PCB设置了Callback,即recv_callback。

tcp_arg(struct tcp_pcb *pcb, void *arg) /* This function is allowed to be called for both listen pcbs and connection pcbs. */ pcb->callback_arg = arg; 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_sent(struct tcp_pcb *pcb, tcp_sent_fn sent) LWIP_ASSERT("invalid socket state for sent callback", pcb->state != LISTEN); pcb->sent = sent;

从这几个函数来看,他们的功能,只是简单的配置TCP_PCB的相关成员。

recv_callback,是当TCP数据包被接收后,TCP_PCB具体执行的Callback。这个函数位于echo.c文件中。此时的PBUF,是已经被LWIP协议处理后的TCP的数据包。

err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
                               struct pbuf *p, err_t err)
	/* do not read the packet if we are not in ESTABLISHED state */
	if (!p) {
		tcp_close(tpcb);
		tcp_recv(tpcb, NULL);
		return ERR_OK;
	/* indicate that the packet has been received */
	tcp_recved(tpcb, p->len);
	/* echo back the payload */
	/* in this case, we assume that the payload is < TCP_SND_BUF */
	if (tcp_sndbuf(tpcb) > p->len) {
		err = tcp_write(tpcb, p->payload, p->len, 1);
	} else
		xil_printf("no space in tcp_sndbuf\n\r");
	/* free the received pbuf */
	pbuf_free(p);
	return ERR_OK;

其中的关键操作就是tcp_write,它的作用是把payload裸内存,加入TCP_PCB的发送数据队列queue中。这个函数实现了ECHO功能。
当LWIP接收到数据包并处理后,会调用recv这个Callback,做一些用户特定的后续操作。
官方说明如下。

#if LWIP_CALLBACK_API
  /* Function to be called when more send buffer space is available. */
  tcp_sent_fn sent;
  /* Function to be called when (in-sequence) data has arrived. */
  tcp_recv_fn recv;
  /* Function to be called when a connection has been set up. */
  tcp_connected_fn connected;
  /* Function which is called periodically. */
  tcp_poll_fn poll;
  /* Function to be called whenever a fatal error occurs. */
  tcp_err_fn errf;
#endif /* LWIP_CALLBACK_API */

LWIP协议栈的运行时机,是由TIMER中断驱动的。可以简单理解为,LWIP运行在另外一个进程中。LWIP协议栈通过轮询NETIF,执行发送和接收任务。LWIP协议栈每次运行返回后,会让出CPU,从而让后台业务可以继续。
LWIP运行时,会逐个处理NETIF所关联的PBUF。所以用户所需要做的,就是把需要发送的数据提交到NETIF的queue中。等到LWIP运行时,再去处理queue。
Callback在LWIP每个运行时机,被调用执行。
当LWIP判断有数据包被接收时,会调用recv,希望由用户的Callback来进一步处理接收的数据包,然后将payload移出接收缓冲区。如果没有Callback,那么这个payload就被直接移出接收缓冲区了。
当LWIP在发送数据包后,判断有空闲的发送缓冲资源时,会调用sent,希望由用户的Callback来填充缓冲资源。如果没有Callback,那么这个缓冲资源就被闲置。
Callback机制,是multiple process编程思想的一种体现。
用户程序并不需要在自己的后台任务中控制Callback操作的运行时机,无需调用Callback。而前台任务则负责控制Callback操作的运行时机,根据条件来调用Callback。从而使Callback转入后台。
用户不需要关心Callback操作具体什么时候运行,只需要关心,当前台另一个进程需要Callback运行时,Callback究竟需要做哪些操作。这样,设计任务就简化为编写Callback的具体操作流程。

standalone的BSP,提供了LWIP141的源代码。对于APP来说,只需要关注LWIP的API函数即可。NEWAPP中,LWIP ECHO SERVER这个工程样例,给我们搭建了使用LWIP的TCP服务的基本框架。我们基于这个框架进行情景分析。整个工程基于RAW API来编写,所以是单进程的,但是仍然需要中断系统的支持,因为接收数据包,需要外设中断,然后在中断ISR中进行处理。当外设... LWIP中 recv接收回调函数就是这样的原理,其调用时间便是接收到数据。 2.TCP协议在lwip中的体现形式是tcp_pcb(协议控制块结构体),不同的tcp协议连接会产生不同的控制块,最终以链表的形式组成,其中定义了recv、sent、poll等指针函数,指向回调函数。 3.lwip协议首先设置netif结构体(... -----lwIP RAW Mode UDP Server Application----- WARNING: Not a Marvell or TI or Realtek Ethernet PHY. Please verify the initialization sequence Start PHY autonegotiation Waitin.
lwIP概述 lwIP是一个用于嵌入式系统的开源TCP/IP协议集,是一套可以独立运行的栈,无需依赖操作系统,但也可以与操作系统同时使用。lwIP提供了两套API(术语为A05PI),供用户选择: RAW API:直接访问核心的lwIP栈; Socket API:通过BSD socket风格的接口访问lwIP栈。 基于lwIP 1.4.1库版本,SDK提供了相应适配的库,称作lwip 141_...
XAPP1026中记录一些lwIP的应用程序示例和性能测试情况,不过提供的示例工程都是在几个Xilinx的官方板子中跑的。可能很多学生没有机会碰到这些板子。。。另外这份应用笔记使用的SDK 2014.3版本也比较老,那个版本lwip还没有直接集成到SDK中。本文将这份笔记其中比较有用的代码编写思路和性能测试结果部分摘取出来。 1. 硬件系统 这个表是几个开发板上搭建的硬件系统。纯FPGA使用的...
iPerf lwIP 是一款基于轻量级 IP 协议(LwIP)的 iPerf 工具,用于测量网络性能和带宽。它可以在 Linux 系统上运行,并使用 lwIP 库作为其底层网络协议栈。 iPerf lwIP 的主要特点包括: 1. 使用 lwIP 库作为网络协议栈,提供了快速、高效的网络传输性能。 2. 支持 TCP 和 UDP 两种传输协议,可以测试不同协议之间的性能。 3. 提供了一组命令行工具,便于在终端上执行测试和结果分析。 4. 支持多线程和并发连接,可以同时对多个网络接口和节点进行测试。 5. 可以与其他网络性能测试工具结合使用,进行全面的网络性能评估。 使用 iPerf lwIP 进行测试时,您需要先安装 lwIP 库和 iPerf 软件,然后通过命令行工具执行测试。测试可以针对本地网络接口或远程节点进行,并可以设置不同的测试参数,如数据包大小、传输速率、并发连接数等。测试结果将以图形化方式展示,便于分析和比较。 总之,iPerf lwIP 是一款功能强大、易于使用的网络性能测试工具,适用于评估局域网和广域网中的网络带宽和性能。