
1. 项目缘起与整体设计思路1.1 为什么要在 W55MH32-ADK 上折腾一个气象站手头这块 W55MH32-ADK 开发板核心是一颗带以太网控制器的 MCU板载 RJ45 网口天生适合做那种“插上网线就能长期跑”的联网设备。我拿到板子之后一直在想做什么项目既能吃透它的以太网能力又能把 ADC、I2C、定时器这些外设都串一遍。后来一拍脑袋干脆做个室内外气象监测终端室内用 AHT20 测温湿度室外数据从 Open-Meteo 的免费接口拉本地再挂个小屏幕或者网页实时显示。这个项目的好处是它把“本地传感器采集”和“云端数据获取”两条链路揉在一起正好覆盖了嵌入式联网开发里最典型的两种数据来源。这个项目适合谁呢如果你已经玩过一阵子 MicroPython点过 LED、读过传感器但还没正经做过一个“联网 定时任务 数据展示”的完整小系统那这个项目就是为你准备的。它不需要你懂 TCP/IP 协议栈的底层实现但会让你真正理解 HTTP 请求在嵌入式端是怎么发出去、怎么解析、怎么容错的。整个方案的核心关键词就几个W55MH32-ADK、MicroPython、Open-Meteo API、AHT20、Ethernet。把这五个东西串起来一个能 7×24 小时运行的气象站就成型了。1.2 方案选型为什么是 MicroPython 而不是裸机 C很多人一提到以太网开发第一反应是上 STM32 的 HAL 库加 LwIP写一堆回调函数。我早期也这么干过但说实话调试网络协议栈的时间成本太高了改一行代码编译烧录要等半天。选 MicroPython 的理由很直接迭代快。你可以在 REPL 里一行一行地试socket连接、试urequests发请求错了立刻改不用重新编译整个固件。对于这种“传感器 网络 定时逻辑”的应用MicroPython 的开发效率至少是裸机 C 的三到五倍。当然MicroPython 也有代价。它的内存占用比裸机大运行速度慢一些而且不是所有芯片都有官方固件支持。W55MH32-ADK 这块板子能不能跑 MicroPython取决于社区有没有移植好的固件。我实测下来只要固件里包含了network、socket、machine这几个模块剩下的就是写业务逻辑的事了。另一个选型点是Open-Meteo它不需要 API Key免费额度对个人项目完全够用返回的是标准 JSON解析起来比 XML 舒服太多。AHT20 则是性价比极高的 I2C 温湿度传感器精度够用驱动简单MicroPython 下几十行代码就能读出来。1.3 系统架构两条数据链路怎么汇合整个系统的数据流可以拆成三条线。第一条是室内采集线MCU 通过 I2C 总线定时读取 AHT20 的温湿度原始值做一次校准换算存到内存变量里。第二条是室外获取线MCU 通过以太网口向 Open-Meteo 的服务器发 HTTP GET 请求拿到 JSON 格式的天气预报数据解析出当前温度、湿度、风速等字段。第三条是展示与上报线把室内外数据拼成一个结构化的字符串要么通过串口打印出来要么起一个极简的 HTTP 服务端让同一局域网内的手机或电脑用浏览器访问查看。这三条线的调度靠一个主循环加定时器来实现。我的做法是AHT20 每 10 秒读一次Open-Meteo 每 15 分钟请求一次因为室外天气变化没那么快频繁请求既浪费流量也没必要网页服务端则一直监听 80 端口。这里有个关键设计决策网络请求必须做超时和重试。嵌入式设备跑在网线上虽然比 WiFi 稳定但路由器重启、DNS 解析失败、服务器偶尔抽风都是常有的事。如果不做容错一次请求卡死就会让整个主循环阻塞传感器也不读了网页也打不开了。提示主循环里千万不要用time.sleep()做长延时否则整个系统会失去响应。正确做法是用time.ticks_ms()记录时间戳在循环里判断是否到达执行间隔。2. 核心细节解析与实操要点2.1 W55MH32-ADK 的以太网初始化从 PHY 到 IPW55MH32-ADK 的以太网外设包含 MAC 控制器和外部 PHY 芯片MicroPython 里通常通过network.LAN类来操作。初始化的第一步是确认固件是否支持 LAN 模块。你可以在 REPL 里输入import network然后dir(network)如果看到LAN这个属性说明固件带以太网驱动。接下来是配置参数lan network.LAN()然后根据你的网络环境决定用 DHCP 还是静态 IP。家用路由器一般开 DHCP直接lan.ifconfig(dhcp)就行如果是工业现场或者需要固定地址的场合就手动指定lan.ifconfig((192.168.1.100, 255.255.255.0, 192.168.1.1, 8.8.8.8))。这里有个容易踩的坑PHY 地址和时钟配置。不同板子的 PHY 芯片挂在不同地址上有的还需要配置 RMII 或 MII 接口的时钟模式。W55MH32-ADK 的固件一般已经把这些底层参数写死了但如果你自己编译固件就得在mpconfigboard.h里确认MICROPY_HW_ETH_PHY_ADDR和MICROPY_HW_ETH_MDC_PIN这些宏定义是否正确。我遇到过 PHY 地址填错导致lan.active(True)之后一直拿不到 IP 的情况排查了半天才发现是地址对不上。另一个细节是网线质量有些便宜网线的线序有问题插上去链路灯亮但就是不通换根线就好了。初始化完成后用lan.isconnected()判断是否拿到 IP。如果返回False先检查网线、路由器端口再检查lan.status()返回的错误码。MicroPython 的 LAN 模块在连接成功后会打印出分配的 IP 地址记下这个地址后面起 HTTP 服务端的时候要用。2.2 AHT20 驱动I2C 时序与温湿度换算AHT20 走标准 I2C 协议默认地址是0x38。MicroPython 里用machine.I2C初始化总线指定 SCL 和 SDA 引脚。W55MH32-ADK 的引脚定义要看板子丝印一般 I2C1 的 SCL 是 PB6、SDA 是 PB7但不同板子可能不一样用之前务必确认。初始化代码大概长这样from machine import I2C, Pin i2c I2C(1, sclPin(PB6), sdaPin(PB7), freq100000)频率设 100kHz 比较稳妥虽然 AHT20 支持 400kHz但长导线或者上拉电阻偏大的时候高速率容易出错。接下来是读取流程先发一个触发测量命令0xAC后面跟两个参数字节0x33和0x00然后等 80 毫秒再读 6 个字节的数据。这 6 个字节里包含了湿度、温度和 CRC 校验值。换算公式是固定的湿度 (raw_humidity / 2^20) * 100温度 (raw_temp / 2^20) * 200 - 50。原始值是从 20 位数据里拼出来的具体来说第 1 字节是状态字第 2、3 字节和第 4 字节的高 4 位组成湿度原始值第 4 字节的低 4 位和第 5、6 字节组成温度原始值。注意AHT20 上电后需要约 100 毫秒的稳定时间刚上电立刻读会返回错误数据。另外如果传感器放在密闭外壳里读出来的温度会比环境温度高 1 到 2 度这是自发热导致的做室内监测时要么留通风孔要么在软件里做补偿。我实际用下来AHT20 的一致性还不错但湿度读数在极端干燥或潮湿环境下会有偏差。如果对精度要求高可以拿标准温湿度计对比在软件里加一个线性校准系数。驱动代码建议封装成一个类把初始化、触发测量、读数据、换算这几个步骤包进去主程序里只调一个read()方法这样代码干净也好维护。2.3 Open-Meteo API 请求URL 拼接与 JSON 解析Open-Meteo 的接口设计得很友好你只需要提供经纬度它就能返回当前天气和未来几天的预报。请求 URL 的格式是https://api.open-meteo.com/v1/forecast?latitude39.90longitude116.40currenttemperature_2m,relative_humidity_2m,wind_speed_10m这里的current参数指定你要哪些字段用逗号分隔。返回的 JSON 结构里current对象下面就是对应的值。在 MicroPython 里发 HTTPS 请求需要ssl模块支持但 W55MH32-ADK 的固件不一定带 SSL。如果固件不支持 HTTPS有两个选择一是用 HTTP 明文请求Open-Meteo 也支持 HTTP但有些网络环境会拦截二是自己编译带ussl的固件。我建议先试 HTTP如果通就用 HTTP毕竟气象数据不是什么敏感信息。请求的代码逻辑是用socket建立 TCP 连接手动构造 HTTP GET 请求头发送出去然后循环recv直到收到完整响应。这里的关键是解析响应体。HTTP 响应分头部和主体头部和主体之间用\r\n\r\n分隔。你要先把头部读出来找到Content-Length或者判断Transfer-Encoding然后按长度读主体。主体是 JSON 字符串用json.loads()解析成字典再按字段名取值。import socket, json addr socket.getaddrinfo(api.open-meteo.com, 80)[0][-1] s socket.socket() s.settimeout(10) s.connect(addr) s.send(bGET /v1/forecast?latitude39.90longitude116.40currenttemperature_2m HTTP/1.1\r\nHost: api.open-meteo.com\r\nConnection: close\r\n\r\n) resp b while True: chunk s.recv(512) if not chunk: break resp chunk s.close() body resp.split(b\r\n\r\n, 1)[1] data json.loads(body) outdoor_temp data[current][temperature_2m]这段代码里settimeout(10)很重要防止网络卡死时无限等待。Connection: close告诉服务器返回完就断开这样recv循环才能正常结束。如果服务器返回的是分块传输编码解析会复杂一些但 Open-Meteo 默认返回Content-Length处理起来简单。2.4 数据展示串口输出与网页服务端最简单的展示方式就是串口打印把室内外温湿度拼成一行格式化字符串每 10 秒输出一次。但既然板子有网口不起个网页服务端就浪费了。MicroPython 起 HTTP 服务端的套路是绑定0.0.0.0:80循环accept客户端连接读到请求行后返回一个固定的 HTML 页面页面里嵌入当前传感器数据。HTML 可以用简单的 meta 刷新实现自动更新比如meta http-equivrefresh content10这样浏览器每 10 秒重新请求一次数据就刷新了。import socket srv socket.socket() srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((0.0.0.0, 80)) srv.listen(1) while True: cl, addr srv.accept() cl.settimeout(2) try: req cl.recv(1024) html htmlbodyh1Weather/h1pIndoor: %.1f C / %.1f %%/ppOutdoor: %.1f C/p/body/html % (in_temp, in_hum, out_temp) cl.send(bHTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n html.encode()) except: pass cl.close()提示SO_REUSEADDR一定要设否则服务端重启时可能报“地址已被占用”。另外accept之后要设超时防止某个客户端连上不发数据把服务端卡住。这个网页服务端很简陋但胜在稳定。如果你想做得好看点可以把 HTML 写得更复杂加 CSS 样式甚至用 JavaScript 定时 fetch 数据。但要注意 MicroPython 的内存有限HTML 字符串别太大否则容易内存溢出。3. 实操过程与核心环节实现3.1 硬件连接与上电检查先把 W55MH32-ADK 用网线连到路由器AHT20 用杜邦线接到 I2C 引脚。接线的时候注意 VCC 和 GND 别接反AHT20 是 3.3V 供电接 5V 会烧。上电后先看板子上的电源灯和链路灯链路灯亮说明 PHY 和路由器协商成功。然后用 USB 转串口线连到板子的调试串口打开串口终端波特率一般是 115200按复位键看有没有 MicroPython 的启动信息。如果能看到提示符说明固件正常。接下来在 REPL 里逐条测试。先import network再lan network.LAN()然后lan.active(True)等几秒看lan.ifconfig()返回的 IP。如果 IP 是0.0.0.0说明 DHCP 没成功检查网线和路由器设置。拿到 IP 后用ping命令从电脑 ping 一下板子确认网络连通。这一步很关键很多人后面 HTTP 请求失败根源就是网络根本没通。3.2 传感器读取与数据校准网络通了之后接上 AHT20 测试。在 REPL 里初始化 I2C扫描地址i2c.scan()如果返回[56]也就是 0x38说明传感器被识别到了。然后手动发命令读一次数据把原始值打印出来对照公式算一下看结果是否合理。室温下温度应该在 20 到 30 度之间湿度在 30% 到 70% 之间。如果读出来是 0 或者 255检查接线和上拉电阻。AHT20 模块一般自带 4.7k 上拉电阻如果没有需要在 SDA 和 SCL 上各接一个 4.7k 到 3.3V。校准这块我的经验是温度基本不用校湿度可以做个偏移。拿一个靠谱的温湿度计放在旁边同时读 AHT20如果湿度差 5% 以上就在代码里加个偏移量。比如 AHT20 读 45%标准计读 50%那就在换算结果上加 5。这个偏移不是线性的但在常见范围内够用。3.3 Open-Meteo 请求的完整实现与容错把前面说的 socket 请求代码封装成一个函数fetch_weather()返回一个字典。函数里要做几件事DNS 解析、TCP 连接、发送请求、接收响应、解析 JSON、提取字段。每一步都要包在try/except里任何一步失败就返回None主循环里判断如果返回None就沿用上一次的数据并记录错误次数。def fetch_weather(): try: addr socket.getaddrinfo(api.open-meteo.com, 80)[0][-1] s socket.socket() s.settimeout(10) s.connect(addr) req GET /v1/forecast?latitude39.90longitude116.40currenttemperature_2m,relative_humidity_2m,wind_speed_10m HTTP/1.1\r\nHost: api.open-meteo.com\r\nConnection: close\r\n\r\n s.send(req.encode()) resp b while True: chunk s.recv(512) if not chunk: break resp chunk s.close() body resp.split(b\r\n\r\n, 1)[1] data json.loads(body) return { temp: data[current][temperature_2m], hum: data[current][relative_humidity_2m], wind: data[current][wind_speed_10m] } except Exception as e: print(fetch error:, e) return None经纬度要换成你所在城市的Open-Meteo 的精度到小数点后两位就够了。请求频率我设的是 15 分钟一次用time.ticks_ms()做计时每次主循环检查是否超过 900000 毫秒。如果请求失败下次循环会重试但不要失败后立刻重试否则可能被服务器限流。我的做法是失败后等 60 秒再试。3.4 主循环调度与网页服务端整合主循环的结构是这样的先检查网络是否连接如果断了就重新初始化 LAN然后检查是否到了读 AHT20 的时间到了就读一次再检查是否到了请求 Open-Meteo 的时间到了就请求一次最后处理网页服务端的accept。这里有个矛盾accept是阻塞的如果一直等客户端连接就没法执行其他任务。解决办法是给服务端 socket 设一个很短的超时比如 100 毫秒超时后抛异常捕获后继续执行循环其他部分。srv.settimeout(0.1) last_sensor time.ticks_ms() last_weather time.ticks_ms() while True: now time.ticks_ms() if time.ticks_diff(now, last_sensor) 10000: in_temp, in_hum aht.read() last_sensor now if time.ticks_diff(now, last_weather) 900000: w fetch_weather() if w: out_temp w[temp] out_hum w[hum] last_weather now try: cl, addr srv.accept() # 处理请求... except OSError: pass这个结构跑起来之后系统就能同时做三件事定时读传感器、定时拉天气、随时响应网页请求。实测下来连续跑 24 小时没有出现内存泄漏或死机稳定性可以接受。4. 常见问题与排查技巧实录4.1 网络连不上从链路层到应用层逐级排查网络问题是最常见的排查要按层次来。第一层是物理层网线插好了吗链路灯亮吗换根网线试试。第二层是数据链路层lan.active(True)之后lan.status()返回什么如果是STAT_NO_LINK说明 PHY 没协商上检查 PHY 地址配置。第三层是网络层lan.ifconfig()拿到 IP 了吗如果是0.0.0.0说明 DHCP 失败试试静态 IP。第四层是应用层能 ping 通网关吗能 ping 通api.open-meteo.com吗如果 DNS 解析失败试试直接用 IP 地址请求。我遇到过一种情况板子能拿到 IP能 ping 通网关但 HTTP 请求就是超时。后来发现是路由器的防火墙拦了出站 80 端口换成 443 或者换个网络环境就好了。还有一种情况是 DNS 服务器响应慢getaddrinfo卡住解决办法是设一个短的 socket 超时或者直接用 IP 地址。4.2 AHT20 读数异常从接线到时序的检查清单AHT20 读出来全是 0 或者 255按这个清单查接线是否松动VCC 是不是 3.3V上拉电阻有没有I2C 地址对不对i2c.scan()能不能扫到如果扫不到换一组 I2C 引脚试试。如果扫到了但读数不对检查触发命令后的延时够不够 80 毫秒。我试过延时 50 毫秒读出来就是错的加到 80 毫秒就正常了。还有一种情况是读数偶尔跳变比如温度突然从 25 度跳到 80 度。这通常是 I2C 通信受到干扰可能是导线太长或者旁边有电机之类的干扰源。解决办法是缩短导线、加屏蔽、降低 I2C 频率到 50kHz。如果还不行就在软件里做中值滤波连续读三次取中间值。4.3 内存不足与请求超时MicroPython 的资源管理MicroPython 在 MCU 上跑内存是稀缺资源。如果你发现程序跑一段时间后报MemoryError大概率是字符串拼接或者 JSON 解析产生的临时对象没被回收。解决办法是尽量复用缓冲区不要频繁创建大字符串手动调用gc.collect()在每次请求完成后强制垃圾回收减小 HTML 页面大小别把整个网页都塞进内存。请求超时的问题除了网络本身还可能是recv循环没有正确结束。如果服务器返回的响应没有Content-Lengthrecv会一直等到连接关闭才返回空这时候如果服务器不主动关连接就会卡死。解决办法是设settimeout超时后抛异常捕获后关闭 socket。另外Connection: close这个请求头一定要加它告诉服务器返回完就断开这样recv才能正常结束。4.4 常见问题速查表现象可能原因排查方法解决措施链路灯不亮网线坏 / 路由器端口坏换网线、换端口更换硬件拿不到 IPDHCP 失败 / PHY 配置错查lan.status()改静态 IP 或修 PHY 地址DNS 解析失败DNS 服务器不可达直接 ping DNS改用 IP 直连或换 DNSHTTP 超时防火墙拦截 / 服务器无响应换端口、换网络加超时和重试AHT20 读数为 0接线错 / 无上拉i2c.scan()检查接线和电阻读数跳变干扰 / 时序不稳示波器看波形降频、加滤波内存不足临时对象过多看gc.mem_free()加gc.collect()网页打不开服务端阻塞 / IP 错查 IP 和端口设 accept 超时这张表是我踩坑之后总结的基本上覆盖了 90% 的常见问题。遇到新问题的时候先按层次定位再对照表格找原因比盲目试错快得多。5. 长期运行优化与扩展思路5.1 看门狗与异常恢复一个要 7×24 小时跑的设备必须考虑死机恢复。W55MH32 的硬件看门狗在 MicroPython 里可以通过machine.WDT来操作。初始化的时候wdt WDT(timeout8000)然后在主循环里定期wdt.feed()。如果程序卡死超过 8 秒看门狗就会复位芯片系统重新启动。这个机制能解决大部分偶发的死锁问题。但看门狗不是万能的如果复位后网络初始化又失败设备就会陷入“复位—失败—复位”的循环。所以还要加一个启动重试逻辑网络初始化失败后等 5 秒重试重试 3 次还不行就再等 30 秒。另外把关键配置比如 WiFi 密码、经纬度存在文件里复位后能自动读取不用手动干预。5.2 数据记录与历史曲线现在系统只显示当前值如果想看历史曲线可以把数据写到文件里。MicroPython 支持简单的文件操作每次读传感器后把时间戳和数值追加到 CSV 文件。但要注意 flash 的写入寿命不能太频繁建议每 5 分钟写一次。文件攒够一定大小后可以通过网页服务端提供一个下载链接把 CSV 导出来用 Excel 画图。如果想做得更专业可以在网页端用 JavaScript 的 Chart.js 画实时曲线。数据通过一个/data接口以 JSON 格式返回前端定时 fetch 并更新图表。这样整个系统就从一个简单的监测终端升级成了带可视化的小型气象站。5.3 多传感器扩展与功耗考量W55MH32-ADK 的 I2C 总线可以挂多个设备除了 AHT20还可以加 BMP280 测气压、BH1750 测光照。每个传感器写一个驱动类主循环里依次读取数据拼成更大的 JSON。这样室内环境监测就更全面了。功耗方面如果设备是插网线供电的那不用太担心。但如果想用电池加网线就得考虑低功耗模式。MicroPython 支持machine.lightsleep()可以在两次采集之间让 MCU 休眠。不过以太网 PHY 在休眠时的功耗也不低真要低功耗可能得换成 WiFi 或者 LoRa 方案。这个就看具体需求了室内固定安装的话一直插着电是最省事的。5.4 固件定制与 OTA 升级的可行性如果你用的 MicroPython 固件缺少某些模块比如ssl或者urequests可以自己编译固件。编译流程是拉取 MicroPython 源码进入ports/w55mh32目录如果有的话配置mpconfigboard.mk然后make。编译出来的.bin文件通过串口或者 DFU 烧进去。这个过程有点折腾但一次配好之后后面加功能就方便了。OTA 升级在 MicroPython 上也能做思路是设备启动时检查服务器上有没有新版本文件有就下载下来覆盖本地的.py文件然后重启。但要注意覆盖文件的时候如果断电可能导致文件损坏。稳妥的做法是下载到临时文件校验通过后再替换。这个功能对个人项目来说有点过度设计但如果你要部署多个节点OTA 就能省很多事。6. 个人实操体会与几个小技巧这个项目我从搭硬件到跑通前后花了大概一个周末。最大的体会是嵌入式联网开发80% 的时间花在排查网络和电源问题上真正写业务逻辑的时间不到 20%。所以前期一定要把网络连通性测试做扎实别急着写上层代码。另外MicroPython 的 REPL 是调试利器遇到问题先在 REPL 里逐条命令试比在完整程序里加 print 高效得多。再分享几个小技巧。第一给板子配一个带开关的 USB 线调试的时候不用反复插拔按开关就行。第二在代码里加一个boot.py把网络初始化放在里面这样每次复位后自动联网不用手动敲命令。第三用os.listdir()看看 flash 里有什么文件有时候残留的旧文件会干扰新程序运行定期清理一下。第四Open-Meteo 的经纬度可以用 IP 定位自动获取但那个接口有时候不准手动填城市坐标更可靠。最后说一个扩展方向如果你觉得网页服务端太简陋可以把数据推到本地的 MQTT 服务器再用 Node-RED 或者 Grafana 做可视化。这样整个系统就从“能看”升级到“好看且好用”了。不过那是另一个项目的事了先把基础版跑稳再说。