Pausing and resume timer with millis()

Hello, I am a bit shame because I have this issue that I want to solve.

I'm basically measuring current time with millis() to use it as a stopwatch. But I want to find a way to see how can I measure time and be able to pause the timer that was running with it's progress and resume it.

There's any body that has an example or something similar to this use case?

When the timing starts you store a timestamp a variable. Perhaps it's named startTimestamp. When the timing is paused you store another timestamp in another variable. Perhaps its named pausedTimestamp. When the timing resumes you increase startTimestamp by the difference between millis() and pausedTimestamp.

Hey, thanks for your reply!

I currently have an approach, however, it's not working at all.

#define startButton D6
#define stopButton D2
#define pauseButton A6
unsigned long startTime, elapsedTime, totalElapsedTime, currentTime;
boolean running = true;

unsigned long lastDebounceTimeStart = 0;  // the last time the output pin was toggled
unsigned long lastDebounceTimePause = 0;
unsigned long debounceDelay = 300;    // the debounce time; increase if the output 
int ledState = HIGH;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin


void setup() {
	totalElapsedTime = 0;
	Serial.begin(9600);
	pinMode(startButton, INPUT_PULLUP);
    attachInterrupt(startButton, startRead, FALLING);
    pinMode(stopButton, INPUT_PULLUP);
    attachInterrupt(stopButton, pauseRead, FALLING);
}

void loop()
{
	currentTime = millis();


	if (running)
	{
		elapsedTime = currentTime - startTime + totalElapsedTime ;
	}
	//elapsedTime = currentTime - startTime;
	Serial.print("running status: ");
	Serial.print(running);
	Serial.print(" elapsed time: ");
	Serial.println(elapsedTime/ 1000);
}

void startRead(void) {

  	if ((millis() - lastDebounceTimeStart) > debounceDelay) {
		if (running)
    	{
    	    startTime = currentTime;
    	    //running = true;
    	}
		lastDebounceTimeStart = millis();
    }
}

void pauseRead(void) {

  	if ((millis() - lastDebounceTimePause) > debounceDelay) {
		//elapsedTime = currentTime - startTime;
		totalElapsedTime = currentTime - startTime;
        //totalElapsedTime += elapsedTime;
        //totalElapsedTime += elapsedTime;
		//elapsedTime += totalElapsedTime;
        running = !running;
		lastDebounceTimePause = millis();
    }
}

What is actually doing is to add the totalElapsedTime in the current time running, so basically is not pausing.

Think about what you need to do for the following sequence of events...

Start
Pause
Unpause
Pause
Unpause

What is the time it should show? Obviously the total time minus the two pauses. But you don't want to have to store an unlimited number of pauses. You might run out of memory if there is a hundred pauses to record.

So change the "obviously" to "the total time since start minus the total time paused." Does that make more sense?

But that doesn't simplify the problem enough. You still have the problem of adding the pauses together. Worse because during a pause you have to add the "current" pause time even while the button is not held down.

I would turn it around entirely. For each millisecond that we are not paused, add that to the timer. But the Arduino does not always count single milliseconds. Sometimes it jumps by 2. Your program is going to grow so it won't always check the timer every millisecond.

So a more-complete description of "for each" would be: Really really often, look at millis; if one or more milliseconds have passed since we last looked and we are not paused, add that number to our elapsed time counter.

MorganS, I don't like to make it inaccurate but adding every millisecond that has passed. The moment you write that, you should know that there is something wrong with it.

MorganS:
"the total time since start minus the total time paused."

Now that is something I can work with, because it is easy to determine how long the pause was. Add every pause to a total, and use only the events of button presses.
I came up with this:

// Note: using pin 3 for the pause button.

enum
{
  STATE_INIT,
  STATE_WAIT_FOR_START,
  STATE_RUNNING,
  STATE_PAUSED,
  STATE_STOPPED,
} state;

const int startButtonPin = 6;      // LOW when pressed
const int stopButtonPin  = 2;      // LOW when pressed
const int pauseButtonPin = 3;      // LOW when pressed

int last_startState = HIGH;        // default HIGH = not pressed
int last_stopState  = HIGH;
int last_pauseState = HIGH;

unsigned long startMillis;         // moment of start
unsigned long pauseMillis;         // moment a pause has begun
unsigned long stopMillis;          // moment of stop
unsigned long totalElapsedPauseMillis; // total combined pause time

void setup() 
{
  Serial.begin(9600);

  pinMode(startButtonPin, INPUT_PULLUP);
  pinMode(stopButtonPin,  INPUT_PULLUP);
  pinMode(pauseButtonPin, INPUT_PULLUP);
  
  state = STATE_INIT;
}

