simple timer using Millis()

I want to make a simple timer.

It needs to start when the program starts (as Millis() does) but then on an event, stop and on another event start again at zero.

The problem I see is Millis() keeps running as long as the program runs and cant be reset to zero as far as I know?

show your code

Please explain your requirements using examples.


‘When’ you want to time something, you set a variable equal to millis(), this basically starts the TIMER process.

From that point on, the variable is subtracted from millis() to the point where an interval has been met.

kpg:
The problem I see is Millis() keeps running as long as the program runs and cant be reset to zero as far as I know?

My clock on the wall also keeps running, but I still can organise my life around that.

millis() is the same. Use it as you would use the clock on the wall.

If you start something, record the time.
When you have finished, subtract the recorded time from the current time, to find the elapsed time.

elapsedTime = currentTime - previousTime

Study the BlinkWithoutDelay example in the IDE.
Leo..

boolean Running = false;
unsigned long StartTime = 0;
unsigned long StopTime = 0;

void setup()
{
  Serial.begin(115200);
}

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

  if (! Running && StartEvent)
  {
    StartTime = currentTime;
    Running = true;
  }

  if (Running && StopEvent)
  {
    StopTime = currentTime - StartTime;
    Running = false;
  }

  if (Running)
  {
    Serial.println(currentTime - StartTime);
  }
  else
  {
    Serial.println(StopTime);
  }
}

Thanks guys

I have looked at the examples and the code sent. Seems simple enough and elegant. Just needed to get my head around what Millis() is doing.Appreciate the help. :slight_smile:

The demo Several Things at a Time illustrates the use of millis() to manage timing. It may help with understanding the technique.

Have a look at Using millis() for timing. A beginners guide if you need more explanation.

...R

Sorry Guys I thought I had this but obviously not.

The web examples referenced either set start time as 0 permanently as a reference or use a time difference count to reset.

The example in this thread from John doesn't do that and I cant see how it works. My notes/questions are in the sketch below...

boolean Running = false;
unsigned long StartTime = 0;
unsigned long StopTime = 0;

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  unsigned long currentTime = millis();// set current time to millis

  if (! Running && StartEvent)
  {
    StartTime = currentTime;// when start event occurs set start time to current time
    Running = true;//and move to (Running)
  }

  if (Running && StopEvent)
  {
    StopTime = currentTime - StartTime;
    Running = false;
  }

  if (Running)
  {
    Serial.println(currentTime - StartTime);//This is where i get confused. aren't these the same? Both were made equal in first IF and are current Millis?
  }
  else
  {
    Serial.println(StopTime);
  }
}

The sketch below is what I am trying to build.

I get a miles from EEprom, run the trip when the start event (rfid trigger) happens and save back to eeprom when the stop event happens.

I have a separate oled showing the total (odo) miles and a trip miles that resets to zero on every stop event.

It almost works but the numbers sometimes increment then decrement a little when running.

I have not included the OLED or RFID code for clarity. The water sender pin is just a pot simulating miles per hour.

The 700 value simulates 0 MPH to stop adding time when the vehicle is stopped.

#include <EEPROM.h>

float MPHour;
int waterSenderPin = A4;
float distanceMiles;//
float distanceMiles2;//
int odoDistance;
float distanceMilesEEPROM;//read distance from eeprom

//EEprom setup
int eeAddress = 0;   //Location I want the Odomoter data to be put.
float OdoTotal = 0.00f;

//Odometer setup
boolean Running = false;
unsigned long StartTime = 0;
unsigned long currentTime;

void setup(void) {

  pinMode (waterSenderPin, INPUT);//Pot used to sim miles
}
void loop()
{ odoMeter();
}

void odoMeter ()
{
  MPHour = analogRead(waterSenderPin); //test pot to simulate speed//(KMHour/1.60934);test add maths back later
  currentTime = millis();
  if (! Running && powerpinState == HIGH && MPHour > 700)//uses rfid for on event. Code not included but it works
  {
    StartTime = currentTime;
    EEPROM.get(eeAddress, distanceMilesEEPROM); //Get the float data from the EEPROM at position 'eeAddress'
    Serial.println("Read float from EEPROM: ");
    Serial.print(distanceMilesEEPROM, 2);
    Serial.println("distanceMilesEEPROM ");
    Serial.println("");

    Running = true;
  }
  if (Running && powerpinState == LOW)
  {
    EEPROM.put(eeAddress, distanceMiles);//One simple call, with the address first and the object second.
    Serial.println("Written float data type!");
    Serial.println("");
    Running = false;
  }
  if (Running)
  {
    Serial.println("");
    int cormph = (MPHour - 700);// adjusted pot value to make mph
    Serial.print((currentTime - StartTime) / 1000);
    Serial.print ("  Trip time secs ");
    Serial.println("");
    distanceMiles = (MPHour - 700) * ((currentTime - StartTime) / 3600000.00);
    distanceMiles2 = (MPHour - 700) * ((currentTime - StartTime) / 3600000.00);// variable sent to lcd display as a resetting trip display before resave to eeprom

    distanceMiles = (distanceMiles + distanceMilesEEPROM);//product of previous miles save and new miles
    OdoTotal = distanceMiles;// value sent to lcd to show total miles
    Serial.print(OdoTotal, 2);
    Serial.println(" OdoTotal");
  }

}

