I am working on a clock movement that is powered using a stepper motor and the GSM2 driver board. The delay between steps needed to achieve 1 rpm on the second hand works out to 25 ms. In my current setup, I am sending pulses to the driver board using the delay() function. This works reasonably well, although it is not accurate enough for me. To remedy this problem, I ordered a DS3231 RTC module, thinking I could use that to replace delay()/millis(). However, I can't figure out how to achieve millisecond resolution using the RTC. It seems like it should be strait forward given the oscillator on board the RTC is 32kHz. I am new to arduino programming and am out of my comfort zone with this one.

Does anyone know of a way to achieve the 25 ms pulses we need while still retaining the accuracy of the RTC? Is it possible to use the RTC to correct the output produced by the built in delay()/millis() function? How would you then translate that output to pulses sent to the stepper motor driver board?

Internet research produces some mixed results on whether or not this is possible. I have yet to see an example of a clock with millisecond resolution. The theory behind it makes sense, but I don't know where to start.

2 Answers 2


It is unclear, why you need the high accuracy, and you didn't include your code. So my explanations will be as broad as your question.

"millisecond resolution" is a broad term. Generally you have to consider 2 different errors.

  1. The jitter for every millis() value
  2. clock drifting over time

You can eliminate the second point quite easily using the RTC with the Time library. It uses the RTC as a reference, so that you don't have the clock drifting this much. Though, you would have to use the libraries functions for all time related things, since it does not change the value of millis() or it's siblings.

Also, for low differences in the range of a few milliseconds, you should better use micros() instead of millis() (if you are not using the RTCs functions).

How would you then translate that output to pulses sent to the stepper motor driver board?

It is unclear, why exactly you need "millisecond resolution", and you didn't show us your code. But there are multiple Stepper libraries on the web, for example the standard Stepper library or the AccelStepper library (Accelstepper is more capable, don't know, if you need it). With the AccelStepper libraries you can set a certain speed and target position and then you simply call then run() method (haven't looked up the name, but there is definitely a function like that) until the desired position is reached. The method itself will only pulse the stepper, if it is time to do so. So you are freed from doing all the stuff with millis(), since the library is doing that for you.

Though the libraries are using the Arduinos internal time: millis() and siblings. To change that, you would have to change the library.


Consider a Phase Locked Loop (PLL) to synchronize your 40x 25ms pulses with your 1pps RTC.

If you need 25ms/pulse to get 1rpm, that's 2400ppm or 1pulse/0.025ms=40pps. If you get a 1pps pulse out of the RTC, you can use it to discipline your timing. https://www.romanblack.com/one_sec.htm is a good reference.

The basic trick is to keep track of the mismatch and run faster when it is slow, and run slower when it is too fast.

Try something like the below, but replace the LED pulse stuff with your stepper code.

// pps_pll.ino for https://arduino.stackexchange.com/questions/67699/stepper-motor-based-clock-movement-with-ds3231
// Adapting code from https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
// to do a PPS PLL per https://www.romanblack.com/one_sec.htm

const int pps = 40;
const int pulse_us = 1;
const int pulse_interval = (1000L / pps);
int count = 0; //
int count_at_sync = 0; //
unsigned long next_pulse = 0; //
unsigned long pulse_off = 0;

const byte led_pin = LED_BUILTIN;
const byte interrupt_pin = 2; // Choose pin per https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
volatile byte state = LOW;

void setup() {
  pinMode(led_pin, OUTPUT);
  pinMode(interrupt_pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interrupt_pin), sync_ISR, RISING);

void loop() {
  unsigned long loop_micros = micros();  // capture start of loop time
  if (loop_micros >= next_pulse) {
    // Schedule pulse
    digitalWrite(led_pin, HIGH);
    next_pulse += pulse_interval ;
    pulse_off = loop_micros + pulse_us; // schedule off time
    // PLL accounting
    if (count_at_sync > 0) next_pulse++; // too fast: slow down
    else if (count_at_sync < 0) next_pulse--; // too slow: speed up
  if (state == HIGH && loop_micros >= pulse_off) {
    state = LOW;
  digitalWrite(led_pin, state);

void sync_ISR() { // called by 1pps interrupt
  count_at_sync = (count -= pps); // PLL accounting and record synchronization state

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.