spudnut1
Published

Chiming, Scrolling LED Matrix Clock

Matrix clock with scrolling time, date, day of week, temp, humidity and event notifications.

IntermediateFull instructions provided18,757
Chiming, Scrolling LED Matrix Clock

Things used in this project

Hardware components

LED Matrix 7219 four module display
×2
Arduino Mega 2560
Arduino Mega 2560
×1
Humidity and Temperature Sensor
Adafruit Humidity and Temperature Sensor
×1
Adafruit RC D3231
×1
Adafruit FX 2mb Soundboard with built in amplifier
×1
Speaker: 0.25W, 8 ohms
Speaker: 0.25W, 8 ohms
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×4
Red Acrylic face plate
×1
Wooden Box for enclosure
×1
Photo resistor
Photo resistor
×1
Resistor 10k ohm
Resistor 10k ohm
×1

Story

Read more

Schematics

Matrix Clock Connections

Schematic substitute

Clock sounds

The ten sounds used to make up the startup sound, Westminster Chimes, bells and Game of Thrones music

Updated code for automatic DST begin and end

I mistakenly thought that the DS3231 RTC module 'knew' about DST transitions. Doesn't. So I've added code to make that transition to/from DST automatic

Code

LED Matrix clock

Arduino
Arduino code for clock
/*
   Clock project using two MX7219 matrix displays and the AdaFruit FX Soundboard
   Provides date, time, temp, humidity and event notifications in rotating order
   Also can chime in multiple ways, including Westminster chimes
   Chimes are provided by Adafruit FX Soundboard in GPIO mode

*/




// Code to set up the clock
// Pushbutton 1 is the toggler , cycle between  year, month, day, hours, minutes, seconds
// Pushbutton 2 is the increment up one
// Pushbutton 3 is the decrement down one

// Chime setup for toggle switch
//  Center = No chimes
//  Up = Westminster hours + quarterly
//  Down = Simple Bell (hours and 1/2 hour)

//      Comment out next line to leave out the little show 

#define showoffcode                                       // include "show off" graphics at startup

// include the libraries for LCD Display, DHT-11 Temperature/humidity sensor and DS3231 RTC and
// include libraries for bigger seven segment display with backpack
#include "DHT.h"
#include <Wire.h>
#include "RTClib.h"
#include <MD_MAX72xx.h>
// uncomment next two lines to use UART for SoundBoard rather than GPIO
//#include <Adafruit_Soundboard.h>
//#include <SoftwareSerial.h>


// Set up output functions

#define DHTPIN 9     //Temp Humidity sensor on pin 9
#define DHTTYPE DHT11   // DHT 11 because the sensor is that type

// Initialize DHT sensor for normal 16mhz Arduino
DHT dht(DHTPIN, DHTTYPE);

// Initialize for Adafruit DS3231 RTC real time clock
RTC_DS3231 rtc;

// uncomment next set of  lines to use UART for SoundBoard
/*
  // Choose any two pins that can be used with SoftwareSerial to RX & TX for UART SoundBoard
  //#define SFX_TX 5
  //#define SFX_RX 6
  // Connect to the RST pin on the Sound Board
  #define SFX_RST 4
  // You can also monitor the ACT pin for when audio is playing!
  // we'll be using software serial
  //SoftwareSerial ss = SoftwareSerial(SFX_TX, SFX_RX);
  // pass the software serial to Adafruit_soundboard, the second
  // argument is the debug port (not used really) and the third
  // arg is the reset pin
  //Adafruit_Soundboard sfx = Adafruit_Soundboard(&ss, NULL, SFX_RST);
  // can also try hardware serial with
  // Adafruit_Soundboard sfx = Adafruit_Soundboard(&Serial1, NULL, SFX_RST);
*/

// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW                             // NOTE: This parameter differs by vendor
#define MAX_DEVICES 8                                                   // 2 x 4 matrices per unit

//Pins for Arduino Uno
//#define CLK_PIN   13  // or SCK  (this is for the UNO, different for the Mega
//#define DATA_PIN  11  // or MOSI
//#define CS_PIN   10  // or SS


//Pins for Arduino Mega
#define CLK_PIN  52  // or SCK  13
#define DATA_PIN  51  // or MOSI   11
#define CS_PIN   53  // or SS 10


// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// We always wait a bit between updates of the display
#define  DELAYTIME  100  // in milliseconds

// Text parameters
#define CHAR_SPACING  1 // pixels between characters
//const int CHAR_PIXELS = 6;            // Pixels taken by each character (including trailing space)
//const int MAX_CHARS = (MAX_DEVICES*COL_SIZE)/ CHAR_PIXELS;   // Maximum characters at once (happens to be 10 btw)
//const int LEFT_OVER = (MAX_DEVICES*COL_SIZE)- (CHAR_PIXELS*MAX_CHARS);      //Since its not 'even' additional pad from front on printText
const int BufferStringLength = 31;
char BufferString [BufferStringLength];                 //used to construct printouts
#define DecToAscii  48                  // convert number to ascii (add as a constant)
#define DegreeSign 0x0090               // for display of 'degrees' after temperature
int TDigits[4] = {0, 0, 0, 0};          // used to convert four digits to four chacacters
#define TDOnes  3                       // positions in arrays and templates
#define TDTens  2
#define TDHundreds 1
#define TDThousands 0
#define sunday  0                         // used for events, floating events and holidays
#define monday  1                         // just for readability
#define thursday  4
#define january 1
#define february 2
#define march 3
#define april 4
#define may 5
#define june 6
#define july 7
#define august 8
#define september 9
#define october 10
#define november 11
#define december 12

//Template Strings and pointers

const int LOTS = 13;                    // Length of TimeString (without terminator)
char TimeString[LOTS + 1 ] = {' ', ' ', '1', '2', ':', '3', '1', ':', '4', '5', ' ', 'P', 'M', '\0'};
char DateString[ ] = {'1', '2', '/', '3', '1', '/', '2', '0', '1', '9', '\0'};
char TempString[ ] = {'T', 'e', 'm', 'p', ' ', '7', '2', '.', '0', DegreeSign, 'F', '\0'};
char HumidString[ ] = {'H', 'u', 'm', ' ', '2', '5', '.', '0', '%', '\0'};
const int THptr = 2;                                                // Pointers within the strings
const int TMptr = 5;                                                // for loading values
const int TSptr = 8;
const int TAMPMptr = 11;
const int DMptr = 0;
const int DDptr = 3;
const int DYptr = 6;
const int TEMPptr = 5;
const int HUMIDptr = 4;

// Differentiated Displays

const int DisplayTime = 0;
const int DisplayDate = 2;
const int DisplayDOW  = 1;
const int DisplayTemp = 3;
const int DisplayHumid = 4;
const int DisplayEvent = 5;
const int DisplayEventRepeat = 2;                         //times to repeat the scrolling 'event' display
const int DisplaySize = 6;                                // Nunber of different displays
int DisplayIndex = DisplayTime;                                    // display we are on at the time
const int DisplayDelayArray[DisplaySize] = {100, 50, 30, 30, 30, 20}; // multiplier for each event
const int DisplayDelay = 100;                              // milliseconds to leave each display * its multiplier
unsigned long DisplayTimer;                               // used to time each matrix display



char daysOfTheWeek[7][12 ] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// Event arrays
// Load with month of 'event', day of event and description to be displayed

const int eventnumber = 21;         // number of events and remember: No strings longer than 30 characters!
int eventmonth [eventnumber] = {june, april, november, february, july, december, november, june, december, june, march, december, january,
                                september, october, march, february, february, july,december, february};
int eventday [eventnumber] = {11, 1, 23, 12, 4, 3, 12, 29, 25, 18, 17, 31, 1, 3, 31, 17, 14, 2, 14, 30,12};
char* eventdescription [eventnumber]  =
{ "Dana's Anniversary", "April Fool's Day", "Joan's Birthday", "Sydney's Birthday", "Happy July 4th!", "Dana's Birthday",
  "David's Birthday", "Our Anniversary", "Merry Christmas", "Leah's Birthday", "St Patrick's Day", "New Year's Eve", "New Year's Day",
  "Forrest's Birthday", "Happy Halloween", "Saint Patrick's Day", "Valentine's Day", "Groundhog Day", "Bastille Day", "Linsey's Birthday",
  "Irwin's Birthday"
};

bool event;                     // logic flag "on" = event found
int eventindex;                 // and the event we found
const int maxevents = 3;          // maximum of three events (real and floating combined)
char* eventstrings[maxevents] ;    // used to store displayed events
String floatstring = "                    "; // used for floating events (e.g., Thanksgiving)

#define pb1pin 2              //  Pin assignments for reset, increment and decrement
#define pb2pin  3
#define pb3pin  4

#define showoffbuttonpin 7                           // invoke showing off the matrix display

#define DoWestminster 5
#define DoHoursOnly 6
#define silent  0                                 // no chiming at all
#define hoursonly  1                              // no prelude, just chime number of hours and half hour single chime
#define westminster 2                            // hours + Westminster prelude per quarter
int ChimeValue = silent;                         // default to silent


const int LEDpin = A0;                          // Pin assignment for analog reading LDR for LED brightness
const int minbright = 0;              // MAX_BRIGHT for this module is 15
const int midbright = 2;              // but these values "seem" to work
const int maxbright = 4;




int setupindex = 0;                 // first thing to set if required
bool insetupmode = false;           // and assume RTC is set, OK, etc., and no 'set' required
bool setinsetup = false;            // flag set to true at interrupt level to go back into setup mode

// date time array for setting, reading, displaying

#define setsize   6            // size of the setting array
#define setyear   0            // index name for each element
#define setmonth   1
#define setday   2
#define sethour   3
#define setminute   4
#define setsecond   5

int setarray [setsize] = {2019, 1, 1, 1, 1, 0};  // set year, month, day,  hour, minutes, seconds
int lowlimit [setsize] = {2019, 1, 1, 0, 1, 0}; // lower limit for each
int highlimit [setsize] = {2080, 12, 31, 23, 59, 59};  //high limit for each
const int setdesclength = 4;                            // maximum length of 'set' descriptor
char setdesc [setsize] [setdesclength] = {"Yr ", "Mon", "Day", "Hr ", "Min", "Sec"};


#define cqtr0   1        //  Chime the hour
#define cqtr1   2        //  Chime the quarter hour
#define cqtr2   3        //  Chime the half hour
#define cqtr3   4         // Chime the 3/4 hour

// Definitions for the Sound Board
#define SoundTruncatedSingle   0                  // Truncated Single Westminster
#define SoundQ1   1                                 // First Quarter
#define SoundQ2   2                               // Second Quarter
#define SoundQ3   3                               //  Third
#define SoundQ4   4                             //   Fourth (before hour chime)
#define SoundTrailingSingle  5                    //  Finish Westminster string of chimes with longer sound tail
#define SoundTruncatedBell   6                         // Simple Bell
#define SoundTrailingBell  7                      // Final Bell (like single, has longer 'tail')
#define SoundStartup   8                        // startup sound
#define SoundShowOff 9                            // Play during show off of matrix
#define FirstSoundPin   30                      // first pin to use for soundboard (0-10 available on soundboard);
#define SizeSoundPin   10                        // number of pins used (must be consecutive)
#define  fxset 150                              // Time soundboard must be held LOW to register (documentation says 125 ms)
#define  SoundBusyPin 8                         // Soundboard pin that goes LOW when board is active

const int SizeQueue = 15;                          // Assume Q4+12 bells is the maximum that'll be in the queue at any point
int SoundQueue[SizeQueue];                       // Circular Queue for waiting sounds
int SoundQueuePtr = -1;                           //pointer for Queue fill Position
int SoundPlayPtr = -1;                           //pointer for Queue play Position
int SoundQueueCtr = 0;                            // number of items in the queue

int  setstrike = -1;                          // Chime/strike flag
byte  alreadychimed = false ;                 // Used to keep from chiming multiple times during the "hot" second
const int BounceDelay = 250;                // Not really 'bounce', its a change of state detection



int i;                          // generic index variable

int dayoftheweek;               // stored day of the week (0-6, 0 = Sunday)

int phours;                     // for print conversion of military time

float temperaturef;             // farenheit temperature back
int temperature;                  // integer version of temperature for matrix
float temperaturec;             // centrigade temperature back (not used)

float humidityf;                 // and humidity
int humidity  ;                 // and same as temp

float tempadjust = -2.0;  // temperature adjustment for sensor (I found it didn't read right against 'comps')
byte degreesymbol = 223;  // LCD output of 'degree' symbol

#define DDT

#ifdef DDT                                         // debugging tools
void DDTl(String st, int vt) {                     // Print descriptor and value and new line
  DDTs(st, vt);
  Serial.println(" ");
}
void DDTs(String st, int vt) {                     // Print descriptor and value
  Serial.print("  ");
  Serial.print(st);
  Serial.print("  ");
  Serial.print(vt);
}
#endif //DDT//


