Yet another arduino clock

Hello there. I'm trying to build an Arduino clock. I have the basic code:

#include<Time.h>
#include<TimeAlarm.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define LCD_WIDTH 16
#define LCD_HEIGHT 2


void loadchars() {  // This subroutine programs the custom character data into the LCD
  lcd.command(64);
  // custom character 0
  lcd.write((byte)B11100);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  // custom character 1
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  // custom character 2
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 3
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11000);
  lcd.write((byte)B11100);
  // custom character 4
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00011);
  lcd.write((byte)B00111);
  // custom character 5
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 6
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  // custom character 7
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.home();
}

byte bignums[61][2][6] = {
// digits are 3 characters wide and 2 lines high
// define which characters to use for each number. 255 is a solid block; 32 or 254 is a space  
// the format is { {TopLeft, TopMiddle, TopRight}, {BottomLeft, BottomMiddle, BottomRight} }
 { {1, 7, 0, 32, 32, 32}, {1, 5, 0, 32, 32, 32} },      // data to display "0"
 { {32, 32, 0, 32, 32, 32}, {32, 32, 0, 32, 32, 32} },  // data to display "1"
 { {4, 2, 0, 32, 32, 32}, {1, 5, 5, 32, 32, 32} },      // data to display "2"
 { {4, 2, 0, 32, 32, 32}, {6, 5, 0, 32, 32, 32} },      // data to display "3"
 { {1, 5, 0, 32, 32, 32}, {32, 32, 0, 32, 32, 32} },    // data to display "4"
 { {1, 2, 3, 32, 32, 32}, {6, 5, 0, 32, 32, 32} },      // data to display "5"
 { {1, 2, 3, 32, 32, 32}, {1, 5, 0, 32, 32, 32} },      // data to display "6"
 { {1, 7, 0, 32, 32, 32}, {32, 32, 0, 32, 32, 32} },    // data to display "7"
 { {1, 2, 0, 32, 32, 32}, {1, 5, 0, 32, 32, 32} },      // data to display "8"
 { {1, 2, 0, 32, 32, 32}, {6, 5, 0, 32, 32, 32} },      // data to display "9"
 { {32, 32, 0, 1, 7, 0}, {32, 32, 0, 1, 5, 0} },        // data to display "10"
 { {32, 32, 0, 32, 32, 0}, {32, 32, 0, 32, 32, 0} },    // data to display "11"
 { {32, 32, 0, 4, 2, 0}, {32, 32, 0, 1, 5, 5} },        // data to display "12"
 { {32, 32, 0, 4, 2, 0}, {32, 32, 0, 6, 5, 0} },        // data to display "13"
 { {32, 32, 0, 1, 5, 0}, {32, 32, 0, 32, 32, 0} },      // data to display "14"
 { {32, 32, 0, 1, 2, 3}, {32, 32, 0, 6, 5, 0} },        // data to display "15"
 { {32, 32, 0, 1, 2, 3}, {32, 32, 0, 1, 5, 0} },        // data to display "16"
 { {32, 32, 0, 1, 7, 0}, {32, 32, 0, 32, 32, 0} },      // data to display "17"
 { {32, 32, 0, 1, 2, 0}, {32, 32, 0, 1, 5, 0} },        // data to display "18"
 { {32, 32, 0, 1, 2, 0}, {32, 32, 0, 6, 5, 0} },        // data to display "19"
 { {4, 2, 0, 1, 7, 0}, {1, 5, 5, 1, 5, 0} },            // data to display "20"
 { {4, 2, 0, 32, 32, 0}, {1, 5, 5, 32, 32, 0} },        // data to display "21"
 { {4, 2, 0, 4, 2, 0}, {1, 5, 5, 1, 5, 5} },            // data to display "22"
 { {4, 2, 0, 4, 2, 0}, {1, 5, 5, 6, 5, 0} },            // data to display "23"
 { {4, 2, 0, 1, 5, 0}, {1, 5, 5, 32, 32, 0} },          // data to display "24"
 { {4, 2, 0, 1, 2, 3}, {1, 5, 5, 6, 5, 0} },            // data to display "25"
 { {4, 2, 0, 1, 2, 3}, {1, 5, 5, 1, 5, 0} },            // data to display "26"
 { {4, 2, 0, 1, 7, 0}, {1, 5, 5, 32, 32, 0} },          // data to display "27"
 { {4, 2, 0, 1, 2, 0}, {1, 5, 5, 1, 5, 0} },            // data to display "28"
 { {4, 2, 0, 1, 2, 0}, {1, 5, 5, 6, 5, 0} },            // data to display "29"
 { {4, 2, 0, 1, 7, 0}, {6, 5, 0, 1, 5, 0} },            // data to display "30"
 { {4, 2, 0, 32, 32, 0}, {6, 5, 0, 32, 32, 0} },        // data to display "31"
 { {4, 2, 0, 4, 2, 0}, {6, 5, 0, 1, 5, 5} },            // data to display "32"
 { {4, 2, 0, 4, 2, 0}, {6, 5, 0, 6, 5, 0} },            // data to display "33"
 { {4, 2, 0, 1, 5, 0}, {6, 5, 0, 32, 32, 0} },          // data to display "34"
 { {4, 2, 0, 1, 2, 3}, {6, 5, 0, 6, 5, 0} },            // data to display "35"
 { {4, 2, 0, 1, 2, 3}, {6, 5, 0, 1, 5, 0} },            // data to display "36"
 { {4, 2, 0, 1, 7, 0}, {6, 5, 0, 32, 32, 0} },          // data to display "37"
 { {4, 2, 0, 1, 2, 0}, {6, 5, 0, 1, 5, 0} },            // data to display "38"
 { {4, 2, 0, 1, 2, 0}, {6, 5, 0, 6, 5, 0} },            // data to display "39"
 { {1, 5, 0, 1, 7, 0}, {32, 32, 0, 1, 5, 0} },          // data to display "40"
 { {1, 5, 0, 32, 32, 0}, {32, 32, 0, 32, 32, 0} },      // data to display "41"
 { {1, 5, 0, 4, 2, 0}, {32, 32, 0, 1, 5, 5} },          // data to display "42"
 { {1, 5, 0, 4, 2, 0}, {32, 32, 0, 6, 5, 0} },          // data to display "43"
 { {1, 5, 0, 1, 5, 0}, {32, 32, 0, 32, 32, 0} },        // data to display "44"
 { {1, 5, 0, 1, 2, 3}, {32, 32, 0, 6, 5, 0} },          // data to display "45"
 { {1, 5, 0, 1, 2, 3}, {32, 32, 0, 1, 5, 0} },          // data to display "46"
 { {1, 5, 0, 1, 7, 0}, {32, 32, 0, 32, 32, 0} },        // data to display "47"
 { {1, 5, 0, 1, 2, 0}, {32, 32, 0, 1, 5, 0} },          // data to display "48"
 { {1, 5, 0, 1, 2, 0}, {32, 32, 0, 6, 5, 0} },          // data to display "49"
 { {1, 2, 3, 1, 7, 0}, {6, 5, 0, 1, 5, 0} },            // data to display "50"
 { {1, 2, 3, 32, 32, 0}, {6, 5, 0, 32, 32, 0} },        // data to display "51"
 { {1, 2, 3, 4, 2, 0}, {6, 5, 0, 1, 5, 5} },            // data to display "52"
 { {1, 2, 3, 4, 2, 0}, {6, 5, 0, 6, 5, 0} },            // data to display "53"
 { {1, 2, 3, 1, 5, 0}, {6, 5, 0, 32, 32, 0} },          // data to display "54"
 { {1, 2, 3, 1, 2, 3}, {6, 5, 0, 6, 5, 0} },            // data to display "55"
 { {1, 2, 3, 1, 2, 3}, {6, 5, 0, 1, 5, 0} },            // data to display "56"
 { {1, 2, 3, 1, 7, 0}, {6, 5, 0, 32, 32, 0} },          // data to display "57"
 { {1, 2, 3, 1, 2, 0}, {6, 5, 0, 1, 5, 0} },            // data to display "58"
 { {1, 2, 3, 1, 2, 0}, {6, 5, 0, 6, 5, 0} },            // data to display "59"
 { {1, 2, 3, 1, 7, 0}, {1, 5, 0, 1, 5, 0} },            // data to display "60"
};


