3

I have written two (1 second) delay programs.

The first program as follows:

void setup()
{
Serial.begin(9600);
TCCR1A = 0;
TCCR1B |= (1<<CS12);
TCCR1B &= ~(1<<CS11);
TCCR1B &= ~(1<<CS10);
TCNT1 = 3036;
TIMSK1 |= (1<<TOIE1);
}

ISR(TIMER1_OVF_vect)
{
  Serial.println(millis());
  TCNT1 = 3036;
}
void loop()
{
}

Whose output is as follows:

999  // 1 sec
1999 // 2 sec
3000
3999
5000
6000
7001
8000
9000
10001
.
.
.
58012
59013
60013
61012
62013 //60 sec

It has an error of around 13 ms at 1 minute and at 1 hour it will be 780 ms and in one day it will be around 19 seconds.

The second program is as follows:

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

void loop() {
  Serial.println(millis());
  delay(1000);
}

Whose output is near about as same as the first one's.

Is there any way to decrease the error and make it more precise? So that I can use it in my Arduino based stopwatch?

  • Printing to serial is the culprit here. Print the count after 1sec , 60sec, 1 hour and one days only. the result won't be as bad as you calculated – MaNyYaCk Jul 18 '18 at 8:54
  • OR OR OR take a value of millis in a variable before you go for printing – MaNyYaCk Jul 18 '18 at 8:55
  • 2
    Swap the Serial.println and TCNT1=... lines. As the print will take different amounts of time depending on the decimal length of the millis value. – Gerben Jul 18 '18 at 10:42
  • your problem is that you are measuring time in multiple inaccurate sections .... the error of each section accumulates .... it is like measuring a 2 meter distance with a 10cm ruler .... you measure off 10cm, then move the ruler to the 10cm mark and measure off another 10cm to get the 20cm mark and so on .... each of the measurements has a small error that accumulates ..... if you use a 2m tape measure, then the error is the same as the 10cm measurement with the ruler ..... the millis() function is like a "time tape measure" that is 50 days long ..... simply monitor millis() inside loop() – jsotola Jul 18 '18 at 18:40
4

Majenko is perfectly right: you cannot expect much accuracy from an Arduino clocked by a ceramic resonator. Typical drift is of the order of 1,000 ppm, and is affected by temperature and aging. You can, however, get a delay which is pretty close to the CPU's idea of one second. In other words, you can get something that is really close to a perfect period of 16,000,000 CPU cycles. If that is what you want, your best bet is to use a timer.

In your overflow ISR, you wrote:

Serial.println(millis());

There are two issues here. The first is the use of Serial.println() within an ISR. This should normally avoided: since Serial relies on interrupts, using it with interrupts disabled can lock your program if you ever fill the output buffer. In this particular instance, it happens to be safe, because at this rate you are never going to fill the output buffer anyway. However, while it can be OK to do that in a small test program like yours, I would advise you against doing it in production code.

The other issue is the use of millis(). This function does not provide true millisecond resolution: it is a counter that is updated every 1024 µs. Every now a then it is updated by 2 ms at once in order to compensate for the drift. Thus, you should think of millis() as being good to within ±2 ms. If you need anything better, use micros().

TCNT1 = 3036;

This should be avoided. Dispatching an interrupt takes time, and this time depends on whether there is already an interrupt handler (or other critical section) running. If the timer happens to increment after the interrupt fires but before you have time to execute the line above, then you miss a timer tic. It has been suggested to you to move this line before the Serial.println(). That would certainly reduce the risk, but it will not completely prevent it.

The right thing to do is to never reset a timer if you want it to have time continuity. Instead, set it to CTC mode (clear timer on compare match) and let it reset itself when it reaches a value of your choosing. In this case, you would have it repeatedly count from 0 to 62,499 instead of from 3,036 to 65,535.

Here is a modified version of your program illustrating the points above:

void setup()
{
    Serial.begin(9600);
    TCCR1A = 0;            // undo the Arduino's timer configuration
    TCCR1B = 0;            // ditto
    TCNT1  = 0;            // reset timer
    OCR1A  = 62500 - 1;    // period = 62500 clock tics
    TCCR1B = _BV(WGM12)    // CTC mode, TOP = OCR1A
           | _BV(CS12);    // clock at F_CPU/256
    TIMSK1 = _BV(OCIE1A);  // interrupt on output compare A
}

