How to add a countdown timer with pause feature?

Hi, I am an arduino noob and I am currently trying to program a countdown timer for the planking exercise using Arduino Uno, HC-SR04 ultrasonic sensor and a I2C LCD.

Currently, I am facing a problem whereby I am clueless on how to implement a pause feature when the planking posture is wrong. Any advices on how I can implement it?

Also, credits to the owner from mechatrofice for the countdown timer code.

Here is my code:

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);
const int trigPin = 8; //set pins for the ultrasonic sensor, button, buzzer and LCD
const int echoPin = 9;
const int buttonPin = 2;
const int buzzer = 13;
long minute = 1, second = 0;//second
long countdown_time = (minute * 60) + second;

long signalDuration; //defining signalDuration as long as it can potentially store a large number
int plankDist;
int buttonState = 0;

void setup()
{
  Serial.begin(9600); // begin in 9600 baud
  pinMode(trigPin, OUTPUT); //set pin modes
  pinMode(echoPin, INPUT);
  pinMode(buttonPin, INPUT);
  pinMode(buzzer, OUTPUT);
  lcd.init();
  lcd.backlight();
  lcd.begin (16, 2);
}

void timer()
{
  long countdowntime_seconds = countdown_time - (millis() / 1000);
  if (countdowntime_seconds >= 0) {
    long countdown_minute = ((countdowntime_seconds / 60) % 60);
    long countdown_sec = countdowntime_seconds % 60;
    lcd.setCursor(6, 1);
    if (countdown_minute < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_minute);
    lcd.print(":");
    if (countdown_sec < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_sec);

    if (countdowntime_seconds == 0) {
      tone(buzzer, 1000);
      delay(200);
      noTone(buzzer);     // Stop sound...
      delay(100);
      tone(buzzer, 1000);
      delay(200);
      noTone(buzzer);     // Stop sound...
      delay(100);
    }
    delay(500);
  }
}

void loop()
{
  digitalWrite(trigPin, LOW); // send out an ultra sonic sound for 5 microseconds and measure the time it took for the sound to go from the trigpin to the echo pin
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  signalDuration = pulseIn(echoPin, HIGH);
  plankDist = signalDuration * 0.034 / 2; //convert the time the signal took to travel to distance in cm

  if (plankDist >= 10 && plankDist <= 40) //proper posture within the distance range of 10cm to 40cm, timer continues counting
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Maintain Posture");
    lcd.setCursor(0, 1);
    lcd.print("Time:");
    lcd.setCursor(10, 1);
    timer();                           //void timer() displays the time here
  }

  else                                 // if outside of the above stated range of 10cm to 40cm, timer pauses as posture is improper
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Adjust Your");
    lcd.setCursor(0, 1);
    lcd.print("Position");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Timer Paused");
    lcd.setCursor(0, 1);
    lcd.print("Time:");

    delay(2000);
  }
}

The easier you make it to read and copy the code the more likely it is that you will get help

Please follow the advice given in the link below when posting code , use code tags and post the code here

Okay, I have updated my code. Thanks for alerting me! :slight_smile:

Thanks. See how much easier it is to read and copy and it also stops the forum software misinterpreting the code as formatting tags under some circumstances

You already have a couple of 2 second pauses/delay()s in your else clause. If that's not what you want you need to say exactly when it should pause and for how long. Or perhaps what condition will cause it to start it up again after the pause.

Steve

This is always counting down from the time the Arduino started (millis() == 0). To pause, you have to remember how far you had counted when you paused, and how long it has been since you re-started:

 void pause()
{
  if (TimerRunning)
  {
    TimeRemaining = timeRemaining - (millis() - TimeStarted);
    TimerRunning = false;
  }
}

void resume()
{
  if (!TimerRunning)
  {
    TimeStarted = millis();
    TimerRunning = true;
  }
}

  long countdowntime_seconds;
  if (TimerRunning)
  {
     countdowntime_seconds = (TimeRemaining - (millis() - TimeStarted)) / 1000;
  }
  else
  {
     countdowntime_seconds = TimeRemaining / 1000;
  }
1 Like

Check out my tutorial on How to write Timers and Delays in Arduino which provides a millisDelay class that handle the TimerRunning stuff for you
Under Other millisDelay Library Functions you will find a Pausing Delay example FreezeDelay.ino
which underneath is similar to the code above in #6

1 Like

Alternate approach.
Sloppy.

If plankGood
PlankSeconds++ // increments
Delay(1000)

If plankBad
Serialprint (bad)

If plankSeconds >= timeAllowed
Serialprint ("complete")

==================

To break that down.
If the feedback shows good.
Then increment the counter by 1.
Pause 1 second.
That makes the counter equal 1 second.

In the feedback is not good. No increment no added time.

