STM32F407+W5500实现TCP客户端:基于HAL库的以太网通讯实战
发布时间:2026/9/27 23:15:27来源:尧图网络
STM32F407从去年开始在我手里的几个项目里出现的频率越来越高但真正让我把它和网络通讯联系到一起的是一次设备数据上云的需求现场一台检测设备用F407采集数据需要实时上报到上位机。当时我手里要选型最头疼的就是以太网方案。F407本身带MAC但要外接PHY芯片RMII接口的线不少还要移植协议栈用ESP8266这种WiFi模块倒是简单可工厂环境里WiFi的可靠性真不敢赌。最后我选了W5500理由很简单它把MAC、PHY和TCP/IP协议栈全做进了芯片里MCU这边只需要通过SPI读写寄存器就能把TCP客户端跑起来。这篇文章就围绕这套组合来写。如果你也在做STM32和以太网通讯手头用的是HAL库想把TCP客户端功能快速落地那这篇的内容应该能帮你省不少时间。全文不绕弯子直接讲清楚方案选型、电路连接、CubeMX配置、驱动封装、TCP建连和收发代码以及我在实际调试中踩过的坑。1. 为什么要用F407W5500三种以太网方案的取舍1.1 F407自带MAC为什么还要外挂一颗芯片STM32F407这颗芯片其实自带以太网MAC控制器支持MII和RMII两种接口。很多人刚接触时会觉得既然MAC已经在片内了那外面再接一颗便宜的PHY芯片比如LAN8720A或者DP83848不就行了吗理论上是这样但实际落地你会发现事情没那么简单。PHY芯片只是物理层收发器MAC要正常工作得先把ETH驱动、DMA描述符、PHY管理接口MDIO、链接状态检测这套东西全部跑通然后再移植一个TCP/IP协议栈。lwIP是大家用得最多的但移植lwIP对新手来说并不轻松内存池配置、网卡接口结构体、中断和轮询的配合、超时处理哪一环没弄好调试起来都够喝一壶的。而且在F407这种主频168MHz的MCU上跑lwIP虽然不至于带不动但协议栈本身会占用不少RAM和CPU时间数据量一大CPU被网络任务吃掉的情况也常见。1.2 W5500相比PHY外挂方案和WiFi方案的几大优势W5500是WIZnet推出的一款以太网控制芯片它把MAC、PHY、TCP/IP硬协议栈集成在一颗芯片里对外只暴露SPI接口。也就是说TCP的三次握手、数据重发、分片重组这些事全由芯片内部硬件完成MCU只需要关心业务逻辑。我用一张表来对比一下几种常见方案的差别对比项F407 MAC 外挂PHY lwIPF407 W5500F407 ESP8266/ESP32 WiFiMCU接口复杂度RMII约9根信号线 MDIOSPI 4根线 复位/中断UART AT指令或SPI/SDIO协议栈负担需要移植lwIP占用RAM/CPU芯片硬件处理MCU零负担AT指令内置但依赖模组固件可靠性工业级但调试门槛高工业级链路稳定依赖WiFi环境抗干扰一般源码工作量较大驱动、PHY、lwIP都要调小只需SPI驱动和寄存器操作小AT指令拼字符串典型应用场景高速率、大流量、成本敏感中小流量、快速上线、稳定优先无线、便携、家庭场景从这个表能看出来W5500最大的价值在于把网络协议这个复杂度从MCU侧剥离掉。对F407HAL库这个组合来说W5500简直就是为快速交付网络功能准备的SPI是HAL库最成熟的接口之一W5500的寄存器操作思路也清晰半天就能把驱动跑起来。还有一个隐藏优势是成本可控。W5500模组在市场上的价格不算贵而且因为有硬协议栈MCU选低配型号也能扛网络任务整体BOM反而能压下来。我后来在几个对稳定性要求更高的小批量项目里直接沿用了这个方案省下的调试时间非常可观。2. 电路设计几个必须注意的地方引脚分配、复位时序和网络变压器2.1 引脚连接表与SPI接口分配W5500和STM32F407的硬件连接非常直接本质上就是一组SPI主从连接加上复位和中断控制。我习惯把W5500挂在SPI1上因为SPI1挂载在APB2总线上时钟频率可以跑到84MHz分频下来SPI速率更充裕。STM32F407引脚功能连接目标说明PA5SPI1_SCKW5500 SCLKSPI时钟建议10MHz左右PA6SPI1_MISOW5500 MISO从W5500读到MCUPA7SPI1_MOSIW5500 MOSI从MCU写到W5500PA4普通GPIO输出W5500 SCS片选引脚软件控制PE1普通GPIO输出W5500 RSTn硬件复位低电平有效PE0普通GPIO输入W5500 INTn中断请求可选注意PA4我没有把它配置成SPI1_NSS的硬件片选而是当作普通GPIO来用。原因在后面驱动部分会详细说这里先说结论W5500的片选时序需要非常精准地配合SPI收发用软件片选才能保证每一帧读写都能完整控制。F407和W5500都是3.3V电平所以不需要电平转换直接连就行。如果你用的是5V供电的MCU那中间必须加电平转换芯片W5500的IO耐受范围不推荐直接接5V。2.2 W5500最小系统的四个关键器件W5500不是拿来就能跑它周围需要几个基本器件。我在第一版PCB上吃过亏这里把经验写清楚。首先是25MHz晶振。W5500内部有PLL但基准时钟必须从外部接入通常是在XI和XO引脚之间接一颗25MHz无源晶振配两个20pF左右的负载电容。晶振起振不正常芯片上电后SPI读寄存器会全返回0xFF这种问题最让人头疼。其次是复位电路。W5500的RSTn引脚需要保证至少500us的低电平复位脉冲。很多人直接用RC复位或者MCU的GPIO控制我最推荐用MCU的GPIO控制这样可以在初始化时随时软复位芯片。如果只用简单的上电RC复位那在上电后必须等待足够时间再访问芯片否则可能出现第一次读寄存器超时的情况。第三是网络变压器。W5500的TXN/TXP、RXN/RXP引脚不能直接接到RJ45上必须经过网络变压器做电气隔离。市面上很多RJ45座自带变压器比如HR911105A这种带变压器的RJ45插座用起来最方便外围器件也少。第四是3.3V供电和去耦。W5500的电源引脚需要加0.1uF去耦电容靠近电源引脚放置。如果供电纹波大会导致网络丢包甚至断连。我遇到过一次ping时断时续的问题最后排查下来就是电源纹波超标。3. CubeMX配置的关键点SPI参数、时钟树和GPIO3.1 时钟树配置让SPI1拿到一个合适的时钟打开STM32CubeMX芯片选STM32F407VET6或你手上的具体型号先配RCCHSE选择Crystal/Ceramic Resonator然后在Clock Configuration里把系统时钟配到168MHz。经典配法是HSE8MHzPLLM8PLLN336PLLP2这样SYSCLK168MHzAHB168MHzAPB142MHzAPB284MHz。SPI1挂在APB2上所以SPI1的输入时钟是84MHz。W5500最高支持约33MHz的SPI时钟但实际工程中我把预分频设为8也就是SPI时钟84/810.5MHz。这个速率对W5500来说非常稳定而且对于驱动TCP客户端这种数据量不大的场景完全够用。不要一上来就追求最高速率稳定优先。3.2 SPI主机模式参数模式0配合8位数据宽度在SPI1的配置页面里关键参数这么设Mode: Transmit Receive全双工主机Hardware NSS Signal: Disable因为用软件片选Data Size: 8 BitsFirst Bit: MSB FirstPrescaler: 8分频CPOL: Low空闲时钟低电平CPHA: 1 Edge第一个边沿采样也就是常说SPI Mode 0。W5500的SPI接口是标准SPI从机模式0是最常用的搭配两边时钟极性和相位一致才能正常通信。如果配置成模式3也能用但代码要统一我习惯全工程用模式0。Hardware NSS Signal这里要选择Disable不要把NSS引脚交给SPI外设控制。W5500的SCS片选在每次通信时由MCU软件拉低拉高这样才能精确控制一帧SPI数据从开始到结束的完整性。3.3 GPIO与中断引脚的处理生成代码前要把GPIO配置好。PA4配置为GPIO_OutputPE0配置为GPIO_InputPE1配置为GPIO_Output。CubeMX里操作很简单在Pinout视图中直接点击引脚选择功能即可。PE0这个中断引脚我建议接上但驱动里先用轮询方式。原因在于W5500的INTn引脚可以用于通知MCU有数据到达或连接状态变化但在初期调通SPI和TCP功能时引入中断反而会增加排查难度。先把轮询功能跑通再考虑是否用中断。我的项目最终用的是轮询加短超时效果已经很好。生成代码后在main.c里不要着急写业务逻辑先确认HAL_SPI_MspInit函数能被正确调用SPI的GPIO复用配置已经自动生成。如果发现SPI通信一直不通优先检查MspInit里的GPIO复用是否正确。4. W5500驱动封装寄存器读写背后的SPI帧结构4.1 拆开SPI帧地址、块选择和控制字节W5500的SPI通信和普通SPI从机芯片不太一样它的一帧数据由三部分组成2字节寄存器地址、1字节控制字节、若干字节数据。这个结构非常关键理解透了驱动写起来就顺了。控制字节才是真正的难点。它里面包含块选择Block Select、读写标志RWB和可变数据长度模式VDM。块选择决定访问的是公共寄存器、某个Socket的寄存器还是某个Socket的发送/接收缓冲区。控制字节位含义说明Bit[7:4]BSB3~BSB0块选择0表示公共寄存器1/2/3/4对应Socket0~3寄存器5/6~11/12对应各Socket收发缓冲区Bit[2]VDM0表示可变数据长度模式连续读写1表示固定数据长度模式Bit[1]RWB0表示写1表示读块选择的具体映射如下BSB 0b0000访问公共寄存器BSB 0b0001~0b0100访问Socket0~3的寄存器BSB 0b0101Socket0发送缓冲区后续每两个值对应一个Socket的TX和RX有了这个基础读写函数就可以抽象出来了。底层只需要两个函数一个是连续写一个是连续读所有上层操作都基于这两个函数。static void W5500_ReadData(uint16_t addr, uint8_t bsb, uint8_t *buf, uint16_t len) { uint8_t head[3]; head[0] (uint8_t)(addr 8); head[1] (uint8_t)(addr 0xFF); head[2] bsb | W5500_VDM_VAR | W5500_RW_READ; W5500_CS_LOW(); HAL_SPI_Transmit(hspi1, head, 3, 100); if (len 0) HAL_SPI_Receive(hspi1, buf, len, 1000); W5500_CS_HIGH(); } static void W5500_WriteData(uint16_t addr, uint8_t bsb, uint8_t *buf, uint16_t len) { uint8_t head[3]; head[0] (uint8_t)(addr 8); head[1] (uint8_t)(addr 0xFF); head[2] bsb | W5500_VDM_VAR | W5500_RW_WRITE; W5500_CS_LOW(); HAL_SPI_Transmit(hspi1, head, 3, 100); if (len 0) HAL_SPI_Transmit(hspi1, buf, len, 1000); W5500_CS_HIGH(); }这两个函数里CS_LOW和CS_HIGH之间必须保证SPI收发是连续的中间不能被打断。在裸机环境下这没问题但如果用了RTOS需要防止任务切换导致片选中间被拉高否则W5500会把一次未完成的通信当成错误帧丢弃。4.2 公共寄存器与Socket寄存器的分层W5500的寄存器分两大层公共寄存器和Socket寄存器。公共寄存器负责整个芯片的全局配置包括网关地址、子网掩码、MAC地址、本机IP等。Socket寄存器则负责每个独立socket的配置和状态。我习惯把寄存器地址用宏定义管理起来写代码时清晰很多// 公共寄存器 #define REG_MR 0x0000 #define REG_GAR 0x0001 #define REG_SUBR 0x0005 #define REG_SHAR 0x0009 #define REG_SIPR 0x000F #define REG_IR 0x0015 #define REG_IMR 0x0016 // Socket寄存器偏移量 #define REG_Sn_MR 0x0000 #define REG_Sn_CR 0x0001 #define REG_Sn_SR 0x0003 #define REG_Sn_PORT 0x0004 #define REG_Sn_DIPR 0x000C #define REG_Sn_DPORT 0x0010 #define REG_Sn_TX_FSR 0x0020 #define REG_Sn_TX_WR 0x0024 #define REG_Sn_RX_RSR 0x0026 #define REG_Sn_RX_RD 0x0028 // 块选择 #define BSB_COMMON 0x00 #define BSB_SOCKET(n) ((n 1) 4) #define BSB_TX_BUF(n) ((n * 2 5) 4) #define BSB_RX_BUF(n) ((n * 2 6) 4)Socket0的寄存器块选择是0b0001所以BSB_SOCKET(0) 1 4 0x10。这个换算关系写代码时容易搞错我建议封装好后就不要反复去算统一走宏。4.3 底层读写函数实现在W5500_ReadData和W5500_WriteData基础上还需要封装单字节寄存器的读写因为很多状态寄存器是8位的每次读单字节就够了。static uint8_t W5500_ReadReg(uint16_t addr, uint8_t bsb) { uint8_t val 0; W5500_ReadData(addr, bsb, val, 1); return val; } static void W5500_WriteReg(uint16_t addr, uint8_t bsb, uint8_t val) { W5500_WriteData(addr, bsb, val, 1); }到这里驱动的地基就打好了。接下来就是芯片初始化和TCP客户端逻辑。5. TCP连接和数据收发完整代码解读5.1 连接流程OPEN、CONNECT和三次握手的实际关系先说明一个很多人刚接触W5500时的误区TCP三次握手这个动作并不是MCU代码去实现的而是W5500硬件协议栈自动完成的。MCU要做的只是先open socket然后设置目的IP和端口再发一条CONNECT命令剩下的SYN、SYN-ACK、ACK的交互都由芯片内部处理。初始化部分要做的事很明确硬件复位然后写入网关、子网掩码、MAC和本机IP。void W5500_Init(void) { uint8_t gw[4] {192, 168, 1, 1}; uint8_t mask[4] {255, 255, 255, 0}; uint8_t mac[6] {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56}; uint8_t ip[4] {192, 168, 1, 100}; W5500_RST_LOW(); HAL_Delay(100); W5500_RST_HIGH(); HAL_Delay(200); W5500_WriteData(REG_GAR, BSB_COMMON, gw, 4); W5500_WriteData(REG_SUBR, BSB_COMMON, mask, 4); W5500_WriteData(REG_SHAR, BSB_COMMON, mac, 6); W5500_WriteData(REG_SIPR, BSB_COMMON, ip, 4); }这里有几个注意点MAC地址不要和局域网里其他设备冲突IP地址要和服务器在同一网段网关和子网掩码必须和实际路由器一致。如果W5500和电脑直连不经过路由器网关可以设成和电脑IP同网段的任意值重点是把IP配到同一网段。socket打开和连接的函数如下uint8_t W5500_SocketTCPClient(uint8_t sock, uint16_t sport, uint8_t *dip, uint16_t dport) { uint8_t status; uint16_t timeout; status W5500_ReadReg(REG_Sn_SR, BSB_SOCKET(sock)); if (status SOCK_CLOSED) { W5500_WriteReg(REG_Sn_MR, BSB_SOCKET(sock), 0x01); // TCP模式 W5500_WriteReg(REG_Sn_PORT, BSB_SOCKET(sock), sport 8); W5500_WriteReg(REG_Sn_PORT 1, BSB_SOCKET(sock), sport 0xFF); W5500_WriteReg(REG_Sn_CR, BSB_SOCKET(sock), Sn_CR_OPEN); timeout W5500_TIMEOUT; while (W5500_ReadReg(REG_Sn_CR, BSB_SOCKET(sock)) ! 0 --timeout); status W5500_ReadReg(REG_Sn_SR, BSB_SOCKET(sock)); if (status ! SOCK_INIT) return 0; } W5500_WriteData(REG_Sn_DIPR, BSB_SOCKET(sock), dip, 4); W5500_WriteReg(REG_Sn_DPORT, BSB_SOCKET(sock), dport 8); W5500_WriteReg(REG_Sn_DPORT 1, BSB_SOCKET(sock), dport 0xFF); W5500_WriteReg(REG_Sn_CR, BSB_SOCKET(sock), Sn_CR_CONNECT); timeout 3000; while (--timeout) { status W5500_ReadReg(REG_Sn_SR, BSB_SOCKET(sock)); if (status SOCK_ESTABLISHED) return 1; if (status SOCK_CLOSED) return 0; HAL_Delay(1); } return 0; }CONNECT命令发出后W5500进入SYN_SENT状态对应的状态值0x15。如果服务器正常监听端口很快会返回SYN-ACK然后状态变成SOCK_ESTABLISHED0x17。如果服务器IP不可达、端口没监听或者有防火墙拦截状态最终会回到SOCK_CLOSED函数返回0。这就是判断连接是否成功的依据。5.2 发送数据写缓冲区、更新指针、SEND命令的完整链路发送数据这个环节最大的坑在于W5500的发送缓冲区是环形结构。Sn_TX_WR是发送写指针数据要写入TX缓冲区中Sn_TX_WR对应的位置写完后更新指针最后发SEND命令。如果写指针加上数据长度超过了缓冲区末尾必须分成两段写入否则会覆盖缓冲区头部的数据。int32_t W5500_Send(uint8_t sock, uint8_t *data, uint16_t len) { uint16_t free_size 0; uint16_t tx_wr 0; uint16_t timeout; timeout W5500_TIMEOUT; do { free_size (W5500_ReadReg(REG_Sn_TX_FSR, BSB_SOCKET(sock)) 8) | W5500_ReadReg(REG_Sn_TX_FSR 1, BSB_SOCKET(sock)); } while (free_size len --timeout); if (free_size len) return -1; tx_wr (W5500_ReadReg(REG_Sn_TX_WR, BSB_SOCKET(sock)) 8) | W5500_ReadReg(REG_Sn_TX_WR 1, BSB_SOCKET(sock)); uint16_t offset tx_wr % W5500_TX_BUF_SIZE; if (offset len W5500_TX_BUF_SIZE) { W5500_WriteData(tx_wr, BSB_TX_BUF(sock), data, len); } else { uint16_t size1 W5500_TX_BUF_SIZE - offset; W5500_WriteData(tx_wr, BSB_TX_BUF(sock), data, size1); W5500_WriteData(tx_wr size1, BSB_TX_BUF(sock), data size1, len - size1); } uint16_t new_wr tx_wr len; W5500_WriteReg(REG_Sn_TX_WR, BSB_SOCKET(sock), new_wr 8); W5500_WriteReg(REG_Sn_TX_WR 1, BSB_SOCKET(sock), new_wr 0xFF); W5500_WriteReg(REG_Sn_CR, BSB_SOCKET(sock), Sn_CR_SEND); timeout W5500_TIMEOUT; while (W5500_ReadReg(REG_Sn_CR, BSB_SOCKET(sock)) ! 0 --timeout); if (timeout 0) return -2; timeout W5500_TIMEOUT; while (--timeout) { uint8_t ir W5500_ReadReg(REG_Sn_IR, BSB_SOCKET(sock)); if (ir SN_IR_SEND_OK) { W5500_WriteReg(REG_Sn_IR, BSB_SOCKET(sock), SN_IR_SEND_OK); return len; } if (ir SN_IR_DISCON) return -3; } return -4; }把W5500_TX_BUF_SIZE定为2048这是W5500每个Socket默认的发送缓冲区大小。发送数据超过这个大小就必须拆分多次发送不能一包塞进去。这个尺寸限制在实际项目里很关键TCP客户端通常每次上报几十到几百字节没遇到过大包问题但如果你要传大块数据务必设计分包逻辑。5.3 接收数据RX缓冲区的回绕处理陷阱接收方向和发送方向是对称的但多了一个细节处理完一段数据后必须更新Sn_RX_RD指针然后向Sn_CR写入RECV命令。不少人在这个环节卡住读了数据但忘了发RECV命令结果接收缓冲区一直被占着后面的数据收不进来。int32_t W5500_Recv(uint8_t sock, uint8_t *buf, uint16_t len, uint16_t timeout) { uint16_t rx_size 0; uint32_t tick HAL_GetTick(); do { rx_size (W5500_ReadReg(REG_Sn_RX_RSR, BSB_SOCKET(sock)) 8) | W5500_ReadReg(REG_Sn_RX_RSR 1, BSB_SOCKET(sock)); } while (rx_size 0 (HAL_GetTick() - tick) timeout); if (rx_size 0) return 0; if (rx_size len) rx_size len; uint16_t rx_rd (W5500_ReadReg(REG_Sn_RX_RD, BSB_SOCKET(sock)) 8) | W5500_ReadReg(REG_Sn_RX_RD 1, BSB_SOCKET(sock)); uint16_t offset rx_rd % W5500_RX_BUF_SIZE; if (offset rx_size W5500_RX_BUF_SIZE) { W5500_ReadData(rx_rd, BSB_RX_BUF(sock), buf, rx_size); } else { uint16_t size1 W5500_RX_BUF_SIZE - offset; W5500_ReadData(rx_rd, BSB_RX_BUF(sock), buf, size1); W5500_ReadData(rx_rd size1, BSB_RX_BUF(sock), buf size1, rx_size - size1); } W5500_WriteReg(REG_Sn_RX_RD, BSB_SOCKET(sock), (rx_rd rx_size) 8); W5500_WriteReg(REG_Sn_RX_RD 1, BSB_SOCKET(sock), (rx_rd rx_size) 0xFF); W5500_WriteReg(REG_Sn_CR, BSB_SOCKET(sock), Sn_CR_RECV); return rx_size; }接收时如果Sn_RX_RSR返回的数据长度大于你传入的buf长度我只截取buf长度的部分。剩下的数据留在硬件缓冲区里下次再读。如果你希望一次性读完就得保证buf足够大或者循环调用Recv直到RSR归零。5.4 断线重连和主循环的写法TCP连接不是建立起来就一劳永逸的。服务器重启、网线被拔、路由器NAT老化都会导致连接断开。所以主循环里必须有断线检测和自动重连机制。最直接的方式就是每次循环检查Sn_SR状态不是ESTABLISHED就重新走一遍连接流程。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); W5500_Init(); uint8_t server_ip[4] {192, 168, 1, 50}; uint16_t server_port 8080; uint8_t buf[256]; while (1) { uint8_t status W5500_ReadReg(REG_Sn_SR, BSB_SOCKET(0)); if (status ! SOCK_ESTABLISHED) { if (W5500_SocketTCPClient(0, 5000, server_ip, server_port)) { // 连接成功 } HAL_Delay(2000); continue; } char *heartbeat PING; W5500_Send(0, (uint8_t *)heartbeat, 4); int32_t rlen W5500_Recv(0, buf, sizeof(buf), 100); if (rlen 0) { // 在这里解析服务器下发的数据 } HAL_Delay(1000); } }重连机制里有一个要点重连之前最好先调用CLOSE命令确保上次连接的socket资源被完全释放否则在极端情况下会出现socket状态卡在FIN_WAIT或者TIME_WAIT导致重连失败。我一般会在检测到连接断开后先对Sn_CR写CLOSEHAL_Delay(100)再走open和connect流程。6. 实测记录连接失败、丢包和长时间运行不稳的排查6.1 TCP连接不上先按这个顺序排查说实话这章的内容比前面代码更值钱。我在实验室和现场调试W5500最常遇到的第一类问题是TCP连接根本建立不起来。这类问题排查我建议严格按下面的顺序走第一步确认W5500有没有正常工作。方法很简单读一下公共寄存器的版本号寄存器地址0x0039正常返回0x04。如果读出来是0xFF基本就是SPI通信有问题检查接线、复位时序和CubeMX配置。第二步确认网络参数有没有配对。用电脑ping一下W5500的IP如果能ping通说明MAC、IP、链路层都没问题。如果ping不通检查W5500的IP和电脑是不是同一网段网关和子网掩码是否正确以及网线有没有插好。第三步确认服务器端在监听。可以在电脑上跑一个TCP调试助手开启TCP Server模式监听某个端口。要注意如果你的服务器电脑开了防火墙入站规则没有放行这个端口连接也会失败。第四步用网络抓包定位。如果上述都正常还是连不上打开Wireshark抓包ping一下W5500的IP并让它主动连接服务器看有没有TCP SYN报文发出。如果SYN发出来了但没人回应问题在服务器端如果SYN都没发出来问题在W5500的socket配置。6.2 ping通但TCP连不上的场景这是一个很容易踩的坑W5500能被ping通说明网络底层完全正常但TCP客户端总是连不上。出现这种情况多半是目的IP或端口没有正确写入socket寄存器。我在自己的代码里踩过一次类似的坑用W5500_SocketTCPClient函数时把目的IP以参数传进去后在函数里写入了Sn_DIPR寄存器但端口号用了小端写法高字节和低字节写反了。服务器一边明明在监听8080端口W5500却往8080之外的端口发SYN服务器当然没反应。排查方法也很简单用W5500的读寄存器功能把Sn_DIPR和Sn_DPORT的内容读出来和预期值对比。只要这两个值正确连接通常都能建立起来。另外如果本地源端口设置成了0W5500可能会随机选一个端口这个在TCP客户端模式下一般也能工作但为了方便排查问题建议设置一个固定的源端口比如5000到9000之间。6.3 长时间运行后断连以及ping时断时续W5500正常运行几天后连不上的问题网上讨论很多。我自己实测下来最常见的原因是socket资源没有被正确释放。TCP连接异常断开后W5500的socket状态可能停留在CLOSE_WAIT或TIME_WAIT如果代码只在SOCK_CLOSED时才重连那这个状态会一直卡住。解决办法有两个一是定期检查socket状态只要不是ESTABLISHED就直接关闭重连二是设置更积极的超时和心跳机制比如每秒发一次心跳连续几次没有收到响应就主动刷新连接。至于ping时断时续的问题往往是硬件层面的因素居多。我遇到的情况是W5500电源纹波偏大导致PHY工作不稳定。处理办法是在W5500电源引脚附近增加高质量去耦电容并用示波器确认纹波在可接受范围内。SPI速率过高也可能导致驱动时序不稳比如寄存器读写偶发失败最简单的验证办法是把SPI分频调大一点比如从8分频改到16分频看是否稳定。提示遇到W5500相关的不稳定问题不要一开始就怀疑是芯片质量问题。先检查电源、复位、晶振这三大件再考虑代码逻辑。硬件不稳定导致的软件怪问题比纯软件bug更难定位但也更常见。最后再说一个实际经验W5500的中断引脚INTn如果你接了但代码里没用建议把它配置成普通输入并开启上拉。否则芯片产生中断时这个引脚拉低而你又在轮询模式下工作某些情况下会影响SPI通信的稳定性。当然这只是我个人的习惯不接也问题不大但接了不用总归是隐患。这套F407HAL库W5500的组合我已经在多个项目里跑通了。从原理到代码到调试整个过程其实没有特别玄学的地方核心就是把SPI帧结构理解清楚把寄存器流程走顺。如果你正在调TCP客户端连不上不妨按我第6章的排查顺序走一遍大概率能很快找到问题。
网站建设高端定制企业官网