void printbigchar(byte digit, byte col) { // This subroutine prints the big font characters on the LCD screen
 
 for (int i = 0; i < 2; i++) {            // count i from 0 to 1
   lcd.setCursor(col*4 , i);              // set LCD cursor at correct point
   for (int j = 0; j < 6; j++) {          // count j from 0 to 2
     lcd.write((byte)bignums[digit][i][j]);     // write proper block to LCD from array
   }
   lcd.write(254);                        // write an empty space
 }
 lcd.setCursor(col + 4, 0);               // move the cursor to the top line, col + 4
}


void setup()
{
  Serial.begin(9600);
  setTime(20,26,0,1,1,11); //Set time to Saturday 20:26:00 Jan 1 2011

  lcd.begin(LCD_WIDTH,LCD_HEIGHT);
  loadchars();
}
unsigned long secondCounter;

void loop(){
  digitalClockDisplay();
  Alarm.delay(1000); //waith one second between clock display
}
void digitalClockDisplay()
{
  //digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.println();
}

void printDigits(int digits)
{
  Serial.print(":");
  if(digits<10)
  Serial.print('0');
  Serial.print(digits);

  printbigchar(hour(),0);
  printbigchar(minute(),2);
}

But this is not enough for me. I want to make two dots appear between the hour and minute digits. (Any ideas how to do that?) Also I want to set the clock using a push buttons (when pressed button 1 the hour digits start to blinking, pressing button 2 the hours increase by 1, and the same with the minutes). Also I want to integrate RTC module. Any suggestion about easy to use RTC module?

