Long-term timing with millis(), which method is better?

Hi experts here,

in order to get a precise timing that method is commonly described:

//Method one

starttime = millis();
endtime = starttime;
while ((endtime - starttime) <=1000) // do this loop for up to 1000mS
{
// code here
loopcount++;
endtime = millis();
}
loopcount=0;

I can imagine that the storage of the comparison variables may cause a long-term drift and would break after 25 days.

I have imagined following method that should avoid that:

//Method 2
millisCurrent = millis()%1000; 
while (millisCurrent >= millisMemory)
{
// code here (must be longer than the millis() resolution)
loopcount++;
millisMemory = millisCurrent;
}
loopcount=0;

The millisCurrent should provide a chainsaw profile and the while should end when the chainsaw resets.

Do you agree with my considerations?

Looking forward to reading your remarks.
Laszlo

I would do it like this

//Method one

starttime = millis();

while ((millis() - starttime) <=1000) // do this loop for up to 1000mS
{
    // code here
     loopcount++;
}
loopcount=0;

…R

You probably would have a problem after 25 days as well.

[Edited] Finally not, the rollover appears to be unproblematic. I expected trouble, but with the substraction really works.

Sorry for the inadequate answer.

IMHO it would be better to use an if (instead of the while) and let loop do the iteration.

jurs: No, both codes create problems.

BTW: The terms "precise timing" and "timing derived from 16 MHz controller clock" does not fit together.

OK, you are right, precision is relative.

I don't need an atomic clock. I just must ensure, that the software does not break on the long term, and does not add unnecessary drift.

On the other side I have a Linux program that expects to get 3600 measures every hour.

It could cope with 3610 or 3590 but begins to do weirds things if that count drifts too much. My sketch gets complexer every day, so my lazy delay() instructions are not suitable any more.

RIN67630: I don't need an atomic clock. I just must ensure, that the software does not break on the long term, and does not add unnecessary drift.

On the other side I have a Linux program that expects to get 3600 measures every hour.

It could cope with 3610 or 3590 but begins to do weirds things if that count drifts too much. My sketch gets complexer every day, so my lazy delay() instructions are not suitable any more.

With boards in R3 design it is not guaranteed that the seconds accuracy is within 3610 and 3590 per hour.

Let's calculate with 0.5% accurate ceramic resonator again: 3600s * 0.005 = 18 seconds

So using millis() along with "accurate code" and a R3 board, Each hour may acutally have something between 3582 and 3618 "counted seconds".

Accuracy of millis() depends on the board you use. Could you use one of the older Arduino design boards which are using a "crystal oscillator" instead of a "ceramic resonator" to create their 16 MHz clock?

Suitable board design would be "UNO DUEMILANOVE" (UNO 2009).

I think Sainsmart have them until now, sold as board "SainSmart Duemilanove".

Cystal oscillator boards are clocking much more accurate, let's say accurate up to 0.008%. Accuracy would then be 86400* 0.00008 = ca. 7 seconds per day

Could you use a "Duemilanove" board disign with crystal oscillator clocking?

If not, I'd recommend to use a RTC module. A DS1307 at least, or a DS3131 for very high accuracy on the long run.

delay() is implemented with millis(). So if delay() is good enough, millis() is good enough. There is no "break after 25 days", in the code in your original post if unsigned long variables are used, because the unsigned subtraction that is used provides a correct result regardless of the value of millis().

But badly written code will fail. Things like comparing to a future timestamp, using signed values or 16 bit values. If you stick to the rules, all will be well.

If you want better precision, there are Arduino boards that have a real crystal oscillator instead of a resonator. Any that use the 32U4.

You can get or build a 'Duino' with crystal instead of resonator.

You can connect a watch crystal to a couple of pins and watch that or get an RTC module, maybe it can time your interval and send an interrupt when it's up -- I'm not sure.

Have the Linux computer send a 'synch char' every second is another solution. Linux is never too busy to do that.

GoForSmoke: Have the Linux computer send a 'synch char' every second is another solution. Linux is never too busy to do that.

