/* WeatherStation: Created by Gavin Maxwell, 2010 */ #include #include #include #include #include // --> 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: " ); else lcd.print( "T:" ); } // was it down long enough to indicate a real press if ( millis() > press + 1500 ) { lcd.setCursor( 0, 1 ); lcd.print( "T: " ); minTemp = 255; maxTemp = -255; EEPROM.write( MIN_TEMP_ADDRESS, 255 ); EEPROM.write( MAX_TEMP_ADDRESS, 255 ); } // force a temp update lastUpdate = millis() - UPDATE_INTERVAL; } } // // 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; }