回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【Arduino 动手做】带DS3231、BME280、BH1750和ESP01的LED矩阵NTP时钟 简单

头像 驴友花雕 2025.07.27 11 0

修改了现有项目以包含一些传感器和额外的按钮。可以使用ESP01通过NTP设置时间。

这就是让我进入 Arduino 的原因。我遇到了 Nick 的 LED 字钟,并想做它。
自从下载他的代码以来,我对其进行了修改,以包含额外的传感器和按钮,并添加了一个 ESP01。
BH1750 用于根据光线条件自动调暗显示屏,并且可以在夜间关闭显示屏。BME280 显示温度、湿度和压力。ESP01 用于从 pool.ntp.org 获取时间。
额外的按钮可以轻松调整设置。您可以使用按钮调整光传感器设置,以及更改字体和 NTP/DST/UTC 设置。
添加了六种新字体,我使用以下方法制作了字体:http://dotmatrixtool.com/。
我对单词时钟进行了相当多的更改,它现在有更多的单词,并且已经移动到 PROGMEM 以节省 RAM。
Nick 的项目页面:https://123led.wordpress.com/mini-led-clock/
我的代码:https://github.com/Ratti3/miniclock
代码现在已经完成,除了未知的错误和到处的调整之外,我将为此提供理由。
您需要调整光传感器代码以匹配您的光线条件,我是在明亮的阳光下坐在窗户旁边对其进行编码的。
ESP01 用于通过 NTP 获取时间,ESP01 在不需要时编码为睡眠(wifi 关闭),并通过串行唤醒以获取 UNIX 格式的时间。为了方便起见,SSID 名称和密码通过 Arduino 代码传递。
此版本还具有 DST/UTC 和 BST 计算。这些设置可以通过菜单调整或禁用。
设置保存到 EEPROM,这意味着如果您关闭并重新打开电源,则通过菜单所做的更改将被保存。

 

02.jpg

03.jpg
04.jpg
05.jpg
06.jpg
07.jpg
08.jpg
09.jpg
10.jpg
11.jpg
12.jpg
13.jpg
14.jpg
00190.gif
matrixclock_bb_6XlOAfEhqS.png

项目代码

 

代码
/***********************************************************************

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.1 (Non ESP01 Version)
Tested on IDE v1.8.12

26,046 bytes 84%
1019 bytes 49%

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

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

//include libraries:
#include "ProgmemData.h"                 // Progmem Storage File, holds day, month and time names, frees up precious RAM
#include <LedControl.h>                  // v1.0.6 https://github.com/wayoda/LedControl
#include <FontLEDClock.h>                // https://github.com/javastraat/arduino/blob/master/libraries/FontLEDClock/FontLEDClock.h - however, it has been modified
#include <Wire.h>                        // Standard Arduino library
#include <RTClib.h>                      // v1.7.0 DS3231 RTC - https://github.com/adafruit/RTClib
#include <Button.h>                      // https://github.com/tigoe/Button
#include <Adafruit_Sensor.h>             // v1.1.2 Required by BME280 - https://github.com/adafruit/Adafruit_Sensor
#include <Adafruit_BME280.h>             // v2.0.2 BME280 Environmental Sensor - https://github.com/adafruit/Adafruit_BME280_Library
#include <BH1750FVI.h>                   // v1.1.1 BH1750 Light Sensor - https://github.com/PeterEmbedded/BH1750FVI
#include <EEPROM.h>                      // Used to save settings to Arduino EEPROM

// Setup LED Matrix
// pin 12 is connected to the DataIn on the display
// pin 11 is connected to the CLK on the display
// pin 10 is connected to LOAD on the display
LedControl lc = LedControl(12, 11, 10, 4); //sets the 3 pins as 12, 11 & 10 and then sets 4 displays (max is 8 displays)

// Global variables (changeable defaults), numbers in [] brackets are the EEPROM storage location for that value
  // Clock settings
byte intensity = 2;                      // [200] Default intensity/brightness (0-15), can be set via menu
byte clock_mode = 0;                     // [201] Default clock mode. Default = 0 (basic_mode)
bool random_mode = 0;                    // [206] Define random mode - changes the display type every few hours. Default = 0 (off)
bool random_font_mode = 0;               // [207] Define font random mode - changes the font every few hours. 1 = random font on
bool ampm = 0;                           // [208] Define 12 or 24 hour time. 0 = 24 hour. 1 = 12 hour
  // Light settings
byte display_mode = 4;                   // [202] Default display on/off mode, used by light sensor. 0 = normal, 1 = always on, 2 - 4 = defined by hour_off_1,2,3
bool auto_intensity = 1;                 // [209] Default auto light intensity setting
byte hour_off_1 = 21;                    // These three define the hour light sensor can turn off display if dark enough, format is 24 hours, the routine for
byte hour_off_2 = 22;                    // this checks between hour_on and one of these values
byte hour_off_3 = 23;
byte hour_on = 6;                        // This is used to turn on the display, works with hour_off_1,2,3
  // Font settings - these are set via the setup Font menu, see set_font_case() routine for all default values:
byte font_style = 2;                     // [203] Default clock large font style
byte font_offset = 1;                    // [204] Default clock large font offset adjustment
byte font_cols = 6;                      // [205] Default clock large font columns adjustment
  // DST NTP and UTC settings
bool dst_mode = 1;                       // [210] Enable DST function, 1 = enable, 0 = disable
byte ntp_dst_hour = 2;                   // The hour daily NTP/DST sync happens, should be left at 2am if using DST mode

// Global variables
bool shut = 0;                           // Stores matrix on/off state
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.
unsigned long delaytime = 500;           // We always wait a bit between updates of the display
int rtc[7];                              // Holds real time clock output
int light_count = 0;                     // Counter for light routine
byte auto_intensity_value = 0;           // Stores the last intensity value set by the light sensor, this value is set automatically
char words[1];                           // Holds word clock words, retrieved from progmem
bool DST = 0;                            // [212] Holds DST applied value, 1 = summertime +1hr applied, this is to ensure DST +1/-1 runs only once
bool dst_ntp_run = 0;                    // Holds the value to see if ntp() and dst() have run once a day
bool dont_turn_off = 0;                  // Holds value for turning display on or off
byte FirstRunValue = 128;                // The check digits to see if EEPROM has values saved, change this [1-254] if you want to reset EEPROM to default values
byte FirstRunAddress = 255;              // [255] Address on EEPROM FirstRunValue is saved

//define constants
#define NUM_DISPLAY_MODES  3                    // Number display modes = 3 (counting zero as the first mode)
#define NUM_SETTINGS_MODES 9                    // Number settings modes = 9 (counting zero as the first mode)
#define NUM_FONTS          7                    // Number of fonts, as defined in FontLEDClock.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 cls                clear_display        // Clear display
#define RandomSeed         A0                   // Pin used to generate random seed
#define TX                 6                    // RX pin of ESP01
#define RX                 7                    // TX pin of ESP01
//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

RTC_DS3231 ds3231;                              // Create RTC object
Adafruit_BME280 bme;                            // BME280 object (pins 4 and 5 and 3.3v)
BH1750FVI lux(BH1750FVI::k_DevModeContHighRes); // BH1750 object (pins 4 and 5 and 3.3v)

Button buttonA = Button(2, BUTTON_PULLUP);      // Menu button
Button buttonB = Button(3, BUTTON_PULLUP);      // Display date / + button
Button buttonC = Button(4, BUTTON_PULLUP);      // Temp/Humidity/Pressure / - button

void setup() {

  digitalWrite(2, HIGH);                        // turn on pullup resistor for button on pin 2
  digitalWrite(3, HIGH);                        // turn on pullup resistor for button on pin 3
  digitalWrite(4, HIGH);                        // turn on pullup resistor for button on pin 4
  
  Serial.begin(9600); //start serial

  //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
    lc.clearDisplay(address);            // and clear the display
  }

  //I2C
  Wire.begin();

  //Setup DS3231 RTC
  ds3231.begin();
  //ds3231.adjust(DateTime(2019, 6, 29, 12, 59, 40));  // Set time manually
  //ds3231.adjust(DateTime(__DATE__, __TIME__)); // sets the RTC to the date & time this sketch was compiled
  if (!ds3231.begin()) {
    Serial.println("Couldn't find RTC");
    while(1);
  }

  //BME280 environmental sensor, this sensor from Ebay has address 0x76
  bme.begin(0x76);
  //Reduce BME sampling rate to prevent overheating
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
    Adafruit_BME280::SAMPLING_X1, // temperature
    Adafruit_BME280::SAMPLING_X1, // pressure
    Adafruit_BME280::SAMPLING_X1, // humidity
    Adafruit_BME280::FILTER_OFF);

  //BH1750 light sensor
  lux.begin();

  //what is this silliness, needed for random() to work properly
  randomSeed(analogRead(RandomSeed)); // Pin A0

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

  byte FirstRun = eeprom_read_byte(FirstRunAddress);
  bool FR = 0;
  if (FirstRun == FirstRunValue) FR = 1;

  byte value1;
  bool value2;

  /*Serial.print("Read FirstRun: [");
  Serial.print(FirstRunAddress);
  Serial.print("] ");
  Serial.println(FirstRun);*/

  byte i = 200;
  while (i < 213) {
    if (i <= 205) {
    
      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;
        }
      }
      else if (i == 202) {
        if (FR) {
          display_mode = value1;
        }
        else {
          value1 = display_mode;
        }
      }
      else if (i == 203) {
        if (FR) {
          font_style = value1;
        }
        else {
          value1 = font_style;
        }
      }
      else if (i == 204) {
        if (FR) {
          font_offset = value1;
        }
        else {
          value1 = font_offset;
        }
      }
      else if (i == 205) {
        if (FR) {
          font_cols = value1;
        }
        else {
          value1 = font_cols;
        }
      }

      if (!FR) {
        eeprom_save(i, value1, 0, 0);
        /*Serial.print("FirstRun - Update Byte: [");
        Serial.print(i);
        Serial.print("] ");
        Serial.println(value1);*/
      }
      //Serial.print("Read Byte: [");
      //Serial.print(i);
      //Serial.print("] ");
      //Serial.println(value1);
    
    }
    else if (i >= 206 && i <= 212) {
    
      value2 = eeprom_read_bool(i);
      if (i == 206) {
        if (FR) {
          random_mode = value2;
        }
        else {
          value2 = random_mode;
        }
      }
      else if (i == 207) {
        if (FR) {
          random_font_mode = value2;
        }
        else {
          value2 = random_font_mode;
        }
      }
      else if (i == 208) {
        if (FR) {
          ampm = value2;
        }
        else {
          value2 = ampm;
        }
      }
      else if (i == 209) {
        if (FR) {
          auto_intensity = value2;
        }
        else {
          value2 = auto_intensity;
        }
      }
      else if (i == 210) {
        if (FR) {
          dst_mode = value2;
        }
        else {
          value2 = dst_mode;
        }
      }
      else if (i == 211) {
        //Reserved for NTP mode
      }
      else if (i == 212) {
        if (FR) {
          DST = value2;
        }
        else {
          value2 = DST;
        }
      }

      if (!FR) {
        eeprom_save(i, 0, value2, 0);
        /*Serial.print("FirstRun - Update Bool: [");
        Serial.print(i);
        Serial.print("] ");
        Serial.println(value2);*/
      }
      //Serial.print("Read Bool: [");
      //Serial.print(i);
      //Serial.print("] ");
      //Serial.println(value2);

    }
    i++;
  }

  if (!FR) {
    eeprom_save(FirstRunAddress, FirstRunValue, 0, 0);
    /*Serial.print("Update FirstRun: [");
    Serial.print(FirstRunAddress);
    Serial.print("] ");
    Serial.println(FirstRunValue);*/
  }

  //run dst() calculation
  if (dst_mode) {
    dst();
  }

}