If I only had control of that Linux program! :-(

By the way we have got a Windows version of the host, that is more tolerant. But it must work with evry variant and with the original hardware as well.

Whandall: IMHO it would be better to use an if (instead of the while) and let loop do the iteration.

Could you please explain why that one should be better? Thank you

That is a documented approach to compare the drift of two millis()based methods to generate an event more or less exactly every second.

The first (and usual) method stores the initial() millis value and waits in a loop until the new millis() value has reached the requested time.

unsigned long  millisNow;
unsigned long  millisCurrent;
unsigned long  millisMemory = 0;
int loopcount = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}
void loop()
{
  // put your main code here, to run repeatedly:
  millisCurrent = millis();

  while ((millis() - millisCurrent) <= 1000UL)
  {
    delay(39);
    if (loopcount < 1)
    {
      Serial.print (" MillisNow: ");
      Serial.print (millisNow);
      Serial.print (" | MillisCurrent: ");
      Serial.print (millisCurrent);
      Serial.print (" | MillisMemory: ");
      Serial.print (millisMemory);
      Serial.print (" | loopcount: ");
      Serial.print (loopcount);
      Serial.println ();
      millisMemory = millis();
    }
    millisNow = millis();
    loopcount++;
  }
  loopcount = 0;
}

That method accrues also the errors due to the processing time and the low defnintion of millis():

As you can see in the serial output the millisNow value is always more than the preceeding value +1000. by about 25mS

MillisNow: 16418 | MillisCurrent: 16418 | MillisMemory: 15442 | loopcount: 0
MillisNow: 17446 | MillisCurrent: 17446 | MillisMemory: 16472 | loopcount: 0
MillisNow: 18476 | MillisCurrent: 18476 | MillisMemory: 17501 | loopcount: 0
MillisNow: 19505 | MillisCurrent: 19505 | MillisMemory: 18530 | loopcount: 0
MillisNow: 20534 | MillisCurrent: 20534 | MillisMemory: 19558 | loopcount: 0
MillisNow: 21563 | MillisCurrent: 21563 | MillisMemory: 20587 | loopcount: 0
MillisNow: 22591 | MillisCurrent: 22591 | MillisMemory: 21616 | loopcount: 0
MillisNow: 23620 | MillisCurrent: 23620 | MillisMemory: 22645 | loopcount: 0
MillisNow: 24649 | MillisCurrent: 24649 | MillisMemory: 23674 | loopcount: 0
MillisNow: 25678 | MillisCurrent: 25678 | MillisMemory: 24702 | loopcount: 0
MillisNow: 26707 | MillisCurrent: 26707 | MillisMemory: 25732 | loopcount: 0
MillisNow: 27736 | MillisCurrent: 27736 | MillisMemory: 26761 | loopcount: 0
MillisNow: 28765 | MillisCurrent: 28765 | MillisMemory: 27790 | loopcount: 0
MillisNow: 29794 | MillisCurrent: 29794 | MillisMemory: 28819 | loopcount: 0
MillisNow: 30823 | MillisCurrent: 30823 | MillisMemory: 29847 | loopcount: 0
MillisNow: 31852 | MillisCurrent: 31852 | MillisMemory: 30876 | loopcount: 0
MillisNow: 32880 | MillisCurrent: 32880 | MillisMemory: 31905 | loopcount: 0
MillisNow: 33909 | MillisCurrent: 33909 | MillisMemory: 32934 | loopcount: 0
MillisNow: 34938 | MillisCurrent: 34938 | MillisMemory: 33964 | loopcount: 0
MillisNow: 35968 | MillisCurrent: 35968 | MillisMemory: 34992 | loopcount: 0
MillisNow: 36997 | MillisCurrent: 36997 | MillisMemory: 36021 | loopcount: 0
MillisNow: 38025 | MillisCurrent: 38025 | MillisMemory: 37050 | loopcount: 0
MillisNow: 39054 | MillisCurrent: 39054 | MillisMemory: 38079 | loopcount: 0
MillisNow: 40083 | MillisCurrent: 40083 | MillisMemory: 39108 | loopcount: 0
MillisNow: 41112 | MillisCurrent: 41112 | MillisMemory: 40136 | loopcount: 0
MillisNow: 42141 | MillisCurrent: 42141 | MillisMemory: 41165 | loopcount: 0
MillisNow: 43169 | MillisCurrent: 43169 | MillisMemory: 42194 | loopcount: 0
MillisNow: 44198 | MillisCurrent: 44198 | MillisMemory: 43224 | loopcount: 0
MillisNow: 45228 | MillisCurrent: 45228 | MillisMemory: 44252 | loopcount: 0

