First project - Splitflap clock using alexyu132 code

Hi guys,

fairly new to coding and decided to have a go at making a split flap clock, I am good at 3d design and such but my coding side is lacking slighty. I am attempting to use alexyu132 on githubs code to run it. I will add the code i am trying to run below, but basically all its doing is flipping the minutes clock 2 whole rotations due to the feature that calls it to abort when it does 2 rotations without reaching the endstop which is a hall effect sensor set to be at 0. I am not asking for anyone to GIVE me the answer but any tips would be appreciated.

// Flip clock driver code
#include <RTClib.h>

#define STEPS_PER_REV 4096
#define STEPPER_DELAY 1400
#define USE_DST_EU
#define timeZone 1
#define UPLOAD_OFFSET 5
#define POLLING_DELAY 50
#define ENDSTOP_DEBOUNCE_READS 3

const byte stepper_pins[][4] = { {13, 12, 11, 10},
                               {9, 8, 7, 6},
                               {2, 3, 4, 5} };

// Hall effec sensor definitions

//Hours, Minutes (Tens), Minutes (Ones)
const byte endstop_pins[] = {A6, A7, A3};

// Homing settings

// Set these to the number that is displayed after the endstop is triggered.
const byte starting_digits[] = {0, 0, 0};

// The following parameter configures how much the stepper turns after homing.
const unsigned int starting_offset[] = {0, 0, 0};

// Position in steps of each stepper (relative to homing point)
unsigned int stepper_pos[] = {0, 0, 0};
byte drive_step[] = {0, 0, 0};

RTC_DS3231 rtc;

#if defined(USE_DST_EU)
  unsigned int year_old = 0;
  DateTime dst_start, dst_end;
#endif

void e_stop() {
  #if defined(LED_PIN)
    while(1) {
      digitalWrite(LED_PIN, HIGH);
      delay(1000);
      digitalWrite(LED_PIN, LOW);
      delay(1000);
    }
  #else
    abort();
  #endif
}

void disable_stepper(const byte stepper_num) {
  digitalWrite(stepper_pins[stepper_num][0], LOW);
  digitalWrite(stepper_pins[stepper_num][1], LOW);
  digitalWrite(stepper_pins[stepper_num][2], LOW);
  digitalWrite(stepper_pins[stepper_num][3], LOW);
}

void half_step(const byte stepper_num) {
  const byte pos = drive_step[stepper_num];
  digitalWrite(stepper_pins[stepper_num][0], pos < 3);
  digitalWrite(stepper_pins[stepper_num][1], ((pos + 6) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][2], ((pos + 4) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][3], ((pos + 2) % 8) < 3);
  drive_step[stepper_num] = (pos + 1) % 8;
}

// Take the specified amount of steps for a stepper connected to the specified pins, with a
// specified delay (in microseconds) between each step.
void step_num(const byte stepper_num, unsigned int steps, const unsigned int wait) {
  stepper_pos[stepper_num] = (stepper_pos[stepper_num] + steps) % STEPS_PER_REV;
  while(steps > 0) {
    half_step(stepper_num);
    steps--;  
    delayMicroseconds(wait);
  }
}
// Step until the endstop is pressed and released, and update the stepper position to 0.
// If homing doesn't end after 2 rotations, the endstop is assumed to have failed and the program aborts.
void step_to_home(const byte stepper_num, const unsigned int wait) {  
  unsigned int total_steps = 0;
  byte endstop_repeats = 0;
  
  // Step until endstop reads low ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == LOW ? endstop_repeats + 1 : 0;
    half_step(stepper_num);    
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    } 
    delayMicroseconds(wait);
  }
  endstop_repeats = 0;
}

// Step to the specified position. If the current position is greater than the target position, this
// function will re-home and then step to the target position.
void step_to_position(const byte stepper_num, unsigned int target_pos, const unsigned int wait) {
  if (target_pos == stepper_pos[stepper_num]) {
    return;
  }

  // Limit target position to between 0 and STEPS_PER_REV-1
  target_pos %= STEPS_PER_REV;
  
  if (target_pos < stepper_pos[stepper_num]) {
    step_to_home(stepper_num, wait);
    step_num(stepper_num, target_pos, wait);
  } else {
    step_num(stepper_num, target_pos - stepper_pos[stepper_num], wait);
  }
}

// Steps to the specified digit on the display
// To save power, stepper is powered off after running, so exact positional accuracy is not maintained.
// This is allowable since the displays self-calibrate via the endstops every rotation.
void step_to_digit(const byte stepper_num, const byte digit, const unsigned int wait) {
  // The ones display has 10 flaps, the others have 12 flaps
  const byte num_flaps = (stepper_num == 2) ? 10 : 12;
  
  const byte num_digits = (stepper_num == 0) ? 12 : (stepper_num == 1) ? 6 : 10;

  const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
}

bool DST_EU(int year, int month, int day, int hour)
      {
      // There is no DST in Jan, Feb, Nov, Dec
      if(month < 3 || month > 10)
      { 
      return false;
      }
      //There is always DST in Apr, May, Jun, Jul, Aug, Sep
      if(month > 3 && month < 10)
      { 
      return true;
      }
      //Determin if its summertime accoridng to the European normation
      if(month == 3&&(hour+24*day)>=(1+timeZone+24*(31-(5*year/4+4)%7)) || month==10&&(hour+24*day)<(1+timeZone+24*(31-(5*year/4+1)%7)))
      { 
      return true;
      }
      
      else
      {
      return false;
      }
      }

