/*
* Clock project using LED, LCD and Wave Shield
* Provides date, time, temp, humidity and limited number of event notifications
* Also can chime in multiple ways, including Westminster chimes and cuckoo clock
* Chimes are provided by Adafruit Wave Shield on serial bus
* A second Arduino to run the Wave Shield is used due to memory limitations
* and all the global variables and libraries involved in sound, LED, LCD, etc.
*/
/* Files on SD Card must match this sketch:
* in format SOUNDnn.WAV where nn is two digit sound number
* and one file called "STARTUP.WAV" that is played once on SETUP
1 Short 1 (no Westminster)
2 Short 2
3 Short 3
4 Short 4
5 Short 5
6 Short 6
7 Short 7
8 Short 8
9 Short 9
10 Short 10
11 Short 11
12 Short 12
13 Q1 (Quarter Hour Chimes)
14 Q2
15 Q3
16 Long 1 (Westminster Hours)
17 Long 2 (with built in Q4)
18 Long 3
19 Long 4
20 Long 5
21 Long 6
22 Long 7
23 Long 8
24 Long 9
25 Long 10
26 Long 11
27 Long 12
28 Cuckoo Clock 1
29 Cuckoo Clock 2
30 Cuckoo Clock 3
31 Cuckoo Clock 4
32 Cuckoo Clock 5
33 Cuckoo Clock 6
34 Cuckoo Clock 7
35 Cuckoo Clock 8
36 Cuckoo Clock 9
37 Cuckoo Clock 10
38 Cuckoo Clock 11
39 Cuckoo Clock 12
*/
// Code to set up the clock
// Pushbutton 1 is the toggler , cycle between vhime, year, month, day, hours, minutes, seconds
// Pushbutton 2 is the increment up one
// Pushbutton 3 is the decrement down one
// Chime setup
// 0 = No chimes
// 1 = Chime for hour and 1/2 hour only
// 2 = Westminster hours + quarterly
// 3 = Cuckoo clock (hours and 1/2 hour only like above but different chime
// 4 = Westminster silenced after 11pm and before 7am
// include the libraries for LCD Display, DHT-11 Temperature/humidity sensor and DS3231 RTC and
// include libraries for bigger seven segment display with backpack
#include "DHT.h"
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
#include "Adafruit_LEDBackpack.h"
// Set up output functions
Adafruit_7segment matrix = Adafruit_7segment();
LiquidCrystal_I2C lcd(0x27,20,4); // initialize LCD
#define DHTPIN 8 //Temp Humidity sensor on pin 8
#define DHTTYPE DHT11 // DHT 11 because the sensor is that type
// Initialize DHT sensor for normal 16mhz Arduino
DHT dht(DHTPIN, DHTTYPE);
// Initialize for Adafruit DS3231 RTC real time clock
RTC_DS3231 rtc;
char daysOfTheWeek[7][12] = {"Sunday ", "Monday ", "Tuesday ", "Wednesday", "Thursday ", "Friday ", "Saturday "};
// Event arrays
// Load with month of 'event', day of event and description to be displayed
const int eventnumber = 19; // number of events and remember: No strings longer than 20 characters!
int eventmonth [eventnumber] = {6,4,11,2,7,12,11,6,12,6,3,12,1,9,10,3,2,2,7};
int eventday [eventnumber] = {11,1, 23, 12,4,3,12,29,25,18,17,31,1,3,31,17,14,2,14};
char eventdescription [eventnumber] [21] =
{"Dana's Anniversary","April Fool's Day", "Joan's Birthday", "Sydney's Birthday", "Happy July 4th!", "Dana's Birthday",
"David's Birthday", "Our Anniversary", "Merry Christmas", "Leah's Birthday", "St Patrick's Day", "New Year's Eve", "New Year's Day",
"Forrest's Birthday", "Happy Halloween", "Saint Patrick's Day", "Valentine's Day!", "Groundhog Day", "Bastille Day"};
const int pb1pin = 10; // Pin assignments for reset, increment and decrement
const int pb2pin = 11;
const int pb3pin = 12;
const int potpin = A0; // Pin assignment for analog reading potentiometer for LED brightness
int potvalue = 15; // Brightness for LED (default to full 0-15)
int setupindex = 0; // first thing to set if required
bool insetupmode = false; // and assume RTC is set, OK, etc., and no 'set' required
// date time array for setting, reading, displaying
const int setsize = 7; // size of the setting array
const int setchime = 0; // set chime choice first to help seconds synching
const int setyear = 1; // index name for each element
const int setmonth = 2;
const int setday = 3;
const int sethour = 4;
const int setminute = 5;
const int setsecond = 6;
int setarray [setsize] = {0,2019, 1, 1, 1, 1,0}; // set year, month, day, hour, minutes, seconds and chimes
int lowlimit [setsize] = {0,2019, 1, 1, 0, 1,0}; // lower limit for each
int highlimit [setsize] = {4,2050, 12, 31, 23, 59, 59}; //high limit for each
char setdesc [setsize] [8] = {"Chimes","Year", "Month", "Day", "Hour", "Minutes", "Seconds"};
char chimedesc [5] [12] = {"Silent ", "Hours Only ", "Westminster", "Cuckoo ", "Night Quiet"};
char shortchimedesc [5] [5] = {"SLNT","HOUR","WSTM", "CUCK", "ANSO"}; // ANSO stands for Automatic Night Silent Option (11:00 to 6:59am silenced)
const int silent = 0; // no chiming at all
const int hoursonly = 1; // no prelude, just chime number of hours and half hour single chime
const int westminster =2; // hours + Westminster prelude per quarter
const int cuckoo = 3; // Same as hoursonly, but cuckoo clock sounds
const int ANSO = 4; // Same as westminster, but skip hour from 11:01pm-6:59am (silent then)
const int cqtr0 = 1; // Chime the hour
const int cqtr1 = 2; // Chime the quarter hour
const int cqtr2 = 3; // Chime the half hour
const int cqtr3 = 4; // Chime the 3/4 hour
// The following four 'starts' need to be synchronized with the SD Card files
// They correspond to first file of each chime type (e.g. cuckoo single is SOUND27.WAV)
const int firstshort = 1; //file position and name of short, single chime
const int firstquarter = 13; //same for Westminster quarters (1,2,3)
const int firstwestminster = 16; //same for Westminster 1-12 (including their 4th quarter introductions
const int firstcuckoo = 28; // finally, where the cuckoo clock sounds start
int setstrike = -1; // Chime/strike flag
byte alreadychimed = false ; // Used to keep from chiming multiple times during the "hot" second
int displaychiming = 0; // display'chiming' when a signal sent to Wave Shield
const int BounceDelay = 250; // Not really 'bounce', its a change of state detection
const int wavechannel = 8; // I2C channel for communicating to Wave Shield
int i; // generic index variable
bool event; // logic flag "on" = event found
int eventindex; // and the event we found
int dayoftheweek; // stored day of the week (0-6, 0 = Sunday)
int phours; // for print conversion of military time
int oldseconds = -1; // only update display if seconds change (current seconds not equal last time)
float temperaturef; // farenheit temperature back
float temperaturec; // centrigade temperature back (not used)
float humidity; // and humidity
int LEDTime; // used for converting single time to 4 digit
float tempadjust = -3.9; // temperature adjustment for sensor (I found it didn't read right against 'comps')
byte degreesymbol = 223; // LCD output of 'degree' symbol
int THupdate = -1; // sensor changes too frequently, only update every 15 seconds
void setup()
// put your setup code here, to run once:
{
Wire.begin(); // initialize I2C interface
lcd.init(); // initialize LCD
lcd.backlight();
dht.begin(); // initialize the temp/humidity sensor
matrix.begin(0x70); // initialize LED
Serial.begin (9600); //uncomment if needed to debug
pinMode(pb1pin, INPUT_PULLUP); // The three pushbuttons - reset
pinMode(pb2pin, INPUT_PULLUP); // increment
pinMode(pb3pin, INPUT_PULLUP); // decrement
if (! rtc.begin()) { // check that clock is there
lcd.print("Couldn't find RTC"); // clock missing is a fatal error
while (1);
}
if (rtc.lostPower()) { // if power lost force a setup, else load 'current' values and
lcd.print("RTC lost power!"); // only change time on a PB 1 push if its known already
insetupmode = true;
for (i=0; i<setsize;i++) {setarray[i] = lowlimit[i];}
delay(3000);} else
{DateTime (setarray[setyear],setarray[setmonth],setarray[setday],setarray[sethour],setarray[setminute],setarray[setsecond]) = rtc.now();
insetupmode = false;
}
// Two alternative modes of setting date and time (for debugging):
// This line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
setarray[setchime] = westminster; // default chime is Westminster after reboot
} // end of setup
void loop() {
// Main code divided into setup mode and non-setup mode
while (insetupmode){ // Main code for time, date set
LEDBrightness(); // Make sure LED brightness hasn't changed
// Read the increment button
if (digitalRead(pb2pin) == LOW) {setarray[setupindex]++;
delay(BounceDelay);
if (setarray[setupindex] > highlimit[setupindex]) {setarray[setupindex] = lowlimit[setupindex];}
}
// Read the decrement Button
if (digitalRead(pb3pin) == LOW) {setarray[setupindex]--;
delay(BounceDelay);
if (setarray[setupindex] < lowlimit[setupindex]) {setarray[setupindex] = highlimit[setupindex];}
}
// Display what we are setting up and the value
lcd.setCursor(0,0);
lcd.print("In Setup Mode: ");
lcd.setCursor(0,2);
lcd.print(setdesc[setupindex]); // what we are setting up
lcd.print(" "); // and current value
if (setupindex != setchime) {lcd.print(setarray[setupindex]);
lcd.print(" ");}
if (setupindex == setchime) {lcd.print(" "); // Treat Chimes diffently
i = setarray[setupindex]; // because its not a number, it is
lcd.print(chimedesc[i]);} // it is none, hours, all westminster, night silent or cuckoo!
// Read for another increment of index
if (digitalRead(pb1pin) == LOW){ // Rolling through Chime, Year, Month, Day, Hour, Minutes, Seconds
setupindex++ ;
clearline(2);
delay(BounceDelay);
if (setupindex >= setsize) {insetupmode = false; // and finally exiting setup mode after setting chime, date and time
rtc.adjust(DateTime(setarray[setyear],setarray[setmonth],setarray[setday],setarray[sethour],setarray[setminute],setarray[setsecond]));
lcd.clear(); // clear display from setup stuff
THupdate = -1; // and force immediate update of temperature and humidity
} //exit setup mode when done
}
} // End of "While" for setup
// Begin regular loop for date, time, temp humidity and event display
while (!insetupmode){
// and onto date, time, etc.
DateTime now = rtc.now();
setarray[setyear] = now.year();
setarray[setmonth] = now.month();
setarray[setday] = now.day();
setarray[sethour] = now.hour();
setarray[setminute] = now.minute();
oldseconds = setarray[setsecond]; // store old seconds
setarray[setsecond] = now.second();
dayoftheweek = now.dayOfTheWeek();
// Update display once a second, but not more frequently
if (oldseconds != setarray[setsecond])
{ // start display code
// At start of new day, clear the event & date lines because may have become shorter
if (setarray[sethour] == 0 && setarray[setminute] == 0 && setarray[setsecond] == 0){
clearline(3); // clear the event line
clearline(1);} // clear the date line
// Temperature and humidity update
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
humidity = dht.readHumidity();
// Read temperature as Fahrenheit
temperaturef = (dht.readTemperature(true))+ tempadjust; // read and correct for inaccuracy in sensor
// Check if any reads failed and exit early (to try again).
if (isnan(humidity) || isnan(temperaturec) || isnan(temperaturef)) {
temperaturef = 0; // use 0 as a no-read
humidity = 0; // error indication
}
// (note: line 1 is the second row, since counting begins with 0)
// only update temp and humidity display every 15 seconds to eliminate flicker
if (THupdate <= 0)
{
lcd.setCursor(0, 2);
// Print Temperature Farenheit
lcd.print(temperaturef,1);
// lcd.setCursor(4,2);
lcd.print((char)degreesymbol); // print degree symbol
lcd.print("F "); // farenheit
// Print Humidity
lcd.print(humidity,1);
lcd.print("%H");
THupdate = 15; // after print, reset the counter
} else --THupdate; // if no print of temp and hum, decrement
// Print chime condition
lcd.setCursor(16,2);
i = setarray[setchime];
lcd.print(shortchimedesc[i]);
lcd.setCursor(4, 0); // position for date, time, etc.
if (setarray[sethour] <=12) // convert military time to am pm
{phours = setarray[sethour];
}
else
{phours = setarray[sethour]-12;}
if (phours <= 0){phours = 12;} // don't print 0 for midnite, print "12"
printtwo(phours," ");
lcd.print(":");
printtwo(setarray[setminute],"0");
lcd.print(":");
printtwo(setarray[setsecond],"0");
if (setarray[sethour] < 12)
{lcd.print(" AM ");}
else
{lcd.print(" PM ");}
// Now update the LED Time
LEDBrightness(); // Make sure brightness hasn't changed
LEDTime = (phours*100) + setarray[setminute] ; // hours and minutes (shifting hours 2 segments left)
matrix.print(LEDTime, DEC); // print to LED
// matrix.drawColon((setarray[setsecond] ==((setarray[setsecond]/2)*2))); // blink only on even seconds
matrix.drawColon(setarray[setsecond]%2); // blink only on even seconds
matrix.writeDisplay(); // and push out the LED print
// Now update the LCD date and event lines too
lcd.setCursor(0, 1);
// lcd.print(monthsOfTheYear[now.month()-1]); // took up too much space on the LCD
lcd.print(setarray[setmonth]); // so went with mm/dd/yyyy
lcd.print("/");
lcd.print(setarray[setday], DEC);
lcd.print("/");
lcd.print(setarray[setyear], DEC);
lcd.print(" ");
lcd.print(daysOfTheWeek[dayoftheweek]);
lcd.setCursor(0,3); // Last line of display is Event or greeting`
event = floatingevent(setarray[setmonth], setarray[setday], dayoftheweek); // return true if already have an event, false if not
if (event == false) { // if no special (non-static) event
for (int i = 0; i < eventnumber; i++){ // then check the statics
if ((setarray[setmonth] == eventmonth[i]) && (setarray[setday] == eventday[i])) {
event = true; // set if match on month and day
eventindex = i;
}}
if (event == true) { // if so, use it, else generic msg
lcd.print( eventdescription[eventindex]);}
else
if (setarray[sethour] >= 6 && setarray[sethour] <= 11){lcd.print("Good Morning! ");} else
if (setarray[sethour] >= 12 && setarray[sethour] <= 16){lcd.print("Good Afternoon! ");} else
if (setarray[sethour] >= 17 && setarray[sethour] <= 21){lcd.print("Good Evening! ");} else
{lcd.print("Good Night! ");}
}
} // end of display update logic
// Logic for chiming
setstrike = -1; // initialize 'strike' flag to "no strike"
if ((setarray[setchime] != silent) && !((setarray[setchime] == ANSO) && ((setarray[sethour] == 23 && setarray[setminute] > 0) || (setarray[sethour] >= 0 && setarray[sethour] < 7)))) // if silenced due to setting silent
{ // or ANSO and 11pm-6:59am then skip this
if (setarray[setminute] == 0 && setarray[setsecond] == 0){ // Look for 'on the hour'
setstrike = cqtr0;
} else
if (setarray[setminute] == 15 && setarray[setsecond] == 0) { // Look for on the quarter hour
setstrike = cqtr1;
} else
if (setarray[setminute] == 30 && setarray[setsecond] == 0){ // Look for on the 1/2 hour
setstrike = cqtr2;}
else
if (setarray[setminute] == 45 && setarray[setsecond] == 0){ // Look for on the three-quarter hour
setstrike = cqtr3;}
else {alreadychimed = false;} // none of the above, reset ability to chime
if (setstrike >0 && !alreadychimed ) {
chime(setarray[setchime], setstrike, setarray[sethour]); // call chiming with 'type of chime'; 0,15,30,45 ; and # hours
alreadychimed = true; // we will be here multiple times within a second
}
} // end of logic for chiming
// Read a potential request for an entry into setup from PB 1
if (digitalRead(pb1pin) == LOW){ // to see if we go back to setup mode
insetupmode = true;
setupindex = 0;
lcd.clear();
delay(BounceDelay);
}
} // end of not in setup while
} // end of sketch
void clearline(int line){ // Simply clears the line its called with and
lcd.setCursor(0,line);
lcd.print(" "); // and then
lcd.setCursor(0,line); } // Repositions cursor to start of line
void printtwo(int value, String fillchar){ // print two always and use fill to make it so
if (value <10) {lcd.print(fillchar);} // blank space for hours, 0 for minutes, seconds
lcd.print(value);
}
// Routine to deal with combinations of chime type, time of day and the current hour
// called parameters are chime type (e.g., Westminster), Strike type (hour, qtr, half, 3/4) and the hour in military time
void chime (int chimetype, int strikeflag, int chour){
int chour1; // am pm variable
int callbyte =0; // used to talk to Wave Shield
if (chour <=12){chour1 = chour;} else {chour1 = chour-12;} // convert military time to am pm
if (chour1 <= 0){chour1 = 12;} // don't chime 0 for midnite, chime 12
if (chimetype == hoursonly && strikeflag == cqtr2){callbyte = firstshort;} // 1/2 hour only, do single chime (same as 1pm, short chime)
else if ( chimetype == hoursonly && strikeflag == cqtr0) {
callbyte = chour1;} // hours only and strike hours
else if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr1) {callbyte = firstquarter;} // First Quarter
else if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr2) {callbyte = firstquarter+1;} // Second Quarter
else if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr3) {callbyte = firstquarter+2;} // Third Quarter
else if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr0) {callbyte = (chour1+firstwestminster-1);} // Strike Westminster hours
else if (chimetype == cuckoo && strikeflag == cqtr2){callbyte = firstcuckoo;} // 1/2 hour only, do single cuckoo (same as 1pm, single cuckoo)
else if ( chimetype == cuckoo && strikeflag == cqtr0) { callbyte = (chour1+firstcuckoo-1);} // hours only and strike cuckoo hours
Wire.beginTransmission(wavechannel); // finally transmit single byte result to Wave Shield
Wire.write(callbyte); // sends one byte
Wire.endTransmission(); // stop transmitting
} // end of Chime
// Routine to look up floating events (Memorial Day, Thanksgiving, Mother's Day and Fathers Day)
/* Memorial Day is Last Monday in May
* Thanksgiving is 4th Thursday in November
* Mother's Day is 2nd Sunday in May
* Father's Day is 3rd Sunday in June
* MLK Day is 3rd Monday in February
* Memorial Day is last Monday in May
* Labor Day is first Monday in September
* Daylight Savings Times starts 2nd Sunday in March
* Daylight Savings Times ends first Sunday in November
*
*/
bool floatingevent(int m, int d, int dow){ // called with Month, Day within month and day of the week (0 = Sunday)
const int sunday = 0;
const int monday = 1;
const int thursday = 4;
String floatstring = " ";
if ((dow == thursday) && (m==11) && (d >= 22) && (d <= 28)){ // Thanksgiving
floatstring = "Thanksgiving Day!";}
else if ((dow == sunday) && (m==5) && (d >= 8) && (d <= 14)){ // Mother's Day
floatstring = "Mother's Day!";}
else if ((dow == sunday) && (m==6) && (d >= 15) && (d <= 21)){ //Father's Day
floatstring = "Father's Day!";}
else if ((dow == monday) && (m==1) && (d >= 15) && (d <= 21)){ //MLK Day
floatstring = "MLK Day!";}
else if ((dow == monday) && (m==2) && (d >= 15) && (d <= 21)){ //President's Day
floatstring = "President's Day!";}
else if ((dow == monday) && (m==9) && (d <= 7) ){ //Labor Day
floatstring = "Labor Day!";}
else if ((dow == monday) && (m==5) && (d+7 > 31) ){ //Memorial Day
floatstring = "Memorial Day!";}
else if ((dow == sunday) && (m==3) && (d >= 8) && (d <= 14)){ // DST begins
floatstring = "DST Begins";}
else if ((dow == sunday) && (m==11) && (d <= 7) ){ //DST ends
floatstring = "DST Ends";}
if (floatstring == " " ){ return false;} else {
lcd.print(floatstring);
return true;}
} // end of floatingevent
void LEDBrightness(){
potvalue = analogRead(potpin); // Read potentiometer value
potvalue = map(potvalue, 0, 1023, 0, 15); //map to valid value for brightness
if (potvalue == 14) {potvalue = potvalue+1;} // round up if not full brightness
matrix.setBrightness(potvalue);
return;
} //end of LEDbrightness
Comments