
如果你手头有块STM32F407第一回用HAL库和W5500做TCP客户端通讯那我这篇分享应该能帮你少走不少弯路。W5500是一款集成TCP/IP协议栈的以太网控制芯片配上F407的硬件SPI大概一个下午就能把网络通讯跑通。这篇文章我会从方案选型、CubeMX配置、驱动移植、代码实现到问题排查完整走一遍最后给出可以直接抄作业的关键代码。这个组合最适合的场景是设备要上报数据到局域网服务器、采集终端做远程控制、或者你只是想学会“让单片机上网”这件事。它不需要你去啃lwIP协议栈也不需要为TCP状态机发愁W5500在芯片内部把TCP/IP处理完了MCU这边只负责SPI读写和业务逻辑。对新手非常友好对有经验的工程师来说也是快速出活的方案。1. 方案选型为什么是STM32F407 HAL库 W55001.1 三种联网方案怎么选最省力做嵌入式以太网绕不开方案选型。很多人在STM32F407上做过RMII接口的以太网比如外接LAN8720A、YT8512H或者DP83848这类PHY芯片配合lwIP协议栈跑。这套方案性能上限高能跑大流量但是配置过程相当考验人RMII的50MHz参考时钟、PHY寄存器初始化、lwIP的内存池配置稍微一个细节不对ping就是不通。还有一种常见方案是串口转以太网模块比如USR-TCP232这种MCU通过串口发AT指令就能联网。这种方案确实简单但是每个数据包都要过一遍串口协议解析实时性一般而且模块成本并不低很多应用还要额外处理AT指令的回调状态机。第三种就是W5500方案。W5500内部集成了MAC和PHY更重要的是它把TCP/IP协议栈用硬件实现了MCU只需要通过SPI接口读写数据不需要跑任何协议栈代码。对STM32F407这种本身就带多个硬件SPI的芯片来说简直是绝配。三种方案我整理了一张对比表方便你根据自己的项目情况选对比项MCU W5500MCU内置MAC 外部PHY lwIP串口转以太网模块开发难度低SPI读写即可高RMII布线PHY配置lwIP移植最低AT指令协议栈芯片硬件处理MCU软件处理模块内部处理资源占用很少RAM占用几十KB以内需要大量RAM和Flash跑lwIP很少吞吐量中等适合中小数据包高适合大数据量中等成本中较低中高适合项目数据上报、远程控制、设备状态采集视频流、高速文件传输快速原型、小数据量如果你的项目只是周期性上报数据、接收几条控制指令W5500这套方案的开发体验和稳定性都是最好的。1.2 HAL库、标准库、LL库到底该选谁有朋友会问为什么不直接用标准外设库或者干脆用LL库代码更精简。这个话题在嵌入式圈子里讨论过很多次我的建议很明确新项目、新工程师、或者项目周期紧的情况下直接用HAL库。标准库的好处是资料多网上老教程基本都是标准库写的寄存器级操作逻辑更透明。但它的缺点也很明显ST早就停止标准库的更新维护了F4系列还好如果你以后换到H7、G4系列标准库的代码基本推倒重来移植成本很高。HAL库的抽象层更厚执行效率确实不如直接抠寄存器但对我们做应用层的人来说这点性能损失完全感知不到。尤其配合STM32CubeMX图形化配置时钟、SPI、GPIO、中断生成工程后主要逻辑直接写在main.c和对应外设回调里出错的概率大幅降低。LL库则是另一个极端它的代码非常接近寄存器操作轻量、高效但开发效率低每个外设寄存器都要自己摸清。W5500的驱动本身已经是独立封装好的你并不需要深挖SPI寄存器的位定义所以用LL库纯属给自己增加工作量和踩坑机会。1.3 W5500参考电路与硬件检查清单W5500的硬件设计不算复杂但有几个地方容易踩坑。模块化的W5500小板子通常已经画好了参考电路直接四根SPI线加一根中断线一根复位线就能用。如果你是自己画板子参考官方数据手册里的电路即可但下面几个点务必检查第一W5500需要一颗25MHz的无源晶振晶振的两个负载电容一般用18pF到22pF具体看晶振规格。这颗晶振不起振是很多“W5500无响应”问题的根源。上电后建议用示波器或者频率计量一下晶振引脚有没有25MHz信号而不是只测VCC有没有3.3V。第二芯片供电是3.3V注意W5500的I/O口也是3.3V电平。很多开发板的W5500模块带了电平转换电路可以兼容5V的SPI引脚但如果是你自己画的最小系统千万别拿5V直接怼到W5500的SPI引脚上。第三RSTn复位引脚虽然是低电平有效但很多模块直接用一个电容做上电复位不接到MCU上。我的经验是RSTn一定要用GPIO控制这样可以在程序里随时做硬件复位遇到异常状态可以先拉低复位一下再重新初始化比软件复位可靠得多。第四INTn中断引脚建议接MCU的外部中断输入这能让你及时感知TCP连接断开、接收到数据等事件而不是在主循环里拼命轮询寄存器状态。我做过的项目里一次板子W5500反复连接不上查了半天最后发现是SPI的CS引脚虚焊另一次是杜邦线太长导致SPI时序毛刺太多把SPI时钟频率降低到1MHz左右就好了。硬件问题是最折磨人的所以强烈建议开工前过一遍检查清单检查清单25MHz晶振有波形、3.3V供电稳定、CS/MOSI/MISO/SCK/RSTn/INTn六根线连通且没有短接、模块地和MCU地共地、SPI空闲电平和W5500要求匹配。2. CubeMX配置与HAL库工程结构2.1 HAL库工程文件结构先理清用STM32CubeMX生成工程后很多人会被一堆代码目录搞晕尤其是第一次接触HAL库的人。我在这里把目录结构捋一下省的对着文件夹发呆。生成工程里的Core文件夹放着最关键的用户代码其中Src目录下是main.c、gpio.c、spi.c、usart.c这些外设初始化文件Inc目录里是对应的头文件。这些文件里用户代码被放在特定的“USER CODE BEGIN”和“USER CODE END”注释块之间下次用CubeMX重新生成代码的时候只有这两个注释块里的内容会被保留写在其他地方的内容会被覆盖。这是一个非常核心的规则很多人在这里吃过亏。Drivers文件夹是HAL库本体。CMSIS子目录放的是ARM内核相关的头文件和启动文件STM32F4xx_HAL_Driver目录下就是各种外设的驱动源码比如stm32f4xx_hal_spi.c、stm32f4xx_hal_gpio.c等。这些驱动文件一般不需要修改直接用API函数即可。还有个容易忽略的文件是stm32f4xx_hal_conf.h在Core/Inc目录下它相当于HAL库的“总开关”里面有一个模块宏列表把用不到的外设模块宏注释掉可以明显缩短编译时间。比如这个项目只用SPI、GPIO、UART、EXTI就可以把DMA、TIM、ADC等不需要的模块宏注释掉如果选择不注释也不影响功能就是编译时间稍长。2.2 CubeMX配置SPI、中断和GPIO的关键参数CubeMX版本在6.x左右的操作逻辑都差不多。创建新工程选好STM32F407VET6或者你手上对应的具体型号在Pinout Configuration视图下依次做以下配置。时钟方面RCC里选HSE外部晶振SYS里Debug选项选Serial Wire避免占用SWDIO/SWCLK引脚导致无法下载程序。Clock Configuration里配置系统主频168MHzAPB1总线频率42MHzAPB2总线频率84MHz。要注意的是SPI1挂载在APB2总线上SPI2和SPI3挂载在APB1上后面算分频的时候要用对总线频率。然后是SPI1的配置。Mode选Full-Duplex Master模式硬件NSSNSSP这个参数默认可能就是Disable保持Disable就好片选我们完全用软件GPIO控制。参数细节上数据帧大小Data Size选8位First Bit选MSB First这跟W5500的SPI时序是匹配的。预设分频器Prescaler建议第一次调试选64分频这样SPI时钟就是84MHz除以64约等于1.3MHz。W5500对SPI频率上限标称很高但实际项目里1MHz到10MHz都常见我习惯先用低速跑通再去提速。这个值选64最大的好处是信号完整性好杜邦线连接的情况下也基本不会误码。CPOL和CPHA的组合对应SPI Mode 0或者Mode 3W5500手册里这两个模式都支持但多数官方驱动和例程默认用Mode 0即CPOL为Low、CPHA为1 Edge。直接在CubeMX里保持默认的Low和1 Edge即可不要乱切换。GPIO分配上我习惯用PA4做CSPA5做RSTnPA6做INTn这些可以按自己板子调整只要不跟SPI1的SCKPA5、MISOPA6、MOSIPA7冲突即可。CS配置为GPIO Output初始电平为HighRSTn也是Output初始电平为HighINTn配置为External Interrupt with Falling edge trigger detection也就是外部中断下降沿触发再把GPIO上拉Internal Pull-Up打开因为W5500的INTn是开漏输出必须靠上拉电阻才能正常输出高电平。最后在Project Manager里设置工程名和存储路径Toolchain选MDK-ARM或者STM32CubeIDE都行然后生成代码。检查一下main.c里SystemClock_Config、MX_GPIO_Init、MX_SPI1_Init这些函数是否都被正常调用初始化顺序无所谓反正都在用户代码之前执行。2.3 初始化时序先复位芯片再写网络参数代码生成好之后不要急着写业务逻辑先把W5500的复位和初始化顺序搞清楚。W5500的复位逻辑很简单把RSTn拉低至少500微秒然后拉高再等待500微秒左右芯片就完成内部上电复位了。我一般在初始化函数里写一个简单的硬件复位static void W5500_HardReset(void) { HAL_GPIO_WritePin(RSTn_GPIO_Port, RSTn_Pin, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(RSTn_GPIO_Port, RSTn_Pin, GPIO_PIN_SET); HAL_Delay(1); }复位之后调用wizchip_init()完成芯片内部的寄存器初始化然后依次写入MAC地址、本地IP、网关、子网掩码。这里有个非常容易犯的错误在某一个配置函数调用之前SPI接口必须已经初始化完成而且CS、RSTn这些GPIO必须已经处于正确的电平状态否则芯片可能一直处于复位状态SPI写入的寄存器全部无效。合理的初始化顺序是HAL_Init和时钟配置由CubeMX生成保证然后MX_GPIO_Init、MX_SPI1_Init接着执行W5500_HardReset再调用wizchip_init和网络参数配置函数最后才能进行socket操作。3. 5步实现TCP客户端通讯附关键代码3.1 第一步接线确认与IO映射开始写代码前先确认MCU和W5500之间的连接。以SPI1为例典型的接线方式如下STM32F407引脚SPI1功能连接W5500模块引脚PA5SPI1_SCKSCLKPA6SPI1_MISOMISOPA7SPI1_MOSIMOSIPA4普通GPIO做CSSCSnPA3普通GPIO做RSTnRSTnPA2普通GPIO外部中断INTn3.3V电源VCCGND地GND注意CS、RSTn、INTn这三个引脚是可以按自己板子随意改的因为代码里用的是宏定义改起来很简单。只有SCK、MISO、MOSI受到SPI外设引脚映射限制F407上SPI1也可以用不同引脚重映射但用CubeMX配置时它已经帮你规避了冲突。3.2 第二步SPI读写函数与驱动注册W5500官方提供的驱动代码叫ioLibrary_Driver一般大家用的是里面Ethernet目录下的W5500驱动和socket.c文件。这个库做得很规范但它默认用户自己实现几个底层回调函数SPI读一个字节、写一个字节、批量读、批量写、CS拉低、CS拉高、复位控制、临界区保护。HAL库下最稳的SPI读写方式是使用HAL_SPI_TransmitReceive因为全双工模式下接收数据必须同时发送数据产生时钟。下面是一套可用的底层函数static void W5500_CS_Select(void) { HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_RESET); } static void W5500_CS_Release(void) { HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_SET); } static uint8_t W5500_SPI_ReadByte(void) { uint8_t tx 0x00; uint8_t rx 0x00; HAL_SPI_TransmitReceive(hspi1, tx, rx, 1, HAL_MAX_DELAY); return rx; } static void W5500_SPI_WriteByte(uint8_t byte) { uint8_t tx byte; uint8_t rx 0; HAL_SPI_TransmitReceive(hspi1, tx, rx, 1, HAL_MAX_DELAY); } static void W5500_SPI_ReadBurst(uint8_t *pBuf, uint16_t len) { // 发送len个0字节的同时读取pBuf for (uint16_t i 0; i len; i) { pBuf[i] W5500_SPI_ReadByte(); } } static void W5500_SPI_WriteBurst(uint8_t *pBuf, uint16_t len) { for (uint16_t i 0; i len; i) { W5500_SPI_WriteByte(pBuf[i]); } } static void W5500_Reset(void) { W5500_HardReset(); } static void W5500_CriticalSectionEnter(void) { __disable_irq(); } static void W5500_CriticalSectionExit(void) { __enable_irq(); }然后把这些回调注册到协议栈一般放在主初始化代码里reg_wizchip_cs_cbfunc(W5500_CS_Select, W5500_CS_Release); reg_wizchip_spi_cbfunc(W5500_SPI_ReadByte, W5500_SPI_WriteByte); reg_wizchip_spi_readburst_cbfunc(W5500_SPI_ReadBurst); reg_wizchip_spi_writeburst_cbfunc(W5500_SPI_WriteBurst); reg_wizchip_reset_cbfunc(W5500_Reset); reg_wizchip_cris_cbfunc(W5500_CriticalSectionEnter, W5500_CriticalSectionExit);注意这里SPI读写函数返回和入参都是uint8_t如果你用HAL_SPI_Transmit的阻塞函数时间会长一点但对W5500这种低速控制场景完全够用。临界区保护在裸机环境下直接关中断和开中断就行如果上了RTOS再改成互斥锁或者关调度器。3.3 第三步W5500芯片初始化和网络参数配置初始化库时要告诉W5500每个socket的收发缓存大小。W5500一共有8个socket共享32KB收发缓存一般每个socket根据用途分配2KB或4KB。如果你的项目只用一个socket做TCP客户端分配2KB发送、2KB接收就够了uint8_t txsize[8] {2, 0, 0, 0, 0, 0, 0, 0}; uint8_t rxsize[8] {2, 0, 0, 0, 0, 0, 0, 0}; if (wizchip_init(txsize, rxsize) 0) { // 初始化成功 }接下来设置网络参数这三个函数分别写MAC地址、IP地址、网关、子网掩码uint8_t mac[6] {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56}; uint8_t ip[4] {192, 168, 1, 100}; uint8_t gw[4] {192, 168, 1, 1}; uint8_t subnet[4] {255, 255, 255, 0}; setSHAR(mac); setSIPR(ip); setGAR(gw); setSUBR(subnet);如果你只是局域网内调试网关填不填其实都能通因为W5500发送ARP请求时会在本地子网内广播。但正式项目里建议全部填正确避免设备接入复杂网络环境时出问题。3.4 第四步创建socket并连接TCP服务器W5500的socket API封装在socket.c里用起来和BSD Socket很像。作为客户端开一个TCP socket然后connect即可。下面的代码演示了连接一个IP为192.168.1.50、端口为8080的服务器#define SOCKET_TCP 0 #define TCP_LOCAL_PORT 5000 uint8_t server_ip[4] {192, 168, 1, 50}; uint16_t server_port 8080; int8_t W5500_TCP_Client_Connect(void) { if (socket(SOCKET_TCP, Sn_MR_TCP, TCP_LOCAL_PORT, 0) ! SOCKET_TCP) { return -1; } if (connect(SOCKET_TCP, server_ip, server_port) ! SOCK_OK) { close(SOCKET_TCP); return -1; } return 0; }这里有两个坑要提醒。第一个坑是connect函数是阻塞的如果服务器IP不可达这个函数会一直卡在socket状态机的等待循环里导致整个程序卡死。ioLibrary_Driver里其实提供了超时控制的接口用wizchip_settimeout设置超时参数或者自己定义一个超时宏定期检查socket状态超时就强制close并返回失败。第二个坑是socket的本地端口不要每次连接都用相同固定值如果上一次连接没有正常关闭本地端口还处于TIME_WAIT状态再次连接就会失败。简单粗暴的解决办法是每次连接失败后先close稍等几百毫秒再重试。连接是否成功本质上是socket状态机推进到了SOCK_ESTABLISHED状态。W5500底层会自动完成TCP三次握手我们只需要通过getSn_SR函数读取当前socket状态就行uint8_t status getSn_SR(SOCKET_TCP); if (status SOCK_ESTABLISHED) { // 连接成功 }3.5 第五步数据收发与断线重连连接建立之后收发数据就是调用send和recv两个函数。这两个函数在socket.c里有现成实现会自动处理W5500的收发缓存区读写不需要我们碰寄存器细节。发送一段数据非常直接int32_t sentLen send(SOCKET_TCP, (uint8_t*)tx_buf, tx_len); if (sentLen 0) { // 发送失败多半连接已经断开 }接收时先查一下接收缓存里有多少字节可读避免无意义地循环调用recv。在主循环或者定时任务里这么处理int32_t remain getSn_RX_RSR(SOCKET_TCP); if (remain 0) { uint8_t rx_buf[128]; int32_t recvLen recv(SOCKET_TCP, rx_buf, sizeof(rx_buf)); if (recvLen 0) { // 处理收到的数据 } }收发函数本身很简单但真正考验工程项目的是断线重连。W5500是硬件协议栈如果服务器主动断开连接或者网线被拔出socket状态会回到SOCK_CLOSED如果不做重连逻辑TCP客户端就永久失效了。我比较推荐用中断和轮询结合的方式处理。CubeMX已经配置好INTn外部中断当有接收、断开等事件发生时W5500的INTn引脚会拉低在中断回调里读取事件标识void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin INTn_Pin) { uint8_t irq getSn_IR(SOCKET_TCP); if (irq Sn_IR_DISCON) { setSn_IR(SOCKET_TCP, Sn_IR_DISCON); tcp_connected 0; } if (irq Sn_IR_RECV) { setSn_IR(SOCKET_TCP, Sn_IR_RECV); tcp_has_data 1; } } }主循环里再统一处理状态while (1) { if (!tcp_connected) { if (W5500_TCP_Client_Connect() 0) { tcp_connected 1; } else { HAL_Delay(2000); // 重连间隔避免疯狂重连 } } else { // 周期心跳、数据发送、数据接收 } }重连间隔建议至少1秒以上否则服务器端可能会因为SYN洪水保护策略暂时拒绝你的连接请求。4. 调试验证与常见问题排查实录4.1 链路层验证先让数据在内网里跑通代码写完不要急着连服务器先把链路层验证通过。链路层不代表TCP通路而是你先确认W5500能正常响应SPI读写、ARP请求能回复、ICMP能回应。最简单的验证方法是读取W5500的版本寄存器看返回值是否为0x04uint8_t version getVERSIONR();如果读取出来不是0x04说明SPI配置或者W5500本身有问题。先查CS片选控制、SPI模式、复位时序。接下来设置好静态IP后在电脑上执行ping命令。能ping通说明W5500的MAC、IP、SPI链路、网络驱动都是正常的。如果ping不通先检查电脑和板子是否在同一网段、网关和子网掩码是否一致再查W5500是否成功发送了ARP请求。我用一个很土但有效的调试办法把W5500的IP固定成192.168.1.100然后电脑网卡手动设置成192.168.1.50用一根网线直连绕过交换机排除交换机端口、VLAN等干扰。做TCP通讯调试时电脑上开一个NetworkAssist或者SocketTool工具监听8080端口看客户端能不能连上来能不能双向收发数据。这样可以把问题限定在W5500驱动和代码范围内。4.2 W5500跑几天后连接不上问题出在哪很多朋友在实际项目中会遇到一个很隐蔽的问题W5500刚上电工作正常跑了几个小时甚至几天后TCP连接突然就断了而且再也连不上必须断电重启才能恢复。这个现象我见得非常多原因一般出在以下几个方面。第一没有处理连接断开事件。TCP连接并不是时刻稳定的对端可能重启、网线抖动、路由器老化都会导致连接断开。如果代码里只写了一次connect就再也不管socket状态了那断开后socket停留在SOCK_CLOSED或者错误状态自然永远连不上。解决办法就是前面说的断线重连机制而且要处理SOCK_DISCON事件不能靠发送失败才后知后觉。第二重连时没有关闭旧socket资源。W5500每个socket对应一组寄存器在重新connect之前必须确保socket已经close并且等待足够长的时间让本地端口释放。如果connect失败立刻再次connect很容易因为残留状态导致新的三次握手失败。我的做法是每次重连前强制调close再延时500毫秒再执行socket和connect。第三没有发送心跳包。如果TCP连接长时间没有数据交互中间设备比如路由器、防火墙可能会清理空闲连接导致链路处于半开放状态。客户端的连接不会立刻感知到直到下一次发送数据才触发超时重传但此时对端可能早已认为连接无效。所以TCP客户端通常要周期性发送心跳包比如每30秒发一个自定义的短数据帧服务器端定时清除超时连接。心跳间隔不要小于服务器的清理时间否则无效。第四硬件层面的长期稳定性。W5500本身稳定但有的模块使用时间长了会因为供电纹波增大、晶振老化导致异常。排查方式是在故障发生时用示波器看3.3V电源波形和25MHz晶振波形确认不是硬件问题再回去查代码。4.3 HAL库SPI/串口DMA的几个典型坑用HAL库做W5500这个项目大部分人不会用DMA因为W5500的SPI交互是短小快速的CPU阻塞读写完全没问题。但如果你将来要在同一个工程里用SPI DMA收发数据或者用串口DMA做日志输出有一些HAL库特有的坑值得提前预防。最常见的是“串口DMA发送不能连续发送”的问题。现象是第一次调用HAL_UART_Transmit_DMA没问题第二次调用数据发不出来或者要等很长时间才能发。原因是上一次DMA传输还没有完成就把下一次数据丢进去了HAL库的状态机还停在HAL_UART_STATE_BUSY_TX新的请求直接返回HAL_BUSY。解决办法是在发送完成中断回调HAL_UART_TxCpltCallback里设置一个“发送完成标志”下次发送前检查这个标志或者干脆换成阻塞式的HAL_UART_Transmit降低吞吐但保证不出错。SPI DMA循环模式也是个容易踩坑的地方。HAL库配置DMA时如果选了Cyclic循环模式DMA会自己重载到起始地址不断搬运数据。这个模式适合采集连续数据流但不适合W5500这种一问一答的事务型通信。因为W5500每次访问都是“发送地址和控制段再读写数据”的一个完整时序中间CS片选会拉低循环DMA会在这期间继续搬数据很容易把时序打乱。所以W5500项目不要用SPI DMA循环模式用普通Normal模式按需触发甚至直接阻塞读写反而是最可靠的。还有一个容易被忽略的问题是DMA请求映射。F407的SPI1_TX、SPI1_RX对应的DMA通道是固定的比如SPI1_TX对应DMA2_Stream3SPI1_RX对应DMA2_Stream0。CubeMX配置时不会让你填通道号因为它已经自动绑定了但如果你在别的代码里手工改过DMA配置就要回去核对映射关系避免两个外设抢同一个DMA通道。4.4 常见问题速查表现象可能原因排查方向读取VERSIONR返回0xFF或0x00SPI模式不对、CS没拉低、复位脚未释放检查SPI Mode 0、GPIO初始电平、RSTn是否复位ping通但TCP连不上服务器IP端口错误、防火墙拦截检查connect参数关闭双方防火墙测试TCP连接后立即断开对端未正确accept、本地socket被关闭上位机工具看TCP握手日志检查数据格式连接稳定但收发偶尔丢包SPI速率过高、杜邦线过长、电源纹波降低SPI分频换短线加电容ping时断时续晶振虚焊、接触不良、网线问题示波器看晶振重新插拔网线换质量好的网络跑几天后连不上无重连逻辑、socket未close、心跳缺失实现断线重连重连前close加心跳串口DMA第二次发送失败HAL库状态未复位上一次DMA未完成等TxCpltCallback再发下一次或改用阻塞发送socket卡在某个状态不动超时未设置、connect阻塞使用wizchip_settimeout或手动超时轮询排查问题时有条件的建议用逻辑分析仪抓SPI时序没条件就老老实实降低SPI速率、打印socket状态寄存器把问题一步一步缩小。在实际跑项目的过程中我用F407加W5500这套组合做过不少数据采集和远程控制类的设备最大的体会是低速率先把链路跑通再谈优化。W5500这类硬件协议栈芯片真正考验你的并不是“能不能连上”而是“断线后能不能恢复”。把复位时序、SPI读写、socket状态机、断线重连这四个环节梳理清楚TCP客户端通讯这件事基本就稳了。最后再分享一个调试小技巧如果你不确定是SPI时序问题还是W5500硬件问题可以在复位后逐个读取通用寄存器把读到的MR、SHAR、SIPR打印出来和设置值对比。只要这些回读值正确硬件链路就是通的后面剩下的问题基本都在socket状态机或者网络环境里了。