void setup()
// put your setup code here, to run once:

{
  Wire.begin();                     // initialize I2C interface
  dht.begin();                      // initialize the temp/humidity sensor
  mx.begin();                      // and MX7219 display
  Serial.begin (9600);            //Terminal monitor printing for debugging

  // softwareserial at 9600 baud (uncomment this line and next for UART control of sound board
  // ss.begin(9600);


  pinMode(pb1pin, INPUT_PULLUP);  // The three pushbuttons - reset
  pinMode(pb2pin, INPUT_PULLUP);   // increment
  pinMode(pb3pin, INPUT_PULLUP);   // decrement

  pinMode (DoWestminster, INPUT_PULLUP);            // When pulled 'low' we want Westminster chimes
  pinMode (DoHoursOnly , INPUT_PULLUP);             // When pulled 'low' we want hours and 1/2 bell (neither is 'silent')

  #ifdef showoffcode
      pinMode(showoffbuttonpin, INPUT_PULLUP) ;             // if show off matrix is there, initialize pin
  #endif //showoffcode// 

  for (i = FirstSoundPin; i < FirstSoundPin + SizeSoundPin; i++) {     // pins for sound board
    pinMode(i, OUTPUT);                                               // each an output
    digitalWrite(i, HIGH);                                           // and initialize high (off)
  }    

  // set up interupt for PB1                                               // if PB1 is pushed, it'll pick up on next release from Matrix display
      attachInterrupt(digitalPinToInterrupt(pb1pin), SetToSetup, FALLING);     // and re-enter setup mode (after the end of current display)

#ifdef showoffcode
// ShowOff();                                       // Show off a little (uncomment to give initial showoff at beginning)
#endif //showoffcode// 

    QueueSound(SoundStartup);                        // play boot up sound 
    PlaySound();

  if (! rtc.begin()) {                        // check that clock is there
    scrollText("Couldn't find RTC");       // clock missing is a fatal error
    while (1);
  }

  if (rtc.lostPower()) {           // if power lost force a setup, else load 'current' values and
    scrollText("RTC lost power!");
    delay(1000);
    scrollText("Replace battery?");
    delay(1000);
    scrollText("Setup Mode: ");
    setupindex = setyear - 1;         // setup differently (becuase it will increment)
    insetupmode = true;
    for (i = 0; i < setsize; i++) {
      setarray[i] = lowlimit[i];
    }
      } else                                                      // else reload from RTC
  { DateTime (setarray[setyear], setarray[setmonth], setarray[setday], setarray[sethour], setarray[setminute], setarray[setsecond])  = rtc.now();
    insetupmode = false;                                                        // not in setup mode, and
    setinsetup = false;                                                          //no interrupt
  }

  // Two alternative modes of setting date and time (for debugging, just un-comment):
  // This line sets the RTC to the date & time this sketch was compiled
  //    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  //   rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
        LEDBrightness();                                                          // Do an initial check for room brightness
}      // end of setup

void loop() {                                                             // Main code divided into setup mode and non-setup mode

  while (insetupmode) {                                                 // code for setting time, date set, etc.

    // Read the increment button

    if (digitalRead(pb2pin) == LOW) {
      setarray[setupindex]++;            // read increment of current item
      delay(BounceDelay);
      if (setarray[setupindex] > highlimit[setupindex]) {
        setarray[setupindex] = lowlimit[setupindex];
      }
      ShowValue(setupindex, setdesc[setupindex]);                         // and display description and value
    }

    // Read the decrement Button
    if (digitalRead(pb3pin) == LOW) {
      setarray[setupindex]--;                  // read decrement of value
      delay(BounceDelay);
      if (setarray[setupindex] < lowlimit[setupindex]) {
        setarray[setupindex] = highlimit[setupindex];
      }
      ShowValue(setupindex, setdesc[setupindex]);                              // and display description and value
    }

    // Read for another increment of index

    if (digitalRead(pb1pin) == LOW) {             // Rolling through Chime, Year, Month, Day, Hour, Minutes, Seconds
      setupindex++ ;                            // increment index
      delay(BounceDelay);
      if (setupindex < setsize) {
        ShowValue(setupindex, setdesc[setupindex]); // show description and value if in bounds
      }
      if (setupindex >= setsize) {                               // and finally exiting setup mode after setting chime, date and time
        rtc.adjust(DateTime(setarray[setyear], setarray[setmonth], setarray[setday], setarray[sethour], setarray[setminute], setarray[setsecond]));
        insetupmode = false;
        DisplayIndex = DisplayTime;
      }  //exit setup mode when done
    }

  }  // End of "While" for setup

  // Begin regular loop for date, time, temp humidity and event  display

  while (!insetupmode) {

    GetTempandHumid();                                            // read temperature and humidity from sensor
    
    ChimeValue = ReadChimeSetting();                               // read SPDT Center off switch for silent, westminster or hours only (and 1/2)
    
    GetTheDate();                                                 // load date array from RTC for a chime/bell check
    
    CheckForChime();                                              // Check for a chime event between displays

    CheckForEvent(setarray[setmonth], setarray[setday], dayoftheweek);    // check for static and floating events
              
    MatrixDisplay();                                              // Do whatever display is required on the MX7219

    LEDBrightness();                                                              // Good place to check for change in room brightness

    // end of display update logic

    // Read a potential request for an entry into setup from PB 1
    if ((digitalRead(pb1pin) == LOW) || setinsetup) {                // to see if we go back to setup mode
      insetupmode = true;                                             // via a pushbutton or the interrupt
      setinsetup = false;                                              // clear the interrupt flag
      setupindex = 0;                                                 // re-initialize to 'Year' in setup
      DisplayIndex = DisplayTime;                                       // and go back to time display when exit setup
      scrollText("Setup Mode: ");
      delay(2000);
      ShowValue(setupindex, setdesc[setupindex]);                     // show the first setup item (Year)
      delay(BounceDelay);
    }
      #ifdef showoffcode
      if (digitalRead(showoffbuttonpin) == LOW) {ShowOff();}             // check for want to show off matrix
      #endif //showoffcode// 

  } // end of not in setup
}  // end of sketch

void MatrixDisplay() {                                                           // Main display routine for display sequences

  Scrollup();                                                                   // Clear last display
  if (DisplayIndex >= DisplaySize) {
    DisplayIndex = DisplayTime; // reset if at the end
  }
  DisplayTimer = DisplayDelayArray[DisplayIndex] * DisplayDelay;                // set individual display time

  switch (DisplayIndex) {                                                       // and do next display

    case DisplayTime:    // Dislay the time
      do                                                                          // time is different in that there's a constant
      {                                                                           // update of time during display and also play chime, bell etc.
        GetTheDate();                                                               // Get Current Time
        CheckForChime();                                                            // Check for a chime event
        LoadNumber(phours);                                                         // Load the 4 digit character array from number
        TimeString[THptr] = TDigits[TDTens];                                        // left digit of the two digit hour
        TimeString[THptr + 1] = TDigits[TDOnes];                                    // rightmost digit
        if (TimeString[THptr] == '0') {
          TimeString[THptr] = ' '; // eliminate leading zero
        }
        LoadNumber(setarray[setminute]);                                             // do same for minutes
        TimeString[TMptr] = TDigits[TDTens];                                          // except no need to do space for zero
        TimeString[TMptr + 1] = TDigits[TDOnes];
        LoadNumber(setarray[setsecond]);                                              // seconds then
        TimeString[TSptr] = TDigits[TDTens];
        TimeString[TSptr + 1] = TDigits[TDOnes];
        if (setarray[sethour] < 12)                                                   // and AM vs PM
        {
          TimeString[TAMPMptr] = 'A';
        }
        else
        {
          TimeString[TAMPMptr] = 'P';
        }
        printText(TimeString, LOTS, false);                                           // Keep position (no centering) because time keeps updating during display and chimes
        PlaySound();                                                                // Play any chimes or bells
        if (SoundQueueCtr == 0) {
          DisplayTimer = DisplayTimer - DisplayDelay;          // if no sound, use display delay logic
          delay(DisplayDelay);
        }
      } while ((DisplayTimer > 0) || (SoundQueueCtr > 0));                         // leave this display when no sound or no delay left
      DisplayIndex++ ;                                                                // then move on to next display item
      break;

    case DisplayDate:                         // Month, Day and Year Display
      LoadNumber(setarray[setmonth]);
      DateString[DMptr] = TDigits[TDTens];
      if (DateString[DMptr] == '0') {
        DateString[DMptr] = ' ';
      }
      DateString[DMptr + 1] = TDigits[TDOnes];
      LoadNumber(setarray[setday]);
      DateString[DDptr] = TDigits[TDTens];
      DateString[DDptr + 1] = TDigits[TDOnes];
      LoadNumber(setarray[setyear]);
      for (int i = 0; i < 4; i++) {
        DateString[DYptr + i] = TDigits[i];
      }
      printCenter(DateString);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayDOW:                                                               //  Just display the day of week string
      printCenter(daysOfTheWeek[dayoftheweek]);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayTemp:                                                          // Temperature display
      LoadNumber(temperature);
      TempString[TEMPptr] = TDigits[TDHundreds];
      TempString[TEMPptr + 1] = TDigits[TDTens];
      TempString[TEMPptr + 3] = TDigits[TDOnes];
      printCenter(TempString);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayHumid:                        // Humidity display
      LoadNumber(humidity);
      HumidString[HUMIDptr] = TDigits[TDHundreds];
      HumidString[HUMIDptr + 1] = TDigits[TDTens];
      HumidString[HUMIDptr + 3] = TDigits[TDOnes];
      printCenter(HumidString);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayEvent:                                          // Event Display
      if (event) {                                                // "Real" events repeat 3 times
          if (eventindex > 0 ) {                                  // for case where there is more than one event 
              while (eventindex >= 0)
                  {scrollText(eventstrings[eventindex]);                        // scroll each of them once
                  if (eventindex > 0) {
                     delay(DisplayDelay * 6);
                     printCenter("and");
                     delay(DisplayDelay * 6);}
                   eventindex--;                                                // and decrement
                   delay(DisplayDelay * 2);
                  }
          }
          else {                                                      // case of only one event -- repeat it for readability
            for (int i = 1; i <= DisplayEventRepeat; i++) {          // scroll either static or floating event 
            scrollText(eventstrings[eventindex]);                     // the index is 0 (greater than zero if multiple events)
          } 
          delay(DisplayDelay * 2);
        }
          }                                                             // end of "if event" logic
            if (!event){                                                // Scroll Default message only once
              if (setarray[sethour] >= 6 && setarray[sethour] <= 11) {
              scrollText("Good Morning ");}
              else if (setarray[sethour] >= 12 && setarray[sethour] <= 16) {
              scrollText("Good Afternoon");} 
              else if (setarray[sethour] >= 17 && setarray[sethour] <= 21) {
              scrollText("Good Evening");} else{scrollText("Good Night"); }
              }                                       
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    default:
      scrollText("Should never get here");
      while (1);

  }    //  End of Switch

}  // End of MatrixDisplay

void GetTheDate() {                                                          // Read RTC into array
  DateTime now = rtc.now();
  setarray[setyear] = now.year();
  setarray[setmonth] = now.month();
  setarray[setday] = now.day();
  setarray[sethour] = now.hour();
  setarray[setminute] = now.minute();
  setarray[setsecond] = now.second();
  dayoftheweek = now.dayOfTheWeek();
  if (setarray[sethour] <= 12)              // convert military time to am pm
  {
    phours = setarray[sethour];
  }
  else
  {phours = setarray[sethour] - 12;}
  if (phours <= 0) { phours = 12;  }            // don't print 0 for midnite, print "12"
 
}   // End of Get The Date

void GetTempandHumid(){                         // Temperature and humidity update

    // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
    humidityf = dht.readHumidity();

    // Read temperature as Fahrenheit
    temperaturef = (dht.readTemperature(true)) + tempadjust;  // read and correct for inaccuracy in sensor

    // Check if any reads failed and exit early (to try again).
    if (isnan(humidityf) || isnan(temperaturec) || isnan(temperaturef)) {
      temperaturef = 0;              // use 0 as a no-read
      humidityf = 0;                  // error indication
    }
    temperature = temperaturef * 10.0;          // get read for matrix printout
    humidity = humidityf * 10.0;
}   // end of Temperature and Humidity update

void SetToSetup() {                               // interrupt level back to setup
  if ((!insetupmode) && (!setinsetup)) {                              // skip if we are already in Setup mode or have seen the interrupt
    setinsetup = true;                              // Go back into setup at next loop
    DisplayTimer = 0;
  }                                // and zero the timer to speed that up if you can
}  // SetToSetup

