millis() compensation function

Hello,

I'd like to write a function which compensates millis() value on certain intervals.

First, timer0_millis has to be declared as extern as far as I know.

extern volatile unsigned long timer0_millis;

Then the function: (based on the millis() implementation in wiring.c)

void compensateMillis(long compensationValue)
{
  uint8_t oldSREG = SREG;
  // disable interrupts while we read timer0_millis or we might get an
  // inconsistent value (e.g. in the middle of a write to timer0_millis)
  cli();
  timer0_millis += compensationValue;
  SREG = oldSREG;
}

This should work until timer0_millis is not around the max value of unsigned long (or 0 if compensation value is negative), so this solution is not complete.

What would be the most efficient way to compensate timer0_millis in any direction and properly handle overflow?

Thanks in advance.

for what use of millis() di you want to compensated millis()? how do you determine the amount of compensation? what is the clock source? rezonator or crystal?

I've built a module with a standalone Atmega328p and the clock source is an external crystal. I've measured 1 sec drift in 12 hours. So I could make it more accurate if I add 83ms to the timer0_millis every hour.

For what use? Nothing concrete at this point. :) Just experimenting.

amazed:
the clock source is an external crystal. I’ve measured 1 sec drift in 12 hours.

Adjust the (22pF?) caps on the crystal until the drift is within you specifications.
Leo…

Yes, two 22 pF caps are the load on the crystal. This is also an option indeed, but possibly require more iterations.

I'd like to compensate it in software. I have the basic idea, just not sure how to handle overflow correctly.

amazed: I've built a module with a standalone Atmega328p and the clock source is an external crystal. I've measured 1 sec drift in 12 hours. So I could make it more accurate if I add 83ms to the timer0_millis every hour.

Rather than fiddle with the Arduino code for millis() why not use it (or micros() ) to update your own millisCounter and adjust that with your correction factor. Something like

if (micros() - previousMicros >= 1000) (
  myMillisCounter ++;
  previousMicros += 1000;
}
if (myMillisCounter - myPreviousMillisValue >= (60 *  60 * 1000UL)) {
   myMillisCounter += 83;
   myPreviousMillisValue = myMillisCounter;
}

...R

I don't want to help to mess around with millis() and its Timer0. That is too scary.

Can you do it in at a higher level in the sketch ? You could even store the compensation in EEPROM for each Arduino board. Since micros() does not work in the same way as millis(), I prefer to use millis() timing accuracy over a long period.

How to do that in the sketch depends on the sketch. To measure a time period, simple adjust it with a certain percentage. For a clock, perhaps changing the interval now and then might be the most accurate. For example setting the interval to 1001 or 999 once in a while.

I'm sorry for this first example, there is a conversion with 'unsigned long', 'float' and 'long'. That's not pretty. That should be made better.

// To measure a interval.
// For example a crystal that runs 0.007% too fast.
// When using 'float', then the correction can be stored in EEPROM.

// -------------------
// global variables.
// -------------------
const float compensation = -0.00007;

// -------------------
// in the loop()
// -------------------
unsigned long t1 = millis();

// some code that takes some time

unsigned long t2 = millis();
long elapsedMillis = (long) (t2 - t1);
float correction = float( elapsedMillis) * compensation;
elapsedMillis += (long) correction;
// for a clock
// substract a second every 2.5 days

// -------------------
// global variables
// -------------------
int compensation = 0;
unsigned long secondsCounter;
int interval;

// -------------------
// in the loop()
// -------------------
if( currentMillis - previousMillis >= (1000 + compensation))
{
  previousMillis += (1000 + compensation);
  compensation = 0UL;
  secondsCounter++;
  if( secondsCount >= 216000UL)
  {
    secondsCounter = 0;
    compensation = -1;
  }
}

I've built a module with a standalone Atmega328p and the clock source is an external crystal. I've measured 1 sec drift in 12 hours. So I could make it more accurate if I add 83ms to the timer0_millis every hour.

Does the drift change with temperature ?

UKHeliBob: Does the drift change with temperature ?

O, you really did it. Now amazed has to add a temperature sensor or use the temperature sensor inside the ATmega328P :smiley_cat:

Of course, then you would need a way of compensating for the accuracy of the temperature sensor and possible drift over time :)

Thanks for the ideas @Koepel, @Robin2.

@Robin2: this sounds good, I will give it a try.

Does the drift change with temperature ?

@UKHeliBob: you are right. I should have emphasized that the 1sec/12hours drift is only valid at room temperature, where it was measured.

I know using millis() would be a silly way to have an accurate “RTC”, apart from the inital inaccuracy, it has to be set-up after power-up, no backup battery, etc. I’m just experimenting with different solutions to know which one could be used in different applications with different needs. I attach a table with my results so far. The big disappointment is the DS1307, possibly because the poor crystal on the board. The older DS3231 chip (1123 → 2011) is better somewhat and at least it has an Aging offset register to work with. Teensy and the newer DS3231 (1948A3 → 2019, the exact accuracy is not measured yet, it looks promising) is the clear winner, and of course, the DS3231 should be used in case of varying temperature (TXCO).

I agree with comment #6 by Koepel. my clock sketch: https://github.com/jandrassy/KitchenTimerClock/blob/master/KitchenTimerClock/KitchenTimerClock.ino has CLOCK_MILIS_CORRECTION_PER_HOUR

If you don't want to mess with the actual millis() function, you can configure timer0, which is used for millis with an overflow interrupt, for an additional compare interrupt and drive your own millis-like counter. Note that millis already implements an adjustment because the interrupt is not at 1mS intervals.

What is your project. Can't you get "external time", form a GPS module or NTP with a WiFi board. Leo..