Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
doodles2000
Published © GPL3+

32x8 LED Matrix NTP Clock with DS3231 RTC & ESP01

Yet another LED Matrix clock!

IntermediateWork in progress2,695
32x8 LED Matrix NTP Clock with DS3231 RTC & ESP01

Things used in this project

Story

Read more

Schematics

Fritzing Layout

Code

ESP-01 Code

Arduino
/***********************************************************************

Mini Clock ESP01 code by Ratti3 - 16 Jul 2019
Distributed under the terms of the GPL.
Tested on IDE v1.8.9

268,688 bytes 53%
27,232 bytes 33%

https://github.com/Ratti3/miniclock
https://youtu.be/krdAU_GUc3k
https://create.arduino.cc/projecthub/Ratti3/led-matrix-word-clock-with-bme280-bh1750-and-esp01-fdde2b

Updated by Doodles2000
Added functions to blink onboard LED, showing activity

Tested on: 
LOLIN(WEMOS) D1 R2 & mini
Generic ESP8266 Module

Progmem = 273,073 bytes 26%
Variables = 28,428 bytes 34%


***********************************************************************/

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define FPM_SLEEP_MAX_TIME 0xFFFFFFF
#define NTP_PACKET_SIZE 48

#define ledPin 2  //Built in LED on GPIO2

// Required for wifi_station_connect() to work
extern "C" {
  #include "user_interface.h"
}

// Variable for SSID and Password
char ssid[34];
char pass[34];

IPAddress timeServerIP;                     // stores the IP address of the time server
const char* ntpServerName = "pool.ntp.org"; // the NTP pool to query
byte try_count = 15;                        // number of packet send attempts, 1 try_count = 2 seconds
byte packetBuffer[NTP_PACKET_SIZE];
/*int*/ bool cb;              // holds parsed NTP packet                              
byte count;          // counter for retrying packets
byte retry_count;    // holds the total attempts so far
byte retry_max;      // number of total attempts (retry_count * try_count), this value is obtained from the Arduino
char buf[1];         // hold the retry_max from the Arduino, so it can be converted
char buffer[80];     // holds the data received on software serial
bool WiFiFail = 0;   // Holds status for WiFi connection failure

WiFiUDP udp;         // WiFi UDP library

void setup() {

  // max speed as using softwareserial on Arduino
  Serial.begin(9600);
  delay(1000);
  // Initialize the BUILTIN_LED1 pin as an output and turn off
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  // Begin UDP library, listening on port 2390
  udp.begin(2390);

  Serial.println(F("ESP01 Start"));

}


void loop() {

  // Read the serial port and look for NTP string received from the Arduino
  while (readline(Serial.read(), buffer, 80) > 0) {
    if (buffer[0] == 'N' && buffer[1] == 'T' && buffer[2] == 'P') {

      // Work out the length of the SSID
      char ssid_len[2];
      ssid_len[0] = buffer[4];
      ssid_len[1] = buffer[5];
      ssid_len[2] = '\0';
      int ssid_length = atoi(ssid_len);

      // Using the calculated length, work out the SSID name
      byte i = 6;
      byte x = 0;
      while (i < ssid_length + 6) {
        ssid[x] = buffer[i];
        i++;
        x++;
      }
      ssid[ssid_length] = '\0';

      // Work out the length of the SSID password
      char pass_len[2];
      pass_len[0] = buffer[ssid_length + 6];
      pass_len[1] = buffer[ssid_length + 7];
      pass_len[2] = '\0';
      int pass_length = atoi(pass_len);

      // Using the calculated length, work out the SSID password
      i = ssid_length + 8;
      x = 0;
      while (i < pass_length + ssid_length + 8) {
        pass[x] = buffer[i];
        i++;
        x++;
      }
      pass[pass_length] = '\0';

      //Uncomment to troubleshoot SSID and Password
      //Serial.println(ssid_len);
      //Serial.println(ssid);
      //Serial.println(pass_len);
      //Serial.println(pass);

      retry_count = 1;
      // Work out the retry count received from the Arduino
      buf[0] = buffer[3];
      buf[1] = '\0';
      retry_max = atoi(buf);

      // Send an acknowledgement to the Arduino
      SETLED(HIGH, 2);
      Serial.println(F("ESP01 : NTP request received from Arduino"));
      SETLED(LOW, 0);
      while (retry_count <= retry_max) {
        SETLED(HIGH, 2);
        Serial.print(F("[attempt "));
        SETLED(LOW, 0);
        Serial.print(retry_count);
        SETLED(HIGH, 2);
        Serial.print(F(" of "));
        SETLED(LOW, 0);
        Serial.print(retry_max);
        SETLED(HIGH, 2);
        Serial.println(F("]"));
        SETLED(LOW, 0);
        WiFiOn();
        if (WiFiFail) {
          break;
        }
        GetNTPTime();
        WiFiOff();
        retry_count++;
        if (cb) break;
        if (retry_count > retry_max) {
          SETLED(HIGH, 2);
          Serial.println();
          SETLED(LOW, 0);
          Serial.println(F("NTP Fail"));
          //Serial.println();
          SETLED(HIGH, 1000);
          SETLED(LOW, 0);
        }
      }
    }
  }
  delay(50);

}


// Main routine for getting NTP time and sending to Arduino via Serial
void GetNTPTime() {
  
  WiFi.hostByName(ntpServerName, timeServerIP);

  count = 1;
  sendNTPpacket(timeServerIP);
  delay(50);
  cb = udp.parsePacket();
  int i = 0;
  while (!cb) {
    if (count >= try_count) {
      break;
    }
    if (i == 400) { //resend NTP packet every 2 seconds
      i = 0;
      count++;
      sendNTPpacket(timeServerIP);
      delay(5);
      cb = udp.parsePacket();
    }
    delay(5);
    i++;
  }
  
  if (cb) {
    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    const unsigned long seventyYears = 2208988800UL;
    unsigned long epoch = secsSince1900 - seventyYears;
    // The end result is here, the UNIX time, send it to the Arduino via Serial
    Serial.print(F("UNIX"));
    Serial.println(epoch);
    SETLED(HIGH, 500);
    SETLED(LOW, 250);
    SETLED(HIGH, 500);
    SETLED(LOW, 0);
  }
  
}

// Turns on WiFi
void WiFiOn() {

  wifi_fpm_do_wakeup();
  wifi_fpm_close();
  wifi_set_opmode(STATION_MODE);
  wifi_station_connect();
  wifi_connect();
  
}

// Turns off WiFi
void WiFiOff() {

  Serial.print(F("Disabling WiFi.."));
  //client.disconnect();
  wifi_station_disconnect();
  wifi_set_opmode(NULL_MODE);
  wifi_set_sleep_type(MODEM_SLEEP_T);
  wifi_fpm_open();
  wifi_fpm_do_sleep(FPM_SLEEP_MAX_TIME);
  Serial.println(F("..ok"));
  //Serial.println();
  //delay(5000);

}

// Connects WiFi
void wifi_connect() {

  count = 1;
  WiFiFail = 0;
  WiFi.begin(ssid, pass);
  Serial.print(F("Connecting to SSID: "));
  Serial.println(ssid);
  while (WiFi.status() != WL_CONNECTED) {
    WiFi.mode(WIFI_STA);
    delay(1000);
    count++;
    if (count > 7) {
      Serial.println(F("Wifi Fail"));
      //Serial.println();
      SETLED(HIGH, 1000);
      SETLED(LOW, 0);
      WiFiFail = 1;
      WiFiOff();
      break;
    }
  }
  if (!WiFiFail) {
    Serial.println(F("Connected"));
    printWifiStatus();
  }

}

// Prints WiFi connection info
void printWifiStatus() {

  // print the SSID of the network you're attached to:
  //Serial.print(F("SSID: "));
  //Serial.println(WiFi.SSID());

  // print your WiFi IP address:
  //IPAddress ip = WiFi.localIP();
  Serial.print(F("IP Address: "));
  //Serial.println(ip);
  Serial.println(WiFi.localIP());
  
  // print the received signal strength:
  //long rssi = WiFi.RSSI();
  Serial.print(F("Signal Strength (RSSI): "));
  //Serial.print(rssi);
  Serial.print(WiFi.RSSI());
  Serial.println(F(" dBm"));

}

// Build and send UDP packet
void sendNTPpacket(IPAddress& address) {
  
  Serial.print(F("Sending NTP packet to: "));
  Serial.print(address);
  Serial.print(F(" ["));
  Serial.print(count);
  Serial.print(F(" of "));
  Serial.print(try_count);
  Serial.println(F("]"));
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011; // LI, Version, Mode
  packetBuffer[1] = 0;          // Stratum, or type of clock
  packetBuffer[2] = 6;          // Polling Interval
  packetBuffer[3] = 0xEC;       // Peer Clock Precision
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  udp.beginPacket(address, 123);
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();

}

// Used to readline from serial output
int readline(int readch, char *buffer, int len) {

  static int pos = 0;
  int rpos;

  if (readch > 0) {
    switch (readch) {
      case '\r': // Ignore CR
      break;
      case '\n': // Return on new-line
      rpos = pos;
      pos = 0;  // Reset position index ready for next time
      return rpos;
      default:
      if (pos < len-1) {
        buffer[pos++] = readch;
        SETLED((pos % 2), 5);
        buffer[pos] = 0;
      }
      digitalWrite(2, HIGH);
    }
  }
  return 0;

}

void SETLED(bool state, int dlay) {
  digitalWrite(ledPin, !state);
  delay(dlay);
}

Mini-LED-Clock-Main

Arduino
Main code tab
/***********************************************************************

  Mini Clock v1.0, Jul 2014 by Nick Hall
  Distributed under the terms of the GPL.

  For help on how to build the clock see my blog:
  http://123led.wordpress.com/

  =======================================================================

  Modified by Ratti3 - 17 May 2020
  Mini Clock v1.3 (ESP01 Version, LED Switch power via pin D8)
  Tested on IDE v1.8.12

  29,398 bytes 95%
  1,252 bytes 61%

  https://github.com/Ratti3/miniclock
  https://youtu.be/MRocFW43dEg
  https://youtu.be/krdAU_GUc3k
  https://create.arduino.cc/projecthub/Ratti3/led-matrix-ntp-clock-with-ds3231-bme280-bh1750-esp01-fdde2b

  =======================================================================
  
   Doodles2000 modifications, Start: 20210803
   Built in Arduino IDE v1.8.16 for Arduino Nano, ATMega328P
   
   20210807 - Create complete 5x7 ASCII font set
              test functionallity and start adding in 'Wifi setup' section
            - Fixed bug with font selector in setup not displaying font upon entry
            - Bug appeared where fonts 5,6 don't display properly, removed for time being
   20210815 - Defined EEPROM locations for ESSID and Password storage
            - Created "enter" character
            - Added wifi setup menu
            - Start work on set_wifi function
            - Intent is for user to use the display and buttons to enter / change ESSID + Password
              data will save to EEPROM, NTP function will pull data from EEPROM, in theory freeing up RAM
   20210823 - Made progress with updating characters on LED matrix and storing them into an array
            - User selects the "enter" character to 'save and exit'
            - Test EEPROM reading + writing of user input code
   20210826 - EEPROM reading + writing of user input completed
            - Fixed a bug with slide() function, where it would keep scrolling over the tens hour digit,
              by commenting out unused if statements
            - Fixed a bug with scroll function, where it was reading from wrong section of font table, causing
              invalid character display
   20210828 - Change ntp() function to get SSID and Password vaules from EEPROM and pass on to Wifi chip
            - Fixed putnormalchar() function, bug with fonts 5,6 not displaying correctly
              'char c' input variable was wrapping around to produce negative number, changed to 'byte c'
   20210829 - Scrolling ssid/password text during entry working
            - Fixed timeout issues in ntp() function
            - Moved "ntp" on/off switch into wifi menu
            - Added feature for user to change the fade delay, stores value in EEPROM
            - Version 1.0 
            All debugging off:
            25632 bytes (83%) program space
            979 bytes (47%) dynamic memory
   20210830 - Fixed bug with temperature screen displaying wrong characters inplace of '.' '-' and 'space' characters
            - Version 1.1
            All debugging off:
            25714 bytes (83%) program space
            979 bytes (47%) dynamic memory
   20210901 - Adjusted logic in putnormalchar() function to be more efficient (well I think so anyhows!)
            - Start attempt to use global font_style in slide() function
            All debugging off:
            25806 bytes (83%) program space
            979 bytes (47%) dynamic memory
   20210904 - putnormalchar() function now uses global value for font_style and font_cols
            - Removed unnecessary use of 'y' value from putnormalchar() function
            - Fixed slideanim() function to recognise and properly draw the wider fonts
            - Fixed issue with set_wifi_str() not displaying enter symbol end of current array
            - Removed unnecessary passing of parameters from set_font() to get_font_value() functions
            - slide() function now using global variables for font_style
            - font_offset variable is only used in basic_mode() function, removed and hard coded number
              positions to be the same as slide()
            - Version 1.2
            All debugging off:
            25328 bytes (82%) program space
            979 bytes (47%) dynamic memory
   20210905 - Create new map_font() function in attempt to reduce program storage
              font lookps from putnormalchar() and slideanim() use map_font() function
            - Start work on aligning basic() and slide() functions.  Using very different methods to obtain same
              outcome.  eg: 12/24 hour calculations, blanking of hour tens when in 12 hour mode
            - Version 1.3
            All debugging off:
            25120 bytes (81%) program space
            977 bytes (47%) dynamic memory
   20210906 - Draw a dot top left of display, in all number modes, to represent PM when 12 hour mode selected
            - Version 1.4
            All debugging off:
            25294 bytes (82%) program space
            977 bytes (47%) dynamic memory  
   20210907 - added summer option to DST/TZ menu.  Allows user to select summer / winter mode.  Handy if user cannot
              wait for the daily DST check to run at 0100
            - re-wrote timeout routines in NTP function to use millis() instead of time from the DS3231.  Eliminate
              potential issues that could occur as a result of time changes being applied to the DS3231
            - Version 1.5
            All debugging off:             
            25416 bytes (82%) program space
            989 bytes (47%) dynamic memory
   20210908 - learning how to use timelib.h to sync time from RTC into arduino internal clock every x (55) seconds
              reduce traffic on I2C communications bus with DS3231.
              Unfortunatley Adafruit doesn't support 'SyncProvider' (yet)
              Looking into using a smaller library to communicate with the DS3231
              A lot of overlapping between adafruit and timelib.h
            All debugging off:
            26436 bytes (86%) program space
            1014 bytes (49%) dynamic memory
   20210913 - TimeLib and DS3232RTC libraries implemented and utilised.
            - Removed refrences to rtc[x] throughout
            - Aligned clock calculations in all display modes
            - Created new function buf_hour(bool tn) to reduce repetitive code in the display modes
            - Moved "PM" dot to top right corner.  Some of the larger fonts looked odd with it at top left
            - Found a bug in slide mode where center "0"'s do not display after returning from another display mode
              eg: '10:09' shows as '1 : 9' - slowly fixes itself as a non zero takes the spot
            - Version 1.6
            All debugging off:              
            24514 bytes (79%) program space
            1008 bytes (49%) dynamic memory
   20210916 - Fixed slide mode blank digit bug
            - Version 1.7
            All debugging off:              
            24490 bytes (79%) program space
            1008 bytes (49%) dynamic memory
   20210925 - Aligned digit positions in slide and basic
            - Fixed temperature screen defaulting to font 1, now uses the set font
            - Version 1.8
            All debugging off:              
            24658 bytes (80%) program space
            1008 bytes (49%) dynamic memory
   20211113 - Fixed issue where hour leading zero in small mode not being displayed
            - Moved display on/of checks into run_mode() function
            - Added feature to wake display if shut when a button pressed
            - Keep display awake in setup menus, regardless of timer setting
            - Fixed bug where ntp() function would not exit when no response from ESP in defined
              time period
            - Performed sanity checking in ntp() function, send SSID and pass info to ESP01 if length
              less than 32 characters
            - Version 1.9
            All debugging off:
            24928 bytes (81%) program space
            1009 bytes (49%) dynamic memory
   20211119 - Added feature to increase randomness of "random font"
              same font cannot be selected multiple times in a row
            - Version 2.0
            All debugging off:
            25094 bytes (81%) program space
            1016 bytes (49%) dynamic memory
   20211228 - Added festure to wake display for WAKE_DELAY when asleep and a button pressed
            - Version 2.1
            All debugging off:
            25308 bytes (82%) program space
            1016 bytes (49%) dynamic memory
   20220115 - Add feature to allow user adjustment of NTP update frequency and select hour, day of week, day of month
            - Daily, Weekly, Monthly
            - Free up dynamic memory usage by moving some menu items to PROGMEM 
            - Version 2.2
            All debugging off:
            25434 bytes (82%) program space
            754 bytes (36%) dynamic memory
   20220116 - Change setup menus to be consistent and intuitive. 
            - ">" at beginning has timeout on it, press button 'a' before 2sec timeout to select next option
            - nothing at beginning is changeable value.  Buttons 'b' and 'c' to increase / decrease value, button'a' saves
            - cleaned up menus to reduce screen 'flicker' during changes
            - changed display menu option so each sub item can be individually selected
            - can now skip directly to the required field in set_time, set_dst_ntp functions
            - moved all menus into PROGMEM
            - total re-design of set_bool and set_value functions. no longer have a message, they are simply to change values
            -
            - Version 2.3
            All debugging off:
            25664 bytes (83%) program space
            670 bytes (32%) dynamic memory
   20220205 - Fixed issue in Wifi menu - going outside bounds and crashing
            - Fixed issue in display, wifi, and NTP menus where it wasn't returning after setting a value
            - Fixed issue where text was overlapping in Wifi and NTP menus
            - 
            - Version 2.4
            All debugging off:
            25834 bytes (84%) program space
            670 bytes (32%) dynamic memory            
   20220206 - Fixed issues where EEPROM detection in startup would get stuck in a loop when no external
              EEPROM present and auto detect enabled
            - Create global variable 'str_a' holding string to be displayed
            - Aligned functions to use str_a where possible
            - 
            - Version 2.5
            All debugging off:
            25534 bytes (83%) program space
            677 bytes (33%) dynamic memory
   20220619 - Add a new colon flash mode - setting available in set_display_options() menu item
              three options;
              0 = sync flash
              1 = alternate flash
              2 = solid on
              3 = knightrider (work in progress)
            - 
            - Version 2.6
            All debugging off:
            25864 bytes (84%) program space
            678 bytes (33%) dynamic memory
   20220904 - Knightrider colon mode code in and running
            -
            - Version 2.7
            All debugging off:
            26046 bytes (84%) program space
            683 bytes (33%) dynamic memory
   20220911 - re-arrange knight rider routine to use less memory
            - replaced blocking delay() with non blocking code in slide function
              reduce inteference / stumbling of knight rider colon
            -
            - Version 2.8
            All debugging off:
            26038 bytes (84%) program space
            680 bytes (33%) dynamic memory
            
***********************************************************************           
*/

#include <LedControl.h>                  // LedControl by Eberhard Fahle v1.0.6 - used to communicate with MAX7219 LED matrix driver https://github.com/wayoda/LedControl
#include "LEDFont.h"                     // LED Fonts File, with additional tweaks by Doodles2000
#include "ProgmemData.h"                 // Progmem Storage File, holds day, month and time names, frees up precious RAM
//#include <RTClib.h>                      // RTCLib by Adafruit v1.14.1 - DS3231 RTC - https://github.com/adafruit/RTClib
#include "Button_Master.h"               // https://github.com/tigoe/Button
#include <SoftwareSerial.h>              // Standard Arduino library Used to communicate with ESP01 module
#include <Wire.h>                        // Standard Arduino library to communicate with wire deivces (I2C)
#include <EEPROM.h>                      // Standard Arduino library to read/write data to/from EEPROM
#include <TimeLib.h>                     // Arduino Time Library by PaulStoffregen https://github.com/PaulStoffregen/Time
#include <DS3232RTC.h>                   // Arduino DS3232RTC Library Copyright (C) 2018 Jack Christensen GNU GPL v3.0 https://github.com/JChristensen/DS3232RTC

