/*
 * ๐Ÿš€ Code created by ChatGPT and designed the specifications of Technical Shubham. 
 * ๐Ÿ“… Date: August 17, 2024
 * ๐Ÿ“บ YouTube Channel: https://www.youtube.com/channel/UCGxI73ap6RpDDfIzGzrwcuQ
 *
 * Description:
 * This code is designed for controlling a stepper motor using an OLED display and keypad.
 * It allows users to set the number of steps, repetitions, and speed percentage. The code
 * also includes direction control and a buzzer that triggers on key presses. The motor 
 * performs the specified number of movements based on the inputs provided and displays 
 * the current position and other relevant information on the OLED screen.
 *
 * Keep innovating and happy coding! ๐Ÿš€
 */

#include <Wire.h>
#include <Keypad.h>
#include <Adafruit_SSD1306.h>
#include <AccelStepper.h>

#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_ADDR 0x3C // OLED I2C address

Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT);
#define dirPin 10 // Direction pin for the stepper motor.
#define stepPin 9 // Step pin for the stepper motor.
#define motorInterfaceType 1
#define beep 11 // Buzzer pin

AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);

volatile long CtaEnc = 0; // Position relative to the target (encoder count).
int Vmax = 255; // Maximum PWM value.
int Vmin = 20; // Minimum PWM value without stopping the motor.
int Vprom = 195; // Average speed.
int Fvel = 0; // Speed reduction factor.
int Steps = 0; // Number of steps to move.
int Repetitions = 0; // Number of times to repeat the movement.
int MotorDirection = 1; // Motor direction (1 for forward, -1 for backward).
int MotorSpeed = 100; // Motor speed percentage (0 to 100%).
int BlinkDelay = 500; // Delay for blinking effect.

byte col = 42; // Column for display.
const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
  {'1', '2', '3', '@'},
  {'4', '5', '6', 'ยง'},
  {'7', '8', '9', 'รฉ'},
  {'*', '0', '#', 'D'}
};

byte colPins[COLS] = {A3, A2, A1, A0}; // Column pins.
byte rowPins[ROWS] = {6, 5, 4, 3}; // Row pins.

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
char key;
boolean entryComplete;
boolean isSettingSteps = true; // Flag to switch between setting steps, repetitions, speed, and position.
boolean isSettingReps = false; // Flag to set repetitions.
boolean isSettingSpeed = false; // Flag to set speed.
boolean isSettingPos = false; // Flag to set position.
boolean startMotor = false; // Flag to start the motor.
unsigned long lastBlinkTime = 0; // For handling the blink effect.
boolean blinkState = false; // For controlling blinking.

void setup() {
  pinMode(beep, OUTPUT); // Initialize buzzer pin
  stepper.setMaxSpeed(1000);
  stepper.setAcceleration(500);

  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.setTextSize(1); // Use smaller text size
  display.setTextColor(WHITE);
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("Set Steps:");
  display.display();
  delay(100);
}

void loop() {
  readKeypad(); // Handle keypad input.
  updateDisplay(); // Update the display with current values and blinking effect.
  
  if (startMotor) {
    executeSteps(); // Start motor only when flag is set.
    startMotor = false; // Reset the flag after starting the motor.
  }
}

void updateDisplay() {
  display.clearDisplay();
  display.setTextSize(1); // Use smaller text size
  unsigned long currentTime = millis();

  if (currentTime - lastBlinkTime >= BlinkDelay) {
    lastBlinkTime = currentTime;
    blinkState = !blinkState; // Toggle blink state.
  }

  if (isSettingSteps) {
    display.setCursor(0, 0);
    display.print(blinkState ? "> Steps: " : "  Steps: ");
    display.print(Steps);
  } else {
    display.setCursor(0, 0);
    display.print("  Steps: ");
    display.print(Steps);
  }

  if (isSettingReps) {
    display.setCursor(0, 10);
    display.print(blinkState ? "> Reps: " : "  Reps: ");
    display.print(Repetitions);
  } else {
    display.setCursor(0, 10);
    display.print("  Reps: ");
    display.print(Repetitions);
  }

  if (isSettingSpeed) {
    display.setCursor(0, 20);
    display.print(blinkState ? "> Speed: " : "  Speed: ");
    display.print(MotorSpeed);
    display.print("%");
  } else {
    display.setCursor(0, 20);
    display.print("  Speed: ");
    display.print(MotorSpeed);
    display.print("%");
  }

  if (isSettingPos) {
    display.setCursor(0, 30);
    display.print(blinkState ? "> Pos: " : "  Pos: ");
    display.print(stepper.currentPosition());
  } else {
    display.setCursor(0, 30);
    display.print("  Pos: ");
    display.print(stepper.currentPosition());
  }

  display.setCursor(0, 40);
  display.print("Dir: ");
  display.print(MotorDirection == 1 ? "Forward" : "Backward");
  
  display.display();
}

void readKeypad() {
  key = keypad.getKey();

  if (key) {
    tone(beep, 4000, 10); // Sound buzzer when a key is pressed.

    if (isDigit(key)) {
      // Process numeric input based on the current mode.
      int digit = key - '0';
      if (isSettingSteps) {
        Steps = Steps * 10 + digit;
      } else if (isSettingSpeed) {
        MotorSpeed = MotorSpeed * 10 + digit;
        MotorSpeed = constrain(MotorSpeed, 0, 100); // Limit speed to 0-100%.
      } else if (isSettingReps) {
        Repetitions = Repetitions * 10 + digit;
      } else if (isSettingPos) {
        stepper.setCurrentPosition(stepper.currentPosition() + digit);
      }
    } else if (key == '#') {
      // Switch between Steps, Reps, Speed, and Position.
      if (isSettingSteps) {
        isSettingSteps = false;
        isSettingReps = true;
      } else if (isSettingReps) {
        isSettingReps = false;
        isSettingSpeed = true;
      } else if (isSettingSpeed) {
        isSettingSpeed = false;
        isSettingPos = true;
      } else if (isSettingPos) {
        isSettingPos = false;
        isSettingSteps = true;
      }
    } else if (key == 'D') {
      // Start motor when 'D' is pressed.
      startMotor = true;
    } else if (key == 'รฉ') {
      // Enter Speed setting mode.
      isSettingSteps = false;
      isSettingReps = false;
      isSettingSpeed = true;
    } else if (key == '@') {
      // Toggle motor direction.
      MotorDirection = -MotorDirection;
    } else if (key == '*') {
      // Handle delete function.
      if (isSettingSteps && Steps > 0) {
        Steps /= 10;
      } else if (isSettingReps && Repetitions > 0) {
        Repetitions /= 10;
      } else if (isSettingSpeed && MotorSpeed > 0) {
        MotorSpeed /= 10;
      }
    }
  }
}

void executeSteps() {
  stepper.setMaxSpeed(map(MotorSpeed, 0, 100, 0, 1000)); // Map motor speed percentage to actual speed value.
  for (int i = 0; i < Repetitions; i++) {
    stepper.setCurrentPosition(0);
    stepper.moveTo(Steps * MotorDirection);
    while (stepper.distanceToGo() != 0) {
      stepper.run();
    }
    delay(1000); // Delay between repetitions.
  }
}
A4988