#include <LiquidCrystal_I2C.h>

#define DIR1_PIN 4
#define STEP1_PIN 5
#define POT1_PIN A0

#define DIR2_PIN 2
#define STEP2_PIN 3
#define POT2_PIN A1

#define BUTTON1_PIN 7
#define BUTTON2_PIN 6

#define I2C_ADDR    0x27
#define LCD_COLUMNS 16
#define LCD_LINES   2
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);

#define MIN_RAW 0
#define MAX_RAW 1023
#define MIN_REVOLUTION 1
#define MAX_REVOLUTION 1000
#define STEPS_PER_REVOLUTION 200
#define DEFAULT_REVOLUTION 60
#define INTERVAL_LCD_PRINT 250

enum direction_t {
  CCW,
  CW
};

enum state_t {
  NONE,
  ENABLE,
  DIRECTION,
  REVOLUTION,
  POSITION
};

typedef struct {
  uint8_t id;
  state_t state;
} EventArgs;

typedef void (*CallbackFunction)(EventArgs e);

typedef struct {
  private:
    uint8_t id;
    uint8_t stepPin;
    uint8_t dirPin;
    EventArgs eventArgs;

    void internalStateChanged(state_t state) {
      if (eventArgs.state != state) {
        eventArgs.state = state;
        if (callbackFunction) {
          callbackFunction(eventArgs);
        }
      }
      eventArgs.state = NONE;
    }
  public:
    bool direction;
    int revolution;
    int position;
    bool enable;
    bool changed;

    CallbackFunction callbackFunction;

    unsigned long startTime;
    unsigned long timeDelay;

    void para(uint8_t step, uint8_t dir, uint8_t motorId = 0, uint8_t motorDirection = CW) {
      eventArgs.id = motorId;
      stepPin = step;
      dirPin = dir;
      direction = motorDirection;
      eventArgs.state = NONE;
      pinMode(stepPin, OUTPUT);
      pinMode(dirPin, OUTPUT);
      digitalWrite(stepPin, LOW);
      digitalWrite(dirPin, init);
      rpm(DEFAULT_REVOLUTION);
    }

    void onChanged(CallbackFunction onChangeFunction) {
      callbackFunction = onChangeFunction;
    }

    // per minute
    void rpm(int rpm) {
      int prevRPM = revolution;
      revolution = rpm;
      unsigned long div = (unsigned long)revolution * (unsigned long)STEPS_PER_REVOLUTION;
      timeDelay = 60000000UL / div;
      if (rpm != prevRPM) {
        internalStateChanged(REVOLUTION);
      }
    }

    void run(bool motorEnable, direction_t motorDirection = CW) {
      unsigned long currentTime = micros();

      if (motorEnable) {
        if (!enable) {
          startTime = currentTime;
          enable = motorEnable;
          internalStateChanged(ENABLE);
        }
        unsigned long elapsedTime = currentTime - startTime;
        if (elapsedTime >= timeDelay) {
          startTime = currentTime;
          digitalWrite(dirPin, direction);
          digitalWrite(stepPin, HIGH);
          digitalWrite(stepPin, LOW);
          if (motorDirection == CW) {
            position++;
          } else {
            position--;
          }
          internalStateChanged(POSITION);
        }
      } else {
        if (enable) {
          enable = motorEnable;
          internalStateChanged(ENABLE);
        }
      }
      if (direction != motorDirection) {
        direction = motorDirection;
        internalStateChanged(DIRECTION);
      }
      enable = motorEnable;
    }
} motor_t;

#define DEFAULT_DEBOUNCE_PERIOD 10UL  //  Debounce Delay 10 milliseconds
typedef struct {
  private:
    bool input;
    unsigned long startTime;

  public:
    bool state;

    bool debounce(bool bounce, const unsigned long timeDelay = DEFAULT_DEBOUNCE_PERIOD) {
      unsigned long currentTime = millis();
      bool prevState = state;
      if (bounce) {
        state = true;
      } else {
        if (state) {
          if (input) {
            startTime = currentTime;
          }
          unsigned long elapsedTime = currentTime - startTime;
          if (elapsedTime >= timeDelay) {
            state = false;
          }
        }
      }
      input = bounce;
      return state != prevState & state == true;
    }
} debounce_t;


const uint8_t numberOfMotors = 2;
const uint8_t dirPins[numberOfMotors] = {DIR1_PIN, DIR2_PIN};
const uint8_t stepPins[numberOfMotors] = {STEP1_PIN, STEP2_PIN};
const uint8_t buttonPins[numberOfMotors] = {BUTTON1_PIN, BUTTON2_PIN};
const uint8_t potPins[numberOfMotors] = {POT1_PIN, POT2_PIN};

debounce_t buttons[numberOfMotors];
motor_t motors[numberOfMotors];

bool oneSecond;

void MotorEnable();
void MotorSpeed();
void LCDPrint(bool manual = false);

void OnMotorChanged(EventArgs e);

void setup() {
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();

  for (uint8_t index = 0; index < numberOfMotors; index++) {
    lcd.setCursor(0, index);
    lcd.print("M" + String(index + 1) + ":");
    lcd.setCursor(7, index);
    lcd.print("RPM:");
    lcd.setCursor(13, index);

    motors[index].para(stepPins[index], dirPins[index], index, CW);
    motors[index].onChanged(OnMotorChanged);
  }

  MotorSpeed();
  MotorEnable();
  LCDPrint(true);
}

void loop() {
  MotorSpeed();
  MotorEnable();
}

void MotorEnable() {
  for (uint8_t index = 0; index < numberOfMotors; index++) {
    bool pressed = buttons[index].debounce(!digitalRead(buttonPins[index]));
    motors[index].run(buttons[index].state);
  }
}

void MotorSpeed() {
  for (uint8_t index = 0; index < numberOfMotors; index++) {
    int rawValue = analogRead(potPins[index]);
    motors[index].rpm(map(rawValue, MIN_RAW, MAX_RAW, MIN_REVOLUTION, MAX_REVOLUTION));
  }
}

void OnMotorChanged(EventArgs e) {
  switch (e.state) {
    case   ENABLE...REVOLUTION:
      LCDPrint(true);
      break;
  }
  if (e.state == POSITION) {
    //Serial.println(motors[e.id].position);
  }
}

void LCDPrint(bool manual) {
  for (uint8_t index = 0; index < LCD_LINES; index++) {
    if (manual) {
      lcd.setCursor(3, index);
      lcd.print(motors[index].enable ? "ENA" : "DIS");
      lcd.setCursor(11, index);
      if (motors[index].revolution < 10) lcd.print("   ");
      else if (motors[index].revolution < 100) lcd.print("  ");
      else if (motors[index].revolution < 1000) lcd.print(' ');
      lcd.print(String(motors[index].revolution));
    }
  }
}
A4988
A4988
Double ButtonsBreakout