Limit Switch Issue for Stepper Motor Application

Hi all,

I have been having this problem using the stepper.h library with a limit switch.

The prepose of this code:
Stepper motor is a linear actuator that drive a load forward and back per cycle. The limit switch is installed so that when the load returns, it hit the limit switch, which will trigger another cycle and so on.

My problem:
The limit switch only stop my motor from running but refuse to move forward after hitting the switch.

Some of my own troubleshoot:

  1. I tried using AccelStepper library with ezButton and that worked just fine, but for this
    project i really need to use stepper.h instead.
  2. It seems like sometime it does move again after a very long pause and execute the
    commands.
  3. I wrote Serial.print('Turn') in the serial monitor and it only display 'Tu' when I press the
    button and it display 'TuTu' second time I try.

Here is my code:

int speed = 0;
int position = 0;
int speedAbs = 0;
int positionAbs = 0;


#include <Stepper.h>

bool interrupt = false;

const byte interruptPin = 21;

const int stepsPerRevolution = 1600;

Stepper myStepper(stepsPerRevolution, 22, 24);

void setup() {

Serial.begin(9600);

pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), returnCycle, FALLING);

speed = 30;
position = 25.4;
positionAbs = position*(1600/25.4);

}

void loop() {
    myStepper.setSpeed(speed);
    myStepper.step(-1000);
    myStepper.step(10000);  
}

void returnCycle() { 
    myStepper.setSpeed(speed);
    myStepper.step(-1000);
    myStepper.step(10000);
}

Thanks in advance

How are you supplying power to the stepper and the Arduino? This looks like a lack-of-power issue.

1 Like

I plug in a 12V adapter to a Haydon DCM8054/8027 driver that is rated 20 to 80 Vdc for input voltage. My stepper is 12Vdc with 1.3A so I am reluctant to use a 24V source since it heats up the motor tremendously and very fast.

just tried the 24V adopter, still the same.

I do not know the DCM8054, but using 12VDC power for a 20VDC device sounds like a problem... and probably the cause of post #2.

I think it is because of running returnCycle inside an interrupt context. -- micros() can't advance very far while interrupts are being blocked, so it can't make a next step or do serial.

The problem doesn't need interrupts, it needs a state machine to handle FORWARD versus BACKWARD motion and transitioning between the them.

1 Like

Why? stepper.h is the worst selection here. It is not designed to work with step/dir drivers ( as your DCM8054 is ). And it is a blocking design - it blocks while creating steps.

When using interrupts you should really know what you are doing. You must know the limitations of an ISR,, it's NOT an ordinary function.
Your usage of an ISR is shurely the main reason of your problems - as @DaveX already pointed out.

1 Like

Post a link to datasheet or brand name and exact part number.

This is my attempt on using state machine and it still doesnt work. From what i observe in this code, when i press the switch, the motor will still spin(probably finishing the rest of the command) and then stop. And it stops and do nothing, not go to the opposite direction like i hope.

int speed = 0;
int position = 0;
int speedAbs = 0;
int positionAbs = 0;
int digitalState = 0;
int lastdigitalState = 0;
int Pin_Digital_Input = 21;
int stateSwitch = 0;

#include <Stepper.h>

const int stepsPerRevolution = 1600;


Stepper myStepper(stepsPerRevolution, 22, 24);

void setup() {

Serial.begin(9600);

pinMode(Pin_Digital_Input, INPUT_PULLUP);

speed = 70;
position = 100;
positionAbs = position*(1600/25.4);
}

void loop() {
myStepper.setSpeed(speed); 

  switch(stateSwitch){
    case 0:
      if (digitalRead(Pin_Digital_Input) == HIGH){
        myStepper.step(-positionAbs);
        delay(1000);
        myStepper.step(100000); //Value large enough to hit the homing sensor
      }
    break;
    case 1:
      if (digitalRead(Pin_Digital_Input) == LOW){
        myStepper.step(-positionAbs);
        delay(1000);
        myStepper.step(100000); //Value large enough to hit the homing sensor
        delay(1000);
      }
    break;
    delay(50);
  }
}