The following method appears to give better results:

unsigned long  millisNow;
int  millisCurrent;
int  millisMemory = 0;
int loopcount = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}
void loop()
{
  // put your main code here, to run repeatedly:
  millisCurrent = millis() % 1000UL;
  delay(39);
  millisNow = millis();
  if (millisMemory > millisCurrent)
  {
    Serial.print (" MillisNow: ");
    Serial.print (millisNow);
    Serial.print (" | MillisCurrent: ");
    Serial.print (millisCurrent);
    Serial.print (" | MillisMemory: ");
    Serial.print (millisMemory);
    Serial.print (" | loopcount: ");
    Serial.print (loopcount);
    Serial.println ();
    loopcount = 0;
  }
  ++loopcount;
  millisMemory = millisCurrent;
}

The serial monitor returns a millisNow value with a strong jitter, but the mean distance between two values keeps being 1000.

MillisNow: 631045 | MillisCurrent: 6 | MillisMemory: 967 | loopcount: 25
MillisNow: 632071 | MillisCurrent: 32 | MillisMemory: 992 | loopcount: 26
MillisNow: 633058 | MillisCurrent: 19 | MillisMemory: 980 | loopcount: 25
MillisNow: 634046 | MillisCurrent: 7 | MillisMemory: 968 | loopcount: 25
MillisNow: 635072 | MillisCurrent: 33 | MillisMemory: 993 | loopcount: 26
MillisNow: 636059 | MillisCurrent: 20 | MillisMemory: 981 | loopcount: 25
MillisNow: 637047 | MillisCurrent: 8 | MillisMemory: 969 | loopcount: 25
MillisNow: 638073 | MillisCurrent: 33 | MillisMemory: 995 | loopcount: 26
MillisNow: 639060 | MillisCurrent: 22 | MillisMemory: 983 | loopcount: 25
MillisNow: 640049 | MillisCurrent: 10 | MillisMemory: 971 | loopcount: 25
MillisNow: 641075 | MillisCurrent: 36 | MillisMemory: 997 | loopcount: 26
MillisNow: 642063 | MillisCurrent: 24 | MillisMemory: 985 | loopcount: 25
MillisNow: 643051 | MillisCurrent: 11 | MillisMemory: 972 | loopcount: 25
MillisNow: 644077 | MillisCurrent: 38 | MillisMemory: 999 | loopcount: 26
MillisNow: 645065 | MillisCurrent: 26 | MillisMemory: 987 | loopcount: 25
MillisNow: 646053 | MillisCurrent: 13 | MillisMemory: 975 | loopcount: 25
MillisNow: 647041 | MillisCurrent: 2 | MillisMemory: 963 | loopcount: 25
MillisNow: 648067 | MillisCurrent: 28 | MillisMemory: 989 | loopcount: 26
MillisNow: 649054 | MillisCurrent: 15 | MillisMemory: 976 | loopcount: 25
MillisNow: 650042 | MillisCurrent: 3 | MillisMemory: 964 | loopcount: 25
MillisNow: 651068 | MillisCurrent: 28 | MillisMemory: 989 | loopcount: 26
MillisNow: 652055 | MillisCurrent: 16 | MillisMemory: 977 | loopcount: 25
MillisNow: 653043 | MillisCurrent: 4 | MillisMemory: 965 | loopcount: 25
MillisNow: 654069 | MillisCurrent: 29 | MillisMemory: 990 | loopcount: 26
MillisNow: 655056 | MillisCurrent: 17 | MillisMemory: 979 | loopcount: 25
MillisNow: 656045 | MillisCurrent: 6 | MillisMemory: 966 | loopcount: 25
MillisNow: 657070 | MillisCurrent: 31 | MillisMemory: 992 | loopcount: 26
MillisNow: 658058 | MillisCurrent: 19 | MillisMemory: 980 | loopcount: 25
MillisNow: 659046 | MillisCurrent: 6 | MillisMemory: 967 | loopcount: 25
MillisNow: 660071 | MillisCurrent: 32 | MillisMemory: 993 | loopcount: 26
MillisNow: 661059 | MillisCurrent: 20 | MillisMemory: 981 | loopcount: 25
MillisNow: 662046 | MillisCurrent: 7 | MillisMemory: 968 | loopcount: 25
MillisNow: 663072 | MillisCurrent: 33 | MillisMemory: 994 | loopcount: 26
MillisNow: 664060 | MillisCurrent: 22 | MillisMemory: 983 | loopcount: 25
MillisNow: 665048 | MillisCurrent: 9 | MillisMemory: 970 | loopcount: 25
MillisNow: 666074 | MillisCurrent: 35 | MillisMemory: 996 | loopcount: 26
MillisNow: 667062 | MillisCurrent: 23 | MillisMemory: 983 | loopcount: 25
MillisNow: 668049 | MillisCurrent: 10 | MillisMemory: 971 | loopcount: 25
MillisNow: 669076 | MillisCurrent: 37 | MillisMemory: 998 | loopcount: 26
MillisNow: 670064 | MillisCurrent: 24 | MillisMemory: 985 | loopcount: 25
MillisNow: 671051 | MillisCurrent: 12 | MillisMemory: 973 | loopcount: 25
MillisNow: 672039 | MillisCurrent: 1 | MillisMemory: 962 | loopcount: 25
MillisNow: 673064 | MillisCurrent: 26 | MillisMemory: 987 | loopcount: 26
MillisNow: 674053 | MillisCurrent: 14 | MillisMemory: 975 | loopcount: 25
MillisNow: 675041 | MillisCurrent: 2 | MillisMemory: 962 | loopcount: 25
MillisNow: 676066 | MillisCurrent: 27 | MillisMemory: 988 | loopcount: 26
MillisNow: 677054 | MillisCurrent: 15 | MillisMemory: 976 | loopcount: 25
MillisNow: 678042 | MillisCurrent: 2 | MillisMemory: 963 | loopcount: 25
MillisNow: 679067 | MillisCurrent: 28 | MillisMemory: 989 | loopcount: 26
MillisNow: 680055 | MillisCurrent: 16 | MillisMemory: 977 | loopcount: 25

