尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

ESP32水质监测系统实战:传感器校准、数据滤波与MQTT告警

发布时间:2026/9/29 20:45:28

资讯中心
01
ARTICLE

ESP32水质监测系统实战:传感器校准、数据滤波与MQTT告警

ESP32水质监测系统实战:传感器校准、数据滤波与MQTT告警
这套系统做完之后我最大的感悟是水质监测听起来是个买几个传感器接上去的活儿但真正做起来从电极的保存方式到ADC的非线性修正每一个细节都藏着能让数据变成废纸的坑。下面把这套基于ESP32的水质安全监测系统从需求拆解到实测运行完整记录下来给想复刻的朋友们省点时间。1. 水质监测到底在测什么需求拆解比选型更重要1.1 三个指标决定了我这套系统的形态先说最重要的部分——不是硬件选型而是想清楚你到底要测什么。很多人一上来就买一堆传感器结果发现数据对不上、噪音大、不知道参考值是多少最后项目烂尾。我做这套系统时目标场景是观赏鱼池和家用净水器前后的水质对比所以最终确定三个指标水温用DS18B20防水探头精度±0.5°C1-Wire协议一根线就能接多个探头pH值用PH-4502C模块搭配电极测量范围0-14输出模拟电压TDS用TDS Meter V1.0模块测量范围0-1000ppm输出模拟电压为什么是这三个而不是更多水温直接影响鱼类代谢速率和溶氧量也影响pH电极和TDS探头的测量准确性所以它是所有补偿计算的前提。pH值是养鱼和饮用水领域最核心的参考指标酸碱失衡对鱼是致命的。TDS则直接反映水中的可溶性矿物质总量净水器是否失效、水质是否变硬看它的趋势比看单次绝对值更有意义。我确实考虑过加装浊度传感器但最终决定留到二期。原因很简单浊度传感器需要更复杂的光学标定且水体颜色、气泡都会干扰读数对第一版系统来说调试成本会吞噬掉大量时间。真正做项目时先跑通一条完整链路比一次堆满所有功能要重要得多。1.2 监测频率、精度和成本之间的平衡这套系统的监测频率设定为30秒一次。有人会说水质变化那么慢一分钟甚至五分钟采集一次不就行了频率低确实省电、省流量但有一个问题告警的及时性。如果pH值正在快速恶化比如鱼池里突然有死鱼分解30秒采样结合连续三次超标才告警的策略能把误报率压下来同时保证5分钟内一定触发通知。对于家庭鱼池和净水器监测这个场景这个响应速度已经足够。精度需求也要想清楚。普通PH-4502C模块配合工业电极误差大概在±0.1到±0.2 pH单位对鱼池养殖完全够用。TDS模块的误差通常在±10%但我们要的是趋势变化这个精度足够识别滤芯失效。真正值得花钱升级的不是传感器本身而是ADC采样精度——ESP32内置ADC在某些电压区间的非线性比较明显后文会详细讲我是怎么处理和校准的。1.3 为什么ESP32而不是Arduino Uno或树莓派这个选择题几乎每个做IoT项目的人都会遇到。我的答案很明确ESP32。理由有三层网络能力ESP32自带WiFi和蓝牙双模Arduino Uno需要外挂ESP8266模块才有网络能力多一个模块就多一个故障点和一层供电设计模拟采样ESP32有12位ADC0-4095比Uno的10位ADC分辨率高4倍对pH和TDS这种毫伏级变化的传感器来说很关键成本和功耗ESP32单颗芯片十几块钱运行功耗几十毫安比树莓派这种完整Linux系统轻量得多而且裸机编程比Linux上写Python再调库要直接不上电就能确保程序行为可控树莓派也不是不能用但对水质监测这种7x24小时运行的场景树莓派的SD卡寿命、系统更新、异常断电恢复都是额外的运维负担。ESP32写好的固件上电就干活稳定如老狗这才是关键优势。2. 硬件方案与接线细节ESP32为核心传感器怎么配2.1 主控板选型和引脚规划我用的是一块ESP32-DevKitC V4开发板带30个引脚的那种经典版本理由很简单资料最多、社区案例最全、引脚兼容性好。如果要复刻这个项目选ESP32-S3或者ESP32-C3也完全可以但注意模拟输入引脚数量不同需要核对你的传感器数量。引脚分配我的设计如下外设引脚接口类型说明pH传感器模块GPIO34模拟输入ADC1-CH6输入-only引脚不可输出TDS传感器模块GPIO35模拟输入ADC1-CH7输入-only引脚DS18B20水温探头GPIO41-Wire数字总线需要4.7kΩ上拉电阻到3.3V0.96寸OLEDSSD1306GPIO21(SDA)、GPIO22(SCL)I2C本地实时显示继电器预留GPIO26数字输出后期接增氧泵/加热棒这里有个血泪教训必须说GPIO34到GPIO39是纯输入引脚没有内部上拉也不能驱动输出。第一次做项目时我图省事把OLED的SDA接到了GPIO34结果屏幕死活不亮查了半天才发现引脚选错。规划引脚前一定要把datasheet里每个引脚的能力看一遍。2.2 传感器模块接线说明与供电策略pH传感器PH-4502C本质是个高输入阻抗的电压放大模块pH电极的BNC接口输出的是毫伏级电势差模块内部放大后从PO口输出0-3V左右的模拟电压直接接ESP32的ADC引脚即可。但注意模块板的供电是5V逻辑输出并不需要5V兼容——因为模拟输出最大值约3VESP32的ADC输入范围是0-3.3V所以可以直连。TDS传感器模块的工作原理是电导率测量。两块不锈钢电极插入水中施加交流激励信号通过测量水体的导电能力换算成TDS值。模块输出0-2.3V模拟电压对应0-1000ppm同样直接接ADC引脚供电5V。DS18B20是数字传感器不用ADC直接接GPIO4。1-Wire总线要求数据线必须有4.7kΩ电阻上拉到3.3V这个电阻不能省省了你会看到温度读数偶尔跳到-127。供电这块我的做法是用一个5V/2A的DC电源适配器经过Type-C口给ESP32开发板供电。开发板板载的AMS1117稳压器会输出3.3V给ESP32芯片本身但注意板载3.3V输出电流有限大约500mA我算过整个系统的电流ESP32本身约80mA、传感器模块加起来约50mA、OLED最亮时约20mA、DS18B20约1mA总共不到200mA所以板载3.3V够用不需要额外降压模块。关键是所有的地线要共地——pH模块、TDS模块、OLED和ESP32的地都连在一起否则模拟读数会漂到怀疑人生。2.3 探头的安装位置比接线更容易被忽略接线只是第一步探头装在哪直接决定数据有效性。我吃过一个亏第一次把pH探头贴在水池侧壁安装读数一直偏低后来才发现电极前端紧贴池壁局部水流不畅形成了微酸性的沉积层。正确做法是让探头悬空浸入水体中部距离池壁至少5cm且探头前端完全没入水中。DS18B20防水探头则建议安装在靠近出水口的位置这样测到的是水流充分混合后的温度。如果直接放在死水区温度读数会有几度的滞后。另外TDS电极必须保证两片金属极片完全浸没不能只泡一半否则有效接触面积变化读数会乱跳。这些细节都没写进模块说明书但恰恰是决定系统好不好用的关键。3. 校准那几天传感器信号的可信度从哪来3.1 ESP32内置ADC的非线性问题先说一个很多人栽过跟头的坑ESP32的内置ADC精度没有想象中那么好。它的标称是12位0-4095但官方文档自己都承认在3.3V供电时0-3.3V全量程存在非线性尤其是在2.5V到3.3V的高电压区间误差可以大到几十毫伏。对pH传感器这种电压范围覆盖0-3V的模拟输出来说直接用analogRead读出的原始值换算误差会相当可观。我的处理办法是配备一两个精密的参考电压进行单点修正。具体做法是借用板载Vref或者外接精密电压源在0.5V、1.0V、1.5V、2.0V、2.5V这五个点实测analogRead返回值然后做分段线性插值。实际操作下来我写了一个查找表校正函数把ADC读数映射到实际电压整体的电压测量误差从±50mV压到了±10mV以内。如果你不想折腾也可以直接外挂一片ADS111516位ADCI2C接口约10块钱解决所有模拟输入精度问题代价是多一根I2C总线和几行代码。3.2 pH传感器两点校准的完整流程pH电极的输出电压与pH值之间的关系本质上是能斯特方程温度每升高1°C斜率会改变约0.2%。所以校准分两步走先调零点和斜率再考虑温度补偿。我用的PH-4502C模块上有一个电位器用于调节输出零点。配套的工业pH电极在出厂时通常已经封装好了不需要自己灌凝胶。校准我按下面的流程操作配制pH 6.86和4.00的标准缓冲液各倒一小杯温度控制在25°C左右把探头泡在pH 6.86溶液中等待1分钟让读数稳定然后调整模块上的零点电位器使ADC换算后的电压对应pH 6.86再泡入pH 4.00溶液中等待稳定记下当前读数和电压值算出实际的斜率在固件中把这两个点做成标定系数写入NVS非易失存储后续重启不用重新校准还有一个必须强调的细节pH电极不能长期干燥放置。电极的玻璃球泡内填充有KCl凝胶干燥后凝胶会结晶析出轻则响应变慢重则直接报废。不用的电极要浸泡在3M KCl溶液中或者至少用配套的保护帽加几滴KCl溶液保湿。我第一次做的时候电极空放了三天再测时响应速度从几秒变成了一分钟教训相当深刻。TDS校准相对简单。TDS与电导率EC成线性关系比例系数K在0.5到0.7之间不同水质略有不同。我用了市售的1413μS/cm电导率标准液对应约707ppm TDSK0.5把探头放进去调整模块上的电位器使读数等于707ppm即可。TDS模块内部已经做了温度补偿大多数模块内置NTC热敏电阻所以不需要单独处理温度。3.3 数据滤波滑动平均真的够用水质传感器输出的模拟信号不是干净的。水体流动、电极表面状态、电源纹波都会叠加噪声尤其是pH模块输出阻抗高一点干扰都会被放大。我在代码里做了两层处理。第一层是采样滤波。每30秒一个采集周期内每个传感器连续采样10次每次间隔50ms然后去掉最大值和最小值剩下的8个值取平均。这个叫去极值滑动平均兼顾了实时性和抗冲击性。相比之下单纯滑动平均面对突发毛刺时会把毛刺平滑成一个凸起还要好几个周期才能消化掉去极值版本就直接把毛刺剔除掉了。第二层是时间维度上的趋势判断。我在告警逻辑里用了连续三次超标才触发的规则相当于一个数字防抖。pH值偶尔瞬时跳到8.5不一定是水质问题但连着三次数值都在8.5以上那说明真的有趋势性偏移这时候发起告警可信度就高多了。要提醒的是这三次判断之间依然保持30秒间隔所以触发告警的最坏延迟是90秒对绝大多数场景都够快。我贴一段核心的采样滤波代码float readFilteredValue(int pin, int samples, int intervalMs) { int readings[samples]; for (int i 0; i samples; i) { readings[i] analogRead(pin); delay(intervalMs); } // 冒泡排序取中间段 for (int i 0; i samples - 1; i) { for (int j i 1; j samples; j) { if (readings[j] readings[i]) { int t readings[i]; readings[i] readings[j]; readings[j] t; } } } long sum 0; for (int i 1; i samples - 1; i) sum readings[i]; float avg sum / (samples - 2); return avg / 4095.0 * 3.3; }这段代码虽然朴素但用了很久没出过问题。需要注意的是如果选ADC引脚ESP32有ADC1和ADC2两组ADC2和WiFi共用部分资源WiFi开启时ADC2会不稳定所以模拟传感器全部接在ADC1上GPIO32-GPIO39才是正确做法。4. 数据上云与异常告警从串口到手机通知4.1 为什么选MQTT而不是HTTP轮询数据采集到位了接下来就是传输层。我直接用MQTT协议做数据上云而不是HTTP接口定时POST。原因在于它们解决的是不同的问题HTTP是请求-响应模式服务器侧要维护大量的连接状态才有推送能力而MQTT是发布-订阅模式一条消息发出去所有订阅者都能收到非常适合传感器到云端再到手机的这种扇出场景。而且MQTT基于TCP虽然协议头稍大但能够保持长连接服务器可以实时推送指令到设备端做远程控制或者参数修改非常方便。实际使用中我搭了一套Mosquitto MQTT Broker跑在一台低配置的云服务器上开放1883端口做了简单的用户名密码认证。Topic结构定成了这样water/device001/status —— 设备上线状态用LWT遗嘱消息实现掉线检测water/device001/data —— 周期上报的JSON数据water/device001/alarm —— 告警消息QoS我全部选了1确保消息至少送达一次同时不用担心QoS 2那种四步握手的额外开销。在ESP32端用的是PubSubClient库这个库轻量、稳定配合WiFiManager做自动配网整个连接管理大概一百行代码搞定。4.2 ESP32端的关键代码架构代码的组织其实不复杂核心是三个模块传感器采集、MQTT发布、告警判断。主循环里用millis()做非阻塞调度避免delay阻塞导致WiFi断线检测不及时。下面这一段是MQTT连接和发布的骨架#include WiFi.h #include PubSubClient.h const char* mqttServer your.broker.address; const char* mqttUser water_user; const char* mqttPass water_pass; WiFiClient espClient; PubSubClient client(espClient); void reconnectMQTT() { while (!client.connected()) { if (client.connect(ESP32_Water_Sensor, mqttUser, mqttPass, water/device001/status, 1, true, offline)) { client.publish(water/device001/status, online); } else { delay(2000); } } } void publishData(float temp, float ph, int tds) { String payload {\temp\: String(temp, 1) ,\ph\: String(ph, 2) ,\tds\: String(tds) }; client.publish(water/device001/data, payload.c_str(), true); }这里有一个很关键的设计MQTT发布时我把retain标志设成了true代码里最后一个参数。这意味着每次发布都会在Broker端保留最新一份消息任何新订阅者连上来立刻就能看到最新状态而不用等下一个采集周期。对于Dashboard展示和移动端启动时立刻刷新数据这个设置能省掉很多麻烦。关于断线重连我用的是LWTLast Will and Testament遗嘱消息设备异常掉线时Broker会自动替它发布一条offline状态到status这个Topic。上层监控服务只要订阅了这个Topic就能实时感知设备离线和恢复不需要做HTTP心跳轮询。这个机制在MQTT里是免费的不用白不用。4.3 告警规则的设计思路宁可漏报不可误报告警逻辑我放在上层服务端处理而不是把所有判断都塞进ESP32。原因是ESP32本地只能做简单阈值比较但真正的业务规则——比如pH在夜间可以放宽到7.8、白天超过8.2才告警TDS连续1小时超过500才算异常——需要时间窗口和多条件组合放服务端用Node-RED或者Python脚本处理就灵活得多。实际跑通的流程是ESP32只负责上传数据和基本的心跳云端通过Node-RED订阅water/device001/data用function节点做判定命中条件后调用钉钉机器人Webhook发告警。这套流程的好处是改规则不用重新烧固件改完云端逻辑立刻生效。有一次朋友说夜里老收到不必要的TDS告警我远程把阈值从450改到600前后不到一分钟就完成了完全不用动设备端。告警消息我会发两条一条给手机钉钉机器人或者邮件一条单独发布到water/device001/alarm这个Topic给后续接入的自动化控制逻辑用比如当你看到水温超过30度时自动触发继电器打开增氧泵。4.4 数据可视化的快速方案数据可视化我用的是比较省事的一条路云端用Node-RED把数据写入InfluxDB时序数据库然后接Grafana展示面板。InfluxDB和Grafana这套组合在时序监控领域是标配配置一次之后水温、pH、TDS的历史曲线都能按时间范围拖拽浏览每一次告警事件还会在面板上叠加标注。相比自己写一套Web图表这个方案稳定得多也省得多。如果你不想自己搭服务器也可以用一些现成的物联网云平台它们都支持ESP32的MQTT接入自带Dashboard和告警功能。这类平台的上手成本更低适合不想维护服务器的朋友。我最终选择自建Mosquitto InfluxDB Grafana是因为我想对数据有完全的掌控权而且这套系统可以平滑扩展到未来多个水质监测节点。5. 七天实测数据与踩坑清单5.1 一组真实的实测数据系统连续运行了一周我记录了每天上午10点左右的稳定读数。测试水体是一个120升的淡水鱼池水源是自来水经过三胞胎滤瓶过滤后的直饮水。日期水温(°C)pHTDS(ppm)备注第1天24.37.41128刚换水1/4数据正常第2天24.87.38135喂食后TDS略升第3天25.27.45141气温回升第4天25.07.39145稳定第5天24.97.35158喂食量增加TDS明显上升第6天25.47.43150轻微波动第7天25.17.40153换水前趋势正常从这组数据可以看到TDS的缓慢上升和喂食有明显相关性这符合预期——鱼食在水中分解会增加溶解性固体。pH基本稳定在7.35-7.45之间波动幅度小于0.1说明整套系统的传感器精度和稳定性是达标的。如果趋势反向比如pH持续下降且TDS猛增那大概率是水质恶化的先兆。5.2 最容易踩的坑逐个说清楚ADC引脚接5V信号pH模块虽然要5V供电但它的输出端是0-3V模拟信号有的人接了5V直接把ESP32的ADC引脚烧了。拆机前一定用万用表量一下传感器输出端的实际电压。探头的保存方式pH电极干燥放置一段时间后响应速度会急剧下降。即使测试间歇期也应把探头泡在3M KCl或者至少纯净水里用前再校准一次。WiFi掉线导致的假数据ESP32的WiFi堆栈偶尔会挂死尤其是长期7x24运行时最好在loop里定期检查WiFi连接状态断线超过30秒就主动esp_restart()。电源纹波导致读数抖动如果传感器和ESP32共用电源电机或者加热棒启动瞬间的电流冲击会让ADC读数突变。解决思路是分路供电或者加一颗1000μF的滤波电容在电源入口。DS18B20上拉电阻每次接线我都提醒自己1-Wire总线必须接4.7kΩ上拉到3.3V没有这个电阻数据线干扰会让温度读数直接跳到错误区间。5.3 后续扩展空间这套系统的架构天然是面向多节点的。当前是单台ESP32做三参数监测但只要把Topic里的device001改成device00X再在云端稍加配置就能扩展成多设备分布式监测——比如同时监测鱼池、龟缸和净水器出水口。我接下来打算做两件事一是加装浊度传感器通过光敏二极管检测悬浮颗粒浓度用于鱼池的泥砂浊度监测二是把ESP32和GPS定位模块结合做成一个便携式野外水质检测装置便于外出采样时使用。还有一个小优化方向是功耗。当前一直是市电供电如果做电池供电版本可以通过ESP32的深度睡眠模式把采集周期拉长到每10分钟唤醒一次功耗可以降到微安级别这样用18650电池能撑好几个月。最后再分享一点个人体会。这套系统做完之后最大的收获不是那几张曲线图而是真切理解了传感器数据信任链这个概念——每一次读数背后都串着电极状态、偏置电压、ADC精度、滤波算法、传输协议这一整条链路任何一环松懈最终读数就不可信。所以我不建议急着堆功能先把一条链路跑扎实再逐步扩展开来这才是做IoT设备稳妥的节奏。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。