Lesson 22. Work with time in Arduino. A sports stopwatch project.

Time in Arduino

We will consider the Arduino functions for working with time. Develop a sports stopwatch based on the Arduino board.

Previous lesson     List of lessons     Next lesson

There are 4 standard functions for working with time in the Arduino system:

  • delay (time);
  • delayMicroseconds (time);
  • millis ();
  • micros ().

Consider these functions in detail.

 

void delay (unsigned long time)

The function suspends the program for the time specified in ms.

delay (500); // pause for 0.5 seconds

We have already used this function in previous lessons.  More often it is used in debugging programs. In practical programs, the function is rarely used.

The fact is that it stops the program. The program does nothing for a given interval of time. This is not a permissible luxury, only valid in debug modes.

In addition, the function does not allow you to create cycles that exactly work out the specified time. For example, the time of the next cycle will not be exactly 100 ms.

while (true) {
  // code of the program block
  delay (100);
}

The cycle time is equal to the sum: the specified time 100 ms, the execution time of the program block and the transition time to the beginning of the while() cycle. The program block time may vary depending on the program execution algorithm. The transition time to the beginning of the cycle is also not defined exactly. As a result, the cycle time can only be determined approximately.

For the organization of cycles with a given time, it is better to use a timer interrupt (lesson 10). It is only necessary to understand that this method provides a stable cycle time, but it may differ slightly from the specified one. For example, the MsTimer2 library sets the time in ms. Real time may differ by a few µs. In some applications, for example in a clock, the error can accumulate and lead to unacceptable errors.

 

void delayMicroseconds (int time)

The function suspends the program for the time specified in μs.

delayMicroseconds (50); // pause for 50 µs

The analogue of the function delay (), only provides shorter program stops. It is quite acceptable for use in practical programs for two reasons.

  • Short periods of time are difficult to implement with other methods.
  • Stopping the program even for several tens of microseconds will not cause disastrous consequences.

 

unsigned long millis (void)

The function returns the time in ms, since the start of the current program. The time value overflows after 1193 hours, approximately 50 days.

tm = millis (); // read time in tm

In fact, this is a function of reading the Arduino system time. The time is counted in a parallel process and does not depend on the algorithms of program execution, stops, including the function delay(), etc. To measure a time interval, it is necessary to read the system time at the beginning and end of the interval and calculate the difference of these values. Below is an example of a program for working with time intervals.

Disabling interrupts with the noInterrupts() function for a long time may disrupt the system clock.

 

The accuracy of the millis () function.

The accuracy of the time counting by the millis() function is determined by the accuracy and stability of the frequency of the Arduino board quartz resonator. Even for cheap resonators, the frequency error does not exceed 30 ppm. Together with temperature instability, under normal conditions, it is 50 ppm, which corresponds to ± 0.00005%. Thus, the total absolute error of the Arduino system time will be:

  • 0.18 seconds for 1 hour;
  • 4.32 seconds for a day;
  • 129.6 seconds for a month;
  • 26 minutes for a year.

Probably quite acceptable accuracy for creating timers, stopwatches and even clocks.

 

unsigned long micros (void)

The function returns the time in μs since the start of the current program. The value overflows in about 70 minutes.

tm = micros (); // read time in tm

For Arduino boards with a clocking frequency of 16 MHz, the resolution of the value of the micros() function is 4 μs. For boards with a frequency of 8 MHz - 8 μs.

 

A sports stopwatch based on the Arduino board.

We implement a fairly simple project - a stopwatch. Probably, the practical application of such a device is limited. The stopwatch with power from the ac network has the right to exist only in stationary devices. In the scoreboard for sports competitions, in systems for intellectual games, etc. But having understood the program, you can easily correct the stopwatch for your tasks or create a completely different device.

The stopwatch is controlled by two buttons:

  • START / STOP;
  • RESET.

After pressing the START / STOP button, the stopwatch starts counting down the time. Pressing this button again stops the counting. The next press continues the countdown from the stopped value. Thus, the stopwatch can be used to count the “pure” playing time of sports competitions.

To reset the time, you must press the RESET button.

The time is displayed on a four-digit seven-segment display in the following formats.

Time value Digit 3 Digit 2 Digit 1 Digit 0
0 … 59 seconds seconds hundredths of a second
1 … 10 minutes minutes seconds tenths of a second
10 … 99 minutes minutes seconds
More than 99 minutes - - - -

The output format changes automatically depending on the time value. Minutes, seconds and the fractional part are separated on the indicator by decimal points. Pressing the buttons are accompanied by beeps.

 

The scheme of the sports stopwatch based on the Arduino UNO R3 board.

