ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ZYNQ-7020裸机LWIP双网口实战:EMIO扩展GEM1从配置到调通

ZYNQ-7020裸机LWIP双网口实战:EMIO扩展GEM1从配置到调通 1. 为什么ZYNQ上第二个网口值得用EMIO来扩展手里这块ZYNQ-7020开发板跑裸机程序PS端自带的GEM0已经占用了MIO引脚接了一个PHY芯片LWIP跑得挺顺。但项目需求突然加了一条要双网口一个做调试通道一个做数据采集回传。这时候摆在面前的路其实就三条——换带双GEM的芯片、外挂SPI以太网控制器、或者用EMIO把GEM1引到PL端再出去。换芯片成本太高板子都画好了SPI以太网控制器带宽上不去百兆都跑不满更别说数据回传场景。所以EMIO扩展GEM1是性价比最高的方案。但这条路踩下去才知道坑比想象中多得多。Vivado里勾选一下EMIO导出到SDKLWIP初始化直接卡死或者ping不通或者能ping通但丢包严重。这些问题在官方文档里基本找不到完整答案得靠自己一点点啃。这篇文章就是把我从零开始用EMIO在PL端扩展第二个网口、并在裸机LWIP下跑通的完整过程拆开来讲。包括Vivado里怎么配、PL端引脚约束怎么写、时钟怎么给、LWIP的lwipopts.h和xemacps配置怎么改、PHY地址冲突怎么处理、MDIO总线怎么共享。适合正在用ZYNQ做裸机网络开发、被双网口配置卡住的同行参考。如果你用的是ZYNQ-7000系列SDK或Vitis裸机环境LWIP 1.4或2.x版本这篇内容基本可以照着复现。2. Vivado里GEM1的EMIO配置那些默认值不能信2.1 在ZYNQ Processing System里打开GEM1的正确姿势打开Vivado的Block Design双击ZYNQ7 Processing System进到Peripheral I/O Pins页面。这里你会看到GEM0已经占用了MIO引脚GEM1默认是灰色的。很多人第一反应是直接勾选GEM1然后发现它自动分配了MIO引脚但你的板子上GEM1的MIO引脚可能根本没引出或者被其他外设占了。正确做法是先勾选GEM1然后在下面的MIO Configuration里把GEM1的引脚分配从MIO改成EMIO。这一步在Vivado 2018.3之后的版本里界面略有不同但逻辑一样。改完之后你会看到GEM1的引脚从MIO列表里消失出现在EMIO列表里。这里有个细节GEM1的MDIO接口。GEM0和GEM1可以共享MDIO总线也可以各自独立。如果你的两个PHY挂在同一条MDIO总线上那GEM1的MDIO也要设成EMIO并且和GEM0的MDIO引脚在PL端做逻辑或。如果各自独立那就分别引出来。我建议共享MDIO省引脚而且LWIP里配置PHY地址时更清晰。注意GEM1的EMIO接口在Vivado里不会自动给你分配引脚号你需要在PL端的约束文件里手动指定。而且GEM1的EMIO信号名在综合后的网表里是一串很长的名字写约束的时候要小心。2.2 EMIO引脚约束别用错管脚标准GEM1通过EMIO引到PL端后你需要把这些信号分配到具体的PL引脚上。GEM1的EMIO接口包括GMII接口的收发数据、时钟、控制信号以及MDIO接口。具体信号列表如下信号名方向说明GEM1_GMII_TXD[7:0]输出发送数据GEM1_GMII_TXEN输出发送使能GEM1_GMII_TXCLK输出发送时钟GEM1_GMII_RXD[7:0]输入接收数据GEM1_GMII_RXDV输入接收数据有效GEM1_GMII_RXCLK输入接收时钟GEM1_GMII_RXER输入接收错误GEM1_GMII_COL输入冲突检测GEM1_GMII_CRS输入载波侦听GEM1_MDIO_MDC输出MDIO时钟GEM1_MDIO_MDIO双向MDIO数据在XDC约束文件里你需要把这些信号映射到PL的物理引脚。这里最容易踩的坑是管脚标准选错。GMII接口的时钟和数据线如果PHY芯片是3.3V电平那PL引脚要设成LVCMOS33如果是2.5V就设LVCMOS25。选错了要么不工作要么直接烧PHY。# GEM1 GMII接口约束示例 set_property PACKAGE_PIN Y14 [get_ports GEM1_GMII_TXCLK] set_property IOSTANDARD LVCMOS33 [get_ports GEM1_GMII_TXCLK] set_property PACKAGE_PIN Y15 [get_ports GEM1_GMII_TXD[0]] set_property IOSTANDARD LVCMOS33 [get_ports GEM1_GMII_TXD[0]] # ... 其他信号类似还有一个隐藏坑TXCLK的时钟约束。GEM1的GMII_TXCLK是从PS端输出的频率125MHz千兆或25MHz百兆。如果你在PL端还用这个时钟驱动其他逻辑需要在XDC里创建时钟约束否则时序报告会很难看。但如果你只是把信号直接引到PHY不经过PL逻辑那可以不加。2.3 时钟和复位PL端PHY的复位信号别漏了大多数PHY芯片需要一个复位信号通常低电平有效。这个复位信号你可以从PL端引出一个GPIO来控制也可以在PS端用MIO控制。我建议用PL端GPIO因为PHY在PL端复位走线短而且LWIP初始化时可以精确控制复位时序。在Vivado的Block Design里添加一个AXI GPIO IP配置成输出连接到PL引脚再接到PHY的复位脚。然后在SDK里LWIP初始化之前先拉低复位延时至少10ms再拉高再延时100ms等PHY内部稳定。这个时序很多PHY手册里写的是“至少10ms”但实测下来有些PHY比如Realtek RTL8211需要更长时间建议延时200ms以上。另外GEM1的参考时钟。ZYNQ的GEM1需要一个外部参考时钟通常是25MHz或125MHz从PL端输入。这个时钟在Vivado里要连接到ZYNQ Processing System的GEM1_REF_CLK引脚。如果你板子上没有单独的晶振给GEM1可以从GEM0的时钟分出来但要注意抖动要求。3. LWIP裸机下的双网口初始化xemacps和lwipopts的联动3.1 xemacps配置两个网口的基地址和中断号在SDK或Vitis里创建LWIP工程后默认只初始化一个网口。要加第二个网口核心是修改xemacps的配置。ZYNQ的GEM0和GEM1在PS端有固定的基地址GEM0: 0xE000B000GEM1: 0xE000C000中断号也不同GEM0是54GEM1是55具体值查UG585。在xparameters.h里Vivado会自动生成这些宏但如果你用的是老版本SDK可能只生成了GEM0的。检查xparameters.h里有没有XPAR_XEMACPS_1_BASEADDR和XPAR_XEMACPS_1_DEVICE_ID。LWIP的xemacpsif结构体里每个网口需要一个独立的xemacps实例。在lwip_init之后你需要手动调用xemacps_init两次分别传入GEM0和GEM1的配置。这里有个坑中断优先级。两个网口的中断如果优先级相同可能会互相打断导致LWIP的pbuf链表损坏。建议GEM0优先级设高一点比如0x20GEM1设低一点0x40。// 双网口初始化示例 struct xemacps emacps0, emacps1; XEmacPs_Config *cfg0 XEmacPs_LookupConfig(XPAR_XEMACPS_0_DEVICE_ID); XEmacPs_CfgInitialize(emacps0, cfg0, cfg0-BaseAddress); XEmacPs_SetHandler(emacps0, XEMACPS_HANDLER_DMASEND, ...); // 同样初始化emacps13.2 lwipopts.h里必须改的几个参数LWIP默认配置是单网口双网口需要改lwipopts.h里的几个关键参数参数默认值双网口建议值说明MEMP_NUM_NETIF12网络接口数量MEMP_NUM_PBUF1632pbuf数量双网口翻倍PBUF_POOL_SIZE1632pbuf池大小MEMP_NUM_TCP_PCB510TCP控制块LWIP_NETIF_HOSTNAME01方便区分两个网口还有一个隐藏参数LWIP_NETIF_STATUS_CALLBACK。双网口下如果两个网口的link状态变化频繁没有回调的话LWIP可能不知道哪个网口通了。建议打开这个选项在回调里更新netif的flags。注意MEMP_NUM_NETIF如果设成1第二个网口注册时会直接失败但错误信息很隐晦可能只是netif_add返回NULL。我当初就是卡在这里查了半天才发现是这个参数没改。3.3 PHY地址冲突MDIO总线上的“门牌号”两个PHY如果挂在同一条MDIO总线上必须有不同的PHY地址。PHY地址通常由PHY芯片的PHYAD引脚决定硬件上拉或下拉。如果你的板子设计时两个PHY的PHYAD都设成了同一个值比如都是0x01那MDIO总线就会冲突LWIP初始化时读到的PHY ID会是乱的。解决办法有两个一是硬件上改PHYAD电阻二是软件上分时复用MDIO。硬件改是最彻底的但板子已经打样了的话只能软件绕。软件绕的方法是在LWIP初始化时先只初始化GEM0读GEM0的PHY然后关闭GEM0的MDIO再初始化GEM1读GEM1的PHY。但这样两个网口不能同时工作只能分时。所以画板子的时候一定要确保两个PHY的PHYAD不同。我现在的板子就是吃了这个亏后来飞线改了一个电阻才搞定。4. 实测中的那些“玄学”问题从ping不通到丢包4.1 ping不通的第一反应先查link状态LWIP初始化完成后第一个测试通常是ping。如果ping不通先别急着改代码先看PHY的link状态。在LWIP里可以通过netif-flags的NETIF_FLAG_LINK_UP位判断。如果link没起来那问题在PHY硬件或MDIO配置。我遇到过一次PHY的link灯亮了但LWIP里读到的link状态是down。后来发现是MDIO的时钟频率太高。ZYNQ的MDIO时钟默认是分频自CPU时钟如果CPU跑667MHzMDIO分频系数设小了MDIO时钟可能超过2.5MHz有些PHY不支持这么高的MDIO时钟。解决办法是在xemacps配置里把MDIO时钟分频调大让MDIO时钟降到1MHz左右。// 调整MDIO时钟分频 XEmacPs_SetMdioClkDiv(emacps1, XEMACPS_MDIO_DIV_48);4.2 能ping通但丢包pbuf和中断的锅如果ping能通但丢包率很高比如50%以上通常是pbuf不够或者中断处理有问题。双网口下两个网口的中断可能同时触发如果中断处理函数里没有做好保护pbuf链表会被破坏。我的做法是在中断处理函数里先关中断处理完再开。另外把PBUF_POOL_SIZE从16加到64丢包率明显下降。还有一个细节GEM1的EMIO接口在PL端走线如果太长信号完整性不好也会导致丢包。这时候需要在Vivado里加时序约束或者降低GMII时钟频率从125MHz降到25MHz跑百兆。4.3 两个网口互相干扰中断优先级和内存对齐双网口同时跑数据时如果发现一个网口跑得快另一个就卡那多半是中断优先级问题。ZYNQ的中断控制器支持优先级抢占如果两个网口中断优先级相同高频率的那个会一直抢占低频率的得不到响应。解决办法给两个网口分配不同的中断优先级并且确保LWIP的tcpip_thread优先级高于网口中断。另外DMA描述符的内存要对齐到4字节边界否则DMA传输会出错。ZYNQ的GEM DMA对描述符地址有对齐要求xemacps的驱动里通常会处理但如果你自己分配内存要注意用memalign而不是malloc。5. 从Vivado到SDK的完整流程复盘5.1 导出硬件后的第一件事检查xparameters.hVivado导出硬件到SDK后第一件事是打开xparameters.h确认GEM1的宏定义都在。特别是XPAR_XEMACPS_1_DEVICE_ID、XPAR_XEMACPS_1_BASEADDR、XPAR_XEMACPS_1_ISR这几个。如果缺了说明Vivado里GEM1的EMIO配置没生效或者导出时没勾选。还有一个容易忽略的XPAR_XEMACPS_1_ISR的中断号。如果这个宏不存在LWIP初始化时注册中断会失败但错误信息可能只是“中断注册失败”不会告诉你具体原因。5.2 修改LWIP工程的Makefile和链接脚本双网口下LWIP的代码量会增加链接脚本里的堆栈大小要调整。默认的lscript.ld里堆可能只有几KB双网口下pbuf池就需要几十KB。建议把堆调到至少64KB栈调到8KB。另外Makefile里要确保xemacps的驱动文件被编译进去。有些LWIP例程只包含了GEM0的驱动GEM1的需要手动添加xemacps_g.c和xemacps_sinit.c。5.3 上板调试JTAG和串口的配合上板调试时JTAG和串口要配合使用。串口打印LWIP的初始化日志JTAG用来单步调试。如果LWIP初始化卡死先在xemacps_init里打断点看是卡在MDIO读PHY ID还是卡在DMA描述符分配。我遇到过一次卡在MDIO读PHY ID后来发现是PL端的MDIO引脚约束写错了MDIO_MDIO信号没接上拉电阻。MDIO总线需要上拉通常4.7kΩ到10kΩ。如果PL引脚内部上拉不够需要外部加。6. 几个让我熬夜的坑和最终解决方案6.1 EMIO信号在综合后被优化掉Vivado综合时如果EMIO信号在PL端没有负载综合器会把它优化掉。表现是约束文件里写了引脚但实现后引脚没信号。解决办法是在PL端加一个OBUF或ILBUF或者把信号引到一个空的ILA核。我一般加一个mark_debug属性既保留信号又方便调试。set_property MARK_DEBUG true [get_nets GEM1_GMII_TXCLK]6.2 LWIP的netif_add返回NULLnetif_add返回NULL最常见的原因是MEMP_NUM_NETIF不够或者netif结构体内存没分配。双网口下每个网口需要一个独立的struct netif不能共用。我见过有人把两个网口用同一个netif结构体结果第二个网口覆盖了第一个的配置。6.3 PHY的自动协商和强制模式有些PHY默认是自动协商模式如果对端不支持自动协商link就起不来。这时候需要在LWIP里强制设置PHY的模式。xemacps的驱动里提供了XEmacPs_SetPhyMode函数可以设成百兆全双工或千兆全双工。我一般先在自动协商下测试如果不通再强制百兆全双工排除协商问题。6.4 中断号在Vivado版本间的差异不同Vivado版本GEM1的中断号可能不同。2018.3里是552020.2里可能是56。所以不要硬编码中断号一定要用xparameters.h里的宏。我吃过这个亏代码在2018.3上跑得好好的换到2020.2就不行了查了一天才发现中断号变了。7. 双网口LWIP的进阶优化方向7.1 用DMA描述符环提升吞吐量ZYNQ的GEM支持DMA描述符环默认是单环。双网口下如果两个网口都跑高吞吐单环可能不够。可以在xemacps配置里把描述符数量从默认的64增加到128或256。但要注意描述符内存要连续而且对齐到4字节。7.2 中断合并减少CPU占用高吞吐场景下每个包都触发中断会让CPU很忙。ZYNQ的GEM支持中断合并可以设置收到N个包或超时后才触发中断。在xemacps里通过XEmacPs_SetOptions打开XEMACPS_RX_CHKSUM_OFFLOAD和XEMACPS_INTR_COALESCE。7.3 两个网口做链路聚合或冗余如果两个网口接同一个交换机可以做链路聚合提升带宽。但LWIP裸机下没有现成的链路聚合支持需要自己实现。更简单的做法是做冗余一个网口主用一个备用主网口link down时自动切到备用。这个在LWIP里可以通过netif_set_link_callback实现。8. 最后分享几个实测有效的小技巧第一个技巧在Vivado里给GEM1的EMIO信号加KEEP_HIERARCHY属性防止综合器跨层次优化。这个属性在调试EMIO问题时特别有用能保证信号在网表里可见。第二个技巧LWIP的tcpip_thread优先级要高于网口中断。在FreeRTOS下tcpip_thread的优先级设成configMAX_PRIORITIES-2网口中断设成configMAX_PRIORITIES-3。裸机下没有RTOS但中断优先级也要注意GEM0的中断优先级高于GEM1。第三个技巧如果ping不通但link灯亮先用MDIO工具读PHY的寄存器。ZYNQ的xemacps驱动里有XEmacPs_PhyRead函数可以读PHY的BMCR和BMSR寄存器看link状态和协商结果。我一般会写一个简单的测试函数上电后先读PHY ID确认MDIO通信正常再初始化LWIP。第四个技巧PL端的GMII接口走线如果超过5cm建议在Vivado里加输入延迟约束set_input_delay和输出延迟约束set_output_delay否则时序可能不满足。特别是RXCLK和RXD之间的 skew如果太大接收会出错。第五个技巧双网口下两个网口的MAC地址要不同。LWIP默认用同一个MAC地址如果两个网口接同一个网络MAC地址冲突会导致通信异常。在netif初始化时分别设置不同的MAC地址比如GEM0用00:0a:35:00:01:02GEM1用00:0a:35:00:01:03。这些经验都是我在实际项目中一点点试出来的有些是官方文档里不会写的有些是论坛里只言片语的。ZYNQ的EMIO双网口配置说难不难但细节太多一个地方没注意到就可能卡好几天。希望这篇内容能帮你少走点弯路。
返回列表