Help with Stepper and Momentary Switches (Circuit and Code)

I need help with with my first Arduino project that I'm working on, here are the relevant details:

Objective
I want to drive one stepper motor CW when I push momentary switch 1, and have it stop when I release it. I want a second switch to drive CCW when I push switch 2 and stop when I let off. I would also like to use a 10K potentiometer to control the speed of the motor when its driving.

Where I currently Stand
I currently have a circuit wired as shown below with two switches and no potentiometer yet. The circuit and code works as expected and I'm almost happy. The main issue is that I want to vary the speed of the motor, hence the potentiometer, but not sure exactly how to wire it and modify the code.

Hardware

  • Arduino Uno
  • TB6600 4A 9-42V Motor Driver
  • Nema 17 bipolar stepper motor (1.8deg and 1.8-2.0A)
  • 24v Power supply

Current Circuit
Note this image is showing a different driver, i am using TB6600 wired in a similar way.

Code

// define a constant value named stepPin and assign the value 9 to it - this value will not change during our code
// this assumes digital pin 9 of your Arduino is attached to the step input of your driver
#define stepPin 9
 
// define a constant value named dirPin and assign the value 8 to it - this value will not change during our code
// this assumes digital pin 8 of your Arduino is attached to the step input of your driver
#define dirPin 8
 
// define the pins on which we've put our N.O. buttons
#define button1 2
#define button2 3
 
// setup() loop, the Arduino only runs through this once
void setup() {
  // digital pins on the Arduino can only be either set as an output or input - in our case we want to send data to the driver, so we choose output
  pinMode(stepPin , OUTPUT);
  pinMode(dirPin , OUTPUT);
 
  // define our button pins as input pullup type - see http://arduino.cc/en/Tutorial/DigitalPins#.Uyphr4WN7q4
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
 
  // let's set an initial value of low to both our step and dir pins, we could easily write false or 0 instead of LOW
  digitalWrite(stepPin , LOW);
  digitalWrite(dirPin , LOW);
}
 
// loop() loop, the Arduino continuously cycles through this as fast as it can
void loop() {
 
  if (digitalRead(button1) == LOW && digitalRead(button2) == HIGH) { // if button1 is pressed and button2 is not pressed
    digitalWrite(dirPin, LOW); // move in the LOW direction
  } else if (digitalRead(button1) == HIGH && digitalRead(button2) == LOW) { // if btton1 is not pressed and button2 is pressed
    digitalWrite(dirPin, HIGH); // move in HIGH direction
  }
 
  if (digitalRead(button1) == LOW || digitalRead(button2) == LOW) { // if either button is pressed
 
    // set a HIGH value to our step pin, this turns the voltage on for that pin
    digitalWrite(stepPin , HIGH);
 
    // let's wait here for 1 millisecond
    delay(1);
 
    // let's set our step pin to false, this turns the voltage off for that pin and gives us the on/off cycle we need
    digitalWrite(stepPin , LOW);
 
    // wait another millisecond after which time we loop back to the beginning of the loop() loop
    delay(1);
 
  }
}

I think I have everything required, thanks for your help!

That is the part that steps the motor. How long that it takes for a step is controlled by the delay()s. Increase the delay() and the stepper slows. Use the analog reading of the pot to set the length of the delay().

Using delay() will limit the responsiveness of the code. Nothing else can happen during a delay(). Robin2's simple stepper code tutorial includes a way to step the stepper using non-blocking (no delay()) code.

And if you will want higher speeds you will run into the fact that the motor can not go from stop to full speed instantaneously. Then you need acceleration like the AccelStepper library provides.

Stepper motor basics.

Thanks for the reply, I'm struggling through this a bit but how about something like this if I were to add a potentiometer on pin 10?



// input pin for the potentiometer
int read_potentiometer = 10;

byte directionPin = 8;
byte stepPin = 9;

byte buttonCWpin = 2;
byte buttonCCWpin = 3;