void loop() {

  //run the clock with whatever mode is set by clock_mode - the default is set at top of code.
  switch(clock_mode) {
  case 0:
    basic_mode();
    break;
  case 1:
    small_mode();
    break;
  case 2:
    slide();
    break;
  case 3:
    word_clock();
    break;
  case 4:
    setup_menu();
    break;
  }

}


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

  switch(Address) {
    case 200 ... 205:
      EEPROM.update(Address, value1);
      //Serial.print("Update Byte: [");
      //Serial.print(Address);
      //Serial.print("] ");
      //Serial.println(value1);
      break;
    case 206 ... 212:
      EEPROM.update(Address, value2);
      //Serial.print("Update Bool: [");
      //Serial.print(Address);
      //Serial.print("] ");
      //Serial.println(value2);
      break;
    case 255:
      EEPROM.update(Address, value1);
      //Serial.print("Update Byte: [");
      //Serial.print(Address);
      //Serial.print("] ");
      //Serial.println(value1);
      break;
  }
  
}


//function to read EEPROM data as byte
byte eeprom_read_byte(byte Address) {
  
  byte result = EEPROM.read(Address);
  return result;
  
}


//function to read EEPROM data as bool
bool eeprom_read_bool(byte Address) {
  
  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() {

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

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

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

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

}


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

  byte i = 0;
  const char ver_a[] = "Vers 1.1";
  const char ver_b[] = " Ratti3 ";

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

  while (ver_a[i]) {
    puttinychar((i * 4), 1, ver_a[i]);
    delay(35);
    i++;
  }
  delay(700);
  fade_down();
  i = 0;
  while (ver_b[i]) {
    puttinychar((i * 4), 1, ver_b[i]);
    delay(35);
    i++;
  }
  delay(700);
  fade_down();

}


// 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.
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') + 33;
  }
  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
  }

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

}


