DIY Arduino 冷镜露点仪:高精度温湿度监测
组件与材料
- Arduino UNO × 1
- 12V 10A 电源 × 1
- 12V 40mm 大功率风扇 × 1
- 12V 40mm 帕尔贴 (TEC) × 1
- 40mm 散热片 × 1
- 小镜片 × 1
- 导热胶 × 1
- 导热硅脂 × 1
- BTS7960 电机驱动板 × 1
- 3mm 黄色 LED × 1
- OPT101 光传感器 × 1
- IRF520 MOSFET 模块 × 1
- DS18B20 防水温度传感器 × 1
- Si7021 温湿度传感器模块 × 1
所需工具
- 热熔胶枪
软件与在线服务
- Arduino IDE
项目简介
背景
测量湿度常用电容式传感器,但长期高湿环境下易漂移失准。冷镜露点仪无此缺陷,且高湿段精度更高,只是商用设备动辄数千美元。原理简单,自制虽达不到商用级 0.1°C 精度,但对大多数用途足够,且优于电容式传感器。
本项目为概念验证,非量产就绪,仍有大量改进空间,但已证明可行。
工作原理
帕尔贴制冷镜面至露点产生凝露,光源与光传感器检测凝露出现瞬间,记录镜面温度即为露点温度。结合环境温度,通过露点公式计算相对湿度。
简化与妥协
- 镜面材质:商用仪用铂/铑镀铜镜,导热均匀。本项目用普通平面镜(方镜更佳但难买),或抛光不锈钢板更佳。
- 测温方式:商用仪用铂电阻 (PRT) 嵌入镜下,本项目用 SMD 温度传感器贴镜面,虽影响热分布但易实现、精度够用。
- 镜面尺寸:商用约 5mm,功耗小、体积小。本项目用 4cm 镜面,匹配现成帕尔贴,驱动电压高、易驱动。
- 避光罩:当前无避光外壳,强烈建议 3D 打印避光罩并留通风口(吸风不吹风),且便于清洁镜面。
- 霜点测量:需区分冰晶与水滴,需第二光传感器测散射光,当前版本不支持。
制作步骤
- 清洁帕尔贴两面,热侧涂导热胶贴散热片(无导热胶可用导热硅脂+边缘点滴瞬干胶),晾干。
- 冷侧同样清洁涂胶贴镜片,确保导热膏均匀无气泡,否则镜面温度不均。
- 粘贴风扇(热熔胶固定),风扇需风量足以压制散热片发热。

- 通电测试:短暂通电观察镜面凝露初现位置(勿久通电致均匀凝露)。拍照标记凝露同时出现的区域,此处为光传感器与测温传感器安装位。


- 作者导热膏涂布不均导致凝露分布不规则(见上图)。
- 安装 Si7021(镜面温度):温度传感器芯片大面涂非导电导热膏,PCB 也涂防凝露,四角大量热熔胶快速压贴镜面标记位置。需横向放置、压实。可用美工刀撬开重贴。PCB 凝露用热熔胶或喷胶绝缘。

- 风扇由 IRF520 MOSFET 模块驱动。
- 光传感器用 OPT101(灵敏、低噪、易用)。光源可用低功率激光二极管或聚光 LED,激光响应好但难对准,LED 易对准但响应平坦。需串联电阻降低亮度,避免 PWM 干扰传感器。
- LED/激光与 OPT101 用铜线扭绞固定,热熔胶封死,仅适合实验室原型。建议 3D 打印固定座并集成避光罩。

- 帕尔贴驱动用 BTS7960 电机驱动板。最优驱动方式是变电流非 PWM,但帕尔贴专用驱动难买,本概念验证不考虑功耗。

- 上传代码,调节电位器设定 OPT101 灵敏度(参考数据手册 Figure 3)。调试可对着镜面吹气制造凝露,或开关空调观察湿度变化。下图为温度(蓝)、光学读数(红)、测得露点(绿)曲线,关空调后露点上升。

安全提示
硬件支持反向驱动帕尔贴加热镜面(快速除露、闪蒸污染物),但镜面无散热片,若代码卡死在加热模式,轻则融化热熔胶脱落传感器,重则引发短路起火。代码未启用加热功能,请勿自行启用除非加完善保护。
精度分析
- 露点温度为绝对值,校准不如电容式关键。但镜面测温点与测光点有温差,需用商用冷镜仪校准。
- 镜面污染部分影响:反射光检测是相对冷却周期起始值,污染降低基线反射光不影响凝露检测;但某些污染物改变凝露温度,建议定期清洁镜面。
- 测温传感器分辨率比绝对精度更重要。若两传感器同偏 0.5°C 且抖动 <0.1°C 可归一化。推荐 TSYS01(0.1°C 精度,媲美铂电阻)。
- 必须用非导电导热膏保证传感器与镜面良好热接触。
- 降温速度不得超过传感器响应时间,否则露点读数偏低。
- 传感器须安装在凝露同步出现的区域。
- 贴装传感器改变热分布降低精度。红外测温受镜面反射干扰不适用。