According to the already well-known circuits, we connect to the Arduino board:

  • 4-digit seven-segment LED display GNQ-3641BUE;
  • two buttons;
  • sound piezo emitter.

A sports stopwatch circuit based on Arduino

All of these components may be different. If you are developing a stopwatch for a sports scoreboard, the display should be large. You can even assemble it from individual LEDs. How to connect the display to the board can be found in lesson 19.

If the stopwatch control buttons are physically located at a considerable distance from the board, then it is better to connect them according to the security alarm scheme, lesson 17. Or at least connect the inputs of the buttons with + 5 V power through 1 kΩ resistors.

But for debugging and checking the program is enough of the above scheme. I assembled it on a breadboard.

An Arduino based sports stopwatch

 

The program for the sports stopwatch on Arduino.

The sketch of the program can be downloaded from this link.

Do not forget to install the libraries from previous lessons:

The sketch for the stopwatch looks like this.

// sports stopwatch
#include <MsTimer2.h>
#include <Led4Digits.h>
#include <Button.h>

#define SOUND_PIN 18 // sound emitter pin, pin 18 (A4)

// the display type 1; digit pins 5,4,3,2; segment pins 6,7,8,9,10,11,12,13
Led4Digits disp(1, 5,4,3,2, 6,7,8,9,10,11,12,13);

Button buttonReset(16, 10); // RESET button, pin 16 (A2)
Button buttonStartStop(17, 10); // START / STOP button, pin 17 (A3)

byte mode= 0; // mode, 0 - STOP, 1 - START
unsigned long msTime=0; // time of interval, milliseconds
byte minTime=0; // time of interval, minutes
byte decMinTime=0; // time of interval, tens of minutes
unsigned long prevTime; // previous time value
unsigned long curentTime; // current time value
byte soundCount=0; // sound time counter

void setup() {
  MsTimer2::set(2, timerInterrupt); // timer interrupts 2 ms
  MsTimer2::start(); // enable interrupt
  pinMode(SOUND_PIN, OUTPUT); // sound emitter pin
}

void loop() {

//----------------------- switching the START / STOP mode
  if ( buttonStartStop.flagClick == true ) {
    buttonStartStop.flagClick= false; // reset sign
    // mode inversion
    if ( mode == 0) {
      mode= 1; // START
      soundCount= 250; // sound for start of 250 * 2 ms
    }
    else { // STOP
    mode= 0;
    soundCount= 50; // sound on stop 50 * 2 ms
    }
  }

  //----------------------- RESET button
  if ( buttonReset.flagClick == true ) {
    buttonReset.flagClick= false; // reset sign
    msTime=0;
    minTime=0;
    decMinTime=0;
    soundCount= 50; // sound on reset 50 * 2 ms
  }

  //----------------------- time counting
  if ( mode == 0 ) {
    // STOP
    prevTime= millis();
  }
  else {
    // START
    curentTime= millis(); // read current time
    msTime += curentTime - prevTime; // add time to milliseconds
    if ( msTime > 59999 ) {
      // milliseconds overflowed, more than a minute
      msTime -= 60000;
      minTime ++; // +1 to minutes
      if ( minTime > 9 ) {
        // units of minutes overflowed
        minTime -= 10;
        decMinTime ++; // +1 to tens of minutes
      }
    }
    prevTime= curentTime; // overload previous time
  }

  //--------------------- display time
  if ( (minTime == 0) && (decMinTime == 0)) {
    // less than a minute
    disp.print(msTime / 10, 4, 0); // output four digits of milliseconds
    // points
    disp.digit[0] &= 0x7f; // extinguish
    disp.digit[1] &= 0x7f; // extinguish
    disp.digit[2] |= 0x80; // light
    disp.digit[3] &= 0x7f; // extinguish
  }
  else if ( decMinTime == 0 ) {
    // less than 10 minutes
    disp.print(msTime / 100, 3, 0); // output three digits of milliseconds
    disp.tetradToSegCod(3, minTime); // units of minutes output to the high digit
    // points
    disp.digit[0] &= 0x7f; // extinguish
    disp.digit[1] |= 0x80; // light
    disp.digit[2] &= 0x7f; // extinguish
    disp.digit[3] |= 0x80; // light
  }
  else if ( decMinTime < 10 ) {
    // less than 100 minutes
    disp.print(msTime / 1000, 2, 0); // output two digits of milliseconds
    disp.tetradToSegCod(3, decMinTime); // output of tens of minutes to the high digit
    disp.tetradToSegCod(2, minTime); // output units of minutes to 3 digit
    // points
    disp.digit[0] &= 0x7f; // extinguish
    disp.digit[1] &= 0x7f; // extinguish
    disp.digit[2] |= 0x80; // light
    disp.digit[3] &= 0x7f; // extinguish
  }
  else {
    // more than 100 minutes
    // ----
    disp.digit[0]= 0x40;
    disp.digit[1]= 0x40;
    disp.digit[2]= 0x40;
    disp.digit[3]= 0x40;
  }
}

