Sunday, September 04, 2011

Temperature and Relative Humidity Data Logging

... wanted to share a hobby project that I've been tweaking with for the past several months. This item represents my entree into embedded computing. I hope to eventually put together a couple of environmental sensors that can be deployed to a field site or can be used in the lab.  This project seemed like a good place to start.
This thing measures temperature and relative humidity (RH)
  
Press the button and the LCD screen display current conditions, updating every 2 seconds, for 10 seconds.
The sensor is on the lower left of the face.

The insides.  The Arduino Uno is mounted at the bottom of the project box and the breadboard and other components are on the inside of the lid.  Hooray for double-sided sticky-tape.

Anyway, the electronics for the item are based on Arduino and several breakout components available from a local, boulder-based, electronics hobby company called Sparkfun.  The code that controls data aquisition, logging, and interactions were put together in the Arduino development environment and rely heavily on libraries made available by others.  I've been pleased with level of resources available for working with the Arduino, and the relative ease with which circuits can be put together and the logic can be worked out.  Holly and I attended a one day beginning-Arduino couse several months ago out of curiousity.
The temperature humidity sensor was designed for a friend's wine storage area in his cellar.  The item:

Crudest of crude circuit diagrams for the Temp/RH sensor
My code is here:

/* This "sketch" is code for a temperature and relative humidity logger using the following hardware:

   - an Arduino Uno, 
   - a DHT22 Temperature and Relative Humidity Sensor (http://www.sparkfun.com/products/10167), 
   - a SparkFun data logger (http://www.sparkfun.com/products/9530), 
   - a Sparkfun LCD Screen breakout board (http://www.sparkfun.com/products/9393), and 
   - a real time clock (RTC) module (http://www.sparkfun.com/products/99).
   - a normally open momentary switch
   
   Currently set up to record data approximately every 5 minutes.  I say approximately, because the logic 
   also allows for a user to push current temp and RH to the LCD screen.  The DHT22 apparently has a response
   time of 2 seconds, and user input from the LCD screen takes precedent over the intermittent logging.
   Anyway, the code make generous use of librarys for the using the DHT22, serial communical (the NewSoftSerial
   library deals with printing floats better than the serial functions that are embedded in the Arduino IDE), 
   and communicating with the RTC.  
   
   ... even though there's a real time clock, the codes makes plenty use of the Arduino's own internal clock to 
   keep track of timers for:
     interpreting noise in the LCD activation switch (this is all the debounce stuff that's in the code), 
     figuring out how long to keep the LCD illuminated after pressing the button (think of it like a sustain on a guitar chord or a piano note),
     logging data
     making sure that the arduino doesn't poll the temp/RH sensor too frequently.
     
   Anyway, there's a whole lotta long integers to keep track of all these different timers.  They should roll over every couple of weeks.
   
  Author:  Matt Findley, Boulder, Colorado, matthewfindley at hotmail tod com.
*/


//import statements
#include   // This is the DHT22 library: https://github.com/nethoncho/Arduino-DHT22
#include   // http://arduiniana.org/libraries/newsoftserial/   
#include "Wire.h"  // ...think this library comes with the Arduino IDE.

#define DS1307_ADDRESS 0x68  //address for the real time clock.  This line of code is from an example.

//declare global variables and pin constants

//assign pins
const int txPin = 2;  // pin used to transmit character data to the LCD screen. 
const int buttonPin = 3; // used to poll the normally open momentary switch.  This pin is also connected to a 10K ohm pull down resistor, so it is firmly in the "low" state unless the button is pressed.
const int DHT22Pin = 4;  // used to get the signal from the Temp/RH sensor. This pin is also connected to a 10K ohm pull up resistor, so it is firmly in the "high" state unless the sensor pulls the line to ground.
const int loggerPin = 5;  // pin used to transmit temp and RH measurements to the data logger. 

// Variables will change:
boolean displayState = 0;         // boolean for the current state of power to the LCD screen
int buttonState = LOW;           // the current reading from the button input pin
int lastButtonState = LOW;       // the previous reading from the button  input pin

// the following variables are long's because the time, measured in miliseconds,

