Help creating a stopwatch/lap timer

Hello, I'm trying to create a stopwatch with a lap timer built in. There is a button to start the timer and a button to measure the time per lap, I'm using the remote reset button to 'reset' the timer. All of this is displayed on a 16,2 LCD screen. The goal is after developing the project is to 3d print a case and mount it into the car for autocross; however, there is one major problem I'm running into and I cant seem to phrase it right into google. The problem is: when I run my program on my Arduino UNO R3 the program starts and runs as intended. Start screen reads "press start" then after pressing the start button the timer starts and displays correctly. the trouble is when I press the 'lap' button the timer stops, lap time displays correctly on the second row. Why does the timer stop and how can I keep it counting while pressing the lap timer button? Im still very new to coding/arduino so if it is obvious be gentle, this is mid project so sorry the code looks like a train wreck. Thanks in advance.

#include <LiquidCrystal.h>


const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
signed short hour, min, sec, tens;
char timeline[16];

void setup() {
  lcd.begin(16, 2);
  lcd.clear();

  Serial.begin(9600);

  pinMode(8, INPUT);
  digitalWrite(8, HIGH);
  pinMode(9, INPUT);
  digitalWrite(9, HIGH);
  
}

//FIGURE OUT: how to keep the time counting while the lap prints
void loop()
{
  lcd.clear();
  lcd.print("press start");
  delay(100);

  if(digitalRead(8) == LOW)
  {
  while(digitalRead(9) == HIGH)
  {
  lcd.setCursor(5, 0);
  sprintf(timeline,"%0.2d:%0.2d:%0.2d:%0.2d ", hour, min, sec, tens);
  lcd.print(timeline);
  lcd.setCursor(0,0);
  lcd.print("Time: ");
  
  delay(10);
  tens++;

  if (tens == 100)
  {
    tens = 0;
    sec ++;
  }
  
  if (sec == 60)
  {
    sec = 0;
    min ++;
  }
 
  if (min==60)
  {
    min = 0;
    hour ++;
  }
  if(digitalRead(9) == LOW)
  {
  while(digitalRead(8) == HIGH)
  {
   lcd.setCursor(0,1);
   lcd.print("Lap: ");
   lcd.setCursor(5,1);
   lcd.print(timeline);
   delay(100); 
  }
  }
}
}
}
type or paste code here

Always handle switches by looking for a change in state, not the switch level.


Stay away from while( ) as if it was a plague. :wink:

1 Like

It's safer to use:

if (tens >= 100)

in case you miss the count of 100...

Your code would be easier to read and understand if you gave the pins meaningful names. For instance, when you say

it is not obvious which pin you are referring to

10-4 looking into it now. thank you.

sorry, pin 8 is the start button and pin 9 is the lap button.

thanks for the tip.

Good buddy, this is an international forum. Vernacular is not well understood by all members.

No, I think the suggestion was aimed at giving them names, like:

const int startButtonPin = 8;
...
key = digitalRead(startButtonPin);

I now understand amendment made. Im also having trouble implementing larryDs state change direction. I believe I've managed to confuse myself on the syntax. also does each push button need a draw down resistor?

Each button should have either a pullup or pulldown resistor. The easiest way to do this is to use INPUT_PULLUP in pinMode() to turn on the built in pullup resistor but the program logic and switch wiring must match the resistor wiring

Syntax, or semantics?

hard to say some days. so I changed the wiring and have added resistors to my push buttons, but much to my dismay i have lost some functionality.... the start button will start the timer but the lap button will no longer work. I have double checked the wiring but to no avail. here is the current ineration of the code:

#include <LiquidCrystal.h>


const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
signed short hour, min, sec, tens;
char timeline[16];
const int startButtonPin = 8;
const int lapButtonPin = 9;
int startButtonState = 0;
int lapButtonState = 0;


void setup() {
  lcd.begin(16, 2);
  lcd.clear();

  Serial.begin(9600);

  pinMode(startButtonPin, INPUT_PULLUP);
  digitalWrite(startButtonPin, HIGH);
  pinMode(lapButtonPin, INPUT_PULLUP);
  digitalWrite(lapButtonPin, HIGH);
  
}