https://www.haydonkerkpittman.com/products/drives/stepper-motor-non-programmable-drives/dcm8027-dcm8054

https://www.haydonkerkpittman.com/products/linear-actuators/hybrid-stepper/size-34
Stepper motor is the Z variant with 12VDC and 1.3A

And also I dont know how to use AccelStepper library to do cycle..... been trying but I cant find any examples

You have already created examples on the Arduino IDE when you download the AccelStepper library

1 Like

What's supposed to be the difference between these two motions:

They look exactly the same to me. I also do not see how the SM is supposed to change between states.

Where is the limit switch in terms of position? Somewhere < 0 or somewhere > 0? it is unclear. Does the limit switch stop the negative-ward travel or the positive-ward travel? And what is suppoed to happen after it hits the limit switch?

Your interrupt scheme did detect the limit switch triggering and stopped the (blocking) myStepper.step() function. To do a similar thing without an interrupt, you need to check in between the individual steps for whether it is time to change the planned destination.

The AccelStepper Bounce example does cycles:

It changes from moving CW to +500 into CCW to -500 and vice versa whenever it reaches its planned target.

If I was going to modify the AccelStepper Bounce example to cycle between CCW to -positionAbs and CW to (100000 - positionAbs), I'd use state machine for moving CCW and CW, and update the target and state when it reaches position.

I'd try something like this completely untested snippet:

void loop()
{
    // If at the end of travel go to the other end
    switch(stateSwitch){
    case 0: // moving CCW
      if (stepper.distanceToGo() == 0){
         stepper.moveTo(100000-positionAbs);
         stateSwitch = 1;
      }
      break;
    case 1: // moving CW
      if (stepper.distanceToGo() == 0){
         stepper.moveTo(-positionAbs);
         stateSwitch = 0;
      }
      break;
    }
    stepper.run(); // move stepper as needed
}

If the limit switch is supposed to act on the CCW end, I'd make the CCW case's test to be for hitting the limit switch instead of if (stepper.distanceToGo() == 0)

1 Like

Thanks! I will give it a try now!

the stepper motor is a actuator driving a nut backward and forward. The nut attached to a load on a railing and drive the block forward to a set distance and drive backward all the way until it hit the limit switch. That is one cycle. Ultimately I would like to set the Cycle as a variable so that it would run let say 10 cycle and in each cycle the stepper will drive the load forward to a set distance and backward until it hits the limitswitch and it will go back to that set distance again.

Currently this is what I have, without the limit switch part after playing with the example. The stepper is able to run in my desired cycle and distance. Now I just need to incorporate the part where it hits the limitswitch when return instead of the negative set value there

#include <AccelStepper.h>
int currentCycle = 0;
int cycleCount = 0;
int motorSpeed = 7000;
float motorAccel = 5000;

AccelStepper motor(AccelStepper::FULL2WIRE, 22, 24);

void setup() {
  cycleCount = 2;
}

void loop() {

while (currentCycle < cycleCount){

 motor.setCurrentPosition(0);
  motor.setMaxSpeed(motorSpeed);
  motor.setAcceleration(motorAccel);
 motor.move(8000);
 delay(500);
   while (motor.run());
   motor.run();

 motor.setCurrentPosition(0);
  motor.setMaxSpeed(motorSpeed);
  motor.setAcceleration(motorAccel);
 motor.move(-8000);
 delay(500);
   while (motor.run());
   motor.run();

currentCycle = currentCycle+1;
}
}

Shouldn't that be DRIVER?

In reference to FULL2WIRE/DRIVER:

http://www.airspayce.com/mikem/arduino/AccelStepper/classAccelStepper.html

1 Like

One of the problems with incorporating a limit switch is planning where to do the checking of the limit switch.

When you have blocking code like:

or:

the program blocks other things from happening until the move is complete, so it can move past the limit switch before it gets a chance to read the switch.

The non-blocking code as in the AccelStepper Bounce Example, runs all the way through loop 1000s of times per second, allowing lots of opportunity to read the limit switch and act/re-target appropriately or do several other things at apparently the same time.

Looking at the code you have in #15, I wonder what you would want to have happen after the cycleCount cycles, or what might initiate a second set (or the first set) of cycles.

