Stop the rotation of stepper motor when the key is pressed

Greetings, everyone. I am seeking assistance with my straightforward project. I've developed a Python program that captures an image for classification and an Arduino program for a stepper motor using an A4988 motor driver, which are interconnected. My objective is to have the stepper motor continuously rotate as soon as I initiate my Python program. It should only cease rotation when I press the "c" key on the keyboard to capture an image. After image capture, the stepper motor should resume its rotation once the image classification process is complete. Currently, I've successfully achieved continuous rotation of the stepper motor, but it doesn't stop when I press the "c" key. Is there a method to halt the stepper motor's rotation when I press the "c" key? Could you please provide a sample of the corresponding code? This would greatly benefit my project.

Hi @Newbie1.1.1,

in general you had to do the following:

  • Let your Python script send via Serial to the Arduino

    • a "stop" command when "c" was pressed
    • a "continue" command when the image classification was done (or after image capture?)
  • Add Serial input to your Arduino sketch that

    • stops rotation when the "stop" command was received
    • continues rotating when "continue" command was received

For a direct support it would be required that you post your code (at least the Arduino sketch) ...

Good luck!
ec2021

The Serial Input Basics tutorial on this forum is a fine introduction, with usable code.

Show us your stepper code. Read the forum guidelines to see how to properly post code and some good information on making a good post.
Use the IDE autoformat tool (ctrl-t or Tools, Auto format) before posting code in code tags.

If your stepper code blocks ( uses for or while loops and/or delay()) the code will be unresponsive. Use the non-blocking movement functions of a library like AccelStepper of MobaTools stepper. Or write your own non-blocking stepper code. Robin2's simple stepper program tutorial has example code showing how using millis() or micros() for timing

Here is a demo using the MobaTools stepper library and methods from Robin2's serial input basics tutorial to control a stepper via the serial port. Install the MobaTools library using the IDE library manager.

For this demo, send commands with the serial monitor. Writing Python code is beyond the scope of the Arduino forum.

// simple serial stepper control by groundFungus

#include <MobaTools.h>

const byte stepPin = 2;
const byte dirPin = 5;
const byte enablePin = 8;

const byte numChars = 10;
char receivedChars[numChars];
char stepperCommand; 

const unsigned int motorStepsPerRev = 200;  // 
const unsigned int microstepMultiplier = 4;  // 
const int STEPS_REVOLUTION = motorStepsPerRev * microstepMultiplier;

MoToStepper stepper( STEPS_REVOLUTION, STEPDIR );

boolean newData = false;

void setup()
{
   Serial.begin(115200);

   stepper.attach( stepPin, dirPin );
   stepper.attachEnable(enablePin, 0, 1);
   stepper.setSpeedSteps(10000);  // 1000 steps per second
   stepper.setRampLen(10);
   stepper.setZero();


   Serial.println("Enter 's' to stop motor, 'f' motor forward, 'r' motor reverse");
   Serial.println();

   stepper.setZero();
   stepper.rotate(1);  // start motor movement
}

void loop()
{
   recvWithEndMarker();
   showNewData();
   if (newData)
   {
      actOnData();
   }

} // ################4444444444444$$$$$$$$$$$$$&&&&&&&&&&&&&&&&

void actOnData()
{
   stepperCommand = receivedChars[0];
   if (stepperCommand == 's')
   {
      stepper.rotate(0);
   }
   else  if (stepperCommand == 'f')
   {
      stepper.rotate(1);
   }
   else  if (stepperCommand == 'r')
   {
      stepper.rotate(-1);
   }
   newData = false;
}


void recvWithEndMarker()
{
   static byte ndx = 0;
   char endMarker = '\n';
   char rc;

   while (Serial.available() > 0 && newData == false)
   {
      rc = Serial.read();
      if (rc == '\r') // ignore carruage return
      {
         return;
      }
      if (rc != endMarker)
      {
         receivedChars[ndx] = rc;
         ndx++;
         if (ndx >= numChars)
         {
            ndx = numChars - 1;
         }
      }
      else
      {
         receivedChars[ndx] = '\0'; // terminate the string         
         ndx = 0;
         newData = true;
      }
   }
}

void showNewData()
{
   if (newData == true)
   {
      Serial.print("This just in ... ");
      Serial.println(receivedChars);
      //newData = false;
   }
}


Just to give you another example (see @groundFungus for the stepper control):