// memory libraries for debugging memory issues
//#include <pgmStrToRAM.h>
//#include <MemoryFree.h>

//define constants

#define FIRSTRUNVALUE      127           // The check digits to see if EEPROM has values saved, change this [1-254] if you want to reset EEPROM to default values   

#define FIRSTRUNADDRESS    255           // [255] Address on EEPROM FIRSTRUNVALUE is saved
#define NUM_DISPLAY_MODES  3             // Number display modes (counting zero as the first mode)
#define NUM_SETTINGS_MODES 11            // Number settings modes = 9 (counting zero as the first mode)
#define NUM_FONTS          6             // Number of fonts, as defined in LEDFont.h
#define SLIDE_DELAY        20            // The time in milliseconds for the slide effect per character in slide mode. Make this higher for a slower effect
#define RANDOMSEED         A1            // Pin used to generate random seed
#define LDR_BRIGHT         1000          // Analog value of LDR with full light on it
#define LDR_DARK           150           // Analog value od LDR with minimal light on it
#define LDR_APIN           A0            // Analog pin connected to LDR
#define TX                 6             // Connects to RX pin of ESP01
#define RX                 7             // Connects to TX pin of ESP01
#define NTP_MAX_RETRY      3             // Number of times to retry NTP request 1 = 35 seconds(ish) in total, values 1 - 9
#define NTP_TIMEOUT        20            // Used to calculate when to quit ntp() when it's not receiving data, value in seconds, it is multiplied by NTP_MAX_RETRY
#define NTP_ADJUST         1             // Number of seconds to adjust NTP value before applying to DS3231, takes a few hundred milliseconds to process the ESP01 data
#define DST_ON_HOUR        2             // Hour to enter DaylightSavingsTime (add 1 hour)
#define DST_OFF_HOUR       3             // Hour to exit DaylinghtSavingsTime (subtract 1 hour)
#define LED_DATAIN         12            // Connects to DataIn on the display
#define LED_CLK            11            // Connects to CLK on the display
#define LED_LOAD           10            // Connects to LOAD on the display
#define SSID_LOCATION      1             // EEPROM location for SSID.  First byte = length
#define PASS_LOCATION      35            // EEPROM location for PASSWORD. First byte = length
#define WAKE_DELAY         3000          // Time in milliseconds to keep display awake after pressing button

// these can be used to change the order of displays, some displays from ebay are wrong way round
#define MATRIX0            3
#define MATRIX1            2
#define MATRIX2            1
#define MATRIX3            0

// Debugging features, enable as required
//#define ARDUINOSERIAL                    // Turn on/off general messages
//#define ARDUINOSERIALDEBUG               // Turn on/off additional debug messages
//#define ARDUINOSERIALDEBUGSTARTUP        // Turn on/off additional debug messages for Startup
//#define ARDUINOSERIALDEBUGESP            // Turn on/off additional debug messages with ESP (Wifi) Chip
//#define ARDUINOSERIALDEBUGWIFISETUP      // Turn on/off additional debug messages for Wifi setup

#if defined(ARDUINOSERIAL) || defined(ARDUINOSERIALDEBUG) || defined(ARDUINOSERIALDEBUGESP) || defined (ARDUINOSERIALDEBUGSTARTUP) || defined(ARDUINOSERIALDEBUGWIFISETUP)
#define ARDUINO_SERIAL     Serial
#define BAUD               115200         // serial port speed (bps)
#endif

// create object instances
DS3232RTC myRTC;
LedControl lc = LedControl(LED_DATAIN, LED_CLK, LED_LOAD, 4); // sets the 3 pins as 12, 11 & 10 and then sets 4 displays (max is 8 displays)
SoftwareSerial Wifi_Serial(RX, TX);                           // Software Serial 7 and 6, ESP01 serial connects to these, (pins RX and TX and 3.3v via external regulator)
// RTC_DS3231 ds3231;                                            // Create RTC object - not needed DS3232RTC
Button buttonA = Button(2, BUTTON_PULLUP_INTERNAL);           // Menu / enter button
Button buttonB = Button(3, BUTTON_PULLUP_INTERNAL);           // Display date / + button
Button buttonC = Button(4, BUTTON_PULLUP_INTERNAL);           // Display temp / - button

// Global variables (changeable defaults), numbers in [] brackets are the EEPROM storage location for that value
byte i2cadd = 0;                         // EEPROM address on I2C bus, 0 means search for external with fail safe to internal EEPROM
byte intensity = 7;                      // [200] Default intensity/brightness (0-15), can be set via menu
byte clock_mode = 0;                     // [201] Default clock mode. Default = 0 (basic_mode)
byte font_style = 1;                     // [202] Default clock large font style
byte ntp_freq = 0;                       // [203] NTP update frequencey (0-2) where 0=daily, 1=weekly, 2=monthly
byte font_cols = 6;                      // [204] Default clock large font columns adjustment
byte hour_off = 21;                      // [205] Hour the display will turn off, format is 24 hours
byte min_off = 00;                       // [206] and minute the display will turn off
byte hour_on = 6;                        // [207] Hour the display will turn on, format is 24 hours
byte min_on = 00;                        // [208] and minute the display will turn on
byte fade_timer = 20;                    // [209] fade down timer in milliseconds
byte ntp_hour = 3;                       // [210] hour to perform NTP check
byte ntp_dow = 1;                        // [211] day of week to perform ntp check
byte ntp_dom = 1;                        // [212] day of month to perform ntp check
byte colon_mode = 0;                     // [213] Colon display mode (0-x), where 0=sync flash, 1=alternate flash, 2=solid on

bool random_mode = false;                // [220] Define random mode - changes the display type every few hours. Default = 0 (off)
bool random_font_mode = false;           // [221] Define font random mode - changes the font every few hours. true = random font on
bool ampm = false;                       // [222] Define 12 or 24 hour time. false = 24 hour. true = 12 hour
bool display_mode = false;               // [223] Default display on/off mode. false = always on, true = determined by timer
bool auto_intensity = false;             // [224] Default auto light intensity setting
bool dst_mode = true;                    // [225] Enable dst function. false = disable, true = enable
bool ntp_mode = false;                   // [226] Enable NTP function. false = disable, true = enable
bool dst = true;                         // [227] Holds dst applied value, true = summertime +1hr applied, this is to ensure dst +1/-1 runs only once

int8_t utc_offset = 10;                  // [229] UTC offset adjustment in hours

// Global run time variables
bool shut = false;                       // Stores matrix on/off state, true means display off, false means display on
bool o_shut = shut;                      // Stores the previous matrix on/off state.  used for waking display when a button pressed and asleep
byte old_mode = clock_mode;              // Stores the previous clock mode, so if we go to date or whatever, we know what mode to go back to after.
byte change_mode_time = 0;               // Holds hour when clock mode will next change if in random mode.
byte auto_intensity_value = 0;           // Stores the last intensity value set by the light sensor, this value is set automatically
byte used_font[NUM_FONTS + 1];           // Array to hold the used font, to ensure all font's are used when 'random font' is enabled
bool ntp_run = 0;                        // Holds the value to see if ntp() has run once a day
bool dst_run = 0;                        // Holds the value to see if dst() has run once a day
char str_a[9];                           // Holds the text to be displayed
byte knight_rider = 0;                   // Holds sequence value for knight rider colon mode
bool knight_prev_run = 0;
//long knight_previous_millis = 0;

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

  //pinMode(8, OUTPUT);                           // I use this to control the LEDs on the switches
  //digitalWrite(8, HIGH);                        // Turn on the power for LED switches

  Wifi_Serial.begin(9600); //start serial with ESP8266 Wifi module - software serial max baud = 9600

#if defined(ARDUINOSERIAL) || defined(ARDUINOSERIALDEBUG) || defined(ARDUINOSERIALDEBUGESP) || defined (ARDUINOSERIALDEBUGSTARTUP) || defined(ARDUINOSERIALDEBUGWIFISETUP)
  ARDUINO_SERIAL.begin(BAUD);
  ARDUINO_SERIAL.println(F("32x8 clock start"));
#endif

  // Fill used_font array with zero's
  for (byte c = 0; c < (NUM_FONTS + 1); c++) {
    used_font[c] = 0;
  }
  
  //Needed for random() to work properly
  randomSeed(analogRead(RANDOMSEED));
  
  // I2C
  Wire.begin();


  // Detect EEPROM - looking for EEPROM on RTC board, if not present default to onchip EEPROM
  // 8 possible address locations for EEPROM on DS3231 board.  0x50 (80 DEC) through to 0x57 (87 DEC)

  if(i2cadd != 0) {

#ifdef ARDUINOSERIALDEBUGSTARTUP
  ARDUINO_SERIAL.print(F("Testing manual EEPROM address: 0x"));
  ARDUINO_SERIAL.println(i2cadd, HEX);
#endif

  Wire.beginTransmission(i2cadd);
  if (Wire.endTransmission() == 0) {

#ifdef ARDUINOSERIALDEBUGSTARTUP
    ARDUINO_SERIAL.print(F("Manual EEPROM at: 0x"));
    ARDUINO_SERIAL.print(i2cadd, HEX);
    ARDUINO_SERIAL.println(F(" Responding"));
#endif

  }
  else {

#ifdef ARDUINOSERIALDEBUGSTARTUP
  ARDUINO_SERIAL.print(F("Manual EEPROM at: 0x"));
  ARDUINO_SERIAL.print(i2cadd, HEX);
  ARDUINO_SERIAL.println(F(" Not responding, using internal"));
#endif

    i2cadd = 0;  
    }
  }
  else {

  //if (i2cadd == 0) {
    byte EEAddress = 80;
    bool EEFound = false;
    
    while (EEAddress < 88 && !EEFound) {
    
#ifdef ARDUINOSERIALDEBUGSTARTUP
      ARDUINO_SERIAL.print(F("Testing EEPROM address: 0x"));
      ARDUINO_SERIAL.println(EEAddress, HEX);
#endif
    
      Wire.beginTransmission(EEAddress);
      if (Wire.endTransmission() == 0) {
        i2cadd = EEAddress;
        EEFound = true;

#ifdef ARDUINOSERIALDEBUGSTARTUP
        ARDUINO_SERIAL.print(F("Found EEPROM at: 0x"));
        ARDUINO_SERIAL.println(i2cadd, HEX);
#endif

      }
      EEAddress++;  
    }
    if (!EEFound) {

#ifdef ARDUINOSERIALDEBUGSTARTUP
      ARDUINO_SERIAL.println(F("No External EEPROM"));
      ARDUINO_SERIAL.println(F("Using internal"));
#endif

      i2cadd = 0;
    }
  }

  byte FirstRun = eeprom_read_byte(FIRSTRUNADDRESS);
  bool FR = 0;
  if (FirstRun == FIRSTRUNVALUE) FR = 1;

  byte value1;
  bool value2;
  int8_t value3;

#ifdef ARDUINOSERIALDEBUGSTARTUP
  ARDUINO_SERIAL.print(F("Read FirstRun: ["));
  ARDUINO_SERIAL.print(FIRSTRUNADDRESS);
  ARDUINO_SERIAL.print(F("] "));
  ARDUINO_SERIAL.println(FirstRun);
#endif

  byte i = 200;
  while (i < 230) {
    if (i <= 213) {

      value1 = eeprom_read_byte(i);
      if (i == 200) {
        if (FR) {
          intensity = value1;
        }
        else {
          value1 = intensity;
        }
      }
      else if (i == 201) {
        if (FR) {
          clock_mode = value1;
          old_mode = clock_mode;
        }
        else {
          value1 = clock_mode;
          old_mode = clock_mode;
        }
      }
      else if (i == 202) {
        if (FR) {
          font_style = value1;
        }
        else {
          value1 = font_style;
        }
      }
      else if (i == 203) {
        if (FR) {
          ntp_freq = value1;
        }
        else {
          value1 = ntp_freq;
        }
      }
      else if (i == 204) {
        if (FR) {
          font_cols = value1;
        }
        else {
          value1 = font_cols;
        }
      }
      else if (i == 205) {
        if (FR) {
          hour_off = value1;
        }
        else {
          value1 = hour_off;
        }
      }
      else if (i == 206) {
        if (FR) {
          min_off = value1;
        }
        else {
          value1 = min_off;
        }
      }
      else if (i == 207) {
        if (FR) {
          hour_on = value1;
        }
        else {
          value1 = hour_on;
        }
      }
      else if (i == 208) {
        if (FR) {
          min_on = value1;
        }
        else {
          value1 = min_on;
        }
      }
      else if (i == 209) {
        if (FR) {
          fade_timer = value1;
        }
        else {
          value1 = fade_timer;
        }
      }
      else if (i == 210) {
        if (FR) {
          ntp_hour = value1;
        }
        else {
          value1 = ntp_hour;
        }
      }
      else if (i == 211) {
        if (FR) {
          ntp_dow = value1;
        }
        else {
          value1 = ntp_dow;
        }
      }
      else if (i == 212) {
        if (FR) {
          ntp_dom = value1;
        }
        else {
          value1 = ntp_dom;
        }
      }
      else if (i == 213) {
        if (FR) {
          colon_mode = value1;
        }
        else {
          value1 = colon_mode;
        }
      }      
      
      if (!FR) {
        eeprom_save(i, value1, 0, 0);

#ifdef ARDUINOSERIALDEBUGSTARTUP
        ARDUINO_SERIAL.print("FirstRun - Update Byte: [");
        ARDUINO_SERIAL.print(i);
        ARDUINO_SERIAL.print("] ");
        ARDUINO_SERIAL.println(value1);
#endif

      }

#ifdef ARDUINOSERIALDEBUGSTARTUP
      ARDUINO_SERIAL.print(F("Read Byte: ["));
      ARDUINO_SERIAL.print(i);
      ARDUINO_SERIAL.print(F("] "));
      ARDUINO_SERIAL.println(value1);
#endif

    }
    else if (i >= 220 && i <= 227) {

      value2 = eeprom_read_bool(i);
      if (i == 220) {
        if (FR) {
          random_mode = value2;
        }
        else {
          value2 = random_mode;
        }
      }
      else if (i == 221) {
        if (FR) {
          random_font_mode = value2;
        }
        else {
          value2 = random_font_mode;
        }
      }
      else if (i == 222) {
        if (FR) {
          ampm = value2;
        }
        else {
          value2 = ampm;
        }
      }
      else if (i == 223) {
        if (FR) {
          display_mode = value2;
        }
        else {
          value2 = display_mode;
        }
      }
      else if (i == 224) {
        if (FR) {
          auto_intensity = value2;
        }
        else {
          value2 = auto_intensity;
        }
      }
      else if (i == 225) {
        if (FR) {
          dst_mode = value2;
        }
        else {
          value2 = dst_mode;
        }
      }
      else if (i == 226) {
        if (FR) {
          ntp_mode = value2;
        }
        else {
          value2 = ntp_mode;
        }
      }
      else if (i == 227) {
        if (FR) {
          dst = value2;
        }
        else {
          value2 = dst;
        }
      }
      if (!FR) {
        eeprom_save(i, 0, value2, 0);

#ifdef ARDUINOSERIALDEBUGSTARTUP
        ARDUINO_SERIAL.print(F("FirstRun - Update Bool: ["));
        ARDUINO_SERIAL.print(i);
        ARDUINO_SERIAL.print(F("] "));
        ARDUINO_SERIAL.println(value2);
#endif

      }

#ifdef ARDUINOSERIALDEBUGSTARTUP
      ARDUINO_SERIAL.print(F("Read Bool: ["));
      ARDUINO_SERIAL.print(i);
      ARDUINO_SERIAL.print(F("] "));
      ARDUINO_SERIAL.println(value2);
#endif

    }
    else if (i == 229) {
      value3 = eeprom_read_int8_t(i);
      if (FR) {
        utc_offset = value3;
      }
      else {
        value3 = utc_offset;
      }
      if (!FR) {
        eeprom_save(i, 0, 0, value3);

#ifdef ARDUINOSERIALDEBUGSTARTUP
        ARDUINO_SERIAL.print(F("FirstRun - Update int8_t:"));
        ARDUINO_SERIAL.print(i);
        ARDUINO_SERIAL.print(F("] "));
        ARDUINO_SERIAL.println(value3);
#endif

      }

#ifdef ARDUINOSERIALDEBUGSTARTUP
      ARDUINO_SERIAL.print(F("Read int8_t: ["));
      ARDUINO_SERIAL.print(i);
      ARDUINO_SERIAL.print(F("] "));
      ARDUINO_SERIAL.println(value3);
#endif

    }
    i++;
  }

  if (!FR) {
    eeprom_save(FIRSTRUNADDRESS, FIRSTRUNVALUE, 0, 0);

#ifdef ARDUINOSERIALDEBUGSTARTUP
    ARDUINO_SERIAL.print(F("Update FirstRun: ["));
    ARDUINO_SERIAL.print(FIRSTRUNADDRESS);
    ARDUINO_SERIAL.print(F("] "));
    ARDUINO_SERIAL.println(FIRSTRUNVALUE);
#endif

  }
  
  // initialize the 4 matrix panels
  // we have already set the number of devices when we created the LedControl
  int devices = lc.getDeviceCount();
  // we have to init all devices in a loop
  for (int address = 0; address < devices; address++) {
    lc.shutdown(address, false);         // The MAX72XX is in power-saving mode on startup
    lc.setIntensity(address, intensity); // Set the brightness to a medium values
    delay(20);
    lc.clearDisplay(address);            // and clear the display
  }

  // Show software version & startup message
  printver();

  // Setup DS3231 RTC

#if defined(ARDUINOSERIAL) || defined(ARDUINOSERIALDEBUG) || defined(ARDUINOSERIALDEBUGESP)|| defined(ARDUINOSERIALDEBUGSTARTUP)
    ARDUINO_SERIAL.println(F("Checking RTC"));
#endif

  setSyncProvider(myRTC.get);
  //myRTC.begin();
  //timelibrary default sync interval is 300 seconds, uncomment line below to customise
  //setSyncInterval(x);            //sync Time Library to RTC every x seconds

  if (timeStatus() != timeSet) {
#if defined(ARDUINOSERIAL) || defined(ARDUINOSERIALDEBUG) || defined(ARDUINOSERIALDEBUGESP)|| defined(ARDUINOSERIALDEBUGSTARTUP)
    ARDUINO_SERIAL.println(F("Couldn't find RTC"));
#endif
    no_rtc();
    while (1);
  }
  if (myRTC.oscStopped(false) && !ntp_mode) {
    set_time();
  }
  else if (ntp_mode) {
    ntp();
  }
  // initialize the alarms to known values, clear the alarm flags, clear the alarm interrupt flags
  myRTC.setAlarm(DS3232RTC::ALM1_MATCH_DATE, 0, 0, 0, 1);
  myRTC.setAlarm(DS3232RTC::ALM2_MATCH_DATE, 0, 0, 0, 1);
  myRTC.alarm(DS3232RTC::ALARM_1);
  myRTC.alarm(DS3232RTC::ALARM_2);
  myRTC.alarmInterrupt(DS3232RTC::ALARM_1, false);
  myRTC.alarmInterrupt(DS3232RTC::ALARM_2, false);
  myRTC.squareWave(DS3232RTC::SQWAVE_NONE);

}

void loop() {
  // put your main code here, to run repeatedly:
  switch (clock_mode) {
    case 0:
      basic_mode();
      break;
    case 1:
      small_mode();
      break;
    case 2:
      slide();
      break;
    case 3:
      word_clock();
      break;            
    case 4:
      o_shut = shut;
      //if display is shut, wake it up
      if (o_shut) {
        shut = false;
        set_devices(false, 0);
        light();
      }
      
      setup_menu();
      
      if (o_shut) {
        o_shut = false;
        shut = true;
        set_devices(false, 0);
      }  
      break;
  }
}

