Ideas for implementing a clock

I am trying to build the code for a clock that counts up and shows MM:SS on 4 7-segment displays. So far, I can display any number (0-9) by using the shiftOut() function. However, I cant think of a way to implement the counting up feature. I have tried a few examples found online but none have worked very well for me.

The circuit can be found on Tinkercad here: Circuit design 4 7-Segment Displays | Tinkercad

code:

#define clockPin 10		// clock on pin 10
#define latchPin 9			// latch on pin 9

#define data1 	8			// display 00:0x
#define data2	7			// display 00:x0
#define data3	6			// display 0x:00
#define data4	5			// display x0:00

void disp(int pin, int n);

void setup(){
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT); 
  pinMode(data1, 	OUTPUT);
  pinMode(data2, 	OUTPUT);
  pinMode(data3, 	OUTPUT);
  pinMode(data4, 	OUTPUT);
  
  for(int i=data4; i<=data1; i++){	// Turn off displays
    digitalWrite(i, HIGH);
  }
} 

void loop() {
  int n = millis() / 1000;
  int seconds = n % 60  ;
  int minutes =  n / 60  ;
  int hours = minutes / 24;
  minutes = minutes % 60;
  hours = hours % 24;
}

void disp(int pin, int n){
  switch (n){
    case 0:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x3);
    	digitalWrite(latchPin, HIGH);
    break;
    case 1:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x9F);
    	digitalWrite(latchPin, HIGH);
    break;
    case 2:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x25);
    	digitalWrite(latchPin, HIGH);
    break;
    case 3:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0xD);
    	digitalWrite(latchPin, HIGH);
    break;
    case 4:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x99);
    	digitalWrite(latchPin, HIGH);
    break;
    case 5:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x49);
    	digitalWrite(latchPin, HIGH);
    break;
    case 6:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x41);
    	digitalWrite(latchPin, HIGH);
    break;
    case 7:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x1F);
    	digitalWrite(latchPin, HIGH);
    break;
    case 8:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x1);
    	digitalWrite(latchPin, HIGH);
    break;
    case 9:
    	digitalWrite(latchPin, LOW); 
    	shiftOut(pin, clockPin, LSBFIRST, 0x19);
    	digitalWrite(latchPin, HIGH);
    break;
    default:
    break;
  }
}

Welcome to the forum.

The millis() value will rollover after 50 days, so your clock would only work for 50 days.

Since you already use millis(), do you know the Blink Without Delay ?
Then you can make a millis-timer that is actived every second.

Have you heard of 'epoch' or 'unix time' ? That is the number of seconds since 1970. It is possible to have a 'unsigned long' variable that counts the number of seconds and then you can calculate the seconds, minutes and hours from that variable.

However, you could also just increase the values and update if needed:

seconds++;
if( seconds >= 60)
{
  seconds = 0;
  minutes++;
  if( minutes >= 60)
  {
    minutes = 0;
    hours++;
    if( hours >= 24)
    {
      hours = 0;
    }
  }
}

Does that make sense ?

I took the code from my "millis_clock.ino" sketch on my Fun with millis page.

If you have that working, then find some information about a Real Time Clock (RTC) and the DS3231.

Try:
unsigned long StartTime = 0;

int n = (millis() - StartTime) / 1000;

Then, when you want to re-start the count-up:
StartTime = millis();

Some tips on your circuit.


You need a 0.1uF ceramic capacitor for each 74hc595 chip, connected close to the Vcc & ground pins of each chip. It may work in Tinkercad without them, but Tinkercad is not the real world.

You also need a series resistor for each segment of each digit, so 28 in total. With only one resistor on the common pin of each digit, you will not get even brightness because "1" will look much brighter than "8". The more segments that are lit, the more current wants to flow through that single resistor, but the resistor won't allow that, forcing the lit segments to share the current between them, making them dimmer.

You can save some Arduino pins by chaining the 74hc595 chips together. Only one pin to output the data for all 4 digits, or even more. Makes your code simpler, too.

So I scratched the original circuit and just made a new, slightly more complicated circuit but it helped with my code. Circuit design 4 digit timer | Tinkercad
I am now using two 74C595 8-bit shift register IC to drive four CD4511 7-segment decoders.

This helped with my code tremendously. However, I am now running into issues with the counting. Every time the seconds reach a number divisible by 10 (i.e. 10, 20, 30) the display just gets "stuck" showing nothing on the first digit and the previous value on the next digit.
I think it may just be Tinkercad but could also be a bug in my code or circuit.