Both were made equal in first IF and are current Millis?

No, they were individually set in different if clauses.

Make sure you understand this example before applying it to your own code.

I missed the initial set of;

StartTime = currentTime;

It makes sense now and works. Thanks for the pointer.

So expanding it a little to calculate distance. While the basic maths of distance = speed * time is used, i get strange results in that the trip distance, while increasing, occasionally jumps back and forwards a little.

My speed it being simulated by an analog read input which does vary by a few miles per hour up and down but that is a vehicles behavior as well.

Do I need some form of averaging to get a stable and incrementing distance?

Odometer ()
{
  unsigned long currentTime = millis();
  float MPHour;  

  
  if (! Running && powerpinState == HIGH && MPHour > 1)//powerpinState is from RFID
  {
    StartTime = currentTime;
        
    Running = true;
  }

  if (Running && powerpinState == LOW)
  {
    TripTime = currentTime - StartTime;//declared in setup as UL's
   
    tripdistanceMiles = ((MPHour  * (TripTime / 3600000.00));// trip distance on power down
    
    Running = false;
  }

  if (Running)
  {
    Serial.print(currentTime - StartTime);
    rundistanceMiles = ((MPHour  * (currentTime - StartTime) / 3600000.00));
    Serial.print(rundistanceMiles);// cumulative trip distance while running
   

  }
  else
  {

    Serial.print(TripTime / 1000);// in seconds
    
  }
}

Please give examples of the problem.

Always post ALL the code. Snippets leave out vital information.

This uses floating point to get around overflowing unsigned long variables?

rundistanceMiles = ((MPHour  * (currentTime - StartTime) / 3600000.00));

Instead of MPH which has miles and hours scale, you would not need inherently inaccurate 32-bit floats (Arduino doubles are also 32-bit not 64) to divide MPH-baked-in hours to get milliseconds if your speed units are on a scale of feet/second.

What's more is that Arduinos in general have no FPU, floating point calculations are very slow on them on top of the inherent dirt-in-sand way that IEEE floating point does. When I wrote business apps, especially payroll and billing where people go nuts over a single penny I used integers and small units (it's like fixed-point only simpler) to calculate with.

Oh BTW, Arduino does have 64-bit long long and unsigned long long integers with 19 places that can be 0 to 9.

.................

I don't see code that deals with speed changes. It looks like you use whatever speed you're going times elapsed time to get your final distance. I hope I missed the part where it handles speed change but those print statements in Odometer() seem to say no.

With every speed change I would calculate a segment of the trip and add it to the total. There will be segments/second.
I would want an Arduino with a crystal, not any with a resonator (temperature sensitive clock source) even if I had to roll my own.

Maybe think of adding an SD module and recording a log of time and speed recordings, at least 100/second to see what's happening. Maybe your trip can be calculated in 10ms or even 1ms segments? With more math using last and next segment speed values, rises and falls can be averaged a bit more accurately but do not expect to do more than get closer to real.

I think you have highlighted what I dont understand.

MPH comes in serially from another arduino with a simple revolution counter on it and as you say, I am not accounting for changes in speed, I am just using distance = speed x time which clearly isnt right. I'm pretty new to this so may struggle to understand a bit.

I have EEPROm save code but at the moment am just saving trip time, recalling it and adding it to new trip time to get total (Odo) time (and distance using the simple formula).

It sounds like I need some complex code to look at rising and falling edges etc.

I scanned the forum but cant find anything that helps much understanding the issue.

MPH comes in serially from another arduino with a simple revolution counter on it.

It sounds like I need some complex code to look at rising and falling edges etc.

You get speed that the other Arduino can give in small units that need no decimal point via serial, your code looks for Serial.available and not rising/falling edges.

The Arduino that reads the wheel may detect edges using fast polling (read the pin 50+K/sec, no biggie with non-blocking code) or use an interrupt if you want to get closer than +/- 10 microseconds.

Nick Gammon's Interrupts Tutorial:

