Millisecond precision time with DS3231

I would like to be able to record events and attach a timestamp with millisecond precision. My idea is to use a combination of an RTC such as DS3231 and an internal timer/counter in Arduino. Here is a general idea how it would work: RTC generates a signal every 1 second, and I don't mean just 1 Hz frequency signal, but a signal when a new second starts according to real time (at 000 milliseconds, not somewhere inside a second). This signal generates an external interrupt for Arduino which starts a timer which counts up to 1000 milliseconds. So when an event happens, for example a push on a button, the counted time is retrieved from the timer register (for example 326 milliseconds), the actual time is retrieved from RTC via I2C, and finally these two are combined to get the precise time.

I notice that DS3231 can generate a 1 Hz square wave on its pin INT/SQW. My question is this: Is this signal synchronized so that each rising/falling edge happens exactly at 000 milliseconds, i.e. when one second ends and another begins? If not, are there any other (better, simpler) ideas how to achieve my goal?

Something to begin with
https://www.youtube.com/watch?v=2xZ15ntJy20

If you want to know when each second begins then I suggest a GPS receiver. Some of them, perhaps all of them, have a 1 pulse per second output that is aligned with the start of each second as per international references. You won't do better.

3 Likes

Spend some time thinking about how you will test this to see if things are working this way.

1 Like

A simpler idea is to use the DS3231 to determine how fast or slow the Arduino clock is, then use millis() to time events, and rescale the results by the correction factor.

You do need to be aware that millis() occasionally it is off by 1 increment, since it counts to 1000 while the Arduino counts 1024 ticks of the CPU clock.

I would probably go for a free running timer with input capture.
In the capture interrupt you need to copy the capture register for later use.
After 2 captures you can calculate a correction factor.

The falling edge of the ~INT/SQW pin marks the time that the DS3231 will start replying with the new second. You could save the millis() (or micros()) that this pin falls, and use that timestamp as the reference from which to measure sub-second events.

One can also set the SQW to emit other than 1-Hz square waves.

Thanks for all the replies. There is some good advice there. Some of it addresses the issue of counting milliseconds which I don't see as a problem, at least for now.

Anyway, I saw the suggested video and also read some parts of the RTC datasheet. It seems to me that the 1 Hz square wave is not synchronized with the start of each second, but it just starts working as soon as the chip is energized. The other option, which might be promising, is to activate the SQW output when a value in the alarm register is matched, and one of those possible values is to generate an alarm every second. I can only suppose that this alarm is generated at the turn of the next second, but it is not explicit.

As for the GPS module, Neo-6M seems by far the most popular. But it doesn't have a digital pin, just serial communication. How do I get from it the exact instant of the start of new second? I suppose, if the module sends the new time to the Arduino immediately after the turn of the second, and if the transmission takes less than 1 ms, I could use the moment of reception of the new time as a trigger to start the internal timer. But is it so? Any other GPS module to recommend?

It's synchronized with the last write of the seconds register. It goes high at 500ms, and falls low at 1000ms. If you didn't take care in setting the seconds of the RTC at a millisecond-precise time, it holds that same offset to the best of its ability.

That's what I was looking for.

I'm really afraid that what you're going to find is 1020 - 1025 ms per second and I don't know how you're going to reconcile that.

If you're referring to the imprecision of the Arduino clock: it is possible to use an external quartz oscillator. With it I am confident that I can get 1000 ms. I am using the timer function, not millis(). When the event happens I can read the value in the counter register, and since I know the oscillator frequency and the prescaler, I can calculate the time. Remember, the counter is reset every second. How much error can it accumulate in one second?

The issue is going to be that 1ms won't equal 1000 ticks of the clock. You'll have to do some scaling and that is going to mess with the resolution.

Ok, so if I get a 4 MHz oscillator, set the prescaler to 64, then 1 second equals 62500. Maximum value is 65535 for Timer1. 1 ms equals 62.5. All I need is to check the value in TCNT1 and divide by 62.5. Or am I getting something wrong? I can accept if due to rounding I get +/- 1 ms uncertainty.

Just brainstorming
Store the difference between millis() and zero in an int on the flank of the 1Hz RTC pulse.
The millis timestamp is "millis() + difference".

Note that a DS3231 can drift 7ms/hour.
Leo..

In theory in a perfect world with perfectly perfect components you are correct. In the real world it is a little messier.

I hadn't thought of it that way: +/- 2ppm*1000*3600=7.2ms/hr

But that's still loads better than a stock Arduino's +/-0.5% ceramic oscillator

1000*3600*0.005=18000ms or +/-1000*0.005=5ms/sec

Along with saving the millis() I'd also increment a counter by 1000 each DS3231 falling edge and use the difference to measure the longer term accuracy and ratio so you'd know where on the +/-5ms/sec you'd expect to be. And I'd probably do it with micros() instead of millis() to avoid the 24 double-jumps in each second's worth of millis() incrementing.

Over how long of an interval is the millisecond precision needed?

Another thing to check is which board you are using. The Arduinos with built-in USB, like the Micro, use a much more accurate crystal than the cheap ceramic resonators in the Uno, Nano and Mega.

Brief explanation of what I am trying to do: I want to build a very simple version of a numerical protection relay. In the real world those devices are used for protection of power systems and machines such as generators, transformers, power lines, industrial plants etc. They need very precise and universal time reference so that later, when analyzing faults, the recordings from several relays located at a vast distance can be compared. As far as I know their time is synchronized at regular intervals with an outside source, probably GPS.

In reality I am going to build only one such device with Arduino Nano, and it will be infinitely less powerful and much simpler. And its recordings won't be compared to other devices, so in theory any time reference could be good enough. But I was still hoping to make it "as good as possible", ideally as precise as UTC. So if I am using a DS3231, it would probably help to synchronize its time as often as possible. As for the sub-second precision, some of you have suggested methods by which the controller basically measures its inaccuracy over time and "self corrects", and I might try that.

One other question now comes to mind: when calibrating time on DS3231, is it at all possible to synchronize it to UTC down to 1 ms? If not, then maybe I should move on to different ideas, or simply accept less accurate solutions.

Don't use it then!

Mine is a GlobalTop-FGPMMOPA6H, but I bought it ages ago so I imagine there are better ones available now.

I did a search on Amazon and found this:

I'm sure there are others.

On the one I have the serial data output is not reliably aligned to the start of a second, so cannot be used as a reference for the start of a second. I can't comment on whether that applies to other modules.

Yes! By this metod the OP can get a resolution of 0.0625 us in Arduinoo UNO which uses 16 MHz clock frequency.