#include #include ///////////////////////////////// // 25/08/2026 // Version 8.5YI // Por: LU4EG // Para Yaesu G-5400B ///////////////////////////////// // ============================== // LCD // ============================== LiquidCrystal lcd(2, 3, 4, 5, 6, 7); bool autoMode = false; bool lastConfigBtn = HIGH; unsigned long configPressTime = 0; // ============================== // RELAYS // ============================== #define RELAY_AZ_CW 12 #define RELAY_AZ_CCW 13 #define RELAY_EL_UP A2 #define RELAY_EL_DOWN A3 // ============================== // BUTTONS // ============================== #define BTN_AZ_CW 9 #define BTN_AZ_CCW 8 #define BTN_EL_DOWN 11 #define BTN_EL_UP A4 #define BTN_CONFIG 10 // ============================== // POTS // ============================== #define POT_AZ A0 #define POT_EL A1 // ============================== // CALIB DATA // ============================== struct CalibData { int azMin; int azMax; int elMin; int elMax; }; CalibData calib; // ============================== // TARGET GS-232 // ============================== int targetAz = 0; int targetEl = 0; char serialBuf[20]; byte serialPos = 0; // ============================== // CONTROL // ============================== #define AZ_DEADBAND 2 #define EL_DEADBAND 1 enum Dir { STOP, CW, CCW }; Dir azDir = STOP; Dir elDir = STOP; // ============================== // LCD CONTROL // ============================== unsigned long lastLCD = 0; const int lcdInterval = 200; // ============================== // CONFIG // ============================== byte cfgStep = 0; bool inConfig = false; // ============================== // LCD helper // ============================== void printLine(byte row, const char *msg) { lcd.setCursor(0, row); for (int i = 0; i < 16; i++) { lcd.print(msg[i] ? msg[i] : ' '); } } // ============================== // STOP // ============================== void stopAll() { digitalWrite(RELAY_AZ_CW, LOW); digitalWrite(RELAY_AZ_CCW, LOW); digitalWrite(RELAY_EL_UP, LOW); digitalWrite(RELAY_EL_DOWN, LOW); } // ============================== // ADC smoothing // ============================== int smoothRead(int pin) { long sum = 0; for (int i = 0; i < 5; i++) sum += analogRead(pin); return sum / 5; } // ============================== // AZ - YAESU G-5400B // ============================== int readAz() { int adc = smoothRead(POT_AZ); // ========================================== // EXTREMOS DE CALIBRACIÓN // ========================================== // En el Yaesu G-5400B: // // 180° = tensión MÁXIMA // 181° = tensión MÍNIMA // // Por eso la calibración está invertida. if (adc >= calib.azMin) return 180; if (adc <= calib.azMax) return 181; // ========================================== // INTERPOLACIÓN // ========================================== long recorrido = map(adc, calib.azMin, calib.azMax, 0, 359); int az = 180 - recorrido; // ========================================== // PASO CIRCULAR // ========================================== if (az < 0) az += 360; return az; } // ============================== // EL // ============================== int readEl() { return constrain( map(smoothRead(POT_EL), calib.elMin, calib.elMax, 0, 90), 0, 90); } // ============================== // SERIAL GS-232 // ============================== void handleSerial() { while (Serial.available()) { char c = Serial.read(); if (c == '\n' || c == '\r') { serialBuf[serialPos] = 0; if (serialBuf[0] == 'W') { int az, el; if (sscanf(serialBuf, "W%d %d", &az, &el) == 2) { targetAz = constrain(az, 0, 359); targetEl = constrain(el, 0, 90); } } serialPos = 0; } else { if (serialPos < sizeof(serialBuf) - 1) serialBuf[serialPos++] = c; } } } // ============================== // AUTO AZ // ============================== Dir autoAzDir() { int actual = readAz(); int d = abs(actual - targetAz); // Ya llegó (incluye 359↔0) if (d <= AZ_DEADBAND || d >= (360 - AZ_DEADBAND)) return STOP; // Cruce por el Norte if (actual <= 180 && targetAz > 180) return CCW; if (actual > 180 && targetAz <= 180) return CW; // Ambos entre 0..180 if (actual <= 180) { if (targetAz > actual) return CW; else return CCW; } // Ambos entre 181..359 if (targetAz > actual) return CW; else return CCW; } // ============================== // AUTO EL // ============================== Dir autoElDir() { int err = targetEl - readEl(); if (abs(err) <= EL_DEADBAND) return STOP; return (err > 0) ? CW : CCW; } // ============================== // INPUTS // ============================== void readInputs() { bool azCW = digitalRead(BTN_AZ_CW) == LOW; bool azCCW = digitalRead(BTN_AZ_CCW) == LOW; bool elUP = digitalRead(BTN_EL_UP) == LOW; bool elDN = digitalRead(BTN_EL_DOWN) == LOW; int az = readAz(); int el = readEl(); // ========================== // AUTO MODE (IGNORA BOTONES) // ========================== if (autoMode) { azDir = autoAzDir(); elDir = autoElDir(); return; } // ========================== // MANUAL MODE // ========================== // -------------------------- // AZIMUT (0–359) // -------------------------- // -------------------------- // AZIMUT (LÍMITES 180° / 181°) // -------------------------- if (azCW && !azCCW) { // ========================================== // LÍMITE EN 180° // ========================================== // No permitir seguir en este sentido. if (az == 180) { azDir = STOP; } else { azDir = CW; } } else if (azCCW && !azCW) { // ========================================== // LÍMITE EN 181° // ========================================== // No permitir seguir en este sentido. if (az == 181) { azDir = STOP; } else { azDir = CCW; } } else { azDir = STOP; } // -------------------------- // ELEVACIÓN (0–90) // -------------------------- if (elUP && !elDN) { if (el >= 90) { elDir = STOP; } else { elDir = CW; } } else if (elDN && !elUP) { if (el <= 0) { elDir = STOP; } else { elDir = CCW; } } else { elDir = STOP; } } // ============================== // DISPLAY // ============================== void handleNormal() { int az = readAz(); int el = readEl(); digitalWrite(RELAY_AZ_CW, azDir == CW); digitalWrite(RELAY_AZ_CCW, azDir == CCW); digitalWrite(RELAY_EL_UP, elDir == CW); digitalWrite(RELAY_EL_DOWN, elDir == CCW); char l1[17] = " "; char l2[17] = " "; if (autoMode) { snprintf(l1, 17, "TE:%02d TA:%03d Aut", targetEl, targetAz); } else { snprintf(l1, 17, "TE:-- TA:--- Man"); } snprintf(l2, 17, "%cE:%02d %cA:%03d", (elDir == CW) ? '^' : (elDir == CCW ? 'v' : '*'), el, (azDir == CW) ? '>' : (azDir == CCW ? '<' : '*'), az); printLine(0, l1); printLine(1, l2); } // ============================== // CALIBRATION // ============================== void handleCalibration() { static bool released = false; //------------------------------------ // Movimiento manual durante calibración //------------------------------------ bool azCW = digitalRead(BTN_AZ_CW) == LOW; bool azCCW = digitalRead(BTN_AZ_CCW) == LOW; bool elUP = digitalRead(BTN_EL_UP) == LOW; bool elDN = digitalRead(BTN_EL_DOWN) == LOW; digitalWrite(RELAY_AZ_CW, azCW && !azCCW); digitalWrite(RELAY_AZ_CCW, azCCW && !azCW); digitalWrite(RELAY_EL_UP, elUP && !elDN); digitalWrite(RELAY_EL_DOWN, elDN && !elUP); if (!released) { if (digitalRead(BTN_CONFIG) == HIGH) released = true; return; } switch (cfgStep) { case 0: printLine(0, "CAL1 AZ=180"); lcd.setCursor(0,1); lcd.print("ADC:"); lcd.print(smoothRead(POT_AZ)); lcd.print(" "); if (digitalRead(BTN_CONFIG) == LOW) { calib.azMin = smoothRead(POT_AZ); while (digitalRead(BTN_CONFIG) == LOW); delay(200); cfgStep++; } break; case 1: printLine(0, "CAL2 AZ=181"); lcd.setCursor(0,1); lcd.print("ADC:"); lcd.print(smoothRead(POT_AZ)); lcd.print(" "); if (digitalRead(BTN_CONFIG) == LOW) { calib.azMax = smoothRead(POT_AZ); while (digitalRead(BTN_CONFIG) == LOW); delay(200); cfgStep++; } break; case 2: printLine(0, "CAL3 EL=000"); lcd.setCursor(0,1); lcd.print("ADC:"); lcd.print(smoothRead(POT_EL)); lcd.print(" "); if (digitalRead(BTN_CONFIG) == LOW) { calib.elMin = smoothRead(POT_EL); while (digitalRead(BTN_CONFIG) == LOW); delay(200); cfgStep++; } break; case 3: printLine(0, "CAL4 EL=090"); lcd.setCursor(0,1); lcd.print("ADC:"); lcd.print(smoothRead(POT_EL)); lcd.print(" "); if (digitalRead(BTN_CONFIG) == LOW) { calib.elMax = smoothRead(POT_EL); EEPROM.put(0, calib); lcd.clear(); printLine(0, "CAL OK"); printLine(1, "EEPROM SAVED"); delay(2000); released = false; inConfig = false; cfgStep = 0; } break; } } // ============================== // SETUP // ============================== void setup() { Serial.begin(9600); pinMode(RELAY_AZ_CW, OUTPUT); pinMode(RELAY_AZ_CCW, OUTPUT); pinMode(RELAY_EL_UP, OUTPUT); pinMode(RELAY_EL_DOWN, OUTPUT); pinMode(BTN_AZ_CW, INPUT_PULLUP); pinMode(BTN_AZ_CCW, INPUT_PULLUP); pinMode(BTN_EL_DOWN, INPUT_PULLUP); pinMode(BTN_EL_UP, INPUT_PULLUP); pinMode(BTN_CONFIG, INPUT_PULLUP); lcd.begin(16, 2); if (digitalRead(BTN_CONFIG) == LOW) { inConfig = true; } lcd.clear(); lcd.setCursor(0, 0); lcd.print(" LU4EG GS-232 "); lcd.setCursor(0, 1); lcd.print(" Iniciando. "); delay(500); lcd.setCursor(0, 1); lcd.print(" Iniciando.. "); delay(500); lcd.setCursor(0, 1); lcd.print(" Iniciando... "); delay(500); stopAll(); EEPROM.get(0, calib); targetAz = readAz(); targetEl = readEl(); } // ============================== // LOOP // ============================== void loop() { if (inConfig) { handleCalibration(); return; } bool cfg = digitalRead(BTN_CONFIG); if (lastConfigBtn == HIGH && cfg == LOW) configPressTime = millis(); if (lastConfigBtn == LOW && cfg == HIGH) { if (millis() - configPressTime < 600) { autoMode = !autoMode; stopAll(); } } lastConfigBtn = cfg; handleSerial(); readInputs(); if (millis() - lastLCD > lcdInterval) { lastLCD = millis(); handleNormal(); } }