His site has a load of microprocessor how-to tutorials, all fully-practical detailed with commonsense explanations to help and line by line code & explanations. Nick wants to make sure you get good help, bookmarking the MCU page and some lesson addys is a really good idea even if it will be a while before you get to them.

Yes, the other arduino speedo code uses interupts and seems pretty good at generating MPH.

On the Odometer, I was just wondering if some simple averaging would make any significant improvement (like only calculate miles every 5 seconds etc.)? and maybe ignore any results that is < last result.

I know it wont be finitely accurate but would that help much?

The finer you slice it, the more accurate your approximation will be. I take my techniques from integral calculus and the maths like Simpson's that pre-dated Newton.

See how thicker slices miss filling under the curve by bigger margins than thinner slices?

And the finest slices fill under the curve most exactly?

So if part of the sketch watches the speedometer and updates a variable holding speed then perhaps 1000x a second the known speed is used to add a tiny bit to distance with an even tinier bit of error. Our slice is 1 ms wide and as high as our speed, the area is distance.

Do you see how the slices in the pictures are all flat topped? By keeping 3 reads in RAM it is possible to give the slice corresponding to the middle slice a triangle on top connecting the corners and a slice at the top or bottom of a curve might get an extra up or down pointing triangle filling under the real curve even better.

Keeping a running average without much RAM involves squaring and taking square roots that would run like a lead turtle on Arduinos that have no FPU and only 32-bit floating point. You might get 10 reads processed in a second and lose some accuracy with every FP operation.

The integer math to do this is area of rectangle and triangle that can be done quickly on AVRs if floats are avoided.
Choose small enough working units, you don't need decimal points.

If your speedometer only reads once per rev then speed change info is delayed. That can be remedied to reduce error.

When error of the whole device gets small enough, tire pressure increase from running heat can make a bigger difference.

When you get to where you can calibrate speed even more error goes.

Consider that 60 miles is 60 * 5280 * 12 * 1000 = 3801600000 * 0.001 inch.

60 MPH is that per hour but the same speed as thousandths of an inch per millisecond

3801600000 / 3600000 = 1056 * 0.001" per millisecond that would add to the trip distance.
Speedometer would give 1056 for 60MPH, 105 for 6MPH. These have room to divide without rounding badly.

At this fine scale a 32-bit unsigned long could hold a trip 67.7867... miles long.
A 64-bit unsigned long long could hold a trip over 4 billion times as long so can drive past Jupiter.

Thank you for being so helpful.

This version of my speedo code uses KM per Hour to make it a bit simpler and KMHour is an Int.;

int BCDStart = 3;

int commonStart = 7;

boolean commonHigh = false;

int hall_pin = 2;           
int wheelCirc = 1000;
int KMHour = 0;
int hall_count = 0;

#define SPEED_MEAS_WINDOW       500     //mS update speed at 2Hz (500mS/update)
#define WHEEL_CIRC              1000    //mm
#define NUM_TICKS_PER_REV       1      
#define SPEED_K             (float)((WHEEL_CIRC * 3600.0)/(1.0E+06 * (float)NUM_TICKS_PER_REV * ((float)SPEED_MEAS_WINDOW/1000.0) ))

#define SPEED_STATE_WAIT        0
#define SPEED_STATE_INIT        1
#define SPEED_STATE_MEASURE     2
#define SPEED_STATE_COMPUTE     3

#define BLANK                   0x0a    //invalid BCD digit used to discern blank digit; 4511 produces "blank" for this 0b1010
#define DISP_UPDATE_TIME        5       //update rate of digits (30mS "frame rate")
#define DISP_STATE_SETUP        0
#define DISP_STATE_DIGIT_HUNDS  1
#define DISP_STATE_DIGIT_TENS   2
#define DISP_STATE_DIGIT_ONES   3

#define COMMON_CATHODE          1
//#define COMMON_ANODE          1//alternate display option

#ifdef  COMMON_CATHODE
#define COM_ON                  LOW
#define COM_OFF                 HIGH
#endif

#ifdef  COMMON_ANODE
#define COM_ON                  HIGH
#define COM_OFF                 LOW
#endif

void setup()
{
  int
  i;

  //set up Hall interrupt
  pinMode(hall_pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(hall_pin), HallInterrupt, FALLING);

  // Enable all bcd outputs,
  // set ABCD to blank (send 0b1010 which causes 4511 to blank outputs)
  for ( i = 0; i < 4; i++ )
  {
    pinMode(BCDStart + i, OUTPUT);
    digitalWrite(BCDStart + i, (BLANK & (1 << i)) ? HIGH : LOW);
  }//for

  //num digits == 3
  for ( i = 0; i < 3; i++ )
  {
    pinMode(commonStart + i, OUTPUT);
    digitalWrite(commonStart + i, COM_OFF );
  }//for

  KMHour = 0;

  Serial.begin(9600);
}

