Dual position watch winder with independent steppers

Hello everyone, this is my first post for what I want to be my second Arduino project, an evolution of one already completed.

What I want to accomplish:
A 2 positions (possibly extendable to 4 positions) watch winder using 1 Arduino Nano board and 2 (4) 28byj-48 + ULN2003 steppers that will run simultaneously and that can be programmed independently to run a certain number of turns per day (TPD), either 650 or 800, in a given direction (only CW, or CW/CCW cycle) using sliding switches whit quick response.
Each stepper will complete a series of cycles, for example:
2 turns CCW - small pause - 2 turns CW - repeat until 22 total turns - 10 min pause - repeat for 1 the cycle for 1 hour - do nothing for 3 hours.
A blinking led (1 for each position) will signal the phase, depending on the blinking speed.

What I've accomplished so far:
I was able to modify an existing code (see code below) to have 1 stepper driven with 2 possible programs, 650 TPD with CW/CCW cycle or 800 TPD only CW, that are the 2 most common programs for windings.
I've created 2 custom printed winders, one with 1 position and one with 2 positions where the second one is actually driven via gears. (see videos below)

Guidance needed:
I understand I have to switch to "non blocking" code execution. I'm studying ( Demonstration code for several things at the same time, but I need to some advice:

  • Will the current approach with "timed execution" long pauses etc still work with non blocking approach?
  • I see there are many libraries for running 2 or more steppers at same time (i.e. AccelStepper with constant speed, Unistep2), which is the best for what I want to accomplish?
  • Can you hint me with pseudocode to pursue the goal I've in mind?
  • Do you advice for some completely different approach to this problem?

my code:

//**************************************************************
//  Watch_Winder
//      rotate the stepper motor in a 3D-printed watch-winder.
//
//      This watch-winder contains a 4-wire unipolar stepper motor
//
//  Code rewritten by Jeby starting from the code by Allan Schwartz and Michael Diamond
//
//  Code has been rewritten to incorporate these main changes:
//  - Different Arduino to ULN2003 pin connections
//  - Speed in RPM for real: set stepsPerRevolution to 2038 and not 100
//  - Number of turns per day reduced from roughly 2400 to roughly 660 both directions
//   or 816 clockwise per day, to meet watchmaker spec.
//    To know the number of Turns Per Day (TBD) you can visit this two website
//  - Added the possibility to select the modes for the rotations through a switch
//  - Added a lot of serial output and debug counter
//
//  TO BE DONE:
//  - Non blocking code and multiple stepper.
//  - Change approach to get exactly 650 TPD both directions and 800 TPD clockwise
//  - Select CW, CCW or CW/CCW
//  - Extend to add 900 TPD
//
//**************************************************************

// define variables
int cclock = 1; // definition of a variable needed to reverse the direction of the rotation
int switchState = 0; // variable to define status of the switcher to choose 800 TPD or 650 TPD
int hour_rep = 0; // variable to count how many time in 1 hour the cycles of rotations are done
int Steps2Take = 0; // variable to set the number of steps the stepper will do
int TurnsTaken = 0; // variable to count the number of turns performed in every cycle and in 1 hour
int TotalTurns = 0; // variabel to count all the turns accumulated since the start

// timing constants
typedef unsigned long clock_t;  
const clock_t SECOND = 1000L;           // 1 second = 1000 ms  
const clock_t MINUTE = 60L * SECOND;    // 1 minute = 60000 ms  
const clock_t HOUR = 60L * MINUTE;      // 1 hour = 3600000 ms

#include <Stepper.h>        // include the Arduino Stepper library
const int stepsPerRevolution = 100; // check stepper datasheet and change accordingly

// create one instance of this stepper class for the ULN2003 stepper
// motor, whose wires 1 2 3 4 are connected to Arduino digital pins 11 10 9 8
Stepper stepper(stepsPerRevolution, 11, 9, 10, 8);    // normally rotate clockwise 

//************************************************************
// For the 28BYJ-48 stepper motor:
// 64 steps per revolution, 4 phases, 5.625 ° angle according to motor specifications
// 1:64 reduction for this mechanically reduced engine
//   360° / 5.625° * 64 = 4096 angles with the reduction
//   360° / 5.625° * 64  * 4 coils / 2 bipolar = 2048 step / turn
// Actually the more accurate value for reduction ratio is 1:63.68395
// this means that in full step mode 1 complete turn is made in 2038 steps
//************************************************************

