Raspberry Pi Pico+ENC28J60+lwIP跨路由TCP无法连接求助
排查Raspberry Pi Pico+ENC28J60跨路由器TCP连接失败问题
环境说明
- 硬件:Raspberry Pi Pico 通过SPI接口连接ENC28J60网卡
- 软件:2023-11-29版本pico-sdk、同日获取的pico-enc28j60库;因lwIP从Pico Extras迁移至Pico SDK,已修改项目CMakeLists.txt
初始化代码
static bool ethernet_init() { err_t err; lwip_init(); queue_init( &R.rx_queue, sizeof(struct pbuf *), ENC28J60_RX_QUEUE_SIZE ); critical_section_init( &R.spi_cs ); // initialize modbus clients for( int i=0; i<MODBUS_CLIENTS_MAX; i++ ) { R.modbus_clients[i].tcp = 0; R.modbus_clients[i].state = ST_UNUSED; R.modbus_clients[i].seq_nr = (i << 12) | ((i << 8) ^ 0x0F00); } R.enc28j60.spi = spi1; R.enc28j60.cs_pin = SPI1_CSn_PIN; R.enc28j60.mac_address[0] = R.cfg.mac_addr[0]; R.enc28j60.mac_address[1] = R.cfg.mac_addr[1]; R.enc28j60.mac_address[2] = R.cfg.mac_addr[2]; R.enc28j60.mac_address[3] = R.cfg.mac_addr[3]; R.enc28j60.mac_address[4] = R.cfg.mac_addr[4]; R.enc28j60.mac_address[5] = R.cfg.mac_addr[5]; R.enc28j60.next_packet = 0; R.enc28j60.critical_section = &R.spi_cs; R.netif.name[0] = 'e'; R.netif.name[1] = 'n'; gpio_set_function( SPI1_SCK_PIN, GPIO_FUNC_SPI ); gpio_set_function( SPI1_MOSI_PIN, GPIO_FUNC_SPI ); gpio_set_function( SPI1_MISO_PIN, GPIO_FUNC_SPI ); gpio_init( SPI1_CSn_PIN ); gpio_put( SPI1_CSn_PIN, true ); gpio_set_dir( SPI1_CSn_PIN, true ); gpio_init( ETH_INT_PIN ); gpio_init( ETH_RST_PIN ); // reset the chip gpio_put( ETH_RST_PIN, false ); gpio_set_dir( ETH_RST_PIN, true ); sleep_ms( 1 ); gpio_put( ETH_RST_PIN, true ); spi_init( R.enc28j60.spi, SPI1_SPEED ); netif_add( &R.netif, &R.cfg.ip_addr, &R.cfg.netmask, &R.cfg.gateway, &R.enc28j60, ethernetif_init, netif_input ); netif_set_up( &R.netif ); netif_set_link_up( &R.netif ); // +++ should check the enc if the link is up gpio_set_irq_enabled_with_callback( ETH_INT_PIN, GPIO_IRQ_EDGE_FALL, true, eth_irq ); enc28j60_interrupts( &R.enc28j60, ENC28J60_PKTIE | ENC28J60_TXERIE | ENC28J60_RXERIE ); R.tcp_modbus = tcp_new_ip_type( IPADDR_TYPE_V4 ); err = tcp_bind( R.tcp_modbus, IP4_ADDR_ANY, TCP_PORT_MODBUS ); if( err != ERR_OK ) { return false; } R.tcp_modbus = tcp_listen_with_backlog( R.tcp_modbus, BACKLOG_MODBUS ); if( !R.tcp_modbus ) { return false; } tcp_accept( R.tcp_modbus, modbus_accept ); return true; }
问题描述
本地直连电脑与Pico时,网络功能完全正常:可ping通设备,能正常建立TCP连接并进行Modbus查询。但在电脑与Pico之间加入路由器后,出现异常:
- ping仍能正常通
- TCP无法建立连接:抓包可见电脑发送的SYN包已到达Pico所在网络,但Pico无SYN/ACK响应,
modbus_accept回调函数从未被调用
怀疑问题出在自身代码或lwIP配置中,求排查线索。
排查线索
1. lwIP核心配置检查
- 确认
lwipopts.h中TCP_MSS设置:路由器环境下MTU可能存在限制,建议将TCP_MSS设为1460(标准以太网MTU 1500减去IP+TCP头部40字节),避免因MSS过大导致SYN/ACK被丢弃。同时检查IP_FRAG是否开启,确保分片包能被正确处理。 - 检查TCP超时与队列参数:确认
TCP_SYNACK_DELAY、TCP_LISTEN_BACKLOG是否合理,BACKLOG_MODBUS值需与lwIP配置的TCP_LISTEN_BACKLOG匹配,防止连接队列溢出。
2. 网络链路与ARP缓存验证
- 替换硬编码的
netif_set_link_up:当前代码直接标记链路为up,需改为读取ENC28J60的PHY状态(调用enc28j60_phy_read读取PHCON1寄存器),确认物理链路真的连通后再设置链路up。 - 检查ARP缓存有效性:在路由器环境下,Pico可能未正确获取电脑或路由器的MAC地址。添加ARP缓存调试输出,确认目标IP对应的MAC是否存在,必要时手动触发ARP请求(比如提前ping电脑)。
3. 中断与数据包接收逻辑排查
- 验证ETH_INT中断触发:在
eth_irq回调中添加调试动作(如LED闪烁),确认SYN包到达时中断是否被触发,排除中断电平配置错误或硬件干扰导致的中断丢失。 - 检查RX队列状态:若
ENC28J60_RX_QUEUE_SIZE过小,路由器环境下的广播包、ARP包可能占满队列,导致SYN包无法被处理。增大队列大小,并添加队列溢出检测逻辑。 - 确认
ethernetif_input函数正确性:检查该函数是否将接收到的pbuf完整传递给netif_input,是否存在丢包或错误处理逻辑导致SYN包被丢弃。
4. TCP监听逻辑验证
- 尝试绑定具体IP而非
IP4_ADDR_ANY:若Pico存在多IP配置(如静态IP+DHCP),IP4_ADDR_ANY可能未绑定到路由器分配的有效IP上,改为绑定Pico的实际局域网IP再测试。 - 检查TCP监听PCB的正确性:代码中
tcp_listen_with_backlog会返回新的PCB并覆盖R.tcp_modbus,需确认新PCB未被错误释放,且tcp_accept回调已正确注册到该PCB上。
5. 抓包与调试输出
- 在路由器LAN端口抓包:确认SYN包是否真的转发到Pico,以及Pico是否发出SYN/ACK(可能被路由器ACL规则过滤)。
- 开启lwIP调试:打开
LWIP_DEBUG、TCP_DEBUG等宏,输出TCP层状态变化,确认SYN包是否被lwIP接收,以及是否尝试生成SYN/ACK响应。
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