// fs = font_style, fc = font_cols
void putnormalchar(byte x, byte y, char c, byte fs, byte fc) {

  byte dots;
  if (c >= 'A' && c <= 'Z' ) {
    c = (c - 'A') + 27;   // A-Z maps to 27-52
  }
  else if (c >= 'a' && c <= 'z') {
    c &= 0x1F;   // a-z maps to 1-26
  }
  else if (c >= '0' && c <= '9') {
    switch(fs) {
      case 1:
        c = (c - '0') + 59;   // 0-9 maps to 59-68
        break;
      case 2:
        c = (c - '0') + 69;   // 0-9 maps to 69-78
        break;
      case 3:
        c = (c - '0') + 79;   // 0-9 maps to 79-88
        break;
      case 4:
        c = (c - '0') + 89;   // 0-9 maps to 89-98
        break;
      case 5:
        c = (c - '0') + 99;   // 0-9 maps to 99-108
        break;
      case 6:
        c = (c - '0') + 69;   // 0-9 maps to 69-78
        break;
      case 7:
        c = (c - '0') + 109;   // 0-9 maps to 109-108
        break;
    }
  }
  else if (c == ' ') {
    c = 0; // space
  }
  else if (c == '.') {
    c = 53; // full stop
  }
  else if (c == '\'') {
    c = 54; // single quote mark
  }
  else if (c == ':') {
    c = 55; // colon
  }
  else if (c == '>') {
    c = 56; // clock_mode selector arrow
  }
  else if (c == '~') {
    c = 57; // degrees
  }
  else if (c == '%') {
    c = 58; // percentage
  }
  else if (c >= -80 && c <= -67) {
    c *= -1;
  }

  for (char col = 0; col < fc; col++) {
    dots = pgm_read_byte_near(&myfont[c][col]);
    for (char row = 0; row < 7; row++) {
      //check coords are on screen before trying to plot
      //if ((x >= 0) && (x <= 31) && (y >= 0) && (y <= 7)){

      if (dots & (64 >> row)) {   // only 7 rows.
        plot(x + col, y + row, 1);
      } else {
        plot(x + col, y + row, 0);
      }
      //}
    }
  }

}


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

  char textchar[8]; // the 16 characters on the display
  byte mins = 100; //mins
  byte secs = rtc[0]; //seconds
  byte old_secs = secs; //holds old seconds value - from last time seconds were updated o display - used to check if seconds have changed
  
  cls();

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

    //Check light levels for turning on/off matrix
    if (light_count > 100) {
      light();
      light_count = 0;
    }
    light_count++;

    get_time();
  
    //check for button press
    if (buttonA.uniquePress()) {
      switch_mode();
      return;
    }
    if (buttonB.uniquePress()) {
      display_date();
      return;
    }
    if (buttonC.uniquePress()) {
      display_thp();
      return;
    }
    
    //if secs changed then update them on the display
    secs = rtc[0];
    if (secs != old_secs) {

      //secs
      char buffer[3];
      itoa(secs, buffer, 10);

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

      puttinychar( 20, 1, ':'); //seconds colon
      puttinychar( 24, 1, buffer[0]); //seconds
      puttinychar( 28, 1, buffer[1]); //seconds
      old_secs = secs;
    }

    //if minute changes change time
    if (mins != rtc[1]) {

      //reset these for comparison next time
      mins = rtc[1];
      byte hours = rtc[2];
      if (hours > 12) {
        hours = hours - ampm * 12;
      }
      if (hours < 1) {
        hours = hours + ampm * 12;
      }

      //set characters
      char buffer[3];
      itoa(hours, buffer, 10);

      //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa converts this to chars with space "3 ".
      if (hours < 10) {
        buffer[1] = buffer[0];
        //if we are in 12 hour mode blank the leading zero.
        if (ampm) {
          buffer[0] = ' ';
        }
        else {
          buffer[0] = '0';
        }
      }
      //set hours chars
      textchar[0] = buffer[0];
      textchar[1] = buffer[1];
      textchar[2] = ':';

      itoa (mins, buffer, 10);
      if (mins < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }
      //set mins characters
      textchar[3] = buffer[0];
      textchar[4] = buffer[1];

      //do seconds
      textchar[5] = ':';
      secs = rtc[0];
      itoa(secs, buffer, 10);

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

      //print each char
      for (byte x = 0; x < 6 ; x++) {
        puttinychar( x * 4, 1, textchar[x]);
      }
    }
    delay(50);
  }
  fade_down();

}


