Need Guidance on Pausing and Resuming 4 Digit 7 Segment LED Countdown Clock

I'm trying to put together a countdown clock using a 4 digit 7 Segment LED and Max7219 IC. I'm basing my design and the code on this one I found on YouTube: - YouTube .

I've modified my display to countdown like a hockey or basketball scoreboard but I have two questions right now. My countdown starts with two minutes digits and two seconds digits in a 00:00 format. Once it gets below 10 min the first digit goes dark and it is a 0:00 format. Once it gets below a minute it is a seconds and tenths of seconds format at 00.0. Once it gets below 10 seconds it is a minutes and tenths of seconds format at 0.0 until zero. My first issue is that for a second from the transition from 10:00 min to 9:59 min there is a flash of a zero in the first digit (the one that will go dark). The same thing happens for a 10th of a second from 10.0 to 9.9 seconds. Any idea how I can get rid of that?

I'm also trying to figure out how to pause and resume the timer. I've tried to incorporate a switch and a pause for a period of time but my button is not responsive.

Code to follow.
Thanks, Steve

Code as promised:

[code]/////////////////////////////////
// 4-DIGIT TIMER USING MAX7219 //
//       By BulletMagnet83     //
/////////////////////////////////


#include "LedControl.h"


LedControl lc=LedControl(12, 11, 10, 1); //data, clock, load, number of devices

int minutes_t = 0;
int minutes_u = 0;
int seconds_t = 0;
int seconds_u = 0;
int tenths_u  = 0;

void setup () {
  lc.shutdown(0, false);    //wake-up MAX7219
  lc.setIntensity(0, 1);    //medium display intensity
  lc.clearDisplay(0);       //blank display
  pinMode(A5, INPUT);       //minutes button
  pinMode(A4, INPUT);       //seconds button
  pinMode(A3, INPUT);       //start
  digitalWrite(A5, HIGH);   //set pull-up
  digitalWrite(A4, HIGH);   //set pull-up
  digitalWrite(A3, HIGH);   //set pull-up
  
  pinMode(A2, INPUT);       // not really necessary, pins default to INPUT anyway
  digitalWrite(A2, HIGH);   // turn on pullup resistors. Wire button so that press shorts pin to ground.


}

void delaySet () {          //allow user to preset delay time

  while(1) {                  //infinite-loop this block while delay is being set

    lc.setChar(0, 0, minutes_t, false);   //display tens of minutes
    lc.setChar(0, 1, minutes_u, true);    //display ones of minutes, with decimal point
    lc.setChar(0, 2, seconds_t, true);   //display tens of seconds
    lc.setChar(0, 3, seconds_u, false);   //display ones of seconds

    if(digitalRead(A5) == LOW) {          //read input from minutes button
      minutes_u++;                        //increment value by 1
      delay(150);                         //short delay to debounce input, can hold button without it going mad
      if(minutes_u > 9) {                 //this block of statements controls counting
        minutes_t++;
        minutes_u = 0;
      }
      if(minutes_t > 5) {
        minutes_t = 0;
        minutes_u = 0;
      }

    }

    if(digitalRead(A4) == LOW) {          //same here but for seconds
      seconds_u++;
      delay(150);
      if(seconds_u > 9) {
        seconds_t++;
        seconds_u = 0;
      }
      if(seconds_t > 5) {
        seconds_t = 0;
        seconds_u = 0;
        minutes_u++;
      }


    }

    if(digitalRead(A3) == LOW) {

      countDown();            //begin countdown
    }



  } 

}

void error() {

  lc.clearDisplay(0);               //blank display
  lc.setChar(0, 1, 'E', false);     //this block writes "Err" to display
  lc.setRow(0, 2, 0x05);
  lc.setRow(0, 3, 0x05);
  tone(7, 700, 10);
  delay(50);
  tone(7, 700, 10);
  delay(50);
  tone(7, 700, 10);
  delay(300);
  delaySet();                       //return to delay set function, previous entry is remembered
  

}


void updateDisplay () {  //displays minutes and seconds in 00:00 format

  lc.setChar(0, 0, minutes_t, false);   //display tens of minutes
  lc.setChar(0, 1, minutes_u, true);    //display units of minutes and lower decimal point
  lc.setChar(0, 2, seconds_t, true);   //display tens of seconds and upper decimal point
  lc.setChar(0, 3, seconds_u, false);   //display units of seconds
}

void udateDisplayUnderTenMinutes(){  // displays minutes and seconds 0:00 format
  lc.setChar(0, 0, ' ', false);   
  lc.setChar(0, 1, minutes_u, true);   //display units of minutes and lower decimal point
  lc.setChar(0, 2, seconds_t, true);   //display tens of second and upper decimal point
  lc.setChar(0, 3, seconds_u, false);  //display units of seconds
}