ISR(TIMER1_COMPA_vect)
{
    // OK in test code, don't do this in production code.
    Serial.println(micros());
}

void loop(){}

The output is as follows;

1000048
2000048
3000048
...
58000048
59000048
60000048  // 60 seconds
3

Your biggest problem is that you don't know what a second is. You have no reliable time-base to measure time against.

millis() is not accurate. You can't rely on that giving you a precise time - otherwise, something as simple as:

uint32_t secs = 0;

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

void loop() {
    if (millis() / 1000 > secs) {
        secs = millis() / 1000;
        Serial.println(millis());
    }
}

would give you a precise 1000 millisecond "delay". However, all that gives you is a precise "Arduino 1000 millisecond" delay. That is - it triggers on the rollover between 1000 millisecond blocks where 1 millisecond is whatever the Arduino's millis() function thinks 1 millisecond is.

While it may appear to be accurate, in that it would give you round numbers "1000, 2000, 3000, 4000" etc, compare that to an external clock and you would find it's actually drifting just as bad (if not worse) than your existing methods.

The problem is, since what you are measuring the drift against is itself drifting, your measurements are completely meaningless.

If you require an exact 1-second pulse with near zero drift you will have to use an external clock source. The most accurate would be GPS. Many GPS modules have the ability to output a 1-second pulse which is synchronised to the GPS clock.

An RTC module would improve accuracy somewhat as well - again most of those have a 1Hz square wave output that can be used to synchronise things - however, even those aren't 100% accurate and rely on the accuracy of the attached crystal. They're more accurate than the Arduino's ceramic resonator, though.

0

I never would have thought this day would come, but I disagree with @Majenko and @EdgarBonet.

The source of your clock is important. If the Arduino resonator is accurate enough, then you can use millis(). If you want more accuracy, then you can use a rtc like the ds3231. The ds3231 is temperature compensated for more accuracy. If you want something even better then you can use gps or a internet time server or even a long wave radio signal time server.

When you need a 1 second timer as the basis for a clock or stopwatch, then you can start by using millis() which is based upon the 16MHz resonator. The millis() is compensated with a offset, it will not drift. It runs 100% perfect with the same accuracy as the resonator. Use the blink without delay example and change the increment of previousMillis to this:

if (currentMillis - previousMillis >= interval) {
  // increment the previousMillis with the interval
  previousMillis += interval;

That way the one second interval will stay in sync with the time, even when there is a big delay now and then in the sketch.

You will not get more accuracy by using a internal timer.

Some Arduino clones have a crystal instead of a resonator. If you use one of those clones with a crystal and use millis as I mentioned above, then your timing is as accurate as the crystal. When you build a clock with that, it might run a few minutes per year out of sync with the real time.

  • I'm sorry... ceramic resonator "accurate enough...?" That made my day... XD – Majenko Jul 18 '18 at 13:29
  • The ceramic resonator is ±0.5%. That's 5 parts in a thousand, or 5000ppm...! A typical crystal is in the order of 100ppm. GPS is about as close to zero drift as you can get. An RTC is only as good as the crystal, regardless of temperature compensation - that only keeps the drift the same over a certain temperature range. If the crystal is already 100ppm out from the target frequency you will still drift. It just means it'll drift the same at -10C as it does at 30C. – Majenko Jul 18 '18 at 13:36
  • @Majenko I don't know what the stopwatch is for. Perhaps for a toy. Perhaps a photographic or scientific experiment. The clone boards with a real crystal that is always at room temperature might be okay for many applications. Peouse Dutta, please add more information to your question. What is the stopwatch for? What is the maximum time that you want to measure? Should it operate at a large range of temperatures? – Jot Jul 18 '18 at 16:09
  • @Jot It is for a scientific experiment – Peouse Dutta Jul 18 '18 at 16:35
  • 1
    @PeouseDutta: If it's for a scientific experiment, you may want to calibrate your clock and, most importantly, be aware of its limitations. – Edgar Bonet Jul 18 '18 at 18:30
-2

Use micros() vs millis() and blink without delay time tracking as Jot described. Results will have much less drift. And for better results go with a board that uses a crystal instead of a resonator as mentioned.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.