Once the reading is back in range.
Start incrementing from where you left off.

Check each scan to see if your total elapsed time has reached your limit.

Very clunky and not recomended as a sketch.
But it shows one way not allow timer to run if the feedback is out of range.

1 Like

Hi Mr Johnwasser, thank you for your kind input, however, after countless time spent on integrating the code that you have provided into my own code, I still could not achieve the results.

I know this would be asking for too much but is it possible for you to aid me by providing the integrated code (the code that you have provided and my own code) so that I could better understand what went wrong and how would the proper flow of codes be like? :sweat: :sweat:

Thank you so much and sorry for the inconvenience caused.

Hi Dave, thank you for your input :blush: . This seems like a count up timer which I might add as an additional feature as well. Thank you once again for the idea!

Hi Steve, those delays are for the LCD. The conditions that I want is that if the planking position is correct, the timer will continue to count. If the position is incorrect, I want the timer to pause and resume once the position returns to the correct position.

delay() is a horrible way to do anything.

the BWD idea is rather long but extremely more useful.

a bad excuse for an alternative.

delay(1)
delayTimer++

if delayTimer >= 1000
then do that thing

this is a crude and not recommended way to count to something, sort of.. about, close to.... 1 second.

other things take time so it is not accurate, it will always result in more, not less time.
But for most things we do, being off by 10% is not going to effect the outcome.

in the end, as you skills advance, the BWD becomes second nature.

Here you go. Now you just have to call pause() when you want to pause the count and and resume() when you want the count to continue.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);
const int trigPin = 8; //set pins for the ultrasonic sensor, button, buzzer and LCD
const int echoPin = 9;
const int buttonPin = 2;
const int buzzer = 13;
long minute = 1, second = 0;//second
long countdown_time = (minute * 60) + second;

bool TimerRunning = true;
unsigned long TimeRemaining = countdown_time * 1000ul;
unsigned long TimeStarted = 0;


long signalDuration; //defining signalDuration as long as it can potentially store a large number
int plankDist;
int buttonState = 0;

void setup()
{
  Serial.begin(9600); // begin in 9600 baud
  pinMode(trigPin, OUTPUT); //set pin modes
  pinMode(echoPin, INPUT);
  pinMode(buttonPin, INPUT);
  pinMode(buzzer, OUTPUT);
  lcd.init();
  lcd.backlight();
  lcd.begin (16, 2);
}

void pause()
{
  if (TimerRunning)
  {
    TimeRemaining = TimeRemaining - (millis() - TimeStarted);
    TimerRunning = false;
  }
}

void resume()
{
  if (!TimerRunning)
  {
    TimeStarted = millis();
    TimerRunning = true;
  }
}

void timer()
{
  long countdowntime_seconds;

  if (TimerRunning)
  {
    countdowntime_seconds = (TimeRemaining - (millis() - TimeStarted)) / 1000;
  }
  else
  {
    countdowntime_seconds = TimeRemaining / 1000;
  }

  if (countdowntime_seconds >= 0)
  {
    long countdown_minute = ((countdowntime_seconds / 60) % 60);
    long countdown_sec = countdowntime_seconds % 60;
    lcd.setCursor(6, 1);
    if (countdown_minute < 10)
    {
      lcd.print("0");
    }
    lcd.print(countdown_minute);
    lcd.print(":");
    if (countdown_sec < 10)
    {
      lcd.print("0");
    }
    lcd.print(countdown_sec);

    if (countdowntime_seconds == 0)
    {
      tone(buzzer, 1000);
      delay(200);
      noTone(buzzer);     // Stop sound...
      delay(100);
      tone(buzzer, 1000);
      delay(200);
      noTone(buzzer);     // Stop sound...
      delay(100);
    }
    delay(500);
  }
}

void loop()
{
  digitalWrite(trigPin, LOW); // send out an ultra sonic sound for 5 microseconds and measure the time it took for the sound to go from the trigpin to the echo pin
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  signalDuration = pulseIn(echoPin, HIGH);
  plankDist = signalDuration * 0.034 / 2; //convert the time the signal took to travel to distance in cm

  if (plankDist >= 10 && plankDist <= 40) //proper posture within the distance range of 10cm to 40cm, timer continues counting
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Maintain Posture");
    lcd.setCursor(0, 1);
    lcd.print("Time:");
    lcd.setCursor(10, 1);
    timer();                           //void timer() displays the time here
  }
  else                                 // if outside of the above stated range of 10cm to 40cm, timer pauses as posture is improper
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Adjust Your");
    lcd.setCursor(0, 1);
    lcd.print("Position");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Timer Paused");
    lcd.setCursor(0, 1);
    lcd.print("Time:");

    delay(2000);
  }
}
1 Like