// define function to perform rotations. 
void rotateStepper(int Steps2Take) {  
  // One complete rotation with 2038 steps 
  clock_t startT = millis();
  stepper.step(Steps2Take);  //it turns CW
  //Stopwatch this rotatation -- expect 6.236 sec per revolution at speed 200
  blinkLed(100);          // Flash LED briefly, just for 0.1 second
  //displays the time (in ms) for this rotation
  Serial.println("rotation took " + String(millis() - startT) + "ms");
}

void enableStepper() {  
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  digitalWrite(5, HIGH);        // LED 5 on means the stepper is enabled
}

void disableStepper() {  
  // this reduces current to the drive
  pinMode(8, INPUT); 
  pinMode(9, INPUT);
  pinMode(10, INPUT);
  pinMode(11, INPUT);
  digitalWrite(5, LOW);         // LED 5 off means the stepper is disabled
}

void blinkLed(clock_t ms) {  
  digitalWrite(5, LOW);
  delay(ms);  
  digitalWrite(5, HIGH);
  delay(ms);
}

void setup()  
{
  Serial.begin(9600);
  Serial.println( __FILE__ );
  Serial.println("Watchwinder restart");
  pinMode(13, OUTPUT);    //Declare Pin 13 or LED "L" on the board for debug feedback
  pinMode(5, OUTPUT);     // Declare Pin 5 as output for the LED
  pinMode(4, OUTPUT);     // Used to send power to switch, can be avoided just sending 5V to switch through the 5V output pin
  pinMode(7, INPUT);      // Used to get the status on the switch to choose between 800 CW or 650 BD
}

void loop()  
{
  // We do the main rotational activity for one hour and then rest for 3 hours.
  // To have 660 TPD we neeed a total of 5 executions of a cycle with 22 turns in 1 hour
  // and then rest for 3 hours. This means 110 turns in 4 hours => 660 in 24 hours.
  // To have 5 executions in 1 hour the single cycle shall take 12 minutes.
  // To be sure the cycles will be executed only 5 times and not 6, the single cycle will have
  // a duration of a little bit more than 12 minutes, so at the end of the 4th cycle the 
  // elapsed time will be slighly more than 48 minutes or so and for sure at the end
  // of the 5th cycle the elapsed time will be more than 60 minutes and the
  //  "for < 1 hour" loop will end.
  // In case of 816 TPD we are going to get 34 turns 4 times in 1 hour = 136 turns
  // then rest for 3 hours. This get us 136 turns in 4 hours => 816 turns in 24 hours
  digitalWrite(4,HIGH); // power to switcher to select 800 or 650 TPD
  
  // Start 1 hour loop of turns and pauses
  for (clock_t t = millis(); (clock_t)(millis() - t) < HOUR; ) {
    

    Serial.println("Stepper running ");
    stepper.setSpeed(191);      // depends on "const int stepsPerRevolution = 100"

    enableStepper();

    switchState = digitalRead(7); // read switch status

    if (switchState== LOW) {
    //****************************************//
    //       CASE 660 TPD both directions     //
    //****************************************//
        
    // we do a sequence of clockwise / counter-clockwise rotations
    // since we need 22 turns we are doing 11 cycles with 2 turns each
    // At every cycle we switch direction using cclock*-1 to get an
    // even distribution

      Serial.println("Mode: 660 TPD both directions");
      digitalWrite(13,LOW); // suggestion: use the "L" led only for initial debug because in any way it will not be visible once the board is in the case
      for (int count2=0; count2<11; count2++){
        cclock = cclock * -1; // change direction of rotation at every cycle: first cycle cclock = 1*-1 = -1; second cycle cclock = -1*-1 = +1 etc..
            
        if (cclock == 1){
          Serial.println("Sense of Rotation: clockwise");
        }
        else{
          Serial.println("Sense of Rotation: counter clockwise");
        }
 
        Steps2Take = cclock*2*2038;   // complete 2 rotation with 2048 steps (1 turn around 6.4sec).
        rotateStepper(Steps2Take); // on top of the 6.4 sec for the rotation we need to add 0.1 sec for the blinkLed called in the function
              
        delay(4*SECOND);       // pause 16 seconds
        TurnsTaken = TurnsTaken + abs(Steps2Take/2038); // counting TurnsTaken in every cycle
        // total duration of the cycle is 11*2*6.5+11*4 = 187 seconds = 3 minutes and 7 seconds
      }   
    }
    else {
    //****************************************//
    //    CASE 880 TPD clockwise directions   //
    //****************************************//
      
    // we do a sequence of clockwise rotations
    // since we need 34 turns we are doing 17 cycles with 2 turns each
          
      for (int counter2 = 0; counter2 < 17; counter2++){
        Serial.println("Mode: 880 TPD clockwise");
        digitalWrite(13,HIGH); // suggestion: use the "L" led only for initial debug because in any way it will not be visible once the board is in the case
    
        Steps2Take = 2*2038;   // complete 2 rotation with 2048 steps (1 turn around 6.4 sec).
        rotateStepper(Steps2Take); // on top of the 6.4 sec for the rotation we need to add 0.1 sec for the blinkLed called in the function
            
        delay(8.5*SECOND);       // pause 8.5 seconds
                
        TurnsTaken = TurnsTaken + abs(Steps2Take/2038);  // counting TurnsTaken in every cycle
        //total duration of the single cycle is (17*2*6.5)+17*9 = 365 seconds = 6 minutes and 5.5 seconds
      }
    }

   disableStepper();
   Serial.println("Stepper disabled. ");
    
   // then, pause for 9 minutes:
   for (clock_t t0 = millis(); (clock_t)(millis() - t0) < 9*MINUTE; ) {
    // blink the LED at a 0.5 Hz rate
    blinkLed(0.5*SECOND);
   }
    
   hour_rep = hour_rep + 1; // counting how many repetitions of cycles are done in 1 hour
   Serial.println("Turns accumulated in iteration number " + String(hour_rep) + ": " + String(TurnsTaken));
    
   // - in case of 660 TPD we get a total of 12 min and 7 seconds -> 5 repetitions in 1 hour
    //  - in case of 816 TPD we get a total of 15 min and 5.5 seconds -> 4 repetitions in 1 hour
  }
  
  TotalTurns = TotalTurns + TurnsTaken; //counting total turns accumulated
  
  hour_rep = 0; // reset number of repetitions in 1 hour
  TurnsTaken=0; // reset turns accumulated in 1 hour
  
  Serial.println("Turns accumulated so far: " + String(TotalTurns));
  
  // now rest for 3 hours changing position of the watch every 15 minutes to improve
  // position accuracy
  for (int counter3 = 0; counter3 < 12; counter3++){
    for (clock_t t0 = millis(); (clock_t)(millis() - t0) < 15*MINUTE; ) {
      // blink the LED at a 2 Hz rate for 15 minutes
      blinkLed(2*SECOND);
    }
    enableStepper();
    Serial.println("Stepper enabled. ");
    stepper.setSpeed(300);
    Steps2Take = 0.25*2038;
    rotateStepper(Steps2Take);
    disableStepper();
    Serial.println("Stepper disabled. ");
  }
}