Stan_be:
But this is not enough for me. I want to make two dots appear between the hour and minute digits. (Any ideas how to do that?)

Yes, possibly. But first a question about your plans:
Is this going to be a clock (hours/minutes display) only?
Or are you going to use also any date display and date functions with that project?

Stan_be:
Also I want to integrate RTC module. Any suggestion about easy to use RTC module?

Easy to use RTC modules are DS1307 and DS3231 modules, using I2C (Arduino "Wire" library).

But as China-made DS1307 modules (esp. those labeled "Tiny RTC") are of very poor circuit design and therefore tend to huge amounts of inaccuracy as well as to function failures, I'd strongly recommend the much more accurate and reliable DS3231 modules.

Programming for both types of modules would be the same.
Do you already have one of them?

For now I do not plan to use any date display and date functions with that project? And no, I do not have any RTC module so far. I am planning to bye and that is why I am asking for a advice about choosing one.

Stan_be:
For now I do not plan to use any date display and date functions with that project? And no, I do not have any RTC module so far. I am planning to bye and that is why I am asking for a advice about choosing one.

OK, so handling time only is enough for now.

Recommended RTC chip is DS3231, which can be obtained really cheap from China sellers like ebay 201061620203, but unfortunately time of delivery from China can be 4-6 weeks.

As I can see in cour code you include <TimeAlarm.h>, a library to handle alarm times.

So you do not only want to set the time in the RTC, but you want to set at least two time settings:

  • RTC time
  • alarm time
    And perhaps also a "Alarm ON" and "Alarm OFF"?

This is going to be something like a "alarm clock" with current time setting and alarm time setting?

The code I use is combination of several projects I found on the Internet. One of which is time alarm clock. So I do not need that. I am sure the code I use needs to be optimized, and probably the use of timealarms.h is not necessary. I want only a simple clock that can be set via push buttons for now (for later I want to make the setting of time with rotary encoder).

Stan_be:
The code I use is combination of several projects I found on the Internet. One of which is time alarm clock. So I do not need that. I am sure the code I use needs to be optimized, and probably the use of timealarms.h is not necessary. I want only a simple clock that can be set via push buttons for now (for later I want to make the setting of time with rotary encoder).

OK, so before starting editing the time, here a solution for "I want to make two dots appear between the hour and minute digits":

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define LCD_WIDTH 16
#define LCD_HEIGHT 2

struct time_s {byte hour; byte minute; byte second;};

time_s time={20, 26, 0}; // here you set the initial time

void updateTime()
{
  static unsigned long lastUpdate;
  if (millis()-lastUpdate>=1000)
  {
    lastUpdate+=1000;
    time.second++;
    if (time.second>=60)
    {
      time.second=0;
      time.hour++;
      if (time.hour>=24)
      {
        time.hour=0;
      }
    }
  }
}


void loadchars() {  // This subroutine programs the custom character data into the LCD
  lcd.command(64);
  // custom character 0
  lcd.write((byte)B11100);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  // custom character 1
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  // custom character 2
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 3
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11000);
  lcd.write((byte)B11100);
  // custom character 4
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00011);
  lcd.write((byte)B00111);
  // custom character 5
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 6
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  // custom character 7
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.home();
}

byte bignums[12][2][3] = {
// digits are 3 characters wide and 2 lines high
// define which characters to use for each number. 255 is a solid block; 32 or 254 is a space  
// the format is { {TopLeft, TopMiddle, TopRight}, {BottomLeft, BottomMiddle, BottomRight} }
 { {1, 7, 0}, {1, 5, 0} },      // data to display "0"
 { {32, 32, 0}, {32, 32, 0} },  // data to display "1"
 { {4, 2, 0}, {1, 5, 5} },      // data to display "2"
 { {4, 2, 0}, {6, 5, 0} },      // data to display "3"
 { {1, 5, 0}, {32, 32, 0} },    // data to display "4"
 { {1, 2, 3}, {6, 5, 0} },      // data to display "5"
 { {1, 2, 3}, {1, 5, 0} },      // data to display "6"
 { {1, 7, 0}, {32, 32, 0} },    // data to display "7"
 { {1, 2, 0}, {1, 5, 0} },      // data to display "8"
 { {1, 2, 0}, {6, 5, 0} },      // data to display "9"
 { {32,32,32}, {32, 32, 32} },  // data to display " " (space)
 { {32,46,32}, {32, 165, 32} }, // data to display ":"
};


