Stepper Motor Control with Momentary Switch

Hello everyone,

I recently started my first project. I am building a workbench that has a table that can hide away inside to store my 3d printer when not in use. It will use 4 NEMA23 steppers each controlled with a TB6600 driver. It will need to move the steppers a specific number of turns that are yet to be determined. I was able to piece together same code to run each stepper. I am having some issues with making a switch work. Here is the code I have:

//definepins for buttons
#define buttonPinUp 11
#define buttonPinDown 12
//define pins for direction output
#define directionPin1 2
#define directionPin2 4
#define directionPin3 6
#define directionPin4 8
//define pins for step output
#define stepPin1 3
#define stepPin2 5
#define stepPin3 7
#define stepPin4 9
//define steps per revolution 1/32 steps = 6400
#define stepsPerRevolution 6400

void setup() {
  // put your setup code here, to run once:
  pinMode (buttonPinUp, INPUT_PULLUP);
  pinMode (buttonPinDown, INPUT_PULLUP);
  pinMode (directionPin1, OUTPUT);
  pinMode (stepPin1, OUTPUT);
  pinMode (directionPin2, OUTPUT);
  pinMode (stepPin2, OUTPUT);
  pinMode (directionPin3, OUTPUT);
  pinMode (stepPin3, OUTPUT);
  pinMode (directionPin4, OUTPUT);
  pinMode (stepPin4, OUTPUT);
  digitalWrite (stepPin1 , LOW);
  digitalWrite (directionPin1 , LOW);
  digitalWrite (stepPin2 , LOW);
  digitalWrite (directionPin2 , LOW);
  digitalWrite (stepPin3 , LOW);
  digitalWrite (directionPin3 , LOW);
  digitalWrite (stepPin4 , LOW);
  digitalWrite (directionPin4 , LOW);
}

void loop() {
  // put your main code here, to run repeatedly:
  for (int i = 0; i < 4 * stepsPerRevolution; i++) {
    if (digitalRead (buttonPinUp) == LOW && digitalRead (buttonPinDown) == HIGH) {
      digitalWrite (directionPin1, LOW) ;
      digitalWrite (stepPin1, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin1, LOW) ;
      digitalWrite (directionPin2, LOW) ;
      digitalWrite (stepPin2, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin2, LOW) ;
      digitalWrite (directionPin3, LOW) ;
      digitalWrite (stepPin3, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin3, LOW) ;
      digitalWrite (directionPin4, LOW) ;
      digitalWrite (stepPin4, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin4, LOW) ;
    } else if (digitalRead (buttonPinUp) == HIGH && digitalRead(buttonPinDown) == LOW) {
      digitalWrite (directionPin1, HIGH) ;
      digitalWrite (stepPin1, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin1, LOW) ;
      digitalWrite (directionPin2, HIGH) ;
      digitalWrite (stepPin2, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin2, LOW) ;
      digitalWrite (directionPin3, HIGH) ;
      digitalWrite (stepPin3, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin3, LOW) ;
      digitalWrite (directionPin4, HIGH) ;
      digitalWrite (stepPin4, HIGH) ;
      delayMicroseconds (25) ;
      digitalWrite (stepPin4, LOW) ;
    }

  while (1){
  }
  }
}



There may be a more elegant solution to the code, but this is what I was able to piece together from a few youtube videos, and looking at code in some forum posts to get a general idea of what each line does.

I can get the motors to move without the code for the buttons, but I cant get the buttons to work . I would like to have the motors move to position on a single press of the momentary switch (press and release "up" button, motors turn CW X amount of steps and hold, press and release "down" button, motors turn CCW X amount of steps and hold). Is that possible?

Did you develop the program in steps and test each addition? If not, extract or write a single program that only has the switch code. Test that alone and fix the logic if mistakes are found!

Thanks I'll try that. I kind of wrote it in steps. I first just got it to turn one motor one full revolution in each direction, then I got it working on several pin sets to run all 4 motors. Now I have to figure out how to make the button activate the motors.

Good plan! Always use serial.Print() to display the values being received and to drop cookie crumbs showing the flow of logic in your code.

If Understand the mechanical construction right you have four steppermotors each in one edge of a rectangle plate to rotate a trapezoidal thread for moving the plate up and down.

This means all four motors shall rotate in perfect syncronicity.
This can be achieved by connecting all four step-inputs to one IO-pin
and all four direction-inputs to one IO-pin.

The logic control-inputs of stepperdrivers draw a rather small current.
Though it depends on if there are optocouplers at the input and how much current the optocoupler LED is drawing.