// power up led test & display software version number
void printver() {

  byte i = 0;
  byte len = 0;

  // set brightness
    light();

  // test all leds.
  for (byte x = 0; x <= 31; x++) {
    for (byte y = 0; y <= 7; y++) {
      plot(x, y, 1);
    }
  }
  delay(500);
  fade_down();

  strcpy (str_a, "Ver 2.7");
  
  // get length of message and center it
  while (str_a[len]) {
    len++;
  }
  // work out center offset
  byte offset_top = (31 - ((len - 1) * 4)) / 2;
  // print message with slight delay between characters
  while (str_a[i]) {
    puttinychar(i * 4 + offset_top, 1, str_a[i]);
    delay(40);
    i++;
  }
  delay(700);
  fade_down();

  len = 0;
  i = 0;
  strcpy (str_a, "Potty");
  
  // get length of message and center it
  while (str_a[len]) {
    len++;
  }
  // work out center offset
  offset_top = (30 - ((len - 1) * 4)) / 2;
  // print message with slight delay between characters
  while (str_a[i]) {
    puttinychar(i * 4 + offset_top, 1, str_a[i]);
    delay(40);
    i++;
  }
  delay(700);
  fade_down();

}

// Show a 'NO RTC' error message on LEDS
void no_rtc() {
  //char str_a[6];
  byte len = 0;
  byte i = 0;
  
  strcpy (str_a, "No RTC");
    while (str_a[len]) {
    len++;
  }
  byte offset_top = (31 - ((len - 1) * 4)) / 2;
  
  while (str_a[i]) {
    puttinychar(i * 4 + offset_top, 1, str_a[i]);
    i++;
  }
}

// save settings to Arduino EEPROM
void eeprom_save(byte Address, byte value1, bool value2, int8_t value3) {

  if (i2cadd != 0) {
    Wire.beginTransmission(i2cadd);
  }

  switch (Address) {
    case 0 ... 70:
      EEPROM.update(Address, value1);
      break;
    case 200 ... 213:
      EEPROM.update(Address, value1);

#ifdef ARDUINOSERIALDEBUG
      ARDUINO_SERIAL.print("Update Byte: [");
      ARDUINO_SERIAL.print(Address);
      ARDUINO_SERIAL.print("] ");
      ARDUINO_SERIAL.println(value1);
#endif

      break;
    case 220 ... 227:
      EEPROM.update(Address, value2);

#ifdef ARDUINOSERIALDEBUG
      ARDUINO_SERIAL.print("Update Bool: [");
      ARDUINO_SERIAL.print(Address);
      ARDUINO_SERIAL.print("] ");
      ARDUINO_SERIAL.println(value2);
#endif

      break;
    case 229:
      EEPROM.update(Address, value3);

#ifdef ARDUINOSERIALDEBUG
      ARDUINO_SERIAL.print("Update int8_t: [");
      ARDUINO_SERIAL.print(Address);
      ARDUINO_SERIAL.print("] ");
      ARDUINO_SERIAL.println(value3);
#endif

      break;
    case 255:
      EEPROM.update(Address, value1);

#ifdef ARDUINOSERIALDEBUG
      ARDUINO_SERIAL.print("Update Byte: [");
      ARDUINO_SERIAL.print(Address);
      ARDUINO_SERIAL.print("] ");
      ARDUINO_SERIAL.println(value1);
#endif

      break;
  }

}

// read EEPROM data as byte (unsigned 8 bit value)
byte eeprom_read_byte(byte Address) {

  if (i2cadd != 0) {
    Wire.beginTransmission(i2cadd);
  }

  byte result = EEPROM.read(Address);
  return result;

}

// read EEPROM data as int8_t (signed 8 bit value - used for Time Zone)
int8_t eeprom_read_int8_t(byte Address) {

  if (i2cadd != 0) {
    Wire.beginTransmission(i2cadd);
  }

  int8_t result = (int8_t)EEPROM.read(Address);
  return result;

}

// read EEPROM data as bool
bool eeprom_read_bool(byte Address) {

  if (i2cadd != 0) {
    Wire.beginTransmission(i2cadd);
  }

  bool result = (bool)EEPROM.read(Address);
  return result;

}

// plot a point on the display
void plot(byte x, byte y, byte val_) {

  // select which matrix depending on the x coord
  byte address;
  if (x >= 0 && x <= 7)   {
    address = MATRIX0;
  }
  if (x >= 8 && x <= 15)  {
    address = MATRIX1;
    x = x - 8;
  }
  if (x >= 16 && x <= 23) {
    address = MATRIX2;
    x = x - 16;
  }
  if (x >= 24 && x <= 31) {
    address = MATRIX3;
    x = x - 24;
  }

  if (val_ == 1) {
    lc.setLed(address, y, x, true);
  } else {
    lc.setLed(address, y, x, false);
  }
}

// clear screen
void clear_display() {

  for (byte address = 0; address < 4; address++) {
    lc.clearDisplay(address);
  }

}

// fade screen down
void fade_down() {

  // to hold temp intensity value
  byte ix = 0;
  if (auto_intensity) {
    // use the last light sensor intensity settings, prevents display from constantly flicking between global and light sensor value
    ix = auto_intensity_value;  
  }
  else {
    ix = intensity;
  }

  // fade from global intensity to 1
  for (byte i = ix; i > 0; i--) {
    for (byte address = 0; address < 4; address++) {
      lc.setIntensity(address, i);
    }
    // change this to change fade down speed
    delay(fade_timer);
  }

  // clear display completely (off)
  clear_display();

  // reset intentsity to global val
  for (byte address = 0; address < 4; address++) {
    lc.setIntensity(address, ix);
  }

}

// puttinychar
// Copy a 3x5 character glyph from the myfont data structure to display memory, with its upper left at the given coordinate
// This is unoptimized and simply uses plot() to draw each dot.
// x = x co-ordinate
// y = y co-ordinate
// c = character to be displayed
void puttinychar(byte x, byte y, char c) {

  byte dots;
  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
    c &= 0x1F;   // A-Z maps to 1-26
  }
  else if (c >= '0' && c <= '9') {
    c = (c - '0') + 35;
  }
  else if (c == ' ') {
    c = 0; // space
  }
  else if (c == '.') {
    c = 27; // full stop
  }
  else if (c == ':') {
    c = 28; // colon
  }
  else if (c == '\'') {
    c = 29; // single quote mark
  }
  else if (c == '>') {
    c = 30; // >
  }
  else if (c == '-') {
    c = 31; // -
  }
  else if (c == '/') {
    c = 32; // forward slash
  }
  else if (c == '+') {
    c = 33; // plus
  }
  else if (c == '?') {
    c = 34; // question mark
  }

  for (byte col = 0; col < 3; col++) {
    dots = pgm_read_byte_near(&mytinyfont[c][col]);
    for (char row = 0; row < 5; row++) {
      if (dots & (16 >> row))
        plot(x + col, y + row, 1);
      else
        plot(x + col, y + row, 0);
    }
  }

}

// putnormalchar
// Copy a 5x7 character glyph from the myfont data structure to display memory, with its upper left at the given coordinate
// This is unoptimized and simply uses plot() to draw each dot.
// take the following input values
// x = x co-ordinate
// y = y co-ordinate - not needed as we are using a single matrix row
// c = character to be displayed
// fs = font_style - these two are global variables, why pass them in again?
// fc = font_cols
void putnormalchar(byte x, byte c) {

  byte dots;
  
  c = map_font(c);
  for (byte col = 0; col < font_cols; col++) {
    dots = pgm_read_byte_near(&myfont[c][col]);
    for (byte row = 0; row < 7; row++) {
      if (dots & (64 >> row)) {   // only 7 rows.
        plot(x + col, row, 1);
      } else {
        plot(x + col, row, 0);
      }
    }
  }
}

// show the time in small 3x5 (width x Height) characters with seconds display
void small_mode() {

  char textchar[2]; // the 8 characters on the display
  byte old_hours = 25; // hours
  byte old_mins = 61; //mins
  byte old_secs = 61; //holds old seconds value - from last time seconds were updated o display - used to check if seconds have changed
  byte light_count = 201; //light counter, set to large value to ensre light routine is run upon entering
  unsigned long wake_start = millis();
  
  clear_display();

    // draw the ':' once as they wont change
    puttinychar( 8, 1, ':');
    puttinychar( 20, 1, ':');
    //plot dot top left to represent PM when in 12 hour mode
    plot_pm_dot();

  //run clock main loop as long as run_mode returns true
  while (run_mode()) {

    // keep display awake for WAKE_DELAY time when first entering routine and display is shut
    if (o_shut) {
      if (millis() - wake_start <= WAKE_DELAY) {
        shut = false;
        set_devices(false, 0);
      } 
      else {
        o_shut = false;
        shut = true;
        set_devices(false, 0);
      }   
    }
    
    //Check light levels for turning on/off matrix
    if (light_count > 200) {
      light();
      light_count = 0;
    }
    light_count++;

    //check for button press
    if (buttonA.uniquePress()) {
      switch_mode();
      return;
    }
    if (buttonB.uniquePress()) {
      display_date();
      return;
    }
    if (buttonC.uniquePress()) {
      display_thp();
      //ntp();
      return;
    }

    //if hour changed update on display
    if (old_hours != hour()) {
      //reset for comparison next time
      old_hours = hour();
      if (ampm) {
        itoa(hourFormat12(), textchar, 10);
        //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa converts this to chars with space "3 ".
        if (hourFormat12() < 10) {
          textchar[1] = textchar[0];
          textchar[0] = ' ';
        }       
      }
      else {
        itoa(hour(), textchar, 10);
        if (hour() < 10) {
          textchar[1] = textchar[0];
          textchar[0] = '0';
        }
      }
      //plot the values
      puttinychar( 0, 1, textchar[0]);
      puttinychar( 4, 1, textchar[1]);
    }
    
    //if minute changed update on display
    if (old_mins != minute()) {
      //reset for comparison next time
      old_mins = minute();
      itoa (minute(), textchar, 10);
      if (minute() < 10) {
        textchar[1] = textchar[0];
        textchar[0] = '0';
      }
      //plot the values
      puttinychar( 12, 1, textchar[0]);
      puttinychar( 16, 1, textchar[1]);
      
    }

    //if secs changed then update them on the display
    if (old_secs != second()) {
      //reset for comparison next time
      old_secs = second();
      
      //secs
      //char buff[3];
      itoa(second(), textchar, 10);

      //fix - as otherwise if num has leading zero, e.g. "03" secs, itoa converts this to chars with space "3 ".
      if (second() < 10) {
        textchar[1] = textchar[0];
        textchar[0] = '0';
      }

      puttinychar( 24, 1, textchar[0]); //seconds
      puttinychar( 28, 1, textchar[1]); //seconds
      
    }  
  
  }
  
  fade_down();
}

// show the time in (Width x Height) characters
void basic_mode() {

  // setup variables
  byte light_count = 201; // light counter, set to large value to ensre light routine is run upon entering
  byte old_mins = 61;     // initially set mins to a value so it wll never equal rtc[1] on the first loop of the clock, 
                          // meaning we draw the clock display when we enter the function
  byte digits_x_pos[] = {24, 17, 7, 1}; // default x pos for which to draw each digit at
                                        // [0-min ones],[1-min tens],[2-hour ones],[3-hour tens]
  unsigned long wake_start = millis();
  clear_display();

  // set digits position for larger font_style
  if (font_style > 2) {
    digits_x_pos[0] = 24;
    digits_x_pos[1] = 17;
    digits_x_pos[2] = 8;
    digits_x_pos[3] = 1;
  }

  //print dot to represent PM when in 12 hour mode
  plot_pm_dot();

  //run clock main loop as long as run_mode returns true
  while (run_mode()) {

    // keep display awake for WAKE_DELAY time when first entering routine and display is shut
    if (o_shut) {
      if (millis() - wake_start <= WAKE_DELAY) {
        shut = false;
        set_devices(false, 0);
      } 
      else {
        o_shut = false;
        shut = true;
        set_devices(false, 0);
      }   
    }
    
    //Check light levels for turning on/off matrix
    if (light_count > 200) {
      light();
      light_count = 0;
    }
    light_count++;

    //check for button press
    if (buttonA.uniquePress()) {
      switch_mode();
      return;
    }
    if (buttonB.uniquePress()) {
      display_date();
      return;
    }
    if (buttonC.uniquePress()) {
      display_thp();
      return;
    }

    // re draw the display if button pressed or if mins != rtc[1] i.e. if the time has changed from what we had stored in old_mins 
    // also trigggered on first entering function
    if (minute() != old_mins) {
      
      //update old_mins with the new values
      old_mins = minute();

      //print hour tens one digit
      putnormalchar(digits_x_pos[3], buf_hour(true));

      //print hour ones digit
      putnormalchar(digits_x_pos[2], buf_hour(false));

      //print mins tens digit
      putnormalchar(digits_x_pos[1], ((minute() / 10) % 10));
      
      //print mins ones digit
      putnormalchar(digits_x_pos[0], (minute() % 10));
      
    } // end of mins if statement
    
    /*
    //draw the flashing :
    if (font_style > 2) {
      plot (15, 2, ((millis() / 500) %2)); //top point
      plot (15, 5, ((millis() / 500) %2)); //bottom point
    }
    else {
      plot (14, 2, ((millis() / 500) %2)); //top point
      plot (14, 5, ((millis() / 500) %2)); //bottom point
    }
    */
    //plot colon dots
    plot_colon();

  } //end of while
  
  fade_down();
  
}

// like basic_mode but with slide effect
void slide() {

  // setup variables
  byte digits_old[] = {0, 0, 0, 0}; // old values  we store time in 
                                        // [0-min ones],[1-min tens],[2-hour ones],[3-hour tens]
                                        // Set to something that will never match the time initially 
                                        // so all digits get drawn when the mode starts
  byte digits_new[] = {0, 0, 0, 0};     // new digits time will slide to reveal, filling with dummy values
  byte digits_x_pos[] = {24, 17, 7, 1}; // x pos for which to draw each digit at
                                        // [0-min ones],[1-min tens],[2-hour ones],[3-hour tens]
  byte light_count = 201; // light counter, set to large value to ensure light routine is run upon entering
  bool firstrun = true;   //set first run to true so clock is drawn upon entry
  unsigned long wake_start = millis();
  unsigned long time_now = 0;

  clear_display();

  // set position for middle 2 digits with larger font_style
  if (font_style > 2) {
    digits_x_pos[0] = 24;
    digits_x_pos[1] = 17;
    digits_x_pos[2] = 8;
    digits_x_pos[3] = 1;
  }

  //print dot top right to represent PM when in 12 hour mode
  plot_pm_dot();
  
  
  //run clock main loop as long as run_mode returns true
  while (run_mode()) {

    // keep display awake for WAKE_DELAY time when first entering routine and display is shut
    if (o_shut) {
      if (millis() - wake_start <= WAKE_DELAY) {
        shut = false;
        set_devices(false, 0);
      } 
      else {
        o_shut = false;
        shut = true;
        set_devices(false, 0);
      }   
    }
    
    // Check light levels
    if (light_count > 200) {
      light();
      light_count = 0;
    }
    light_count++;

    //check for button press
    if (buttonA.uniquePress()) {
      switch_mode();
      return;
    }
    if (buttonB.uniquePress()) {
      display_date();
      return;
    }
    if (buttonC.uniquePress()) {
      display_thp();
      return;
    }

    // if minute has changed, calc and update accordingly
    if (digits_old[0] != (minute() % 10) || firstrun) {

      // split all date and time into individual digits - stick in digits_new array
      digits_new[0] = (minute() % 10);         // mins ones
      digits_new[1] = ((minute() / 10) % 10);  // mins tens
      digits_new[2] = (buf_hour(false));       // hour ones
      digits_new[3] = (buf_hour(true));        // hour tens

      // compare digits 0 to 3 (mins and hours)
      for (byte k = 0; k <= 3; k++) {
        // see if digit has changed...
        if (digits_old[k] != digits_new[k] || firstrun) {

#ifdef ARDUINOSERIAL
    ARDUINO_SERIAL.print(F("slide() digits_old["));
    ARDUINO_SERIAL.print(k);
    ARDUINO_SERIAL.print(F("] value: '"));
    ARDUINO_SERIAL.print(digits_old[k]);
    ARDUINO_SERIAL.println("'");
    ARDUINO_SERIAL.print(F("slide() digits_new["));
    ARDUINO_SERIAL.print(k);
    ARDUINO_SERIAL.print(F("] value: '"));
    ARDUINO_SERIAL.print(digits_new[k]);
    ARDUINO_SERIAL.println("'");
#endif          

          // run 9 step animation sequence for each in turn
          for (byte seq = 0; seq <= 8 ; seq++) {
            //draw the animation frame for each digit
            //slideanim(digits_x_pos[i], 0, seq, old_char[0], new_char[0]);
            slideanim(digits_x_pos[k], seq, digits_old[k], digits_new[k]);
            //delay(SLIDE_DELAY);
            time_now = millis();
            while (millis() < time_now + SLIDE_DELAY) {
              //plot colon dots
              plot_colon();
            }
          }
          //save digit to old for comparison next loop
          digits_old[k] = digits_new[k];
        }
      }

      //set first run to false
      firstrun = false;

    }//end mins comparison

    //plot colon dots
    plot_colon();

  }// end while loop
  
  fade_down();
  
}

// called by slide
// this draws the animation of one char sliding on and the other sliding off. There are 8 steps in the animation, we call the function to draw one of the steps from 0-7
// inputs are are char x and y, animation frame sequence (0-7) and the current and new chars being drawn.
void slideanim(byte x, byte sequence, byte current_c, byte new_c) {

  //  To slide one char off and another on we need 9 steps or frames in sequence...

  //  seq# 0123456 <-rows of the display
  //   |   |||||||
  //  seq0 0123456  START - all rows of the display 0-6 show the current characters rows 0-6
  //  seq1  012345  current char moves down one row on the display. We only see it's rows 0-5. There are at display positions 1-6 There is a blank row inserted at the top
  //  seq2 6 01234  current char moves down 2 rows. we now only see rows 0-4 at display rows 2-6 on the display. Row 1 of the display is blank. Row 0 shows row 6 of the new char
  //  seq3 56 0123
  //  seq4 456 012  half old / half new char
  //  seq5 3456 01
  //  seq6 23456 0
  //  seq7 123456
  //  seq8 0123456  END - all rows show the new char

  //from above we can see...
  //currentchar runs 0-6 then 0-5 then 0-4 all the way to 0. starting Y position increases by 1 row each time.
  //new char runs 6 then 5-6 then 4-6 then 3-6. starting Y position increases by 1 row each time.

  byte dots;

  //if sequence number is below 7, we need to draw the current char
  if (sequence < 7) {

    current_c = map_font(current_c);
    //byte curr_char_row_max = 7 - sequence; //the maximum number of rows to draw is 6 - sequence number
    byte start_y = sequence; //y position to start at - is same as sequence number. We inc this each loop

    //plot each row up to row maximum (calculated from sequence number)
    for (byte curr_char_row = 0; curr_char_row <= (7 - sequence); curr_char_row++) {
      for (byte col = 0; col < font_cols; col++) {
        dots = pgm_read_byte_near(&myfont[current_c][col]);
        if (dots & (64 >> curr_char_row))
          plot(x + col, start_y, 1); //plot led on
        else
          plot(x + col, start_y, 0); //else plot led off
      }
      start_y++;//add one to y so we draw next row one down
    }
  }

  //draw a blank line between the characters if sequence is between 1 and 7. If we don't do this we get the remnants of the current chars last position left on the display
  if (sequence >= 1 && sequence <= 8) {
    for (byte col = 0; col < font_cols; col++) {
      plot(x + col, (sequence - 1), 0); //the y position to draw the line is equivalent to the sequence number - 1
    }
  }


  //if sequence is above 2, we also need to start drawing the new char
  if (sequence >= 2) {

    //work out char
    new_c = map_font(new_c);
    //byte new_char_row_min = 6 - (sequence - 2); //minimumm row num to draw for new char - this generates an output of 6 to 0 when fed sequence numbers 2-8. This is the minimum row to draw for the new char
    byte start_y = 0; //y position to start at - is same as sequence number. we inc it each row

    //plot each row up from row minimum (calculated by sequence number) up to 6
    for (byte new_char_row = 6 - (sequence - 2); new_char_row <= 6; new_char_row++) {
      for (byte col = 0; col < font_cols; col++) {
        dots = pgm_read_byte_near(&myfont[new_c][col]);
        if (dots & (64 >> new_char_row))
          plot(x + col, start_y, 1); //plot led on
        else
          plot(x + col, start_y, 0); //else plot led off
      }
      start_y++;//add one to y so we draw next row one down
    }
  }

}