void printbigchar(char c, byte col) { // This subroutine prints the big font characters on the LCD screen
  byte digit;
  if (c==' ') digit=10;
  else if (c==':') digit=11;
  else if(c>='0' && c<='9') digit= c-'0';
  else return;
  for (int i = 0; i < 2; i++) {            // count i from 0 to 1
    lcd.setCursor(col*3 , i);              // set LCD cursor at correct point
    for (int j = 0; j < 3; j++) {          // count j from 0 to 2
      lcd.write((byte)bignums[digit][i][j]);     // write proper block to LCD from array
    }
    lcd.write(254);                        // write an empty space
  }
  lcd.setCursor(col + 3, 0);               // move the cursor to the top line, col + 3
}


void setup()
{
  Serial.begin(9600);
  lcd.begin(LCD_WIDTH,LCD_HEIGHT);
  loadchars();
}
unsigned long secondCounter;

byte lastSecond;
void loop(){
  updateTime();
  if (time.second!=lastSecond)
  {
    lastSecond=time.second;
    digitalClockDisplay();
  }
}

void digitalClockDisplay()
{   //digital clock display of the time
  char buf[11];
  char *format;
  if (time.second%2==0) format="%02d:%02d"; // even second: format with colon
  else format="%02d %02d"; // odd second: format with space
  snprintf(buf,sizeof(buf),format,time.hour, time.minute);
  for (int i=0;i<5;i++) printbigchar(buf[i],i);
  Serial.println(buf);
}

Are the dots OK that way?
Blinking dots one second on and one second off?
Or would you like to see the dots a little bit bigger, if possible?

Edit:
Just found a mistake in my code: The clock code showed minute and second.
Fixed that to show hour and minute.

It is looking great! Now the dots are 2x2, I just want to see how they are going to look 3x3.

There is another problem with the clock code, now it is showing minutes:hours not hours:minutes.

Stan_be:
There is another problem with the clock code, now it is showing minutes:hours not hours:minutes.

That will be really easy to fix. Just look closely at your code for a while.

With this project I'm way over my head. It is difficult for me to understand the code jurs provided.

Stan_be:
It is looking great! Now the dots are 2x2, I just want to see how they are going to look 3x3.

It must be some dots that are in the character set of the LCD. All user definable characters are already in use for the big digits. I found some bigger 3x3 pixel dots, non-filled.

Stan_be:
There is another problem with the clock code, now it is showing minutes:hours not hours:minutes.

I see. The updateTime() function was wrong. I made a new one.

Fixed code with bigger dots:

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define LCD_WIDTH 16
#define LCD_HEIGHT 2

struct time_s {byte hour; byte minute; byte second;};

time_s time={20, 26, 0}; // here you set the initial time

void updateTime()
{
  static unsigned long lastUpdate;
  if (millis()-lastUpdate>=1000)
  {
    lastUpdate+=1000;
    long secondOfDay= time.hour*3600L + time.minute*60L + time.second;
    secondOfDay= (secondOfDay+1)%86400L;
    time.hour= secondOfDay/3600;
    secondOfDay-= time.hour*3600L;
    time.minute= secondOfDay/60;
    time.second= secondOfDay%60;
  }
}


void loadchars() {  // This subroutine programs the custom character data into the LCD
  lcd.command(64);
  // custom character 0
  lcd.write((byte)B11100);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  // custom character 1
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  // custom character 2
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 3
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11000);
  lcd.write((byte)B11100);
  // custom character 4
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00011);
  lcd.write((byte)B00111);
  // custom character 5
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 6
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  // custom character 7
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.home();
}

byte bignums[12][2][3] = {
// digits are 3 characters wide and 2 lines high
// define which characters to use for each number. 255 is a solid block; 32 or 254 is a space  
// the format is { {TopLeft, TopMiddle, TopRight}, {BottomLeft, BottomMiddle, BottomRight} }
 { {1, 7, 0}, {1, 5, 0} },      // data to display "0"
 { {32, 32, 0}, {32, 32, 0} },  // data to display "1"
 { {4, 2, 0}, {1, 5, 5} },      // data to display "2"
 { {4, 2, 0}, {6, 5, 0} },      // data to display "3"
 { {1, 5, 0}, {32, 32, 0} },    // data to display "4"
 { {1, 2, 3}, {6, 5, 0} },      // data to display "5"
 { {1, 2, 3}, {1, 5, 0} },      // data to display "6"
 { {1, 7, 0}, {32, 32, 0} },    // data to display "7"
 { {1, 2, 0}, {1, 5, 0} },      // data to display "8"
 { {1, 2, 0}, {6, 5, 0} },      // data to display "9"
 { {32,32,32}, {32, 32, 32} },  // data to display " " (space)
 { {32,161,32}, {32, 223, 32} }, // data to display ":"
};