Python code to receive/send data to Arduino:

import serial
import keyboard

# Change comPort to the appropriate
# port your board is using
# e.g. "com3" for Windows
# or e.g. " /dev/ttyUSB0" for Linux
comPort = "com3"

# Set baudrate and the timeout of the serial read operation
# timeout=0 provides non blocking activity
# so that CTRL-C will stop the the script immediately
arduino = serial.Serial(comPort, 115200, timeout=0)

# Flush the serial input buffer
arduino.flushInput()

# The following lines will read from serial and
# - if a character is received - print it to console
# You can press "S" for Stop and "G" for Go/Continue commands
# as decoded in the appropriate Arduino script
# To stop the script press CTRL-C on the keyboard
# This will create a keyboard interrupt
# and return to the OS or IDLE shell


Pressed = False;

while True:
        byteIn = arduino.read()
        if len(byteIn)!= 0:
          print(byteIn.decode(),end='')
        if  keyboard.is_pressed("S"):
            arduino.write(bytes("S", 'utf-8'))
            while keyboard.is_pressed("S"):
                #Wait for Realease
                Pressed = True;
        if  keyboard.is_pressed("G"):
            arduino.write(bytes("G", 'utf-8'))
            while keyboard.is_pressed("G"):
                #Wait for Release
                Pressed = True;

and a simple Arduino script:

void setup() {
  Serial.begin(115200);
  Serial.println("Ready");
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    switch(c){
     case 'S':
     case 's': Serial.println("Stop!");
         break;
     case 'G':
     case 'g': Serial.println("Go!");
         break;
    }
  }
}

Good luck and have fun!

Hi! Thank you for your reply. I apologize for not including the code earlier. Here are the codes.

Here's the snippet of Python code that sends a command to the Arduino. (Please note that the stepper motor is intended to keep rotating continuously until the user presses the "c" key to capture an image for classification.)

import serial

ser = serial.Serial("COM3", 9600)
image_file_name = "capture.jpg"
stepper_motor_running = False  # Variable to track the stepper motor status

# Create an if-else condition to check if the stepper motor is starting to rotate or not
# If the stepper motor does not start rotating, it should send a command to the Arduino to rotate it
# Then if it is already rotating, proceed to next process

while True:
    
    if imageCapture == 0:
        ret, frame = cap.read()

        # If the frame was read successfully
        if ret:
            # Display the frame in the window
            cv2.imshow('Camera Feed', frame)
            if not stepper_motor_running:
                ser.write(b's\n') # To start the rotation of stepper motor
                stepper_motor_running = True

            # Check if the user wants to capture an image (press 'c' key)
            key = cv2.waitKey(1)
            if key == ord('c'):
                ser.write(b'p\n') # To stop the rotation of stepper motor when "c" key was pressed
                print('Updating Image')
                cv2.imwrite(image_file_name, frame)
                print('Image Updated')
                imageCapture = 1
    else:
# In the 'else' section, the image classification process takes place, and once it's completed, the stepper motor should continue its rotation. And the cycle of this program repeats accordingly.

And here is the Arduino code that I created:

#include <AccelStepper.h>

#define STEPS_PER_REVOLUTION 1000  // Change this to match your stepper motor's specification
#define DIR_PIN 8
#define STEP_PIN 9

AccelStepper mystepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

void setup() {
  mystepper.setMaxSpeed(2000);     // Set the maximum speed of the stepper motor (adjust as needed)
  mystepper.setAcceleration(200);  // Set the acceleration of the stepper motor (adjust as needed)
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');

    if (command.startsWith("s")) {
      // Move stepper motor for 3 seconds
      mystepper.moveTo(STEPS_PER_REVOLUTION);
      mystepper.runToPosition();
    }
    if (command.startsWith("p")) {
      mystepper.stop();
      mystepper.setCurrentPosition(0);
    }
  }
}


Hi! Thank you for your reply. I apologize for not including the code earlier. Here are the codes.

Here's the snippet of Python code that sends a command to the Arduino. (Please note that the stepper motor is intended to keep rotating continuously until the user presses the "c" key to capture an image for classification.)

import serial

ser = serial.Serial("COM3", 9600)
image_file_name = "capture.jpg"
stepper_motor_running = False  # Variable to track the stepper motor status

# Create an if-else condition to check if the stepper motor is starting to rotate or not
# If the stepper motor does not start rotating, it should send a command to the Arduino to rotate it
# Then if it is already rotating, proceed to next process