void updateDisplayUnderOneMinute(){  // displays seconds and tenths of seconds in 00:0 format
  lc.setChar(0, 0, seconds_t, false);   
  lc.setChar(0, 1, seconds_u, true);   //display units of seconds and lower decimal point
  lc.setChar(0, 2, tenths_u, false);   //display tenths of second
  lc.setChar(0, 3, ' ', false); 
}  

void updateDisplayUnderTen() {  // displays seconds and tenths of second in 0.0 format
  lc.setChar(0, 0, ' ', false);   
  lc.setChar(0, 1, seconds_u, true);   //display ones of seconds and lower decimal point
  lc.setChar(0, 2, tenths_u, false);   //display tenths of second
  lc.setChar(0, 3, ' ', false); 
}

void timeUp () {

  for(int i = 0; i < 6; i++) {

    lc.clearDisplay(0);
    delay(100);
    lc.setChar(0, 0, 0, false);
    lc.setChar(0, 1, 0, true);
    lc.setChar(0, 2, 0, true);
    lc.setChar(0, 3, 0, false);

  }
  seconds_u = 0;    //initialise all variables back to zero
  seconds_t = 0;
  minutes_u = 0;
  minutes_t = 0;
  delaySet();       //return to delay set function

}

void countDown() {

  if(seconds_t < 1 && minutes_u == 0 && minutes_t == 0) {        //user cannot select a delay of less than 10 seconds
    error();
  }
  else {

    while(1) {                  //infinite-loop this block while timer is running unless all digits equal to zero

      static unsigned long lastTick = 0;

      if(millis() - lastTick >= 100) {
        lastTick = millis();
        tenths_u--;
       }
        
      if(tenths_u < 0) {  
        seconds_u--;
        tenths_u = 9;
        updateDisplay();
       }

      if(seconds_u < 0) {
        seconds_t--;
        seconds_u = 9;
        updateDisplay();
      }

      if(seconds_t < 0) {
        minutes_u--;
        seconds_t = 5;
        updateDisplay();
      }

      if(minutes_u < 0 && minutes_t > 0) {
        minutes_t--;
        minutes_u = 9;
        updateDisplay();
      }
      if(tenths_u == 9 && seconds_u == 9 && seconds_t == 5 && minutes_u == 9 && minutes_t == 0) { // display shifts to 9.59 minutes 
        udateDisplayUnderTenMinutes();
        
        while(1) {                  
      
           if(millis() - lastTick >= 100) {
           lastTick = millis();
           tenths_u--;
          }
        
          if(tenths_u < 0) {  
          seconds_u--;
          tenths_u = 9;
          udateDisplayUnderTenMinutes();
          }

          if(seconds_u < 0) {
          seconds_t--;
          seconds_u = 9;
          udateDisplayUnderTenMinutes();
          }

          if(seconds_t < 0) {
          minutes_u--;
          seconds_t = 5;
          udateDisplayUnderTenMinutes();
          }
          
        if(tenths_u == 9 && seconds_u == 9 && seconds_t == 5 && minutes_u == 0 && minutes_t == 0)  {  // display shifts to 59.99 seconds
      // At this point the first digit flashes 0 -- !@#%?
        updateDisplayUnderOneMinute();
      
        while(1) {                  
                 
            if(millis() - lastTick >= 100) {
            lastTick = millis();
            tenths_u--;
            updateDisplayUnderOneMinute();   
            }   
            
            if(tenths_u < 0) {
              seconds_u--;
              tenths_u = 9;
              updateDisplayUnderOneMinute();
            }
            
            if(seconds_u < 0) {
              seconds_t--;
              seconds_u = 9;
              updateDisplayUnderOneMinute();
            } 
              if(tenths_u == 9 && seconds_u == 9 && seconds_t == 0 && minutes_u == 0 && minutes_t == 0)  {   //display shifts to 9.99 seconds  
                 updateDisplayUnderTen();
          
            while(1) {             
                  
            if(millis() - lastTick >= 100) {
            lastTick = millis();
            tenths_u--;
            updateDisplayUnderTen();   
            }   
            
            if(tenths_u < 0) {
              seconds_u--;
              tenths_u = 9;
              updateDisplayUnderTen();
            }
              
   if (digitalRead(A2) == LOW){   //Button to stop clock - delay it actually
     delay(10000);
      }
            if(tenths_u == 0 && seconds_u == 0 && seconds_t == 0 && minutes_u == 0 && minutes_t == 0)  {   //time up operation - flashing 00:00
        timeUp();
         }  // time up if bracket
        }  //while bracket    
       }  // display shifts to 9.99 if bracket 
      }  // 59.99 while bracket    
     }  // display shifts to 59.99 seconds if bracket
    }  // 9.59 while bracket
   } // display shifts to 9.59 minutes if bracket
  }  ////infinite-loop this block while timer is running unless all digits equal to zero bracket
 }  //else bracket
}  //void countdown bracket

