#include <AccelStepper.h>
#include <math.h>

// =====================================================
// Plattenspielersteuerung
// Arduino Nano + CNC Shield V4 + ein Stepper-Treiber
// Motor: NEMA17
// Treiber: DRV8825 / kompatibler STEP-DIR-Treiber
// Poti: A7
// Versorgung: 10 V Motorversorgung am Shield
// =====================================================


// =====================================================
// Pinbelegung laut deinem CNC Shield
// =====================================================

#define DIR_PIN     2
#define STEP_PIN    5
#define ENABLE_PIN  8

// Poti am Arduino Nano A7
#define POT_PIN     A7


// =====================================================
// Motor / Mechanik
// =====================================================

// Typischer NEMA17: 1,8° pro Vollschritt = 200 Vollschritte/Umdrehung
#define MOTOR_FULL_STEPS_PER_REV 200.0

// Muss zur Jumper-Einstellung am Shield passen.
// DRV8825:
// keine Jumper = Vollschritt
// M0           = 1/2
// M1           = 1/4
// M0 + M1      = 1/8
// M2           = 1/16
// M0 + M2      = 1/32
//
// Für Plattenspieler mit 78 rpm ist 1/8 ein guter Start.
#define MICROSTEPS 8

// Übersetzung Motor -> Plattenteller.
// 1.0 = Motor dreht 1x pro 1 Tellerumdrehung
// 2.0 = Motor dreht 2x pro 1 Tellerumdrehung
// Diesen Wert musst du an deine Riemen-/Pulley-Übersetzung anpassen.
#define GEAR_RATIO 2.0

// Falls der Teller falsch herum läuft: 1 auf -1 ändern.
#define DIRECTION 1


// =====================================================
// Drehzahlbereich
// =====================================================

// Enthält 33 1/3, 45 und 78 rpm.
// Für feinere Kontrolle nur für LP/Single: MAX_PLATTER_RPM auf 46.0 setzen.
#define MIN_PLATTER_RPM 60.0
#define MAX_PLATTER_RPM 80.0

// Unterhalb dieses Potiwerts wird sanft abgebremst und dann abgeschaltet.
#define OFF_THRESHOLD 40

// Hysterese gegen Flackern an der Ein-/Aus-Schwelle
#define OFF_HYSTERESIS 10


// =====================================================
// Laufruhe / Rampe / Filter
// =====================================================

// Poti-Glättung.
// Kleiner = ruhiger, aber träger.
#define POT_FILTER_FACTOR 0.05

// Beschleunigungs-/Bremsrampe in Steps/s².
// Niedriger = sanfter/leiser, aber längere Anlaufzeit.
#define RAMP_STEPS_PER_SEC2 1200.0

// Maximale STEP-Frequenz.
// Bei MICROSTEPS=8 und GEAR_RATIO=2:
// 33,33 rpm ≈ 1778 steps/s
// 45 rpm    ≈ 2400 steps/s
// 78 rpm    ≈ 4160 steps/s
#define MAX_STEP_RATE 4300.0

// Debug-Ausgabe.
// Für finalen Betrieb auf 0 setzen.
#define DEBUG_OUTPUT 0


// =====================================================
// AccelStepper
// =====================================================

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

float filteredPot = 0.0;
float targetStepRate = 0.0;
float currentStepRate = 0.0;

bool driverEnabled = false;
bool offState = true;

unsigned long lastRampMicros = 0;
unsigned long lastDebugMillis = 0;


// =====================================================
// Hilfsfunktionen
// =====================================================

float mapFloat(float x, float inMin, float inMax, float outMin, float outMax) {
  return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}

float platterRpmToStepRate(float platterRpm) {
  return platterRpm * MOTOR_FULL_STEPS_PER_REV * MICROSTEPS * GEAR_RATIO / 60.0;
}

float stepRateToPlatterRpm(float stepRate) {
  return stepRate * 60.0 / (MOTOR_FULL_STEPS_PER_REV * MICROSTEPS * GEAR_RATIO);
}

void enableDriver() {
  if (!driverEnabled) {
    // DRV8825 / A4988 Enable ist active LOW:
    // LOW = aktiv
    // HIGH = deaktiviert
    digitalWrite(ENABLE_PIN, LOW);
    stepper.enableOutputs();
    driverEnabled = true;
  }
}