void printbigchar(char c, byte col) { // This subroutine prints the big font characters on the LCD screen
  byte digit;
  if (c==' ') digit=10;
  else if (c==':') digit=11;
  else if(c>='0' && c<='9') digit= c-'0';
  else return;
  for (int i = 0; i < 2; i++) {            // count i from 0 to 1
    lcd.setCursor(col*3 , i);              // set LCD cursor at correct point
    for (int j = 0; j < 3; j++) {          // count j from 0 to 2
      lcd.write((byte)bignums[digit][i][j]);     // write proper block to LCD from array
    }
    lcd.write(254);                        // write an empty space
  }
  lcd.setCursor(col + 3, 0);               // move the cursor to the top line, col + 3
}


void setup()
{
  Serial.begin(9600);
  lcd.begin(LCD_WIDTH,LCD_HEIGHT);
  loadchars();
}
unsigned long secondCounter;

byte lastSecond;
void loop(){
  updateTime();
  if (time.second!=lastSecond)
  {
    lastSecond=time.second;
    digitalClockDisplay();
  }
}

void digitalClockDisplay()
{   //digital clock display of the time
  char buf[11];
  char *format;
  if (time.second%2==0) format="%02d:%02d"; // even second: format with colon
  else format="%02d %02d"; // odd second: format with space
  snprintf(buf,sizeof(buf),format,time.hour, time.minute);
  for (int i=0;i<5;i++) printbigchar(buf[i],i);
  Serial.println(buf);
}

It is working and looking great! For testing the code I'm using LCD keypad shield, can you make a code that can set the time using the kays ot the shield?

I bought the DS3231 module.

Stan_be:
For testing the code I'm using LCD keypad shield, can you make a code that can set the time using the kays ot the shield?

Yes, of course.

As there are a lot of buttons, a simple solution would be just to change the time when a button was pressed:

  • button 'Left' pressed ==> count minute down
  • button 'Right' pressed ==> count minute up
  • button 'Up' pressed ==> count hour up
  • button 'Down' pressed ==> count hour down
    Seconds remain unchanged in any case and keep on counting (invisible in the background) as normal.

So time setting with this code is only accurate up to half a minute at most:

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define LCD_WIDTH 16
#define LCD_HEIGHT 2

struct time_s {byte hour; byte minute; byte second;};

time_s time={20, 26, 0}; // here you set the initial time

void updateTime()
{
  static unsigned long lastUpdate;
  if (millis()-lastUpdate>=1000)
  {
    lastUpdate+=1000;
    long secondOfDay= time.hour*3600L + time.minute*60L + time.second;
    secondOfDay= (secondOfDay+1)%86400L;
    time.hour= secondOfDay/3600;
    secondOfDay-= time.hour*3600L;
    time.minute= secondOfDay/60;
    time.second= secondOfDay%60;
  }
}


void loadchars() {  // This subroutine programs the custom character data into the LCD
  lcd.command(64);
  // custom character 0
  lcd.write((byte)B11100);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  // custom character 1
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  // custom character 2
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 3
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11000);
  lcd.write((byte)B11100);
  // custom character 4
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00011);
  lcd.write((byte)B00111);
  // custom character 5
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 6
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  // custom character 7
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.home();
}

byte bignums[12][2][3] = {
// digits are 3 characters wide and 2 lines high
// define which characters to use for each number. 255 is a solid block; 32 or 254 is a space  
// the format is { {TopLeft, TopMiddle, TopRight}, {BottomLeft, BottomMiddle, BottomRight} }
 { {1, 7, 0}, {1, 5, 0} },      // data to display "0"
 { {32, 32, 0}, {32, 32, 0} },  // data to display "1"
 { {4, 2, 0}, {1, 5, 5} },      // data to display "2"
 { {4, 2, 0}, {6, 5, 0} },      // data to display "3"
 { {1, 5, 0}, {32, 32, 0} },    // data to display "4"
 { {1, 2, 3}, {6, 5, 0} },      // data to display "5"
 { {1, 2, 3}, {1, 5, 0} },      // data to display "6"
 { {1, 7, 0}, {32, 32, 0} },    // data to display "7"
 { {1, 2, 0}, {1, 5, 0} },      // data to display "8"
 { {1, 2, 0}, {6, 5, 0} },      // data to display "9"
 { {32,32,32}, {32, 32, 32} },  // data to display " " (space)
 { {32,161,32}, {32, 223, 32} }, // data to display ":"
};