void loop () {
  delaySet ();

   
}

[/code]

Actually I no longer have the display of 09:59 but there is still an image of the zero flashing for a brief time.

I'm thinking stopping and starting the clock should be so simple but I can't get any of my attempts to work.

Also my ultimate goal is to integrate displays to show scoring, periods, and sound files to start and stop play. My current code takes 4672 bytes. I'm afraid I might run out of memory for the sketch. Any suggestions on how to simplify what I have at this point?

It would help if you put each { on a new line, use Tools + Auto Format. How are we supposed to follow code that jumps all over the place like this?

            if(tenths_u < 0) {
              seconds_u--;
              tenths_u = 9;
              updateDisplayUnderTen();
            }
              
   if (digitalRead(A2) == LOW){   //Button to stop clock - delay it actually
     delay(10000);
      }
            if(tenths_u == 0 && seconds_u == 0 && seconds_t == 0 && minutes_u == 0 && minutes_t == 0)  {   //time up operation - flashing 00:00
        timeUp();

In delaySet(), you have an infinite loop with no way to exit it. countDown() should NOT be called from delaySet(). Instead, where the call to countDown() is should be a break statement. That will exit the infinite loop in delaySet(), allowing delaySet() to end, and control to return to loop.

That is where/when you should call countDown().

The countDown() function contains another infinite loop. There is no way that I can see (admittedly, the code structure makes it hard to see, so I might have missed it) to stop the "timer". There is code to suspend the "timer" for 10 seconds, but it's hard to see where that fits in the while loop.

I would suggest that you split the tasks into three part:

  1. displaying from a buffer: run this part from a timer interrupt so it is done transparently. All you need is to stuff font information into the buffer;
  2. time keeping: put this in another timer interrupt to keep track of count-down time.
  3. processing: do this in the main loop. Process buttons, process time keeping and convert it to the right font info into the display buffer. For example, if the time is less than 10 minutes, put 'blank' into the 1st char of the buffer, '8' into the 2nd char of the buffer, '3' into the 3rd and '2' into the fourth to display ' 8:32'. To pause, you just need to pause the time keeping function.

First of all, I cribbed this code from someone else so I can't take credit. However, I can assume blame for aggravating any of its weaknesses. Basically I took a countdown sketch and attempted to shoehorn my application into it. I'm new to programming and electronics but I have above average technical skills so I'm hoping to tackle this with guidance.

PaulS:
It would help if you put each { on a new line, use Tools + Auto Format.

I've never known about this feature but I'll do this when I get home later to clean things up.

PaulS:
In delaySet(), you have an infinite loop with no way to exit it. countDown() should NOT be called from delaySet(). Instead, where the call to countDown() is should be a break statement. That will exit the infinite loop in delaySet(), allowing delaySet() to end, and control to return to loop.

I am familliar with the principals of break statements but have not written any. I will try my hand at putting one together and give it another shot.

PaulS:
The countDown() function contains another infinite loop. There is no way that I can see (admittedly, the code structure makes it hard to see, so I might have missed it) to stop the "timer". There is code to suspend the "timer" for 10 seconds, but it's hard to see where that fits in the while loop.

These infinite loops are likely preventing my attempts to stop the timer. I just pasted the code in the sketch at various places and crossed my fingers (this should be an indication of the programming expertise you are dealing with).

Dhenry,
I have to do some research to understand your suggestions. Are there example sketches you could point me to that illustrate these principals? I'm sure I've seen some but I'm still learning to understand code.

Thanks, Steve

Are there example sketches you could point me to that illustrate these principals?

No sketch that I have seen of. But tons of code pieces / libraries in the avr world: those written for gcc-avr / winavr should compile without changes.

SteveOr:
Dhenry,
I have to do some research to understand your suggestions. Are there example sketches you could point me to that illustrate these principals? I'm sure I've seen some but I'm still learning to understand code.

Dhenry is quite keen on using interrupts, but in my opinion they are not needed here and just make the solution needlessly complicated.

All you need is the technique shown in 'blink without delay' to trigger some clock processing at regular intervals. Define global variable holding the hours, minutes, seconds, tenths remaining. Your clock processing code decrements the timer values and displays them.

If you want a button to pause and resume the countdown then you just need to add a global flag indicating whether the clock is currently running, and poll the switch input to decide when to change it between 'running' and 'paused'. In your clock processing code you skip the 'decrement and redisplay' code if the clock is 'paused'. This is all straight forward.

I did the same with a MAX7221 for my fencing scoring machine.
I check every 1/4 second to make the colon flash on/off, and every 4 checks update the seconds and other digits as needed.
The rest of the time I am checking for button presses, RF commands, etc.
Easy to change that for 1/10 second checks, still plenty of time to read slow (by comparison) button presses.

I've been experimenting with breaks and my while loops and cleaned up my sketch a bit but it is very likely my timer and display functions could be simplified further. As I mentioned above I have five timer display situations depending on the time elapsed. Instead of running a bunch of while statements would a switch statement be more clean and efficient?

Something like this for example:

switch (FUNCTION X) //some sort of clock signal every 100 microseconds
{
	case 0:
		if(minutes_t > 0); //Time is 20:00-10:00
		updateDisplay ();  //Display 00:00 format
	case 1:
		if(minutes_t <= 0 && minutes_u >= 1)  //Time is 9:59-1:00
		updateDisplayUnderTenMinutes();  //Display 0:00 format
	case 2: 
		if(minutes_t == 0 && minutes_u == 0 && seconds_t >= 1); //Time is :59-:10
		updateDisplayUnderOneMinute();  //Display 00.0 format
	case 3:
		if(seconds_t == 0);  //Time is :09.9-:00.0
		updateDisplayUnderTenSeconds();  //Display 0.0 format
	case 4:
		if(tenths_u == 0 && seconds_u == 0 && seconds_t == 0 && minutes_u == 0 && minutes_t == 0) //time up operation - flashing 00:00 
		timeUp();
         
}

I just need to figure out how to do FUNCTION X and integrate buttons and other controls. I'd prefer to use the Arduino's internal clock rather than an external clock though.

A guy who made a similar scoreboard used a CD4021 shift register and an external clock for various controls such as time out, reset, advancing score and such. I've steered away from his design because his sketch was hard to understand at my current stage. Should I lean in that direction?

No, your approach fine. I did similar, recoginizing the various situations and rolling over the upper digits as needed:

from 10:00 to 9:59
from 0 x :00 to 0 (x-1):59
from 00:x0 to 00:(x-1) 9
from 00:01 to 00:00 for end of period buzzer

You look to have similar situation, altho I you were displaying 1/10s and I only had seconds, the logic is similar.

The buttons & stuff can be read during the intervals when it is Not time to decrement the 1/10th second counter:

if (100mS elapsed){
// do the digit changing & display updating stuff via MAX7219 SPI.transfer commands
// 
}
// rest of the time, read buttons, do other stuff - there is a lot to processing time left to do that stuff.

Lots of things will happen when time is not running - incrementing the score, the period counter, buzzing the score indicator, etc.

Should I lean in that direction?

That approach is a lot easier to code and provides better brightness - as the display is essentially is static.

SteveOr:
Something like this for example:

I don't see what the switch statement is achieving there. All you need is to apply range checks to the time value to decide which display format to use, followed by a call to the corresponding output function. You already have the range checks in the code there, and if you throw away the switch and simply run through the sequence of if / else if statements it should do what you want.

PeterH:

SteveOr:
Something like this for example:

I don't see what the switch statement is achieving there.

What it was acheiving for me was making the structure simpler for me to understand. Yet it lacks a time function which might be the complicated part. As some one new to all this I'm still trying to figure out how to meld all these functions to produce a coherent system.

PeterH:
All you need is to apply range checks to the time value to decide which display format to use, followed by a call to the corresponding output function. You already have the range checks in the code there, and if you throw away the switch and simply run through the sequence of if / else if statements it should do what you want.

OK. I'll work on my if/else functions some more to figure this out. Last night I tried different approaches using my limited skills and I think I bruised my brain.

dhenry:

Should I lean in that direction?

That approach is a lot easier to code and provides better brightness - as the display is essentially is static.

The sketch I referred to only confused me (more so than the cluster I'm dealing with now). Since I don't have a CD4021 this is a last resort. Plus my display is plenty bright. In fact I had to set the brightness at the lowest level to prevent burning my retinas.

CrossRoads:
No, your approach fine. I did similar, recoginizing the various situations and rolling over the upper digits as needed:...

The buttons & stuff can be read during the intervals when it is Not time to decrement the 1/10th second counter:

if (100mS elapsed){

// do the digit changing & display updating stuff via MAX7219 SPI.transfer commands
//
}
// rest of the time, read buttons, do other stuff - there is a lot to processing time left to do that stuff.



Lots of things will happen when time is not running - incrementing the score, the period counter, buzzing the score indicator, etc.

So did use use all the millis() if statements to do your timeline or some other timing method?

Thanks everyone,
Steve

I use millis, and several if( ) combinations - they are tested in a select sequence so that only 1 can be true.

// ***********************************************************************************************
// Loop here endlessly, checking if time or score needs updating, if wireless message came in, 
// if touch lights are on
// ***********************************************************************************************
void loop()
{
  // check if time needs updating
  if ((time_running == 1) || (time_running == 2))  // fencing time is counting down or delay time is counting down
  {
    unsigned long currentMillis = millis();  // see how long its been

    if (currentMillis - previousMillis >= interval) // more than our quarter second interval?
    {
      // save the last time we okayed time updates 
      previousMillis = currentMillis; 
      quarter_interval = quarter_interval+1;
      // cycle the colon state
      if (colon == 0x80)
      {
        colon = 0x00;
      }
      else
      {
        colon = 0x80;
      }
      update_time = 1;   //  enable time display to be updated

      if (quarter_interval == 4) // we hit the one second update time
      {
        quarter_interval = 0;
        // update the time digits
        // cases: 
        // 0:01, final second - stop time, disable touch lights, sound buzzer
        // Tens of seconds rollover: time = x:50, x:40, x:30, x:20, x:10: decrement tens of seconds, rollover seconds to 9
        // Minutes rollover: time = 9:00, 8:00, etc. 2:00, 1:00: decrement ones of minutes, rollover tens of 
        // seconds to 5, ones of seconds to 9
          // 10:00: Roll all the digits over
        // otherwise: just roll over the seconds

        // Case: Final Second
        if ((minutes_ones == 0) && (seconds_tens == 0) && (seconds_ones == 1)) // don't need minutes_tens, can't have 10:01
        {
          touchlight_enable = 0;          // score touches are locked out
          time_running = 0;  // stop time running
          seconds_ones = 0;  // clear the last second
          updated = 1;  // fake a Case complete flag
          buzzer = 1;                     // add a buzzer for this: end of time buzzer sounds       

          if ((time_running == 1) && (period>0) && (period<9)) { //  update period counter if was fencing time, not 1:00 or 10:00 break
            period = period +1;
            update_period=1;  // enable period display to be updated
          }
        }  // end of  if final second

        // Case: x:50, x:40, x:30, x:20, x:10
        if ((seconds_tens >0) && (seconds_ones == 0))  // case for the last tens of seconds 
        {
          seconds_tens = seconds_tens - 1;  // decrement the tens
          seconds_ones = 9;  // rollover the ones
          updated = 1;
        }  // end of if 10 of seconds rollover

        // Case: 9:00, 8:00, etc 2:00, 1:00
        if ((minutes_ones > 0) && (seconds_tens == 0) && (seconds_ones == 0)) // case for the last ones of minutes
        { 
          minutes_tens = 0x00;  //
          minutes_ones = minutes_ones - 1;  // decrement the minutes
          seconds_tens = 5;  // rollover the tens of seconds;
          seconds_ones = 9;  // rollover the ones of seconds;
          updated = 1;
        } // end of if minutes rollover

        // Case: starting from 10:00
        if (minutes_tens == 0x01)  // roll over all digits
        {
          minutes_tens = 0x00;  // rollover the tens of minutes
          minutes_ones = 9;  // rollover the ones of mints;
          seconds_tens = 5;  // rollover the tens of seconds;
          seconds_ones = 9;  // rollover the ones of seconds; 
          updated = 1;
        } // end of if 10:00 rollover

        // General Case: just decrement the seconds
        if (updated == 0)  // nothing else updated - but don't decrement if = 0.
        {
          seconds_ones = seconds_ones - 1;
        }
        updated = 0;  // reset for next pass thru
      } // end of if quarter_interval
    } // end of reaching our interval
  } // end of if time_running
  else
  {
    update_time = 0;   // no time update this time around - probably don't need this
  }

CrossRoads:

...

if ((time_running == 1) || (time_running == 2))  // fencing time is counting down or delay time is counting down
 {
   ...

Is this where your button press is a factor? If so, how do you make your sketch send these values?

No, there is another area for keypresses. Once a keypress happens, that part turns these flags off if they were on. This section then gets passed over until something happens to turn one of these on again.

Then how does the "if ((time_running == 1) || (time_running == 2))" work in your sketch?