//FIGURE OUT: how to keep the time counting while the lap prints
void loop()
{
  lcd.clear();
  lcd.print("press start");
  startButtonState = digitalRead(startButtonPin);
  lapButtonState = digitalRead(lapButtonPin);
  delay(100);

  if(startButtonState == LOW)
  {
  while(lapButtonState == HIGH)
  {
  lcd.setCursor(5, 0);
  sprintf(timeline,"%0.2d:%0.2d:%0.2d:%0.2d ", hour, min, sec, tens);
  lcd.print(timeline);
  lcd.setCursor(0,0);
  lcd.print("Time: ");
  
  delay(10);
  tens++;

  if (tens >= 100)
  {
    tens = 0;
    sec ++;
  }
  
  if (sec == 60)
  {
    sec = 0;
    min ++;
  }
 
  if (min==60)
  {
    min = 0;
    hour ++;
  }
  if(lapButtonState == LOW)
  {
  while(startButtonState == HIGH)
  {
   lcd.setCursor(0,1);
   lcd.print("Lap: ");
   lcd.setCursor(5,1);
   lcd.print(timeline); 
  }
  }
}
}
}

Please post a wiring diagram.

Try this sketch.

To be tested.

#include <LiquidCrystal.h>

#define OPEN                       HIGH
#define CLOSED                     LOW
#define ENABLED                    true
#define DISABLED                   false

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

signed short hour, min, sec, tens;

char timeline[16];

bool timingFlag                  = DISABLED;

byte heartbeatLED                = 13;
byte startSwitch                 = 8;
byte lapSwitch                   = 9;

byte lastStartSwitch             = OPEN;
byte lastLapSwitch               = OPEN;

//timing stuff
unsigned long heartbeatMillis;
unsigned long switchMillis;
unsigned long lcdMillis;

//****************************************************************************
void setup()
{
  Serial.begin(115200);

  lcd.begin(16, 2);
  lcd.clear();

  pinMode(startSwitch, INPUT_PULLUP);
  pinMode(lapSwitch, INPUT_PULLUP);

  lcd.clear();
  lcd.print("Press Start");

} //END of   setup()