void disableDriver() {
  if (driverEnabled) {
    stepper.disableOutputs();
    digitalWrite(ENABLE_PIN, HIGH);
    driverEnabled = false;
  }
}

void updateTargetFromPot() {
  int rawPot = analogRead(POT_PIN);

  // Potiwert glätten
  filteredPot += POT_FILTER_FACTOR * ((float)rawPot - filteredPot);

  // Aus-Zone mit Hysterese
  if (offState) {
    if (filteredPot > OFF_THRESHOLD + OFF_HYSTERESIS) {
      offState = false;
    }
  } else {
    if (filteredPot < OFF_THRESHOLD) {
      offState = true;
    }
  }

  if (offState) {
    targetStepRate = 0.0;
    return;
  }

  float platterRpm = mapFloat(
    filteredPot,
    OFF_THRESHOLD + OFF_HYSTERESIS,
    1023.0,
    MIN_PLATTER_RPM,
    MAX_PLATTER_RPM
  );

  if (platterRpm < MIN_PLATTER_RPM) platterRpm = MIN_PLATTER_RPM;
  if (platterRpm > MAX_PLATTER_RPM) platterRpm = MAX_PLATTER_RPM;

  targetStepRate = platterRpmToStepRate(platterRpm);

  if (targetStepRate > MAX_STEP_RATE) {
    targetStepRate = MAX_STEP_RATE;
  }

  targetStepRate *= DIRECTION;
}

void updateSpeedRamp() {
  unsigned long now = micros();

  if (lastRampMicros == 0) {
    lastRampMicros = now;
    return;
  }

  float dt = (now - lastRampMicros) / 1000000.0;
  lastRampMicros = now;

  float maxChange = RAMP_STEPS_PER_SEC2 * dt;

  if (currentStepRate < targetStepRate) {
    currentStepRate += maxChange;
    if (currentStepRate > targetStepRate) currentStepRate = targetStepRate;
  } else if (currentStepRate > targetStepRate) {
    currentStepRate -= maxChange;
    if (currentStepRate < targetStepRate) currentStepRate = targetStepRate;
  }

  // Im Nullbereich: erst sanft auf 0, dann Treiber komplett aus.
  if (offState && fabs(currentStepRate) < 1.0) {
    currentStepRate = 0.0;
    stepper.setSpeed(0);
    disableDriver();
    return;
  }

  if (fabs(currentStepRate) >= 1.0) {
    enableDriver();
  }

  stepper.setSpeed(currentStepRate);
}

void debugOutput() {
  #if DEBUG_OUTPUT
  unsigned long now = millis();

  if (now - lastDebugMillis >= 1000) {
    lastDebugMillis = now;

    Serial.print(F("Poti="));
    Serial.print((int)filteredPot);

    Serial.print(F(" | Ziel rpm="));
    Serial.print(stepRateToPlatterRpm(fabs(targetStepRate)), 2);

    Serial.print(F(" | Ist rpm="));
    Serial.print(stepRateToPlatterRpm(fabs(currentStepRate)), 2);

    Serial.print(F(" | steps/s="));
    Serial.print(fabs(currentStepRate), 0);

    Serial.print(F(" | Driver="));
    Serial.println(driverEnabled ? F("ON") : F("OFF"));
  }
  #endif
}


// =====================================================
// Setup
// =====================================================

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

  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);
  pinMode(POT_PIN, INPUT);

  // Treiber beim Start deaktivieren
  digitalWrite(ENABLE_PIN, HIGH);
  driverEnabled = false;

  stepper.setMaxSpeed(MAX_STEP_RATE);
  stepper.setSpeed(0);

  disableDriver();

  Serial.println(F("NANO CNC SHIELD V4 TURNTABLE CONTROLLER READY"));
  Serial.println(F("DIR=D2 STEP=D5 EN=D8 POT=A7"));
  Serial.println(F("Poti: null = off, sonst 40-85 rpm"));
}


// =====================================================
// Loop
// =====================================================

void loop() {
  updateTargetFromPot();
  updateSpeedRamp();

  if (driverEnabled && fabs(currentStepRate) >= 1.0) {
    stepper.runSpeed();
  }

  debugOutput();
}