5

I've a wall clock (sorry, code's a mess, look for updateClockVars) which makes use of the Timezone library (and ultimately Time). Nothing fancy, call millis() every loop; turn to Hrs, Mins, Secs; display on LEDs.

The magnitude of the drift I am seeing on my 2 Arduino Pro Mini - compatible boards - up to ~8% lag, not a typo - blows my mind. Replacing the 16MHz quartz with one bought at a local retailer didn't help much (~33% improvement). Is something else at play here?

  • 1
    Can you please extract from the code only the part regarding time? There are 300 lines and it's difficult to understand everything. – FrAxl93 Feb 29 '16 at 20:30
  • 2
    Didn't check the whole code, but here is an idea: Since your LED strip is quite long, the NeoPixel library may be blocking interrupts for too long when you strip.show();. You may then be missing timer interrupts. Try with a much shorter strip and see if the problem persists. – Edgar Bonet Feb 29 '16 at 20:39
  • 1
    Much better idea would be using a RTC module (the easiest way to be honest). – Avamander Feb 29 '16 at 20:53
  • I get 40-60 loop iterations per second. I don't keep track of time on my own, I ask for millis(). RTC: even having read so many suggestions for it, I still didn't catch WHY that is better - it's a quartz either way, and if that's not the only relevant thing - than we have an answer to the original question. @frax93: updateClockVars(). All else is just setting the LEDs based on the acquired values. – kaay Feb 29 '16 at 21:57
  • 1
    I would encourage you to have a look at the code behind millis(), micros() and the Timer Overflow ISR. Please notice the accumulated error and the leap milli-seconds. It is possible to adjust the Timer handling and get within better accuracy. Please see Cosa Clock and Calibration Tools. Start here: github.com/mikaelpatel/Cosa/blob/master/examples/Tools/…. – Mikael Patel Feb 29 '16 at 22:29
6

The problem is the interaction between the interrupt handler that updates the value for the millis() function and the neopixels show() function.

To maintain the counter for the millis() function a interrupt is triggered approximately every 1.024 ms.

When the show() function is called all interrupts are disabled to get the precise timing needed to send the data to the neopixels. For a 60 led strip this takes about 1800 microseconds (60 leds * 24 bits * 1.25 us per bit) so at least one interrupt is skipped each time you update the display.

This page has details about how the millis() function works.

  • at least one interrupt is skipped each time you update the display”. This is incorrect. See my answer. – Edgar Bonet Mar 1 '16 at 9:50
4

Expanding on my previous comment...

As I said before, I suspect the NeoPixel library to be blocking interrupts for too long when you strip.show(). If a timer interrupt fires while interrupts are blocked... it's not a big deal: the interrupt request is put on hold and serviced as soon as interrupts are enabled again. If two timer interrupts fire while interrupts are blocked... you have a problem: the interrupt service routine will be called only once, and your clock will miss a tick.

Craig has worked out the timings in his answer: the timer interrupt fires every 1.024 ms, and NeoPixel blocks interrupts for 1.8 ms. So it is perfectly expected that, from time to time, you will have two timer interrupts within this 1.8 ms window.

Here is a quick hack that could solve your problem: if you strip.show() right after a timer interrupt is serviced, then only one more interrupt should fire during the subsequent 1.8 ms window, and you should be fine. So you could simply add this right before strip.show():

/* Wait for a timer interrupt to be serviced. */
uint32_t lastMillis = millis();
while (millis() == lastMillis) /* wait */;

A possibly better solution would be to avoid blocking interrupts for so long. NeoPixel blocks them in order to comply with the very tight timing requirements of the WS2812 LEDs. But it turns out the WS2812s are not nearly as picky about timings as their datasheet suggests. You can drive them in a more relaxed way, with interrupts enabled except for 1-bit durations, and they still work fine. For a detailed explanation and sample code, see NeoPixels Revealed: How to (not need to) generate precisely timed signals.

A third solution would be to follow the general advice given here and grab an RTC. However, although it certainly is a good and clean solution, I have to admit I completely agree with your comment “I still didn't catch WHY that is better – it's a quartz either way”.

  • Alternatively, you could use a second timer that ticks slower, and implement your own version of millis. This function could also implement some kind of calibration. By measuring how much the clock is of after a longer period, you can see if the 16mHz clock is too fast or too slow, and by how much. You could use this value in your millis implementation to get better accuracy. – Gerben Mar 1 '16 at 15:09
3

TL;DR You're doing it wrong. Use RTC.

Ok, longer explaination is required here. millis() is not accurate and here is why.

First, grab official Arduino source code from their Github repo. The file Arduino-master/hardware/arduino/avr/cores/arduino/Arduino.h contains few interesting macros (I'm pasting grep output here):

Arduino.h:#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
Arduino.h:#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
Arduino.h:#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

At 16MHz clockCyclesPerMicrosecond() will evaluate to 16. Keep this number in memory.

Those macros are used in Arduino-master/hardware/arduino/avr/cores/arduino/wiring.c:

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

MICROSECONDS_PER_TIMER0_OVERFLOW macro is evaluated as (64 * 256)/16 - a dodgy looking 1024. Look how they round it down to calcuate MILLIS_INC.

Let's assume that you run your Arduino with ideal main clock signal. At 16MHz main clock, the timer frequency is 1.024kHz, not 1kHz. This results in 2.4% inaccuracy. You can get away with that in most cases, but not when you're building a clock.

This 2.4% error is best case. Your main oscillator is not 16MHz - it has it's own inaccuracy, that is unknown and it sums up.

You should use an external, accurate time reference provided by RTC module. When assembled properly, they guarante low time drift, temperature compensation and other goodies. Be smart - use RTC. :)

If you are scared of I2C RTC modules, you may find my tutorial and another tutorial on MCP7940N RTC module. Know-how is applicable with other RTCs as well.

  • Thank you. I had assembled a DS1307 module, but only used it for sync on startup, as in the timeRTC example. Ditched it in favor of NTP via an ESP8266 (so the Arduino is I2C slave now). – kaay Feb 29 '16 at 23:20
  • 2
    I hope you aren't calling an NTP node every second. You might get IP blocked if you keep doing that. They are supposed to synchronize your clocks, not replace them. – Nick Gammon Mar 1 '16 at 6:02
  • −1 because you visibly do not know what you are talking about. The timer frequency is 1/1.024 kHz, not 1.024 kHz. (the period is 1024 µs). Most importantly, this does not imply millis() is inaccurate: the extra 24 µs are accounted for: see timer0_fract and FRACT_INC. – Edgar Bonet Mar 1 '16 at 9:04

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.