用ESP32和Arduino Nano打造智能净水器水质监测系统
最近在折腾家用净水器时,发现一个痛点:滤芯寿命很难准确判断。商家建议的更换周期往往过于保守,而水质变化又难以直观感知。于是萌生了自己做一个水质监测仪的想法,既能实时掌握出水质量,又能科学判断滤芯更换时机。经过几轮迭代,最终用ESP32和Arduino Nano实现了这个方案,特别分享其中的温度补偿算法和实战经验。
1. 硬件选型与核心组件
1.1 开发板选择:ESP32 vs Arduino Nano
两种开发板各有优势,选择取决于具体需求:
| 特性 | ESP32 | Arduino Nano |
|---|---|---|
| 处理器性能 | 双核240MHz | 单核16MHz |
| 内存 | 520KB SRAM + 4MB Flash | 2KB SRAM + 32KB Flash |
| 无线功能 | 内置WiFi/蓝牙 | 需外接模块 |
| ADC精度 | 12位(可配置) | 10位 |
| 价格 | 约¥40-60 | 约¥20-30 |
| 适用场景 | 需要联网或复杂数据处理 | 简单本地监测 |
实际建议:如果只需要基础监测功能,Nano性价比更高;若需要远程监控或复杂数据处理,ESP32是更好的选择。
1.2 关键传感器选型
水质监测的核心是TDS传感器,但配套组件也不可忽视:
- TDS传感器模块:推荐Gravity系列,自带防水探头和标准接口
- 温度传感器:DS18B20(数字接口)或LM35(模拟输出)
- 显示模块(可选):
- 0.96寸OLED(I2C接口)
- LCD1602(需额外转接板)
- 电源方案:
- 5V/2A USB电源适配器
- 18650电池+TP4056充电模块(移动方案)
提示:TDS探头长期浸泡可能导致读数漂移,建议每3个月用标准溶液校准一次。
2. 电路连接与硬件搭建
2.1 ESP32连接方案
ESP32的引脚配置相对灵活,但ADC2引脚在WiFi启用时不可用:
# ESP32典型连接方式 TDS_SENSOR_PIN = 34 # ADC1_CH6,GPIO34 DS18B20_PIN = 4 # 单总线数据引脚 OLED_SDA = 21 # I2C数据线 OLED_SCL = 22 # I2C时钟线实际接线时注意:
- TDS模块VCC接3.3V(部分模块需5V)
- 单总线需接4.7K上拉电阻
- I2C设备地址需通过扫描确认
2.2 Arduino Nano连接要点
Nano的模拟引脚有限,需合理规划:
// Nano引脚定义 #define TDS_PIN A0 #define TEMP_PIN A1 // LM35使用 #define ONE_WIRE_BUS 2 // DS18B20使用常见问题排查:
- 读数不稳定:检查电源滤波(建议加100μF电容)
- 温度异常:DS18B20接线顺序(黄线为数据)
- OLED不显示:检查I2C地址(通常0x3C或0x78)
3. 核心算法与温度补偿实现
3.1 TDS测量原理深度解析
TDS值反映的是水中导电物质总量,其测量本质是通过电导率推算:
- 传感器输出模拟电压(与电导率正相关)
- ADC转换后得到数字量
- 通过经验公式转换为ppm值
关键公式:
补偿电压 = 原始电压 / (1 + 0.02*(当前温度 - 25)) TDS值 = 133.42*V³ - 255.86*V² + 857.39*V3.2 温度补偿的三种实现方式
根据传感器类型不同,温度获取方式也不同:
方案1:DS18B20数字温度传感器
#include <OneWire.h> #include <DallasTemperature.h> OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); void setup() { sensors.begin(); } float getTemperature() { sensors.requestTemperatures(); return sensors.getTempCByIndex(0); }方案2:LM35模拟温度传感器
float readLM35() { int raw = analogRead(TEMP_PIN); return raw * 0.48828125; // (5000mV/1024) / 10mV/℃ }方案3:固定补偿(不推荐)
float temperature = 25.0; // 默认值注意:温度采样间隔建议≥1秒,频繁读取可能导致传感器发热
3.3 信号处理算法优化
原始数据往往存在噪声,需要滤波处理:
中值滤波改进版:
float medianFilter(float newVal) { static float buffer[5] = {0}; static byte index = 0; buffer[index] = newVal; index = (index + 1) % 5; float temp[5]; memcpy(temp, buffer, sizeof(temp)); // 冒泡排序 for(int i=0; i<4; i++) { for(int j=i+1; j<5; j++) { if(temp[i] > temp[j]) { float swap = temp[i]; temp[i] = temp[j]; temp[j] = swap; } } } return temp[2]; }滑动平均滤波:
float movingAvg(float newVal) { static float sum = 0; static float buffer[10] = {0}; static byte index = 0; sum -= buffer[index]; buffer[index] = newVal; sum += newVal; index = (index + 1) % 10; return sum / 10; }4. 系统集成与实用功能扩展
4.1 数据可视化方案
串口绘图(简易方案):
void serialPlot(float tds) { Serial.print("TDS:"); Serial.print(tds); Serial.print(","); Serial.println(millis()); }OLED显示(高级方案):
#include <U8g2lib.h> U8g2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); void drawDisplay(float tds, float temp) { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB10_tr); u8g2.drawStr(0,15,"Water Quality"); u8g2.setFont(u8g2_font_logisoso24_tn); u8g2.setCursor(0,50); u8g2.print(tds,0); u8g2.print(" ppm"); u8g2.setFont(u8g2_font_6x10_tr); u8g2.setCursor(80,50); u8g2.print(temp,1); u8g2.print("C"); u8g2.sendBuffer(); }4.2 滤芯寿命预测算法
基于TDS变化趋势判断滤芯状态:
# 伪代码示例 class FilterMonitor: def __init__(self): self.base_tds = None self.max_tds = 500 # ppm阈值 def update(self, current_tds): if self.base_tds is None: self.base_tds = current_tds * 0.8 # 初始基准值 ratio = current_tds / self.base_tds if ratio > 2.0: return "REPLACE NOW" elif ratio > 1.5: return "WARNING" else: return "GOOD"4.3 物联网功能扩展(ESP32专属)
通过MQTT实现远程监控:
#include <WiFi.h> #include <PubSubClient.h> WiFiClient espClient; PubSubClient client(espClient); void setupWifi() { WiFi.begin("SSID", "password"); while (WiFi.status() != WL_CONNECTED) { delay(500); } } void reconnectMQTT() { while (!client.connected()) { if (client.connect("ESP32Client")) { client.subscribe("water/command"); } } } void publishData(float tds) { char payload[50]; sprintf(payload, "{\"tds\":%.1f}", tds); client.publish("water/tds", payload); }5. 校准与维护实践
5.1 三步校准法
零点校准:使用蒸馏水(理论TDS=0)
void calibrateZero() { float sum = 0; for(int i=0; i<10; i++) { sum += analogRead(TDS_PIN); delay(100); } zero_offset = sum / 10; }标准液校准:使用342ppm NaCl溶液
void calibrateStandard() { float voltage = readVoltage(); // 获取原始电压 scale_factor = 342 / (voltage * voltage * 0.67); }温度补偿验证:在不同水温下测试
5.2 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读数始终为0 | 探头未接触水/电源故障 | 检查连接/更换电源 |
| 数值剧烈波动 | 电源干扰/接触不良 | 增加滤波电容/检查接线 |
| 温度显示-127℃ | DS18B20通信失败 | 检查上拉电阻/接线顺序 |
| TDS值随温度降低 | 补偿系数错误 | 检查温度传感器读数是否准确 |
| WiFi频繁断开 | 信号弱/电源不足 | 改用5V供电/增强信号 |
在项目开发过程中,最耗时的部分是温度补偿算法的调试。最初直接使用厂家提供的系数,发现低温环境下误差较大。后来通过实测不同温度下的TDS值,最终将补偿系数从0.02调整为0.0195,25℃时的精度提升了12%。