NEMA 17 stepper with 2 buttons for separate run functions, struggling with conflicts

Hi,

Frequent visitor, first time poster. Appreciate all the topics and help out there. I'm generally new to Arduino, though I've completed a few successful projects so far. This one is stumping me.

I'm making a motorized winder with a NEMA17 stepper and A4988 driver which defaults to 31 turns, but can be increased to 100. I have 3 buttons:

  • run (executes the desired number of turns, a second press during the run cycle aborts the process and resets the count)
  • advance (meant to turn the motor about 30rpm or 100 steps/sec while it's being pressed only)
  • count (this increases the turn count before wrapping back to 1 after 100).

I also have a TFT showing basic turn cycle info: number of turns and "complete" or "abort" when motor stops.

I'm struggling with the advance button. I'll spare the long winded version and post what it's doing now - the closest I could get. In this version, two things are happening that should not happen:

  • advance turns about 10rpm
  • advance interrupts run if pressed during the cycle and stops/aborts.

Here's my setup (note: at this moment, screen switch is bypassed, buck is bypassed and board is powered direct from laptop)

Here is my code. I've done several tests so it may have gotten a little messy. Open to any input. Thank you!

#include <Adafruit_GFX.h>
#include <Adafruit_ILI9163.h>
#include <AccelStepper.h>
#include <SPI.h>
#include <SD.h>

// Define stepper motor pins
#define STEP_PIN 2
#define DIR_PIN 3
#define ENABLE_PIN 4

// Define button pins
#define RUN_BUTTON_PIN 5
#define ADVANCE_BUTTON_PIN 6
#define COUNT_BUTTON_PIN 7#include <Adafruit_GFX.h>
#include <Adafruit_ILI9163.h>
#include <AccelStepper.h>
#include <SPI.h>
#include <SD.h>

// Define stepper motor pins
#define STEP_PIN 2
#define DIR_PIN 3
#define ENABLE_PIN 4

// Define button pins
#define RUN_BUTTON_PIN 5
#define ADVANCE_BUTTON_PIN 6
#define COUNT_BUTTON_PIN 7
// Initialize the TFT LCD display
#define TFT_CS     8
#define TFT_RST    9
#define TFT_DC     10
Adafruit_ILI9163 tft = Adafruit_ILI9163(TFT_CS, TFT_DC, TFT_RST);

// Create two AccelStepper objects: one for Run, one for Advance
AccelStepper stepperRun(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
AccelStepper stepperAdvance(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

// Define the number of steps per revolution
const int stepsPerRevolution = 200; // 1.8 degrees per step

// Button state variables
bool motorRunning = false;
bool lastRunButtonState = HIGH;  // Initialize the last run button state
bool currentRunButtonState = HIGH;  // Initialize the current run button state
unsigned long lastButtonPressTime = 0;  // Time of last button press
const unsigned long buttonCooldown = 250;  // 250ms cooldown period
int currentTurns = 31;  // Default turns
const int maxTurns = 100;  // Maximum turns before wrapping around

// Advance button state variables
bool advanceMode = false;  // Flag to indicate if advance mode is active
bool lastAdvanceButtonState = HIGH;  // Last state of advance button
bool currentAdvanceButtonState = HIGH;  // Current state of advance button

// Count button debounce
bool lastCountButtonState = HIGH;  // Initialize the last count button state
unsigned long lastCountButtonPressTime = 0;
const unsigned long debounceDelay = 100;  // Debounce delay for count button

// Flag to disable inputs during "COMPLETE" or "ABORT" message
bool inputDisabled = false;

// Function prototypes
void handleRunButton();
void handleCountButton();
void handleAdvanceButton();
void updateTurnsDisplay();
void displayCompleteMessage(bool aborted);

void setup() {
  // Set the motor control pins as outputs
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);

  // Set the button pins as input with pull-up resistor enabled
  pinMode(RUN_BUTTON_PIN, INPUT_PULLUP);
  pinMode(ADVANCE_BUTTON_PIN, INPUT_PULLUP);
  pinMode(COUNT_BUTTON_PIN, INPUT_PULLUP);

  // Initialize the TFT LCD display
  tft.begin();

  // Set background color and text color
  tft.fillScreen(ILI9163_BLACK);
  tft.setTextColor(ILI9163_WHITE);
  tft.setTextSize(2);

  // Print initial message
  updateTurnsDisplay(); // Function to update the turns display on TFT

  // Set up **Run** mode stepper (high speed and high acceleration)
  stepperRun.setMaxSpeed(1600);   // Set max speed for run mode
  stepperRun.setAcceleration(4000); // Set acceleration for smoother starts/stops in run mode

  // Set up **Advance** mode stepper (lower speed and acceleration)
  stepperAdvance.setMaxSpeed(1200);    // Set max speed for advance mode
  stepperAdvance.setAcceleration(1000); // Lower acceleration for advance mode

  // Initialize Serial communication
  Serial.begin(9600);
  Serial.println("Initialization complete.");
}

void loop() {
  // Handle run button press and hold
  if (!inputDisabled) {
    handleRunButton();
  }

  // Handle advance button press
  if (!inputDisabled) {
    handleAdvanceButton();
  }

  // Handle count button press and hold
  if (!inputDisabled) {
    handleCountButton();
  }

  // Run the stepper motor if motorRunning is true (only for Run mode)
  if (motorRunning) {
    digitalWrite(ENABLE_PIN, LOW);  // Enable the motor driver
    stepperRun.run();  // Run the stepper motor for run mode

    // Check if the motor is running and update TFT LCD display with the number of rotations completed
    if (stepperRun.distanceToGo() == 0 && !stepperRun.isRunning()) {
      digitalWrite(ENABLE_PIN, HIGH); // Disable the motor driver when not running

      // Display "COMPLETE" message
      displayCompleteMessage(false); // Display "COMPLETE"

      // Update turns display after completing the run
      updateTurnsDisplay();

      // Reset motorRunning flag
      motorRunning = false;
    }
  } 
  else if (advanceMode) {
    // Run the motor at a fixed speed in advance mode
    digitalWrite(ENABLE_PIN, LOW);  // Enable motor driver
    stepperAdvance.setSpeed(1200);         // Set speed for advance mode
    stepperAdvance.runSpeed();             // Run the motor at set speed

    // Debugging: Confirm advance mode is active
    Serial.println("Advance mode is active, running motor at set speed.");
  } 
  else {
    // Disable motor driver when neither Run nor Advance button is pressed
    digitalWrite(ENABLE_PIN, HIGH); // Disable the motor driver
  }
}

void handleRunButton() {
  currentRunButtonState = digitalRead(RUN_BUTTON_PIN);  // Read the state of the button

  // Check if button was just pressed (falling edge detection)
  if (currentRunButtonState == LOW && lastRunButtonState == HIGH) {
    // Check if enough time has passed since last button press (cooldown)
    if (millis() - lastButtonPressTime > buttonCooldown) {
      lastButtonPressTime = millis();  // Update the last press time

      if (!motorRunning) {
        // Start motor if not already running
        motorRunning = true;
        stepperRun.setCurrentPosition(0);  // Reset position to zero
        stepperRun.moveTo(stepsPerRevolution * currentTurns);  // Move to current turns
        Serial.println("Motor started");
      } else {
        // Stop motor if running and reset position
        motorRunning = false;
        stepperRun.stop();  // Stop the motor
        stepperRun.setCurrentPosition(0);  // Reset position to zero
        Serial.println("Motor stopped, position reset");

        // Display "ABORTED" message
        displayCompleteMessage(true); // Show the aborted message in red
      }
    }
  }

  // Update last button state to current state for the next loop
  lastRunButtonState = currentRunButtonState;
}

void handleAdvanceButton() {
  currentAdvanceButtonState = digitalRead(ADVANCE_BUTTON_PIN);  // Read the state of the advance button

  // If the advance button is pressed, enable advance mode
  if (currentAdvanceButtonState == LOW && lastAdvanceButtonState == HIGH) {
    advanceMode = true;  // Set advance mode flag
    stepperAdvance.setSpeed(1200);  // Set speed for advance mode
    Serial.println("Advance button pressed, motor running at 1200 speed");  // Debugging
  }

  // If the advance button is released, disable advance mode
  if (currentAdvanceButtonState == HIGH && lastAdvanceButtonState == LOW) {
    advanceMode = false;  // Reset advance mode flag
    stepperAdvance.setSpeed(0);  // Stop the motor
    Serial.println("Advance button released, motor stopped");  // Debugging
  }

  // Update last button state for the next loop
  lastAdvanceButtonState = currentAdvanceButtonState;
}

void handleCountButton() {
  int reading = digitalRead(COUNT_BUTTON_PIN);  // Read the state of the count button

  // Check for count button state change with debounce
  if (reading != lastCountButtonState && millis() - lastCountButtonPressTime > debounceDelay) {
    if (reading == LOW) {  // Button pressed
      // Record the button press time
      lastCountButtonPressTime = millis();

      // Increment currentTurns immediately for a short press
      currentTurns++;
      if (currentTurns > maxTurns) {
        currentTurns = 1;  // Wrap around to 1 if it exceeds maxTurns
      }
      // Update the TFT display
      updateTurnsDisplay();
    }
    // Update last button state
    lastCountButtonState = reading;
  }
}

void updateTurnsDisplay() {
  // Clear previous text on TFT display
  tft.fillRect(10, 35, 120, 16, ILI9163_BLACK);

  // Update TFT display with current turns count
  tft.setCursor(20, 35);
  tft.print("TURNS:");
  tft.println(currentTurns);  // Print updated turns count on TFT display
  Serial.print("Current turns: ");
  Serial.println(currentTurns);  // Debugging: Print updated turns count to Serial Monitor
}

void displayCompleteMessage(bool aborted) {
  unsigned long startTime = millis();
  boolean showComplete = true;

  // Disable input during message display
  inputDisabled = true;

  // Display "COMPLETE" or "ABORT" on TFT for 5 seconds
  while (millis() - startTime < 5000) {
    // Toggle visibility every 500 milliseconds
    if ((millis() - startTime) % 1000 < 500) {
      if (showComplete) {
        if (aborted) {
          tft.setCursor(25, 70);
          tft.setTextColor(ILI9163_RED);  // Set text color to red for "ABORT"
          tft.println("ABORTED");
        } else {
          tft.setCursor(20, 70);
          tft.setTextColor(ILI9163_GREEN);  // Set text color to green for "COMPLETE"
          tft.println("COMPLETE");
        }
        showComplete = false;
      }
    } else {
      if (!showComplete) {
        tft.fillRect(20, 70, 120, 16, ILI9163_BLACK);  // Clear text area
        showComplete = true;
      }
    }

    // Allow other tasks to run during display
    delay(10);  // Small delay to prevent excessive loop iterations
  }

  // Clear "COMPLETE" or "ABORT" after 5 seconds
  tft.fillRect(20, 70, 120, 16, ILI9163_BLACK);
  tft.setTextColor(ILI9163_WHITE);  // Reset text color to white

  // Enable input after message display
  inputDisabled = false;
}

Please post a correct schematic diagram. Fritzing diagrams are frowned upon, as they are usually misleading, difficult to interpret and often wrong.

A laptop cannot be used to power a stepper motor.

Finally, if the switch is intended to power down the display, this is a bad idea. Connecting a powered MCU to an unpowered device via the I/O pins can damage one or both devices.

what's this?

why not simply

    advanceMode = ! digitalRead (ADVANCE_BUTTON_PIN);

Thank you. I do not have a schematic. Thank you for the comment on the switch. It's currently unused and will remain that way moving forward.

The laptop is powering the board. 12v to the driver.

Thank you

A mistake from a previous attempt to pull out the advance and test it and return it. thank you for catching that!

Fritzing can produce one of poor quality, but pencil and paper work very well.

Thanks! that does simplify things. I tried this, with new behavior: Advance still creeps at about 20steps/sec. Now when pressing run after advancing, the motor jitters back and forth for the duration of its cycle.

void handleAdvanceButton() {
 advanceMode = ! digitalRead (ADVANCE_BUTTON_PIN);
}

So the behavior of the system should be one of these:

  • idle
  • running
  • advancing

..and the system should act on the buttons differently in the different states.

Since the logic of different speeds, booleans. and button-presses all interact with each other, I'd separate the logic into the different state machine states and their transitions separately--you can only transition into ADVANCE from IDLE, and that's when you set the speed.

Also, when you put the stepper.run()/stepper.runspeed() inside of the logic, it breaks the deceleration feature of AccelStepper. It would be better to change the logic to use stepper.run() unconditionally and increase the acceleration to a level just below step-skipping. Then you could poll the inputs less frequently and devote more attention to stepping and accelerating.

Also, AccelStepper can handle the enable pin automatically with stepper.setEnablePin()

1 Like

that's what he's done with motorRunning and advanceMode.

the advance-button simply sets the advanceMode while it is pressed, but don't understand what advanceMode does.

the count button increases a count of the # of revolutions.

the run button invoked moveTo with the # of steps for the specified # of revolutions and set motorRunning. the motor steps until it reaches the target # of steps and then clears motorRunning

It is a very, very bad idea to create 2 AccelStepper instances for one stepper. You must not do this, the objects will conflict with each other. And it is never necessary.
Create only one object for all functions.

1 Like

I appreciate everyone's input. This has taken quite some time to learn, as it's an advanced sketch for me. It's taken days and weeks to get this far, but your help has gone a long way. After another full day of testing and experimenting, I have something that seems to work as expected. I will continue testing when I get back to this project, but this has helped a lot so far. Thanks again.

#include <Adafruit_GFX.h>
#include <Adafruit_ILI9163.h>
#include <AccelStepper.h>
#include <SPI.h>
#include <SD.h>

// Define stepper motor pins
#define STEP_PIN 2
#define DIR_PIN 3
#define ENABLE_PIN 4

// Define button pins
#define RUN_BUTTON_PIN 5
#define ADVANCE_BUTTON_PIN 6
#define COUNT_BUTTON_PIN 7

// Initialize the TFT LCD display
#define TFT_CS     8
#define TFT_RST    9
#define TFT_DC     10
Adafruit_ILI9163 tft = Adafruit_ILI9163(TFT_CS, TFT_DC, TFT_RST);

// Initialize AccelStepper
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

// Define the number of steps per revolution
const int stepsPerRevolution = 200;  // 1.8 degrees per step

// Button state variables
bool motorRunning = false;
bool lastRunButtonState = HIGH;
bool currentRunButtonState = HIGH;
unsigned long lastButtonPressTime = 0;
const unsigned long buttonCooldown = 250;  // 250ms cooldown period
int currentTurns = 31;  // Default turns
const int maxTurns = 100;  // Maximum turns before wrapping around

// Advance button state variables
bool lastAdvanceButtonState = HIGH;
bool currentAdvanceButtonState = HIGH;
bool advanceMode = false;  // Flag for advance mode

// Count button debounce
bool lastCountButtonState = HIGH;
unsigned long lastCountButtonPressTime = 0;
const unsigned long debounceDelay = 100;  // Debounce delay for count button

// Flag to disable inputs during "COMPLETE" or "ABORT" message
bool inputDisabled = false;

// Store last turns value to avoid unnecessary updates
int lastTurns = -1;

// Function prototypes
void handleRunButton();
void handleCountButton();
void handleAdvanceButton();
void updateTurnsDisplay();
void displayCompleteMessage(bool aborted);

void setup() {
  // Set motor control pins as outputs
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);

  // Set button pins as input with pull-up resistors
  pinMode(RUN_BUTTON_PIN, INPUT_PULLUP);
  pinMode(ADVANCE_BUTTON_PIN, INPUT_PULLUP);
  pinMode(COUNT_BUTTON_PIN, INPUT_PULLUP);

  // Initialize TFT LCD display
  tft.begin();
  tft.fillScreen(ILI9163_BLACK);
  tft.setTextColor(ILI9163_WHITE);
  tft.setTextSize(2);

  // Set up AccelStepper
  stepper.setMaxSpeed(1600);  // Speed for run mode (fast speed)
  stepper.setAcceleration(4000);  // Acceleration for run mode

  // Initialize Serial communication
  Serial.begin(9600);
  Serial.println("Initialization complete.");

  // Initialize turns display
  updateTurnsDisplay();
}

void loop() {
  // Handle button presses
  if (!inputDisabled) {
    handleRunButton();
    handleAdvanceButton();
    handleCountButton();
  }

  // Execute actions based on current mode
  if (motorRunning) {
    digitalWrite(ENABLE_PIN, LOW);  // Enable motor driver
    stepper.run();  // Run the stepper motor at high speed
    if (stepper.distanceToGo() == 0) {  // Motor has completed the movement
      digitalWrite(ENABLE_PIN, HIGH);  // Disable motor driver
      displayCompleteMessage(false);  // Display complete message
      motorRunning = false;  // Stop motor
    }
  } else if (advanceMode) {
    // Advance mode: run the motor at a fixed speed (50 steps/sec)
    digitalWrite(ENABLE_PIN, LOW);  // Enable motor driver
    stepper.setSpeed(50);  // Set speed for advance mode (50 steps/sec)
    stepper.runSpeed();  // Run at the fixed speed
  } else {
    // Idle mode: Hold torque (motor stationary but holding position)
    digitalWrite(ENABLE_PIN, LOW);  // Keep motor driver enabled
    stepper.setSpeed(0);  // Zero speed to keep motor in idle position
    stepper.runSpeed();  // Ensure the motor holds position (torque applied)
  }
}

void handleRunButton() {
  currentRunButtonState = digitalRead(RUN_BUTTON_PIN);  // Read the state of the button

  if (currentRunButtonState == LOW && lastRunButtonState == HIGH) {
    if (millis() - lastButtonPressTime > buttonCooldown) {
      lastButtonPressTime = millis();
      if (!motorRunning) {
        motorRunning = true;
        stepper.setCurrentPosition(0);  // Reset position to zero
        stepper.moveTo(stepsPerRevolution * currentTurns);  // Move to current turns
        stepper.setMaxSpeed(1600);  // Run mode speed (fast speed)
        Serial.println("Motor started in Run Mode");
      } else {
        motorRunning = false;
        stepper.stop();  // Stop motor
        stepper.setCurrentPosition(0);  // Reset position to zero
        Serial.println("Motor stopped, position reset");
        displayCompleteMessage(true);  // Display aborted message
      }
    }
  }

  lastRunButtonState = currentRunButtonState;
}

void handleAdvanceButton() {
  currentAdvanceButtonState = digitalRead(ADVANCE_BUTTON_PIN);  // Read the state of the advance button

  if (currentAdvanceButtonState == LOW && lastAdvanceButtonState == HIGH) {
    advanceMode = true;  // Set advance mode flag
    stepper.setSpeed(50);  // Set speed for advance mode (slower)
    Serial.println("Advance mode active");
  } else if (currentAdvanceButtonState == HIGH && lastAdvanceButtonState == LOW) {
    advanceMode = false;  // Disable advance mode
    stepper.setSpeed(0);  // Stop motor
    Serial.println("Advance mode stopped");
  }

  lastAdvanceButtonState = currentAdvanceButtonState;
}

void handleCountButton() {
  int reading = digitalRead(COUNT_BUTTON_PIN);  // Read the state of the count button

  // If the button state has changed and enough time has passed since the last press
  if (reading != lastCountButtonState && millis() - lastCountButtonPressTime > debounceDelay) {
    // Button pressed (LOW state)
    if (reading == LOW) {
      // Record the button press time
      lastCountButtonPressTime = millis();

      // Increment currentTurns immediately for a short press (no delay for first increment)
      currentTurns++;
      if (currentTurns > maxTurns) {
        currentTurns = 1;  // Wrap around to 1 if it exceeds maxTurns
      }
      // Update the TFT display
      updateTurnsDisplay();
    }
    // Update last button state
    lastCountButtonState = reading;
  }

  // If the button is held down for more than 500ms, slow down the increment rate
  if (reading == LOW && millis() - lastCountButtonPressTime >= 500) {
    // Only allow increment every 200ms once held down
    if (millis() - lastCountButtonPressTime >= 500 + 100) { // After 500ms, increment every 200ms
      currentTurns++;
      if (currentTurns > maxTurns) {
        currentTurns = 1;  // Wrap around to 1 if it exceeds maxTurns
      }
      // Update the TFT display
      updateTurnsDisplay();
      // Prevent incrementing too fast by resetting the last press time to now
      lastCountButtonPressTime = millis();  // Reset to create the 200ms interval
    }
  }
}

  // If the button is held down for more than 500ms, slow down the increment rate
  if (reading == LOW && millis() - lastCountButtonPressTime >= 500) {
    // Only allow increment every 200ms once held down
    if (millis() - lastCountButtonPressTime >= 500 + 100) { // After 500ms, increment every 200ms
      currentTurns++;
      if (currentTurns > maxTurns) {
        currentTurns = 1;  // Wrap around to 1 if it exceeds maxTurns
      }
      // Update the TFT display
      updateTurnsDisplay();
      // Prevent incrementing too fast by resetting the last press time to now
      lastCountButtonPressTime = millis();  // Reset to create the 200ms interval
    }
  }
}

