Model Railway Turntable - in a spin so much Im dizzy

A minor extra thought…

If the turntable isn’t aligned to a specific input track, , maybe cutting ‘forward’ * power to the last six inches of that track might avoid trains running on to a non position on the turntable - until that TT position is confirmed as available.

  • a diode would still allow the train to be reversed off the lead-in track, and or operate as an output route.
1 Like

Some programming hints.

1 Like

Hello Larry.

So in relation to:

I believe i have resolved this one with the code below.

//================================================^================================================
//
//  https://forum.arduino.cc/t/model-railway-turntable-in-a-spin-so-much-im-dizzy/1263988
//
//  HLD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       24/05/25    Started writing sketch
//  1.10       24/05/25    Added code to read the Rotary Encoder
//  1.20       24/05/25    Added Stepping Motor code
//  1.30       24/05/27    Added the State Machine named "resetMachine"
//  1.40       24/05/27    Added Homing after zeroing the motor
//  1.50       24/05/27    Added LED effects when Zeroing and Homing
//  1.60       24/05/27    When the motor stops stepping, turn off motor coil currents
//  1.70       24/05/27    Added LED action when calibrating the Motor position
//  1.80       24/05/28    Added added debug macros, added some sketch comments
//

//#include <Wire.h>


//                                           M A C R O S
//================================================^================================================
#define LEDon HIGH  //PIN---[220R]---A[LED]K---GND
#define LEDoff LOW

#define PRESSED LOW  //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED HIGH

#define CLOSED LOW  //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED HIGH

#define OPTOclosed HIGH  //use with "HIGH when closed" opto devices
#define OPTOopened LOW   //use with "LOW when open" opto devices

//========================================================================  <-------<<<<<<<
//these macros are used for debug purposes
//
#define DEBUG  //comment this line to stop printing to the Serial Monitor,
//                            less memory used, speeds execution. i.e. all Serial.print... lines are deleted
//debug Macros
#ifdef DEBUG
#define SERIALBEGIN(...) Serial.begin(__VA_ARGS__)

#define DPRINT(...) Serial.print(__VA_ARGS__)
#define DPRINTLN(...) Serial.println(__VA_ARGS__)

#define DPRINTF(...) Serial.print(F(__VA_ARGS__))      //text only, using the F macro
#define DPRINTLNF(...) Serial.println(F(__VA_ARGS__))  //text only, using the F macro plus new line

#else
#define SERIALBEGIN(...)

#define DPRINT(...)
#define DPRINTLN(...)

#define DPRINTF(...)
#define DPRINTLNF(...)
#endif


//                                            G P I O s
//================================================^================================================
//
//a structure to define input objects, switches or sensors
struct makeInput {
  const byte pin;            //the digital input pin number
  unsigned long switchTime;  //the time the switch was closed
  byte lastState;            //the state the input was last in
  byte counter;              //a counter used to validate a switch change in state
};                           //END of   struct makeInput

const byte inputPins[] = { 4, 14, 15, 16, 17 };

//Digital Inputs
//define the input connected to a PB switch

//========================
makeInput EncoderSw = {
  4, 0, OPENED, 0  //pin, switchTime, lastState, counter
};

//========================
makeInput DeleteSw = {
  14, 0, OPENED, 0  //pin, switchTime, lastState, counter
};

//========================
makeInput SetPosSw = {
  15, 0, OPENED, 0  //pin, switchTime, lastState, counter
};

//========================
makeInput CCW_Sw = {
  16, 0, OPENED, 0  //pin, switchTime, lastState, counter
};

//========================
makeInput CW_Sw = {
  17, 0, OPENED, 0  //pin, switchTime, lastState, counter
};

//========================
makeInput OptoSensor = {
  18, 0, OPTOopened, 0  //pin, switchTime, lastState, counter
};


byte filter = 10;
//TIMER "switches" runs every 5ms.
//5ms * 10 = 50ms is needed to validate a switch change in state.
//A switch change in state is valid "only after" 10 identical changes are detected.
//This is used to filter out EMI noise in the system


const byte EncoderOutA = 3;  //there is a pull-up resistor inside the rotary encoder
const byte EncoderOutB = 2;  //there is a pull-up resistor inside the rotary encoder


//OUTPUTS
//===================================
const byte outputPins[] = { 13, 12, 11, 10, 9, 8, 7, 6, 5 };