void setup () {
  // Setting stepper pins to output
  for (byte i = 0; i < 3; i++) {
    for (byte j = 0; j < 4; j++) {
      pinMode(stepper_pins[i][j], OUTPUT);
    }
  }
  
  // Set endstop pins as input with pullups enabled
  pinMode(endstop_pins[0], INPUT_PULLUP);
  pinMode(endstop_pins[1], INPUT_PULLUP);
  pinMode(endstop_pins[2], INPUT_PULLUP);


  if (!rtc.begin()) {
    e_stop();
  }

  if (rtc.lostPower()) {
    // Set the time to the compile time + offset
    DateTime compile_time = DateTime(F(__DATE__), F(__TIME__)) + TimeSpan(UPLOAD_OFFSET);

    
    #if defined(USE_DST_EU)
      // Checking if compile time is in DST
      uint16_t year = compile_time.year();



//      //DST for US      
//      // DST starts on the second Sunday of March, 2AM
//      // Get beginning of second week and then offset to Sunday
      DateTime dst_start = DateTime(year, 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // DST ends on the first Sunday of November, 2AM
//      // Get first day of month and then offset to Sunday
      DateTime dst_end = DateTime(year, 11, 1, 2, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // If compile time is between DST start and end, then subtract 1 hour to get standard time
      compile_time = compile_time >= dst_start && compile_time < dst_end ? (compile_time - TimeSpan(0,1,0,0)) : compile_time;
    #endif

    #if defined(USE_DST_EU)
    
      if(DST_EU(compile_time.year(), compile_time.month(), compile_time.day(), compile_time.hour()))
      {
      compile_time = compile_time - TimeSpan(0,1,0,0);  
      }

    #endif
    
    rtc.adjust(compile_time);
  }
  
  // If the minute displays share an endstop pin (necessary on the Pro Micro), we need to make sure both of their endstops are unpressed before homing.
  // We can do this by alternatingly stepping each display in small increments until the endstop pin reads high.
  unsigned int total_steps = 0;


  // Step to home digit
  step_to_home(2, STEPPER_DELAY);
  step_to_digit(2, starting_digits[2], STEPPER_DELAY);
  step_to_home(1, STEPPER_DELAY);
  step_to_digit(1, starting_digits[1], STEPPER_DELAY);
  step_to_home(0, STEPPER_DELAY);
  step_to_digit(0, starting_digits[0], STEPPER_DELAY);
}

void loop () {
  
  DateTime now = rtc.now();
  byte hr = now.hour() % 12;
  byte tens = now.minute() / 6;
  byte ones = now.minute() % 10;
  
  if (hr > 12){
   hr = hr - 12;
  }
  
  #if defined(USE_DST_US)
    // Calculate new DST cutoffs if the year changes
    if(now.year() != year_old) {
      // DST starts on the second Sunday of March, 2AM
      dst_start = DateTime(now.year(), 3, 31, 0, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
    
      // DST ends on the first Sunday of November, 1AM (standard time)
      dst_end = DateTime(now.year(), 10, 28, 0, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0); 
      
      year_old = now.year();
    }
    
    // If current time is between the DST cutoffs, add 1 to the hour digit
    hr = (now >= dst_start && now < dst_end) ? (hr + 1) % 12 : hr;
  #endif

      #if defined(USE_DST_EU)

      //DST for EU
      if(DST_EU(now.year(), now.month(), now.day(), now.hour()))
      {
      hr = now.hour() + 1;  
      }

    #endif

  
  
  step_to_digit(2, ones, STEPPER_DELAY);
  step_to_digit(1, tens, STEPPER_DELAY);
  step_to_digit(0, hr, STEPPER_DELAY);

  delay(POLLING_DELAY);

}

I do not see a question, so I am guessing the motors continue to rotate, when they should stop at two rotations. Perhaps your limit switches are not wired correctly? I see the switches are configured as INPUT_PULLUP, so normally HIGH, with a LOW when pressed.

For reference:

I assume this is what you are talking about:
A split flap clock is a type of mechanical clock that displays the time using rotating flaps, similar to the old-style departure boards you might see at airports or train stations. Each digit or letter is printed on a flap, and as the time changes, the flaps rotate to display the correct numbers.

The split flap clock operates using motors or solenoids to control the flipping motion, and the movement of the flaps makes it visually distinct from traditional clocks with hands or digital displays. These clocks are popular for their retro, nostalgic aesthetic.

Hi yes, sorry. the hall effect sensors when untriggered give a high value and when triggered give a low. I have since managed to make clock function but then I lost the code and cannot figure out what I had done before.

The problem I was having before was that the hour and mintens hall sensor was connect to A6&7 which cannot be used as digital so when I changed them it started to function ISH.

I cannot seem to get it to work again now, i believe it’s something related to the section of code that talks about the “Mintens and min sharing a homing sensor” which mine do not.

My question for you guys then is first, how would you edit the code to account for each set of digits having their own dedicated hall sensor?
And second question, how would I make the necessary changes to have it function on the bst and gmt time zones for the uk?

Any additional help/advice beyond those questions welcome.

Thanks

If it were my project, I would use an ESP. My favourite model is Wemos D1 mini. With these, you don't need an RTC and the clock is always highly accurate, and time zones and daylight savings adjustments are taken care of for you, the functions are all built into the ESP Arduino Core. But the clock would need WiFi internet access.

Problem is, you need 12 pins to control your motors and 3 to monitor your hall sensors. D1 mini doesn't have that many pins. An ESP32 board might.

Knowing this, I would have chosen better stepper motor drivers that need only 2 pins: a direction pin and a pulse pin. And because your clock motors only ever go on one direction, you don't even need the direction pin. So only 3 pins needed to control the motors and 3 for the sensors.

If you want to stick with your current motor drivers, you could add a pcf8575 I/o extender board to give the D1 mini an extra 16 inputs or outputs.

Did A6 and A7 ever work? To which pin did you move the hours? tens? My guess is all motors turned one shaft, so only one limit sensor was used.

Thanks for the responses guys,

Paul in response to using an ESP, I did look into it however there are factors relating to internet connection that won’t make it viable to do it that way where I am doing this project.

I wanted to use an rtc so that no matter where it’s located it will have a reasonably accurate time. Better than the Arduino itself anyway. Down the line I may look into using MSF signal if it isn’t accurate enough as it is.

So with that being said, this code as is almost gets the job done as it sort of worked the other day but I don’t remember what i did to the code haha. So just need some guidance on fixing the code.

And yes on pins A6&7 I couldn’t get a proper reading from the hall sensors. But the hall sensors now occupy A1,2 and 3.

Any suggestions reguarding the odd section about mintens being linked to mins once??

I see no reference to this word... looking.

But... the "minutes" have two places, the "units" "ones" and the "tens"... and the "tens" flip one number after "units" "ones" flips ten times. Then "hours" flips one every six "tens".

Yep my mistake, just using it to describe the minute tens. I’m going to do some testing tomorrow and I’ll let you know what the exact problem I’m experiencing is.

1 Like

Hi guys,

As promised I have done some testing and have ran into a slightly different problem than expected.
So basically I run the code below and the clock functions and flips until the clock gets to its home position by the use of the hall sensors which is great. However it then does not flip to the time and just instead just stays at the home position.
code attached below.

// Flip clock driver code
// Automatically sets time on compile, supports US DST
// Requires Adafruit RTCLib to be installed

#include "RTClib.h"

// ----------- BEGIN CONFIGURABLE SECTION -----------



// Half steps needed for the motors to perform a single rotation.
// Usually 4096 for 28byj-48 steppers
#define STEPS_PER_REV 4096

// The default delay after each motor step in microseconds. 
// Adjusts the speed of the displays. Higher value = slower
#define STEPPER_DELAY 1400

// Comment out the following line to disable DST support if EU DST is used set your time zone
#define USE_DST_EU
//Timezone for EU
#define timeZone 1

// Adjust this to the time in seconds it takes to compile and upload to the Arduino
#define UPLOAD_OFFSET 5

// Delay amount in ms added to the end of each loop iteration.
#define POLLING_DELAY 50

// Number of repeated endstop reads during homing.
#define ENDSTOP_DEBOUNCE_READS 3

// Stepper pin definitions
// 1st row - Hours
// 2nd row - Minutes (Tens)
// 3rd row - Minutes (Ones)
const byte stepper_pins[][4] = { {13, 12, 11, 10},
                               {9, 8, 7, 6},
                               {2, 3, 4, 5} };

// Endstop pin definitions
// Same order as before: Hours, Minutes (Tens), Minutes (Ones)
const byte endstop_pins[] = {A3, A2, A1};

// Homing settings - modify these to calibrate your displays

// Set these to the number that is displayed after the endstop is triggered.
const byte starting_digits[] = {0, 0, 0};

// Starting offset should be increased until the top flap touches the front stop.
const unsigned int starting_offset[] = {0, 0, 0};

// Position in steps of each stepper (relative to homing point)
unsigned int stepper_pos[] = {0,0,0};

// Current drive step for each stepper - 0 to 7 for half-stepping
byte drive_step[] = {0, 0, 0};

RTC_DS3231 rtc;

#if defined(USE_DST_US)
  unsigned int year_old = 0;
  DateTime dst_start, dst_end;
#endif

// Stops the program. Flashes LED slowly if LED pin is defined.
void e_stop() {
  abort();
}

void disable_stepper(const byte stepper_num) {
  digitalWrite(stepper_pins[stepper_num][0], LOW);
  digitalWrite(stepper_pins[stepper_num][1], LOW);
  digitalWrite(stepper_pins[stepper_num][2], LOW);
  digitalWrite(stepper_pins[stepper_num][3], LOW);
}

void half_step(const byte stepper_num) {
  const byte pos = drive_step[stepper_num];
  digitalWrite(stepper_pins[stepper_num][0], pos < 3);
  digitalWrite(stepper_pins[stepper_num][1], ((pos + 6) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][2], ((pos + 4) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][3], ((pos + 2) % 8) < 3);
  drive_step[stepper_num] = (pos + 1) % 8;
}

// Take the specified amount of steps for a stepper connected to the specified pins, with a
// specified delay (in microseconds) between each step.
void step_num(const byte stepper_num, unsigned int steps, const unsigned int wait) {
  stepper_pos[stepper_num] = (stepper_pos[stepper_num] + steps) % STEPS_PER_REV;
  while(steps > 0) {
    half_step(stepper_num);
    steps--;  
    delayMicroseconds(wait);
  }
}

// Step until the endstop is pressed and released, and update the stepper position to 0.
// If homing doesn't end after 2 rotations, the endstop is assumed to have failed and the program aborts.
void step_to_home(const byte stepper_num, const unsigned int wait) {  
  unsigned int total_steps = 0;
  byte endstop_repeats = 0;
  
  // Step until endstop reads low ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == LOW ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);    
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    } 
    delayMicroseconds(wait);
  }
  endstop_repeats = 0;
  
  // Step until endstop reads high ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == HIGH ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    }
    delayMicroseconds(wait);
  }

  stepper_pos[stepper_num] = 0; 
}

