How to use RTC module

I have an existing sketch which works with my arduino uno and a light barrier to do time measurements (for a race). The time measurement with the light barrier works really well, so the main functionality is already there.

However, for longer measurements it turns out the measured time is somewhat off what 'real' clocks will measure. For the measurement I just record the start time using millis() and substract again millis() once done.

So we thought we need a RTC to get precise time measurements, and we got ourselves a ZS-042 DS3231 RTC module. We wired the SCL/SDA to A5/A4, and used some RTC lib to measure the time. It went well, and we were able to get the current time from the RTC module.

However, all the RTC libs will only offer a precision of seconds when getting the current time, but we need at least milliseconds precision. I read that the SQW output is able to supply a 1hz-8khz signal, which is thus more precise, however I find myself unable to understand how to wire/implement this.

So my questions are:

  • How can I measure the current time in milliseconds (as I do currently with millis()) but with the precision of the RTC module?
  • Do I need to use the SQW output?
  • If so, how do I need to make up the wiring? I spend more than 2 hours of googling, and I found tutorials stating I need a resistor, other stating I don't, and some say I would need 4,7k others say I need 10k
  • How exactly do I make use of the RTC in the code? Can I use the RTC to make the arduinos built-in clock more precise, and still use millis() for the measurement?
  • Can the battery be safely removed? The whole device (arduino, light barrier, RTC)

If you want really precise you need the SQW output in my opinion because you don't want to include I2C communication into the measurement.

But how you use it is up to you. You can connect it to the input of one of the timers and use that at for example at 8.192kHz. It's actually the same as millis() only with the SQW of the RTC as source. how to set up the timer for that you can find in the datasheet or Google Arduino timer or something.

Other option would be to use the 1Hz and on every pulse also check what the time passed for millis is. That way you can compensate for the millis() inaccuracy.

I don't know what I2C is, but using the SQW is fine for me as long I know how I should do it.

What do you mean with "connect it to the input of one of the timers"?

I don't know how to wire up the SQW output and how to implement a time measurement with it.

[edit] Should I be doing something like:

  • connect SQW from the ZS-042 to pin 2 on my arduino uno
  • set the frequency to 1024hz
  • according to attachInterrupt() - Arduino Reference, attach an interrupt handler to pin 2
  • in the interrupt handler increase count of a global volatile variable
  • for the measurement, record the count twice, then do (count2-count1) / 1.024

?

s710:
What do you mean with "connect it to the input of one of the timers"?

If the difference in millis() between the first SQW edge (running at 1Hz) and second SQW edge is 1060ms, you know millis is running fast. So whatever you where timing with millis() is also running the same percentage fast. So if you timed something as 200ms, you know it should be 200 x 1000 / 1060 = 189ms.

But for longer times it gets complicated. For example, for longer times, how do you calculate (between what differences) do you calculate that factor?

s710:
I don't know how to wire up the SQW output and how to implement a time measurement with it.

We run a forum for help but we do expect the work from you. Google is your friend. But yeah, a fair note, it's probably not a very beginner friendly project.

Via an interrupt it's also possible indeed. Although the float math makes it a bit mmmmm.

s710:
I don't know what I2C is, but using the SQW is fine for me as long I know how I should do it.

I2C is a communications bus. You'll need to connect your RTC over I2C to be able to communicate with it, and actually enable the SQW output (the data sheet explains exactly how).

What do you mean with "connect it to the input of one of the timers"?

Then the SQW connects to the T1 pin (pin 5), and you have to set TIMER1 to use that as timer input (you'll have to do some register manipulation - do some Googling or read the data sheet of the ATmega328p for details).

What you then do in your program:

  1. upon triggering the first beam, set TCNT1 = 0 to start counting at 0.
  2. set a timer overflow interrupt: the timer overflows at 65535 counts as it's a 16-bit counter. That's every 8 seconds. Count the overflows for the number of 8-second periods elapsed.
  3. upon triggering the second beam, read the current value of TCNT1. Combine that with the number of overflows, and you got your total.

For best precision, you may have to connect your break beam sensors to interrupts (the external ones at pin 2 and 3 are easiest).

Hi, thanks for your reply. Took me a while to find time again for this.

I was not familiar with neither timers nor interrupts, so all this didn't clarify much. However I found some helpful websites regarding timers, and now I have some basic understanding.
While I don't fully understand how/why to set TCNT1 to a certain value, I was able to implement a working time measurement using overflow interrupts on timer1 with a 1khz resolution, still using the internal clock as source, so that part worked.

However, adding the RTC module as timer source seems to be what I cannot get to work. I am able to wire up the module, and use one of the RTC libs to enable the SQW output, and set its frequency to 1024hz. I've wired up the SQW to digital pin5, which is the T1's external source. I've set TCCR1B accordingly, so it uses the external source.

I DO get an values, however it seems like the SQW is stuck at 1hz, no matter how I configure the RTC module (be it 1024hz or even 8khz).

This is my current testing code:

#include <Wire.h>       //I2C library
#include <RtcDS3231.h>  //RTC library
 
RtcDS3231 <TwoWire> rtcObject(Wire);

static volatile unsigned long overflows = 0;


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

  rtcObject.Begin(); //Starts I2C
  rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeClock); //Sets pin mode
  rtcObject.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_8kHz); //Sets frequency
  
  noInterrupts(); // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  
  OCR1A = 32000;
  TCCR1B |= (1 << WGM12); // CTC mode
  TCCR1B |= (1 << CS11); // external source / no prescaler
  TCCR1B |= (1 << CS12); // external source / no prescaler

//  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  interrupts(); // enable all interrupts
}

ISR(TIMER1_OVF_vect)
{
  TCNT1 = 0;
  overflows++;
}


void loop() {
  delay(1024);
  Serial.println("loop");
  Serial.println(TCNT1);
}

And its going to print something like:

loop
1
loop
2
loop
3
loop
4
loop
5

What is missing?

Also, I did NOT use any resistor, I directly wired SQW to pin5. Is that correct? Because various howtos indicate some resistor shall be used.

I don't see anything obviously missing from your code.
Setting TCNT1=0 in the overflow interrupt is unnecessary, as it just overflowed so it just went back to zero already.

s710:
Also, I did NOT use any resistor, I directly wired SQW to pin5. Is that correct? Because various howtos indicate some resistor shall be used.

Those SQW pins are open collector outputs so you need a pull-up resistor. You can simply enable the built-in one. No experience using this pin for the SQW output myself.

Okay, so I'm a bit lost now. I am in no way an expert, but looking at https://www.e-lab.de/downloads/DOCs/mega328P.pdf page 18 it says that PD0-7 have internal pull-up resistors, and looking at Arduino Boards-Pin mapping - iCircuit it says that PD5 is pin 5 / T1 pin.

So at least the T1 pin also seems to have a pull-up resistor. Still I have no clue how to activate it, at least pinMode(5, INPUT_PULLUP); does not change anything.

On the other hand, when I copy & paste this example: ESP8266: DS3231 1Hz Square wave generator - techtutorialsx and wire the SQW to pin 13, I get no interrupt at all.

So still stuck at 1hz timer (instead of 1024hz), and I can't seem to solve this).

Okay actually the code was working. But for some reason, this RTC module only spit out 1Hz SQW at most.

I've replaced it with another ZS-042 / DS3231 and this one works perfectly, and I get 1KHz on the SQW. The time measuring works now, and compared to the built in millis() is more precise, as expected.