void loop()
{
  unsigned long currentMillis = millis();
  
  bool start = false;
  bool stop  = false;
  bool pause = false;

  // --------------------------------------------------
  // Gather all the information from button and sensors.
  // --------------------------------------------------
  int startState = digitalRead( startButtonPin);
  int stopState  = digitalRead( stopButtonPin);
  int pauseState = digitalRead( pauseButtonPin);
  
  // --------------------------------------------------
  // State Change Detection
  // --------------------------------------------------
  if( startState != last_startState)
  {
    if( startState == LOW)    // LOW is pressed/active
    {
      start = true;
    }
    last_startState = startState;
  }

  if( stopState != last_stopState)
  {
    if( stopState == LOW)     // LOW is pressed/active
    {
      stop = true;
    }
    last_stopState = stopState;
  }

  if( pauseState != last_pauseState)
  {
    if( pauseState == LOW)     // LOW is pressed/active
    {
      pause = true;
    }
    last_pauseState = pauseState;
  }
  
  // --------------------------------------------------
  // Finite State Machine
  // --------------------------------------------------
  switch( state)
  {
    case STATE_INIT:
      totalElapsedPauseMillis = 0;
      Serial.println(F( "Ready"));
      state = STATE_WAIT_FOR_START;
      break;
    case STATE_WAIT_FOR_START:
      if( start)
      {
        startMillis = currentMillis;
        Serial.println(F( "Started"));
        state = STATE_RUNNING;
      }
      break;
    case STATE_RUNNING:
      if( stop)  // when more buttons are pressed, stop is more important
      {
        stopMillis = currentMillis;
        state = STATE_STOPPED;
      }
      else if( pause)
      {
        pauseMillis = currentMillis;
        Serial.println(F( "Paused"));
        state = STATE_PAUSED;
      }
      break;
    case STATE_PAUSED:
      if( stop)  // when more buttons are pressed, stop is more important
      {
        stopMillis = currentMillis;
        unsigned long elapsedThisPause = currentMillis - pauseMillis;
        totalElapsedPauseMillis += elapsedThisPause;
        state = STATE_STOPPED;
      }
      else if( start || pause)
      {
        // Continue if either start or pause is pressed
        // Calculate the time of the pause and add that
        // to the total of the paused time.
        unsigned long elapsedThisPause = currentMillis - pauseMillis;
        totalElapsedPauseMillis += elapsedThisPause;
        Serial.println(F( "Continued"));
        state = STATE_RUNNING;
      }
      break;
    case STATE_STOPPED:
      unsigned long elapsedTime = stopMillis - startMillis - totalElapsedPauseMillis;

      Serial.print(F( "Stopped. "));
      Serial.print(F( "Stopwatch Time: "));
      Serial.print( elapsedTime);
      Serial.print(F( " (Paused: "));
      Serial.print( totalElapsedPauseMillis);
      
      unsigned long total = stopMillis - startMillis;
      Serial.print(F( ", Total: "));
      Serial.print( total);
      Serial.print(F( ")"));
    
      Serial.println();
        
      state = STATE_INIT;
      break;
  }
}

@fabianbambam this can not be solved by adding an extra if-statement to your sketch somewhere. To avoid a spaghetti of if-statements, a Finite State Machine can be useful.
Try my example and check if it does what you want.
I have removed the debouncing to make it simpler.
The sketch can be smaller by using the Bounce2 library (it has debouncing and the State Change Detection in it).
The Arduino Uno does not have pin A6, so I used pin 3 for the pause button.
Why are you not using the Arduino IDE ?

MorganS, I don't like to make it inaccurate but adding every millisecond that has passed. The moment you write that, you should know that there is something wrong with it.

Which is why I added the last paragraph. Show me how that is inaccurate.

Your solution does not calculate a "running" time. It can only show the correct time after stopping.

MorganS:
Which is why I added the last paragraph. Show me how that is inaccurate.

Your solution does not calculate a “running” time. It can only show the correct time after stopping.

Every solution that I can think of is inaccurate as soon as something takes more than a millisecond (I2C bus, microSD card, DS18B20, and so on). You can make a sketch that uses that, so fabianbambam can see different approaches :wink:
I did not think about showing the running time. Well, it is a stopwatch :o

With my sketch, the running time has to be calculated each time. That is no problem. I have added a millis timer to be able to determine how often the update should be displayed.

// Note: using pin 3 for the pause button.

enum
{
  STATE_INIT,
  STATE_WAIT_FOR_START,
  STATE_RUNNING,
  STATE_PAUSED,
  STATE_STOPPED,
} state;

const int startButtonPin = 6;      // LOW when pressed
const int stopButtonPin  = 2;      // LOW when pressed
const int pauseButtonPin = 3;      // LOW when pressed

int last_startState = HIGH;        // default HIGH = not pressed
int last_stopState  = HIGH;
int last_pauseState = HIGH;