here are the videos of the 2 watch winder I made so far

Yes. The Arduino can easily count up to 4.2 billion of whatever interval you want.

It doesn't matter since you are only doing one step at a time and the various motors are not synchronized, except possibly once an hour.

Put the data for each winder in a 'struct'.

void loop()
{
  unsigned long currentMillis = millis();

  // Tell each winder what time it is in the current cycle.
  for (int i=0; i< WinderCount; i++)
    Winders[i].update(currentMillis - LastCycleStart);

  // Every four hours, tell the Winder to start a new 4-hour cycle
  if (millis() - LastCycleStart >= FourHoursInMilliseconds)
  {
     LastCycleStart += FourHourInMilliseconds;
     for (int i=0; i< WinderCount; i++)
       Winders[i].restart();
  }
}
1 Like

Given the power of a programmable winder you could have a single-shot 300 TPD test cycle (50 turns total) for determining TPD:

  1. Let the watch wind down completely.
  2. Set the watch for midnight.
  3. Run a single CW test cycle.
  4. Note the time indicated when the watch has again run down.
  5. Repeat 1-4 with a CCW cycle.
  6. If one direction didn't start the watch at all, use the other direction. Otherwise use a bi-directional program and average the two run times.
  7. Divide 24 hours by the run time. Multiply that by 300 and round up to get the TPD that will keep the watch wound.

Thanks, this is also interesting. Manaufacturers are actually issuing the recommended TPD for each caliber, but what I've experimented is that the value is fine if the watch is in "vertical" position, while at different angles the results are changing due to the fact the the resulting force is not enough to generate the torque needed to make the rotor spin. One may want to have the watch to rest at 30°-45° in order to have less effect on speed of the ticking, but then the TPDs must be increased.

I'll try to use Christmas holydays to have some code done and then come back here :slight_smile: thanks for the help so far!