schouten_tjeerd
Published © MIT

Arduino Chilled Mirror Hygrometer

Measure the dew point and humidity with an Arduino-based Chilled Mirror Hygrometer.

IntermediateFull instructions provided14,561
Arduino Chilled Mirror Hygrometer

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
12V 10A power supply
×1
12V 40mm high power fan
×1
12V 40mm TEC
×1
40mm heat sink
×1
small mirror
×1
Thermally conductive adhesive
×1
Thermal paste
×1
BTS7960 motor driver
×1
3 mm LED: Yellow
3 mm LED: Yellow
×1
OPT101 light sensor
×1
IRF520 MOSFET module
×1
DS18B20 Programmable Resolution 1-Wire Digital Thermometer
Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital Thermometer
×1
SparkFun Humidity and Temperature Sensor Breakout - Si7021
SparkFun Humidity and Temperature Sensor Breakout - Si7021
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Schematics

Chilled Mirror Hygrometer schematic

Code

Chilled Mirror Hygrometer

C/C++
Arduino code
#include <math.h>
#include <avr/wdt.h>  //Watchdog crash detection

//These are custom libraries.
#include "Si7021.h" //humidity sensor with heater
#include <OneWire.h> //DS18B20 temp sensor
#include <DallasTemperature.h> //DS18B20 temp sensor

//Timer library: https://github.com/brunocalou/Timer
#include "timer.h"
#include "timerManager.h" 

//Define the hardware pins on the Arduino board.
#define coolingPWM 6
#define heatingPWM 5
#define coolingEnable 13
#define heatingEnable 12
#define tecFan 7
#define opticalSensor 0 //Analog in
#define oneWireBus A3 //DS18B20 temp sensor

//The state of the TEC.
#define COOLING 0
#define HEATING 1
#define OFF 2

//Timers
Timer timerMainLoop;
Timer timerTecCooling;
Timer timerSampleNoise;

//Temperature sensor (humidity not used). 
Si7021 si7021;

//DS18B20 temp sensor
OneWire oneWire(oneWireBus); 
DallasTemperature sensors(&oneWire);

float humidity = 0;
float ambientTemp = 0;
float opticalDewpoint = 0;

//Set these to an initial higher value to get the Serial Plotter range correct.
float mirrorTemp = 30; 
float optical = 30; 
float dewPoint = 15; //initial value must be lower than the mirror temp.
float relativeHumidity = 30;

int tecState = OFF;
bool cooling = false;

int intervalTecCooling = 200; //How often the TEC timer is updated in ms.
float opticalThreshold = 0.5f; //0.5 //The amount of degrees C the optical reading has to drop below the reference in order to flag condensation detection. This must be a bigger number than the signal noise.
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){

  //The dew point cannot be higher than the temperature.
  if(TD > T){

    TD = T;
  }

  //August-Roche-Magnus approximation. 
  float rh = 100*(exp((17.625*TD)/(243.04+TD))/exp((17.625*T)/(243.04+T)));

  return rh;
}


//Set the TEC to heating, cooling, or off.
void SetTEC(int state, int amount){

  tecState = state;

  //Note that for both heating and cooling, the heating AND cooling pin need to be set to high. Ask the PCB designer why.
  //Driver used to control the TEC: BTS7960 motor driver board. Note that PWM to drive a TEC is not efficient and it is better to use a variable current source. 
  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;
      
    default: 
      digitalWrite(coolingEnable, LOW);
      analogWrite(coolingPWM, 0); 
      digitalWrite(heatingEnable, LOW); 
      analogWrite(heatingPWM, 0);
  }
}