// Step to the specified position. If the current position is greater than the target position, this
// function will re-home and then step to the target position.
void step_to_position(const byte stepper_num, unsigned int target_pos, const unsigned int wait) {
  if (target_pos == stepper_pos[stepper_num]) {
    return;
  }

  // Limit target position to between 0 and STEPS_PER_REV-1
  target_pos %= STEPS_PER_REV;
  
  if (target_pos < stepper_pos[stepper_num]) {
    step_to_home(stepper_num, wait);
    step_num(stepper_num, target_pos, wait);
  } else {
    step_num(stepper_num, target_pos - stepper_pos[stepper_num], wait);
  }
}

// Steps to the specified digit on the display
// To save power, stepper is powered off after running, so exact positional accuracy is not maintained.
// This is allowable since the displays self-calibrate via the endstops every rotation.
void step_to_digit(const byte stepper_num, const byte digit, const unsigned int wait) {
  // The ones display has 10 flaps, the others have 12 flaps
  const byte num_flaps = (stepper_num == 2) ? 10 : 12;
  
  const byte num_digits = (stepper_num == 0) ? 12 : (stepper_num == 1) ? 6 : 10;

  const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
}

bool DST_EU(int year, int month, int day, int hour)
      {
      // There is no DST in Jan, Feb, Nov, Dec
      if(month < 3 || month > 10)
      { 
      return false;
      }
      //There is always DST in Apr, May, Jun, Jul, Aug, Sep
      if(month > 3 && month < 10)
      { 
      return true;
      }
      //Determin if its summertime accoridng to the European normation
      if(month == 3&&(hour+24*day)>=(1+timeZone+24*(31-(5*year/4+4)%7)) || month==10&&(hour+24*day)<(1+timeZone+24*(31-(5*year/4+1)%7)))
      { 
      return true;
      }
      
      else
      {
      return false;
      }
      }

