Dot Matrix LED Display Digital Clock - Part 2 Software
Published
In the first part of this project/tutorial, we discussed the hardware of the dot matrix LED display digital clock. We discussed the function of each component and how they are all connected. Now, in this part, we are going to discuss the software or the code of the digital clock to see how its operation was programmed.
/******************************************************************************
USBasp Connection to ATmega328P-PU:
* USBasp GND to ground
* USBasp MOSI to PB3 or MOSI or ATmega328P-PU pin 17
* USBasp +5V to +5V supply
* USBasp SS to PC6 or /RESET or ATmega328P-PU pin 1
* USBasp SCK to PB5 or SCK or ATmega328P-PU pin 19
* USBasp MISO to PB4 or MISO or ATmega328P-PU pin 18
1MHz Output[*] // e-Gizmo USBasp Pinout
GND[*][*]SS
MOSI[*][*]SCK
+5V[*][*]MISO
******************************************************************************/
/******************************************************************************
CH340 USB to Serial Connection to ATmega328P-PU:
* CH340 GND to ground
* CH340 TXD to RXD or PD0 or ATmega328P-PU pin 2
* CH340 RXD to TXD or PD1 or ATmega328P-PU pin 3
* CH340 +5V to +5V supply
******************************************************************************/
/*******************************************************************************************************
DS3231 I2C Real-Time Clock Library
----------------------------------
DS3231.cpp - Arduino/chipKit library support for the DS3231 I2C Real-Time Clock
Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
This library has been made to easily interface and use the DS3231 RTC with
an Arduino or chipKit.
You can find the latest version of the library at
http://www.RinkyDinkElectronics.com
This library is free software; you can redistribute it and/or
modify it under the terms of the CC BY-NC-SA 3.0 license.
Please see the included documents for further information.
Commercial use of this library requires you to buy a license that
will allow commercial use. This includes using the library,
modified or not, as a tool to sell products.
The license applies to all part of the library including the
examples and tools supplied with the library.
*******************************************************************************************************/
/*******************************************************************************************************
MAX7219/7221 LED Matrix Controller Library
------------------------------------------
MD_MAX72xx - Library for using a MAX7219/7221 LED matrix controller
See header file for comments
This file contains class and hardware related methods.
Copyright (C) 2012-14 Marco Colli. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*******************************************************************************************************/
/*******************************************************************************************************
MD_Parola - Library for modular scrolling text and Effects
----------------------------------------------------------
See header file for comments
Copyright (C) 2013 Marco Colli. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*******************************************************************************************************/
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <DS3231.h>
#include "JB_Font_Data.h" // Place the header file in this directory Arduino\libraries\MD_MAX72XX\src
// Define the number of devices we have in the chain and the hardware interface
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 8
#define CLK_PIN 13
#define DATA_PIN 11
#define CS_PIN 10
// HARDWARE SPI
MD_Parola digitalClock = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Init the DS3231 using the hardware interface
DS3231 rtc(SDA, SCL);
// Init a Time-data structure
Time t;
int setButton = 2;
int downButton = 3;
int upButton = 5;
int setSelectButton = 6;
int resetPin = 4;
int DOW = 7;
int dd = 1;
int d = 0;
int mm = 1;
int m = 0;
int yyyy = 2021;
int yy = 0;
int hr = 0;
int h = 0;
int mins = 0;
int mi = 0;
int secs = 0;
int sc = 0;
int timeHr_1stDigit;
int timeHr_2ndDigit;
int timeMin_1stDigit;
int timeMin_2ndDigit;
int timeSec_1stDigit;
int timeSec_2ndDigit;
String meridiem;
void setup()
{
//Serial.begin(9600);
pinMode(resetPin, OUTPUT);
digitalWrite(resetPin, LOW);
digitalClock.begin(2);
digitalClock.setZone(0, 0, 3);
digitalClock.setZone(1, 4, 7);
digitalClock.setFont(0, JBSmallFont);
digitalClock.setFont(1, JBSmallFont);
// Set the intensity (brightness) of the display (0-15)
digitalClock.setIntensity(1);
// Initialize the rtc object
rtc.begin();
pinMode(setButton, INPUT_PULLUP);
pinMode(downButton, INPUT_PULLUP);
pinMode(upButton, INPUT_PULLUP);
pinMode(setSelectButton, INPUT_PULLUP);
}
void loop()
{
// Get data from the DS3231
t = rtc.getTime(); // Read current time and date.
Convert_to_12Hour_Clock_Format();
String timeDisplay = String (timeHr_1stDigit) + String (timeHr_2ndDigit) + ":" + String (timeMin_1stDigit) + String (timeMin_2ndDigit) + ":" + String (timeSec_1stDigit) + String (timeSec_2ndDigit) + String (meridiem);
char char_timeDisplay[timeDisplay.length()+1];
timeDisplay.toCharArray(char_timeDisplay,timeDisplay.length()+1);
String dateDisplay = String (rtc.getDOWStr()) + " " + "-" + " " + String (rtc.getMonthStr()) + " " + String (t.date, DEC) + ", " + String (t.year, DEC);
char char_dateDisplay[dateDisplay.length()+1];
dateDisplay.toCharArray(char_dateDisplay,dateDisplay.length()+1);
digitalClock.displayZoneText(0, char_timeDisplay, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayZoneText(1, char_dateDisplay, PA_LEFT, 120, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
for (uint8_t i=0; i<2; i++)
{
if (digitalClock.getZoneStatus(i))
{
// do something with the parameters for the animation then reset it
digitalClock.displayReset(i);
}
}
}
int setButtonState = digitalRead(2);
if (setButtonState == LOW)
{
set_select();
}
resetClock();
}
void Convert_to_12Hour_Clock_Format()
{
String timeHr = String (t.hour, DEC);
int int_timeHr = timeHr.toInt();
//Serial.println(timeHr);
String timeMin = String (t.min, DEC);
int int_timeMin = timeMin.toInt();
String timeSec = String (t.sec, DEC);
int int_timeSec = timeSec.toInt();
if (int_timeHr == 0)
{
timeHr_1stDigit = 1;
timeHr_2ndDigit = 2;
meridiem = "<";
}
else if ((int_timeHr > 0) && (int_timeHr < 10))
{
timeHr_1stDigit = 0;
timeHr_2ndDigit = int_timeHr%10;
meridiem = "<";
}
else if ((int_timeHr > 9) && (int_timeHr < 12))
{
timeHr_1stDigit = int_timeHr/10;
timeHr_2ndDigit = int_timeHr%10;
meridiem = "<";
}
else if (int_timeHr == 12)
{
timeHr_1stDigit = int_timeHr/10;
timeHr_2ndDigit = int_timeHr%10;
meridiem = ">";
}
else if ((int_timeHr > 12) && (int_timeHr < 22))
{
timeHr_1stDigit = 0;
timeHr_2ndDigit = int_timeHr - 12;
meridiem = ">";
}
else
{
timeHr_1stDigit = int_timeHr/20;
timeHr_2ndDigit = (int_timeHr - 12) - 10;
meridiem = ">";
}
if (int_timeMin < 10)
{
timeMin_1stDigit = 0;
timeMin_2ndDigit = int_timeMin%10;
}
else
{
timeMin_1stDigit = int_timeMin/10;
timeMin_2ndDigit = int_timeMin%10;
}
if (int_timeSec < 10)
{
timeSec_1stDigit = 0;
timeSec_2ndDigit = int_timeSec%10;
}
else
{
timeSec_1stDigit = int_timeSec/10;
timeSec_2ndDigit = int_timeSec%10;
}
}
void set_select()
{
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int set_sel = 0;
int setButtonState = digitalRead(2);
while (setButtonState == HIGH)
{
setButtonState = digitalRead(2);
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
set_sel--;
delay(150);
}
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
set_sel++;
delay(150);
}
if (set_sel > 2)
{
set_sel = 0;
}
else if (set_sel < 0)
{
set_sel = 2;
}
String set_select_String = " ";
switch (set_sel)
{
case 0: // Set Day of the Week
set_select_String = "DOW";
set_DOW();
break;
case 1: // Set Date
set_select_String = "Date";
set_Date();
break;
case 2: // Set Time
set_select_String = "Time";
set_Time();
break;
}
String set_sel_Str = String (set_select_String);
char char_set_sel_Str[set_sel_Str.length()+1];
set_sel_Str.toCharArray(char_set_sel_Str,set_sel_Str.length()+1);
digitalClock.displayZoneText(1, char_set_sel_Str, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
void set_DOW()
{
int setSelectButtonState = digitalRead(6);
if (setSelectButtonState == LOW)
{
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
DOW--;
delay(150);
}
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
DOW++;
delay(150);
}
if (DOW > 7)
{
DOW = 1;
}
else if (DOW < 1)
{
DOW = 7;
}
rtc.setDOW(DOW);
digitalClock.displayZoneText(1, rtc.getDOWStr(FORMAT_SHORT), PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
/**************************************************************************************************/
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
}
void set_Date()
{
int setSelectButtonState = digitalRead(6);
if (setSelectButtonState == LOW)
{
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int days[31] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
d++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
d--;
delay(150);
}
if (d > 30)
{
d = 0;
}
else if (d < 0)
{
d = 30;
}
dd = days[d];
String dateStr = "D: " + String (dd);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int months[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
m++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
m--;
delay(150);
}
if (m > 11)
{
m = 0;
}
else if (m < 0)
{
m = 11;
}
mm = months[m];
String dateStr = "M: " + String (mm);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int years[10] = {2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
yy++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
yy--;
delay(150);
}
if (yy > 9)
{
yy = 0;
}
else if (yy < 0)
{
yy = 9;
}
yyyy = years[yy];
String dateStr = "Y: " + String (yyyy);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
rtc.setDate(dd, mm, yyyy);
/**************************************************************************************************/
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
}
void set_Time()
{
int setSelectButtonState = digitalRead(6);
if (setSelectButtonState == LOW)
{
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int hours[24] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
h++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
h--;
delay(150);
}
if (h > 23)
{
h = 0;
}
else if (h < 0)
{
h = 23;
}
hr = hours[h];
String timeStr = "HR: " + String (hr);
char char_timeStr[timeStr.length()+1];
timeStr.toCharArray(char_timeStr,timeStr.length()+1);
digitalClock.displayZoneText(1, char_timeStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int minutes[60] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
mi++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
mi--;
delay(150);
}
if (mi > 59)
{
mi = 0;
}
else if (mi < 0)
{
mi = 59;
}
mins = minutes[mi];
String timeStr = "MIN: " + String (mins);
char char_timeStr[timeStr.length()+1];
timeStr.toCharArray(char_timeStr,timeStr.length()+1);
digitalClock.displayZoneText(1, char_timeStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int seconds[60] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
sc++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
sc--;
delay(150);
}
if (sc > 59)
{
sc = 0;
}
else if (sc < 0)
{
sc = 59;
}
secs = seconds[sc];
String timeStr = "SEC: " + String (secs);
char char_timeStr[timeStr.length()+1];
timeStr.toCharArray(char_timeStr,timeStr.length()+1);
digitalClock.displayZoneText(1, char_timeStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
rtc.setTime(hr, mins, secs);
/**************************************************************************************************/
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
}
}
void resetClock()
{
String timeMin = String (t.min, DEC);
int int_timeMin = timeMin.toInt();
String timeSec = String (t.sec, DEC);
int int_timeSec = timeSec.toInt();
if ((int_timeMin == 59) && (int_timeSec == 59)) // Reset every hour.
{
digitalWrite(resetPin, HIGH);
}
else {}
}
So the code above is the code for the dot matrix LED display digital clock. It has 813 lines but as you can see in the flowchart below, the operation of the digital clock is still very easy. I guess the only complicated part here is when we set the clock’s time and date. But even that, I think, is easy for you to understand. So let’s discuss the operation of the digital clock.
The program starts by initializing the libraries and hardware. Then after that, the program reads the current time and date, converts it to 12hr clock format
, and then displays it on the dot matrix LED display. After displaying the time and date, the program will then check if the SET
button (used for setting time and date) is pressed or not. If it’s pressed, then the program will jump to another function where we set the time and date. But if it is not, then the program will proceed to reading the time again and checking if the value of minute
and second
is equal to 59
. If their value is not equal to 59
, the program will repeat the process starting at Read current Time and Date
. But if their value is equal to 59
, the MCU will be reset and the program will go back to START
again.
So that is how the digital clock normally operates when it is just displaying the time and date. Later we will discuss how the digital clock’s time and date are set but for now let’s check the code first of the operation that we have just discussed.
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <DS3231.h>
#include "JB_Font_Data.h" // Place the header file in this directory Arduino\libraries\MD_MAX72XX\src
The code starts in line 109 (up to 113) where we include the MD_Parola
, MD_MAX72xx
, Arduino SPI
, Rinky-Dink Electronics DS3231
libraries and the header JB_Font_Data
which contains the font that we are going to use in the display. The MD_Parola
library contains functions that simplify the implementation of text special effects on the display. It works with the MD_MAX72xx
library which allows the MAX7219 to be used for LED matrices (64 individual LEDs). Since the MAX7219 connects to the MCU through the SPI port, we included Arduino’s SPI library.
The DS3231 library here is created by Rinky-Dink Electronics. It allows us to easily interface the DS3231 RTC to the MCU (or the minimal Arduino Uno). The library doesn’t utilize the Arduino Wire library, so there might be problems if you’re using the Wire library and share the MCU I2C pins with other I2C devices. But the good thing about this library is that you can use other available pins of the MCU. The library will just fall back to a software-based TWI-/I2C-like protocol.
// Define the number of devices we have in the chain and the hardware interface
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 8
#define CLK_PIN 13
#define DATA_PIN 11
#define CS_PIN 10
// HARDWARE SPI
MD_Parola digitalClock = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
In line 115 to 120, we define the hardware type of the MAX7219 dot LED matrix display that we are using, the maximum number of the devices, and the MCU pins connected to the MAX7219. For the hardware type, you may refer to this page but in our case, we are using the FC16_HW
type. Since we are using two 4-in-1 MAX7219 dot LED matrix display modules here, we set the MAX_DEVICES
to 8
. For the MCU pins we connected D13
to CLK
, D11
to DIN
, and D10
to the CS
pin of the MAX7219. Then in line 123, we instantiate the digitalClock
object of the MD_Parola
class.
// Init the DS3231 using the hardware interface
DS3231 rtc(SDA, SCL);
// Init a Time-data structure
Time t;
In line 125 to 129, we set the MCU I2C pins connected to the DS3231 and defined a structure named t
of the class Time.
int setButton = 2;
int downButton = 3;
int upButton = 5;
int setSelectButton = 6;
int resetPin = 4;
In line 131 to 135, we assigned variable names for digital pins 2, 3, 5, 6, and 4.
int DOW = 7;
int dd = 1;
int d = 0;
int mm = 1;
int m = 0;
int yyyy = 2021;
int yy = 0;
int hr = 0;
int h = 0;
int mins = 0;
int mi = 0;
int secs = 0;
int sc = 0;
In line 137 to 149, we initialized the variables that we are going to use later in setting the time and date of the digital clock.
int timeHr_1stDigit;
int timeHr_2ndDigit;
int timeMin_1stDigit;
int timeMin_2ndDigit;
int timeSec_1stDigit;
int timeSec_2ndDigit;
String meridiem;
In line 151 to 157, we defined the type of the variables that we will use later in converting the time and date from String to Character Array.
void setup()
{
//Serial.begin(9600);
pinMode(resetPin, OUTPUT);
digitalWrite(resetPin, LOW);
digitalClock.begin(2);
digitalClock.setZone(0, 0, 3);
digitalClock.setZone(1, 4, 7);
digitalClock.setFont(0, JBSmallFont);
digitalClock.setFont(1, JBSmallFont);
// Set the intensity (brightness) of the display (0-15)
digitalClock.setIntensity(1);
// Initialize the rtc object
rtc.begin();
pinMode(setButton, INPUT_PULLUP);
pinMode(downButton, INPUT_PULLUP);
pinMode(upButton, INPUT_PULLUP);
pinMode(setSelectButton, INPUT_PULLUP);
}
Now, in the setup()
function, in line 163, we set the resetPin
, which is D4
, as an output then we set it to LOW
in line 164. If you check the schematic diagram again, resetPin
or D4
is connected to the base of the 2N3904 transistor through the resistor R2. The 2N3904 transistor here acts as a switch which we will use to reset the MCU. The reset pin of the MCU, PC6
, is connected to the collector of the 2N3904 and the emitter of the 2N3904 is connected to ground. (Check How to use a Bipolar Junction Transistor (BJT) as a Switch? tutorial) If we set resetPin
or D4
to LOW
, the 2N3904 will act as an open switch, so the MCU will not be reset. But if we set resetPin
or D4
to HIGH
, the 2N3904 will act as a closed switch shorting the reset pin of the MCU (PC6) to the ground which will cause the MCU to be reset. Since we only want to reset the MCU when the minute and second value is equal to 59, here we set resetPin
to LOW
.
digitalClock.begin(2);
digitalClock.setZone(0, 0, 3);
digitalClock.setZone(1, 4, 7);
digitalClock.setFont(0, JBSmallFont);
digitalClock.setFont(1, JBSmallFont);
// Set the intensity (brightness) of the display (0-15)
digitalClock.setIntensity(1);
In line 166, we call the begin(uint8_t numZones)
function of the MD_Parola
to specify the number of zones used on the display. The parameter is the number of zones which in our code is set to 2
since we use 2 zones. The first zone displays the time while the 2nd zone displays the date. In line 167 to 168, we set the zones limit using the setZone(uint8_t z, uint8_t moduleStart, uint8_t moduleEnd)
function of the MD_Parola
. The first parameter is the zone number, the second parameter sets the first module number for the zone, and the third parameter sets the last module number for the zone. Here, module number 0 to 3
are set for Zone 0 and module number 4 to 7
are set for Zone 1.
In line 170 to 171, we use the setFont(uint8_t z, MD_MAX72XX::fontType_t * fontDef)
to set the font of the display. The first parameter is the zone number and the second parameter is the font name which is the name of the array inside the font header JB_Font_Data.h
that we included. If you open the header file, you will see the name of the array there which we put in the second parameter of the setFont()
function. In line 174, to set the brightness of the dot matrix LED display, we use the setIntensity(uint8_t intensity)
function of the MD_Parola
. The parameter intensity ranges from 0 to 15. I just set it to 1
since intensity 1 is already bright and I want the LEDs to last longer too by operating it at low brightness.
// Initialize the rtc object
rtc.begin();
pinMode(setButton, INPUT_PULLUP);
pinMode(downButton, INPUT_PULLUP);
pinMode(upButton, INPUT_PULLUP);
pinMode(setSelectButton, INPUT_PULLUP);
}
In line 176 to 182, we initialize the rtc
object and set setButton (D2)
, downButton (D3)
, upButton (D5)
, and selectButton (D6)
as inputs with internal pull-up resistors enabled.
So that’s the code for the libraries and hardware initialization. Let’s proceed to the loop()
function.
void loop()
{
// Get data from the DS3231
t = rtc.getTime(); // Read current time and date.
Convert_to_12Hour_Clock_Format();
String timeDisplay = String (timeHr_1stDigit) + String (timeHr_2ndDigit) + ":" + String (timeMin_1stDigit) + String (timeMin_2ndDigit) + ":" + String (timeSec_1stDigit) + String (timeSec_2ndDigit) + String (meridiem);
char char_timeDisplay[timeDisplay.length()+1];
timeDisplay.toCharArray(char_timeDisplay,timeDisplay.length()+1);
String dateDisplay = String (rtc.getDOWStr()) + " " + "-" + " " + String (rtc.getMonthStr()) + " " + String (t.date, DEC) + ", " + String (t.year, DEC);
char char_dateDisplay[dateDisplay.length()+1];
dateDisplay.toCharArray(char_dateDisplay,dateDisplay.length()+1);
digitalClock.displayZoneText(0, char_timeDisplay, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayZoneText(1, char_dateDisplay, PA_LEFT, 120, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
for (uint8_t i=0; i<2; i++)
{
if (digitalClock.getZoneStatus(i))
{
// do something with the parameters for the animation then reset it
digitalClock.displayReset(i);
}
}
}
int setButtonState = digitalRead(2);
if (setButtonState == LOW)
{
set_select();
}
resetClock();
}
So the loop()
function is in line 185 to 223. After initializing the libraries and hardware, the program will just keep on repeating the code inside the loop function. In line 188, we read the time and date using the getTime()
function of the DS3231 library. I decided to display a 12hr clock format but since the library doesn’t have a function that converts 24hr clock format to 12hr format, I created a function for this Convert_to_12Hour_Clock_Format()
which we are going to call in line 190. You can see the Convert_to_12Hour_Clock_Format()
function in line 225 to 297.
void Convert_to_12Hour_Clock_Format()
{
String timeHr = String (t.hour, DEC);
int int_timeHr = timeHr.toInt();
//Serial.println(timeHr);
String timeMin = String (t.min, DEC);
int int_timeMin = timeMin.toInt();
String timeSec = String (t.sec, DEC);
int int_timeSec = timeSec.toInt();
if (int_timeHr == 0)
{
timeHr_1stDigit = 1;
timeHr_2ndDigit = 2;
meridiem = "<";
}
else if ((int_timeHr > 0) && (int_timeHr < 10))
{
timeHr_1stDigit = 0;
timeHr_2ndDigit = int_timeHr%10;
meridiem = "<";
}
else if ((int_timeHr > 9) && (int_timeHr < 12))
{
timeHr_1stDigit = int_timeHr/10;
timeHr_2ndDigit = int_timeHr%10;
meridiem = "<";
}
else if (int_timeHr == 12)
{
timeHr_1stDigit = int_timeHr/10;
timeHr_2ndDigit = int_timeHr%10;
meridiem = ">";
}
else if ((int_timeHr > 12) && (int_timeHr < 22))
{
timeHr_1stDigit = 0;
timeHr_2ndDigit = int_timeHr - 12;
meridiem = ">";
}
else
{
timeHr_1stDigit = int_timeHr/20;
timeHr_2ndDigit = (int_timeHr - 12) - 10;
meridiem = ">";
}
if (int_timeMin < 10)
{
timeMin_1stDigit = 0;
timeMin_2ndDigit = int_timeMin%10;
}
else
{
timeMin_1stDigit = int_timeMin/10;
timeMin_2ndDigit = int_timeMin%10;
}
if (int_timeSec < 10)
{
timeSec_1stDigit = 0;
timeSec_2ndDigit = int_timeSec%10;
}
else
{
timeSec_1stDigit = int_timeSec/10;
timeSec_2ndDigit = int_timeSec%10;
}
}
To convert the time in 24hr clock format
into a 12hr format
, we just need to look at the value of the hour and compare it to some integers so that we can set the 1st and 2nd digits of the hour on the dot matrix LED display. But since we can’t directly compare the output of the getTime()
function to an integer, we need to convert them (hour, minute, and second) first to integers.
Before discussing the code inside the Convert_to_12Hour_Clock_Format()
function, these are the variables that we will use later in displaying the time and date: timeHr_1stDigit
, timeHr_2ndDigit
, timeMin_1stDigit
, timeMin_2ndDigit
, timeSec_1stDigit
, timeSec_2ndDigit
, and meridiem
. These variables will be modified inside the Convert_to_12Hour_Clock_Format()
function based on the value of hour, minute and second.
String timeHr = String (t.hour, DEC);
int int_timeHr = timeHr.toInt();
//Serial.println(timeHr);
String timeMin = String (t.min, DEC);
int int_timeMin = timeMin.toInt();
String timeSec = String (t.sec, DEC);
int int_timeSec = timeSec.toInt();
In lines 227, 231, and 234, we format the output of (t.hour, DEC)
, (t.min, DEC)
, and (t.sec, DEC)
as strings then convert them to integers in line 228, 232, and 235 using the toInt()
function, respectively. After converting the value of the hour, minute, and second into integer data type, we store them in the variables int_timeHr
, int_timeMin
, and int_timeSec
, respectively.
To convert the time into 12hr format, we will compare the value of int_timeHr
to some integers and set the 1st (timeHr_1stDigit)
and 2nd (timeHr_2ndDigit)
digits of the hour using the if statements in line 237 to 272.
if (int_timeHr == 0)
{
timeHr_1stDigit = 1;
timeHr_2ndDigit = 2;
meridiem = "<";
}
else if ((int_timeHr > 0) && (int_timeHr < 10))
{
timeHr_1stDigit = 0;
timeHr_2ndDigit = int_timeHr%10;
meridiem = "<";
}
else if ((int_timeHr > 9) && (int_timeHr < 12))
{
timeHr_1stDigit = int_timeHr/10;
timeHr_2ndDigit = int_timeHr%10;
meridiem = "<";
}
else if (int_timeHr == 12)
{
timeHr_1stDigit = int_timeHr/10;
timeHr_2ndDigit = int_timeHr%10;
meridiem = ">";
}
else if ((int_timeHr > 12) && (int_timeHr < 22))
{
timeHr_1stDigit = 0;
timeHr_2ndDigit = int_timeHr - 12;
meridiem = ">";
}
else
{
timeHr_1stDigit = int_timeHr/20;
timeHr_2ndDigit = (int_timeHr - 12) - 10;
meridiem = ">";
}
For example, in line 237, if int_timeHr
is equal to 0
(12AM), we set the 1st digit to 1
(line 239) and the 2nd digit to 2
(line 240). Then in line 241, we store <
to the meridiem
variable. The meridiem
variable here is used to show if the time is AM or PM. Notice that we store either of the <
and >
characters in the meridiem
variable. I modified these two characters inside the JB_Font_Data.h
file. The <
character will display A
for AM and the >
character will display P
for PM.
The following else-if statements are just similar to the first if statement. They will just check if int_timeHr
is between 0 and 10 (1AM to 9AM), between 9 and 12 (10AM to 11AM), equal to 12 (12PM), between 12 and 22 (1PM to 9PM), or greater than 21 (10PM to 11PM) and will just set the timeHr_1stDigit
, timeHr_2ndDigit
, and meridiem
variables based on the equations inside their body. Those equations are just easy to figure out so I think I don’t need to explain them anymore.
if (int_timeMin < 10)
{
timeMin_1stDigit = 0;
timeMin_2ndDigit = int_timeMin%10;
}
else
{
timeMin_1stDigit = int_timeMin/10;
timeMin_2ndDigit = int_timeMin%10;
}
if (int_timeSec < 10)
{
timeSec_1stDigit = 0;
timeSec_2ndDigit = int_timeSec%10;
}
else
{
timeSec_1stDigit = int_timeSec/10;
timeSec_2ndDigit = int_timeSec%10;
}
}
The code in line 274 to 296 sets the 1st and 2nd digits of the minute and second. It will just check if int_timeMin
and int_timeSec
is less than 10 or greater than 9 then will set the timeMin_1stDigit
, timeMin_2ndDigit
, timeSec_1stDigit
, and timeSec_2ndDigit
variables.
So that’s the end of the Convert_to_12Hour_Clock_Format()
function. The 12hr clock format, the hour, minute, and second are now stored in the timeHr_1stDigit
, timeHr_2ndDigit
, timeMin_1stDigit
, timeMin_2ndDigit
, timeSec_1stDigit
, timeSec_2ndDigit
, and meridiem
variables. The program now will return to line 192.
String timeDisplay = String (timeHr_1stDigit) + String (timeHr_2ndDigit) + ":" + String (timeMin_1stDigit) + String (timeMin_2ndDigit) + ":" + String (timeSec_1stDigit) + String (timeSec_2ndDigit) + String (meridiem);
char char_timeDisplay[timeDisplay.length()+1];
timeDisplay.toCharArray(char_timeDisplay,timeDisplay.length()+1);
String dateDisplay = String (rtc.getDOWStr()) + " " + "-" + " " + String (rtc.getMonthStr()) + " " + String (t.date, DEC) + ", " + String (t.year, DEC);
char char_dateDisplay[dateDisplay.length()+1];
dateDisplay.toCharArray(char_dateDisplay,dateDisplay.length()+1);
digitalClock.displayZoneText(0, char_timeDisplay, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayZoneText(1, char_dateDisplay, PA_LEFT, 120, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
for (uint8_t i=0; i<2; i++)
{
if (digitalClock.getZoneStatus(i))
{
// do something with the parameters for the animation then reset it
digitalClock.displayReset(i);
}
}
}
In order to display the time and date on the dot matrix LED display, we need to store or copy them first in a character array buffer. One way to do this is to first format the variables we mentioned recently as strings, concatenate them and store them in a string variable, and then copy them to a character array buffer.
The code in line 192 formats the variables timeHr_1stDigit
, timeHr_2ndDigit
, timeMin_1stDigit
, timeMin_2ndDigit
, timeSec_1stDigit
, timeSec_2ndDigit
, and meridiem
as strings, concatenates them, and stores them in the timeDisplay
string variable. In line 193, we create a character array buffer char_timeDisplay
with a length equal to the length of the string timeDisplay
plus 1 since the length()
function doesn’t include a trailing null character. In line 194, we copy the String timeDisplay
characters to the character array buffer char_timeDisplay
using the toCharArray()
function.
For the date, we display it on the 2nd 4-in-1 MAX7219 dot matrix LED display module in this format: DOW - MONTH DATE, YEAR (ex. SATURDAY - DECEMBER 25, 2021)
. In line 196, in order to retrieve the current DOW, month, date, and year, we used the functions getDOWStr()
, getMonthStr()
, and getTime(t.date and t.year)
, respectively. Then we format their output as strings and store them in the dateDisplay
string variable. In line 197, we create a character array buffer char_dateDisplay
with a length equal to the length of the string dateDisplay
plus 1 since, again, the length()
function doesn’t include a trailing null character. Then in line 198, we copy the characters inside the dateDisplay
variable to the character array buffer char_dateDisplay
using the toCharArray()
function.
digitalClock.displayZoneText(0, char_timeDisplay, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayZoneText(1, char_dateDisplay, PA_LEFT, 120, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
for (uint8_t i=0; i<2; i++)
{
if (digitalClock.getZoneStatus(i))
{
// do something with the parameters for the animation then reset it
digitalClock.displayReset(i);
}
}
}
The code in line 200 displays the characters inside the buffer char_timeDisplay
or the time in Zone 0 (1st 4-in-1 MAX7219 dot matrix LED display module) using the displayZoneText()
function of the MD_Parola
. The code doesn’t use any scrolling effect so there’s no changes in the position of the digits. Just the numbers changing as time changes. The time is displayed in this format HH:MM:SSA/P (ex. 12:00:00A or 12:00:00P)
.
The code in lines 202 to 214 displays the date with a PA_SCROLL_LEFT
effect or animation. As you can see, the code uses displayAnimate()
, displayZoneText()
, getZoneStatus()
, displayReset()
functions and a for loop which, honestly, I’m still confused on how they work because sometimes I encounter problems when I use them. But so far the code in lines 202 to 214 works properly in displaying the date.
int setButtonState = digitalRead(2);
if (setButtonState == LOW)
{
set_select();
}
resetClock();
}
Now, in line 216, this is the part where we check the SET
button to see if it is pressed or not. If the SET
button is not pressed, the program will proceed to line 222 where we call the resetClock()
function. However, if the SET
button is pressed, the program jumps to the set_select()
function where we set the time and date using the tactile switches. But let’s discuss that later after we discuss the resetClock()
function.
void resetClock()
{
String timeMin = String (t.min, DEC);
int int_timeMin = timeMin.toInt();
String timeSec = String (t.sec, DEC);
int int_timeSec = timeSec.toInt();
if ((int_timeMin == 59) && (int_timeSec == 59)) // Reset every hour.
{
digitalWrite(resetPin, HIGH);
}
else {}
}
As mentioned earlier, we have the D4
pin (resetPin)
connected to the base of the 2N3904 transistor through the resistor R2 to reset the MCU as the value of minute and second equals 59
. The resetClock()
function which is in line 800 to 813 handles this task. In line 802 and line 805, we format the output of (t.min, DEC)
and (t.sec, DEC)
as strings, then convert them into integers and store them in int_timeMin
and int_timeSec
in line 803 and 806, respectively.
Now in line 808, we check if both int_timeMin
and int_timeSec
are equal to 59
. If not, the program exits the resetClock()
function and returns to line 188 to repeat the loop()
function again. If int_timeMin
and int_timeSec
are both equal to 59
, in line 810 we set the resetPin (D4)
to HIGH
. This will make the 2N3904 act as a closed switch shorting the reset pin of the MCU (PC6) (connected to the collector) to the ground (connected to the emitter). In this case, the MCU resets and starts initializing the libraries and hardware again.
The MCU reset will happen every hour at 59:59
. I added this function because I noticed that sometimes, after many hours, the 2nd Zone of the dot matrix LED display stops working. This might be a bug in the MD_Parola
library but I’m not really sure. This problem rarely happens though and can be resolved by resetting the MCU. To automate things, I used a BJT switch and added the resetClock()
function to automatically reset the MCU every hour to avoid the problem regarding the display.
So now, let’s discuss the code in setting the time and date of the digital clock using the tactile switches.
int setButtonState = digitalRead(2);
if (setButtonState == LOW)
{
set_select();
}
Let’s check lines 216 to 220 again. If the program reaches line 216 and the SET
button is pressed, the if statement in lines 217 to 220 will be executed and the program will jump to the set_select()
function which is in lines 299 to 375.
void set_select()
{
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int set_sel = 0;
int setButtonState = digitalRead(2);
while (setButtonState == HIGH)
{
setButtonState = digitalRead(2);
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
set_sel--;
delay(150);
}
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
set_sel++;
delay(150);
}
if (set_sel > 2)
{
set_sel = 0;
}
else if (set_sel < 0)
{
set_sel = 2;
}
String set_select_String = " ";
switch (set_sel)
{
case 0: // Set Day of the Week
set_select_String = "DOW";
set_DOW();
break;
case 1: // Set Date
set_select_String = "Date";
set_Date();
break;
case 2: // Set Time
set_select_String = "Time";
set_Time();
break;
}
String set_sel_Str = String (set_select_String);
char char_set_sel_Str[set_sel_Str.length()+1];
set_sel_Str.toCharArray(char_set_sel_Str,set_sel_Str.length()+1);
digitalClock.displayZoneText(1, char_set_sel_Str, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
The flowchart above shows the operation of the set_select()
function. As you can see, the first thing that the program will do is reset and clear the entire dot matrix LED display (line 301 to 304). Then next is display the word SET
on Zone 0 or the 1st 4-in-1 MAX7219 dot matrix LED display (line 306).
void set_select()
{
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
After displaying SET
on Zone 0, we declare and initialize to 0
the set_sel
variable (line 309). The set_sel
variable will be used later to select a setting (DOW, DATE, or TIME). To stay inside the set_select()
function and select a setting, I used a while loop in line 313 to 368 that will keep on looping as long as the SET
button is HIGH
. After declaring and initializing the set_sel
variable, the program reads the state of the SET
button. If the SET
button is pressed (LOW)
, the program will skip the while loop and jump to line 369 then exit the set_select()
function after executing the code in line 369 to 374. But if the SET
button is not pressed (HIGH)
, the program will enter the while loop. After entering the while loop, the program reads the SET
button again in line 315. The reading here will be used later (after setting DOW, DATE, or TIME) to decide if the program will exit the while loop and return to the loop()
function.
/**************************************************************************************************/
int set_sel = 0;
int setButtonState = digitalRead(2);
while (setButtonState == HIGH)
{
setButtonState = digitalRead(2);
In the set_select()
function, we used a switch statement (line 342 to 356) to select and set the digital clock’s DOW
, DATE
, or TIME
. The expression that we used in the switch statement is the value of the set_sel
variable. The switch statement here has 3 cases: case 0
for DOW
, case 1
for DATE
, and case 2
for TIME
.
switch (set_sel)
{
case 0: // Set Day of the Week
set_select_String = "DOW";
set_DOW();
break;
case 1: // Set Date
set_select_String = "Date";
set_Date();
break;
case 2: // Set Time
set_select_String = "Time";
set_Time();
break;
}
In case you don’t know how a switch statement works, in a switch statement, the value of the expression is compared to the constant-expression of a case. If their values match, the code under that case will be executed. For example, if the value of the set_sel
variable is 2
, then the case with a constant-expression equal to 2 will be executed which is of course case 2
.
So to set and select the digital clock’s DOW
, DATE
, or TIME
, we vary the value of the set_sel
variable using the UP
and DOWN
buttons. As you can see in the flowchart, after reading the SET
button again, the program then reads the DOWN
button. If the DOWN
button is pressed (LOW)
, the value of the set_sel
variable is decremented. But if not, the value of the set_sel
variable is not changed. Then the program proceeds into reading the UP
button. If the UP
button is pressed (LOW)
, the value of the set_sel
variable is incremented. If not, no change will be made in the set_sel
variable. The code in lines 317 to 329 handles this operation.
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
set_sel--;
delay(150);
}
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
set_sel++;
delay(150);
}
Since there are only 3 cases in the switch statement, case 0 to 2, we want to make sure that the value of the set_sel
variable remains in the 0 to 2
range so that we won’t have any problem. So in lines 331 to 338, I used if statements to make sure that the value of the set_sel
variable would stick in the 0 to 2
range regardless of how much we press the UP
or DOWN
buttons.
if (set_sel > 2)
{
set_sel = 0;
}
else if (set_sel < 0)
{
set_sel = 2;
}
String set_select_String = " ";
In line 340 we declare the string variable set_select_String
. We will use this variable later in displaying the setting that we want to set. After this, the program enters the switch statement. Initially, the value of the set_sel
variable is equal to 0
. If you are not going to press the UP
or DOWN
button, the value of the set_sel
variable will just be equal to 0
. In this case, the case that will match is case 0
. So the code under it will be executed.
As you can see, case 0
here is for setting the Day of the Week (or DOW)
. First line under case 0
stores DOW
in the set_select_String
variable. Then the next code makes the program jump to the set_DOW()
function (line 377 to 438) where we set the digital clock’s DOW
. However, the code that will set the DOW
will not be executed if we don't press the SEL/EDIT
button. The program will just jump to the set_DOW()
function and check if the SEL/EDIT
button is pressed. If it is not, the program will exit the set_DOW()
function and return to line 362 to execute break and exit switch case 0
.
switch (set_sel)
{
case 0: // Set Day of the Week
set_select_String = "DOW";
set_DOW();
break;
case 1: // Set Date
set_select_String = "Date";
set_Date();
break;
case 2: // Set Time
set_select_String = "Time";
set_Time();
break;
}
After the program exits the switch case statement, it will display the characters inside the string set_select_String
on the dot matrix LED display Zone 1. In this case, the word DOW
will be displayed on Zone 1 of the dot matrix LED display. The code for this operation is in lines 358 to 367.
String set_sel_Str = String (set_select_String);
char char_set_sel_Str[set_sel_Str.length()+1];
set_sel_Str.toCharArray(char_set_sel_Str,set_sel_Str.length()+1);
digitalClock.displayZoneText(1, char_set_sel_Str, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
So that is the end of the while loop. If we don’t press the SET
button, the program will stay in the while loop and will keep on repeating the code inside it. However, If we press the SET
button, the program will exit the while loop, resets and clears the dot matrix LED display in line 371 to 374, then returns to the loop()
function to resume the normal operation of the digital clock which is to display the time and date.
To switch to another case, case 1
or case 2
, just use the UP
or DOWN
buttons. The operation of the set_select()
function will just be the same. But if case 1
or case 2
is selected, the word that will be displayed on the dot matrix LED display Zone 1 will be DATE
or TIME
, respectively. Then the program will jump to set_Date()
or set_Time()
function.
Assuming that this time you want to set the digital clock’s DOW
, DATE
, or TIME
and then you’ve pressed the SEL/EDIT
button, this time the program will execute the code inside the set_DOW()
, set_Date()
, or set_Time()
function. So now, let’s check what’s inside these functions. Let’s start with the set_DOW()
function.
void set_DOW()
{
int setSelectButtonState = digitalRead(6);
if (setSelectButtonState == LOW)
{
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
DOW--;
delay(150);
}
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
DOW++;
delay(150);
}
if (DOW > 7)
{
DOW = 1;
}
else if (DOW < 1)
{
DOW = 7;
}
rtc.setDOW(DOW);
digitalClock.displayZoneText(1, rtc.getDOWStr(FORMAT_SHORT), PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
/**************************************************************************************************/
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
}
The first line in the set_DOW()
function, line 379, reads the SEL/EDIT
button. Since you’ve pressed the SEL/EDIT
button, the if statement in lines 380 to 437 will be executed. As you can see in the code lines 382 to 385 and on the set_DOW()
function flowchart, the first thing that will happen here is that the dot matrix LED display will be reset and cleared. Then next is the word SET
will be displayed on Zone 0 of the dot matrix LED display using the code in line 387. In order to stay inside the set_DOW()
function, we also used a while loop and we used the state of the SEL/EDIT
button to enter and exit it.
So after displaying the word SET
on Zone 0, we read the state of the SEL/EDIT
button in line 390. If the SEL/EDIT
button is pressed (LOW)
, the program skips the while loop and goes to line 428, resets and clears the dot matrix LED display, then exits the set_DOW()
function. But if the SEL/EDIT
button is not pressed (HIGH)
, the program will enter the while loop (line 392 to 427).
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
So if the program enters the while loop, in line 394, we read the state of the SEL/EDIT
button again. The reading here will be used later to exit the while loop after setting the DOW
.
To select the DOW
, we will use the UP
and DOWN
buttons again. So in line 396 to 408, we have that code again that we used earlier to increment or decrement the set_sel
variable using the UP
or DOWN
button, respectively. But this time the variable that we are varying here is the DOW
variable. We’ve already initialized the variable DOW
to 7
in line 137.
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
DOW--;
delay(150);
}
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
DOW++;
delay(150);
}
Since there are 7 days in a week, Sunday to Saturday, we will make sure that the value of the DOW
variable will stick in the 1 to 7
range. So we used this code in line 410 to 417 to limit the value of the DOW
variable from 1 up to 7.
if (DOW > 7)
{
DOW = 1;
}
else if (DOW < 1)
{
DOW = 7;
}
rtc.setDOW(DOW);
digitalClock.displayZoneText(1, rtc.getDOWStr(FORMAT_SHORT), PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
/**************************************************************************************************/
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
}
In line 419, we set the RTC DOW
using the set_DOW()
function of the DS3231 library. As you can see, we used the DOW
variable as its parameter. Then we display the DOW
set on Zone 1 using the code in line 421 to 426.
After this, if you don’t press the SEL/EDIT
button, the program will remain in the while loop and execute the code inside it again. However, if you pressed the SEL/EDIT
button, the program will exit the while loop, then in line 428 to 436, the dot matrix LED display will be reset and cleared. The program will then exit the set_DOW()
function and return to the set_select()
function in line 347. So that’s how the DOW is set. Now let’s check the set_Date()
function.
void set_Date()
{
int setSelectButtonState = digitalRead(6);
if (setSelectButtonState == LOW)
{
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int days[31] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
d++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
d--;
delay(150);
}
if (d > 30)
{
d = 0;
}
else if (d < 0)
{
d = 30;
}
dd = days[d];
String dateStr = "D: " + String (dd);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int months[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
m++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
m--;
delay(150);
}
if (m > 11)
{
m = 0;
}
else if (m < 0)
{
m = 11;
}
mm = months[m];
String dateStr = "M: " + String (mm);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int years[10] = {2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
yy++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
yy--;
delay(150);
}
if (yy > 9)
{
yy = 0;
}
else if (yy < 0)
{
yy = 9;
}
yyyy = years[yy];
String dateStr = "Y: " + String (yyyy);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
rtc.setDate(dd, mm, yyyy);
/**************************************************************************************************/
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
}
So you’ve used the UP
or DOWN
button and set the variable set_sel
to 1
. In this case, switch case 1
will be executed and the program jumps to the set_Date()
function in line 440. Then you’ve pressed the SEL/EDIT
button. Therefore, in line 442, the MCU will read the state of the SEL/EDIT
button as LOW
and the code inside the if statement in lines 443 to 617 will be executed.
Similar to the set_DOW()
function, the program will reset and clear the display first and then display the word SET
on Zone 0 of the dot matrix LED display (line 445 to 450). Again, we’re using a while loop here so that the program will stay in the set_Date()
function and we’re using the state of the SEL/EDIT
button to make the program enter and exit the while loop.
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int days[31] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
There are 3 while loops used in the set_Date()
function. The 1st while loop (line 455 to 496) selects the date, the 2nd (line 510 to 551) selects the month, and the 3rd (line 565 to 606) selects the year. In line 453, we read the state of the SEL/EDIT
button and if you’re not just going to press the SET/EDIT
button, the program will enter the 1st while loop to select the date. In line 457, we read the state of the SEL/EDIT
button again. The reading here will be used later to exit the 1st while loop after we set the date. In line 459, we created the array days[31]
with a size of 31
since there can be up to 31 days in a month. We will access this array later when we select the date.
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
d++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
d--;
delay(150);
}
Our method of selecting the date here is that we will use a variable (which in this case is the variable d
) to access the days[]
array, retrieve the value of the days[]
array element, and store it on the dd
variable which we will use later to set the date of the RTC. For example, the date today is 10. So we should set the value of the variable d
to 9
(since the first element of the array is at index 0). Then we access and retrieve the value of the 10th element of the days[]
array days[d]
and store it to the dd
variable dd = days[d];
.
To vary the value of the d
variable, we will use the UP
or DOWN
button again. In lines 461 to 473, we can see that code again that we used earlier to vary the value of a variable using the UP
or DOWN
button. This time, we just changed the variable to d
.
if (d > 30)
{
d = 0;
}
else if (d < 0)
{
d = 30;
}
dd = days[d];
String dateStr = "D: " + String (dd);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
Since the days[]
array only contains 31 elements, we should make sure that the d
variable won’t exceed 30 or be less than 0 to avoid getting invalid or random values. So in lines 475 to 482, we used that code again that limits the value of a variable but we just changed the variable to d
. Then in line 484, we access the days[]
array using the variable d
then store the value of the days[]
element on the dd
variable. In line 486, we format the dd
variable as a string, concatenate it with the string “D: “
, and store them in the dateStr
variable. In line 487, we create the character array buffer char_dateStr
, and in line 488 copy the characters of the dateStr
variable to the char_dateStr
buffer. Then in line 490 to 495, we display char_dateStr
on Zone 1 of the dot matrix LED display.
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
So that’s the end of the 1st while loop in the set_Date()
function. After that, the program will check if you’ve pressed the SEL/EDIT
button. If the button is not pressed, then the program will just stay inside the while loop and will repeat the code inside it again. But if the button is pressed, the program exits the while loop, resets and clears Zone 1 of the dot matrix LED display (line 499 to 502), reads the SEL/EDIT
button again (line 508), and enters the 2nd while loop if you did not press the SEL/EDIT
button.
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int months[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
m++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
m--;
delay(150);
}
if (m > 11)
{
m = 0;
}
else if (m < 0)
{
m = 11;
}
mm = months[m];
String dateStr = "M: " + String (mm);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
The 2nd while loop in the set_Date()
function which selects the month of RTC is just similar to the 1st while loop. But instead of days[]
, here we used the months[]
array (line 514) which contains 12 elements as there are 12 months in a year. Then instead of the d
variable, here we used the m
variable and varied its value using the UP
or DOWN
button (line 516 to 537). Then in line 538, instead of the dd
variable, we access the months[]
array using the variable m
and store the value in the mm
variable. Then, in lines 514 to 550, we display char_dateStr
which contains the characters “M: mm”
on Zone 1 of the dot matrix LED display. Then we press the SEL/EDIT
button to select the month. The program exits the 2nd while loop and in line 554 to 557, it resets and clears Zone 1 of the dot matrix LED display.
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int years[10] = {2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
yy++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
yy--;
delay(150);
}
if (yy > 9)
{
yy = 0;
}
else if (yy < 0)
{
yy = 9;
}
yyyy = years[yy];
String dateStr = "Y: " + String (yyyy);
char char_dateStr[dateStr.length()+1];
dateStr.toCharArray(char_dateStr,dateStr.length()+1);
digitalClock.displayZoneText(1, char_dateStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
rtc.setDate(dd, mm, yyyy);
/**************************************************************************************************/
digitalClock.displayReset();
delay(200);
digitalClock.displayClear();
delay(200);
}
}
After resetting and clearing Zone 1 of the dot matrix LED display, the MCU checks the state of the SEL/EDIT
button again and if it is not pressed, the program enters the 3rd while loop which selects the year of the RTC. Actually, the 3rd while loop is just similar to the 1st and 2nd while loops. The array was just changed to years[]
and the variables were changed to yy
and yyyy
. For the years[]
array, I only put 10 elements, just year 2021 up to year 2030 because I think that’s already long enough. Well, If this digital clock still works in the year 2031, then you can just change the elements of the years[]
array to maybe 2031-2040.
After selecting the year and pressing the SEL/EDIT
button, the program will exit the 3rd while loop. Then in line 608, the date of the RTC will be set using the setDate(dd, mm, yyyy)
function of the DS3231 library. As you see, we used the dd
, mm
, and yyyy
variables as parameters in the setDate()
function. After setting the date, the program resets and clears the entire dot matrix LED display and returns to the set_select()
function in line 351.
void set_Time()
{
int setSelectButtonState = digitalRead(6);
if (setSelectButtonState == LOW)
{
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
digitalClock.displayZoneText(0, "Set", PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
/**************************************************************************************************/
int setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int hours[24] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
h++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
h--;
delay(150);
}
if (h > 23)
{
h = 0;
}
else if (h < 0)
{
h = 23;
}
hr = hours[h];
String timeStr = "HR: " + String (hr);
char char_timeStr[timeStr.length()+1];
timeStr.toCharArray(char_timeStr,timeStr.length()+1);
digitalClock.displayZoneText(1, char_timeStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int minutes[60] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
mi++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
mi--;
delay(150);
}
if (mi > 59)
{
mi = 0;
}
else if (mi < 0)
{
mi = 59;
}
mins = minutes[mi];
String timeStr = "MIN: " + String (mins);
char char_timeStr[timeStr.length()+1];
timeStr.toCharArray(char_timeStr,timeStr.length()+1);
digitalClock.displayZoneText(1, char_timeStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(500);
digitalClock.displayReset(1);
delay(5);
digitalClock.displayClear(1);
delay(5);
/**************************************************************************************************/
/**************************************************************************************************/
setSelectButtonState = digitalRead(6);
while (setSelectButtonState == HIGH)
{
setSelectButtonState = digitalRead(6);
int seconds[60] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
int upButtonState = digitalRead(5);
if (upButtonState == LOW)
{
sc++;
delay(150);
}
int downButtonState = digitalRead(3);
if (downButtonState == LOW)
{
sc--;
delay(150);
}
if (sc > 59)
{
sc = 0;
}
else if (sc < 0)
{
sc = 59;
}
secs = seconds[sc];
String timeStr = "SEC: " + String (secs);
char char_timeStr[timeStr.length()+1];
timeStr.toCharArray(char_timeStr,timeStr.length()+1);
digitalClock.displayZoneText(1, char_timeStr, PA_CENTER, 100, 0, PA_NO_EFFECT, PA_NO_EFFECT);
if (digitalClock.displayAnimate())
{
digitalClock.displayReset(1);
}
}
delay(100);
rtc.setTime(hr, mins, secs);
/**************************************************************************************************/
digitalClock.displayClear();
delay(200);
digitalClock.displayReset();
delay(200);
}
}
Now, if you set the set_sel
variable to 2
and press the SEL/EDIT
button, the program jumps to the set_Time()
function and sets the time of the RTC. The set_Time()
function is in line 620 to 798. However, I think I don’t need to explain it anymore since the way it sets the time is just similar to the set_Date()
function. They just differ in the name of the variables and the size of the arrays used. After selecting the hour (stored in hr
variable), minute (stored in mins
variable), and second (stored in secs
variable), the time of the RTC is set using the setTime(hr, mins, secs)
function of the DS3231 library. The entire dot matrix LED display will be reset and cleared in line 793 to 796 and the program returns to line 355 of the set_select()
function.
So the program loops inside the set_select()
function now. If you have already set everything, again, to exit the sel_select()
function, just press the SET
button then the program will return to the loop()
function line 222 to execute the resetClock()
function. After that, the program will repeat the loop()
function again and operate in display time and date
mode.
So that’s all for the software or code part. In the next or last part, we will show you the PCBs and the assembled dot matrix LED display digital Clock. We will also show how it works and how you can set it using the tactile switches.
Get the latest tools and tutorials, fresh from the toaster.