unsigned long startMillis;         // moment of start
unsigned long pauseMillis;         // moment a pause has begun
unsigned long stopMillis;          // moment of stop
unsigned long totalElapsedPauseMillis; // total combined pause time

unsigned long previousMillisUpdate; // for update running time
const unsigned long intervalUpdate = 200;  // update every 200 ms


void setup() 
{
  Serial.begin(9600);

  pinMode(startButtonPin, INPUT_PULLUP);
  pinMode(stopButtonPin,  INPUT_PULLUP);
  pinMode(pauseButtonPin, INPUT_PULLUP);
  
  state = STATE_INIT;
}

void loop()
{
  unsigned long currentMillis = millis();
  
  bool start = false;
  bool stop  = false;
  bool pause = false;

  // --------------------------------------------------
  // Gather all the information from button and sensors.
  // --------------------------------------------------
  int startState = digitalRead( startButtonPin);
  int stopState  = digitalRead( stopButtonPin);
  int pauseState = digitalRead( pauseButtonPin);
  
  // --------------------------------------------------
  // State Change Detection
  // --------------------------------------------------
  if( startState != last_startState)
  {
    if( startState == LOW)    // LOW is pressed/active
    {
      start = true;
    }
    last_startState = startState;
  }

  if( stopState != last_stopState)
  {
    if( stopState == LOW)     // LOW is pressed/active
    {
      stop = true;
    }
    last_stopState = stopState;
  }

  if( pauseState != last_pauseState)
  {
    if( pauseState == LOW)     // LOW is pressed/active
    {
      pause = true;
    }
    last_pauseState = pauseState;
  }
  
  // --------------------------------------------------
  // Finite State Machine
  // --------------------------------------------------
  switch( state)
  {
    case STATE_INIT:
      totalElapsedPauseMillis = 0;
      Serial.println(F( "Ready"));
      state = STATE_WAIT_FOR_START;
      break;
    case STATE_WAIT_FOR_START:
      if( start)
      {
        startMillis = currentMillis;
        Serial.println(F( "Started"));
        state = STATE_RUNNING;
      }
      break;
    case STATE_RUNNING:
      if( stop)  // when more buttons are pressed, stop is more important
      {
        stopMillis = currentMillis;
        state = STATE_STOPPED;
      }
      else if( pause)
      {
        pauseMillis = currentMillis;
        Serial.println(F( "Paused"));
        state = STATE_PAUSED;
      }
      break;
    case STATE_PAUSED:
      if( stop)  // when more buttons are pressed, stop is more important
      {
        stopMillis = currentMillis;
        unsigned long elapsedThisPause = currentMillis - pauseMillis;
        totalElapsedPauseMillis += elapsedThisPause;
        state = STATE_STOPPED;
      }
      else if( start || pause)
      {
        // Continue if either start or pause is pressed
        // Calculate the time of the pause and add that
        // to the total of the paused time.
        unsigned long elapsedThisPause = currentMillis - pauseMillis;
        totalElapsedPauseMillis += elapsedThisPause;
        Serial.println(F( "Continued"));
        state = STATE_RUNNING;
      }
      break;
    case STATE_STOPPED:
      unsigned long elapsedTime = stopMillis - startMillis - totalElapsedPauseMillis;

      Serial.print(F( "Stopped. "));
      Serial.print(F( "Stopwatch Time: "));
      Serial.print( elapsedTime);
      Serial.print(F( " (Paused: "));
      Serial.print( totalElapsedPauseMillis);
      
      unsigned long total = stopMillis - startMillis;
      Serial.print(F( ", Total: "));
      Serial.print( total);
      Serial.print(F( ")"));
    
      Serial.println();
        
      state = STATE_INIT;
      break;
  }
  
  
  if( state == STATE_RUNNING || state == STATE_PAUSED)
  {
    if( currentMillis - previousMillisUpdate >= intervalUpdate)
    {
      previousMillisUpdate = currentMillis;
      
      unsigned long elapsedTimeSoFar;
      
      // During the pause, the currentMillis continues.
      // Therefor a different calculation is used during the pause.
      // The total of paused time until this new pause is known.
      // Also the start of the new pause is known. 
      // So the stopwatch time can be calulated without problems of an increasing
      // millis value.
      if( state == STATE_PAUSED)
      {
        elapsedTimeSoFar = pauseMillis - startMillis - totalElapsedPauseMillis;
      }
      else
      {
        elapsedTimeSoFar = currentMillis - startMillis - totalElapsedPauseMillis;
      }
      Serial.println( elapsedTimeSoFar);
    }
  }
}

MorganS:
if one or more milliseconds have passed since we last looked and we are not paused, add that number to our elapsed time counter.