void CommonDelay(long int MyDelay) {                          // Common delay routine
  DisplayTimer = MyDelay;
  do {
    DisplayTimer = DisplayTimer - DisplayDelay;
    delay(DisplayDelay);
    GetTheDate();                                                 // load date array from RTC for a chime/bell check
    CheckForChime();                                              // if a chime happens, setup to end delay
  } while ((DisplayTimer > 0) && (!setinsetup) && (SoundQueueCtr <= 0));      // repeat while still time and no interrupt and no sound waiting
    if (SoundQueueCtr > 0){DisplayIndex = DisplayTime-1;}                       // if leaving due to chime, go directly to time
} // End of CommonDelay


int ReadChimeSetting() {
  int returnvalue = silent;                       // Assume 'silent' (Center position)
  if (digitalRead(DoWestminster) == LOW) {
    returnvalue = westminster;
  } else if (digitalRead(DoHoursOnly) == LOW) {
    returnvalue = hoursonly;
  }
  return returnvalue;
}

// Routine to deal with combinations of chime type, time of day and the current hour
// called parameters are chime type (e.g., Westminster), Strike type (hour, qtr, half, 3/4) and the hour in military time
// Note for delays and print cycles: You need to call this at least once during the 'golden' minute (0,15,30,45)

void CheckForChime() {                           // Logic for chiming

  setstrike = -1;                            // initialize 'strike' flag to "no strike"

  if (ChimeValue != silent)    // if silenced due to setting silent, skip the whole thing
  {
    if (setarray[setminute] == 0) {         // Look for 'on the hour'
      setstrike = cqtr0;
    } else if (setarray[setminute] == 15 ) {   // Look for on the quarter hour
      setstrike = cqtr1;
    } else if (setarray[setminute] == 30 ) { // Look for on the 1/2 hour
      setstrike = cqtr2;
    }
    else if (setarray[setminute] == 45 ) { // Look for on the three-quarter hour
      setstrike = cqtr3;
    }
    else {
      alreadychimed = false; // none of the above, reset ability to chime
    }

    if (setstrike > 0 && !alreadychimed ) {
      chime(ChimeValue, setstrike, setarray[sethour]);  // call chiming with 'type of chime'; 0,15,30,45 ; and # hours
      DisplayIndex = DisplayTime;                                     // force display of time while chiming
      alreadychimed = true;                                     // we will be here multiple times but only chime once
 }
 }             // end of logic for chiming
}  // end of CheckForChime

void chime (int chimetype, int strikeflag, int chour) {

  int chour1;                                                                     // am pm variable\

  if (chour <= 12) {
    chour1 = chour; // convert military time to am pm
  }  else {
    chour1 = chour - 12;
  }


  if (chour1 <= 0) {
    chour1 = 12; // don't chime 0 for midnite, chime 12
  }
  if (chimetype == hoursonly && strikeflag == cqtr2) {
    QueueSound(SoundTrailingBell); //  1/2 hour only, do single 'ding'
  }
  else if ( chimetype == hoursonly && strikeflag == cqtr0) {
    for (int i = 1; i < chour1; i++) {
      QueueSound(SoundTruncatedBell); // Ding once less than hours
    }
    QueueSound(SoundTrailingBell);
  }                                               //  and follow by trailing bell as last 'ding' (if used)
  else   if (((chimetype == westminster) )  && strikeflag == cqtr1) {
    QueueSound(SoundQ1); // First Quarter
  }
  else   if (((chimetype == westminster) ) && strikeflag == cqtr2) {
    QueueSound(SoundQ2); // Second Quarter
  }
  else   if (((chimetype == westminster)) && strikeflag == cqtr3) {
    QueueSound(SoundQ3); // Third Quarter
  }
  else   if (((chimetype == westminster)) && strikeflag == cqtr0) {
    QueueSound(SoundQ4);
    for (int i = 1; i < chour1; i++) {
      QueueSound(SoundTruncatedSingle); // Chime once less than hours
    }
    QueueSound(SoundTrailingSingle);
  }                   // Chime Westminster final hours


}     // end of Chime


// Routine to check for 'real' (static) events and floating events (e.g., Memorial Day, Thanksgiving, Mother's Day and Fathers Day)

/* Memorial Day is Last Monday in May
    Thanksgiving is 4th Thursday in November
    Mother's Day is 2nd Sunday in May
    Father's Day is 3rd Sunday in June
    MLK Day is 3rd Monday in February
    Memorial Day is last Monday in May
    Labor Day is first Monday in September
    Daylight Savings Times starts 2nd Sunday in March
    Daylight Savings Times ends first Sunday in November
    Indigenous People's Day is second Monday in October (also its Columbus Day)

*/

void CheckForEvent(int m, int d, int dow) {                          // called with Month, Day within month and day of the week (0 = Sunday)

  event = false;                                                      // assume neither static or floating
  eventindex = -1;                                                      // initial index to eventstrings

  //  Static event check
                                                                           
      for (int i = 0; i < eventnumber; i++) {                                               // then check the static events
        if ((setarray[setmonth] == eventmonth[i]) && (setarray[setday] == eventday[i]))
        { event = true;                                                // set if match on month and day
          eventindex++;                                                 // found one!
          eventstrings[eventindex] = eventdescription[i];                 // store pointer to static event description
        }
      }

  //  Floating event check

  floatstring = "none";                                                           // initialize string 
  
  if ((dow == thursday) && (m == november) && (d >= 22) && (d <= 28)) {                         // Thanksgiving
    floatstring = "Thanksgiving";
  }
  else if ((dow == sunday) && (m == may) && (d >= 8) && (d <= 14)) {                    // Mother's Day
    floatstring = "Mother's Day";
  }
  else if ((dow == sunday) && (m == june) && (d >= 15) && (d <= 21)) {                 //Father's Day
    floatstring = "Father's Day";
  }
  else if ((dow == monday) && (m == january) && (d >= 15) && (d <= 21)) {               //MLK Day
    floatstring = "Martin Luther King Day";
  }
  else if ((dow == monday) && (m == february) && (d >= 15) && (d <= 21)) {             //President's Day
    floatstring = "President's Day!";
  }
  else if ((dow == monday) && (m == september) && (d <= 7) ) {                        //Labor Day
    floatstring = "Labor Day";
  }
  else if ((dow == monday) && (m == october) && (d >= 8) && (d <= 14) ) {                        //Indigenous People's Day
    floatstring = "Indigenous People's Day";
  }
  else if ((dow == monday) && (m == may) && (d + 7 > 31) ) {                        //Memorial Day
    floatstring = "Memorial Day";
  }
  else if ((dow == sunday) && (m == march) && (d >= 8) && (d <= 14)) {              // DST begins
    floatstring = "DST Begins";
  }
  else if ((dow == sunday) && (m == november) && (d <= 7) ) {                       //DST ends
    floatstring = "DST Ends";
  }

  if (floatstring != "none" ) {                                             // did we load anything?
     event = true;                                                          // if so, then we have one
     eventindex++;                                                          // so load it into event display queue
     eventstrings[eventindex] = floatstring.c_str();                      // store pointer to static event description
        }

}  // end of floatingevent

void LEDBrightness() {
  int LEDvalue;
  LEDvalue  = analogRead(LEDpin);                                       // Read LDR value (may need to play with values)
  DDTs("LED value",LEDvalue);
  LEDvalue = map(LEDvalue, 0, 1023, 0, MAX_INTENSITY);              //map to valid value for brightness  (max intensity is 15 btw)
  if (LEDvalue <= 11) {
    mx.control(MD_MAX72XX::INTENSITY, minbright);}
    else if (LEDvalue > 13 ) {
    mx.control(MD_MAX72XX::INTENSITY, maxbright); }
    else
  {mx.control(MD_MAX72XX::INTENSITY, midbright); }
  DDTl("Mapped LED value", LEDvalue);

  

}  //end of LEDbrightness

void Scrollup() {                                                                            // used to 'wipe' previous display
  for (uint8_t i = 0; i < 8; i++) {
    mx.transform(MD_MAX72XX::TSU); delay(2 * DELAYTIME);
    delay(DELAYTIME);
  }
} // End of Scrollup


void scrollText(char *p)                                                                      // copied from library
{
  uint8_t charWidth;
  uint8_t cBuf[8];  // this should be ok for all built-in fonts
  mx.clear();
  while (*p != '\0')
  {
    charWidth = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);

    for (uint8_t i = 0; i <= charWidth; i++) // allow space between characters
    {mx.transform(MD_MAX72XX::TSL);
      if (i < charWidth)
        mx.setColumn(0, cBuf[i]);
      delay(DELAYTIME);}
  }
}  // End of Scroll Text

void printText(char *pMsg, int LOS, bool CenterJustify)                                   // copied and modified from library
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
// And center justified if third argument is "true"
{
  uint8_t modStart = 0;
  uint8_t modEnd = MAX_DEVICES - 1;
  uint8_t   state = 0;
  uint8_t   curLen;
  uint16_t  showLen;
  uint8_t   cBuf[8];
  int16_t   col = ((modEnd + 1) * COL_SIZE) - 1;
  int pixelcount = 0;
  int ccounter = LOS;

  mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  do     // finite state machine to print the characters in the space available
  {   switch (state)
    {
      case 0: // Load the next character from the font table
        // if we reached end of message, reset the message pointer
        if (*pMsg == '\0')
        {
          showLen = col - (modEnd * COL_SIZE);  // padding characters
          state = 2;
          break;
        }

        // retrieve the next character form the font file

        showLen = mx.getChar(*pMsg++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
        if (ccounter > 0) {
          pixelcount = (pixelcount + showLen) + CHAR_SPACING;
          ccounter--;
        }
        curLen = 0;
        state++;
      // !! deliberately fall through to next state to start displaying

      case 1: // display the next part of the character
        mx.setColumn(col--, cBuf[curLen++]);

        // done with font character, now display the space between chars
        if (curLen == showLen)
        {
          showLen = CHAR_SPACING;
          state = 2;
        }
        break;

      case 2: // initialize state for displaying empty columns

        curLen = 0;

        state++;
      // fall through

      case 3:  // display inter-character spacing or end of message padding (blank columns)
        mx.setColumn(col--, 0);
        curLen++;
        if (curLen == showLen)
          state = 0;
        break;

      default:
        col = -1;   // this definitely ends the do loop
    }
  } while (col >= (modStart * COL_SIZE));

  if (CenterJustify) {
    for (int i = 1; i <= (((MAX_DEVICES * COL_SIZE) - pixelcount) / 2); i++) {
      mx.transform( MD_MAX72XX::TSR);
    }
  }
  mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void printCenter(String StringtoPrint)                                    // Loads the print buffer and centers it
{
  int LOS = StringtoPrint.length();                                          // Get the string's length
  for (int i = 0; i < BufferStringLength; i++) {
    BufferString[i] = ' '; // clear buffer
  }
  for (int i = 0; i < LOS; i++) {
    BufferString[i] = StringtoPrint[i];        // transfer
    printText(BufferString, LOS, true);                                       // Print, providing length of string and "Yes, center"

  }
}  // End of Center and Load

void ShowValue(int myindex, String setdescription)                                    // Display while in setup mode
{                                                                                   // shows what item is setting and its value

  for (int i = 0; i < BufferStringLength; i++) {BufferString[i] = ' ';}            // clear buffer
  setdescription.toCharArray(BufferString, setdesclength);                                       // move description to output buffer
  LoadNumber(setarray[setupindex]);
  BufferString[setdesclength-1] = ' ';
  for (int i = 0; i <setdesclength; i++) {BufferString[i + setdesclength] = TDigits[i];}
  if (myindex != setyear) {                                                           // only the year is 4 digits, else 2
    BufferString[4] = ' ';                                                            
    BufferString[5] = ' ';
    if (BufferString[6] == '0') {BufferString[6] = ' ';}                             // and suppress leading 0 for 2 digit numbers too
  }
  BufferString[8] = '\0';                                                          // and terminate
  printText(BufferString, 7, false);


}  // End of Show Value

void LoadNumber(int numtoload) {                                            // Converts number to four digit Ascii array
  int tempnum = numtoload;
  for (int i = 3; i >= 0; i--) {
    TDigits[i] = (tempnum % 10) + DecToAscii;;
    tempnum = tempnum / 10;
  }
}  // end of LoadNumber

void QueueSound(int SoundFile) {                                          // place a sound to be played into sequence

  if ((SoundQueuePtr == SizeQueue) || (SoundQueueCtr <= 0)) {
    SoundQueuePtr = -1; // Dont overflow the queue
  }
  SoundQueuePtr++;                                                 // Increment pointer
  SoundQueue[SoundQueuePtr] = SoundFile;                         // and store the sound chosen
  SoundQueueCtr++;                                                // and increment # sounds in queue
}     // end of Queue Sound File

void PlaySound() {                                                // Plays appropriate Sound file
  if (SoundQueueCtr > 0) {                                   // if nothing in the queue, just return
    if (digitalRead(SoundBusyPin) == LOW) {                    //or if sound card busy, just return
      return;
    }                                                      //until idle
    if (SoundPlayPtr == SizeQueue) {
      SoundPlayPtr = -1; // Dont go past the queue
    }
    SoundPlayPtr++;                                                 // Increment pointer
    digitalWrite(FirstSoundPin + SoundQueue[SoundPlayPtr], LOW);            //Play the sound
    delay(fxset);                                                   // hold to start the sound
    digitalWrite(FirstSoundPin + SoundQueue[SoundPlayPtr], HIGH);            //Turn off the sound
    delay(fxset);                                                     // Ensure its set
    SoundQueueCtr--;                                                  // and decrement sounds yet to be played
    if (SoundQueueCtr <= 0) {
      SoundPlayPtr = -1;
    };                       // reset play pointer when queue is empty
  }

}     // end of PlaySound
/*
   void PlaySoundUART()                                                   // uncomment for FX sound board UART use

   //   sfx.playTrack(name);  }
      {
      Serial.print("\nPlaying track \""); Serial.print(name); Serial.print("\"");
      if (! sfx.playTrack(name) ) {
        Serial.println("Failed to play track?");
      }
   }
*/


#ifdef showoffcode

void ShowOff() {                                             // Played at startup -- just the MX Panel Test Graphics
  QueueSound(SoundShowOff);                                 // play Game of Thones theme as sound 
  PlaySound();
  scrollText("Showing Off");
  rows();
  columns();
  cross();
  checkboard();
  bullseye();
  bounce();
  stripe();
  stripe();
  transformation1();
  transformation2();
  bullseye();
  spiral();
  delay(2000);
  DisplayIndex = DisplayTime;                                     // force display of time after showing off display
}                                                           // end of Show Off

void rows()                                                   // these routines are all
// Demonstrates the use of setRow()                               // from the MX library
{mx.clear();
 for (uint8_t row = 0; row < ROW_SIZE; row++)
  {
    mx.setRow(row, 0xff);
    delay(2 * DELAYTIME);
    mx.setRow(row, 0x00);
  }
}

void checkboard()
// nested rectangles spanning the entire display
{
  uint8_t chkCols[][2] = { { 0x55, 0xaa }, { 0x33, 0xcc }, { 0x0f, 0xf0 }, { 0xff, 0x00 } };
  mx.clear();
  for (uint8_t pattern = 0; pattern < sizeof(chkCols) / sizeof(chkCols[0]); pattern++)
  {
    uint8_t col = 0;
    uint8_t idx = 0;
    uint8_t rep = 1 << pattern;

    while (col < mx.getColumnCount())
    {
      for (uint8_t r = 0; r < rep; r++)
        mx.setColumn(col++, chkCols[pattern][idx]);   // use odd/even column masks
      idx++;
      if (idx > 1) idx = 0;
    }
    delay(10 * DELAYTIME);
  }
}                 // end of Checkboard

void columns()
// Demonstrates the use of setColumn()
{

  mx.clear();

  for (uint8_t col = 0; col < mx.getColumnCount(); col++)
  {
    mx.setColumn(col, 0xff);
    delay(DELAYTIME / MAX_DEVICES);
    mx.setColumn(col, 0x00);
  }
}

void cross()
// Combination of setRow() and setColumn() with user controlled
// display updates to ensure concurrent changes.
{

  mx.clear();
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  // diagonally down the display R to L
  for (uint8_t i = 0; i < ROW_SIZE; i++)
  {
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0xff);
      mx.setRow(j, i, 0xff);
    }
    mx.update();
    delay(DELAYTIME);
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0x00);
      mx.setRow(j, i, 0x00);
    }
  }

  // moving up the display on the R
  for (int8_t i = ROW_SIZE - 1; i >= 0; i--)
  {
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0xff);
      mx.setRow(j, ROW_SIZE - 1, 0xff);
    }
    mx.update();
    delay(DELAYTIME);
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0x00);
      mx.setRow(j, ROW_SIZE - 1, 0x00);
    }
  }

  // diagonally up the display L to R
  for (uint8_t i = 0; i < ROW_SIZE; i++)
  {
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0xff);
      mx.setRow(j, ROW_SIZE - 1 - i, 0xff);
    }
    mx.update();
    delay(DELAYTIME);
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0x00);
      mx.setRow(j, ROW_SIZE - 1 - i, 0x00);
    }
  }
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

