资讯动态

ESP32小型气象站DIY:传感器选型、数据采集到Web可视化全流程解析

发布时间:2026/10/13 1:48:33 来源:尧图企业网站定制
1. 为什么“小”气象站反而更难先说个扎心的结论买一套成品小型综合气象站几百块就能到手但真正动手自己攒一套难度并不在于电路也不在于代码而在于你如何看待“测量”这件事。一个温度传感器读数是25.3℃一个风速传感器报出2.7m/s听起来很简单但数据从传感器到屏幕中间要经过信号采集、单位换算、噪声抑制、时间戳对齐、传输与存储任何一环出现问题读出来的数字都会骗你。我见过不少人一夜之间搭完了硬件却在接下来几个星期里为了风杯偶尔不转、雨量筒半夜误报这类问题挠头。小型综合气象站这个项目之所以适合拿来练手是因为它把微控制器、各种传感器接口I2C、SPI、脉冲计数、ADC、无线通信、低功耗设计、Web可视化全部串在了一起相当于一个小型物联网项目的完整闭环。做完之后你手机上能看到自家院子里的实时温度、湿度、气压、风速风向、雨量放到办公桌或者阳台上也能随时打开网页看一眼环境变化配合历史曲线能明显感觉到天气系统过境时气压骤降、风速抬升的整个过程这种实时感是天气预报App给不了的。适合谁来玩初级选手可以拿它当传感器入门的第一课只用温度和湿度两路数据也能先跑通全链路有经验的开发者则可以把它做成低功耗版本靠太阳能电池板供电部署在屋顶或农田连续跑几个月不维护。文章里我会把传感器选型、关键电路、软件处理、部署校准和排障经验全部摊开讲你不需要照着某一块开发板抄作业拿一份通用的ESP32 传感器组合就能复现。2. 硬件选型先把传感器这关过了2.1 温湿度与气压核心传感器怎么选温湿度和气压是整个气象站里最简单的三路数据但也是最容易被忽视的环节。目前市面上最常见的组合有三个档位DHT22、SHT30、BME280。我的建议是能上BME280就直接上价格也就十来块钱却能同时输出温度、湿度、气压三个物理量一个传感器解决三路采集省掉了I2C总线上的设备数量从根上降低了出问题的概率。BME280的典型参数如下参数典型值备注温度精度±0.5℃0~65℃实际传感器偏差可通过校准修正湿度精度±3%RH20~80%RH湿度是气象站里最难测准的参数气压精度±1hPa950~1050hPa拆机时注意不要堵住传感器通气孔接口I2C / SPII2C最常用地址默认为0x76供电1.71V~3.6V3.3V下使用最方便很多人会为了省钱选DHT22但它只有温湿度而且时序是单总线协议的代码处理稍显繁琐数据稳定性也不如I2C设备。更关键的一点是DHT22的湿度响应速度比较慢放到百叶箱里面对湿度突变时读数像心电图一样抖动你会怀疑人生。既然都做气象站了就让传感器模块省点心不要在这个环节上较劲。如果环境湿度经常超过90%比如放在南方梅雨季的户外建议在PCB正反两面刷一层三防漆。BME280本身集成度已经很高但裸露在户外会面临冷凝、灰尘和昆虫的威胁。刷三防漆不会影响传感器正常读取但能够有效降低焊点和走线被腐蚀的概率。注意BME280这类传感器封装很小焊接时务必控制温度和时间。手残党不建议直接买裸芯片封装优先选带转接板的模块引脚间距大接线方便而且模块上通常已经加好了上拉电阻。2.2 风速风向雨量物理量不同采集方式也完全不同这三路数据才是小型综合气象站区别于“室内温湿度计”的核心。风速传感器常见的方案有两种。一种是我个人更推荐的翻杯式风速计机械结构简单通过切割红外光或者干簧管产生脉冲信号风速越快单位时间脉冲数越多。另一种是超声波风速计没有机械运动部件通过超声波飞行时间差反算风速精度高但价格贵到飞起适合做研究型项目不适合入门复现。用电脉冲测风速的公式很简单风速(v) 脉冲频率(f) × 换算系数(K)不同厂家的风杯系数不一样常见K值在0.2~0.3之间意思是每秒钟一个脉冲大约对应0.2到0.3m/s风速。强烈建议到手后先转一圈确认脉冲数和厂家手册对得上不要直接相信默认系数。我踩过一次坑某国产风杯标称K0.25实际转出来是0.28最后风速整体偏高10%。风向传感器相对传统内部是一个由8个磁簧开关组成的格雷码盘通过干簧管的通断组合把风向映射到0~360度范围分辨率45度也就是8个方向。如果用模拟电压输出的风向传感器常见的0~5V对应0~360度ADC读回来的电压换算成角度即可但要注意传感器供电电压必须和ADC的参考电压严格一致否则角度会整体偏移。雨量传感器用的是翻斗式雨量筒。内部有一个双腔翻斗雨水积累到一定重量后翻斗翻转触发一次磁簧开关或干簧管信号翻斗每翻转一次代表降水量0.2mm或0.5mm看具体型号。这个物理机制决定了雨量数据天然是“离散累加”的累计雨量(mm) 翻转次数 × 单次翻斗容量(mm)采集程序只需要对脉冲做计数每检测到一个脉冲就把雨量累计值加上单次容量。这个数据不需要连续读取断电也不怕丢只要把累计值存在Flash或SD卡里重启后接着累加就行。要注意翻斗式雨量筒对水平度非常敏感装歪了会导致斗在某一侧提前翻转累计值忽大忽小。2.3 主控与通信ESP32为什么是首选小型气象站的主控芯片选择我推荐ESP32而不是Arduino UNO或者STM32。原因很简单它自带WiFi和蓝牙一个芯片解决了采集、联网、Web服务三件事省掉外接WiFi模块的接线烦恼而且价格不到20块钱性能对气象站这种低负载应用绰绰有余。具体我用的型号是ESP32-DevKitC或者ESP32-WROOM-32。处理逻辑完全够用Flash存储也够放网页和配置GPIO数量足够接两路脉冲计数风速和雨量、I2C总线传感器、风向ADC输入外加一路太阳能板电压采样。如果你希望功耗压到极端比如用一节18650锂电池想让气象站野外在无人看管情况下跑半年那就要用它支持的深度睡眠模式了。传感器每5分钟唤醒一次采集完数据发送到局域网或者公共MQTT Broker然后再睡过去。实测下来带太阳能板的情况下平均功耗可以控制在2mA以下晴天两天就能把电池充满。凌乱且带电的线路放在户外是对自己不负责任。MCU和传感器模块之间要用防水的航空插头或RJ11接口连接方便测试时插拔。所有裸露的线头全部用热缩管套好再用扎带固定避免被风吹得左右剧烈摆动导致接触不良。2.4 供电与结构设计供电方案直接在本次项目中决定了运行可靠性。市电供电简单粗暴但一台户外气象站的部署场地通常离插头十万八千里于是太阳能锂电池就成了主流的组合。我的配置是6V/5W的太阳能板接一个TP4056充电模块给3.7V/18650锂电池充电电池端经过一个低 dropout 稳压芯片比如AMS1117-3.3给ESP32和传感器供电。千万别直接用太阳能板给ESP32供电光照变化会导致输出电压剧烈波动轻则数据乱跳重则烧板子。锂电池容量建议往大了选我用的是3400mAh实测阴天情况下撑五天没问题。注意锂电池在高温暴晒下存在膨胀甚至起火的风险如果部署位置夏季阳光强烈建议加一层隔热棉或者把电池放在百叶箱的阴影侧宁可充电效率低一点也不要让电池在60℃以上环境里持续工作。结构方面最省事的方案是买成品百叶箱壳子防辐射还透气中间放温湿度气压传感器风速风向和雨量筒安装在顶部或侧面支架上。仪器安装高度建议在1.5m~2m之间这是气象观测规范里的推荐高度太矮容易受地面辐射和人为遮挡影响太高又失去了生活场景的参考意义。3. 软件实现数据从传感器到屏幕的全链路3.1 传感器驱动与数据解析软件部分的第一步就是把BME280的温湿度气压读出来。ESP32上用Wire库做I2C通信可以自己写寄存器级驱动也可以直接用Adafruit的BME280库后者更省时间。但别过度依赖库至少要大概知道它内部是怎么算的BME280出厂时芯片内有校准参数通过补偿公式把原始ADC值换算成物理量库内部自动做了这些我们只需要拿到浮点结果。编程接口的关键代码长这样以Arduino环境为例#include Wire.h #include Adafruit_BME280.h Adafruit_BME280 bme; void setup() { Wire.begin(21, 22); // 注意ESP32的I2C引脚可以任意指定 bme.begin(0x76); // 默认地址若读不到试试0x77 Serial.begin(115200); } void loop() { float temp bme.readTemperature(); float hum bme.readHumidity(); float pres bme.readPressure() / 100.0F; // 转成 hPa Serial.printf(Temp: %.2f C, Hum: %.2f %%, Pres: %.2f hPa\n, temp, hum, pres); delay(5000); }一个容易踩的坑ESP32的GPIO 21和GPIO 22是默认I2C引脚但如果你把传感器接在了其他引脚上必须在Wire.begin里显式写出来。我见过太多人接好硬件才发现I2C设备扫不到最后发现是引脚下意识接错浪费了大半个小时。湿度数据的稳定性比温度差需要做一下平滑滤波。直接展示原始值曲线会像锯齿一样看着不够直观。我用了简单的低通滤波filtered_hum filter_factor * raw_hum (1 - filter_factor) * filtered_hum;filter_factor 取0.3比较合适既能感知快速变化又不会让曲线过于抖动。气压数据则可以直接用原始值因为大气压变化本身就很平滑反而不要滤波避免掩盖天气系统过境时的真实波动。3.2 风速脉冲计数中断别乱用风速和雨量的脉冲计数是软件部分最需要动脑筋的地方。推荐用外部中断去捕获每次上升沿然后通过累计脉冲数来计算。代码核心逻辑volatile unsigned long pulseCount 0; volatile unsigned long lastTime 0; volatile float windSpeedMs 0.0; void IRAM_ATTR onWindPulse() { unsigned long now millis(); if (now - lastTime 50) { // 简单防抖小于50ms的脉冲忽略 pulseCount; lastTime now; } } void setup() { pinMode(windPin, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(windPin), onWindPulse, RISING); } void loop() { static unsigned long prevPulseCount 0; static unsigned long prevMillis 0; unsigned long now millis(); if (now - prevMillis 2000) { float freq (pulseCount - prevPulseCount) / 2.0; // 每秒脉冲数 windSpeedMs freq * K_FACTOR; // K_FACTOR约为0.25 prevPulseCount pulseCount; prevMillis now; } delay(100); }中断回调函数里严禁调用delay、Serial之类的阻塞函数因为中断服务例程里做这类操作极易导致系统崩溃或者数据错乱。我的防抖参数50ms来自翻杯式风速计的实际物理特性——风杯在极端阵风下也不可能在50ms内连续触发两次脉冲这个值可以滤掉大部分电磁干扰和机械弹跳。雨量计也同理只是不需要换算风速直接累加脉冲数volatile unsigned long rainTipCount 0; void IRAM_ATTR onRainPulse() { unsigned long now millis(); if (now - lastRainTime 100) { rainTipCount; lastRainTime now; } } float rainMM rainTipCount * 0.2; // 单次翻斗容量0.2mm注意雨量筒的防抖时间要设得比风速计略大。因为雨量筒的翻斗在正常翻转过程中会有一个回弹干簧管可能瞬间通断多次只记一次才是正确的翻斗动作。100ms的防抖值是经过实测比较稳的配置改小容易形成误计数改大则可能在暴雨时漏掉连续翻转。3.3 WiFi与数据传输局域网优先云端可选数据传输是气象站项目中很能够体现架构水平的一部分。我最推荐的方式是本地HTTP服务加可选的MQTT上报。ESP32自带WiFi直接开一个Web服务器把最新数据以JSON格式暴露出来这样局域网内任何设备手机、电脑、树莓派访问http://192.168.x.x:8080/api/weather就能拿到JSON数据。关键代码思路#include WiFi.h #include WebServer.h WebServer server(80); void handleApi() { String json {\temp\: String(temp) ,\hum\: String(hum) ,\pres\: String(pres) ,\wind_speed\: String(windSpeedMs) ,\rain_mm\: String(rainMM) }; server.send(200, application/json, json); } void setup() { WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) delay(500); server.on(/api/weather, handleApi); server.begin(); } void loop() { server.handleClient(); delay(50); }有人会问MQTT都出来这么多年了为什么不直接用云端平台我的建议是如果只是自己看数据本地HTTP最省事拔电插电都不依赖外网。但如果你想让数据沉淀成历史曲线或者在外网随时随地想查看那就要引入MQTT协议把数据推送到公共Broker了。我用的是EMQX提供的免费公共Broker作为体验当然自己部署一个Mosquitto在本地的树莓派上也行。发布端用ESP-MQTT库订阅端可以用Node-RED或者Python脚本绘图。推送频率不宜太高每5分钟一次足够气象数据的展示也省电省流量。MQTT发布的核心思路#include PubSubClient.h void publishWeather() { char topic[64]; snprintf(topic, sizeof(topic), station/%s/weather, stationID); char payload[128]; snprintf(payload, sizeof(payload), {\temp\:%.2f,\hum\:%.2f,\pres\:%.2f,\wind\:%.2f,\rain\:%.2f}, temp, hum, pres, windSpeedMs, rainMM); mqttClient.publish(topic, payload); }在这里强调一个细节发布数据前务必检查MQTT连接状态如果Broker没连上就尝试发布会导致客户端堵塞甚至自动重启。稳妥的做法是if (!mqttClient.connected()) { mqttClient.connect(clientID); } if (mqttClient.connected()) { mqttClient.publish(topic, payload); }3.4 Web可视化本地页面实时刷新本地部署一个Web页面难度不大通篇只有一个静态HTML文件JavaScript的setInterval定时拉取上面的接口。页面非常轻量展示温度、湿度、气压、风速、风向、雨量等主数据并配上近期趋势的简单折线图。迷你Chart.js或者ECharts都行我用的是Chart.js的轻量版加载快不需要额外依赖。实时刷新间隔5到10秒就够了气象站数据变化慢刷新太快反而造成无意义的请求和闪烁。一个很实用的细节风向数据展示不仅仅是显示度数最好把8个方位的中文名北、东北、东等映射好配合一个旋转的箭头图标看起来一目了然。代码可以这么处理function degToDir(deg) { const dirs [北, 东北, 东, 东南, 南, 西南, 西, 西北]; return dirs[Math.round(deg / 45) % 8]; }页面拿到JSON之后把每个字段填到对应的HTML元素里Chart.js负责更新曲线数据。这个方案够用而且改动起来非常灵活也不需要考虑额外的后端框架。提示如果你把这个Web页面放在公网上建议在前面套一层反向代理并开启鉴权不要让陌生人随意查看你家里的气象数据。本地局域网自嗨的话就不用管这些。4. 实际部署与校准数据不准一切为零4.1 安装选址位置比仪器贵传感器部署的物理位置对数据准确性的影响比传感器本身精度的影响还要大。这是我经历过N次实测之后最深切的体会。温湿度传感器必须放在百叶箱内百叶箱可以防辐射、防雨淋同时保证空气流通。千万不要把传感器直接暴露在阳光下外壳吸热以后温差能达到5~10℃读数彻底失去意义。如果你的场地没有现成百叶箱用塑料花盆倒扣钻孔也能临时救急但空气流通效果差一些只能算凑合方案。风速计要安装在距离障碍物至少10倍障碍物高度以上的位置这是气象观测的经典原则。在城市环境很难找到这种开阔场地那就至少选择屋顶最高处尽量远离空调外机、排气扇等人为干扰源。雨量筒则要求周围没有遮挡物否则风场会在雨量筒上方形成漏斗效应导致雨量测量偏差。安装高度上风速计和雨量筒建议在1.5m到2m温湿度百叶箱最好离地面1.5米左右。太阳能板朝向正南倾角45度在北方冬天要稍微加大倾角避免积雪遮盖太久。4.2 传感器校准用身边一切可用的参照物很多气象站做出来数据波动剧烈90%的原因是传感器根本没有校准而非硬件故障。温度校准的方法最接地气拿一支经过计量校准的体温计或户外温度计和传感器放在同一个环境里静置20分钟以上等到两者读数稳定后记录差值在代码里做偏移量修正。我的一个BME280在第一次通电时显示27.3℃同一时间参照温度计显示26.5℃偏差0.8℃代码里减去这个差值之后后续读数才靠谱。湿度校准稍微麻烦一点最佳参照物是饱和盐溶液。用食盐加上纯净水搅成饱和盐水放入密封袋里几个小时之后密封袋内湿度为75%RH左右。把传感器放进去测如果读数偏离75%RH太多说明模组本身有问题。这种校准在业余条件下已经足够了。当然饱和食盐水在不同温度下湿度值略有差异精确值网上都能查到。气压校准相对简单打开手机天气App里的“海平面气压”或者中国天气网发布的本地气压和传感器读数对比。值得注意的是传感器读出的是所在地的“本站气压”而手机显示的多是“海平面气压”这两个值之间差一个海拔修正不能直接画等号。在海拔200米以内修正量很小超过300米差异就比较明显了。至于风速计和雨量计的校准业余条件下很难做精准验证。我能提供的最实用办法是拿一个手持风速计几十块钱的也能用在相同风速下跟你的气象站对比几个点修正换算系数雨量计则用一个小量杯匀速往翻斗里倒水数翻转次数对比单次翻斗容量的标称值倒近100ml水测出来总雨量误差在5%以内就算合格。4.3 长时间运行与故障排查我连续运行了三个月之后遇到了下面这几个问题这里直接给出结论性经验问题一风速数据突然变成0持续半天后恢复。排查结果是太阳能板输出电压在黄昏时段跌到了3V以下ESP32供电不稳导致中断丢失。解决方案是把稳压芯片换成低功耗的LDO并把检测到电压过低时切换到深度睡眠模式的逻辑加上避免在半睡半醒状态下运行。问题二雨量数据在无雨的夜晚偶尔增长0.2mm或0.4mm。仔细观察发现是凌晨露水积累到一定程度顺着漏斗滴入翻斗导致半夜误计数。解决方法是把雨量筒用细目不锈钢网罩住防止露水和昆虫进入同时适当收紧安装水平度确保翻斗只在真正降雨时翻转。问题三Web页面偶尔打不开需要重启ESP32才能恢复。原因是ESP32跑的时间长了内存碎片积累或者TCP连接没有完全释放。解决方式是在loop循环里定期检查FreeHeap低于阈值强制重启。懒人方案是每隔24小时软重启一次这不算优雅但很实用。5. 常见问题速查表问题现象可能原因解决方案温湿度读数明显偏高传感器受阳光直射或靠近发热体放入百叶箱远离热源湿度读数长期为100%或0%传感器通气孔被灰尘/凝露堵住更换传感器或清理通气孔气压读数突然跳变I2C总线受到干扰或电源毛刺加长传感器引线时用屏蔽线电源端并10μF电容风速一直为0但手转风杯有读数中断引脚接线松动或防抖时间过大检查接线调低防抖时间风速数值整体偏高换算系数K设置不准确用手持风速计对比校准K雨量数据无规律增长露水凝结进入翻斗或翻斗水平失准加装防露网校正水平MQTT数据丢失Broker连接不稳定每5秒重连一次发送前检查连接状态太阳能供电下频繁重启电池电压跌落增大电池容量代码增加低压休眠逻辑Web页面无法实时更新TCP连接被挂起或JS轮询接口超时增加fetch超时判断定期自动重载页面配合一个长期运行的观察细节数据在时间轴上要连续不能有断点。如果你发现某段数据完全缺失大概率不是传感器坏了而是MCU复位了。把串口日志打开观察复位原因esp_reset_reason()能看到是电源问题、看门狗还是手动复位这一步能省很多排查时间。另外我建议把ESP32的Timelib协议跑起来用NTP服务器同步真实时间保证数据的时间戳准确。气象数据如果没有时间轴就等于零无法后续统计日均、月均或者评估某个天气事件的过程曲线。个人在实际操作中的经验是别追求传感器数量多先把温度、湿度、气压、风速、雨量这几路做到准确稳定再考虑加装光照、PM2.5、土壤湿度这类扩展。每路传感器加进来表面上看只是多了一组数据实际上对应的电源管理、滤波算法和存储逻辑都在变大摊子一旦协调不好就会互相拖累。我能顺畅跑起来这个小气象站很大程度上的起因是克制住了不断加传感器的冲动先把基础六要素做扎实。如果你后续想把项目做得更深入可以试试把数据从本地Web改成SQLite存储用Python脚本批量拉取历史数据或者接入一个内网自建的可视化面板慢慢会变成一个有意思的家庭数据小平台。这个项目的上限比你想的高下限也足够低适合新手完整走一遍软硬件全流程也适合老手拿来做性能优化和功耗压榨。

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

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

免费获取报价 →
↑