void setup () {
  // Setting stepper pins to output
  for (byte i = 0; i < 3; i++) {
    for (byte j = 0; j < 4; j++) {
      pinMode(stepper_pins[i][j], OUTPUT);
    }
  }
  
  // Set endstop pins as input with pullups enabled
  pinMode(endstop_pins[0], INPUT_PULLUP);
  pinMode(endstop_pins[1], INPUT_PULLUP);
  pinMode(endstop_pins[2], INPUT_PULLUP);

  if (!rtc.begin()) {
    e_stop();
  }

  if (rtc.lostPower()) {
    // Set the time to the compile time + offset
    DateTime compile_time = DateTime(F(__DATE__), F(__TIME__)) + TimeSpan(UPLOAD_OFFSET);
    
    #if defined(USE_DST_EU)
      // Checking if compile time is in DST
      uint16_t year = compile_time.year();



//      //DST for US      
//      // DST starts on the second Sunday of March, 2AM
//      // Get beginning of second week and then offset to Sunday
      DateTime dst_start = DateTime(year, 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // DST ends on the first Sunday of November, 2AM
//      // Get first day of month and then offset to Sunday
      DateTime dst_end = DateTime(year, 11, 1, 2, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // If compile time is between DST start and end, then subtract 1 hour to get standard time
      compile_time = compile_time >= dst_start && compile_time < dst_end ? (compile_time - TimeSpan(0,1,0,0)) : compile_time;
    #endif

    #if defined(USE_DST_EU)
    
      if(DST_EU(compile_time.year(), compile_time.month(), compile_time.day(), compile_time.hour()))
      {
      compile_time = compile_time - TimeSpan(0,1,0,0);  
      }

    #endif
    
    rtc.adjust(compile_time);
  }
  
  // If the minute displays share an endstop pin (necessary on the Pro Micro), we need to make sure both of their endstops are unpressed before homing.
  // We can do this by alternatingly stepping each display in small increments until the endstop pin reads high.
  unsigned int total_steps = 0;
  if(endstop_pins[1] == endstop_pins[2]) {
    while(digitalRead(endstop_pins[1]) == LOW) {
      step_num(1, 200, STEPPER_DELAY);
      disable_stepper(1);

      // Still pressed, try the other display
      if(digitalRead(endstop_pins[1]) == LOW) {
        step_num(2, 200, STEPPER_DELAY);  
        disable_stepper(2);
      }

      // Similar to homing, if a max number of steps is reached, the endstop is assumed to have failed and the program aborts.
      total_steps += 200;
      if(total_steps > STEPS_PER_REV) {
        e_stop();
      }
    }
  }

  // Step to home digit
  step_to_home(2, STEPPER_DELAY);
  step_to_digit(2, starting_digits[2], STEPPER_DELAY);
  step_to_home(1, STEPPER_DELAY);
  step_to_digit(1, starting_digits[1], STEPPER_DELAY);
  step_to_home(0, STEPPER_DELAY);
  step_to_digit(0, starting_digits[0], STEPPER_DELAY);
}

void loop () {
  
  DateTime now = rtc.now();
  byte hr = now.hour() % 12;
  byte tens = now.minute() / 6;
  byte ones = now.minute() % 10;
  
  #if defined(USE_DST_US)
    // Calculate new DST cutoffs if the year changes
    if(now.year() != year_old) {
      // DST starts on the second Sunday of March, 2AM
      dst_start = DateTime(now.year(), 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
    
      // DST ends on the first Sunday of November, 1AM (standard time)
      dst_end = DateTime(now.year(), 11, 1, 1, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0); 
      
      year_old = now.year();
    }
    
    // If current time is between the DST cutoffs, add 1 to the hour digit
    hr = (now >= dst_start && now < dst_end) ? (hr + 1) % 12 : hr;
  #endif

      #if defined(USE_DST_EU)

      //DST for EU
      if(DST_EU(now.year(), now.month(), now.day(), now.hour()))
      {
      hr = now.hour() + 1;  
      }

    #endif

  
  
  step_to_digit(2, ones, STEPPER_DELAY);
  step_to_digit(1, tens, STEPPER_DELAY);
  step_to_digit(0, hr, STEPPER_DELAY);

  delay(POLLING_DELAY);
}
// Flip clock driver code
// Automatically sets time on compile, supports US DST
// Requires Adafruit RTCLib to be installed

#include "RTClib.h"

// ----------- BEGIN CONFIGURABLE SECTION -----------



// Half steps needed for the motors to perform a single rotation.
// Usually 4096 for 28byj-48 steppers
#define STEPS_PER_REV 4096

// The default delay after each motor step in microseconds. 
// Adjusts the speed of the displays. Higher value = slower
#define STEPPER_DELAY 1400

// Comment out the following line to disable DST support if EU DST is used set your time zone
#define USE_DST_EU
//Timezone for EU
#define timeZone 1

// Adjust this to the time in seconds it takes to compile and upload to the Arduino
#define UPLOAD_OFFSET 700

// Delay amount in ms added to the end of each loop iteration.
#define POLLING_DELAY 70

// Number of repeated endstop reads during homing.
#define ENDSTOP_DEBOUNCE_READS 3

// Stepper pin definitions
// 1st row - Hours
// 2nd row - Minutes (Tens)
// 3rd row - Minutes (Ones)
const byte stepper_pins[][4] = { {13, 12, 11, 10},
                               {9, 8, 7, 6},
                               {2, 3, 4, 5} };

// Endstop pin definitions
// Same order as before: Hours, Minutes (Tens), Minutes (Ones)
const byte endstop_pins[] = {A3, A2, A1};

// Homing settings - modify these to calibrate your displays

// Set these to the number that is displayed after the endstop is triggered.
const byte starting_digits[] = {0, 0, 0};

// Starting offset should be increased until the top flap touches the front stop.
const unsigned int starting_offset[] = {0, 0, 0};

// Position in steps of each stepper (relative to homing point)
unsigned int stepper_pos[] = {0,0,0};

// Current drive step for each stepper - 0 to 7 for half-stepping
byte drive_step[] = {0, 0, 0};

RTC_DS3231 rtc;

#if defined(USE_DST_US)
  unsigned int year_old = 0;
  DateTime dst_start, dst_end;
#endif

// Stops the program. Flashes LED slowly if LED pin is defined.
void e_stop() {
  abort();
}

void disable_stepper(const byte stepper_num) {
  digitalWrite(stepper_pins[stepper_num][0], LOW);
  digitalWrite(stepper_pins[stepper_num][1], LOW);
  digitalWrite(stepper_pins[stepper_num][2], LOW);
  digitalWrite(stepper_pins[stepper_num][3], LOW);
}

void half_step(const byte stepper_num) {
  const byte pos = drive_step[stepper_num];
  digitalWrite(stepper_pins[stepper_num][0], pos < 3);
  digitalWrite(stepper_pins[stepper_num][1], ((pos + 6) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][2], ((pos + 4) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][3], ((pos + 2) % 8) < 3);
  drive_step[stepper_num] = (pos + 1) % 8;
}

// Take the specified amount of steps for a stepper connected to the specified pins, with a
// specified delay (in microseconds) between each step.
void step_num(const byte stepper_num, unsigned int steps, const unsigned int wait) {
  stepper_pos[stepper_num] = (stepper_pos[stepper_num] + steps) % STEPS_PER_REV;
  while(steps > 0) {
    half_step(stepper_num);
    steps--;  
    delayMicroseconds(wait);
  }
}

// Step until the endstop is pressed and released, and update the stepper position to 0.
// If homing doesn't end after 2 rotations, the endstop is assumed to have failed and the program aborts.
void step_to_home(const byte stepper_num, const unsigned int wait) {  
  unsigned int total_steps = 0;
  byte endstop_repeats = 0;
  
  // Step until endstop reads low ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == LOW ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);    
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    } 
    delayMicroseconds(wait);
  }
  endstop_repeats = 0;
  
  // Step until endstop reads high ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == HIGH ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    }
    delayMicroseconds(wait);
  }

  stepper_pos[stepper_num] = 0; 
}

// Step to the specified position. If the current position is greater than the target position, this
// function will re-home and then step to the target position.
void step_to_position(const byte stepper_num, unsigned int target_pos, const unsigned int wait) {
  if (target_pos == stepper_pos[stepper_num]) {
    return;
  }

  // Limit target position to between 0 and STEPS_PER_REV-1
  target_pos %= STEPS_PER_REV;
  
  if (target_pos < stepper_pos[stepper_num]) {
    step_to_home(stepper_num, wait);
    step_num(stepper_num, target_pos, wait);
  } else {
    step_num(stepper_num, target_pos - stepper_pos[stepper_num], wait);
  }
}

// Steps to the specified digit on the display
// To save power, stepper is powered off after running, so exact positional accuracy is not maintained.
// This is allowable since the displays self-calibrate via the endstops every rotation.
void step_to_digit(const byte stepper_num, const byte digit, const unsigned int wait) {
  // The ones display has 10 flaps, the others have 12 flaps
  const byte num_flaps = (stepper_num == 2) ? 10 : 12;
  
  const byte num_digits = (stepper_num == 0) ? 12 : (stepper_num == 1) ? 6 : 10;

  const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
  
  // The tens display has 2 full sets of digits, so we'll need to step to the closest target digit.
  if(stepper_num == 1) {
    // The repeated digit is offset by a half-rotation from the first target.
    const unsigned int second_target = target_pos + STEPS_PER_REV/2;

    // If the current position is between the two target positions, step to the second target position, as it's the closest given that we can't step backwards.
    // Otherwise, step to the first target position.
    if(stepper_pos[stepper_num] > target_pos && stepper_pos[stepper_num] <= second_target) {
      step_to_position(stepper_num, second_target, wait);
    } else {
      step_to_position(stepper_num, target_pos, wait);
    }
  } else {
    // The ones and hour displays only have a single set of digits, so simply step to the target position.
    step_to_position(stepper_num, target_pos, wait);
  }
  
  disable_stepper(stepper_num);
}

