// INCLUDES
#include <LiquidCrystal_I2C.h>
#include <Stepper.h>
// CONSTANTS
#define PIN_BUTTON_LEFT 5 // Adaptable
#define PIN_BUTTON_RIGHT 6 // Adaptable
#define PIN_BUTTON_STOP 7 // Adaptable
#define PIN_POTENTIOMETER A0 // Adaptable
#define PIN_STEPPER_IN1 8 // Adaptable
#define PIN_STEPPER_IN2 9 // Adaptable
#define PIN_STEPPER_IN3 10 // Adaptable
#define PIN_STEPPER_IN4 11 // Adaptable
#define STEPPER_STEPS_PER_REVOLUTION 2024 // Adaptable, depends on hardware
#define DO_STEPS_AT_ONCE 3 // Adaptable, STOP_EVERY_DEGREES should be roughly divisible by this. The lower, the more responsive are the buttons; if it's too low the stepper doesn't move correctly.
#define STOP_EVERY_DEGREES_DEFAULT 45 // Adaptable, can be changed at runtime in the menu
#define POTENTIOMETER_RPM_MOD 0.025 // Adaptable, the result of analogRead(PIN_POTENTIOMETER) is multiplied with this to calculate the stepper RPM.
#define BAUDRATE 9600 // Adaptable
#define BUTTON_DEBOUNCE_TIME 20 // in ms, Adaptable
#define BUTTON_HOLD_DURATION 1500 // in ms, Adaptable. Duration to hold until it's registered as holding.
#define VERSION "0.1.0"
// GLOBALS
unsigned int stepperRpm = 0;
short direction = 1;
LiquidCrystal_I2C display(0x27, 16, 2);
Stepper stepper(STEPPER_STEPS_PER_REVOLUTION, PIN_STEPPER_IN1, PIN_STEPPER_IN3, PIN_STEPPER_IN2, PIN_STEPPER_IN4);
unsigned int currentStepInMove = 0;
unsigned long buttonLeftStart = 0;
unsigned long buttonLeftHoldStart = 0;
bool buttonLeftDisabled = false;
unsigned long buttonRightStart = 0;
unsigned long buttonRightHoldStart = 0;
bool buttonRightDisabled = false;
unsigned long buttonStopStart = 0;
unsigned long buttonStopHoldStart = 0;
bool buttonStopDisabled = false;
bool waitingForNext = 0;
uint8_t menuState = 0; // 0 = Not in menu; 1 = Mode selection; 2 = Degrees selection
bool isContinuous = true;
unsigned long stopEveryDegrees = STOP_EVERY_DEGREES_DEFAULT;
bool blockSingleIncrease = false;
bool blockSingleDecrease = false;
void updateDisplay() {
if (menuState != 0) return;
display.clear();
display.setCursor(0, 0);
Serial.print("Rotating ");
if (isContinuous) {
Serial.print("continuously");
display.print("Continuously");
} else {
Serial.print("with stops (");
Serial.print(stopEveryDegrees);
Serial.print("°)");
display.print("Deg.: ");
display.print(stopEveryDegrees);
}
Serial.print(" at ");
Serial.print(stepperRpm);
Serial.print(" RPM ");
display.setCursor(0, 1);
display.print("at ");
display.print(stepperRpm);
display.print(" RPM ");
if (direction == 1) {
Serial.println("(CW).");
display.print("(CW).");
} else {
Serial.println("(CCW).");
display.print("(CCW).");
}
}
void setup() {
pinMode(PIN_BUTTON_LEFT, INPUT);
pinMode(PIN_BUTTON_RIGHT, INPUT);
pinMode(PIN_BUTTON_STOP, INPUT);
pinMode(PIN_POTENTIOMETER, INPUT);
Serial.begin(BAUDRATE);
Serial.print("Turntable - v");
Serial.println(VERSION);
display.init();
display.backlight();
stepperRpm = readPotentiometer();
stepper.setSpeed(stepperRpm);
updateDisplay();
}
void readButtons() {
int buttonLeftState = digitalRead(PIN_BUTTON_LEFT);
if (buttonLeftState && !buttonLeftDisabled) {
if (buttonLeftStart && millis() - buttonLeftStart > BUTTON_DEBOUNCE_TIME) {
switch (menuState) {
case 0:
if (direction == 1) {
direction = -1;
currentStepInMove = 0;
updateDisplay();
}
buttonLeftDisabled = true;
break;
case 1:
menuState = 0;
isContinuous = true;
buttonLeftDisabled = true;
updateDisplay();
break;
case 2:
if (stopEveryDegrees > 0 && !blockSingleDecrease) {
stopEveryDegrees--;
blockSingleDecrease = true;
Serial.println(stopEveryDegrees);
display.setCursor(0, 1);
display.print(stopEveryDegrees);
display.print(" "); // If the new number has a digit less than the previous, this will clear the last digit.
}
}
if (!buttonLeftHoldStart) buttonLeftHoldStart = millis();
if (millis() - buttonLeftHoldStart >= BUTTON_HOLD_DURATION) {
if (menuState == 2) {
if (stopEveryDegrees > 0) {
stopEveryDegrees--;
Serial.println(stopEveryDegrees);
display.setCursor(0, 1);
display.print(stopEveryDegrees);
display.print(" "); // If the new number has a digit less than the previous, this will clear the last digit.
delay(20);
}
}
}
} else if (!buttonLeftStart) {
buttonLeftStart = millis();
}
} else if (!buttonLeftState) {
buttonLeftStart = 0;
buttonLeftHoldStart = 0;
buttonLeftDisabled = false;
blockSingleDecrease = false;
}
int buttonRightState = digitalRead(PIN_BUTTON_RIGHT);
if (buttonRightState && !buttonRightDisabled) {
if (buttonRightStart && millis() - buttonRightStart > BUTTON_DEBOUNCE_TIME) {
switch (menuState) {
case 0:
if (direction == -1) {
direction = 1;
currentStepInMove = 0;
updateDisplay();
}
buttonRightDisabled = true;
break;
case 1:
menuState = 2;
isContinuous = false;
buttonRightDisabled = true;
Serial.print("Set degrees to move. Currently at ");
Serial.print(stopEveryDegrees);
Serial.println(". Decrease (LEFT + Hold), Increase (RIGHT + Hold), Confirm (OK).");
display.clear();
display.setCursor(0, 0);
display.print("Set degrees.");
display.setCursor(0, 1);
display.print(stopEveryDegrees);
break;
case 2:
if (!blockSingleIncrease) {
stopEveryDegrees++;
blockSingleIncrease = true;
Serial.println(stopEveryDegrees);
display.setCursor(0, 1);
display.print(stopEveryDegrees);
}
break;
}
if (!buttonRightHoldStart) buttonRightHoldStart = millis();
if (millis() - buttonRightHoldStart >= BUTTON_HOLD_DURATION) {
if (menuState == 2) {
stopEveryDegrees++;
Serial.println(stopEveryDegrees);
display.setCursor(0, 1);
display.print(stopEveryDegrees);
delay(20);
}
}
} else if (!buttonRightStart) {
buttonRightStart = millis();
}
} else if (!buttonRightState) {
buttonRightStart = 0;
buttonRightHoldStart = 0;
buttonRightDisabled = false;
blockSingleIncrease = false;
}
int buttonStopState = digitalRead(PIN_BUTTON_STOP);
if (buttonStopState && !buttonStopDisabled) {
if (buttonStopStart && millis() - buttonStopStart > BUTTON_DEBOUNCE_TIME) {
switch(menuState) {
case 0:
waitingForNext = false;
break;
case 2:
menuState = 0;
buttonStopDisabled = true;
waitingForNext = false;
updateDisplay();
break;
}
if (!buttonStopHoldStart) buttonStopHoldStart = millis();
if (millis() - buttonStopHoldStart >= BUTTON_HOLD_DURATION) {
if (menuState == 0) {
menuState = 1;
buttonStopDisabled = true;
Serial.println("Select Mode: Continuous (LEFT) or Stopping (RIGHT).");
display.clear();
display.setCursor(0, 0);
display.print("Select Mode:");
display.setCursor(0, 1);
display.print("Con. L Stop R");
}
}
} else if (!buttonStopStart) {
buttonStopStart = millis();
}
} else if (!buttonStopState) {
buttonStopStart = 0;
buttonStopHoldStart = 0;
buttonStopDisabled = false;
}
}
int readPotentiometer() {
int value = analogRead(PIN_POTENTIOMETER);
return value * POTENTIOMETER_RPM_MOD;
}
void loop() {
readButtons();
unsigned int newRpm = readPotentiometer();
if (newRpm != stepperRpm) {
stepperRpm = newRpm;
stepper.setSpeed(stepperRpm);
updateDisplay();
}
if (waitingForNext && !isContinuous) {
if (isContinuous) {
waitingForNext = false;
} else {
return;
}
}
if (stepperRpm > 0) stepper.step(5 * direction);
currentStepInMove += 5;
if (currentStepInMove >= STEPPER_STEPS_PER_REVOLUTION * (stopEveryDegrees / 360.0)) {
currentStepInMove = 0;
if (!isContinuous) {
waitingForNext = true;
if (menuState == 0) Serial.println("Press OK to continue.");
}
}
}