DIY зеркальный гигрометр на Arduino: точное измерение точки росы и влажности
Компоненты и материалы
- Arduino UNO — 1 шт.
- Блок питания 12 В 10 А — 1 шт.
- Вентилятор 12 В 40 мм (высокой мощности) — 1 шт.
- Пелтье-элемент (TEC) 12 В 40 мм — 1 шт.
- Радиатор 40 мм — 1 шт.
- Маленькое зеркало (квадратное лучше) — 1 шт.
- Термопроводящий клей / термопаста + суперклей по краям — 1 компл.
- Драйвер двигателя BTS7960 (H-мост) — 1 шт. (управление ТЭК)
- Светодиод 3 мм жёлтый (источник света) — 1 шт. (или лазер)
- Оптический сенсор OPT101 (фотодиод + усилитель) — 1 шт.
- MOSFET-модуль IRF520 (управление вентилятором) — 1 шт.
- Датчик температуры DS18B20 (1-Wire) — 1 шт. (окружающая среда)
- Датчик температуры/влажности Si7021 (I2C) — 1 шт. (температура зеркала)
Инструменты
- Клеевой пистолет
Программное обеспечение
- Arduino IDE
- Библиотеки: Si7021, OneWire, DallasTemperature, Timer (brunocalou/Timer)
Принцип работы зеркального гигрометра
Пелтье охлаждает зеркало до появления конденсата. Оптический сенсор (OPT101) фиксирует падение отраженного света от светодиода/лазера. Температура зеркала в момент появления конденсата = точка росы. Относительная влажность рассчитывается по формуле Магнуса по точке росы и температуре воздуха.
Упрощения vs коммерческие приборы
- Зеркало обычное (не Pt/Rh), термодатчик SMD на поверхности (не PRT внутри).
- Размер зеркала 4 см (коммерческие ~5 мм) — больше мощность, проще монтаж.
- Нет корпуса от света (рекомендуется 3D-печать с вентиляцией).
- Не измеряет точку замерзания (нужен второй сенсор на рассеянном свете).
Сборка
- Очистите ТЭК и радиатор. Нанесите термоклей на горячую сторону ТЭК, прижмите радиатор, дайте высохнуть.
- Точно так же приклейте зеркало на холодную сторону (термопаста + клей по краям). Равномерность слоя критична.
- Приклейте вентилятор на радиатор (дует через радиатор наружу).
- Подайте питание на вентилятор, кратковременно включите ТЭК — найдите зоны, где конденсат появляется одновременно. Запомните/промаркируйте.
- Приклейте Si7021 (температура зеркала) в эту зону: капля термопасты на сенсор, 4 капли горячего клея по углам PCB, прижмите к зеркалу. Изолируйте плату клеем от конденсата.
- Смонтируйте светодиод/лазер и OPT101 на жесткой медной проволоке (или 3D-держак), направьте луч на зеркало, приёмник — на отраженный луч.
- Управление ТЭК: BTS7960 (PWM на Pin 5/6, Enable на 12/13). Вентилятор — IRF520 на Pin 7.
- Настройте чувствительность OPT101 подстроечным резистором (см. даташит, Fig.3).
Безопасность
Код не поддерживает нагрев, но аппаратно возможно (смена полярности на BTS7960). Нагрев без радиатора на холодной стороне опасен: перегрев клея, пожар. Используйте WDT (см. проект 1010016401).
Точность и калибровка
- Точка росы — абсолютная величина, калибровка менее критична, чем у ёмкостных датчиков.
- Разница температур между зоной сенсора и зоной оптики дает погрешность. Можно калибровать по коммерческому гигрометру.
- Загрязнение зеркала частично компенсируется (опорное измерение в начале цикла), но меняет температуру конденсации — чистите периодически.
- Требуется высокое разрешение датчиков (0.01 °C), абсолютная точность второстепенна. Лучше использовать TSYS01 (0.1 °C).
- Не охлаждайте быстрее времени отклика термодатчика.
- Горячий воздух с радиатора не должен попадать на зеркало.
Код (Chilled Mirror Hygrometer)
#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 A0
#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, ambientTemp = 0, opticalDewpoint = 0;
float mirrorTemp = 30, optical = 30, dewPoint = 15, relativeHumidity = 30;
int tecState = OFF;
bool cooling = false;
int intervalTecCooling = 200;
float opticalThreshold = 0.5;
int pwmIncrement = 1, startPwm = 100, maxPwm = 255;
int intervalMainLoop = 200;
int tecPwm = 0, noiseSampleIndex = 0, noiseSampleAmount = 10;
float noiseSampleHighest = 0, noiseSampleLowest = 10000;
bool noiseSampling = false;
float calculateHumidity(float TD, float T) {
if (TD > T) TD = T;
return 100 * exp((17.625*TD)/(243.04+TD)) / exp((17.625*T)/(243.04+T));
}
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:
digitalWrite(coolingEnable, LOW); analogWrite(coolingPWM, 0);
digitalWrite(heatingEnable, LOW); analogWrite(heatingPWM, 0);
break;
}
}
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);
si7021.begin(); sensors.begin();
startNoiseSampling();
}
float getOptical() { return analogRead(opticalSensor) / 30.0; }
void tecCoolingCallback() {
digitalWrite(tecFan, HIGH);
tecPwm = min(tecPwm + pwmIncrement, 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 Plotter: mirrorTemp optical dewPoint
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();
}
Схемы
Схема подключения всех модулей (фото в оригинале).
Производственный процесс
- Наука и мастерство современных зеркал: от полированного металла к diéлектрическим покрытиям
- Сборка Bluetooth-робота-шпиона на Arduino с управлением с телефона
- FlickMote: жестовое IR-пульт для умного дома на Arduino 101 и ультразвуковом датчике
- DIY Arduino TV-B-Gone: универсальный пульт для выключения любого телевизора
- DIY Калейдоскоп-бесконечное зеркало: потрясающий LED-дисплей
- Создание пользовательских LED-часов с будильником — видно с 12 метров
- Find Me: Умный поисковик вещей на Arduino и Bluetooth
- Оптимизированные решения питания для проектов на Arduino
- Крестики-нолики на Arduino с матрицей MAX7219 и картонным корпусом
- Пошаговое руководство: сборка умного четырёхногого робота на Arduino