Инструменты пользователя

Инструменты сайта


banddecoder_code

Это старая версия документа!


IP 192.168.0.240 (можно сменить через настройки)

v0.80 (примерно)

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

// =====================================================
// === CONFIG: все константы устройства в одном месте ===
// =====================================================

// --- EEPROM ---
const byte EEPROM_MAGIC      = 0x42;
const int  EEPROM_ADDR_MAGIC = 0;
const int  EEPROM_ADDR_IP    = 1;   // 1..4

// --- Версия прошивки ---
const float VERSION = 0.80;

// --- CAT / сеть ---
const unsigned long CAT_TIMEOUT = 7000;
const unsigned long NET_TIMEOUT = 5000;

// --- Пины энкодера ---
const int ENC_A   = 8;
const int ENC_B   = A1;
const int ENC_BTN = 3;

// --- Пины реле ---
const int RELAYS[] = {4, 5, 6, 7};
const int RELAY_COUNT = 4;

// --- Кнопка ---
const unsigned long SHORT_PRESS = 100;
const unsigned long LONG_PRESS  = 3000;
const unsigned long HOLD_TIME   = 700;

// --- Редактор IP ---
const unsigned long EDITOR_CLICK_GUARD = 200;

// --- Анимация CAT ---
const int CAT_ANIM_STEPS = 5;


// =====================================================
// === ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ (состояние устройства) ===
// =====================================================

// --- IP редактор ---
bool ipEditMode = false;
byte ipEdit[4];
int ipOctetIndex = 0;

// --- LCD ---
LiquidCrystal_I2C lcd(0x27, 16, 2);

// --- Ethernet ---
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 240);
unsigned int localPort = 12060;
IPAddress effectiveIP = ip;
EthernetUDP Udp;

// --- CAT состояние ---
unsigned long lastCatTime = 0;
bool catActive = false;
unsigned long lastPacketTime = 0;
long lastFreq = 0;
bool freqReceived = false;
byte catAnimPos = 0;
int catPacketCounter = 0;

byte catDot[8] = {
  0b00000,
  0b00100,
  0b01110,
  0b01110,
  0b01110,
  0b00100,
  0b00000,
  0b00000
};

// --- Режимы ---
enum Mode { AUTO, MANUAL };
Mode mode = AUTO;
bool manualForced = false;

// --- Диапазоны ---
struct Band {
  const char* name;
  long from;
  long to;
  byte abcdCode;
};

Band bands[] = {
  {"160M", 1800000, 2000000, 0b00000001},
  {"80M",  3500000, 3800000, 0b00000010},
  {"40M",  7000000, 7350000, 0b00000011},
  {"30M", 10100000, 10150000, 0b00000100},
  {"20M", 14000000, 14350000, 0b00000101},
  {"17M", 18068000, 18168000, 0b00000110},
  {"15M", 21000000, 21450000, 0b00000111},
  {"12M", 24890000, 24990000, 0b00001000},
  {"11M", 26900000, 27999000, 0b00001001},
  {"10M", 28000000, 29700000, 0b00001001},
  {"6M",  50000000, 52000000, 0b00001010},
  {"2M",  144000000, 146000000, 0b00000000}
};

const int BAND_COUNT = sizeof(bands) / sizeof(Band);

// --- Текущее состояние диапазонов ---
int currentBand = -1;
int manualBandIndex = 0;

// --- Энкодер ---
int lastA = HIGH;

// --- Кнопка ---
bool buttonHeld = false;
bool pressed = false;
unsigned long pressStart = 0;
bool ignoreNextEditorClick = false;

// --- LCD обновление ---
Mode lastMode = AUTO;
int lastBandIndex = -1;
long lastDisplayedFreq = 0;
bool forceDisplayUpdate = false;

int detectBand(long freq) {
  for (int i = 0; i < BAND_COUNT; i++) {
    if (freq >= bands[i].from && freq <= bands[i].to)
      return i;
  }
  return -1;
}

void setRelaysByABCD(byte abcdCode) {
  for (int i = 0; i < RELAY_COUNT; i++) {
    bool state = (abcdCode >> i) & 1;
    digitalWrite(RELAYS[i], state ? LOW : HIGH);
  }
}

void setRelaysByBand(int bandIndex) {
  if (bandIndex >= 0 && bandIndex < BAND_COUNT)
    setRelaysByABCD(bands[bandIndex].abcdCode);
  else
    setRelaysByABCD(0);
}

void disableAllRelays() {
  setRelaysByABCD(0);
}

void saveIPToEEPROM(IPAddress ipToSave) {
  EEPROM.update(EEPROM_ADDR_MAGIC, EEPROM_MAGIC);
  for (int i = 0; i < 4; i++)
    EEPROM.update(EEPROM_ADDR_IP + i, ipToSave[i]);
}

bool loadIPFromEEPROM(IPAddress &out) {
  if (EEPROM.read(EEPROM_ADDR_MAGIC) != EEPROM_MAGIC) return false;
  byte b[4];
  for (int i = 0; i < 4; i++)
    b[i] = EEPROM.read(EEPROM_ADDR_IP + i);
  out = IPAddress(b[0], b[1], b[2], b[3]);
  return true;
}

void enterIpEditMode() {
  ipEditMode = true;

  for (int i = 0; i < 4; i++)
    ipEdit[i] = effectiveIP[i];

  ipOctetIndex = 0;
  drawIpEditor();
  ignoreNextEditorClick = true;
}

void drawIpEditor() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("SET IP:");

  lcd.setCursor(0,1);

  for (int i = 0; i < 4; i++) {

    // подчёркивание перед редактируемым октетом
    if (i == ipOctetIndex)
      lcd.print("_");
    else
      lcd.print("");   // ← НИЧЕГО НЕ ПЕЧАТАЕМ

    // вывод октета с ведущими нулями
    int val = ipEdit[i];
    if (val < 100) lcd.print("0");
    if (val < 10)  lcd.print("0");
    lcd.print(val);

    if (i < 3) lcd.print(".");
  }
}



void printFreqCompact(long f) {
  int MHz = f / 1000000;
  int kHz = (f / 1000) % 1000;
  int hundred = (f / 100) % 10;
  int tens = (f / 10) % 10;

  lcd.print(MHz);
  lcd.print(".");
  if (kHz < 100) lcd.print("0");
  if (kHz < 10)  lcd.print("0");
  lcd.print(kHz);
  lcd.print(".");
  lcd.print(hundred);
  lcd.print(tens);
}


void runRelayTest() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("TEST RELAYS");

  for (int i = 0; i < BAND_COUNT; i++) {

    // включаем реле
    setRelaysByBand(i);

    // выводим комбинацию ABCD
    lcd.setCursor(0,1);
    for (int b = 3; b >= 0; b--)
      lcd.print((bands[i].abcdCode >> b) & 1);

    delay(600);
  }

  disableAllRelays();
  delay(300);

  forceDisplayUpdate = true;
  updateLCDIfNeeded();
}

void updateLCDIfNeeded() {
  if (ipEditMode) return;
  bool changed = false;

  if (lastMode != mode) changed = true;
  if (mode != AUTO && lastBandIndex != manualBandIndex) changed = true;
  if (mode == AUTO && lastBandIndex != currentBand) changed = true;
  if (mode == AUTO && lastDisplayedFreq != lastFreq) changed = true;
  if (forceDisplayUpdate) { changed = true; forceDisplayUpdate = false; }

  if (!changed) return;

  updateTopLine();
  
  lcd.setCursor(0,1);
  lcd.print("                ");
  lcd.setCursor(0,1);

  if (mode == AUTO) {
    lcd.print("[A] ");
    if (currentBand >= 0) lcd.print(bands[currentBand].name);
    else lcd.print("--");

    lcd.print(" ");
    if (catActive) printFreqCompact(lastFreq);
    else lcd.print("NO DATA       ");

    lastBandIndex = currentBand;
    lastDisplayedFreq = lastFreq;
  } else {
    lcd.print("[M] ");
    lcd.print(bands[manualBandIndex].name);
    lcd.print(" ");
    for (int i = 3; i >= 0; i--)
      lcd.print((bands[manualBandIndex].abcdCode >> i) & 1);

    lastBandIndex = manualBandIndex;
  }

  lastMode = mode;
}

void updateTopLine() {
  if (ipEditMode) return;
  lcd.setCursor(0,0);
  lcd.print("                ");  
  lcd.setCursor(0,0);

  // === AUTO режим ===
  if (mode == AUTO) {
    lcd.print("CAT ");

    if (catActive) {
      // 5-позиционная анимация
      for (int i = 0; i < 5; i++) {
        if (i == catAnimPos)
          lcd.write(byte(0));   // кастомная точка
        else
          lcd.print(".");
      }

      lcd.print("   ");

      // Показ реле
      if (currentBand >= 0) {
        for (int i = 3; i >= 0; i--)
          lcd.print((bands[currentBand].abcdCode >> i) & 1);
      } else {
        lcd.print("----");
      }

    } else {
      // CAT пропал
      lcd.print(".....   ");
      if (currentBand >= 0) {
        for (int i = 3; i >= 0; i--)
          lcd.print((bands[currentBand].abcdCode >> i) & 1);
      } else {
        lcd.print("----");
      }
    }

    return;
  }

  // === MANUAL режим ===
  lcd.print("CAT ");
  if (catActive) lcd.print("OK");
  else lcd.print("--");
}



void splashScreen() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("EW8ZO v");
  lcd.print(VERSION, 2);
  lcd.setCursor(0,1);
  lcd.print("ABCD Decoder");
  delay(2000);
}


void readUDP() {
  int packetSize = Udp.parsePacket();
  if (!packetSize) return;

  char buf[200];
  int len = Udp.read(buf, sizeof(buf) - 1);
  if (len <= 0) return;
  buf[len] = 0;

  // Любой пакет = связь жива
  lastPacketTime = millis();
  lastCatTime = millis();
  catActive = true;

  // Если MANUAL был включён автоматически (fallback), а CAT снова появился — возвращаемся в AUTO
  if (mode == MANUAL && !manualForced) {
      mode = AUTO;
      
  
      // Восстанавливаем реле по текущему диапазону
      if (currentBand >= 0)
          setRelaysByBand(currentBand);
  
      forceDisplayUpdate = true;
      updateLCDIfNeeded();
  }

  
  // Двигаем CAT-анимацию каждые 2 пакета  
  catPacketCounter++;
  if (catPacketCounter >= 2) {   // ← каждые 2 пакета
      catPacketCounter = 0;
      catAnimPos = (catAnimPos + 1) % 5;
      updateTopLine();
  }


  // Ищем <Freq>
  char* f = strstr(buf, "<Freq>");
  if (f) {
    f += 6;

    lastFreq = atol(f) * 10;
    freqReceived = true;   // ← ВОТ ЭТОТ if ТЕБЕ НУЖЕН

    int b = detectBand(lastFreq);
    if (b >= 0)
      currentBand = b;

    // В AUTO обновляем реле
    if (mode == AUTO && currentBand >= 0)
      setRelaysByBand(currentBand);
  }

  // Если <Freq> нет — это heartbeat, ничего не трогаем
  updateLCDIfNeeded();
}



void handleButtonPress() {
    pressed = true;
    pressStart = millis();
    buttonHeld = false;
}

void handleButtonHold(unsigned long held) {

    // длинное → вход в редактор
    if (!ipEditMode && held >= LONG_PRESS) {
        enterIpEditMode();
        pressed = false;
        buttonHeld = false;
        return;
    }

    // жест удержания
    if (!ipEditMode && !buttonHeld && held >= HOLD_TIME) {
        buttonHeld = true;
    }
}

void handleIpEditorClick(unsigned long pressTime) {
    static unsigned long lastEditorClick = 0;

    // игнорируем первый клик после входа в редактор
    if (ignoreNextEditorClick) {
        ignoreNextEditorClick = false;
        return;
    }

    // длинное → выход без сохранения
    if (pressTime >= LONG_PRESS) {
        ipEditMode = false;
        forceDisplayUpdate = true;
        updateLCDIfNeeded();
        return;
    }

    // защита от двойного клика
    if (millis() - lastEditorClick < 200) return;
    lastEditorClick = millis();

    // короткое → следующий октет
    if (pressTime >= 50) {
        ipOctetIndex++;

        if (ipOctetIndex > 3) {
            effectiveIP = IPAddress(ipEdit[0], ipEdit[1], ipEdit[2], ipEdit[3]);
            saveIPToEEPROM(effectiveIP);

            lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("IP SAVED");
            lcd.setCursor(0,1);
            lcd.print(effectiveIP);
            delay(1200);

            ipEditMode = false;
            forceDisplayUpdate = true;
            updateLCDIfNeeded();
            return;
        }

        drawIpEditor();
    }
}

void toggleAutoManual() {

    if (mode == AUTO) {
        mode = MANUAL;
        manualForced = true;
        

        manualBandIndex = (currentBand >= 0 ? currentBand : 0);
        setRelaysByBand(manualBandIndex);
    }
    else {
        if (!catActive) {
            lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("NO CAT LINK");
            lcd.setCursor(0,1);
            lcd.print("AUTO DISABLED");
            delay(1200);

            forceDisplayUpdate = true;
            updateLCDIfNeeded();
            return;
        }

        mode = AUTO;
        manualForced = false;
        

        if (currentBand >= 0)
            setRelaysByBand(currentBand);
        else
            disableAllRelays();
    }

    forceDisplayUpdate = true;
    updateLCDIfNeeded();
}

void handleButtonRelease(unsigned long pressTime) {

    // редактор
    if (ipEditMode) {
        handleIpEditorClick(pressTime);
        return;
    }

    // длинное → вход в редактор
    if (pressTime >= LONG_PRESS) {
        enterIpEditMode();
        return;
    }

    // короткое → AUTO/MANUAL
    if (!buttonHeld && pressTime >= SHORT_PRESS) {
        toggleAutoManual();
    }

    buttonHeld = false;
}


//
// === КНОПКА С АНТИДРЕБЕЗГОМ И ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ ===
//
void handleButton() {
    static unsigned long lastCheck = 0;

    if (millis() - lastCheck < 30) return;
    lastCheck = millis();

    bool btn = !digitalRead(ENC_BTN);

    // начало нажатия
    if (btn && !pressed) {
        handleButtonPress();
        return;
    }

    // удержание
    if (btn && pressed) {
        unsigned long held = millis() - pressStart;
        handleButtonHold(held);
        return;
    }

    // отпускание
    if (!btn && pressed) {
        pressed = false;
        unsigned long pressTime = millis() - pressStart;
        handleButtonRelease(pressTime);
        return;
    }
}



//
// === ЭНКОДЕР С ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ ===
//
void handleEncoder() {
  static unsigned long lastMove = 0;

  int A = digitalRead(ENC_A);
  if (A == lastA) return;

  // реагируем только на фронт A == LOW
  if (A != lastA && A == LOW) {

    bool clockwise = digitalRead(ENC_B);

    // === УДЕРЖАНИЕ КНОПКИ + ПОВОРОТ ===
    if (buttonHeld && !ipEditMode) {

      if (clockwise) {
        runRelayTest();
      } else {
        // резерв
      }

      buttonHeld = false;
      lastA = A;
      return;
    }

    // === РЕЖИМ IP-РЕДАКТОРА ===
    if (ipEditMode) {

      if (millis() - lastMove < 80) {
        lastA = A;
        return;
      }
      lastMove = millis();

      int val = ipEdit[ipOctetIndex];
      if (clockwise) val++;
      else val--;

      if (val < 0) val = 255;
      if (val > 255) val = 0;

      ipEdit[ipOctetIndex] = val;
      drawIpEditor();

      lastA = A;
      return;
    }

    // === MANUAL режим ===
    if (mode == MANUAL) {

      if (millis() - lastMove < 50) {
        lastA = A;
        return;
      }
      lastMove = millis();

      if (clockwise)
        manualBandIndex = (manualBandIndex + 1) % BAND_COUNT;
      else
        manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT;

      setRelaysByBand(manualBandIndex);
      forceDisplayUpdate = true;
      updateLCDIfNeeded();
    }
  }

  lastA = A;
}





void checkCatTimeout() {

  // 1) Сбрасываем CAT независимо от режима
  if (catActive && millis() - lastCatTime > CAT_TIMEOUT) {
      catActive = false;

      // ОБНОВЛЯЕМ ПЕРВУЮ СТРОКУ
      updateTopLine();
  }

  // 2) Логика fallback только в AUTO
  if (mode == AUTO && !catActive) {
      mode = MANUAL;      
      manualForced = false;

      if (currentBand >= 0) {
          manualBandIndex = currentBand;
          setRelaysByBand(manualBandIndex);
      }

      forceDisplayUpdate = true;
      updateLCDIfNeeded();
  }
}




void setup() {  
  //Serial.begin(115200); delay(500); Serial.println("START DECODER");  

  // --- Пины энкодера ---
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  pinMode(ENC_BTN, INPUT_PULLUP);

  // --- Пины реле ---
  for (int i = 0; i < RELAY_COUNT; i++) {
    pinMode(RELAYS[i], OUTPUT);
    digitalWrite(RELAYS[i], LOW);
  }

  disableAllRelays();

  // --- LCD ---
  lcd.begin();
  lcd.backlight();

  // если есть сохранённый — подхватим
  effectiveIP = ip; 
  loadIPFromEEPROM(effectiveIP); 

  // Регистрируем кастомный символ CAT‑точки
  lcd.createChar(0, catDot);

  // Splash screen
  splashScreen();

  // --- Ethernet ---
  Ethernet.begin(mac, effectiveIP);
  Udp.begin(localPort);

  // Первичное отображение IP (потом будет перерисовано updateTopLine)
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("IP:");
  lcd.print(effectiveIP);
  lcd.setCursor(0,1);
  lcd.print("READY");

  // --- Начальные значения логики ---
  currentBand = -1;
  catActive = false;
  manualBandIndex = 0;  
  lastFreq = 0;  
  lastPacketTime = millis();

  disableAllRelays();

  // Обновляем дисплей по новой логике
  forceDisplayUpdate = true;  
  updateLCDIfNeeded();

  // Инициализация энкодера (важно!)
  lastA = digitalRead(ENC_A);
}


void loop() {  
  // === Всегда читаем UDP ===
  readUDP();

  // === Обработка энкодера и кнопки ===
  handleEncoder();
  handleButton();

  // === Проверка таймаута CAT ===
  checkCatTimeout();

  // === Периодическое обновление дисплея ===
  static unsigned long lastDisplayUpdate = 0;
  if (!ipEditMode && millis() - lastDisplayUpdate > 1000) {
    lastDisplayUpdate = millis();
    updateLCDIfNeeded();
  }  
  delay(10);
}









banddecoder_code.1768675877.txt.gz · Последнее изменение: eu8t