Clock/temperature display for my Father

My Father loves clocks and anything that reports the temperature (and other weather related info). So for Xmas I built this little piece to add to his collection.

It uses a DS1307 with backup battery to track time, and a LM35 for recording the temperature. The LCD displays time, date, current temperature and min and max recorded temperatures (which are backed up in EEPROM). The button at the bottom of the board is used to reset the min/max values. Also I included a serial connection so the sketch can be updated, and also a little serial protocol so the time can be adjusted if required. I used an iPod power supply for nice clean 5V supply - which I modded to use a 2-pin connector to attach to the board.

I also added a bunch of canned messages for specific dates, Xmas, NY, birthdays etc.

Finally presented it to him along with a laminated version of the schematic I created. He loved it!

Next version I'm thinking more weather info (humidity and baro pressure probably), and using nixies for the display instead of boring old LCD.

Cheers,

The perspex front finishes it off nicely.

it's great! congratulations!

Wow very cool! You would perhaps add a ambient light sensor to control the back light state (to save power during the day). Would you mind sharing your code and/or schematic?

I love that it records the max/min temps, and it looks great.

I made myself a clock using two LED matrices hidden in photo frames though only one's working here:

The first frame is time, the second alternates between day of week/month and date/temperature. I've been planning to update it (making a PCB for it rather than a full arduino and have buttons to change the time as currently it's set in a separate sketch that I upload to it, then reload the clock sketch). I'll add an eeprom for logging the temp now :slight_smile:

I've just remembered, it can tick as well. I put a relay in it which flips every second. There's a switch to turn it off though, it's a bit annoying.

I'll add an eeprom for logging the temp now

You know there's EEPROM onboard the 168/328 chips right? That's what I used as I'm not logging historical data, just a few bytes for the min and max values.

I've just remembered, it can tick as well. I put a relay in it which flips every second. There's a switch to turn it off though, it's a bit annoying.

Oh I loove that!! I'll add a relay to my nixie version - on the hour I'm planning the flip through all the digits to avoid cathode poisoning, and if I whack the relay at the same time it'll feel like the flight boards at the airports flipping over! :slight_smile:

The back on my unit above is visible through perspex as well - I tried to be nice and neat with the point to point soldering. Will post a pic of that plus my schematic later (it's at home).

Cheers,

You might want a relay per digit as they're relatively slow. I initially had the digits change in a cascade style and wanted to click the change but found the relay wasn't up to it.

I'd entirely forgotten about the onboard eeprom! Ta for the reminder.
Ta for mentioning cathode poisoning too, I hadn't heard of that and Iv'e just got myself a set of nixies.

Thanks for the tip.

Back of device shown:

Schematic:

Cheers,

Thanks for the schematic and pictures. Would you mind posting your source code as well? If you'd prefer to keep it closed-source that's totally understandable. Keep up the good work!

Hi, got no problem posting the code - there's not a lot of rocket science going on! I will remove the bits with personal date-based messages though. Shan't be long...

G.

First bit:

/*
  WeatherStation:
    Created by Gavin Maxwell, 2010

*/

#include <WProgram.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>
#include <Wire.h>
#include <DS1307.h>

// --> START PIN ASSIGNMENTS

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd( 17, 16, 12, 11, 10, 9 ); // modified for better board routing

#define TEMP_PIN 0            // analog IN pin 0
#define TEMP_REF_PIN 1        // analog IN pin 1
#define RESET_MINMAX_PIN  13  // button to reset the stored values in EEPROM

#define INT_0 0  // SQW interrupt from DS1307

// --> END PIN ASSIGNMENTS

int rtc[ DS1307_MAX_ITEMS ];
char* DOW[8] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };

#define MV_PER_DEGREE 10 // each 10mV is 1C
#define FULLSCALE_MV ((float)(4700)) // 5000 mV = 1024 from the ADC - actually 4700mV is approx what is supplied to LM35
#define FULLSCALE_ADC ((float)(1024)) // ADC returns from 0 - 1023 so 1024 unique values
#define MV_PER_ADC_STEP ((float)( FULLSCALE_MV / FULLSCALE_ADC )) // mV per ADC increment
#define INTER_READ_DELAY 20 // milliseconds between reads
#define UPDATE_INTERVAL  10000 // milliseconds between updates
#define USE_GROUND_REF 1

#define TEMP_SAMPLES 20 // number of samples to average

// EEPROM Addresses
#define MIN_TEMP_ADDRESS  0x00
#define MAX_TEMP_ADDRESS  0x01
#define CLOCK_SET_ADDRESS 0x02
#define CALIBRATION_ADDRESS 0x03  // NOT USED

unsigned long lastUpdate;
long minTemp = 255;
long maxTemp = -255;
long LM35Calibration = -1;  // Rough as nails

boolean showColon = true; // flag to blink the colon seperator

#define DEGREE_CHAR 4
#define STEP1_CHAR 5
#define STEP2_CHAR 6
#define STEP3_CHAR 7
#define ARROW_CHAR 0x7E