//  button debounce variables
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 20;    // the debounce time; increase if the output flickers

// power the lcd display for ~10 seconds after releasing button.
long lastButtonOnTime = 0;
long sustainTime = 10000;

// measure temp and RH no more than every 2000 milliseconds
long lastMeasureTime = 0;
long measureInterval = 2000;

// log temp and RH every 300,000 milliseconds (5 minutes)
long lastLogTime = 0;
long logInterval = 300000;  

// variables for storing temperature, RH, and Time
float temperature;
float relHumid;

int month, monthDay, weekDay, year, hour, minute, second; 

// initialize all the connections

// start NewSoftSerial instance for LCD output
NewSoftSerial LCD(0, txPin);  //TX on 2
NewSoftSerial logger(0, loggerPin);

// Setup a DHT22 instance
DHT22 myDHT22(DHT22Pin);

void setup() {

  // set up real time clock
  Wire.begin();

  // set up serial out to push debugging info over USB to the PC terminal
  Serial.begin(9600);
  
  // set up button for lcd display
  pinMode(buttonPin, INPUT);

  // initializeLogger
  logger.begin(9600); //9600bps is default for OpenLog
  delay(1000); //Wait a second for OpenLog to init
  logger.println("Run OpenLog Test"); 

  // collect initial round of temp/RH measurements
  pollSensor();

  //  initialize lcd
  LCD.begin(9600);  //start 9600 baud com channel with the LCD
  powerUpLCD();
  clearLCD();
  LCD.print("Findley's       Temp/RH Sensor");
  delay(3000);

  clearLCD();
  LCD.print("Version 3.5     7/16/2011");
  delay(3000);

  clearLCD();
  powerDownLCD();

}

void loop() {
  //poll button, but filter out false-positive bounces.  Button will cause the LCD to illuminate and will display current temp and RH
  int reading = digitalRead(buttonPin);
  if(reading != lastButtonState) {
    lastDebounceTime = millis();  //reset timer
  }

  if((millis() - lastDebounceTime) > debounceDelay) {
    buttonState = reading;
  }

  lastButtonState = reading;  //save so that there's something to compare against the next time around the loop.

  //interpret button and power up the lcd screen as needed.
  if (buttonState == HIGH) {
    lastButtonOnTime = millis();
    powerUpLCD();
//    getTime();
    goTo(0,0);
    LCD.print("Temp:");
    goTo(1,0);
    LCD.print("RH:");
    goTo(0, 5);
    LCD.print(temperature, 1);  // print temperature with one decimal place.
    LCD.print("C");
    goTo(1, 3);
    LCD.print(relHumid, 1);   // print relative humidity with one decimal place.
    LCD.print("%");
//    LCD.print(" :");
//    LCD.print(minute);

  } else {
    if((millis()-lastButtonOnTime) > sustainTime) {
      powerDownLCD();
    }
  }

//conditional poll sensor.  Only poll the sensor if it is time to log data or if the display is on.
//   in both cases, we can't poll the temp humidity sensor more than every couple of seconds - seems to return error otherwise.

  if (((millis() - lastLogTime) > logInterval) && ((millis() - lastMeasureTime) > measureInterval)) {
    pollSensor();

    logData();

    //  reset last log time and last measure time.
    lastLogTime = millis();
    lastMeasureTime = millis();
  }
  else if ((displayState == 1) && ((millis() - lastMeasureTime) > measureInterval)) {
    pollSensor();
//    getTime();
  
    //  updateDisplay with the refreshed temp and humidity values.
    goTo(0, 5);
    LCD.print(temperature, 1);  // update temperature with one decimal place, don't need to update the other parts of the displayed info.
    goTo(1, 3);
    LCD.print(relHumid, 1);   // update temperature with one decimal place, don't need to update the other parts of the displayed info.
//    LCD.print(" :");
//    LCD.print(minute);
    
    //  reset last measure time.
    lastMeasureTime = millis();
  }

}  // end bracket for the loop statement