//----------- interrupt handler 2 ms
void timerInterrupt() {
  disp.regen(); // regeneration of the display
  buttonReset.filterAvarage(); // scanning of button, filtering method by average
  buttonStartStop.filterAvarage(); // scanning of button, filtering method by average

  // sound
  if (soundCount != 0) {
    digitalWrite(SOUND_PIN, ! digitalRead(SOUND_PIN));
    soundCount--;
  }
}

Most of the program is designed according to the principles detailed in previous lessons.

  • Implemented timer interrupt with a period of 2 ms.
  • In the handler, interrupts are called:
    • the display regeneration method;
    • the methods for scanning the button signals;
    • the sound signal block .
  • According to the signs of pressing the buttons occur:
    • mode switching (START / STOP);
    • resetting the time.
  • Depending on the time value in the display unit, the format of data output changes.

I would like to dwell on the program block "time counting". Time counting can be implemented in different ways. For example, you can count the time in milliseconds, and in the display block to do the translation in the desired format. I decided that it was much easier to count time in the format:

  • milliseconds;
  • minutes;
  • tens of minutes.

In this case, it is not necessary to perform binary-decimal conversions to display the minutes. Of course, if we had the task of measuring only one time interval, then it would be enough to read time by the millis() function at the beginning and end of the interval, and then calculate the difference. But we need to accumulate time, so the algorithm is somewhat more complicated.

The program has enough comments. I think you can easily figure it out and be able to create a device for your tasks, be it a sports stopwatch, a system for intellectual games " What? Where? When? ” or ” Brain Ring ” and much more. We will return to the stopwatch and work with time in the development of sports scoreboards based on LED modules.

Previous lesson     List of lessons     Next lesson