void bullseye()
// Demonstrate the use of buffer based repeated patterns
// across all devices.
{

  mx.clear();
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  for (uint8_t n = 0; n < 3; n++)
  {
    byte  b = 0xff;
    int   i = 0;

    while (b != 0x00)
    {
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, b);
        mx.setColumn(j, i, b);
        mx.setRow(j, ROW_SIZE - 1 - i, b);
        mx.setColumn(j, COL_SIZE - 1 - i, b);
      }
      mx.update();
      delay(3 * DELAYTIME);
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, 0);
        mx.setColumn(j, i, 0);
        mx.setRow(j, ROW_SIZE - 1 - i, 0);
        mx.setColumn(j, COL_SIZE - 1 - i, 0);
      }

      bitClear(b, i);
      bitClear(b, 7 - i);
      i++;
    }

    while (b != 0xff)
    {
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, b);
        mx.setColumn(j, i, b);
        mx.setRow(j, ROW_SIZE - 1 - i, b);
        mx.setColumn(j, COL_SIZE - 1 - i, b);
      }
      mx.update();
      delay(3 * DELAYTIME);
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, 0);
        mx.setColumn(j, i, 0);
        mx.setRow(j, ROW_SIZE - 1 - i, 0);
        mx.setColumn(j, COL_SIZE - 1 - i, 0);
      }

      i--;
      bitSet(b, i);
      bitSet(b, 7 - i);
    }
  }

  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

