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.

Dot Matrix LED Display Digital Clock Code Flowchart
Dot Matrix LED Display Digital Clock Code Flowchart

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

}

set_select() Function Flowchart
set_select() Function Flowchart

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.

set_DOW() Function Flowchart
set_DOW() Function Flowchart

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.

set_Date() Function Flowchart
set_Date() Function Flowchart

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.

set_Time() Function Flowchart
set_Time() Function Flowchart

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.

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?