void printbigchar(char c, byte col) { // This subroutine prints the big font characters on the LCD screen
  byte digit;
  if (c==' ') digit=10;
  else if (c==':') digit=11;
  else if(c>='0' && c<='9') digit= c-'0';
  else return;
  for (int i = 0; i < 2; i++) {            // count i from 0 to 1
    lcd.setCursor(col*3 , i);              // set LCD cursor at correct point
    for (int j = 0; j < 3; j++) {          // count j from 0 to 2
      lcd.write((byte)bignums[digit][i][j]);     // write proper block to LCD from array
    }
    lcd.write(254);                        // write an empty space
  }
  lcd.setCursor(col + 3, 0);               // move the cursor to the top line, col + 3
}



void digitalClockDisplay()
{   //digital clock display of the time
  char buf[11];
  char *format;
  if (time.second%2==0) format="%02d:%02d"; // even second: format with colon
  else format="%02d %02d"; // odd second: format with space
  snprintf(buf,sizeof(buf),format,time.hour, time.minute);
  for (int i=0;i<5;i++) printbigchar(buf[i],i);
  Serial.println(buf);
}

// Define some values used by the LCD panel and buttons:
enum keypadButtons {btnNONE, btnSELECT, btnLEFT, btnRIGHT, btnDOWN, btnUP };

int readPadButtons() // read the pressed button from analog interface:
{
  static boolean waitForNoButton;
  int adcValue=analogRead(A0);
  if (waitForNoButton && adcValue!=1023) return btnNONE;
  if ((labs(analogRead(A0)-adcValue)>1)) return btnNONE;
  // my buttons when read are centered at these valies: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  waitForNoButton=true;
  if (adcValue < 50)   return btnRIGHT; 
  if (adcValue < 195)  return btnUP;
  if (adcValue < 380)  return btnDOWN;
  if (adcValue < 555)  return btnLEFT;
  if (adcValue < 790)  return btnSELECT;  
  waitForNoButton=false;
  return btnNONE;  // when all others fail, return this...
}

void handleButtons()
{
  byte b=readPadButtons(); // read button when pressed down
  if (b==btnNONE || b==btnSELECT) return;
  if (b== btnLEFT && time.minute>0) time.minute--;
  if (b== btnRIGHT && time.minute<59) time.minute++;
  if (b== btnUP && time.hour<23) time.hour++;
  if (b== btnDOWN && time.hour>0) time.hour--;
  digitalClockDisplay();
}

void setup()
{
  Serial.begin(9600);
  lcd.begin(LCD_WIDTH,LCD_HEIGHT);
  loadchars();
}
unsigned long secondCounter;

byte lastSecond;
void loop(){
  updateTime();
  handleButtons();
  if (time.second!=lastSecond)
  {
    lastSecond=time.second;
    digitalClockDisplay();
  }
}

The accuracy of such a clock will depend on the accuracy of the Arduino 16 MHz frequency:

Older boards like "Duemilanove" have a "crystal osciallator" and are maybe accurate up to 7 seconds per day.

Modern boards like those in "R3" design have a "ceramic resonator" and those are maybe off 400 seconds per day.

For better accuracy with modern boards in "R3" design you need a RTC module, preferably a DS3231.

Ok, now how to make the clock work with the ds3231?

Stan_be:
Ok, now how to make the clock work with the ds3231?

Do you know how to create a "New Tab" for the editor in the Arduino-IDE?
So that your program consists of the main sketch window and an extra "tab" window in the Editor?

I have made a program consisting of two parts.
This is the main sketch, create a sketch with a name of your choice:

#define USE_RTC 1  // configure 0= no RTC or 1= use RTC

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define LCD_WIDTH 16
#define LCD_HEIGHT 2

#include <Wire.h>
#include "jursRTC.h"
sTime time; // a global time variable

void loadchars() {  // This subroutine programs the custom character data into the LCD
  lcd.command(64);
  // custom character 0
  lcd.write((byte)B11100);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  // custom character 1
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  // custom character 2
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 3
  lcd.write((byte)B11110);
  lcd.write((byte)B11100);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11000);
  lcd.write((byte)B11100);
  // custom character 4
  lcd.write((byte)B01111);
  lcd.write((byte)B00111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00011);
  lcd.write((byte)B00111);
  // custom character 5
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  // custom character 6
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00111);
  lcd.write((byte)B01111);
  // custom character 7
  lcd.write((byte)B11111);
  lcd.write((byte)B11111);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.write((byte)B00000);
  lcd.home();
}