boolean buttonCWpressed = false;
boolean buttonCCWpressed = false;

byte ledPin = 13;

unsigned long curMillis;
unsigned long prevStepMillis = 0;
//unsigned long millisBetweenSteps = 1; // milliseconds

//float millisBetweenSteps;
int value_potentiometer = 0; 

void setup() { 

     Serial.begin(9600);
     Serial.println("Starting Stepper Demo with millis()");

     pinMode(directionPin, OUTPUT);
     pinMode(stepPin, OUTPUT);
     pinMode(ledPin, OUTPUT);
     
     pinMode(buttonCWpin, INPUT_PULLUP);
     pinMode(buttonCCWpin, INPUT_PULLUP);
     
}

void loop() { 
    
    curMillis = millis();
    readButtons();
    actOnButtons();

    {
value_potentiometer = analogRead(read_potentiometer); 

   if (value_potentiometer >  0   ){
      delay_time=210-((value_potentiometer/5));  
    }
    else
    {
      delay_time=0;    // Minumum speed when it is 0
      }
    
}

void readButtons() {
    
    buttonCCWpressed = false;
    buttonCWpressed = false;
    
    if (digitalRead(buttonCWpin) == LOW) {
        buttonCWpressed = true;
    }
    if (digitalRead(buttonCCWpin) == LOW) {
        buttonCCWpressed = true;
    }
}

void actOnButtons() {
    if (buttonCWpressed == true) {
        digitalWrite(directionPin, LOW);
        singleStep();
    }
    if (buttonCCWpressed == true) {
        digitalWrite(directionPin, HIGH);
        singleStep();
    }
}

void singleStep() {
    if (curMillis - prevStepMillis >= delay_time) {
            // next 2 lines changed 28 Nov 2018
        //prevStepMillis += delay_time;
        prevStepMillis = curMillis;
        digitalWrite(stepPin, HIGH);
        digitalWrite(stepPin, LOW);
    }
}

Pin 10 is not an analog input on an Uno. You need to connect the wiper of the pot to an analog input (A0 - A5).

Did you try to compile the code? I did. You are missing a } to close the loop() function. Use the IDE autoformat tool (ctrl-t or Tools, Auto Format) and you will see it. Fix that and you have

'delay_time' was not declared in this scope

Declare a global variable
unsigned long delay_time = 200; // change to suit
And the code compiles.

I fixed the mistakes and errors and breadboarded the circuit with my Uno, CNC shield, stepper, 2 switches and pot. I had to change pin numbers to suit my setup, fix the code with your stuff.
The code works OK. If you hold a switch the motor rotates the direction according to which switch and the speed is adjusted by the pot. I put reading the pot in its own timed function and print the delay_time to track it.

Aside from a couple of mistakes I am impressed with how you mixed and adapted the code. Nice job.

Here is my tested code, make sure to change pin numbers and baud rate:

// input pin for the potentiometer
int read_potentiometer = A0;

const byte directionPin = 5;
const byte stepPin = 2;
const byte enablePin = 8; // for the cnc shield
const byte buttonCWpin = 9;
const byte buttonCCWpin = 10;

boolean buttonCWpressed = false;
boolean buttonCCWpressed = false;

byte ledPin = 13;

unsigned long curMillis;
unsigned long prevStepMillis = 0;
//unsigned long millisBetweenSteps = 1; // milliseconds
unsigned long delay_time = 200;

//float millisBetweenSteps;
int value_potentiometer = 0;

void setup()
{
   Serial.begin(115200);
   Serial.println("Starting Stepper Demo with millis()");

   pinMode(directionPin, OUTPUT);
   pinMode(stepPin, OUTPUT);
   pinMode(enablePin, OUTPUT);
   digitalWrite(enablePin, LOW);
   pinMode(ledPin, OUTPUT);

   pinMode(buttonCWpin, INPUT_PULLUP);
   pinMode(buttonCCWpin, INPUT_PULLUP);
}

