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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FPGADesigner/article/details/88689771

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等其它协议的参数。

1.定制lwIP API模式

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

选项名称 说明 默认值
api_mode{RAW_API|SOCKET_API} lwIP库的操作模式 RAW_API
socket_mode_thread_prio lwIP线程的优先级,仅当使用Xilkernel的优先级模式时才有效 1
use_axieth_on_zynq 如果要在Zynq中使用AxiEthernet软核IP,需要设置此选项来关闭MAC核(GigE) 0表示使用GigE控制器;1表示使用AxiEthernet

2.配置Xilinx适配器选项

axi_ethernetlite适配器(ethernetlite_adapter_options)相关的配置参数如下:

属性 说明 默认值
sw_rx_fifo_size 软件缓冲区大小,以EMAC和处理器之间接收数据的字节为单位 8192
sw_tx_fifo_size 软件缓冲区大小,以处理器和EMAC之间发送数据的字节为单位 8192

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

属性 默认值 说明
n_tx_descriptors 64 使用Tx描述符的数量,高性能系统增大此值
n_rx_descriptors 64 使用Rx描述符的数量,高性能系统增大此值
n_tx_coalesce 1 设置Tx中断合并
n_rx_coalesce 1 设置Rx中断合并
tcp_rx_checksum_offload false 卸载(offload)TCP接收校验和计算
tcp_tx_checksum_offload false 卸载(offload)TCP发送校验和计算
tcp_ip_rx_checksum_ofload false 卸载(offload)TCP和IP接收校验和计算
tcp_ip_tx_checksum_ofload false 卸载(offload)TCP和IP发送校验和计算
phy_link_speed AUTO 由物理层自动协商链路速度,lwIP据此配置TEMAC/GigE,某些PHY可能不支持自动检测,此时这个值必须设置正确
temac_use_jumbo_frames_experimental false 使用TEMAC巨型帧(可达9k字节)。设置为true,TEMAC将允许传输和接收巨型帧。

3.配置内存选项

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

属性 默认值 说明
mem _size 131072 可用堆内存的总大小(字节),如果应用程序需要从堆中使用大量内存应考虑增大此值
memp_n_pbuf 16 memp结构pbuf的数量,如果应用程序需要从ROM等静态内存中发送大量数据,考虑增大此值
memp_n_udp_pcb 4 UDP协议控制块的数量,每个活跃的UDP连接占用一个控制块
memp_n_tcp_pcb 32 同上,同时活跃的TCP连接数
memp_n_tcp_pcb_listen 8 监听TCP连接的数量
memp_n_tcp_seg 256 同时排队的TCP段的数量
memp_n_sys_timeout 8 同时活跃的超时(timeout)数量
memp_num_netbuf 8 netbufs类型的结构体实例的数量,仅在socket模式下可用
memp_num_netconn 16 netconns类型的结构体实例的数量,仅在socket模式下可用
memp_num_api_msg 16 api_msg类型的结构体实例的数量,仅在socket模式下可用
memp_num_tcpip_msg 64 TCP/IP msg结构体的数量,仅在socket模式下可用

4.pbuf_options

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

属性 默认值 说明
pbuf_pool_size 256 pbuf池中的缓冲区数目,对于高性能系统,可以考虑设置更大的值
pbuf_pool_bufsize 1700 pbuf池中每个pbuf的大小,对于支持巨型帧的系统,要设置比巨型帧更大的值
pbuf_link_hlen 16 分配给链路级header的字节数

5.arp_options

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

属性 默认值 说明
arp_table_size 10 缓存的硬件地址IP地址对数目
arp_queueing 1 出站数据包在硬件地址解析期间会排队

6.lwip_ip_options

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

属性 默认值 说明
ip_forward 0 1表示启用跨网络接口转发IP数据包的功能在单个网络接口上运行lwIP时设置为0
ip_options 0 1表示允许使用IP选项0表示丢弃所有带IP选项的包
ip_reassembly 1 重新组装传入的碎片IP数据包
ip_flag 1 发送的IP数据包大小超过MTU时对其分段
ip_reass_max_pbufs 128 重组的pbuf队列长度
ip_frag_max_mtu 1500 任意接口的IP碎片缓冲区的的最大MTU
ip_default_ttl 255 传输层使用的全局默认TTL值

7.icmp_options

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

8.igmp_options

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

9.udp_options

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

属性 默认值 说明
lwip_udp true 设定是否需要UDP
udp_ttl 255 UDP TTL值

10.tcp_options

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

属性 默认值 说明
lwip_tcp true 设定是否需要TCP
tcp_ttl 255 TCP
tcp_wnd 2048 TCP窗口大小(字节)
tcp_maxrtx 12 TCP最大重传值
tcp_synmaxrtx 4 TCP最大SYN重传值
tcp_queue_ooseq 1 接收顺序错误的TCP队列段在内存不足的情况下可设为0
tcp_mss 1460 TCP最大段的大小
tcp_snd_buf 8192 TCP发送缓冲空间(字节)

11.dhcp_options

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

属性 默认值 说明
lwip_dhcp false 设定是否需要DHCP
Dhcp_does_arp_check false 设定ARP是否检查提供的地址

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

猜你喜欢

转载自blog.csdn.net/FPGADesigner/article/details/88689771
今日推荐