commit 1c87618a239cd94ee4759074cb66be3886ba7a7d Author: rifkimaul2005 Date: Thu Jul 30 00:45:32 2026 +0700 Upload files to "/" diff --git a/kode_penggulungan_otomaris.ino b/kode_penggulungan_otomaris.ino new file mode 100644 index 0000000..288024a --- /dev/null +++ b/kode_penggulungan_otomaris.ino @@ -0,0 +1,1143 @@ +#include +#include +#include +#include + +// ====================================================== +// LCD I2C +// ====================================================== +LiquidCrystal_I2C lcd(0x27, 16, 2); + +// ====================================================== +// TB6600 + NEMA17 (Guide Kiri-Kanan) +// ====================================================== +#define STEP_PIN 26 +#define DIR_PIN 27 +#define EN_PIN 15 + +AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN); + +// ====================================================== +// BTS7960 + PG45 (Motor Penarik Kabel) - SELALU KANAN/FORWARD +// ====================================================== +#define RPWM 14 +#define LPWM 12 +#define PWM_FREQ 20000 +#define PWM_RESOLUTION 8 + +int motorSpeed = 0; +unsigned long lastMotorRamp = 0; + +// ====================================================== +// BUZZER +// ====================================================== +#define BUZZER_PIN 25 + +// ====================================================== +// KY-040 ENCODER [FIX: DT = GPIO 34 (input only OK)] +// ====================================================== +#define ENC_CLK 32 +#define ENC_DT 34 + +volatile unsigned long pulseCount = 0; +unsigned long lastPulseCount = 0; +unsigned long lastPulseDetected = 0; +volatile unsigned long lastPulseTime = 0; +const unsigned long DEBOUNCE_US = 3000; + +// ====================================================== +// ENCODER OFFSET - KABEL MELEWATI ENCODER SEBELUM SPOOL +// ====================================================== +const float ENCODER_OFFSET_METER = 0.80; // 80 cm offset +bool encoderReset = false; // Flag untuk reset encoder di awal proses +unsigned long resetPulseCount = 0; // Pulse count saat reset + +void IRAM_ATTR onEncoderPulse() { + unsigned long now = micros(); + if ((now - lastPulseTime) > DEBOUNCE_US) { + if (digitalRead(ENC_DT) == HIGH) { + pulseCount++; + } + lastPulseTime = now; + } +} + +// ====================================================== +// KEYPAD 4x4 [FIX: C4 = GPIO 33 (full I/O)] +// ====================================================== +const byte ROWS = 4; +const byte COLS = 4; +char keys[ROWS][COLS] = { + {'1','2','3','A'}, + {'4','5','6','B'}, + {'7','8','9','C'}, + {'*','0','#','D'} +}; + +byte rowPins[ROWS] = {4, 16, 17, 5}; +byte colPins[COLS] = {18, 19, 23, 33}; + +Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); + +// ====================================================== +// VARIABLE INPUT & PROSES +// ====================================================== +char inputBuffer[8] = ""; +byte inputIndex = 0; + +float targetMeter = 0; +float sisaMeter = 0; +float totalMeter = 0; + +// ====================================================== +// KALIBRASI ENCODER +// ====================================================== +float meterPerPulse = 0.01; +bool kalibrasiMode = false; +unsigned long kalibrasiPulseStart = 0; +unsigned long kalibrasiStartTime = 0; +const unsigned long KALIBRASI_TIMEOUT = 30000; // 30 detik timeout + +bool autoMode = false; +bool prosesSelesai = false; + +// ====================================================== +// MODE MANUAL 20 PUTARAN +// ====================================================== +enum ManualState { + MANUAL_IDLE, + MANUAL_MAJU_20, + MANUAL_JEDA_1, + MANUAL_MUNDUR_20, + MANUAL_JEDA_2, + MANUAL_SELESAI +}; + +ManualState manualState = MANUAL_IDLE; + +const float STEPS_PER_REV = 3200.0; +const int PUTARAN_20 = 20; +const long TARGET_STEPS_20 = (long)(STEPS_PER_REV * PUTARAN_20); + +const float MANUAL_MAX_SPEED = 3000; +const float MANUAL_ACCEL = 1500; +const unsigned long MANUAL_JEDA_MS = 500; + +unsigned long manualJedaStart = 0; +float putaranSaatIni = 0; + +// ====================================================== +// LOGIKA STEPPER AUTO - 65 PUTARAN KANAN-KIRI LOOP +// ====================================================== +const int PUTARAN_65 = 65; +const long STEPS_65_PUTARAN = (long)(STEPS_PER_REV * PUTARAN_65); + +const float AUTO_MAX_SPEED = 15000; +const float AUTO_ACCEL = 8000; + +bool arahKanan = true; + +// ====================================================== +// TOGGLE STEPPER KE KIRI (TOMBOL A) +// ====================================================== +bool stepperKiriJalan = false; + +// ====================================================== +// EEPROM ADDRESSES +// ====================================================== +#define EEPROM_ADDR_CALIBRATED 0 +#define EEPROM_ADDR_METER_PP 4 + +// ====================================================== +// TIMING VARIABLES +// ====================================================== +unsigned long lastKey = 0; +unsigned long lastLCD = 0; +unsigned long lastSerialPrint = 0; + +// ====================================================== +// BUZZER FUNCTIONS +// ====================================================== +void beep(int waktu) { + digitalWrite(BUZZER_PIN, HIGH); + delay(waktu); + digitalWrite(BUZZER_PIN, LOW); +} + +void selesaiBuzzer() { + for (int i = 0; i < 5; i++) { + beep(100); + delay(100); + } +} + +void errorBuzzer() { + for (int i = 0; i < 3; i++) { + beep(300); + delay(200); + } +} + +void kalibrasiBuzzer() { + beep(500); + delay(200); + beep(500); + delay(200); + beep(500); +} + +// ====================================================== +// LCD FUNCTIONS +// ====================================================== +void lcdInit() { + Wire.begin(21, 22); + delay(100); + lcd.begin(16, 2); + delay(100); + lcd.backlight(); + delay(100); + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("M14 Spool 16cm"); + lcd.setCursor(0,1); + lcd.print("Ready"); + delay(800); + lcd.clear(); +} + +void tampilLCD() { + static bool lastModeKalibrasi = false; + static bool lastModeAuto = false; + static bool lastModeSelesai = false; + static bool lastModeInput = false; + static bool lastKiriJalan = false; + static ManualState lastManualState = MANUAL_IDLE; + + bool modeKalibrasi = kalibrasiMode; + bool modeAuto = autoMode; + bool modeSelesai = prosesSelesai; + bool modeInput = !kalibrasiMode && !autoMode && !prosesSelesai && manualState == MANUAL_IDLE && !stepperKiriJalan; + bool kiriJalan = stepperKiriJalan; + + if (modeKalibrasi != lastModeKalibrasi || + modeAuto != lastModeAuto || + modeSelesai != lastModeSelesai || + modeInput != lastModeInput || + manualState != lastManualState || + kiriJalan != lastKiriJalan) { + lcd.clear(); + lastModeKalibrasi = modeKalibrasi; + lastModeAuto = modeAuto; + lastModeSelesai = modeSelesai; + lastModeInput = modeInput; + lastManualState = manualState; + lastKiriJalan = kiriJalan; + } + + if (stepperKiriJalan) { + lcd.setCursor(0,0); + lcd.print("KE KIRI... "); + lcd.setCursor(0,1); + lcd.print("Pos:"); + lcd.print(stepper.currentPosition()); + lcd.print(" "); + } + else if (kalibrasiMode) { + lcd.setCursor(0,0); + lcd.print("KALIBRASI "); + lcd.setCursor(0,1); + lcd.print("Pulse:"); + unsigned long currentPulse = pulseCount; + unsigned long diff = currentPulse - kalibrasiPulseStart; + lcd.print(" "); + lcd.setCursor(6,1); + lcd.print(diff); + lcd.print(" "); + + // Tampilkan sisa waktu + unsigned long elapsed = millis() - kalibrasiStartTime; + unsigned long remaining = (KALIBRASI_TIMEOUT - elapsed) / 1000; + lcd.setCursor(12,1); + lcd.print(remaining); + lcd.print("s "); + } + else if (autoMode) { + lcd.setCursor(0,0); + lcd.print("Sisa:"); + lcd.print(sisaMeter, 1); + lcd.print("m "); + lcd.setCursor(0,1); + lcd.print(arahKanan ? ">>" : "<<"); + lcd.print(" "); + lcd.print(totalMeter, 1); + lcd.print("m "); + + // Tambahkan indikator offset di pojok + lcd.setCursor(13,0); + lcd.print("+80"); + } + else if (manualState != MANUAL_IDLE && manualState != MANUAL_SELESAI) { + lcd.setCursor(0,0); + lcd.print("M20:"); + lcd.print(abs(putaranSaatIni), 1); + lcd.print("/20"); + lcd.setCursor(0,1); + if (manualState == MANUAL_MAJU_20) { + lcd.print("MAJU CW "); + } else if (manualState == MANUAL_JEDA_1) { + lcd.print("JEDA... "); + } else if (manualState == MANUAL_MUNDUR_20) { + lcd.print("MUNDUR CCW "); + } else if (manualState == MANUAL_JEDA_2) { + lcd.print("JEDA... "); + } + } + else if (manualState == MANUAL_SELESAI) { + lcd.setCursor(0,0); + lcd.print("M20 SELESAI! "); + lcd.setCursor(0,1); + lcd.print("20CW 20CCW Done"); + } + else if (prosesSelesai) { + lcd.setCursor(0,0); + lcd.print("SELESAI "); + lcd.setCursor(0,1); + lcd.print("Total:"); + lcd.print(totalMeter, 2); + lcd.print("m"); + } + else { + lcd.setCursor(0,0); + lcd.print("INPUT:"); + lcd.print(inputBuffer); + for (int i = inputIndex; i < 6; i++) { + lcd.print(" "); + } + lcd.setCursor(0,1); + lcd.print("A=KIRI #=GO"); + lcd.print(" "); + // Tampilkan status kalibrasi di pojok + if (meterPerPulse > 0 && meterPerPulse < 1) { + lcd.setCursor(13,1); + lcd.print("CLB"); + } + } +} + +// ====================================================== +// STOP STEPPER +// ====================================================== +void stopStepperNow() { + stepper.stop(); + unsigned long timeout = millis() + 3000; + while (stepper.distanceToGo() != 0 && millis() < timeout) { + stepper.run(); + } + long currentPos = stepper.currentPosition(); + stepper.setCurrentPosition(currentPos); + stepper.moveTo(currentPos); + stepper.setSpeed(0); + digitalWrite(EN_PIN, HIGH); +} + +// ====================================================== +// EEPROM +// ====================================================== +void saveCalibration() { + EEPROM.writeFloat(EEPROM_ADDR_METER_PP, meterPerPulse); + EEPROM.writeInt(EEPROM_ADDR_CALIBRATED, 1); + EEPROM.commit(); + Serial.println("Kalibrasi tersimpan ke EEPROM!"); +} + +void loadCalibration() { + int calibrated = EEPROM.readInt(EEPROM_ADDR_CALIBRATED); + if (calibrated == 1) { + float saved = EEPROM.readFloat(EEPROM_ADDR_METER_PP); + if (saved > 0.0 && saved < 1.0) { + meterPerPulse = saved; + Serial.print("Calibration loaded: "); + Serial.println(meterPerPulse, 6); + } + } +} + +// ====================================================== +// KALIBRASI FUNCTIONS +// ====================================================== +void toggleKalibrasi() { + if (autoMode || manualState != MANUAL_IDLE || stepperKiriJalan) { + Serial.println("ERROR: Sedang mode aktif!"); + errorBuzzer(); + return; + } + + if (!kalibrasiMode) { + // Start kalibrasi + kalibrasiMode = true; + kalibrasiPulseStart = pulseCount; + kalibrasiStartTime = millis(); + + // Matikan auto mode + autoMode = false; + prosesSelesai = false; + + // Hidupkan motor penarik dengan kecepatan rendah + motorSpeed = 50; + ledcWrite(RPWM, motorSpeed); + ledcWrite(LPWM, 0); + + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("KALIBRASI"); + lcd.setCursor(0,1); + lcd.print("Gulung 1 meter"); + + Serial.println(""); + Serial.println("========================================"); + Serial.println(" MULAI KALIBRASI"); + Serial.println("========================================"); + Serial.println("Gulung kabel tepat 1 meter"); + Serial.println("Tekan C lagi untuk selesai"); + Serial.println("Atau tunggu 30 detik timeout"); + Serial.println("========================================"); + + kalibrasiBuzzer(); + } else { + // Stop kalibrasi + unsigned long currentPulse = pulseCount; + unsigned long totalPulse = currentPulse - kalibrasiPulseStart; + + // Matikan motor + motorSpeed = 0; + ledcWrite(RPWM, 0); + ledcWrite(LPWM, 0); + + kalibrasiMode = false; + + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("Pulse:"); + lcd.print(totalPulse); + + Serial.println(""); + Serial.println("========================================"); + Serial.println(" SELESAI KALIBRASI"); + Serial.println("========================================"); + Serial.print("Total pulse untuk 1 meter: "); + Serial.println(totalPulse); + + if (totalPulse > 0) { + float newMeterPerPulse = 1.0 / (float)totalPulse; + meterPerPulse = newMeterPerPulse; + + Serial.print("meterPerPulse = "); + Serial.println(newMeterPerPulse, 6); + + saveCalibration(); + + lcd.setCursor(0,1); + lcd.print("Saved! "); + lcd.print(meterPerPulse, 6); + + kalibrasiBuzzer(); + } else { + Serial.println("ERROR: Tidak ada pulse terbaca!"); + lcd.setCursor(0,1); + lcd.print("ERROR! No pulse"); + errorBuzzer(); + } + + delay(3000); + tampilLCD(); + } +} + +void checkKalibrasiTimeout() { + if (kalibrasiMode) { + if (millis() - kalibrasiStartTime > KALIBRASI_TIMEOUT) { + Serial.println("Kalibrasi timeout!"); + + // Matikan motor + motorSpeed = 0; + ledcWrite(RPWM, 0); + ledcWrite(LPWM, 0); + + kalibrasiMode = false; + + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("TIMEOUT!"); + lcd.setCursor(0,1); + lcd.print("Coba ulang"); + errorBuzzer(); + delay(2000); + tampilLCD(); + } + } +} + +// ====================================================== +// RESET ENCODER DI AWAL PROSES +// ====================================================== +void resetEncoderForProcess() { + if (!encoderReset) { + noInterrupts(); + pulseCount = 0; + lastPulseCount = 0; + interrupts(); + encoderReset = true; + resetPulseCount = 0; + + // Set total meter dengan offset 80cm + totalMeter = ENCODER_OFFSET_METER; + sisaMeter = targetMeter - ENCODER_OFFSET_METER; + if (sisaMeter < 0) sisaMeter = 0; + + Serial.println("=== ENCODER RESET ==="); + Serial.print("Offset 80cm ditambahkan, totalMeter = "); + Serial.println(totalMeter, 3); + Serial.print("Sisa meter = "); + Serial.println(sisaMeter, 3); + } +} + +// ====================================================== +// SETUP +// ====================================================== +void setup() { + Serial.begin(115200); + delay(1000); + + EEPROM.begin(512); + loadCalibration(); + + Serial.println("========================================"); + Serial.println(" M14 SPOOL 16cm - KABEL OTOMATIS"); + Serial.println(" [v5.5 - With Encoder Offset 80cm]"); + Serial.println("========================================"); + Serial.print("Keypad Row: "); + Serial.print(rowPins[0]); Serial.print(", "); + Serial.print(rowPins[1]); Serial.print(", "); + Serial.print(rowPins[2]); Serial.print(", "); + Serial.println(rowPins[3]); + Serial.print("Keypad Col: "); + Serial.print(colPins[0]); Serial.print(", "); + Serial.print(colPins[1]); Serial.print(", "); + Serial.print(colPins[2]); Serial.print(", "); + Serial.println(colPins[3]); + Serial.print("Encoder CLK: "); Serial.println(ENC_CLK); + Serial.print("Encoder DT: "); Serial.println(ENC_DT); + Serial.print("Steps 20 putaran: "); + Serial.println(TARGET_STEPS_20); + Serial.print("Steps 65 putaran: "); + Serial.println(STEPS_65_PUTARAN); + Serial.print("Current meterPerPulse: "); + Serial.println(meterPerPulse, 6); + Serial.print("Encoder Offset: "); + Serial.print(ENCODER_OFFSET_METER); + Serial.println(" meter (80cm)"); + Serial.println("========================================"); + Serial.println("C = Kalibrasi (tekan 2x)"); + Serial.println("A = Ke Kiri"); + Serial.println("B = Manual 20 Putaran"); + Serial.println("* = Stop"); + Serial.println("# = GO (mulai proses)"); + Serial.println("========================================"); + + lcdInit(); + + pinMode(BUZZER_PIN, OUTPUT); + digitalWrite(BUZZER_PIN, LOW); + + pinMode(ENC_CLK, INPUT_PULLUP); + pinMode(ENC_DT, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(ENC_CLK), onEncoderPulse, FALLING); + + pinMode(EN_PIN, OUTPUT); + digitalWrite(EN_PIN, HIGH); + + ledcAttach(RPWM, PWM_FREQ, PWM_RESOLUTION); + ledcAttach(LPWM, PWM_FREQ, PWM_RESOLUTION); + ledcWrite(RPWM, 0); + ledcWrite(LPWM, 0); + + stepper.setMaxSpeed(AUTO_MAX_SPEED); + stepper.setAcceleration(AUTO_ACCEL); + stepper.setSpeed(0); + + tampilLCD(); + Serial.println("System Ready"); +} + +// ====================================================== +// LOOP +// ====================================================== +// ====================================================== +// LOOP (LENGKAP) +// ====================================================== +void loop() { + stepper.run(); + + bacaKeypad(); + bacaEncoder(); + handleManual20(); + checkKalibrasiTimeout(); + + if (stepperKiriJalan && stepper.distanceToGo() == 0) { + stepperKiriJalan = false; + digitalWrite(EN_PIN, HIGH); + Serial.println(">>> SUDAH SAMPAI KIRI <<<"); + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("SUDAH KIRI"); + delay(800); + tampilLCD(); + } + + if (autoMode) { + if (millis() - lastMotorRamp > 30) { + lastMotorRamp = millis(); + if (motorSpeed < 100) motorSpeed += 4; + } + ledcWrite(RPWM, motorSpeed); + ledcWrite(LPWM, 0); + } + else { + if (millis() - lastMotorRamp > 30) { + lastMotorRamp = millis(); + if (motorSpeed > 0) { + motorSpeed -= 8; + if (motorSpeed < 0) motorSpeed = 0; + } + } + ledcWrite(RPWM, motorSpeed); + ledcWrite(LPWM, 0); + } + + if (autoMode && (millis() - lastPulseDetected > 15000)) { + autoMode = false; + stopStepperNow(); + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("ENCODER ERROR"); + errorBuzzer(); + } + + if (millis() - lastLCD > 200) { + tampilLCD(); + lastLCD = millis(); + } + + if (millis() - lastSerialPrint > 1000) { + if (autoMode || kalibrasiMode || manualState != MANUAL_IDLE || stepperKiriJalan) { + Serial.print("Pos: "); + Serial.print(stepper.currentPosition()); + Serial.print(" | KiriJalan: "); + Serial.print(stepperKiriJalan ? "YES" : "NO"); + Serial.print(" | Putaran: "); + Serial.print(abs(putaranSaatIni), 2); + if (kalibrasiMode) { + Serial.print(" | Kalibrasi Pulse: "); + Serial.print(pulseCount - kalibrasiPulseStart); + } + if (autoMode) { + Serial.print(" | Total: "); + Serial.print(totalMeter, 3); + Serial.print("m | Sisa: "); + Serial.print(sisaMeter, 3); + Serial.print("m | Offset: 0.80m"); + } + Serial.println(""); + } + lastSerialPrint = millis(); + } +} + +// ====================================================== +// HANDLE MANUAL 20 PUTARAN +// ====================================================== +void handleManual20() { + if (manualState == MANUAL_IDLE || manualState == MANUAL_SELESAI) return; + + hitungPutaranManual(); + + switch (manualState) { + case MANUAL_MAJU_20: + if (stepper.distanceToGo() == 0) { + Serial.println(">>> 20 PUTARAN MAJU SELESAI!"); + Serial.print("Posisi: "); + Serial.print(stepper.currentPosition()); + Serial.println(" steps"); + manualState = MANUAL_JEDA_1; + manualJedaStart = millis(); + } + break; + + case MANUAL_JEDA_1: + if (millis() - manualJedaStart >= MANUAL_JEDA_MS) { + Serial.println(">>> MULAI MUNDUR 20 PUTARAN"); + stepper.moveTo(0); + manualState = MANUAL_MUNDUR_20; + putaranSaatIni = 0; + } + break; + + case MANUAL_MUNDUR_20: + if (stepper.distanceToGo() == 0) { + Serial.println(">>> 20 PUTARAN MUNDUR SELESAI!"); + Serial.print("Posisi: "); + Serial.print(stepper.currentPosition()); + Serial.println(" steps"); + manualState = MANUAL_JEDA_2; + manualJedaStart = millis(); + } + break; + + case MANUAL_JEDA_2: + if (millis() - manualJedaStart >= MANUAL_JEDA_MS) { + Serial.println("================================="); + Serial.println(" MANUAL 20 PUTARAN SELESAI!"); + Serial.println("================================="); + manualState = MANUAL_SELESAI; + digitalWrite(EN_PIN, HIGH); + for (int i = 0; i < 3; i++) { + beep(200); + delay(100); + } + } + break; + + default: + break; + } +} + +// ====================================================== +// HITUNG PUTARAN REAL-TIME +// ====================================================== +void hitungPutaranManual() { + long posisi = stepper.currentPosition(); + putaranSaatIni = (float)posisi / STEPS_PER_REV; +} + +// ====================================================== +// FUNGSI MANUAL STEPPER +// ====================================================== +void mulaiManualMaju20() { + if (autoMode || kalibrasiMode || stepperKiriJalan) { + Serial.println("ERROR: Sedang mode aktif!"); + errorBuzzer(); + return; + } + Serial.println(""); + Serial.println(">>> MULAI MAJU 20 PUTARAN <<<"); + Serial.print("Target: "); + Serial.print(TARGET_STEPS_20); + Serial.println(" steps"); + + manualState = MANUAL_MAJU_20; + putaranSaatIni = 0; + + digitalWrite(EN_PIN, LOW); + stepper.setMaxSpeed(MANUAL_MAX_SPEED); + stepper.setAcceleration(MANUAL_ACCEL); + stepper.setCurrentPosition(0); + stepper.moveTo(TARGET_STEPS_20); + + beep(100); +} + +void mulaiManualMundur20() { + if (autoMode || kalibrasiMode || stepperKiriJalan) { + Serial.println("ERROR: Sedang mode aktif!"); + errorBuzzer(); + return; + } + Serial.println(""); + Serial.println(">>> MULAI MUNDUR 20 PUTARAN <<<"); + + manualState = MANUAL_MUNDUR_20; + putaranSaatIni = 0; + + digitalWrite(EN_PIN, LOW); + stepper.setMaxSpeed(MANUAL_MAX_SPEED); + stepper.setAcceleration(MANUAL_ACCEL); + stepper.setCurrentPosition(0); + stepper.moveTo(-TARGET_STEPS_20); + + beep(100); +} + +void stopManual20() { + Serial.println(">>> STOP MANUAL 20 <<<"); + stepper.stop(); + unsigned long timeout = millis() + 3000; + while (stepper.distanceToGo() != 0 && millis() < timeout) { + stepper.run(); + } + long currentPos = stepper.currentPosition(); + stepper.setCurrentPosition(currentPos); + stepper.moveTo(currentPos); + stepper.setSpeed(0); + digitalWrite(EN_PIN, HIGH); + + manualState = MANUAL_IDLE; + putaranSaatIni = 0; + + beep(100); + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("MANUAL STOPPED"); + delay(1000); + tampilLCD(); +} + +// ====================================================== +// STEPPER KE KIRI (TOMBOL A) +// ====================================================== +void toggleStepperKiri() { + if (autoMode || kalibrasiMode) { + Serial.println("ERROR: Sedang mode auto/kalibrasi!"); + errorBuzzer(); + return; + } + if (manualState != MANUAL_IDLE && manualState != MANUAL_SELESAI) { + Serial.println("ERROR: Sedang mode manual!"); + errorBuzzer(); + return; + } + + if (!stepperKiriJalan) { + Serial.println(""); + Serial.println(">>> MULAI KE KIRI <<<"); + Serial.print("Dari: "); + Serial.print(stepper.currentPosition()); + Serial.println(" -> 0"); + + stepperKiriJalan = true; + digitalWrite(EN_PIN, LOW); + stepper.setMaxSpeed(AUTO_MAX_SPEED); + stepper.setAcceleration(AUTO_ACCEL); + stepper.moveTo(0); + arahKanan = false; + + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("KE KIRI... "); + beep(100); + } else { + Serial.println(">>> STOP KE KIRI <<<"); + stepperKiriJalan = false; + stopStepperNow(); + + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("STOP KIRI"); + delay(1000); + tampilLCD(); + } +} + +// ====================================================== +// KEYPAD +// ====================================================== +void bacaKeypad() { + char key = keypad.getKey(); + if (millis() - lastKey < 150) return; + if (!key) return; + + lastKey = millis(); + beep(15); + + // ==================================================== + // TOMBOL C = KALIBRASI (TANPA MENGGANGGU SISTEM LAIN) + // ==================================================== + if (key == 'C') { + // Cek apakah bisa kalibrasi + if (autoMode || manualState != MANUAL_IDLE || stepperKiriJalan) { + Serial.println("ERROR: Sedang mode aktif!"); + errorBuzzer(); + return; + } + toggleKalibrasi(); + return; + } + + // Tombol A = TOGGLE KE KIRI + if (key == 'A') { + // Kalibrasi mode tidak bisa akses ke kiri + if (kalibrasiMode) { + Serial.println("ERROR: Sedang kalibrasi!"); + errorBuzzer(); + return; + } + toggleStepperKiri(); + return; + } + + // Tombol * = STOP + if (key == '*') { + if (stepperKiriJalan) { + toggleStepperKiri(); + } else if (manualState != MANUAL_IDLE && manualState != MANUAL_SELESAI) { + stopManual20(); + } else if (kalibrasiMode) { + // Jika kalibrasi, stop motor tapi tetap di mode kalibrasi + motorSpeed = 0; + ledcWrite(RPWM, 0); + ledcWrite(LPWM, 0); + Serial.println("Motor kalibrasi di-stop"); + } else { + stopProses(); + } + return; + } + + // Tombol B = Manual Maju 20 + Mundur 20 + if (key == 'B') { + if (autoMode || kalibrasiMode || stepperKiriJalan) { + Serial.println("ERROR: Sedang mode aktif!"); + errorBuzzer(); + return; + } + if (manualState != MANUAL_IDLE && manualState != MANUAL_SELESAI) { + stopManual20(); + return; + } + mulaiManualMaju20(); + return; + } + + // Jika sedang kalibrasi, tombol lain tidak berfungsi (kecuali C dan *) + if (kalibrasiMode) return; + + // Jika mode manual aktif, tombol angka tidak berfungsi + if (manualState != MANUAL_IDLE && manualState != MANUAL_SELESAI) return; + + // Tombol D = DELETE / HAPUS + if (key == 'D') { + if (inputIndex > 0) { + inputIndex--; + inputBuffer[inputIndex] = '\0'; + Serial.print("Hapus: inputBuffer = "); + Serial.println(inputBuffer); + tampilLCD(); + } + return; + } + + // Tombol angka = Input meter + if (key >= '0' && key <= '9') { + if (prosesSelesai) { + prosesSelesai = false; + inputBuffer[0] = '\0'; + inputIndex = 0; + } + if (inputIndex < 5) { + inputBuffer[inputIndex] = key; + inputIndex++; + inputBuffer[inputIndex] = '\0'; + Serial.print("Input: "); + Serial.println(inputBuffer); + tampilLCD(); + } + } + else if (key == '#') { + if (inputIndex > 0) { + startProses(); + } + } +} + +// ====================================================== +// START PROSES AUTO - 65 PUTARAN LOOP +// ====================================================== +void startProses() { + if (manualState != MANUAL_IDLE && manualState != MANUAL_SELESAI) { + Serial.println("ERROR: Sedang mode manual!"); + errorBuzzer(); + return; + } + if (stepperKiriJalan) { + Serial.println("ERROR: Stepper sedang ke kiri!"); + errorBuzzer(); + return; + } + if (kalibrasiMode) { + Serial.println("ERROR: Sedang kalibrasi!"); + errorBuzzer(); + return; + } + + targetMeter = atof(inputBuffer); + if (targetMeter <= 0.0 || targetMeter > 9999.0) { + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("ERROR: Invalid"); + lcd.setCursor(0,1); + lcd.print("Input!"); + errorBuzzer(); + delay(2000); + inputBuffer[0] = '\0'; + inputIndex = 0; + return; + } + + // Cek apakah kalibrasi sudah dilakukan + if (meterPerPulse <= 0.0 || meterPerPulse >= 1.0) { + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("ERROR: Belum"); + lcd.setCursor(0,1); + lcd.print("Kalibrasi!"); + errorBuzzer(); + delay(2000); + return; + } + + prosesSelesai = false; + autoMode = true; + arahKanan = true; + motorSpeed = 0; + lastPulseDetected = millis(); + encoderReset = false; // Reset flag untuk proses baru + + // RESET ENCODER KE 0 DAN SET OFFSET 80CM + noInterrupts(); + pulseCount = 0; + lastPulseCount = 0; + interrupts(); + + // Set total meter dengan offset 80cm + totalMeter = ENCODER_OFFSET_METER; + sisaMeter = targetMeter - ENCODER_OFFSET_METER; + if (sisaMeter < 0) sisaMeter = 0; + + Serial.println("=== ENCODER RESET ==="); + Serial.print("Offset 80cm ditambahkan, totalMeter = "); + Serial.println(totalMeter, 3); + Serial.print("Sisa meter = "); + Serial.println(sisaMeter, 3); + + digitalWrite(EN_PIN, LOW); + + stepper.setMaxSpeed(AUTO_MAX_SPEED); + stepper.setAcceleration(AUTO_ACCEL); + stepper.setMinPulseWidth(5); + stepper.setCurrentPosition(0); + stepper.moveTo(STEPS_65_PUTARAN); + + Serial.println(""); + Serial.println("=== PROSES DIMULAI ==="); + Serial.print("Target: "); + Serial.print(targetMeter); + Serial.println(" meter"); + Serial.print("meterPerPulse: "); + Serial.println(meterPerPulse, 6); + Serial.println("Stepper loop: 65 putaran kanan <-> kiri"); + Serial.print("Encoder offset: "); + Serial.print(ENCODER_OFFSET_METER); + Serial.println(" meter (80cm)"); + + beep(100); + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("MULAI"); + lcd.setCursor(0,1); + lcd.print("Target:"); + lcd.print(targetMeter, 1); + lcd.print("m"); +} + +// ====================================================== +// STOP PROSES - BERHENTI TOTAL +// ====================================================== +void stopProses() { + autoMode = false; + kalibrasiMode = false; + + unsigned long stopTimeout = millis() + 2000; + while (motorSpeed > 0 && millis() < stopTimeout) { + if (millis() - lastMotorRamp > 30) { + lastMotorRamp = millis(); + motorSpeed -= 8; + if (motorSpeed < 0) motorSpeed = 0; + ledcWrite(RPWM, motorSpeed); + } + delay(5); + } + + ledcWrite(RPWM, 0); + ledcWrite(LPWM, 0); + stopStepperNow(); + + inputBuffer[0] = '\0'; + inputIndex = 0; + prosesSelesai = true; + + // Hitung total aktual (sudah termasuk offset 80cm) + float totalAktual = totalMeter; + if (totalAktual < 0) totalAktual = 0; + + Serial.println(""); + Serial.println("=== PROSES SELESAI ==="); + Serial.print("Total meter (termasuk 80cm offset): "); + Serial.print(totalAktual, 3); + Serial.println("m"); + Serial.print("Panjang kabel yang digulung: "); + Serial.print(totalAktual - ENCODER_OFFSET_METER, 3); + Serial.println("m (dari encoder)"); + Serial.print("Target: "); + Serial.print(targetMeter, 3); + Serial.println("m"); + + selesaiBuzzer(); + + lcd.clear(); + lcd.setCursor(0,0); + lcd.print("SELESAI"); + lcd.setCursor(0,1); + lcd.print("Total:"); + lcd.print(totalAktual, 2); + lcd.print("m"); +} + +// ====================================================== +// ENCODER + STEPPER 65 PUTARAN LOOP +// ====================================================== +void bacaEncoder() { + if (!autoMode && !kalibrasiMode) return; + + unsigned long currentPulse = pulseCount; + + if (currentPulse > lastPulseCount) { + unsigned long newPulses = currentPulse - lastPulseCount; + lastPulseCount = currentPulse; + lastPulseDetected = millis(); + + // Tambahkan pulse ke total meter (mulai dari offset 80cm) + totalMeter += (newPulses * meterPerPulse); + sisaMeter -= (newPulses * meterPerPulse); + if (sisaMeter < 0) sisaMeter = 0; + + if (autoMode) { + if (stepper.distanceToGo() == 0) { + if (arahKanan) { + stepper.moveTo(0); + arahKanan = false; + Serial.println("Kanan 65x selesai -> Langsung kiri 65x"); + } else { + stepper.moveTo(STEPS_65_PUTARAN); + arahKanan = true; + Serial.println("Kiri 65x selesai -> Langsung kanan 65x"); + } + } + + if (sisaMeter <= 0) { + Serial.println("TARGET TERCAPAI! STOP SEMUA."); + stopProses(); + } + } + } +}