void loop()
{
   curMillis = millis();
   readButtons();
   actOnButtons();
   readPot();
}

void readPot()
{
   static unsigned long timer = 0;
   unsigned long interval = 200;
   if (millis() - timer >= interval)
   {
      timer = millis();
      value_potentiometer = analogRead(read_potentiometer);
      if (value_potentiometer >  0   )
      {
         delay_time = 210 - ((value_potentiometer / 5));
      }
      else
      {
         delay_time = 0;  // Minumum speed when it is 0
      }
      Serial.print("delay time = ");
      Serial.println(delay_time);
   }
}

void readButtons()
{

   buttonCCWpressed = false;
   buttonCWpressed = false;

   if (digitalRead(buttonCWpin) == LOW)
   {
      buttonCWpressed = true;
   }
   if (digitalRead(buttonCCWpin) == LOW)
   {
      buttonCCWpressed = true;
   }
}

void actOnButtons()
{
   if (buttonCWpressed == true)
   {
      digitalWrite(directionPin, LOW);
      singleStep();
   }
   if (buttonCCWpressed == true)
   {
      digitalWrite(directionPin, HIGH);
      singleStep();
   }
}

void singleStep()
{
   if (curMillis - prevStepMillis >= delay_time)
   {
      // next 2 lines changed 28 Nov 2018
      //prevStepMillis += delay_time;
      prevStepMillis = curMillis;
      digitalWrite(stepPin, HIGH);
      digitalWrite(stepPin, LOW);
   }
}

Thank you so much! I corrected the pins and the code works with the following observations:

  1. The maximum speed (with the potentiometer adjusted) is slower than my previous code without the potentiometer, I tried making some adjustments in the code such as making the delay time smaller but it would cause the stepper to not operate throughout the range of the potentiometer. Can I change some existing parameters in the code or do I need to use the accelstepper library?

  2. Without running the motor, it has very little holding torque. I can spin the motor shaft without too much effort. Far less than what I noticed on different sketches.

  3. When operating the motor, it appears to be skipping steps periodically (likely related to observation number 2?). Any idea why this is?

When us showing a schematic and wiring image of your circuit, it should be the one you are using.

Don’t power the motor from the Vin pin on the Arduino.

