学会Zynq(12)lwIP 1.4.1库的配置与使用

lwIP概述
lwIP是一个用于嵌入式系统的开源TCP/IP协议集,是一套可以独立运行的栈,无需依赖操作系统,但也可以与操作系统同时使用。lwIP提供了两套API(术语为A05PI),供用户选择:

  • RAW API:直接访问核心的lwIP栈;
  • Socket API:通过BSD socket风格的接口访问lwIP栈。
  • 基于lwIP 1.4.1库版本,SDK提供了相应适配的库,称作lwip 141_v1_x。这个库为Ethernetlite、TEMAC、GigE、MAC核提供了适配器(adapter)。Ethernetlite和TEMAC核用于MicroBlaze系统;GigE控制器和MAC核用于Zynq。想在Xilinx FPGA环境下熟练使用lwIP,不仅要了解lwIP的API用法,还要掌握xilinx适配器的一些知识。

    Xilinx中lwIP的使用可以参考xapp1026和UG650;lwIP的开发者主页为 http://savannah.nongnu.org/projects/lwip/ ;查阅lwip的相关知识:https://lwip.fandom.com/wiki/LwIP_Wiki

    设置硬件系统
    lwIP支持的硬件系统包含的关键组件如下:

  • 处理器:MicroBlaze、Zynq中的Cortex-A9、Zynq UltraScale+ MPSoC系统中的Cortex-A53和Cortex-R5;
  • MAC:lwIP支持axi_ethernetlite、axi_ethernet、GigE控制器和MAC核;
  • 定时器:基于lwIP RAW API的应用需要按周期间隔调用某些函数,可通过一个带定时器的中断处理器来实现。
  • DMA:对于MicroBlaze,axi_ethernet核可以配置一个软DMA引擎或一个FIFO接口。对于Zynq,已经有嵌入的DMA,因此无需额外配置。
  • 上图是一个MicroBlaze系统架构的示例,使用了带DMA的axi_ethernet核。

    设置软件系统
    把Vivado的硬件平台导入到SDK中时,默认是不包含lwIP库的,因此必须先做相应配置,编译lwIP库到应用程序中。步骤如下:

    1.SDK中选择File->New->Xilinx Board Support Package,创建新的板级支持包。

    2.设置工程名称和目录。Zynq系列选择FreeRTOS或裸机;MicroBlaze还可以选择XilKernel。点击Finish,弹出配置窗口,配置BSP。

    3.选中lwip 141,窗口左侧会出现对lwip库做更详细配置的窗口。

    4.配置完成后点击OK,SDK会自动构建包含lwIP的板级支持包。

    lwIP的详细配置
    lwIP提供了可配置的参数,SDK中可以改变这些参数值。可配置的选项可以分为两类:

  • Xilinx适配器的相关选项:Xilinx适配器把这些控制设置用于以太网核;
  • 基本lwIP选项:这些选项是lwIP库本身的一部分,包括用于TCP、UDP、IP等其它协议的参数。/li>

    1.定制lwIP API模式
    lwip141_v1_x支持RAM API和Socket API。RAW API有更好的性能和更低的内存占用,但由于是基于回调机制的,因此不能与其它TCP 栈兼容;Socket API提供了一个BSD socket风格的接口,因此移植性很强,但在性能和内存需求方面没有RAW API效率高。

    2.配置Xilinx适配器选项
    axi_ethernetlite适配器(ethernetlite_adapter_options)相关的配置参数如下:

    axi_ethernet和GigE适配器(temac_adapter_options)的相关配置参数如下:

    3.配置内存选项
    lwIP栈提供了不同种类的内存。当应用程序使用socket模式时,将使用不同的内存选项。所有可配置的内存选项都作为单独的类别提供。

    4.pbuf_options
    包缓冲区(Pbuf)跨TCP/IP栈的不同层,下面是lwip栈提供的pbuf内存选项,一般情况下无需修改该选项的默认值。

    5.arp_options
    一般情况下无需修改该选项的默认值。

    6.lwip_ip_options
    下表是下级菜单中的IP参数选项,一般情况下无需修改该选项的默认值。

    7.icmp_options
    该选项只可以设置ICMP的TTL值。Zynq中的GigE核不支持使用ICMP。

    8.igmp_options
    lwIP支持IGMP协议,该选项没有下级菜单,设置为true可以启用IGMP协议。

    9.udp_options
    lwIP支持UDP协议,一般情况下无需修改该选项的默认值。

    10.tcp_options
    lwIP支持TCP协议,一般情况下无需修改该选项的默认值。

    11.dhcp_options
    lwIP支持DHCP协议,一般情况下无需修改该选项的默认值。

    12.stats_options
    lwIP栈在设计上可以收集一些统计信息,比如使用的连接数、内存使用量、应用程序使用的信号量的数量。lwIP库提供了函数stats_display()来显示统计值。是否启用该功能在stats选项中设置。该选项下只有一个boolean类型的lwip_stats,默认为false。

    13.debug_options
    lwIP可以提供调试信息,debug_options下包括lwip_debug、ip_debug、tcp_debug、udp_debug、icmp_debug、igmp_debug、netif_debug、sys_debug、pbuf_debug几个选项,都是boolean类型,设置true/false来打开/关闭对应的调试功能。

    软件API
    lwIP库提供了两种不同的API:RAW mode和Socket mode。

    RAW API基于回调机制,应用程序与TCP栈之间可以直接访问。因此没有额外的socket层,RAW API有优秀的性能表现,但不能与其它TCP栈兼容。此外,Xilinx适配器还为接收数据包提供了xemacif_input程序函数。必须经常调用此函数,将接收到的数据包从中断处理程序移动到lwIP栈。根据接受包的类型,lwIP回调相应的程序。

    Socket API是一套BSD socket风格的API。该API提供了一个执行模型,它是一个阻塞的、“打开-读-写-关闭(open-read-write-close)”模型。使用Socket API和Xilinx适配器的应用程序需要生成一个单独的线程xemacif_input_thread。这个线程将接收到的数据包从中断处理程序移动到lwIP的tcpip_thread。必须使用lwIP的sys_thread_new API创建使用lwIP的应用程序线程。

    Xilinx适配器提供了一些辅助函数,以简化lwIP API的使用,各函数简要说明如下:

    1.lwip_init
    void lwip_init()

    这个函数为lwIP数据结构做了初始化,会替换对初始化状态、系统、内存、pbuf、ARP、IP、UDP、TCP的特定调用。

    2.xemac_add
    struct netif *xemac_add (struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip_addr *gw, unsigned char *mac_ethernet_address, unsigned mac_baseaddr)

    这个函数为添加任何Xilinx EMAC IP和GigE核提供了一个统一的接口。这个函数在lwIP的netif_add函数基础上封装的,用于初始化网络接口‘netif’,给定它的IP地址、网络掩码、网关的IP地址、6字节的以太网地址(MAC地址),以及axi_ethernetlite或axi_ethernet MAC核的基地址。

    3.xemacif_input
    void xemacif_input(struct netif *netif)

    该函数只在RAW模式下可用。Xlinx lwIP适配器在中断模式下工作。接收中断处理程序从EMAC/GigE中将包数据移动并存储在队列中。xemacif_input函数从队列中取出这些包,传递给lwIP。因此在RAW mode下,需要使用这个程序。下面是一个简单示例:
    while (1) {
    /* receive packets */
    xemacif_input(netif);

    /* do application specific processing */
    }

    该程序会通过回调通知已经接收到的数据。

    4.xemacif_input_thread
    void xemacif_input_thread(struct netif *netif)

    该函数只在Socket模式下可用。Socket模式中,应用程序必须启动一个单独的线程来接收输入包。这与RAW模式下的xemacif_input函数功能相同,只不过它驻留在独立的线程中。因此,任何lwIP socket模式下的应用程序都需要有类似如下的代码:
    sys_thread_new(“xemacif_input_thread”, xemacif_input_thread, netif,
    THREAD_STACK_SIZE, DEFAULT_THREAD_PRIO);

    然后,应用程序可以启动单独的线程来完成应用程序中特定的任务。xemacif_input_thread会接收中断处理程序处理的数据,将其传递给lwIP tcpip_thread。

    5.xemacpsif_resetrx_on_no_rxdata
    void xemacpsif_resetrx_on_no_rxdata(struct netif *netif)

    该函数在Raw模式和Socket模式下都可用,但只能用于Zynq系列的GigE控制器。GigE控制器上有一个与Rx路径有关的勘误表(errata)。该勘误表描述了当小数据包的Rx流量过大时,GigE的Rx路径完全没有响应的情况。这种情况很少发生,但发生时需要对控制器中的Rx逻辑进行软件重置。用户的应用程序必须周期性地调用这个函数,以确保Rx路径不会再超过100ms的时间内停止响应。

    RAW API程序架构
    使用RAW API的应用程序是单线程的,一般具有与下面伪代码类似的主架构:
    int main()
    {
    struct netif *netif, server_netif;
    struct ip_addr ipaddr, netmask, gw;
    //板子的MAC地址,每个PHY都不同
    unsigned char mac_ethernet_address[] =
    {0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};

    lwip_init();

    //把网络接口添加到netif_list, 并设为默认
    if (!xemac_add(netif, &ipaddr, &netmask,
    &gw, mac_ethernet_address, EMAC_BASEADDR)) {
    printf(“Error adding N/W interface\n\r”);
    return -1;
    }
    netif_set_default(netif);

    platform_enable_interrupts(); //使能中断
    netif_set_up(netif); //指定网络是否打开
    start_application(); //启动应用程序,设置回调

    //接收并处理包
    while (1) {
    xemacif_input(netif);
    transfer_data(); //执行应用程序的特定功能
    }
    }

    RAW API主要通过异步调用发送和接收的回调函数来工作。

    Socket API程序架构
    Socket模式下,基于Xilkernel的应用程序可以在Xilkernel软件平台的设置对话框中指定一个静态线程列表,这些线程会在Xilkernel启动时生成。假设main_thread()是一个设定的由Xilkernel启动的线程,在启动Xilkernel调度后,控制权会从应用程序中的“main”转移到这个线程。在main线程中,再创建一个线程(network_thread)来初始化MAC层。

    对于基于FreeRTOS(Zynq-7000处理器系统)的应用程序,一旦控制权到达“main”,就会在启动调度程序之前创建一个带有main_thread()入口函数的任务。在FreeRTOS调度程序启动之后,控制权到达main_thread(),在这里进行lwIP的初始化。然后,应用程序再创建一个线程(network_thread)来初始化MAC层。

    下面的伪代码展示了一个典型的Socket模式下的程序架构:
    void network_thread(void *p)
    {
    struct netif *netif, server_netif;
    struct ip_addr ipaddr, netmask, gw;
    //板子的MAC地址,每个PHY都不同
    unsigned char mac_ethernet_address[] =
    {0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};
    netif = &server_netif;

    //初始化使用的IP地址
    IP4_ADDR(&ipaddr,192,168,1,10);
    IP4_ADDR(&netmask,255,255,255,0);
    IP4_ADDR(&gw,192,168,1,1);
    //把网络接口添加到netif_list, 并设为默认
    if (!xemac_add(netif, &ipaddr, &netmask,
    &gw, mac_ethernet_address, EMAC_BASEADDR)) {
    printf(“Error adding N/W interface\n\r”);
    return;
    }
    netif_set_default(netif);

    netif_set_up(netif); //指定网络是否打开
    //启动包接收线程
    sys_thread_new(“xemacif_input_thread”, xemacif_input_thread,
    netif, THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
    //启动应用程序线程
    sys_thread_new(“httpd” web_application_thread, 0,
    THREAD_STACKSIZE DEFAULT_THREAD_PRIO);
    }

    int main_thread()
    {
    //调用sys_thread_new前初始化lwIP
    lwip_init();
    //使用lwIP的所有线程都要用sys_thread_new()创建
    sys_thread_new(“network_thread” network_thread,
    NULL, THREAD_STACKSIZE DEFAULT_THREAD_PRIO);
    return 0;
    }

    ---------------------


    文章来源:FPGADesigner的博客
    *本文由作者授权转发,如需转载请联系作者本人

  • 推荐阅读