精华内容
下载资源
问答
  • 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使用经验—变态级(好文章)

    展开全文
  • 01 ZYNQ LWIP 学习

    千次阅读 2017-02-19 16:25:52
    如下图,在SDK 中新建 project 时选用lwip 的例程即可。这里推荐zynq 学习视频,南京米联客论坛对我帮助很大,有视频有代码,提问题也会有工程师回复 http://www.osrc.cn/home.php?mod=space&do=notice&view=mypost...

    ZYNQ 使用 VAVIDO 和 SDK 进行编程。其中 VAVIDO 中设置 很简单,加入ETH0 即可。如下图,在SDK 中新建 project 时选用lwip 的例程即可。这里推荐zynq 学习视频,南京米联客论坛对我帮助很大,有视频有代码,提问题也会有工程师回复 微笑http://www.osrc.cn/home.php?mod=space&do=notice&view=mypost

    这里因为我手里的板子是z-turn 板,以太网PHY 为 KSZ931,与代码默认的不同,尤其是在检查phy speed 时,寄存器编号不同,导致于查不到速度,会卡在这里,

    我在 xemacpsif_physpeed.c 中,复制函数get_Marvell_phy_speed()函数改为get_Micrel_phy_speed(),对以下语句进行修改


    XEmacPs_PhyRead(xemacpsp, phy_addr,31,
    &status_speed); // 读取寄存器17,改为31 IEEE_SPECIFIC_STATUS_REG
    if (!(status_speed & 0x01)) {  //link on 原来0x400,第10 位
    xil_printf("PHY Link stutus:not failing \r\n");
    temp_speed = status_speed & 0x70; // 读取最高两位速度status_speed & IEEE_SPEED_MASK


    if (temp_speed == 0x40)//IEEE_SPEED_1000
    return 1000;
    else if(temp_speed == 0x20)//IEEE_SPEED_100
    return 100;
    else
    return 10;
    }


    之后即可运行,在用电脑Ping之前千万注意,最后确认下电脑的 IP 地址。


    第一次写博客写的好乱啊,下次要整理成更加清晰的呢



    展开全文
  • zynq lwip for micrel phy

    千次阅读 2018-04-20 11:03:39
    原文地址:...如下图,在SDK 中新建 project 时选用lwip 的例程即可。这里推荐zynq 学习视频,南京米联客论坛对我帮助很大,有视频有代码,提问题也会有工程师回...

    原文地址:https://blog.csdn.net/yezizhangxinya/article/details/55805512

    ZYNQ 使用 VAVIDO 和 SDK 进行编程。其中 VAVIDO 中设置 很简单,加入ETH0 即可。如下图,在SDK 中新建 project 时选用lwip 的例程即可。这里推荐zynq 学习视频,南京米联客论坛对我帮助很大,有视频有代码,提问题也会有工程师回复 微笑http://www.osrc.cn/home.php?mod=space&do=notice&view=mypost

    这里因为我手里的板子是z-turn 板,以太网PHY 为 KSZ931,与代码默认的不同,尤其是在检查phy speed 时,寄存器编号不同,导致于查不到速度,会卡在这里,

    我在 xemacpsif_physpeed.c 中,复制函数get_Marvell_phy_speed()函数改为get_Micrel_phy_speed(),对以下语句进行修改


    XEmacPs_PhyRead(xemacpsp, phy_addr,31,
    &status_speed); // 读取寄存器17,改为31 IEEE_SPECIFIC_STATUS_REG
    if (!(status_speed & 0x01)) {  //link on 原来0x400,第10 位
    xil_printf("PHY Link stutus:not failing \r\n");
    temp_speed = status_speed & 0x70; // 读取最高两位速度status_speed & IEEE_SPEED_MASK


    if (temp_speed == 0x40)//IEEE_SPEED_1000
    return 1000;
    else if(temp_speed == 0x20)//IEEE_SPEED_100
    return 100;
    else
    return 10;
    }


    之后即可运行,在用电脑Ping之前千万注意,最后确认下电脑的 IP 地址。


    展开全文
  • ZYNQ LWIP pbuf out of memory

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

    ZYNQ LWIP pbuf out of memory

    记录错误

    在使用AC7020教程中,当板子作为lwip server每隔1s发送“hello world”的时候出现了错误提示: pbuf out of memory. 在XILINX论坛上找到了看似有效的解决办法,在工程相对应的BSP中选择modify,
    在这里插入图片描述
    修改lwip栈的内存大小以及pbuf的size。但是重新run之后错误还在,参考https://blog.csdn.net/fpgadesigner/article/details/88776615之后问题解决了。因为同样适用了延时函数,xemacif_input(netif)函数并没有被及时调用,IP协议栈中的数据没有被及时发送,造成了溢出错误。

    展开全文
  • ZYNQ7020 Lwip echo 测试

    千次阅读 2018-09-06 17:35:17
    在vivado中新建一个Block Design,添加一个ZYNQ7 PS (Processing System) 其配置只保留一个网口、一个串口,生成.bit 之后导入到SDK中。结果如图: 二、SDK工程 导入到SDK之后,新建一个Lwip echo的...
  • 首先力推此人写的一系列关于lwip的博客:...需要注意的是,使用此人的博客在lwip211 1.0里面,需要将变量 struct ip_addr 改成 ip_addr_t 。 我的代码如下: main.c #include "user_udp.h" int main(void) { ...
  • ZynqLWIP裸奔应用

    千次阅读 2016-08-13 20:25:25
    最近,模仿zedboard做了一个Zynq的ARM+FPGA开发平台。 在Vivado上生成硬件bit后,就使用SDK开发软件了,直接使用LWIP示例。 可以和电脑连上,电脑显示未知网络,可以用CMD ping成功,但有50%的丢包率。 使用TCP/...
  • 学会Zynq(10)lwIP简介

    千次阅读 多人点赞 2019-03-20 14:30:50
    从本篇开始,将花大量篇幅介绍Zynq在裸机环境下以太网的使用。裸机时最方便的就是使用SDK已经集成了的lwIP 1.4.1库,我们将先了解lwIP的相关知识,然后再以实例的方式学习TCP、UDP的程序设计方法。 研究背景 在过去...
  • DMA也是zynq中PS与PL通信的一个重要内容,主要的作用是将PS的内存数据搬运到PL,或者将PL的数据搬运到PS内存,简单的讲就是搬运工。使用xilinx提供的IP核,可以不用非常了解AXI4的时序,只要简单了解一下AXI-stream...
  • Zynq 7000裸机的lwip 样例程序echo server 实验

    万次阅读 热门讨论 2017-10-12 00:15:14
    zynq 7000 裸机跑lwip 的echo server
  • 引言:本节我们继续使用Xilinx SDK自带的LwIP协议测试例程测试电路板千兆网接口,验证电路板PHY硬件设计是否正确。1.实验系统框图本实验系统框图如图1所示。图1中PHY在电路图上连接至PS侧BANK501 MIO接口,UART接口...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 148
精华内容 59
关键字:

lwipzynq