That is where you calculate the delay_time from the pot input (0 -1023). What is the maximum speed, in steps per second, that you will want. Modify the math in that part to get that speed. At some point, if you want more speed, you can switch to using micros() for the step timing:

  if (curMillis - prevStepMillis >= delay_time)
   {
      // next 2 lines changed 28 Nov 2018
      //prevStepMillis += delay_time;
      prevStepMillis = curMillis;

Replace millis with micros, create and use a currMicros variable for timing.

What is the coil current setting on the stepper driver set to? What are the specifications of your motor power supply?

Are you running microstepping? It is not unusual for an unloaded stepper motor to resonate. When that happens it skips steps. Microstepping can help to eliminate the behavior. Try 4x microstepping. That, of course, means more steps for any given speed.

If you want to start the motor at high speed, acceleration becomes necessary. No motor can go from zero to high speed instantaneously. AccelStepper library is one library that allows acceleration.

Thanks, thats a fair point I'll try to make an updated schematic.

I am not powering the motor from the Arduino, its coming from my power supply.

Thanks this is helpful, I tried playing with the delay_time but was having some issues with the motor operating at the full pot range...but now that you have explained it more I have some other ideas. The micros option is a good suggestion too.

The coil setting on the driver is set to 2 amps. The power supply is pretty hefty (was used for another application) at 24V 16.7A. Motor is Nema 17, 1.8 degree step angle, 0.68Nm holding torque, 1.8-2.0A rated current.

The driver is currently set to 200 pulse/revolution. I will increase this as suggest and see how it smooths out.

I really appreciate your help!

Here is a quick schematic of my circuit along with my most recent code. Now everything works as expected except the speed control. As I rotate the pot, the speed changes but will go from fast to slow while turning in one direction, instead gradually going faster going one direction and slow in the other. I assume its something with the readPot loop.

// input pin for the potentiometer
int read_potentiometer = A0;

const byte directionPin = 8;
const byte stepPin = 9;
const byte buttonCWpin = 2;
const byte buttonCCWpin = 3;

boolean buttonCWpressed = false;
boolean buttonCCWpressed = false;


unsigned long curMicros;
unsigned long prevStepMicros = 0;
//unsigned long MicrosBetweenSteps = 1; // Microseconds
unsigned long delay_time = 200;

//float MicrosBetweenSteps;
int value_potentiometer = 0;

void setup()
{
   Serial.begin(115200);
   Serial.println("Starting Stepper Demo with micros()");

   pinMode(directionPin, OUTPUT);
   pinMode(stepPin, OUTPUT);

   pinMode(buttonCWpin, INPUT_PULLUP);
   pinMode(buttonCCWpin, INPUT_PULLUP);
}

void loop()
{
   curMicros = micros();
   readButtons();
   actOnButtons();
   readPot();
}

void readPot()
{
   static unsigned long timer = 0;
   unsigned long interval = 200;
   if (micros() - timer >= interval)
   {
      timer = micros();
      value_potentiometer = analogRead(read_potentiometer);
      if (value_potentiometer >  0   )
      {
         delay_time = 210 - ((value_potentiometer / 5));
      }
      else
      {
         delay_time = 0;  // Minumum speed when it is 0
      }
      Serial.print("delay time = ");
      Serial.println(delay_time);
   }
}

void readButtons()
{

   buttonCCWpressed = false;
   buttonCWpressed = false;

   if (digitalRead(buttonCWpin) == LOW)
   {
      buttonCWpressed = true;
   }
   if (digitalRead(buttonCCWpin) == LOW)
   {
      buttonCCWpressed = true;
   }
}

void actOnButtons()
{
   if (buttonCWpressed == true)
   {
      digitalWrite(directionPin, LOW);
      singleStep();
   }
   if (buttonCCWpressed == true)
   {
      digitalWrite(directionPin, HIGH);
      singleStep();
   }
}

void singleStep()
{
   if (curMicros - prevStepMicros >= delay_time)
   {
      //prevStepMicros += delay_time;
      prevStepMicros = curMicros;
      digitalWrite(stepPin, HIGH);
      digitalWrite(stepPin, LOW);
   }
}

Hello
change the power supply pins at the poti.

Just gave that a try, however it didn't change the issue.

You changed the timer in the readPot() function to micros. It is not necessary to read the pot 5000 times a second.
Change the readPot() function back to millis() so that you can read and follow the value of delay_time in serial monitor.

static unsigned long timer = 0;
   unsigned long interval = 200;
   if (millis() - timer >= interval)
   {
      timer = millis();
      value_potentiometer = analogRead(read_potentiometer);
      if (value_potentiometer >  0   )
      {
         delay_time = 210 - ((value_potentiometer / 5));
      }
      else
      {
         delay_time = 0;  // Minumum speed when it is 0
      }
      Serial.print("delay time = ");
      Serial.println(delay_time);
   }
}

That line of code determines the speeds of the stepper. Consider what that line of code is doing. Your range of values of delay_time is going to be from 210 to 0. That is now in microseconds which works out to 5000 steps per second to up around infinity. You need to adjust that equation to give you the range of speeds that you want.

The delay_time code makes sense to me, I was leaving that alone until everything was looking better. I changed the readPot function back to millis and that was an improvement. However there is only a short range that the pot can be adjusted before the stepper just starts to hum without turning. This occurs at either end of the pot range of travel.

I can play with the delay_time function and this seems to help a bit, but its still pretty fussy as when the stepper will work properly.