// show the time in 5x7 characters
void basic_mode() {

  cls();

  char buffer[3];   //for int to char conversion to turn rtc values into chars we can print on screen
  byte offset = 0;  //used to offset the x postition of the digits and centre the display when we are in 12 hour mode and the clock shows only 3 digits. e.g. 3:21

  //do 12/24 hour conversion if ampm set to 1
  byte hours = rtc[2];

  if (hours > 12) {
    hours = hours - ampm * 12;
  }
  if (hours < 1) {
    hours = hours + ampm * 12;
  }

  //do offset conversion
  if (ampm && hours < 10) {
    offset = 2;
  }

  //set the next minute we show the date at
  //set_next_date();

  // initially set mins to value 100 - 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 secs = 100;
  byte mins = 100;
  int count = 0;

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

    //Check light levels for turning on/off matrix
    if (light_count > 4000) {
      light();
      light_count = 0;
    }
    light_count++;

    //get the time from the clock chip
    get_time();
    
    //check for button press
    if (buttonA.uniquePress()) {
      switch_mode();
      return;
    }
    if (buttonB.uniquePress()) {
      display_date();
      return;
    }
    if (buttonC.uniquePress()) {
      display_thp();
      return;
    }

    //check whether it's time to automatically display the date
    //check_show_date();

    //draw the flashing : as on if the secs have changed.
    if (secs != rtc[0]) {

      //update secs with new value
      secs = rtc[0];

      //draw :
      plot (15 - offset, 2, 1); //top point
      plot (15 - offset, 5, 1); //bottom point
      count = 400;
    }

    //if count has run out, turn off the :
    if (count == 0) {
      plot (15 - offset, 2, 0); //top point
      plot (15 - offset, 5, 0); //bottom point
    }
    else {
      count--;
    }

    //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 mins, (also trigggered on first entering function when mins is 100)
    if (mins != rtc[1]) {

      //update mins and hours with the new values
      mins = rtc[1];
      hours = rtc[2];

      //adjust hours of ampm set to 12 hour mode
      if (hours > 12) {
        hours = hours - ampm * 12;
      }
      if (hours < 1) {
        hours = hours + ampm * 12;
      }

      itoa(hours, buffer, 10);

      //if hours < 10 the num e.g. "3" hours, itoa coverts this to chars with space "3 " which we dont want
      if (hours < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }

      //print hours
      //if we in 12 hour mode and hours < 10, then don't print the leading zero, and set the offset so we centre the display with 3 digits.
      if (ampm && hours < 10) {
        offset = 2;

        //if the time is 1:00am clear the entire display as the offset changes at this time and we need to blank out the old 12:59
        if ((hours == 1 && mins == 0) ) {
          cls();
        }
      }
      else {
        //else no offset and print hours tens digit
        offset = 0;

        //if the time is 10:00am clear the entire display as the offset changes at this time and we need to blank out the old 9:59
        if (hours == 10 && mins == 0) {
          cls();
        }

        putnormalchar(1,  0, buffer[0], font_style, font_cols);
      }
      //print hours ones digit
      putnormalchar(7 - offset + font_offset, 0, buffer[1], font_style, font_cols);

      //print mins
      //add leading zero if mins < 10
      itoa (mins, buffer, 10);
      if (mins < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }
      //print mins tens and ones digits
      putnormalchar(19 - offset - font_offset - font_offset, 0, buffer[0], font_style, font_cols);
      putnormalchar(25 - offset - font_offset, 0, buffer[1], font_style, font_cols);
    }
  }
  fade_down();

}


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

  byte digits_old[4] = {99, 99, 99, 99}; //old values  we store time in. Set to somthing that will never match the time initially so all digits get drawn wnen the mode starts
  byte digits_new[4]; //new digits time will slide to reveal
  byte digits_x_pos[4] = {25, 19, 7, 1}; //x pos for which to draw each digit at

  char old_char[2]; //used when we use itoa to transpose the current digit (type byte) into a char to pass to the animation function
  char new_char[2]; //used when we use itoa to transpose the new digit (type byte) into a char to pass to the animation function

  //old_chars - stores the 5 day and date suffix chars on the display. e.g. "mon" and "st". We feed these into the slide animation as the current char when these chars are updated.
  //We sent them as A initially, which are used when the clocl enters the mode and no last chars are stored.
  //char old_chars[6] = "AAAAA";

  //plot the clock colon on the display
  cls();
  putnormalchar(13, 0, ':', font_style, font_cols);

  byte old_secs = rtc[0]; //store seconds in old_secs. We compare secs and old secs. WHen they are different we redraw the display

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

    //Check light levels for turning on/offf matrix
    if (light_count > 5000) {
      light();
      light_count = 0;
    }
    light_count++;

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

    //if secs have changed then update the display
    if (rtc[0] != old_secs) {
      old_secs = rtc[0];

      //do 12/24 hour conversion if ampm set to 1
      byte hours = rtc[2];
      if (hours > 12) {
        hours = hours - ampm * 12;
      }
      if (hours < 1) {
        hours = hours + ampm * 12;
      }

      //split all date and time into individual digits - stick in digits_new array
      digits_new[0] = (rtc[1] % 10);         //2 - mins ones
      digits_new[1] = ((rtc[1] / 10) % 10);  //3 - mins tens
      digits_new[2] = (hours % 10);         //4 - hour ones
      digits_new[3] = ((hours / 10) % 10);  //5 - hour tens

      //draw initial screen of all chars. After this we just draw the changes.

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

          //run 9 step animation sequence for each in turn
          for (byte seq = 0; seq <= 8 ; seq++) {

            //convert digit to string
            itoa(digits_old[i], old_char, 10);
            itoa(digits_new[i], new_char, 10);

            //if set to 12 hour mode and we're on digit 2 (hours tens mode) then check to see if this is a zero. If it is, blank it instead so we get 2.00pm not 02.00pm
            if (ampm && i == 3) {
              if (digits_new[3] == 0) {
                new_char[0] = ' ';
              }
              if (digits_old[3] == 0) {
                old_char[0] = ' ';
              }
            }
            //draw the animation frame for each digit
            slideanim(digits_x_pos[i], 0, seq, old_char[0], new_char[0]);
            delay(SLIDE_DELAY);
          }
        }
      }

      //save digita array tol old for comparison next loop
      for (byte i = 0; i <= 3; i++) {
        digits_old[i] =  digits_new[i];
      }
    }//secs/oldsecs
  }//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 y, byte sequence, char current_c, char 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.

  //if sequence number is below 7, we need to draw the current char
  if (sequence < 7) {
    byte dots;
    if (current_c >= 'A' && current_c <= 'Z' ) {
      current_c = (current_c - 'A') + 27;   // A-Z maps to 27-52
    }
    else if (current_c >= 'a' && current_c <= 'z') {
      current_c &= 0x1F;   // a-z maps to 1-26
    }
    else if (current_c >= '0' && current_c <= '9') {
      current_c = (current_c - '0') + 59;   // 0-9 maps to 59-68
    }
    else if (current_c == ' ') {
      current_c = 0; // space
    }
    else if (current_c == '.') {
      current_c = 53; // full stop
    }
    else if (current_c == '\'') {
      current_c = 54; // single quote mark
    }
    else if (current_c == ':') {
      current_c = 55; //colon
    }
    else if (current_c == '>') {
      current_c = 56; // >
    }

    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 <= curr_char_row_max; curr_char_row++) {
      for (byte col = 0; col < 5; col++) {
        dots = pgm_read_byte_near(&myfont[current_c][col]);
        if (dots & (64 >> curr_char_row))
          plot(x + col, y + start_y, 1); //plot led on
        else
          plot(x + col, y + 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 < 5; col++) {
      plot(x + col, y + (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
    byte dots;
    if (new_c >= 'A' && new_c <= 'Z' ) {
      new_c = (new_c - 'A') + 27;   // A-Z maps to 27-52
    }
    else if (new_c >= 'a' && new_c <= 'z') {
      new_c &= 0x1F;   // a-z maps to 1-26
    }
    else if (new_c >= '0' && new_c <= '9') {
      new_c = (new_c - '0') + 59;   // 0-9 maps to 59-68
    }
    else if (new_c == ' ') {
      new_c = 0; // space
    }
    else if (new_c == '.') {
      new_c = 27; // full stop
    }
    else if (new_c == '\'') {
      new_c = 28; // single quote mark
    }
    else if (new_c == ':') {
      new_c = 29; // colon
    }
    else if (new_c == '>') {
      new_c = 30; // >
    }

    byte newcharrowmin = 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 newcharrow = newcharrowmin; newcharrow <= 6; newcharrow++) {
      for (byte col = 0; col < 5; col++) {
        dots = pgm_read_byte_near(&myfont[new_c][col]);
        if (dots & (64 >> newcharrow))
          plot(x + col, y + start_y, 1); //plot led on
        else
          plot(x + col, y + 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() {

  cls();
  
  //potentially 3 lines to display
  char str_a[8];
  char str_b[9];
  char str_c[8];

  //byte hours_y, mins_y; //hours and mins and positions for hours and mins lines

  byte hours = rtc[2];
  if (hours > 12) {
    hours = hours - ampm * 12;
  }
  if (hours < 1) {
    hours = hours + ampm * 12;
  }

  get_time(); //get the time from the clock chip
  byte old_mins = 100; //store mins in old_mins. We compare mins and old mins & when they are different we redraw the display. Set this to 100 initially so display is drawn when mode starts.
  byte mins;

  //run clock main loop as long as run_mode returns true
  while (run_mode()) {
    
    //check for button press
    if (buttonA.uniquePress()) {
      switch_mode();
      return;
    }
    if (buttonB.uniquePress()) {
      display_date();
    }
    if (buttonC.uniquePress()) {
      display_thp();
      return;
    }

    get_time(); //get the time from the clock chip
    mins = rtc[1];  //get mins

    //if mins is different from old_mins - redraw display
    if (mins != old_mins) {

      //update old_mins with current mins value
      old_mins = mins;

      //reset these for comparison next time
      mins = rtc[1];
      hours = rtc[2];

      //make hours into 12 hour format
      if (hours > 12) {
        hours = hours - 12;
      }
      if (hours == 0) {
        hours = 12;
      }

      //split mins value up into two separate digits
      int minsdigit = rtc[1] % 10;
      byte minsdigitten = (rtc[1] / 10) % 10;

      const char past[] = "PAST";
      const char to[] = "TO";
      const char half[] = "HALF";
      const char quar[] = "QUARTER";
      const char oclk[] = "O'CLOCK";

      //if both mins are zero, i.e. it is on the hour, the top line reads "hours" and bottom line reads "o'clock"
      if (minsdigitten == 0 && minsdigit == 0  ) {
        progmem_numbers(0, hours - 1);
        strcpy (str_a, words);
        strcpy (str_b, oclk);
        strcpy (str_c, "");
      }
      //if mins <= 10 , then top line has to read "minsdigti past" and bottom line reads hours
      else if (mins < 10) {
        progmem_numbers(0, minsdigit - 1);
        strcpy (str_a, words);
        strcpy (str_b, past);
        progmem_numbers(0, hours - 1);
        strcpy (str_c, words);
      }
      //if mins = 10, cant use minsdigit as above, so soecial case to print 10 past /n hour.
      if (mins == 10) {
        progmem_numbers(0, 9);
        strcpy (str_a, words);
        strcpy (str_b, past);
        progmem_numbers(0, hours - 1);
        strcpy (str_c, words);
      }
      else if (mins == 15) {
        strcpy (str_a, quar);
        strcpy (str_b, past);
        progmem_numbers(0, hours - 1);
        strcpy (str_c, words);
      }
      else if (mins == 20) {
        progmem_numbers(1, minsdigitten - 1);
        strcpy (str_a, words);
        strcpy (str_b, past);
        progmem_numbers(0, hours - 1);
        strcpy (str_c, words);
      }
      else if (mins == 30) {
        strcpy (str_a, half);
        strcpy (str_b, past);
        progmem_numbers(0, hours - 1);
        strcpy (str_c, words);
      }
      else if (mins == 40) {
        progmem_numbers(1, 1);
        strcpy (str_a, words);
        strcpy (str_b, to);
        if (hours == 12) {
          progmem_numbers(0, hours - hours);
        }
        else {
          progmem_numbers(0, hours);

        }
        strcpy (str_c, words);
      }
      else if (mins == 50) {
        progmem_numbers(0, 9);
        strcpy (str_a, words);
        strcpy (str_b, to);
        if (hours == 12) {
          progmem_numbers(0, hours - hours);
        }
        else {
          progmem_numbers(0, hours);

        }
        strcpy (str_c, words);
      }
      else if (mins == 45) {
        strcpy (str_a, quar);
        strcpy (str_b, to);
        if (hours == 12) {
          progmem_numbers(0, hours - hours);
        }
        else {
          progmem_numbers(0, hours);

        }
        strcpy (str_c, words);
      }

      //if time is not on the hour - i.e. both mins digits are not zero,
      //then make first line read "hours" and 2 & 3rd lines read "minstens"  "mins" e.g. "three /n twenty /n one"

      else if (minsdigitten != 0 && minsdigit != 0) {
        progmem_numbers(0, hours - 1);
        strcpy (str_a, words);

        //if mins is in the teens, use teens from the numbers array for the 2nd line, e.g. "fifteen"
        if (mins >= 11 && mins <= 19) {
          progmem_numbers(0, mins - 1);
          strcpy (str_a, words);
          strcpy (str_b, past);
          progmem_numbers(0, hours - 1);
          strcpy (str_c, words);
        }
        else if (mins > 50) {
          progmem_numbers(0, 60 - (mins + 1));
          strcpy (str_a, words);
          strcpy (str_b, to);
          if (hours == 12) {
            progmem_numbers(0, hours - 12);
            strcpy (str_c, words);
          }
          else {
            progmem_numbers(0, hours);
            strcpy (str_c, words);
          }
        }
        else {
          progmem_numbers(1, minsdigitten - 1);
          strcpy (str_b, words);
          progmem_numbers(0, minsdigit - 1);
          strcpy (str_c, words);
        }
      }
    }//end working out time

    //run in a loop
    //print line a "twelve"
    byte len = 0;
    while (str_a[len]) {
      len++;
    }; //get length of message
    byte offset_top = (31 - ((len - 1) * 4)) / 2; //

    //plot hours line
    byte i = 0;
    while (str_a[i]) {
      puttinychar((i * 4) + offset_top, 1, str_a[i]);
      i++;
    }
    
    //hold display but check for button presses
    int counter = 1000;
    while (counter > 0) {
      //check for button press
      if (buttonA.uniquePress()) {
        switch_mode();
        return;
      }
      if (buttonB.uniquePress()) {
        display_date();
      }
      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 but check for button presses
    counter = 1000;
    while (counter > 0){
      if (buttonA.uniquePress()) {
        switch_mode();
        return;
      }
      if (buttonB.uniquePress()) {
        display_date();
      }
      if (buttonC.uniquePress()) {
        display_thp();
        return;
      }

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

    //print line c if there.
    len = 0;
    while (str_c[len]) {
      len++;
    }; //get length of message
    offset_top = (31 - ((len - 1) * 4)) / 2; 

    i = 0;
    while (str_c[i]) {
      puttinychar((i * 4) + offset_top, 1, str_c[i]);
      i++;
    }
    counter = 1000;
    while (counter > 0){
      //check for button press
      if (buttonA.uniquePress()) {
        switch_mode();
        return;
      }
      if (buttonB.uniquePress()) {
        display_date();
      }
      if (buttonC.uniquePress()) {
        display_thp();
        return;
      }

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

    //hold display blank but check 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();
      }
      if (buttonC.uniquePress()) {
        display_thp();
        return;
      }

      delay(1);
      counter--;

      //Check light levels for turning on/off matrix
      if (light_count > 2000) {
        light();
        light_count = 0;
      }
      light_count++;
    }
  }
  fade_down();

}


//used by word mode to retrieve words from progmem, m : 0 = numbers, 1 = numberstens. i = index
void progmem_numbers(byte m, byte i) {

  if (m == 0) {
    strcpy_P(words, (char *)pgm_read_word(&(numbers[i])));
  }
  else if (m == 1) {
    strcpy_P(words, (char *)pgm_read_word(&(numberstens[i])));
  }

}


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

  cls();
  bme.takeForcedMeasurement();
  char temp[5];
  char humi[3];
  char pres[5];
  dtostrf(bme.readTemperature(), 4, 1, temp);
  dtostrf(bme.readHumidity(), 3, 0, humi);
  dtostrf(bme.readPressure() / 100.0F, 4, 0, pres);

  byte i = 0;
  byte offset = 0;
  while (temp[i]) {
    if (i == 0) {
      offset = 2;
    }
    else if (i == 2) {
      offset = 4;
    }
    else if (i == 3) {
      offset = 1;
    }
    else {
      offset = 3;
    }
    putnormalchar(i * 6 + offset + font_offset, 0, temp[i], font_style, font_cols);
    i++;
  }
  putnormalchar(i * 6 + 2 + font_offset, 0, '~', font_style, font_cols);  // '~' translates to degress symbol via FontLEDClock.h

  delay(2000);
  fade_down();

  i = 0;
  while (humi[i]) {
    if (i < 2) {
      offset = 0;
    }
    else {
      offset = 1;
    }
    putnormalchar(i * 6 + offset + font_offset, 0, humi[i], font_style, font_cols);
    i++;
  }
  putnormalchar(i * 6 + 2 + font_offset, 0, '%', font_style, font_cols);

  delay(2000);
  fade_down();

  i = 0;
  while (pres[i]) {
    if (strlen(pres) < 4 && i == 0) {
      offset = 5;
    }
    else {
      offset = 0;
    }
    putnormalchar(i * 6 - offset, 0, pres[i], 1, 5);
    i++;
  }
  byte x = 0;
  const char mb[] = "mb";
  while (mb[x]) {
    puttinychar(i * 4 + 8, 1, mb[x]);
    x++;i++;
  }
  
  delay(2000);
  fade_down();
  cls();
    
}


//display_date - print the day of week, date and month with a flashing cursor effect
void display_date() {

  cls();

  //date suffix array
  const char suffix[4][3] = {"st", "nd", "rd", "th"};

  //read the date from the DS3231
  byte dow = rtc[3]; // day of week 0 = Sunday
  byte date = rtc[4];
  byte month = rtc[5] - 1;

  //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
  byte len = 0;
  char dayfullname[9];
  strcpy_P(dayfullname, (char *)pgm_read_word(&(daysfull[dow])));
  while(dayfullname[len]) {
    len++; 
  };
  byte offset = (31 - ((len - 1) * 4)) / 2; //our offset to centre up the text
      
  //print the name     
  int i = 0;
  while(dayfullname[i])
  {
    puttinychar((i * 4) + offset , 1, dayfullname[i]);
    i++;
  }
  delay(1000);
  fade_down();
  cls();
  
  // print date numerals
  char buffer[3];
  itoa(date, buffer, 10);
  offset = 10; //offset to centre text if 3 chars - e.g. 3rd
  
  byte s = 3; 
  if(date == 1 || date == 21 || date == 31) {
    s = 0;
  } 
  else if (date == 2 || date == 22) {
    s = 1;
  } 
  else if (date == 3 || date == 23) {
    s = 2;
  } 

  //print the 1st date number
  puttinychar(0 + offset, 1, buffer[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 (date > 9){
    suffixposx = 10;
    puttinychar(4 + offset, 1, buffer[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, (char *)pgm_read_word(&(monthsfull[month])));
  while(monthfullname[len]) {
    len++;
  };
  offset = (31 - ((len - 1) * 4)) / 2; //our offset to centre up the text
  i = 0;
  while(monthfullname[i])
  {
    puttinychar((i * 4) + offset, 1, monthfullname[i]);
    i++;
  }
  
  delay(1000);
  fade_down();

}


//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(208);
  
  //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;

  const char modes[5][7] = {">Basic", ">Small", ">Slide", ">Words", ">Setup"};

  byte firstrun = 1;

  //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 == 1) {

      count = 0;
      cls();

      if (firstrun == 0) {
        clock_mode++;
      }
      if (clock_mode > NUM_DISPLAY_MODES + 1) {
        clock_mode = 0;
      }

      //print arrow and current clock_mode name on line one and print next clock_mode name on line two
      char str_top[9];

      strcpy (str_top, modes[clock_mode]);

      byte i = 0;
      while (str_top[i]) {
        puttinychar(i * 4 + 1, 1, str_top[i]);
        i++;
      }
      firstrun = 0;
    }
    delay(50);
  }
  if (old_mode != clock_mode && clock_mode < 4){
      //save the values to EEPROM
      eeprom_save(201, clock_mode, 0, 0);
      //Serial.print(clock_mode);
  }

}


//run clock main loop as long as run_mode returns true
byte 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 == rtc[2]) {
      //set the next random clock mode and time to change it
      set_next_random();
      //exit the current mode.
      return 0;
    }
  }
  //run dst() calculation
  if (dst_mode && rtc[2] == ntp_dst_hour && !dst_ntp_run) {
    dst();
    dst_ntp_run = 1;
  }
  //reset once a day dst/ntp check so it runs next day
  if (rtc[2] > ntp_dst_hour) {
    dst_ntp_run = 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 - current time plus 1
  get_time();
  change_mode_time = rtc[2] + 1;

  //if change_mode_time now happens to be over 23, then set it 12am
  if (change_mode_time > 23) {
    change_mode_time = 0;
  }

  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) {
    //set new random font
    set_font_case(random(1, NUM_FONTS + 1));  //pick new random font mode
  }

}


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

  const char set_modes[10][9] = {">Rnd Clk", ">Rnd Fnt", ">12 Hr", ">Font", ">DST", ">D/Time", ">Auto LX", ">Display", ">Bright", ">Exit"};

  byte setting_mode = 0;
  byte firstrun = 1;

  //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 == 1) {

      count = 0;
      cls();

      if (firstrun == 0) { 
        setting_mode++; 
      } 
      if (setting_mode > NUM_SETTINGS_MODES) { 
        setting_mode = 0; 
      }

      //print arrow and current clock_mode name on line one and print next clock_mode name on line two
      char str_top[9];
    
      strcpy (str_top, set_modes[setting_mode]);

      byte i = 0;
      while(str_top[i]) {
        puttinychar(i * 4 + 1, 1, str_top[i]);
        i++;
      }

      firstrun = 0;
    }
    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_display_options();
      break;
    case 8:
      set_intensity();
      break;
    case 9:
      //exit menu
      break;
  }
    
  //change the clock from mode 6 (settings) back to the one it was in before 
  clock_mode = old_mode;

}


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

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

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

  //set the values
  random_mode = set_random_mode;

  //set hour mode will change
  set_next_random();

  //save the values to EEPROM
  eeprom_save(206, 0, random_mode, 0);
  //Serial.println("random_mode");

}


//toggle random font
void set_random_font() {
  
  cls();
  
  //get current values
  bool set_random_font_mode = eeprom_read_bool(207);

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

  //set the values
  random_font_mode = set_random_font_mode;

  //set hour mode will change
  set_next_random();

  //save the values to EEPROM
  eeprom_save(207, 0, random_font_mode, 0);
  //Serial.println("random_font_mode");
  
}


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

  cls();

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

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

  //set the values
  ampm = set_ampm_mode;

  //save the values to EEPROM
  eeprom_save(208, 0, set_ampm_mode, 0);
  //Serial.println("ampm");

}


