1144 lines
31 KiB
C++
1144 lines
31 KiB
C++
#include <LiquidCrystal_I2C.h>
|
|
#include <AccelStepper.h>
|
|
#include <Keypad.h>
|
|
#include <EEPROM.h>
|
|
|
|
// ======================================================
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
}
|