bool DST_EU(int year, int month, int day, int hour)
      {
      // There is no DST in Jan, Feb, Nov, Dec
      if(month < 3 || month > 10)
      { 
      return false;
      }
      //There is always DST in Apr, May, Jun, Jul, Aug, Sep
      if(month > 3 && month < 10)
      { 
      return true;
      }
      //Determin if its summertime accoridng to the European normation
      if(month == 3&&(hour+24*day)>=(1+timeZone+24*(31-(5*year/4+4)%7)) || month==10&&(hour+24*day)<(1+timeZone+24*(31-(5*year/4+1)%7)))
      { 
      return true;
      }
      
      else
      {
      return false;
      }
      }

void setup () {
  // Setting stepper pins to output
  for (byte i = 0; i < 3; i++) {
    for (byte j = 0; j < 4; j++) {
      pinMode(stepper_pins[i][j], OUTPUT);
    }
  }
  
  // Set endstop pins as input with pullups enabled
  pinMode(endstop_pins[0], INPUT_PULLUP);
  pinMode(endstop_pins[1], INPUT_PULLUP);
  pinMode(endstop_pins[2], INPUT_PULLUP);

  // Set LED pin to output and power it off
  #if defined(LED_PIN)
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, LED_INVERT);
  #endif

  // Turn off the second LED if needed
  #if defined(LED_2_PIN)
    pinMode(LED_2_PIN, OUTPUT);
    digitalWrite(LED_2_PIN, LED_2_INVERT);
  #endif

  if (!rtc.begin()) {
    e_stop();
  }

  if (rtc.lostPower()) {
    // Set the time to the compile time + offset
    DateTime compile_time = DateTime(F(__DATE__), F(__TIME__)) + TimeSpan(UPLOAD_OFFSET);
    
    #if defined(USE_DST_US)
      // Checking if compile time is in DST
      uint16_t year = compile_time.year();



//      //DST for US      
//      // DST starts on the second Sunday of March, 2AM
//      // Get beginning of second week and then offset to Sunday
      DateTime dst_start = DateTime(year, 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // DST ends on the first Sunday of November, 2AM
//      // Get first day of month and then offset to Sunday
      DateTime dst_end = DateTime(year, 11, 1, 2, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // If compile time is between DST start and end, then subtract 1 hour to get standard time
      compile_time = compile_time >= dst_start && compile_time < dst_end ? (compile_time - TimeSpan(0,1,0,0)) : compile_time;
    #endif

    #if defined(USE_DST_EU)
    
      if(DST_EU(compile_time.year(), compile_time.month(), compile_time.day(), compile_time.hour()))
      {
      compile_time = compile_time - TimeSpan(0,1,0,0);  
      }

    #endif
    
    rtc.adjust(compile_time);
  }
  
  // If the minute displays share an endstop pin (necessary on the Pro Micro), we need to make sure both of their endstops are unpressed before homing.
  // We can do this by alternatingly stepping each display in small increments until the endstop pin reads high.
  unsigned int total_steps = 0;
  if(endstop_pins[1] == endstop_pins[2]) {
    while(digitalRead(endstop_pins[1]) == LOW) {
      step_num(1, 200, STEPPER_DELAY);
      disable_stepper(1);

      // Still pressed, try the other display
      if(digitalRead(endstop_pins[1]) == LOW) {
        step_num(2, 200, STEPPER_DELAY);  
        disable_stepper(2);
      }

      // Similar to homing, if a max number of steps is reached, the endstop is assumed to have failed and the program aborts.
      total_steps += 200;
      if(total_steps > STEPS_PER_REV) {
        e_stop();
      }
    }
  }

  // Step to home digit
  step_to_home(2, STEPPER_DELAY);
  step_to_digit(2, starting_digits[2], STEPPER_DELAY);
  step_to_home(1, STEPPER_DELAY);
  step_to_digit(1, starting_digits[1], STEPPER_DELAY);
  step_to_home(0, STEPPER_DELAY);
  step_to_digit(0, starting_digits[0], STEPPER_DELAY);
}

void loop () {
  
  DateTime now = rtc.now();
  byte hr = now.hour() % 12;
  byte tens = now.minute() % 6;
  byte ones = now.minute() % 10;
  
  #if defined(USE_DST_US)
    // Calculate new DST cutoffs if the year changes
    if(now.year() != year_old) {
      // DST starts on the second Sunday of March, 2AM
      dst_start = DateTime(now.year(), 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
    
      // DST ends on the first Sunday of November, 1AM (standard time)
      dst_end = DateTime(now.year(), 11, 1, 1, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0); 
      
      year_old = now.year();
    }
    
    // If current time is between the DST cutoffs, add 1 to the hour digit
    hr = (now >= dst_start && now < dst_end) ? (hr + 1) % 12 : hr;
  #endif

      #if defined(USE_DST_EU)

      //DST for EU
      if(DST_EU(now.year(), now.month(), now.day(), now.hour()))
      {
      hr = now.hour() + 0;  
      }

    #endif

  
  
  step_to_digit(2, ones, STEPPER_DELAY);
  step_to_digit(1, tens, STEPPER_DELAY);
  step_to_digit(0, hr, STEPPER_DELAY);

  delay(POLLING_DELAY);

This now turns the tens flap at the incorrect time. no idea why

After a long test, it's clear something about the tens flaps that is not functioning properly. Its almost like its turning the tens motor every other minute. I have looked through the code and cannot work out why! any help welcome

A final bit of information, the attached code is running the clock.

  • Minutes track perfectly and turn over at the correct time.
  • Tens are not tracking very well at all, at 9:35 the clock instead of turning to 9:36 decided to home the tens display to 0 and on the full rotation of the minutes display 9-->0 did not rotate the tens display until it got around to 2 minutes on the minute display when the tens went 0-->1. They also seem to not be rotating the correct amount of steps, almost like the ratio of steps per flap on the main crank is wrong in the code, maybe because alex thinks there is 12 flaps on the tens crank when there is 6? Unsure how and where to make that change. Any pointers?
  • Hours so far seem to work ok but not enough time has passed to tell.

Thanks

// Flip clock driver code
// Automatically sets time on compile, supports US DST
// Requires Adafruit RTCLib to be installed

#include "RTClib.h"

// ----------- BEGIN CONFIGURABLE SECTION -----------



// Half steps needed for the motors to perform a single rotation.
// Usually 4096 for 28byj-48 steppers
#define STEPS_PER_REV 4096

// The default delay after each motor step in microseconds. 
// Adjusts the speed of the displays. Higher value = slower
#define STEPPER_DELAY 1400

// Comment out the following line to disable DST support if EU DST is used set your time zone
#define USE_DST_EU
//Timezone for EU
#define timeZone 1

// Adjust this to the time in seconds it takes to compile and upload to the Arduino
#define UPLOAD_OFFSET 700

// Delay amount in ms added to the end of each loop iteration.
#define POLLING_DELAY 50

// Number of repeated endstop reads during homing.
#define ENDSTOP_DEBOUNCE_READS 3

// Stepper pin definitions
// 1st row - Hours
// 2nd row - Minutes (Tens)
// 3rd row - Minutes (Ones)
const byte stepper_pins[][4] = { {13, 12, 11, 10},
                               {9, 8, 7, 6},
                               {2, 3, 4, 5} };

// Endstop pin definitions
// Same order as before: Hours, Minutes (Tens), Minutes (Ones)
const byte endstop_pins[] = {A3, A2, A1};

// Homing settings - modify these to calibrate your displays

// Set these to the number that is displayed after the endstop is triggered.
const byte starting_digits[] = {0, 0, 0};

// Starting offset should be increased until the top flap touches the front stop.
const unsigned int starting_offset[] = {0, 0, 0};

// Position in steps of each stepper (relative to homing point)
unsigned int stepper_pos[] = {0,0,0};

// Current drive step for each stepper - 0 to 7 for half-stepping
byte drive_step[] = {0, 0, 0};

RTC_DS3231 rtc;

#if defined(USE_DST_US)
  unsigned int year_old = 0;
  DateTime dst_start, dst_end;
#endif

// Stops the program. Flashes LED slowly if LED pin is defined.
void e_stop() {
  abort();
}

void disable_stepper(const byte stepper_num) {
  digitalWrite(stepper_pins[stepper_num][0], LOW);
  digitalWrite(stepper_pins[stepper_num][1], LOW);
  digitalWrite(stepper_pins[stepper_num][2], LOW);
  digitalWrite(stepper_pins[stepper_num][3], LOW);
}

void half_step(const byte stepper_num) {
  const byte pos = drive_step[stepper_num];
  digitalWrite(stepper_pins[stepper_num][0], pos < 3);
  digitalWrite(stepper_pins[stepper_num][1], ((pos + 6) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][2], ((pos + 4) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][3], ((pos + 2) % 8) < 3);
  drive_step[stepper_num] = (pos + 1) % 8;
}

// Take the specified amount of steps for a stepper connected to the specified pins, with a
// specified delay (in microseconds) between each step.
void step_num(const byte stepper_num, unsigned int steps, const unsigned int wait) {
  stepper_pos[stepper_num] = (stepper_pos[stepper_num] + steps) % STEPS_PER_REV;
  while(steps > 0) {
    half_step(stepper_num);
    steps--;  
    delayMicroseconds(wait);
  }
}

// Step until the endstop is pressed and released, and update the stepper position to 0.
// If homing doesn't end after 2 rotations, the endstop is assumed to have failed and the program aborts.
void step_to_home(const byte stepper_num, const unsigned int wait) {  
  unsigned int total_steps = 0;
  byte endstop_repeats = 0;
  
  // Step until endstop reads low ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == LOW ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);    
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    } 
    delayMicroseconds(wait);
  }
  endstop_repeats = 0;
  
  // Step until endstop reads high ENDSTOP_DEBOUNCE_READS times in a row
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == HIGH ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    }
    delayMicroseconds(wait);
  }

  stepper_pos[stepper_num] = 0; 
}