byte degree[8] = {
  B01100,
  B10010,
  B10010,
  B01100,
  B00000,
  B00000,
  B00000,
};
byte step1[8] = {
  B00000,
  B01110,
  B10001,
  B10001,
  B10001,
  B01110,
  B00000,
};
byte step2[8] = {
  B00000,
  B01110,
  B11111,
  B11011,
  B11111,
  B01110,
  B00000,
};
byte step3[8] = {
  B00000,
  B01110,
  B11111,
  B11111,
  B11111,
  B01110,
  B00000,
};

volatile boolean updateTime = true;

// DS1307 1Hz pulse ISR - set our flag and get out!
void pulseISR()
{
  updateTime = true;
}

//
// Setup all of our pins, initialise the RTC, serial port and LCD
//
void setup()
{
  int i, ndx;
  pinMode( TEMP_PIN, INPUT );
  pinMode( TEMP_REF_PIN, INPUT );
  
  pinMode( RESET_MINMAX_PIN, INPUT );
  digitalWrite( RESET_MINMAX_PIN, HIGH );  // internal pull-up - switch will pull low to indicate reset
  
  // DS1307 sends a 1Hz pulse - we use this to update the display  
  attachInterrupt( INT_0, pulseISR, FALLING );
  
  Serial.begin( 9600 );

  // read the stored min and max
  // values will be 255 if never written before
  // if max has never been recorded set it super low
  // to force an update first time we measure
  minTemp = EEPROM.read( MIN_TEMP_ADDRESS );
  maxTemp = EEPROM.read( MAX_TEMP_ADDRESS );
  if ( maxTemp == 255 )
    maxTemp = -255;
    
  // EEPROM stores a unsigned byte, so we need to handle
  // negatives. 200 limit OK as sensor goes down to -55 (0xC9, 201)
  if ( minTemp > 200 )
    minTemp = minTemp - 256;
  
  if ( maxTemp > 200 )
    maxTemp = maxTemp - 256;

  // if EEPROM has never been set then use a default date
  if ( EEPROM.read( CLOCK_SET_ADDRESS ) == 255 )
  {
    RTC.stop();
    RTC.set( DS1307_SEC, 1 );
    RTC.set( DS1307_MIN, 17 );
    RTC.set( DS1307_HR, 15 );
    RTC.set( DS1307_DOW, 6 );
    RTC.set( DS1307_DATE, 16 );
    RTC.set( DS1307_MTH, 10 );
    RTC.set( DS1307_YR, 10 );
    RTC.start();
    EEPROM.write( CLOCK_SET_ADDRESS, 1 );
  }
  
  // Define custom characters
  lcd.createChar( DEGREE_CHAR, degree );
  lcd.createChar( STEP1_CHAR, step1 );
  lcd.createChar( STEP2_CHAR, step2 );
  lcd.createChar( STEP3_CHAR, step3 );

  // set up the LCD's number of rows and columns: 
  lcd.begin( 16, 2 );
  
  // Get the current time and display Xmas Card if it's Xmas!
  RTC.get( rtc, true );

  // SNIP FOR PRIVACY REASONS
  
  // enable 1Hz square wave output
  RTC.enableOutput();
  RTC.setOutputRate( 0 );
  
  lcd.clear();
  
  lastUpdate = millis() - UPDATE_INTERVAL;
}

//
// Simple formatter of numeric values
//
void BufferPrint( char *buffer, unsigned long value, unsigned long i, int bufLen )
{
  unsigned long temp;
  int bufindex = 0;
 
  if ( buffer )
  {
    memset( buffer, 0, bufLen );
    while ( i > 0 )
    {
      temp = value / i;
      value -= temp * i;
      buffer[ bufindex++ ] = (char)( '0' + temp );
      i/=10;
    }
  }
}

//
// Retrieves updated time from DS1307, formats it then displays on LCD
//
void UpdateTime()
{
  const int timeBufLen = 8;
  char buf[ timeBufLen ];

  RTC.get( rtc, true );

  lcd.setCursor( 0, 0 );
  BufferPrint( buf, rtc[ DS1307_HR ], 10, timeBufLen );
  lcd.print( buf );
  lcd.print( showColon ? ":" : " " );
  showColon = !showColon;
  BufferPrint( buf, rtc[ DS1307_MIN ], 10, timeBufLen );
  lcd.print( buf );

  lcd.print( " " );

  // Check DOW is a valid value
  if ( rtc[ DS1307_DOW ] <= 0 || rtc[ DS1307_DOW ] > 7 )
    lcd.print( "???" );
  else
    lcd.print( DOW[ rtc[ DS1307_DOW ] ] );
  lcd.print( " " );

  BufferPrint( buf, rtc[ DS1307_DATE ], 10, timeBufLen );
  lcd.print( buf );
  lcd.print( "/" );
  BufferPrint( buf, rtc[ DS1307_MTH ], 10, timeBufLen );
  lcd.print( buf );
  
  // reset flag until next interrupt
  updateTime = false;
}

