How to use RTC for correcting stepper motor RPM

Hello. I need help please. I need the stepper motor to rotate at constant RPM (10 RPM in the attached example). It is used to drive a gear clock so the RPM needs to be accurate.
I am using NEMA 17 Stepper Motor with TMC2208 driver. the HW connections are as in the picture. I also connected DS3231 Real Time Clock.
As you can see in the code, I turn the stepper motor at 10RPM but I do not know how to correct the RPM via reference to the RTC.

Can anyone help with the code needed for using the RTC for achieving clock-grade RPM accuracy?

#define stepPin 2
#define dirPin 5 
 
void setup() {
  // Sets the two pins as Outputs
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);
}
void loop() {
  digitalWrite(dirPin,HIGH); // Enables the motor to move in a particular direction
  // Makes 3200 pulses for making one full cycle rotation
  for(int x = 0; x < 3200; x++) {
    digitalWrite(stepPin,HIGH); 
    delayMicroseconds(937.5);
    digitalWrite(stepPin,LOW); 
    delayMicroseconds(937.5); 

  }

}

delayMicroseconds() takes an integer argument.

Three possibilities for making an accurate clock are to calibrate the CPU oscillator, correct the clock (stepper motor position) periodically using the RTC, or use an RTC with a pulse-per-second output to advance the clock hands.

Clocks are a popular project, so check on line for posted tutorials.

Thanks for the info.
I would like to correct the clock (stepper motor position) periodically using the RTC but I don’t know how to do it and couldn’t find tutorial or reference.
Could you please either help with the code or refer me to other reference?

You can use the TimeLib.h library to keep time on the Arduino, which depends on the CPU clock frequency, and may lose or gain several seconds every day.

Compare that time with the RTC time, say at midnight, and calculate a correction.

You forgot to explain how well your clock currently performs.

This tutorial might give you some ideas: Quartz_Clock_Control

One way to do this is to run the stepper motor slightly faster than 10 RPM, and then wait for a new second to start. But, since your stepper motor does not make a whole number of steps for every second, maybe better to do the correction once every six seconds (once per revolution of your motor).

I would try something like this:

#include <Wire.h>

#define stepPin 2
#define dirPin 5 

byte ss = 99;
byte last_ss = 99;
unsigned long lastMicros = 0UL;
bool keepWaiting = false;
 
void setup() {
  Wire.begin();

  // Sets the two pins as Outputs
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);

  readTheSeconds();
  last_ss = ss;
  lastMicros = micros();
}

void loop() {
  digitalWrite(dirPin,HIGH); // Enables the motor to move in a particular direction
  // Makes 3200 pulses for making one full cycle rotation
  // this should take 5.9712 seconds
  for(int x = 0; x < 3200; x++) {
    digitalWrite(stepPin,HIGH);
    while (micros() - lastMicros < 933) { ; }
    digitalWrite(stepPin,LOW); 
    while (micros() - lastMicros < 1866) { ; }
    lastMicros += 1866;
  }
  keepWaiting = true;
  while (keepWaiting) {
    readTheSeconds();
    switch (((60 + ss) - last_ss) % 60) {
      case 5:
        delay(10);
        break;
      case 6:
        keepWaiting = false;
        break;
      default:
        // signal an error condition
        while (1) {
          for(int x = 0; x < 267; x++) {
            digitalWrite(stepPin,HIGH);
            delayMicroseconds(933);
            digitalWrite(stepPin,LOW);
            delayMicroseconds(933);
          }
          delay(1000);
        }
    }
  }
  last_ss = ss;
  lastMicros = micros();
}

void readTheSeconds() {
  // ask RTC for the seconds
  // send request to receive data starting at register 0
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write((byte)0); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1); // request one byte (seconds)
  // check for a reply from the RTC, and use it if we can
  if (Wire.available()) { 
    // if we're here, we got a reply
    // so now we read the seconds
    ss = bcd2bin(Wire.read()); // get seconds
  }
  else {
    // if we're here, that means we were unable to read the time
    ss = 99;
  }
}

byte bcd2bin(byte x) {
  // converts from binary-coded decimal to a "regular" binary number
  return ((((x >> 4) & 0xF) * 10) + (x & 0xF)) ;
}
1 Like

Is that an accurate picture of your DS3231 module? Does the module have an INT/SQW output pin? It would be connected to pin 3 of the DS3231 chip.

Yes this is picture of my DS3231 module. It doesn’t have an INT/SQW output pin.

My code does not require the use of INT or SQW pins. It just uses SDA and SDL to read the time from the RTC chip, and you said you already have SDA and SDL connected to the Arduino.

1 Like

thanks, I'll try it and let you know..

Just so you know:
The way my code is meant to behave, your clock's hands will move forward 6 seconds in about 5.97 seconds real time, then will wait for about 0.03 second and then will start moving again. Hopefully, a pause of 0.03 second is too short to notice.

The 5.97 seconds are timed using the Arduino's internal timer, which, as you know, is not meant to be extremely accurate. So it might in fact be 5.95 seconds or 5.99 seconds, for example. The aim is to make it slightly less than 6 seconds.

If, according to the RTC, only 5 seconds have passed, then the clock must pause slightly to wait for the next second to begin. If the RTC says that 6 seconds have passed, then it is time for the clock's motor to start turning again. If, according to the RTC, the number of seconds that have passed is neither 5 nor 6, it indicates a malfunction.

I hope that this helps explain my code and how it is meant to behave.

As suggested by @jremington in post #2

If you used a module with INT/SQW output, you could use that output to trigger and time the steps. It should be more accurate than the clock in the Arduino.

Thank you for the explanation. I will try it and let you know if it works.

Can you please show how to use the output as trigger to time the steps?
Thanks

The chip does, if it is not counterfeit. If the module doesn't have that pinout (which is surprising), you can solder a wire directly to the pin.

Please post a link to the product page of the module you have.

Just found this amazing item on AliExpress. Check it out! ₪5.75 5% Off | DS3231 AT24C32 IIC Module Precision Clock Module DS3231SN Memory module DS3231 mini module Real Time 3.3V/5V for Raspberry Pi
https://a.aliexpress.com/_oEn1APS

The yellow one

Connect to the correct chip pin to get INT/SQW. The blue module would have been a better choice.

What would code to drive this clock using the INT / SQW pin on the RTC look like? The number of steps per second is not an integer.

You would use digitalRead() to get the state of the SQW output, and make timing decisions according to the result.

Those three words "make timing decisions" handwave over the entire point of my question.

You could use the accurate INT/SQW signal to make continuous fine adjustments to the number of millis() counted to represent one second (or any fraction of a second you desire). You could do the same thing with micros() to get even finer resolution.