Updated stopwatch class on the playground.

This version uses a function pointer to either millis(), micros() or seconds() (which it defines as a class method). Decision about what function to use is made at constructor time, thus no overhead and no different codepaths at runtime when _gettime() is called.

StopWatch.h

#ifndef StopWatch_h
#define StopWatch_h
// 
//    FILE: StopWatch.h
//  AUTHOR: Rob Tillaart
// PURPOSE: Simple StopWatch library for Arduino
// HISTORY: See StopWatch.cpp
//     URL: http://www.arduino.cc/playground/Code/StopWatchClass
//
// Released to the public domain
//

#define STOPWATCH_LIB_VERSION "0.1.02"

#if ARDUINO >= 100
    #include "Arduino.h"
#else
    #include "WProgram.h"
#endif


class StopWatch 
{
public:
    enum State { RESET, RUNNING, STOPPED };
    enum Resolution { MILLIS, MICROS, SECONDS };
    StopWatch(enum Resolution res = MILLIS);
    void start();
    void stop(); 
    void reset();
    unsigned long value();              // should this be deprecated ?
    unsigned long elapsed() { return value(); };
    bool isRunning();
    enum State state();
    enum Resolution resolution() { return _res; };

private:
    enum State _state;
    enum Resolution _res;
    unsigned long _starttime;
    unsigned long _stoptime;
    //unsigned long _gettime();
    unsigned long (*_gettime)(void);
    static unsigned long seconds() { return millis()/1000; };
};

#endif
// END OF FILE

StopWatch.cpp

// 
//    FILE: StopWatch.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.02
// PURPOSE: Simple StopWatch library for Arduino
//
// The library is based upon millis() and therefore
// has the same restrictions as millis() has wrt overflow.
//
// HISTORY: 
// 0.1.00 - 2011-01-04 initial version
// 0.1.01 - 2011-01-04 Added better state
// 0.1.02 - 2011-06-15 Added state() + #defines + lib version
// 
// Released to the public domain
//

#include "StopWatch.h"


StopWatch::StopWatch(enum Resolution res)
{
    _res = res;
    switch(_res) {
        case MICROS:
            _gettime = micros;
            break;
        case MILLIS:
            _gettime = millis;
            break;
        case SECONDS:
            _gettime = seconds;
            break;
    }
    reset();
}

void StopWatch::reset()
{
    _state = StopWatch::RESET;
    _starttime = _stoptime = 0;
}

void StopWatch::start()
{
    if (_state == StopWatch::RESET || _state == StopWatch::STOPPED)
    {
        _state = StopWatch::RUNNING;
        unsigned long t = _gettime();
        _starttime += t - _stoptime;
        _stoptime = t;
    }
}

unsigned long StopWatch::value()
{
    if (_state == StopWatch::RUNNING) _stoptime = _gettime();
    return _stoptime - _starttime;
}

void StopWatch::stop()
{
    if (_state == StopWatch::RUNNING)
    {
        _state = StopWatch::STOPPED;
        _stoptime = _gettime();
    }
}

bool StopWatch::isRunning()
{
    return (_state == StopWatch::RUNNING);
}

enum StopWatch::State StopWatch::state()
{
    return _state;
}
/*
unsigned long StopWatch::_gettime() {
    switch(_res) {
        case MICROS:
            return micros();
            
        case MILLIS:
            return millis();
            
        case SECONDS:
            return millis()/1000;
    }
}
*/
// END OF FILE

The moment the micros() would switch from 1.999.999 micros to 2.000.000 micros should be the same moment that 1 second goes to 2 ideally. However that only will work if the app was able to start simultaneaously which is not supported. You mentioned it "serial nature of the code". My mistake.

Latest code looks quite good !!

  • added new version number
  • added credits (change name if you like)
  • added default in constructor switch, to

propose TENTHS HUNDREDS as additional time-functions.?