void updateTurnsDisplay() {
  // Only update the display if the number of turns has changed
  if (currentTurns != lastTurns) {
    tft.fillRect(10, 35, 120, 16, ILI9163_BLACK);  // Clear previous text on TFT display
    tft.setCursor(20, 35);
    tft.print("TURNS:");
    tft.println(currentTurns);  // Print updated turns count
    lastTurns = currentTurns;  // Update last turns value
  }
}

void displayCompleteMessage(bool aborted) {
  unsigned long startTime = millis();
  boolean showComplete = true;
  inputDisabled = true;  // Disable input during message display

  // Display the message for 5 seconds
  while (millis() - startTime < 5000) {
    if ((millis() - startTime) % 1000 < 500) {
      if (showComplete) {
        if (aborted) {
          tft.setCursor(25, 70);
          tft.setTextColor(ILI9163_RED);  // Red color for "ABORT"
          tft.println("ABORTED");
        } else {
          tft.setCursor(20, 70);
          tft.setTextColor(ILI9163_GREEN);  // Green color for "COMPLETE"
          tft.println("COMPLETE");
        }
        showComplete = false;
      }
    } else {
      if (!showComplete) {
        tft.fillRect(20, 70, 120, 16, ILI9163_BLACK);  // Clear text area
        showComplete = true;
      }
    }
    delay(10);  // Allow other tasks to run
  }

  tft.fillRect(20, 70, 120, 16, ILI9163_BLACK);  // Clear message
  tft.setTextColor(ILI9163_WHITE);  // Reset text color
  inputDisabled = false;  // Re-enable inputs
}

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