while True:
    
    if imageCapture == 0:
        ret, frame = cap.read()

        # If the frame was read successfully
        if ret:
            # Display the frame in the window
            cv2.imshow('Camera Feed', frame)
            if not stepper_motor_running:
                ser.write(b's\n') # To start the rotation of stepper motor
                stepper_motor_running = True

            # Check if the user wants to capture an image (press 'c' key)
            key = cv2.waitKey(1)
            if key == ord('c'):
                ser.write(b'p\n') # To stop the rotation of stepper motor when "c" key was pressed
                print('Updating Image')
                cv2.imwrite(image_file_name, frame)
                print('Image Updated')
                imageCapture = 1
    else:
# In the 'else' section, the image classification process takes place, and once it's completed, the stepper motor should continue its rotation. And the cycle of this program repeats accordingly.

And here is the Arduino code that I created:

#include <AccelStepper.h>

#define STEPS_PER_REVOLUTION 1000  // Change this to match your stepper motor's specification
#define DIR_PIN 8
#define STEP_PIN 9

AccelStepper mystepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

void setup() {
  mystepper.setMaxSpeed(2000);     // Set the maximum speed of the stepper motor (adjust as needed)
  mystepper.setAcceleration(200);  // Set the acceleration of the stepper motor (adjust as needed)
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');

    if (command.startsWith("s")) {
      // Move stepper motor for 3 seconds
      mystepper.moveTo(STEPS_PER_REVOLUTION);
      mystepper.runToPosition();
    }
    if (command.startsWith("p")) {
      mystepper.stop();
      mystepper.setCurrentPosition(0);
    }
  }
}

That's a good step forward!

Now separate the control of the stepper motor from reading the serial data:

  • E.g. create a variable boolean stepperOn
  • if you receive a go signal set stepperOn to true
  • if you receive a stop command
    • stop the stepper
    • set stepperOn to false
  • Directly In loop() place
if (stepperOn) {
  // Do what's required to keep the stepper rotating
}

Hope that's understandable... :wink:

Hi ! Can you provide the sample code for that? I am quite confused. Thank you in advance. :blush:

Here is an example how it should work:

/*
   Forum: https://forum.arduino.cc/t/stop-the-rotation-of-stepper-motor-when-the-key-is-pressed/1164919/8
   Wokwi: https://wokwi.com/projects/374954917803485185

*/

#include <AccelStepper.h>

#define STEPS_PER_REVOLUTION 1000  // Change this to match your stepper motor's specification
#define DIR_PIN 8
#define STEP_PIN 9

constexpr int maxSpeed = 2000;  // Set the maximum speed of the stepper motor (adjust as needed)
constexpr int runAtSpeed = 1000;  // Set the speed of the stepper motor (adjust as needed)

AccelStepper mystepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

boolean stepperOn = false;

void setup() {
  mystepper.setMaxSpeed(maxSpeed);    
  Serial.begin(115200);
  Serial.println("Begin");
}


void loop() {
  if (Serial.available() > 0) {
    char command = Serial.read();
    if (command == 's') {
      // Move stepper motor for 3 seconds
      Serial.println("Start received");
      mystepper.setSpeed(runAtSpeed);       
      stepperOn = true;
    }
    if (command == 'p') {
      Serial.println("Stop received");
      mystepper.stop();
      stepperOn = false;
    }
  }
  if (stepperOn) {
    mystepper.runSpeed();
  }
}

For a simulation see Wokwi: https://wokwi.com/projects/374954917803485185

The command you used in your sketch to control the stepper are blocking the loop() until the position has been reached.

In my sketch the stepper lib does not block the loop() (which is a very significant issue in a lot of sketches!) and allows to start/stop at almost any time (from the point of view of a human being ... :wink: ).

The part that reads from Serial ignores all other characters except 's' and 'p'.

Thank you for your help. It works perfectly fine. Thank you very much

Great! Would be nice if you mark this thread as solved for others...

Regards
ec2021

Noted

Hello! Thank you for the solution you provided for my concern. It helped me a lot and solved my problem regarding starting and stopping the stepper motor when a key was pressed.

However, I have another concern. I want to add that at the beginning of the program, a push button must be pressed to initialize the rotation of the stepper motor. After that, the next process will involve pressing a key to stop the stepper motor by sending a command from the Python code ('s' and 'p' commands). Here's the Arduino code you provided, with additional code for the push button, but when I pressed the push button, the stepper motor did not start rotating.