If you have a datasheet of your stepper-drivers please post it.

For moving the plate up and down you need a state-change-detection and a boolean flag-variable which determines moving up or down.

There are multiple stepper-libraries that make life easier than creating the step-pulses from scratch.

I recommend the MobaTools-Library which can be installed from the library-manager of the Arduino-IDE.

You simply write a function-call like

myStepper.moveTo( upPosition );
myStepper.moveTo( downPosition );

and the rest os done by the library code internally

The Mobatools have the advantage that the step-pulses ae created based on a timer interrupt which means your code is free to do additional things "in parallel" to the stepper-motors rotating.

There is also a library called toggle.h

Here is a demo-code that detects If a button is pressed a specified number of milliseconds
If yes return true if press was too short return false

What the code does if button is pressed down take action

#include <Toggle.h>

const byte buttonPin   = 12;
const byte ledUpPin    =  7;
const byte ledUDownPin =  2;
const unsigned int ms = 1000;

boolean IsInPositionUp = true;

Toggle sw1(buttonPin);

void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  digitalWrite(ledUpPin,   LOW);
  digitalWrite(ledUDownPin, LOW);

  pinMode(ledUpPin,    OUTPUT);
  pinMode(ledUDownPin, OUTPUT);
  sw1.begin(buttonPin);
}

void loop() {
  sw1.poll(); // must be called very often for prompt reaction to buttonpress

  if ( sw1.onPress() ) {  // if pressing down is detected
    Serial.println();  
    Serial.println("pressing down detected");
    if (IsInPositionUp == true) {
      downPosition(); // excecute lines defined in "void "downPosition()"
    }
    else {
      upPosition();    // excecute lines defined in "void "upPosition()"
    }
  }
}



void upPosition() {
  Serial.println("entering function upPosition");
  digitalWrite(ledUpPin,   HIGH);
  digitalWrite(ledUDownPin, LOW);
  IsInPositionUp = true;
  Serial.println("exiting function upPosition");
} // end of definition of function "upPosition()"


void downPosition() {
  Serial.println("entering function downPosition");
  digitalWrite(ledUpPin,   LOW);
  digitalWrite(ledUDownPin, HIGH);
  IsInPositionUp = false;  // which means is in position down
  Serial.println("exiting function downPosition");
} // end of definition of function "downPosition()"

This code shows how to use

  • libraries
  • serial printing to make visible what the code is doing
  • defining a function for each sub-unit of code that do belong to a certain action

Here is a WOKWI-Simulation for the code

@StefanL38 - the printing feedback would make 200 times more sense if you said

"pressed button detected"

or

"button press detected"

Either tells a better truth, and neither overloads the concept of "down" which is part of the up/down thing you got going on.

Just sayin'.

a7

look this over

  • recognizes debounced button presses
  • synchronously steps each motor
  • check motor direction
// server driven table

const byte PinBut  [] = { 11, 12 };
const byte PinDir  [] = { 2, 4, 6, 8 };
const byte PinStep [] = { 3, 5, 7, 9 };

const int  StepsPerRevolution = 6400;       // Capitalize Constants

inline void delayStep () { delayMicroseconds (25); }

// -----------------------------------------------------------------------------
const int  Nmotor = sizeof (PinStep);
const int  Nbut   = sizeof (PinBut);

byte butState [Nbut];

enum { B_Up, B_Down };      // indices into PinBut

enum { CW = LOW, CCW = HIGH};

// -----------------------------------------------------------------------------
void
step4 (
    int nStep,
    int dir )
{
    for (int n = 0; n < Nmotor; n++)
        digitalWrite (PinDir [n], dir);

    while (nStep--)  {
        for (int n = 0; n < Nmotor; n++)
            digitalWrite (PinStep [n], HIGH);
        delayStep    ();

        for (int n = 0; n < Nmotor; n++)
            digitalWrite (PinStep [n], LOW);
        delayStep    ();
    }
}

// -----------------------------------------------------------------------------
bool
isPressed (
    int idx )
{
    byte but = digitalRead (PinBut [idx]);
    if (butState [idx] != but)  {
        butState [idx]  = but;
        delay (20);
        return LOW == but;
    }
    return false;
}