If I were doing it with a state machine as in my #13 untested snippet above, I'd add a third state, IDLE, for when the device isn't supposed to be moving. And then each time I completed a cycle, I'd increase your 'currentCycle' variable and decide whether to do another cycle or switch to IDLE.

It would be something like the slightly more tested code in this simulation:

// https://wokwi.com/projects/399355387445360641
// for https://forum.arduino.cc/t/limit-switch-issue-for-stepper-motor-application/1265784/12
//
// based on sim https://wokwi.com/projects/388661915235241985
// and https://github.com/waspinator/AccelStepper/tree/master/examples/Bounce
// adding a state machine to have an un-balanced CW-CCW movement
// and a limited number of cycle counts for
//

//
// Bounce.pde
// -*- mode: C++ -*-
//
// Make a single stepper bounce from one limit to another
//
// Copyright (C) 2012 Mike McCauley
// $Id: Random.pde,v 1.1 2011/01/05 01:51:01 mikem Exp mikem $

#include <AccelStepper.h>

// Define a stepper and the pins it will use
AccelStepper stepper(1, 10, 7); // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5

int stateSwitch = 0;
long positionAbs = 200;
long bigPos = 1000;
int cycleCount = 2;
int currentCycle = 0;
const byte limitSwitchPin = A1;

void setup()
{
  pinMode(limitSwitchPin, INPUT_PULLUP);
  // Change these to suit your stepper if you want
  stepper.setMaxSpeed(100);
  stepper.setAcceleration(100);
  stepper.moveTo(-500);
}


void loop()
{

  bool limitSwitchActive = digitalRead(limitSwitchPin)==LOW || stepper.currentPosition() < -100;

  // If at the end of travel go to the other end
  switch (stateSwitch) {
    case 0: // moving CCW
      if (stepper.distanceToGo() == 0 // finished motion?
          || limitSwitchActive // active limit switch
         )
      {
        currentCycle += 1;
        if (currentCycle <= cycleCount) {
          stepper.moveTo(bigPos - positionAbs);
          stateSwitch = 1; // move CW
        } else {
          stateSwitch = 2; // go idle
        }

      }
      break;
    case 1: // moving CW
      if (stepper.distanceToGo() == 0) {
        stepper.moveTo(-positionAbs);
        stateSwitch = 0;
      }
      break;
    case 2: // IDLE
      ; // do nothing
      break;
  }
  stepper.run(); // move stepper as needed
}

(A more professional SM would use an enum States {CCW,CW,IDLE} stateSwitch = CCW; to make the code cleaner and more self-documenting.)

One could check a button within the IDLE state in order to reset the currentCount and restart.

The FULL2WIRE configuration will work, but it will require 4x the number of steps to cycle the STEP pin on a STEP/DIR driver:

@gundamzzpk - Here is a simulation for a single A4988 stepper motor driver. It might not work on your live system. It uses a square wave (50% duty cycle) with the option of single pulse. It does not use a library. It is non-blocking. Press the "limit switch" to change motor direction.

Files for WOKWI.COM

sketch.ino
// Stepper motor, no delay(), no library, non-blocking

byte dirPin    = 2;
byte stepPin   = 3;
byte ledPin    = 4;
byte buttonPin = 5;

bool stepNow   = 0;       // flag to indicate time to step
bool stepState = LOW;     // used to start a stepper pulse
bool currentButtonState;  // current button state (pressed/not pressed)
bool lastButtonRead;      // previous button reading

unsigned long previousMillis = 0;   // store last step
unsigned long interval = 2;         // (milliseconds) half-interval for stepping
unsigned long debounceTimeout = 50; // debounceTimeout
unsigned long timer = 0;            // measures button press time