- 气压影响极小,门关窗开引起的压差干扰更大,忽略气压修正。
- 散热片热风不得吹过镜面。
- 慢速降温精度高但响应慢;可在露点附近振荡温度提高响应速度。
Arduino 代码
#include <math.h>
#include <avr/wdt.h>
#include "Si7021.h"
#include <OneWire.h>
#include <DallasTemperature.h>
#include "timer.h"
#include "timerManager.h"
#define coolingPWM 6
#define heatingPWM 5
#define coolingEnable 13
#define heatingEnable 12
#define tecFan 7
#define opticalSensor 0
#define oneWireBus A3
#define COOLING 0
#define HEATING 1
#define OFF 2
Timer timerMainLoop;
Timer timerTecCooling;
Timer timerSampleNoise;
Si7021 si7021;
OneWire oneWire(oneWireBus);
DallasTemperature sensors(&oneWire);
float humidity = 0;
float ambientTemp = 0;
float opticalDewpoint = 0;
float mirrorTemp = 30;
float optical = 30;
float dewPoint = 15;
float relativeHumidity = 30;
int tecState = OFF;
bool cooling = false;
int intervalTecCooling = 200;
float opticalThreshold = 0.5f;
int pwmIncrement = 1;
int startPwm = 100;
int maxPwm = 255;
int intervalMainLoop = 200;
int tecPwm = 0;
int noiseSampleIndex = 0;
int noiseSampleAmount = 10;
float noiseSampleHighest = 0;
float noiseSampleLowest = 10000;
bool noiseSampling = false;
float calculateHumidity(float TD, float T) {
if (TD > T) TD = T;
float rh = 100 * (exp((17.625 * TD) / (243.04 + TD)) / exp((17.625 * T) / (243.04 + T)));
return rh;
}
void SetTEC(int state, int amount) {
tecState = state;
switch (state) {
case COOLING:
digitalWrite(heatingEnable, HIGH);
analogWrite(heatingPWM, 0);
digitalWrite(coolingEnable, HIGH);
analogWrite(coolingPWM, amount);
break;
case HEATING:
digitalWrite(coolingEnable, HIGH);
analogWrite(coolingPWM, 0);
digitalWrite(heatingEnable, HIGH);
analogWrite(heatingPWM, amount);
break;
case OFF:
default:
digitalWrite(coolingEnable, LOW);
analogWrite(coolingPWM, 0);
digitalWrite(heatingEnable, LOW);
analogWrite(heatingPWM, 0);
}
}
void setup() {
wdt_enable(WDTO_2S);
Serial.begin(9600);
pinMode(coolingPWM, OUTPUT);
pinMode(heatingPWM, OUTPUT);
pinMode(coolingEnable, OUTPUT);
pinMode(heatingEnable, OUTPUT);
pinMode(tecFan, OUTPUT);
pinMode(opticalSensor, INPUT);
timerMainLoop.setInterval(intervalMainLoop);
timerMainLoop.setCallback(mainLoop);
timerMainLoop.start();
timerTecCooling.setInterval(intervalTecCooling);
timerTecCooling.setCallback(tecCoolingCallback);
timerSampleNoise.setInterval(intervalTecCooling);
timerSampleNoise.setCallback(sampleNoiseCallback);
uint64_t serialNumber = 0ULL;
si7021.begin();
serialNumber = si7021.getSerialNumber();
sensors.begin();
startNoiseSampling();
}
float getOptical() {
int opt = analogRead(opticalSensor);
return (float)opt / 30.0f;
}
void tecCoolingCallback() {
digitalWrite(tecFan, HIGH);
tecPwm += pwmIncrement;
if (tecPwm > maxPwm) tecPwm = maxPwm;
SetTEC(COOLING, tecPwm);
if (optical <= (noiseSampleLowest - opticalThreshold)) {
dewPoint = mirrorTemp;
opticalDewpoint = optical;
stopTec();
}
}
void startNoiseSampling() {
noiseSampling = true;
noiseSampleHighest = 0;
noiseSampleLowest = 10000;
timerSampleNoise.start();
}
void sampleNoiseReset() {
timerSampleNoise.stop();
noiseSampleIndex = 0;
noiseSampling = false;
}
void sampleNoiseCallback() {
if (noiseSampleIndex > noiseSampleAmount) {
sampleNoiseReset();
startTecCooling();
} else {
if (optical > noiseSampleHighest) noiseSampleHighest = optical;
if (optical < noiseSampleLowest) noiseSampleLowest = optical;
}
noiseSampleIndex++;
}
void startTecCooling() {
cooling = true;
digitalWrite(tecFan, HIGH);
tecPwm = startPwm;
timerTecCooling.start();
}
void stopTec() {
cooling = false;
digitalWrite(tecFan, LOW);
timerTecCooling.stop();
SetTEC(OFF, 0);
}
void mainLoop() {
sensors.setResolution(10);
sensors.requestTemperatures();
ambientTemp = sensors.getTempCByIndex(0);
mirrorTemp = si7021.measureTemperature();
optical = getOptical();
relativeHumidity = calculateHumidity(dewPoint, ambientTemp);
Serial.print(mirrorTemp, 2); Serial.print(" ");
Serial.print(optical, 2); Serial.print(" ");
Serial.println(dewPoint, 2);
if (!cooling && !noiseSampling && (optical >= noiseSampleLowest)) {
startNoiseSampling();
}
}
void loop() {
wdt_reset();
TimerManager::instance().update();
}
原理图

制造工艺
- 现代镜子的科学与工艺
- DIY 蓝牙控制 Arduino 间谍小车
- FlickMote:基于手势的智能家居红外遥控器
- DIY Arduino TV-B-Gone:万能关机遥控器
- DIY 万花筒无限镜 — 打造震撼的 LED 灯光艺术装置
- 自制大尺寸LED主时钟带闹钟功能 — 12米外清晰可见
- Find Me:基于 Arduino 与蓝牙的智能寻物器制作指南
- Arduino 项目优化供电方案:从按键开关到电池监测与数据记录
- Arduino 井字棋游戏:MAX7219 点阵屏 + 纸板外壳 DIY
- 从零构建智能 Arduino 四足机器人:完整指南
- DIY Arduino 手柄方向盘 - 游戏竞速模拟器自制指南
- 利用 Arduino Nano R3 自制全功能 CNC 雕刻机