//set font style
void set_font() {

  cls();

  byte i = 0;
  const char text[] = ">Set Fnt";
  while(text[i]) {
    puttinychar(i * 4, 1, text[i]);
    i++;
  }
  
  delay(1500);
  cls();

  byte set_font_value;
  if (font_style == 2 && font_cols == 5) {
    set_font_value = 6;
  }
  else {
    set_font_value = font_style;
  }

  get_font_value(set_font_value, 1, NUM_FONTS);

  //save the values to EEPROM
  eeprom_save(203, font_style, 0, 0);
  eeprom_save(204, font_offset, 0, 0);
  eeprom_save(205, font_cols, 0, 0);
  //Serial.println("font style, offset and cols");

}


//set font_style, font_offset & font_cols variables, used by set_font()
void set_font_case(int value) {
  
  switch(value) {
    case 1:
      font_style = 1;
      font_offset = 0;
      font_cols = 5;
      break;
    case 2:
      font_style = 2;
      font_offset = 1;
      font_cols = 6;
      break;
    case 3:
      font_style = 3;
      font_offset = 1;
      font_cols = 6;
      break;
    case 4:
      font_style = 4;
      font_offset = 1;
      font_cols = 6;
      break;
    case 5:
      font_style = 5;
      font_offset = 0;
      font_cols = 5;
      break;
    case 6:
      font_style = 2;
      font_offset = 0;
      font_cols = 5;  //cheap way to create a new font (crop 1 column right side of font 2)
      break;
    case 7:
      font_style = 7;
      font_offset = 1;
      font_cols = 6;
      break;
  }

}


