28BYJ-48 stepper losing steps (maybe)

I'm an Arduino novice but have dabbled in electronics and Basic programming for some time.

The Project: I want to drive a 3D printed clock with a 28BYJ-48 stepper. The stepper is driven by a ULN2003 driver board connected to an Arduino Nano. And I'm using a DS3231 RTC to provide timing updates to keep the clock accurate. Preliminary testing suggests it should all work fine. The motor has enough torque (we'll see about longevity).

The Problem: The Arduino gets updated seconds from the RTC. Every time the Arduino gets an incremented second from the RTC, it drives the motor enough steps to spin the seconds wheel 6 degrees (or 1 second). Of course, the 4096 steps/rev for the motor is not exactly divisible by 360 degrees, so I have to add steps here and there to get the total step count up to 4096. But after 60 RTC signals, one per second, the seconds wheel on the stepper motor is always about 4 seconds slow regardless of how fast I drive the motor. I'm using the AccelStepper library to control step speed and number of steps. If I set up a sketch to continuously drive the motor 4096 steps, it correctly runs the full 360 degree revolution. It only comes up short when I drive the motor in 1 second bursts. Everything works as expected except that it takes 64 seconds from the RTC, instead of the expected 60, to cause a full revolution of the stepper motor.

All my calculations are described in remarks in the code. So am I losing steps? Because of the motor or the coding? Is my use of the AccelStepper library or the RTC flawed? Any suggestions would be welcome.

Here's the code:


