Start a timer when button is pressed

I am creating a timer for a race. I have a photosensor that has a laser pointed to so when someone crosses the finish, it trips the sensor, and the system logs the racer's time. I am using millis() to time the race, but I need the timer to start when I push the button. I have tried using edge detection to start the timer, but the timer starts when the program starts, not when the program starts. The only other problem I have is that if the time since the race started goes over the time between when the program starts vs when the race starts, the time goes into the negatives. Any help is much appreciated, I have been googling how to do this all day.

Code:

const int sensorPin = A0;
int sensorValue = 0;
int place = 1;
float  timer;
int prevButtonState = 0;
float  personTime;
const int greenPin = 3;
const int redPin = 4;
const int switchPin = 5;
int currentMillis = 0;
int prevMillis = millis;
int buttonPushCounter = 0;
int buttonState = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(greenPin, OUTPUT);
  pinMode(redPin, OUTPUT);
  pinMode(switchPin, INPUT);

}
void flash(){
  digitalWrite(greenPin, HIGH);
  delay(100);
  digitalWrite(greenPin, LOW);
}
void runTimer(){
  timer = currentMillis - prevMillis;
  float personTime = timer/1000;
 
  sensorValue = analogRead(sensorPin);
  //Serial.println(sensorValue);
  delay(100);
  
  
 if(sensorValue < 250){
  Serial.print("PLACE ");
    Serial.print(place);
    Serial.print(" TIME: ");
    Serial.print(personTime, 3);
    Serial.println(" SECONDS");
    flash();
    place++;
    
  }
}
void waiting(){
  Serial.println("WAITING");
}

void loop() {
  // put your main code here, to run repeatedly:
  
  buttonState = digitalRead(switchPin);
  if(buttonState != prevButtonState){
    if(buttonState == HIGH){
      buttonPushCounter ++;
    }
  }

  if(buttonPushCounter % 2 == 0){
    runTimer();
  }
  else{
    waiting();
  }
  
  

}

Some things to start with:

Where is prevButtonState updated ?

How is the switch wired ?

int currentMillis = 0; <- unsigned long

int prevMillis = millis;
should be:
unsigned long prevMillis = millis();

Where is currentMillis updated ?

Add prevButtonState = digitalRead(switchPin); to setup().

Take a look at there two tutorials
How to write Timers and Delays in Arduino
and Debouncing Switches in Arduino

Also look at my tutorial on Multi-tasking in Arduino

Or, you could try this. Its a stop watch class that times in 1/10 second chunks. It can be set to smaller chunks, but this gives you some time to do other Arduino things while racing.


#include <mechButton.h>
#include <timeObj.h>

// *****************************************
//          A nifty stopwatch class..
// *****************************************

#define MS_PER_COUNT 100

class stopWatch : public idler {

   public:
            stopWatch(void);
            ~stopWatch(void);
      
            void  clickStart(void);
            void  clickStop(void);
            void  clickReset(void);
            float readTime(void);
   virtual  void  idle(void);
            
            timeObj  mTimer;
            int      mCounts;
};

stopWatch::stopWatch(void) {
   
   mCounts = 0;
   mTimer.setTime(MS_PER_COUNT);
}

stopWatch::~stopWatch(void) {  }
      
void stopWatch::clickStart(void) {
   
   mTimer.start();
   mCounts = 0;
   hookup();
}

void stopWatch::clickStop(void) { mTimer.reset(); }

void stopWatch::clickReset(void) { mCounts = 0; }

float stopWatch::readTime(void) { return (mCounts*MS_PER_COUNT)/1000.0; }

void stopWatch::idle(void) {

   if (mTimer.ding()) {
      mTimer.stepTime();
      mCounts++;
   }
}

// *****************************************
//          Begin main program..
// *****************************************

#define BTN_PIN      2

stopWatch   stopwatchOne;
mechButton  watchButton(BTN_PIN);
bool        running;

void setup(void) {
   
   Serial.begin(57600);                   // Serial to see what's going on.
   running = false;                       // We are not running a race at the moment.
   watchButton.setCallback(btnClicked);   // When the button is clicked, it'll call this function.
}

// Button clicked, do action!
void btnClicked(void) {

   if (!watchButton.trueFalse()) {                 // If the button has been ground. (pressed)
      if (running) {                               // If we are currently runnig a race..
         stopwatchOne.clickStop();                 // We click stop on the stopwatch.
         running = false;                          // We are no longer running.
         Serial.print("Finish time : ");           // Lets see the results.
         Serial.print(stopwatchOne.readTime(),1);  //
         Serial.println(" Sec.");                  //
      } else {                                     // Else, we are NOT running a race..
         stopwatchOne.clickStart();                // Start the race!
         Serial.println("And they're off!");       // Tell the users.
         running = true;                           // Note that the race is running.
      }
   }
}