// Step to the specified position. If the current position is greater than the target position, this
// function will re-home and then step to the target position.
void step_to_position(const byte stepper_num, unsigned int target_pos, const unsigned int wait) {
  if (target_pos == stepper_pos[stepper_num]) {
    return;
  }

  // Limit target position to between 0 and STEPS_PER_REV-1
  target_pos %= STEPS_PER_REV;
  
  if (target_pos < stepper_pos[stepper_num]) {
    step_to_home(stepper_num, wait);
    step_num(stepper_num, target_pos, wait);
  } else {
    step_num(stepper_num, target_pos - stepper_pos[stepper_num], wait);
  }
}

// Steps to the specified digit on the display
// To save power, stepper is powered off after running, so exact positional accuracy is not maintained.
// This is allowable since the displays self-calibrate via the endstops every rotation.
void step_to_digit(const byte stepper_num, const byte digit, const unsigned int wait) {
  // The ones display has 10 flaps, the others have 12 flaps
  const byte num_flaps = (stepper_num == 2) ? 10 : 12;
  
  const byte num_digits = (stepper_num == 0) ? 12 : (stepper_num == 1) ? 6 : 10;

  const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
  
  // The tens display has 2 full sets of digits, so we'll need to step to the closest target digit.
  if(stepper_num == 1) {
    // The repeated digit is offset by a half-rotation from the first target.
    const unsigned int second_target = target_pos + STEPS_PER_REV/2;

    // If the current position is between the two target positions, step to the second target position, as it's the closest given that we can't step backwards.
    // Otherwise, step to the first target position.
    if(stepper_pos[stepper_num] > target_pos && stepper_pos[stepper_num] <= second_target) {
      step_to_position(stepper_num, second_target, wait);
    } else {
      step_to_position(stepper_num, target_pos, wait);
    }
  } else {
    // The ones and hour displays only have a single set of digits, so simply step to the target position.
    step_to_position(stepper_num, target_pos, wait);
  }
  
  disable_stepper(stepper_num);
}

bool DST_EU(int year, int month, int day, int hour)
      {
      // There is no DST in Jan, Feb, Nov, Dec
      if(month < 3 || month > 10)
      { 
      return false;
      }
      //There is always DST in Apr, May, Jun, Jul, Aug, Sep
      if(month > 3 && month < 10)
      { 
      return true;
      }
      //Determin if its summertime accoridng to the European normation
      if(month == 3&&(hour+24*day)>=(1+timeZone+24*(31-(5*year/4+4)%7)) || month==10&&(hour+24*day)<(1+timeZone+24*(31-(5*year/4+1)%7)))
      { 
      return true;
      }
      
      else
      {
      return false;
      }
      }