// print a clock using words rather than numbers
void word_clock() {

  // potentially 3 lines to display
  //char str_a[9];
  char str_b[9];
  char str_c[9];

  clear_display();

  while (run_mode()) {
    
    // update display brightess
    light();

    switch (minute()) {
      case 0:
        // read hour 'o'clock'
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[4])));
        strcpy (str_c, "");
        break;
      case 1 ... 14:
        // read mins, 'past' hour
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(minute() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[0])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        break;
      case 15:
        // use the word 'quarter' in place of number 15
        strcpy_P(str_a, (char *)pgm_read_word(&(cwords[3])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[0])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        break;
      case 16 ... 19:
        // use 'teen' wording for minute numbers - note: seventeen shows as "sevent'n"
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(minute() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[0])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        break;
      case 20:
        // use the 'tens' range from promem
        strcpy_P(str_a, (char *)pgm_read_word(&(numberstens[1])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[0])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        break;
      case 21 ... 29:
        // read hours, tens, units
        // split minute() value up into two separate digits; unit and tens
        // unit = minute() % 10;
        // tens = (minute() / 10) % 10;
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(numberstens[(((minute() / 10) % 10) - 1)])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[((minute() % 10) - 1)])));
        break;
      case 30:
        // read "half" "past" hour
        strcpy_P(str_a, (char *)pgm_read_word(&(cwords[2])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[0])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        break;
      case 31 ... 39:
        // read hours, tens, units
        // split minute() value up into two separate digits; unit and tens
        // unit = minute() % 10;
        // tens = (minute() / 10) % 10;
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(numberstens[(((minute() / 10) % 10) - 1)])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[((minute() % 10) - 1)])));
        break;
      case 40:
        // read 'twenty' to the hour
        strcpy_P(str_a, (char *)pgm_read_word(&(numberstens[1])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[1])));
        if (hourFormat12() == 12) {
          strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - hourFormat12())])));
        }
        else {
          strcpy_P(str_c, (char *)pgm_read_word(&(numbers[hourFormat12()])));
        }
        break;
      case 41 ... 44:
        // read hours, tens, units
        // split minute() value up into two separate digits; unit and tens
        // unit = minute() % 10;
        // tens = (minute() / 10) % 10;
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(numberstens[(((minute() / 10) % 10) - 1)])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[((minute() % 10) - 1)])));
        break;
      case 45:
        // read 'quarter' 'to' the hour
        strcpy_P(str_a, (char *)pgm_read_word(&(cwords[3])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[1])));
        if (hourFormat12() == 12) {
          strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - hourFormat12())])));
        }
        else {
          strcpy_P(str_c, (char *)pgm_read_word(&(numbers[hourFormat12()])));
        }
        break;
      case 46 ... 49:
        // read hours, tens, units
        // split minute() value up into two separate digits; unit and tens
        // unit = minute() % 10;
        // tens = (minute() / 10) % 10;
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(hourFormat12() - 1)])));
        strcpy_P(str_b, (char *)pgm_read_word(&(numberstens[(((minute() / 10) % 10) - 1)])));
        strcpy_P(str_c, (char *)pgm_read_word(&(numbers[((minute() % 10) - 1)])));
        break;
      case 50 ... 59:
        // read 60-(mins+1) "to" hour
        strcpy_P(str_a, (char *)pgm_read_word(&(numbers[(60 - (minute() + 1))])));
        strcpy_P(str_b, (char *)pgm_read_word(&(cwords[1])));
        if (hourFormat12() == 12) {
          strcpy_P(str_c, (char *)pgm_read_word(&(numbers[(hourFormat12() - 12)])));
        }
        else {
          strcpy_P(str_c, (char *)pgm_read_word(&(numbers[hourFormat12()])));
        }
        break;
      default:
        strcpy (str_a, "");
        strcpy (str_b, "");
        strcpy (str_c, "");
        break;
    }// end working out time

// debug print strings to serial port for sanity checking
#ifdef ARDUINOSERIAL
    ARDUINO_SERIAL.print(F("Line A: "));
    ARDUINO_SERIAL.println(str_a);
    ARDUINO_SERIAL.print(F("Line B: "));
    ARDUINO_SERIAL.println(str_b);
    ARDUINO_SERIAL.print(F("Line C: "));
    ARDUINO_SERIAL.println(str_c);
#endif

    // print line a
    byte len = 0;
    while (str_a[len]) {
      len++;
    }; // get length of message
    byte offset_top = (31 - ((len - 1) * 4)) / 2;
    byte i = 0;
    while (str_a[i]) {
      puttinychar(i * 4 + offset_top, 1, str_a[i]);
      i++;
    }
    // hold display while checking for button presses
    int counter = 1000;
    while (counter > 0) {
      // check for button press
      if (buttonA.uniquePress()) {
        switch_mode();
        return;
      }
      if (buttonB.uniquePress()) {
        display_date();
        return;
      }
      if (buttonC.uniquePress()) {
        display_thp();
        return;
      }

      delay(1);
      counter--;
    }
    fade_down();

    // print line b
    len = 0;
    while (str_b[len]) {
      len++;
    }; // get length of message
    offset_top = (31 - ((len - 1) * 4)) / 2;
    i = 0;
    while (str_b[i]) {
      puttinychar(i * 4 + offset_top, 1, str_b[i]);
      i++;
    }
    // hold display while checking for button presses
    counter = 1000;
    while (counter > 0) {
      if (buttonA.uniquePress()) {
        switch_mode();
        return;
      }
      if (buttonB.uniquePress()) {
        display_date();
        return;
      }
      if (buttonC.uniquePress()) {
        display_thp();
        return;
      }
      delay(1);
      counter--;
    }
    fade_down();

    // print line c
    // get length of message
    len = 0;
    while (str_c[len]) {
      len++;
    };
    // skip if nothing to print
    if (len != 0) {
      // center and display the message
      offset_top = (31 - ((len - 1) * 4)) / 2;
      i = 0;
      while (str_c[i]) {
        puttinychar(i * 4 + offset_top, 1, str_c[i]);
        i++;
      }
      // hold display while checking for button presses
      counter = 1000;
      while (counter > 0) {
        // check for button press
        if (buttonA.uniquePress()) {
          switch_mode();
          return;
        }
        if (buttonB.uniquePress()) {
          display_date();
          return;
        }
        if (buttonC.uniquePress()) {
          display_thp();
          return;
        }
        delay(1);
        counter--;
      }
      fade_down();
    }

    // hold display while checking for button presses before starting again.
    counter = 1000;
    while (counter > 0) {
      // check for button press
      if (buttonA.uniquePress()) {
        switch_mode();
        return;
      }
      if (buttonB.uniquePress()) {
        display_date();
        return;
      }
      if (buttonC.uniquePress()) {
        display_thp();
        return;
      }
      delay(1);
      counter--;
    }// end while counter

#ifdef ARDUINOSERIALDEBUG
  ARDUINO_SERIAL.print(F("Free Ram: "));
  ARDUINO_SERIAL.print(freeRam());
  ARDUINO_SERIAL.println(F(" Bytes"));
#endif

  } //end while runmode
}

// display_thp - print temperature, humidity and pressue
void display_thp() {

  //setup variables
  byte i = 0;
  //byte temp[7];
  o_shut = shut;
  
  clear_display();
  
  if (o_shut) {
    //o_shut = true;
    shut = false;
    set_devices(false, 0);
    light();
  }
  
  //put temperature from DS3231 into temp array, maximum 5 places (including 0)
  dtostrf(myRTC.temperature() / 4, 4, 1, /*temp*/str_a);

  //set brightness
  light();

  //print temp
  while (/*temp*/str_a[i]) {
    // map value of temp[i] from DEC to 0-9
    if (/*temp*/str_a[i] >= 48 && /*temp*/str_a[i] <= 57) {
      /*temp*/str_a[i] = /*temp*/str_a[i] - 48;
    }
    if (i == 0) {
      putnormalchar(i * font_cols, /*temp*/str_a[i]);
    } 
    else {
      putnormalchar(i * font_cols + 1, /*temp*/str_a[i]);
    }
    i++;
  }
  
  //put the degrees symbol at end
  byte old_font_style = font_style;
  byte old_font_cols = font_cols;
  set_font_case(1);
  putnormalchar(i * old_font_cols + 2, 128);
  set_font_case(old_font_style);

  delay(2000);
  fade_down();

  if (o_shut) {
    o_shut = false;
    shut = true;
    set_devices(false, 0);
  }    

}

// display_date - print the day of week, date and month
void display_date() {

  //setup variables
  //date suffix array
  const char suffix[4][3] = {"st", "nd", "rd", "th"};
  o_shut = shut;
  
  clear_display();

  if (o_shut) {
    shut = false;
    set_devices(false, 0);
    light();
  }

  //print the day name
  //get length of text in pixels, that way we can centre it on the display by divindin the remaining pixels b2 and using that as an offset
  //char dayfullname[9];
  strcpy_P(/*dayfullname*/str_a, (char *)pgm_read_word(&(daysfull[weekday() - 1])));
  byte len = 0;
  while (/*dayfullname*/str_a[len]) {
    len++;
  };
  byte offset = (31 - ((len - 1) * 4)) / 2; //our offset to centre up the text

  //print the name
  int i = 0;
  while (/*dayfullname*/str_a[i])
  {
    puttinychar(i * 4 + offset , 1, /*dayfullname*/str_a[i]);
    i++;
  }
  delay(1000);
  fade_down();
  clear_display();

  // print date numerals
  //char buffer[3];
  itoa(day(), /*buffer*/str_a, 10);
  offset = 10; //offset to centre text if 3 chars - e.g. 3rd

  // first work out date 2 letter suffix - eg st, nd, rd, th etc
  // char suffix[4][3]={"st", "nd", "rd", "th"  }; is defined at top of
  byte s = 3;
  if (day() == 1 || day() == 21 || day() == 31) {
    s = 0;
  }
  else if (day() == 2 || day() == 22) {
    s = 1;
  }
  else if (day() == 3 || day() == 23) {
    s = 2;
  }

  //print the 1st date number
  puttinychar(0 + offset, 1, /*buffer*/str_a[0]);

  //if date is under 10 - then we only have 1 digit so set positions of sufix etc one character nearer
  byte suffixposx = 4;

  //if date over 9 then print second number and set xpos of suffix to be 1 char further away
  if (day() > 9) {
    suffixposx = 10;
    puttinychar(4 + offset, 1, /*buffer*/str_a[1]);
    offset = 8; //offset to centre text if 4 chars
  }
  //print the 2 suffix characters
  puttinychar(suffixposx + offset, 1, suffix[s][0]);
  puttinychar(suffixposx + 4 + offset, 1, suffix[s][1]);
  delay(1000);
  fade_down();

  //print the month name
  //get length of text in pixels, that way we can centre it on the display by divindin the remaining pixels b2 and using that as an offset
  len = 0;
  //char monthfullname[9];
  strcpy_P(/*monthfullname*/str_a, (char *)pgm_read_word(&(monthsfull[month() - 1])));
  while (/*monthfullname*/str_a[len]) {
    len++;
  };
  offset = (31 - ((len - 1) * 4)) / 2; //our offset to centre up the text
  i = 0;
  while (/*monthfullname*/str_a[i])
  {
    puttinychar(i * 4 + offset, 1, /*monthfullname*/str_a[i]);
    i++;
  }
  delay(1000);
  fade_down();

  if (o_shut) {
    o_shut = false;
    shut = true;
    set_devices(false, 0);
  }  
    
}

// dislpay menu to change the clock mode
void switch_mode() {

  //not sure why, but this is needed to stop ampm bool getting messed up
  ampm = eeprom_read_bool(222);

  //remember mode we are in. We use this value if we go into settings mode, so we can change back from settings mode (6) to whatever mode we were in.
  old_mode = clock_mode;

  //remember current display status so we can change back when finished
  o_shut = shut;

  bool firstrun = true;

  //if display is shut, wake it up
  if (o_shut) {
    shut = false;
    set_devices(false, 0);
    light();
  }

  clear_display();
  
  //loop waiting for button (timeout after 35 loops to return to mode X)
  for (int count = 0; count < 35 ; count++) {

    //if user hits button, change the clock_mode
    if (buttonA.uniquePress() || firstrun ) {
      count = 0;
      if (!firstrun) {
        clock_mode++;
      }
      if (clock_mode > NUM_DISPLAY_MODES + 1) {
        clock_mode = 0;
      }

      //print arrow and current clock_mode name
      strcpy_P(str_a, (char *)pgm_read_word(&(cmodes[clock_mode])));
      byte i = 0;
      while (str_a[i]) {
        puttinychar(i * 4, 1, str_a[i]);
        i++;
      }
      firstrun = false;
    }
    delay(50);
  }
  if (old_mode != clock_mode && clock_mode < NUM_DISPLAY_MODES + 1) {
    //save the values to EEPROM
    eeprom_save(201, clock_mode, 0, 0);
  }

  //if display was shut, return to shut
  if (o_shut) {
    shut = true;
    set_devices(false, 0);
  }

}

// run clock main loop as long as run_mode returns true
bool run_mode() {

  // if random mode is on... check the hour when we change mode.
  if (random_mode || random_font_mode) {
    // if hour value in change mode time = hours. then return false = i.e. exit mode.
    if (change_mode_time == hour()) {
      // set the next random clock mode and time to change it
      set_next_random();
      // exit the current mode.
      return 0;
    }
  }

  if (ntp_mode) {
    if (!ntp_run) {
      switch (ntp_freq) {
        case 0:
        //daily ntp check
        if (hour() == ntp_hour) {
          ntp();
          ntp_run = 1;
        }
        break;
        case 1:
        //weekly ntp check
        if (weekday() == ntp_dow && hour() == ntp_hour) {
          ntp();
          ntp_run = 1;        
        }
        break;
        case 2:
        //monthly ntp check
        if (day() == ntp_dom && hour() == ntp_hour) {
          ntp();
          ntp_run = 1;        
        }
        break;
        default:
        break;
        }
      }
    // reset once a day ntp check so it runs next day
    if (hour() != ntp_hour && ntp_run) {
      ntp_run = 0;
    }
  }

  // run dst() calculation if dst_mode = 1
  if (dst_mode && !dst_run && (hour() == DST_ON_HOUR || hour() == DST_OFF_HOUR)) {
    dst_run = dstcalc();
  }

  // reset so dst is checked again
  if (dst_mode && dst_run && (hour() <= 1 || hour() >= 4)) {
    dst_run = 0;
  }

  // checks if display can be turned on/off
  if (display_mode) {
    // check off time and shut variable
    // shut true means display off, false means display on
    if (hour_off == hour() && min_off == minute()) {
      shut = true;
      set_devices(false, 0);
    }
    if (hour_on == hour() && min_on == minute()) {
      shut = false;
      set_devices(false, 0);
    }
  }

  // else return 1 - keep running in this mode
  return 1;
}

//set the next hour the clock will change mode when random mode is on, also does the random font mode
void set_next_random() {

  //set the next hour the clock mode will change - random value between 0 and 23
  change_mode_time = random(0, 23);

  if (random_mode) {
    //set the new clock mode
    clock_mode = random(0, NUM_DISPLAY_MODES + 1);  //pick new random clock mode
  }
  
  if (random_font_mode && !shut) {
    // due to lack of 'randomness' of the random number generator, forcing the use of every font
    // by storing in used_font array.  When the array is full (all fonts have been selected)
    // zero out the array and go again.
    // generate a random number between 1 and NUM_FONTS
    bool success = false;
    byte pos_num = 0;
    byte new_value = 0;
    
    // systematically go through the array starting at position 1
    while (!success) {
      pos_num++;
      if (used_font[pos_num] == 0) {
        new_value = random(1, NUM_FONTS + 1);
        while (used_font[new_value] == 1) {
          new_value = random(1, NUM_FONTS + 1);
        }
        used_font[new_value] = 1;
        set_font_case(new_value);  //pick new random font mode
        success = true;
      }
      else if (pos_num == 6 && used_font[6] == 1) {
        for (byte p = 0; p <= 6; p++) {
          used_font[p] = 0;
        }
      }
    }
  }

}

// dislpay menu to change the clock settings
void setup_menu() {

  byte setting_mode = 0;
  bool firstrun = true;
  
#ifdef ARDUINOSERIALDEBUG
  ARDUINO_SERIAL.print(F("Enter Setup Free Ram: "));
  ARDUINO_SERIAL.print(freeRam());
  ARDUINO_SERIAL.println(F(" Bytes"));
#endif

  //clear_display();
  // loop waiting for button (timeout after 40 loops [approx 2 seconds])
  for (byte count = 0; count < 40 ; count++) {

    // if user hits button, change the clock_mode
    if (buttonA.uniquePress() || firstrun) {
      count = 0;
      if (!firstrun) {
        setting_mode++;
      }
      if (setting_mode > NUM_SETTINGS_MODES) {
        setting_mode = 0;
      }

      // print arrow and current clock_mode name
      strcpy_P(str_a, (char *)pgm_read_word(&(smewords[setting_mode])));
      byte i = 0;
      while (str_a[i])
      {
        puttinychar(i * 4, 1, str_a[i]);
        i++;
      }

      firstrun = false;
      
    }
    
    delay(50);
    
  }

  // pick the mode
  switch (setting_mode) {
    case 0:
      set_random();
      break;
    case 1:
      set_random_font();
      break;
    case 2:
      set_ampm();
      break;
    case 3:
      set_font();
      break;
    case 4:
      set_ntp_dst();
      break;
    case 5:
      set_time();
      break;
    case 6:
      set_auto_intensity();
      break;
    case 7:
      set_intensity();
      break;
    case 8:
      set_display_options();
      break;
    case 9:
      set_wifi();
      break;
    case 10:
      set_ntp_options();
      break;
    case 11:
      // exit menu
      break;
  }

  // change the clock from mode 6 (settings) back to the one it was in before
  clock_mode = old_mode;

}

// set random mode - pick a different clock mode every few hours
void set_random() {

  // get current values
  //bool set_random_mode = eeprom_read_bool(220);

  // Set function - we pass in: which 'set' message to show at top, current value
  //set_random_mode = set_bool_value(set_random_mode);

  // set the values
  //random_mode = set_random_mode;
  
  random_mode = set_bool_value(random_mode);
  
  // set hour mode will change
  set_next_random();

  // save the values to EEPROM
  eeprom_save(220, 0, random_mode, 0);

}

// toggle random font
void set_random_font() {

  // get current values
  //bool set_random_font_mode = eeprom_read_bool(221);

  // Set function - we pass in: which 'set' message to show at top, current value
  //set_random_font_mode = set_bool_value(set_random_font_mode);

  // set the values
  //random_font_mode = set_random_font_mode;

  random_font_mode = set_bool_value(random_font_mode);

  // set hour mode will change
  set_next_random();

  // save the values to EEPROM
  eeprom_save(221, 0, random_font_mode, 0);

}