Maybe I have a bug in the first sketch, but i tried to avoid having much code outside the loop to minimize the drift, which i never could get smaller.

I cannot explain, why the drift in the first version is so strong. It is about 29 mS per second.
That is extreme and makes the code useless!

What is your analysis?
Regards
Laszlo

You are spending 39 mSec EVERY iteration of loop in delay(). That is your single, biggest error source. Hitting exactly 1000 mSec is nearly impossible, because 1000 is not divisible by 39.

If you want to drive your car exactly 1000 miles, but only look at your odometer once every 39 miles, how close will you come to your destination?

Regards, Ray L.

Why not this method? Only has a few microseconds of time difference from second to second

unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedTime;

byte hundredths;
byte tenths;
byte secondsOnes;
byte oldSecondsOnes;
byte secondsTens;
byte minutesOnes = 0;
byte minutesTens = 4;
byte hoursOnes = 1;
byte hoursTens = 1;

void setup(){

  Serial.begin(115200); // make serial monitor match
  currentMicros = micros();
  previousMicros = currentMicros;
  Serial.println ("Setup Done");
}

void loop(){

  currentMicros = micros();

  // how long's it been?
  elapsedTime = currentMicros - previousMicros;
  //Serial.print ("Elapsed: ");  
  //Serial.println (elapsedTime);
  if ( elapsedTime >=10000UL){  // 0.01 second passed? Update the timers
    elapsedTime = 0;
    previousMicros  = previousMicros + 10000UL;
    hundredths = hundredths+1;
    if (hundredths == 10){
      hundredths = 0;
      tenths = tenths +1;
      if (tenths == 10){
        tenths = 0;
        secondsOnes = secondsOnes + 1;
        if (secondsOnes == 10){
          secondsOnes = 0;
          secondsTens = secondsTens +1;
          if (secondsTens == 6){ 
            secondsTens = 0;
            minutesOnes =  minutesOnes + 1;
            if (minutesOnes == 10){
              minutesOnes = 0;
              minutesTens = minutesTens +1;
              if (minutesTens == 6){
                minutesTens = 0;
                hoursOnes = hoursOnes +1;
                if (hoursOnes == 10){
                  hoursOnes = 0;
                  hoursTens = hoursTens =1;
                  if (hoursOnes == 4 && hoursTens ==2){
                    hoursOnes = 0;
                    hoursTens = 0;
                  }
                }
              } // minutesTens rollover check
            } // minutesOnes rollover check
          } // secondsTens rollover check
        } // secondsOnes rollover check
      } // tenths rollover check
    } // hundredths rollover check
  } // hundredths passing check



  if (oldSecondsOnes != secondsOnes){  // show the elapsed time
    oldSecondsOnes = secondsOnes;
    Serial.print ("Time: ");
    Serial.print (hoursTens);
    Serial.print(hoursOnes);
    Serial.print(":");
    Serial.print(minutesTens);
    Serial.print(minutesOnes);
    Serial.print(":");
    Serial.print(secondsTens);
    Serial.print(secondsOnes);
    Serial.print(" micros: ");
    Serial.println (currentMicros);

  } // end one second check

} // end loop
Time: 11:40:01 micros: 1000064
Time: 11:40:02 micros: 2000064
Time: 11:40:03 micros: 3000068
Time: 11:40:04 micros: 4000068
Time: 11:40:05 micros: 5000064
Time: 11:40:06 micros: 6000064
Time: 11:40:07 micros: 7000060
Time: 11:40:08 micros: 8000060
Time: 11:40:09 micros: 9000060
Time: 11:40:10 micros: 10000068
Time: 11:40:11 micros: 11000068
Time: 11:40:12 micros: 12000060
Time: 11:40:13 micros: 13000064
Time: 11:40:14 micros: 14000068
Time: 11:40:15 micros: 15000060
Time: 11:40:16 micros: 16000060
Time: 11:40:17 micros: 17000064
Time: 11:40:18 micros: 18000064
Time: 11:40:19 micros: 19000060
Time: 11:40:20 micros: 20000064
Time: 11:40:21 micros: 21000068
Time: 11:40:22 micros: 22000068
Time: 11:40:23 micros: 23000060
Time: 11:40:24 micros: 24000060
Time: 11:40:25 micros: 25000064

