
先交代一下背景。早前我桌面上堆了三样东西一个温度计、一个湿度计、一个小爱音箱。每天早上看天气要依次扫过去还要掏手机看降雨概率信息碎得不行。后来干脆花两个周末用常见的开发板和传感器做了个不到拳头大的桌面小盒子从一个面看温度湿度、气压趋势另一个面亮着当前天气图标旁边颜色会随着温度变化。这个项目也没什么神秘技术核心是几个传感器、一块LED点阵、一个能联网的主控再套一个自己打印的立方体外壳。做完之后我把这玩意儿叫 Smart Weather Cube。这篇想把从选型、组装到写代码的完整过程都整理出来给想自己动手做一个智能桌面天气终端的人当个参考。如果你正好手头有ESP32或者树莓派Pico这类板子又想给桌面添个有点科技感又不至于太复杂的小设备那这篇文章应该正合适。没有PCB设计经验也能做传感器模块都是现成的接线用杜邦线就能搞定代码逻辑我也会讲清楚不会只扔给你一段能跑就行的脚本。1. 为什么是“魔方”从使用场景反推产品形态做东西之前我习惯先想清楚到底要解决什么问题。天气数据本质上就是那三五个数温度、湿度、气压、天气现象、降雨概率。放在手机上没问题问题是我经常懒得解锁屏幕或者解锁之后看了眼微信就忘了去看天气。它需要一个常驻物理空间、一抬眼就能读到的载体。这个载体最好安静、不打扰、不需要交互但信息密度又足够高。当时想过几个形态一是水平底座上放个球像米家温度计那种但只能显示一两个数据二是做成卡片式的电子墨水屏省电是省电但刷新慢、屏幕小视觉上有点单调三是竖直小屏幕信息放得下可放在桌上总觉得像块小广告牌。最后定了立方体主要原因是它有六个面天然适合分区展示信息——上下面放传感器前面放天气图标和温度颜色指示左右两侧做通风孔背面放充电口和开关物理上逻辑很清晰。外形定了尺寸也需要较真。太小的立方体比如3厘米见方传感器和电池还有屏幕根本塞不下LED点阵的间距也会挤得看不清太大的立方体又失去了桌面设备该有的内敛感。我最后定的是7厘米见方。这个尺寸下一颗1.69寸的LCD屏或8×8的WS2812B点阵放前面刚好侧面能塞下18650电池仓或USB供电口内部走线也有余量。如果只做显示不做电池5厘米其实也能做但线会很挤不建议首次做就挑战。还有个容易忽略的细节桌面上凡是灯光特别亮、形状特别显眼的设备多少都会干扰注意力。天气魔方的默认亮度应控制在室内环境光之下尽量只在靠近、翻转或轻拍时才短暂提高亮度。我后面在代码里也把亮度曲线调低了这是单纯看产品图时想不起来的点。2. 硬件选型这套组合我用得最顺手这一节把核心器件列一下并说明我为什么这样选。不会搞很长的配置清单重点说思路以及几个容易踩的坑。2.1 主控ESP32-S3 还是 RP2040主控这块我在ESP32-S3和树莓派Pico W之间犹豫过一段时间。Pico W的优点是Python支持好、功耗略低缺点是WiFi连接的稳定性一般而且受浮点性能限制计算气压趋势这种任务虽能跑但不爽快。ESP32-S3的优点是WiFi和BLE都稳外设丰富有硬件I2C、SPIFlash和PSRAM容量也充裕想刷个小屏幕或跑简单动画都不会捉襟见肘。我最终选了ESP32-S3。原因很简单这项目需要频繁联网拉天气数据、解析JSON偶尔还要根据传感器数据做滤波和趋势判断ESP32-S3的240MHz双核跑起来轻松很多。另外一个重要原因是Arduino生态对ESP32-S3的支持已经很成熟库也齐全开发效率高。如果你只做本地监测不需要联网那用RP2040或STM32也完全没问题成本还能更低。提示如果选ESP32主控建议买带外部天线的版本不要买那种PCB天线的塞进立方体外壳后信号衰减特别明显。我的第一版就是吃了这个亏盒子放桌面上WiFi信号从两格掉到一格换成外部陶瓷天线后稳定多了。2.2 传感器组合温湿度、气压、环境光温湿度传感器我用了SHT30。这颗芯片精度在±0.3°C和±2%RH左右典型值I2C接口地址是0x44。市面上有不少模块会把地址做成可选的方便挂多颗。温湿度这个数据看似简单实际容易掉坑SHT30放在壳体内部如果旁边是发热的电源芯片或LED测出来的温度能比桌面真实温度高两三度。所以传感器的位置要尽量靠近进气孔远离发热源。气压传感器选的是BMP390。这是目前性价比比较高的气压计绝对精度约±0.5 hPaI2C/SPI都支持。气压数据的作用有两个一是辅助判断天气趋势持续下降通常意味着低压系统接近降雨概率上升二是可以换算海拔高度——不过我们放在桌面上用海拔基本固定主要看变化趋势。BMP390在低功耗模式下电流只有几微安可以一直开着采样对整体功耗影响很小。环境光传感器用BH1750。这货是I2C接口地址0x23部分模块是0x5C能输出1到65535勒克斯分辨率也够用。为什么需要它因为早上太阳晒进来和晚上开台灯同一个LED点阵亮度看起来会差很多。固定亮度要么白天看不清要么晚上刺眼。有环境光传感器后代码可以根据lux值自动调节亮度和色温映射。2.3 显示方案LED点阵加小屏的组合逻辑显示方案我试过几种最后保留的是混合组合前脸用一块8×8的WS2812B RGB点阵做天气图标旁边放一块1.69寸的LCD屏显示数字信息温度、湿度、气压、更新时间当然你只想要一个屏把东西全塞进去也行但分开有个好处——图形信息天气类别和数字信息具体数值互不干扰人的视线扫一眼就能各取所需。WS2812B点阵的驱动原理很简单用单总线协议给每个灯珠发24bit颜色数据GRB顺序一颗一颗串下去。驱动时注意不要长时间全亮全白全亮状态下64颗灯的总电流可能到1.28A对USB口的电流压力不小。我实际使用时通常限制整屏峰值电流在200mA以内通过软件做亮度缩放和颜色阈值来实现。LCD屏我选的是ST7789驱动方案的1.69寸TFT分辨率240×280显示细腻程度足够。驱动用SPI接口只需要占用几个GPIO和LED点阵的数据引脚分开避免互相干扰。屏幕刷新率不需要高天气数据变化很慢1秒刷新一两次足够了。2.4 供电与结构件供电方面最简单的方案是USB-C口供电5V输入耗流控制在300mA以内。如果想内置电池建议用18650电芯加一颗充电管理芯片比如IP5306或TP4056。不过内置电池会让外壳设计复杂不少还要担心锂电安全对第一版项目来说不太划算。我就使用USB-C供电功耗控制在0.8W左右插根线放在桌上干净又稳定。外壳是3D打印的材料用PLA或PETG都行。PLA外观好打也容易调参耐热性差一点但桌面设备环境舒适问题不大。注意通风孔和传感器暴露区域的布局详见后面第6节。3. 传感层设计数据的精度比想象中更难保证拿到传感器模块后很多人第一步就是接好线跑样例代码看着串口出来的数据觉得一切正常。但这恰恰是最容易出问题的阶段。我这次在传感器部分就踩了好几个坑而且全是第一次做时根本想不到的。3.1 I2C地址冲突与总线复用SHT30地址0x44BH1750地址0x23BMP390默认地址是0x77部分模块可通过SDO引脚改成0x76。这三颗芯片放同一个I2C总线上地址不冲突可以直接一起挂。但如果以后再加其他传感器比如SGP30空气传感器地址0x58就需要留意地址冲突。办法是先用一个I2C扫描程序Wire库的scan示例把所有在线设备地址打印出来再在代码里逐一确认。还有一个常见的坑I2C总线长度。如果传感器模块和主控之间用10cm以上的杜邦线连接信号完整性和上拉电阻都容易出问题。表现为设备偶尔掉线或读出的数据随机跳变。我在项目里用了一根差不多15cm的线连接前脸的屏幕和内部的主控板结果气压数据偶尔会蹦出几个离谱的异常值。后来把传感器模块和主控靠近到5cm以内数据就干净了。注意I2C总线上每个设备需要4.7kΩ左右的上拉电阻到VCC大多数模块板载已经有了。但如果模块上没有或你自己飞线连接多个设备就要在总线上加合适的上拉电阻否则时钟和数据线上噪声会很大。3.2 温湿度传感器的发热误差与校准SHT30自身工作电流很低自热效应不明显。真正发热的是旁边的ESP32-S3主控它的WiFi天线开启时峰值电流能到300mA以上发热可观如果传感器紧贴着主控温度读数会偏高0.5到1°C。而这个误差在日常使用中很难察觉除非你拿一个水银温度计做对照。我的解决办法是让传感器通过一小段排线单独安装在外壳侧面的通风孔附近和主控至少保持5cm距离。温度校准还有个土办法在同样的环境里放一个已知可靠的温度计记录传感器读数然后做一个两点校准。比如同样在25.0°C和30.0°C的恒温环境下对比传感器读数偏差得到一个线性修正方程再在代码里把偏移值补偿掉。湿度校准就没那么容易因为标准湿度源不好弄我一般只在代码里做一个单点偏移修正约±3%RH以内的误差就满意了。3.3 气压趋势原始数据没法直接看气压数据本身的波动不小室内开空调、开关门都会引起短时气压抖动。如果直接拿原始值算趋势结果会非常跳。我在代码里做两层处理一是滑动平均滤波取最近10分钟的气压数据做平均值二是在此基础上算每小时变化率比如过去1小时内气压变化超过1 hPa且方向向下就判定为“趋势走低降雨可能性上升”。滑动平均的实现开销很小用一个循环数组存最近N个采样值就行。滤波窗口的选择需要权衡窗口太短噪声大窗口太长趋势滞后。实测下来10分钟窗口比较合适。更新频率设为每30秒采一次窗口20个点刚好覆盖10分钟。4. 本地感知与网络天气数据的融合策略这块是Smart Weather Cube的核心逻辑。本地传感器只能感知“当前正在发生的天气”比如温度突然升高、湿度上升、气压骤降这些规律可以通过变化趋势推断但没法告诉你今天下午是晴天还是阵雨更没法提前知道你所在位置的精确天气预报。所以必须把本地感知和网络数据融合在一起。4.1 网络天气数据来源Open-Meteo现在很多天气API要么限制调用次数要么需要注册、填KEY对个人DIY项目来说都是负担。我用的免费接口是Open-Meteo不需要API Key直接按经纬度请求即可。它的数据来源是各大气象模型如ECMWF、GFS每天多次更新覆盖全球免费使用是它最大的优点。请求URL大致长这样我用的方式是构造一个包含经纬度和参数配置的GET请求然后解析返回的JSON。Arduino环境下我用的是HTTPClient库加ArduinoJson库ArduinoJson负责解析返回数据具体解析过程不复杂映射成结构体成员后显示端直接读取就行。GET https://api.open-meteo.com/v1/forecast?latitude39.90longitude116.40current_weathertruehourlytemperature_2m,relativehumidity_2m,rainOpen-Meteo返回的JSON里current_weather部分有温度、风速、天气代码和时间。hourly部分可以拿到未来逐小时数据适合做趋势图或多时段显示。我主要在魔方里显示当前天气和未来3小时的降雨概率用hourly数据比current_weather更合适。4.2 天气代码映射把WMO代码变成看得懂的图标Open-Meteo返回的是一个WMO天气代码数字比如0表示晴天1表示大致晴朗2表示多云3表示阴天45表示雾51表示毛毛雨61表示小雨63表示中雨80表示阵雨等等。直接把数字显示出来肯定不行所以我的代码里有一个映射函数把天气代码映射到一组我自己定义的图标编号然后根据图标编号点亮对应的LED点阵图案。这个映射逻辑很直接类似下面这样伪代码int mapWeatherCodeToIcon(int code) { if (code 0) return ICON_SUNNY; if (code 1 code 2) return ICON_PARTLY_CLOUDY; if (code 3) return ICON_OVERCAST; if (code 51 code 67) return ICON_RAIN; if (code 80 code 82) return ICON_RAIN; if (code 95) return ICON_STORM; return ICON_DEFAULT; }天气图标的像素图案我是在8×8点阵上一个个“画”出来的。比如太阳就是中心一个亮色小方块加四周八条射线云就是三四个像素块连在一起。这部分没什么技术含量纯粹是像素美术耐心活。每个像素样式用64个uint8_t字符数组定义每像素对应一个颜色索引颜色索引再映射到具体的RGB值。4.3 更新策略与离线降级网络请求频率不用太高。天气数据变化很慢我设置每30分钟请求一次网络。如果请求失败或超时不直接清空屏幕显示一个错误页而是保留上一轮的数据并标记一个“数据过期”状态。在LCD右上角显示一个小灰点表示网络不在最新状态。如果本地传感器正常工作这些实时数据仍然有效只是天气预报部分可能滞后。为了进一步优化体验我把网络天气数据按“小时”缓存起来每次请求拿到未来24小时的逐小时数据显示时按当前时间索引取值这样即使一整天都不重新请求当前时段的数据也依然可用。这个策略对耗电、对稳定性的帮助都非常大。4.4 本地与网络的冲突处理有个别情况下本地传感器读到的天气和网络预报会“打架”。比如网络显示晴天但本地湿度从40%快速升到80%气压下降明显这说明可能正在下雨只是预报还没覆盖到。我的处理方式是天气图标仍然以网络为准但在LCD边上显示一个“本地感知预警”的小标志提示“局地天气正在变化”。同时把本地气压趋势作为一种辅助判断只有当网络数据缺失时才把本地判断提升为主显示。这种“网络为主、本地为辅”的融合策略既保证了信息准确又保留了本地感知的实时性。纯粹依赖网络你的设备就只是个显示屏纯粹依赖本地传感器又无法预测未来一两小时的天气。5. 显示与交互六面体的信息分区和亮度管理硬件是骨显示是皮。信息怎么排、怎么按优先级呈现决定了这个魔方用起来顺不顺手。5.1 前脸布局图标 数字双区我把立方体的前脸分成两个区域左上块是8×8 LED点阵用来显示天气图标右侧和下方是一块LCD屏用来显示温度、湿度、气压、更新时间。为什么不像很多设计那样让LED点阵占满整个面因为如果一整个面都是像素灯显示数字会非常吃力而且LED像素密度有限小字看不清。LED点阵适合低频变化的图形信息LCD适合高频变化的数字信息各司其职。5.2 颜色映射与亮度自适应温度颜色映射是个可以做得很有设计感的细节。我的规则是低于10°C蓝色系RGB 60, 120, 25510~20°C青色系RGB 0, 200, 20020~26°C绿色系RGB 0, 220, 10026~32°C橙色系RGB 255, 160, 40高于32°C红色系RGB 255, 60, 50映射函数如下插值部分让颜色过渡平滑void setTempColor(float temp) { int r 0, g 0, b 0; if (temp 10) { r 60; g 120; b 255; } else if (temp 20) { r 0; g 200; b 200; } else if (temp 26) { r 0; g 220; b 100; } else if (temp 32) { r 255; g 160; b 40; } else { r 255; g 60; b 50; } setCubeGlow(r, g, b); }亮度管理上我读BH1750的环境光值后映射到一个0到255范围内的全局亮度系数。白天室内300~800 lux亮度系数设为30%左右夜晚10 lux设为3%左右。WS2812B的全局亮度其实不需要在灯珠数据上做缩放只要在写颜色值时乘以一个0~1的系数即可。这样既保证可见性又不会亮得晃眼。5.3 交互翻转和轻触魔方的交互不需要太多我给前脸装了一个电容触摸传感器轻触一下切换显示模式默认模式是“当前天气温度湿度”轻触一次切换为“24小时温度曲线”再轻触切换到“气压历史曲线”第三次轻触回到默认。用触摸而不是按键或翻转检测最大的好处是机械结构简单不需要开孔装按钮外壳更完整。如果你想让交互更“魔方”可以考虑加加速度计检测翻转动作。我最初测试过这个方案翻转一下切换显示模式听起来炫酷实际用起来容易误触发比如随手拿起来看背面时屏幕就切换了。后来还是改回了触摸方案稳定性好很多。5.4 断电记忆与开机动画使用中我发现每次断电重启后魔方都会丢失上一轮的显示模式重新回到默认。如果你只是放在桌面上不管这倒无所谓。但如果你习惯夜间调成暗色模式掉电后一切重置体验会差一些。所以我用ESP32的NVS非易失存储保存了当前显示模式、亮度系数和天气数据缓存每次开机时先读出来恢复状态再启动网络刷新。这个细节简单却能显著提升日常使用的舒适度。6. 外壳设计与组装让立方体真正像个立方体外壳的设计可能被很多人低估。实际上外壳决定了设备散热、传感器通风、显示可视角度、甚至是信号强度直接影响使用体验。我这次用OpenSCAD建模3D打印前前后后改了四版。6.1 通风孔与传感器布局传感器的数据准确性对外壳的通风设计要求很高。我把SHT30和BMP390放在立方体的右侧面靠下位置在这个位置开了一圈直径3mm的小孔约20个形成自然对流。自然对流能让传感器周围的空气和外界环境快速交换温度读数更接近真实环境而不是机壳内部的闷热空气。主控发热最大的地方是ESP32-S3的WiFi射频部分我把它放在左侧面右侧传感器区域尽量远离它。散热处理上我在主控芯片和外壳之间贴了一小块导热硅胶垫把热量导到外壳的铝合金或PETG表面辅助散热。温度读数这样校准后误差在0.5°C以内是能做到的。6.2 光扩散与窗口设计LED点阵如果直接暴露在空气中每个灯珠都是一颗亮刺眼的光点看起来很不柔和。我做了两层处理一是给LED灯珠前面加一层2mm厚的白色亚克力扩散板让光均匀散开二是亚克力板和LED矩阵之间留出5mm的间距给光一定的混合空间。这样处理后的效果单个像素点几乎不可见整个点阵呈现柔和统一的图像感观感提升非常明显。LCD屏那边则不需要扩散板直接开一个精准的矩形窗口即可窗口边缘留0.5mm的间隙防止按压时摩擦。如果3D打印的精度不够窗口周围容易出现毛边建议用丙烯酸涂层做一层表面处理。6.3 组装顺序与走线管理组装顺序上我总结了一套固定流程先把传感器模块固定到外壳右侧的传感器槽位上用少量热熔胶固定注意方向和线缆出口接着安装LCD屏和LED点阵到前脸内侧屏用双面胶点阵用3D打印的卡扣然后固定主控板主控板用铜柱支撑避免PCB背面焊点短路最后接线并用扎线带松耦合固定走线避免线缆压到传感器或屏幕排线。走线管理是新手最容易忽略的。如果线束乱成一团不仅影响散热还会在安装外壳时把某个排线扯断。建议每根线都留出足够长度但不要超量用热熔胶在几个关键点做应力释放这样以后检修翻盖时不会扯断线。6.4 外壳版本迭代中的实测问题第一版外壳我做得太严实只有一个很小的进线孔结果不到半小时温度读数就比桌面高了两度。我拆开用手摸了一下发现主控附近的外壳表面明显发烫这才意识到散热通风不是小事。第二版加了通风孔温度读数明显改善。第三版又暴露了一个信号问题PCB天线刚好被一块外壳塑料支架挡住WiFi信号衰减严重。于是把天线改到外壳顶部伸出或换成外置天线接口信号才稳定下来。所以每一版外壳打样后都建议不要急着量产先跑一天的持续监测确认温湿度和WiFi都稳定了再定稿。7. 实测数据连续一周的稳定性追踪项目做完之后我把它放在办公室桌面上连续跑了7天每小时记录一次数据主要检验三件事数据准确性、网络稳定性、功耗是否可控。7.1 温度和湿度对比数据我拿了一个水银温度计和一个校准过的电子湿度计放在旁边做参照。结果如下时间点SHT30读取参照温度计偏差09:0024.8°C24.5°C0.3°C12:0025.6°C25.2°C0.4°C15:0026.1°C25.7°C0.4°C18:0024.3°C24.1°C0.2°C湿度偏差在±3%RH以内整体符合SHT30的标称精度。下午时段偏差略大可能是因为旁边的设备发热和阳光直射外壳内部的温度梯度稍高。加了通风孔和校准公式后这个偏差完全在可接受范围内。7.2 气压趋势与天气判断气压数据我拉出来和当地气象台的报告对比。连续几天如果BMP390的气压曲线整体平稳天气也确实没变化有一次气压在6小时内下降了约2.2 hPa当天傍晚果然下了一场阵雨。这种趋势判断的准确性没法用数据严谨验证但至少说明本地传感器确实能提供天气预报之外的“实时天气感知”。7.3 网络刷新与离线恢复我统计了一下7天内网络请求的成功率大概在97%左右。剩余3%的失败主要发生在路由器重启、网络短暂断连的时段。离线降级策略表现不错即使某次请求失败设备依旧能显示上一轮的天气数据且本地传感器数据完全不受影响。等到下一次请求周期成功时数据自动恢复更新全程不需要人工干预。7.4 功耗实测用USB电流表测了下正常待机无WiFi传输时整机电流约120mA5V功耗0.6WWiFi传输瞬间可以到280mA但持续时间不到一秒。按每天144次请求每10分钟一次实际上30分钟一次平均每日48次请求来算平均功耗不到0.7W一天下来不到17Wh。如果是接USB电源几乎可以忽略不计。如果以后换电池方案按2000mAh锂电来算理论上能撑两天多但会受WiFi频率影响所以对桌面设备来说插电方案其实是更明智的选择。8. 踩坑记录与改进方向最后一节我把这几次迭代里最典型的弯路和后续可能的升级方向整理一下。8.1 典型问题LED发热导致的温度漂移最初版本里我把SHT30放在了前脸LED点阵的背后。这个位置离LED灯珠只有几毫米LED一旦点亮灯珠温升会直接辐射到传感器上。实测在持续显示彩色动画10分钟后温度读数比实际高了1.8°C。发现问题后我把传感器移到侧面通风孔附近并和LED区域之间加了一块1mm厚的隔热片普通塑料片就够问题才解决。这个坑很典型提醒大家LED虽然单个功率不大但几十颗汇聚在一起就是不小的热源。布局时务必把热源和传感器物理隔离数据准确比什么都重要。8.2 典型问题LCD屏的显示残留ST7789屏幕上如果长时间显示静态画面会出现轻微的残影。这在天气魔方这种信息变化慢的设备上特别明显。解决办法有两个方向一是定期做一次全屏刷新比如每30秒把缓存的整屏数据重新写一遍能显著减轻残影二是在代码里把数字区域设计成动态刷新比如每隔几秒改变一下时间戳显示或多显示一位小数破坏静态画面的稳定性。我现在是用第一种方案简单有效。8.3 典型问题WiFi天线被外壳挡住之前提到过PCB天线贴近外壳塑料支架会严重影响信号。后来我的做法是选用了带IPEX外接天线的模组天线通过一根细线引到外壳顶部再固定在壳体上开的凹槽里。这样信号稳定得多。如果你用的主控板没有IPEX接口也可以考虑把外壳顶部厚度做到1mm以内甚至留一个开口让天线区域直接暴露出来。8.4 后续可扩展方向魔方做好了之后可以做的扩展其实很多。 一是加空气质量传感器比如SGP30或SEN55把CO2、TVOC也显示出来就变成了一个桌面环境综合监测站。 二是用电池供电并加低功耗休眠让它在无操作时进入深度睡眠定时唤醒更新数据做一个完全无线的小设备。 三是加更多显示面比如其他五个面都放上小屏或LED矩阵做成真正的“智能魔方”每一面展示一类数据不过这会大幅增加设计复杂度适合作为下一期的挑战。 四是加音频反馈用一个小蜂鸣器做整点报时或天气突变提醒但要注意别做成噪音源桌面设备安静是第一位的。对于第一版的改进我个人觉得最值得优先做的是加SGP30空气传感器。桌面环境CO2浓度是很多人关心但又默认忽略的指标有了它这个盒子就从“天气站”升级成了“环境管家”实用价值提升一个档次。如果说这次整个项目下来有什么体会我的感受是小型桌面设备的难点往往不在某一个单项技术上而在怎么把传感器、显示、网络、外壳这些东西的互相影响平衡好。你把温度计放热源旁边再贵的传感器数据也不可信你把天线挡住再好的WiFi模块也发挥不出来。每个细节单独看都不起眼加在一起就是这台设备靠不靠谱的分水岭。希望这篇能给你做个参考。如果看完也打算搞一个优先买齐ESP32-S3、SHT30、BMP390、BH1750和WS2812B点阵然后从接线和传感器代码开始折腾。不用追求一步到位先让数据在串口里能看、能稳定再逐步加网络和显示。在这条路上踩的每一个坑最后都会变成你做下一个项目时比别人的那一点从容。