void spiral()
// setPoint() used to draw a spiral across the whole display
{

  int  rmin = 0, rmax = ROW_SIZE - 1;
  int  cmin = 0, cmax = (COL_SIZE * MAX_DEVICES) - 1;

  mx.clear();
  while ((rmax > rmin) && (cmax > cmin))
  {
    // do row
    for (int i = cmin; i <= cmax; i++)
    {
      mx.setPoint(rmin, i, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    rmin++;

    // do column
    for (uint8_t i = rmin; i <= rmax; i++)
    {
      mx.setPoint(i, cmax, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    cmax--;

    // do row
    for (int i = cmax; i >= cmin; i--)
    {
      mx.setPoint(rmax, i, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    rmax--;

    // do column
    for (uint8_t i = rmax; i >= rmin; i--)
    {
      mx.setPoint(i, cmin, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    cmin++;
  }
}

void bounce()
// Animation of a bouncing ball
{
  const int minC = 0;
  const int maxC = mx.getColumnCount() - 1;
  const int minR = 0;
  const int maxR = ROW_SIZE - 1;

  int  nCounter = 0;

  int  r = 0, c = 2;
  int8_t dR = 1, dC = 1;  // delta row and column


  mx.clear();

  while (nCounter++ < 200)
  {
    mx.setPoint(r, c, false);
    r += dR;
    c += dC;
    mx.setPoint(r, c, true);
    delay(DELAYTIME / 2);

    if ((r == minR) || (r == maxR))
      dR = -dR;
    if ((c == minC) || (c == maxC))
      dC = -dC;
  }
}
void stripe()
// Demonstrates animation of a diagonal stripe moving across the display
// with points plotted outside the display region ignored.
{
  const uint16_t maxCol = MAX_DEVICES*ROW_SIZE;
  const uint8_t  stripeWidth = 10;

  mx.clear();

  for (uint16_t col=0; col<maxCol + ROW_SIZE + stripeWidth; col++)
  {
    for (uint8_t row=0; row < ROW_SIZE; row++)
    {
      mx.setPoint(row, col-row, true);
      mx.setPoint(row, col-row - stripeWidth, false);
    }
    delay(DELAYTIME);
  }
}

void transformation1()
// Demonstrates the use of transform() to move bitmaps on the display
// In this case a user defined bitmap is created and animated.
{
  uint8_t arrow[COL_SIZE] =
  {
    0b00001000,
    0b00011100,
    0b00111110,
    0b01111111,
    0b00011100,
    0b00011100,
    0b00111110,
    0b00000000
  };

  MD_MAX72XX::transformType_t  t[] =
  {
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TFLR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TRC,
    MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD,
    MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD,
    MD_MAX72XX::TFUD,
    MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU,
    MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU,
    MD_MAX72XX::TINV,
    MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC,
    MD_MAX72XX::TINV
  };

  // transformation 
  
  mx.clear();

  // use the arrow bitmap
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
  for (uint8_t j=0; j<mx.getDeviceCount(); j++)
    mx.setBuffer(((j+1)*COL_SIZE)-1, COL_SIZE, arrow);
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
  delay(DELAYTIME);

  // run through the transformations
  mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::ON);
  for (uint8_t i=0; i<(sizeof(t)/sizeof(t[0])); i++)
  {
    mx.transform(t[i]);
    delay(DELAYTIME*4);
  }
  mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::OFF);
}

void transformation2()
// Demonstrates the use of transform() to move bitmaps on the display
// In this case font characters are loaded into the display for animation.
{
  MD_MAX72XX::transformType_t  t[] =
  {
    MD_MAX72XX::TINV,
    MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC,
    MD_MAX72XX::TINV,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSD, MD_MAX72XX::TSU, MD_MAX72XX::TSD, MD_MAX72XX::TSU,
    MD_MAX72XX::TFLR, MD_MAX72XX::TFLR, MD_MAX72XX::TFUD, MD_MAX72XX::TFUD
  };}
#endif //showoffcode// 

Updated code for clock

Arduino
Added some more events
/*
   Clock project using two MX7219 matrix displays and the AdaFruit FX Soundboard
   Provides date, time, temp, humidity and event notifications in rotating order
   Also can chime in multiple ways, including Westminster chimes
   Chimes are provided by Adafruit FX Soundboard in GPIO mode
   There are some conditional options:
      Davids_Clock changes some of the events and notifications 
      Extra_Display uses a small Adafruit 1306 OLED to continuously show date and time while the main display
      rolls through its iterations
      DDT includes a couple of small debugging (get it?) routines and opens serial terminal

*/




// Code to set up the clock
// Pushbutton 1 is the toggler , cycle between  year, month, day, hours, minutes, seconds
// Pushbutton 2 is the increment up one
// Pushbutton 3 is the decrement down one

// Chime setup for toggle switch
//  Center = No chimes
//  Up = Westminster hours + quarterly
//  Down = Simple Bell (hours and 1/2 hour)

//      Comment out next line to leave out the little show 

#define showoffcode                                       // include "show off" graphics and music for Easter Egg 

//#define Davids_Clock                                      // if this is david's clock slightly different announcements

#define Extra_Display                                     // Enables constant date and time in OLED display so never have to wait

#define DDT                                               // simple debug tools (print string, print string and value, same with NL)


// Clock "ID's" for temperature adjustment 
// Define only one of these!

//#define Clock_1                                     // First wine box clock (mine)
//#define Clock_2                                     // 2nd wine box clock (For David)
#define Clock_3                                     // Acrylic long matrix clock (Mercer Island)


// include the libraries for LCD Display, DHT-11 Temperature/humidity sensor and DS3231 RTC and
// include libraries for MX7219 mult-unit matrix display
#include "DHT.h"
#include <Wire.h>
#include "RTClib.h"
#include <MD_MAX72xx.h>

// uncomment next two lines to use UART for SoundBoard rather than GPIO
//#include <Adafruit_Soundboard.h>
//#include <SoftwareSerial.h>

#ifdef Extra_Display
#include <Adafruit_GFX.h>                                   // for OLED need graphics and
#include <Adafruit_SSD1306.h>                               // the specific driver
#endif


// Set up output functions

#define DHTPIN 9     //Temp Humidity sensor on pin 9
#define DHTTYPE DHT11   // DHT 11 because the sensor is that type

// Initialize DHT sensor for normal 16mhz Arduino
DHT dht(DHTPIN, DHTTYPE);

// Initialize for Adafruit DS3231 RTC real time clock
RTC_DS3231 rtc;

#ifdef Extra_Display                                          // used to include a small OLED date/time display
// Initialize the Small OLED display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
//#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#endif  Extra_Display


// uncomment next set of  lines to use UART for SoundBoard
/*
  // Choose any two pins that can be used with SoftwareSerial to RX & TX for UART SoundBoard
  //#define SFX_TX 5
  //#define SFX_RX 6
  // Connect to the RST pin on the Sound Board
  #define SFX_RST 4
  // You can also monitor the ACT pin for when audio is playing!
  // we'll be using software serial
  //SoftwareSerial ss = SoftwareSerial(SFX_TX, SFX_RX);
  // pass the software serial to Adafruit_soundboard, the second
  // argument is the debug port (not used really) and the third
  // arg is the reset pin
  //Adafruit_Soundboard sfx = Adafruit_Soundboard(&ss, NULL, SFX_RST);
  // can also try hardware serial with
  // Adafruit_Soundboard sfx = Adafruit_Soundboard(&Serial1, NULL, SFX_RST);
*/

// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW                             // NOTE: This parameter differs by vendor
#define MAX_DEVICES 8                                                   // 2 x 4 matrices per unit

//Pins for Arduino Uno
//#define CLK_PIN   13  // or SCK  (this is for the UNO, different for the Mega
//#define DATA_PIN  11  // or MOSI
//#define CS_PIN   10  // or SS


//Pins for Arduino Mega
#define CLK_PIN  52  // or SCK  13
#define DATA_PIN  51  // or MOSI   11
#define CS_PIN   53  // or SS 10


// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// We always wait a bit between updates of the display
#define  DELAYTIME  100  // in milliseconds

// Text parameters
#define CHAR_SPACING  1 // pixels between characters
//const int CHAR_PIXELS = 6;            // Pixels taken by each character (including trailing space)
//const int MAX_CHARS = (MAX_DEVICES*COL_SIZE)/ CHAR_PIXELS;   // Maximum characters at once (happens to be 10 btw)
//const int LEFT_OVER = (MAX_DEVICES*COL_SIZE)- (CHAR_PIXELS*MAX_CHARS);      //Since its not 'even' additional pad from front on printText
const int BufferStringLength = 31;
char BufferString [BufferStringLength];                 //used to construct printouts
#define DecToAscii  48                  // convert number to ascii (add as a constant)
char DegreeSign = '~';               // for display of 'degrees' after temperature
int TDigits[4] = {0, 0, 0, 0};          // used to convert four digits to four chacacters
#define TDOnes  3                       // positions in arrays and templates
#define TDTens  2
#define TDHundreds 1
#define TDThousands 0
#define sunday  0                         // used for events, floating events and holidays
#define monday  1                         // just for readability
#define tuesday 2
#define wednesday 3
#define thursday  4
#define friday 5
#define saturday 6
#define january 1
#define february 2
#define march 3
#define april 4
#define may 5
#define june 6
#define july 7
#define august 8
#define september 9
#define october 10
#define november 11
#define december 12

//Template Strings and pointers

const int LOTS = 13;                    // Length of TimeString (without terminator)
char TimeString[LOTS + 1 ] = {' ', ' ', '1', '2', ':', '3', '1', ':', '4', '5', ' ', 'P', 'M', '\0'};
char DateString[ ] = {'1', '2', '/', '3', '1', '/', '2', '0', '1', '9', '\0'};
char TempString[ ] = {'T', 'e', 'm', 'p', ' ', '7', '2', '.','0', DegreeSign,'F', '\0'};
char HumidString[ ] = {'H', 'u', 'm', ' ', '2', '5', '.', '0', '%', '\0'};
const int THptr = 2;                                                // Pointers within the strings
const int TMptr = 5;                                                // for loading values
const int TSptr = 8;
const int TAMPMptr = 11;
const int DMptr = 0;
const int DDptr = 3;
const int DYptr = 6;
const int TEMPptr = 5;
const int HUMIDptr = 4;

// Differentiated Displays

const int DisplayTime = 0;
const int DisplayDate = 2;
const int DisplayDOW  = 1;
const int DisplayTemp = 3;
const int DisplayHumid = 4;
const int DisplayEvent = 5;
const int DisplayEventRepeat = 2;                         //times to repeat the scrolling 'event' display
const int DisplaySize = 6;                                // Nunber of different displays
int DisplayIndex = DisplayTime;                                    // display we are on at the time
const int DisplayDelayArray[DisplaySize] = {100, 50, 30, 30, 30, 20}; // multiplier for each event
const int DisplayDelay = 100;                              // milliseconds to leave each display * its multiplier
unsigned long DisplayTimer;                               // used to time each matrix display



char daysOfTheWeek[7][12 ] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};


 #ifndef Davids_Clock 

const int eventnumber = 50;         // number of events and remember: No strings longer than 30 characters!

int eventmonth [eventnumber] = {june, april, november, february, july, december, november, june, december, june, december, january,
                                september, october, march, february, february, july,december, february, december, january, january, february,
                                march,april,april,april,april,may,may,may,may,june,june,june,july,july,october, september, may, april,
                                march,february,march,october,march,november,august,october
                };
int eventday [eventnumber] = {11, 1, 23, 12, 4, 3, 12, 29, 25, 16, 31, 1, 3, 31, 17, 14, 2, 14, 30,12,21,5,18,18,6,7,12,15,24,1,5,20,30,14,14,21,
                              22,24,29,28,18,26,14,3,27,12,30,5,5,4};
                              
char* eventdescription [eventnumber]  =
{ "Dana's Anniversary", "April Fool's Day", "Joan's Birthday", "Sydney's Birthday", "July 4th!", "Dana's Birthday",
  "David's Birthday", "Our Anniversary", "Merry Christmas", "Leah's Birthday", "New Year's Eve", "New Year's Day",
  "Forrest's Birthday", "Happy Halloween", "Saint Patrick's Day", "Valentine's Day", "Groundhog Day", "Bastille Day", "Linsey's Birthday",
  "Irwin's Birthday", "Winter Solstice", "National Bird Day", "National Winnie the Pooh Day", "National Drink Wine Day","National Oreo Day",
  "National Beer Day","Grilled Cheese Day", "Tax Day","National DNA Day", "May Day", "Cinco de Mayo", "World Bee Day", "World MS Day",
  "National Bourbon Day", "Flag Day", "Summer Solstice","National Hot Dog Day", "National Tequila Day","National Cat Day", "National Drink Beer Day",
  "National Whisky Day", "Nat'l Audubon Day", "National Pi Day", "Nat'l Women Physicians Day", "National Paella Day", "National Gumbo Day",
  "National Doctors Day", "National Doughnut Day", "National Oyster Day", "National Taco Day"
};
#endif
#ifdef Davids_Clock
const int eventnumber = 43;         // number of events and remember: No strings longer than 30 characters!

int eventmonth [eventnumber] = {june, april, november, february, july, december, november, june, december, june, december, january,
                                september, october, march, february, february, july,december, february, december, 
                                march,april,april,april,may,may,may,june,june,june,july,july,september,
                                march,february,march,october,march,november,october,august
                };
int eventday [eventnumber] = {11, 1, 23, 12, 4, 3, 12, 29, 25, 16, 31, 1, 3, 31, 17, 14, 2, 14, 30,12,21,18,6,7,12,15,1,5,20,14,14,21,
                              22,24,28,14,3,27,12,30,5,4,4};
                              
char* eventdescription [eventnumber]  =
{ "Dana's Anniversary", "April Fool's Day", "Mom's Birthday", "Sydney's Birthday", "July 4th!", "Dana's Birthday",
  "My Birthday", "Mom & Dad's Anniversary", "Merry Christmas", "Leah's Birthday", "New Year's Eve", "New Year's Day",
  "Forrest's Birthday", "Happy Halloween", "Saint Patrick's Day", "Valentine's Day", "Groundhog Day", "Bastille Day", "Linsey's Birthday",
  "Dad's Birthday", "Winter Solstice", "National Drink Wine Day","National Oreo Day",
  "National Beer Day","Grilled Cheese Day", "Tax Day", "May Day", "Cinco de Mayo", "World Bee Day", 
  "National Bourbon Day", "Flag Day", "Summer Solstice","National Hot Dog Day", "National Tequila Day","National Drink Beer Day",
  "National Pi Day", "Nat'l Women Physicians Day", "National Paella Day", "National Gumbo Day", "National Doctors Day", "National Doughnut Day",
  "National Taco Day", "Nat'l Champagne Day"
};
#endif


bool event;                     // logic flag "on" = event found
int eventindex;                 // and the event we found
const int maxevents = 3;          // maximum of three events (real and floating combined)
char* eventstrings[maxevents] ;    // used to store displayed events
String floatstring = "                              "; // used for floating events (e.g., Thanksgiving)

#define pb1pin 2              //  Pin assignments for reset, increment and decrement
#define pb2pin  3
#define pb3pin  4

#define showoffbuttonpin 7                           // invoke showing off the matrix display

#define DoWestminster 5
#define DoHoursOnly 6
#define silent  0                                 // no chiming at all
#define hoursonly  1                              // no prelude, just chime number of hours and half hour single chime
#define westminster 2                            // hours + Westminster prelude per quarter
int ChimeValue = silent;                         // default to silent


const int LEDpin = A0;                          // Pin assignment for analog reading LDR for LED brightness
const int minbright = 0;              // MAX_BRIGHT for this module is 15
const int midbright = 2;              // but these values "seem" to work
const int maxbright = 4;




int setupindex = 0;                 // first thing to set if required
bool insetupmode = false;           // and assume RTC is set, OK, etc., and no 'set' required
bool setinsetup = false;            // flag set to true at interrupt level to go back into setup mode

// date time array for setting, reading, displaying

#define setsize   6            // size of the setting array
#define setyear   0            // index name for each element
#define setmonth   1
#define setday   2
#define sethour   3
#define setminute   4
#define setsecond   5

int setarray [setsize] = {2019, 1, 1, 1, 1, 0};  // set year, month, day,  hour, minutes, seconds
int lowlimit [setsize] = {2019, 1, 1, 0, 1, 0}; // lower limit for each
int highlimit [setsize] = {2080, 12, 31, 23, 59, 59};  //high limit for each
const int setdesclength = 4;                            // maximum length of 'set' descriptor
char setdesc [setsize] [setdesclength] = {"Yr ", "Mon", "Day", "Hr ", "Min", "Sec"};


#define cqtr0   1        //  Chime the hour
#define cqtr1   2        //  Chime the quarter hour
#define cqtr2   3        //  Chime the half hour
#define cqtr3   4         // Chime the 3/4 hour

// Definitions for the Sound Board
#define SoundTruncatedSingle   0                  // Truncated Single Westminster
#define SoundQ1   1                                 // First Quarter
#define SoundQ2   2                               // Second Quarter
#define SoundQ3   3                               //  Third
#define SoundQ4   4                             //   Fourth (before hour chime)
#define SoundTrailingSingle  5                    //  Finish Westminster string of chimes with longer sound tail
#define SoundTruncatedBell   6                         // Simple Bell
#define SoundTrailingBell  7                      // Final Bell (like single, has longer 'tail')
#define SoundStartup   8                        // startup sound
#define SoundShowOff 9                            // Play during show off of matrix
#define FirstSoundPin   30                      // first pin to use for soundboard (0-10 available on soundboard);
#define SizeSoundPin   10                        // number of pins used (must be consecutive)
#define  fxset 150                              // Time soundboard must be held LOW to register (documentation says 125 ms)
#define  SoundBusyPin 8                         // Soundboard pin that goes LOW when board is active

const int SizeQueue = 15;                          // Assume Q4+12 bells is the maximum that'll be in the queue at any point
int SoundQueue[SizeQueue];                       // Circular Queue for waiting sounds
int SoundQueuePtr = -1;                           //pointer for Queue fill Position
int SoundPlayPtr = -1;                           //pointer for Queue play Position
int SoundQueueCtr = 0;                            // number of items in the queue

int  setstrike = -1;                          // Chime/strike flag
byte  alreadychimed = false ;                 // Used to keep from chiming multiple times during the "hot" second
const int BounceDelay = 250;                // Not really 'bounce', its a change of state detection

int i;                          // generic index variable

int dayoftheweek;               // stored day of the week (0-6, 0 = Sunday)

int phours;                     // for print conversion of military time

float temperaturef;             // farenheit temperature back
int temperature;                  // integer version of temperature for matrix
float temperaturec;             // centrigade temperature back (not used)

float humidityf;                 // and humidity
int humidity  ;                 // and same as temp

// The DHT sensors are cheap and inaccurate so these are 'custom' adjustments

#ifdef Clock_1                // first wine box clock
float tempadjust = -2.0;      // temperature adjustment for sensor (I found it didn't read right against 'comps')
float humidadjust = +8.0;      // corresponding humidity adjustment
#endif

#ifdef Clock_2                  // second wine box clock for David
float tempadjust = -0.6;      // temperature adjustment for sensor
float humidadjust = +8.0;      
#endif

#ifdef Clock_3                // clear clock
float tempadjust =  0.0; 
float humidadjust = +8.0;             
#endif    


#ifdef DDT                                         // debugging tools
void DDTl(String st, int vt) {                     // Print descriptor and value and new line
  DDTs(st, vt);
  Serial.println(" ");
}
void DDTs(String st, int vt) {                     // Print descriptor and value
  Serial.print("  ");
  Serial.print(st);
  Serial.print("  ");
  Serial.print(vt);
}
void DDTt(String st) {                     // Print descriptor and value and new line
  Serial.println(st);
}
#endif //DDT//


void setup()
// put your setup code here, to run once:

{
  Wire.begin();                     // initialize I2C interface
  dht.begin();                      // initialize the temp/humidity sensor
  mx.begin();                      // and MX7219 display
  #ifdef DDT
  Serial.begin (9600);            //Terminal monitor printing for debugging
  #endif

  // softwareserial at 9600 baud (uncomment this line and next for UART control of sound board
  // ss.begin(9600);

#ifdef OLED_Display
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
    scrollText("Small OLED display startup failed");
    delay(2000);}  else                                  //and wait
        { display.clearDisplay();                         // give a little startup msg         
          display.setTextSize(2);                         // Normal 1:1 pixel scale
          display.setTextColor(SSD1306_WHITE);            // Draw white text
          display.setCursor(0,0);                         // Start at top-left corner
          display.println("Hello");
          display.setTextSize(2);    
          display.println("Starting");
          display.setTextSize(3);             // Draw 2X-scale text
          display.setTextColor(SSD1306_WHITE);
          display.print("  UP!  ");
          display.display();
          delay(1500);
        }
#endif OLED_Display

  pinMode(pb1pin, INPUT_PULLUP);  // The three pushbuttons - reset
  pinMode(pb2pin, INPUT_PULLUP);   // increment
  pinMode(pb3pin, INPUT_PULLUP);   // decrement

  pinMode (DoWestminster, INPUT_PULLUP);            // When pulled 'low' we want Westminster chimes
  pinMode (DoHoursOnly , INPUT_PULLUP);             // When pulled 'low' we want hours and 1/2 bell (neither is 'silent')

  #ifdef showoffcode
      pinMode(showoffbuttonpin, INPUT_PULLUP) ;             // if show off matrix is there, initialize pin
  #endif //showoffcode// 

  for (i = FirstSoundPin; i < FirstSoundPin + SizeSoundPin; i++) {     // pins for sound board
    pinMode(i, OUTPUT);                                               // each an output
    digitalWrite(i, HIGH);                                           // and initialize high (off)
  }    

  // set up interupt for PB1                                               // if PB1 is pushed, it'll pick up on next release from Matrix display
      attachInterrupt(digitalPinToInterrupt(pb1pin), SetToSetup, FALLING);     // and re-enter setup mode (after the end of current display)

    QueueSound(SoundStartup);                        // play boot up sound 
    PlaySound();

  if (! rtc.begin()) {                        // check that clock is there
    scrollText("Couldn't find RTC");       // clock missing is a fatal error
    while (1);
  }

  if (rtc.lostPower()) {           // if power lost force a setup, else load 'current' values and
    scrollText("RTC lost power!");
    delay(1000);
    scrollText("Replace battery?");
    delay(1000);
    scrollText("Setup Mode: ");
    setupindex = setyear - 1;         // setup differently (becuase it will increment)
    insetupmode = true;
    for (i = 0; i < setsize; i++) {
      setarray[i] = lowlimit[i];
    }
      } else                                                      // else reload from RTC
  { DateTime (setarray[setyear], setarray[setmonth], setarray[setday], setarray[sethour], setarray[setminute], setarray[setsecond])  = rtc.now();
    insetupmode = false;                                                        // not in setup mode, and
    setinsetup = false;                                                          //no interrupt
  }

  // Two alternative modes of setting date and time (for debugging, just un-comment):
  // This line sets the RTC to the date & time this sketch was compiled
  //    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  //   rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
        LEDBrightness();                                                          // Do an initial check for room brightness
}      // end of setup

void loop() {                                                             // Main code divided into setup mode and non-setup mode

  while (insetupmode) {                                                 // code for setting time, date set, etc.

    // Read the increment button

    if (digitalRead(pb2pin) == LOW) {
      setarray[setupindex]++;            // read increment of current item
      delay(BounceDelay);
      if (setarray[setupindex] > highlimit[setupindex]) {
        setarray[setupindex] = lowlimit[setupindex];
      }
      ShowValue(setupindex, setdesc[setupindex]);                         // and display description and value
    }

    // Read the decrement Button
    if (digitalRead(pb3pin) == LOW) {
      setarray[setupindex]--;                  // read decrement of value
      delay(BounceDelay);
      if (setarray[setupindex] < lowlimit[setupindex]) {
        setarray[setupindex] = highlimit[setupindex];
      }
      ShowValue(setupindex, setdesc[setupindex]);                              // and display description and value
    }

    // Read for another increment of index

    if (digitalRead(pb1pin) == LOW) {             // Rolling through Chime, Year, Month, Day, Hour, Minutes, Seconds
      setupindex++ ;                            // increment index
      delay(BounceDelay);
      if (setupindex < setsize) {
        ShowValue(setupindex, setdesc[setupindex]); // show description and value if in bounds
      }
      if (setupindex >= setsize) {                               // and finally exiting setup mode after setting chime, date and time
        rtc.adjust(DateTime(setarray[setyear], setarray[setmonth], setarray[setday], setarray[sethour], setarray[setminute], setarray[setsecond]));
        insetupmode = false;
        DisplayIndex = DisplayTime;
      }  //exit setup mode when done
    }

  }  // End of "While" for setup

  // Begin regular loop for date, time, temp humidity and event  display

  while (!insetupmode) {

    GetTempandHumid();                                            // read temperature and humidity from sensor
    
    ChimeValue = ReadChimeSetting();                               // read SPDT Center off switch for silent, westminster or hours only (and 1/2)
    
    GetTheDate();                                                 // load date array from RTC for a chime/bell check
    
    CheckForChime();                                              // Check for a chime event between displays

    CheckForEvent(setarray[setmonth], setarray[setday], dayoftheweek);    // check for static and floating events
              
    MatrixDisplay();                                              // Do whatever display is required on the MX7219

    #ifdef OLED_Display                                              // if the little OLED display is used
     OLED_Update();                                                   // update it
    #endif Extra display

    LEDBrightness();                                                // Good place to check for change in room brightness

    // end of display update logic

    // Read a potential request for an entry into setup from PB 1
    if ((digitalRead(pb1pin) == LOW) || setinsetup) {                // to see if we go back to setup mode
      insetupmode = true;                                             // via a pushbutton or the interrupt
      setinsetup = false;                                              // clear the interrupt flag
      setupindex = 0;                                                 // re-initialize to 'Year' in setup
      DisplayIndex = DisplayTime;                                       // and go back to time display when exit setup
      scrollText("Setup Mode: ");
      delay(2000);
      ShowValue(setupindex, setdesc[setupindex]);                     // show the first setup item (Year)
      delay(BounceDelay);
    }
      #ifdef showoffcode
      if (digitalRead(showoffbuttonpin) == LOW) {ShowOff();}             // check for want to show off matrix
      #endif //showoffcode// 

  } // end of not in setup
}  // end of sketch

void MatrixDisplay() {                                                           // Main display routine for display sequences

  Scrollup();                                                                   // Clear last display
  if (DisplayIndex >= DisplaySize) {
    DisplayIndex = DisplayTime; // reset if at the end
  }
  DisplayTimer = DisplayDelayArray[DisplayIndex] * DisplayDelay;                // set individual display time

  switch (DisplayIndex) {                                                       // and do next display

    case DisplayTime:    // Dislay the time
      do                                                                          // time is different in that there's a constant
      {                                                                           // update of time during display and also play chime, bell etc.
        GetTheDate();                                                               // Get Current Time
        #ifdef OLED_Display                                                        // If the OLED is there,
        OLED_Update();
        #endif OLED_Display                                                          
        CheckForChime();                                                            // Check for a chime event
        LoadNumber(phours);                                                         // Load the 4 digit character array from number
        TimeString[THptr] = TDigits[TDTens];                                        // left digit of the two digit hour
        TimeString[THptr + 1] = TDigits[TDOnes];                                    // rightmost digit
        if (TimeString[THptr] == '0') {
          TimeString[THptr] = ' '; // eliminate leading zero
        }
        LoadNumber(setarray[setminute]);                                             // do same for minutes
        TimeString[TMptr] = TDigits[TDTens];                                          // except no need to do space for zero
        TimeString[TMptr + 1] = TDigits[TDOnes];
        LoadNumber(setarray[setsecond]);                                              // seconds then
        TimeString[TSptr] = TDigits[TDTens];
        TimeString[TSptr + 1] = TDigits[TDOnes];
        if (setarray[sethour] < 12)                                                   // and AM vs PM
        {
          TimeString[TAMPMptr] = 'A';
        }
        else
        {
          TimeString[TAMPMptr] = 'P';
        }
        printText(TimeString, LOTS, false);                                           // Keep position (no centering) because time keeps updating during display and chimes
        PlaySound();                                                                // Play any chimes or bells
        if (SoundQueueCtr == 0) {
          DisplayTimer = DisplayTimer - DisplayDelay;          // if no sound, use display delay logic
          delay(DisplayDelay);
        }
      } while ((DisplayTimer > 0) || (SoundQueueCtr > 0));                         // leave this display when no sound or no delay left
      DisplayIndex++ ;                                                                // then move on to next display item
      break;

    case DisplayDate:                         // Month, Day and Year Display
      LoadNumber(setarray[setmonth]);
      DateString[DMptr] = TDigits[TDTens];
      if (DateString[DMptr] == '0') {
        DateString[DMptr] = ' ';
      }
      DateString[DMptr + 1] = TDigits[TDOnes];
      LoadNumber(setarray[setday]);
      DateString[DDptr] = TDigits[TDTens];
      DateString[DDptr + 1] = TDigits[TDOnes];
      LoadNumber(setarray[setyear]);
      for (int i = 0; i < 4; i++) {
        DateString[DYptr + i] = TDigits[i];
      }
      printCenter(DateString);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayDOW:                                                               //  Just display the day of week string
      printCenter(daysOfTheWeek[dayoftheweek]);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayTemp:                                                          // Temperature display
      LoadNumber(temperature);
      TempString[TEMPptr] = TDigits[TDHundreds];
      TempString[TEMPptr + 1] = TDigits[TDTens];
      TempString[TEMPptr + 3] = TDigits[TDOnes];
      printCenter(TempString);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayHumid:                        // Humidity display
      LoadNumber(humidity);
      HumidString[HUMIDptr] = TDigits[TDHundreds];
      HumidString[HUMIDptr + 1] = TDigits[TDTens];
      HumidString[HUMIDptr + 3] = TDigits[TDOnes];
      printCenter(HumidString);
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    case DisplayEvent:                                          // Event Display
      if (event) {                                                // "Real" events repeat 3 times
          if (eventindex > 0 ) {                                  // for case where there is more than one event 
              while (eventindex >= 0)
                  {scrollText(eventstrings[eventindex]);                        // scroll each of them once
                  if (eventindex > 0) {
                     delay(DisplayDelay * 6);
                     printCenter("and");
                     delay(DisplayDelay * 6);}
                   eventindex--;                                                // and decrement
                   delay(DisplayDelay * 2);
                  }
          }
          else {                                                      // case of only one event -- repeat it for readability
            for (int i = 1; i <= DisplayEventRepeat; i++) {          // scroll either static or floating event 
            scrollText(eventstrings[eventindex]);                     // the index is 0 (greater than zero if multiple events)
          } 
          delay(DisplayDelay * 2);
        }
          }                                                             // end of "if event" logic
            if (!event){                                                // Scroll Default message only once
     #ifndef Davids_Clock       
              if (setarray[sethour] >= 6 && setarray[sethour] <= 11) {
              scrollText("Good Morning ");}
              else if (setarray[sethour] >= 12 && setarray[sethour] <= 16) {
              scrollText("Good Afternoon");} 
              else if (setarray[sethour] >= 17 && setarray[sethour] <= 21) {
              scrollText("Good Evening");} else{scrollText("Good Night"); }
     #endif
     #ifdef Davids_Clock 
              if (setarray[sethour] >= 6 && setarray[sethour] <= 11) {
              scrollText("Good Morning, David");}
              else if (setarray[sethour] >= 12 && setarray[sethour] <= 16) {
              scrollText("Good Afternoon, David");} 
              else if (setarray[sethour] >= 17 && setarray[sethour] <= 21) {
              scrollText("Good Evening, David");} else{scrollText("Good Night, David"); }
      #endif        
              }                                                         // end of "if not event" above                              
      CommonDelay(DisplayTimer);
      DisplayIndex++ ;
      break;

    default:
      scrollText("Should never get here");
      while (1);

  }    //  End of Switch

}  // End of MatrixDisplay

void GetTheDate() {                                                          // Read RTC into array
  DateTime now = rtc.now();
  setarray[setyear] = now.year();
  setarray[setmonth] = now.month();
  setarray[setday] = now.day();
  setarray[sethour] = now.hour();
  setarray[setminute] = now.minute();
  setarray[setsecond] = now.second();
  dayoftheweek = now.dayOfTheWeek();
  if (setarray[sethour] <= 12)              // convert military time to am pm
  {
    phours = setarray[sethour];
  }
  else
  {phours = setarray[sethour] - 12;}
  if (phours <= 0) { phours = 12;  }            // don't print 0 for midnite, print "12"
 
}   // End of Get The Date

void GetTempandHumid(){                         // Temperature and humidity update

    // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
    humidityf = dht.readHumidity()+humidadjust;             // read humidity and adjust for any error

    // Read temperature as Fahrenheit
    temperaturef = (dht.readTemperature(true)) + tempadjust;  // read and correct for inaccuracy in sensor

    // Check if any reads failed and exit early (to try again).
    if (isnan(humidityf) || isnan(temperaturec) || isnan(temperaturef)) {
      temperaturef = 0;              // use 0 as a no-read
      humidityf = 0;                  // error indication
    }
    temperature = temperaturef * 10.0;          // get read for matrix printout
    humidity = humidityf * 10.0;
}   // end of Temperature and Humidity update

void SetToSetup() {                               // interrupt level back to setup
  if ((!insetupmode) && (!setinsetup)) {                              // skip if we are already in Setup mode or have seen the interrupt
    setinsetup = true;                              // Go back into setup at next loop
    DisplayTimer = 0;
  }                                // and zero the timer to speed that up if you can
}  // SetToSetup

void CommonDelay(long int MyDelay) {                          // Common delay routine
  DisplayTimer = MyDelay;
  do {
    DisplayTimer = DisplayTimer - DisplayDelay;
    delay(DisplayDelay);
    GetTheDate();                                                 // load date array from RTC for a chime/bell check
    CheckForChime();                                              // if a chime happens, setup to end delay
  } while ((DisplayTimer > 0) && (!setinsetup) && (SoundQueueCtr <= 0));      // repeat while still time and no interrupt and no sound waiting
    if (SoundQueueCtr > 0){DisplayIndex = DisplayTime-1;}                       // if leaving due to chime, go directly to time
} // End of CommonDelay


int ReadChimeSetting() {
  int returnvalue = silent;                       // Assume 'silent' (Center position)
  if (digitalRead(DoWestminster) == LOW) {
    returnvalue = westminster;
  } else if (digitalRead(DoHoursOnly) == LOW) {
    returnvalue = hoursonly;
  }
  return returnvalue;
}

// Routine to deal with combinations of chime type, time of day and the current hour
// called parameters are chime type (e.g., Westminster), Strike type (hour, qtr, half, 3/4) and the hour in military time
// Note for delays and print cycles: You need to call this at least once during the 'golden' minute (0,15,30,45)

void CheckForChime() {                           // Logic for chiming

  setstrike = -1;                            // initialize 'strike' flag to "no strike"

  if (ChimeValue != silent)    // if silenced due to setting silent, skip the whole thing
  {
    if (setarray[setminute] == 0) {         // Look for 'on the hour'
      setstrike = cqtr0;
    } else if (setarray[setminute] == 15 ) {   // Look for on the quarter hour
      setstrike = cqtr1;
    } else if (setarray[setminute] == 30 ) { // Look for on the 1/2 hour
      setstrike = cqtr2;
    }
    else if (setarray[setminute] == 45 ) { // Look for on the three-quarter hour
      setstrike = cqtr3;
    }
    else {
      alreadychimed = false; // none of the above, reset ability to chime
    }

    if (setstrike > 0 && !alreadychimed ) {
      chime(ChimeValue, setstrike, setarray[sethour]);  // call chiming with 'type of chime'; 0,15,30,45 ; and # hours
      DisplayIndex = DisplayTime;                                     // force display of time while chiming
      alreadychimed = true;                                     // we will be here multiple times but only chime once
 }
 }             // end of logic for chiming
}  // end of CheckForChime

void chime (int chimetype, int strikeflag, int chour) {

  int chour1;                                                                     // am pm variable\

  if (chour <= 12) {
    chour1 = chour; // convert military time to am pm
  }  else {
    chour1 = chour - 12;
  }


  if (chour1 <= 0) {
    chour1 = 12; // don't chime 0 for midnite, chime 12
  }
  if (chimetype == hoursonly && strikeflag == cqtr2) {
    QueueSound(SoundTrailingBell); //  1/2 hour only, do single 'ding'
  }
  else if ( chimetype == hoursonly && strikeflag == cqtr0) {
    for (int i = 1; i < chour1; i++) {
      QueueSound(SoundTruncatedBell); // Ding once less than hours
    }
    QueueSound(SoundTrailingBell);
  }                                               //  and follow by trailing bell as last 'ding' (if used)
  else   if (((chimetype == westminster) )  && strikeflag == cqtr1) {
    QueueSound(SoundQ1); // First Quarter
  }
  else   if (((chimetype == westminster) ) && strikeflag == cqtr2) {
    QueueSound(SoundQ2); // Second Quarter
  }
  else   if (((chimetype == westminster)) && strikeflag == cqtr3) {
    QueueSound(SoundQ3); // Third Quarter
  }
  else   if (((chimetype == westminster)) && strikeflag == cqtr0) {
    QueueSound(SoundQ4);
    for (int i = 1; i < chour1; i++) {
      QueueSound(SoundTruncatedSingle); // Chime once less than hours
    }
    QueueSound(SoundTrailingSingle);
  }                   // Chime Westminster final hours


}     // end of Chime


// Routine to check for 'real' (static) events and floating events (e.g., Memorial Day, Thanksgiving, Mother's Day and Fathers Day)

/* Memorial Day is Last Monday in May
    Thanksgiving is 4th Thursday in November
    Mother's Day is 2nd Sunday in May
    Father's Day is 3rd Sunday in June
    MLK Day is 3rd Monday in February
    Memorial Day is last Monday in May
    Labor Day is first Monday in September
    Daylight Savings Times starts 2nd Sunday in March
    Daylight Savings Times ends first Sunday in November
    Indigenous People's Day is second Monday in October (also its Columbus Day)
    Oktoberfest is 3rd Saturday in September
    Election Day is first Tuesday in November
    National Ice Cream Day is 3rd Sunday in July

*/

void CheckForEvent(int m, int d, int dow) {                          // called with Month, Day within month and day of the week (0 = Sunday)

  event = false;                                                      // assume neither static or floating
  eventindex = -1;                                                      // initial index to eventstrings

  //  Static event check
                                                                           
      for (int i = 0; i < eventnumber; i++) {                                               // then check the static events
        if ((setarray[setmonth] == eventmonth[i]) && (setarray[setday] == eventday[i]))
        { event = true;                                                // set if match on month and day
          eventindex++;                                                 // found one!
          eventstrings[eventindex] = eventdescription[i];                 // store pointer to static event description
        }
      }

  //  Floating event check

  floatstring = "none";                                                           // initialize string 
  
  if ((dow == thursday) && (m == november) && (d >= 22) && (d <= 28)) {                         // Thanksgiving
    floatstring = "Thanksgiving";
  }
  else if ((dow == sunday) && (m == may) && (d >= 8) && (d <= 14)) {                    // Mother's Day
    floatstring = "Mother's Day";
  }
  else if ((dow == sunday) && (m == june) && (d >= 15) && (d <= 21)) {                 //Father's Day
    floatstring = "Father's Day";
  }
  else if ((dow == monday) && (m == january) && (d >= 15) && (d <= 21)) {               //MLK Day
    floatstring = "Martin Luther King Day";
  }
  else if ((dow == saturday) && (m == october) && (d >= 15) && (d <= 21)) {               //Oktoberfest starts
    floatstring = "Oktoberfest Begins";
  }
  else if ((dow == sunday) && (m == july) && (d >= 15) && (d <= 21)) {               //Oktoberfest starts
    floatstring = "National Ice Cream Day";
  }
  else if ((dow == monday) && (m == february) && (d >= 15) && (d <= 21)) {             //President's Day
    floatstring = "President's Day!";
  }
  else if ((dow == monday) && (m == september) && (d <= 7) ) {                        //Labor Day
    floatstring = "Labor Day";
  }
  else if ((dow == tuesday) && (m == november) && (d <= 7) ) {                        //Election Day
    floatstring = "Election Day";
  }
  else if ((dow == monday) && (m == october) && (d >= 8) && (d <= 14) ) {                        //Indigenous People's Day
    floatstring = "Indigenous People's Day";
  }
  else if ((dow == monday) && (m == may) && (d + 7 > 31) ) {                        //Memorial Day
    floatstring = "Memorial Day";
  }
  else if ((dow == sunday) && (m == march) && (d >= 8) && (d <= 14)) {              // DST begins
    floatstring = "DST Begins";
  }
  else if ((dow == sunday) && (m == november) && (d <= 7) ) {                       //DST ends
    floatstring = "DST Ends";
  }

  if (floatstring != "none" ) {                                             // did we load anything?
     event = true;                                                          // if so, then we have one
     eventindex++;                                                          // so load it into event display queue
     eventstrings[eventindex] = floatstring.c_str();                      // store pointer to static event description
        }

}  // end of floatingevent

void LEDBrightness() {
  int LEDvalue;
  LEDvalue  = analogRead(LEDpin);                                       // Read LDR value (may need to play with values)
  DDTl("LEDvalue",LEDvalue);
 //DDTs("LED value",LEDvalue);
 // LEDvalue = map(LEDvalue, 0, 1023, 0, MAX_INTENSITY);              //map to valid value for brightness  (max intensity is 15 btw)
 // if (LEDvalue <= 12) {
 //   i = minbright;}
 //   else if (LEDvalue > 13 ) {
 //   i=maxbright; }
 //   else {i = midbright;}
      if (LEDvalue >=950) {i=maxbright;}
        else if (LEDvalue >=700) {i=midbright;}
            else {i=minbright;}
    
  mx.control(MD_MAX72XX::INTENSITY, i); 
 // DDTl("Mapped LED value", i);

  

}  //end of LEDbrightness

void Scrollup() {                                                                            // used to 'wipe' previous display
  for (uint8_t i = 0; i < 8; i++) {
    mx.transform(MD_MAX72XX::TSU); delay(2 * DELAYTIME);
    delay(DELAYTIME);
  }
} // End of Scrollup

#ifdef OLED_Display

void OLED_Update(){
  display.clearDisplay();
  display.setTextSize(2);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(" Date/Time");                 // header line
  display.print(setarray[setmonth]);                        // use mm/dd/yyyy
  display.print("/");
  display.print(setarray[setday]);
  display.print("/");
  display.println(setarray[setyear]); 
  display.setTextSize(3);             // Draw 3X-scale text so time is somewhat readable
  if (setarray[sethour] <=12)     // convert military time to am pm
  {phours = setarray[sethour];}
     else
     {phours = setarray[sethour]-12;}
    if (phours <= 0){phours = 12;} // don't print 0 for midnite, print "12"
          printtwo(phours," ");                                   // print a standard hh:mm am/pm time on the oled
          display.print(":");
          printtwo(setarray[setminute],"0");
          if (setarray[sethour] < 12)
            {display.print("am");}
            else
          {display.print("pm");}      
     display.display();  // update display
   
}  // end of OLED display

void printtwo(int value, String  fillchar){      // print two always and use fill to make it so
        if (value <10)  {display.print(fillchar);}    // blank space for hours, 0 for minutes, seconds
        display.print(value);
 }
#endif 

void scrollText(char *p)                                                                      // copied from library
{
  uint8_t charWidth;
  uint8_t cBuf[8];  // this should be ok for all built-in fonts
  mx.clear();
  while (*p != '\0')
  {
    charWidth = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);

    for (uint8_t i = 0; i <= charWidth; i++) // allow space between characters
    {mx.transform(MD_MAX72XX::TSL);
      if (i < charWidth)
        mx.setColumn(0, cBuf[i]);
      delay(DELAYTIME);}
  }
}  // End of Scroll Text

void printText(char *pMsg, int LOS, bool CenterJustify)                                   // copied and modified from library
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
// And center justified if third argument is "true"
{
  uint8_t modStart = 0;
  uint8_t modEnd = MAX_DEVICES - 1;
  uint8_t   state = 0;
  uint8_t   curLen;
  uint16_t  showLen;
  uint8_t   cBuf[8];
  int16_t   col = ((modEnd + 1) * COL_SIZE) - 1;
  int pixelcount = 0;
  int ccounter = LOS;

  mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  do     // finite state machine to print the characters in the space available
  {   switch (state)
    {
      case 0: // Load the next character from the font table
        // if we reached end of message, reset the message pointer
        if (*pMsg == '\0')
        {
          showLen = col - (modEnd * COL_SIZE);  // padding characters
          state = 2;
          break;
        }

        // retrieve the next character form the font file

        showLen = mx.getChar(*pMsg++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
        if (ccounter > 0) {
          pixelcount = (pixelcount + showLen) + CHAR_SPACING;
          ccounter--;
        }
        curLen = 0;
        state++;
      // !! deliberately fall through to next state to start displaying

      case 1: // display the next part of the character
        mx.setColumn(col--, cBuf[curLen++]);

        // done with font character, now display the space between chars
        if (curLen == showLen)
        {
          showLen = CHAR_SPACING;
          state = 2;
        }
        break;

      case 2: // initialize state for displaying empty columns

        curLen = 0;

        state++;
      // fall through

      case 3:  // display inter-character spacing or end of message padding (blank columns)
        mx.setColumn(col--, 0);
        curLen++;
        if (curLen == showLen)
          state = 0;
        break;

      default:
        col = -1;   // this definitely ends the do loop
    }
  } while (col >= (modStart * COL_SIZE));

  if (CenterJustify) {
    for (int i = 1; i <= (((MAX_DEVICES * COL_SIZE) - pixelcount) / 2); i++) {
      mx.transform( MD_MAX72XX::TSR);
    }
  }
  mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void printCenter(String StringtoPrint)                                    // Loads the print buffer and centers it
{
  int LOS = StringtoPrint.length();                                          // Get the string's length
  for (int i = 0; i < BufferStringLength; i++) {
    BufferString[i] = ' '; // clear buffer
  }
  for (int i = 0; i < LOS; i++) {
    BufferString[i] = StringtoPrint[i];        // transfer
    printText(BufferString, LOS, true);                                       // Print, providing length of string and "Yes, center"

  }
}  // End of Center and Load

void ShowValue(int myindex, String setdescription)                                    // Display while in setup mode
{                                                                                   // shows what item is setting and its value

  for (int i = 0; i < BufferStringLength; i++) {BufferString[i] = ' ';}            // clear buffer
  setdescription.toCharArray(BufferString, setdesclength);                                       // move description to output buffer
  LoadNumber(setarray[setupindex]);
  BufferString[setdesclength-1] = ' ';
  for (int i = 0; i <setdesclength; i++) {BufferString[i + setdesclength] = TDigits[i];}
  if (myindex != setyear) {                                                           // only the year is 4 digits, else 2
    BufferString[4] = ' ';                                                            
    BufferString[5] = ' ';
    if (BufferString[6] == '0') {BufferString[6] = ' ';}                             // and suppress leading 0 for 2 digit numbers too
  }
  BufferString[8] = '\0';                                                          // and terminate
  printText(BufferString, 7, false);


}  // End of Show Value

void LoadNumber(int numtoload) {                                            // Converts number to four digit Ascii array
  int tempnum = numtoload;
  for (int i = 3; i >= 0; i--) {
    TDigits[i] = (tempnum % 10) + DecToAscii;;
    tempnum = tempnum / 10;
  }
}  // end of LoadNumber

void QueueSound(int SoundFile) {                                          // place a sound to be played into sequence

  if ((SoundQueuePtr == SizeQueue) || (SoundQueueCtr <= 0)) {
    SoundQueuePtr = -1; // Dont overflow the queue
  }
  SoundQueuePtr++;                                                 // Increment pointer
  SoundQueue[SoundQueuePtr] = SoundFile;                         // and store the sound chosen
  SoundQueueCtr++;                                                // and increment # sounds in queue
}     // end of Queue Sound File

void PlaySound() {                                                // Plays appropriate Sound file
  if (SoundQueueCtr > 0) {                                   // if nothing in the queue, just return
    if (digitalRead(SoundBusyPin) == LOW) {                    //or if sound card busy, just return
      return;
    }                                                      //until idle
    if (SoundPlayPtr == SizeQueue) {
      SoundPlayPtr = -1; // Dont go past the queue
    }
    SoundPlayPtr++;                                                 // Increment pointer
    digitalWrite(FirstSoundPin + SoundQueue[SoundPlayPtr], LOW);            //Play the sound
    delay(fxset);                                                   // hold to start the sound
    digitalWrite(FirstSoundPin + SoundQueue[SoundPlayPtr], HIGH);            //Turn off the sound
    delay(fxset);                                                     // Ensure its set
    SoundQueueCtr--;                                                  // and decrement sounds yet to be played
    if (SoundQueueCtr <= 0) {
      SoundPlayPtr = -1;
    };                       // reset play pointer when queue is empty
  }

}     // end of PlaySound
/*
   void PlaySoundUART()                                                   // uncomment for FX sound board UART use

   //   sfx.playTrack(name);  }
      {
      Serial.print("\nPlaying track \""); Serial.print(name); Serial.print("\"");
      if (! sfx.playTrack(name) ) {
        Serial.println("Failed to play track?");
      }
   }
*/


#ifdef showoffcode

void ShowOff() {                                             // Played at startup -- just the MX Panel Test Graphics
  QueueSound(SoundShowOff);                                 // play Game of Thones theme as sound 
  PlaySound();
  scrollText("Showing Off");
  rows();
  columns();
  cross();
  checkboard();
  bullseye();
  bounce();
  stripe();
  stripe();
  transformation1();
  transformation2();
  bullseye();
  spiral();
  delay(2000);
  DisplayIndex = DisplayTime;                                     // force display of time after showing off display
}                                                           // end of Show Off

void rows()                                                   // these routines are all
// Demonstrates the use of setRow()                               // from the MX library
{mx.clear();
 for (uint8_t row = 0; row < ROW_SIZE; row++)
  {
    mx.setRow(row, 0xff);
    delay(2 * DELAYTIME);
    mx.setRow(row, 0x00);
  }
}

void checkboard()
// nested rectangles spanning the entire display
{
  uint8_t chkCols[][2] = { { 0x55, 0xaa }, { 0x33, 0xcc }, { 0x0f, 0xf0 }, { 0xff, 0x00 } };
  mx.clear();
  for (uint8_t pattern = 0; pattern < sizeof(chkCols) / sizeof(chkCols[0]); pattern++)
  {
    uint8_t col = 0;
    uint8_t idx = 0;
    uint8_t rep = 1 << pattern;

    while (col < mx.getColumnCount())
    {
      for (uint8_t r = 0; r < rep; r++)
        mx.setColumn(col++, chkCols[pattern][idx]);   // use odd/even column masks
      idx++;
      if (idx > 1) idx = 0;
    }
    delay(10 * DELAYTIME);
  }
}                 // end of Checkboard

void columns()
// Demonstrates the use of setColumn()
{

  mx.clear();

  for (uint8_t col = 0; col < mx.getColumnCount(); col++)
  {
    mx.setColumn(col, 0xff);
    delay(DELAYTIME / MAX_DEVICES);
    mx.setColumn(col, 0x00);
  }
}

void cross()
// Combination of setRow() and setColumn() with user controlled
// display updates to ensure concurrent changes.
{

  mx.clear();
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  // diagonally down the display R to L
  for (uint8_t i = 0; i < ROW_SIZE; i++)
  {
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0xff);
      mx.setRow(j, i, 0xff);
    }
    mx.update();
    delay(DELAYTIME);
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0x00);
      mx.setRow(j, i, 0x00);
    }
  }

  // moving up the display on the R
  for (int8_t i = ROW_SIZE - 1; i >= 0; i--)
  {
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0xff);
      mx.setRow(j, ROW_SIZE - 1, 0xff);
    }
    mx.update();
    delay(DELAYTIME);
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0x00);
      mx.setRow(j, ROW_SIZE - 1, 0x00);
    }
  }

  // diagonally up the display L to R
  for (uint8_t i = 0; i < ROW_SIZE; i++)
  {
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0xff);
      mx.setRow(j, ROW_SIZE - 1 - i, 0xff);
    }
    mx.update();
    delay(DELAYTIME);
    for (uint8_t j = 0; j < MAX_DEVICES; j++)
    {
      mx.setColumn(j, i, 0x00);
      mx.setRow(j, ROW_SIZE - 1 - i, 0x00);
    }
  }
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