#include <AccelStepper.h>

#define STEPS_PER_REVOLUTION 1000  // Change this to match your stepper motor's specification
#define DIR_PIN 8
#define STEP_PIN 9

const int pushButtonPin = 12;  // Replace with the actual pin connected to the push button

constexpr int maxSpeed = 2000;    // Set the maximum speed of the stepper motor (adjust as needed)
constexpr int runAtSpeed = 1000;  // Set the speed of the stepper motor (adjust as needed)

AccelStepper mystepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

boolean stepperOn = false;

void setup() {
  mystepper.setMaxSpeed(maxSpeed);
  pinMode(pushButtonPin, INPUT_PULLUP);
  Serial.begin(115200);
  Serial.println("Begin");
}


void loop() {
  int buttonState = digitalRead(pushButtonPin);

  if (buttonState == LOW) {
      Serial.println("Button pressed");
      mystepper.setSpeed(runAtSpeed);
      stepperOn = true;
    }
  
  if (Serial.available() > 0) {
    char command = Serial.read();
    if (command == 's') {
      // Move stepper motor for 3 seconds
      Serial.println("Start received");
      mystepper.setSpeed(runAtSpeed);
      stepperOn = true;
    }
    if (command == 'p') {
      Serial.println("Stop received");
      mystepper.stop();
      stepperOn = false;
    }
  }
  if (stepperOn) {
    mystepper.runSpeed();
  }
}

Your button is wired wrong for that code. Wire one side of the button to ground and the diagonal terminal to the input pin. Remove the resistor and connection to Vcc.

1 Like

I already did that but nothing happened. So I searched on the internet for the wiring of the push button to Arduino.

Hi,

I copied the sketch from post 15 to Wokwi and changed the wiring to the usual way (see @groundFungus!) and it worked ...

So it is/was quite likely just a wiring problem!

The way you programmed it does not consider bouncing effects of mechanical switches/buttons and the case that a controller (if blocking functions have been avoided) goes through loop() hundreds or thousands of times a second.

A human button press is very long compared to the speed of a controller. To avoid running into the if clause after (buttonState == LOW) I changed it to a simple "if (buttonState == LOW && !stepperOn)".

That way the controller only reacts on the button press if the stepper is off.

Sketch:

/*
  Forum: https://forum.arduino.cc/t/stop-the-rotation-of-stepper-motor-when-the-key-is-pressed/1164919/16
  Wokwi: https://wokwi.com/projects/375480344513780737
*/

#include <AccelStepper.h>

#define STEPS_PER_REVOLUTION 1000  // Change this to match your stepper motor's specification
#define DIR_PIN 8
#define STEP_PIN 9

const int pushButtonPin = 12;  // Replace with the actual pin connected to the push button

constexpr int maxSpeed = 2000;    // Set the maximum speed of the stepper motor (adjust as needed)
constexpr int runAtSpeed = 1000;  // Set the speed of the stepper motor (adjust as needed)

AccelStepper mystepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

boolean stepperOn = false;

void setup() {
 mystepper.setMaxSpeed(maxSpeed);
 pinMode(pushButtonPin, INPUT_PULLUP);
 Serial.begin(115200);
 Serial.println("Begin");
}


void loop() {
 int buttonState = digitalRead(pushButtonPin);

 if (buttonState == LOW && !stepperOn ) {
     Serial.println("Button pressed");
     mystepper.setSpeed(runAtSpeed);
     stepperOn = true;
   }
 
 if (Serial.available() > 0) {
   char command = Serial.read();
   if (command == 's') {
     // Move stepper motor for 3 seconds
     Serial.println("Start received");
     mystepper.setSpeed(runAtSpeed);
     stepperOn = true;
   }
   if (command == 'p') {
     Serial.println("Stop received");
     mystepper.stop();
     stepperOn = false;
   }
 }
 if (stepperOn) {
   mystepper.runSpeed();
 }
}

Wiring:

At first, I made the same wirings as yours, but the stepper motor did not rotate when I pressed the button, using the same code that I sent here. Maybe it didn't rotate because there might be a minor mistake in my code. Thank you for clarifying and for your help.

Great that it works now. As you can see @groundFungus's post was correct. In most cases it is not a bad idea to rely on the posts of experienced members... :wink:

1 Like