zoukankan      html  css  js  c++  java
  • 利用ZYNQ SOC快速打开算法验证通路(6)——LWIP实现千兆TCP/IP网络传输

    一、前言

      之前ZYNQ与PC之间的网络连接依赖于外接硬件协议栈芯片,虽然C驱动非常简单,但网络带宽受限。现采用LWIP+PS端MAC控制器+PHY芯片的通用架构。关于LWIP库,已经有很多现成的资料和书籍。其有两套API,一个是SOCKET,另一个是本例中要用到的RAW。RAW API理解起来较为复杂,整个程序基于中断机制运行,通过函数指针完成多层回调函数的执行。SOCKET API需要支持多线程操作系统的支持,也牺牲了效率,但理解和编程都较为容易。实际上SOCKET API是对RAW API的进一步封装。

    二、LWIP Echo Server demo解读

      首先打开Xilinx SDK自带的LwIP Echo Server demo.

      1 /******************************************************************************
      2 *
      3 * Copyright (C) 2009 - 2014 Xilinx, Inc.  All rights reserved.
      4 *
      5 * Permission is hereby granted, free of charge, to any person obtaining a copy
      6 * of this software and associated documentation files (the "Software"), to deal
      7 * in the Software without restriction, including without limitation the rights
      8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      9 * copies of the Software, and to permit persons to whom the Software is
     10 * furnished to do so, subject to the following conditions:
     11 *
     12 * The above copyright notice and this permission notice shall be included in
     13 * all copies or substantial portions of the Software.
     14 *
     15 * Use of the Software is limited solely to applications:
     16 * (a) running on a Xilinx device, or
     17 * (b) that interact with a Xilinx device through a bus or interconnect.
     18 *
     19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     22 * XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
     24 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     25 * SOFTWARE.
     26 *
     27 * Except as contained in this notice, the name of the Xilinx shall not be used
     28 * in advertising or otherwise to promote the sale, use or other dealings in
     29 * this Software without prior written authorization from Xilinx.
     30 *
     31 ******************************************************************************/
     32 
     33 #include <stdio.h>
     34 
     35 #include "xparameters.h"
     36 
     37 #include "netif/xadapter.h"
     38 
     39 #include "platform.h"
     40 #include "platform_config.h"
     41 #if defined (__arm__) || defined(__aarch64__)
     42 #include "xil_printf.h"
     43 #endif
     44 
     45 #include "lwip/tcp.h"
     46 #include "xil_cache.h"
     47 
     48 #if LWIP_DHCP==1
     49 #include "lwip/dhcp.h"
     50 #endif
     51 
     52 /* defined by each RAW mode application */
     53 void print_app_header();
     54 int start_application();
     55 int transfer_data();
     56 void tcp_fasttmr(void);
     57 void tcp_slowtmr(void);
     58 
     59 /* missing declaration in lwIP */
     60 void lwip_init();
     61 
     62 #if LWIP_DHCP==1
     63 extern volatile int dhcp_timoutcntr;
     64 err_t dhcp_start(struct netif *netif);
     65 #endif
     66 
     67 extern volatile int TcpFastTmrFlag;
     68 extern volatile int TcpSlowTmrFlag;
     69 static struct netif server_netif;
     70 struct netif *echo_netif;
     71 
     72 void
     73 print_ip(char *msg, struct ip_addr *ip) 
     74 {
     75     print(msg);
     76     xil_printf("%d.%d.%d.%d
    
    ", ip4_addr1(ip), ip4_addr2(ip), 
     77             ip4_addr3(ip), ip4_addr4(ip));
     78 }
     79 
     80 void
     81 print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw)
     82 {
     83 
     84     print_ip("Board IP: ", ip);
     85     print_ip("Netmask : ", mask);
     86     print_ip("Gateway : ", gw);
     87 }
     88 
     89 #if defined (__arm__) && !defined (ARMR5)
     90 #if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1
     91 int ProgramSi5324(void);
     92 int ProgramSfpPhy(void);
     93 #endif
     94 #endif
     95 
     96 #ifdef XPS_BOARD_ZCU102
     97 #ifdef XPAR_XIICPS_0_DEVICE_ID
     98 int IicPhyReset(void);
     99 #endif
    100 #endif
    101 
    102 int main()
    103 {
    104     struct ip_addr ipaddr, netmask, gw;
    105 
    106     /* the mac address of the board. this should be unique per board */
    107     unsigned char mac_ethernet_address[] =
    108     { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
    109 
    110     echo_netif = &server_netif;
    111 #if defined (__arm__) && !defined (ARMR5)
    112 #if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1
    113     ProgramSi5324();
    114     ProgramSfpPhy();
    115 #endif
    116 #endif
    117 
    118 /* Define this board specific macro in order perform PHY reset on ZCU102 */
    119 #ifdef XPS_BOARD_ZCU102
    120     IicPhyReset();
    121 #endif
    122 
    123     init_platform();
    124 
    125 #if LWIP_DHCP==1
    126     ipaddr.addr = 0;
    127     gw.addr = 0;
    128     netmask.addr = 0;
    129 #else
    130     /* initliaze IP addresses to be used */
    131     IP4_ADDR(&ipaddr,  192, 168,   1, 10);
    132     IP4_ADDR(&netmask, 255, 255, 255,  0);
    133     IP4_ADDR(&gw,      192, 168,   1,  1);
    134 #endif    
    135     print_app_header();
    136 
    137     lwip_init();//网络参数初始化
    138 
    139       /* Add network interface to the netif_list, and set it as default */
    140     if (!xemac_add(echo_netif, &ipaddr, &netmask,
    141                         &gw, mac_ethernet_address,
    142                         PLATFORM_EMAC_BASEADDR)) {
    143         xil_printf("Error adding N/W interface
    
    ");
    144         return -1;
    145     }
    146     netif_set_default(echo_netif);
    147 
    148     /* now enable interrupts */
    149     platform_enable_interrupts();
    150 
    151     /* specify that the network if is up */
    152     netif_set_up(echo_netif);
    153 
    154 #if (LWIP_DHCP==1)
    155     /* Create a new DHCP client for this interface.
    156      * Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
    157      * the predefined regular intervals after starting the client.
    158      */
    159     dhcp_start(echo_netif);
    160     dhcp_timoutcntr = 24;
    161 
    162     while(((echo_netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0))
    163         xemacif_input(echo_netif);
    164 
    165     if (dhcp_timoutcntr <= 0) {
    166         if ((echo_netif->ip_addr.addr) == 0) {
    167             xil_printf("DHCP Timeout
    ");
    168             xil_printf("Configuring default IP of 192.168.1.10
    ");
    169             IP4_ADDR(&(echo_netif->ip_addr),  192, 168,   1, 10);
    170             IP4_ADDR(&(echo_netif->netmask), 255, 255, 255,  0);
    171             IP4_ADDR(&(echo_netif->gw),      192, 168,   1,  1);
    172         }
    173     }
    174 
    175     ipaddr.addr = echo_netif->ip_addr.addr;
    176     gw.addr = echo_netif->gw.addr;
    177     netmask.addr = echo_netif->netmask.addr;
    178 #endif
    179 
    180     print_ip_settings(&ipaddr, &netmask, &gw);//打印关键网络参数
    181 
    182     /* start the application (web server, rxtest, txtest, etc..) */
    183     start_application();//设置回调函数,这些函数在特定事件发生时以函数指针的方式被调用
    184 
    185     /* receive and process packets */
    186     while (1) {
    187         if (TcpFastTmrFlag) {//发送处理,如差错重传,通过定时器置位标志位
    188             tcp_fasttmr();
    189             TcpFastTmrFlag = 0;
    190         }
    191         if (TcpSlowTmrFlag) {
    192             tcp_slowtmr();
    193             TcpSlowTmrFlag = 0;
    194         }
    195         xemacif_input(echo_netif);//连续接收数据包,并将数据包存入LWIP
    196         transfer_data();//空函数
    197     }
    198   
    199     /* never reached */
    200     cleanup_platform();
    201 
    202     return 0;
    203 }
    echo

      整体流程为:初始化LWIP、添加网络接口(MAC)、使能中断、设置回调函数。最终进入主循环,内部不断检测定时器中断标志位,当标志位TcpFastTmrFlag或TcpSlowTmrFlag为1则调用相应的处理函数,完成超时重传等任务。接下来查看回调函数的设置:

    int start_application()
    {
        struct tcp_pcb *pcb;//protocol control block 简称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
    
    ");
            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
    
    ", 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
    
    ");
            return -3;
        }
    
        /* specify callback to use for incoming connections */
        tcp_accept(pcb, accept_callback);
    
        xil_printf("TCP echo server started @ port %d
    
    ", port);
    
        return 0;
    }
    start_application

      创建PCB(protocol control block)建立连接、绑定IP地址和端口号、监听请求,最后tcp_accept函数用于指定当监听到连接请求时调用的函数accept_callback。进入该函数内部查看:

     1 err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
     2 {
     3     static int connection = 1;
     4 
     5     /* set the receive callback for this connection */
     6     tcp_recv(newpcb, recv_callback);
     7 
     8     /* just use an integer number indicating the connection id as the
     9        callback argument */
    10     tcp_arg(newpcb, (void*)(UINTPTR)connection);
    11 
    12     /* increment for subsequent accepted connections */
    13     connection++;
    14 
    15     return ERR_OK;
    16 }
    accept_callback

      内部主要通过tcp_recv函数来指定当收到TCP包后调用的函数recv_callback。我们再次观察其内容:

     1 err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
     2                                struct pbuf *p, err_t err)
     3 {
     4     /* do not read the packet if we are not in ESTABLISHED state */
     5     if (!p) {
     6         tcp_close(tpcb);
     7         tcp_recv(tpcb, NULL);
     8         return ERR_OK;
     9     }
    10 
    11     /* indicate that the packet has been received */
    12     tcp_recved(tpcb, p->len);
    13 
    14     /* echo back the payload */
    15     /* in this case, we assume that the payload is < TCP_SND_BUF */
    16     if (tcp_sndbuf(tpcb) > p->len) {
    17         err = tcp_write(tpcb, p->payload, p->len, 1);
    18     } else
    19         xil_printf("no space in tcp_sndbuf
    
    ");
    20 
    21     /* free the received pbuf */
    22     pbuf_free(p);
    23 
    24     return ERR_OK;
    25 }
    recv_callback

      tcp_recved函数指示用来告知LWIP接收数据量,然后检测发送缓冲区是否足够容纳接收内容,若大于则调用tcp_write函数将接收数据写入发送缓冲区等待发送。综上,整体的调用流程为:tcp_accept -> accept_callback -> tcp_recv -> recv_callback -> tcp_recved和tcp_write。前四个用于接收,后两个用于发送。

      函数解析完毕,之后改动上位机网络参数,使PC机IP地址与Board在同一网段内,这里设置为192.168.1.11.打开网络调试助手,设置PC为TCP Client。以下是ZYNQ串口打印及网络调试结果。 

         

      

     

    三、TCP Client Send data

      现在我们来改动demo,设计一个客户端发送数据包的示例工程,功能是循环发送一个常数数组中数据到远程服务器。该工程参考米联客教程中相关章节内容。代码如下:

    /******************************************************************************
    *
    * Copyright (C) 2009 - 2014 Xilinx, Inc.  All rights reserved.
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy
    * of this software and associated documentation files (the "Software"), to deal
    * in the Software without restriction, including without limitation the rights
    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the Software is
    * furnished to do so, subject to the following conditions:
    *
    * The above copyright notice and this permission notice shall be included in
    * all copies or substantial portions of the Software.
    *
    * Use of the Software is limited solely to applications:
    * (a) running on a Xilinx device, or
    * (b) that interact with a Xilinx device through a bus or interconnect.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    * XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
    * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    * SOFTWARE.
    *
    * Except as contained in this notice, the name of the Xilinx shall not be used
    * in advertising or otherwise to promote the sale, use or other dealings in
    * this Software without prior written authorization from Xilinx.
    *
    ******************************************************************************/
    
    #include <stdio.h>
    
    #include "xparameters.h"
    
    #include "netif/xadapter.h"
    
    #include "platform.h"
    #include "platform_config.h"
    #if defined (__arm__) || defined(__aarch64__)
    #include "xil_printf.h"
    #endif
    
    #include "lwip/tcp.h"
    #include "xil_cache.h"
    
    #if LWIP_DHCP==1
    #include "lwip/dhcp.h"
    #endif
    
    /* defined by each RAW mode application */
    void print_app_header();
    int client_application();
    //int start_application();
    //int transfer_data();
    int send_data();
    void tcp_fasttmr(void);
    void tcp_slowtmr(void);
    
    /* missing declaration in lwIP */
    void lwip_init();
    
    #if LWIP_DHCP==1
    extern volatile int dhcp_timoutcntr;
    err_t dhcp_start(struct netif *netif);
    #endif
    
    extern volatile int TcpFastTmrFlag;
    extern volatile int TcpSlowTmrFlag;
    static struct netif server_netif;
    struct netif *echo_netif;
    
    void
    print_ip(char *msg, struct ip_addr *ip) 
    {
        print(msg);
        xil_printf("%d.%d.%d.%d
    
    ", ip4_addr1(ip), ip4_addr2(ip), 
                ip4_addr3(ip), ip4_addr4(ip));
    }
    
    void
    print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw)
    {
    
        print_ip("Board IP: ", ip);
        print_ip("Netmask : ", mask);
        print_ip("Gateway : ", gw);
    }
    
    
    int main()
    {
        uint cycle = 0;
        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 */
    
    
        init_platform();
    
        /* 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);
    
        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
    
    ");
            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);
    
        print_ip_settings(&ipaddr, &netmask, &gw);
    
        /* start the application (web server, rxtest, txtest, etc..) */
        //start_application();
        client_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();
            if(cycle == 9999){
                cycle = 0;
                send_data();
            }
            else
                cycle++;
        }
      
    
        return 0;
    }
    main

      函数定义:

      1 /*
      2  * tcp_trans.c
      3  *
      4  *  Created on: 2018年10月18日
      5  *      Author: s
      6  */
      7 
      8 
      9 #include <stdio.h>
     10 #include <string.h>
     11 
     12 #include "lwip/err.h"
     13 #include "lwip/tcp.h"
     14 #include "lwipopts.h"
     15 #include "xil_cache.h"
     16 #include "xil_printf.h"
     17 #include "sleep.h"
     18 
     19 #define TX_SIZE 10
     20 
     21 static struct tcp_pcb*connected_pcb = NULL;
     22 unsigned client_connected = 0;
     23 //静态全局函数 外部文件不可见
     24 uint tcp_trans_done = 0;
     25 
     26 u_char data[TX_SIZE] = {0,1,2,3,4,5,6,7,8,9};
     27 
     28 int send_data()
     29 {
     30     err_t err;
     31     struct tcp_pcb *tpcb = connected_pcb;
     32 
     33     if (!tpcb)
     34         return -1;
     35 
     36     //判断发送数据长度是否小于发送缓冲区剩余可用长度
     37     if (TX_SIZE < tcp_sndbuf(tpcb)) {
     38         //Write data for sending (but does not send it immediately).
     39         err = tcp_write(tpcb, data, TX_SIZE, 1);
     40         if (err != ERR_OK) {
     41             xil_printf("txperf: Error on tcp_write: %d
    ", err);
     42             connected_pcb = NULL;
     43             return -1;
     44         }
     45 
     46         //Find out what we can send and send it
     47         err = tcp_output(tpcb);
     48         if (err != ERR_OK) {
     49             xil_printf("txperf: Error on tcp_output: %d
    ",err);
     50             return -1;
     51         }
     52     }
     53     else
     54         xil_printf("no space in tcp_sndbuf
    
    ");
     55 
     56     return 0;
     57 }
     58 
     59 static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb,u16_t len)
     60 {
     61     tcp_trans_done ++;
     62     return ERR_OK;
     63 }
     64 
     65 //tcp连接回调函数 设置为静态函数,外部文件不可见
     66 static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
     67 {
     68     /* store state */
     69     connected_pcb = tpcb;
     70 
     71     /* set callback values & functions */
     72     tcp_arg(tpcb, NULL);
     73 
     74     //发送到远程主机后调用tcp_sent_callback
     75     tcp_sent(tpcb, tcp_sent_callback);
     76 
     77     client_connected = 1;
     78 
     79     /* initiate data transfer */
     80     return ERR_OK;
     81 }
     82 
     83 int client_application()
     84 {
     85     struct tcp_pcb *pcb;
     86     struct ip_addr ipaddr;
     87     err_t err;
     88     unsigned port = 7;
     89 
     90     /* create new TCP PCB structure */
     91     pcb = tcp_new();
     92     if (!pcb) {
     93         xil_printf("Error creating PCB. Out of Memory
    
    ");
     94         return -1;
     95     }
     96 
     97     /* connect to iperf tcp server */
     98     IP4_ADDR(&ipaddr,  192, 168,   1, 209);//设置要连接的主机的地址
     99 
    100     //当连接到主机时,调用tcp_connected_callback
    101     err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback);
    102     if (err != ERR_OK) {
    103         xil_printf("txperf: tcp_connect returned error: %d
    ", err);
    104         return err;
    105     }
    106 
    107     return 0;
    108 }
    tcp_trans

      可以看出还是一样的套路,在client_application函数中设置回调函数。首先新建PCB,tcp_connect函数设定要连接远程服务器的IP地址和端口号,连接建立时将调用回调函数tcp_connected_callback。tcp_connected_callback内部tcp_sent函数用于指定当发送数据包完成后执行的tcp_sent_callback。tcp_sent_callback内部只利用tcp_trans_done变量计数发送次数。而真正的发送处理任务则交给主循环中的send_data。若处于连接状态,且发送缓冲区容量比带发送数据量大,则调用tcp_write将待发送数据写入发送缓冲区,之后调用tcp_output函数立即传输发送缓冲区内容。如果不调用tcp_output,LWIP会等待数据量达到一定值时一起发送来提高效率,是否调用tcp_output函数可根据具体需求而定。

      接下来看下实验结果:

      PC端正确接收到常数数组,实验无误。

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    参考文献:

    1 LWIP 无OS RAW-API 函数 - 专注的力量 - CSDN博客 https://blog.csdn.net/liang890319/article/details/8574603

    解读TCP 四种定时器 - xiaofei0859的专栏 - CSDN博客 https://blog.csdn.net/xiaofei0859/article/details/52794576

    3 米联 《ZYNQ SOC修炼秘籍》

  • 相关阅读:
    【校招内推】2022届菜鸟网络Java内推
    Git版本管理流程与规范-生活圈
    Jenkins:Git拉取代码版本不对
    java html2image
    CentOS 7.6构建MySQL PXC高可用集群
    你可能不知道的Docker资源限制
    Docker镜像源修改
    centos7.2安装docker(二进制离线安装)
    ansible playbook 配置计划任务
    NFS服务的用户身份映射
  • 原文地址:https://www.cnblogs.com/moluoqishi/p/9865480.html
Copyright © 2011-2022 走看看