ZYNQ裸机双网口实战:手把手教你用SDK和LWIP配置TCP服务器(附完整源码)
ZYNQ裸机双网口实战从硬件配置到TCP服务器搭建全解析在嵌入式系统开发中ZYNQ系列芯片因其独特的PSPL架构而备受青睐。本文将深入探讨如何在ZYNQ平台上实现裸机环境下的双网口TCP服务器功能涵盖从硬件配置到软件实现的完整流程。不同于常见的单网口方案双网口设计能够满足更复杂的网络通信需求如数据分流、冗余备份或多协议并行处理。1. 硬件环境准备与基础配置1.1 ZYNQ硬件平台选择与配置ZYNQ-7000系列芯片的PS部分集成了双千兆以太网控制器这为我们的双网口实现提供了硬件基础。在Vivado中创建工程时需要特别注意以下配置项使能两个EMAC控制器通常位于PS7配置中的IO Configuration选项卡为每个EMAC分配正确的引脚MIO或EMIO配置适当的时钟和复位信号典型的MIO引脚分配如下表所示信号名称ETH0引脚ETH1引脚备注MDIOMIO52MIO28管理数据IOMDCMIO53MIO29管理数据时钟RGMII_RX_CTRLMIO50MIO26接收控制RGMII_RXD[3:0]MIO49:MIO46MIO25:MIO22接收数据RGMII_TX_CTRLMIO45MIO21发送控制RGMII_TXD[3:0]MIO44:MIO41MIO20:MIO17发送数据1.2 LWIP库的配置与修改在SDK中创建BSP时需要正确配置LWIP库以支持双网口功能。关键配置步骤如下在BSP设置中勾选lwip141库修改lwip库的配置参数#define use_axieth_on_zynq 0 #define use_emaclite_on_zynq 0 #define use_gmii2rgmii_core_on_eth1 1 #define gmii2rgmii_core_address_on_eth1 8确保ETH0不使用EMIO接口#define use_gmii2rgmii_core_on_eth0 0注意如果配置选项中缺少上述参数可能需要先参考LWIP库文件修改的相关文档进行调整。2. 软件架构设计与核心模块实现2.1 主程序框架构建裸机程序的主循环需要高效处理网络通信任务同时兼顾定时器中断等系统功能。以下是典型的主程序结构#include platform_config.h #include service/timer/app_timer.h #define USING_ULOG #include ulog.h int lwip_tcp_init(void); int app_timer_init(void); int func_doing_always(void) { static timer_flag_t *_timer_flag g_timer_flag; #ifdef USING_ETHERNET_TCP _EXPORT_FUNC(tcp_func_always); #endif if(_timer_flag-timer_flag_250ms 1) { _timer_flag-timer_flag_250ms 0; #ifdef USING_ETHERNET_TCP _EXPORT_FUNC(tcp_func_per_250ms); #endif } if(_timer_flag-timer_flag_500ms) { _timer_flag-timer_flag_500ms 0; #ifdef USING_ETHERNET_TCP _EXPORT_FUNC(tcp_func_per_500ms); #endif } if(_timer_flag-timer_flag_1s 1) { _timer_flag-timer_flag_1s 0; #ifdef USING_ETHERNET_TCP _EXPORT_FUNC(tcp_func_per_1s); #endif } return 0; } int main(void) { ULOG(\r\n------ Main Function Running ------\r\n); app_timer_init(); #ifdef USING_ETHERNET_TCP _EXPORT_FUNC(lwip_tcp_init); #endif while(1) { func_doing_always(); } return 0; }2.2 双网卡初始化实现每个网卡的初始化过程需要独立配置包括MAC地址、IP地址、子网掩码和网关等参数。以下是ETH0初始化的核心代码static int ethernet0_init(void) { err_t err; struct netif *echo_netif0 server_netif0; struct ip_addr ipaddr0, netmask0, gw0; unsigned char mac_ethernet_address0[] { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; IP4_ADDR(ipaddr0, 192, 168, 6, 10); IP4_ADDR(netmask0, 255, 255, 255, 0); IP4_ADDR(gw0, 192, 168, 1, 1); if (!xemac_add(echo_netif0, ipaddr0, netmask0, gw0, mac_ethernet_address0, PLATFORM_EMAC_BASEADDR)) { kprintf(ENET0 Error adding N/W interface!\r\n); return -1; } netif_set_default(echo_netif0); netif_set_up(echo_netif0); tcp_node_t *tcp0_node g_tcp0_node; tcp0_node-local_addr.addr ipaddr0.addr; tcp0_node-local_port DEFAULT_PORT0; IP4_ADDR((tcp0_node-target_addr), 192,168,6,81); tcp0_node-remote_port 8080; tcp0_node-connected_flag 0; err start_tcp_application(tcp0_node); if(err ! ERR_OK) { kprintf(Start TCP0 application failed!\r\n); return -1; } else { tcp_node_printf(TCP0 Node, tcp0_node); } return 0; }ETH1的初始化过程类似但需要使用不同的MAC地址、IP地址和端口号配置。3. TCP服务器核心功能实现3.1 TCP连接管理与数据收发LWIP提供了完整的TCP协议栈实现我们需要实现几个关键回调函数来处理连接建立、数据接收等事件static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p_rx, err_t err) { if (!p_rx) { tcp_connection_close(tpcb); return ERR_OK; } tcp_recved(tpcb, p_rx-len); #ifdef USING_ETHERNET0 static tcp_msg_t *tcp0_msg g_tcp0_msg; if(tpcb-local_port DEFAULT_PORT0) { if(!tcp0_msg-rx_vflag) { tcp0_msg-rx_pdata tcp0_msg-rx_pdata_buf; memcpy(tcp0_msg-rx_pdata, (char *) p_rx-payload, p_rx-len); tcp0_msg-rx_plen p_rx-len; tcp0_msg-rx_vflag 1; } } #endif #ifdef USING_ETHERNET1 static tcp_msg_t *tcp1_msg g_tcp1_msg; if(tpcb-local_port DEFAULT_PORT1) { if(!tcp1_msg-rx_vflag) { tcp1_msg-rx_pdata tcp1_msg-rx_pdata_buf; memcpy(tcp1_msg-rx_pdata, (char *) p_rx-payload, p_rx-len); tcp1_msg-rx_plen p_rx-len; tcp1_msg-rx_vflag 1; } } #endif pbuf_free(p_rx); return ERR_OK; }3.2 定时器任务与状态维护在裸机环境中我们需要通过定时器来定期处理TCP协议栈的维护任务int tcp_func_per_250ms(void) { tcp_fasttmr(); return 0; } int tcp_func_per_500ms(void) { tcp_slowtmr(); return 0; } int tcp_func_per_1s(void) { #ifdef USING_TCP_STATUS tcp_check_connect_status(); #endif #ifdef USING_TCP_TEST_CMD tcp_test_cmd(); #endif return 0; } int tcp_func_always(void) { #ifdef USING_ETHERNET0 static struct netif *echo_netif0 server_netif0; xemacif_input(echo_netif0); #endif #ifdef USING_ETHERNET1 static struct netif *echo_netif1 server_netif1; xemacif_input(echo_netif1); #endif #ifdef USING_TCP_PRINT_MSG tcp_print_msg(); #endif return 0; }4. 系统测试与性能优化4.1 基础网络功能测试完成代码实现后需要进行全面的功能测试Ping测试确保两个网口都能响应ping请求ping 192.168.6.10 # ETH0 ping 192.168.6.20 # ETH1TCP连接测试使用网络调试工具连接两个端口ETH0: 192.168.6.10:7ETH1: 192.168.6.20:8数据吞吐测试测量两个网口同时工作时的数据传输性能4.2 性能优化技巧在实际应用中可能需要考虑以下优化措施内存管理优化调整LWIP内存池大小以适应具体应用需求中断处理优化合理配置EMAC中断优先级和处理流程数据缓冲策略根据应用场景选择合适的数据缓冲机制双网口负载均衡实现自定义的分流算法提高整体吞吐量4.3 常见问题排查在开发过程中可能会遇到以下典型问题网口无法连接检查PHY芯片的硬件连接验证MAC地址配置是否正确确认IP地址设置无误数据传输不稳定检查时钟信号质量调整LWIP缓冲区大小优化定时器处理频率双网口互相干扰确保两个网口使用不同的中断号检查内存访问冲突验证资源分配是否合理