const byte heartbeatLED = 13;
const byte cwLED = 12;
const byte ccwLED = 11;
const byte deleteLED = 10;
const byte setPosLED = 9;

//stepping motor
const byte IN4 = 8;
const byte IN3 = 7;
const byte IN2 = 6;
const byte IN1 = 5;


//                                        V A R I A B L E S
//================================================^================================================
//
//                                         "Setpping Motor"                         2 8 B Y J - 4 8
//a 5-wire unipolar stepper motor that runs on 5V
//
//Stepping Motor stepping pattern
//index                                0       1       2       3       4       5       6       7       8
int lookup[] = { B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001, B00000 };
//index 8 in the array is B00000
//we use this step pattern to turn off the motor current when the motor is stopped

//Stepper Motor inputs
//                  IN1, Bit 0 _____
//                  IN2, Bit 1 ____ |
//                  IN3, Bit 2 ___ ||
//                  IN4, Bit 3 __ |||
//                               ||||
//ex. step pattern             B01000
//IN1 is bit 0
//IN2 is bit 1
//IN3 is bit 2
//IN4 is bit 3

//where we are pointing to in the above array
int stepIndex = 0;   //where we are in the Look-Up table
int savedStepIndex;  //a copy of stepIndex when powering off the motor

byte homeFlashCounter;  //used in flshing the LEDs at the HOME position
int EncoderCounter;
int homingCounter = 0;  //number of steps away from the motor zero position

long currentMotorPosition = 0;  //number of steps away from the HOME position
const int homeSteps = 100;      //number of CW steps from the motor zero position

const unsigned long motorSpeed = 2400ul;  //larger the number, slower the the motor movement

const unsigned long CW_CCW_Interval = 5000ul;  //5 seconds to verify if CW and CCW still being held


//================================================^================================================
//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//TIMER objects are non-blocking
struct makeTIMER {
#define MILLIS 1
#define MICROS 1000  //we can use this value to divide into a variable to get milliseconds

#define ENABLED true
#define DISABLED false

#define YES true
#define NO false

#define STILLtiming 0
#define EXPIRED 1
#define TIMERdisabled 2

  //these are the bare minimum "members" needed when defining a TIMER
  int TimerType;           //what kind of TIMER is this? MILLIS/MICROS
  unsigned long Time;      //when the TIMER started
  unsigned long Interval;  //delay time which we are looking for
  bool TimerFlag;          //is the TIMER enabled ? ENABLED/DISABLED
  bool Restart;            //do we restart this TIMER   ? YES/NO

  //================================================
  //condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
  //function to check the state of our TIMER  ex: if(myTimer.checkTIMER() == EXPIRED);
  byte checkTIMER() {
    //========================
    //is this TIMER enabled ?
    if (TimerFlag == ENABLED) {
      //========================
      //has this TIMER expired ?
      if (getTime() - Time >= Interval) {
        //========================
        //should this TIMER restart again?
        if (Restart == YES) {
          //restart this TIMER
          Time = getTime();
        }

        //this TIMER has expired
        return EXPIRED;
      }

      //========================
      else {
        //this TIMER has not expired
        return STILLtiming;
      }

    }  //END of   if (TimerFlag == ENABLED)

    //========================
    else {
      //this TIMER is disabled
      return TIMERdisabled;
    }

  }  //END of   checkTime()

  //================================================
  //function to enable and restart this TIMER  ex: myTimer.enableRestartTIMER();
  void enableRestartTIMER() {
    TimerFlag = ENABLED;

    //restart this TIMER
    Time = getTime();

  }  //END of   enableRestartTIMER()

  //================================================
  //function to disable this TIMER  ex: myTimer.disableTIMER();
  void disableTIMER() {
    TimerFlag = DISABLED;

  }  //END of    disableTIMER()

  //================================================
  //function to restart this TIMER  ex: myTimer.restartTIMER();
  void restartTIMER() {
    Time = getTime();

  }  //END of    restartTIMER()

  //================================================
  //function to force this TIMER to expire ex: myTimer.expireTimer();
  void expireTimer() {
    //force this TIMER to expire
    Time = getTime() - Interval;

  }  //END of   expireTimer()

  //================================================
  //function to set the Interval for this TIMER ex: myTimer.setInterval(100);
  void setInterval(unsigned long value) {
    //set the Interval
    Interval = value;

  }  //END of   setInterval()

  //================================================
  //function to return the current time
  unsigned long getTime() {
    //return the time             i.e. millis() or micros()
    //========================
    if (TimerType == MILLIS) {
      return millis();
    }

    //========================
    else {
      return micros();
    }

  }  //END of   getTime()

};  //END of   struct makeTIMER


//                             D e f i n e   a l l   t h e   T I M E R S
//================================================^================================================
/*example
  //========================
  makeTIMER toggleLED =
  {
     MILLIS/MICROS, 0, 500ul, ENABLED/DISABLED, YES/NO  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
  };

  Functions we can access:
  toggleLED.checkTIMER();
  toggleLED.enableRestartTIMER();
  toggleLED.disableTIMER();
  toggleLED.expireTimer();
  toggleLED.setInterval(100ul);
*/

//========================
makeTIMER heartbeatTIMER = {
  MILLIS, 0, 500ul, ENABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER switchesTIMER = {
  MILLIS, 0, 5ul, ENABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER encoderTIMER = {
  MILLIS, 0, 2ul, ENABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER machineTIMER = {
  MILLIS, 0, 5ul, ENABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER resetCommonTIMER = {
  MILLIS, 0, 1000ul, DISABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER stepperTIMER = {
  MICROS, 0, motorSpeed, DISABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER deleteLedTIMER = {
  MILLIS, 0, 100ul, DISABLED, YES  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};


//                                     S t a t e   M a c h i n e
//================================================^================================================
//the States in our machine (use names that mean something to you)
enum STATES : byte  //save RAM, use bytes
{
  ResetStartup,     //do power up stuff
  Check_CW_CCW_Sw,  //checking to see if CW and CCW are closed
  CW_CCW_Timing,    //wait to see if the switches have been closed long enough
  ResetMotor,       //wait here while motor is going CCW and looking for Opto Sensor
  MovingHome,       //set up things to home the motor
  HomingWait,       //wait here while the motor is homing, waiting for the Opto Sensor to close
  ResetFinished     //flash LEDs, reset the "Reset State Machine"
};
STATES resetMachine = ResetStartup;


//                                           s e t u p ( )
//================================================^================================================
void setup() {
  //SERIALBEGIN(115200);
  Serial.begin(115200);

  //some time to get serial stuff working
  //delay(1000);

  //======================
  //use INPUT_PULLUP so the pin does not float, floating pins can cause faulty readings
  //configure the input pins
  for (byte x = 0; x < sizeof(inputPins); x++) {
    pinMode(inputPins[x], INPUT_PULLUP);
  }

  //======================
  //configure the output pins
  for (byte x = 0; x < sizeof(outputPins); x++) {
    digitalWrite(outputPins[x], LOW);
    pinMode(outputPins[x], OUTPUT);
  }

  DPRINTLNF("Power Up");

}  //END of   setup()


//                                            l o o p ( )
//================================================^================================================
void loop() {
  //========================================================================  T I M E R  heartbeat
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED) {
    //when this TIMER expires, toggle the heartbeat LED
    //this LED is a rudimentary way of displaying if the sketch code is blocking or stuttering

    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED) {
    //when this TIMER expires, check if any switch/sensor has changed state

    checkSwitches();
  }

  //========================================================================  T I M E R  encoder
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to read the Rotary Encoder ?
  if (encoderTIMER.checkTIMER() == EXPIRED) {
    //when this TIMER expires, check the rotary encode to see if it has been turned

    readRotaryEncoder();
  }

  //========================================================================  T I M E R  machine
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to service our State Machines ?
  if (machineTIMER.checkTIMER() == EXPIRED) {
    //when this TIMER expires, service any/all the State Machines

    //check all the "State Machines"
    checkMachines();
  }

  //========================================================================  T I M E R  stepper
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to move the Stepping Motor ?
  if (stepperTIMER.checkTIMER() == EXPIRED) {
    //when this TIMER expires, step the motor
    //there are 4 exclusionary events that move the stepper motor
    //i.e. only one event can be operated on at a time
    // 1. HomingWait:        Motor going HOME
    // 2. ResetMotor:        Motor going to ZERO
    // 3. CW switch closed:  Motor going CW
    // 4. CCW switch closed: Motor going CCW

    //========================                       H o m i n g W a i t
    //are we waiting for the motor to home ?
    if (resetMachine == HomingWait) {
      //get ready for the next step
      stepIndex++;

      //should we go back to the beginning index ?
      if (stepIndex > 7) {
        stepIndex = 0;
      }

      //move the motor
      stepMotor(stepIndex);

      //we have gone 1 more CW step
      homingCounter++;
      //DPRINTLN(homingCounter);

      //========================
      //are we finished homing ?
      if (homingCounter >= homeSteps) {
        //motor is now sitting at HOME
        currentMotorPosition = 0;


        //setting up toggling of the LEDs every 500ms
        homeFlashCounter = 0;
        //LED toggle duration is 500ms
        resetCommonTIMER.setInterval(500ul);
        //restart the common TIMER
        resetCommonTIMER.enableRestartTIMER();

        //all LEDs OFF
        digitalWrite(cwLED, LEDoff);
        digitalWrite(ccwLED, LEDoff);
        digitalWrite(deleteLED, LEDoff);
        digitalWrite(setPosLED, LEDoff);

        //we are at HOME, reset the "Reset State Machine"
        resetMachine = ResetFinished;

        //save the current stepIndex
        savedStepIndex = stepIndex;
        //to save power, turn off stepper coils
        stepIndex = 8;
        stepMotor(stepIndex);

        //we are done, disable this TIMER
        stepperTIMER.disableTIMER();
      }
    }

    //========================                       R e s e t M o t o r
    //are we resetting the motor position to zero ?
    else if (resetMachine == ResetMotor) {
      //get ready for the next step
      stepIndex--;

      //should we go back to the ending index ?
      if (stepIndex < 0) {
        stepIndex = 7;
      }

      //move the motor
      stepMotor(stepIndex);

      //========================
      //has the OptoSensor gone CLOSED ?
      if (OptoSensor.lastState == OPTOclosed) {
        //we are at position ZERO
        currentMotorPosition = 0;

        //it is now time to HOME the motor
        //next state in the "Reset State Machine"
        resetMachine = MovingHome;

        //we are done, disable this TIMER
        stepperTIMER.disableTIMER();
      }
    }

    //========================                       C W   s w i t c h   c l o s e d
    //when the CW switch is closed AND the CCW switch is OPENED
    else if (CW_Sw.lastState == CLOSED && CCW_Sw.lastState == OPENED) {
      //get ready for the next step
      stepIndex++;

      //should we go back to the beginning index ?
      if (stepIndex > 7) {
        stepIndex = 0;
      }

      //move the motor
      stepMotor(stepIndex);

      //advance the Motor Position
      currentMotorPosition++;
      DPRINTLN(currentMotorPosition);
    }

    //========================                       C C W   s w i t c h   c l o s e d
    //when the CCW switch is closed AND the CW switch is OPENED
    else if (CCW_Sw.lastState == CLOSED && CW_Sw.lastState == OPENED) {
      //get ready for the next step
      stepIndex--;

      //should we go back to the ending index ?
      if (stepIndex < 0) {
        stepIndex = 7;
      }

      //move the motor
      stepMotor(stepIndex);

      //reduce the Motor Position
      currentMotorPosition--;
      DPRINTLN("Distance from Home Position = ");
      DPRINTLN(currentMotorPosition);
    }
  }

  //========================================================================  T I M E R  deleteLed
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if the TIMER is enabled, is it time to toggle the Delete LED ?
  if (deleteLedTIMER.checkTIMER() == EXPIRED) {
    //toggle the Delete LED
    digitalWrite(deleteLED, digitalRead(deleteLED) == HIGH ? LOW : HIGH);
  }

  //================================================
  //other non blocking code goes here
  //================================================

}  //END of   loop()


//                                   c h e c k M a c h i n e s ( )
//================================================^================================================
void checkMachines() {
  //========================================================================  "Reset State Machine"
  switch (resetMachine) {
    //========================          R e s e t S t a r t u p
    case ResetStartup:
      {
        //do startup stuff here

        //next state in the "Reset State Machine"
        resetMachine = Check_CW_CCW_Sw;
      }
      break;

    //================================================ <-------<<<<<<<
    //When the mortor is stopped,
    //this State Machine will sit
    //in this state
    //========================          C h e c k _ C W _ C C W _ S w
    case Check_CW_CCW_Sw:
      {
        //are both CW and CCW switches CLOSED ?
        if (digitalRead(CW_Sw.pin) == CLOSED && digitalRead(CCW_Sw.pin) == CLOSED) {
          DPRINTLNF("Both switches are closed");

          //disable any motor movement
          stepperTIMER.disableTIMER();

          resetCommonTIMER.setInterval(CW_CCW_Interval);
          resetCommonTIMER.enableRestartTIMER();

          //next state in the "Reset State Machine"
          resetMachine = CW_CCW_Timing;
        }
      }
      break;

    //========================          C W _ C C W _ T i m i n g
    case CW_CCW_Timing:
      {
        //has a switch been released ?
        if (digitalRead(CW_Sw.pin) == OPENED || digitalRead(CCW_Sw.pin) == OPENED) {
          digitalWrite(cwLED, LEDoff);
          digitalWrite(ccwLED, LEDoff);

          //back to checking for the 2 switches CLOSED
          //next state in the "Reset State Machine"
          resetMachine = Check_CW_CCW_Sw;

          break;
        }

        //has the TIMER expired ?
        if (resetCommonTIMER.checkTIMER() == EXPIRED) {
          //during motor zeroing, turn ON the LEDs
          digitalWrite(cwLED, LEDon);
          digitalWrite(ccwLED, LEDon);
          digitalWrite(deleteLED, LEDon);
          digitalWrite(setPosLED, LEDon);

          //restore the saved stepIndex
          stepIndex = savedStepIndex;
          stepMotor(stepIndex);

          //allow the motor to move
          stepperTIMER.enableRestartTIMER();

          //next state in the "Reset State Machine"
          resetMachine = ResetMotor;
        }
      }
      break;

    //========================          R e s e t M o t o r
    case ResetMotor:
      {
        //wait here until the motor is at the zero position
      }
      break;

    //========================          M o v i n g H o m e
    case MovingHome:
      {
        DPRINTLNF("Motor is at Zero");
        DPRINTLNF("Motor is now Homing");

        //start out at the first element on our Lookup table
        stepIndex = 0;

        //initialize the number of steps away from the motor zero position
        homingCounter = 0;

        //allow the motor to move
        stepperTIMER.enableRestartTIMER();

        //next state in the "Reset State Machine"
        resetMachine = HomingWait;
      }
      break;


    //========================          H o m i n g W a i t
    case HomingWait:
      {
        //wait here while homing the motor
      }
      break;


    //========================          R e s e t F i n i s h e d
    case ResetFinished:
      {
        //we are at the Motor Home position, toggle the LEDs
        if (homeFlashCounter < 4) {
          //has the common TIMER expired ?
          if (resetCommonTIMER.checkTIMER() == EXPIRED) {
            //toggle the LEDs
            digitalWrite(cwLED, digitalRead(cwLED) == HIGH ? LOW : HIGH);
            digitalWrite(ccwLED, digitalRead(ccwLED) == HIGH ? LOW : HIGH);
            digitalWrite(deleteLED, digitalRead(deleteLED) == HIGH ? LOW : HIGH);
            digitalWrite(setPosLED, digitalRead(setPosLED) == HIGH ? LOW : HIGH);

            //ready next flash
            homeFlashCounter++;
          }

          break;
        }

        //all LEDs OFF
        digitalWrite(cwLED, LEDoff);
        digitalWrite(ccwLED, LEDoff);
        digitalWrite(deleteLED, LEDoff);
        digitalWrite(setPosLED, LEDoff);

        //we are now finished with motor calibration
        DPRINTLNF("Motor is Home");
        DPRINTLNF("System has Reset");

        //reset this State Machine
        //next state in the "Reset State Machine"
        resetMachine = ResetStartup;
      }
      break;

  }  //END of  switch/case

}  //END of   checkMachines()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
void checkSwitches() {
  byte currentState;

  //========================================================================  EncoderSw.pin
  currentState = digitalRead(EncoderSw.pin);

  //===================================
  //has this switch changed state ?
  if (EncoderSw.lastState != currentState) {
    EncoderSw.counter++;

    //is this change in state stable ?
    if (EncoderSw.counter >= filter) {
      //get ready for the next sequence
      EncoderSw.counter = 0;

      //update to this new state
      EncoderSw.lastState = currentState;

      //========================
      //did this switch close ?
      if (currentState == CLOSED) {
        //reset the Encoder Counter
        EncoderCounter = 0;

        DPRINTF("Encoder Counter = ");
        DPRINTLN(EncoderCounter);

        //the time this switch closed
        EncoderSw.switchTime = millis();
      }

      //========================
      //did this switch open ?
      else if (currentState == OPENED) {
        DPRINTF("The time the switch was closed = ");
        DPRINTLN(millis() - EncoderSw.switchTime);
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else {
    EncoderSw.counter = 0;
  }

  //END of EncoderSw.pin

  //========================================================================  CW_Sw.pin
  //only proceed if the "Reset State Machine" is sitting in the Check_CW_CCW_Sw state
  if (resetMachine == Check_CW_CCW_Sw) {
    currentState = digitalRead(CW_Sw.pin);

    //===================================
    //has this switch changed state ?
    if (CW_Sw.lastState != currentState) {
      CW_Sw.counter++;

      //is this change in state stable ?
      if (CW_Sw.counter >= filter) {
        //get ready for the next sequence
        CW_Sw.counter = 0;

        //update to this new state
        CW_Sw.lastState = currentState;

        //========================
        //did this switch close ?
        if (currentState == CLOSED) {
          DPRINTLNF("Motor going Clockwise");

          //restore the saved stepIndex
          stepIndex = savedStepIndex;
          stepMotor(stepIndex);

          //movement is now allowed and the Motor will move CW
          stepperTIMER.enableRestartTIMER();

          digitalWrite(cwLED, LEDon);
        }

        //========================
        //did this switch open ?
        else if (currentState == OPENED) {
          DPRINTLNF("Motor Stopped");

          //save the current stepIndex
          savedStepIndex = stepIndex;

          //to save power, turn off stepper coils
          stepIndex = 8;
          stepMotor(stepIndex);

          //the TIMER is now disabled and the Motor will stop
          stepperTIMER.disableTIMER();

          digitalWrite(cwLED, LEDoff);
        }
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else {
    CW_Sw.counter = 0;
  }

  //END of CW_Sw.pin

  //========================================================================  CCW_Sw.pin
  //only proceed if the "Reset State Machine" is sitting in the Check_CW_CCW_Sw state
  if (resetMachine == Check_CW_CCW_Sw) {
    currentState = digitalRead(CCW_Sw.pin);

    //===================================
    //has this switch changed state ?
    if (CCW_Sw.lastState != currentState) {
      CCW_Sw.counter++;

      //is this change in state stable ?
      if (CCW_Sw.counter >= filter) {
        //get ready for the next sequence
        CCW_Sw.counter = 0;

        //update to this new state
        CCW_Sw.lastState = currentState;

        //========================
        //did this switch close ?
        if (currentState == CLOSED) {
          DPRINTLNF("Motor going Counter Clockwise");

          //restore the saved stepIndex
          stepIndex = savedStepIndex;
          stepMotor(stepIndex);

          //movement is now allowed and the Motor will move CCW
          stepperTIMER.enableRestartTIMER();

          digitalWrite(ccwLED, LEDon);
        }

        //========================
        //did this switch open ?
        else if (currentState == OPENED) {
          DPRINTLNF("Motor Stopped");

          //save the current stepIndex
          savedStepIndex = stepIndex;

          //to save power, turn off stepper coils
          stepIndex = 8;
          stepMotor(stepIndex);

          //the TIMER is now disabled and the Motor will stop
          stepperTIMER.disableTIMER();

          digitalWrite(ccwLED, LEDoff);
        }
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else {
    CCW_Sw.counter = 0;
  }

  //END of CCW_Sw.pin

  //========================================================================  DeleteSw.pin
  currentState = digitalRead(DeleteSw.pin);

  //===================================
  //has this switch changed state ?
  if (DeleteSw.lastState != currentState) {
    DeleteSw.counter++;

    //is this change in state stable ?
    if (DeleteSw.counter >= filter) {
      //get ready for the next sequence
      DeleteSw.counter = 0;

      //update to this new state
      DeleteSw.lastState = currentState;

      //========================
      //did this switch close ?
      if (currentState == CLOSED) {
        DPRINTLNF("Delete switch closed");

        deleteLedTIMER.enableRestartTIMER();
      }

      //========================
      //did this switch open ?
      else if (currentState == OPENED) {
        DPRINTLNF("Delete switch opened");

        digitalWrite(deleteLED, LEDoff);

        deleteLedTIMER.disableTIMER();
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else {
    DeleteSw.counter = 0;
  }

  //END of DeleteSw.pin

  //========================================================================  SetPosSw.pin
  currentState = digitalRead(SetPosSw.pin);

  //===================================
  //has this switch changed state ?
  if (SetPosSw.lastState != currentState) {
    SetPosSw.counter++;

    //is this change in state stable ?
    if (SetPosSw.counter >= filter) {
      //get ready for the next sequence
      SetPosSw.counter = 0;

      //update to this new state
      SetPosSw.lastState = currentState;

      //========================
      //did this switch close ?
      if (currentState == CLOSED) {
        DPRINTLNF("Set switch closed");

        //toggle LED
        //digitalWrite(setPosLED, digitalRead(setPosLED) == HIGH ? LOW : HIGH);

        digitalWrite(setPosLED, LEDon);
      }

      //========================
      //did this switch open ?
      else if (currentState == OPENED) {
        DPRINTLNF("Set switch opened");

        digitalWrite(setPosLED, LEDoff);
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else {
    SetPosSw.counter = 0;
  }

  //END of SetPosSw.pin

  //========================================================================  OptoSensor.pin
  currentState = digitalRead(OptoSensor.pin);

  //===================================
  //has this switch changed state ?
  if (OptoSensor.lastState != currentState) {
    OptoSensor.counter++;

    //is this change in state stable ?
    if (OptoSensor.counter >= filter) {
      //get ready for the next sequence
      OptoSensor.counter = 0;

      //update to this new state
      OptoSensor.lastState = currentState;

      //========================
      //did this switch close ?
      //if (currentState == CLOSED)
      if (currentState == OPTOclosed) {
        DPRINTLNF("Opto Sensor has Closed");
      }

      //========================
      //did this switch open ?
      //else if (currentState == OPENED)
      else if (currentState == OPTOopened) {
        DPRINTLNF("Opto Sensor has Opened");
      }
    }
  }

  //===================================
  //a valid switch change has not been confirmed
  else {
    OptoSensor.counter = 0;
  }

  //END of OptoSensor.pin

}  //END of   checkSwitches()


//                               r e a d R o t a r y E n c o d e r ( )
//================================================^================================================
void readRotaryEncoder() {
  static byte lastStateA = digitalRead(EncoderOutA);
  byte currentStateA;

  currentStateA = digitalRead(EncoderOutA);

  //=========================================
  //has this state changed ?
  if (lastStateA != currentStateA) {
    //update to the new state
    lastStateA = currentStateA;

    //========================
    //when the outputB state is different from the outputA state,
    //we are rotating clockwise CW
    if (digitalRead(EncoderOutB) != currentStateA) {
      //CW
      EncoderCounter++;
    }

    //========================
    //we are rotating counter clock wise CCW
    else {
      //CCW
      EncoderCounter--;
    }

    DPRINTF("Encoder Counter = ");
    DPRINTLN(EncoderCounter);
  }

}  //END of  readRotaryEncoder()


//                                       s t e p M o t o r ( )
//================================================^================================================
void stepMotor(int output) {
  digitalWrite(IN1, bitRead(lookup[output], 0));
  digitalWrite(IN2, bitRead(lookup[output], 1));
  digitalWrite(IN3, bitRead(lookup[output], 2));
  digitalWrite(IN4, bitRead(lookup[output], 3));

}  //END of  stepMotor()



//================================================^================================================

Im guessing from your line of questioning that there is a way to code in the reported value change whenever there is a motor position change rather than CW and CCW individually but im not sure how.

The answer for me was nothing. After going over the code i found that you had edited line 40 to:

//#define DEBUG  //comment this line to stop printing to the Serial Monitor,

That resulted in you commenting out the line and as such no debug info was being fed back.

  • No, this line will print the value.

Serial.println(currentMotorPosition);

So Ive been researching how to determine this and i have found the following formula:

RPM = (steps_per_interval x 60) / (steps_per_revolution x interval_in_seconds)

Having done some more reading on the motor im currently using ive found the following data:

The 28BYJ-48 motor has a step angle of 5.625°.
This translates to 64 steps per revolution in full-step mode.
However, the 28BYJ-48 motor also has an internal gearbox with a reduction ratio of approximately 64:1.
Therefore, the total steps per revolution of the output shaft is 64
steps/rev × 64 gear ratio = 4096 steps/rev

Therefore:
RPM = (steps_per_interval x 60) / (steps_per_revolution x interval_in_seconds)
= (4107x60)/(4096x10)
= 246420/40960
= 6.016

Based on this steps per interval over a 10sec period was done by my pressing a button from 0 and letting go at 10 on a timer. I have to assume a level of inaccuracy.
I would therefore estimate the answer is:
6rpm as currently coded.

Is that what you meant?

I will concede thought that line 173

const unsigned long motorSpeed = 2400ul;  //larger the number, slower the the motor movement

Would have a direct impact on this number.

  • You can use the <ctrl><F> to look for all lines that change the variable currentMotorPosition and examine the line of code to determine if you should add Serial Monitor updating.

Ah, that would be why.
I changed the code to DPRINTLN(currentMotorPosition) when you asked me to change the code for the CCW switch as i thought it was a test.

  • You can simply turn on the power to your hardware, note where the motor is sitting, use the CW switch to go 1 revolution, stop when it gets to where it started out, look at the Serial Monitor to get the count in 1 revolution.
  • That’s fine, both will work.

I understand how the binary works in that the columns are assigned the following value right to left - 1,2,4,8
I also understand that you add any of the columns with a 1 together to come out with the value.
i.e. 0110 is one 2 and one 4. so it is a value of 6.

One thing ive been thinking about today though is if it is not IN pins assignments/states, like i had originally thought. Then what do these numbers assign/relate to?
I also noticed that the numbers dont run concurrently. ie 1, 2, 3.

So how do these numbers effect the motor?
Do they have to always appear in the order in which you have placed them?

Im sorry, only on reflection did this come to mind. Im currently trying to take on board a LOT of complex information in a short period of time. so mea culpa on that one.

  • Please reword the above :thinking:

  • The far left element, B01000 , in the array is called element 0
    When this is sent to the motor, IN4 Is ON i.e. current flowing, the rest are OFF.

  • The next element, B01100 , in the array is called element 1
    When it is sent to the motor, IN4 and IN3 are ON i.e. currents flowing, the rest are OFF.

  • Do you understand what was just said ?

As a side bar, following some comments yesterday.
Do you believe i will need to use a gear system to achieve my goal?
Ive been looking around and come across the following:
https://www.amazon.co.uk/gp/product/B0C54ZL4PT/ref=ox_sc_saved_image_2?smid=A3G949QXPNMPWH&psc=1
They sell various gear ratios but any with the smaller shaft size would be able to be implemented.
Do you think i need to do so?

The reason im asking now is that i dont know whether the use of this would effect the coding we are doing.
In any event we can come back to this at a later time - i just wanted to recognise some of the comments.

  • Can be a bit heavy :woozy_face:

  • Ask questions about things you do not understand.

  • Ask for clarification on those things you don’t understand why they are being as they are.

1 Like

Ok, then yes I understand it.
I just misunderstood our previous communication which had made me believe this was not the case.

What i need to reword then is if it is PIN state, how does writing the value as say 8 or 12 tell the motor what state it should be in? Does the system automatically take the 8 and convert it to 1000? im presuming so.
and that is why writing the 8, 12, 4 etc is the same as writing 1000, 1100, 0100 etc.

  • I would stay with the stepper.

  • When you make the stepper go CW with the CW switch (just after turning the power on), what number is on the serial monitor after moving the motor one revolution ?

  • The controller knows nothing about decimal numbers, decimal numbers are for humans.

  • The compiler converts all decimal numbers into binary numbers.
    Everything in the controller is binary.


You may not have look at this part of the sketch but this section actually sends the IN1-4 pins the 1s and 0s.


//                                       s t e p M o t o r ( )
//================================================^================================================
void stepMotor(int output)
{
  digitalWrite(IN1, bitRead(lookup[output], 0));
  digitalWrite(IN2, bitRead(lookup[output], 1));
  digitalWrite(IN3, bitRead(lookup[output], 2));
  digitalWrite(IN4, bitRead(lookup[output], 3));

} //END of  stepMotor()

:ok_hand:

When i power down the system and restart it the serial monitor thinks i am at 0 again. even if i haven't done a home routine.

  • At power up, what is printed on the Serial Monitor when you press and hold the CW switch ?

oh i misunderstood.

if i power up the system whilst holding the CW switch there is no difference to when i power up as normal.

i get 'Power Up', then 'motor going clockwise' and then '1', '2', '3' etc.

  • Good.

  • What happens if you let go of the CW switch and press and hold the CCW switch ?