精华内容
下载资源
问答
  • 本文是在参考了FPGADesigner大神的文章-https://blog.csdn.net/FPGADesigner/article/details/88746532,在黑金AX7021开发板上实验成功,里面包含完整的工程,基于vivado2017.4开发,
  • ZYNQlwip的使用

    千次阅读 2020-01-08 01:06:04
    文章目录ZYNQlwip的使用lwip (Lightweight IP)步骤使用Socket API创建lwIP应用程序使用RAW API创建lwIP应用程序lwip的小东西实操-zynq下实现回传服务器bsp下使能lwip和dhcp初始化定时器和中断lwip初始化获取网络...

    ZYNQ下lwip的使用

    lwip (Lightweight IP)

    lwIP 是用于嵌入式系统的开源TCP / IP网络协议栈。针对嵌入式设备上有没有跑操作系统,lwip提供了两套api:

    • Raw/native API
      这是一个事件驱动的API,设计为在没有实现操作系统的情况下使用。这个API也被核心栈用于各种协议之间的交互。它是在没有操作系统的情况下运行lwIP时惟一可用的API。
    • Socket API
      bsd风格的套接字API。线程安全,只能从非tcpip线程调用。

    底层的东西待会再说,赛灵思又两个文档,一个旧的XAPP1026(2014.11.21)和一个新的XAPP1306(2017.08.08)个人觉得旧文档参考价值大一点1

    在这里为了方便 我就取了官方的例程(lwip_example)来说明,
    pl端只有一个zynq的ip,里面需要配置一点东西ddr,时钟那些就不说了
    需要在zynq的ip核中写明自己的板子的MAC IC 的io 注意输入输出和接口速度

    要在这里根据板子来设定io
    如果用的是赛灵思家的软核那也可以,不过就自己折腾吧

    步骤

    官方文档中给了4个应用实例:

    • Echo Server(回传服务器)
    • Web服务器
    • TFTP服务器
    • TCP RX与TX吞吐量测试

    但是这些我们都先不管,我们先来看看官方文档中给出的步骤是怎样的(下面就是翻译):

    使用Socket API创建lwIP应用程序

    lwIP Socket API与Berkeley / BSD套接字非常相似。 因此,编写应用程序本身应该没有问题。 唯一的区别在于与lwIP 1.4.1库和xilkernel(或FreeRTOS)耦合的初始化过程

    1. 对于使用Xilkernel的基于MicroBlaze处理器的系统,请为Xilkernel配置静态线程。 在示例应用程序中,该线程名为main_thread。 另外,通过指定系统中断控制器来确保正确配置了Xilkernel。 对于使用FreeRTOS的基于Zynq-7000 AP SoC的系统,在启动FreeRTOS调度程序之前,先创建一个名称为main_thread的任务。 有关套接字应用程序的信息,请参见main.c,以了解Xilkernel / FreeRTOS的任务/线程初始化的详细信息。
    2. 主线程使用lwIP_init函数调用初始化lwIP,然后使用sys_thread_new函数启动网络线程。所有使用lwIP Socket API的线程都必须使用lwIP提供的sys_thread_new函数来启动
    3. 主线程使用xemac_add函数添加网络接口。该函数接受接口的IP地址和以太网MAC地址,并对其进行初始化
    4. 然后,在网络层线程初始化之后,需要运行xemacif_input_thread。 使用Xilinx适配器时,lwIP操作需要此线程。 该线程处理从中断处理程序接收到的数据移动到lwIP用于TCP / IP处理的tcpip_thread。
    5. lwIP库现在已经完全初始化,可以根据应用程序的需要启动其他线程。

    使用RAW API创建lwIP应用程序

    lwIP RAW API更加复杂,因为它需要lwIP内部的知识。原始模式程序的典型结构如下:

    1. 使用lwIP_init初始化所有lwIP结构
    2. 初始化lwIP之后,可以使用xemac_add 函数添加以太网MAC。
    3. 因为Xilinx lwIP适配器是基于中断的,所以在处理器和中断控制器中启用中断
    4. 设置一个定时器,以固定的间隔来中断。通常,间隔约为250毫秒。在计时器中断中,更新必要的标志,以便从前面解释的主应用程序循环中调用lwIP TCP api、TCP_fasttmrtcp和TCP_slowtmr。
    5. 初始化应用程序后,主程序进入执行包接收操作的无限循环,并执行它需要执行的任何其他应用程序特定的操作。
    6. 包接收操作(xemacif_input)处理中断处理程序接收到的包,并将它们传递到lwIP,然后lwIP为每个接收到的包调用适当的回调处理程序。

    lwip的小东西

    上面的过程我想大家已经看出,这里补充一些lwip的小东西,以帮助理解为什么要这样做:

    1. Socket API是基于RAW API实现的
    2. 使用RAW API编程,用户编程的方法是向内核注册各种自定义的回调函数,回调函数是与内核实现交换的唯一方式.
    3. 在后面会出现一个东西叫pcb 他指的是protocol control block 协议控制块
    4. lwip的内存管理,一种是链表,一种是堆,详情可看参考文献,引入一个数据包管理结构pbuf
    5. 在lwip的实现中,每一层都会有单独的线程来处理,好处是简单,坏处是线程间通信效率并不高

    实操-zynq下实现回传服务器

    下面说明部分

    1. 建议也跟我一样拿着例程看,不然可能有种云里雾里的感觉.不需要黑金的哪个例程,就在sdk下面launch一个lwip_example的模板就可以了
    2. 下面介绍的一些结构体由于去繁就简的原则去除了一些没使能的宏定义.

    bsp下使能lwip和dhcp

    修改bsp(board support package),加入lwip
    bsp-part
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t71PTlps-1578416417687)(https://i.loli.net/2020/01/07/LPfojuJgT6EzYKp.png)]
    在lwip设置页,设置成RAW API 和使能dhcp
    config
    这里我们就只用RAW API了,因为看了应用用途,要用到socket API那天我还是想想要不要上linux吧

    下面我们根据文档的步骤来说明一下,每一步的细节

    初始化定时器和中断

    因为在这里我们用到了dhcp,而且需要先初始化硬件.
    这里不讲具体的操作了,我之前的博客有写,这里我们要留意一下他的回调函数timer_callback

    void
    timer_callback(XScuTimer * TimerInstance)
    {
    	/* we need to call tcp_fasttmr & tcp_slowtmr at intervals specified
    	 * by lwIP. It is not important that the timing is absoluetly accurate.
    	 */
    	static int odd = 1;
    #if LWIP_DHCP==1
        static int dhcp_timer = 0;
    #endif
    	 TcpFastTmrFlag = 1;
    
    	odd = !odd;
    #ifndef USE_SOFTETH_ON_ZYNQ
    	ResetRxCntr++;
    #endif
    	if (odd) {
    #if LWIP_DHCP==1
    		dhcp_timer++;
    		dhcp_timoutcntr--;
    #endif
    		TcpSlowTmrFlag = 1;
    #if LWIP_DHCP==1
    		dhcp_fine_tmr();
    		if (dhcp_timer >= 120) {
    			dhcp_coarse_tmr();
    			dhcp_timer = 0;
    		}
    #endif
    	}
    

    这里主要做了两个操作,一个是dhcp的超时处理,另外一个就是第四步中两个标志位的判断,他们分别驱动一个定时器(1个250ms -> tcp_fasttmr 一个500ms -> tcp_slowtmr)主要用于协议中各个定时器的更新.

    lwip初始化

    对应第一步,这里就是对lwip中每一个层进行初始化
    image.png

    获取网络参数

    这里需要先了解一个结构体 netif:

    官方描述: Generic data structure used for all lwIP network interfaces.
    netif

    别的先不管,看到我们需要在这里注册他的ip地址,网关和子网掩码
    所以再看一个结构体 ip_addr:

    struct ip_addr {
      u32_t addr;
    };
    

    这里为了对齐,官方特意整了这个东西…
    如果存在dhcp,而且dhcp成功的话,我们只要把ip,子网掩码,网关设置成0,先绑定好网卡地址(MAC),再去创建一个dhcp客户端获取ip地址就可以了.

    //指定MAC地址
    	struct ip_addr ipaddr, netmask, gw;
    	unsigned char mac_ethernet_address[] =
    	{ 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
        ipaddr.addr = 0;
    	gw.addr = 0;
    	netmask.addr = 0;
    
    	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);
    		/* specify that the network if is up */
    	netif_set_up(echo_netif);
    

    然后在网络中注册dhcp 客户端:

    	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;
    

    这里又回到第一步中我们所注册的定时器,里面所指定的dhcp定时器,在这里一超时就会手动设置成静态ip,正好省了我解释怎么设置静态ip

    注册应用

    那么 上述的步骤相当于在网络中"站稳了脚步",也可以理解为完成了网络层的配置,下面要进入运输层和应用层的注册.函数是start_application()
    所以这里就要用到上面说到的protocol control block 协议控制块来指定一个tcp/udp中各个连接,这里tcp的pcb着实是太长太长了,我们来看看udp_pcb这个结构体的定义:

    struct udp_pcb {
    /* Common members of all PCB types */
      IP_PCB;
    
    /* Protocol specific PCB members */
    
      struct udp_pcb *next;
    
      u8_t flags;
      /** ports are in host byte order */
      u16_t local_port, remote_port;
    
      /** receive callback function */
      udp_recv_fn recv;
      /** user-supplied argument for the recv callback */
      void *recv_arg;
    };
    

    你大概就知道他是在干啥了.而且注册也不需要我们自己来干(因为tcp真的太复杂了)
    我们只需要简单地:

    ~~~~start_application()~~~~
    ...
    pcb = tcp_new();
    err = tcp_bind(pcb, IP_ADDR_ANY, port);
    ...
    /* we do not need any arguments to callback functions */
    tcp_arg(pcb, NULL);
    /* listen for connections */
    pcb = tcp_listen(pcb);
    ...
    

    剩下的也不解读了,都是白菜级的函数调用

    重头戏不在这里,在各种回调函数的注册,下面我们来分析一下:

    1. 第一步 tcp_accept(pcb, accept_callback);
      跳进这个函数
    	void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
    {
      /* This function is allowed to be called for both listen pcbs and
         connection pcbs. */
      pcb->accept = accept;
    }
    

    这个时候我们去看tcp_pcb是压根没有accept 这个属性的,因为他被定义在TCP_PCB_COMMON(type)这个宏定义里面的DEF_ACCEPT_CALLBACK.

    	#define DEF_ACCEPT_CALLBACK  tcp_accept_fn accept;
    

    所以我们到 tcp_accept_fn 中看看他是怎么定义这个回调函数的:

    	typedef err_t (*tcp_accept_fn)(void *arg, struct tcp_pcb *newpcb, err_t err);
    	/** Function prototype for tcp receive callback functions. Called when data has
     * been received.
     * * @param arg Additional argument to pass to the callback function (@see tcp_arg())
     * @param tpcb The connection pcb which received data
     * @param err An error code if there has been an error receiving
     *            Only return ERR_ABRT if you have called tcp_abort from within the
     *            callback function! */
    
    1. 基于此 我们所注册的回调函数是:
    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;
    }
    

    在这个我们需要先跳进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;
    }
    

    果不其然,然后这里我们再看看回调函数的格式定义:

    typedef err_t (*tcp_recv_fn)(void *arg, struct tcp_pcb *tpcb,
                                 struct pbuf *p, err_t err);
    
    /** Function prototype for tcp sent callback functions. Called when sent data has
     * been acknowledged by the remote side. Use it to free corresponding resources.
     * This also means that the pcb has now space available to send new data.
     *
     * @param arg Additional argument to pass to the callback function (@see tcp_arg())
     * @param tpcb The connection pcb for which data has been acknowledged
     * @param len The amount of bytes acknowledged
     * @return ERR_OK: try to send some data by calling tcp_output
     *            Only return ERR_ABRT if you have called tcp_abort from within the
     *            callback function!
     */
    

    这里终于出现了我们的tcp的帧结构了, 上文也介绍过这个pbuf 就是我们所想拿到的"信息"啦
    3. 基于此,我们的接收回调函数是这样的:

    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;
    }
    

    其中 err = tcp_write(tpcb, p->payload, p->len, 1); 就是我们所说的回传了
    !千万不要忘记 pbuf_free();

    1. 回到accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
      在这个函数中,我们看见了有一段莫名奇妙的:

    static int connection = 1;
    tcp_arg(newpcb, (void*)(UINTPTR)connection);
    connection++;

    那我们也是一步一步跳进去,看见:

    /**
     * Used to specify the argument that should be passed callback
     * functions.
     *
     * @param pcb tcp_pcb to set the callback argument
     * @param arg void pointer argument to pass to callback functions
     */
    void 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_PCB_COMMON 中的参数,所以现在我们来看看这到底是什么神仙东西:

    #define TCP_PCB_COMMON(type) \
      type *next; /* for the linked list */ \
      void *callback_arg; \
      /* the accept callback for listen- and normal pcbs, if LWIP_CALLBACK_API */ \
      DEF_ACCEPT_CALLBACK \
      enum tcp_state state; /* TCP state */ \
      u8_t prio; \
      /* ports are in host byte order */ \
      u16_t local_port
    

    这里还是没解释清楚,但是他说了 需要 LWIP_CALLBACK_API 支持才会起效,那我们继续看使能这个宏定义下面的注释:

      /* Function to call when a listener has been connected.
       * @param arg user-supplied argument (tcp_pcb.callback_arg)
       * @param pcb a new tcp_pcb that now is connected
       * @param err an error argument (TODO: that is current always ERR_OK?)
       * @return ERR_OK: accept the new connection,
       *                 any other err_t abortsthe new connection
       */
    

    好 这里了,这个参数就是个用户提供的参数…想干啥干啥.
    5. 好啦 醉翁之意不在酒
    其实绕来绕去是为了让大家看到LWIP_CALLBACK_API 这个使能位下的注释,他解释了为什么在accept_callback这个回调中需要重新建立一个新的tcp_pcb的原因.免得一些朋友看得乱了.

    最后的最后 引一个图
    lwip_lib

    结语

    切勿 囫囵吞枣 穿凿附会

    如果你觉得有丶收获的话

    参考文献

    官方docs
    步骤和参数
    lwip_wiki
    XAPP1026翻译
    lwip api介绍
    LWIP使用经验—变态级(好文章)

    展开全文
  • 学会Zynq(10)lwIP简介

    千次阅读 多人点赞 2019-03-20 14:30:50
    从本篇开始,将花大量篇幅介绍Zynq在裸机环境下以太网的使用。裸机时最方便的就是使用SDK已经集成了的lwIP 1.4.1库,我们将先了解lwIP的相关知识,然后再以实例的方式学习TCP、UDP的程序设计方法。 研究背景 在过去...

    从本篇开始,将花大量篇幅介绍Zynq在裸机环境下以太网的使用。裸机时最方便的就是使用SDK已经集成了的lwIP 1.4.1库,我们将先了解lwIP的相关知识,然后再以实例的方式学习TCP、UDP的程序设计方法。


    研究背景

    在过去几年里,将计算机和计算机支持的设备连接到无线网络的需求逐渐增长。计算机与日常设备之间的集成度越来越高,价格也在下降。同时,蓝牙、IEEE 802.11b/g(俗称“wifi”)等无线网络技术已经非常普遍。这导致在医疗保健、安全保障、交通业、加工业等领域出现了许多新颖的场景。传感器等小型设备可以连接到现有的网络设施中(如全球互联网),人们可以在任何地方监控它们。

    互联网技术非常灵活,能够适应过去几十年不断变化的网络环境。互联网技术虽然最初是为阿帕网(ARPANET)等低速网络开发的,但现在可以在一个很大的链路技术频谱上运行,在带宽和误码率方面由截然不同的特性。由于现在已经开发了大量使用互联网技术的应用程序,能在未来的无线网络中使用现有的互联网技术是非常有利的。诸如传感器之类的小型设备通常需要体积小且价格便宜,因此不得不在有限的计算资源和内存上实现互联网协议。

    lwIP最早由Adam Dunkels编写,目前由Kieran Mansley带领的团队开发(开发者主页http://savannah.nongnu.org/projects/lwip )。lwIP是一个小型TCP/IP栈,可以在嵌入式系统中使用。lwIP采取模块化设计,核心栈是IP协议的实现,用户可以在其上选择添加TCP、UDP、DHCP等其它协议,包括这些协议的各种特性。当然这样会导致代码量增加、复杂性提高,需要根据用户的需求进行调整。此外,lwIP在有无操作系统、支持或不支持线程的情况下都可以运行,适用于8位或32位微处理器,支持小端和大端系统。


    支持协议

    在这里插入图片描述
    lwIP是模块化设计,且支持多种协议,大部分协议在无需使用时可以将其移除,减小代码量。lwIP支持的链路层和网络层协议包括:

    • ARP:一种链路层协议,用于将本地硬件地址(即MAC地址)转译为IP地址。
    • IPv4:目前互联网中使用的主要的网络层协议。
    • IPv6:Ipv4的下一代,IP地址大小扩展到了128位。
    • ICMP:一种IP的控制协议。
    • IGMP:一种IP中多点传播的管理协议。

    lwIP支持的传输层协议包括:

    • UDP:一种没有可靠性机制的无连接socket协议。
    • TCP:一种面向连接的“流”协议。

    lwIP支持的高层次协议包括:

    • DHCP:一种带服务器的IP地址获取方法。
    • AUTOIP:一种没有服务器的IP地址选择方法。
    • SNMP:用于监控网络状况。
    • PPP:在两个节点之间创建直接连接。

    应用程序接口

    lwIP提供了三种应用程序接口(Xilinx只支持RAW API和socket API两种),用于程序和TCP/IP代码之间的通信:

    • 低层次的、基于“核”和“回调”的RAW API
    • 两种高层次的、基于“顺序”的API:netconn APIsocket API

    顺序型API类似于BSD socket API,执行模型是基于阻塞的开-读-写-关(open-read-write-close)模式。由于TCP/IP栈本质上是基于事件的,所以TCP/IP代码和应用程序必须在不同的线程中。API之间的差别如下,据此选择使用哪种API:

    • netconn API和raw API只能用于lwIP中,用这些API编写的代码不能移植到其它栈中重用。
    • socket API的目的是能与其它posix操作系统/栈之间可移植,但会降低吞吐量。
    • socket API和netconn API是需要线程的顺序型API
    • raw API基于回调机制,比如当新数据到达时调用已注册的回调函数。由于它不需要切换线程,所以由最佳的性能表现。
    • raw API和netconn API支持TX和RX的“零拷贝(zero-copy)”。

    带或不带操作系统的lwIP

    lwIP可以在裸机环境下运行,也可以在多线程操作系统中运行。

    当在没有操作系统的单线程环境中运行lwIP时,只需要IP、ICMP、UDP和TCP协议的实现、缓冲区和内存管理等核心组件,当然也可以添加DHCP、DNS等组件,但它们不是必须的。甚至可以只编译UDP或TCP。在Xilinx中使用的lwIP,如果运行TCP,需要每250ms调用依次tcp_tmr,它会执行重传等TCP定时器的处理工作。UDP则没有必要。

    单线程的主循环中需要调用链路层驱动程序(Xilinx适配器有专用函数),依次处理IP数据报,然后调用上层协议处理程序,最后调用应用程序的回调函数。

    多线程系统中应用程序在并发线程中运行。可以让所有TCP/IP的处理在一个线程中完成,用户应用程序的线程通过API函数与TCP/IP线程通信。


    最大化吞吐量

    一般设计者想尽可能减少代码量,并让lwIP工作在最大吞吐量状态,有诸多原因会影响到使用lwIP的以太网设备的性能。架构设计方面包括:

    • 由于网络字节顺序是大端模式,因此最好选择大端模式的系统,可以省略其中的转换。
    • 系统的一个瓶颈是以太网MAC驱动程序(lwIP中称作netif-driver),应尽可能使用中断和DMA。通常驱动程序可以编写为偏向于TX或RX,如果应用程序中某个传输方向更重要,应确保在高负载情况下首选该方向。在硬件允许的情况下,确保驱动程序支持分散收集(scatter-gather)。
    • 另一个瓶颈是TCP和UDP的校验和计算,发送数据时生成校验和,接收数据时检查校验和。如果硬件支持,则将校验和的生成和检验留给硬件完成。如果硬件不支持,则确保有一个优化的计算校验和的软件架构。

    对于在Xilinx中实现,第一点我们无法选择,第二点Xilinx已经帮你解决了。我们需要注意的就是第三点。lwIP的配置也会影响到吞吐量性能,SDK中如何配置的相关部分查看本系列其它文章。

    如果希望最大化吞吐量,应用程序应该使用RAW API,而不是netconn/socket API。设计程序前,我们首先要选择使用UDP还是TCP。

    • UDP:优点是开销更少,设计者自己选择消息大小;缺点是没有提供安全的通信路径,该协议不能通知用户对方是否收到了消息。
    • TCP:优点是提供了一个安全的通信路径,当对方成功收到消息时用户会收到通知;缺点是开销更大,还会自动选择消息大小。

    选定协议后,设计者要决定应用程序如何通过网络传递数据:

    • UDP:确保传递的数据块不会小于网络所允许的最大数据包,比如在标准以太网中,使用udp_send一次发送1472个字节,以最大化一个包中数据字节和报头字节的比,同时最小化网络中包间的间隔。
    • TCP:虽然TCP可以将多个tcp_write调用的数据合并到一个包中,但由于这个包被分割到多个pbuf中,可能会降低性能。由于TCP需要将数据包存储起来重新传输,直到远程主机发出应答信号,所以在tcp_write/tcp_output返回后花费几秒的时间。如果要发送小块数据,应该关掉nagle算法,让堆栈立即发送数据,而不是等待更多数据形成更大的数据包后才发送数据。应该避免发送小块数据,总是等待应答会降低性能。
    展开全文
  • 1. LWIP协议栈·· 1 1.1 LWIP库·· 2 1.2 LwIP原理分析·· 3 1.2.1 动态内存管理·· 3 1.2.2 数据包pbuf 4 1.2.3 网络接口·· 7 1.3 PS的千兆以太网控制器·· 7 2. 硬件部署·· 9 2.1 Ethernet硬件设计·· 9...

    1. LWIP协议栈·· 1

    1.1 LWIP库·· 2

    1.2 LwIP原理分析·· 3

    1.2.1 动态内存管理·· 3

    1.2.2 数据包pbuf 4

    1.2.3 网络接口·· 7

    1.3 PS的千兆以太网控制器·· 7

    2. 硬件部署·· 9

    2.1 Ethernet硬件设计·· 9

    2.2 Vivado工程创建·· 10

    3. 软件设计·· 13

    3.1 LwIP echo Server 13

    3.2 Ethernet Server构建·· 15

    3.2.1 系统平台·· 15

    3.2.2 TCP Server 18

    3.2.3 UDP Server 22

    4. 下载测试·· 23

    1. LWIP协议栈

    LwIP(LightWeight IP)是TCP/IP协议栈的一个实现。优势在于内存使用率和代码量较小。适用在资源受限的情况下实现和处理Internet协议。TCP/IP协议即传输控制协议/因特网互联协议,是Internet最基本的协议,由网络层的IP协议和传输层的TCP协议组成。其协议内部采用层级结构,每一层通过呼叫下一层所提供的协议来完成数据传输,通俗来说就是根据层级不同不停的分包解包,直至数据顺利传输。IP则提供一个互联网地址给接入网络的地址。

    需要注意的是TCP/IP协议不是TCP和IP两个协议的合称。而是因特网整个TCP/IP协议族。TCP/IP协议族以分层的方式设计,好处在于每一种协议可以被单独实现,但是如果严格按照分层实现,协议层之间的通讯会拉低总体性能,所以在保证框架的前提下,重要的的信息可以在各层之间进行共享。目前大部分TCP/IP的实现方式为应用层和底层协议层进行严格划分,底层协议之间存在交叉存取。

    网络协议分层如下表:

    OSI****七层模型
    应用层针对特定应用的协议
    表示层设备固有格式转网络标准格式
    会话层通信管理建立和断开通信连接
    传输层管理两节点之间的数据传输,负责可靠传输
    网络层地址管理,路由器管理
    数据链路层互连设备之间的传送和识别数据帧
    物理层界定连接器和网线的规格
    TCP/IP四层(五层)模型
    应用层应用程序DNS/HTTP/SMTP POP/MIME/SSH SIP/TLS/SSL
    传输层操作系统TCP/UDP/UDP_lite ARP/IP/ICMP
    网络层操作系统
    物理接口层网卡层设备驱动以太网协议
    物理层网络接口硬件

    大部分操作系统中,底层协议族作为拥有应用层进程通讯入口的操作系统内核的一部分被实现。而LwIP在各层之间使用比较松散的通讯机制,通过共享内存的方式实现应用层和底层协议族之间通讯,应用层了解底层协议使用的缓冲机制使应用层更加有效的使用缓冲区,应用层与网络层可以使用相同的内存区且可以直接读写内部缓冲区,从而减少内存复制产生的性能损失。

    1.1 LWIP库

    LwIP协议栈作为轻量级IP协议,不依赖于操作系统的支持,从指标上来看其减少了对RAM的占用,运行仅需10几KB的RAM和40K左右的ROM。LwIP主要特征如下:

    • IGMP协议,用于网络组管理,可以实现多播数据的接收
    • Internet协议(IP),包括IPv4和IPv6,支持IP分片与重装,包括通过多个网络接口的数据包转发
    • 用于网络维护和调试的Internet控制消息协议ICMP
    • 用户数据报协议UDP
    • 传输控制协议TCP拥塞控制,往返时间(RTT)估计,快速重传和恢复
    • DNS,域名解析
    • SNMP,简单网络管理协议
    • 动态主机配置协议DHCP
    • 以太网地址解析协议ARP
    • AUTOIP,IP地址自动配置
    • PPP,点对点协议,支持PPPoE

    Vivado2017.4提供版本为lwip1.4.1的SDK库,lwip1.4.1为Ethernetlite(axi_ethernetlite)、TEMAC(axi_ethernet)、MAC(GigE)和千兆以太网控制器提供适配器。此库可以在MicroBlaze、ARM Cortex-A9、ARM Cortex-A53、ARM Cortex-R5处理器上运行。Ethernetlite 和 TEMAC 核心适用于 MicroBlaze 系统。千兆以太网控制器和 MAC(GigE)内核仅适用于 ARM Cortex-A9(Zynq-7000处理器设备)、ARM Cortex-A53 和 ARM Cortex-R5(Zynq UltraScale + MPSoC)。

    几种硬件环境如下表,纯FPGA使用的处理器是软核MicroBlaze,以太网控制器是软核axi_Ethernet和axi_Ethernetlite。Zynq平台使用的是硬核Crotex-A9,以太网是GigE。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hmYTArqE-1607853535475)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175159683.png)]
    Lwip1.4.1提供两套用户编程接口方式:raw API 和 socket API。

    Raw API:为高性能和低内存开销定制。特点是单线程,即网络协议和应用程序存在于一个线程,通过回调函数的进行实现。当接收数据时,应用程序会首先向协议栈注册一个回调函数,当关联的连接有一个信息到达,该回调函数就会被协议栈调用。此种实现方式优点在于执行速度快,消耗内存少,缺点在于网络协议的处理和运算是在同一进程中完成,这就导致应用程序无法连续运算,无法并行。

    Socket API:提供一个基于 open – read – write – close模块的BSD socket-style接口,需要操作系统支持,移植性更好,但是内存开销大且性能不如Raw API。

    Raw API是在没有操作系统的情况下运行LwIP唯一可用的API,所以本文将以Raw API搭建网络服务,实现向flash写入bin文件。

    1.2 LwIP原理分析

    一般来说LwIP的运行都存在操作系统的支持,因为会存在进程间通信。最简单的即信号量和邮箱机制。当存在操作系统时,LwIP使用邮箱和信号量实现应用层、协议栈、下层驱动、协议栈间的信息交互。LwIP从本质上讲只是模拟了TCP/IP协议的分层思想,其只是在一个进程中完成了各个层次的所有工作。首先LwIP完成初始化后,阻塞在一个邮箱上,等待接收数据进行处理,数据来源为底层硬件驱动接收到的数据或者应用程序。当在该邮箱获取到数据就,lwIP进行数据解析,依次调用协议栈内部上层相关处理函数处理数据,结束后LwIP继续阻塞等待数据,这个过程期间需要内存管理机制进行辅助,避免时间、内存开销过大。总之,典型LwIP应用系统至少包括三个进程,上层应用程序进程,LwIP协议栈进程,底层硬件数据包收发进程。LwIP协议栈进程在应用进程中调用初始化函数创建且拥有最高优先级,目的在于实时正确的对数据进行响应。

    裸机运行时,机制与上述过程类似,只是实现方式上存在差异,是利用回调函数进行实现。

    1.2.1 动态内存管理

    LWIP协议栈动态内存管理机制分三种:

    • C运行库自带的内存分配策略
    • 动态内存堆(HEAP)分配策略
    • 动态内存池(POOL)分配策略

    最常用的方式为动态内存堆(HEAP)分配策略.

    其原理是在事先定义好大小的内存块中进行管理, 其内存分配的策略是First Fit方式,只要找到一个比所请求的内存大的空闲块,就从其中切割出合适的块,并把剩余的部分返回到动态内存堆中. 分配的内存块的大小最小会按MIN_SIZE进行分配 (一般MIN_SIZE = 12byte). 12字节中前几字节会存放管理器管理的私有数据,用户不可见.

    内存释放是相反过程, 同时分配器会检查其相邻内存块是否空闲,是则合并.。优点是内存浪费小,比较简单,适合用于小内存管理,缺点是如果频繁的动态分配释放,内存随便严重.所以尽量保持分配释放同步。

    LWIP对内存管理的实现:

    • mem_init() 内存堆得初始化函数, 告知内存堆起止地址, 以及初始化空闲列表,由LWIP初始化时自动调用,内部私有接口,不开放用户层
    • mem_malloc() 申请分配内存, 将总共需要的字节数作为参数传递给该函数,返回值是指向最新分配的内存的指针, 如果内存没有分配好, 返回值为空, 分配的空间大小会受到内存对齐的影响,可能会比申请的略大. 并且申请的内存并没有进行初始化. 内存的分配和释放, 不能在中断函数中进行, 内存堆是全局变量. 因此内存的申请\释放必须做线程安全保护,如果有多个线程在同时进行内存得申请和释放,那么可能会因为信号量等待而导致申请耗时较长
    • mem_calloc() 对malloc的简单封装,参数为元素数目和每个元素大小,此函数在分配空间的同时对申请的内存去进行清0.

    动态内存池(POOL)分配策略:

    POOL的分类根据LWIP的配置不同而不同, 定义LWIP_UDP为1,则在编译的时候与UDP相关的内存池就会被建立 ; 定义LWIP_TCP为1, 则在编译的时候建立与TCP类型内存池就会被建立 ; 存放网络包数据信息的 pbuf_pool等.

    值得注意的是某种类型的POOL其单个大小是固定的,而分配的个数是由用户决定,把协议栈中的所有的POOL挨个放到一起, 并组成一片连续的内存区域,整合起来就是一个缓冲池.缓冲池必须是以单个缓冲池为基本单位。

    LwP常用的内存分配策略两种结合使用:内存堆分配(优点随便分配合适的内存块、缺点内存堆会存在内存碎片,此时有可能在申请较大内存块时申请失败) 内存池分配(优点简单链表操作,分配速度快,缺点是因为提前建立各种POOL,会浪费一定内存空间)。

    1.2.2 数据包pbuf

    内存管理和数据包的管理密不可分。协议栈各层交互数据种类繁复,大小不定,且数据包来源不定。此时要极力禁止内存拷贝就需要一个标准且高效的数据包管理核心,LwIP数据包管理核心采用pbuf结构体描述数据包。

    每个pbuf管理的数据不能覆盖整个数据包,所以需要链表结构.

    • next 链表实现结构
    • payload 数据指针, 指向该pbuf管理的数据起始地址. 数据的起始地址可以是紧跟在pbuf之后堆RAM,也可以是ROM上的某个地址. 地址位置在哪决定于pbuf类型,
    • len字段表示当前pbuf中的有效数据长度
    • tot_len字段是pbuf和其后有pbuf有效数据长度,所以链表第一个元素的的tot_len表示整个数据包的长度,最后一个pbuf的tot_len字段必等于len
    • ref字段表示pbuf被引用的次数,初始化时被置为 1,当有其他pbuf的next指针指向该buf时,该buf的ref字段值加一,所以要删除一个pbuf,ref的值必须为1时才可以

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K9fSoyqm-1607853535480)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175231081.png)]

    图1-1 Pbuf结构及分配方式结构图

    Pbuf****的创建:

    PBUF_RAM :

    该类型使用最多,常用于协议栈要发送的数据和应用程序要传递的数据。分配过程会分配相应的大小,从内存堆分配,大小包括结构头大小。申请函数如下:

    p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset)+ LWIP_MEM_ALIGN_SIZE(length));

    分配空间的大小包括:pbuf 结构头大小 SIZEOF_STRUCT_PBUF,需要的数据存储空间大小 length,还有一个 offset。位置存在于连续的内存空间。

    分配成功的pbuf_ram结构如图1-2。

    ​     [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FtRDaQAc-1607853535485)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175259840.png)]

    图1-2 Pbuf_Ram结构

    PBUF_POOL:

    可以在极短时间内完成分配,因为该类型主要通过内存池分配,常用于接收数据包时。申请该类型时协议栈会在内存池中分配满足申请大小的内存池个数。申请函数如下:

    p = memp_malloc(MEMP_PBUF_POOL);

    申请成功的pbuf_pool结构为链表结构,如图1-3。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DdDoiEqJ-1607853535489)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175307487.png)]

    图1-3 Pbuf_Pool结构

    PBUF_ROM:

    在内存堆分配一个相应的pbuf结构头,不申请数据区的空间。申请函数如下:

    p = memp_malloc(MEMP_PBUF);

    此时申请的内存池类型是MEMP_PBUF,而不是MEMP_PBUF_POOL,因为MEMP_PBUF的类型内存池大小刚好是一个pbuf的大小,该类型的内存池是LwIP为PBUF_ROM和PBUF_REF定制,并且LwIP会为不同的数据结构定制不同类型的内存池。正确分配的PBUF_POOL的结构如图1-4。

    图1-4 Pbuf_RAM结构

    PBUF_REF:

    与PBUF_ROM类似,区别只在于PBUF_ROM指向ROM空间内的某段数据,PBUF_REF指向RAM空间内的某段数据。

    总结如下:

    任意类型的pbuf可以随意组合,通常情况下是很多不同类型的pbuf组成链表用来保存一段数据。

    pbuf****类型分配方式分配函数特征
    PBUF_RAM内存堆mem_malloc() pbuf****结构头、数据length、offset连续的内存区
    PBUF_ROM内存堆memp_malloc(MEMP_PBUF);只分配pbuf头不申请空间
    PBUF_REF内存堆memp_malloc(MEMP_PBUF);只分配pbuf头不申请空间
    PBUF_POOL内存池memp_malloc(MEMP_PBUF_POOL);分配时间极短

    Pbuf****的释放:

    ​ Pbuf可以被释放的前提是当前pbuf结构体的ref字段为1,该字段表示当前pbuf被引用的次数,当pbuf被创建时初始化为1。也就是说能删除的节点必然是当前链表的首节点,当首节点被删除,LwIP会访问第二个节点如ref为1则继续删除,以此类推,反之此节点之后pbuf链在别处被引用,不在进行后续删除。

    ​ 当要删除某个pbuf链表时,LwIP首先检查当前pbuf类型,根据类型不同调用不同的内存释放函数进行删除,除PBUF_RAM调用mem_free()删除,其余3类pbuf均调用memp_free()。当然调用函数释放回内存堆涉及内存管理机制,此处是产生内存堆碎片的根源问题,简单来说当内存被释放时为防止产生内存碎片会检查上一个和下一个分配块的使用标志,如其一未被使用就会将释放内存与其合并,组成更大的未使用内存块,使用标志的维护是在分配内存时向数据区后插入的结构体同样为链式结构,已经分配的内存回收后会将标志used清除,但是如果上下内存块都被使用此时就会释放的内存就会成为一个小且独立的内存碎片,此乃根源。释放回内存池类似链表的一套操作。

    1.2.3 网络接口

    如图1-5,LwIP协议栈通过netif结构体描述一个硬件网络接口。

    在这里插入图片描述

    图1-5 网络接口结构图

    网络接口初始化,以太网数据传输等将在软件设计模块根据源码进行解析。

    1.3 PS的千兆以太网控制器

    根据OSI模型,以太网卡工作在最后两层,物理层和数据链路层。其中物理层定义了数据发送及接受所需要的光电信号,线路状态,时钟基准,数据编码,电路等,同时向数据链路层设备提供标准接口。物理层芯片即为PHY芯片。PHY芯片提供和对端设备连接的功能,同时通过一定的LED显示表示当前连接状态。网卡插入网线,PHY不断发出脉冲信号检测对端设备,协商连接速度,工作模式,是否流控。通常情况下,协商的结果是两个设备中能同时支持的最大速度和最好的双工模式,即AutoNegotiation,自协商。

    数据链路层则提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能,以太网卡中数据链路层芯片即MAC控制器。

    MAC控制芯片与PHY芯片通过MII(Medium Independent Interface)接口进行连接,千兆以太网常用MII接口为GMII(Gigabit Medium Independent Interface)或RGMII(Reduced Gigabit Media Independent Interface)接口连接。

    在这里插入图片描述

    图1-6 GMII接口

    如图1-6所示,GMII接口提供了8位数据通道,125MHz的时钟频率,从而具有1000Mbps的数据传输速率。其24根信号线具体功能如下表所示。

    信号名称数量功能备注
    GTX_CLK1发送时钟信号MAC****到PHY的发送数据接口
    TXD[7:0]8发送数据信号
    TX_ER1发送错误提示信号
    TX_EN1发送使能信号
    RX_CLK1接收时钟信号PHY****到MAC的接收数据信号
    RXD[7:0]8接收数据信号
    RX_ER1接受错误提示信号
    RX_DV1接收数据有效信号
    COL1冲突检测信号PHY****到MAC的状态指示信号接口
    CRS1载波侦听信号
    MDC1Management ClockMAC****和PHY间传送控制和状态信息接口
    MDIO1management Data IO

    RGMII接口即Reduced GMII,GMII的简化版。GMII的引脚为16个。如图1-7,TX_CTL为信号线,传输TX_EN和TX_ER,上升沿发送TX_EN,下降沿发送TX_ER。RX_CTL信号线上传送RX_DV信号和RX_ER信号。上升沿发送RX_DV,下降沿发送RX_EV。其余同GMII。

    在这里插入图片描述

    图1-7 GMII接口

    PS的以太网控制器(GEM)如图1-8所示。PS的千兆以太网兼容上述3种速率的以太网MAC,在这三种速率下可以工作全双工或者半双工模式。DMA控制器通过AHB总线接口连接到存储器,MAC控制器与FIFO接口的连接作为为系统提供scatter-gather类型的功能(scatter-gather DMA 和 block DMA)。

    当通过MIO口连接至PS的以太网PHY芯片,则每个控制器使用RGMII接口,目的在于节省引脚,如果使用EMIO;连接至PL端的以太网,每个接口使用GMII接口,可以通过APB总线访问千兆以太网控制器的寄存器,寄存器用于配置MAC的功能,选择不同的操作模式,以及启动和监控网络哦管理统计信息。控制器为管理PHY芯片提供MDIO接口,可以从MDIO接口控制PHY芯片。

    在这里插入图片描述

    图1-8 以太网控制器

    2. 硬件部署

    2.1 Ethernet硬件设计

    本平台搭载两个RJ45以太网接口用于连接以太网线,本次只是用其中XS001,原理图如图2-1。

    在这里插入图片描述

    图2-1 RJ45接口原理图

    数据的传输需要依靠PHY芯片,平台网口挂在于PS,所以控制接口为RGMII。其数据流程如图2-2。原理图如图2-3。

    图2-2 以太网数据流向

    在这里插入图片描述

    图2-3 88E1518原理图

    其与MIO口的具体连接参考核心板的原理图设计。**需要特别注意的是在该平台PHY芯片的复位信号需要PL****给出。**具体引脚约束将在vivado环境搭建时给出。

    2.2 Vivado工程创建

    本工程使用版本为vivado2017.4。

    选择create project 创建工程名称,选择存储位置

    在这里插入图片描述
    在这里插入图片描述

    图2-4 创建工程

    选择RTL project,勾选下方选项会省略添加源文件和约束文件步骤,之后直接进行芯片的选型,对照芯片丝印或者电路图选择合适的芯片型号即可。(在选型时可先选封装类型)工程创建完成。

    在这里插入图片描述

    在这里插入图片描述

    图2-5 选择工程类型及器件选型

    点击Create Block Design 并设置Design name。在Digram点击“+”,输入Zynq添加Zynq7。

    在这里插入图片描述
    在这里插入图片描述

    图2-6 创建block design及添加zynq IP

    之后可以双击Zynq7 根据项目进行相应的配置,注意DDR的型号以及时钟频率的对应即可。配置完成后按F6 Validata Design。有错误需要仔细检查配置问题。

    相比于之前重配置工程的配置,网口工程配置略有不同。首先双击打开zynq7 重定义串口后,在peripheral I/O pins界面勾选如下:

    在这里插入图片描述

    图2-7 网口参数配置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbAyyy0b-1607853535541)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175517520.png)]

    图2-8 Zynq外设配置

    除红框内必须配置外其余外设根据需求可自由发挥。此工程不在需要添加其他IP。

    注意因为PHY芯片的复位信号在本平台上由PL给出所以在顶层文件中需要拉出复位引脚eth_reset,拉出信号后在约束文件config_pins.xdc添加如下约束语句。
    在这里插入图片描述

    图2-9 引脚约束

    set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_txd]

    set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_rxd]

    set_property PACKAGE_PIN AA22 [get_ports UART_0_0_txd]

    set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]

    set_property PACKAGE_PIN AA23 [get_ports UART_0_0_rxd]

    set_property IOSTANDARD LVCMOS18 [get_ports FCLK_RESET0_N_0]

    set_property IOSTANDARD LVCMOS18 [get_ports FCLK_CLK0_0]

    set_property PACKAGE_PIN AA25 [get_ports led1]

    set_property IOSTANDARD LVCMOS33 [get_ports led1]

    set_property PACKAGE_PIN L3 [get_ports eth_reset]

    set_property IOSTANDARD LVCMOS18 [get_ports eth_reset]

    约束完成过后正常综合,生成bitstream,之后export handware,启动SDK,进行软件设计。

    3. 软件设计

    在完成服务器代码的重构和写入flash功能前可以先利用echo测试网络通路,保证最基本的网络畅通。

    3.1 LwIP echo Server

    软件设计基于官方LwIP Echo Server工程模板实现。Echo工程模板创建步骤如下:

    Step1: file -> new ->application project 输入工程名后 -> next ->finish
    在这里插入图片描述
    在这里插入图片描述

    图3-1 LwIP echo server 创建

    之后点击右击“工程名_bsp”,点击board support package setting进行如下的配置,确认后等待编译完成。
    在这里插入图片描述

    图3-2 LwIP协议栈参数配置

    编译完成后修改main.c,修改板子IP和主机在同一局域网下,strat_application()函数修改端口号(不修改默认为7)


    在这里插入图片描述

    图3-3 板子IP设置

    修改后编译下载程序。如果一切顺利,在串口打印出PHY信息后会等待几秒打印DHCP Timeout后打印板子IP信息,利用网口助手配置如下,发送信息即可接受相同的信息,完成echo 测试。

    在这里插入图片描述

    图3-4 串口、网口调试信息

    注意:

    如果不想等待几秒,可以在板级LwIP配置关掉DHCP。

    如果卡在xamac_add()函数,尝试关掉自协商,上述配置时是关掉的。

    如果网口灯在下载程序后熄灭,检查复位信号是否成功引出。

    如果网络连接失败,先检查硬件连接,之后检查IP配置,尤其注意子网掩码和本机IP。

    不要进行太过细节的找错,因为echo测试是一个很简单的测试,不涉及性能等配置,所以LwIP参数修改对其影响不大。

    3.2 Ethernet Server构建

    3.2.1 系统平台

    首先和echo server不同的是为提高传输文件效率需要对LwIP板级支持做出一些参数调整。如图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CWKR4vgO-1607853535561)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175611079.png)]在这里插入图片描述

    图3-5 LwIP参数配置

    主要参数设置如下表所示:

    mem_sizemem_n_pbufmemp_n_tcp_seg
    可得到的总的对空间pbuf****数同时排队的TCP段数
    pbuf_pool_sizetcp_snd_buftcp_wnd
    pbuf****池中的缓冲区数量tcp****发送缓冲区空间tcp****窗口大小

    在按照上述参数对LwIP协议进行初始化等待编译完成后,通过对echo server实验的功能提取,LwIP平台初始化可封装成如下的函数:
    在这里插入图片描述

    此函数是裸跑LwIP对平台初始化,LwIP初始化的一个通用设置函数,通过改变ETH_CHANCE的值建立相应的连接(ETH_CHANCE为0:TCP反之UDP)。总的来说一个网卡结构通过如下的源码进行初始化,完成数据包的接收和发送:

    static struct netif server_netif; (1)
    struct ip_addr ipaddr, netmask, gw; (2)
    IP4_ADDR(&gw, 10,129,15,10); (3)
    IP4_ADDR(&ipaddr, 10,129,0,1); (4)
    IP4_ADDR(&netmask, 255,255,0,0); (5)
    netif_init(); (6)
    netif_add(&server_netif, &ipaddr, &netmask, &gw,

    NULL, ethernetif_init, tcpip_input); (7)

    netif_set_default(&server_netif); (8)
    netif_set_up(&server_netif); (9)

    (1) 声明netif结构体变量

    (2) 声明IP地址、子网掩码、网关地址的变量、

    (3) 对IP的初始化

    (4) 对网关的初始化

    (5) 对掩子网掩码的初始化

    (6) 初始化全局变量netif_list : netif_list = NULL;

    (7) 调用netif_add()函数初始化变量server_netif ,其中ethernetif_init为用户定义的底层接口初始化函数,tcpip_input函数是IP层递交数据包的函数,该值会被传递给server_netif的input字段。

    在本平台中ethernetif_init函数替换为xemacpsif_init,tcpip_input替换为Ethernet_input。以下将根据源码对网络接口初始化进行说明:

    对源码进行简单的梳理可得到如下netif_add()函数

    struct netif *netif_add(struct netif *netif, struct ip_addr *ipaddr,

    struct ip_addr *netmask,struct ip_addr *gw,void *state,

    err_t (* init)(struct netif *netif),

    err_t (* input)(struct pbuf *p, struct netif *netif))
    {
    static u8_t netifnum = 0;
    netif->ip_addr.addr = 0; //复位变量网络接口结构体中各字段的值
    netif->netmask.addr = 0;
    netif->gw.addr = 0;
    netif->flags = 0; //该网卡不允许任何功能使能
    netif->state = state; //指向用户关心的信息,这里为 NULL
    netif->num = netifnum++; //设置 num 字段,
    netif->input = input; //如前所述, input 函数被赋值
    netif_set_addr(netif, ipaddr, netmask, gw); //设置变量 enc28j60 的三个地址
    if (init(netif) != ERR_OK) { //用户自己的底层接口初始化函数
    return NULL;
    }
    netif->next = netif_list; //将初始化后的节点插入链表 netif_list
    netif_list = netif; // netif_list 指向链表头
    return netif;
    }

    上述init()函数为用户自定义函数,在本平台即为xemacpsif_init(),源码:

    err_t xemacpsif_init(struct netif *netif)

    {

    ​ netif->name[0] = IFNAME0; //‘t’初始化结构体name字段

    ​ netif->name[1] = IFNAME1; //‘e’这个值不需要关心

    ​ netif->output = xemacpsif_output; //IP层发送数据包函数

    ​ netif->linkoutput = low_level_output; //ARP模块发送数据包函数

    ​ low_level_init(netif); //底层硬件初始化函数

    return ERR_OK;

    }

    其中又进行了底层的low_level_init()函数调用,此函数是底层硬件的初始化函数,主要是对netif结构体的MAC地址字段、mtu字段、flags字段、硬件驱动等进行初始化,其中mtu为最大允许传输单元,flags字段一般设置为开启网卡广播、ARP、并允许有硬件链路连接。至此,网络接口初始化完毕。

    (8) 调用netif_set_default函数初始化缺省网络接口。在协议栈中netif_list指向netif网络接口结构体链表,netif_default指向缺省的网络接口结构。当IP层数据需要发送,netif_default会以netif_list为索引选择满足需求的网络接口发送数据包,如果链表中没有可发送数据的接口,则调用缺省的网络接口直接发送数据包。此语句执行的效果就是该接口设置为缺省的网络结构。

    (9) 使能网络接口函数。使能成功后网络接口即可正常收发数据包。

    网口初始化完成后就可以使用一些应用程序完成功能。在编写应用程序,最好是对网络的数据传输、数据帧格式、IP分片重装、ICMP处理等一些网络知识有一定了解。

    3.2.2 TCP Server

    TCP是LwIP很庞大且繁复的一个部分,所以本节只是基于协议栈建立一个TCP服务并简单设计一个应用程序。在RAW API编程中常用TCP函数如下:

    组别API功能描述
    TCP****建立连接tcp_new()创建一个TCP的PCB控制块
    tcp_bind()为TCP的PCB控制块绑定本地IP和端口
    tcp_listen()开启TCP的侦听
    tcp_accept()控制块accept字段注册的回调函数,侦听到连接时被调用
    tcp_conect()连接远程主机
    发送TCP数据tcp_write()构造一个报文并放到控制块的发送缓冲队列中
    tcp_sent()控制块sent字段注册的回调函数,数据发送成功后被回调
    tcp_output()将发送缓冲队列中的数据发送出去
    接收TCP数据tcp_recv()控制块recv字段注册的回调函数,当接收到新数据时被调用
    tcp_recved()当程序处理完数据后必须调用的函数,通知内核更新接收窗口
    轮训函数tcp_poll()控制块poll字段注册的回调函数,实现周期性调用
    关闭和中止连接tcp_close()关闭一个TCP连接
    tcp_err()控制块err字段注册的回调函数,遇到错误时被调用
    tcp_abort()中断TCP连接

    在上述内容中提到,裸核运行LwIP是基于回调函数实现,回调即是在此处回调。建立一个TCP连接可封装成如下的函数:

    int new_tcp_connect()

    {

    struct tcp_pcb *pcb;

    ​ err_t err;

    ​ pcb = tcp_new(); //创建PCB

    if(!pcb){

    ​ xil_printf(“error createing pcb\r\n”);

    return -1;

    ​ }

    ​ err = tcp_bind(pcb,IP_ADDR_ANY,ETH_PORT); //绑定端口号

    if(err != ERR_OK){

    ​ xil_printf(“unable to bind to port\r\n”);

    return -2;

    ​ }

    ​ tcp_arg(pcb,NULL); //可调用相应的回调函数,关联相应的PCB

    ​ pcb = tcp_listen(pcb); //监听连接

    if(!pcb){

    ​ xil_printf(“out of memory while tcp_listen\r\n”);

    return -3;

    ​ }

    ​ tcp_accept(pcb,(tcp_accept_fn) accept_callback_tcp);

    ​ xil_printf(“tcp server started @port %d\r\n”,ETH_PORT);

    return 0;

    }

    accept_callback_tcp()函数为侦听到连接时的回调函数,accept()会分配资源给此次连接,同时回调。“DDOS攻击”就是利用accept(),发起大量客户端请求耗空服务器资源。回调函数如下:

    int accept_callback_tcp(void *arg, struct tcp_pcb *newpcb, err_t err)

    {

    ​ xil_printf(“tcp_server : connection accepted\r\n”);

    ​ c_t_pcb = newpcb;

    ​ //设置接收回调

    ​ tcp_recv(c_t_pcb,recv_callback_tcp);

    ​ tcp_arg(c_t_pcb,NULL);

    return ERR_OK;

    }

    此时会继续回调接收回调函数recv_callback_tcp(),注意这两次回调的意义并不一样,区别在于accept()和recive()的区别。在接收回调函数里会对接收的数据进行简单的分析处理,recv_callback_tcp实现如下:

    static err_t recv_callback_tcp(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)

    {

    struct pbuf *q;

    if (!p) {

    ​ tcp_close(tpcb);

    ​ tcp_recv(tpcb, NULL);

    ​ xil_printf(“tcp connection closed\r\n”);

    return ERR_OK;

    }

    q = p;

    if (q->tot_len == 6 && !(memcmp(“update”, p->payload, 6))) {

    ​ start_update_flag = 1;

    ​ sent_msg_tcp("\r\nStart QSPI Update\r\n");

    } else if (q->tot_len == 5 && !(memcmp(“clear”, p->payload, 5))) {

    ​ start_update_flag = 0;

    ​ total_bytes = 0;

    ​ sent_msg_tcp(“Clear received data\r\n”);

    ​ xil_printf(“Clear received data\r\n”);

    } else {

    while (q->tot_len != q->len) {

    memcpy(&rxbuffer[total_bytes], q->payload, q->len);

    ​ total_bytes += q->len;

    ​ q = q->next;

    ​ }

    memcpy(&rxbuffer[total_bytes], q->payload, q->len);

    ​ total_bytes += q->len;

    }

    tcp_recved(tpcb, p->tot_len);

    pbuf_free§;

    return ERR_OK;

    }

    在接收回调函数内完成对数据的接收和处理,这里的数据处理是总体来说对上文提到的pbuf的处理。之后程序进入while(1)循环执行数据包的接收,以及在transfer_data_tcp()函数里执行写入flash操作。值得注意的是while循环里面的TcpFastTmrFlag 和 TcpSlowTmrFlag是保证TCP传输的标志位(目前测试结果显示并不影响点对点连接),定时器中断分别以250ms和500ms的周期来改变这两个标志位。数据包接收函数xemacif_input函数将接收的数据包传递给LwIP,LwIP调用相关回调处理程序。

    LwIP使用两个周期性定时器,周期250ms和500ms,这点类似于BSD中的TCP。这个两个定时器同时会被用于实现更复杂的逻辑定时器,比如重发定时器,TIME_WAIT定时器以及延迟ACK定时器。这只是TCP处理的很小的一个部分,因为对于LwIP而言,50%的代码量都是在维护TCP,目的在于为应用层提供可靠地字节流服务,包括但不限于糊涂窗口的避免、快速重发、往返时间估计、拥塞控制等等。

    本demo的最终目的在于实现网口写入flash操作,所以当完成TCP服务的搭建后即需要完成对flash的初始化、擦除、写入,本平台flash大小16MB。如上文所说,更新flash的操作在transfer_data_tcp()完成,具体实现如下:

    int transfer_data_tcp()

    {

    if (start_update_flag) {

    ​ xil_printf("\r\nStart QSPI Update!\r\n");

    ​ xil_printf(“file size of BOOT.bin is %lu Bytes\r\n”, total_bytes);

    if(qspi_update(&(initps_para_ptr->Xqspi),total_bytes,rxbuffer)

    != XST_SUCCESS)

    {

    ​ sent_msg_tcp(“Update Qspi Error!\r\n”);

    ​ xil_printf(“Update Qspi Error!\r\n”);

    ​ }

    else

    ​ total_bytes = 0;

    }

    start_update_flag = 0;

    return 0;

    }

    ​ 在flash操作中,主要实现以下几个函数来实现功能:

    ​ s32 qspi_init(XQspiPs* _XQspiPs_Ptr);

    void FlashErase(XQspiPs* _XQspiPs_Ptr, u32 Address, u32 ByteCount);

    void FlashRead(XQspiPs* _XQspiPs_Ptr, u32 Address, u32 ByteCount, u8 Command);

    void FlashWrite(XQspiPs* _XQspiPs_Ptr, u32 Address, u32 ByteCount, u8 Command);

    int qspi_update(XQspiPs* _XQspiPs_Ptr,u32 total_bytes, const u8 *flash_data);

    3.2.3 UDP Server

    UDP服务的创建相比于TCP在理论上简单许多,实现上类似。系统平台设计不需要变更,只需要注意将UDP选项打开即可。建立服务的过程封装成如下的函数:

    int new_udp_connect(){

    struct udp_pcb *pcb;

    ​ err_t err;

    unsigned port = ETH_PORT;

    ​ //创建pcb

    ​ pcb = udp_new();

    if(!pcb)

    ​ {

    ​ xil_printf(“Error creating PCB. Out of Memory\r\n”);

    return -1;

    ​ }

    ​ //绑定端口

    ​ err = udp_bind(pcb,IP_ADDR_ANY,port);

    if(err != ERR_OK)

    ​ xil_printf(“error on udp_connect: %x\n\r”, err);

    ​ //设置接收回调函数

    ​ udp_recv(pcb,(udp_recv_fn)recv_callback_udp,NULL);

    return 0;

    }

    同样是基于回调函数,但是UDP是直接回调接收回调函数,这与TCP略有差异,UDP接收回调函数如下:
    在这里插入图片描述

    回调函数同样是对接收数据进行简单的处理。While(1)循环内部transfer_data_udp()与TCP相同,也可自行实现其他应用程序。

    4. 下载测试

    对于搭建TCP服务器更新flash来说,下载验证只需要串口调试助手和网络调试助手,辅助以内部实现的进度打印函数即可很容易的进行验证。配置参数,连接成功后如图4-1。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R4oPqjT6-1607853535571)(C:\Users\yang\AppData\Roaming\Typora\typora-user-images\image-20201213175701431.png)]

    图4-1 测试设置

    勾选启动文件下载,选择合适bin文件发送,发送完毕后发送“update”,等待片刻即可看到图4-2更新flash成功的串口和网口信息
    在这里插入图片描述

    图4-2 更新flash成功

    通过改变应用函数可以进行功能的改变,以网口传输bin文件实现PS重配置PL为例,只需要将重配置函数进行封装,将接受地址指针作为参数传入重配置函数即可,但是需要注意的是因为跟新PL所以网口必须断开,然后进行重配置,如果新bin文件配置了网口,那么可以重新new一个服务出来进行连接.

    DDR_ANY,port);

    if(err != ERR_OK)

    ​ xil_printf(“error on udp_connect: %x\n\r”, err);

    ​ //设置接收回调函数

    ​ udp_recv(pcb,(udp_recv_fn)recv_callback_udp,NULL);

    return 0;

    }

    同样是基于回调函数,但是UDP是直接回调接收回调函数,这与TCP略有差异,UDP接收回调函数如下:

    [外链图片转存中…(img-6Y7ydUFi-1607853535569)]

    回调函数同样是对接收数据进行简单的处理。While(1)循环内部transfer_data_udp()与TCP相同,也可自行实现其他应用程序。

    4. 下载测试

    对于搭建TCP服务器更新flash来说,下载验证只需要串口调试助手和网络调试助手,辅助以内部实现的进度打印函数即可很容易的进行验证。配置参数,连接成功后如图4-1。

    [外链图片转存中…(img-R4oPqjT6-1607853535571)]

    图4-1 测试设置

    勾选启动文件下载,选择合适bin文件发送,发送完毕后发送“update”,等待片刻即可看到图4-2更新flash成功的串口和网口信息

    [外链图片转存中…(img-1xpycC0S-1607853535574)]

    图4-2 更新flash成功

    通过改变应用函数可以进行功能的改变,以网口传输bin文件实现PS重配置PL为例,只需要将重配置函数进行封装,将接受地址指针作为参数传入重配置函数即可,但是需要注意的是因为跟新PL所以网口必须断开,然后进行重配置,如果新bin文件配置了网口,那么可以重新new一个服务出来进行连接.

    展开全文
  • LWIP141上行传输数据

            此篇是我在学习中做的归纳与总结,其中如果存在版权或知识错误或问题请直接联系我,欢迎留言。
    PS:本着知识共享的原则,此篇博客可以转载,但请标明出处!

    目录

    1.项目简介:

    1.1 完成功能:

    1.1 使用工具:

    2. LWIP141+DMA上行传输数据

    2.1 LWIP141

    2.2 PS端代码开发

    2.3 PC端网络配置

    3. 高速数字系统时钟设计-AD9516

    4. 高速ADC--ADS62P49

    5. 测试

    MATLAB读取TXT数据文件的频谱分析--FFT:


    1.项目简介:

    1.1 完成功能:

      Zynq控制高速高精度ADC(ADS62P49)完成250MSPS采样率模数转换功能,并将采集的大批量数据(100000x14x2bit)通过DMA传输至DDR3,而后经过网口传输至PC机做后续处理。

    1.1 使用工具:

    Vivado2017.4;

    北京太速科技ZYNQ板卡:(XC7Z100)核心处理器及外设通用板卡;

    双通道高速高精密ADDA子卡(ADS62P49、AD9122)

    2. LWIP141+DMA上行传输数据

    上行传输整体实现框图:

    与DMA-FIFO对接需要使用AXI接口形式将数据传输至FIFO中,因此掌握AXI-FIFO的数据流传输机制是非常关键的,在此针对AXI接口传输具体实现细节不做描述,具体实现代码如下:

    module data(
        output  [31:0]      S_AXIS_tdata ,
        output  [3:0]       S_AXIS_tkeep,
        output              S_AXIS_tlast,
        input               S_AXIS_tready,
        output              S_AXIS_tvalid,
        input               gpio_rtl_tri_o,
        input               peripheral_aresetn,
        input               FCLK_CLK0_0 ,
        output              s_axis_aclk,
        output              s_axis_aresetn
        );
        
     reg [31:0]S_AXIS_tdata;
     reg  S_AXIS_tlast;
     reg S_AXIS_tvalid; 
     wire FCLK_CLK0_0;
     wire s_axis_aclk;
     wire s_axis_aresetn;
     wire [3:0]S_AXIS_tkeep;
     wire S_AXIS_tready;
     wire [0:0]gpio_rtl_tri_o;
     wire [0:0]peripheral_aresetn;
    reg [1:0] state;
     
    assign S_AXIS_tkeep = 4'b1111;  
    assign s_axis_aclk =  FCLK_CLK0_0;
    assign s_axis_aresetn = peripheral_aresetn;
      
    always@(posedge FCLK_CLK0_0) begin
           if(!peripheral_aresetn) begin
               S_AXIS_tvalid <= 1'b0;
               S_AXIS_tdata <= 32'd0;
               S_AXIS_tlast <= 1'b0;
               state <=0;
           end else begin
              case(state)
                0: begin
                    if(gpio_rtl_tri_o&& S_AXIS_tready) begin
                       S_AXIS_tvalid <= 1'b1;
                       state <= 1;
                    end else begin
                       S_AXIS_tvalid <= 1'b0;
                       state <= 0;
                    end
                  end
                1:begin
                     if(S_AXIS_tready) begin
                         S_AXIS_tdata <= S_AXIS_tdata + 1'b1;
                         if(S_AXIS_tdata == 32'd100000) begin
                            S_AXIS_tlast <= 1'b1;
                            state <= 2;
                         end else begin
                            S_AXIS_tlast <= 1'b0;
                            state <= 1;
                         end
                     end
                     else begin
                        S_AXIS_tdata <= S_AXIS_tdata;                   
                        state <= 1;
                     end
                  end       
                2:begin
                     if(!S_AXIS_tready) begin
                        S_AXIS_tvalid <= 1'b1;
                        S_AXIS_tlast <= 1'b1;
                        S_AXIS_tdata <= S_AXIS_tdata;
                        state <= 2;
                     end else begin
                        S_AXIS_tvalid <= 1'b0;
                        S_AXIS_tlast <= 1'b0;
                        S_AXIS_tdata <= 32'd0;
                        state <= 0;
                     end
                  end
               default: state <=0;
               endcase
           end              
       end  
    
    endmodule

    DMA数据流传输部分仿真数据图:

    2.1 LWIP141

    LWIP配置

    phy_link_speed中不建议使用Autodetect模式,容易出现问题!建议根据自身的Zynq网口、网线、PC网口速率配置,本项目选择使用100M速率网络通信。

    2.2 PS端代码开发

    PS端上行传输代码功能:        1、完成所需设备初始化;

                                                     2、将PS端DDR3中数据上传至PC机

    PS端程序开发同时也适用于Zynq-7010、Zynq-7020等。

    工程目录表如下所示:

    每次启动函数:XAxiDma_SimpleTransfer()时,传输10000组ADC采集数据,分十次发送。

    tcp_transmission.c

    
    #include <stdio.h>
    #include <string.h>
    
    #include "lwip/err.h"
    #include "lwip/tcp.h"
    #include "lwipopts.h"
    #include "xaxidma.h"
    #include "xil_cache.h"
    #include "xil_printf.h"
    #include "sleep.h"
    
    
    #define SEND_SIZE (40000)
    #define PAKET_LENGTH (40048)
    
    static struct tcp_pcb *connected_pcb = NULL;
    
    volatile unsigned tcp_client_connected = 0;
    static int tcp_trans_done = 0;
    static unsigned first_trans_start = 0;
    
    static u32_t packet_index = 0;
    
    extern XAxiDma AxiDma;
    extern u16 *RxBufferPtr[4];
    extern u8_t packet_trans_done;
    
    void send_received_data()
    {
    #if __arm__
    	int copy = 1;
    #else
    	int copy = 0;
    #endif
    	err_t err;
    	int Status;
    	struct tcp_pcb *tpcb = connected_pcb;
    
    	/*initial the first axdma transmission, only excuse once*/
    	if(!first_trans_start)
    	{
    		Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)RxBufferPtr[packet_index],
    				(u32)(PAKET_LENGTH), XAXIDMA_DEVICE_TO_DMA);
    		if (Status != XST_SUCCESS)
    		{
    			xil_printf("axi dma failed! 0 %d\r\n", Status);
    			return;
    		}
    		/*set the flag, so this part of code will not excuse again*/
    		first_trans_start = 1;
    	}
    
    	/*if the last axidma transmission is done, the interrupt triggered, then start TCP transmission*/
    	if(packet_trans_done)
    	{
    		if (!connected_pcb)
    			return;
    
    		/* if tcp send buffer has enough space to hold the data we want to transmit from PL, then start tcp transmission*/
    		if (tcp_sndbuf(tpcb) > SEND_SIZE)
    		{
    			/*transmit received data through TCP*/
    			err = tcp_write(tpcb, RxBufferPtr[packet_index], SEND_SIZE, copy);
    			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;
    			}
    
    			packet_index++ ;
    
    //			/*clear the axidma done flag*/
    			packet_trans_done = 0;
    			first_trans_start = 0;
    
    			/*initial the other axidma transmission when the current transmission is done*/
    //			Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)RxBufferPtr[(packet_index + 1)&1],
    //						(u32)(PAKET_LENGTH), XAXIDMA_DEVICE_TO_DMA);
    //			if (Status != XST_SUCCESS)
    //			{
    //				xil_printf("axi dma %d failed! %d \r\n", (packet_index + 1), Status);
    //				return;
    //			}
    
    		}
    	}
    }
    
    
    /*this fuction just used to count the tcp transmission times*/
    static err_t
    tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len)
    {
    	err_t err;
    	tcp_trans_done = 1;
    	err = tcp_output(tpcb);
    	if (err != ERR_OK)
            {
    		xil_printf("txperf: Error on tcp_output: %d\r\n",err);
    		return -1;
    	}
    	return ERR_OK;
    }
    
    
    static err_t
    tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
    {
    	xil_printf("txperf: Connected to iperf server\r\n");
    
    	/* store state */
    	connected_pcb = tpcb;
    
    	/* set callback values & functions */
    	tcp_arg(tpcb, NULL);
    	tcp_sent(tpcb, tcp_sent_callback);
    	//tcp_recv(tpcb, tcp_recv_callback);
    	tcp_nagle_disable(tpcb);
    
    	if(!tcp_nagle_disabled(tpcb))
            {
    		xil_printf("tcp nagle disable failed!\r\n");
            }
    	tcp_client_connected = 1;
    
    	/* initiate data transfer */
    	return ERR_OK;
    }
    
    
    int tcp_send_init()
    {
    	struct tcp_pcb *pcb;
    	struct ip_addr ipaddr;
    	err_t err;
    	u16_t port;
    	port = 7;					/* iperf default port */
    
    	/* create new TCP PCB structure */
    	pcb = tcp_new();
    	if (!pcb) {
    		xil_printf("txperf: Error creating PCB. Out of Memory\r\n");
    		return -1;
    	}
    
    	/* connect to iperf tcp server */
    	IP4_ADDR(&ipaddr,  192, 168,   1, 209);		/* iperf server address */
    
        tcp_client_connected = 0;
        first_trans_start = 0;
        packet_trans_done = 0;
        packet_index = 0;
    
    	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;
    	}
    
    
    	return 0;
    }
    
    
    
    

    main函数:

    
    #include "dma_intr.h"
    #include "timer_intr.h"
    #include "sys_intr.h"
    #include "xgpio.h"
    
    #include "lwip/err.h"
    #include "lwip/tcp.h"
    #include "lwipopts.h"
    #include "netif/xadapter.h"
    #include "lwipopts.h"
    
    
    static  XScuGic Intc; //GIC
    static  XScuTimer Timer;//timer
    XAxiDma AxiDma;
    u16 *RxBufferPtr[10];  /* ping pong buffers*/
    
    volatile u32 RX_success;
    volatile u32 TX_success;
    
    volatile u32 RX_ready=1;
    volatile u32 TX_ready=1;
    
    #define TIMER_LOAD_VALUE    XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8 //0.5S
    #define AXIDMA_DEV_ID		XPAR_AXIDMA_0_DEVICE_ID
    
    extern void send_received_data(void);
    extern unsigned tcp_client_connected;
    
    
    static XGpio Gpio;
    
    #define AXI_GPIO_DEV_ID	        XPAR_AXI_GPIO_0_DEVICE_ID
    
    int init_intr_sys(void)
    {
    	DMA_Intr_Init(&AxiDma,AXIDMA_DEV_ID);//initial interrupt system
    	Timer_init(&Timer,TIMER_LOAD_VALUE,TIMER_DEVICE_ID);
    	Init_Intr_System(&Intc); // initial DMA interrupt system
    	Setup_Intr_Exception(&Intc);
    	DMA_Setup_Intr_System(&Intc,&AxiDma,0,RX_INTR_ID);//setup dma interrpt system
    	Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR);
    	DMA_Intr_Enable(&Intc,&AxiDma);
    }
    
    int main(void)
    {
    	int Status;
    	struct netif *netif, server_netif;
    	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 };
    
    	/* Initialize the ping pong buffers for the data received from axidma */
    	RxBufferPtr[0] = (u16 *)RX_BUFFER0_BASE;
    	RxBufferPtr[1] = (u16 *)RX_BUFFER1_BASE;
    	RxBufferPtr[2] = (u16 *)RX_BUFFER2_BASE;
    	RxBufferPtr[3] = (u16 *)RX_BUFFER3_BASE;
    	RxBufferPtr[4] = (u16 *)RX_BUFFER4_BASE;
    	RxBufferPtr[5] = (u16 *)RX_BUFFER5_BASE;
    	RxBufferPtr[6] = (u16 *)RX_BUFFER6_BASE;
    	RxBufferPtr[7] = (u16 *)RX_BUFFER7_BASE;
    	RxBufferPtr[8] = (u16 *)RX_BUFFER8_BASE;
    	RxBufferPtr[9] = (u16 *)RX_BUFFER9_BASE;
    
    	XGpio_Initialize(&Gpio, AXI_GPIO_DEV_ID);
    	XGpio_SetDataDirection(&Gpio, 1, 0);
    	init_intr_sys();
    	TcpTmrFlag = 0;
    
    	netif = &server_netif;
    
    	IP4_ADDR(&ipaddr,  192, 168,   1,  10);
    	IP4_ADDR(&netmask, 255, 255, 255,  0);
    	IP4_ADDR(&gw,      192, 168,   1,  1);
    
    	/*lwip library init*/
    	lwip_init();
    	/* Add network interface to the netif_list, and set it as default */
    	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);
    
    	/* specify that the network if is up */
    	netif_set_up(netif);
    
    	/* initialize tcp pcb */
    	tcp_send_init();
    
    	XGpio_DiscreteWrite(&Gpio, 1, 1);
    	//oled_fresh_en();// enable oled
    	Timer_start(&Timer);
    
    	while (1)
    	{
    		/* call tcp timer every 250ms */
    		if(TcpTmrFlag) {
    			tcp_tmr();
    			TcpTmrFlag = 0;
    		}
    		/*receive input packet from emac*/
    		xemacif_input(netif);
    		/* if connected to the server, start receive data from PL through axidma, then transmit the data to the PC software by TCP*/
    		if(tcp_client_connected){
    			send_received_data();
    		}
    		if(packet_trans_done) {
    			XGpio_DiscreteWrite(&Gpio, 1, 0);
    		}
    	}
    	return 0;
    }
    
    
    

    2.3 PC端网络配置

    关掉电脑网络防火墙!!!!

    下载PS程序(debug下载方式)

    打开网络调试助手(TCP Server;地址:192.168.1.209)

    3. 高速数字系统时钟设计-AD9516

    https://blog.csdn.net/m0_37779673/article/details/118459908

    4. 高速ADC--ADS62P49

    高速高精密ADC数据采集-ADS62P49:https://blog.csdn.net/m0_37779673/article/details/118460370

    5. 测试

    MATLAB读取TXT数据文件的频谱分析--FFT:

            https://blog.csdn.net/m0_37779673/article/details/119285146

    10MHz正弦波采集测试:

    50MHz正弦波采集测试:

    展开全文
  • zynqlwip 之tcp调试

    千次阅读 2020-06-02 14:26:45
    由于博主之前参考zynq 示例的时候有zynq2018 版本的sdk中,有一个tcp_perf的例程,而其在这个过程中采用的就是函数回调的方式。 例程 /*************************************************************************...
  • 官方自带的LWIP测试回环程序,实现的功能只是一个单纯的把接收到的数据原封不动的回传回PC机,实际工程运用自然是用不起来的,这里介绍一下如何修改成任意长度数据接收及发送: 首先就是基本的配置代码,这里不采用...
  • LWIP数据通路 实验准备: 基础概念 以太网MAC是一个基础模块,它使得我们可以实现一个TCP/IP协议栈,协议使得应用可以基于网络来通信。如果想要解析一个TCP/IP协议栈,需要了解以下层次: • 第一层——物理层...
  • zynq的sdk中添加了lwip和freertos库,在使用库API后编译报错如下: arm-none-eabi-gcc -Wall -O0 -g3 -c -fmessage-length=0 -MT"src/freertos_tcp_perf_server.o" -mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard ...
  • 博主今天在将lwIP以太网程序移植到RedPitaya(火龙果)开发板上时,发现了一个问题。 我们一般都会使用SDK自带的“lwIP Echo Server”例程测试以太网硬件是否正确。...《学会Zynq》系列第12篇详细介绍...
  • xilinx zynqlwip的官方例程解析

    万次阅读 2018-10-25 22:06:49
    init_platform()函数原型 在platform_zynq.c中 main函数 #if LWIP_DHCP==1 ipaddr.addr = 0; gw.addr = 0; netmask.addr = 0; #else /* initliaze IP addresses to be used */ IP4_ADDR(&ipaddr, 192,...
  • 1.用了米联客的核心板和例程"CH28 利用 LWIP 实现 ADC DAQ7606 数据采集",例程中的AD为16位200K的采样率,PS 的 dma 数据接收采用了乒乓操作的模式,两个缓冲区交替进行数据接收。现在用在100M的采集卡上,收到的数...
  • LWIP下UDP组播协议——zynq使用

    千次阅读 2018-02-07 22:22:43
    平台:zynq zc702 LWIP : 1.4.1是否有系统: 否一、udp基础知识:每一个UDP连接都对应一个UDP控制块,UDP协议的实现就是对这些控制块结构成员进行操作。为什么需要控制块链表?为了让协议栈可以实现多个连接,可以...
  • 使用官方的freertos+lwip的socket程序,一开始不接网线,程序打印如下信息 Start PHY autonegotiation Waiting for PHY to complete autonegotiation. 过一段时间后,程序继续打印如下信息 Auto negotiation error...
  • 在sdk中选择lwip模板,编译调试可轻松连接成功并进行通信,模板中代码完成的任务是client给server发什么,server就会回复什么。 但是传输速度非常低下,只有50KB左右,所以需要改进速度,修改lwip BSP中的设置参数...
  • 学会Zynq(13)lwIP官方应用程序示例

    千次阅读 2019-03-20 15:30:40
    XAPP1026中记录一些lwIP的应用程序示例和性能测试情况,不过提供的示例工程都是在几个Xilinx的官方板子中跑的。可能很多学生没有机会碰到这些板子。。。另外这份应用笔记使用的SDK 2014.3版本也比较老,那个版本lwip...
  • ebaz4205以太网裸机lwip echo以太网速度自适应原理。使用gmii_to_rgmii这个IP核时,lwip实验会自动调整时钟来配合phy的自协商速度。我自己写的文档,分享一下自适应原理。
  • ZYNQ7020 Lwip echo 测试

    千次阅读 2018-09-06 17:35:17
    在vivado中新建一个Block Design,添加一个ZYNQ7 PS (Processing System) 其配置只保留一个网口、一个串口,生成.bit 之后导入到SDK中。结果如图: 二、SDK工程 导入到SDK之后,新建一个Lwip echo的...
  • ZYNQ -Lwip和TCP/IP简介

    2021-08-02 19:54:23
    TCP/IP TCP/IP 通信协议是对计算机必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信。浏览器与服务器都在使用 TCP/IP 协议... references 正点原子嵌入式开发教程 学会Zynq(11)RAW API的TCP和UDP编程
  • Zynq 7000裸机的lwip 样例程序echo server 实验

    万次阅读 热门讨论 2017-10-12 00:15:14
    zynq 7000 裸机跑lwip 的echo server
  • #include "xil_printf.h" #include "sleep.h" #include "sleep.h" #include "sys_intr.h" #include "lwip/init.h" #include "lwip/inet.h" #include "xil_cache.h" //#include "ip_addr.h" #include "user_udp.h" #...
  • zynq lwip for micrel phy

    千次阅读 2018-04-20 11:03:39
    原文地址:...如下图,在SDK 中新建 project 时选用lwip 的例程即可。这里推荐zynq 学习视频,南京米联客论坛对我帮助很大,有视频有代码,提问题也会有工程师回...
  • 其它值: LwIP的一些错误代码标志,表示连接没有正确建立 5、udp_disconnect() 该函数关闭参数“pcb”指定的连接,同函数udp_connect()作用相反。由于UDP通信是面向无连接的,所以这个函数同样不会参数...
  • 学会Zynq(12)lwIP 1.4.1库的配置与使用

    万次阅读 多人点赞 2019-03-20 15:17:25
    lwIP概述 lwIP是一个用于嵌入式系统的开源TCP/IP协议集,是一套可以独立运行的栈,无需依赖操作系统,但也可以与操作系统同时使用。lwIP提供了两套API(术语为A05PI),供用户选择: RAW API:直接访问核心的...
  • zynq++88E1111的lwip

    2018-10-26 10:55:01
    zynq7000+88E1111的lwip,支持ps中两路mac分别挂接两路phy的应用,同时修复了88E1111初始化中的速率协商部分
  • ZYNQ LWIP pbuf out of memory

    2020-06-15 12:32:53
    ZYNQ LWIP pbuf out of memory新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右...
  • 02 ZYNQ LWIP TCP_SERVER 发送设置

    千次阅读 热门讨论 2017-02-27 21:59:49
    上一次的lwip 调通之后,用网络调试助手,将网路调试助手设为tcp_client 形式,通过网络助手向板子发送数据,板子会在接收到之后发回给网络助手。现在需要测出以太网的速度,思路是使板子一直向pc发送数据,根据30s...
  •  初始化过程的前半部分主要针对lwip的内存管理和各个协议层,在src/core/init.c中有一个lwip_init()函数已经为我们做好了,直接调用即可;  初始化过程的后半部分初始化网络接口,依次调用以下函数: netif_add...
  • 引言:本节我们继续使用Xilinx SDK自带的LwIP协议测试例程测试电路板千兆网接口,验证电路板PHY硬件设计是否正确。 1.实验系统框图 本实验系统框图如图1所示。图1中PHY在电路图上连接至PS侧BANK501 MIO接口,UART...
  • 继续学习ZYNQ,吃了好几年灰的ZYNQ 7020开发板一直没时间玩,现在稍微有点时间,准备空闲之余折腾一下,之前一直使用STM32等各式单片机,LINUX知识也知道一点,对HDL也是一窍不通,现在准备恶补一下,对于我来说,...

空空如也

空空如也

1 2 3 4 5 ... 19
收藏数 368
精华内容 147
关键字:

lwipzynq