//get user values for setting font
void get_font_value(int current_value, int min_value, int max_value) {
  
  //print digits bottom line
  char buffer[2] = " ";
  itoa(current_value, buffer ,10);
  puttinychar(0, 1, '>'); 
  puttinychar(4, 1, buffer[0]); 
  delay(300);
  //wait for button input
  while (!buttonA.uniquePress()) {
    char preview[4] = "   ";
    //font preview numbers
    itoa(123, preview, 10);
    while (buttonB.isPressed()){

      if(current_value < max_value) { 
        current_value++;
      } 
      else {
        current_value = min_value;
      }
      //print the new value
      cls();
      itoa(current_value, buffer, 10);
      puttinychar(0, 1, '>'); 
      puttinychar(4, 1, buffer[0]);
      //preview the font and set the font
      set_font_case(current_value);
      byte i = 0;
      while(preview[i]) {
        putnormalchar(i * (font_cols + 1) + 10, 0, preview[i], font_style, font_cols);
        i++;
      }
      delay(150);
    }

    while (buttonC.isPressed()) {

      if(current_value > min_value) {
        current_value--;
      } 
      else {
        current_value = max_value;
      }
      //print the new value
      cls();
      itoa(current_value, buffer ,10);
      puttinychar(0, 1, '>');
      puttinychar(4, 1, buffer[0]);
      //preview the font and set the font
      set_font_case(current_value);
      byte i = 0;
      while(preview[i]) {
        putnormalchar(i * (font_cols + 1) + 10, 0, preview[i], font_style, font_cols);
        i++;
      }
      delay(150);
    }

  }
  //return current_value;

}


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

  cls();

  //get current values
  bool set_dst_mode = eeprom_read_bool(210);

  //Set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.
  set_dst_mode = set_bool_value(0, set_dst_mode);

  //set the values
  dst_mode = set_dst_mode;
  
  //save the values to EEPROM
  eeprom_save(210, 0, set_dst_mode, 0);
  //Serial.println(set_dst_mode);
  
  //cls();

}