byte bignums[12][2][3] = {
// digits are 3 characters wide and 2 lines high
// define which characters to use for each number. 255 is a solid block; 32 or 254 is a space  
// the format is { {TopLeft, TopMiddle, TopRight}, {BottomLeft, BottomMiddle, BottomRight} }
 { {1, 7, 0}, {1, 5, 0} },      // data to display "0"
 { {32, 32, 0}, {32, 32, 0} },  // data to display "1"
 { {4, 2, 0}, {1, 5, 5} },      // data to display "2"
 { {4, 2, 0}, {6, 5, 0} },      // data to display "3"
 { {1, 5, 0}, {32, 32, 0} },    // data to display "4"
 { {1, 2, 3}, {6, 5, 0} },      // data to display "5"
 { {1, 2, 3}, {1, 5, 0} },      // data to display "6"
 { {1, 7, 0}, {32, 32, 0} },    // data to display "7"
 { {1, 2, 0}, {1, 5, 0} },      // data to display "8"
 { {1, 2, 0}, {6, 5, 0} },      // data to display "9"
 { {32,32,32}, {32, 32, 32} },  // data to display " " (space)
 { {32,161,32}, {32, 223, 32} }, // data to display ":"
};


void printbigchar(char c, byte col) { // This subroutine prints the big font characters on the LCD screen
  byte digit;
  if (c==' ') digit=10;
  else if (c==':') digit=11;
  else if(c>='0' && c<='9') digit= c-'0';
  else return;
  for (int i = 0; i < 2; i++) {            // count i from 0 to 1
    lcd.setCursor(col*3 , i);              // set LCD cursor at correct point
    for (int j = 0; j < 3; j++) {          // count j from 0 to 2
      lcd.write((byte)bignums[digit][i][j]);     // write proper block to LCD from array
    }
    lcd.write(254);                        // write an empty space
  }
  lcd.setCursor(col + 3, 0);               // move the cursor to the top line, col + 3
}



void digitalClockDisplay()
{   //digital clock display of the time
  char buf[11];
  char *format;
  if (time.bSecond%2==0) format="%02d:%02d"; // even second: format with colon
  else format="%02d %02d"; // odd second: format with space
  snprintf(buf,sizeof(buf),format,time.bHour, time.bMinute);
  for (int i=0;i<5;i++) printbigchar(buf[i],i);
  Serial.println(buf);
}

// Define some values used by the LCD panel and buttons:
enum keypadButtons {btnNONE, btnSELECT, btnLEFT, btnRIGHT, btnDOWN, btnUP };

int readPadButtons() // read the pressed button from analog interface:
{
  static boolean waitForNoButton;
  int adcValue=analogRead(A0);
  if (waitForNoButton && adcValue!=1023) return btnNONE;
  if ((labs(analogRead(A0)-adcValue)>1)) return btnNONE;
  // my buttons when read are centered at these valies: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  waitForNoButton=true;
  if (adcValue < 50)   return btnRIGHT; 
  if (adcValue < 195)  return btnUP;
  if (adcValue < 380)  return btnDOWN;
  if (adcValue < 555)  return btnLEFT;
  if (adcValue < 790)  return btnSELECT;  
  waitForNoButton=false;
  return btnNONE;  // when all others fail, return this...
}

void handleButtons()
{
  byte b=readPadButtons(); // read button when pressed down
  if (b==btnNONE || b==btnSELECT) return;
  if (b== btnLEFT && time.bMinute>0) time.bMinute--;
  if (b== btnRIGHT && time.bMinute<59) time.bMinute++;
  if (b== btnUP && time.bHour<23) time.bHour++;
  if (b== btnDOWN && time.bHour>0) time.bHour--;
#if (USE_RTC==1)
    RTCwriteTime(time);
#endif
  digitalClockDisplay();
}

void setup()
{
  Serial.begin(9600);
#if (USE_RTC==1)
  if (RTCinit()) Serial.println("RTC OK");
  else Serial.println("RTC FAIL");
#endif  
  lcd.begin(LCD_WIDTH,LCD_HEIGHT);
  loadchars();
}
unsigned long secondCounter;

byte lastSecond;
void loop(){
  timeTask(time);
  handleButtons();
  if (time.bSecond!=lastSecond)
  {
#if (USE_RTC==1)
    RTCreadTime(time);
#endif
    lastSecond=time.bSecond;
    digitalClockDisplay();
  }
}

And this is a small RTC library for a "New Tab" with the file name "jursRTC.h":

#include <Arduino.h>