// -----------------------------------------------------------------------------
void loop ()
{
    if (isPressed (B_Up))  {
        Serial.println ("up");
        step4 (4 * StepsPerRevolution, CW);
    }

    else if (isPressed (B_Down))  {
        Serial.println ("down");
        step4 (4 * StepsPerRevolution, CCW);
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    for (int n = 0; n < Nbut; n++)  {
        pinMode (PinBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (PinBut [n]);
    }

    for (int n = 0; n < Nmotor; n++)  {
        pinMode (PinDir  [n], OUTPUT);
        pinMode (PinStep [n], OUTPUT);
    }
}

Here is your wanted functionality with the mobatools library

The code uses self-explaining names and has comments for further explanation
standard things like debouncing buttons is done internally by the MobaTools-library
Code includes serial printing to make visible what the code is doing

#include <MobaTools.h>
// Stepper connections - Please adapt to your own needs.
const byte dirPin    = 2;
const byte stepPin   = 3;

const byte buttonPin[] = {7} ; 
const byte buttonCnt = sizeof(buttonPin);                  // number of buttons/positions

const int debounceTime =   20; // 20 milliseconds
const int pressingTime = 1000; // 1000 milliseconds

MoToButtons myButton( buttonPin, buttonCnt, debounceTime, pressingTime );

const int stepsPerRev = 200;    // Steps per revolution - may need to be adjusted

long upStepPosition   = 500;
long downStepPosition =    0;

MoToStepper stepper1( stepsPerRev, STEPDIR );  // create a stepper instance

boolean IsInPositionUp = true;


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  stepper1.attach( stepPin, dirPin );
  stepper1.setSpeed( 3000 );             
  stepper1.setRampLen( stepsPerRev / 2); // Ramp length is 1/2 revolution
}

void loop() {
  myButton.processButtons();  // Check buttonstate

  if ( myButton.pressed(0) ) {  // button numbering starts at zero. 1 button check button number 0
    // if pressing down is detected
    Serial.println();
    Serial.println("pressing down detected");
    if (IsInPositionUp == true) {
      moveToDownPosition(); // excecute lines defined in "void "downPosition()"
    }
    else {
      moveToUpPosition();    // excecute lines defined in "void "upPosition()"
    }
  }
}




void moveToUpPosition() {
  Serial.println("entering function upPosition");
  Serial.print("move motor to position ");
  Serial.println(upStepPosition);
  
  stepper1.moveTo(upStepPosition);
  IsInPositionUp = true;
  Serial.println("exiting function upPosition");
} // end of definition of function "upPosition()"


void moveToDownPosition() {
  Serial.println("entering function downPosition");
  stepper1.moveTo(downStepPosition);
  Serial.print("move motor to position ");
  Serial.println(downStepPosition);
  IsInPositionUp = false;  // which means is in position down
  Serial.println("exiting function downPosition");
} // end of definition of function "downPosition()"

WOKWI-Simulation

Consider changing isPressed() to properly use digitalRead(). The you'd have a consistent boolean variable where true means down is the button or when returned down did the button go.

bool isPressed(int idx )
{
    byte but = digitalRead (PinBut [idx]) == LOW;  // switch normally reads HIGH

    if (butState [idx] != but)  {
        butState [idx]  = but;
        delay (20);
        return but;
    }
    return false;
}

I'd also use some more of my lifetime allowance of characters on all the variable names, eliminate returning from within the body of the function. And so forth.

a7

@StefanL38 has blocked me, so he didn't see my idea of writing something different here. Or why it's an easy improvement.

 Serial.println("button is pressed.");

a7

Thank you! This seems to be working for what I need it to do. The only thing I cant figure out is the

stepper1.setSpeed( 3000 );          

under void setup doesn't seem to change the speed at which the motor turns, I know these aren't really designed to move fast, but with a 4:1 gear ratio, its going to move painfully slow. Whatever value I put in there, nothing changes.

This depends on what microstepping you have set.
The Mobatools have limits in the maximum stepper-frequency

the documentation of the mobatools say

So to what microstepping did you set your stepper-drivers?
fullstep= halfstep? 1/4 step? 1/8 step? 1/16 step
higher microstepping means higher steppulsefrequency for a certain rpm

Also the acceleration / deccelartion ramplength has impact on the rpm
did you test with a run of let's say 10000 steps?

I guess half step-mode is precise enough for your application
This results in a rpm on output shaft 1:4 gear
2500 steps per second / 400 stps per rotation * 60 second/minute / 4 gear ratio = 93.75 rpm

If this is too slow you might need to use step-pulse creation direct in the code with delaymicrosecods()

Or using a timer-interrupt.

It is always recommended to post the most important technical data in your very first post
especially the exact type of microcontroller that you use

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.