资讯动态

树莓派Pico与W5500实战:用MicroPython实现TCP客户端全指南

发布时间:2026/9/9 6:23:04 来源:尧图企业网站定制
最近在折腾树莓派Pico因为手头一个数据采集项目需要把传感器数据稳定地送到上位机实验环境又在办公楼的局域网里WiFi信号飘忽不定动不动还要重新认证。考虑了一圈最后决定走有线以太网方案选了 Pico W5500 这个组合。这篇就把我从零开始用 MicroPython 在 Pico 上驱动 W5500 实现 TCP 客户端的完整过程写下来包括硬件选型、接线、固件烧录、代码实现以及调试时用 Wireshark 抓包验证三次握手的实战记录。这个方案特别适合有固定工位、局域网环境、对连接稳定性有要求的嵌入式开发者或者刚接触以太网编程想找个低成本平台练手的同学。整篇内容不绕弯子直接给能抄作业的方案顺便把我在实操中踩过的坑也都标注出来。1. 方案选型为什么是 Pico W5500 而不是 WiFi 或者纯软协议栈这个项目最初的原始需求其实很简单——把一块环境监测板上的温度和湿度数据每隔几秒推到局域网里的一台电脑上。听起来用 ESP8266 或者 Pico W 都能搞定但我在实际评估之后还是把网线方案放到了首选位置。1.1 嵌入式联网方案的横向对比先别急着下结论我把目前常见的几类方案放在一张表里对比一下。方案优点缺点典型场景ESP8266 / ESP32WiFi无线部署、价格低、开源资料多协议栈消耗MCU资源射频受环境干扰大智能家居、可穿戴、原型验证树莓派 Pico WWiFi与无线的Pico开发体验一致生态统一信号覆盖不稳定、需要WPA2配置等环境依赖IoT节点、教学演示W5500以太网硬件TCP/IP协议栈、延迟低、稳定、不依赖无线环境需要拉网线、模块成本略高工业控制、数据采集、边缘网关ENC28J60以太网模块便宜、功耗低只有MACPHYTCP/IP协议栈要MCU自己跑成本敏感的以太网应用CH395 等国产方案同样有硬件协议栈生态和参考资料不如WIZnet丰富国产替代、批量量产从这张表能看出来W5500 最大的杀手锏就是硬件协议栈。它把 TCP/IP 这整套复杂的状态机直接固化在芯片内部MCU 根本不参与 TCP 分包、重传、应答这些琐碎工作。这对 MicroPython 这种解释型高级语言来说尤其重要——MicroPython 本身的运行效率就不如 C 语言裸机程序如果还要在应用层上面跑一个软件协议栈性能会非常难看而且调试难度直线上升。1.2 W5500 的硬件协议栈到底帮我们省了多少事简单说W5500 内部集成了 10/100M 以太网的 MAC 和 PHY同时还把 TCP、UDP、IPv4、ARP、ICMP、PPPoE 这些协议固化成了硬件电路。MCU 通过 SPI 接口访问 W5500 的寄存器把要发送的数据写进去W5500 会自动完成数据封装、路由查找、超时重传、连接状态维护这些底层工作。我打个比方你就明白了用 ESP32 跑软件协议栈相当于你不仅要把信的内容写好还得自己研究信封尺寸、邮政编码、邮局分拣规则并且每次寄信都亲手过一遍流程而用 W5500相当于请了一个专职的秘书你只管把信的内容交给他剩下的写信封、贴邮票、处理退信和催收都由秘书代劳。最终我敲定了 Pico W5500 方案。Pico 的售价不到二十块钱MicroPython 支持得非常成熟GPIO 资源也够用W5500 模块的价格也很亲民而且它在 MicroPython 官方固件里就有现成的驱动不需要自己移植别人的 C 库。这种组合对个人开发者和中小企业做原型验证性价比几乎拉满。2. 硬件连接与电路设计核心注意点方案定了之后下一步就是把两块板子老老实实接起来。别看这步操作简单百分之六十的调试事故都出在接线上——要么是引脚搞错要么是供电不足要么是 SPI 线太长导致信号畸变。2.1 W5500 模块引脚速查市面上的 W5500 模块通常集成 RJ45 网口一般引出 8 个引脚功能如下引脚类型说明VCC电源输入3.3V工作电流约 120mA部分模块可接受 5V 但需板载稳压GND电源地与 Pico 共地SCLKSPI 时钟接 Pico 的 SPI SCK 引脚MOSISPI 主机输出接 Pico 的 SPI TX 引脚MISOSPI 主机输入接 Pico 的 SPI RX 引脚SCSSPI 片选低电平有效接任意 GPIORST复位低电平复位接任意 GPIOINT中断输出可选用于事件通知本例不接2.2 Pico 与 W5500 的 SPI 接线Pico 的 SPI0 可以用一组很方便的引脚默认推荐使用 GP16、GP17、GP18、GP19。接线对应关系如下Pico 引脚功能接 W5500GP18SPI0 SCKSCLKGP19SPI0 TX (MOSI)MOSIGP16SPI0 RX (MISO)MISOGP17GPIO用作 CSSCSGP20GPIO用作复位RST3V33.3V 电源VCCGND地GND我选择的 CS 和 RST 引脚其实可以任意指定不一定非要是 GP17 和 GP20只要在代码里保持一致就行。不过建议避开 Pico 板子上已经默认占用的引脚比如 GP25板载 LED避免后面扩展外设时冲突。接好线之后不要急着上电先拿万用表测一遍 VCC 和 GND 之间是不是 3.3V同时确认所有信号线没有接反。SPI 信号线接反是最常见的错误——MOSI 和 MISO 一旦对调驱动会卡在初始化阶段而且查起来特别费劲。2.3 硬件布局与供电避坑这里有一个容易被忽视的重点W5500 上电瞬间尤其是网口 Link 建立时电流会有比较明显的波动。如果直接用 Pico 的 3V3 引脚供电Pico 的板载稳压器是够用的但如果你同时挂了传感器、显示屏之类的外设建议给 W5500 单独用一个 3.3V LDO 供电并且把模块的 VCC 和 GND 之间加一个 10uF 电解电容和 0.1uF 陶瓷电容做去耦。SPI 信号线的长度也需要注意。Pico 和 W5500 模块之间如果用的是面包板跳线线长最好控制在 10cm 以内。超过这个长度时钟频率一高信号反射和串扰就会冒出来现象非常诡异——有时候初始化正常收发数据时好时坏。我实测下来面包板跳线超过 15cm 之后SPI 频率跑 20MHz 就开始出问题了低频到 4MHz 才能稳定。所以后面我所有实验都控制在短线上SPI 频率也没敢跑到极限。3. MicroPython 环境准备与 W5500 驱动确认接线完成只是万里长征第一步接下来要把软件环境准备好确保 MicroPython 固件里真的带 W5500 驱动。3.1 先确认固件版本支不支持 W5500这里必须强调一下不是所有的 MicroPython 固件都内置了 W5500 驱动。较老的官方固件比如 2023 年初的版本在 rp2 port 里并没有 WIZNET5K 这个驱动类很多人拿到老固件之后反复 import 报错还以为是板子坏了所以环境检查一定要做在前面。推荐大家直接去树莓派 Pico 的 MicroPython 官方下载页面选择版本号比较新的 UF2 固件文件。2024 年之后的版本比如 1.23 及以上通常已经原生支持 W5500flash 之后可以现场验证一下import network print(network.WIZNET5K)如果执行后能打印出class WIZNET5K就说明驱动已经内置。如果报AttributeError说明你这版固件没有 W5500 支持就得换一个带该驱动的新固件或者去找 WIZnet 官方发布的固件包。固件烧录流程很简单按住 Pico 板子上的 BOOTSEL 按键用 USB 线接电脑这时电脑会出现一个名为 RPI-RP2 的 U 盘把下载好的 .uf2 文件直接拖进去板子会自动重启MicroPython 就刷进去了。3.2 驱动初始化与网络参数配置固件就绪之后先写一段最基础的初始化代码让 W5500 跑起来并拿到网络参数。import network import time from machine import Pin, SPI # 初始化 SPI0注意引脚要与接线保持一致 spi SPI(0, baudrate20000000, polarity0, phase0, sckPin(18), mosiPin(19), misoPin(16)) # 创建 W5500 网络接口传入 SPI、片选引脚、复位引脚 eth network.WIZNET5K(spi, Pin(17), Pin(20)) eth.active(True) # 配置静态 IP避免依赖 DHCP eth.ifconfig((192.168.1.100, 255.255.255.0, 192.168.1.1, 8.8.8.8)) # 等待链路就绪 for i in range(10): if eth.isconnected(): break time.sleep(1) print(Link status:, eth.isconnected()) print(IP config:, eth.ifconfig())这段代码里需要注意两个点。第一baudrate20000000也就是 20MHz我实际用下来很稳定但如果你用手工飞线或者面包板接线建议先降到 4MHz 验证功能确认没问题之后再逐步往上调。第二官方驱动构造WIZNET5K时传入的复位引脚会在初始化过程中自动拉低再拉高完成复位所以不需要自己在代码里再做额外的复位时序。关于网络参数我推荐直接用静态 IP。DHCP 虽然能省去手动配置的麻烦但在嵌入式场景里往往会引入额外的不确定性——比如 DHCP 分配失败、租约到期没有续期等问题。固定 IP 之后调试和抓包都方便得多一台设备一个 IP一目了然。跑完这段代码如果控制台打印出Link status: True和一组 IP 配置说明 W5500 已经被 MicroPython 正确驱动了。4. TCP 客户端核心代码与完整示例网络接口打通之后真正的重头戏来了——写一个 TCP 客户端主动连接远端的服务器发送数据并接收响应。这个章节我把代码从零到完整示例讲透。4.1 从零实现一个最小 TCP 客户端先看核心逻辑创建一个 Socket设置超时连接服务器发送数据接收数据最后关闭连接。这段代码就是整个 TCP 客户端的骨架。import socket server_ip 192.168.1.50 server_port 8080 # 创建 TCP Socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(5) try: # 主动连接服务器这里会触发 TCP 三次握手 s.connect((server_ip, server_port)) print(Connected to, server_ip, server_port) # 发送数据注意是 bytes 类型 s.send(bHello from Pico W5500!\n) # 接收服务器响应最多接收 1024 字节 data s.recv(1024) print(Received:, data) except OSError as e: print(TCP error:, e) finally: s.close()connect这一步是整个 TCP 客户端的核心它内部完成的就是 TCP 三次握手——客户端发 SYN服务器回 SYNACK客户端再回 ACK。握手成功之后连接才建立才能进行数据收发。用 W5500 的硬件协议栈实现时这些状态转换都发生在芯片内部MicroPython 层只能感知到 connect 成功或者超时。send和recv是阻塞调用所以设置超时很重要。如果不设置超时万一服务器一直不响应程序会一直卡在recv这里整个设备就假死了。settimeout(5)的意思是如果 5 秒内没有新数据就抛出一个超时异常我们可以在这个异常里做重试或重连逻辑。为了验证代码我还写了一个简单的 PC 端 TCP 服务器方便在本地测试# 电脑端运行的 Python 测试服务器 import socket server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((0.0.0.0, 8080)) server.listen(1) print(Waiting for connection...) conn, addr server.accept() print(Connected from:, addr) data conn.recv(1024) print(Received:, data) conn.send(bACK from server!\n) conn.close() server.close()把服务器跑在电脑上Pico 执行上面的客户端代码电脑端会打印出收到的消息然后 Pico 端也会收到服务器的 ACK。这样一个闭环的最小实验就通了。4.2 工程化改造断线重连与心跳但是把上面的代码直接放进实际项目里是要被骂的。真实的网络环境没有这么友好——网线可能被踢到交换机可能断电服务器可能重启。任何一个环节出问题TCP 连接都会断掉而断线之后必须有一套断线重连的机制。下面这个函数封装了带重试的 TCP 连接逻辑import socket import time SERVER_IP 192.168.1.50 SERVER_PORT 8080 def connect_tcp(): while True: try: s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(5) s.connect((SERVER_IP, SERVER_PORT)) print(Connected to server) return s except OSError as e: print(Connect failed:, e, retry in 3s) time.sleep(3)主循环里这样用s connect_tcp() while True: try: # 发送心跳或业务数据 s.send(bping\n) resp s.recv(64) print(Server response:, resp) except OSError: print(Connection lost, reconnecting...) s.close() s connect_tcp() time.sleep(5)这套模式里最关键的思路是一旦send或recv抛出异常就认为连接已经不可用先close掉再重连。不要在一个坏掉的 Socket 上反复send那样只会一直报异常而且会浪费系统资源。close之后重新走connect_tcp()的逻辑反复尝试直到连上为止。实际项目中还可以在重连逻辑里加入最大重试次数、退避算法比如指数退避等策略这里不再展开但至少要做到“断了能自动连回来”。4.3 完整示例向服务器发送传感器数据我拿开头提到的环境监测场景举个例子。假设 Pico 通过 ADC 读取一个模拟温度传感器的电压值然后通过 TCP 每 5 秒推送到 PC 服务器上。import network import socket import time from machine import Pin, SPI, ADC # 1. 初始化 W5500 spi SPI(0, baudrate20000000, polarity0, phase0, sckPin(18), mosiPin(19), misoPin(16)) eth network.WIZNET5K(spi, Pin(17), Pin(20)) eth.active(True) eth.ifconfig((192.168.1.100, 255.255.255.0, 192.168.1.1, 8.8.8.8)) while not eth.isconnected(): time.sleep(1) print(Network ready:, eth.ifconfig()) # 2. 初始化 ADC 和服务器地址 adc ADC(26) SERVER (192.168.1.50, 8080) def connect_tcp(): while True: try: s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(5) s.connect(SERVER) return s except OSError: time.sleep(3) # 3. 主循环采集并推送 s connect_tcp() while True: try: vin adc.read_u16() / 65535 * 3.3 msg temp{:.2f}V\n.format(vin) s.send(msg.encode()) ack s.recv(128) print(ACK:, ack) except OSError: print(Reconnecting...) s.close() s connect_tcp() time.sleep(5)这里我把connect_tcp()和主循环拆开就是想让代码的职责更清晰。第一次启动时建立长连接之后的每一次收发都复用一个 Socket避免频繁建立和断开连接带来的开销也降低了 W5500 硬件 Socket 被占满的风险。W5500 支持多个硬件 Socket但在 MicroPython 的 socket 库封装下我们尽量保持一瓶到底的长连接模式对嵌入式设备来说更稳定。这个例子稍作改动就可以扩展到 Modbus TCP、自定义二进制协议、MQTT 等上层应用。W5500 的硬件协议栈负责把 TCP 数据可靠地传输出去应用层只管解析内容即可。5. 抓包调试与常见问题排查代码写完不代表万事大吉。以太网调试最大的优势就是可以非常直观地抓包验证。这个章节我分享一下怎么用 Wireshark 亲眼看到 TCP 三次握手以及我实际调试中最大的几个坑。5.1 用 Wireshark 亲眼验证 TCP 三次握手Wireshark 是网络调试的必备工具。在 PC 上打开它选择正在监听 TCP 服务器的网卡然后在过滤栏里输入tcp.port 8080接着让 Pico 执行一次 TCP 连接你就能在抓包列表里看到完整的握手过程。正常情况下应该能看到这样三条记录客户端 IP 发起SYNSeq 置为 0表示请求建立连接。服务器回复SYNACKSeq 也为 0同时在 ACK 字段里回了一个 1表示收到对方的 SYN。客户端再发一个ACKAck 字段为 1三次握手完成。抓到这三条之后紧接着就能看到 Pico 发出的数据包以及服务器返回的 ACK 包。从抓包里能看到发送的数据内容、TCP 序号、确认号、窗口大小等一系列信息。这比在代码里打印几条日志要直观得多也更容易定位到底是哪一层出了问题。比如我做第一次数据发送实验时代码里明明发了send(bHello)服务器端却迟迟收不到看抓包发现 TCP 第三次握手的 ACK 包根本没发出去最后排查下来是 SPI 时钟频率太高导致 W5500 丢包。这一点在下一节详细展开。5.2 常见连接故障速查表我把实际调试中遇到过的问题整理成一张速查表方便大家直接对照排错。现象可能原因排查方法eth.isconnected()一直返回 False网线没有插好、交换机端口禁用、ifconfig 未生效检查网线指示灯插拔网线打印 ifconfig 核对 IP 配置import network 后 WIZNET5K 不存在固件版本过旧没有内置 W5500 驱动升级新版本固件或用 WIZnet 官方固件connect() 一直超时服务器 IP 端口写错、防火墙拦截、服务器没开启用电脑 telnet 测试端口通不通检查服务器监听状态send 之后服务器收不到数据但 TCP 连接正常建立SPI 频率过高导致 W5500 丢包或数据格式不对降低 SPI baudrate 到 4MHz 验证检查发送内容是 bytes 类型recv() 卡死不返回没有设置 socket 超时服务器迟迟不应答用 settimeout 明确设置超时时间设备跑一段时间后 TCP 自动断开网络链路中断、服务器主动断开、供电不稳检查网线连接状态查看服务器日志增加断线重连机制这每一条背后都是血泪教训。比如防火墙拦截这个问题开发机上跑着某安全软件默认拦截了外部设备的 TCP 入站连接让我排查了整整一个下午。后来把防火墙关掉之后Pico 蹭地一下就连接成功了。所以遇到连接超时第一反应不要总是怀疑代码先拿电脑或者手机连一下服务器确认服务器的监听端口对外可达。5.3 稳定性和性能优化笔记项目做完之后我又做了一轮稳定性和性能层面的调优这里总结几个关键点给你参考。SPI 频率W5500 数据手册标称最大可以跑到 40MHz但这属于理论极限值实际是否稳定取决于 PCB 布线和信号完整度。在面包板实验阶段20MHz 是我的安全线如果你用的是手工飞线建议直接降到 4MHz 到 10MHz功能稳定后再慢慢提高。socket 超时一定要给每个 recv 设置合理的超时时间否则任何一个网络异常都可能让设备卡死。我在收到异常之后会执行s.close()避免 Socket 资源泄漏然后重新建立连接。数据量控制W5500 内部为每个 Socket 划分了收发缓冲区单次发送的数据量过大时需要分次发送。一般情况下每次 send 几百字节以内的数据是完全没有问题的不需要额外处理。电源稳定上电瞬间和网线插拔瞬间W5500 的电流变化会比较明显供电要给足余量并且加好去耦电容。这里建议在 W5500 模块 VCC 附近并联 10uF 和 0.1uF 电容各一只能极大降低数据错乱的概率。日志要打印到位MicroPython 端连接成功、发送成功、接收成功、异常断开这四个点每个都建议输出一行日志这样配合 Wireshark 抓包能快速定位问题出在哪一层。另外还有一个细节TCP 长连接场景下建议在应用层做心跳。因为 TCP 本身虽然有保活机制Keep-Alive但默认触发时间非常长并不适合嵌入式设备快速感知对端掉线。最简单的心跳协议就是客户端每隔一段时间发送一个心跳包服务器应答如果连续几次都没有应答客户端就主动断开重连。这也呼应了前面断线重连的设计。最后分享一个调试中的小技巧在 Wireshark 里过滤时可以同时用 IP 和端口双重条件比如ip.addr 192.168.1.100 tcp.port 8080这样即使网络里有其他广播包和噪声也能一眼定位到 Pico 与服务器之间的交互。如果你熟悉 Wireshark 的Follow TCP Stream功能点一下就能直接看到完整的数据流分析粘包、拆包、乱序等问题非常方便。我在实际项目中已经用这套 Pico W5500 方案稳定跑了一个多用例中间经历了交换机重启、网线被误拔等不少意外情况断线重连机制都能在几秒之内恢复。如果你也在找一种低成本、高稳定性的嵌入式有线联网方案跟着这篇内容操作一遍基本能把从零到链接、再到断线重连的全链路跑通。接下来我想在这个基础上继续扩展 Modbus TCP 从站功能和 MQTT 客户端让这个硬件平台在工业场景里发挥更大价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价