void bullseye()
// Demonstrate the use of buffer based repeated patterns
// across all devices.
{

  mx.clear();
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);

  for (uint8_t n = 0; n < 3; n++)
  {
    byte  b = 0xff;
    int   i = 0;

    while (b != 0x00)
    {
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, b);
        mx.setColumn(j, i, b);
        mx.setRow(j, ROW_SIZE - 1 - i, b);
        mx.setColumn(j, COL_SIZE - 1 - i, b);
      }
      mx.update();
      delay(3 * DELAYTIME);
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, 0);
        mx.setColumn(j, i, 0);
        mx.setRow(j, ROW_SIZE - 1 - i, 0);
        mx.setColumn(j, COL_SIZE - 1 - i, 0);
      }

      bitClear(b, i);
      bitClear(b, 7 - i);
      i++;
    }

    while (b != 0xff)
    {
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, b);
        mx.setColumn(j, i, b);
        mx.setRow(j, ROW_SIZE - 1 - i, b);
        mx.setColumn(j, COL_SIZE - 1 - i, b);
      }
      mx.update();
      delay(3 * DELAYTIME);
      for (uint8_t j = 0; j < MAX_DEVICES + 1; j++)
      {
        mx.setRow(j, i, 0);
        mx.setColumn(j, i, 0);
        mx.setRow(j, ROW_SIZE - 1 - i, 0);
        mx.setColumn(j, COL_SIZE - 1 - i, 0);
      }

      i--;
      bitSet(b, i);
      bitSet(b, 7 - i);
    }
  }

  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

