01-15-2023, 01:46 AM
I made a clock for my parents, and I decided to post the code here.
Required hardware:
Required hardware:
- DS3231 real-time clock
- LCD 16x2 display (one that does not use I2C)
- A speaker or buzzer for the chimes
- EDIT: And of course an Arduino.
- EDIT: You will also need a few resistors to hook everything up properly.
- A toggle switch (to turn Daylight Saving Time on and off)
- Displays the date (month/day/year) and time (12 hour format)
- Calculates and displays the day of the week and week number
- Chimes every hour, on the hour (Westminster Chimes)
- When the Daylight Saving Time switch is turned on, it adds 1 hour to the time to be displayed and chimed
- Allow you to set the time manually (you have to re-upload the sketch for that)
Code:
#include "Wire.h"
#include <LiquidCrystal.h>
// Make sure your pin numbers match these!
// RS EN D4 D5 D6 D7
LiquidCrystal lcd( 7, 6, 5, 4, 3, 2);
const byte DST_SWITCH_PIN = 8;
const byte SPEAKER_PIN = 9;
// variables for the current date and time
byte yy=0, mo=1, dd=0, wd=6;
byte hh=0, mi=0, ss=0;
byte hhTwelve = 12;
byte wn=52;
// other helpful variables
bool gotTheTime = false;
byte old_ss = 99, halfSec = 198, old_halfSec = 198;
unsigned long microsNow = 0UL;
unsigned long microsAtLastSecond = 0UL;
bool dstOn = false, old_dstOn = false;
// a buffer for text to be displayed
char buf[20] = "";
void setup() {
pinMode(DST_SWITCH_PIN, INPUT_PULLUP);
pinMode(SPEAKER_PIN, OUTPUT);
Wire.begin();
lcd.begin(16, 2);
// BEGINNING of code for setting the date and time
// If you wish to set the date and time,
// uncomment the following:
/*
// code to precisely set the external real-time clock
Wire.beginTransmission(0x68); // address DS3231
Wire.write(0x00); // select register
// NOTE: before you run this code, you *must*
// change the following numbers to the correct time!
// (plus a few seconds to allow for compilation, etc.)
Wire.write(numberToBcd( 0)); // seconds
Wire.write(numberToBcd(21)); // minutes
Wire.write(numberToBcd( 1)); // hours (use 24-hour format)
Wire.write(numberToBcd( 6)); // day of week (I use Mon=1 .. Sun=7)
Wire.write(numberToBcd( 9)); // day of month
Wire.write(numberToBcd( 4)); // month
Wire.write(numberToBcd(22)); // year (use only two digits)
Wire.endTransmission();
*/
// END of code for setting the date and time
/*
// define special characters for single cell numerals 10 through 12
byte singleCellTen[] = { 18, 21, 21, 21, 21, 21, 18, 0 };
byte singleCellEleven[] = { 9, 27, 9, 9, 9, 9, 9, 0 };
byte singleCellTwelve[] = { 22, 21, 17, 18, 20, 20, 23, 0 };
lcd.createChar(10, singleCellTen);
lcd.createChar(11, singleCellEleven);
lcd.createChar(12, singleCellTwelve);
*/
// play a short tone (for testing the speaker)
tone(SPEAKER_PIN, 1000, 500);
// display a demo pattern (for testing the display)
lcd.setCursor(0, 0); // go to beginning of top line
lcd.print(" Display test ");
lcd.setCursor(0, 1); // go to beginning of bottom line
lcd.print("0123456789 (^_^)");
for (int i = 5; i >= 1; i--) { // countdown from 5 to 1
lcd.setCursor(0, 0); // go to beginning of top line
lcd.print((char)('0' + i)); // print the digit
lcd.setCursor(15, 0); // go to end of top line
lcd.print((char)('0' + i)); // print the digit again
delay(998);
}
}
void loop() {
// first, we (try to) read the time from the RTC
// send request to receive data starting at register 0
Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
Wire.write((byte)0); // start at register 0
Wire.endTransmission();
Wire.requestFrom(0x68, 7); // request seven bytes
gotTheTime = false;
while(Wire.available())
{
ss = bcdToNumber(Wire.read()); // get seconds
mi = bcdToNumber(Wire.read()); // get minutes
hh = bcdToNumber(Wire.read()); // get hours
Wire.read(); // discard the day of the week (we will calculate it ourself)
dd = bcdToNumber(Wire.read()); // get day of month
mo = bcdToNumber(Wire.read()); // get month
yy = bcdToNumber(Wire.read()); // get year
gotTheTime = true;
}
microsNow = micros();
// read the Daylight Saving Time on/off switch
// NOTE: because we are using INPUT_PULLUP, LOW means on, and HIGH means off
dstOn = (digitalRead(DST_SWITCH_PIN) == LOW);
// adjust for Daylight Saving Time if applicable
if (dstOn) {
hh++;
if (hh >= 24) {
hh -= 24;
dd++;
if (dd > daysInMonth(yy, mo)) {
dd = 1;
mo++;
if (mo > 12) {
mo = 1;
yy++;
}
}
}
}
// try to figure out which half-second we are in
// (this is important to making the striking work properly)
if (ss != old_ss) microsAtLastSecond = microsNow;
halfSec = ss * 2;
if ((microsNow - microsAtLastSecond) >= 500000UL) halfSec++;
// calculate day of the week
wd = ymdToWeekday(yy, mo, dd);
// calculate week number
wn = ymdToWeekNumber(yy, mo, dd);
// convert hour to 12-hour format
hhTwelve = hh;
if (hhTwelve > 12) {
hhTwelve -= 12;
}
if (hhTwelve == 0) {
hhTwelve = 12;
}
if (gotTheTime) {
// only if we have successfully read the time
// do we then attempt to indicate the time
if (halfSec != old_halfSec) { // do this only once every half-second
// see if it is time for the clock to strike
if (mi == 0) { // strike on the hour, i.e. when minutes are 0
if (halfSec < 26) {
// play the Westminster Chimes
switch (halfSec) {
case 0: tone(SPEAKER_PIN, 330, 420); break;
case 1: tone(SPEAKER_PIN, 415, 420); break;
case 2: tone(SPEAKER_PIN, 370, 420); break;
case 3: tone(SPEAKER_PIN, 247, 735); break;
case 6: tone(SPEAKER_PIN, 330, 420); break;
case 7: tone(SPEAKER_PIN, 370, 420); break;
case 8: tone(SPEAKER_PIN, 415, 420); break;
case 9: tone(SPEAKER_PIN, 330, 735); break;
case 12: tone(SPEAKER_PIN, 415, 420); break;
case 13: tone(SPEAKER_PIN, 330, 420); break;
case 14: tone(SPEAKER_PIN, 370, 420); break;
case 15: tone(SPEAKER_PIN, 247, 735); break;
case 18: tone(SPEAKER_PIN, 247, 420); break;
case 19: tone(SPEAKER_PIN, 370, 420); break;
case 20: tone(SPEAKER_PIN, 415, 420); break;
case 21: tone(SPEAKER_PIN, 330, 735); break;
default: break;
}
}
else if ((halfSec < (26 + 3 * hhTwelve)) && ((halfSec % 3) == 2)) {
// bong the hours
tone(SPEAKER_PIN, 415, 750);
}
}
}
if ((ss != old_ss) || (dstOn != old_dstOn)) { // only once every second
// update the display to show the current date and time
// build a string of text containing the weekday and the full date
// (Hint: this code makes more sense if you read it vertically)
buf[0] = "BMTWTFSS"[wd];
buf[1] = "aouehrau"[wd];
buf[2] = "dneduitn"[wd];
buf[3] = ' ';
buf[4] = ' ';
buf[5] = ' ';
buf[6] = '0' + (mo/10);
buf[7] = '0' + (mo%10);
buf[8] = '/';
buf[9] = '0' + (dd/10);
buf[10] = '0' + (dd%10);
buf[11] = '/';
buf[12] = '2';
buf[13] = '0';
buf[14] = '0' + (yy/10);
buf[15] = '0' + (yy%10);
buf[16] = 0;
if (buf[6] == '0') buf[6] = ' ';
// display the weekday and full date on the top line
lcd.setCursor(0, 0); // move to beginning of top line
lcd.print(buf); // print the text to the display
// build a string of text containing the week number and the time
buf[0] = 'W';
buf[1] = 'k';
buf[2] = '0' + (wn/10);
buf[3] = '0' + (wn%10);
buf[4] = ' ';
buf[5] = ' ';
buf[6] = '0' + (hhTwelve/10);
buf[7] = '0' + (hhTwelve%10);
buf[8] = ':';
buf[9] = '0' + (mi/10);
buf[10] = '0' + (mi%10);
buf[11] = ':';
buf[12] = '0' + (ss/10);
buf[13] = '0' + (ss%10);
buf[14] = ((hh<12) ? 'a' : 'p');
buf[15] = 'm';
buf[16] = 0;
if (buf[6] == '0') buf[6] = ' ';
// display the week number and the time on the bottom line
lcd.setCursor(0, 1); // move to beginning of bottom line
lcd.print(buf); // print the text to the display
}
}
else {
// if we have failed to read the time,
// then we will end up inside this "else"
// indicate failure to read the time
lcd.setCursor(0, 0); // go to beginning of top line
lcd.print("Error: ");
lcd.setCursor(0, 1); // go to beginning of bottom line
lcd.print("Can\'t read time ");
}