Accuracy and Temperature Variation of micros() and millis() Function


I’ve seen posts questioning the accuracy of the micros() or millis() function.

I wrote a sketch that measures the number of micros() in an accurately known period of time. I made these tests on a Teensy 3.2 but I believe the same 16 MHz crystal may be used on various other Teensy versions. I used the PPS output of a GPS module to provide a precise one pulse per second trigger and tallied micros() over a 60 second period.

A perfect Teensy would measure a million micros() in one second. The four Teensies that I measured ranged from 999980 to 999983 micros() in one second. This might be accurate enough for some applications. But if you were making a clock, for example, 999980 micros() in a second would accumulate an error of 1.72 seconds per day—nearly a minute in a month. By correcting for the actual number of micros() in a second you can greatly improve timing accuracy.

It would be wonderful if the micros() error were constant but it varies with temperature. I made the same measurement over a range of temperature from 67 ’F to 79 ’F. Temperature was measured with a thermistor connected to an analog input on the Teensy. The data show fractional values for micros() per second because it is averaged over a one-minute period. With these data, it’s relatively easy to correct for temperature variation in software. Of course, each Teensy may have its own unique error and temperature variation.

Comments, questions are welcome.

micros error vs temp.jpg


  • micros error vs temp.jpg
    micros error vs temp.jpg
    62 KB · Views: 30
For a clock you'd probably need the battery-backup facility of a RTC chip/module anyhow so you'd pick one of those with the desired accuracy in practice

It's pretty common for microontroller based systems to pick a standard 25ppm or 50ppm crystal as this suffices for many uses and such crystals are cheap and small and are jellybean parts (in times of shortages this matters!). On a larger board with space for alternate footprints you can make it possible to substitute different crystals and oscillators more flexibly, but the T3.2 (and Teensy's in general) isn't gifted with a lot of spare area, the clue is in the name... It would be nice to have a high-spec TCXO on the thing, but not many people would like having to pay the price for something they never need.

I'm sure someone's managed to do some hot-air rework and replace the crystal with a better one or a TCXO (if there's a footprint-compatible one), which is one route to go.
When I use a Teensy 3.2 I solder in a 5ppm 32khz crystal (citizen CFS-20632768HZFB) and use a 1F super cap tied to the RTC battery input. The super cap is charged via a schottky diode tied to 3.3v. I've found it keeps the RTC running for several days without power. I sync the on-board RTC with either GPS or NTP and measure the drift while the clock is powered. I periodically use the rtc_compensate() function to trim the RTC. I pick up the RTC seconds using the RTC_TSR register and fractional seconds using the RTC_TPR register.

The newer Teensy 4.0 and 4.1 don't have the rtc_compensation() function, at least I didn't find it.