Thank you sooo much Mr Johnwasser, this really helped me out a bunch :blush:.

I have placed the pause() function under the "if" statement whereby the position is incorrect. However, do you have any recommendation on where the resume() function should be placed once the planking position returns to the correct position?

 if (plankDist >= 10 && plankDist <= 40) //proper posture within the distance range of 10cm to 40cm, timer continues counting
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Maintain Posture");
    lcd.setCursor(0, 1);
    lcd.print("Time:");
    lcd.setCursor(10, 1);
    timer();                           //void timer() displays the countdown time here
  }
  else                                 // if outside of the above stated range of 10cm to 40cm, timer pauses as posture is improper
  {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Adjust Your");
    lcd.setCursor(0, 1);
    lcd.print("Position");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Timer Paused");
    lcd.setCursor(0, 1);
    lcd.print("Time:");
    pause();               
    delay(2000);
  }
}

The pause() function does nothing if the timer is already paused and the resume() function does nothing if the timer is already running. You can call pause() whenever the form is wrong and call resume() whenever the form is not wrong.

Okay got it!

Unfortunately, I have encountered two problems:

  1. Timer always starts at 00:57 instead of the intended 01:00
  2. Once timer reaches 00:00, it then restarts at 02:47

Do you have any idea on why is this happening? Could it be the delays that are affecting the timer or could it be the time it took for everything to start up and display on the LCD which caused it to start with a 3 second deficit? Also, I realise that the second issue is due to the millis() which starts counting ever since the arduino board starts running, so is there anyway to just stop it after 1 minute is up?

Thank you in advance :grinning_face_with_smiling_eyes:

These are the codes that have delay() in them:

void timer()
{
  long countdowntime_seconds;

  if (TimerRunning)
  {
    countdowntime_seconds = (TimeRemaining - (millis() - TimeStarted)) / 1000;
  }
  else
  {
    countdowntime_seconds = TimeRemaining / 1000;
  }

  if (countdowntime_seconds >= 0)
  {
    long countdown_minute = ((countdowntime_seconds / 60) % 60);
    long countdown_sec = countdowntime_seconds % 60;
    lcd.setCursor(6, 1);
    if (countdown_minute < 10)
    {
      lcd.print("0");
    }
    lcd.print(countdown_minute);
    lcd.print(":");
    if (countdown_sec < 10)
    {
      lcd.print("0");
    }
    lcd.print(countdown_sec);

    if (countdowntime_seconds == 0)
    {
      tone(buzzer, 1000);
      delay(200);
      noTone(buzzer);     // Stop sound...
      delay(100);
      tone(buzzer, 1000);
      delay(200);
      noTone(buzzer);     // Stop sound...
      delay(100);
    }
    delay(500);
  }
}

void loop()
{
  digitalWrite(trigPin, LOW); // send out an ultra sonic sound for 5 microseconds and measure the time it took for the sound to go from the trigpin to the echo pin
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  signalDuration = pulseIn(echoPin, HIGH);
  plankDist = signalDuration * 0.034 / 2; //convert the time the signal took to travel to distance in cm

  if (plankDist >= 10 && plankDist <= 40) //proper posture within the distance range of 10cm to 40cm, timer continues counting
  {
    lcd.setCursor(0, 0);
    lcd.print("Maintain Posture");
    lcd.setCursor(0, 1);
    lcd.print("Time:");
    timer();                           //void timer() displays the countdown time here
    resume();
  }
  else                                 // if outside of the above stated range of 10cm to 40cm, timer pauses as posture is improper
  {
    pause();
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Adjust Your");
    lcd.setCursor(0, 1);
    lcd.print("Position");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Timer Paused");
    lcd.setCursor(0, 1);
    lcd.print("Time:");
    delay(2000);
 
  }
  
}

Forgive me for jumping in, but have you thought about trying to debug this yourself? It will massively improve your understanding of the program.

When you first get to loop() you take a distance measurement. That can take a full second if no echo is detected (default timeout on pulseIn() is 1 million microseconds). If your posture is not right you will display a message and delay another two seconds. That could explain why the timer is already down to 57 seconds by the time you display it.

You can start the time in pause() mode by initializing 'TimerRunning' to false instead of true.

You don't pause the timer when it reaches 0 so the timer keeps running and the calculated 'countdowntime_seconds' becomes negative. Somehow that gets displayed as 2:47.

1 Like

Hi Mr SteveThackery, yes I have tried debugging by trail and error. However I have little knowledge of arduino coding as such that was the best i could do :sweat: Thanks for the advice though!

Well, "trial and error" is no way to debug code! :grinning:

You should try to understand what each step of the code does, and then insert Serial.print() statements at relevant points to check that it is doing what you think it should do.

1 Like