//used to set bool for DST, NTP, 12h, Random and Light
//message = which 'set' message to print, 
//current value = current value of property we are setting
bool set_bool_value(byte message, bool current_value){

  cls();

  const char options[4][9] = {">Set DST", ">Set 12h", ">Set Rnd", ">Set LX"};

  //Print "set xyz" top line
  byte i = 0;
  while(options[message][i])
  {
    puttinychar(i * 4, 1, options[message][i]);
    i++;
  }

  delay(1500);
  cls();

  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;

}
//change screen intensity
void set_intensity() {

  cls();
  
  byte i = 0;
  const char text[] = ">Bright";
  while(text[i]) {
    puttinychar((i * 4) + 3, 0, text[i]);
    i++;
  }

  //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;
        cls ();
      }
      else {
        intensity++;
      }
      //print the new value 
      i = 0;
      while(text[i]) {
        puttinychar((i * 4) + 4, 0, text[i]);
        i++;
      }
      
      //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
      cls ();
      levelbar (0, 6,(intensity * 2) + 2, 2);
      
      //print the new value 
      i = 0;
      while(text[i]) {
        puttinychar((i * 4) + 4, 0, text[i]);
        i++;
      }
      
      //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);
  //Serial.println("intensity");

}


// menu for setting auto intensity settings
void set_auto_intensity() {

  cls();

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

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

  //set the values
  auto_intensity = set_auto_intensity_value;

  //save the values to EEPROM
  eeprom_save(209, 0, auto_intensity, 0);
  //Serial.println("auto_intensity");

}


// display a horizontal bar on the screen at offset xposr by ypos with height and width of xbar, ybar
void levelbar (byte xpos, byte ypos, byte xbar, byte ybar) {

  for (byte x = 0; x < xbar; x++) {
    for (byte y = 0; y <= ybar; y++) {
      plot(x + xpos, y + ypos, 1);
    }
  }

}


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

  cls();

  //fill settings with current clock values read from clock
  get_time();
  byte set_min   = rtc[1];
  byte set_hr    = rtc[2];
  byte set_date  = rtc[4];
  byte set_mnth  = rtc[5];
  int  set_yr    = rtc[6]; 

  //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);
  set_mnth = set_value(3, set_mnth, 1, 12);
  set_yr   = set_value(4, set_yr, 2019, 2099);
  set_hr   = set_value(1, set_hr, 0, 23);
  set_min  = set_value(0, set_min, 0, 59);

  ds3231.adjust(DateTime(set_yr, set_mnth, set_date, set_hr, set_min));
  
  cls();

}


//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(byte message, int current_value, int reset_value, int rollover_limit) {

  cls();
  const char messages[5][9] = {">Set Min", ">Set Hr", ">Set Day", ">Set Mth", ">Set Yr"};

  //Print "set xyz" top line
  byte i = 0;
  while(messages[message][i])
  {
    puttinychar(i * 4, 1, messages[message][i]); 
    i++;
  }

  delay(1500);
  cls();

  //print digits bottom line
  char buffer[5] = "    ";
  itoa(current_value,buffer,10);
  puttinychar(0 , 1, '>');
  puttinychar(4 , 1, buffer[0]);
  puttinychar(8 , 1, buffer[1]);
  puttinychar(12, 1, buffer[2]);
  puttinychar(16, 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, '>');
      puttinychar(4 , 1, buffer[0]);
      puttinychar(8 , 1, buffer[1]);
      puttinychar(12, 1, buffer[2]);
      puttinychar(16, 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, '>');
      puttinychar(4 , 1, buffer[0]);
      puttinychar(8 , 1, buffer[1]);
      puttinychar(12, 1, buffer[2]);
      puttinychar(16, 1, buffer[3]);
      delay(150);
    }
    
  }
  return current_value;

}