void setup () {
  // Setting stepper pins to output
  for (byte i = 0; i < 3; i++) {
    for (byte j = 0; j < 4; j++) {
      pinMode(stepper_pins[i][j], OUTPUT);
    }
  }
  
  // Set endstop pins as input with pullups enabled
  pinMode(endstop_pins[0], INPUT_PULLUP);
  pinMode(endstop_pins[1], INPUT_PULLUP);
  pinMode(endstop_pins[2], INPUT_PULLUP);


  if (!rtc.begin()) {
    e_stop();
  }

  if (rtc.lostPower()) {
    // Set the time to the compile time + offset
    DateTime compile_time = DateTime(F(__DATE__), F(__TIME__)) + TimeSpan(UPLOAD_OFFSET);
    
    #if defined(USE_DST_US)
      // Checking if compile time is in DST
      uint16_t year = compile_time.year();



//      //DST for US      
//      // DST starts on the second Sunday of March, 2AM
//      // Get beginning of second week and then offset to Sunday
      DateTime dst_start = DateTime(year, 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // DST ends on the first Sunday of November, 2AM
//      // Get first day of month and then offset to Sunday
      DateTime dst_end = DateTime(year, 11, 1, 2, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0);
//    
//      // If compile time is between DST start and end, then subtract 1 hour to get standard time
      compile_time = compile_time >= dst_start && compile_time < dst_end ? (compile_time - TimeSpan(0,1,0,0)) : compile_time;
    #endif

    #if defined(USE_DST_EU)
    
      if(DST_EU(compile_time.year(), compile_time.month(), compile_time.day(), compile_time.hour()))
      {
      compile_time = compile_time - TimeSpan(0,1,0,0);  
      }

    #endif
    
    rtc.adjust(compile_time);
  }
  
  // If the minute displays share an endstop pin (necessary on the Pro Micro), we need to make sure both of their endstops are unpressed before homing.
  // We can do this by alternatingly stepping each display in small increments until the endstop pin reads high.
  unsigned int total_steps = 0;
  if(endstop_pins[1] == endstop_pins[2]) {
    while(digitalRead(endstop_pins[1]) == LOW) {
      step_num(1, 200, STEPPER_DELAY);
      disable_stepper(1);

      // Still pressed, try the other display
      if(digitalRead(endstop_pins[1]) == LOW) {
        step_num(2, 200, STEPPER_DELAY);  
        disable_stepper(2);
      }

      // Similar to homing, if a max number of steps is reached, the endstop is assumed to have failed and the program aborts.
      total_steps += 200;
      if(total_steps > STEPS_PER_REV) {
        e_stop();
      }
    }
  }

  // Step to home digit
  step_to_home(2, STEPPER_DELAY);
  step_to_digit(2, starting_digits[2], STEPPER_DELAY);
  step_to_home(1, STEPPER_DELAY);
  step_to_digit(1, starting_digits[1], STEPPER_DELAY);
  step_to_home(0, STEPPER_DELAY);
  step_to_digit(0, starting_digits[0], STEPPER_DELAY);
}

void loop () {
  
  DateTime now = rtc.now();
  byte hr = now.hour() % 12;
  byte tens = now.minute() / 6;
  byte ones = now.minute() % 10;
  
  #if defined(USE_DST_US)
    // Calculate new DST cutoffs if the year changes
    if(now.year() != year_old) {
      // DST starts on the second Sunday of March, 2AM
      dst_start = DateTime(now.year(), 3, 8, 2, 0, 0);
      dst_start = dst_start + TimeSpan((7-dst_start.dayOfTheWeek()) % 7, 0, 0, 0);
    
      // DST ends on the first Sunday of November, 1AM (standard time)
      dst_end = DateTime(now.year(), 11, 1, 1, 0, 0);
      dst_end = dst_end + TimeSpan((7-dst_end.dayOfTheWeek()) % 7, 0, 0, 0); 
      
      year_old = now.year();
    }
    
    // If current time is between the DST cutoffs, add 1 to the hour digit
    hr = (now >= dst_start && now < dst_end) ? (hr + 1) % 12 : hr;
  #endif

      #if defined(USE_DST_EU)

      //DST for EU
      if(DST_EU(now.year(), now.month(), now.day(), now.hour()))
      {
      hr = now.hour() + 0;  
      }

    #endif

  
  
  step_to_digit(2, ones, STEPPER_DELAY);
  step_to_digit(1, tens, STEPPER_DELAY);
  step_to_digit(0, hr, STEPPER_DELAY);

  delay(POLLING_DELAY);
}

Could this have to do with the microstep radian movement (degree) not falling where the flap/hook need to release?
Could the physical size of the tens flaps be effecting the movement (were the printed in a different batch)?
Can you switch motor connections (can you drive the units with the tens motor, and drive the tens with the units motor)?
Can you make the code output to the Serial Monitor for movement, and ensure the test are advancing once for every units advance?

Do you need a delay after each of these "step_to_digits" or should all flips be accomplished, and only one delay? My thinking is; are these delays accumulative (ones get one, tens get "ones" and "tens" delay, hours get three delays)

  step_to_digit(2, ones, STEPPER_DELAY);
  step_to_digit(1, tens, STEPPER_DELAY);
  step_to_digit(0, hr, STEPPER_DELAY);
  delay(POLLING_DELAY);

And another guess... does this line evaluate how you want? "Half the target and constant" or the "target plus half the constant."

const unsigned int second_target = target_pos + STEPS_PER_REV / 2;

With parentheses...

const unsigned int second_target = target_pos + (STEPS_PER_REV / 2);

Thanks for the message, i got to the bottom of it. It was related to how many digits and flaps on each motor was set for each motor. Fully functioning code below.

// Flip clock driver code
#include "RTClib.h"

// Half steps needed for the  stepper motors to perform a single rotation.
#define STEPS_PER_REV 4096

// The default delay after each motor step in microseconds. 
#define STEPPER_DELAY 1400

#define USE_DST_EU
#define timeZone 1

// Adjust this to the time in seconds it takes to compile and upload to the Arduino, stops clock being slow as rtc timestamp is before it compiles.
#define UPLOAD_OFFSET 10

// Delay amount in ms added to the end of each loop iteration.
#define POLLING_DELAY 50

// Number of Hall sensor reads while homing.
#define ENDSTOP_DEBOUNCE_READS 3

// Stepper pin definitions
// 1st row - Hours
// 2nd row - Minutes (Tens)
// 3rd row - Minutes (Ones)
const byte stepper_pins[][4] = { {13, 12, 11, 10},
                               {9, 8, 7, 6},
                               {2, 3, 4, 5} };

// Hall effect sensor definitions
// Hours, Minutes (Tens), Minutes (Ones)
const byte endstop_pins[] = {A3, A2, A1};

// Homing settings

// Set these to the number that is displayed after the hall sensor pulses.
const byte starting_digits[] = {0, 0, 0};

// Starting offset, fine step adjustment for flaps.
const unsigned int starting_offset[] = {-100, -50, 0};

// Position in steps of each stepper (relative to homing point)
unsigned int stepper_pos[] = {0,0,0};

// Current drive step for each stepper - 0 to 7 for half-stepping
byte drive_step[] = {0, 0, 0};

RTC_DS3231 rtc;

// Stops the program.
void e_stop() {
  abort();
}

void disable_stepper(const byte stepper_num) {
  digitalWrite(stepper_pins[stepper_num][0], LOW);
  digitalWrite(stepper_pins[stepper_num][1], LOW);
  digitalWrite(stepper_pins[stepper_num][2], LOW);
  digitalWrite(stepper_pins[stepper_num][3], LOW);
}

void half_step(const byte stepper_num) {
  const byte pos = drive_step[stepper_num];
  digitalWrite(stepper_pins[stepper_num][0], pos < 3);
  digitalWrite(stepper_pins[stepper_num][1], ((pos + 6) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][2], ((pos + 4) % 8) < 3);
  digitalWrite(stepper_pins[stepper_num][3], ((pos + 2) % 8) < 3);
  drive_step[stepper_num] = (pos + 1) % 8;
}

// Take the specified amount of steps for a stepper connected to the specified pins, with a
// specified delay (in microseconds) between each step.
void step_num(const byte stepper_num, unsigned int steps, const unsigned int wait) {
  stepper_pos[stepper_num] = (stepper_pos[stepper_num] + steps) % STEPS_PER_REV;
  while(steps > 0) {
    half_step(stepper_num);
    steps--;  
    delayMicroseconds(wait);
  }
}

// Step until the endstop is pressed and released, and update the stepper position to 0.
// If homing doesn't end after 2 rotations, the endstop is assumed to have failed and the program aborts.
void step_to_home(const byte stepper_num, const unsigned int wait) {  
  unsigned int total_steps = 0;
  byte endstop_repeats = 0;
  
  // Step until hall sensors all read low.
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == LOW ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);    
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    } 
    delayMicroseconds(wait);
  }
  endstop_repeats = 0;
  
  // Step until all hall sensors read high.
  while(endstop_repeats < ENDSTOP_DEBOUNCE_READS) {
    endstop_repeats = digitalRead(endstop_pins[stepper_num]) == HIGH ? endstop_repeats + 1 : 0;
    
    half_step(stepper_num);
    total_steps++;
    if(total_steps > STEPS_PER_REV * 2u) {
      disable_stepper(stepper_num);
      e_stop();
    }
    delayMicroseconds(wait);
  }

  stepper_pos[stepper_num] = 0; 
}