```cpp
/* Sketch is meant to control the "ticking" of the "seconds" wheel of a 3D printed clock driven by a 28BYJ-48
  stepper motor.  The stepper motor is driven by a ULN2003 driver board connected to an Arduino Nano.  The
  Nano gets seconds timing from a DS3231 RTC*/

#include "AccelStepper.h"
#include <Wire.h>
#include <RTClib.h>

// Variable declarations
int oldSecs = 0;
int newSecs = 0;
int steps = 68;
int stepSpeed = 750;

// RTC declaration
RTC_DS3231 rtc;

// Motor pin definitions (Motor Pins are the digital pin numbers on Arduino):
#define motorPin1  8      // IN1 on the ULN2003 driver
#define motorPin2  9      // IN2 on the ULN2003 driver
#define motorPin3  10     // IN3 on the ULN2003 driver
#define motorPin4  11     // IN4 on the ULN2003 driver

// Define the AccelStepper interface type; 4 wire motor in half step mode:
#define MotorInterfaceType 8

// Initialize stepper and  pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper library
AccelStepper stepper = AccelStepper(MotorInterfaceType, motorPin1, motorPin3, motorPin2, motorPin4);

void setup() {
	// Start the serial interface
	Serial.begin(57600);

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }

  // Set the maximum steps per second:
  stepper.setMaxSpeed(750);

  // Set oldSec to current seconds from RTC
  DateTime now = rtc.now();
  oldSecs = now.second();
  //Serial.println(oldSecs);
}

void loop() {
  // Set newSecs to current seconds from RTC.  For first iteration newSecs = oldSecs
  DateTime now = rtc.now();
  newSecs = now.second();
  //Serial.println(newSecs);

  // If a second has passed, rotate the motor
  if (newSecs != oldSecs) {

    // Adjust oldSecs for next iteration
    oldSecs = newSecs;

    // Print an indicator to signal a rotatation is expected
    //Serial.println("rotate");

    // Set the number of steps for this iteration of a "second"
    // Approximate steps per second:
    //    The 28BYJ-48 motor does 4096 microsteps per full revolution
    //    4096 steps/360 degrees x 6 degrees/second = 68.2666... steps/second
    //    68 steps x 60 seconds = 4080 steps.
    //    Spread remaining 16 steps (4096 - 4080 = 16) over the full 60 second period
    //    Every 4th second do 69 steps (instead of 68) to use 15 (60/4 = 15) of the steps
    //    On the 60th second do 70 steps to make total step count = 4096
    //    68*45 + 69*14 + 70*1 = 4096
    steps = 68;
    if (newSecs % 4 == 0) steps = 69;
    if (newSecs == 0) steps = 70;

    // Set the stepper speed to a value, under 750, that will cause ~6 degrees (1 second) of
    //    rotation in ~100ms.
    stepSpeed = steps * 10;

    // Reset the current motor position to 0:
    stepper.setCurrentPosition(0);

    // Print value to confirm correct variable values
    //Serial.print(newSecs);
    //Serial.print(" : ");
    //Serial.print(steps);
    //Serial.println();

    // Rotate the motor the required number of steps
    while (stepper.currentPosition() != steps) {
      stepper.setSpeed(stepSpeed);
      stepper.runSpeed();
      }
  }
}

Your small error is accumulating because off addings. Try a strategy like this: Convert the seconds position to an absolute step position. For example 17 seconds has the stepper position 17 * 4096 / 60 == 1160.533333333.
If You use float calculation like (17.0 * 4096.0 / 60.0 ) + 0.5 the step error is 0.5 step or less.
Summing: step position = (second * 4096.0 / 60.0 ) + 0.5. Remembering the previous step position and calculating the difference, the steps needed for the next seconds position, You'll make it!

Take special care about the 59 to 0 seconds move!

Long shot...
The DS3231 RTC has a 4096Hz output, which might be suitable to drive the stepper motor directly.
Leo..

1 Like

Thanks for the suggestion. It got me digging more into the AccelStepper functions. I found a "runToNewPosition" function that seems to have solved the problem. I'm still using the integer method in the original code to get the step count for each second. I add that new step count to the old position to get a new position. Then I move the motor to the new position. After 60 seconds I reset the "CurrentPosition", the targetPosition, and the secondsCount to 0 and start the process over.

Not sure I understand everything in the documentation, but the "runToNewPosition" function is a "blocking" function that, evidently, halts everything else going on until the motor achieves its new position. The "While" loop that I was using apparently allowed background(?) activities to interfere with the stepping and caused me to lose steps.

Anyway, my problem is solved. Thanks again.

I'm new to the DS3231. From my reading it looks like it has a 32kHz output pin and a square wave output pin with a frequency that can be user-determined. Didn't see anything specific to a 4096Hz output. Even if I could generate and use a 4096Hz output, that would give me continuous rotation of my seconds wheel instead of the burst of motion I want to simulate ticking. But thanks for the suggestion. I got what I needed with a different function from the AccelStepper library.

Not likely. What there is inside the while loop are the activities.

That's the goal.

I recently built and coded a digital control of a machinist rotating table and had to think more than once as the step size can be either an angle or an angle defined by the number of steps/sections per 360 degree revolution. Then fractions of steps will more or less always be there.

Regarding your "not likely", I hear you. All I know is that replacing the "While" loop with the blocking function, "runToNewPosition()" from the AccelStepper library has, so far, allowed my second wheel to tick for the last 2.5 hours without losing or gaining any part of a second. With the old code, I was losing 4 seconds per minute.

And I don't mean to belabor this, but to be clear, with my process I don't worry about fractions of a step. Instead, I worry about "left over" steps

4096 steps / 360 degrees * 6 degrees / second = 68.2666... steps/second

I can't do fractions of a step, so how many steps are left over if I do 68 steps/second?
68*60 = 4080 with 16 steps left over. Those 16 steps need to be distributed across the 60 seconds. The algorithm I used to distribute the 16 needed steps is as follows.

  • index 68 steps for all seconds except:

  • if mod(seconds,4) = 0, index 69 steps

  • unless seconds value = 60 when I index 70 steps

  • So there are 15 seconds values where mod(seconds,4) = 0. One of those is at 60 which gets 70 steps. So for the 15 seconds values where mod(seconds,4) = 0, there are 6914 + 701 = 1036 steps

  • The other 45 seconds values get 68 steps or 68*45 = 3060 steps

  • Total steps = 1036 + 3060 = 4096 = number of steps per one revolution.

All the necessary steps are delivered to the motor, but there are slight differences in steps per second.

The main thing is Your project works.
Didn't check Your algoritm. Keeping tracks of the steps You manage a perfect 360 degree action.
You basicaly do the same in a "manual way" and in my my project math did it.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.