// set 12 or 24 hour clock
void set_ampm() {

  // get current values
  //bool set_ampm_mode = eeprom_read_bool(222);

  // Set function - we pass in: which 'set' message to show at top, current value
  //set_ampm_mode = set_bool_value(set_ampm_mode);

  // set the values
  //ampm = set_ampm_mode;

  ampm = set_bool_value(ampm);

  // save the values to EEPROM
  eeprom_save(222, 0, ampm, 0);

}

// set font style
void set_font() {

  clear_display();
  byte i = 0;
  byte current_value = font_style;
  char buff[2] = " ";
  char preview[3] = {1,2,3};

  itoa(current_value, buff, 10);
  puttinychar(4, 1, buff[0]);
  while (i < 3) {
    putnormalchar(i * (font_cols + 1) + 10, preview[i]);
    i++;
  }

  delay(300);
  // wait for button input
  while (!buttonA.uniquePress()) {
    while (buttonB.isPressed()) {
      if (current_value < NUM_FONTS) {
        current_value++;
      }
      else {
        current_value = 1;
      }
      // print the new value
      clear_display();
      itoa(current_value, buff, 10);
      puttinychar(4, 1, buff[0]);

      // preview the font and set the font
      set_font_case(current_value);
      i = 0;
      while (i < 3) {
        putnormalchar(i * (font_cols + 1) + 10, preview[i]);
        i++;
      }
      delay(150);
    }
    while (buttonC.isPressed()) {
      if (current_value > 1) {
        current_value--;
      }
      else {
        current_value = NUM_FONTS;
      }
      // print the new value
      clear_display();
      itoa(current_value, buff , 10);
      puttinychar(4, 1, buff[0]);
      
      // preview the font and set the font
      set_font_case(current_value);
      i = 0;
      while (i < 3) {
        putnormalchar(i * (font_cols + 1) + 10, preview[i]);
        i++;
      }
      delay(150);
    }
  }

  // save the values to EEPROM
  eeprom_save(202, font_style, 0, 0);
  eeprom_save(204, font_cols, 0, 0);

}

// set ntp and dst settings
void set_ntp_dst() {

  bool first_run = true;
  bool complete = false;
  byte dst_setting = 0;
  byte i;
  //char str_top[8];
  
  
  // get current values
  bool original_dst_mode = dst_mode;
  int8_t original_utc_offset = utc_offset;
  bool original_dst = dst;

  //clear_display();
  //print menu
  // loop waiting for button (timeout after 40 loops [approx 2 seconds])
  while (!complete){
    for (byte count = 0; count < 40 ; count++) {
      //if user hits button, change mode
      if (buttonA.uniquePress() || first_run) {
        count = 0;
        if (!first_run) {
          dst_setting++;
        }

        if (dst_setting > 3) {
          dst_setting = 0;
        }

        // print setting mode
        strcpy_P(str_a, (char *)pgm_read_word(&(dstwords[dst_setting])));
        i = 0;
        while (str_a[i]) {
          puttinychar(i * 4, 1, str_a[i]);
          i++;
        }      
        first_run = false;
      }
      delay(50);
    }

    // pick the mode
    switch (dst_setting) {
      case 0:
      // enable / disable DST calculations
      dst_mode = set_bool_value(dst_mode);
      dst_setting++;
      first_run = true;
      break;
      case 1:
      //summer - allow user to add / subtract 1 hour and update EEPROM
      dst = set_bool_value(dst);
      dst_setting++;
      first_run = true;
      break;
      case 2:
      utc_offset = set_value(utc_offset, -12, 12);
      dst_setting++;
      first_run = true;
      break;
      case 3:
      // exit menu
      complete = true;
      break;
    }
  }
  // if UTC offset has changed, work out differenct and apply to the clock
  if (utc_offset != original_utc_offset) {
    // Step 1 - Obtain current time from clock
    // Step 2 - Subtract old offset from clock
    // Step 3 - Add new offset to clock
    // Step 4 - Update the clock
    // Step 5 - save new offset to EEPROM
    
    // provision and zero long variables to hold time in unix format
    unsigned long utc_unix_time = 0;
    unsigned long new_unix_time = 0;
    // Step 1
    //use time library rather than pull directly from clock
    // Step 2
    utc_unix_time = (now() - (original_utc_offset * 3600L));
    // Step 3
    new_unix_time = (utc_unix_time + (utc_offset * 3600L));
    // Step 4
    //update Timelibrary with new time value
    setTime(new_unix_time);
    //ensure the RTC module is updated
    myRTC.set(now());
    // Step 5
    eeprom_save(229, 0, 0, utc_offset);
  }

  // if dst_mode changed, apply appropriate adjustments
  if (dst_mode != original_dst_mode) {
    // save new value to EEPROM
    eeprom_save(225, 0, dst_mode, 0);
    // force a dst calculation by calling dst routine in manual mode
    if (dst_mode) {
      dst_run = dstcalc();
    }
    else if (dst) {
      dst_onoff(0);
    }

#ifdef ARDUINOSERIALDEBUG
    ARDUINO_SERIAL.print(F("Old dst_mode: "));
    ARDUINO_SERIAL.print(original_dst_mode, DEC);
    ARDUINO_SERIAL.print(F(", New dst_mode: "));
    ARDUINO_SERIAL.print(dst_mode, DEC);
    ARDUINO_SERIAL.print(F(", dst_run: "));
    ARDUINO_SERIAL.println(dst_run, DEC);
#endif

  }

  if (dst != original_dst) {
    eeprom_save(227, 0, dst, 0);
    if (original_dst && !dst) {
      adjustTime(-3600L);
      //ensure the RTC module is updated
      myRTC.set(now());
    }
    else {
      adjustTime(3600L);
      //ensure the RTC module is updated
      myRTC.set(now());
    }
  }


/*
  //-------------Original code--------------
  // Set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.
  dst_mode = set_bool_value(dst_mode);
  //allow user to add / subtract 1 hour and update EEPROM
  dst = set_bool_value(dst);
  utc_offset = set_value(5, utc_offset, -12, 12, false);

  // if UTC offset has changed, work out differenct and apply to the clock
  if (utc_offset != original_utc_offset) {
    // Step 1 - Obtain current time from clock
    // Step 2 - Subtract old offset from clock
    // Step 3 - Add new offset to clock
    // Step 4 - Update the clock
    // Step 5 - save new offset to EEPROM
    
    // provision and zero long variables to hold time in unix format
    unsigned long utc_unix_time = 0;
    unsigned long new_unix_time = 0;
    // Step 1
    //use time library rather than pull directly from clock
    // Step 2
    utc_unix_time = (now() - (original_utc_offset * 3600L));
    // Step 3
    new_unix_time = (utc_unix_time + (utc_offset * 3600L));
    // Step 4
    //update Timelibrary with new time value
    setTime(new_unix_time);
    //ensure the RTC module is updated
    RTC.set(now());
    // Step 5
    eeprom_save(229, 0, 0, utc_offset);
  }

  // if dst_mode changed, apply appropriate adjustments
  if (dst_mode != original_dst_mode) {
    // save new value to EEPROM
    eeprom_save(225, 0, dst_mode, 0);
    // force a dst calculation by calling dst routine in manual mode
    if (dst_mode) {
      dst_run = dstcalc();
    }
    else if (dst) {
      dst_onoff(0);
    }

#ifdef ARDUINOSERIALDEBUG
    ARDUINO_SERIAL.print(F("Old dst_mode: "));
    ARDUINO_SERIAL.print(original_dst_mode, DEC);
    ARDUINO_SERIAL.print(F(", New dst_mode: "));
    ARDUINO_SERIAL.print(dst_mode, DEC);
    ARDUINO_SERIAL.print(F(", dst_run: "));
    ARDUINO_SERIAL.println(dst_run, DEC);
#endif

  }

  if (dst != original_dst) {
    eeprom_save(227, 0, dst, 0);
    if (original_dst && !dst) {
      adjustTime(-3600L);
      //ensure the RTC module is updated
      RTC.set(now());
    }
    else {
      adjustTime(3600L);
      //ensure the RTC module is updated
      RTC.set(now());
    }
  }
*/
}

// set time and date routine
void set_time() {

  bool first_run = true;
  bool complete = false;
  byte time_setting = 0;
  byte i;
  //char str_top[8];
  
  
  // get current values
  // fill settings with current clock values read from clock
  byte set_min   = minute();
  byte set_hr    = hour();
  byte set_date  = day();
  byte set_mnth  = month();
  int  set_yr    = year();

  //print menu
  // loop waiting for button (timeout after 40 loops [approx 2 seconds])
  while (!complete){
    for (byte count = 0; count < 40 ; count++) {
      //if user hits button, change mode
      if (buttonA.uniquePress() || first_run) {
        count = 0;
        if (!first_run) {
          time_setting++;
        }

        if (time_setting > 5) {
          time_setting = 0;
        }

        // print setting mode
        strcpy_P(str_a, (char *)pgm_read_word(&(tmewords[time_setting])));
        i = 0;
        while (str_a[i]) {
          puttinychar(i * 4, 1, str_a[i]);
          i++;
        }      
        first_run = false;
      }
      delay(50);
    }

    // pick the mode
    switch (time_setting) {
      case 0:
      //set minute
      set_min = set_value(set_min, 0, 59);
      time_setting++;
      first_run = true;
      break;
      case 1:
      //set hour
      set_hr = set_value(set_hr, 0, 23);
      time_setting++;
      first_run = true;
      break;
      case 2:
      //set date
      set_date = set_value(set_date, 1, 31);
      time_setting++;
      first_run = true;
      break;
      case 3:
      //set month
      set_mnth = set_value(set_mnth, 1, 12);
      time_setting++;
      first_run = true;
      break;
      case 4:
      //set year
      set_yr = set_value(set_yr, 2019, 2099);
      time_setting++;
      first_run = true;
      break;
      case 5:
      //exit
      complete = true;
      break;
    }
  }
  setTime(set_hr, set_min, 0, set_date, set_mnth, set_yr);
  myRTC.set(now());

/*
  //---------------Original config --------------------------
  // fill settings with current clock values read from clock
  byte set_min   = minute();
  byte set_hr    = hour();
  byte set_date  = day();
  byte set_mnth  = month();
  int  set_yr    = year();

  clear_display();

  // set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.
  set_date = set_value(2, set_date, 1, 31, true);
  set_mnth = set_value(3, set_mnth, 1, 12, true);
  set_yr   = set_value(4, set_yr, 2019, 2099, true);
  set_hr   = set_value(1, set_hr, 0, 23, true);
  set_min  = set_value(0, set_min, 0, 59, true);

  setTime(set_hr, set_min, 0, set_date, set_mnth, set_yr);
  RTC.set(now());
*/
}

// set auto intensity routine
void set_auto_intensity() {

  // get current values
  //bool set_auto_intensity_value = eeprom_read_bool(224);

  // Set function - we pass in: which 'set' message to show at top, current value
  //set_auto_intensity_value = set_bool_value(set_auto_intensity_value);

  // set the values
  //auto_intensity = set_auto_intensity_value;

  auto_intensity = set_bool_value(auto_intensity);

  // save the values to EEPROM
  eeprom_save(224, 0, auto_intensity, 0);

}

// set intensity routine
void set_intensity() {

  clear_display();

  //char str_top[7];
  byte i = 0;

  strcpy_P(str_a, (char *)pgm_read_word(&(cwords[10])));
  while (str_a[i]) {
    puttinychar(i * 4 + 4, 0, str_a[i]);
    i++;
  }
  
  // display the intensity level as a bar
  levelbar (0, 6, (intensity * 2) + 2, 2);

  // wait for button input
  while (!buttonA.uniquePress()) {
    // levelbar (0, 6, (intensity * 2) + 2, 2);   //display the intensity level as a bar
    while (buttonB.isPressed()) {
      if (intensity == 15) {
        intensity = 0;
      }
      else {
        intensity++;
      }
      // display the intensity level as a bar
      levelbar (0, 6, (intensity * 2) + 2, 2);
      // change the brightness setting on the displays
      for (byte address = 0; address < 4; address++) {
        lc.setIntensity(address, intensity);
      }
      delay(150);
    }
    while (buttonC.isPressed()) {
      if (intensity == 0) {
        intensity = 15;
      }
      else {
        intensity--;
      }
      // display the intensity level as a bar
      levelbar (0, 6, (intensity * 2) + 2, 2);
      // change the brightness setting on the displays
      for (byte address = 0; address < 4; address++) {
        lc.setIntensity(address, intensity);
      }
      delay(150);
    }
  }

  // save the values to EEPROM
  eeprom_save(200, intensity, 0, 0);

}

// set display fade speed and on/off options (0 = timer based, 1 = always on)
void set_display_options() {

  bool first_run = true;
  bool completed = false;
  byte dopt = 0;
  byte i;
  
  // get current values
  byte original_fade_timer = fade_timer;
  bool original_display_mode = display_mode;
  byte original_hour_off = hour_off;
  byte original_min_off = min_off;
  byte original_hour_on = hour_on;
  byte original_min_on = min_on;
  byte original_colon_mode = colon_mode;

  while (!completed) {
    // loop waiting for button (timeout after 40 loops [approx 2 seconds])
    for (byte count = 0; count < 40 ; count++) {

      //if user hits button, change mode
      if (buttonA.uniquePress() || first_run) {
        count = 0;
        if (!first_run) {
          dopt++;
        }
        // display_mode false = always on, true = determined by timer, skip setting on and off times; hour + minute
        if (!display_mode && dopt == 3) {
          dopt = 7;
        }
        else if (dopt > 7) {
          dopt = 0;
        }
        // print setting mode
        strcpy_P(str_a, (char *)pgm_read_word(&(doptwords[dopt])));
        i = 0;
        while (str_a[i]) {
          puttinychar(i * 4, 1, str_a[i]);
          i++;
        }     
        first_run = false;
      }
      delay(50);
    }

    // pick the mode
    switch (dopt) {
      case 0:
      // set display fade speed 0 to 250 (milliseconds)
      fade_timer = set_value(fade_timer, 0, 250);
      if (fade_timer != original_fade_timer) {
        eeprom_save(209, fade_timer, 0, 0);
      }
      dopt++;
      first_run = true;
      break;    
      case 1:
      //set colon mode
      //print current mode
      strcpy_P(str_a, (char *)pgm_read_word(&(colonwords[colon_mode])));
      i=0;    
      while (str_a[i]) {
        puttinychar(i * 4, 1, str_a[i]);
        i++;
      }
      delay(300);

      // wait for button input
      while (!buttonA.uniquePress()) {
        while (buttonB.isPressed()) {
          if (colon_mode < 3) {
            colon_mode++;
          }
          else {
            colon_mode = 0;
          }
          // print the new value
          strcpy_P(str_a, (char *)pgm_read_word(&(colonwords[colon_mode])));
          i=0;    
          while (str_a[i]) {
            puttinychar(i * 4, 1, str_a[i]);
            i++;
          }
          delay(150);
        }
        while (buttonC.isPressed()) {
          if (colon_mode > 0) {
            colon_mode--;
          }
          else {
            colon_mode = 3;
          }
          // print the new value
          strcpy_P(str_a, (char *)pgm_read_word(&(colonwords[colon_mode])));
          i=0;    
          while (str_a[i]) {
            puttinychar(i * 4, 1, str_a[i]);
            i++;
          }
          delay(150);
        }
      }
      
      //clear_display;
      //save new value to EEPROM
      if (colon_mode != original_colon_mode) {
        eeprom_save(213, colon_mode, 0, 0);
      }
      dopt++;
      first_run = true;
      break;      
      case 2:
      // toggle display timer on/off
      display_mode = set_bool_value(display_mode);
      if (display_mode != original_display_mode) {
        eeprom_save(223, 0, display_mode, 0);
      }
      dopt++;
      first_run = true;
      break;
      case 3:
      // set on hour
      hour_on = set_value(hour_on, 0, 23);
      if (hour_on != original_hour_on) {
        eeprom_save(207, hour_on, 0, 0);
      }
      dopt++;
      first_run = true;
      break;
      case 4:
      //set on minute
      min_on = set_value(min_on, 0, 59);
      if (min_on != original_min_on) {
        eeprom_save(208, min_on, 0, 0);
      }
      dopt++;
      first_run = true;
      break;
      case 5:
      //set off hour
      hour_off = set_value(hour_off, 0, 23);
      if (hour_off != original_hour_off) {
        eeprom_save(205, hour_off, 0, 0);
      }
      dopt++;
      first_run = true;
      break;
      case 6:
      //set off minute
      min_off = set_value(min_off, 0, 59);
      if (min_off != original_min_off) {
        eeprom_save(206, min_off, 0, 0);
      }
      dopt++;
      first_run = true;
      break;
      case 7:
      //exit menu
      completed = true;
      break;
    }
  }
}

// set wifi options routine
// allow user to choose the parameter to be set (ESSID / Password)
void set_wifi() {
  
  bool first_run = true;
  bool complete = false;
  byte wifi_setting = 0;
  byte i;
  //char str_top[8];
 
  // loop waiting for button (timeout after 40 loops [approx 2 seconds])
  while (!complete) {
    for (byte count = 0; count < 40 ; count++) {
      
      //if user hits button, change mode
      if (buttonA.uniquePress() || first_run) {
        count = 0;
        if (!first_run) {
          wifi_setting++;
        }
        
        if (wifi_setting > 3) {
          wifi_setting = 0;
        }
        
        // print setting mode
        strcpy_P(str_a, (char *)pgm_read_word(&(wiwords[wifi_setting])));
        i = 0;
        while (str_a[i]) {
          puttinychar(i * 4, 1, str_a[i]);
          i++;
        }      
        first_run = false;
      }
      delay(50);
    }

    // pick the mode
    switch (wifi_setting) {
      case 0:
      // turn WiFi (ntp) on/off
      ntp_mode = set_bool_value(ntp_mode);
      //save the values to EEPROM
      eeprom_save(226, 0, ntp_mode, 0);
      wifi_setting++;
      first_run = true;
      break;    
      case 1:
      // set wifi ESSID
      set_wifi_str(true);
      wifi_setting++;
      first_run = true;
      break;
      case 2:
      // set wifi password
      set_wifi_str(false);
      wifi_setting++;
      first_run = true;
      break;
      case 3:
      // exit wifi menu
      complete = true;
      break;
    }
  }
}

// set NTP options routine
void set_ntp_options() {
  
  bool first_run = true;
  bool complete = false;
  byte ntp_option_setting = 0;
  byte i;
  //char str_top[8];
  
  // loop waiting for button (timeout after 40 loops [approx 2 seconds])
  while (!complete){
    for (byte count = 0; count < 40 ; count++) {
      
      //if user hits button, change mode
      if (buttonA.uniquePress() || first_run) {
        count = 0;
        if (!first_run) {
          ntp_option_setting++;
        }

        if (ntp_option_setting > 4) {
          ntp_option_setting = 0;
        }

        // print setting mode
        strcpy_P(str_a, (char *)pgm_read_word(&(cwords[ntp_option_setting + 5])));
        i = 0;
        while (str_a[i]) {
          puttinychar(i * 4, 1, str_a[i]);
          i++;
        }      
        first_run = false;
      }
      delay(50);
    }

    // pick the mode
    switch (ntp_option_setting) {
      case 0:
      // set NTP frequency (daily / weekly / monthly)
      ntp_freq = eeprom_read_byte(203); //get current save value from EEPROM
      //print current mode
      strcpy_P(str_a, (char *)pgm_read_word(&(freqwords[ntp_freq])));
      i=0;    
      while (str_a[i]) {
        puttinychar(i * 4, 1, str_a[i]);
        i++;
      }
      delay(300);

      // wait for button input
      while (!buttonA.uniquePress()) {
        while (buttonB.isPressed()) {
          if (ntp_freq < 2) {
            ntp_freq++;
          }
          else {
            ntp_freq = 0;
          }
          // print the new value
          strcpy_P(str_a, (char *)pgm_read_word(&(freqwords[ntp_freq])));
          i=0;    
          while (str_a[i]) {
            puttinychar(i * 4, 1, str_a[i]);
            i++;
          }
          delay(150);
        }
        while (buttonC.isPressed()) {
          if (ntp_freq > 0) {
            ntp_freq--;
          }
          else {
            ntp_freq = 2;
          }
          // print the new value
          strcpy_P(str_a, (char *)pgm_read_word(&(freqwords[ntp_freq])));
          i=0;    
          while (str_a[i]) {
            puttinychar(i * 4, 1, str_a[i]);
            i++;
          }
          delay(150);
        }
      }
      
      //clear_display;
      //save new value to EEPROM
      eeprom_save(203, ntp_freq, 0, 0);
      ntp_option_setting++;
      first_run = true;
      break;
      case 1:
      ntp_hour = set_value(ntp_hour, 3, 23);
      ntp_option_setting++;
      first_run = true;
      break;
      case 2:
      ntp_dow = set_value(ntp_dow, 1, 7);
      ntp_option_setting++;
      first_run = true;
      break;
      case 3:
      ntp_dom = set_value(ntp_dom, 1, 31);
      ntp_option_setting++;
      first_run = true;
      break;
      case 4:
      // exit wifi menu
      complete = true;
      break;
    }
  } 
}