// Step to the specified position. If the current position is greater than the target position, this
// function will re-home and then step to the target position.
void step_to_position(const byte stepper_num, unsigned int target_pos, const unsigned int wait) {
  if (target_pos == stepper_pos[stepper_num]) {
    return;
  }

  // Limit target position to between 0 and STEPS_PER_REV-1
  target_pos %= STEPS_PER_REV;
  
  if (target_pos < stepper_pos[stepper_num]) {
    step_to_home(stepper_num, wait);
    step_num(stepper_num, target_pos, wait);
  } else {
    step_num(stepper_num, target_pos - stepper_pos[stepper_num], wait);
  }
}

// Steps to the specified digit on the display
// To save power, stepper is powered off after running, so exact positional accuracy is not maintained.
// This is allowable since the displays self-calibrate via the endstops every rotation.
void step_to_digit(const byte stepper_num, const byte digit, const unsigned int wait) {
  // The ones display has 10 flaps, tens has 6 and hours have 12.

  if (stepper_num == 2){
    const byte num_flaps = 10;
    const byte num_digits = 10;
    const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
    step_to_position(stepper_num, target_pos, wait);
    disable_stepper(stepper_num);
  }
  else if (stepper_num == 1){
    const byte num_flaps = 6;
    const byte num_digits = 6;
    const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
    step_to_position(stepper_num, target_pos, wait);
    disable_stepper(stepper_num);
  }
  else if(stepper_num == 0){
    const byte num_flaps = 12;
    const byte num_digits = 12;
    const unsigned int target_pos = starting_offset[stepper_num] + (unsigned int)((num_digits + digit - starting_digits[stepper_num]) % num_digits) * STEPS_PER_REV/num_flaps;
    step_to_position(stepper_num, target_pos, wait);
    disable_stepper(stepper_num);
  }
}

// EU daylight saving hours adjustment.
bool DST_EU(int year, int month, int day, int hour)
      {
      // There is no DST in Jan, Feb, Nov, Dec
      if(month < 3 || month > 10)
      { 
      return false;
      }
      //There is always DST in Apr, May, Jun, Jul, Aug, Sep
      if(month > 3 && month < 10)
      { 
      return true;
      }
      //Determin if its summertime accoridng to the European normation
      if(month == 3&&(hour+24*day)>=(1+timeZone+24*(31-(5*year/4+4)%7)) || month==10&&(hour+24*day)<(1+timeZone+24*(31-(5*year/4+1)%7)))
      { 
      return true;
      }
      
      else
      {
      return false;
      }
      }

void setup () {
  // Setting stepper pins to output
  for (byte i = 0; i < 3; i++) {
    for (byte j = 0; j < 4; j++) {
      pinMode(stepper_pins[i][j], OUTPUT);
    }
  }
  
  // Set Hall sensor pins as input with pullups enabled, pulls the signal high so that when triggered it pulses low.
  pinMode(endstop_pins[0], INPUT_PULLUP);
  pinMode(endstop_pins[1], INPUT_PULLUP);
  pinMode(endstop_pins[2], INPUT_PULLUP);


  if (!rtc.begin()) {
    e_stop();
  }

  if (rtc.lostPower()) {
    // Set the time to the compile time + offset
    DateTime compile_time = DateTime(F(__DATE__), F(__TIME__)) + TimeSpan(UPLOAD_OFFSET);

    #if defined(USE_DST_EU)
    
      if(DST_EU(compile_time.year(), compile_time.month(), compile_time.day(), compile_time.hour()))
      {
      compile_time = compile_time - TimeSpan(0,1,0,0);  
      }

    #endif
    
    rtc.adjust(compile_time);
  }
  
  unsigned int total_steps = 0;
  if(endstop_pins[1] == endstop_pins[2]) {
    while(digitalRead(endstop_pins[1]) == LOW) {
      step_num(1, 200, STEPPER_DELAY);
      disable_stepper(1);

      // Still pressed, try the other display
      if(digitalRead(endstop_pins[1]) == LOW) {
        step_num(2, 200, STEPPER_DELAY);  
        disable_stepper(2);
      }

      // Similar to homing, if a max number of steps is reached, the endstop is assumed to have failed and the program aborts.
      total_steps += 200;
      if(total_steps > STEPS_PER_REV) {
        e_stop();
      }
    }
  }

  // Step to home digit
  step_to_home(2, STEPPER_DELAY);
  step_to_digit(2, starting_digits[2], STEPPER_DELAY);
  step_to_home(1, STEPPER_DELAY);
  step_to_digit(1, starting_digits[1], STEPPER_DELAY);
  step_to_home(0, STEPPER_DELAY);
  step_to_digit(0, starting_digits[0], STEPPER_DELAY);
}

void loop () {
  
  DateTime now = rtc.now();
  byte hr = now.hour() % 12;
  byte tens = now.minute() / 10;
  byte ones = now.minute() % 10;
  
      #if defined(USE_DST_EU)

      //DST for EU
      if(DST_EU(now.year(), now.month(), now.day(), now.hour()))
      {
      hr = now.hour() + 1;  
      }

    #endif

  
  
  step_to_digit(2, ones, STEPPER_DELAY);
  step_to_digit(1, tens, STEPPER_DELAY);
  step_to_digit(0, hr, STEPPER_DELAY);

  delay(POLLING_DELAY);
}

1 Like