//
// Decode the entered time and set the DS1307
// Format: THHMMSSWDDMMYY
// where W is 1->7 for Monday->Sunday
//
void HandleTimeSet()
{
  int inByte = Serial.read();
  
  if ( inByte == 't' || inByte == 'T' )
  {
    int b1, b2, hh, mm, ss, wd, dd, mo, yy;
    
    Serial.println( "SETTING THE TIME" );
    b1 = Serial.read();
    b2 = Serial.read();
    hh = ( ( b1 - '0')  * 10 ) + ( b2 - '0' );
    b1 = Serial.read();
    b2 = Serial.read();
    mm = ( ( b1 - '0')  * 10 ) + ( b2 - '0' );
    b1 = Serial.read();
    b2 = Serial.read();
    ss = ( ( b1 - '0')  * 10 ) + ( b2 - '0' );
    b1 = Serial.read();
    wd = ( b1 - '0');
    b1 = Serial.read();
    b2 = Serial.read();
    dd = ( ( b1 - '0')  * 10 ) + ( b2 - '0' );
    b1 = Serial.read();
    b2 = Serial.read();
    mo = ( ( b1 - '0')  * 10 ) + ( b2 - '0' );
    b1 = Serial.read();
    b2 = Serial.read();
    yy = ( ( b1 - '0')  * 10 ) + ( b2 - '0' );

    RTC.stop();
    RTC.set( DS1307_SEC, ss );
    RTC.set( DS1307_MIN, mm );
    RTC.set( DS1307_HR, hh );
    RTC.set( DS1307_DOW, wd );
    RTC.set( DS1307_DATE, dd );
    RTC.set( DS1307_MTH, mo );
    RTC.set( DS1307_YR, yy );
    RTC.start();
  }
  else if ( inByte == 'c' || inByte == 'C' )
  {
    Serial.println( "TEMPERATURE CALIBRATION NOT IMPLEMENTED YET" );
    
  }
}

//
// A press on the rest button over a set duration indicates
// we need to clear the current stored min and max temperatures
//
void CheckForResetMinMax()
{
  // Handle resetting the min and max temp values
  if ( digitalRead( RESET_MINMAX_PIN ) == LOW )
  {
    long press = millis();
    
    // spin whilst the buton is down
    while ( digitalRead( RESET_MINMAX_PIN ) == LOW )
    {
      lcd.setCursor( 0, 1 );
      if ( millis() > press + 1500 )
        lcd.print( "T:<RELEASE>     " );
      else
        lcd.print( "T:<HOLD 2 RESET>" );
    }
    
    // was it down long enough to indicate a real press
    if ( millis() > press + 1500 )
    {
      lcd.setCursor( 0, 1 );
      lcd.print( "T:<RESETTING>   " );

      minTemp = 255;
      maxTemp = -255;
      EEPROM.write( MIN_TEMP_ADDRESS, 255 );
      EEPROM.write( MAX_TEMP_ADDRESS, 255 );
    }

    // force a temp update
    lastUpdate = millis() - UPDATE_INTERVAL;
  }
}

Second bit:

//
// We loop around here whilst ever we have power!
//
void loop()
{
  long temp;
  
  // Time to update the time?
  if ( updateTime )
    UpdateTime();
  
  // Any input to process?
  if ( Serial.available() )
    HandleTimeSet();

  // Has user pressed the reset button
  CheckForResetMinMax();
  
  // time to update the temp reading?
  if ( ( millis() - lastUpdate ) > UPDATE_INTERVAL )
  {
    temp = LM35Read();
    
    // Keep min and max values stored in EEPROM
    if ( temp < minTemp )
    {
      minTemp = temp;
      EEPROM.write( MIN_TEMP_ADDRESS, minTemp );
    }
    if ( temp > maxTemp )
    {
      maxTemp = temp;
      EEPROM.write( MAX_TEMP_ADDRESS, maxTemp );
    }
    
    lcd.setCursor( 0, 1 );
    lcd.print( "T:");
    lcd.print( temp );
    lcd.write( DEGREE_CHAR );  // custom degree sign
    lcd.print( "C " );
    lcd.print( "[" );
    lcd.print( minTemp );
    lcd.write( ARROW_CHAR );  // right facing arrow
    lcd.print( maxTemp );
    lcd.print( "]   " );
    lastUpdate = millis();
  }
}

//
// Take a reading from the sensor. We actually take multiple and average them.
// The sensor returns a voltage that maps directly to temperature
//
long LM35Read()
{
  int ndx;
  long adcVal = 0, mV;
  
  animateStart();
  for (ndx = 0; ndx < TEMP_SAMPLES; ndx++ )
  {
    mV = analogRead( TEMP_PIN );
    adcVal += mV;
#if ( USE_GROUND_REF )
    adcVal -= analogRead( TEMP_REF_PIN );
#endif
    delay( INTER_READ_DELAY );
    animateStep();
  }
  animateStop();

  // average it
  adcVal = (long)( adcVal / TEMP_SAMPLES );
  
  // convert value from ADC steps (0-1023) to mV
  mV = adcVal * MV_PER_ADC_STEP;

  // now convert mV to degrees C
  return( mV / MV_PER_DEGREE ) + LM35Calibration;
}

Note I started implementing a calibration mechanism but haven't completed that yet.

Thanks pocketscience, much appreciated!