EDIT: I figured out what's wrong. It has to do with the way I am adding HEX values. When seconds =0x9 and I add 0x1, the result is 0xA, not 0x10 which is what I want for it to display "10". Now I just need to figure out the correct way to do this.

#define clockPin1 10		// clock on pin 10
#define clockPin2 11		// clock on pin 11
#define latchPin 9			// latch on pin 9
#define data1 	8			// display 00:xx
#define data2	7			// display xx:00

int seconds = 0x0;
int minutes = 0x0;

void disp(int m, int s);

void setup(){
  pinMode(clockPin1, OUTPUT);
  pinMode(clockPin2, OUTPUT);
  pinMode(latchPin, OUTPUT); 
  pinMode(data1, 	OUTPUT);
  pinMode(data2, 	OUTPUT);
}

void loop(){
  while (minutes<0x60){
    seconds+=0x1;
    if(seconds==0x60){
      minutes += 0x1;
      seconds = 0x0;
    }
    disp(minutes, seconds);
    delay(500);		// normally 1000, set to 500 for test
  }
  minutes = 0x0;
}

void disp(int m, int s){
  digitalWrite(latchPin, LOW); 
  shiftOut(data1, clockPin1, MSBFIRST, s);
  shiftOut(data2, clockPin2, MSBFIRST, m); 
  digitalWrite(latchPin, HIGH);
}

Figured it out. I probably did it the hard way as I am sure there is a more efficient way of doing this, but I am happy with this for now.

#define clockPin1 10		// clock on pin 10
#define clockPin2 11		// clock on pin 11
#define latchPin 9			// latch on pin 9
#define data1 	8			// display 00:xx
#define data2	7			// display xx:00

byte S;
byte s;
byte M;
byte m;
int seconds = 0;
int minutes = 0;
byte seconds_formatted;
byte minutes_formatted;

byte toHex(int tmp);
void format();
void disp(int m, int s);

void setup(){
  pinMode(clockPin1, OUTPUT);
  pinMode(clockPin2, OUTPUT);
  pinMode(latchPin, OUTPUT); 
  pinMode(data1, 	OUTPUT);
  pinMode(data2, 	OUTPUT);
}

void loop(){
  while(minutes <60){
    if(seconds==60){
      minutes++;
      seconds=0;
    }
    
    format();
    disp(minutes_formatted, seconds_formatted);
    delay(1000);
    seconds++;
  }
  minutes = 0;
}

byte toHex(int tmp){  
  switch (tmp){
    case 0:
    	tmp = tmp<<4;
    	break;
    case 1:
    	tmp = 0x1;
    	break;
    case 2:
    	tmp = 0x2;
    	break;
    case 3:
    	tmp = 0x3;
    	break;
    case 4:
    	tmp = 0x4;
    	break;
    case 5:
    	tmp = 0x5;
    	break;
    case 6:
    	tmp = 0x6;
    	break;
    case 7:
    	tmp = 0x7;
    	break;
    case 8:
    	tmp = 0x8;
    	break;
    case 9:
    	tmp = 0x9;
    	break;
    default:
    	break;
    return tmp;
  }  
}

void format(){
  S = seconds / 10;
  s = seconds % 10;
  M = minutes / 10;
  m = minutes % 10;
  int tmp1 = toHex(S);
  int tmp2 = toHex(s);
  int tmp3 = toHex(M);
  int tmp4 = toHex(m);
  seconds_formatted = (tmp1<<4)|tmp2;
  minutes_formatted = (tmp3<<4)|tmp4;
}

void disp(int m, int s){
  digitalWrite(latchPin, LOW); 
  shiftOut(data1, clockPin1, MSBFIRST, s);
  shiftOut(data2, clockPin2, MSBFIRST, m); 
  digitalWrite(latchPin, HIGH);
}

I'm pretty sure your ToHex() function is doing nothing, so you could remove that.

I didn't look at your new circuit on Tinkercad. Last time you posted a link to it, I took a screenshot and posted it on my reply. I had hoped you would be kind enough to do that this time.

Making circuits like this on a simulator like Tinkercad is educational, but if you want to built the circuit for real, I would recommend using a max7219 chip. With that chip you need only 2 caps and 1 resistor to drive up to 8 7-seg digits. You can even use the muti-digit 7-seg displays. But while max7219 can be used with common-anode displays, it is really designed for common-cathode displays, and is much easier to use with them.

Another possibility is a an ht16k33 chip. These generally come on small PCBs with most of the other components needed, making these also easy to use. Each chip can drive up to 16 digits of 7-segment, or 8 digits alpha-numeric led displays.