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());
}

Saturday, March 31, 2007

Anthony Hopkins is Forest Gump.

So apparently the theme of this blog is now:
drink wine. watch netflix. ramble on.

I'm ok with that. It beats prose any day.

...just finished watching "The Fastest Indian" starring Anthony Hopkins. ...not a bad movie, but I just couldn't help making the comparison to Forest Gump. The guy gets through the entire movie on a handshake and a queer accent. Not bad... gotta wonder if that phony TX accent from G.W. hasn't opened a couple of doors for him.

...never underestimate the affect of a bright smile and funny accent.

MF

Friday, October 20, 2006

Wilderness Ain't All Wild Flowers

This recent news story reminds me of the occasional tragedy that happened on the front range. A small child wanders off into the wilderness and is lost to humanity (including, most tragically, the child's parents). In Colorado, the culprit was usually a mountain lion. In Oregon - I don't know.

Anyway, my sympathy goes out to the distraught mother, father, siblings, and extended family. To have your kin extinguished by the wilderness seems a particularly cruel and random fate.

..So sad. ...So sad.

MF

Sunday, September 17, 2006

Down by Law is a really good movie

This week was a period of NetFlix reckoning for me. I made the mistake of adding a burst of movies involving Tom Waits and that impulsive act finally nipped me in the ass. This week I was visited by the ghosts of Tom Waits past. Namely: Brahm stoker's Dracula, Mystery Train, and Down By Law. Of the three, I apparently saved the best for last. Down by Law kinda caught me by surprise. Apparently the writer and director, Jim Jarmusch, can actually pull a mildly interesting PLOT out of his ass (who knew?). I took a cue from the Bob Belini character and jotted some fragments that struck me as notable:

  • ...You're valuable time (when speaking with people make sure you slip this in in a way where they don't know if you're being sarcastic or not).
  • "It's a sad and beautiful world" (an artsy phartsy phrase but what ever).
  • ...bad mood... (this is the universal excuse. As in - yeah I killed your cat but, I was in a bad mood.)
  • "Mr. Almighty Hot Shit.
  • "White limousine" (Detroit's equivalent to sayin': "my shit don't stink")
  • "Out of the car, asshole".
  • As far as I'm concerned, you don't even exist. Not at all. Got it? (man, that's a pretty good put down.)
  • New York and New Orleans are the only two cities in the United States that can be considered separate countries. (this is a paraphrase of the directors remarks on the bonus tracks. Still - pretty foreboding considering this statement was uttered before Katrina or 9/11.)

Saturday, April 15, 2006

I'm not an engineer - Reason Number 67


Changing the wiper blades on my car is the mental equivalent to solving three sides to a rubix cube. No, seriously, Holly usually has to do this.

(image from Popular Mechanics)

Friday, April 07, 2006

Geekin' Hard

OK, so this post is gonna make me seem like a Giant Geek.

I have to build a stock of continuing education credits as part of the certified professional soil scientist thing (I need forty hours in two years I think). This is kind of new to me, so I'm trying to get a jump on things and figure out how much of this I can do without actually going to $600 conferences (or whatever the going rate for these things is). I can get up to 10 or 20 credits just by reading journal articles. The nice thing is that I'm real close to OSU and they have plenty of journals to choose from.

So, one of the weekly e-mails that I get is from the National Academies and it mentioned a soil science related workshop. I went out and retrieved the headline paper related to the workshop and read it as my first CEU credit. It was called Advancing the Frontiers of Soil Science Towards a Geoscience and contained very little practical information about cleaning up a petroleum spill in the oil field, figuring out how to make synthetic Hanford groundwater, or some of the other challenges that confront me from time to time. In the paper, somebody is making a noisy case for introducing yet another hyphenated discipline into the field. Anyway, the paper had all sorts of BIG IDEA topics that I suspect academic proposal reviewers would eat up like girl scout cookies.

The take away point for me: Sometimes the scope of a scientific discipline is only limited by the boundaries placed upon it by the practitioners - its all about the conception of soil science by the people that do it. Soil Science used to be an applied science about making crops grow better. Now it has evolved and works in the environmental arena (my emphasis), and is starting to take on global climate issues.

It was published in an issue devoted to Hydropedology: Bridging disciplines, scales and data (When I asked Holly to pull the paper, she exclaimed "who ever heard of Geoderma". Beats me.). It looked like it had all sorts of good stuff in there for someone who is looking at soil/water interactions. For example: Soil moisture patterns in a forested catchment: A hydropedological perspective.

Anyway, by now it should be apparent that this post is simply a way to make a couple of readers that I know are out there aware of some stuff out there in the literature (literature that I have no business reading, I think) that might or might not be of interest.

At this point I will stop pretending to be a grad student. Go check out Kenny Roby's new album called the Mercy Filter. This guy was from the Clemson, South Carolina area and rocks harder than you.