// used to set bool for dst, NTP, 12h, Random and Light
// current value = current value of property we are setting
bool set_bool_value(bool current_value) {

  clear_display();

  byte i;
  const char text[2][5] = {"OFF", "ON "};

  // print current value
  i = 0;
  while (text[current_value][i]) {
    puttinychar(i * 4, 1, text[current_value][i]);
    i++;
  }
  delay(300);
  // wait for button input
  while (!buttonA.uniquePress()) {
    while (buttonB.isPressed()) {
      current_value = (current_value ^ 1);
      // print the new value
      i = 0;
      while (text[current_value][i]) {
        puttinychar(i * 4, 1, text[current_value][i]);
        i++;
      }
      delay(150);
    }
    while (buttonC.isPressed()) {
      current_value = (current_value ^ 1);
      // print the new value
      i = 0;
      while (text[current_value][i]) {
        puttinychar(i * 4, 1, text[current_value][i]);
        i++;
      }
      delay(150);
    }
  }
  return current_value;
}

// set font_style, font_offset & font_cols variables, used by get_font_value()
void set_font_case(byte value) {

  switch (value) {
    case 1:
      font_style = 1;
      font_cols = 5;
      break;
    case 2:
      font_style = 2;
      font_cols = 5;
      break;    
    case 3:
      font_style = 3;
      font_cols = 6;
      break;
    case 4:
      font_style = 4;
      font_cols = 6;
      break;
    case 5:
      font_style = 5;
      font_cols = 6;
      break;
    case 6:
      font_style = 6;
      font_cols = 6;
      break;
  }

}

// used to set min, hr, date, month, year values. pass
// message = which 'set' message to print,
// current value = current value of property we are setting
// reset_value = what to reset value to if to rolls over. E.g. mins roll from 60 to 0, months from 12 to 1
// rollover limit = when value rolls over
int set_value(int current_value, int reset_value, int rollover_limit) {

  //const char messages[12][9] = {">Set Min", ">Set Hr", ">Set Day", ">Set Mth", ">Set Yr", ">Set TZ", ">On Hr", ">On Min", ">Off Hr", ">Off Min", ">Fad Dly", ">Set DAT"};
  //char str_top[10];
  char buffer[5] = "    ";
  byte i;
    
  clear_display();
  // print current value
  itoa(current_value, buffer, 10);
  puttinychar(0 , 1, buffer[0]);
  puttinychar(4 , 1, buffer[1]);
  puttinychar(8, 1, buffer[2]);
  puttinychar(12, 1, buffer[3]);
  delay(300);
  // wait for button input
  while (!buttonA.uniquePress()) {
    while (buttonB.isPressed()) {
      if (current_value < rollover_limit) {
        current_value++;
      }
      else {
        current_value = reset_value;
      }
      // print the new value
      itoa(current_value, buffer , 10);
      puttinychar(0 , 1, buffer[0]);
      puttinychar(4 , 1, buffer[1]);
      puttinychar(8, 1, buffer[2]);
      puttinychar(12, 1, buffer[3]);
      delay(150);
    }
    while (buttonC.isPressed()) {
      if (current_value > reset_value) {
        current_value--;
      }
      else {
        current_value = rollover_limit;
      }
      // print the new value
      itoa(current_value, buffer, 10);
      puttinychar(0 , 1, buffer[0]);
      puttinychar(4 , 1, buffer[1]);
      puttinychar(8, 1, buffer[2]);
      puttinychar(12, 1, buffer[3]);
      delay(150);
    }
  }
  return current_value;
}

// display a horizontal bar on the screen at offset xpos by ypos with height and width of xbar, ybar
void levelbar (byte xpos, byte ypos, byte xbar, byte ybar) {
  // erase the bar
  for (byte x = 0; x <=32; x++) {
    for (byte y = 0; y<= ybar; y++) {
      plot(x, y + ypos, 0);
    }
  }
  // plot the bar
  for (byte x = 0; x < xbar; x++) {
    for (byte y = 0; y <= ybar; y++) {
      plot(x + xpos, y + ypos, 1);
    }
  }
}

// function to check light level and turn on/off matrix
void light() {

  // Get light reading
  uint16_t lx = analogRead(LDR_APIN);

  // this runs if auto_intensity is true, it defines the intensity based on the light sensor and 
  // calls set_devices to set intensity.
  if (auto_intensity) {
    if (LDR_BRIGHT > LDR_DARK) {
      auto_intensity_value = map(lx, LDR_DARK, LDR_BRIGHT, 1, 15);
    }
    else {
      auto_intensity_value = map(lx, LDR_BRIGHT, LDR_DARK, 1, 15);
    }
      set_devices(true, auto_intensity_value);
  }
  else {
    set_devices(true, intensity);
  }
}

// function called by light() to turn on/off matrix and by auto light intensity to adjust device intensity. 
// bool m = true (light intensity), false (matrix on/off), byte i = intensity
void set_devices(bool m, byte i) {

  int devices = lc.getDeviceCount();
  for (int address = 0; address < devices; address++) {
    if (!m) {
      // turns on/off matrix
      lc.shutdown(address, shut);
    }
    else {
      // sets matrix intensity
      lc.setIntensity(address, i);
    }
  }

}

// function for getting NTP time from ESP01
void ntp() {

  char buff_[80];
  char unixString[10];
  bool timeSync = false;
  dst = eeprom_read_bool(227);

  clear_display();

  // show the user NTP update running
  //char msg[] = ">GET NTP";
  strcpy (str_a, ">GET NTP");
  int i = 0;
  // display message on LED matrix
  while (/*msg*/str_a[i])
  {
    puttinychar(i * 4, 1, /*msg*/str_a[i]);
    i++;
  }

  // trigger ESP01 via software serial to receive NTP time

  // EEPROM locations:
  // 1 = ESSID length, used to read the correct values from remaining cells
  // 2-33 = ESSID "string" in decimal
  // 34 = Password length, used to read the correct values from remaining cells
  // 35-66 = Password "string" in decimal
  
  // string to pass to ESP01 (wifi) looks like:
  // NTPabbxxxxccyyyy
  // where:
  // a = 1 digit retry count (1 to 9)
  // bb = 2 digit ssid length (01 to 99)
  // xxxx = ssid characters
  // cc = 2 digit password length (01 to 99)
  // yyyy = password characters

  // get the two lengths from EEPROM
  byte ssid_len = eeprom_read_byte(SSID_LOCATION);
  if (ssid_len > 32) {
    ssid_len = 0;
  }
  byte pass_len = eeprom_read_byte(PASS_LOCATION);
  if (pass_len > 32) {
    pass_len = 0;
  }
  
#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(F("NTP"));
  ARDUINO_SERIAL.print(NTP_MAX_RETRY);
#endif

  Wifi_Serial.print(F("NTP"));
  Wifi_Serial.print(NTP_MAX_RETRY);
  if (ssid_len < 10) {

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(F("0"));
#endif
    
    Wifi_Serial.print(F("0"));
  }

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(ssid_len);
#endif
  
  Wifi_Serial.print(ssid_len);
  if (ssid_len > 0) {
    for (byte p=0; p < ssid_len; p++) {

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print((char)eeprom_read_byte(SSID_LOCATION + 1 + p));
#endif

      Wifi_Serial.print((char)eeprom_read_byte(SSID_LOCATION + 1 + p));
    }
  }
  else {
    Wifi_Serial.print(F("!"));

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(F("!"));
#endif

  }
  if (pass_len < 10) {

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(F("0"));
#endif

    Wifi_Serial.print(F("0"));
  }

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(pass_len);
#endif

  Wifi_Serial.print(pass_len);
  if (pass_len > 0) {
    for (byte p=0; p < pass_len; p++) {

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print((char)eeprom_read_byte(PASS_LOCATION + 1 + p));
#endif

      Wifi_Serial.print((char)eeprom_read_byte(PASS_LOCATION + 1 + p));
    }
  }
  else {
    Wifi_Serial.print(F("!"));

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.print(F("!"));
#endif
  }
  
#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.println();
#endif

  Wifi_Serial.println();

  // putting these here for sanity reasons
//#define NTP_MAX_RETRY      3  // Number of time to retry NTP request 1 = 35 seconds(ish) in total, values 1 - 9
//#define NTP_TIMEOUT        20 // Used to calculate when to quit ntp() when it's not receiving data, value in seconds, it is multiplied by NTP_MAX_RETRY

  // holds count to quit routine if no data received from ESP01
  unsigned long ntp_count_delay_start = millis();
  unsigned long ntp_count_delay = (NTP_MAX_RETRY * NTP_TIMEOUT * 1000L);
  
  unsigned int ntp_counter = 1;

  while (!timeSync) {
    if (readline(Wifi_Serial.read(), buff_, 80) > 0) {
      
// used for debugging output from ESP01
#ifdef ARDUINOSERIALDEBUGESP
      ARDUINO_SERIAL.print(F("Data Rec'd: "));
      ARDUINO_SERIAL.println(buff_);
#endif

      if (buff_[0] == 'U' && buff_[1] == 'N' && buff_[2] == 'I' && buff_[3] == 'X' /*&& wait == 0*/) {
        // if data sent is the UNIX token, take it
        int i = 0;
        while (i < 10) {
          unixString[i] = buff_[i + 4];
          i++;
        }
        unixString[10] = '\0';

        // if dst is on add 1 hour to off set, else use offset
        // convert UNIX time to long integer and save to DS3231, add NTP_ADJUST secs and utc_offset
        if (dst) {
          myRTC.set(atol(unixString) + NTP_ADJUST + ((utc_offset + 1) * 3600L));

#ifdef ARDUINOSERIALDEBUGESP
      ARDUINO_SERIAL.print(F("DST UNIX time sent to DS3231 ["));
      ARDUINO_SERIAL.print((atol(unixString) + NTP_ADJUST + ((utc_offset +1)* 3600L)));
      ARDUINO_SERIAL.println(F("]"));
#endif          
        }
        else {
          myRTC.set(atol(unixString) + NTP_ADJUST + (utc_offset * 3600L));

#ifdef ARDUINOSERIALDEBUGESP
      ARDUINO_SERIAL.print(F("NOT DST UNIX time sent to DS3231 ["));
      ARDUINO_SERIAL.print((atol(unixString) + NTP_ADJUST + (utc_offset * 3600L)));
      ARDUINO_SERIAL.println(F("]"));
#endif
          
        }

        // increase counter to allow one more line to be read, this is just to read a blank line into serial
        //wait++;

        // set time using ESP01 NTP - success
        fade_down();
        char msg[] = ">NTP OK";
        i = 0;
        while (msg[i]) {
          puttinychar(i * 4, 1, msg[i]);
          i++;
        }
        delay(1000);
        fade_down();
        timeSync = true;
      }
      // NTP sending failed
      else if (buff_[4] == 'F' && buff_[5] == 'a' && buff_[6] == 'i' && buff_[7] == 'l') {
        char msg[] = ">NTP ERR";
        i = 0;
        while (msg[i]) {
          puttinychar(i * 4, 1, msg[i]);
          i++;
        }
        delay(1000);
        fade_down();
        //wait++;
        timeSync = true;
      }
      // WiFi connection failed
      else if (buff_[5] == 'F' && buff_[6] == 'a' && buff_[7] == 'i' && buff_[8] == 'l') {
        char msg[] = ">WIFI ER";
        i = 0;
        while (msg[i]) {
          puttinychar(i * 4, 1, msg[i]);
          i++;
        }
        delay(1000);
        fade_down();
        //wait++;
        timeSync = true;
      }
    }

    // quit ntp routine if nothing comes from the ESP, the calculation below ensures it does not quit before ESP01 processing
    ntp_counter++;
    delay(1);
    
    if (ntp_counter > 5000 && !timeSync) {

#ifdef ARDUINOSERIALDEBUGESP
  ARDUINO_SERIAL.println(F("ntp_counter > 5000 && !timeSync"));
  ARDUINO_SERIAL.print(F("millis(): "));
  ARDUINO_SERIAL.println(millis());
  ARDUINO_SERIAL.print(F("ntp_count_delay_start: "));
  ARDUINO_SERIAL.println(ntp_count_delay_start);
  ARDUINO_SERIAL.print(F("ntp_count_delay: "));
  ARDUINO_SERIAL.println(ntp_count_delay);
  ARDUINO_SERIAL.print(F("millis - delay_start value: "));
  ARDUINO_SERIAL.println(millis() - ntp_count_delay_start);
#endif

      if (millis() - ntp_count_delay_start >= ntp_count_delay) {
        char msg[] = ">NO WIFI";
        i = 0;
        while (msg[i]) {
          puttinychar(i * 4, 1, msg[i]);
          i++;
        }
        delay(1000);
        fade_down();
        timeSync = true;
      }
      ntp_counter = 1;
    }
  }
}

// used to readline from serial output
int readline(int readch, char *buffer, int len) {

  static int pos = 0;
  int rpos = 0;

  if (readch > 0) {
    switch (readch) {
      case '\r': // Ignore CR
        break;
      case '\n': // Return on new-line
        rpos = pos;
        pos = 0;  // Reset position index ready for next time
        return rpos;
      default:
        if (pos < len - 1) {
          buffer[pos++] = readch;
          buffer[pos] = 0;
        }
    }
  }
  return 0;

}

// calculates dst
bool dstcalc() {

  // Summer calculation
  if (isdst() && !dst) {
    dst_onoff(1);

#ifdef ARDUINOSERIAL
    ARDUINO_SERIAL.println(F("Summer"));
#endif

    return 1;
  }

  // Winter calculation
  if (!isdst() && dst) {
    dst_onoff(0);

#ifdef ARDUINOSERIAL
    ARDUINO_SERIAL.println(F("Winter"));
#endif

    return 1;
  }
  return 0;
}

// Check whether we are in dst time or not
bool isdst() {

  // November to March are in dst
  if (month() < 4 || month() > 10) return true;
  // May (5), June (6), July (7), August (8), September (9) are out.
  if (month() > 4 && month() < 10) return false;

  if (month() == 4 && (hour() + 24 * day()) < (-8 + utc_offset + 24 * (7 - (5 * year() / 4 + 4) % 7))
      || month() == 10 && (hour() + 24 * day()) >= (-8 + utc_offset + 24 * (7 - (5 * year() / 4 + 5) % 7))) {
    return true;
  }
  else return false;

}

// called by dstcalc()
// adjusts RTC time and saves value to EEPROM
void dst_onoff(bool add_hr) {

  if (add_hr) {

    myRTC.set((now() + 3600L));
    dst = 1;
    eeprom_save(227, 0, dst, 0);

#ifdef ArduioSerial
    ARDUINO_SERIAL.println(F("+1 Hour"));
#endif

    return;
  }

  if (!add_hr) {
    //RTC.get();
    myRTC.set((now() - 3600L));
    dst = 0;
    eeprom_save(227, 0, dst, 0);

#ifdef ArduioSerial
    ARDUINO_SERIAL.println(F("-1 Hour"));
#endif

    return;
  }
}

// called by wifi_menu for setting wifi string values
// takes boolean input - [t]ype;
// true = set ESSID
// false = set password
// Wifi standard defines maximum SSID length at 32 characters
// this routine handles a maximum of 32 characters for both
void set_wifi_str(bool t) {
  
  // EEPROM locations:
  // 1 = ESSID length, used to read the correct values from remaining cells
  // 2-33 = ESSID "string"
  // 34 = Password length, used to read the correct values from remaining cells
  // 35-66 = Password "string"

  // DEC(127) = "enter" symbol
  // DEC(128) = "degrees" symbol

  // setup variables
  byte j;
  byte current_value = 127;
  byte len = 0;
  char input[33];

  // Full ASCII characterset is in font_style 1 only
  // capture current font style to re-set when exiting
  byte old_font_style = font_style;

  // set font_style to 1
  set_font_case(1);
  
  // clear display
  clear_display();

  // read eeprom data
  // first byte holds the length of 'input'
  if (t) {
    len = eeprom_read_byte (SSID_LOCATION);
  }
  else {
    len = eeprom_read_byte (PASS_LOCATION);
  }

  if (len > 32 || len < 0) {
    len = 0;
  }

#ifdef ARDUINOSERIALDEBUGWIFISETUP
  ARDUINO_SERIAL.print (F("EEPROM Array Length: "));
  ARDUINO_SERIAL.println (len);
  ARDUINO_SERIAL.print (F("Data: '"));
#endif
  
  // fill 'input' array with data from EEPROM
  for (byte p = 0; p < len; p++) {
    if (t) {
      input[p] = eeprom_read_byte (SSID_LOCATION + 1 + p);
    }
    else {
      input[p] = eeprom_read_byte (PASS_LOCATION + 1 + p);
    }
    
#ifdef ARDUINOSERIALDEBUGWIFISETUP
    ARDUINO_SERIAL.print (input[p]);
#endif
    
  }

#ifdef ARDUINOSERIALDEBUGWIFISETUP
    ARDUINO_SERIAL.println (F("'"));
#endif

  j = 0;
  while (j < 33) {
    // set current_value to match first location of input
    current_value = input[j];

#ifdef ARDUINOSERIALDEBUGWIFISETUP
    ARDUINO_SERIAL.print (F("Array position: ["));
    ARDUINO_SERIAL.print (j);
    ARDUINO_SERIAL.println (F("]"));
#endif

    // set default value to enter when at (or beyond) end of EEPROM reading
    if (j > len - 1) {
      current_value = 127;
    }
    // ensure value is within acceptable ranges (compensate for invalid EEPROM data)
    if (current_value > 127 || current_value < 32) {
      current_value = 127;
    }
  
    // wait for button input
    while (!buttonA.uniquePress()) {
      // draw current value
      putnormalchar(25, current_value);
      while (buttonB.isPressed()) {
        if (current_value < 127) {
          current_value++;
        }
        else {
          current_value = 32;
        }
        // print the new value
        putnormalchar(25, current_value);
        delay(150);
      }
      while (buttonC.isPressed()) {
        if (current_value > 0) {
          current_value--;
        }
        else {
          current_value = 127;
        }
        // print the new value
        putnormalchar(25, current_value);
        delay(150);
      }
    } // end of button pressing while loops. Enter button pressed.

    // put the value into the array
    input[j] = current_value;

    // have we received an 'enter' or hit end of the array
    if (current_value == 127 || j == 32) {

// Sanity print of the 'input' values
#ifdef ARDUINOSERIALDEBUGWIFISETUP
  ARDUINO_SERIAL.print (F("Array Length: "));
  ARDUINO_SERIAL.println (j);
  ARDUINO_SERIAL.print (F("Data: '"));
  for (byte p = 0; p < j; p++) {
    ARDUINO_SERIAL.print (input[p]);
  }
  ARDUINO_SERIAL.println(F("'"));
#endif

      // save array length to EEPROM
      if (t) {
        eeprom_save (SSID_LOCATION, j, 0, 0);
      }
      else {
        eeprom_save (PASS_LOCATION, j, 0, 0);
      }

      // save current value to matching EEPROM location
      for (byte p = 0; p < j; p++){
        if (t) {
          eeprom_save (SSID_LOCATION + 1 + p, input[p], 0, 0);
        }
        else {
          eeprom_save (PASS_LOCATION + 1 + p, input[p], 0, 0);
        }
      }
  
      // after save, guarantee we exit the do..while loop  
      j=33;
      } //end if current_value statement

    // displaying the previous 4 characters, including current character
    // character positions:
    // position 1 = LED column 0
    // position 2 = LED column 6
    // position 3 = LED column 12
    // position 4 = LED column 18
    // position 5 = LED column 25  <-- the character being modified
    switch (j) {
      case 0:
        // start the first character on LED column 18
        putnormalchar(18, input[j]);
        break;
      case 1:
        // start the first character on LED column 12
        putnormalchar(12, input[j-1]);
        // start the second character on LED column 18
        putnormalchar(18, input[j]);
        break;
      case 2:
        // start the first character on LED column 6
        putnormalchar(6, input[j-2]);
        // start the second character on LED column 12
        putnormalchar(12, input[j-1]);
        // start the third character on LED column 18
        putnormalchar(18, input[j]);
        break;
      default:
        // scroll the last 4 characters (5th charater being the currently edit)
        // start the first character on LED column 0
        putnormalchar(0, input[j-3]);
        // start the second character on LED column 6
        putnormalchar(6, input[j-2]);
        // start the third character on LED column 12
        putnormalchar(12, input[j-1]);
        // start the fourth character on LED column 18
        putnormalchar(18, input[j]);
        break;
    }
    // increment position in array
    j++;

  } // end of while loop

  clear_display();
  //delay(20);

  //reset font back to original value
  set_font_case(old_font_style);
  
} //end void set_wifi function