Think it becomes time to post it on the playground (as the weekend is almost over :wink:

StopWatch.h

#ifndef StopWatch_h
#define StopWatch_h
// 
//    FILE: StopWatch.h
//  AUTHOR: Rob Tillaart
// PURPOSE: Simple StopWatch library for Arduino
// HISTORY: See StopWatch.cpp
//     URL: http://www.arduino.cc/playground/Code/StopWatchClass
//
// Released to the public domain
//

#define STOPWATCH_LIB_VERSION "0.1.03"

#if ARDUINO >= 100
    #include "Arduino.h"
#else
    #include "WProgram.h"
#endif

class StopWatch 
{
public:
    enum State { RESET, RUNNING, STOPPED };
    enum Resolution { MILLIS, MICROS, SECONDS };   // TENTHS, HUNDREDS  TOO ???? 
    StopWatch(enum Resolution res = MILLIS);
    void start();
    void stop(); 
    void reset();
    unsigned long value();                               // should this be deprecated ?
    unsigned long elapsed() { return value(); };
    bool isRunning();
    enum State state();
    enum Resolution resolution() { return _res; };

private:
    enum State _state;
    enum Resolution _res;
    unsigned long _starttime;
    unsigned long _stoptime;
    unsigned long (*_gettime)(void);
    static unsigned long seconds() { return millis()/1000; };
};

#endif
// END OF FILE

StopWatch.cpp

// 
//    FILE: StopWatch.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.03
// PURPOSE: Simple StopWatch library for Arduino
//
// The library is based upon millis() and therefore
// has the same restrictions as millis() has wrt overflow.
//
// HISTORY: 
// 0.1.00 - 2011-01-04 initial version
// 0.1.01 - 2011-01-04 Added better state
// 0.1.02 - 2011-06-15 Added state() + #defines + lib version
// 0.1.10 - 2012-01-22 Added several improvements
//             By mromani & Rob Tillaart
// 
// Released to the public domain
//

#include "StopWatch.h"

StopWatch::StopWatch(enum Resolution res)
{
    _res = res;
    switch(_res) {
        case MICROS:
            _gettime = micros;
            break;
        case MILLIS:
            _gettime = millis;
            break;
        case SECONDS:
            _gettime = seconds;
            break;
        default:  
            _gettime = millis;
            break;
    }
    reset();
}

void StopWatch::reset()
{
    _state = StopWatch::RESET;
    _starttime = _stoptime = 0;
}

void StopWatch::start()
{
    if (_state == StopWatch::RESET || _state == StopWatch::STOPPED)
    {
        _state = StopWatch::RUNNING;
        unsigned long t = _gettime();
        _starttime += t - _stoptime;
        _stoptime = t;
    }
}

unsigned long StopWatch::value()
{
    if (_state == StopWatch::RUNNING) _stoptime = _gettime();
    return _stoptime - _starttime;
}

void StopWatch::stop()
{
    if (_state == StopWatch::RUNNING)
    {
        _state = StopWatch::STOPPED;
        _stoptime = _gettime();
    }
}

bool StopWatch::isRunning()
{
    return (_state == StopWatch::RUNNING);
}

enum StopWatch::State StopWatch::state()
{
    return _state;
}
// END OF FILE

IMHO adding other time functions is not so useful because they would ultimately rely on micros() or millis(). I think one could just get the stopwatch's value() (or elapsed()) and divide it by whatever power of ten she likes.
Anyway, adding those functions wouldn't be so difficult...

One thing I spotted after posting the code: seconds() could be made public.

Also, I think the comment about value() should be "definitized" :wink:

seconds() could be made public.

Think not as it would refer to seconds since millis was 0 instead of seconds the stopwatch counted. Could create an ambiguity ..

propose to keep both value() and elapsed() as synonyms to stay backwards compatible for now as said several posts ago.

Agreed on both points.

updated last version to - http://arduino.cc/playground/Code/StopWatchClass -

If you have any comments or see some errors, let me know!

Seems good to me.

Here's another test sketch.
Pin 8 acts as a start/stop switch: when it's HIGH (+5v), the stopwatch runs, when is LOW (ground), the stopwatch is stopped.
An LCD is used.
The stopwatch value (elapsed) is printed on line 1. Its current state is printed on line 2.
Pin status and display are updated every 100 ms.

#include <StopWatch.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

StopWatch sw(StopWatch::MILLIS);
unsigned long prev_elapsed = 0;
boolean prev_running = false;
const int pin = 8;        // start/stop switch

unsigned long prev_millis = 0;
const unsigned long updateDelay = 100;


void updateDisplay() {
    lcd.clear();
    lcd.print(sw.elapsed());
    lcd.setCursor(0, 1);
    if (sw.isRunning()) {
       lcd.print("RUN");
    }
    else {
        lcd.print("STOP");
    }
}

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


void loop() {
    unsigned long t = millis();

    // every updateDelay ms read the pin status and update the display
    if (t - prev_millis >= updateDelay) {
        prev_millis = t;

        if (digitalRead(pin) == HIGH) {
            sw.start();
        }
        else {
            sw.stop();
        }
    
        if ((sw.isRunning() != prev_running) || (sw.elapsed() != prev_elapsed)) {
            updateDisplay();
            prev_running = sw.isRunning();
            prev_elapsed = sw.elapsed();
        }
    }
}

The stopwatch value (elapsed) is printed on line 1. Its current state is printed on line 2.

Screenshot?

You mean something like this ?

IMG_0189_modificata.JPG

My current concern is the fact that I am getting slow/poor response
from the stop button (down arrow). Seems to take many presses to get
it to activate. Have you played with setting a keypad (requires two
pins HIGH to activate) to an interrupt and using that for stop?

I am concerned about capturing the stop events in real-time regardless
of what else is happening (updating the LCD or sending data to seven
segment displays). I cannot think of another method other than
setting the stop button to an interrupt somehow.

Seems most of the time is spent outputting to LCD and seven segment displays.

RW

4x20 LCD + TPIC6C495 Shift Registers to 6 4" Seven Segment LEDs.

PS - sorry for all of the commented lines in the code - a lot of debugging.
It took me a long to to figure out that I needed the first delay or it
would never start.

   if(used.item==menu1Item3){      //Stopwatch
    //Serial.write(128);
    //Serial.print("Stopwatch...");

    Serial.write(168);
    Serial.print("^-Start V-Stop  ");
    Serial.write(188);
    Serial.print("Press ESC to exit");
 //   sw_millis.stop();    //start at zero...wait for start //   
 sw_millis.reset(); //   clearLCD(); //   delay(100);
    out7 = false;
    Serial.write(128);
    Serial.print("Clock#1 ");
   // keypad.waitForKey();
             do
    {
        delay(100);
    char key = keypad.getKey();
   //Serial.write(206);      //debug
   //Serial.print(keypad.getKey());   //debug

        Serial.write(136);
 //       printtime((MySW.elapsed()), 4);

         if (sw_millis.elapsed() > 1000){   //don't display anything until 1 second has elapsed - speeds up start button response
         printtime((sw_millis.elapsed()), 4);           //print time on LCD and Seven Segment = only mm:ss (4 -digits)
        }


 // Serial.print(itoa(counter, lapbuf, 10));
                   //Enter button pressed



   if (key == '^'){    //pressed up key
     sw_millis.start();
 
   }else if (key == 'V'){     //pressed down key
     sw_millis.stop();
     Serial.write(136);
     out7 = true;             //out7 = true = send result to seven segment display
     printtime((sw_millis.elapsed()), 8);
     Serial.write(148);
     Serial.print(sw_millis.elapsed());
     waitforreset();


  }else if(key == '<'){    //pressed left key reset
     sw_millis.reset();
     Serial.write(136);
     Serial.print("           ");

   }else if(key == 'R'){
       break;
    }

  // switch(sw_millis.state())
  // {
  //   case StopWatch::RESET:
  //     Serial.print("reset");  // e.g. disable stop/reset
         //Serial.print("     ");
         //Serial.write(136);
         //Serial.print("     ");
 //      break;
  //   case StopWatch::RUNNING:
  //     Serial.print("running"); // display laptime //      break;
  //   case StopWatch::STOPPED:
 //      Serial.print("stopped"); // display finaltime
  //     break;
  //   default:
  //     Serial.print("unknown");
  //     break;
 // }

  // Serial.print(" >>>  laptime loop() : ");
   //Serial.println(SWarray[1].elapsed());

    }while(keypad.getKey() != 'R');
           clearLCD();
              menu.toRoot();  //back to main
           Serial.write(128);       //Set Cursor to 0,0
           Serial.print("www.coagula.org");
           sevensegout(-1, -1, -1);

 }

Moderator edit:
</mark> <mark>[code]</mark> <mark>

</mark> <mark>[/code]</mark> <mark>
tags added.

I ran into a problem calculating tenths/hundredths.... There may be a better workaround, but here is what I came up with.
The problem stems from the fact that millis() is an unsigned long - making their range from 0 to 4,294,967,295 (2^32 - 1). Many of the other variables I was using were declared as integers - This yields a range of -32,768 to 32,767. When I went to calculate tenths/hundredths, my result went amiss after 32 seconds. So, here is what I ended up with so far.

int fractional;                     // variable used to store fractional part of time
int fractionalSecs;                 // variable used to store fractional part of Seconds
int fractionalMins;                 // variable used to store fractional part of Minutes
int fractionalHours;                 // variable used to store fractional part of Hours
unsigned long elapsedFrames;                  // elapsed frames for stop watch
unsigned long elapsedSeconds;                 // elapsed seconds for stop watch
unsigned long elapsedMinutes;                 // elapsed Minutes for stop watch
unsigned long elapsedHours;
float elapsedHun;                               //used in tenths/hundredths calculation
float elapsedHun2;                            //used in tenths/hundredths calculation
int elapsedHun3;                               //used in tenths/hundredths calculation


void printtime (
      unsigned long elapsedPTime,  // Time to dispaly
      int printnumberofdigits)   // number of digits to display - 8=HH:MM:SS.TT, 6=HH:MM:SS, 4=MM:SS
    {
	     elapsedHours = (elapsedPTime / 3600000L);
	     elapsedMinutes = (elapsedPTime / 60000L);
             elapsedSeconds = (elapsedPTime / 1000L);              // divide by 1000 to convert to seconds - then cast to an int to print
             elapsedFrames = (elapsedPTime / 10L);            // divide by 100 to convert to 1/100 of a second - then cast to an int to print
             fractional = (int)(elapsedFrames % frameRate);       // use modulo operator to get fractional part of 1 Second
             fractionalSecs = (int)(elapsedSeconds % 60L);        // use modulo operator to get fractional part of 60 Seconds
             fractionalMins = (int)(elapsedMinutes % 60L);        // use modulo operator to get fractional part of 60 Minutes
             fractionalHours = (int)(elapsedHours % 60L);        // use modulo operator to get fractional part of 60 Minutes     
	     elapsedHun = (elapsedPTime - (elapsedSeconds*1000) ) ;   //remove everything but last 3 digits (fractions of a second)
	     elapsedHun2 = elapsedHun/10;                                    //you will have millis with a decimal    (we only use last two digits)
	     elapsedHun3 = int(elapsedHun2 + 0.5);                         // round this number up or down
			 //clearLCD();                                         // clear the LDC

if (printnumberofdigits > 4){    //print hours if needed
    if (fractionalHours > 1){
	Serial.print(elapsedHours);
	Serial.print(":");
 }
}


//always display minutes if > 59 seconds
if (elapsedSeconds > 59){
	if (fractionalHours > 1){
	  if (fractionalMins < 10){                            // pad in leading zeros
         Serial.print("0");                                 // add a zero
      }
	}	

    Serial.print(fractionalMins);       // convert the int to a string and print a fractional part of 60 Minutes to the LCD
      Serial.print(":");                                 //print a colon. 
}

//always display seconds
 if (fractionalSecs < 10){                            // pad in leading zeros
      Serial.print("0");                                 // add a zero
      }
     Serial.print(fractionalSecs);
	 
 if (printnumberofdigits > 6){   //Print Tenths + hundredths
           // convert the int to a string and print a fractional part of 60 Seconds to the LCD
      Serial.print(".");                                    //print a colon. 

 	  if (fractional < 10){                                // pad in leading zeros 
      Serial.print("0");                                 // add a zero
      }     
	Serial.print(elapsedHun3);          // convert the int to a string and print a fractional part of 60 Seconds to the LCD
   // Serial.write(158);         //debug
//	Serial.print(elapsedHun2);         //debug
 }

 //assemble seven segment numbers
 //  elapsedHours +  fractionalMins + fractionalSecs + elapsedHun3

 if (out7 = true){
	 // take elapsedHun3 and send to tens/hundredths seven segment
	 if (printnumberofdigits > 6){
	      sevensegout(elapsedHun3,fractionalSecs, fractionalMins);       //dump tenths + hundredths to the third shift register
	 }
	 else {
         sevensegout(-1,fractionalSecs, fractionalMins);            //just print minutes + seconds
	 }
	 if (elapsedSeconds > 0){
//	 sevensegout(fractionalSecs,dataPins);    //dump seconds to the second shift register
	 }
	 if (elapsedSeconds > 59){
//	 sevensegout(fractionalMins,dataPinm);    //dump minutes to the first shift register
	 }
 }

 else {
         sevensegout(-1,fractionalSecs, fractionalMins);
	 }
 out7 = false;   //reset outputpin 
 	//selectLineTwo();
	// Serial.print(round(elapsedHun2));
	//  	selectLineThree();
	// Serial.print((elapsedHun2));
    //Serial.write(188);
    //Serial.print(elapsedPTime);
  } //end printtime

refactored, give it a try

void printTime(uint32_t t, int digits)
{
  uint32_t x;
  if (digits == 8 || digits == 6)  
  {
  // HOURS
  x = t/ 3600000UL;
  t-= x * 3600000UL;
  print2digits(x);
  Serial.print(':');
  }

  // MINUTES
  x = t/ 60000UL;
  t -= x * 60000UL;
  print2digits(x);
  Serial.print(':');

  // SECONDS
  x = t/1000UL;
  t -= x * 1000UL;
  print2digits(x);

  if (digits == 8)
  {
  // HUNDREDS
  Serial.print('.');
  x = (t+5) /10L;  // rounded hundreds
  print2digits(x);
  }
}

// helper
void print2digits(uint32_t x)
{
  if (x < 10) Serial.print('0');
  Serial.print(x);
}

Great thread the library was really easy to use and I have it running now on my Arduino uni with an OLED display. I am making a quarter mile timer and want to display it as 11.59 say if it took 11.59 seconds, at the moment it is showing in milliseconds, how can i change this? thanks

by posting your code
Then we can see how it works now, and how to change it

Don't worry it turned out to be a simple fix I just had a to manipulate the numbers a little to get it in the right form, thanks for the reply though and its working great now :slight_smile:

Robert what is the significance to using enumerators for variables such as,
enum State { RESET, RUNNING, STOPPED };

enum Resolution { MILLIS, MICROS, SECONDS };

as opposed to
#define RESET 0
#define RUNNING 1
#define STOPPED 2

It allows minimal type safety. Notice the enum names are used to store the value as well as use it:

private:
    enum State _state;
    enum Resolution _res;
StopWatch::StopWatch(enum Resolution res)
{
    _res = res;
    switch(_res) {
        case MICROS:

Using an enum type allows you to prevent out of range data as you need to use the named constants in the enum:

_res = 5; //Error

But it doesn't stop you writing bad code:

_res = (Resolution) 5; //Compiles fine, but out of range.

Enums implcitly convert to an int:

int val = _res;

C++11 fixes this with enum classes:

enum class Resolution { MILLIS, MICROS, SECONDS };

Elements need to be accessed explicitly to assign:

_res = Resolution::SECONDS;

Doing a bad cast still works:

_res = (Resolution) 5; //Compiles fine, but out of range.

However the enum does not implicitly cast to an int, so testing will only be done on real values ( _Res == Resolution::MILLIS ). A switch can be used to detect out of range values.

    switch(_res) {
        case Resolution::MICROS:     //IN RANGE
        case Resolution::MILLIS:      //IN RANGE
        case Resolution::SECONDS:  //IN RANGE
        default:                             //OUT OF RANGE

And enum classes can be sized to reduce or enlarge their width.

enum class Resolution : char { MILLIS, MICROS, SECONDS };

Lol, this is going to look awful for you guys! The preview is about 2 pages long... sorry.

Is it possible to combine both #define and Enum? Meaning for people who are not familiar with enumerators, such as myself, is it still ok to use a #define to substitute Resolution::MICROS ?

#define Millis Resolution::MILLIS
#define Micros Resolution::MICROS
#define Seconds Resolution::SECONDS

switch(_res) {
        case Micros:     //IN RANGE
        case Millis:      //IN RANGE
        case Seconds:  //IN RANGE
        default:

This way it is easy to write and it allows the user to have minimal type safety.