// function to get time from DS3231
void get_time() {

  //get time
  DateTime now = ds3231.now();
  //save time to array
  rtc[6] = now.year();
  rtc[5] = now.month();
  rtc[4] = now.day();
  rtc[3] = now.dayOfTheWeek(); //returns 0-6 where 0 = Sunday
  rtc[2] = now.hour();
  rtc[1] = now.minute();
  rtc[0] = now.second();

  //print the time to the serial port - useful for debuging RTC issues
  /*
  Serial.print(rtc[2]);
  Serial.print(":");
  Serial.print(rtc[1]);
  Serial.print(":");
  Serial.println(rtc[0]);
  */

}


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

  //Get light reading
  uint16_t lx = lux.GetLightIntensity();

  //checks if display can be turned off if option to keep it on until a certain time is met
  bool dont_turn_off = 0;
  if (display_mode > 1) {
    byte hr = rtc[2];
    switch(display_mode) {
      case 2:
      if (hr >= hour_on && hr < hour_off_1) {
        dont_turn_off = 1;
      }
      else {
        dont_turn_off = 0;
      }
      break;
      case 3:
      if (hr >= hour_on && hr < hour_off_2) {
        dont_turn_off = 1;
      }
      else {
        dont_turn_off = 0;
      }
      break;
      case 4:
      if (hr >= hour_on && hr < hour_off_3) {
        dont_turn_off = 1;
      }
      else {
        dont_turn_off = 0;
      }
      break;
    }
  }

  if (lx == 0 && !shut && !dont_turn_off && display_mode != 1) {
    shut = 1;
    set_devices(false, 0); //Call sleep routine to turn off matrix, applies when light is low enough
  }
  if ((lx > 0 && shut) || (lx == 0 && shut && dont_turn_off)) {
    shut = 0;
    set_devices(false, 0); //Call sleep routine to turn on matrix, applies when light is high enough
  }

  //this runs if auto_intensity is true and display is not off, it defines the intensity based on the light sensor and calls set_devices to set intensity.
  if (auto_intensity && !shut) {
    switch(lx) {
      case 0:
      auto_intensity_value = 0;
      break;
      case 2:
      auto_intensity_value = 1;
      break;
      case 3 ... 4:
      auto_intensity_value = 2;
      break;
      case 5 ... 6:
      auto_intensity_value = 3;
      break;
      case 7 ... 10:
      auto_intensity_value = 4;
      break;
      case 11 ... 20:
      auto_intensity_value = 5;
      break;
      case 21 ... 40:
      auto_intensity_value = 6;
      break;
      case 41 ... 60:
      auto_intensity_value = 7;
      break;
      case 61 ... 100:
      auto_intensity_value = 8;
      break;
      case 101 ... 150:
      auto_intensity_value = 9;
      break;
      case 151 ... 200:
      auto_intensity_value = 10;
      break;
      case 201 ... 250:
      auto_intensity_value = 11;
      break;
      case 251 ... 300:
      auto_intensity_value = 12;
      break;
      case 301 ... 350:
      auto_intensity_value = 13;
      break;
      case 351 ... 400:
      auto_intensity_value = 14;
      break;
      case 401 ... 65535:
      auto_intensity_value = 15;
      break;
    }
    set_devices(true, auto_intensity_value);
    //this is useful for help setting the values above
    //Serial.println(lx);
  }

}


//Routine 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);
    }
  }

}


//Routine to set display on/off options (0 = normal, 1 = always on, 2 - 4 = from specific time)
void set_display_options() {

  cls();

  const char options[5][9] = {">NORMAL", ">ON", "> 9.00PM", ">10.00PM", ">11.00PM"};

  byte i = 0;
  while(options[display_mode][i])
  {
    puttinychar(i * 4, 1, options[display_mode][i]); 
    i++;
  }

  //wait for button input
  while (!buttonA.uniquePress()) {

    while (buttonB.isPressed()) {
      display_mode++;
      if (display_mode > 4) {
        display_mode = 0;
      }

      //print the new value
      cls();

      byte i = 0;
      while(options[display_mode][i])
      {
        puttinychar(i * 4, 1, options[display_mode][i]); 
        i++;
      }
      delay(150);
    }
    while (buttonC.isPressed()) {
      //display current lux value
      cls();
      byte i = 0;
      const char msg[] = "LX:";
      i = 0;
      while(msg[i]) {
        puttinychar(i * 4, 1, msg[i]);
        i++;
      }
      char buffer[6];
      dtostrf(lux.GetLightIntensity(), 5, 0, buffer);
      i = 0;
      while(buffer[i]) {
        puttinychar(i * 4 + 12, 1, buffer[i]);
        i++;
      }
      delay(150);
    }

  }

  //save the values to EEPROM
  eeprom_save(202, display_mode, 0, 0);
  //Serial.println("display_mode");

}


//calculates DST, takes NTP output (bool ntp - if applicable) into consideration
void dst() {

  get_time();
  byte day = rtc[4];
  byte month = rtc[5];
  byte hour = rtc[2];
  byte dow = rtc[3];

  //temporarily store DST changes
  bool dst_plus = 0;
  bool dst_minus = 0;

  //winter calculation
  if ((month < 3 || month > 10) || (month == 3 && day < 25 && DST == 1) || (month == 10 && day >= 25 && hour == 2 && dow == 0 && DST == 1)) {
    DST = 0; //winter is coming
    dst_minus = 1;
    //save to EEPROM
    eeprom_save(212, 0, DST, 0);
    //Serial.println("winter");
  }
  //summer calculation
  else if ((month > 3 && month < 10) || (month == 10 && day < 25 && DST == 0) || (month == 3 && day >= 25 && hour == 1 && dow == 0 && DST == 0)) {
    DST = 1; //hosepipe ban is coming
    dst_plus = 1;
    //save to EEPROM
    eeprom_save(212, 0, DST, 0);
    //Serial.println("summer");
  }
  //+1 hour if summertime, or one time calculation is active
  if (DST || dst_plus) {
    DateTime now = ds3231.now();
    ds3231.adjust(DateTime(now.unixtime() + (3600)));
    //Serial.println("summer+1");
  }
  //-1 hour if wintertime, based on one time calculation
  if (dst_minus) {
    DateTime now = ds3231.now();
    ds3231.adjust(DateTime(now.unixtime() - (3600)));
    //Serial.println("winter-1");
  }

}

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟
【Arduino 动手做】带DS3231、BME280、BH1750和ESP01的LED矩阵NTP时钟
项目链接:https://www.hackster.io/Ratti3/led-matrix-ntp-clock-with-ds3231-bme280-bh1750-esp01-fdde2b
项目参考:https://123led.wordpress.com/mini-led-clock/
项目作者:拉蒂3(Ratti3)

项目视频:
https://www.youtube.com/watch?v=krdAU_GUc3k
https://www.youtube.com/watch?v=MRocFW43dEg
项目代码:https://github.com/Ratti3/miniclock
制作字体:http://dotmatrixtool.com/

 

00190.gif

评论

user-avatar
icon 他的勋章
    展开更多