9浏览
查看: 9|回复: 2

[项目] 【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟

[复制链接]
修改了现有项目以包含一些传感器和额外的按钮。可以使用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,这意味着如果您关闭并重新打开电源,则通过菜单所做的更改将被保存。

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图1

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图2

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图3

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图4
【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图5

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图6

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图7

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图8

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图9

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图10

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图11

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图13

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图12

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图14

驴友花雕  中级技神
 楼主|

发表于 7 小时前

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

项目代码

  1. /***********************************************************************
  2. Mini Clock v1.0, Jul 2014 by Nick Hall
  3. Distributed under the terms of the GPL.
  4. For help on how to build the clock see my blog:
  5. http://123led.wordpress.com/
  6. =======================================================================
  7. Modified by Ratti3 - 17 May 2020
  8. Mini Clock v1.1 (Non ESP01 Version)
  9. Tested on IDE v1.8.12
  10. 26,046 bytes 84%
  11. 1019 bytes 49%
  12. https://github.com/Ratti3/miniclock
  13. https://youtu.be/MRocFW43dEg
  14. https://youtu.be/krdAU_GUc3k
  15. https://create.arduino.cc/projecthub/Ratti3/led-matrix-ntp-clock-with-ds3231-bme280-bh1750-esp01-fdde2b
  16. ***********************************************************************/
  17. //include libraries:
  18. #include "ProgmemData.h"                 // Progmem Storage File, holds day, month and time names, frees up precious RAM
  19. #include <LedControl.h>                  // v1.0.6 https://github.com/wayoda/LedControl
  20. #include <FontLEDClock.h>                // https://github.com/javastraat/arduino/blob/master/libraries/FontLEDClock/FontLEDClock.h - however, it has been modified
  21. #include <Wire.h>                        // Standard Arduino library
  22. #include <RTClib.h>                      // v1.7.0 DS3231 RTC - https://github.com/adafruit/RTClib
  23. #include <Button.h>                      // https://github.com/tigoe/Button
  24. #include <Adafruit_Sensor.h>             // v1.1.2 Required by BME280 - https://github.com/adafruit/Adafruit_Sensor
  25. #include <Adafruit_BME280.h>             // v2.0.2 BME280 Environmental Sensor - https://github.com/adafruit/Adafruit_BME280_Library
  26. #include <BH1750FVI.h>                   // v1.1.1 BH1750 Light Sensor - https://github.com/PeterEmbedded/BH1750FVI
  27. #include <EEPROM.h>                      // Used to save settings to Arduino EEPROM
  28. // Setup LED Matrix
  29. // pin 12 is connected to the DataIn on the display
  30. // pin 11 is connected to the CLK on the display
  31. // pin 10 is connected to LOAD on the display
  32. LedControl lc = LedControl(12, 11, 10, 4); //sets the 3 pins as 12, 11 & 10 and then sets 4 displays (max is 8 displays)
  33. // Global variables (changeable defaults), numbers in [] brackets are the EEPROM storage location for that value
  34.   // Clock settings
  35. byte intensity = 2;                      // [200] Default intensity/brightness (0-15), can be set via menu
  36. byte clock_mode = 0;                     // [201] Default clock mode. Default = 0 (basic_mode)
  37. bool random_mode = 0;                    // [206] Define random mode - changes the display type every few hours. Default = 0 (off)
  38. bool random_font_mode = 0;               // [207] Define font random mode - changes the font every few hours. 1 = random font on
  39. bool ampm = 0;                           // [208] Define 12 or 24 hour time. 0 = 24 hour. 1 = 12 hour
  40.   // Light settings
  41. 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
  42. bool auto_intensity = 1;                 // [209] Default auto light intensity setting
  43. 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
  44. byte hour_off_2 = 22;                    // this checks between hour_on and one of these values
  45. byte hour_off_3 = 23;
  46. byte hour_on = 6;                        // This is used to turn on the display, works with hour_off_1,2,3
  47.   // Font settings - these are set via the setup Font menu, see set_font_case() routine for all default values:
  48. byte font_style = 2;                     // [203] Default clock large font style
  49. byte font_offset = 1;                    // [204] Default clock large font offset adjustment
  50. byte font_cols = 6;                      // [205] Default clock large font columns adjustment
  51.   // DST NTP and UTC settings
  52. bool dst_mode = 1;                       // [210] Enable DST function, 1 = enable, 0 = disable
  53. byte ntp_dst_hour = 2;                   // The hour daily NTP/DST sync happens, should be left at 2am if using DST mode
  54. // Global variables
  55. bool shut = 0;                           // Stores matrix on/off state
  56. 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.
  57. byte change_mode_time = 0;               // Holds hour when clock mode will next change if in random mode.
  58. unsigned long delaytime = 500;           // We always wait a bit between updates of the display
  59. int rtc[7];                              // Holds real time clock output
  60. int light_count = 0;                     // Counter for light routine
  61. byte auto_intensity_value = 0;           // Stores the last intensity value set by the light sensor, this value is set automatically
  62. char words[1];                           // Holds word clock words, retrieved from progmem
  63. bool DST = 0;                            // [212] Holds DST applied value, 1 = summertime +1hr applied, this is to ensure DST +1/-1 runs only once
  64. bool dst_ntp_run = 0;                    // Holds the value to see if ntp() and dst() have run once a day
  65. bool dont_turn_off = 0;                  // Holds value for turning display on or off
  66. 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
  67. byte FirstRunAddress = 255;              // [255] Address on EEPROM FirstRunValue is saved
  68. //define constants
  69. #define NUM_DISPLAY_MODES  3                    // Number display modes = 3 (counting zero as the first mode)
  70. #define NUM_SETTINGS_MODES 9                    // Number settings modes = 9 (counting zero as the first mode)
  71. #define NUM_FONTS          7                    // Number of fonts, as defined in FontLEDClock.h
  72. #define SLIDE_DELAY        20                   // The time in milliseconds for the slide effect per character in slide mode. Make this higher for a slower effect
  73. #define cls                clear_display        // Clear display
  74. #define RandomSeed         A0                   // Pin used to generate random seed
  75. #define TX                 6                    // RX pin of ESP01
  76. #define RX                 7                    // TX pin of ESP01
  77. //these can be used to change the order of displays, some displays from ebay are wrong way round
  78. #define Matrix0            3
  79. #define Matrix1            2
  80. #define Matrix2            1
  81. #define Matrix3            0
  82. RTC_DS3231 ds3231;                              // Create RTC object
  83. Adafruit_BME280 bme;                            // BME280 object (pins 4 and 5 and 3.3v)
  84. BH1750FVI lux(BH1750FVI::k_DevModeContHighRes); // BH1750 object (pins 4 and 5 and 3.3v)
  85. Button buttonA = Button(2, BUTTON_PULLUP);      // Menu button
  86. Button buttonB = Button(3, BUTTON_PULLUP);      // Display date / + button
  87. Button buttonC = Button(4, BUTTON_PULLUP);      // Temp/Humidity/Pressure / - button
  88. void setup() {
  89.   digitalWrite(2, HIGH);                        // turn on pullup resistor for button on pin 2
  90.   digitalWrite(3, HIGH);                        // turn on pullup resistor for button on pin 3
  91.   digitalWrite(4, HIGH);                        // turn on pullup resistor for button on pin 4
  92.   
  93.   Serial.begin(9600); //start serial
  94.   //initialize the 4 matrix panels
  95.   //we have already set the number of devices when we created the LedControl
  96.   int devices = lc.getDeviceCount();
  97.   //we have to init all devices in a loop
  98.   for (int address = 0; address < devices; address++) {
  99.     lc.shutdown(address, false);         // The MAX72XX is in power-saving mode on startup
  100.     lc.setIntensity(address, intensity); // Set the brightness to a medium values
  101.     lc.clearDisplay(address);            // and clear the display
  102.   }
  103.   //I2C
  104.   Wire.begin();
  105.   //Setup DS3231 RTC
  106.   ds3231.begin();
  107.   //ds3231.adjust(DateTime(2019, 6, 29, 12, 59, 40));  // Set time manually
  108.   //ds3231.adjust(DateTime(__DATE__, __TIME__)); // sets the RTC to the date & time this sketch was compiled
  109.   if (!ds3231.begin()) {
  110.     Serial.println("Couldn't find RTC");
  111.     while(1);
  112.   }
  113.   //BME280 environmental sensor, this sensor from Ebay has address 0x76
  114.   bme.begin(0x76);
  115.   //Reduce BME sampling rate to prevent overheating
  116.   bme.setSampling(Adafruit_BME280::MODE_FORCED,
  117.     Adafruit_BME280::SAMPLING_X1, // temperature
  118.     Adafruit_BME280::SAMPLING_X1, // pressure
  119.     Adafruit_BME280::SAMPLING_X1, // humidity
  120.     Adafruit_BME280::FILTER_OFF);
  121.   //BH1750 light sensor
  122.   lux.begin();
  123.   //what is this silliness, needed for random() to work properly
  124.   randomSeed(analogRead(RandomSeed)); // Pin A0
  125.   //Show software version & startup message
  126.   printver();
  127.   byte FirstRun = eeprom_read_byte(FirstRunAddress);
  128.   bool FR = 0;
  129.   if (FirstRun == FirstRunValue) FR = 1;
  130.   byte value1;
  131.   bool value2;
  132.   /*Serial.print("Read FirstRun: [");
  133.   Serial.print(FirstRunAddress);
  134.   Serial.print("] ");
  135.   Serial.println(FirstRun);*/
  136.   byte i = 200;
  137.   while (i < 213) {
  138.     if (i <= 205) {
  139.    
  140.       value1 = eeprom_read_byte(i);
  141.       if (i == 200) {
  142.         if (FR) {
  143.           intensity = value1;
  144.         }
  145.         else {
  146.           value1 = intensity;
  147.         }
  148.       }
  149.       else if (i == 201) {
  150.         if (FR) {
  151.           clock_mode = value1;
  152.           old_mode = clock_mode;
  153.         }
  154.         else {
  155.           value1 = clock_mode;
  156.         }
  157.       }
  158.       else if (i == 202) {
  159.         if (FR) {
  160.           display_mode = value1;
  161.         }
  162.         else {
  163.           value1 = display_mode;
  164.         }
  165.       }
  166.       else if (i == 203) {
  167.         if (FR) {
  168.           font_style = value1;
  169.         }
  170.         else {
  171.           value1 = font_style;
  172.         }
  173.       }
  174.       else if (i == 204) {
  175.         if (FR) {
  176.           font_offset = value1;
  177.         }
  178.         else {
  179.           value1 = font_offset;
  180.         }
  181.       }
  182.       else if (i == 205) {
  183.         if (FR) {
  184.           font_cols = value1;
  185.         }
  186.         else {
  187.           value1 = font_cols;
  188.         }
  189.       }
  190.       if (!FR) {
  191.         eeprom_save(i, value1, 0, 0);
  192.         /*Serial.print("FirstRun - Update Byte: [");
  193.         Serial.print(i);
  194.         Serial.print("] ");
  195.         Serial.println(value1);*/
  196.       }
  197.       //Serial.print("Read Byte: [");
  198.       //Serial.print(i);
  199.       //Serial.print("] ");
  200.       //Serial.println(value1);
  201.    
  202.     }
  203.     else if (i >= 206 && i <= 212) {
  204.    
  205.       value2 = eeprom_read_bool(i);
  206.       if (i == 206) {
  207.         if (FR) {
  208.           random_mode = value2;
  209.         }
  210.         else {
  211.           value2 = random_mode;
  212.         }
  213.       }
  214.       else if (i == 207) {
  215.         if (FR) {
  216.           random_font_mode = value2;
  217.         }
  218.         else {
  219.           value2 = random_font_mode;
  220.         }
  221.       }
  222.       else if (i == 208) {
  223.         if (FR) {
  224.           ampm = value2;
  225.         }
  226.         else {
  227.           value2 = ampm;
  228.         }
  229.       }
  230.       else if (i == 209) {
  231.         if (FR) {
  232.           auto_intensity = value2;
  233.         }
  234.         else {
  235.           value2 = auto_intensity;
  236.         }
  237.       }
  238.       else if (i == 210) {
  239.         if (FR) {
  240.           dst_mode = value2;
  241.         }
  242.         else {
  243.           value2 = dst_mode;
  244.         }
  245.       }
  246.       else if (i == 211) {
  247.         //Reserved for NTP mode
  248.       }
  249.       else if (i == 212) {
  250.         if (FR) {
  251.           DST = value2;
  252.         }
  253.         else {
  254.           value2 = DST;
  255.         }
  256.       }
  257.       if (!FR) {
  258.         eeprom_save(i, 0, value2, 0);
  259.         /*Serial.print("FirstRun - Update Bool: [");
  260.         Serial.print(i);
  261.         Serial.print("] ");
  262.         Serial.println(value2);*/
  263.       }
  264.       //Serial.print("Read Bool: [");
  265.       //Serial.print(i);
  266.       //Serial.print("] ");
  267.       //Serial.println(value2);
  268.     }
  269.     i++;
  270.   }
  271.   if (!FR) {
  272.     eeprom_save(FirstRunAddress, FirstRunValue, 0, 0);
  273.     /*Serial.print("Update FirstRun: [");
  274.     Serial.print(FirstRunAddress);
  275.     Serial.print("] ");
  276.     Serial.println(FirstRunValue);*/
  277.   }
  278.   //run dst() calculation
  279.   if (dst_mode) {
  280.     dst();
  281.   }
  282. }
  283. void loop() {
  284.   //run the clock with whatever mode is set by clock_mode - the default is set at top of code.
  285.   switch(clock_mode) {
  286.   case 0:
  287.     basic_mode();
  288.     break;
  289.   case 1:
  290.     small_mode();
  291.     break;
  292.   case 2:
  293.     slide();
  294.     break;
  295.   case 3:
  296.     word_clock();
  297.     break;
  298.   case 4:
  299.     setup_menu();
  300.     break;
  301.   }
  302. }
  303. //function to save settings to Arduino EEPROM
  304. void eeprom_save(byte Address, byte value1, bool value2, int8_t value3) {
  305.   switch(Address) {
  306.     case 200 ... 205:
  307.       EEPROM.update(Address, value1);
  308.       //Serial.print("Update Byte: [");
  309.       //Serial.print(Address);
  310.       //Serial.print("] ");
  311.       //Serial.println(value1);
  312.       break;
  313.     case 206 ... 212:
  314.       EEPROM.update(Address, value2);
  315.       //Serial.print("Update Bool: [");
  316.       //Serial.print(Address);
  317.       //Serial.print("] ");
  318.       //Serial.println(value2);
  319.       break;
  320.     case 255:
  321.       EEPROM.update(Address, value1);
  322.       //Serial.print("Update Byte: [");
  323.       //Serial.print(Address);
  324.       //Serial.print("] ");
  325.       //Serial.println(value1);
  326.       break;
  327.   }
  328.   
  329. }
  330. //function to read EEPROM data as byte
  331. byte eeprom_read_byte(byte Address) {
  332.   
  333.   byte result = EEPROM.read(Address);
  334.   return result;
  335.   
  336. }
  337. //function to read EEPROM data as bool
  338. bool eeprom_read_bool(byte Address) {
  339.   
  340.   bool result = (bool)EEPROM.read(Address);
  341.   return result;
  342.   
  343. }
  344. //plot a point on the display
  345. void plot(byte x, byte y, byte val) {
  346.   //select which matrix depending on the x coord
  347.   byte address;
  348.   if (x >= 0 && x <= 7)   {
  349.     address = Matrix0;
  350.   }
  351.   if (x >= 8 && x <= 15)  {
  352.     address = Matrix1;
  353.     x = x - 8;
  354.   }
  355.   if (x >= 16 && x <= 23) {
  356.     address = Matrix2;
  357.     x = x - 16;
  358.   }
  359.   if (x >= 24 && x <= 31) {
  360.     address = Matrix3;
  361.     x = x - 24;
  362.   }
  363.   if (val == 1) {
  364.     lc.setLed(address, y, x, true);
  365.   } else {
  366.     lc.setLed(address, y, x, false);
  367.   }
  368. }
  369. //clear screen
  370. void clear_display() {
  371.   
  372.   for (byte address = 0; address < 4; address++) {
  373.     lc.clearDisplay(address);
  374.   }
  375. }
  376. //fade screen down
  377. void fade_down() {
  378.   byte x = 0; //to hold temp intensity value
  379.   if (auto_intensity) {
  380.     x = auto_intensity_value;  //uses the last light sensor intensity settings, prevents display from constantly flicking between global and light sensor value
  381.   }
  382.   else {
  383.     x = intensity;
  384.   }
  385.   //fade from global intensity to 1
  386.   for (byte i = x; i > 0; i--) {
  387.     for (byte address = 0; address < 4; address++) {
  388.       lc.setIntensity(address, i);
  389.     }
  390.     delay(30); //change this to change fade down speed
  391.   }
  392.   clear_display(); //clear display completely (off)
  393.   //reset intentsity to global val
  394.   for (byte address = 0; address < 4; address++) {
  395.     lc.setIntensity(address, x);
  396.   }
  397. }
  398. //power up led test & display software version number
  399. void printver() {
  400.   byte i = 0;
  401.   const char ver_a[] = "Vers 1.1";
  402.   const char ver_b[] = " Ratti3 ";
  403.   //test all leds.
  404.   for (byte x = 0; x <= 31; x++) {
  405.     for (byte y = 0; y <= 7; y++) {
  406.       plot(x, y, 1);
  407.     }
  408.   }
  409.   delay(500);
  410.   fade_down();
  411.   while (ver_a[i]) {
  412.     puttinychar((i * 4), 1, ver_a[i]);
  413.     delay(35);
  414.     i++;
  415.   }
  416.   delay(700);
  417.   fade_down();
  418.   i = 0;
  419.   while (ver_b[i]) {
  420.     puttinychar((i * 4), 1, ver_b[i]);
  421.     delay(35);
  422.     i++;
  423.   }
  424.   delay(700);
  425.   fade_down();
  426. }
  427. // Copy a 3x5 character glyph from the myfont data structure to display memory, with its upper left at the given coordinate
  428. // This is unoptimized and simply uses plot() to draw each dot.
  429. void puttinychar(byte x, byte y, char c) {
  430.   byte dots;
  431.   if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
  432.     c &= 0x1F;   // A-Z maps to 1-26
  433.   }
  434.   else if (c >= '0' && c <= '9') {
  435.     c = (c - '0') + 33;
  436.   }
  437.   else if (c == ' ') {
  438.     c = 0; // space
  439.   }
  440.   else if (c == '.') {
  441.     c = 27; // full stop
  442.   }
  443.   else if (c == ':') {
  444.     c = 28; // colon
  445.   }
  446.   else if (c == '\'') {
  447.     c = 29; // single quote mark
  448.   }
  449.   else if (c == '>') {
  450.     c = 30; // >
  451.   }
  452.   else if (c == '-') {
  453.     c = 31; // -
  454.   }
  455.   else if (c == '/') {
  456.     c = 32; // forward slash
  457.   }
  458.   for (byte col = 0; col < 3; col++) {
  459.     dots = pgm_read_byte_near(&mytinyfont[c][col]);
  460.     for (char row = 0; row < 5; row++) {
  461.       if (dots & (16 >> row))
  462.         plot(x + col, y + row, 1);
  463.       else
  464.         plot(x + col, y + row, 0);
  465.     }
  466.   }
  467. }
  468. // fs = font_style, fc = font_cols
  469. void putnormalchar(byte x, byte y, char c, byte fs, byte fc) {
  470.   byte dots;
  471.   if (c >= 'A' && c <= 'Z' ) {
  472.     c = (c - 'A') + 27;   // A-Z maps to 27-52
  473.   }
  474.   else if (c >= 'a' && c <= 'z') {
  475.     c &= 0x1F;   // a-z maps to 1-26
  476.   }
  477.   else if (c >= '0' && c <= '9') {
  478.     switch(fs) {
  479.       case 1:
  480.         c = (c - '0') + 59;   // 0-9 maps to 59-68
  481.         break;
  482.       case 2:
  483.         c = (c - '0') + 69;   // 0-9 maps to 69-78
  484.         break;
  485.       case 3:
  486.         c = (c - '0') + 79;   // 0-9 maps to 79-88
  487.         break;
  488.       case 4:
  489.         c = (c - '0') + 89;   // 0-9 maps to 89-98
  490.         break;
  491.       case 5:
  492.         c = (c - '0') + 99;   // 0-9 maps to 99-108
  493.         break;
  494.       case 6:
  495.         c = (c - '0') + 69;   // 0-9 maps to 69-78
  496.         break;
  497.       case 7:
  498.         c = (c - '0') + 109;   // 0-9 maps to 109-108
  499.         break;
  500.     }
  501.   }
  502.   else if (c == ' ') {
  503.     c = 0; // space
  504.   }
  505.   else if (c == '.') {
  506.     c = 53; // full stop
  507.   }
  508.   else if (c == '\'') {
  509.     c = 54; // single quote mark
  510.   }
  511.   else if (c == ':') {
  512.     c = 55; // colon
  513.   }
  514.   else if (c == '>') {
  515.     c = 56; // clock_mode selector arrow
  516.   }
  517.   else if (c == '~') {
  518.     c = 57; // degrees
  519.   }
  520.   else if (c == '%') {
  521.     c = 58; // percentage
  522.   }
  523.   else if (c >= -80 && c <= -67) {
  524.     c *= -1;
  525.   }
  526.   for (char col = 0; col < fc; col++) {
  527.     dots = pgm_read_byte_near(&myfont[c][col]);
  528.     for (char row = 0; row < 7; row++) {
  529.       //check coords are on screen before trying to plot
  530.       //if ((x >= 0) && (x <= 31) && (y >= 0) && (y <= 7)){
  531.       if (dots & (64 >> row)) {   // only 7 rows.
  532.         plot(x + col, y + row, 1);
  533.       } else {
  534.         plot(x + col, y + row, 0);
  535.       }
  536.       //}
  537.     }
  538.   }
  539. }
  540. //show the time in small 3x5 characters with seconds display
  541. void small_mode() {
  542.   char textchar[8]; // the 16 characters on the display
  543.   byte mins = 100; //mins
  544.   byte secs = rtc[0]; //seconds
  545.   byte old_secs = secs; //holds old seconds value - from last time seconds were updated o display - used to check if seconds have changed
  546.   
  547.   cls();
  548.   //run clock main loop as long as run_mode returns true
  549.   while (run_mode()) {
  550.     //Check light levels for turning on/off matrix
  551.     if (light_count > 100) {
  552.       light();
  553.       light_count = 0;
  554.     }
  555.     light_count++;
  556.     get_time();
  557.   
  558.     //check for button press
  559.     if (buttonA.uniquePress()) {
  560.       switch_mode();
  561.       return;
  562.     }
  563.     if (buttonB.uniquePress()) {
  564.       display_date();
  565.       return;
  566.     }
  567.     if (buttonC.uniquePress()) {
  568.       display_thp();
  569.       return;
  570.     }
  571.    
  572.     //if secs changed then update them on the display
  573.     secs = rtc[0];
  574.     if (secs != old_secs) {
  575.       //secs
  576.       char buffer[3];
  577.       itoa(secs, buffer, 10);
  578.       //fix - as otherwise if num has leading zero, e.g. "03" secs, itoa converts this to chars with space "3 ".
  579.       if (secs < 10) {
  580.         buffer[1] = buffer[0];
  581.         buffer[0] = '0';
  582.       }
  583.       puttinychar( 20, 1, ':'); //seconds colon
  584.       puttinychar( 24, 1, buffer[0]); //seconds
  585.       puttinychar( 28, 1, buffer[1]); //seconds
  586.       old_secs = secs;
  587.     }
  588.     //if minute changes change time
  589.     if (mins != rtc[1]) {
  590.       //reset these for comparison next time
  591.       mins = rtc[1];
  592.       byte hours = rtc[2];
  593.       if (hours > 12) {
  594.         hours = hours - ampm * 12;
  595.       }
  596.       if (hours < 1) {
  597.         hours = hours + ampm * 12;
  598.       }
  599.       //set characters
  600.       char buffer[3];
  601.       itoa(hours, buffer, 10);
  602.       //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa converts this to chars with space "3 ".
  603.       if (hours < 10) {
  604.         buffer[1] = buffer[0];
  605.         //if we are in 12 hour mode blank the leading zero.
  606.         if (ampm) {
  607.           buffer[0] = ' ';
  608.         }
  609.         else {
  610.           buffer[0] = '0';
  611.         }
  612.       }
  613.       //set hours chars
  614.       textchar[0] = buffer[0];
  615.       textchar[1] = buffer[1];
  616.       textchar[2] = ':';
  617.       itoa (mins, buffer, 10);
  618.       if (mins < 10) {
  619.         buffer[1] = buffer[0];
  620.         buffer[0] = '0';
  621.       }
  622.       //set mins characters
  623.       textchar[3] = buffer[0];
  624.       textchar[4] = buffer[1];
  625.       //do seconds
  626.       textchar[5] = ':';
  627.       secs = rtc[0];
  628.       itoa(secs, buffer, 10);
  629.       //fix - as otherwise if num has leading zero, e.g. "03" secs, itoa converts this to chars with space "3 ".
  630.       if (secs < 10) {
  631.         buffer[1] = buffer[0];
  632.         buffer[0] = '0';
  633.       }
  634.       //set seconds
  635.       textchar[6] = buffer[0];
  636.       textchar[7] = buffer[1];
  637.       //print each char
  638.       for (byte x = 0; x < 6 ; x++) {
  639.         puttinychar( x * 4, 1, textchar[x]);
  640.       }
  641.     }
  642.     delay(50);
  643.   }
  644.   fade_down();
  645. }
  646. // show the time in 5x7 characters
  647. void basic_mode() {
  648.   cls();
  649.   char buffer[3];   //for int to char conversion to turn rtc values into chars we can print on screen
  650.   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
  651.   //do 12/24 hour conversion if ampm set to 1
  652.   byte hours = rtc[2];
  653.   if (hours > 12) {
  654.     hours = hours - ampm * 12;
  655.   }
  656.   if (hours < 1) {
  657.     hours = hours + ampm * 12;
  658.   }
  659.   //do offset conversion
  660.   if (ampm && hours < 10) {
  661.     offset = 2;
  662.   }
  663.   //set the next minute we show the date at
  664.   //set_next_date();
  665.   // 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
  666.   byte secs = 100;
  667.   byte mins = 100;
  668.   int count = 0;
  669.   //run clock main loop as long as run_mode returns true
  670.   while (run_mode()) {
  671.     //Check light levels for turning on/off matrix
  672.     if (light_count > 4000) {
  673.       light();
  674.       light_count = 0;
  675.     }
  676.     light_count++;
  677.     //get the time from the clock chip
  678.     get_time();
  679.    
  680.     //check for button press
  681.     if (buttonA.uniquePress()) {
  682.       switch_mode();
  683.       return;
  684.     }
  685.     if (buttonB.uniquePress()) {
  686.       display_date();
  687.       return;
  688.     }
  689.     if (buttonC.uniquePress()) {
  690.       display_thp();
  691.       return;
  692.     }
  693.     //check whether it's time to automatically display the date
  694.     //check_show_date();
  695.     //draw the flashing : as on if the secs have changed.
  696.     if (secs != rtc[0]) {
  697.       //update secs with new value
  698.       secs = rtc[0];
  699.       //draw :
  700.       plot (15 - offset, 2, 1); //top point
  701.       plot (15 - offset, 5, 1); //bottom point
  702.       count = 400;
  703.     }
  704.     //if count has run out, turn off the :
  705.     if (count == 0) {
  706.       plot (15 - offset, 2, 0); //top point
  707.       plot (15 - offset, 5, 0); //bottom point
  708.     }
  709.     else {
  710.       count--;
  711.     }
  712.     //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)
  713.     if (mins != rtc[1]) {
  714.       //update mins and hours with the new values
  715.       mins = rtc[1];
  716.       hours = rtc[2];
  717.       //adjust hours of ampm set to 12 hour mode
  718.       if (hours > 12) {
  719.         hours = hours - ampm * 12;
  720.       }
  721.       if (hours < 1) {
  722.         hours = hours + ampm * 12;
  723.       }
  724.       itoa(hours, buffer, 10);
  725.       //if hours < 10 the num e.g. "3" hours, itoa coverts this to chars with space "3 " which we dont want
  726.       if (hours < 10) {
  727.         buffer[1] = buffer[0];
  728.         buffer[0] = '0';
  729.       }
  730.       //print hours
  731.       //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.
  732.       if (ampm && hours < 10) {
  733.         offset = 2;
  734.         //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
  735.         if ((hours == 1 && mins == 0) ) {
  736.           cls();
  737.         }
  738.       }
  739.       else {
  740.         //else no offset and print hours tens digit
  741.         offset = 0;
  742.         //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
  743.         if (hours == 10 && mins == 0) {
  744.           cls();
  745.         }
  746.         putnormalchar(1,  0, buffer[0], font_style, font_cols);
  747.       }
  748.       //print hours ones digit
  749.       putnormalchar(7 - offset + font_offset, 0, buffer[1], font_style, font_cols);
  750.       //print mins
  751.       //add leading zero if mins < 10
  752.       itoa (mins, buffer, 10);
  753.       if (mins < 10) {
  754.         buffer[1] = buffer[0];
  755.         buffer[0] = '0';
  756.       }
  757.       //print mins tens and ones digits
  758.       putnormalchar(19 - offset - font_offset - font_offset, 0, buffer[0], font_style, font_cols);
  759.       putnormalchar(25 - offset - font_offset, 0, buffer[1], font_style, font_cols);
  760.     }
  761.   }
  762.   fade_down();
  763. }
  764. //like basic_mode but with slide effect
  765. void slide() {
  766.   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
  767.   byte digits_new[4]; //new digits time will slide to reveal
  768.   byte digits_x_pos[4] = {25, 19, 7, 1}; //x pos for which to draw each digit at
  769.   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
  770.   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
  771.   //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.
  772.   //We sent them as A initially, which are used when the clocl enters the mode and no last chars are stored.
  773.   //char old_chars[6] = "AAAAA";
  774.   //plot the clock colon on the display
  775.   cls();
  776.   putnormalchar(13, 0, ':', font_style, font_cols);
  777.   byte old_secs = rtc[0]; //store seconds in old_secs. We compare secs and old secs. WHen they are different we redraw the display
  778.   //run clock main loop as long as run_mode returns true
  779.   while (run_mode()) {
  780.     //Check light levels for turning on/offf matrix
  781.     if (light_count > 5000) {
  782.       light();
  783.       light_count = 0;
  784.     }
  785.     light_count++;
  786.     get_time();
  787.    
  788.     //check for button press
  789.     if (buttonA.uniquePress()) {
  790.       switch_mode();
  791.       return;
  792.     }
  793.       if (buttonB.uniquePress()) {
  794.       display_date();
  795.       return;
  796.     }
  797.     if (buttonC.uniquePress()) {
  798.       display_thp();
  799.       return;
  800.     }
  801.     //if secs have changed then update the display
  802.     if (rtc[0] != old_secs) {
  803.       old_secs = rtc[0];
  804.       //do 12/24 hour conversion if ampm set to 1
  805.       byte hours = rtc[2];
  806.       if (hours > 12) {
  807.         hours = hours - ampm * 12;
  808.       }
  809.       if (hours < 1) {
  810.         hours = hours + ampm * 12;
  811.       }
  812.       //split all date and time into individual digits - stick in digits_new array
  813.       digits_new[0] = (rtc[1] % 10);         //2 - mins ones
  814.       digits_new[1] = ((rtc[1] / 10) % 10);  //3 - mins tens
  815.       digits_new[2] = (hours % 10);         //4 - hour ones
  816.       digits_new[3] = ((hours / 10) % 10);  //5 - hour tens
  817.       //draw initial screen of all chars. After this we just draw the changes.
  818.       //compare digits 0 to 3 (mins and hours)
  819.       for (byte i = 0; i <= 3; i++) {
  820.         //see if digit has changed...
  821.         if (digits_old[i] != digits_new[i]) {
  822.           //run 9 step animation sequence for each in turn
  823.           for (byte seq = 0; seq <= 8 ; seq++) {
  824.             //convert digit to string
  825.             itoa(digits_old[i], old_char, 10);
  826.             itoa(digits_new[i], new_char, 10);
  827.             //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
  828.             if (ampm && i == 3) {
  829.               if (digits_new[3] == 0) {
  830.                 new_char[0] = ' ';
  831.               }
  832.               if (digits_old[3] == 0) {
  833.                 old_char[0] = ' ';
  834.               }
  835.             }
  836.             //draw the animation frame for each digit
  837.             slideanim(digits_x_pos[i], 0, seq, old_char[0], new_char[0]);
  838.             delay(SLIDE_DELAY);
  839.           }
  840.         }
  841.       }
  842.       //save digita array tol old for comparison next loop
  843.       for (byte i = 0; i <= 3; i++) {
  844.         digits_old[i] =  digits_new[i];
  845.       }
  846.     }//secs/oldsecs
  847.   }//while loop
  848.   fade_down();
  849. }
  850. //called by slide
  851. //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
  852. //inputs are are char x and y, animation frame sequence (0-7) and the current and new chars being drawn.
  853. void slideanim(byte x, byte y, byte sequence, char current_c, char new_c) {
  854.   //  To slide one char off and another on we need 9 steps or frames in sequence...
  855.   //  seq# 0123456 <-rows of the display
  856.   //   |   |||||||
  857.   //  seq0 0123456  START - all rows of the display 0-6 show the current characters rows 0-6
  858.   //  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
  859.   //  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
  860.   //  seq3 56 0123
  861.   //  seq4 456 012  half old / half new char
  862.   //  seq5 3456 01
  863.   //  seq6 23456 0
  864.   //  seq7 123456
  865.   //  seq8 0123456  END - all rows show the new char
  866.   //from above we can see...
  867.   //currentchar runs 0-6 then 0-5 then 0-4 all the way to 0. starting Y position increases by 1 row each time.
  868.   //new char runs 6 then 5-6 then 4-6 then 3-6. starting Y position increases by 1 row each time.
  869.   //if sequence number is below 7, we need to draw the current char
  870.   if (sequence < 7) {
  871.     byte dots;
  872.     if (current_c >= 'A' && current_c <= 'Z' ) {
  873.       current_c = (current_c - 'A') + 27;   // A-Z maps to 27-52
  874.     }
  875.     else if (current_c >= 'a' && current_c <= 'z') {
  876.       current_c &= 0x1F;   // a-z maps to 1-26
  877.     }
  878.     else if (current_c >= '0' && current_c <= '9') {
  879.       current_c = (current_c - '0') + 59;   // 0-9 maps to 59-68
  880.     }
  881.     else if (current_c == ' ') {
  882.       current_c = 0; // space
  883.     }
  884.     else if (current_c == '.') {
  885.       current_c = 53; // full stop
  886.     }
  887.     else if (current_c == '\'') {
  888.       current_c = 54; // single quote mark
  889.     }
  890.     else if (current_c == ':') {
  891.       current_c = 55; //colon
  892.     }
  893.     else if (current_c == '>') {
  894.       current_c = 56; // >
  895.     }
  896.     byte curr_char_row_max = 7 - sequence; //the maximum number of rows to draw is 6 - sequence number
  897.     byte start_y = sequence; //y position to start at - is same as sequence number. We inc this each loop
  898.     //plot each row up to row maximum (calculated from sequence number)
  899.     for (byte curr_char_row = 0; curr_char_row <= curr_char_row_max; curr_char_row++) {
  900.       for (byte col = 0; col < 5; col++) {
  901.         dots = pgm_read_byte_near(&myfont[current_c][col]);
  902.         if (dots & (64 >> curr_char_row))
  903.           plot(x + col, y + start_y, 1); //plot led on
  904.         else
  905.           plot(x + col, y + start_y, 0); //else plot led off
  906.       }
  907.       start_y++;//add one to y so we draw next row one down
  908.     }
  909.   }
  910.   //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
  911.   if (sequence >= 1 && sequence <= 8) {
  912.     for (byte col = 0; col < 5; col++) {
  913.       plot(x + col, y + (sequence - 1), 0); //the y position to draw the line is equivalent to the sequence number - 1
  914.     }
  915.   }
  916.   //if sequence is above 2, we also need to start drawing the new char
  917.   if (sequence >= 2) {
  918.     //work out char
  919.     byte dots;
  920.     if (new_c >= 'A' && new_c <= 'Z' ) {
  921.       new_c = (new_c - 'A') + 27;   // A-Z maps to 27-52
  922.     }
  923.     else if (new_c >= 'a' && new_c <= 'z') {
  924.       new_c &= 0x1F;   // a-z maps to 1-26
  925.     }
  926.     else if (new_c >= '0' && new_c <= '9') {
  927.       new_c = (new_c - '0') + 59;   // 0-9 maps to 59-68
  928.     }
  929.     else if (new_c == ' ') {
  930.       new_c = 0; // space
  931.     }
  932.     else if (new_c == '.') {
  933.       new_c = 27; // full stop
  934.     }
  935.     else if (new_c == '\'') {
  936.       new_c = 28; // single quote mark
  937.     }
  938.     else if (new_c == ':') {
  939.       new_c = 29; // colon
  940.     }
  941.     else if (new_c == '>') {
  942.       new_c = 30; // >
  943.     }
  944.     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
  945.     byte start_y = 0; //y position to start at - is same as sequence number. we inc it each row
  946.     //plot each row up from row minimum (calculated by sequence number) up to 6
  947.     for (byte newcharrow = newcharrowmin; newcharrow <= 6; newcharrow++) {
  948.       for (byte col = 0; col < 5; col++) {
  949.         dots = pgm_read_byte_near(&myfont[new_c][col]);
  950.         if (dots & (64 >> newcharrow))
  951.           plot(x + col, y + start_y, 1); //plot led on
  952.         else
  953.           plot(x + col, y + start_y, 0); //else plot led off
  954.       }
  955.       start_y++;//add one to y so we draw next row one down
  956.     }
  957.   }
  958.   
  959. }
  960. //print a clock using words rather than numbers
  961. void word_clock() {
  962.   cls();
  963.   
  964.   //potentially 3 lines to display
  965.   char str_a[8];
  966.   char str_b[9];
  967.   char str_c[8];
  968.   //byte hours_y, mins_y; //hours and mins and positions for hours and mins lines
  969.   byte hours = rtc[2];
  970.   if (hours > 12) {
  971.     hours = hours - ampm * 12;
  972.   }
  973.   if (hours < 1) {
  974.     hours = hours + ampm * 12;
  975.   }
  976.   get_time(); //get the time from the clock chip
  977.   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.
  978.   byte mins;
  979.   //run clock main loop as long as run_mode returns true
  980.   while (run_mode()) {
  981.    
  982.     //check for button press
  983.     if (buttonA.uniquePress()) {
  984.       switch_mode();
  985.       return;
  986.     }
  987.     if (buttonB.uniquePress()) {
  988.       display_date();
  989.     }
  990.     if (buttonC.uniquePress()) {
  991.       display_thp();
  992.       return;
  993.     }
  994.     get_time(); //get the time from the clock chip
  995.     mins = rtc[1];  //get mins
  996.     //if mins is different from old_mins - redraw display
  997.     if (mins != old_mins) {
  998.       //update old_mins with current mins value
  999.       old_mins = mins;
  1000.       //reset these for comparison next time
  1001.       mins = rtc[1];
  1002.       hours = rtc[2];
  1003.       //make hours into 12 hour format
  1004.       if (hours > 12) {
  1005.         hours = hours - 12;
  1006.       }
  1007.       if (hours == 0) {
  1008.         hours = 12;
  1009.       }
  1010.       //split mins value up into two separate digits
  1011.       int minsdigit = rtc[1] % 10;
  1012.       byte minsdigitten = (rtc[1] / 10) % 10;
  1013.       const char past[] = "PAST";
  1014.       const char to[] = "TO";
  1015.       const char half[] = "HALF";
  1016.       const char quar[] = "QUARTER";
  1017.       const char oclk[] = "O'CLOCK";
  1018.       //if both mins are zero, i.e. it is on the hour, the top line reads "hours" and bottom line reads "o'clock"
  1019.       if (minsdigitten == 0 && minsdigit == 0  ) {
  1020.         progmem_numbers(0, hours - 1);
  1021.         strcpy (str_a, words);
  1022.         strcpy (str_b, oclk);
  1023.         strcpy (str_c, "");
  1024.       }
  1025.       //if mins <= 10 , then top line has to read "minsdigti past" and bottom line reads hours
  1026.       else if (mins < 10) {
  1027.         progmem_numbers(0, minsdigit - 1);
  1028.         strcpy (str_a, words);
  1029.         strcpy (str_b, past);
  1030.         progmem_numbers(0, hours - 1);
  1031.         strcpy (str_c, words);
  1032.       }
  1033.       //if mins = 10, cant use minsdigit as above, so soecial case to print 10 past /n hour.
  1034.       if (mins == 10) {
  1035.         progmem_numbers(0, 9);
  1036.         strcpy (str_a, words);
  1037.         strcpy (str_b, past);
  1038.         progmem_numbers(0, hours - 1);
  1039.         strcpy (str_c, words);
  1040.       }
  1041.       else if (mins == 15) {
  1042.         strcpy (str_a, quar);
  1043.         strcpy (str_b, past);
  1044.         progmem_numbers(0, hours - 1);
  1045.         strcpy (str_c, words);
  1046.       }
  1047.       else if (mins == 20) {
  1048.         progmem_numbers(1, minsdigitten - 1);
  1049.         strcpy (str_a, words);
  1050.         strcpy (str_b, past);
  1051.         progmem_numbers(0, hours - 1);
  1052.         strcpy (str_c, words);
  1053.       }
  1054.       else if (mins == 30) {
  1055.         strcpy (str_a, half);
  1056.         strcpy (str_b, past);
  1057.         progmem_numbers(0, hours - 1);
  1058.         strcpy (str_c, words);
  1059.       }
  1060.       else if (mins == 40) {
  1061.         progmem_numbers(1, 1);
  1062.         strcpy (str_a, words);
  1063.         strcpy (str_b, to);
  1064.         if (hours == 12) {
  1065.           progmem_numbers(0, hours - hours);
  1066.         }
  1067.         else {
  1068.           progmem_numbers(0, hours);
  1069.         }
  1070.         strcpy (str_c, words);
  1071.       }
  1072.       else if (mins == 50) {
  1073.         progmem_numbers(0, 9);
  1074.         strcpy (str_a, words);
  1075.         strcpy (str_b, to);
  1076.         if (hours == 12) {
  1077.           progmem_numbers(0, hours - hours);
  1078.         }
  1079.         else {
  1080.           progmem_numbers(0, hours);
  1081.         }
  1082.         strcpy (str_c, words);
  1083.       }
  1084.       else if (mins == 45) {
  1085.         strcpy (str_a, quar);
  1086.         strcpy (str_b, to);
  1087.         if (hours == 12) {
  1088.           progmem_numbers(0, hours - hours);
  1089.         }
  1090.         else {
  1091.           progmem_numbers(0, hours);
  1092.         }
  1093.         strcpy (str_c, words);
  1094.       }
  1095.       //if time is not on the hour - i.e. both mins digits are not zero,
  1096.       //then make first line read "hours" and 2 & 3rd lines read "minstens"  "mins" e.g. "three /n twenty /n one"
  1097.       else if (minsdigitten != 0 && minsdigit != 0) {
  1098.         progmem_numbers(0, hours - 1);
  1099.         strcpy (str_a, words);
  1100.         //if mins is in the teens, use teens from the numbers array for the 2nd line, e.g. "fifteen"
  1101.         if (mins >= 11 && mins <= 19) {
  1102.           progmem_numbers(0, mins - 1);
  1103.           strcpy (str_a, words);
  1104.           strcpy (str_b, past);
  1105.           progmem_numbers(0, hours - 1);
  1106.           strcpy (str_c, words);
  1107.         }
  1108.         else if (mins > 50) {
  1109.           progmem_numbers(0, 60 - (mins + 1));
  1110.           strcpy (str_a, words);
  1111.           strcpy (str_b, to);
  1112.           if (hours == 12) {
  1113.             progmem_numbers(0, hours - 12);
  1114.             strcpy (str_c, words);
  1115.           }
  1116.           else {
  1117.             progmem_numbers(0, hours);
  1118.             strcpy (str_c, words);
  1119.           }
  1120.         }
  1121.         else {
  1122.           progmem_numbers(1, minsdigitten - 1);
  1123.           strcpy (str_b, words);
  1124.           progmem_numbers(0, minsdigit - 1);
  1125.           strcpy (str_c, words);
  1126.         }
  1127.       }
  1128.     }//end working out time
  1129.     //run in a loop
  1130.     //print line a "twelve"
  1131.     byte len = 0;
  1132.     while (str_a[len]) {
  1133.       len++;
  1134.     }; //get length of message
  1135.     byte offset_top = (31 - ((len - 1) * 4)) / 2; //
  1136.     //plot hours line
  1137.     byte i = 0;
  1138.     while (str_a[i]) {
  1139.       puttinychar((i * 4) + offset_top, 1, str_a[i]);
  1140.       i++;
  1141.     }
  1142.    
  1143.     //hold display but check for button presses
  1144.     int counter = 1000;
  1145.     while (counter > 0) {
  1146.       //check for button press
  1147.       if (buttonA.uniquePress()) {
  1148.         switch_mode();
  1149.         return;
  1150.       }
  1151.       if (buttonB.uniquePress()) {
  1152.         display_date();
  1153.       }
  1154.       if (buttonC.uniquePress()) {
  1155.         display_thp();
  1156.         return;
  1157.       }
  1158.     delay(1);
  1159.     counter--;   
  1160.     }
  1161.     fade_down();
  1162.     //print line b
  1163.     len = 0;
  1164.     while (str_b[len]) {
  1165.       len++;
  1166.     }; //get length of message
  1167.     offset_top = (31 - ((len - 1) * 4)) / 2;
  1168.     i = 0;
  1169.     while (str_b[i]) {
  1170.       puttinychar((i * 4) + offset_top, 1, str_b[i]);
  1171.       i++;
  1172.     }
  1173.     //hold display but check for button presses
  1174.     counter = 1000;
  1175.     while (counter > 0){
  1176.       if (buttonA.uniquePress()) {
  1177.         switch_mode();
  1178.         return;
  1179.       }
  1180.       if (buttonB.uniquePress()) {
  1181.         display_date();
  1182.       }
  1183.       if (buttonC.uniquePress()) {
  1184.         display_thp();
  1185.         return;
  1186.       }
  1187.       delay(1);
  1188.       counter--;
  1189.     }
  1190.     fade_down();
  1191.     //print line c if there.
  1192.     len = 0;
  1193.     while (str_c[len]) {
  1194.       len++;
  1195.     }; //get length of message
  1196.     offset_top = (31 - ((len - 1) * 4)) / 2;
  1197.     i = 0;
  1198.     while (str_c[i]) {
  1199.       puttinychar((i * 4) + offset_top, 1, str_c[i]);
  1200.       i++;
  1201.     }
  1202.     counter = 1000;
  1203.     while (counter > 0){
  1204.       //check for button press
  1205.       if (buttonA.uniquePress()) {
  1206.         switch_mode();
  1207.         return;
  1208.       }
  1209.       if (buttonB.uniquePress()) {
  1210.         display_date();
  1211.       }
  1212.       if (buttonC.uniquePress()) {
  1213.         display_thp();
  1214.         return;
  1215.       }
  1216.       delay(1);
  1217.       counter--;
  1218.     }
  1219.     fade_down();
  1220.     //hold display blank but check for button presses before starting again.
  1221.     counter = 1000;
  1222.     while (counter > 0) {
  1223.        //check for button press
  1224.       if (buttonA.uniquePress()) {
  1225.         switch_mode();
  1226.         return;
  1227.       }
  1228.       if (buttonB.uniquePress()) {
  1229.         display_date();
  1230.       }
  1231.       if (buttonC.uniquePress()) {
  1232.         display_thp();
  1233.         return;
  1234.       }
  1235.       delay(1);
  1236.       counter--;
  1237.       //Check light levels for turning on/off matrix
  1238.       if (light_count > 2000) {
  1239.         light();
  1240.         light_count = 0;
  1241.       }
  1242.       light_count++;
  1243.     }
  1244.   }
  1245.   fade_down();
  1246. }
  1247. //used by word mode to retrieve words from progmem, m : 0 = numbers, 1 = numberstens. i = index
  1248. void progmem_numbers(byte m, byte i) {
  1249.   if (m == 0) {
  1250.     strcpy_P(words, (char *)pgm_read_word(&(numbers[i])));
  1251.   }
  1252.   else if (m == 1) {
  1253.     strcpy_P(words, (char *)pgm_read_word(&(numberstens[i])));
  1254.   }
  1255. }
  1256. //display_thp - print temperature, humidity and pressue
  1257. void display_thp()
  1258. {
  1259.   cls();
  1260.   bme.takeForcedMeasurement();
  1261.   char temp[5];
  1262.   char humi[3];
  1263.   char pres[5];
  1264.   dtostrf(bme.readTemperature(), 4, 1, temp);
  1265.   dtostrf(bme.readHumidity(), 3, 0, humi);
  1266.   dtostrf(bme.readPressure() / 100.0F, 4, 0, pres);
  1267.   byte i = 0;
  1268.   byte offset = 0;
  1269.   while (temp[i]) {
  1270.     if (i == 0) {
  1271.       offset = 2;
  1272.     }
  1273.     else if (i == 2) {
  1274.       offset = 4;
  1275.     }
  1276.     else if (i == 3) {
  1277.       offset = 1;
  1278.     }
  1279.     else {
  1280.       offset = 3;
  1281.     }
  1282.     putnormalchar(i * 6 + offset + font_offset, 0, temp[i], font_style, font_cols);
  1283.     i++;
  1284.   }
  1285.   putnormalchar(i * 6 + 2 + font_offset, 0, '~', font_style, font_cols);  // '~' translates to degress symbol via FontLEDClock.h
  1286.   delay(2000);
  1287.   fade_down();
  1288.   i = 0;
  1289.   while (humi[i]) {
  1290.     if (i < 2) {
  1291.       offset = 0;
  1292.     }
  1293.     else {
  1294.       offset = 1;
  1295.     }
  1296.     putnormalchar(i * 6 + offset + font_offset, 0, humi[i], font_style, font_cols);
  1297.     i++;
  1298.   }
  1299.   putnormalchar(i * 6 + 2 + font_offset, 0, '%', font_style, font_cols);
  1300.   delay(2000);
  1301.   fade_down();
  1302.   i = 0;
  1303.   while (pres[i]) {
  1304.     if (strlen(pres) < 4 && i == 0) {
  1305.       offset = 5;
  1306.     }
  1307.     else {
  1308.       offset = 0;
  1309.     }
  1310.     putnormalchar(i * 6 - offset, 0, pres[i], 1, 5);
  1311.     i++;
  1312.   }
  1313.   byte x = 0;
  1314.   const char mb[] = "mb";
  1315.   while (mb[x]) {
  1316.     puttinychar(i * 4 + 8, 1, mb[x]);
  1317.     x++;i++;
  1318.   }
  1319.   
  1320.   delay(2000);
  1321.   fade_down();
  1322.   cls();
  1323.    
  1324. }
  1325. //display_date - print the day of week, date and month with a flashing cursor effect
  1326. void display_date() {
  1327.   cls();
  1328.   //date suffix array
  1329.   const char suffix[4][3] = {"st", "nd", "rd", "th"};
  1330.   //read the date from the DS3231
  1331.   byte dow = rtc[3]; // day of week 0 = Sunday
  1332.   byte date = rtc[4];
  1333.   byte month = rtc[5] - 1;
  1334.   //print the day name
  1335.   
  1336.   //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
  1337.   byte len = 0;
  1338.   char dayfullname[9];
  1339.   strcpy_P(dayfullname, (char *)pgm_read_word(&(daysfull[dow])));
  1340.   while(dayfullname[len]) {
  1341.     len++;
  1342.   };
  1343.   byte offset = (31 - ((len - 1) * 4)) / 2; //our offset to centre up the text
  1344.       
  1345.   //print the name     
  1346.   int i = 0;
  1347.   while(dayfullname[i])
  1348.   {
  1349.     puttinychar((i * 4) + offset , 1, dayfullname[i]);
  1350.     i++;
  1351.   }
  1352.   delay(1000);
  1353.   fade_down();
  1354.   cls();
  1355.   
  1356.   // print date numerals
  1357.   char buffer[3];
  1358.   itoa(date, buffer, 10);
  1359.   offset = 10; //offset to centre text if 3 chars - e.g. 3rd
  1360.   
  1361.   byte s = 3;
  1362.   if(date == 1 || date == 21 || date == 31) {
  1363.     s = 0;
  1364.   }
  1365.   else if (date == 2 || date == 22) {
  1366.     s = 1;
  1367.   }
  1368.   else if (date == 3 || date == 23) {
  1369.     s = 2;
  1370.   }
  1371.   //print the 1st date number
  1372.   puttinychar(0 + offset, 1, buffer[0]);
  1373.   //if date is under 10 - then we only have 1 digit so set positions of sufix etc one character nearer
  1374.   byte suffixposx = 4;
  1375.   //if date over 9 then print second number and set xpos of suffix to be 1 char further away
  1376.   if (date > 9){
  1377.     suffixposx = 10;
  1378.     puttinychar(4 + offset, 1, buffer[1]);
  1379.     offset = 8; //offset to centre text if 4 chars
  1380.   }
  1381.   //print the 2 suffix characters
  1382.   puttinychar(suffixposx + offset, 1, suffix[s][0]);
  1383.   puttinychar(suffixposx + 4 + offset, 1, suffix[s][1]);
  1384.   delay(1000);
  1385.   fade_down();
  1386.   
  1387.   //print the month name
  1388.   
  1389.   //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
  1390.   len = 0;
  1391.   char monthfullname[9];
  1392.   strcpy_P(monthfullname, (char *)pgm_read_word(&(monthsfull[month])));
  1393.   while(monthfullname[len]) {
  1394.     len++;
  1395.   };
  1396.   offset = (31 - ((len - 1) * 4)) / 2; //our offset to centre up the text
  1397.   i = 0;
  1398.   while(monthfullname[i])
  1399.   {
  1400.     puttinychar((i * 4) + offset, 1, monthfullname[i]);
  1401.     i++;
  1402.   }
  1403.   
  1404.   delay(1000);
  1405.   fade_down();
  1406. }
  1407. //dislpay menu to change the clock mode
  1408. void switch_mode() {
  1409.   //not sure why, but this is needed to stop ampm bool getting messed up
  1410.   //ampm = eeprom_read_bool(208);
  1411.   
  1412.   //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.
  1413.   old_mode = clock_mode;
  1414.   const char modes[5][7] = {">Basic", ">Small", ">Slide", ">Words", ">Setup"};
  1415.   byte firstrun = 1;
  1416.   //loop waiting for button (timeout after 35 loops to return to mode X)
  1417.   for (int count = 0; count < 35 ; count++) {
  1418.     //if user hits button, change the clock_mode
  1419.     if (buttonA.uniquePress() || firstrun == 1) {
  1420.       count = 0;
  1421.       cls();
  1422.       if (firstrun == 0) {
  1423.         clock_mode++;
  1424.       }
  1425.       if (clock_mode > NUM_DISPLAY_MODES + 1) {
  1426.         clock_mode = 0;
  1427.       }
  1428.       //print arrow and current clock_mode name on line one and print next clock_mode name on line two
  1429.       char str_top[9];
  1430.       strcpy (str_top, modes[clock_mode]);
  1431.       byte i = 0;
  1432.       while (str_top[i]) {
  1433.         puttinychar(i * 4 + 1, 1, str_top[i]);
  1434.         i++;
  1435.       }
  1436.       firstrun = 0;
  1437.     }
  1438.     delay(50);
  1439.   }
  1440.   if (old_mode != clock_mode && clock_mode < 4){
  1441.       //save the values to EEPROM
  1442.       eeprom_save(201, clock_mode, 0, 0);
  1443.       //Serial.print(clock_mode);
  1444.   }
  1445. }
  1446. //run clock main loop as long as run_mode returns true
  1447. byte run_mode() {
  1448.   //if random mode is on... check the hour when we change mode.
  1449.   if (random_mode || random_font_mode) {
  1450.     //if hour value in change mode time = hours. then return false = i.e. exit mode.
  1451.     if (change_mode_time == rtc[2]) {
  1452.       //set the next random clock mode and time to change it
  1453.       set_next_random();
  1454.       //exit the current mode.
  1455.       return 0;
  1456.     }
  1457.   }
  1458.   //run dst() calculation
  1459.   if (dst_mode && rtc[2] == ntp_dst_hour && !dst_ntp_run) {
  1460.     dst();
  1461.     dst_ntp_run = 1;
  1462.   }
  1463.   //reset once a day dst/ntp check so it runs next day
  1464.   if (rtc[2] > ntp_dst_hour) {
  1465.     dst_ntp_run = 0;
  1466.   }
  1467.   //else return 1 - keep running in this mode
  1468.   return 1;
  1469. }
  1470. //set the next hour the clock will change mode when random mode is on, also does the random font mode
  1471. void set_next_random() {
  1472.   //set the next hour the clock mode will change - current time plus 1
  1473.   get_time();
  1474.   change_mode_time = rtc[2] + 1;
  1475.   //if change_mode_time now happens to be over 23, then set it 12am
  1476.   if (change_mode_time > 23) {
  1477.     change_mode_time = 0;
  1478.   }
  1479.   if (random_mode) {
  1480.     //set the new clock mode
  1481.     clock_mode = random(0, NUM_DISPLAY_MODES + 1);  //pick new random clock mode
  1482.   }
  1483.   if (random_font_mode) {
  1484.     //set new random font
  1485.     set_font_case(random(1, NUM_FONTS + 1));  //pick new random font mode
  1486.   }
  1487. }
  1488. //dislpay menu to change the clock settings
  1489. void setup_menu() {
  1490.   const char set_modes[10][9] = {">Rnd Clk", ">Rnd Fnt", ">12 Hr", ">Font", ">DST", ">D/Time", ">Auto LX", ">Display", ">Bright", ">Exit"};
  1491.   byte setting_mode = 0;
  1492.   byte firstrun = 1;
  1493.   //loop waiting for button (timeout after 35 loops to return to mode X)
  1494.   for (int count = 0; count < 35 ; count++) {
  1495.     //if user hits button, change the clock_mode
  1496.     if (buttonA.uniquePress() || firstrun == 1) {
  1497.       count = 0;
  1498.       cls();
  1499.       if (firstrun == 0) {
  1500.         setting_mode++;
  1501.       }
  1502.       if (setting_mode > NUM_SETTINGS_MODES) {
  1503.         setting_mode = 0;
  1504.       }
  1505.       //print arrow and current clock_mode name on line one and print next clock_mode name on line two
  1506.       char str_top[9];
  1507.    
  1508.       strcpy (str_top, set_modes[setting_mode]);
  1509.       byte i = 0;
  1510.       while(str_top[i]) {
  1511.         puttinychar(i * 4 + 1, 1, str_top[i]);
  1512.         i++;
  1513.       }
  1514.       firstrun = 0;
  1515.     }
  1516.     delay(50);
  1517.   }
  1518.   //pick the mode
  1519.   switch(setting_mode) {
  1520.     case 0:
  1521.       set_random();
  1522.       break;
  1523.     case 1:
  1524.       set_random_font();
  1525.       break;
  1526.     case 2:
  1527.       set_ampm();
  1528.       break;
  1529.     case 3:
  1530.       set_font();
  1531.       break;
  1532.     case 4:
  1533.       set_ntp_dst();
  1534.       break;
  1535.     case 5:
  1536.       set_time();
  1537.       break;
  1538.     case 6:
  1539.       set_auto_intensity();
  1540.       break;
  1541.     case 7:
  1542.       set_display_options();
  1543.       break;
  1544.     case 8:
  1545.       set_intensity();
  1546.       break;
  1547.     case 9:
  1548.       //exit menu
  1549.       break;
  1550.   }
  1551.    
  1552.   //change the clock from mode 6 (settings) back to the one it was in before
  1553.   clock_mode = old_mode;
  1554. }
  1555. //toggle random mode - pick a different clock mode every few hours
  1556. void set_random() {
  1557.   
  1558.   cls();
  1559.   //get current values
  1560.   bool set_random_mode = eeprom_read_bool(206);
  1561.   //Set function - we pass in: which 'set' message to show at top, current value
  1562.   set_random_mode = set_bool_value(3, set_random_mode);
  1563.   //set the values
  1564.   random_mode = set_random_mode;
  1565.   //set hour mode will change
  1566.   set_next_random();
  1567.   //save the values to EEPROM
  1568.   eeprom_save(206, 0, random_mode, 0);
  1569.   //Serial.println("random_mode");
  1570. }
  1571. //toggle random font
  1572. void set_random_font() {
  1573.   
  1574.   cls();
  1575.   
  1576.   //get current values
  1577.   bool set_random_font_mode = eeprom_read_bool(207);
  1578.   //Set function - we pass in: which 'set' message to show at top, current value
  1579.   set_random_font_mode = set_bool_value(3, set_random_font_mode);
  1580.   //set the values
  1581.   random_font_mode = set_random_font_mode;
  1582.   //set hour mode will change
  1583.   set_next_random();
  1584.   //save the values to EEPROM
  1585.   eeprom_save(207, 0, random_font_mode, 0);
  1586.   //Serial.println("random_font_mode");
  1587.   
  1588. }
  1589. //set 12 or 24 hour clock
  1590. void set_ampm() {
  1591.   cls();
  1592.   //get current values
  1593.   bool set_ampm_mode = eeprom_read_bool(208);
  1594.   //Set function - we pass in: which 'set' message to show at top, current value
  1595.   set_ampm_mode = set_bool_value(2, set_ampm_mode);
  1596.   //set the values
  1597.   ampm = set_ampm_mode;
  1598.   //save the values to EEPROM
  1599.   eeprom_save(208, 0, set_ampm_mode, 0);
  1600.   //Serial.println("ampm");
  1601. }
  1602. //set font style
  1603. void set_font() {
  1604.   cls();
  1605.   byte i = 0;
  1606.   const char text[] = ">Set Fnt";
  1607.   while(text[i]) {
  1608.     puttinychar(i * 4, 1, text[i]);
  1609.     i++;
  1610.   }
  1611.   
  1612.   delay(1500);
  1613.   cls();
  1614.   byte set_font_value;
  1615.   if (font_style == 2 && font_cols == 5) {
  1616.     set_font_value = 6;
  1617.   }
  1618.   else {
  1619.     set_font_value = font_style;
  1620.   }
  1621.   get_font_value(set_font_value, 1, NUM_FONTS);
  1622.   //save the values to EEPROM
  1623.   eeprom_save(203, font_style, 0, 0);
  1624.   eeprom_save(204, font_offset, 0, 0);
  1625.   eeprom_save(205, font_cols, 0, 0);
  1626.   //Serial.println("font style, offset and cols");
  1627. }
  1628. //set font_style, font_offset & font_cols variables, used by set_font()
  1629. void set_font_case(int value) {
  1630.   
  1631.   switch(value) {
  1632.     case 1:
  1633.       font_style = 1;
  1634.       font_offset = 0;
  1635.       font_cols = 5;
  1636.       break;
  1637.     case 2:
  1638.       font_style = 2;
  1639.       font_offset = 1;
  1640.       font_cols = 6;
  1641.       break;
  1642.     case 3:
  1643.       font_style = 3;
  1644.       font_offset = 1;
  1645.       font_cols = 6;
  1646.       break;
  1647.     case 4:
  1648.       font_style = 4;
  1649.       font_offset = 1;
  1650.       font_cols = 6;
  1651.       break;
  1652.     case 5:
  1653.       font_style = 5;
  1654.       font_offset = 0;
  1655.       font_cols = 5;
  1656.       break;
  1657.     case 6:
  1658.       font_style = 2;
  1659.       font_offset = 0;
  1660.       font_cols = 5;  //cheap way to create a new font (crop 1 column right side of font 2)
  1661.       break;
  1662.     case 7:
  1663.       font_style = 7;
  1664.       font_offset = 1;
  1665.       font_cols = 6;
  1666.       break;
  1667.   }
  1668. }
  1669. //get user values for setting font
  1670. void get_font_value(int current_value, int min_value, int max_value) {
  1671.   
  1672.   //print digits bottom line
  1673.   char buffer[2] = " ";
  1674.   itoa(current_value, buffer ,10);
  1675.   puttinychar(0, 1, '>');
  1676.   puttinychar(4, 1, buffer[0]);
  1677.   delay(300);
  1678.   //wait for button input
  1679.   while (!buttonA.uniquePress()) {
  1680.     char preview[4] = "   ";
  1681.     //font preview numbers
  1682.     itoa(123, preview, 10);
  1683.     while (buttonB.isPressed()){
  1684.       if(current_value < max_value) {
  1685.         current_value++;
  1686.       }
  1687.       else {
  1688.         current_value = min_value;
  1689.       }
  1690.       //print the new value
  1691.       cls();
  1692.       itoa(current_value, buffer, 10);
  1693.       puttinychar(0, 1, '>');
  1694.       puttinychar(4, 1, buffer[0]);
  1695.       //preview the font and set the font
  1696.       set_font_case(current_value);
  1697.       byte i = 0;
  1698.       while(preview[i]) {
  1699.         putnormalchar(i * (font_cols + 1) + 10, 0, preview[i], font_style, font_cols);
  1700.         i++;
  1701.       }
  1702.       delay(150);
  1703.     }
  1704.     while (buttonC.isPressed()) {
  1705.       if(current_value > min_value) {
  1706.         current_value--;
  1707.       }
  1708.       else {
  1709.         current_value = max_value;
  1710.       }
  1711.       //print the new value
  1712.       cls();
  1713.       itoa(current_value, buffer ,10);
  1714.       puttinychar(0, 1, '>');
  1715.       puttinychar(4, 1, buffer[0]);
  1716.       //preview the font and set the font
  1717.       set_font_case(current_value);
  1718.       byte i = 0;
  1719.       while(preview[i]) {
  1720.         putnormalchar(i * (font_cols + 1) + 10, 0, preview[i], font_style, font_cols);
  1721.         i++;
  1722.       }
  1723.       delay(150);
  1724.     }
  1725.   }
  1726.   //return current_value;
  1727. }
  1728. //set ntp and dst settings
  1729. void set_ntp_dst() {
  1730.   cls();
  1731.   //get current values
  1732.   bool set_dst_mode = eeprom_read_bool(210);
  1733.   //Set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.
  1734.   set_dst_mode = set_bool_value(0, set_dst_mode);
  1735.   //set the values
  1736.   dst_mode = set_dst_mode;
  1737.   
  1738.   //save the values to EEPROM
  1739.   eeprom_save(210, 0, set_dst_mode, 0);
  1740.   //Serial.println(set_dst_mode);
  1741.   
  1742.   //cls();
  1743. }
  1744. //used to set bool for DST, NTP, 12h, Random and Light
  1745. //message = which 'set' message to print,
  1746. //current value = current value of property we are setting
  1747. bool set_bool_value(byte message, bool current_value){
  1748.   cls();
  1749.   const char options[4][9] = {">Set DST", ">Set 12h", ">Set Rnd", ">Set LX"};
  1750.   //Print "set xyz" top line
  1751.   byte i = 0;
  1752.   while(options[message][i])
  1753.   {
  1754.     puttinychar(i * 4, 1, options[message][i]);
  1755.     i++;
  1756.   }
  1757.   delay(1500);
  1758.   cls();
  1759.   const char text[2][5] = {">OFF", ">ON "};
  1760.   //print current value
  1761.   i = 0;
  1762.   while(text[current_value][i]) {
  1763.     puttinychar(i * 4, 1, text[current_value][i]);
  1764.     i++;
  1765.   }
  1766.   delay(300);
  1767.   //wait for button input
  1768.   while (!buttonA.uniquePress()) {
  1769.     while (buttonB.isPressed()) {
  1770.       current_value = (current_value ^ 1);
  1771.       
  1772.       //print the new value
  1773.       i = 0;
  1774.       while(text[current_value][i]) {
  1775.         puttinychar(i * 4, 1, text[current_value][i]);
  1776.         i++;
  1777.       }
  1778.       delay(150);
  1779.     }
  1780.     while (buttonC.isPressed()) {
  1781.       current_value = (current_value ^ 1);
  1782.       //print the new value
  1783.       i = 0;
  1784.       while(text[current_value][i]) {
  1785.         puttinychar(i * 4, 1, text[current_value][i]);
  1786.         i++;
  1787.       }
  1788.       delay(150);
  1789.     }
  1790.    
  1791.   }
  1792.   return current_value;
  1793. }
  1794. //change screen intensity
  1795. void set_intensity() {
  1796.   cls();
  1797.   
  1798.   byte i = 0;
  1799.   const char text[] = ">Bright";
  1800.   while(text[i]) {
  1801.     puttinychar((i * 4) + 3, 0, text[i]);
  1802.     i++;
  1803.   }
  1804.   //wait for button input
  1805.   while (!buttonA.uniquePress()) {
  1806.     levelbar (0, 6,(intensity * 2) + 2, 2);    //display the intensity level as a bar
  1807.     while (buttonB.isPressed()) {
  1808.       if(intensity == 15) {
  1809.         intensity = 0;
  1810.         cls ();
  1811.       }
  1812.       else {
  1813.         intensity++;
  1814.       }
  1815.       //print the new value
  1816.       i = 0;
  1817.       while(text[i]) {
  1818.         puttinychar((i * 4) + 4, 0, text[i]);
  1819.         i++;
  1820.       }
  1821.       
  1822.       //display the intensity level as a bar
  1823.       levelbar (0, 6, (intensity * 2) + 2, 2);   
  1824.       
  1825.       //change the brightness setting on the displays
  1826.       for (byte address = 0; address < 4; address++) {
  1827.         lc.setIntensity(address, intensity);
  1828.       }
  1829.       delay(150);
  1830.     }
  1831.     while (buttonC.isPressed()) {
  1832.       if(intensity == 0) {
  1833.         intensity = 15;
  1834.       }
  1835.       else {
  1836.         intensity--;
  1837.       }
  1838.       //display the intensity level as a bar
  1839.       cls ();
  1840.       levelbar (0, 6,(intensity * 2) + 2, 2);
  1841.       
  1842.       //print the new value
  1843.       i = 0;
  1844.       while(text[i]) {
  1845.         puttinychar((i * 4) + 4, 0, text[i]);
  1846.         i++;
  1847.       }
  1848.       
  1849.       //change the brightness setting on the displays
  1850.       for (byte address = 0; address < 4; address++) {
  1851.         lc.setIntensity(address, intensity);
  1852.       }
  1853.       delay(150);
  1854.     }
  1855.   }
  1856.   //save the values to EEPROM
  1857.   eeprom_save(200, intensity, 0, 0);
  1858.   //Serial.println("intensity");
  1859. }
  1860. // menu for setting auto intensity settings
  1861. void set_auto_intensity() {
  1862.   cls();
  1863.   //get current values
  1864.   bool set_auto_intensity_value = eeprom_read_bool(209);
  1865.   //Set function - we pass in: which 'set' message to show at top, current value
  1866.   set_auto_intensity_value = set_bool_value(4, set_auto_intensity_value);
  1867.   //set the values
  1868.   auto_intensity = set_auto_intensity_value;
  1869.   //save the values to EEPROM
  1870.   eeprom_save(209, 0, auto_intensity, 0);
  1871.   //Serial.println("auto_intensity");
  1872. }
  1873. // display a horizontal bar on the screen at offset xposr by ypos with height and width of xbar, ybar
  1874. void levelbar (byte xpos, byte ypos, byte xbar, byte ybar) {
  1875.   for (byte x = 0; x < xbar; x++) {
  1876.     for (byte y = 0; y <= ybar; y++) {
  1877.       plot(x + xpos, y + ypos, 1);
  1878.     }
  1879.   }
  1880. }
  1881. //set time and date routine
  1882. void set_time() {
  1883.   cls();
  1884.   //fill settings with current clock values read from clock
  1885.   get_time();
  1886.   byte set_min   = rtc[1];
  1887.   byte set_hr    = rtc[2];
  1888.   byte set_date  = rtc[4];
  1889.   byte set_mnth  = rtc[5];
  1890.   int  set_yr    = rtc[6];
  1891.   //Set function - we pass in: which 'set' message to show at top, current value, reset value, and rollover limit.
  1892.   set_date = set_value(2, set_date, 1, 31);
  1893.   set_mnth = set_value(3, set_mnth, 1, 12);
  1894.   set_yr   = set_value(4, set_yr, 2019, 2099);
  1895.   set_hr   = set_value(1, set_hr, 0, 23);
  1896.   set_min  = set_value(0, set_min, 0, 59);
  1897.   ds3231.adjust(DateTime(set_yr, set_mnth, set_date, set_hr, set_min));
  1898.   
  1899.   cls();
  1900. }
  1901. //used to set min, hr, date, month, year values. pass
  1902. //message = which 'set' message to print,
  1903. //current value = current value of property we are setting
  1904. //reset_value = what to reset value to if to rolls over. E.g. mins roll from 60 to 0, months from 12 to 1
  1905. //rollover limit = when value rolls over
  1906. int set_value(byte message, int current_value, int reset_value, int rollover_limit) {
  1907.   cls();
  1908.   const char messages[5][9] = {">Set Min", ">Set Hr", ">Set Day", ">Set Mth", ">Set Yr"};
  1909.   //Print "set xyz" top line
  1910.   byte i = 0;
  1911.   while(messages[message][i])
  1912.   {
  1913.     puttinychar(i * 4, 1, messages[message][i]);
  1914.     i++;
  1915.   }
  1916.   delay(1500);
  1917.   cls();
  1918.   //print digits bottom line
  1919.   char buffer[5] = "    ";
  1920.   itoa(current_value,buffer,10);
  1921.   puttinychar(0 , 1, '>');
  1922.   puttinychar(4 , 1, buffer[0]);
  1923.   puttinychar(8 , 1, buffer[1]);
  1924.   puttinychar(12, 1, buffer[2]);
  1925.   puttinychar(16, 1, buffer[3]);
  1926.   delay(300);
  1927.   //wait for button input
  1928.   while (!buttonA.uniquePress()) {
  1929.     while (buttonB.isPressed()) {
  1930.       if(current_value < rollover_limit) {
  1931.         current_value++;
  1932.       }
  1933.       else {
  1934.         current_value = reset_value;
  1935.       }
  1936.       //print the new value
  1937.       itoa(current_value, buffer ,10);
  1938.       puttinychar(0 , 1, '>');
  1939.       puttinychar(4 , 1, buffer[0]);
  1940.       puttinychar(8 , 1, buffer[1]);
  1941.       puttinychar(12, 1, buffer[2]);
  1942.       puttinychar(16, 1, buffer[3]);
  1943.       delay(150);
  1944.     }
  1945.     while (buttonC.isPressed()) {
  1946.       if(current_value > reset_value) {
  1947.         current_value--;
  1948.       }
  1949.       else {
  1950.         current_value = rollover_limit;
  1951.       }
  1952.       //print the new value
  1953.       itoa(current_value, buffer, 10);
  1954.       puttinychar(0 , 1, '>');
  1955.       puttinychar(4 , 1, buffer[0]);
  1956.       puttinychar(8 , 1, buffer[1]);
  1957.       puttinychar(12, 1, buffer[2]);
  1958.       puttinychar(16, 1, buffer[3]);
  1959.       delay(150);
  1960.     }
  1961.    
  1962.   }
  1963.   return current_value;
  1964. }
  1965. // function to get time from DS3231
  1966. void get_time() {
  1967.   //get time
  1968.   DateTime now = ds3231.now();
  1969.   //save time to array
  1970.   rtc[6] = now.year();
  1971.   rtc[5] = now.month();
  1972.   rtc[4] = now.day();
  1973.   rtc[3] = now.dayOfTheWeek(); //returns 0-6 where 0 = Sunday
  1974.   rtc[2] = now.hour();
  1975.   rtc[1] = now.minute();
  1976.   rtc[0] = now.second();
  1977.   //print the time to the serial port - useful for debuging RTC issues
  1978.   /*
  1979.   Serial.print(rtc[2]);
  1980.   Serial.print(":");
  1981.   Serial.print(rtc[1]);
  1982.   Serial.print(":");
  1983.   Serial.println(rtc[0]);
  1984.   */
  1985. }
  1986. //Routine to check light level and turn on/off matrix
  1987. void light() {
  1988.   //Get light reading
  1989.   uint16_t lx = lux.GetLightIntensity();
  1990.   //checks if display can be turned off if option to keep it on until a certain time is met
  1991.   bool dont_turn_off = 0;
  1992.   if (display_mode > 1) {
  1993.     byte hr = rtc[2];
  1994.     switch(display_mode) {
  1995.       case 2:
  1996.       if (hr >= hour_on && hr < hour_off_1) {
  1997.         dont_turn_off = 1;
  1998.       }
  1999.       else {
  2000.         dont_turn_off = 0;
  2001.       }
  2002.       break;
  2003.       case 3:
  2004.       if (hr >= hour_on && hr < hour_off_2) {
  2005.         dont_turn_off = 1;
  2006.       }
  2007.       else {
  2008.         dont_turn_off = 0;
  2009.       }
  2010.       break;
  2011.       case 4:
  2012.       if (hr >= hour_on && hr < hour_off_3) {
  2013.         dont_turn_off = 1;
  2014.       }
  2015.       else {
  2016.         dont_turn_off = 0;
  2017.       }
  2018.       break;
  2019.     }
  2020.   }
  2021.   if (lx == 0 && !shut && !dont_turn_off && display_mode != 1) {
  2022.     shut = 1;
  2023.     set_devices(false, 0); //Call sleep routine to turn off matrix, applies when light is low enough
  2024.   }
  2025.   if ((lx > 0 && shut) || (lx == 0 && shut && dont_turn_off)) {
  2026.     shut = 0;
  2027.     set_devices(false, 0); //Call sleep routine to turn on matrix, applies when light is high enough
  2028.   }
  2029.   //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.
  2030.   if (auto_intensity && !shut) {
  2031.     switch(lx) {
  2032.       case 0:
  2033.       auto_intensity_value = 0;
  2034.       break;
  2035.       case 2:
  2036.       auto_intensity_value = 1;
  2037.       break;
  2038.       case 3 ... 4:
  2039.       auto_intensity_value = 2;
  2040.       break;
  2041.       case 5 ... 6:
  2042.       auto_intensity_value = 3;
  2043.       break;
  2044.       case 7 ... 10:
  2045.       auto_intensity_value = 4;
  2046.       break;
  2047.       case 11 ... 20:
  2048.       auto_intensity_value = 5;
  2049.       break;
  2050.       case 21 ... 40:
  2051.       auto_intensity_value = 6;
  2052.       break;
  2053.       case 41 ... 60:
  2054.       auto_intensity_value = 7;
  2055.       break;
  2056.       case 61 ... 100:
  2057.       auto_intensity_value = 8;
  2058.       break;
  2059.       case 101 ... 150:
  2060.       auto_intensity_value = 9;
  2061.       break;
  2062.       case 151 ... 200:
  2063.       auto_intensity_value = 10;
  2064.       break;
  2065.       case 201 ... 250:
  2066.       auto_intensity_value = 11;
  2067.       break;
  2068.       case 251 ... 300:
  2069.       auto_intensity_value = 12;
  2070.       break;
  2071.       case 301 ... 350:
  2072.       auto_intensity_value = 13;
  2073.       break;
  2074.       case 351 ... 400:
  2075.       auto_intensity_value = 14;
  2076.       break;
  2077.       case 401 ... 65535:
  2078.       auto_intensity_value = 15;
  2079.       break;
  2080.     }
  2081.     set_devices(true, auto_intensity_value);
  2082.     //this is useful for help setting the values above
  2083.     //Serial.println(lx);
  2084.   }
  2085. }
  2086. //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
  2087. void set_devices(bool m, byte i) {
  2088.   int devices = lc.getDeviceCount();
  2089.   for (int address = 0; address < devices; address++) {
  2090.     if (!m) {
  2091.       //turns on/off matrix
  2092.       lc.shutdown(address, shut);
  2093.     }
  2094.     else {
  2095.       //sets matrix intensity
  2096.       lc.setIntensity(address, i);
  2097.     }
  2098.   }
  2099. }
  2100. //Routine to set display on/off options (0 = normal, 1 = always on, 2 - 4 = from specific time)
  2101. void set_display_options() {
  2102.   cls();
  2103.   const char options[5][9] = {">NORMAL", ">ON", "> 9.00PM", ">10.00PM", ">11.00PM"};
  2104.   byte i = 0;
  2105.   while(options[display_mode][i])
  2106.   {
  2107.     puttinychar(i * 4, 1, options[display_mode][i]);
  2108.     i++;
  2109.   }
  2110.   //wait for button input
  2111.   while (!buttonA.uniquePress()) {
  2112.     while (buttonB.isPressed()) {
  2113.       display_mode++;
  2114.       if (display_mode > 4) {
  2115.         display_mode = 0;
  2116.       }
  2117.       //print the new value
  2118.       cls();
  2119.       byte i = 0;
  2120.       while(options[display_mode][i])
  2121.       {
  2122.         puttinychar(i * 4, 1, options[display_mode][i]);
  2123.         i++;
  2124.       }
  2125.       delay(150);
  2126.     }
  2127.     while (buttonC.isPressed()) {
  2128.       //display current lux value
  2129.       cls();
  2130.       byte i = 0;
  2131.       const char msg[] = "LX:";
  2132.       i = 0;
  2133.       while(msg[i]) {
  2134.         puttinychar(i * 4, 1, msg[i]);
  2135.         i++;
  2136.       }
  2137.       char buffer[6];
  2138.       dtostrf(lux.GetLightIntensity(), 5, 0, buffer);
  2139.       i = 0;
  2140.       while(buffer[i]) {
  2141.         puttinychar(i * 4 + 12, 1, buffer[i]);
  2142.         i++;
  2143.       }
  2144.       delay(150);
  2145.     }
  2146.   }
  2147.   //save the values to EEPROM
  2148.   eeprom_save(202, display_mode, 0, 0);
  2149.   //Serial.println("display_mode");
  2150. }
  2151. //calculates DST, takes NTP output (bool ntp - if applicable) into consideration
  2152. void dst() {
  2153.   get_time();
  2154.   byte day = rtc[4];
  2155.   byte month = rtc[5];
  2156.   byte hour = rtc[2];
  2157.   byte dow = rtc[3];
  2158.   //temporarily store DST changes
  2159.   bool dst_plus = 0;
  2160.   bool dst_minus = 0;
  2161.   //winter calculation
  2162.   if ((month < 3 || month > 10) || (month == 3 && day < 25 && DST == 1) || (month == 10 && day >= 25 && hour == 2 && dow == 0 && DST == 1)) {
  2163.     DST = 0; //winter is coming
  2164.     dst_minus = 1;
  2165.     //save to EEPROM
  2166.     eeprom_save(212, 0, DST, 0);
  2167.     //Serial.println("winter");
  2168.   }
  2169.   //summer calculation
  2170.   else if ((month > 3 && month < 10) || (month == 10 && day < 25 && DST == 0) || (month == 3 && day >= 25 && hour == 1 && dow == 0 && DST == 0)) {
  2171.     DST = 1; //hosepipe ban is coming
  2172.     dst_plus = 1;
  2173.     //save to EEPROM
  2174.     eeprom_save(212, 0, DST, 0);
  2175.     //Serial.println("summer");
  2176.   }
  2177.   //+1 hour if summertime, or one time calculation is active
  2178.   if (DST || dst_plus) {
  2179.     DateTime now = ds3231.now();
  2180.     ds3231.adjust(DateTime(now.unixtime() + (3600)));
  2181.     //Serial.println("summer+1");
  2182.   }
  2183.   //-1 hour if wintertime, based on one time calculation
  2184.   if (dst_minus) {
  2185.     DateTime now = ds3231.now();
  2186.     ds3231.adjust(DateTime(now.unixtime() - (3600)));
  2187.     //Serial.println("winter-1");
  2188.   }
  2189. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 7 小时前

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

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟
【Arduino 动手做】带DS3231、BME280、BH1750和ESP01的LED矩阵NTP时钟
项目链接:https://www.hackster.io/Ratti3/l ... 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/

【Arduino 动手做】带BME280、BH1750和ESP01的LED矩阵NTP时钟图1

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail