код 3.45

/*
        EW8ZO Rotator Controller v3.47
        
        === ОСНОВНЫЕ ФУНКЦИИ ===
        - Автоматическая калибровка COUNTS_PER_REV (CW + CCW)
        - Режим Overlap: North 360° / South 180°
        - DX-индикатор направления (префиксы стран)
        - Chase-mode: реальное время следование за энкодером
        - Многоуровневое меню с энкодером
        - Ручная настройка CPR через меню
        
        === ЖЕЛЕЗО ===
        - Геркон на D2 (прямой подсчет импульсов в ISR)
        - Энкодер EncButton на пинах 5,6,7
        - Реле: RELAY_1 = 8 (CCW), RELAY_2 = 9 (CW)
        - START/STOP кнопка на D3 (INPUT_PULLUP)
        - LCD 16x2 I2C (0x27)
        
        === МЕНЮ ===
        1. Overlap (North/South)
        2. Correct azimuth
        3. Calibration
        4. DX Indicator ON/OFF
        5. Chase Mode
        6. Change CPR
        7. Exit
        
        === КАЛИБРОВКА v3.04 ===
        Фаза 1: Подтверждение входа (показ CPR)
        Фаза 2: Настройка CPR (энкодер + двигатель)
        Фаза 3: Сохранение CPR + финальный круг CCW до 0°
        Фаза 4: Выход в NORMAL режим
        
        === ПОСЛЕДНИЕ ИЗМЕНЕНИЯ ===
        - v3.47: Унифицирована логика overlap (градусы вместо импульсов)
        - v3.46: Исправлена логика South overlap (<= 180 вместо < 180)
        - v3.45: Исправлен выход из Change CPR в NORMAL mode
        - v3.44: Добавлен пункт меню Change CPR для ручной настройки CPR
        - v3.43: Исправлена обработка энкодера в chase-mode во время движения
        - v3.42: Добавлен chase-mode с динамическим следованием за энкодером
        - v3.41: Исправлена синхронизация eb.counter с displayAzimuth
        - v3.40: Добавлена фильтрация чувствительности chase-mode (5°)
        - v3.39: Исправлено сохранение chase-mode в EEPROM
        - v3.38: Добавлен toggle chase-mode через энкодер в меню
  */
      #include <Arduino.h>
      #include <EncButton.h>
      #include <LiquidCrystal_I2C.h>
      #include <EEPROM.h>
    
      // ===== LCD и энкодер =====
      LiquidCrystal_I2C lcd(0x27, 16, 2);
      EncButton eb(5, 6, 7);
    
      // ===== Реле =====
      #define RELAY_1 8
      #define RELAY_2 9
      const bool RELAY_ACTIVE_HIGH = false;
    
      inline void relayWriteRaw(uint8_t pin, bool level) { digitalWrite(pin, level ? HIGH : LOW); }
      inline void relayWrite(uint8_t pin, bool active) {
        if (RELAY_ACTIVE_HIGH) relayWriteRaw(pin, active);
        else relayWriteRaw(pin, !active);
      }
    
      // ===== Константы =====
      const long COUNTS_PER_REV_DEFAULT = 151; 
      const unsigned long SAVE_DELAY_MS = 1000;
      const unsigned long LCD_UPDATE_MS = 100;
      const unsigned long SPLASH_MS = 2000;
      const unsigned long SAVE_DEDUP_MS = 1500;      
      const char* FW_VERSION = "3.47";
    
      // EEPROM
      const int EEPROM_ADDR_MAGIC = 0;
      const int EEPROM_ADDR_VALUE = 2; 
      const int EEPROM_ADDR_OVERLAP = 6; // адрес для настройки overlap
      const int EEPROM_ADDR_CPR = 8; // адрес для настройки CPR
      const int EEPROM_ADDR_DX = 12; // адрес для настройки DX
      const int EEPROM_ADDR_CHASE = 14;  // адрес для настройки chase-mode
      const uint16_t EEPROM_MAGIC = 0xA5A5;
    
      // ===== START/STOP кнопка =====
      const int STARTSTOP_PIN = 3;
      const unsigned long BTN_DEBOUNCE_MS = 50;
      bool startStopLastState = HIGH;
      unsigned long startStopLastChange = 0;
      bool startStopPressedHandled = false;
    
      // ===== Геркон =====
      const int REED_PIN = 2;
    
      // ===== Глобальные переменные =====
      volatile long sensor = 0;
      volatile long temp_sensor = 0;
      long prevSensor = 0;
      long prevCur = 0;  // Для точной остановки
      unsigned long lastMoveMillis = 0;
      bool movedSinceLastSave = false;
      unsigned long lastLcdMillis = 0;
    
      enum Mode {
        MODE_NORMAL,
        MODE_ADD,
        MODE_MENU,
        MODE_OVERLAP_SELECT,
        MODE_DX_SELECT,         // Новый режим: включение/выключение DX
        MODE_CHASE_SELECT,      // Новый режим: chase-mode toggle
        MODE_CPR_CHANGE,        // Новый режим: изменение CPR
        MODE_CALIB_CONFIRM,     // Новый режим: подтверждение входа в калибровку
        MODE_CALIB_MANUAL_SELECT,   // Новый режим: выбор импульсов
        MODE_CALIB_MANUAL_RUN,      // Новый режим: движение CW/CCW
        MODE_CALIB_MANUAL_DONE      // Новый режим: результат
      };
      
      // ===== Menu table structure =====
      struct MenuItem {
          const char* label;      // указатель на строку в PROGMEM
          Mode nextMode;          // режим, в который переходим
      };
      
      // Строки меню в PROGMEM
      const char menu_overlap[] PROGMEM = "Overlap";
      const char menu_change_azimuth[] PROGMEM = "Change Azimuth";
      const char menu_calibration[] PROGMEM = "Calibration";
      const char menu_dx[] PROGMEM = "DX Indicator";
      const char menu_chase[] PROGMEM = "Chase Mode";
      const char menu_cpr_change[] PROGMEM = "Change CPR";
      const char menu_exit[] PROGMEM = "Exit";
      
      const MenuItem menuItems[] PROGMEM = {
          {menu_overlap,       MODE_OVERLAP_SELECT},
          {menu_change_azimuth, MODE_ADD},
          {menu_calibration,   MODE_CALIB_MANUAL_SELECT},
          {menu_dx,            MODE_DX_SELECT},
          {menu_chase,         MODE_CHASE_SELECT},
          {menu_cpr_change,    MODE_CPR_CHANGE},
          {menu_exit,          MODE_NORMAL}
      };
      
      const uint8_t MENU_COUNT = 7;  // 7 пунктов меню
      Mode mode = MODE_NORMAL;
    
      // Overlap mode: 0 = North (360), 1 = South (180)
      int overlapMode = 0;
      int menuSelection = 1;
      
      // DX indicator enable/disable
      bool dxEnabled = true;
      int dxSelectValue = 1;  // 1 = ON, 0 = OFF
      
      // Chase-mode enable/disable
      bool chaseModeEnabled = false;
    
      // Для сохранения overlap режима во время калибровки
      int savedOverlapForCalib = 0;
    
      int addAzimuth = 1;
      
      // Для подтверждения калибровки
      int calibConfirmSelection = 0;  // 0 = Да, 1 = Нет
      bool suppressNextClick = false;
    
      bool movementActive = false;
      long movementTargetCounts = -1;
      int movementDir = 0;
    
      unsigned long savedDisplayMillis = 0;
      unsigned long lastSavedMillis = 0;
    
      // ===== Флаги направления =====
      volatile bool dirRightFlag = false;
      volatile bool dirLeftFlag  = false;
      
    
      // ===== Debug =====
      volatile unsigned long reedEvents = 0;
      volatile unsigned long lastReedTime = 0;
    
      // ===== Отображаемый азимут =====
      int displayAzimuth = 0;
    
      // ===== LCD cache =====
      int lastDisplayedAzimuth = -1;
      int lastDisplayedNew = -1;
      int lastDisplayedAddAzimuth = -1;
      Mode lastDisplayedMode = MODE_NORMAL;
      int lastMenuSelection = -1;
      int lastOverlapMode = -1;
      bool lastDxEnabled = false;  // ← добавляем для отслеживания изменений
      bool lastMovementActive = false;
      int lastMovementDir = 0;
      // ===== Calibration variables =====
      long countsPerRev = COUNTS_PER_REV_DEFAULT;
    
      
      // ===== Manual calibration variables =====
      long manualTargetCounts;   // желаемое CPR
      
    
      // ===== DX direction table tuned for KO52mk, refined =====
      struct Sector {
        int16_t azMin;
        int16_t azMax;
        char prefix[4];   // храним сам текст, НЕ указатель
      };
    
      const Sector dirSectors[] PROGMEM = {
    
        // --- 0–50: Океания ---
        {  0,   4,  "FO"  },
        {  5,  12,  "KH6" },
        { 16,  22,  "E5"  },
        { 49,  54,  "C21" },
    
        // --- Азия / Тихий океан ---
        { 54,  66,  "JA"  },
        { 59,  67,  "UA9" },
        { 68,  84,  "BY"  },
        { 80,  88,  "ZL"  },
        { 94,  98,  "HS"  },
        {102, 108,  "YB"  },
        {113, 121,  "VU"  },
    
        // --- Middle East ---
        {140, 145,  "A4"  },
    
        // --- Africa ---
        {167, 175,  "5H"  },
        {186, 194,  "ZS"  },
    
        // --- South America ---
        {238, 247,  "PY"  },
        {248, 253,  "CX"  },
        {254, 261,  "CE"  },
    
        // --- Atlantic / Azores ---
        {268, 274,  "CU"  },
    
        // --- Central Europe ---
        {269, 273,  "DL"  },
        {259, 263,  "OK"  },
        {246, 250,  "OM"  },
        {278, 283,  "SP"  },
    
        // --- UK ---
        {284, 288,  "G"   },
    
        // --- North America ---
        {311, 319,  "W"   },
        {331, 339,  "VE"  },
    
        // --- Northern Europe ---
        {307, 313,  "LY"  },
        {332, 342,  "ES"  },
        {340, 349,  "OH"  },
        {350, 359,  "SM"  }
      };
    
      const uint8_t DIR_SECTORS_COUNT = sizeof(dirSectors) / sizeof(dirSectors[0]);
    
      void printDXPrefixToLCD(int az, LiquidCrystal_I2C& lcd) {
        for (uint8_t i = 0; i < DIR_SECTORS_COUNT; i++) {
          Sector s;
          memcpy_P(&s, &dirSectors[i], sizeof(Sector));
      
          if (az >= s.azMin && az <= s.azMax) {
            lcd.print(s.prefix);
            return;
          }
        }
        lcd.print("   ");
      }
      
      // Совместимость для старого кода
      const char* getDXPrefix(int az) {
        static char prefixBuf[4];
        
        for (uint8_t i = 0; i < DIR_SECTORS_COUNT; i++) {
          Sector s;
          memcpy_P(&s, &dirSectors[i], sizeof(Sector));
      
          if (az >= s.azMin && az <= s.azMax) {
            strcpy(prefixBuf, s.prefix);
            return prefixBuf;
          }
        }
        strcpy(prefixBuf, "   ");
        return prefixBuf;
      }

      
    
      // ===== EEPROM helpers =====
      void savePositionToEEPROM(long val) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) EEPROM.put(EEPROM_ADDR_MAGIC, EEPROM_MAGIC);
        long stored; EEPROM.get(EEPROM_ADDR_VALUE, stored);
        if (stored != val) EEPROM.put(EEPROM_ADDR_VALUE, val);
        lastSavedMillis = millis();
      }
    
      bool loadPositionFromEEPROM(long &outVal) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) return false;
        EEPROM.get(EEPROM_ADDR_VALUE, outVal);
        return true;
      }
    
      void saveOverlapModeToEEPROM(int mode) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) EEPROM.put(EEPROM_ADDR_MAGIC, EEPROM_MAGIC);
        int stored; EEPROM.get(EEPROM_ADDR_OVERLAP, stored);
        if (stored != mode) EEPROM.put(EEPROM_ADDR_OVERLAP, mode);
      }
    
      bool loadOverlapModeFromEEPROM(int &outMode) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) return false;
        EEPROM.get(EEPROM_ADDR_OVERLAP, outMode);
        return true;
      }
    
      bool loadCPRFromEEPROM(long &outCPR) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) return false;
        EEPROM.get(EEPROM_ADDR_CPR, outCPR);
        return true;
      }
      
      // ===== DX setting EEPROM helpers =====
      void saveDXSettingToEEPROM(bool enabled) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) EEPROM.put(EEPROM_ADDR_MAGIC, EEPROM_MAGIC);
        bool stored; EEPROM.get(EEPROM_ADDR_DX, stored);
        if (stored != enabled) EEPROM.put(EEPROM_ADDR_DX, enabled);
      }
      
      bool loadDXSettingFromEEPROM(bool &outEnabled) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) return false;
        EEPROM.get(EEPROM_ADDR_DX, outEnabled);
        return true;
      }
      
      // ===== Chase-mode EEPROM helpers =====
      void saveChaseModeToEEPROM(bool enabled) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) EEPROM.put(EEPROM_ADDR_MAGIC, EEPROM_MAGIC);
        // Always save current value
        EEPROM.put(EEPROM_ADDR_CHASE, enabled);
        Serial.print(F("EEPROM: Chase-mode saved as "));
        Serial.println(enabled ? F("ON") : F("OFF"));
      }
      
      bool loadChaseModeFromEEPROM(bool &outEnabled) {
        uint16_t magic; EEPROM.get(EEPROM_ADDR_MAGIC, magic);
        if (magic != EEPROM_MAGIC) return false;
        EEPROM.get(EEPROM_ADDR_CHASE, outEnabled);
        return true;
      }


      // ===== Conversions =====
      int roundToMultipleOf5(int value) {
          // нормализуем в диапазон 1..360
          if (value <= 0) value = 1;
          if (value > 360) value = ((value - 1) % 360) + 1;

          int mod = value % 5;
          int base = value - mod;

          if (mod == 1 || mod == 2) return base;       // округляем вниз
          if (mod == 3 || mod == 4) return base + 5;   // округляем вверх
          return base;                                  // если делится на 5 — оставляем
      }
      
      // ===== LCD timer reset =====
      void resetLcdTimer() {
          lastLcdMillis = 0;
      }
    
      int countsToAzimuth(long counts) {
          if (countsPerRev < 10) return 0;  // защита от деления на 0

          // нормализуем в диапазон 0..countsPerRev-1
          long c = counts % countsPerRev;
          if (c < 0) c += countsPerRev;

          // классическое округление до ближайшего целого градуса
          long degTimes10 = (c * 3600L) / countsPerRev;  // умножаем на 10, чтобы учесть десятые
          int az = (int)((degTimes10 + 5) / 10);        // +5 → округление: 0.5 → вверх

          if (az == 0) az = 360;  // 0° → 360°
          return az;
      }
    
    
      long azimuthToCounts(int az) {
        int a = az % 360;
        long c = ((long)a * countsPerRev + 180) / 360;
        if (az == 360) c = 0;
        c = c % countsPerRev;
        if (c < 0) c += countsPerRev;
        return c;
      }
    
        
    int shortestTurnDirection(int curAz, int tgtAz) {
          // === 1. Калибровка — всегда кратчайший путь ===
          if (mode == MODE_CALIB_MANUAL_RUN)
              return (tgtAz - curAz > 0 ? +1 : -1);
      
          // === 2. Переводим в counts ===
          long cur = sensor % countsPerRev;
          if (cur < 0) cur += countsPerRev;
      
          long tgt = azimuthToCounts(tgtAz);
      
          long diff = tgt - cur;
      
          // нормализация разницы в диапазон -CPR/2 .. +CPR/2
          if (diff > countsPerRev / 2) diff -= countsPerRev;
          if (diff < -countsPerRev / 2) diff += countsPerRev;
      
          int dirShort = (diff > 0) ? +1 : -1;
      
          // === 3. Если overlap выключен — просто кратчайший путь ===
          if (overlapMode == -1)
              return dirShort;
      
          // === 4. OVERLAP = NORTH (360°) ===
          if (overlapMode == 0) {
              if (dirShort > 0) {
                  // Если кратчайший путь CW, но мы в северной зоне и цель южная
                  if (curAz >= 360 && tgtAz < 360) return -1;  // CCW через 0°
              } else {
                  // Если кратчайший путь CCW, но мы в северной зоне и цель северная
                  if (curAz <= 0 && tgtAz > 0) return +1;     // CW через 360°
              }
              return dirShort;
          }
      
          // === 5. OVERLAP = SOUTH (180°) ===
          if (overlapMode == 1) {
              if (dirShort > 0) {
                  // Если кратчайший путь CW, но мы в южной зоне и цель тоже южная
                  if (curAz <= 180 && tgtAz > 180) return -1;  // CCW через 0°
              } else {
                  // Если кратчайший путь CCW, но мы в южной зоне и цель северная
                  if (curAz >= 180 && tgtAz < 180) return +1;  // CW через 360°
              }
              return dirShort;
          }
      
          return dirShort;
      }

    
      // ===== Target check ===== 
      bool isTargetReached(int currentAz, int targetAz, int dir) {
        long curCounts = sensor % countsPerRev;
        if (curCounts < 0) curCounts += countsPerRev;
    
        long tgtCounts = azimuthToCounts(targetAz);
    
        long diff = tgtCounts - curCounts;
    
        // нормализация разницы в диапазон -CPR/2 .. +CPR/2
        if (diff > countsPerRev / 2) diff -= countsPerRev;
        if (diff < -countsPerRev / 2) diff += countsPerRev;
        return diff == 0;
    }
    
    
      // ===== Interrupt control =====
      void enableReedInterrupt() {
        attachInterrupt(digitalPinToInterrupt(REED_PIN), reedISR, FALLING);
      }
    
      void disableReedInterrupt() {
        detachInterrupt(digitalPinToInterrupt(REED_PIN));
      }
    
      // ===== Motor control =====
      void motorForward() {
        Serial.println(F("DIR = CW"));
        disableReedInterrupt();
        relayWrite(RELAY_1, false);
        relayWrite(RELAY_2, false);
        delay(50);
        relayWrite(RELAY_2, true);
    
        dirRightFlag = true;
        dirLeftFlag = false;
        movementActive = true;
    
        enableReedInterrupt();
        Serial.println(F("Motor: FORWARD (RELAY_2 ON)"));
      }
    
      void motorBackward() {
        Serial.println(F("DIR = CCW"));
        disableReedInterrupt();
        relayWrite(RELAY_1, false);
        relayWrite(RELAY_2, false);
        delay(50);
        relayWrite(RELAY_1, true);
    
        dirRightFlag = false;
        dirLeftFlag = true;
        movementActive = true;
    
        enableReedInterrupt();
        Serial.println(F("Motor: BACKWARD (RELAY_1 ON)"));
      }
    
      void motorStop() {
        disableReedInterrupt();
        relayWrite(RELAY_1, false);
        relayWrite(RELAY_2, false);
        delay(50);
    
        movementActive = false;
        dirRightFlag = false;
        dirLeftFlag = false;
    
        Serial.println(F("Motor: STOP"));
      }
    
      // ===== Movement control =====
      void stopMovement() {
        motorStop();
    
        noInterrupts();
        long cur = sensor;
        interrupts();
    
        if (millis() - lastSavedMillis > SAVE_DEDUP_MS) {
          savePositionToEEPROM(cur);
          Serial.println(F("STOP: position saved"));
        } else {
          Serial.println(F("STOP: save skipped (recent)"));
        }
    
        savedDisplayMillis = millis();
        movementDir = 0;
    
        // === ДОБАВЛЕНО: корректная обработка STOP в калибровке ===
        if (mode == MODE_CALIB_MANUAL_RUN) {
            mode = MODE_CALIB_MANUAL_SELECT;
            
            resetLcdTimer();
            return;  // ← ВАЖНО: не выполнять код ниже
        }
    
        // === обычный режим ===
        int azimuth = countsToAzimuth(cur);
        eb.counter = roundToMultipleOf5(azimuth);
        
    }
    
    
      void startMovementTo(int targetAzimuth) {
        if (movementActive) return;
    
        // Калибровка — отдельная логика
        if (mode == MODE_CALIB_MANUAL_RUN) {
            if (movementDir > 0) motorForward();
            else motorBackward();
            return;
        }
    
        noInterrupts();
        long cur = sensor;
        interrupts();
    
        // Переводим градусы → импульсы
        long targetCounts = azimuthToCounts(targetAzimuth);
        
        // Определяем направление с учетом overlap
        int curAz = countsToAzimuth(cur);
        int dir = shortestTurnDirection(curAz, targetAzimuth);
    
        movementTargetCounts = targetCounts;
        movementDir = dir;
        prevCur = cur;
    
        if (dir > 0) motorForward();
        else motorBackward();
    
        Serial.print(F("START movement: targetCounts="));
        Serial.print(targetCounts);
        Serial.print(F(" cur="));
        Serial.print(cur);
        Serial.print(F(" dir="));
        Serial.println((dir > 0) ? F("CW") : F("CCW"));
      }
    
      // ===== ISR геркона =====
        void reedISR() {
          static unsigned long lastUs = 0;
          unsigned long now = micros();
      
          // Фильтр дребезга          
          if (now - lastUs < 20000) return; // 20 мс
          unsigned long delta = now - lastUs;
          lastUs = now;
      
          if (!movementActive) return;
      
          // Направление
          int dir = 0;
          if (dirRightFlag) {
              if (sensor < 1000000) sensor++;
              dir = +1;
          } else if (dirLeftFlag) {
              if (sensor > -1000000) sensor--;
              dir = -1;
          }
    
          // Нормализация выполняется только при отображении, не здесь
          
          // Sensor debug disabled for production      
      }
    
    
      // ===== Setup =====
      void setup() {
          
    
        delay(10);
    
        Serial.begin(115200);
        Serial.println(F("=== CONTROLLER START ==="));
    
        // Reed sensor
        pinMode(REED_PIN, INPUT_PULLUP);
        
    
        // Relays
        pinMode(RELAY_1, OUTPUT);
        pinMode(RELAY_2, OUTPUT);
        if (RELAY_ACTIVE_HIGH) {
          relayWriteRaw(RELAY_1, LOW);
          relayWriteRaw(RELAY_2, LOW);
        } else {
          relayWriteRaw(RELAY_1, HIGH);
          relayWriteRaw(RELAY_2, HIGH);
        }
        //motorStop();
        dirRightFlag = false; dirLeftFlag = false; movementActive = false;
    
        // START/STOP button
        pinMode(STARTSTOP_PIN, INPUT_PULLUP);
        startStopLastState = digitalRead(STARTSTOP_PIN);
        startStopLastChange = millis();
    
        // LCD
        lcd.begin();
        lcd.backlight();
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print(F("EW8ZO Rotator"));
        lcd.setCursor(0,1);
        lcd.print(F("VER. "));
        lcd.print(FW_VERSION);
        delay(SPLASH_MS);
        lcd.clear();
    
        // Encoder
        eb.setBtnLevel(LOW);
        eb.setClickTimeout(800);
        eb.setDebTimeout(8);
        eb.setHoldTimeout(1500);
        eb.setEncType(EB_STEP4_LOW);
        eb.setEncReverse(0);
        eb.setFastTimeout(10);
        eb.counter = 0;
    
        // Load saved position
        long saved = 0;
        if (loadPositionFromEEPROM(saved)) {
          noInterrupts();
          sensor = saved;
          interrupts();
          Serial.print("Loaded pos counts: ");
          Serial.println(saved);
          lastSavedMillis = millis();
        } else {
          Serial.println(F("No saved pos in EEPROM"));
        }
    
        // Load overlap mode
        int savedOverlap = 0;
        if (loadOverlapModeFromEEPROM(savedOverlap)) {
          if (savedOverlap < -1 || savedOverlap > 1)
              overlapMode = -1;
          else
              overlapMode = savedOverlap;

          Serial.print("Loaded overlap mode: ");
          if (overlapMode == -1) Serial.println(F("OFF"));
            else if (overlapMode == 0) Serial.println(F("North 360"));
            else if (overlapMode == 1) Serial.println(F("South 180"));

          } else {
            overlapMode = 0;
            Serial.println(F("OFF"));
          }
    
        // Load CPR
        long savedCPR = 0;
        if (loadCPRFromEEPROM(savedCPR)) {
          if (savedCPR > 50 && savedCPR < 2000) {
            countsPerRev = savedCPR;
            Serial.print("Loaded CPR: ");
            Serial.println(countsPerRev);
          } else {
            countsPerRev = COUNTS_PER_REV_DEFAULT;
            Serial.println(F("Invalid CPR in EEPROM, using default"));
          }
        } else {
          countsPerRev = COUNTS_PER_REV_DEFAULT;
          Serial.println(F("No CPR in EEPROM, using default"));
        }
        
        // Load DX setting
        bool savedDX = true;
        if (loadDXSettingFromEEPROM(savedDX)) {
          dxEnabled = savedDX;
          dxSelectValue = dxEnabled ? 1 : 0;  // синхронизируем
          Serial.print("Loaded DX setting: ");
          Serial.println(dxEnabled ? "ON" : "OFF");
        } else {
          dxEnabled = true;
          dxSelectValue = 1;
          Serial.println(F("No DX setting in EEPROM, using default ON"));
        }
        
        // Load Chase-mode setting
        bool savedChase = false;
        if (loadChaseModeFromEEPROM(savedChase)) {
          chaseModeEnabled = savedChase;
          Serial.print(F("Loaded Chase-mode: "));
          Serial.println(chaseModeEnabled ? F("ON") : F("OFF"));
        } else {
          chaseModeEnabled = false;
          Serial.println(F("No Chase-mode in EEPROM, using default OFF"));
        }
    
        // Initial azimuth
        noInterrupts();
        int azimuth = countsToAzimuth(sensor);
        interrupts();
    
        displayAzimuth = (azimuth / 5) * 5;
        if (displayAzimuth <= 0) displayAzimuth = 360;
        eb.counter = displayAzimuth;
    
        lastLcdMillis = millis();
    
        Serial.print("Controller v");
        Serial.print(FW_VERSION);
        Serial.println(F(" started"));
    
        Serial.print("Initial Azimuth=");
        Serial.print(azimuth);
        Serial.print(" displayAzimuth=");
        Serial.println(displayAzimuth);
    
        Serial.print("CPR=");
        Serial.println(countsPerRev);
    
        attachInterrupt(digitalPinToInterrupt(REED_PIN), reedISR, FALLING);
      }
    
      // ===== Main loop =====
      void loop() {
        eb.tick();
      
        // ===== Encoder events (single read per loop) =====
        bool encTurn  = eb.turn();
        bool encClick = eb.click();
        int  encDir   = eb.dir();
    
        static int lastReed = HIGH;
        int cur = digitalRead(REED_PIN);
        if (cur != lastReed) {    
        lastReed = cur;
        }
            
        // Global STOP block REMOVED - was causing race conditions

        // START/STOP кнопка — ТОЛЬКО STOP и ТОЛЬКО ВО ВРЕМЯ ДВИЖЕНИЯ
        bool curState = digitalRead(STARTSTOP_PIN);
        if (curState != startStopLastState) {
          startStopLastChange = millis();
          startStopLastState = curState;
          startStopPressedHandled = false;
        } else {
          if (!startStopPressedHandled && 
              (millis() - startStopLastChange) > BTN_DEBOUNCE_MS) {
      
              if (curState == LOW && movementActive) {
                  stopMovement();
                  suppressNextClick = true;
                  resetLcdTimer();
              }
      
              startStopPressedHandled = true;
          }
        }
    
        // Вход в MENU при удержании энкодера
        if (eb.hold() && mode == MODE_NORMAL && !movementActive) {
          mode = MODE_MENU;
          menuSelection = 1;  // Универсальный сброс - меню всегда начинается с пункта 1
          suppressNextClick = false;
          resetLcdTimer();
        }

          // Track actual sensor value for consistency
          prevSensor = sensor;

        // Отслеживание изменений sensor
        if (sensor != temp_sensor) {
          Serial.print("SENSOR CHANGE: "); 
          Serial.print(temp_sensor); 
          Serial.print(" -> "); 
          Serial.println(sensor);
          
          // ЗАЩИТА ОТ ПЕРЕПОЛНЕНИЯ 
          if (sensor > 1000000 || sensor < -1000000) { 
            sensor = 0; 
            Serial.println(F("Sensor overflow detected — reset to 0"));
          }
          temp_sensor = sensor;
          lastMoveMillis = millis();
          movedSinceLastSave = true;
        }
    
        // ===== Movement target check (IMPULSE-BASED STOP) =====
        if (movementActive && mode == MODE_NORMAL) {
            noInterrupts();
            long cur = sensor;
            interrupts();
            
            // === Chase-mode logic ===
            if (chaseModeEnabled) {
                // обновляем цель на лету
                long newTarget = azimuthToCounts(displayAzimuth);
                
                // если цель изменилась — пересчитать направление
                if (newTarget != movementTargetCounts) {
                    movementTargetCounts = newTarget;
                    
                    long diff = (movementTargetCounts - cur + countsPerRev) % countsPerRev;
                    int newDir = (diff <= countsPerRev / 2) ? +1 : -1;
                    
                    if (newDir != movementDir) {
                        Serial.print(F("CHASE: Direction change "));
                        Serial.print(movementDir > 0 ? F("CW") : F("CCW"));
                        Serial.print(F(" → "));
                        Serial.println(newDir > 0 ? F("CW") : F("CCW"));
                        
                        motorStop();
                        movementDir = newDir;
                        if (movementDir > 0) motorForward();
                        else motorBackward();
                    }
                }
            }
            
            // Нормализованная разница
            long diff = (movementTargetCounts - cur + countsPerRev) % countsPerRev;
            
            // CW
            if (movementDir > 0) {
                if (diff == 0 || prevCur > cur) {
                    Serial.println(F("STOPPING: Target reached (CW, impulse-based)"));
                    stopMovement();
                    resetLcdTimer();
                }
            }
            // CCW
            else {
                if (diff == 0 || prevCur < cur) {
                    Serial.println(F("STOPPING: Target reached (CCW, impulse-based)"));
                    stopMovement();
                    resetLcdTimer();
                }
            }
            
            prevCur = cur;
        }


    
    
        // ===== EEPROM save after movement =====
        if (movedSinceLastSave && (millis() - lastMoveMillis >= SAVE_DELAY_MS)) {
          if (millis() - lastSavedMillis > SAVE_DEDUP_MS) {
            noInterrupts(); long toSave = sensor; interrupts();
            savePositionToEEPROM(toSave);
            movedSinceLastSave = false;
            savedDisplayMillis = millis();
    
            int newAz = countsToAzimuth(toSave);
            //eb.counter = roundToMultipleOf5(newAz);
            resetLcdTimer();
          } else movedSinceLastSave = false;
        }
        
        // =====================================================================
        // ========================= ENCODER TURN ===============================
        // =====================================================================
        
        // ===== TURN (общий) =====
        static unsigned long lastTurnMs = 0;
        if (encTurn) {
            unsigned long now = millis();
            unsigned long dt = now - lastTurnMs;
            lastTurnMs = now;
            int dir = encDir;
                  
            // ===== Special handling for chase-mode during movement =====
            if (mode == MODE_NORMAL && movementActive && chaseModeEnabled) {
                displayAzimuth += dir * 5;
                while (displayAzimuth <= 0) displayAzimuth += 360;
                displayAzimuth = ((displayAzimuth - 1) % 360) + 1;
                          
                eb.counter = displayAzimuth;
                resetLcdTimer();
            }
                      
            else if (mode == MODE_NORMAL) {
                if (!movementActive) {
                    displayAzimuth += dir * 5;
                    while (displayAzimuth <= 0) displayAzimuth += 360;
                    displayAzimuth = ((displayAzimuth - 1) % 360) + 1;
                    eb.counter = displayAzimuth;
                    resetLcdTimer();
                }
            }
        
            else if (mode == MODE_ADD) {
                int baseStep = 5;
                int mult = (dt < 30) ? 20 : (dt < 80 ? 5 : 1);
                int delta = dir * baseStep * mult;
        
                addAzimuth += delta;
                while (addAzimuth <= 0) addAzimuth += 360;
                addAzimuth = ((addAzimuth - 1) % 360) + 1;
                resetLcdTimer();
            }
        
            else if (mode == MODE_MENU) {
                menuSelection += dir;
                if (menuSelection < 0) menuSelection = MENU_COUNT - 1;
                if (menuSelection >= MENU_COUNT) menuSelection = 0;
                suppressNextClick = false;
                resetLcdTimer();
            }
        
            else if (mode == MODE_OVERLAP_SELECT) {
                if (dir > 0) {
                    overlapMode++;
                    if (overlapMode > 1) overlapMode = -1;
                } else {
                    overlapMode--;
                    if (overlapMode < -1) overlapMode = 1;
                }
                resetLcdTimer();
            }
            
            else if (mode == MODE_DX_SELECT) {
                if (dir > 0) {
                    dxSelectValue++;
                    if (dxSelectValue > 1) dxSelectValue = 0;
                } else {
                    dxSelectValue--;
                    if (dxSelectValue < 0) dxSelectValue = 1;
                }
                dxEnabled = (dxSelectValue == 1);
                resetLcdTimer();
            }
            
            else if (mode == MODE_CHASE_SELECT) {
                // Изменяем угол шагами по 5° (как везде)
                displayAzimuth += dir * 5;
                while (displayAzimuth <= 0) displayAzimuth += 360;
                displayAzimuth = ((displayAzimuth - 1) % 360) + 1;
                eb.counter = displayAzimuth;
                suppressNextClick = false;
                resetLcdTimer();
            }
            
            else if (mode == MODE_CPR_CHANGE) {
                // Изменяем CPR шагами по 1
                countsPerRev += dir;
                if (countsPerRev < 10) countsPerRev = 10;
                if (countsPerRev > 2000) countsPerRev = 2000;
                resetLcdTimer();
            }

        }
    
      // =====================================================================
    // =================== CALIBRATION: SELECT ==============================
    // =====================================================================
    
    if (mode == MODE_CALIB_MANUAL_SELECT) {
    
        // Поворот энкодера — меняем CPR (только положительные значения)
        if (encTurn) {
            manualTargetCounts += encDir;
            if (manualTargetCounts < 10) manualTargetCounts = 10;
            if (manualTargetCounts > 2000) manualTargetCounts = 2000;
            resetLcdTimer();
        }
    
        // Клик — запускаем движение
        if (encClick) {
    
            noInterrupts();
            long cur = sensor;
            interrupts();
    
            if (manualTargetCounts > cur) {
                movementDir = +1;
                motorForward();
                Serial.println(F("CALIB: Moving CW to reach target CPR"));
            }
            else if (manualTargetCounts < cur) {
                movementDir = -1;
                motorBackward();
                Serial.println(F("CALIB: Moving CCW to reach target CPR"));
            }
            else {
                // CPR == sensor → сразу DONE
                mode = MODE_CALIB_MANUAL_DONE;
                resetLcdTimer();
                return;
            }
    
            movementActive = true;
            mode = MODE_CALIB_MANUAL_RUN;
            resetLcdTimer();
        }
    }
    
    
    
          // =====================================================================
    // =================== CALIBRATION: RUN ================================
    // =====================================================================
    
    if (mode == MODE_CALIB_MANUAL_RUN && movementActive) {
    
        noInterrupts();
        long cur = sensor;
        interrupts();
    
        // === Обычный RUN: едем к CPR ===
        if (manualTargetCounts > 0) {
    
            // Отладка условия остановки
            bool shouldStop = false;
            if (movementDir > 0) {
                shouldStop = (cur >= manualTargetCounts);
                if (shouldStop) {
                    Serial.print(F("CALIB STOP: CW cur="));
                    Serial.print(cur);
                    Serial.print(F(" target="));
                    Serial.println(manualTargetCounts);
                }
            } else if (movementDir < 0) {
                shouldStop = (cur <= manualTargetCounts);
                if (shouldStop) {
                    Serial.print(F("CALIB STOP: CCW cur="));
                    Serial.print(cur);
                    Serial.print(F(" target="));
                    Serial.println(manualTargetCounts);
                }
            }
            
            if (shouldStop) {
                motorStop();
                movementActive = false;
                mode = MODE_CALIB_MANUAL_DONE;
                resetLcdTimer();
            }
        }
    
        // === Финальный CCW круг до нуля (только после сохранения CPR) ===
        else if (manualTargetCounts == 0 && movementDir < 0) {
                
            // Останавливаемся точно на 0
            // (обнаруживаем переход через ноль)
            static long prevCalibCur = cur;
            bool shouldStop = (cur > prevCalibCur) || (cur == 0);  
            
            // Отладка финального круга
            if (shouldStop) {
                Serial.print(F("CALIB FINAL STOP: cur="));
                Serial.print(cur);
                Serial.print(F(" prev="));
                Serial.print(prevCalibCur);
                if (cur > prevCalibCur) Serial.println(F(" (wrap detected)"));
                else Serial.println(F(" (near zero)"));
            }
            
            prevCalibCur = cur;
                
            if (shouldStop) {
          
                motorStop();
                movementActive = false;
          
                // Не сбрасываем sensor=0 в режиме калибровки!
                // Это делается только после сохранения CPR
                Serial.println(F("CALIB: Full circle completed, exiting to NORMAL mode"));
          
                mode = MODE_NORMAL;
                suppressNextClick = true;
                resetLcdTimer();
            }
        }
    }
    
    
    
        /// =====================================================================
    // =================== CALIBRATION: DONE ===============================
    // =====================================================================
    
    if (mode == MODE_CALIB_MANUAL_DONE) {
    
        // Поворот энкодера → возвращаемся в SELECT
        if (encTurn) {
            manualTargetCounts += encDir;
            if (manualTargetCounts < 10) manualTargetCounts = 10;
            if (manualTargetCounts > 2000) manualTargetCounts = 2000;
    
            mode = MODE_CALIB_MANUAL_SELECT;
            resetLcdTimer();
        }
    
        // Клик → сохранить CPR → CCW полный круг → ноль → NORMAL
        if (encClick) {
    
            // 1. Сохраняем CPR
            countsPerRev = manualTargetCounts;
            EEPROM.put(EEPROM_ADDR_CPR, countsPerRev);
            Serial.print(F("CALIB: CPR saved to EEPROM: "));
            Serial.println(countsPerRev);
    
            // 2. Запускаем CCW полный круг
            movementDir = -1;
            movementActive = true;
            motorBackward();
            Serial.println(F("CALIB: Starting CCW full circle to zero"));
    
            // 3. RUN будет вести до sensor=0
            manualTargetCounts = 0;
    
            mode = MODE_CALIB_MANUAL_RUN;
            resetLcdTimer();
        }
    }
    
        
    
    
        // ===== MENU click =====
        if (mode == MODE_MENU && encClick && !suppressNextClick) {
            // Получаем режим из таблицы
            Mode nextMode;
            memcpy_P(&nextMode, &menuItems[menuSelection].nextMode, sizeof(Mode));
        
            mode = nextMode;
            suppressNextClick = true;
            
            // Special handling for certain modes
            if (mode == MODE_ADD) {
                noInterrupts(); int azimuth = countsToAzimuth(sensor); interrupts();
                addAzimuth = roundToMultipleOf5(azimuth);
            }
            else if (mode == MODE_CALIB_CONFIRM) {
                eb.reset();
                
                // Показываем текущее CPR и спрашиваем подтверждение
                manualTargetCounts = countsPerRev;
                if (manualTargetCounts < 10 || manualTargetCounts > 2000)
                    manualTargetCounts = 100;
                
                // Сброс sensor в 0 при отсутствии сохраненного CPR
                if (countsPerRev == COUNTS_PER_REV_DEFAULT) {
                    noInterrupts();
                    sensor = 0;
                    temp_sensor = 0;
                    prevSensor = 0;
                    interrupts();
                    Serial.println(F("CALIB: Reset sensor to 0 for initial calibration"));
                }
                
                calibConfirmSelection = 0;
                movementActive = false;
                movementDir = 0;
                movementTargetCounts = -1;
            }
            else if (mode == MODE_DX_SELECT) {
                lastDxEnabled = !dxEnabled;
                lastDisplayedMode = (Mode)-1;
            }
            else if (mode == MODE_NORMAL) {
                eb.counter = roundToMultipleOf5(eb.counter);
            }
            
            resetLcdTimer();
        }
    
        // ===== CHASE_SELECT click =====
        if (mode == MODE_CHASE_SELECT && encClick) {
          if (suppressNextClick) suppressNextClick = false;
          else {
            // По образцу DX - сохраняем текущее значение
            saveChaseModeToEEPROM(chaseModeEnabled);
            mode = MODE_NORMAL;
            suppressNextClick = true;
            
            Serial.print(F("Chase-mode: "));
            Serial.println(chaseModeEnabled ? F("ON") : F("OFF"));
            
            // Force LCD update
            lastDisplayedMode = (Mode)-1;
            lastMenuSelection = -1;
            resetLcdTimer();
          }
        }
        
        // ===== CPR_CHANGE click =====
        if (mode == MODE_CPR_CHANGE && encClick) {
          if (suppressNextClick) suppressNextClick = false;
          else {
            // Сохраняем CPR в EEPROM
            EEPROM.put(EEPROM_ADDR_CPR, countsPerRev);
            mode = MODE_NORMAL;
            suppressNextClick = true;
            
            Serial.print(F("CPR saved: "));
            Serial.println(countsPerRev);
            
            // Force LCD update
            lastDisplayedMode = (Mode)-1;
            lastMenuSelection = -1;
            resetLcdTimer();
          }
        }
        
        // ===== OVERLAP_SELECT click =====
        if (mode == MODE_OVERLAP_SELECT && encClick) {
          if (suppressNextClick) suppressNextClick = false;
          else {
            EEPROM.put(EEPROM_ADDR_OVERLAP, overlapMode);
            mode = MODE_NORMAL;
            suppressNextClick = true;
            resetLcdTimer();
          }
        }
        
        // ===== CALIB_CONFIRM encoder turn =====
        if (mode == MODE_CALIB_CONFIRM) {
            if (encTurn) {
                calibConfirmSelection += encDir;
                if (calibConfirmSelection < 0) calibConfirmSelection = 1;
                if (calibConfirmSelection > 1) calibConfirmSelection = 0;
                suppressNextClick = false;
                resetLcdTimer();
            }
            
            // ===== CALIB_CONFIRM click =====
            if (encClick) {
                if (suppressNextClick) suppressNextClick = false;
                else {
                    if (calibConfirmSelection == 0) {
                        // Да - переходим к настройке CPR
                        mode = MODE_CALIB_MANUAL_SELECT;
                    } else {
                        // Нет - возвращаемся в меню
                        mode = MODE_MENU;
                        menuSelection = 4; // Остаемся на пункте калибровки
                    }
                    suppressNextClick = true;
                    resetLcdTimer();
                }
            }
        }
        
        // ===== DX_SELECT click =====
        if (mode == MODE_DX_SELECT && encClick) {
          if (suppressNextClick) suppressNextClick = false;
          else {
            saveDXSettingToEEPROM(dxEnabled);  // сохраняем в EEPROM только при клике
            mode = MODE_NORMAL;
            suppressNextClick = true;
            resetLcdTimer();
          }
        }
    
        // ===== Normal mode encoder click =====
        if (mode == MODE_NORMAL && encClick) {
    
            if (suppressNextClick) {
                suppressNextClick = false;
                return;
            }
    
            // === 1. ЕСЛИ ДВИЖЕНИЕ → STOP ===
            if (movementActive) {
                stopMovement();
                resetLcdTimer();
                return;
            }
    
            // === 2. ПОКОЙ → возможный START ===
            noInterrupts();
            long curSensor = sensor;
            interrupts();
    
            int curAz = countsToAzimuth(curSensor);
            int desiredAz = eb.counter;
    
            if (desiredAz <= 0) desiredAz = 1;
            if (desiredAz > 360)
                desiredAz = ((desiredAz - 1) % 360) + 1;
    
            // кратчайшая разница
            int diff = abs(desiredAz - curAz);
            if (diff > 180) diff = 360 - diff;
    
            // === ПОРОГ 5° ===
            if (diff >= 5) {
                startMovementTo(desiredAz);
            }
    
            resetLcdTimer();
        }
    
        // ===== ADD click =====
        if (mode == MODE_ADD && encClick) {
          if (suppressNextClick) suppressNextClick = false;
          else {
            long newCounts = azimuthToCounts(addAzimuth);
            noInterrupts(); sensor = newCounts; interrupts();
    
            if (millis() - lastSavedMillis > SAVE_DEDUP_MS)
              savePositionToEEPROM(newCounts);
    
            eb.counter = roundToMultipleOf5(addAzimuth);
            mode = MODE_NORMAL;
            suppressNextClick = true;
            savedDisplayMillis = millis();
            resetLcdTimer();
            displayAzimuth = addAzimuth;
          }
        }
        
        // === LCD cache reset when entering NORMAL ===
        if (mode == MODE_NORMAL && lastDisplayedMode != MODE_NORMAL) {
            lastDisplayedMode = (Mode)-1;
            lastDisplayedAzimuth = -1;
            lastDisplayedNew = -1;
            lastDisplayedAddAzimuth = -1;
        }
        
        // === LCD cache reset when entering DX SELECT ===
        if (mode == MODE_DX_SELECT && lastDisplayedMode != MODE_DX_SELECT) {
            lastDxEnabled = !dxEnabled;  // гарантируем needUpdate
        }
        
        

        
        // =====================================================================
        // =========================== LCD UPDATE ===============================
        // =====================================================================
    
        if (millis() - lastLcdMillis >= LCD_UPDATE_MS) {
          lastLcdMillis = millis();
    
          noInterrupts();
          long curSensor = sensor;
          interrupts();
    
          int azimuth = countsToAzimuth(curSensor);
    
          bool needUpdate = false;
    
          if (mode != lastDisplayedMode) {
            needUpdate = true;
            // lastDisplayedMode обновляется ПОСЛЕ успешной отрисовки LCD (строка 1270)
          }
    
          if (mode == MODE_MENU) {
            if (menuSelection != lastMenuSelection) {
              needUpdate = true;
              lastMenuSelection = menuSelection;
            }
          }
    
          else if (mode == MODE_OVERLAP_SELECT) {
            if (overlapMode != lastOverlapMode) {
              needUpdate = true;
              lastOverlapMode = overlapMode;
            }
          }
          
          else if (mode == MODE_DX_SELECT) {
            if (dxEnabled != lastDxEnabled) {  // отслеживаем изменения
              needUpdate = true;
              lastDxEnabled = dxEnabled;      // сохраняем предыдущее значение
            }
          }
    
          else if (mode == MODE_ADD) {
            if (addAzimuth != lastDisplayedAddAzimuth ||
                azimuth != lastDisplayedAzimuth) {
              needUpdate = true;
              lastDisplayedAddAzimuth = addAzimuth;
              lastDisplayedAzimuth = azimuth;
            }
          }
    
          else if (mode == MODE_NORMAL) {
            if (eb.counter != lastDisplayedNew ||
                azimuth != lastDisplayedAzimuth ||
                movementActive != lastMovementActive ||
                movementDir != lastMovementDir) {
    
              needUpdate = true;
              lastDisplayedNew = eb.counter;
              lastDisplayedAzimuth = azimuth;
              lastMovementActive = movementActive;
              lastMovementDir = movementDir;
            }
          }
    
          else {
            needUpdate = true;
          }
    
          if (needUpdate) {
            lcd.clear();
    
            // MENU
            if (mode == MODE_MENU) {
              // Получаем указатель на строку из PROGMEM
              const char* labelPtr;
              memcpy_P(&labelPtr, &menuItems[menuSelection].label, sizeof(const char*));
              
              // Копируем строку в буфер
              char labelBuffer[16];  // немного больше для безопасности
              strcpy_P(labelBuffer, labelPtr);
              
              lcd.setCursor(0,0); lcd.print(F("MENU:"));
              lcd.setCursor(0,1);
              lcd.print(F("> "));
              lcd.print(labelBuffer);
            }
    
            // OVERLAP SELECT
            else if (mode == MODE_OVERLAP_SELECT) {
                lcd.setCursor(0,0); lcd.print(F("Overlap:"));
                lcd.setCursor(0,1);
            
                if (overlapMode == -1) lcd.print(F("OFF"));
                else if (overlapMode == 0) lcd.print(F("North 360"));
                else if (overlapMode == 1) lcd.print(F("South 180"));
            }
            
            // DX SELECT
            else if (mode == MODE_DX_SELECT) {
                lcd.setCursor(0,0); lcd.print(F("DX Indicator:"));
                lcd.setCursor(0,1);
                if (dxEnabled) lcd.print(F("ON"));
                else lcd.print(F("OFF"));
            }
            
            // CHASE SELECT
            else if (mode == MODE_CHASE_SELECT) {
                lcd.setCursor(0,0); lcd.print(F("Chase Mode:"));
                lcd.setCursor(0,1);
                if (chaseModeEnabled) lcd.print(F("ON"));
                else lcd.print(F("OFF"));
            }
            
            // CPR CHANGE
            else if (mode == MODE_CPR_CHANGE) {
                lcd.setCursor(0,0); lcd.print(F("Change CPR"));
                lcd.setCursor(0,1); lcd.print(F("Value: "));
                lcd.print(countsPerRev);
            }
            
            // CALIBRATION CONFIRM
            else if (mode == MODE_CALIB_CONFIRM) {
                lcd.setCursor(0,0); lcd.print(F("Start calibration?"));
                lcd.setCursor(0,1);
                lcd.print(F("CPR: "));
                lcd.print(manualTargetCounts);
                lcd.print(F("  "));
                if (calibConfirmSelection == 0) lcd.print(F("YES"));
                else lcd.print(F("NO"));
            }

    
            // ADD
            else if (mode == MODE_ADD) {
              lcd.setCursor(0,0); lcd.print(F("New Azimuth:"));
              lcd.setCursor(0,1); lcd.print(addAzimuth);
              lcd.setCursor(5,1); lcd.print(F(" Cur:"));
              lcd.setCursor(11,1); lcd.print(azimuth);
            }
    
            else if (mode == MODE_CALIB_MANUAL_SELECT) {
                lcd.setCursor(0,0); lcd.print("Manual Calib");
                lcd.setCursor(0,1); lcd.print("Impulses: ");
                lcd.print(manualTargetCounts);
            }
            
            else if (mode == MODE_CALIB_MANUAL_RUN) {
                lcd.setCursor(0,0);
                lcd.print(movementDir > 0 ? "Moving CW" : "Moving CCW");
                lcd.setCursor(0,1);
                lcd.print("Impulses: ");
                lcd.print(sensor);
            }
            
            else if (mode == MODE_CALIB_MANUAL_DONE) {
                lcd.setCursor(0,0); lcd.print("CPR OK:");
                lcd.setCursor(0,1); lcd.print(manualTargetCounts);
                lcd.print(" Click=Save");
            }
    
    
    
            // NORMAL
            else if (mode == MODE_NORMAL) {
              // Строка 1: New: 156   >
              lcd.setCursor(0,0); lcd.print(F("New:"));
              lcd.setCursor(5,0); lcd.print(eb.counter);
              lcd.setCursor(11,0);
              if (movementActive) {
                if (movementDir > 0) lcd.print(F("CW-->"));
                else lcd.print(F("<-CCW"));
              } else lcd.print(F("  "));  // 2 пробела когда нет движения
              
              // Строка 2: Azimuth: 187     KH6
              lcd.setCursor(0,1); lcd.print(F("Azimuth:"));
              lcd.setCursor(9,1); lcd.print(azimuth);
              
              // Очищаем 3 позиции для префикса (выравнивание по ширине)
              lcd.setCursor(14,1); lcd.print(F("   "));
              if (dxEnabled) {
                  lcd.setCursor(14,1); lcd.print(getDXPrefix(azimuth));
              }
            }
            
            lastDisplayedMode = mode;
          }
        }
        
      }