//////////////////////////////////////////////////////////////////////////////////
// HallInterrupt
//  on each falling edge of hall input, hall_count is incremented
//////////////////////////////////////////////////////////////////////////////////
void HallInterrupt( void )
{
  hall_count++;

}//HallInterrupt

void MeasureSpeed( void )
{
  int
  calc_hall_count;
  static byte
  speed_state = SPEED_STATE_INIT;
  static unsigned long
  timeWindow;
  unsigned long
  timeNow;

  switch ( speed_state )
  {
    case    SPEED_STATE_INIT:
      noInterrupts();
      timeNow = millis();
      hall_count = 0;
      //integration window is SPEED_MEAS_WINDOW mS
      timeWindow = timeNow + SPEED_MEAS_WINDOW;
      interrupts();
      speed_state = SPEED_STATE_MEASURE;

      break;

    case    SPEED_STATE_MEASURE:
      timeNow = millis();
      if ( timeNow >= timeWindow )
      {
        noInterrupts();
        //get a copy of the hall_count and move to compute speed
        calc_hall_count = hall_count;
        interrupts();
        KMHour = (int)((float)calc_hall_count * SPEED_K);

        //limit displayed value to 200kph
        if ( KMHour > 200 )
          KMHour = 200;
        Serial.println(KMHour);

        speed_state = SPEED_STATE_INIT;

      }//if

      break;

    default:
      speed_state = SPEED_STATE_INIT;
      break;

  }//switch

}//MeasureSpeed

void UpdateDisplay(  void )
{
  //update the display at 30Hz (~330mS) overall; requires us to have a 10mS step through this
  //state machine
  static int
  last_digit_num = 0;
  static int
  ones, tens, hunds;
  static byte
  displayState = DISP_STATE_SETUP;
  static unsigned long
  displayUpdate = millis() + DISP_UPDATE_TIME;

  if ( millis() < displayUpdate )
    return;

  //turn off last digit
  digitalWrite(commonStart + last_digit_num, COM_OFF);
  int
  tempKMHour;
  switch ( displayState )
  {
    case    DISP_STATE_SETUP:
      //get digits for latest speed
      tempKMHour = KMHour;
      hunds = (int)(tempKMHour / 100);
      tempKMHour = tempKMHour - 100 * hunds;
      tens = (int)(tempKMHour / 10);
      tempKMHour = tempKMHour - 10 * tens;
      ones = tempKMHour;

      displayState = DISP_STATE_DIGIT_HUNDS;

      break;

    case    DISP_STATE_DIGIT_HUNDS:
      //if hundreds value is 0 skip it (leading zero)
      if ( hunds == 0 )
        writeDigitToDisplay( BLANK );// first send remainder to function then 2nd pass send 10's to function
      else
        writeDigitToDisplay( hunds );

      digitalWrite(commonStart + 2, COM_ON);
      last_digit_num = 2;

      displayUpdate = millis() + DISP_UPDATE_TIME;
      displayState = DISP_STATE_DIGIT_TENS;

      break;

    case    DISP_STATE_DIGIT_TENS:
      //if hundreds value is 0 and tens is zero, skip it (another leading zero)
      //if hundreds value is 0 skip it (leading zero)
      if ( hunds == 0 && tens == 0 )
        writeDigitToDisplay( BLANK );
      else
        writeDigitToDisplay( tens );

      digitalWrite(commonStart + 1, COM_ON);
      last_digit_num = 1;

      displayUpdate = millis() + DISP_UPDATE_TIME;
      displayState = DISP_STATE_DIGIT_ONES;

      break;

    case    DISP_STATE_DIGIT_ONES:
      //always display ones
      writeDigitToDisplay( ones );

      digitalWrite(commonStart + 0, COM_ON);
      last_digit_num = 0;

      displayUpdate = millis() + DISP_UPDATE_TIME;
      displayState = DISP_STATE_SETUP;

      break;

  }//switch

}//UpdateDisplay

void loop()
{
  MeasureSpeed();
  UpdateDisplay();

}//loop

void writeDigitToDisplay(int value)
{
  int
  i;

  for ( i = 0; i < 4; i++ )
    digitalWrite( BCDStart + i, (value & (1 << i)) ? HIGH : LOW );

}//writeDigitToDisplay

I am trying to understand how to use integer maths and how your last post would apply to this?
I can keep it all in KM and just do the miles convert to the display later which probably helps.