The arduino code is listed below and can be downloaded together with the modified library as a zip file. LCD_Alarm_M4_alphanum.zip
I have also made available a download with the audio files that I have used on both the Alarm and the “Talk-the-Time” version of the clock. The zip file can be downloaded at M4 Alarm.zip.
Note: All standard libraries must be loaded in the normal mode from the repositories.
Note this code is completely functional with an Adafruit 7 segment display but some of the alpha characters will look a little strange.
Updated: 30 January 2024
// Based on the Clock example using a seven segment display & DS1307 real-time clock.
//
// Must have the Adafruit RTClib library installed too! See:
// https://github.com/adafruit/RTClib
//
// Originally designed specifically to work with the Adafruit LED 7-Segment backpacks
// and DS1307 real-time clock breakout (note this sketch uses the Adalogger Featherwing- see below):
// ----> http://www.adafruit.com/products/881
// ----> http://www.adafruit.com/products/880
// ----> http://www.adafruit.com/products/879
// ----> http://www.adafruit.com/products/878,
// please support Adafruit and open-source hardware by purchasing
// products from Adafruit!
//
// Original example sketch was written by Tony DiCola for Adafruit Industries.
// ----> https://www.adafruit.com/products/264
//
// Adafruit invests time and resources providing this open source code
// Released under a MIT license: https://opensource.org/licenses/MIT
/*
Modified and added to by Peter Bradley
This version uses an Adafruit Feather M4 Express
Uses the Daylight Savings Time library by Andy Doro which was further modified by Peter Bradley
for EU DST
----> https://github.com/andydoro/DST_RTC
An Adafruit Mono 2.5W Class D Audio Amplifier - PAM8302 was added to provide sound for an alarm when connected to the DAC pin A0.
----> https://www.adafruit.com/product/2130
Buttons were added to advance and retard the time for the alarm and the clock (if necessary)
and a switch to turn the alarm on and off.
This version also optonally uses a library of recorded spoken numbers and words to speak the time when a button is pressed for 1 second.
One second is required because of the delay of one second used in every iteration of the loop.
*** Update: this version includes an extra button, the 'OK' button, that is used to set
the date and other clock parameters.
To aid accurate time setting a single click will zero the seconds.
A double click will reset the program.
Hint: this may be the fastest way to reset the Alarm time to the default Alarm time.
Component list:
Adafruit Feather M4 Express
Adalogger FeatherWing - RTC + SD (SD with Alarm music or tones: Alarm.wav, beep.wav)
CR1220 12mm Diameter - 3V Lithium Coin Cell Battery (for the RTC)
4-Digit 7-Segment Display FeatherWing (original) or
4-character 14-Segment Alphanumeric display
(note: an alphanumeric display works best in the setDate function)
Mono 2.5W Class D Audio Amplifier - PAM8302
Mono Enclosed Speaker - 3W 4 Ohm
Photo Transistor Light Sensor (if required)
10KΩ resistor (if required for light sensor)
Stacking Headers for Feather - 12-pin and 16-pin female headers(3 sets)
Header Kit for Feather - 12-pin and 16-pin Female Header Set (2 sets)
3 pushbuttons (12 mm threaded with nuts for mounting in the case)
1 LED pushbutton (12 mm threaded with nuts for mounting in the case - if TalkTime is used)
1 LED latching pushbutton (12mm - 3 - 5V LED – up for alarm mode if used)
A 1000 mAh Lithium battery to keep the clock running for a period in case of a power outage (if it loses power the alarm time would reset to the default time)
Alternatively a 2000 mAh Lithium battery which will keep the clock functioning without power for at least 24 hours.
A short Micro USB extension with a 90° downwards connection to lead out the back of the box and provide power
Various jumper wires which were cut in half to solder to the pushbuttons.
*/
#include <Wire.h>
// #include "Adafruit_LEDBackpack.h" // Original library
#include "AlphaNum_Clock.h" // modified library for alpha numeric clock LED
// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include <RTClib.h>
#include <DST_RTC.h> // Daylight Savings Time Library (Modified for Europe)
#include <FlashStorage.h> // https://github.com/cmaglie/FlashStorage -
// to store clock parameters: 24 or 12 hour display and DST zone (EU or US or no (for no DST))
#include <SPI.h>
#include <SdFat.h>
#include <Adafruit_SPIFlash.h>
#include <Audio.h>
#include <play_fs_wav.h>
// Clock parameters **************************************
RTC_PCF8523 rtc;
DST_RTC dst_rtc; // DST object
// Set to false to display time in 12 hour format, or true to use 24 hour:
bool TIME_24_HOUR = true; // 24 hour display is the default value
#define Alarm true // Set to false if the alarm button is not connected:
int Alarmhours = 8; // default Alarm time if not changed
int Alarmminutes = 30;
// Define US or EU rules for DST comment out as required. More countries could be added with different rules in DST_RTC.cpp
// char rulesDST[3] = "No"; // No DST rules
// char rulesDST[3] = "US"; // US DST rules
char rulesDST[3] = "EU"; // EU DST rules (EU for default)
// days of the week not used with 7 segment displays
// char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
int hours = 0;
int minutes = 0;
int seconds = 0;
unsigned long time_now; // used to store current millisecond
unsigned long time_in; // current millisecond for time-out in the case of no button press and audio amp
unsigned long time_out = 60000; // time in millisecond for time-out in the case of no button press
// Remember if the colon was drawn on the display so it can be blinked
// on and off every second.
bool blinkColon = false;
// flash memory setup for storing clock parameters ****************************
// Create a structure that is to contain a DST zone and the bool for 24 hour display.
// I used a short for DSTzone because it is easier store and read, and work with, an integer than a character array.
// The "valid" variable is set to "true" once
// the structure is filled with actual data for the first time.
typedef struct {
boolean valid;
char DSTzone[3]; // DSTzone number from 0 to max number of DST zones specified (in this case)
bool disp24hrs;
} Params;
// Reserve a portion of flash memory to store a "Params" and
// call it "flashmem".
FlashStorage(flash_mem, Params);
// Note: the area of flash memory reserved lost every time
// the sketch is uploaded on the board.
// Display parameters *********************************************
// I2C address of the display. Stick with the default address of 0x70
// unless you've changed the address jumpers on the back of the display.
#define DISPLAY_ADDRESS 0x70
// Create display and DS1307 objects. These are global variables that
// can be accessed from both the setup and loop function below.
// Set clockDisplay according to display type used
// Adafruit_7segment clockDisplay = Adafruit_7segment();
AlphaNumClock clockDisplay = AlphaNumClock(); // Lucky Light 14 segment alphanumeric clock display
// AnDpClock clockDisplay = AnDpClock(); // LightBo (Shenzhen Guangzhibao Technology) 14 segment alphanumeric display
bool seven_Seg = false; // true if Adafruit 7 segment display is used
// brightness may be set between 0 and 15
int maxBright = 15;
int minBright = 1; // if display seems unstable at '0' set to '1'
int lastBright = 0; // holds last brightness setting if light sensor is used
// Define I/O pins *************************************
const int buttonTalk = 5; // connect pin to ground via push button to pull pin down used to trigger playTime()
const int buttonAdv = 6; // connect pin to ground via push button to pull pin down used to fast advance minutes.
const int buttonRev = 10; // connect pin to ground via push button to pull pin down used to fast reverse minutes.
const int switchAlarm = 11;
const int lightAlarm = 12;
const int switchAmp = 13;
#define VBATPIN A6 // to measure battery voltage
#define lightSensorPin A3 // if light sensor attached
int SensorValue;
int lightValue;
bool lightSensor = true; // true if light sensor attached
// buttonOK parameters
#include "OneButton.h" // https://github.com/mathertel/OneButton
const int buttonOKpin = 9; // connect pin to ground via push button to pull pin down used to trigger setDateTime()
bool OK;
bool sp; // flag used for serial print to stop continual output
// Setup a new OneButton on pin PIN_INPUT
// The 2. parameter activeLOW is true, because external wiring sets the button to LOW when pressed.
OneButton buttonOK(buttonOKpin, true, true);
int press = 2000; // time in msec of long press
// Miscellaneous variables **********************
int t; // time of button press, used to speed up advance and retard loops
int displayValue;
int clockValue;
int alarmValue;
bool displayDelay = true;
bool Night = true;
DateTime theTime;
DateTime now;
// Variables to use for setting the date *******************
int YYYY; // the year as a 4-digit number (2000–2099) |
// | YY | the year as a 2-digit number (00–99) |
int MM; // the month as a 2-digit number (01–12) |
// | MMM | the abbreviated English month name (“Jan”–“Dec”) |
int DD; // the day as a 2-digit number (01–31) |
// | DDD | the abbreviated English day of the week (“Mon”–“Sun”) |
// | AP | either “AM” or “PM” |
// | ap | either “am” or “pm” |
int hh; // the hour as a 2-digit number (00–23 or 01–12) |
int mm; // the minute as a 2-digit number (00–59) |
int ss; // the second as a 2-digit number (00–59) |
float MMDD; // the month and day with a point between to be used for the display
// Audio parameters *********************************
AudioPlayFSWav playWav1;
AudioOutputAnalogStereo audioOutput; // Dual DACs
//AudioConnection patchCord1(playWav1, 0, audioOutput, 1); //For stero uncomment and comment the line below
AudioConnection patchCord1(playWav1, 1, audioOutput, 0); // Mono - output both on the same dac comment this and uncomment above for stereo
AudioConnection patchCord2(playWav1, 1, audioOutput, 0);
// Set to false if the talk button is not connected:
#define TalkTime false
bool Talk = false;
// Audio SPI Flash parameters ***************************
#define SDCARD_CS_PIN 10
#if defined(EXTERNAL_FLASH_USE_QSPI)
Adafruit_FlashTransport_QSPI flashTransport;
#elif defined(EXTERNAL_FLASH_USE_SPI)
Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, EXTERNAL_FLASH_USE_SPI);
#else
#error No QSPI/SPI flash are defined on your board variant.h !
#endif
Adafruit_SPIFlash flash(&flashTransport);
// file system object from SdFat
FatFileSystem QSPIFS;
SdFat SD;
bool SDOK = false, QSPIOK = false;
void setup() {
Serial.begin(115200);
delay(1000);
// while (!Serial); // wait for serial port to connect. Needed for native USB
AudioMemory(8);
if (TalkTime) { pinMode(buttonTalk, INPUT_PULLUP); }
pinMode(buttonAdv, INPUT_PULLUP);
pinMode(buttonRev, INPUT_PULLUP);
if (Alarm) {
pinMode(switchAlarm, INPUT_PULLUP); // High is alarm on.
pinMode(lightAlarm, OUTPUT); // digital pin connected to LED on Alarm button
digitalWrite(lightAlarm, LOW); // put light off
}
pinMode(switchAmp, OUTPUT); // digital pin connected to SD on amp
digitalWrite(switchAmp, LOW); // put amp on standby
pinMode(lightSensorPin, INPUT); // analog values from light sensor
// buttonOK setup *******************************************
// link the singleClick function to be called on a singleClick event.
buttonOK.attachClick(singleClick);
// link the doubleclick function to be called on a doubleclick event.
buttonOK.attachDoubleClick(Reset);
// link the longpress function to be called on a longpress event.
buttonOK.attachLongPressStop(setDate);
buttonOK.setPressMs(press);
// Clock parameters setup *************************************
// Create a "Params" variable and call it "clockParams"
Params clockParams;
// Read the content of "flash_mem" into the "clockParams" variable
clockParams = flash_mem.read();
// If this is the first run the "valid" value should be "false"...
if (clockParams.valid == false) {
// ...in this case we save the default values.
saveParams();
} else {
getParams(); // on restart or reset read saved params
}
/*
Serial.println();
Serial.print("Alarm time: ");
Serial.print(Alarmhours);
Serial.print(":");
Serial.println(Alarmminutes);
*/
rtc.begin();
/************************************************************************************/
// This line sets the RTC with an explicit date & time, for example to set
// May 21, 2020 at 23:50:5 you would call:*/
// rtc.adjust(DateTime(2020, 5, 21, 23, 50, 5));
// Compile and load again with this line commented out or the clock will revert to this time on power up or reset
// This funtion would only be used if the RTC has had the battery removed or discharged.
// Note that the addition of the OK button allows the date and time to be re-set without the
// need to attach the clock to a computer.
/************************************************************************************/
if (!rtc.isrunning()) {
Serial.println("RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled only in the case that the clock has not already been set.
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// DST? If we're in it, let's subtract an hour from the RTC time to keep our DST calculation correct. This gives us
// Standard Time which our DST check will add an hour back to if we're in DST.
now = rtc.now();
if (dst_rtc.checkDST(now) == true) { // check whether we're in DST right now. If we are, subtract an hour.
now = now.unixtime() - 3600;
}
rtc.adjust(now);
}
now = rtc.now(); // This is the standard time and the time the RTC is set to
theTime = dst_rtc.calculateTime(now); // takes into account DST
/**************************
// Calibration section. Comment out if not used.
// The PCF8523 can be calibrated for:
// - Aging adjustment
// - Temperature compensation
// - Accuracy tuning
// The offset mode to use, once every two hours or once every minute.
// The offset Offset value from -64 to +63. See the Application Note for calculation of offset values.
// https://www.nxp.com/docs/en/application-note/AN11247.pdf
// The deviation in parts per million can be calculated over a period of observation. Both the drift (which can be negative)
// and the observation period must be in seconds. For accuracy the variation should be observed over about 1 week.
// Note: any previous calibration should cancelled prior to any new observation period.
// Recommendation: Syncronise host PC time.
// run this sketch cancelling any previous calibration,
// record the output including timestamp,
// run sketch again after several days,
// calculate period of observation in seconds, and drift in seconds.
// Run sketch with the calculated figures and uncomment rtc.calibrate line as required.
// Example - RTC gaining 43 seconds in 1 week
float drift = 43; // seconds plus or minus over oservation period - set to 0 to cancel previous calibration.
float period_sec = (7 * 86400); // total obsevation period in seconds (86400 = seconds in 1 day: 7 days = (7 * 86400) seconds )
float deviation_ppm = (drift / period_sec * 1000000); // deviation in parts per million (μs)
float drift_unit = 4.34; // use with offset mode PCF8523_TwoHours
// float drift_unit = 4.069; //For corrections every min the drift_unit is 4.069 ppm (use with offset mode PCF8523_OneMinute)
int8_t offset = round(deviation_ppm / drift_unit);
// rtc.calibrate(PCF8523_TwoHours, offset); // Un-comment to perform calibration once drift (seconds) and observation period (seconds) are correct
// rtc.calibrate(PCF8523_OneMinute, offset); // // Un-comment to perform calibration with offset mode PCF8523_OneMinute
// rtc.calibrate(PCF8523_TwoHours, 0); // Un-comment to cancel previous calibration
Serial.println();
Serial.print("Calculated Offset for calibration is: ");
Serial.println(offset); // Print to control calculated offset
// In order to provide a method of reading the offset register, which may contain an previous calibration
// two methods are provided; 1. rtc.readOffsetReg(), or 2. rtc.getOffsetMode() and rtc.getOffset()
// Hint:
// Once the calibration Offset mode and Offset are known a line can be entered in the setup of the operating project sketch
// to re-establish the offset register after a battery replacement or clock reset. Note that your sketch will still require a method
// to insert the actual date and time.
// In the case of the above sample the line to insert in setup() would be:
// rtc.calibrate(PCF8523_TwoHours, 16); // re-insert previously calculated calibration after clock reset.
// read offset register *******************************
Serial.println("Read RTC PCF8523 Offset Register"); // Print to control offset
// Method 1 ****************************
// Read offset register and interpret the result
Serial.println("Method 1");
uint8_t OffsetReg = rtc.readOffsetReg(); // get raw data
Serial.print("Offset mode is: ");
if (bitRead(OffsetReg, 7)) { // if bit 7 is 0b1
Serial.println("PCF8523_OneMinute");
} else { // bit 7 is 0b0
Serial.println("PCF8523_TwoHours ");
}
offset = OffsetReg;
// The offset parameter is held in bits 0 to 6 as a signed 7bit integer
// bit 6 needs to be copied to bit 7 to convert to a signed 8bit integer
bitWrite(offset, 7, bitRead(OffsetReg, 6));
Serial.print("Offset is: ");
Serial.println(offset); // Print to control offset
Serial.println();
// Method 2 ****************************
// Obtain and output Offset Mode
Serial.println("Method 2");
Serial.print("Offset mode is: ");
Serial.println(rtc.getOffsetMode()); // Print to control Offset Mode
// offset = rtc.getOffset();
// Obtain and output Offset value -64 to +63
Serial.print("Offset is: ");
Serial.println(rtc.getOffset()); // Print to control offset
Serial.println();
// End read offset register *******************************
// End Calibration ****************************************/
// Print time to control settings
Serial.println("Standard Time");
printTheTime(now);
Serial.println("Time adjusted for Daylight Saving Time");
printTheTime(theTime);
// End clock setup *************************************
// Setup the display.
clockDisplay.begin(DISPLAY_ADDRESS);
// set initial display brightness
if (!lightSensor) {
// Set day time brightness of display.
if (hours >= 8 && hours <= 19) {
Night = false;
clockDisplay.setBrightness(2); // may need adjustment depending on the LED display
} // set brightness between 0 and 15
else
// Set night time brightness of display.
if (hours >= 20 || hours <= 7) {
Night = true;
clockDisplay.setBrightness(minBright); // set brightness between 0 and 15 (see value minBright)
}
}
// Initialise SD card reader and play 'beep'
SDOK = false;
if (!(SD.begin(SDCARD_CS_PIN))) {
Serial.println("Unable to access the SD card");
} else {
// Serial.println("SD card OK!");
SDOK = true;
}
digitalWrite(switchAmp, HIGH); // put amp off standby
playFile("beep.wav");
delay(50);
digitalWrite(switchAmp, LOW); // put amp on standby
} // end setup
void loop() {
displayTime(); // Time display is refreshed once a second
if (digitalRead(switchAlarm) == LOW || !Alarm) { // Time adjustment mode (if !Alarm the Alarm button is not in use) // *********** Move fast forward and fast backward to funtions with zeroing of seconds on exit. **********************
if (digitalRead(buttonAdv) == LOW) { //******************* minutes fast forward routine
timeAdvance();
} //****************** end minutes fast forward routine*/
if (digitalRead(buttonRev) == LOW) { //****************** minutes fast backward routine
timeRetard();
} //****************** end minutes fast backward routine*/
} //*************************** End time adjustment section
// *************************** Speak the time section
if (TalkTime) {
if (digitalRead(buttonTalk) == LOW) { // playTime() activated
time_in = millis();
Talk = true;
playTime();
}
if (millis() >= (time_in + 5000) && (Talk == true)) {
digitalWrite(switchAmp, LOW); // put amp on standby after 5 secs.
Talk = false;
}
}
// End Speak the time section *****************
// *************************** Alarm section
if (Alarm) {
if (digitalRead(switchAlarm) == HIGH) { // Alarm activated (alarm time always displayed in 24 hour mode)
if (displayDelay == true) { // show alarm time for 3 seconds
alarmValue = Alarmhours * 100 + Alarmminutes;
blinkColon = true; // turn colon on
writeClockDisplay(alarmValue);
// TalkAlarm(Alarmhours,Alarmminutes); // to be done if talking alarm time is required.
digitalWrite(lightAlarm, HIGH); // put Alarm button light on
delay(3000);
displayDelay = false;
}
while (digitalRead(buttonAdv) == LOW) {
alarmAdvance(); //******************* Alarm fast forward routine
}
while (digitalRead(buttonRev) == LOW) {
alarmRetard(); //****************** Alarm fast backward routine
}
// Play alarm
if (Alarmhours == hours && Alarmminutes == minutes && seconds == 0) {
playAlarm();
time_in = millis();
}
if (millis() >= (time_now + 300000) && (digitalRead(switchAmp) == HIGH)) {
digitalWrite(switchAmp, LOW); // put amp on standby after 5 minutes if not otherwise put on standby.
}
}
if (digitalRead(switchAlarm) == LOW) { // Alarm mode off
digitalWrite(switchAmp, LOW); // put amp on standby
digitalWrite(lightAlarm, LOW); // put Alarm button light off
displayDelay = true; // reset dispayDelay for the next time that the alarm is turned on
}
} // End alarm mode *****************
// Pause for a second for time to elapse. This value is in milliseconds
// so 1000 milliseconds = 1 second.
// millis used for a non blocking delay as we need to watch for button presses
time_now = millis();
while (millis() <= (time_now + 1000)) {
buttonOK.tick(); // watch for buttonOK press
if (OK) { // zero seconds to allow more acurate time setting
syncSeconds();
OK = false;
break;
}
}
if (seconds % 5 == 0) { // check battery and light once every 5 seconds
checkBattery();
if (lightSensor) {
SensorValue = analogRead(lightSensorPin);
delay(5);
// Serial.print("Raw Sensor value \t Raw:");
// Serial.println(SensorValue);
// The formula below scales up the lightValue from minBright, the minimum desired value for setBrightness
lightValue = (sqrt(SensorValue) / 1.2) + minBright; // the figure of 1.2 was set after testing
if (lightValue > maxBright) lightValue = maxBright; // max value for setBrightness
if (lightValue != lastBright) { // change the brightness only if lightValue has changed
clockDisplay.setBrightness(lightValue);
lastBright = lightValue;
}
// Serial.print("Mapped Sensor Value \t Sensor:");
// Serial.println(SensorValue);
// Serial.print("Calcuted Light Value \t Light:");
// Serial.println(lightValue);
}
}
} // end of loop
void saveParams() {
Params clockParams;
// Read the content of "flash_mem" into the "clockParams" variable
clockParams = flash_mem.read();
strcpy(clockParams.DSTzone, rulesDST);
// default 24hr time display true or false");
clockParams.disp24hrs = TIME_24_HOUR;
// set "valid" to true, so the next time we know that we
// have valid data inside
clockParams.valid = true;
// ...and finally save everything into "flash_mem"
flash_mem.write(clockParams);
// Print a confirmation of the data inserted.
Serial.println();
Serial.print("Your DST zone: ");
Serial.println(clockParams.DSTzone);
Serial.print("and your 24 hour mode: ");
if (clockParams.disp24hrs) {
Serial.println("true");
} else {
Serial.println("false");
}
Serial.println("have been saved. Thank you!");
}
void getParams() {
Params clockParams;
// Read the content of "flash_mem" into the "clockParams" variable
clockParams = flash_mem.read();
// on restart or reset read saved params
strcpy(rulesDST, clockParams.DSTzone);
bool TIME_24_HOUR = clockParams.disp24hrs;
} // end getParams
void displayTime() {
now = rtc.now();
theTime = dst_rtc.calculateTime(now); // takes into account DST
hours = theTime.hour();
minutes = theTime.minute();
seconds = theTime.second(); //used to trigger the alarm
// Show the time on the display by turning it into a numeric
// value, like 3:30 turns into 330, by multiplying the hour by
// 100 and then adding the minutes.
clockValue = hours * 100 + minutes;
if (!lightSensor) {
// Set day time brightness of display.
if (Night && (hours >= 8 && hours <= 19)) {
Night = false;
clockDisplay.setBrightness(2); // may need adjustment depending on the LED display
} // set brightness between 0 and 15
else
// Set night time brightness of display.
if (!Night && (hours >= 20 || hours <= 7)) {
Night = true;
clockDisplay.setBrightness(minBright);
} // set brightness between 0 and 15 (see value minBright)
}
// Do 24 hour to 12 hour format conversion when required.
if (!TIME_24_HOUR) {
// Handle when hours are past 12 by subtracting 12 hours (1200 value).
if (hours > 12) {
clockValue -= 1200;
}
// Handle hour 0 (midnight) being shown as 12.
else if (hours == 0) {
clockValue += 1200;
}
}
// Now print the time value to the display.
writeClockDisplay(clockValue);
}
void writeClockDisplay(int displayValue) {
clockDisplay.print(displayValue, DEC);
// Add zero padding when in 24 hour mode and it's midnight.
// In this case the print function above won't have leading 0's
// which can look confusing. Go in and explicitly add these zeros.
if (displayValue < 100) {
// Pad hour 0.
clockDisplay.writeDigitNum(1, 0);
// Also pad when the 10's minute is 0 and should be padded.
if (displayValue < 10) {
if (seven_Seg) {
clockDisplay.writeDigitNum(3, 0); // for Adafruit 7 segment display
} else {
clockDisplay.writeDigitNum(2, 0);
}
}
}
// Blink the colon by flipping its value every loop iteration
// (which happens every second).
clockDisplay.drawColon(blinkColon);
blinkColon = !blinkColon;
if (digitalRead(switchAlarm) == HIGH) { // Alarm mode on
if (seven_Seg) {
// DigitNum 4 is the last display digit on 7 segment - not used here on AlphaNum_Clock
clockDisplay.writeDigitNum(4, displayValue % 10, true); // Write last dot to show Alarm is on
} else {
// DigitNum 3 is the last display digit on 14 segment
clockDisplay.writeDigitNum(3, displayValue % 10, true); // Write last dot to show Alarm is on
}
}
// Now push out to the display the new values that were set above.
clockDisplay.writeDisplay();
} // End writeClockDisplay
void timeAdvance() {
t = 0;
while (digitalRead(buttonAdv) == LOW) {
now = rtc.now();
rtc.adjust(rtc.now() + TimeSpan(60)); // Add 1 minute to RTC
blinkColon = true; // turn colon on
displayTime();
t++;
if (t <= 15) {
delay(500);
} else {
delay(50);
}
}
// zero seconds to allow more acurate time setting
// Use single click on OK Button and syncSeconds() function to eventually set time accurately.
if (digitalRead(buttonAdv) == HIGH) {
now = rtc.now();
seconds = now.second();
rtc.adjust(rtc.now() - TimeSpan(seconds)); // round down to current minute
}
} // end timeAdvance
void timeRetard() {
t = 0;
while (digitalRead(buttonRev) == LOW) {
now = rtc.now();
rtc.adjust(rtc.now() - TimeSpan(60)); // deduct 1 minute to RTC
blinkColon = true; // turn colon on
displayTime();
t++;
if (t <= 15) {
delay(500);
} else {
delay(50);
}
}
// zero seconds to allow more acurate time setting
// Use single click on OK Button and syncSeconds() function to eventually set time accurately.
if (digitalRead(buttonRev) == HIGH) {
now = rtc.now();
seconds = now.second();
rtc.adjust(rtc.now() - TimeSpan(seconds)); // round down to current minute
}
} // end timeRetard
void alarmAdvance() {
t = 0;
for (Alarmminutes; Alarmminutes <= 60; Alarmminutes++) {
if (Alarmminutes == 60) {
Alarmminutes = 0;
Alarmhours++;
}
if (Alarmhours == 24) {
Alarmhours = 0;
}
// Serial.print(Alarmhours);
// Serial.print(":");
// Serial.println(Alarmminutes);
t++;
if (t <= 15) {
delay(500);
} else {
delay(50);
}
alarmValue = Alarmhours * 100 + Alarmminutes;
blinkColon = true; // turn colon on
writeClockDisplay(alarmValue);
if (digitalRead(buttonAdv) == HIGH) {
delay(2000); // delay 2 seconds showing alarm time
break; // exit routine if button released
}
}
} //****************** end alarm minutes fast forward routine*/
void alarmRetard() {
t = 0;
for (Alarmminutes; Alarmminutes >= -1; Alarmminutes--) {
if (Alarmminutes <= -1) {
Alarmminutes = 59;
Alarmhours--;
}
if (Alarmhours <= -1) {
Alarmhours = 23;
}
// Serial.print(Alarmhours);
// Serial.print(":");
// Serial.println(Alarmminutes);
t++;
if (t <= 15) {
delay(500);
} else {
delay(50);
}
alarmValue = Alarmhours * 100 + Alarmminutes;
blinkColon = true; // turn colon on
writeClockDisplay(alarmValue);
if (digitalRead(buttonRev) == HIGH) {
delay(2000); // delay 2 seconds showing alarm time
break; // exit routine if button released
}
}
} //****************** end alarm minutes fast backward routine*/
void playAlarm() {
digitalWrite(switchAmp, HIGH); // put amp off standby
playFile("Alarm.wav");
// Serial.println("playing audio file");
}
// End playAlarm
void playTime() {
digitalWrite(switchAmp, HIGH); // put amp off standby
now = rtc.now();
theTime = dst_rtc.calculateTime(now); // takes into account DST
hours = theTime.hour();
minutes = theTime.minute();
// Do 24 hour to 12 hour format conversion when required.
if (!TIME_24_HOUR) {
// Handle when hours are past 12 by subtracting 12 hours.
if (hours > 12) {
hours = hours - 12;
}
// Handle hour 0 (midnight) being shown as 12.
else if (hours == 0) {
hours = 12;
}
}
char hourWav[10] = { "00.wav" }; // construct series of filenames to speak hour and minute
String hourString = String(hours) + ".wav";
Serial.println(hourString);
hourString.toCharArray(hourWav, 10);
Serial.println(hourWav);
char minWav[10] = { "00.wav" }; // construct series of filenames to speak hour and minute
String minString = String(minutes) + ".wav";
minString.toCharArray(minWav, 10);
playFile("timeis.wav"); // Example recorded voice output should be "The time is 4 hours and 45 minutes" for 4:45
playFile(hourWav);
// playFile("hours.wav"); // Example recorded voice output should be "hours"
// playFile("and.wav"); // Example recorded voice output should be "and"
if (minutes > 0 && minutes < 10) {
playFile("oh.wav"); // this is used to have the more natural "The time is" "four" "oh" "five" for 4:05
}
playFile(minWav);
// playFile("minutes.wav"); // Example recorded voice output should be "minutes"
/* Serial.print("The time is: "); // For debug
Serial.print(hourWav); // should print file name
Serial.print(" hours and ");
Serial.print(minWav); // should print file name
Serial.println(" minutes");*/
}
// End playTime
void playFile(const char *filename) {
// Serial.println(); Serial.print("Playing file: "); Serial.print(filename);
File f;
if (SDOK) {
f = SD.open(filename);
} else if (QSPIOK) {
f = QSPIFS.open(filename);
}
// Start playing the file. This sketch continues to
// run while the file plays.
if (!playWav1.play(f)) {
Serial.println("Failed to play");
return;
}
// A brief delay for the library read WAV info
delay(5);
// Simply wait for the file to finish playing.
while (playWav1.isPlaying()) {
// Serial.print(".");
delay(100);
// uncomment these lines if you audio shield
// has the optional volume pot soldered
// float vol = analogRead(15);
// vol = vol / 1024;
// sgtl5000_1.volume(vol);
}
}
// End playAlarm
// this function will be called when the button was pressed more than 2 sec.
void setDate() { // set time zone, 12 or 24 hour display, and the date in the RTC
OK = false;
sp = false;
buttonOK.reset(); // This will stop it returning a long press state when it arrives at the next buttonOK.tick()
clockDisplay.drawColon(false); // turn Colon off
// set DST zone
time_now = millis();
time_in = millis();
while (millis() <= (time_now + 5000)) { // millis used for a non blocking delay
clockDisplay.println("DST ");
clockDisplay.writeDisplay();
delay(500);
clockDisplay.println(" ");
clockDisplay.writeDisplay();
delay(500);
}
// Read in existing parameters
Params clockParams;
// Read the content of "flash_mem" into the "clockParams" variable
clockParams = flash_mem.read();
// DST zone:
// int zone = clockParams.DSTzone;
int zone;
if (strcmp(rulesDST, "US") == 0) {
zone = 1;
} else if (strcmp(rulesDST, "EU") == 0) {
zone = 2;
} else {
zone = 0;
}
// display current zone
clockDisplay.println(rulesDST);
clockDisplay.writeDisplay();
delay(500);
// 500 ms delay after each change
while (!OK) { // perform set DST zone function
/* if (!sp) {
Serial.println("Set DST zone function");
sp = true;
} */
if (millis() > (time_in + time_out)) {
Reset();
} // if no button press for time_out
if (digitalRead(buttonAdv) == LOW) { // next rulesDST routine
delay(50); // delay for debounce button
while (digitalRead(buttonAdv) == LOW) {
// select next *******************************
if (zone < 2) { // possible existing 0, 1, or 2 (must be increased if more DST zones are added)
zone++; // can't go above 2 because of condition above
if (zone == 0) {
strcpy(rulesDST, "No");
} else if (zone == 1) {
strcpy(rulesDST, "US");
} else if (zone == 2) {
strcpy(rulesDST, "EU");
}
}
// display new current rulesDST
clockDisplay.println(rulesDST);
clockDisplay.writeDisplay();
delay(500);
}
time_in = millis(); // reset time_in to indicate button has been pressed
} // End next rulesDST
if (digitalRead(buttonRev) == LOW) { // previous rulesDST routine
delay(50); // delay for debounce button
while (digitalRead(buttonRev) == LOW) {
// select previous *******************************
if (zone > 0) { // possible existing 0, 1, or 2 (must be increased if more DST zones are added)
zone--; // can't go below 0 because of condition above
if (zone == 0) {
strcpy(rulesDST, "No");
} else if (zone == 1) {
strcpy(rulesDST, "US");
} else if (zone == 2) {
strcpy(rulesDST, "EU");
}
}
// display new current rulesDST
clockDisplay.println(rulesDST);
clockDisplay.writeDisplay();
delay(500);
}
time_in = millis(); // reset time_in to indicate button has been pressed
} // End previous rulesDST
buttonOK.tick(); // watch for buttonOK press
} // end set rulesDST funtion
// ********************** after setting the parameters save them
saveParams();
OK = false;
sp = false;
// set 12 or 24 hrs display
time_now = millis();
while (millis() <= (time_now + 5000)) { // millis used for a non blocking delay
clockDisplay.println("24hr");
clockDisplay.writeDisplay();
delay(500);
clockDisplay.println("12hr");
clockDisplay.writeDisplay();
delay(500);
}
// display curent 12 or 24 hr display
if (TIME_24_HOUR) {
clockDisplay.println("24hr");
} else {
clockDisplay.println("12hr");
}
clockDisplay.writeDisplay();
delay(500);
// 500 ms delay after each change
while (!OK) { // perform set set 12 or 24 hr display function
/* if (!sp) {
Serial.println("12 or 24 hr display function");
sp = true;
} */
if (millis() > (time_in + time_out)) {
Reset();
} // if no button press for time_out
if (digitalRead(buttonAdv) == LOW) { // set to 24 hr display
delay(50); // delay for debounce button
while (digitalRead(buttonAdv) == LOW) {
// select 24hr *******************************
TIME_24_HOUR = true;
// display new current 24 hr display status
if (TIME_24_HOUR) {
clockDisplay.println("24hr");
} else {
clockDisplay.println("12hr");
}
clockDisplay.writeDisplay();
delay(500);
clockDisplay.writeDisplay();
delay(500);
}
time_in = millis(); // reset time_in to indicate button has been pressed
} // End set 24 hr display
if (digitalRead(buttonRev) == LOW) { // set to 12 hr display
delay(50); // delay for debounce button
while (digitalRead(buttonRev) == LOW) {
// select 12hr *******************************
TIME_24_HOUR = false;
// display new current 24 hr display status
if (TIME_24_HOUR) {
clockDisplay.println("24hr");
} else {
clockDisplay.println("12hr");
}
clockDisplay.writeDisplay();
delay(500);
}
time_in = millis(); // reset time_in to indicate button has been pressed
}
buttonOK.tick(); // watch for buttonOK press
} // end set 12 or 24 hr display funtion
// ********************** after setting the parameters save them
saveParams();
OK = false;
sp = false; // sp is for serial print, this is a flag that allows certain lines to be printed only once
Serial.println("Set date funtion");
// Display YYYY
time_now = millis();
while (millis() <= (time_now + 5000)) { // millis used for a non blocking delay
clockDisplay.println("YYYY");
clockDisplay.writeDisplay();
delay(500);
clockDisplay.println(" ");
clockDisplay.writeDisplay();
delay(500);
}
while (!OK) { // perform set year
if (!sp) {
Serial.println("Set year function");
sp = true;
}
if (millis() > (time_in + time_out)) {
Reset();
} // if no button press for time_out
// get date and time
now = rtc.now();
YYYY = now.year(); // the year as a 4-digit number (2000–2099)
MM = now.month(); // the month as a 2-digit number (01–12)
DD = now.day(); // the day as a 2-digit number (01–31)
hh = now.hour(); // the hour as a 2-digit number (00–23 or 01–12)
mm = now.minute(); // the minute as a 2-digit number (00–59)
ss = now.second(); // the second as a 2-digit number (00–59)
// display current year
clockDisplay.print(YYYY, DEC);
clockDisplay.writeDisplay();
// add or subtract years while dispaying new year
// 500 ms delay after each change
if (digitalRead(buttonAdv) == LOW) {
while (digitalRead(buttonAdv) == LOW) {
now = rtc.now();
// YYYY = now.year(); // updated during the routine
MM = now.month();
DD = now.day();
hh = now.hour();
mm = now.minute();
ss = now.second();
YYYY++;
// display new current year
clockDisplay.print(YYYY, DEC);
clockDisplay.writeDisplay();
delay(500); // short delay for display during year change
}
if (digitalRead(buttonAdv) == HIGH) {
rtc.adjust(DateTime(YYYY, MM, DD, hh, mm, ss)); // at end of forward or backward button press set clock date and time with new year
}
time_in = millis(); // reset time_in to indicate button has been pressed
}
if (digitalRead(buttonRev) == LOW) {
while (digitalRead(buttonRev) == LOW) {
now = rtc.now();
// YYYY = now.year(); // updated during the routine
MM = now.month();
DD = now.day();
hh = now.hour();
mm = now.minute();
ss = now.second();
YYYY--;
// display new current year
clockDisplay.print(YYYY, DEC);
clockDisplay.writeDisplay();
delay(500); // short delay for display during year change
}
if (digitalRead(buttonRev) == HIGH) {
rtc.adjust(DateTime(YYYY, MM, DD, hh, mm, ss)); // at end of forward or backward button press set clock date and time with new year
}
time_in = millis(); // reset time_in to indicate button has been pressed
}
buttonOK.tick(); // watch for buttonOK press
} // end set year function
OK = false;
sp = false;
Serial.println("Set year function completed");
time_now = millis();
while (millis() <= (time_now + 5000)) { // millis used for a non blocking delay
clockDisplay.println(" DD");
clockDisplay.drawColon(true);
clockDisplay.writeDisplay();
delay(500);
clockDisplay.println("MMDD"); // on seven segment display M does not really look like an M
clockDisplay.drawColon(true);
clockDisplay.writeDisplay();
delay(500);
}
now = rtc.now();
YYYY = now.year(); // the year as a 4-digit number (2000–2099)
MM = now.month(); // the month as a 2-digit number (01–12)
DD = now.day(); // the day as a 2-digit number (01–31)
hh = now.hour(); // the hour as a 2-digit number (00–23 or 01–12)
mm = now.minute(); // the minute as a 2-digit number (00–59)
ss = now.second(); // the second as a 2-digit number (00–59)
// display current month and day
displayMMDD();
while (!OK) { // perform set month function
/*if (!sp) {
Serial.println("Set month function");
sp = true;
}*/
if (millis() > (time_in + time_out)) {
Reset();
} // if no button press for time_out
// get date and time
// add or subtract months while dispaying new month (will not roll over years)
// 500 ms delay after each change
if (digitalRead(buttonAdv) == LOW) { // Month advance routine
while (digitalRead(buttonAdv) == LOW) {
now = rtc.now();
YYYY = now.year();
// MM = now.month(); // updated during the routine
DD = now.day();
hh = now.hour();
mm = now.minute();
ss = now.second();
MM++;
if (MM > 12) {
MM = 1;
}
// display new current month (and day)
displayMMDD();
}
if (digitalRead(buttonAdv) == HIGH) {
rtc.adjust(DateTime(YYYY, MM, DD, hh, mm, ss)); // at end of forward or backward button press set clock date and time with new year
}
time_in = millis(); // reset time_in to indicate button has been pressed
} // End month advance
if (digitalRead(buttonRev) == LOW) { // Month retard routine
while (digitalRead(buttonRev) == LOW) {
now = rtc.now();
YYYY = now.year();
// MM = now.month(); // updated during the routine
DD = now.day();
hh = now.hour();
mm = now.minute();
ss = now.second();
MM--;
if (MM < 1) {
MM = 12;
}
// display new current month (and day)
displayMMDD();
}
if (digitalRead(buttonRev) == HIGH) {
rtc.adjust(DateTime(YYYY, MM, DD, hh, mm, ss)); // at end of forward or backward button press set clock date and time with new year
}
time_in = millis(); // reset time_in to indicate button has been pressed
} // End month retard
buttonOK.tick(); // watch for buttonOK press
} // end set month funtion
OK = false;
sp = false;
Serial.println("Set month function completed");
time_now = millis();
while (millis() <= (time_now + 5000)) { // millis used for a non blocking delay
clockDisplay.println(" ");
clockDisplay.drawColon(true);
clockDisplay.writeDisplay();
delay(500);
clockDisplay.println(" DD");
clockDisplay.drawColon(true);
clockDisplay.writeDisplay();
delay(500);
}
// display current month and day
now = rtc.now();
MM = now.month();
DD = now.day();
displayMMDD();
while (!OK) { // perform set day function
/* if (!sp) {
Serial.println("Set day function");
sp = true;
} */
if (millis() > (time_in + time_out)) {
Reset();
} // if no button press for time_out
// add or subtract days (in seconds) while dispaying new month and day (will roll over months)
// 500 ms delay after each change
if (digitalRead(buttonAdv) == LOW) { // Day advance routine
while (digitalRead(buttonAdv) == LOW) {
rtc.adjust(rtc.now() + TimeSpan(86400)); // Add 1 day (in seconds) to RTC
now = rtc.now();
MM = now.month();
DD = now.day();
displayMMDD();
}
time_in = millis(); // reset time_in to indicate button has been pressed
} // End day advance
if (digitalRead(buttonRev) == LOW) { // day retard routine
while (digitalRead(buttonRev) == LOW) {
rtc.adjust(rtc.now() - TimeSpan(86400)); // Add 1 day (in seconds) to RTC
now = rtc.now();
MM = now.month();
DD = now.day();
displayMMDD();
} // End day retard
time_in = millis(); // reset time_in to indicate button has been pressed
}
buttonOK.tick(); // watch for buttonOK press
} // end set day funtion
Serial.println("Set day function completed");
Serial.println("End set date funtion");
} // End setDate ******************
void syncSeconds() {
/* This function, actuated by a single click on the OK button, synchonises the clock
to a known time source by adjusting the seconds up to the next minute if the clock
is less than 30 seconds slow or, if the clock is up to 30 fast it will reduce the
seconds to the current minute.*/
now = rtc.now();
seconds = now.second();
if (seconds <= 30) {
rtc.adjust(rtc.now() - TimeSpan(seconds)); // round down to current minute
} else {
rtc.adjust(rtc.now() + TimeSpan(60 - seconds)); // round up to minute
}
}
void singleClick() {
Serial.println("singleClick");
OK = true;
time_in = millis(); // reset time_in to indicate button has been pressed
} // singleClick
// this function will be called when the button was pressed 2 times in a short timeframe.
void Reset() {
Serial.println("singleClick");
NVIC_SystemReset();
} // doubleClick
void displayMMDD() {
// display new current month and day
MMDD = (float)MM + (DD / 100.0);
clockDisplay.print(MMDD);
// Add zero padding when MM is less than 10.
// In this case the print function above won't have leading 0's
// which can look confusing. Go in and explicitly add these zeros.
if (MM < 10) {
// Pad MM 0.
clockDisplay.writeDigitNum(0, 0);
}
// Also pad when the day is 10, 20, or 30 and 0 and should be padded at the end.
if ((DD % 10) == 0) {
if (seven_Seg) {
clockDisplay.writeDigitNum(4, 0); // for Adafruit 7 segment display
} else {
clockDisplay.writeDigitNum(3, 0); // for 14 segment display
}
}
clockDisplay.drawColon(true);
clockDisplay.writeDisplay();
delay(500); // short delay for display during date change
}
void checkBattery() {
float measuredvbat = analogRead(VBATPIN);
measuredvbat *= 2; // we divided by 2, so multiply back
measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
measuredvbat /= 1024; // convert to voltage
// Serial.print("VBat: ");
// Serial.println(measuredvbat);
if (measuredvbat <= 3.6) { // if battery is low sound beep and flash bat
digitalWrite(switchAmp, HIGH); // put amp off standby
playFile("beep.wav");
delay(50);
digitalWrite(switchAmp, LOW); // put amp on standby
time_now = millis();
while (millis() <= (time_now + 1000)) { // millis used for a non blocking delay
clockDisplay.drawColon(false);
clockDisplay.println("BAT ");
clockDisplay.writeDisplay();
}
}
}
// print time to serial
void printTheTime(DateTime theTimeP) {
Serial.print(theTimeP.year(), DEC);
Serial.print('/');
Serial.print(theTimeP.month(), DEC);
Serial.print('/');
Serial.print(theTimeP.day(), DEC);
Serial.print(' ');
Serial.print(theTimeP.hour(), DEC);
Serial.print(':');
Serial.print(theTimeP.minute(), DEC);
Serial.print(':');
Serial.print(theTimeP.second(), DEC);
Serial.println();
}