RIN67630: That is a documented approach to compare the drift of two millis()based methods to generate an event more or less exactly every second. ... ... What is your analysis? Regards Laszlo

So what does looking at two versions of millis() counting have to do with your objective? Nothing, really.

Looking at how the Arduino's clock ticks and millis() function returns tells you nothing about its accuracy, however precise it may be.

You are after a red herring, as I think folks have pointed out already on your other post.

Please do not cross-post. Threads merged.

RayLivingston:
You are spending 39 mSec EVERY iteration of loop in delay(). That is your single, biggest error source. Hitting exactly 1000 mSec is nearly impossible, because 1000 is not divisible by 39.

If you want to drive your car exactly 1000 miles, but only look at your odometer once every 39 miles, how close will you come to your destination?

Regards,
Ray L.

Thhe 39mS is a placeholder for the code I have to run. It takes currently between 23 and 29mS to run.
That was precisely my aim: having a timing that does not (in the long time) depend on the processing time.

I don’t care of a jitter due to the fact that “I cannot read the odometer” permanently, but the errors should not accrue, they should compensate in the long term.

have you tried a timer interrupt?

If you want to avoid accumulated errors using millis() do it like this

if (millis() - lastMillis >= interval) {
   lastMillis += interval);
   // other stuff
}

Everything is counted relative to the original value of lastMillis

There is an extensive discussion of this in the Thread Several Things at a Time

...R

CrossRoads: Why not this method? Only has a few microseconds of time difference from second to second

unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedTime;

byte hundredths; byte tenths; byte secondsOnes; byte oldSecondsOnes; byte secondsTens; byte minutesOnes = 0; byte minutesTens = 4; byte hoursOnes = 1; byte hoursTens = 1;