void setup() {
  Serial.begin(9600);
  pinMode(dirPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  digitalWrite(dirPin, HIGH); // 1 = CW
  welcome();
}

void loop() {
  checkElapsedTime();
  stepMotor();
  readSwitch();
}

void readSwitch() {
  bool currentButtonRead = digitalRead(buttonPin);  // read button pin
  if (currentButtonRead != lastButtonRead) {        // if button pin reading changes...
    timer = millis();                               // ...start a timer
    lastButtonRead = currentButtonRead;             // ... and store current state
  }

  if ((millis() - timer) > debounceTimeout) {                   // if button read change was longer than debounceTimeout
    if (currentButtonState == HIGH && lastButtonRead == LOW) {  // ... and State NOT pressed while Button PRESSED

      // change stepper direction and light the LED
      digitalWrite(dirPin, !digitalRead(dirPin));
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    }
    currentButtonState = currentButtonRead;  // change the state
  }
}

void checkElapsedTime() { // for the stepper motor
  unsigned long currentMillis = millis(); // store the time
  if (currentMillis - previousMillis >= interval) { // compare elapsed time to interval
    previousMillis = currentMillis; // store last step time
    stepNow = 1; // flag to indicate it is time to step the motor
  }
}

void stepMotor() {
  if (stepNow) {
    stepNow = 0; // clear step flag

    // two methods to step the stepper...
    squarewave(); // HIGH for interval, LOW for interval
    // singlepulse(); // HIGH/LOW pulse then wait for next interval
  }
}

void squarewave() { // toggle the state
  stepState = !stepState;
  delay(10); // let the motor catch arrive at the next step
  digitalWrite(stepPin, stepState); // step the motor
}

void singlepulse() { // minimum requirement to step a stepper
  digitalWrite(stepPin, HIGH);
  delay(10); // let the motor arrive at the next step
  digitalWrite(stepPin, LOW);
}

void welcome() {
  Serial.println("Step and change direction using event timing, non-blocking, no library, square wave or single pulse.");
}
diagram.json
{
  "version": 1,
  "author": "Anonymous maker",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-arduino-nano", "id": "nano", "top": -33.6, "left": -38.9, "attrs": {} },
    {
      "type": "wokwi-stepper-motor",
      "id": "stepper1",
      "top": -230.41,
      "left": 89.82,
      "attrs": { "size": "8", "arrow": "pink" }
    },
    { "type": "wokwi-a4988", "id": "drv1", "top": -148.8, "left": 14.4, "attrs": {} },
    { "type": "wokwi-vcc", "id": "vcc1", "top": -200.84, "left": 67.2, "attrs": {} },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": -80.2,
      "left": 96,
      "attrs": { "color": "green" }
    }
  ],
  "connections": [
    [ "drv1:GND.2", "drv1:GND.1", "black", [ "h19.35", "v57.6" ] ],
    [ "nano:GND.2", "drv1:GND.1", "black", [ "v0" ] ],
    [ "drv1:STEP", "nano:3", "green", [ "h-19.2", "v38.4", "h67.2" ] ],
    [ "drv1:RESET", "drv1:SLEEP", "green", [ "h-9.6", "v9.6" ] ],
    [ "drv1:GND.2", "drv1:ENABLE", "black", [ "h19.35", "v-20.85", "h-76.8", "v11.33" ] ],
    [ "nano:2", "drv1:DIR", "green", [ "v-28.8", "h-67.2", "v-19.2" ] ],
    [ "vcc1:VCC", "drv1:VMOT", "red", [ "v0" ] ],
    [ "drv1:VMOT", "drv1:VDD", "red", [ "h9.75", "v57.6" ] ],
    [ "drv1:2B", "stepper1:A+", "green", [ "h0" ] ],
    [ "drv1:2A", "stepper1:A-", "green", [ "h0" ] ],
    [ "drv1:1A", "stepper1:B-", "green", [ "h0" ] ],
    [ "drv1:1B", "stepper1:B+", "green", [ "h0" ] ],
    [ "drv1:ENABLE", "drv1:MS1", "green", [ "h-9.6", "v9.6" ] ],
    [ "drv1:MS1", "drv1:MS2", "green", [ "h-9.6", "v9.6" ] ],
    [ "drv1:MS2", "drv1:MS3", "green", [ "h-9.6", "v9.6" ] ],
    [ "nano:GND.2", "btn1:2.l", "black", [ "v0" ] ],
    [ "nano:5", "btn1:1.l", "green", [ "v0" ] ]
  ],
  "dependencies": {}
}
1 Like