// RTC functions
struct sTime // structure for holding a date and a time
{
  int  iYear;
  byte bMonth;
  byte bDay;
  byte bHour;
  byte bMinute;
  byte bSecond;
};

byte daysInMonth(int year, byte month)
{
  if (month == 4 || month == 6 || month == 9 || month == 11)  
    return 30;  
  else if (month == 2)  
  {
    bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);  
    if (isLeapYear) return 29;
    else return 28;
  }  
  else return 31;
}

boolean validDate(int year, int month, int day)
{
  boolean valid=true;
  if (year<2000 || year>2199) valid=false;
  if (month<1 || month>12) valid=false;
  if (day<1 || day> daysInMonth(year, month)) valid=false;
  return valid;
}

boolean validTime(int hour, int minute, int second)
{
  boolean valid=true;
  if (hour<0 || hour>=24) valid=false;
  if (minute<0 || minute>=60) valid=false;
  if (second<0 || second>=60) valid=false;
  return valid;
}


void timeTask(sTime &dateTime)
{
  static unsigned long lastUpdate;
  while (millis()-lastUpdate>=1000)
  {
    lastUpdate+=1000;
    dateTime.bSecond++;
    if (dateTime.bSecond>=60) {dateTime.bSecond=0; dateTime.bMinute++;}
    if (dateTime.bMinute>=60) {dateTime.bMinute=0; dateTime.bHour++;}
    if (dateTime.bHour>=24) {dateTime.bHour=0; dateTime.bDay++;}
    if (dateTime.bDay>daysInMonth(dateTime.iYear, dateTime.bMonth)) {dateTime.bDay=1; dateTime.bMonth++;}
    if (dateTime.bMonth>12) {dateTime.bMonth=1; dateTime.iYear++;}
  }
}

//###################################
// RTC routines for DS1307 and DS3231
//###################################
#include <Wire.h>
#define RTC_I2C_ADDRESS 0x68 // // I2C adress of DS1307 and DS3231 RTC


byte decToBcd(byte val) // RTC helper function
// Convert decimal number to binary coded decimal
{
  return ( (val/10*16) + (val%10) );
}

byte bcdToDec(byte val)  // RTC helper function
// Convert binary coded decimal to decimal number
{
  return ( (val/16*10) + (val%16) );
}

boolean RTCinit()
{
  Wire.begin();
  delay(10); // small delay to stabilize I2C bus voltage
  Wire.beginTransmission(RTC_I2C_ADDRESS);
  Wire.write(0);
  return (Wire.endTransmission()==0);
}

void RTCreadTime(sTime &time);
void RTCreadTime(sTime &time)
// read current time from RTC
{
// Reset the register pointer
  Wire.beginTransmission(RTC_I2C_ADDRESS);
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(RTC_I2C_ADDRESS, 7);
  // A few of these need masks because certain bits are control bits
  time.bSecond= bcdToDec(Wire.read() & 0x7f);
  time.bMinute= bcdToDec(Wire.read());
  time.bHour  = bcdToDec(Wire.read() & 0x3f);  // Need to change this if 12 hour am/pm
  bcdToDec(Wire.read()); // day of week not used
  time.bDay   = bcdToDec(Wire.read());
  time.bMonth = bcdToDec(Wire.read());
  time.iYear  = bcdToDec(Wire.read())+2000;  
}

void RTCwriteTime(sTime time)
// write time to RTC
{
  Wire.beginTransmission(RTC_I2C_ADDRESS);
  Wire.write(0);
  Wire.write(decToBcd(time.bSecond));    // 0 to bit 7 starts the clock
  Wire.write(decToBcd(time.bMinute));
  Wire.write(decToBcd(time.bHour));      // If you want 12 hour am/pm you need to set
                                   // bit 6 (also need to change readDateDs1307)
  Wire.write(decToBcd(0)); // weekday not used
  Wire.write(decToBcd(time.bDay));
  Wire.write(decToBcd(time.bMonth));
  Wire.write(decToBcd(time.iYear-2000));
  Wire.endTransmission();  
}

You then can use the sketch with or without RTC by setting either:

#define USE_RTC 1

or

#define USE_RTC 0

Do you get it managed to create an editor tab with file name "jursRTC.h"?

Ok, I created the "jursRTC.h" tab. How do I connect the RTC module? Should I look at some tutorial about it or did you do it differently?

Stan_be:
Ok, I created the "jursRTC.h" tab. How do I connect the RTC module? Should I look at some tutorial about it or did you do it differently?

DS3231 RTC modules are to be connected to VCC, GND and the I2C bus.

With "UNO" boards the I2C bus is at pins A4 and A5, so you connect:

  • UNO A4 ==> clock SDA
  • UNO A5 ==> clock SCL