8 thoughts on “Lesson 22. Work with time in Arduino. A sports stopwatch project.

  1. good tutorial….

    How to configure for minutes?
    How to add more independent inputs and outputs … without error …in
    thank you.

    Button buttonStartStop1 // START / STOP button
    Button buttonStartStop2 // START / STOP button
    Button buttonStartStop3 // START / STOP button

    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    pinMode(LED3, OUTPUT);

    • Hi!
      Sorry that I did not reply for a while. Was on vacation by the sea.
      Add a counter for minutes. But there are not enough display digits to display more minutes. This is a completely different project.

  2. Great tutorial site! I am beginner and sometimes it’s like a rocket science for my, especially millis() and timing. I have implemented code for time proportioning output on arduino, that has variable in time on/off duty cycle in ms. What I need to do is: when bool flag is true, I want to accumulate sum of all on( HIGH) time pulses during that period. When sum of all pulses in ms is above 60000 ms, pulsing output should by off, set to LOW. When bool flag is false, accumulated value must be reset, set to 0, time proportional pulsing output also must by off, set to LOW. I tried to modify your stopwatch project code but unfortunately with no success. :/

      • /* MotorizedValvePWM is a class that works like slow PWM output, called time proportionig output. Open or close output from floating controller class is connected to DutyCycle input of MotorizedValvePWM object block/class. openRun or closeRun output from floating controller are connected to Run input
        of MotorizedValvePWM object. CycleTime is initial value, set to 20000 ms (20sec) and can be changed by using setCycleTime(unsigned long Cycle) function by typping value in ms. ActuatorStrokeTime is a time of full actuator travel from fully closed to fully opened position. Must be typped in ms, eg. if motorized valve has travel time 180sec, 180000 must be typped. Motorized valve actuator must have 2 objects of MotorizedValvePWM, one for closing valve and another for opening valve, then 2 outputs from floating controller, one for closing and another for opening are connected to 2 different MotorizedValvePWM objects.In situations when accumulated time of eg opening contact is longer then actuator stroke time, that has place when for example mixing valve is fully opened and is not able to get setpoint on mixed output, then controller stops try open valve endless. When closig signal is applied to closing MotorizedValvePWM object and Run input of opening MotorizedValvePWM object is opened by floating controller signal, then accumulated time is reset and will be counted again when Run signal on Run input open MotorizedValvePWM object is high again.*/

        class MotorizedValvePWM{
        public:
        MotorizedValvePWM::MotorizedValvePWM(unsigned long TravelTime); // default constructor that sets initial value of CycleTime to 20000 ms
        bool FBEN; // block enable. VarInput
        bool Run; // Output openRun or closeRun from floating controller is connected to this input variable. VarInput
        unsigned long DutyCycle;// Output “open” or “close” from floating controller is connected to this input variable.Range is from 0 to 100 VarInput
        void setActuatorStrokeTime(unsigned long TravelTime); // method for setting actuator stroke time in ms.
        void setCycleTime(unsigned long Cycle); // method for setting time proportioning cycle time.(initial start value is 20000 ms)
        bool getResults(); // method first calls another work method for computing of pulseOut status, and then returns ready to use result as true or false
        bool getFBENO(); // method returns boolean enabled/disabled status of block. Can be ued to turn on or off another class function block.
        private:
        bool FBENO; // flag-NO contact, true/closed if block enabled. BOOL,VarOutput
        unsigned long ActuatorStrokeTime; // Time period of travel from fully closed to fully opened position, in ms . VarInput
        unsigned long CycleTime; // time proportional control cycle time, inially set for 20000 ms. When used with relays and motors should not by shorter.
        bool pulseOut; // calculated true(1) or false(0) output signal for motorized valve opening or closing relay.
        unsigned long cycle_start_time; // internal helping variable, Var
        unsigned long duty_cycle_time; // internal helping variable,Var
        unsigned long now_; // internal helping variable,Var
        bool pulse_out; // internal helping variable,Var
        unsigned long ms_time; // internal helping variable,take part in turning off output when actuator travel time in open or close direction elapsed,Var
        unsigned long prev_time; // internal helping variable,take part in turning off output when actuator travel time elapsed, Var
        unsigned long current_time; // internal helping variable,take part in turning off output when actuator travel time elapsed, Var
        void calculatePulseOut(); // internal work method for computations.
        };

        // method of default constructor that sets initial value of CycleTime to 20000 ms
        MotorizedValvePWM::MotorizedValvePWM(unsigned long TravelTime){
        ActuatorStrokeTime = TravelTime;
        unsigned long PERIOD_ = 20000;
        setCycleTime(PERIOD_);
        cycle_start_time = millis();
        ms_time = 0;
        }

        // method of setting ActuatorStrokeTime inside of class.
        void MotorizedValvePWM::setActuatorStrokeTime(unsigned long TravelTime){
        ActuatorStrokeTime = TravelTime;
        }

        // method of setting CycleTime inside of class.
        void MotorizedValvePWM::setCycleTime(unsigned long Cycle){
        CycleTime = Cycle;
        }

        // method of getting on/off status of a function block/class.
        bool MotorizedValvePWM::getFBENO(){
        return FBEN;
        }

        // method of getting calculated value, true or false, of needed pulseOut.
        bool MotorizedValvePWM::getResults(){
        calculatePulseOut();
        return pulseOut;
        }

        // internal work method for all needed computations in the class/function block.
        void MotorizedValvePWM::calculatePulseOut(){
        FBENO = FBEN;
        if (!FBEN){
        pulseOut = false;
        }
        if (FBEN){
        if(!Run){
        ms_time = 0; // RESET of counting accumulated on time of motor.
        pulseOut = false;
        }
        if (Run){
        cycle_start_time = millis();
        ms_time = 0;
        if (CycleTime 100){
        pulseOut = false;
        }
        else{
        duty_cycle_time = ((DutyCycle/100.0) * CycleTime);
        now_ = millis();
        if ((now_ – cycle_start_time) > CycleTime){
        cycle_start_time += CycleTime;
        }
        if (duty_cycle_time > (now_ – cycle_start_time)){
        pulse_out = true;
        }
        else{
        pulse_out = false;
        }
        if (pulse_out == false){ //STOP – Program part of counting accumulated time of motor travel
        prev_time = millis();
        }
        else{ // START
        current_time = millis();
        ms_time += current_time – prev_time;
        if (ms_time >= ActuatorStrokeTime){
        pulseOut = false;
        ms_time = ActuatorStrokeTime;
        }
        else{
        pulseOut = pulse_out;
        }
        prev_time = current_time;
        }
        }
        }
        }
        }

        • in comment with my sketch do not appear “or” symbol and some part is missing ! Correct should be :

          if (Run){
          cycle_start_time = millis();
          ms_time = 0;
          if (CycleTime 100){
          pulseOut = false;
          }

          • I see that everything between comparition operators just disappear when I post a comment on your website, so I must try to do it another way 😉

            if (Run){
            cycle_start_time = millis();
            ms_time = 0;
            if ((CycleTime 100)){
            pulseOut = false;
            }

            in fourth line should be:
            “if CycleTime smaller than 500 or DutyCycle greather than 100”

            Hope it will appear correctly now 🙂

          • Send me the sketch by email. It is difficult to understand a small part of the program. I sent you my email.

Leave a Reply

Your email address will not be published. Required fields are marked *