void loop(void) {

   idle();                                      // idle() lets things like the button & stopwatch run.
   if (running) {                               // If we are running..
      Serial.print(stopwatchOne.readTime(),1);  // We can see what's going on.
      Serial.println(" Sec.");                  // 
   }
}

If you'd like to try this, you will need to grab LC_baseTools from the IDE library manager to get it to compile.

Good luck & have fun!

-jim lee

I notice that mCounts is declared as an int, and MS_PER_COUNT looks like it would default to an int.
Wouldn't this lead to a problem after 32.8 seconds?

100 ms / count

100ms * 32k / 1000 = 3,200 sec => almost an hour.

But you can bump it up to an unsigned long easy enough.

Oh wait, I think I see what you are getting at. This should fix it...

float stopWatch::readTime(void) { return (mCounts/1000.0)*MS_PER_COUNT); }

-jim lee

I tried running this program on the Arduino, but I couldn't get any input on the Serial Monitor. I checked to be sure everything was connected properly and the serial was set up correctly, but I didn't see any data. I also made sure that the pin for the button was defined. Still, thank you so much for the help! I really appreciate it.

Thanks again for all the help! I fixed the problem where the timer value would suddenly turn negative around 30 seconds into the race, but I am still having difficulty with getting the timer to start when I press the button, not when the program starts running.
Updated code:

const int sensorPin = A0;
int sensorValue = 0;
int place = 1;
float  timer;
int prevButtonState = 0;
float  personTime;
const int greenPin = 3;
const int redPin = 4;
const int switchPin = 7;
unsigned long currentMillis = 0;
unsigned long prevMillis = millis;
int buttonPushCounter = 0;
int buttonState = 0;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(greenPin, OUTPUT);
  pinMode(redPin, OUTPUT);
  pinMode(switchPin, INPUT);
  prevButtonState = digitalRead(switchPin);


}
void flash(){
  digitalWrite(greenPin, HIGH);
  delay(100);
  digitalWrite(greenPin, LOW);
}
void runTimer(){
  currentMillis = millis();
  timer = currentMillis - prevMillis;
  float personTime = timer/1000;
 
  sensorValue = analogRead(sensorPin);
  //Serial.println(sensorValue);
  delay(50);
  
  
 if(sensorValue < 175){
  Serial.print("PLACE ");
    Serial.print(place);
    Serial.print(" TIME: ");
    Serial.print(personTime, 3);
    Serial.println(" SECONDS");
    flash();
    place++;
    
    
  }
  
}
void waiting(){
  Serial.println("WAITING");
}

void loop() {
 buttonState = digitalRead(switchPin);
 if(buttonState != prevButtonState){
  if(buttonState == HIGH){
    buttonPushCounter ++;
    delay(50);
  }
 }
   prevButtonState = buttonState;

 if(buttonPushCounter % 2 == 0){
  runTimer();
 }
 else{
  waiting();
 }
  
 
  
  

}
Serial.begin(57600);

I'm betting the serial begin() wasn't what you were expecting.
Try...

Serial.begin(9600);

-jim lee

Fixed. Thank you for the help! Is there any way I could run the function runTimer() I created for my program with this program?
code:

void runTimer(){
  currentMillis = millis();
  timer = currentMillis - prevMillis;
  float personTime = timer/1000;
 
  sensorValue = analogRead(sensorPin);
//test sensor value for calibration on race location
  //Serial.println(sensorValue);
  delay(50);
  
  
 if(sensorValue < 175){ // value can be changed based on racing environment
  Serial.print("PLACE ");
    Serial.print(place); //print the place of the person who crossed the line
    Serial.print(" TIME: ");
    Serial.print(personTime, 3); // print the person's time to the millisecond (done by dividing the milliseconds elapsed by 1000)
    Serial.println(" SECONDS");
    flash();
    place++;
    
    
  }
  
}

variables used:

const int sensorPin = A0;
int sensorValue = 0;
int place = 1;
float  timer;
float  personTime;
const int greenPin = 2;
unsigned long currentMillis = 0;
unsigned long prevMillis = millis;

void flash(){
  digitalWrite(greenPin, HIGH);
  delay(100);
  digitalWrite(greenPin, LOW);
}


It might be a little tricky, I am working on that right now. If there isn't a way to run this function on this great program, that's totally fine. But if it can, that would be amazing! Thanks again for all your great help.

You can use an interrupt and make every tick the desired lenght (1/10/100ms).
And then using variables to count the ticks.
The debounce is made with a while loop.
If you have bad bouncing or need a longer treshold
then you can add a capacitator over the button (output).


     TCCR1A = 0;
     TCCR1B = 0;
     TCNT1  = 0;
     OCR1A = 1950;
     TCCR1B |= (1 << WGM12);
     TCCR1B |= (1 << CS12) | (1 << CS10);  
     TIMSK1 |= (1 << OCIE1A);

}
  