//****************************************************************************
void loop()
{
  //************************************************
  //time to toggle the heartbeatLED ?
  if (millis() - heartbeatMillis >= 500)
  {
    //restart this TIMER
    heartbeatMillis = millis();

    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //************************************************
  //time to check the switches ?
  if (millis() - switchMillis >= 50)
  {
    //restart this TIMER
    switchMillis = millis();

    checkSwitches();
  }

  //************************************************
  //is the clock running ?
  if (timingFlag == ENABLED && millis() - lcdMillis >= 500)
  {
    //restart this TIMER
    lcdMillis = millis();
    
    lcd.setCursor(5, 0);
    sprintf(timeline, "%0.2d:%0.2d:%0.2d:%0.2d ", hour, min, sec, tens);
    lcd.print(timeline);
    lcd.setCursor(0, 0);
    lcd.print("Time: ");

    tens++;
    if (tens == 100)
    {
      tens = 0;
      sec ++;
    }

    if (sec == 60)
    {
      sec = 0;
      min ++;
    }

    if (min == 60)
    {
      min = 0;
      hour ++;
    }

    Serial.println("Time: ");
    Serial.println(timeline);
  }

} //END of   loop()


//****************************************************************************
void checkSwitches()
{
  //*********************************************                  startSwitch
  byte state = digitalRead(startSwitch);
  
  //has the switch changed state ?
  if (lastStartSwitch != state)
  {
    //update to the new state
    lastStartSwitch = state;

    //*****************
    if (timingFlag == DISABLED && state == CLOSED)
    {
      timingFlag = ENABLED;

      tens = 0;
      sec = 0;
      min = 0;
      hour = 0;
    }

    //*****************
    else if (timingFlag == ENABLED && state == CLOSED)
    {
      timingFlag = DISABLED;
    }

  } //END of   startSwitch

  //*********************************************                   lapSwitch
  state = digitalRead(lapSwitch);
  //has the switch changed state ?
  if (lastLapSwitch != state)
  {
    //update to the new state
    lastLapSwitch = state;

    //*****************
    if (timingFlag == ENABLED && state == CLOSED)
    {
      lcd.setCursor(0, 1);
      lcd.print("Lap: ");
      lcd.setCursor(5, 1);
      lcd.print(timeline);

      Serial.print("Lap Time = ");
      Serial.println(timeline);
    }
  }

} //END of  checkSwitches()


Thank you LarryD! That sketch worked perfectly.

@anon57585045 if you would still like a wiring diagram I can generate one for you in a few minutes time.

Thank you everyone for your combined efforts.

You have a chance to learn something here, read the sketch and ask questions. :thinking:


This should have better timing:

#include <LiquidCrystal.h>

#define OPEN                       HIGH
#define CLOSED                     LOW
#define ENABLED                    true
#define DISABLED                   false

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

signed short hour, min, sec, tens;

char timeline[16];

bool timingFlag                  = DISABLED;

byte heartbeatLED                = 13;
byte startSwitch                 = 8;
byte lapSwitch                   = 9;

byte lastStartSwitch             = OPEN;
byte lastLapSwitch               = OPEN;

//timing stuff
unsigned long heartbeatMillis;
unsigned long switchMillis;
unsigned long lcdMicros;

//****************************************************************************
void setup()
{
  Serial.begin(115200);

  pinMode(heartbeatLED,OUTPUT);

  lcd.begin(16, 2);
  lcd.clear();

  pinMode(startSwitch, INPUT_PULLUP);
  pinMode(lapSwitch, INPUT_PULLUP);

  lcd.clear();
  lcd.print("Press Start");

} //END of   setup()


//****************************************************************************
void loop()
{
  //************************************************
  //time to toggle the heartbeatLED ?
  if (millis() - heartbeatMillis >= 500)
  {
    //restart this TIMER
    heartbeatMillis = millis();

    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //************************************************
  //time to check the switches ?
  if (millis() - switchMillis >= 50)
  {
    //restart this TIMER
    switchMillis = millis();

    checkSwitches();
  }

  //************************************************
  //is the clock enabled and is it time to update the LCD ?
  if (timingFlag == ENABLED && micros() - lcdMicros >= 10000)
  {
    //restart this TIMER
    lcdMicros = micros();
    
    lcd.setCursor(5, 0);
    sprintf(timeline, "%0.2d:%0.2d:%0.2d:%0.2d ", hour, min, sec, tens);
    lcd.print(timeline);
    lcd.setCursor(0, 0);
    lcd.print("Time: ");

    tens++;
    if (tens >= 100)
    {
      tens = 0;
      sec ++;
    }

    if (sec >= 60)
    {
      sec = 0;
      min ++;
    }

    if (min >= 60)
    {
      min = 0;
      hour ++;
    }

    Serial.println("Time: ");
    Serial.println(timeline);
  }

} //END of   loop()


//****************************************************************************
void checkSwitches()
{
  //*********************************************                  startSwitch
  byte state = digitalRead(startSwitch);
  
  //has the switch changed state ?
  if (lastStartSwitch != state)
  {
    //update to the new state
    lastStartSwitch = state;

    //*****************
    if (timingFlag == DISABLED && state == CLOSED)
    {
      timingFlag = ENABLED;

      tens = 0;
      sec = 0;
      min = 0;
      hour = 0;
    }

    //*****************
    else if (timingFlag == ENABLED && state == CLOSED)
    {
      timingFlag = DISABLED;
    }

  } //END of   startSwitch

  //*********************************************                   lapSwitch
  state = digitalRead(lapSwitch);
  //has the switch changed state ?
  if (lastLapSwitch != state)
  {
    //update to the new state
    lastLapSwitch = state;

    //*****************
    if (timingFlag == ENABLED && state == CLOSED)
    {
      lcd.setCursor(0, 1);
      lcd.print("Lap: ");
      lcd.setCursor(5, 1);
      lcd.print(timeline);

      Serial.print("Lap Time = ");
      Serial.println(timeline);
    }
  }

} //END of  checkSwitches()


EDIT

Absolutely, so I've noticed a few things. mostly differences between your sketch and the sketch I cobbled together via copy and paste. my biggest ask is how do you know the correct syntax to choose and use? its a safe bet that alot of that comes from experience but what makes some syntax more optimal/efficient/preferable than other similar options?

also, where in my sketch did the pinMode()/INPUTPULLUP steer me wrong? I don't have a lot of fundamental understanding about syntax so I don't know why the sketch did not respond well to the current wiring. obviously the wiring was right, still got all my magic smoke. and the sketch was good to a point, it uploaded successfully but where was the miss?

pinMode(8, INPUT);
digitalWrite(8, HIGH);

Is the same as

pinMode(8, INPUT_PULLUP);

Best advice is to master the sketches in the IDE examples.

You may think this is nit picking, but it's important to understand the meaning of "syntax". Syntax is just the grammar of the language, what you are allowed to say in the language. It has nothing to do with program logic or choices of methodology, etc.

If your program compiled, it has 100% correct syntax. If it doesn't work, it has design issues. That is a completely different thing.