void pollSensor(){
  DHT22_ERROR_t errorCode;
  errorCode = myDHT22.readData();
  switch(errorCode)
  {
    case DHT_ERROR_NONE:
      temperature = myDHT22.getTemperatureC();
      relHumid = myDHT22.getHumidity();
      break;
    case DHT_ERROR_CHECKSUM:      
      temperature = myDHT22.getTemperatureC();
      relHumid = myDHT22.getHumidity();
      break;    
    case DHT_BUS_HUNG:      
//      Serial.println("BUS Hung ");      
      break;    
    case DHT_ERROR_NOT_PRESENT:      
//      Serial.println("Not Present ");      
      break;    
    case DHT_ERROR_ACK_TOO_LONG:      
//      Serial.println("ACK time out ");      
      break;    
    case DHT_ERROR_SYNC_TIMEOUT:
//      Serial.println("Sync Timeout ");      
      break;    
    case DHT_ERROR_DATA_TIMEOUT:      
//      Serial.println("Data Timeout ");      
      break;    
    case DHT_ERROR_TOOQUICK:      
//      Serial.println("Polled to quick ");      
      break;
  } //end switch block
}  // end pollSensor Function.  

void logData() {
  getTime();
  logger.print("H,");
  logger.print(month);
  logger.print("/");
  logger.print(monthDay);
  logger.print("/");
  logger.print(year);
  logger.print(" ");
  logger.print(hour);
  logger.print(":");
  if (minute < 10) {
   logger.print("0");
   logger.print(minute);
  } else {
   logger.print(minute);
  } 
  logger.print(":");
  if (second < 10) {
   logger.print("0");
   logger.print(second);
  } else {
   logger.print(second);
  } 
  logger.print(",");
  logger.print(temperature, 1);
  logger.print(",");
  logger.println(relHumid, 1);

  Serial.print("H,");
  Serial.print(month);
  Serial.print("/");
  Serial.print(monthDay);
  Serial.print("/");
  Serial.print(year);
  Serial.print(" ");
  Serial.print(hour);
  Serial.print(":");
  if (minute < 10) {
   Serial.print("0");
   Serial.print(minute);
  } else {
   Serial.print(minute);
  } 
  Serial.print(":");
  if (second < 10) {
   Serial.print("0");
   Serial.print(second);
  } else {
   Serial.print(second);
  } 
  Serial.print(",");
  Serial.print(temperature, 1);
  Serial.print(",");
  Serial.println(relHumid, 1);
}

void clearLCD(){
   LCD.print(0xFE, BYTE);   //command flag
   LCD.print(0x01, BYTE);   //clear command.
   delay(10);
}

void powerDownLCD() {
  LCD.print(0x7C, BYTE); //issues special command
  LCD.print(128, BYTE); //turn off backlight

  LCD.print(0xFE, BYTE); //put in to extended LCD command mode
  LCD.print(0x08, BYTE);   //off command.
  delay(10);
  displayState = 0;
}

void powerUpLCD() {
  LCD.print(0xFE, BYTE); //put in to extended LCD command mode
  LCD.print(0x0C, BYTE);   //on command.
  LCD.print(0x7C, BYTE); //issues special command
  LCD.print(157, BYTE); //turn on backlight
  delay(10);
  displayState = 1;
}

//helper function for navigating around the LCD screen.  
void goTo(int row, int column)  //starts w/ line 0 col 0
{  
  LCD.print(0xFE, BYTE);
  LCD.print((column + row*64 + 128), BYTE);
}

// helper function to convert binary coded decimals from the real time clock to normal decimal numbers

byte bcdToDec(byte val)  {
  return ( (val/16*10) + (val%16) );
}

void getTime() {
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.send(0);
  Wire.endTransmission();

  Wire.requestFrom(DS1307_ADDRESS, 7);

  second = bcdToDec(Wire.receive());
  minute = bcdToDec(Wire.receive());
  hour = bcdToDec(Wire.receive()); //24 hour time
  weekDay = bcdToDec(Wire.receive()); //0-6 -> sunday - Saturday
  monthDay = bcdToDec(Wire.receive());
  month = bcdToDec(Wire.receive());
  year = bcdToDec(Wire.receive());
}