....

 if (oldSecondsOnes != secondsOnes){  // show the elapsed time    oldSecondsOnes = secondsOnes;    Serial.print ("Time: ");    Serial.print (hoursTens);    Serial.print(hoursOnes);    Serial.print(":");    Serial.print(minutesTens);    Serial.print(minutesOnes);    Serial.print(":");    Serial.print(secondsTens);    Serial.print(secondsOnes);    Serial.print(" micros: ");    Serial.println (currentMicros);

 } // end one second check

} // end loop





Time: 11:40:01 micros: 1000064 Time: 11:40:02 micros: 2000064 Time: 11:40:03 micros: 3000068 Time: 11:40:04 micros: 4000068 Time: 11:40:05 micros: 5000064 Time: 11:40:06 micros: 6000064 Time: 11:40:07 micros: 7000060 Time: 11:40:08 micros: 8000060 Time: 11:40:09 micros: 9000060 Time: 11:40:10 micros: 10000068 Time: 11:40:11 micros: 11000068 Time: 11:40:12 micros: 12000060 Time: 11:40:13 micros: 13000064 Time: 11:40:14 micros: 14000068 Time: 11:40:15 micros: 15000060 Time: 11:40:16 micros: 16000060 Time: 11:40:17 micros: 17000064 Time: 11:40:18 micros: 18000064 Time: 11:40:19 micros: 19000060 Time: 11:40:20 micros: 20000064 Time: 11:40:21 micros: 21000068 Time: 11:40:22 micros: 22000068 Time: 11:40:23 micros: 23000060 Time: 11:40:24 micros: 24000060 Time: 11:40:25 micros: 25000064 ```

Yes. The good thing is that errors don't accrue. Even when I stressed the sketch with a delay(39) for the payload code and reduced the serial speed to 9600 Baud (a given constraint) it kept to be precise:

Setup Done
Time: 11:40:01 micros: 1000052
Time: 11:40:02 micros: 2000048
Time: 11:40:03 micros: 3000048
Time: 11:40:04 micros: 4000052
Time: 11:40:05 micros: 5000052
Time: 11:40:06 micros: 6000048
Time: 11:40:07 micros: 7000052
Time: 11:40:08 micros: 8000052
Time: 11:40:09 micros: 9000048
Time: 11:40:10 micros: 10000048
Time: 11:40:11 micros: 11000052
Time: 11:40:12 micros: 12000048
Time: 11:40:13 micros: 13000048
Time: 11:40:14 micros: 14000052
Time: 11:40:15 micros: 15000048
Time: 11:40:16 micros: 16000048
Time: 11:40:17 micros: 17000052
Time: 11:40:18 micros: 18000048
Time: 11:40:19 micros: 19000048
Time: 11:40:20 micros: 20000048
Time: 11:40:21 micros: 21000048
Time: 11:40:22 micros: 22000048
Time: 11:40:23 micros: 23000048
Time: 11:40:24 micros: 24000048
Time: 11:40:25 micros: 25000052
Time: 11:40:26 micros: 26000052
Time: 11:40:27 micros: 27000048
Time: 11:40:28 micros: 28000052
Time: 11:40:29 micros: 29000052
Time: 11:40:30 micros: 30000052
Time: 11:40:31 micros: 31000052
Time: 11:40:32 micros: 32000052
Time: 11:40:33 micros: 33000048
Time: 11:40:34 micros: 34000052
Time: 11:40:35 micros: 35000052
Time: 11:40:36 micros: 36000052
Time: 11:40:37 micros: 37000052
Time: 11:40:38 micros: 38000052
Time: 11:40:39 micros: 39000048
Time: 11:40:40 micros: 40000052
Time: 11:40:41 micros: 41000052
Time: 11:40:42 micros: 42000048
Time: 11:40:43 micros: 43000048
Time: 11:40:44 micros: 44000052
Time: 11:40:45 micros: 45000052
Time: 11:40:46 micros: 46000052
Time: 11:40:47 micros: 47000048

OK, it's a lot of code for the task and it will become a Hard-Duino if we stress it that much.

The modulo division could be done on micros() as well. I tried and got a bit less jitter. That was IMHO not worth, as I don't really mind some erratic differences of +-30ms. The most important thing is that the differences must not accumulate over hours and days.

The hardware drift is another chapter that I don't want to discuss here.