ISR(TIMER1_COMPA_vect)
{
  
     if (Toggle == 1)
     {
        TimerCounter = TimerCounter++;
        BounceTimer  = BounceTimer++;
        Toggle = 0;
     }
        else
        {
             Toggle = 1;
        }
}

void loop()
{
     StartTime();
     LapTime();
}

Void StartTime()
{     
     if (PushButton == 1)
     {  
        TimerCounter = 0;
        BounceTimer = 0;
        StartBounceTimer();
     }

void LapTime()
{
     if (PushButton == 1)
     {
        LapTime = TimerCounter;
        PrintLapTime();
        BounceTimer = 0;
        StartBounceTimer();
     }

void StartBounceTimer()
{
     while (BounceTimer < BounceTime)
     {
     }   
}

void PrintTime()
{
     Serial.println(TimerCounter);
}

void PrintLaptTime()
{
     Serial.println(LapTime);
}

Well, maybe..

Its getting little complicated to hold all in my head without anything to test it on. But here's a shot at it. Hopefully this can at least help.

#include <mechButton.h>
#include <timeObj.h>

// *****************************************
//          A nifty stopwatch class..
// *****************************************

#define MS_PER_COUNT 100

class stopWatch : public idler {

   public:
            stopWatch(void);
            ~stopWatch(void);
      
            void  clickStart(void);
            void  clickStop(void);
            void  clickReset(void);
            float readTime(void);
   virtual  void  idle(void);
            
            timeObj  mTimer;
            int      mCounts;
};

stopWatch::stopWatch(void) {
   
   mCounts = 0;
   mTimer.setTime(MS_PER_COUNT);
}

stopWatch::~stopWatch(void) {  }
      
void stopWatch::clickStart(void) {
   
   mTimer.start();
   mCounts = 0;
   hookup();
}

void stopWatch::clickStop(void) { mTimer.reset(); }

void stopWatch::clickReset(void) { mCounts = 0; }

float stopWatch::readTime(void) { return (mCounts*MS_PER_COUNT)/1000.0; }

void stopWatch::idle(void) {

   if (mTimer.ding()) {
      mTimer.stepTime();
      mCounts++;
   }
}



// *****************************************
//          How about a one shot LED class?
// *****************************************


class oneShot :   public idler {

   public :
            oneShot(int pin);
            ~oneShot(void);

           void   flash(float ms);
   virtual  void  idle(void);

            timeObj  timer;
            bool     init;
            int      pinNum;
};


oneShot::oneShot(int pin) {

   init     = false;
   pinNum   = pin;
}


oneShot::~oneShot(void) {  }


void oneShot::flash(float ms) {

   if (!init) {
      pinMode(pinNum,OUTPUT);
      hookup();
      init = true;
   }
   timer.setTime(ms,true);
   digitalWrite(pinNum, HIGH);
}


void oneShot::idle(void) {

   if (timer.ding()) {
      digitalWrite(pinNum, LOW);
      timer.reset();
   }
}



// *****************************************
//          Begin main program..
// *****************************************

#define BTN_PIN      3

const int sensorPin = A0;
const int greenPin = 2;

stopWatch   stopwatchOne;
mechButton  watchButton(BTN_PIN);
oneShot     theLED(greenPin);
bool        running;
int         place;


void setup(void) {
   
   Serial.begin(9600);                   // Serial to see what's going on.
   running = false;                       // We are not running a race at the moment.
   watchButton.setCallback(btnClicked);   // When the button is clicked, it'll call this function.
   theLED.flash(100);                     // Flash the LED!
}


// Button clicked, do action!
void btnClicked(void) {

   if (!watchButton.trueFalse()) {                 // If the button has been ground. (pressed)
      if (running) {                               // If we are currently runnig a race..
         stopwatchOne.clickStop();                 // We click stop on the stopwatch.
         running = false;                          // We are no longer running.
         Serial.print("Finish time : ");           // Lets see the results.
         Serial.print(stopwatchOne.readTime(),1);  //
         Serial.println(" Sec.");                  //
      } else {                                     // Else, we are NOT running a race..
         stopwatchOne.clickStart();                // Start the race!
         Serial.println("And they're off!");       // Tell the users.
         place = 1;                                // Setup for the winner.
         running = true;                           // Note that the race is running.
      }
   }
}


// Standard loop().
void loop(void) {

   int   sensorValue;
   float seconds;
   
   idle();                                      // idle() lets things like the button & stopwatch run.
   if (running) {                               // If we are running..
      sensorValue = analogRead(sensorPin);      // Read the sensor.
      if(sensorValue < 175) {                   // value can be changed based on racing environment
         seconds = stopwatchOne.readTime();     // Grab the time.
         theLED.flash(100);                     // Flash the LED!
         Serial.print("PLACE ");                // Label.
         Serial.print(place);                   // Print the place of the person who crossed the line
         Serial.print(" TIME: ");               // Label.
         Serial.print(seconds,3);               // print the person's time.
         Serial.println(" SECONDS");            // Label.
         place++;                               // Bump up place.
         while(analogRead(sensorPin)<175) {     // Hold 'till the contestent passes.
            idle();                             // Make sure stuff runs.
         }
      }  
   }
}

-jim lee

I think you'll eventually need state change detection here.  As written, as long as the if() is true the statement block will be executed.  This would record times for the entire length time the sensor is blocked.

Hello
I´ve found a similar, but "class-less", solution in my sketch box. With respect to your project sheet I did some mods in this sketch for you. Try and check. Your shall have to modify the pin numbers before.

#define ProjectName "Start a timer when button is pressed"
const byte StartPin {A0};
const byte PhotoSensorPin {A1};
const byte redLedPin {2};
const byte greenLedPin {3};
const unsigned long BlinkRate {333};
const int heartBeat { LED_BUILTIN};
enum {Start, PhotoSensor};
enum {NoClick, Click};
struct INPUT_ {
  int pin;
  bool state;
  bool click;
} button [] {
  {StartPin, 0, 0},
  {PhotoSensorPin, 0, 0},
};
enum {Red, Green};
struct OUTPUT_ {
  byte pin;
} led[] {redLedPin, greenLedPin};
enum {Led, Arrival};
struct TIMER {
  unsigned long stamp;
  unsigned long duration;
  unsigned long start;
} timer[] {
  {0, BlinkRate, 0},
  {0, 0, 0},
};
unsigned long getTime(TIMER &timer) {
  return (millis() - timer.start);
}
void buttonRead() {
  static unsigned long buttonReadMillis = 0;
  const unsigned long buttonReadDebounceTime = 50;
  if (millis() - buttonReadMillis >= buttonReadDebounceTime) {
    buttonReadMillis = millis();
    for (auto &Button : button) {
      bool state = !digitalRead(Button.pin);
      if (Button.state != state) {
        Button.state = state;
        if (state) Button.click = state;
      }
    }
  }
}
bool buttonClick(INPUT_ &button) {
  bool back = 0;
  if (!button.click) return (back);
  back = button.click;
  button.click = 0;
  return (back);
}
void blinkLed (bool color) {
  if (millis() - timer[Led].stamp >= timer[Led].duration) {
    timer[Led].stamp = millis();
    digitalWrite(led[color].pin, digitalRead(led[color].pin) ? 0 : 1);
  }
}
void personTime(unsigned long arrivalTime) {
  static int place = 1;
  if (!arrivalTime) {
    Serial.println(F("\n\n< the race is started - waiting for objects >"));
    place = 1;
//    return;
  }
  Serial.print(" - PLACE ");
  Serial.print(place);
  Serial.print(" TIME: ");
  Serial.print((float)arrivalTime / 1000, 3);
  Serial.println(" SECONDS");
  place++;
}
void input() {
  buttonRead();
  if (buttonClick(button[Start])) timer[Arrival].start = 0, digitalWrite(led[Green].pin, LOW);
  if (!timer[Arrival].start) {
    if (buttonClick(button[PhotoSensor])) {
      digitalWrite(led[Red].pin, LOW);
      timer[Arrival].start = millis();
      personTime(getTime(timer[Arrival]));
    }
  }
}
void output() {
  blinkLed(timer[Arrival].start);
  if (buttonClick(button[PhotoSensor])) personTime(getTime(timer[Arrival]));
}
void setup() {  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode (heartBeat, OUTPUT);
  for (auto make : button) pinMode (make.pin, INPUT_PULLUP);
  for (auto make : led) pinMode (make.pin, OUTPUT);
  Serial.print(F(".\nlet´s go! "));
}
void loop() { // put your main code here, to run repeatedly:
  digitalWrite(heartBeat, millis() / 500 % 2);
  input();
  output();
}

Thank you so much! I didn't think about how hard it might be to run a program not knowing what the circuit looked like. If it helps, I made a virtual circuit that matches what I have built.
Link: https://www.tinkercad.com/things/b1Oi8eaMo14-fabulous-snaget/editel?sharecode=EkleHbEYZE_cFTQGLeLY9Te-xrA2p8b4XX_oDQok7Oc

I doubt any of my stuff will run on tinkercad. Last time I checked, they didn't allow any dynamic memory allocation. (One of my favorite tools)

-jim lee

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.