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();
}