Pages: 1 [2] 3   Go Down
Author Topic: Updated stopwatch class on the playground.  (Read 9525 times)
0 Members and 1 Guest are viewing this topic.
Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


And do they keep in sync?

idea: rounded seconds?

Code:
unsigned long StopWatch::_gettime()
{
    switch(_res) {
        case MICROS:
            return micros();
           
        case MILLIS:
            return millis();
           
        case SECONDS:
            return (millis() + roundingfactor)/1000;  // roundingfactor can be 0-500-999 depending how you would like to count ..
    }
}

Second as the micros() is such a small time I propose to explicitely do test for micros first.

Code:
unsigned long StopWatch::_gettime()
{
  if (_res == MICROS) return micros();
  else if (_res == MILLIS) return millis();
  else if (_res == SECONDS) return millis()/1000;
}

idea2: we could use a function pointer to point to micros(), millis() or seconds() which is handled in the constructor. _gettime() would become
Code:
unsigned long StopWatch::_gettime()
{
  return resFunction();
}

seconds() should have the same signature as millis() and micros();

my 2 cents..
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I like the function pointer idea, but I'll have to refresh my C++ about function pointers and static functions in OO context.

The if/elseif chain you propose is IMHO exactly the same as my switch/case chain (well, TBH I'm not 100% sure).

Also, what do you mean by "how do they keep in sync" ? Are your referring to the three stopwatches in my test sketch ? I don't think two StopWatch objects can return the same value() as they're "stopped" or queried at different times due to the serial nature of the code...
« Last Edit: January 22, 2012, 10:56:02 am by mromani » Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The if/elseif chain you propose is IMHO exactly the same as my switch/case chain (well, TBH I'm not 100% sure).
The compiler will probably make an if then else ladder from the switch, but I wanna be sure that micros is tested first as the time to test takes time in the order of micros. It would make the MICROS mode more accurate. The function pointer idea would solve this.


Quote
what do you mean by "how do they keep in sync" ?
Are the Seconds from the micros and the millis in sync with the seconds. They should be. This question lead me to the rounding thought.

Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
what do you mean by "how do they keep in sync" ?
Are the Seconds from the micros and the millis in sync with the seconds. They should be. This question lead me to the rounding thought.
[/quote]

I'm sorry, I don't understand. Could you provide an example that would show this "not-in-sync" problem ?
Logged

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
#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

Code:
//
//    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
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley-wink

StopWatch.h
Code:
#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
Code:
//
//    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
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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" ;-)
« Last Edit: January 22, 2012, 01:56:53 pm by mromani » Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


Quote
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.
Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Agreed on both points.
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Code:
#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();
        }
    }
}
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13739
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You mean something like this ?


* IMG_0189_modificata.JPG (54.8 KB, 600x379 - viewed 44 times.)
Logged

Kansas City
Offline Offline
Newbie
*
Karma: 0
Posts: 6
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

 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.

Code:
  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: [code] [/code] tags added.
« Last Edit: April 16, 2012, 02:31:17 pm by Coding Badly » Logged

Pages: 1 [2] 3   Go Up
Jump to: