Stepper limit switch confusion

hi all,

I am trying to make a simple stepper rig but going around in circles (no pun intended) with limit switches...

It now works like this:
i press "left" button switch and the TMC2209 driver is enabled and stepper turns left, as soon as i release the button the driver should disable and stepper stops turning. same goes with "right" push button

now, there are also 2 limit switches, one for each side. Whatever i do i cant make them to work properly.

Limit switches should work like this:
once the stepper triggers the left limit switch, stepper speed should be set to zero or go to HOLD, at this point pressing the "left" turn button should do nothing as we have the limit switch triggered, we can now only go right by pressing the "right" button.

Current loop code looks like this (i am using ezButton for ease of use)

void loop() {

  limitSwitch_1.loop(); // MUST call the loop() function first
  limitSwitch_2.loop(); // MUST call the loop() function first
  left.loop(); // MUST call the loop() function first
  right.loop(); // MUST call the loop() function first


        if (left.isPressed() && limitSwitch_1.isReleased() )  {
          digitalWrite(EN_PIN, LOW);
          driver.VACTUAL(200);   // Set motor speed
          driver.shaft(dir);      // SET DIRECTION
        }
        
        if (right.isPressed() && limitSwitch_2.isReleased()) {
          digitalWrite(EN_PIN, LOW);
          driver.VACTUAL(200);   // Set motor speed
          driver.shaft(!dir);     // SET DIRECTION
        }
        
        if (right.isReleased() || left.isReleased()) {
          digitalWrite(EN_PIN, HIGH);
    }
        

}

My programming skills are modest, i need a bit of a push with this to continue.

Many thanks in advance,
Alek

You description of the intention is clear but please tell what actually happens.

Thank you Railroader!

So what happens is, stepper is turning and when it hits the limit switch it just start clicking, like its stopping and starting over and over. If i activate the servo and press and hold the limit switch before stepper gets to it, stepper stops as it should... but if i then release and press again the direction button while still holding the limit switch, stepper continues to the same direction of the pressed limit switch, although it should not be allowed to move in that direction as long as the limit switch is pressed....

not sure if i explained it clearly.

many thanks,
Alek

ezButton is making things harder for you!

Please write some simple sketches and/or review the operation of ezButton buttons.

I looked at it briefly, so I don't recall exactly how it works.

I believe that isPressed and isReleased would better be called gotPressed and wasReleased.

They report events on the buttons or switches.

Your code look like it would rather be consulting the state of the buttons or switches.

The code (and simulation) say to me ezButton is badly bad. Or that I can't figure out how it is meant to work. You will see that pressing a button reports a few "pressed" events.


Wokwi_badge ezButton Demo


// https://wokwi.com/projects/363250216927001601
// https://forum.arduino.cc/t/stepper-limit-switch-confusion/1120713

# include <ezButton.h>

ezButton button(7);  // create ezButton object that attach to pin 7;

void setup() {
  Serial.begin(9600);
}

void loop() {
  button.loop(); // MUST call the loop() function first

  if(button.isPressed())
    Serial.println("The button is pressed");
    
  if(button.isReleased())
    Serial.println("The button is released");

  static unsigned long last;
  if (millis() - last > 777) {
    last = millis();

    Serial.print("the button is ");
    if (button.getState()) 
      Serial.println("UP");
    else
      Serial.println("DOWN");
  }
}

Perhaps the demo will help you figure out the way ezButton needs you to work with it.

My recommendation is to find a better button library, and learn its corners and doors or...

Just gear up and step back and learn how to do your own button handling. It isn't that hard. You should know how to do it as it will involve learning programming techniques that show up all over the place. Not just for buttons.

Once you have walked through the fire of learning about buttons and doing your own job of it, you may wish to return to the ease and convenience of using a library.

a7

Thank you Alto777!

I did a lot of arduino stuff few years ago, including reading button states without libraries but i feel a bit rusted.
ezButton seemed like ok solution as a proof of concept but somehow it seems that "isPressed" is not actually keeping the state of the button/switch...

many thanks,
Alek

If you run the demo at the link I provided, it is clear.

isPressed does not keep the state. It also doesn't even debounce. So basically it is worthless.

It can't be used for state, and it can't be used for event or edge detection.

I can't name a good button library. I have looked at them all, most seem to work, but all seem to do one thing or another in a manner I do not prefer.

So I just write the code, or steal it from my younger self. Sometimes elaborate meant-to-be-reusable code, other times it just flies off my fingers again in an ad hoc manner.

You will also see ezButton's getState method, which is at least debounced and therefore a bit of improvement over just digitalReading the buttons.

In a loop like yours, a delay(25) at the bottom would not interfere very much, and would totally debounce all switches so you could just use digitslRead() and, if necessary, state change detection as seen in the IDE examples 02.Digital I think.

a7

Looks like ezButton does not do debouncing by default, and if you enable it, debouncing impacts responsiveness:

button.setDebounceTime(2000);  // ezButton state changes after invocation

One would think that a library would write the debounce to be responsive.

Here's a comparison of debounce schemes:

// https://wokwi.com/projects/363256151401922561
// https://forum.arduino.cc/t/stepper-limit-switch-confusion/1120713


# include <ezButton.h>

ezButton button(7);  // create ezButton object that attach to pin 7;

unsigned buttonDebounceInterval = 2000;  // large delay for detecting differences

// stick-built responsive debounce infrastructure
static unsigned long buttonStateChangeTime = -buttonDebounceInterval; // negative to induce rollover
static uint8_t buttonState = HIGH;

void setup() {
  Serial.begin(9600);
  button.setDebounceTime(buttonDebounceInterval);
  pinMode(12,OUTPUT);
  pinMode(3,OUTPUT);
}

void loop() {
  button.loop(); // MUST call the loop() function first

  // Responsive debounce change detection
  uint8_t buttonTransient = digitalRead(7);
  if (buttonTransient != buttonState && millis() - buttonStateChangeTime >= buttonDebounceInterval) {
    buttonStateChangeTime = millis();
    buttonState = buttonTransient;
    Serial.print("Responsive button changed to ");
    if (buttonState == HIGH) { // rising edge
      Serial.println("UP");
      digitalWrite(3,LOW);

    } else { // falling edge
      Serial.println("DOWN");
      digitalWrite(3,HIGH);
    }
  }

  if (button.isPressed()){
    Serial.println("The button is pressed");
    digitalWrite(12,HIGH);
  }

  if (button.isReleased()){
    Serial.println("The button is released");
    digitalWrite(12,LOW);
  }
  static unsigned long last;
  if (millis() - last > 777) {
    last = millis();

    Serial.print("the button is ");
    if (button.getState())
      Serial.println("UP");
    else
      Serial.println("DOWN");
  }
}
1 Like

ok i am trying something else:

void loop() {

  leftbtn = digitalRead(left);
  if (buttonStateLeft == HIGH) {
    leftbtn = true;
  } else {
    leftbtn = false;
  }

  rightbtn = digitalRead(right);
  if (buttonStateRight == HIGH) {
    rightbtn = true;
  } else {
    rightbtn = false;
  }

  leftLimit = digitalRead(limitSwitch_1);
  if (buttonStateLeftLimit == HIGH) {
    leftLimit = true;
  } else {
    leftLimit = false;
  }

  rightLimit = digitalRead(limitSwitch_2);
  if (buttonStateRightLimit == HIGH) {
    rightLimit = true;
  } else {
    rightLimit = false;
  }


  if ((leftbtn == true) && (leftLimit == false)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(200);   // Set motor speed
    driver.shaft(dir);      // SET DIRECTION
  } else if ((leftbtn == false)  && (leftLimit == true)) {
    driver.VACTUAL(0);
  }

  if ((rightbtn == true)  && (rightLimit == false)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(200);   // Set motor speed
    driver.shaft(!dir);      // SET DIRECTION
  } else if ((rightbtn == false)  && (rightLimit == true)) {
    driver.VACTUAL(0);
  }

}

So far it compiles but nothing happens... i must have missed something.

Is this the way too keep the state?

i got it!!!

this is wrong:

rightbtn = digitalRead(right);
  if (buttonStateRight == HIGH) {
    rightbtn = true;
  } else {
    rightbtn = false;
  }

and this is correct:

buttonStateRight = digitalRead(right);
  if (buttonStateRight == HIGH) {
    rightbtn = true;
  } else {
    rightbtn = false;
  }

Awesome! many thanks for showing me the right way!

1 Like

Hi, @elcrni

We have missed the rest of your code.
Like where have you declared buttonStateRight etc....
Please your complete code.

What are you doing here?

 leftbtn = digitalRead(left);
  if (buttonStateLeft == HIGH) {
    leftbtn = true;
  } else {
    leftbtn = false;
  }

You read the button, assign a variable that value, leftbtn, then using some variable called "buttonStateLeft" reassign leftbtn????

You seem obsessed with button bounce, forget it for te moment and write some raw code that just reads all the inputs and acts on them.

A schematic of your project would help so we can see what logic you are using for all your switches.

Tom... :smiley: :+1: :coffee: :australia:

1 Like

hey Tom,
Yeah, figured, see the post above i wrote just a moment ago.
everything works as it should now! :slight_smile:

LOL. I never would have thought of that. I am sure the documentation makes that point clearly. :expressionless:

@elcrni I am glad you have it going now.

I remind you that there is available the serial monitor. I don't think I've ever made a sketch and gotten it working, working well and doing exactly what I thought I wrote without some kind of feedback or reporting. Usually the serial monitor. Sometimes a logic analyser. Even just using an LED.

You should never wonder why (or if) something is happening.

a7

Thanks alto777, well, i do wonder a lot :slight_smile:

Now, still have a small problem, my code now tells the stepper the move only while the direction button is pressed, just as i wanted it... but as soon as i ramp up the speed the stepper starts shaking while turning.

i suspect there is a conflict of some kind telling it to go/stop all the time.

if (leftbtn == false && leftLimit == true) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(1000);   // Set motor speed
    driver.shaft(dir);      // SET DIRECTION
    
  } else if (leftbtn == true && leftLimit == false) {
    driver.VACTUAL(0);
    
  }

  if (rightbtn == false  && rightLimit == true) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(1000);   // Set motor speed
    driver.shaft(!dir);      // SET DIRECTION
    
  } else if (rightbtn == true && rightLimit == false) {
    driver.VACTUAL(0);
    
  }

Above code spins the stepper all the way to the limit switch if i just touch the direction button, without any noises or issues, silent and smooth.

but if i want to spin it only while holding the button then i have the code like this:

else if (rightbtn == true || rightLimit == false) 

so no loner AND but OR...

any ideas of what the conflict would be or how to fix it?

many thanks,
Alek

Consider using the COM and NC (Normally Closed) terminals on your limit switch and having an external pullup resistor on this circuit. That way, if anything in the limit switch circuit fails (disconnect, etc), the motor will be disabled.

If you use the NO (Normally Open) terminal as you would a regular pushbutton switch, then if anything fails, the motor would be enabled.

Good point! As someone who owns 10+ 3D printers, i am ashamed i have not thought of this even for the prototype :slight_smile:

Does your stepper work at those speeds with a test sketch? If they don't have enough current, sometimes they stutter and miss steps at higher speeds.

Consider the "Input-Process-Output model" to disconnect computing the motor state from outputting to the motor pins.

thanks DaveX!

I've sorted it out, my sketch was over defined, made it a bit simpler and everything works great now.
Many thanks.

Current/simplified code:

if ((leftbtn == false) && (leftLimit == true)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(1000);   // Set motor speed
    driver.shaft(dir);      // SET DIRECTION
  }
  

  else if ((rightbtn == false)  && (rightLimit == true)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(1000);   // Set motor speed
    driver.shaft(!dir);      // SET DIRECTION
    
  } else {
    driver.VACTUAL(0);
  }

For the record and to make it easier for those who may come along later, please post the complete sketch with all your adjustments in context.

I can easily make a mistake backfitting the key snippets you've fixed up. Even with a small sketch.

TIA

a7

2 Likes

here goes, entire working code:

#include <SpeedyStepper.h> //Simple & good stepper library, get it.
#include <TMCStepper.h>

#define DIR_PIN          14 // Direction
#define STEP_PIN         12 // Step
#define SERIAL_PORT Serial2 // HardwareSerial port pins 16 & 17
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2

#define R_SENSE 0.11f // Match to your driver
                      // SilentStepStick series use 0.11                    
#define x_pin 34      // Pin 34 connected to joystick x axis pin
#define EN_PIN 32
#define left 25
#define right 26
#define limitSwitch_1 27
#define limitSwitch_2 33

#define MAX_SPEED        40 // In timer value
#define MIN_SPEED      1000

bool shaft = false;  // ONLY NEEDED FOR CHANGING DIRECTION VIA UART, NO NEED FOR DIR PIN FOR THIS
bool dir = false;
int analog_read;

bool leftbtn;
bool rightbtn;
bool leftLimit;
bool rightLimit;
int buttonStateLeft = 0;
int buttonStateRight = 0;
int buttonStateLeftLimit = 0;
int buttonStateRightLimit = 0;

int potPin = 35;
int potValue = 0;

TMC2209Stepper driver(&SERIAL_PORT, R_SENSE, DRIVER_ADDRESS);
SpeedyStepper stepper;

//----------------------------------------------------------------------------------------------------
//      SETUP
//----------------------------------------------------------------------------------------------------


void setup() {

stepper.connectToPins(STEP_PIN, DIR_PIN); // INITIALIZE SpeedyStepper

SERIAL_PORT.begin(115200);      // INITIALIZE UART TMC2209
Serial.begin(115200);
delay(500);
Serial.println(F("Serial Initialized"));

  pinMode(x_pin, INPUT);
  pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, HIGH); // Disable TMC2209 board on boot
  pinMode(DIR_PIN, OUTPUT);
  pinMode(STEP_PIN, OUTPUT);

  pinMode(left, INPUT_PULLUP);
  pinMode(right, INPUT_PULLUP);
  pinMode(limitSwitch_1, INPUT_PULLUP);
  pinMode(limitSwitch_2, INPUT_PULLUP);
  pinMode(potPin, INPUT);


  driver.begin();                // Initialize driver
                           

  driver.rms_current(4800);       // Set motor RMS current
  driver.microsteps(2);            // Set microsteps to 1/2

  driver.pwm_autoscale(true);    // Needed for stealthChop
  driver.en_spreadCycle(false);   // false = StealthChop / true = SpreadCycle

  stepper.setCurrentPositionInSteps(0);                   // Set zero position
  stepper.setSpeedInStepsPerSecond(6400);              //Set Speed
  stepper.setAccelerationInStepsPerSecondPerSecond(100);   //Set acceleration, smaller value for super smooth direction changing
}

//----------------------------------------------------------------------------------------------------
//      LOOP
//----------------------------------------------------------------------------------------------------

void loop() {

  potValue = analogRead(potPin);
  

  buttonStateLeft = digitalRead(left);
  if (buttonStateLeft == HIGH) {
    leftbtn = true;
  } else {
    leftbtn = false;
  }

  buttonStateRight = digitalRead(right);
  if (buttonStateRight == HIGH) {
    rightbtn = true;
  } else {
    rightbtn = false;
  }

  buttonStateLeftLimit = digitalRead(limitSwitch_1);
  if (buttonStateLeftLimit == HIGH) {
    leftLimit = true;
  } else {
    leftLimit = false;
  }

  buttonStateRightLimit = digitalRead(limitSwitch_2);
  if (buttonStateRightLimit == HIGH) {
    rightLimit = true;
  } else {
    rightLimit = false;
  }


  if ((leftbtn == false) && (leftLimit == true)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(potValue / 2);   // Set motor speed
    driver.shaft(dir);      // SET DIRECTION
  }
  

  else if ((rightbtn == false)  && (rightLimit == true)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(potValue / 2);   // Set motor speed
    driver.shaft(!dir);      // SET DIRECTION
    
  } else {
    driver.VACTUAL(0);
  }
}

ok, need a little more guidance. i would like to add millis() to disable the driver after a certain number of seconds ONLY if the motor is not active/turning. I think i have managed to define when the motor is running and when its not, so millis will disable the driver accordingly.

The problem:
It now adds the time each time i stop the motor (each time the motorstatus is "false"), meaning: i run the motor for some time then stop for 1 second, then run some time and then stop for 1 second... millis loop will keep adding the time while stopped and then will disable the driver when i stop it 5 times 1sec each (const long interval = 5000;)

What i need:
Reset the counter each time i start the motor after some inactivity time, ie. i start the motor and run it for some time, then i stop the motor and the "disable driver" countdown starts... i start the motor again at 4th second, just before the time is up... millis counter resets and now when i stop the motor i again get new 5 seconds before disabling the driver...

hope i have explained it clearly.

my current code:

#include <SpeedyStepper.h> //Simple & good stepper library, get it.
#include <TMCStepper.h>

#define DIR_PIN          14 // Direction
#define STEP_PIN         12 // Step
#define SERIAL_PORT Serial2 // HardwareSerial port pins 16 & 17
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2

#define R_SENSE 0.11f // Match to your driver
                      // SilentStepStick series use 0.11                    
#define x_pin 34      // Pin 34 connected to joystick x axis pin
#define EN_PIN 32
#define left 25
#define right 26
#define limitSwitch_1 27
#define limitSwitch_2 33

#define MAX_SPEED        40 // In timer value
#define MIN_SPEED      1000

bool shaft = false;  // ONLY NEEDED FOR CHANGING DIRECTION VIA UART, NO NEED FOR DIR PIN FOR THIS
bool dir = false;
int analog_read;

bool leftbtn;
bool rightbtn;
bool leftLimit;
bool rightLimit;
int buttonStateLeft = 0;
int buttonStateRight = 0;
int buttonStateLeftLimit = 0;
int buttonStateRightLimit = 0;
int motorState = 0;  // Stepper motor state - TRUE = Running, FALSE = holding position

int potPin = 35;
int potValue = 0;

unsigned long previousMillis = 0;  // will store last time stepper driver is disabled
// constants won't change:
const long interval = 5000;  // interval at which to disable the driver

TMC2209Stepper driver(&SERIAL_PORT, R_SENSE, DRIVER_ADDRESS);
SpeedyStepper stepper;

//----------------------------------------------------------------------------------------------------
//      SETUP
//----------------------------------------------------------------------------------------------------


void setup() {

stepper.connectToPins(STEP_PIN, DIR_PIN); // INITIALIZE SpeedyStepper

SERIAL_PORT.begin(115200);      // INITIALIZE UART TMC2209
Serial.begin(115200);
delay(500);
Serial.println(F("Serial Initialized"));

  pinMode(x_pin, INPUT);
  pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, HIGH); // Disable TMC2209 board on boot
  pinMode(DIR_PIN, OUTPUT);
  pinMode(STEP_PIN, OUTPUT);

  pinMode(left, INPUT_PULLUP);
  pinMode(right, INPUT_PULLUP);
  pinMode(limitSwitch_1, INPUT_PULLUP);
  pinMode(limitSwitch_2, INPUT_PULLUP);
  pinMode(potPin, INPUT);


  driver.begin();                // Initialize driver
                           

  driver.rms_current(4800);       // Set motor RMS current
  driver.microsteps(2);            // Set microsteps to 1/2

  driver.pwm_autoscale(true);    // Needed for stealthChop
  driver.en_spreadCycle(false);   // false = StealthChop / true = SpreadCycle

  stepper.setCurrentPositionInSteps(0);                   // Set zero position
  stepper.setSpeedInStepsPerSecond(6400);              //Set Speed
  stepper.setAccelerationInStepsPerSecondPerSecond(100);   //Set acceleration, smaller value for super smooth direction changing
}

//----------------------------------------------------------------------------------------------------
//      LOOP
//----------------------------------------------------------------------------------------------------

void loop() {

  potValue = analogRead(potPin);
  

  buttonStateLeft = digitalRead(left);
  if (buttonStateLeft == HIGH) {
    leftbtn = true;
  } else {
    leftbtn = false;
  }

  buttonStateRight = digitalRead(right);
  if (buttonStateRight == HIGH) {
    rightbtn = true;
  } else {
    rightbtn = false;
  }

  buttonStateLeftLimit = digitalRead(limitSwitch_1);
  if (buttonStateLeftLimit == HIGH) {
    leftLimit = true;
  } else {
    leftLimit = false;
  }

  buttonStateRightLimit = digitalRead(limitSwitch_2);
  if (buttonStateRightLimit == HIGH) {
    rightLimit = true;
  } else {
    rightLimit = false;
  }


  if ((leftbtn == false) && (leftLimit == true)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(potValue / 2);   // Set motor speed
    driver.shaft(dir);      // SET DIRECTION
    motorState = true;
  }
  

  else if ((rightbtn == false)  && (rightLimit == true)) {
    digitalWrite(EN_PIN, LOW);
    driver.VACTUAL(potValue / 2);   // Set motor speed
    driver.shaft(!dir);      // SET DIRECTION
    motorState = true;
    
  } else {
    driver.VACTUAL(0);
    motorState = false;
  }



  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time
    previousMillis = currentMillis;

    // 
    if (motorState == false) {
      digitalWrite(EN_PIN, HIGH);
    } 
    
  }

}