// function to map input value to location in LEDFONT.h
// called by putnormalchar() and slideanim()
byte map_font(byte v) {

// debug printing to serial port for sanity checking
#ifdef ARDUINOSERIAL
    ARDUINO_SERIAL.println(F("map_font() function"));
    ARDUINO_SERIAL.print(F("Input: ["));
    ARDUINO_SERIAL.print(v);
    ARDUINO_SERIAL.println(F("]"));
#endif


  byte old_font_style = font_style;
  if (v >= 32 && v <= 128) {
    font_style = 1;
  }
  switch (font_style) {
    case 1:
      // font_sytle one - contains full Ardiuno ASCII character set
      //v = (v - 32);      //maps to 0-96
      if (v < 10) {
        v = (v + 16); 
      }
      else {
        v = (v - 32);
      }
      break;
    case 2:
      // font_style two - map to appropriate location in 'myfont' table
      v = (v + 97);   // 0-9 maps to 97-106
      break;
    case 3:
      // font_style three - map to appropriate location in 'myfont' table
      v = (v + 107);   // 0-9 maps to 107-116
      break;
    case 4:
      // font_style four - map to appropriate location in 'myfont' table
      v = (v + 117);   // 0-9 maps to 117-126
      break;
    case 5:
      // font_style five - map to appropriate location in 'myfont' table
      v = (v + 127);   // 0-9 maps to 127-136
      break;
    case 6:
      // font_style six (same as two) - map to appropriate location in 'myfont' table
      v = (v + 137);   // 0-9 maps to 97-106
      break;
    case 7:
      // font_style seven - map to appropriate location in 'myfont' table
      v = (v + 107);   // 0-9 maps to 107-116
      break;
  }
  font_style = old_font_style;

// debug printing to serial port for sanity checking
#ifdef ARDUINOSERIAL
    ARDUINO_SERIAL.print(F("Output: ["));
    ARDUINO_SERIAL.print(v);
    ARDUINO_SERIAL.println(F("]"));
#endif
  
  return v;
}

// function to plot the PM indicator dot
void plot_pm_dot() {
  //print dot top right to represent PM when in 12 hour mode
  if (isPM() && ampm) {
    plot(31,0,1);
  }
  else if (isAM() && ampm) {
    plot(31,0,0);
  }
}

void plot_colon() {
  byte dot_column = 14;
  bool dot_norm = ((millis() / 500) %2);
  bool knight_run = ((millis() / 125) %2);

  //calculate colon dot column
  if (font_style > 2) {
    dot_column = 15;
  }
  
  //work out the colon mode and plot
  switch (colon_mode) {
    case 1: //alternate
    plot (dot_column, 2, dot_norm);  //top dot
    plot (dot_column, 5, !dot_norm);  //bottom 
    break;

    case 2: //solid on
    plot (dot_column, 2, 1);  //top dot
    plot (dot_column, 5, 1);  //bottom dot
    break;

    case 3: //knightrider
    if //(millis() - knight_previous_millis >= 125)
       (knight_run != knight_prev_run)
    {
      knight_prev_run = knight_run;
      switch (knight_rider) {
        case 0:
        plot (dot_column, 2, 1);
        plot (dot_column, 3, 0);
        break;

        case 1:
        plot (dot_column, 3, 1);
        break;

        case 2:
        plot (dot_column, 2, 0);
        plot (dot_column, 4, 1);
        break;

        case 3:
        plot (dot_column, 3, 0);
        plot (dot_column, 5, 1);
        break;

        case 4:
        plot (dot_column, 4, 0);
        plot (dot_column, 5, 1);
        break;

        case 5:
        plot (dot_column, 4, 1);
        break;

        case 6:
        plot (dot_column, 3, 1);
        plot (dot_column, 5, 0);
        break;

        case 7:
        plot (dot_column, 2, 1);
        plot (dot_column, 4, 0);
        break;

        default:
        knight_rider = 0;

      }
      knight_rider++;
      if (knight_rider == 8) {
        knight_rider = 0;
      }
      //knight_previous_millis = millis();
    } 
/*    else if (!dot_knight) {
      knight_rider_run = false;
    }*/
    break;

    default: //sync
    plot (dot_column, 2, dot_norm);  //top dot
    plot (dot_column, 5, dot_norm);  //bottom dot
    break;
  }
  
}

// function to return single hour/minute value for filling of buffer[]
// takes bool input
// tn true for tens false for ones
byte buf_hour(bool tn) {
  //return hours based on 12/24 hour time and ampm flag
  if (ampm && hourFormat12() < 10){
    if (tn) {
      return 32; //put space in place of hour tens  
    }
    else {
      return (hourFormat12()); //hour ones
    }
  }
  else if (ampm) {
    if (tn) {
      return ((hourFormat12() / 10) % 10);  //hour tens
    }
    else {
      return (hourFormat12() % 10);         //hour ones
    }
  } 
  else {
    if (tn) {
      return ((hour() / 10) % 10);  //hour tens
    }
    else {
      return (hour() % 10);         //hour ones
    }
  }

}


// Used to calculate free RAM (found on Arduino forum)
#ifdef ARDUINOSERIALDEBUG
int freeRam(void) {
  extern int __bss_end;
  extern int *__brkval;
  int free_memory;
  if ((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  }
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
  }
  return free_memory;
  }
#endif

Button_Master.cpp

C/C++
Called by main code to read buttons - add into new tab of same name
/* $Id$
||
|| @file            Button.cpp
|| @author         Alexander Brevig              <alexanderbrevig@gmail.com>        
|| @url            http://alexanderbrevig.com
||
|| @description
|| | This is a Hardware Abstraction Library for Buttons
|| | It provides an easy way of handling buttons
|| #
||
|| @license LICENSE_REPLACE
||
*/

#include <Arduino.h>
#include "Button_Master.h"

#define CURRENT 0
#define PREVIOUS 1
#define CHANGED 2

/*
|| @constructor
|| | Set the initial state of this button
|| #
|| 
|| @parameter buttonPin  sets the pin that this switch is connected to
|| @parameter buttonMode indicates BUTTON_PULLUP or BUTTON_PULLDOWN resistor
*/
Button::Button(uint8_t buttonPin, uint8_t buttonMode){
  pin=buttonPin;
  pinMode(pin,INPUT);
  
  buttonMode==BUTTON_PULLDOWN ? pulldown() : pullup(buttonMode);
  state = 0;
  bitWrite(state,CURRENT,!mode);
  
  cb_onPress = 0;
  cb_onRelease = 0;
  cb_onClick = 0;
  cb_onHold = 0;
  
  numberOfPresses = 0;
  triggeredHoldEvent = true;
}

/*
|| @description
|| | Prepare logic for a pullup button.
|| | If mode is internal set pin HIGH as default
|| #
*/
void Button::pullup(uint8_t buttonMode)
{
  mode=BUTTON_PULLUP;
  if (buttonMode == BUTTON_PULLUP_INTERNAL) 
  {
    digitalWrite(pin,HIGH);
  }
}

/*
|| @description
|| | Set pin LOW as default
|| #
*/
void Button::pulldown(void)
{
  mode=BUTTON_PULLDOWN;
}

/*
|| @description
|| | Return the bitRead(state,CURRENT) of the switch
|| #
|| 
|| @return true if button is pressed
*/
bool Button::isPressed(void)
{  
  //save the previous value
  bitWrite(state,PREVIOUS,bitRead(state,CURRENT));
  
  //get the current status of the pin
  if (digitalRead(pin) == mode)
  {
    //currently the button is not pressed
    bitWrite(state,CURRENT,false);
  } 
  else 
  {
    //currently the button is pressed
    bitWrite(state,CURRENT,true);
  }
  
  //handle state changes
  if (bitRead(state,CURRENT) != bitRead(state,PREVIOUS))
  {
    //the state changed to PRESSED
    if (bitRead(state,CURRENT) == true) 
    {
      numberOfPresses++;
      if (cb_onPress) { cb_onPress(*this); }   //fire the onPress event
      pressedStartTime = millis();             //start timing
      triggeredHoldEvent = false;
    } 
    else //the state changed to RELEASED
    {
      if (cb_onRelease) { cb_onRelease(*this); } //fire the onRelease event
      if (cb_onClick) { cb_onClick(*this); }   //fire the onClick event AFTER the onRelease
      //reset states (for timing and for event triggering)
      pressedStartTime = -1;
    }
    //note that the state changed
    bitWrite(state,CHANGED,true);
  }
  else
  {
    //note that the state did not change
    bitWrite(state,CHANGED,false);
    //should we trigger a onHold event?
    if (pressedStartTime!=-1 && !triggeredHoldEvent) 
    {
      if (millis()-pressedStartTime > holdEventThreshold) 
      { 
        if (cb_onHold) 
        { 
          cb_onHold(*this); 
          triggeredHoldEvent = true;
        }
      }
    }
  }
  return bitRead(state,CURRENT);
}

/*
|| @description
|| | Return true if the button has been pressed
|| #
*/
bool Button::wasPressed(void)
{
  return bitRead(state,CURRENT);
}

/*
|| @description
|| | Return true if state has been changed
|| #
*/
bool Button::stateChanged(void)
{
  return bitRead(state,CHANGED);
}

/*
|| @description
|| | Return true if the button is pressed, and was not pressed before
|| #
*/
bool Button::uniquePress(void)
{
  return (isPressed() && stateChanged());
}

/*
|| @description
|| | onHold polling model
|| | Check to see if the button has been pressed for time ms
|| | This will clear the counter for next iteration and thus return true once
|| #
*/
bool Button::held(unsigned int time /*=0*/) 
{
  unsigned int threshold = time ? time : holdEventThreshold; //use holdEventThreshold if time == 0
  //should we trigger a onHold event?
  if (pressedStartTime!=-1 && !triggeredHoldEvent) 
  {
    if (millis()-pressedStartTime > threshold) 
    { 
      triggeredHoldEvent = true;
      return true;
    }
  }
  return false;
}

/*
|| @description
|| | Polling model for holding, this is true every check after hold time
|| | Check to see if the button has been pressed for time ms
|| #
*/
bool Button::heldFor(unsigned int time) 
{
  if (isPressed()) 
  {
    if (millis()-pressedStartTime > time) { return true; }
  }
  return false;
}

/*
|| @description
|| | Set the hold event time threshold
|| #
*/
void Button::setHoldThreshold(unsigned int holdTime) 
{ 
  holdEventThreshold = holdTime; 
}

/*
|| @description
|| | Register a handler for presses on this button
|| #
||
|| @parameter handler The function to call when this button is pressed
*/
void Button::pressHandler(buttonEventHandler handler)
{
  cb_onPress = handler;
}

/*
|| @description
|| | Register a handler for releases on this button
|| #
||
|| @parameter handler The function to call when this button is released
*/
void Button::releaseHandler(buttonEventHandler handler)
{
  cb_onRelease = handler;
}

/*
|| @description
|| | Register a handler for clicks on this button
|| #
||
|| @parameter handler The function to call when this button is clicked
*/
void Button::clickHandler(buttonEventHandler handler)
{
  cb_onClick = handler;
}

/*
|| @description
|| | Register a handler for when this button is held
|| #
||
|| @parameter handler The function to call when this button is held
*/
void Button::holdHandler(buttonEventHandler handler, unsigned int holdTime /*=0*/)
{
  if (holdTime>0) { setHoldThreshold(holdTime); }
  cb_onHold = handler;
}

/*
|| @description
|| | Get the time this button has been held
|| #
||
|| @return The time this button has been held
*/
unsigned int Button::holdTime() const { if (pressedStartTime!=-1) { return millis()-pressedStartTime; } else return 0; }

/*
|| @description
|| | Compare a button object against this
|| #
|| 
|| @parameter  rhs the Button to compare against this Button
|| 
|| @return true if they are the same
*/
bool Button::operator==(Button &rhs) 
{
  return (this==&rhs);
}

Button_Master.h

Arduino
Called by main code to read buttons - add into new tab of same name
/* $Id$
||
|| @file            Button_Master.cpp
|| @author         Alexander Brevig              <alexanderbrevig@gmail.com>        
|| @url            http://alexanderbrevig.com
||
|| @description
|| | This is a Hardware Abstraction Library for Buttons
|| | It providea an easy way of handling buttons
|| #
||
|| @license LICENSE_REPLACE
||
*/

#ifndef Button_h
#define Button_h

#include <inttypes.h>

#define BUTTON_PULLUP HIGH
#define BUTTON_PULLUP_INTERNAL 2
#define BUTTON_PULLDOWN LOW

class Button;
typedef void (*buttonEventHandler)(Button&);

class Button {
  public:
  
    Button(uint8_t buttonPin, uint8_t buttonMode=BUTTON_PULLUP_INTERNAL);
    
    void pullup(uint8_t buttonMode);
    void pulldown();
    
    bool isPressed();
    bool wasPressed();
    bool stateChanged();
    bool uniquePress();
    
    void setHoldThreshold(unsigned int holdTime);
    bool held(unsigned int time=0);
    bool heldFor(unsigned int time);
    
    void pressHandler(buttonEventHandler handler);
    void releaseHandler(buttonEventHandler handler);
    void clickHandler(buttonEventHandler handler);
    void holdHandler(buttonEventHandler handler, unsigned int holdTime=0);
  
    unsigned int holdTime() const;
    inline unsigned int presses() const { return numberOfPresses; }
    
    bool operator==(Button &rhs);
    
  private: 
    uint8_t pin;
    uint8_t mode;
    uint8_t state;
    unsigned int pressedStartTime;
    unsigned int holdEventThreshold;
    buttonEventHandler cb_onPress;
    buttonEventHandler cb_onRelease;
    buttonEventHandler cb_onClick;
    buttonEventHandler cb_onHold;
    unsigned int numberOfPresses;
    bool triggeredHoldEvent;
};

#endif

LEDFont.h

