Creating a clock with the 28BYJ-48 motor

I am trying to create a clock using two 28BYJ-48 motors and a RC clock module. I understand that there are some basic issues with using the motor for a clock, but I believe I created code to get around all of them. However, my clock is still not keeping decent time.

So, I started breaking the code down to see what the issue is, and I really think some base assumptions must be wrong.

#include <Stepper.h>

const int stepsPerRev = 2038; // for the 28BYJ-48 motor
// actual steps per rev = 2037.88642 = (32/9)*(22/11)*(26/9)*(31/10)*32

Stepper minuteStepper (stepsPerRev, 6, 8, 7, 9);

void setup() {
   minuteStepper.setSpeed(15);
}

void loop() {
   minuteStepper.step(stepsPerRev);
   delay(2000);  
}

This code should move the motor one revolution, pause 2 seconds, repeat. The motor actually moves a bit less than one revolution. Given that the actual steps per rev is a bit less than 2038, it would seem to me that the motor should actually be moving a bit more than one revolution on this step instruction.

So what’s the deal here? How do I get the motor to move exactly one revolution?

Even if my directional assumption is wrong, the motor is way more than 0.11358 steps off per rev.

Assuming that exactly one rev is not possible, what I had planned was to keep track of the discrepancy in each rev, and move one more or less step whenever the discrepancy is greater than 1.

Timing does not matter as my clock is moving the clock hands minute or hour steps rather than smooth movements as a normal analog clock would.

Thank you.

The 28BYJ-48s I have do exactly 2048 steps per rev. How are you driving the motors? Can you post a wiring diagram? Also, 15 RPM is too fast for a 28BYJ-48, highest step rate I could get without missing steps was about 445 steps per second or 13 RPM.
Try:

#include <Stepper.h>

const int stepsPerRev = 2048; // for the 28BYJ-48 motor
// actual steps per rev = 2037.88642 = (32/9)*(22/11)*(26/9)*(31/10)*32

Stepper minuteStepper (stepsPerRev, 6, 8, 7, 9);

void setup() {
   minuteStepper.setSpeed(5);
}

void loop() {
   minuteStepper.step(stepsPerRev);
   delay(2000); 
}

How do I get the motor to move exactly one revolution?

If you have one of the 28BYJ variants with the odd gear ratio, it is not possible. Check by attaching a pointer to the motor shaft, and slowly sending whatever number of pulses you think corresponds to a full revolution.

A better approach would be to use an RTC and at regular intervals (to be determined) send the appropriate number of steps to move the minute and hour hands. You may have to drop a step now and then to keep the hands aligned with the dial markings, if any. No one will notice :wink:

These motors are made by several manufacturers and the specification is not exact as they are purely for
moving vanes inside vehicle air-con/heater systems - they are not designed for any other purpose, so you
can't expect specific exact gear ratios - they are about 2000 steps/rev.

If you can carefully open it up and measure the gearing ratios you'll be able to correct for it. Since
its a ratio you'll need to use integer arithmetic to model the ratio, not float, or imprecision will add up.

JCA34F: I changed the steps per rev to 2048 and the speed 5 as suggested this morning and it's been running ever since. It is still running exactly 1 revolution every time. It's perfect - THANK YOU.

I had tried 2048 previously and it still wasn't working perfect. It was a combination of that and the speed, which I guess was causing it to skip steps sometimes.

I am a retired software engineer. I have not messed with hardware much, it at all. I recently bought a kit that had an Uno and a whole bunch of sensors and such to learn about all of this. Googling the motor, most sources say there are 2038 steps with the gear ratios that I had in the comment in my original code. I am using motors from two different sources now, as the kit I bought came with one and then I bought a different brand set of 5 motors with controller boards. Even the speed thing is odd, as example code I found for this motor show speeds of 100, which just doesn't work at all. This can be a bit frustrating for a beginner....

Anyway, it kind of bugs me as to why 2048 works. I am very tempted to open one of these motors up to check out the gear ratios. 2048 would imply an exact 64:1 gear ratio.

jremington/MarkT: I am using an RTC and have written code to compensate for the rounding errors caused by having to move the motor integer steps. When I have the whole thing working, I will post my complete code here for someone else to find, should they want to create a clock with these motors.

Thanks for the responses!

I finally got around to finishing this project and thought I would share my code. This seems to keep pretty accurate time.