void spiral()
// setPoint() used to draw a spiral across the whole display
{

  int  rmin = 0, rmax = ROW_SIZE - 1;
  int  cmin = 0, cmax = (COL_SIZE * MAX_DEVICES) - 1;

  mx.clear();
  while ((rmax > rmin) && (cmax > cmin))
  {
    // do row
    for (int i = cmin; i <= cmax; i++)
    {
      mx.setPoint(rmin, i, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    rmin++;

    // do column
    for (uint8_t i = rmin; i <= rmax; i++)
    {
      mx.setPoint(i, cmax, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    cmax--;

    // do row
    for (int i = cmax; i >= cmin; i--)
    {
      mx.setPoint(rmax, i, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    rmax--;

    // do column
    for (uint8_t i = rmax; i >= rmin; i--)
    {
      mx.setPoint(i, cmin, true);
      delay(DELAYTIME / MAX_DEVICES);
    }
    cmin++;
  }
}

void bounce()
// Animation of a bouncing ball
{
  const int minC = 0;
  const int maxC = mx.getColumnCount() - 1;
  const int minR = 0;
  const int maxR = ROW_SIZE - 1;

  int  nCounter = 0;

  int  r = 0, c = 2;
  int8_t dR = 1, dC = 1;  // delta row and column


  mx.clear();

  while (nCounter++ < 200)
  {
    mx.setPoint(r, c, false);
    r += dR;
    c += dC;
    mx.setPoint(r, c, true);
    delay(DELAYTIME / 2);

    if ((r == minR) || (r == maxR))
      dR = -dR;
    if ((c == minC) || (c == maxC))
      dC = -dC;
  }
}
void stripe()
// Demonstrates animation of a diagonal stripe moving across the display
// with points plotted outside the display region ignored.
{
  const uint16_t maxCol = MAX_DEVICES*ROW_SIZE;
  const uint8_t  stripeWidth = 10;

  mx.clear();

  for (uint16_t col=0; col<maxCol + ROW_SIZE + stripeWidth; col++)
  {
    for (uint8_t row=0; row < ROW_SIZE; row++)
    {
      mx.setPoint(row, col-row, true);
      mx.setPoint(row, col-row - stripeWidth, false);
    }
    delay(DELAYTIME);
  }
}

void transformation1()
// Demonstrates the use of transform() to move bitmaps on the display
// In this case a user defined bitmap is created and animated.
{
  uint8_t arrow[COL_SIZE] =
  {
    0b00001000,
    0b00011100,
    0b00111110,
    0b01111111,
    0b00011100,
    0b00011100,
    0b00111110,
    0b00000000
  };

  MD_MAX72XX::transformType_t  t[] =
  {
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TFLR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TRC,
    MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD,
    MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD,
    MD_MAX72XX::TFUD,
    MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU,
    MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU,
    MD_MAX72XX::TINV,
    MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC,
    MD_MAX72XX::TINV
  };

  // transformation 
  
  mx.clear();

  // use the arrow bitmap
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
  for (uint8_t j=0; j<mx.getDeviceCount(); j++)
    mx.setBuffer(((j+1)*COL_SIZE)-1, COL_SIZE, arrow);
  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
  delay(DELAYTIME);

  // run through the transformations
  mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::ON);
  for (uint8_t i=0; i<(sizeof(t)/sizeof(t[0])); i++)
  {
    mx.transform(t[i]);
    delay(DELAYTIME*4);
  }
  mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::OFF);
}

void transformation2()
// Demonstrates the use of transform() to move bitmaps on the display
// In this case font characters are loaded into the display for animation.
{
  MD_MAX72XX::transformType_t  t[] =
  {
    MD_MAX72XX::TINV,
    MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC,
    MD_MAX72XX::TINV,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
    MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
    MD_MAX72XX::TSD, MD_MAX72XX::TSU, MD_MAX72XX::TSD, MD_MAX72XX::TSU,
    MD_MAX72XX::TFLR, MD_MAX72XX::TFLR, MD_MAX72XX::TFUD, MD_MAX72XX::TFUD
  };}
#endif //showoffcode// 

Credits

spudnut1

spudnut1

10 projects • 15 followers

Comments