Arduino
LED fonts called by main code - add into new tab of same name
unsigned const char PROGMEM myfont[][6] = {
/* font_style = 1
 * Full ASCII character set in 7 x 5 format */
  {0x00, 0x00, 0x00, 0x00, 0x00},  // DEC(32), 'space' - position 0
  {0x00, 0x00, 0x7a, 0x00, 0x00},  // DEC(33), '!' - position 1
  {0x00, 0x70, 0x00, 0x70, 0x00},  // DEC(34), '"'
  {0x14, 0x3e, 0x14, 0x3e, 0x14},  // DEC(35), '#'
  {0x12, 0x2a, 0x7f, 0x2a, 0x24},  // DEC(36), '$'
  {0x62, 0x64, 0x08, 0x13, 0x23},  // DEC(37), '%' - position 5
  {0x14, 0x2a, 0x14, 0x02, 0x00},  // DEC(38), '&'
  {0x00, 0x00, 0x70, 0x00, 0x00},  // DEC(39), '''
  {0x00, 0x3c, 0x42, 0x00, 0x00},  // DEC(40), '('
  {0x00, 0x42, 0x3c, 0x00, 0x00},  // DEC(41), ')'
  {0x00, 0x2a, 0x1c, 0x2a, 0x00},  // DEC(42), '*' - position 10
  {0x08, 0x08, 0x3e, 0x08, 0x08},  // DEC(43), '+'
  {0x01, 0x07, 0x06, 0x00, 0x00},  // DEC(44), ','
  {0x08, 0x08, 0x08, 0x08, 0x08},  // DEC(45), '-'
  {0x00, 0x00, 0x03, 0x03, 0x00},  // DEC(46), '.'
  {0x02, 0x06, 0x0c, 0x18, 0x10},  // DEC(47), '/' - position 15
  {0x3E, 0x45, 0x49, 0x51, 0x3E},  // DEC(48), '0' - position 16
  {0x00, 0x21, 0x7F, 0x01, 0x00},  // DEC(49), '1'
  {0x21, 0x43, 0x45, 0x49, 0x31},  // DEC(50), '2'
  {0x22, 0x41, 0x49, 0x49, 0x36},  // DEC(51), '3'
  {0x0C, 0x14, 0x24, 0x7F, 0x04},  // DEC(52), '4' - position 20
  {0x72, 0x51, 0x51, 0x51, 0x4E},  // DEC(53), '5' - position 21
  {0x3E, 0x49, 0x49, 0x49, 0x26},  // DEC(54), '6'
  {0x40, 0x40, 0x4F, 0x50, 0x60},  // DEC(55), '7'
  {0x36, 0x49, 0x49, 0x49, 0x36},  // DEC(56), '8'
  {0x32, 0x49, 0x49, 0x49, 0x3E},  // DEC(57), '9' - position 25
  {0x00, 0x36, 0x36, 0x00, 0x00},  // DEC(58), ':'
  {0x01, 0x37, 0x36, 0x00, 0x00},  // DEC(59), ';'
  {0x00, 0x08, 0x14, 0x22, 0x00},  // DEC(60), '<'
  {0x14, 0x14, 0x14, 0x14, 0x14},  // DEC(61), '='
  {0x00, 0x22, 0x14, 0x08, 0x00},  // DEC(62), '>' - position 30
  {0x00, 0x20, 0x4d, 0x30, 0x00},  // DEC(63), '?'
  {0x3c, 0x42, 0x5a, 0x38, 0x00},  // DEC(64), '@'
  {0x3f, 0x48, 0x48, 0x48, 0x3f},  // DEC(65), 'A'
  {0x7f, 0x49, 0x49, 0x49, 0x36},  // DEC(66), 'B'
  {0x3e, 0x41, 0x41, 0x41, 0x22},  // DEC(67), 'C' - position 35
  {0x7f, 0x41, 0x41, 0x22, 0x1c},  // DEC(68), 'D'
  {0x7f, 0x49, 0x49, 0x49, 0x41},  // DEC(69), 'E'
  {0x7f, 0x48, 0x48, 0x48, 0x40},  // DEC(70), 'F'
  {0x3e, 0x41, 0x49, 0x49, 0x2e},  // DEC(71), 'G'
  {0x7f, 0x08, 0x08, 0x08, 0x7f},  // DEC(72), 'H' - position 40
  {0x00, 0x41, 0x7f, 0x41, 0x00},  // DEC(73), 'I'
  {0x06, 0x01, 0x01, 0x01, 0x7e},  // DEC(74), 'J'
  {0x7f, 0x08, 0x14, 0x22, 0x41},  // DEC(75), 'K'
  {0x7f, 0x01, 0x01, 0x01, 0x01},  // DEC(76), 'L'
  {0x7f, 0x20, 0x10, 0x20, 0x7f},  // DEC(77), 'M' - position 45
  {0x7f, 0x10, 0x08, 0x04, 0x7f},  // DEC(78), 'N'
  {0x3e, 0x41, 0x41, 0x41, 0x3e},  // DEC(79), 'O'
  {0x7f, 0x48, 0x48, 0x48, 0x30},  // DEC(80), 'P'
  {0x3e, 0x41, 0x45, 0x42, 0x3d},  // DEC(81), 'Q'
  {0x7f, 0x48, 0x4c, 0x4a, 0x31},  // DEC(82), 'R' - position 50
  {0x31, 0x49, 0x49, 0x49, 0x46},  // DEC(83), 'T'
  {0x40, 0x40, 0x7f, 0x40, 0x40},  // DEC(84), 'S'
  {0x7e, 0x01, 0x01, 0x01, 0x7e},  // DEC(85), 'U'
  {0x7c, 0x02, 0x01, 0x02, 0x7c},  // DEC(86), 'V'
  {0x7f, 0x02, 0x04, 0x02, 0x7f},  // DEC(87), 'W' - position 55
  {0x63, 0x14, 0x08, 0x14, 0x63},  // DEC(88), 'X'
  {0x60, 0x10, 0x0f, 0x10, 0x60},  // DEC(89), 'Y'
  {0x43, 0x45, 0x49, 0x51, 0x61},  // DEC(90), 'Z'
  {0x00, 0x7e, 0x42, 0x42, 0x00},  // DEC(91), '{'
  {0x20, 0x10, 0x08, 0x04, 0x00},  // DEC(92), '\' - position 60
  {0x00, 0x42, 0x42, 0x7e, 0x00},  // DEC(93), '}'
  {0x00, 0x20, 0x40, 0x20, 0x00},  // DEC(94), '^'
  {0x01, 0x01, 0x01, 0x01, 0x00},  // DEC(95), '_'
  {0x00, 0x40, 0x20, 0x10, 0x00},  // DEC(96), '`'
  {0x02, 0x15, 0x15, 0x15, 0x0F},  // DEC(97), 'a' - position 65
  {0x7F, 0x05, 0x09, 0x09, 0x06},  // DEC(98), 'b'
  {0x0E, 0x11, 0x11, 0x11, 0x02},  // DEC(99), 'c'
  {0x06, 0x09, 0x09, 0x05, 0x7F},  // DEC(100), 'd'
  {0x0E, 0x15, 0x15, 0x15, 0x0C},  // DEC(101), 'e'
  {0x08, 0x3F, 0x44, 0x40, 0x20},  // DEC(102), 'f' - position 70
  {0x18, 0x25, 0x25, 0x25, 0x3E},  // DEC(103), 'g'
  {0x7F, 0x08, 0x10, 0x10, 0x0F},  // DEC(104), 'h'
  {0x00, 0x00, 0x2F, 0x00, 0x00},  // DEC(105), 'i'
  {0x02, 0x01, 0x11, 0x5E, 0x00},  // DEC(106), 'j'
  {0x7F, 0x04, 0x06, 0x11, 0x00},  // DEC(107), 'k' - position 75
  {0x00, 0x41, 0x7F, 0x01, 0x00},  // DEC(108), 'l'
  {0x1F, 0x10, 0x0C, 0x10, 0x0F},  // DEC(109), 'm'
  {0x1F, 0x08, 0x10, 0x10, 0x0F},  // DEC(110), 'n'
  {0x0E, 0x11, 0x11, 0x11, 0x0E},  // DEC(111), 'o'
  {0x1F, 0x14, 0x14, 0x14, 0x08},  // DEC(112), 'p' - position 80
  {0x08, 0x14, 0x14, 0x0C, 0x1F},  // DEC(113), 'q'
  {0x1F, 0x08, 0x10, 0x10, 0x08},  // DEC(114), 'r'
  {0x09, 0x15, 0x15, 0x15, 0x02},  // DEC(115), 's'
  {0x10, 0x7E, 0x11, 0x01, 0x02},  // DEC(116), 't'
  {0x1E, 0x01, 0x01, 0x02, 0x1F},  // DEC(117), 'u' - position 85
  {0x1C, 0x02, 0x01, 0x02, 0x1C},  // DEC(118), 'v'
  {0x1E, 0x01, 0x03, 0x01, 0x1E},  // DEC(119), 'w'
  {0x11, 0x0A, 0x04, 0x0A, 0x11},  // DEC(120), 'x'
  {0x18, 0x05, 0x05, 0x05, 0x1E},  // DEC(121), 'y'
  {0x11, 0x13, 0x15, 0x19, 0x11},  // DEC(122), 'z' - position 90
  {0x00, 0x10, 0x3c, 0x42, 0x00},  // DEC(123), '{'
  {0x00, 0x00, 0x7e, 0x00, 0x00},  // DEC(124), '|'
  {0x00, 0x42, 0x3c, 0x10, 0x00},  // DEC(125), '}'
  {0x20, 0x40, 0x20, 0x40, 0x00},  // DEC(126), '~' - position 94
  {0x04, 0x0e, 0x15, 0x04, 0x7c},  // 'Enter symbol' - position 95 - used to signal end of entry on LED matrix
  {0x20, 0x50, 0x20, 0x00, 0x00},  // 'Degrees symbol' - position 96 - used when showing temperature


  // font_style = 2
  {0x7f, 0x41, 0x41, 0x41, 0x7f},  // '0' - position 97
  {0x00, 0x20, 0x7f, 0x00, 0x00},
  {0x4f, 0x49, 0x49, 0x49, 0x79},
  {0x49, 0x49, 0x49, 0x49, 0x7f},
  {0x78, 0x08, 0x08, 0x08, 0x7f},  // '4' - position 101
  {0x79, 0x49, 0x49, 0x49, 0x4f},
  {0x7f, 0x49, 0x49, 0x49, 0x4f},
  {0x40, 0x40, 0x40, 0x40, 0x7f},
  {0x7f, 0x49, 0x49, 0x49, 0x7f},
  {0x79, 0x49, 0x49, 0x49, 0x7f},   // '9' - position 106

  // font_style = 3
  {0x7f, 0x7f, 0x41, 0x41, 0x7f, 0x7f}, // '0' - position 107
  {0x00, 0x21, 0x7f, 0x7f, 0x01, 0x00},
  {0x67, 0x6f, 0x49, 0x49, 0x7b, 0x73},
  {0x63, 0x63, 0x49, 0x49, 0x7f, 0x7f},
  {0x78, 0x78, 0x08, 0x08, 0x7f, 0x7f}, // '4' - position 111
  {0x79, 0x79, 0x49, 0x49, 0x6f, 0x66},
  {0x7f, 0x7f, 0x49, 0x49, 0x6f, 0x6f},
  {0x60, 0x60, 0x47, 0x4f, 0x78, 0x70},
  {0x7f, 0x7f, 0x49, 0x49, 0x7f, 0x7f},
  {0x7b, 0x7b, 0x49, 0x49, 0x7f, 0x7f}, // '9' - position 116

  // font_style = 4
  {0x3e, 0x7f, 0x49, 0x51, 0x7f, 0x3e}, // '0' - position 117
  {0x01, 0x11, 0x7f, 0x7f, 0x01, 0x01},
  {0x23, 0x67, 0x45, 0x49, 0x79, 0x31},
  {0x22, 0x61, 0x49, 0x49, 0x7f, 0x36},
  {0x0c, 0x14, 0x24, 0x7f, 0x7f, 0x04}, // '4' - position 121
  {0x72, 0x73, 0x51, 0x51, 0x5f, 0x4e},
  {0x3e, 0x7f, 0x49, 0x49, 0x6f, 0x26},
  {0x60, 0x60, 0x47, 0x5f, 0x78, 0x60},
  {0x36, 0x7f, 0x49, 0x49, 0x7f, 0x36},
  {0x32, 0x7b, 0x49, 0x49, 0x7f, 0x3e}, // '9' - position 126

  // font_style = 5
  {0x3e, 0x7f, 0x41, 0x41, 0x7f, 0x3e}, // '0' - position 127
  {0x01, 0x21, 0x7f, 0x7f, 0x01, 0x01},
  {0x23, 0x67, 0x4d, 0x49, 0x7b, 0x33},
  {0x22, 0x63, 0x49, 0x49, 0x7f, 0x36},
  {0x0c, 0x14, 0x25, 0x7f, 0x7f, 0x05}, // '4' - position 131
  {0x72, 0x73, 0x51, 0x51, 0x5f, 0x4e},
  {0x1e, 0x3f, 0x69, 0x49, 0x4f, 0x06},
  {0x60, 0x60, 0x47, 0x4f, 0x78, 0x70},
  {0x36, 0x7f, 0x49, 0x49, 0x7f, 0x36},
  {0x30, 0x79, 0x49, 0x4b, 0x7e, 0x3c}, // '9' - position 136

  // font_style = 6
  {0x7f, 0x41, 0x41, 0x41, 0x41, 0x7f}, // '0' - position 137
  {0x01, 0x41, 0x41, 0x7f, 0x01, 0x01},
  {0x0f, 0x09, 0x49, 0x49, 0x49, 0x79},
  {0x01, 0x09, 0x49, 0x49, 0x49, 0x7f},
  {0x0e, 0x02, 0x02, 0x02, 0x7f, 0x02}, // '4' - position 141
  {0x79, 0x49, 0x49, 0x49, 0x09, 0x0f},
  {0x7f, 0x49, 0x49, 0x49, 0x09, 0x0f},
  {0x40, 0x40, 0x40, 0x40, 0x40, 0x7f},
  {0x0f, 0x79, 0x49, 0x49, 0x79, 0x0f},
  {0x78, 0x48, 0x49, 0x49, 0x49, 0x7f}, // '9' - position 146

};

unsigned const int PROGMEM mytinyfont[45][3] = {

  {0x00, 0x00, 0x00},  // space - number 0
  {0x1F, 0x14, 0x1F},  // A - number 1
  {0x1F, 0x15, 0x0A},
  {0x1F, 0x11, 0x11},
  {0x1F, 0x11, 0x0E},
  {0x1F, 0x15, 0x11},
  {0x1F, 0x14, 0x10},  // F
  {0x1F, 0x11, 0x17},
  {0x1F, 0x04, 0x1F},
  {0x11, 0x1F, 0x11},
  {0x03, 0x01, 0x1F},
  {0x1F, 0x04, 0x1B},  // K
  {0x1F, 0x01, 0x01},
  {0x1F, 0x08, 0x1F},
  {0x1F, 0x10, 0x0F},
  {0x1F, 0x11, 0x1F},
  {0x1F, 0x14, 0x1C},  // P
  {0x1C, 0x14, 0x1F},
  {0x1F, 0x16, 0x1D},
  {0x1D, 0x15, 0x17},
  {0x10, 0x1F, 0x10},
  {0x1F, 0x01, 0x1F},  // U
  {0x1E, 0x01, 0x1E},
  {0x1F, 0x02, 0x1F},
  {0x1B, 0x04, 0x1B},
  {0x1C, 0x07, 0x1C},
  {0x13, 0x15, 0x19},  // Z - number 26

  {0x01, 0x00, 0x00},  // . - number 27
  {0x00, 0x0A, 0x00},  // :
  {0x18, 0x00, 0x00},  // '
  {0x0A, 0x04, 0x00},  // >
  {0x04, 0x04, 0x04},  // -
  {0x02, 0x04, 0x08},  // / - number 32
  {0x04, 0x0e, 0x04},  // +
  {0x17, 0x14, 0x1c},  // ?

  {0x1F, 0x11, 0x1F},  // 0 - number 35
  {0x00, 0x00, 0x1F},
  {0x17, 0x15, 0x1D},
  {0x11, 0x15, 0x1F},
  {0x1C, 0x04, 0x1F},
  {0x1D, 0x15, 0x17},
  {0x1F, 0x15, 0x17},
  {0x10, 0x10, 0x1F},
  {0x1F, 0x15, 0x1F},
  {0x1D, 0x15, 0x1F},  // 9 - number 44
};

ProgmemData.h

Arduino
Called by main code - add into new tab of same name
#include <avr/pgmspace.h>

// Full day names
const char d_0[] PROGMEM = "Sunday";
const char d_1[] PROGMEM = "Monday";
const char d_2[] PROGMEM = "Tuesday";
const char d_3[] PROGMEM = "Wed";
const char d_4[] PROGMEM = "Thursday";
const char d_5[] PROGMEM = "Friday";
const char d_6[] PROGMEM = "Saturday";

const char *const daysfull[] PROGMEM = {d_0, d_1, d_2, d_3, d_4, d_5, d_6};

// Full month names
const char m_0[] PROGMEM = "January";
const char m_1[] PROGMEM = "February";
const char m_2[] PROGMEM = "March";
const char m_3[] PROGMEM = "April";
const char m_4[] PROGMEM = "May";
const char m_5[] PROGMEM = "June";
const char m_6[] PROGMEM = "July";
const char m_7[] PROGMEM = "August";
const char m_8[] PROGMEM = "Sept";
const char m_9[] PROGMEM = "October";
const char m_10[] PROGMEM = "November";
const char m_11[] PROGMEM = "December";

const char *const monthsfull[] PROGMEM = {m_0, m_1, m_2, m_3, m_4, m_5, m_6, m_7, m_8, m_9, m_10, m_11};

// Words 1 - 19
const char n_0[] PROGMEM = "one";
const char n_1[] PROGMEM = "two";
const char n_2[] PROGMEM = "three";
const char n_3[] PROGMEM = "four";
const char n_4[] PROGMEM = "five";
const char n_5[] PROGMEM = "six";
const char n_6[] PROGMEM = "seven";
const char n_7[] PROGMEM = "eight";
const char n_8[] PROGMEM = "nine";
const char n_9[] PROGMEM = "ten";
const char n_10[] PROGMEM = "eleven";
const char n_11[] PROGMEM = "twelve";
const char n_12[] PROGMEM = "thirteen";
const char n_13[] PROGMEM = "fourteen";
const char n_14[] PROGMEM = "fifteen";
const char n_15[] PROGMEM = "sixteen";
const char n_16[] PROGMEM = "sevent'n";
const char n_17[] PROGMEM = "eighteen";
const char n_18[] PROGMEM = "nineteen";

const char *const numbers[] PROGMEM = {n_0, n_1, n_2, n_3, n_4, n_5, n_6, n_7, n_8, n_9, n_10, n_11, n_12, n_13, n_14, n_15, n_16, n_17, n_18};

// Words tens
const char t_0[] PROGMEM = "ten";
const char t_1[] PROGMEM = "twenty";
const char t_2[] PROGMEM = "thirty";
const char t_3[] PROGMEM = "forty";
const char t_4[] PROGMEM = "fifty";

const char *const numberstens[] PROGMEM = {t_0, t_1, t_2, t_3, t_4};

// Words
const char w_0[] PROGMEM = "past";
const char w_1[] PROGMEM = "to";
const char w_2[] PROGMEM = "half";
const char w_3[] PROGMEM = "quarter";
const char w_4[] PROGMEM = "o'clock";

const char w_5[] PROGMEM = ">FREQ";
const char w_6[] PROGMEM = ">HOUR  ";
const char w_7[] PROGMEM = ">DOW ";
const char w_8[] PROGMEM = ">DATE";
const char w_9[] PROGMEM = ">EXIT";
const char w_10[] PROGMEM = "BRIGHT";

const char *const cwords[] PROGMEM = {w_0, w_1, w_2, w_3, w_4, w_5, w_6, w_7, w_8, w_9, w_10};

//Clock modes
const char cm_0[] PROGMEM = ">BASIC";
const char cm_1[] PROGMEM = ">SMALL";
const char cm_2[] PROGMEM = ">SLIDE";
const char cm_3[] PROGMEM = ">WORDS";
const char cm_4[] PROGMEM = ">SETUP";

const char *const cmodes[] PROGMEM = {cm_0, cm_1, cm_2, cm_3, cm_4};

//Setup menu
const char sme_0[] PROGMEM = ">RND CLK";
const char sme_1[] PROGMEM = ">RND FNT";
const char sme_2[] PROGMEM = ">12 HR  ";
const char sme_3[] PROGMEM = ">FONT ";
const char sme_4[] PROGMEM = ">DST/TZ";
const char sme_5[] PROGMEM = ">D/TIME";
const char sme_6[] PROGMEM = ">AUTO LX";
const char sme_7[] PROGMEM = ">BRIGHT ";
const char sme_8[] PROGMEM = ">DISPLAY";
const char sme_9[] PROGMEM = ">WIFI   ";
const char sme_10[] PROGMEM = ">NTP ";
const char sme_11[] PROGMEM = ">EXIT";

const char *const smewords[] PROGMEM = {sme_0, sme_1, sme_2, sme_3, sme_4, sme_5, sme_6, sme_7, sme_8, sme_9, sme_10, sme_11};

//set_ntp_dst menu
const char dstme_0[] PROGMEM = ">DST";
const char dstme_1[] PROGMEM = ">SUMMER "; 
const char dstme_2[] PROGMEM = ">TZ    ";
const char dstme_3[] PROGMEM = ">EXIT";

const char *const dstwords[] PROGMEM = {dstme_0, dstme_1, dstme_2, dstme_3};

//set_time menu
const char tme_0[] PROGMEM = ">SET MIN";
const char tme_1[] PROGMEM = ">SET HR ";
const char tme_2[] PROGMEM = ">SET DAY";
const char tme_3[] PROGMEM = ">SET MTH";
const char tme_4[] PROGMEM = ">SET YR ";
const char tme_5[] PROGMEM = ">EXIT  ";

const char *const tmewords[] PROGMEM = {tme_0, tme_1, tme_2, tme_3, tme_4, tme_5};

//Set NTP frequency wording
const char freq_0[] PROGMEM = "DAILY  ";
const char freq_1[] PROGMEM = "WEEKLY";
const char freq_2[] PROGMEM = "MONTHLY";

const char *const freqwords[] PROGMEM = {freq_0, freq_1, freq_2};

//Display options menu
const char dopt_0[] PROGMEM = ">FAD DLY";
const char dopt_1[] PROGMEM = ">COLON  ";
const char dopt_2[] PROGMEM = ">TIMER  ";
const char dopt_3[] PROGMEM = ">ON HR";
const char dopt_4[] PROGMEM = ">ON MIN";
const char dopt_5[] PROGMEM = ">OFF HR";
const char dopt_6[] PROGMEM = ">OFF MIN";
const char dopt_7[] PROGMEM = ">EXIT   ";

const char *const doptwords[] PROGMEM = {dopt_0, dopt_1, dopt_2, dopt_3, dopt_4, dopt_5, dopt_6, dopt_7};

//Set colon wording
const char colw_0[] PROGMEM = "SYNC  ";
const char colw_1[] PROGMEM = "ALTERN";
const char colw_2[] PROGMEM = "SOLID ";
const char colw_3[] PROGMEM = "SCROLL";

const char *const colonwords[] PROGMEM = {colw_0, colw_1, colw_2, colw_3};

//Wifi menu
const char wi_0[] PROGMEM = ">SET WIF";
const char wi_1[] PROGMEM = ">SET ID ";
const char wi_2[] PROGMEM = ">SET PAS";
const char wi_3[] PROGMEM = ">EXIT   ";

const char *const wiwords[] PROGMEM = {wi_0, wi_1, wi_2, wi_3};

Arduino

Credits

doodles2000
0 projects • 0 followers
Contact

Comments

Please log in or sign up to comment.