#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <Stepper.h>
/*
   Three Stepper Motor Clock
   Author: Mike Bianco

   Monitor time changes on a DS3231 RTC and move three 28BYJ-48 stepper motors as hour,
   minute and second hands.

   Notes on the 28BYJ-48 motor:
   - Many sources say that the motor has 2038 steps per rev and explain that number with this
     calculation: (32/9)*(22/11)*(26/9)*(31/10)*32 = 2037.88642. I have not opened a motor up to verify
     what the actual gear ratios are, but 2038 DOES NOT WORK as that does not actually move the motor one
     full rev.
   - Motor speed - there are lots of examples around with speeds showing 15, 100, and all kinds of other
     bigger numbers.  Seems they all cause the motor to skip steps and affect accurate timekeeping.

   The hours, minutes and seconds don't divide evenly into 2048, so the code needs to make
   adjustments to the motor movement to account for the fractional remainders. Double fields
   keep a running total of the discrepancies and adjust the motor stepping as needed. The
   double field on the Arduino Uno is only 4 bytes, which will somewhat limit the accuracy of the clock.

   Hour Motor Controller 
   - [IN1] -- [2]
   - [IN2] -- [3]
   - [IN3] -- [4]
   - [IN4] -- [5]
   - [+]
   - [-]

   Minute Motor Controller 
   - [IN1] -- [6]
   - [IN2] -- [7]
   - [IN3] -- [8]
   - [IN4] -- [9]
   - [+]
   - [-]
   
   Second Motor Controller 
   - [IN1] -- [10]
   - [IN2] -- [11]
   - [IN3] -- [12]
   - [IN4] -- [13]
   - [+]
   - [-]

  Clock 
  - [SDA] -- [A5]
  - [SCL] -- [A4]
  - [VCC] -- [+]
  - [GND] -- [-]

  Hour Switch
  - [] -- [A1]
  - [] -- [GND]

  Minute Switch
  - [] -- [A2]
  - [] -- [GND]

*/

const int rpm = 5;

const int    stepsPerRev  = 2048; // for the 28BYJ-48 motor
const double step60Actual = stepsPerRev / 60.0;  // 2048/60 = 34.13333
const int    step60       = int(floor(step60Actual)); // 34
const double step12Actual = stepsPerRev / 12.0;  // 2048/12 = 170.66667
const int    step12       = int(floor(step12Actual)); // 170
const int    direction    = -1;  // set to -1 to go counter-clockwise

const int hButton = A0; // analog input accessed as digital
const int mButton = A1;

tmElements_t tm;
int h = 0;
int m = 0;
int s = 0;
double sDiscrep = 0.0;
double mDiscrep = 0.0;
double hDiscrep = 0.0;

Stepper secondStepper (stepsPerRev, 10, 12, 11, 13);
Stepper minuteStepper (stepsPerRev, 6, 8, 7, 9);
Stepper hourStepper   (stepsPerRev, 2, 4, 3, 5);

// ==============================================================

void setup() {
  secondStepper.setSpeed(rpm);
  minuteStepper.setSpeed(rpm);
  hourStepper.setSpeed(rpm);

  if (RTC.read(tm)) {
    h = tm.Hour;
    m = tm.Minute;
    s = tm.Second; 
  } else {
    Serial.print("Failed reading clock in setup!");
  }

  pinMode(hButton, INPUT_PULLUP);  // adjust hour button
  pinMode(mButton, INPUT_PULLUP);  // adjust minute button

  Serial.begin(9600);
  Serial.println("Clock start");
  Serial.println(step60);
  Serial.println(step12);
}

// ==============================================================

void doTime() {
  if (tm.Second != s) {
    s = tm.Second;
    sDiscrep += (step60Actual - step60);
    if (sDiscrep > 1.0) {
      secondStepper.step((step60 - 1) * direction);
      sDiscrep -= 1.0;
      Serial.print("@");
    } else {
      secondStepper.step(step60 * direction);
      Serial.print(".");
    }
  }

  if (tm.Minute != m) {
    m = tm.Minute;
    mDiscrep += (step60Actual - step60);
    if (mDiscrep > 1.0) {
      minuteStepper.step((step60 + 1) * direction);
      mDiscrep -= 1.0;
      Serial.print("mDiscrep=");
      Serial.println(mDiscrep, 6);
    } else {
      minuteStepper.step(step60 * direction);
    }
    Serial.println();
    Serial.print(h);
    Serial.print(":");
    Serial.print(m);
  }

  if (tm.Hour != h) {
    h = tm.Hour;
    hDiscrep += ((step12Actual - step12) * direction);
    if (hDiscrep > 1.0) {
      hourStepper.step(step12 + 1);
      hDiscrep -= 1.0;
      Serial.print("hDiscrep=");
      Serial.print(hDiscrep, 6);
    } else {
      hourStepper.step(step12 * direction);
      Serial.print("hour");
    }
  }
}

// ==============================================================

void loop() {
  // Time adjustment buttons
  if (digitalRead(hButton) == LOW) {
    hourStepper.step(step60 / 2 * direction);
    delay(100);
  }
  if (digitalRead(mButton) == LOW) {
    minuteStepper.step(step60 / 2 * direction);
    delay(100);
  }

  if (RTC.read(tm)) {
    doTime();
  } else {
    if (RTC.chipPresent()) {
      Serial.println("The DS3231 is stopped.");
    } else {
      Serial.println("DS3231 read error! Please check the circuitry.");
    }
  }
}

My first implementation is a bicycle cassette and chain clock. Here’s a couple of pics:

Looks great!

If you really are using the DS1307, be aware that it may be the major source of timekeeping error (up to a couple of minutes per day). The DS3231 modules are around two orders of magnitude better (a couple of minutes per year).

I am actually using the DS3231. Thanks!