void setup() {

  //Watchdog crash detection. This is for safety because you don't want the TEC to be stuck in heating mode.
  wdt_enable(WDTO_2S); //WDTO_500MS //WDTO_1S
  
  Serial.begin(9600); //9600 //57600

  pinMode(coolingPWM, OUTPUT);
  pinMode(heatingPWM, OUTPUT);
  pinMode(coolingEnable, OUTPUT);
  pinMode(heatingEnable, OUTPUT);
  pinMode(tecFan, OUTPUT);
  pinMode(opticalSensor, INPUT);

  //Setup the timers
  timerMainLoop.setInterval(intervalMainLoop);
  timerMainLoop.setCallback(mainLoop);
  timerMainLoop.start();

  timerTecCooling.setInterval(intervalTecCooling);
  timerTecCooling.setCallback(tecCoolingCallback);

  timerSampleNoise.setInterval(intervalTecCooling);
  timerSampleNoise.setCallback(sampleNoiseCallback);

  //si7021 temp sensor setup.
  uint64_t serialNumber = 0ULL;
  si7021.begin();
  serialNumber = si7021.getSerialNumber();

  //DS18B20 onewire temperature sensor
  sensors.begin(); 

  //Disable the temp sensor debug logging in order to get the graph to work correctly.
  /*
  Serial.print("Si7021 serial number: ");
  Serial.print((uint32_t)(serialNumber >> 32), HEX);
  Serial.println((uint32_t)(serialNumber), HEX);
  //Firware version
  Serial.print("Si7021 firmware version: ");
  Serial.println(si7021.getFirmwareVersion(), HEX);
  */

  startNoiseSampling();
  
}

//Get the optical sensor reading.
float getOptical(){

  int opt = analogRead(opticalSensor);
  float optFactored = (float)opt / 30.0f;

  return optFactored;
}

//Timer callback.
void tecCoolingCallback(){
  
  digitalWrite(tecFan, HIGH);

  //Slowly increase the power of the TEC.
  tecPwm += pwmIncrement;

  //Clamp
  if(tecPwm > maxPwm){

    tecPwm = maxPwm;
  }

  //Set the TEC cooling amount
  SetTEC(COOLING, tecPwm); 

  //Is condensation detected?
  if(optical <= (noiseSampleLowest - opticalThreshold)){

    //Log the dew point;
    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;

  //Start the TEC counter callback.
  timerTecCooling.start();
}

void stopTec(){

  cooling = false;

  //Turn the TEC fan off.
  digitalWrite(tecFan, LOW); 
  
  timerTecCooling.stop();

  //No cooling, no heating
  SetTEC(OFF, 0);  
}


//Non blocking timer.
void mainLoop(){

  //DS18B20 temp sensor for ambient temperature.
  sensors.setResolution(10); //has to be done before each temperature measurement. Note that a higher resolution is slower.
  sensors.requestTemperatures();
  ambientTemp = sensors.getTempCByIndex(0);  

  //si7021 temps sensor for mirror.
  mirrorTemp = si7021.measureTemperature();

  //Get the optical sensor reading.
  optical = getOptical();

  relativeHumidity = calculateHumidity(dewPoint, ambientTemp);

/*
 //Readable format
  Serial.print("dewPoint: ");
  Serial.println(dewPoint, 2);
  Serial.print("mirrorTemp: ");
  Serial.println(mirrorTemp, 2);
  Serial.print("ambientTemp: ");
  Serial.println(ambientTemp, 2);
  Serial.print("relativeHumidity: ");
  Serial.println(relativeHumidity, 2);

*/

  //For the Serial Plotter
  Serial.print(mirrorTemp, 2);
  Serial.print(" ");
  Serial.print(optical, 2);
  Serial.print(" ");
  Serial.println(dewPoint, 2);
 // Serial.print(" ");
 // Serial.println(relativeHumidity, 2);
  
  
  

  //Wait for the condensation to disappear
  if(!cooling && !noiseSampling && (optical >= noiseSampleLowest)){
  
    startNoiseSampling();
  }  
}

void loop() {

  //Watchdog crash detection
  wdt_reset();

  //Update all timers.
  TimerManager::instance().update();
}

Credits

schouten_tjeerd

schouten_tjeerd

0 projects • 5 followers

Comments