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

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


rotator_main
/*
  EW8ZO Rotator Controller v1.76
  - displayAz гарантирует шаг 5° при повороте энкодера (300,305,310...)
  - Геркон (reed) на D2
  - EncButton eb(5,6,7)
  - Реле: RELAY_1 = 8, RELAY_2 = 9 (2 реле)
  - START/STOP кнопка на D3 (второй контакт -> GND), INPUT_PULLUP
  - Режимы реле:
      Ожидание: оба OFF
      Вправо: RELAY_2 ON
      Влево: RELAY_1 + RELAY_2 ON
*/

#include <Arduino.h>
#include <EncButton.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

// LCD и энкодер (CLK->5, DT->6, SW->7)
LiquidCrystal_I2C lcd(0x27, 16, 2);
EncButton eb(5, 6, 7);

// ===== Реле (2 реле) =====
#define RELAY_1 8
#define RELAY_2 9
const bool RELAY_ACTIVE_HIGH = false; // true если модуль реле активен HIGH

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 = 1200;
const unsigned long SAVE_DELAY_MS = 1000;
const unsigned long LCD_UPDATE_MS = 100;
const unsigned long SAVED_DISPLAY_MS = 1000;
const unsigned long SPLASH_MS = 2000;
const unsigned long SAVE_DEDUP_MS = 1500;

// EEPROM
const int EEPROM_ADDR_MAGIC = 0;
const int EEPROM_ADDR_VALUE = 2;
const uint16_t EEPROM_MAGIC = 0xA5A5;

// ===== START/STOP кнопка =====
const int STARTSTOP_PIN = 3; // D3
const unsigned long BTN_DEBOUNCE_MS = 50;
bool startStopLastState = HIGH;
unsigned long startStopLastChange = 0;
bool startStopPressedHandled = false;

// ===== Глобальные переменные =====
volatile long sensor = 0;
volatile long temp_sensor = 0;
unsigned long lastMoveMillis = 0;
bool movedSinceLastSave = false;
unsigned long lastLcdMillis = 0;

const int COL_NEW_VAL = 5;
const int W_NEW_VAL = 5;
const int COL_CUR_AZ = 9;
const int W_CUR_AZ = 3;

enum Mode { MODE_NORMAL, MODE_ADD };
Mode mode = MODE_NORMAL;

int addAz = 1;
bool suppressNextClick = false;

bool movementActive = false;
int movementTargetAz = 1;

unsigned long savedDisplayMillis = 0;
unsigned long lastSavedMillis = 0;

// ===== Геркон (reed) =====
const int REED_PIN = 2;
const unsigned long REED_DEBOUNCE_US = 8000UL;
volatile unsigned long lastReedMicros = 0;

// ===== Флаги направления для ISR геркона =====
volatile bool dirRightFlag = false;
volatile bool dirLeftFlag  = false;

// ===== Новая переменная: отображаемый азимут, всегда кратный 5 =====
int displayAz = 0; // будет инициализирован в setup()

// ===== Функции управления мотором (по вашей логике) =====
void motorStop() {
  relayWrite(RELAY_1, false);
  relayWrite(RELAY_2, false);
  dirRightFlag = false;
  dirLeftFlag = false;
  Serial.println("Motor: STOP (both relays OFF)");
}

void motorForward() { // вправо: только RELAY_2 ON
  relayWrite(RELAY_1, false);
  relayWrite(RELAY_2, true);
  dirRightFlag = true;
  dirLeftFlag = false;
  Serial.println("Motor: FORWARD (RELAY_2 ON)");
}

void motorBackward() { // влево: оба реле ON
  relayWrite(RELAY_1, true);
  relayWrite(RELAY_2, true);
  dirRightFlag = false;
  dirLeftFlag = true;
  Serial.println("Motor: BACKWARD (both RELAY_1 & RELAY_2 ON)");
}

// ===== 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;
}

// ===== Вспомогательные конвертации =====
int countsToAzimuth1(long counts) {
  long c = counts % COUNTS_PER_REV; if (c < 0) c += COUNTS_PER_REV;
  long deg = (c * 360L) / COUNTS_PER_REV; int az = (int)deg; return (az == 0) ? 360 : az;
}
long azimuthToCounts(int az) {
  int a = az % 360; long c = ((long)a * COUNTS_PER_REV + 180) / 360;
  if (az == 360) c = 0; c = c % COUNTS_PER_REV; if (c < 0) c += COUNTS_PER_REV; return c;
}
int shortestTurnDirection(int current, int target) {
  if (current == target) return 0;
  int diff = target - current;
  if (diff > 180) diff -= 360;
  if (diff < -180) diff += 360;
  return (diff > 0) ? 1 : -1;
}

// ===== Управление движением =====
void stopMovement() {
  motorStop();
  movementActive = false;
  noInterrupts(); long cur = sensor; interrupts();
  if (millis() - lastSavedMillis > SAVE_DEDUP_MS) { savePositionToEEPROM(cur); Serial.println("Movement stopped, saved (immediate)"); }
  else Serial.println("Movement stopped (save skipped - recent)");
  movedSinceLastSave = false; lastMoveMillis = millis(); savedDisplayMillis = millis();
  eb.counter = countsToAzimuth1(cur);

  // Лог остановки
  Serial.print("STOP event: curCounts="); Serial.print(cur);
  Serial.print(" curAz="); Serial.print(countsToAzimuth1(cur));
  Serial.print(" targetAz="); Serial.print(movementTargetAz);
  Serial.print(" dirR="); Serial.print(dirRightFlag);
  Serial.print(" dirL="); Serial.println(dirLeftFlag);
}

void startMovementTo(int targetAz) {
  if (movementActive) return;
  noInterrupts(); long curSensor = sensor; interrupts();
  int curAz = countsToAzimuth1(curSensor);
  int dir = shortestTurnDirection(curAz, targetAz);
  if (dir == 0) {
    Serial.println("Already at target azimuth, no movement");
    if (millis() - lastSavedMillis > SAVE_DEDUP_MS) { savePositionToEEPROM(curSensor); savedDisplayMillis = millis(); }
    return;
  }
  movementTargetAz = targetAz; movementActive = true;
  if (dir > 0) motorForward(); else motorBackward();

  // Лог старта
  Serial.print("START event: curCounts="); Serial.print(curSensor);
  Serial.print(" curAz="); Serial.print(curAz);
  Serial.print(" targetAz="); Serial.print(targetAz);
  Serial.print(" dir="); Serial.println((dir>0) ? "CW" : "CCW");
}

// ===== LCD helpers =====
void lcdPrintFieldStr(int col, int row, const char* s, int width) {
  lcd.setCursor(col, row); for (int i = 0; i < width; ++i) lcd.print(' '); lcd.setCursor(col, row); lcd.print(s);
}
void lcdPrintFieldLong(int col, int row, long value, int width) { char buf[20]; snprintf(buf, sizeof(buf), "%ld", value); lcdPrintFieldStr(col, row, buf, width); }
void lcdPrintFieldIntFmt(int col, int row, int value, const char* fmt, int width) { char buf[20]; snprintf(buf, sizeof(buf), fmt, value); lcdPrintFieldStr(col, row, buf, width); }

// ===== ISR геркона (reed) =====
void reedISR() {
  unsigned long t = micros();
  if (t - lastReedMicros < REED_DEBOUNCE_US) return;
  lastReedMicros = t;
  if (dirRightFlag) sensor++;
  else if (dirLeftFlag) sensor--;
}

// ===== Setup =====
void setup() {
  Serial.begin(115200);

  // Геркон
  pinMode(REED_PIN, INPUT_PULLUP);

  // Реле: безопасный уровень до OUTPUT
  if (RELAY_ACTIVE_HIGH) { relayWriteRaw(RELAY_1, LOW); relayWriteRaw(RELAY_2, LOW); }
  else { relayWriteRaw(RELAY_1, HIGH); relayWriteRaw(RELAY_2, HIGH); }
  pinMode(RELAY_1, OUTPUT); pinMode(RELAY_2, OUTPUT);
  motorStop();

  // START/STOP кнопка (D3)
  pinMode(STARTSTOP_PIN, INPUT_PULLUP);
  startStopLastState = digitalRead(STARTSTOP_PIN);
  startStopLastChange = millis();

  // Прерывание для геркона
  attachInterrupt(digitalPinToInterrupt(REED_PIN), reedISR, FALLING);

  // LCD splash
  lcd.begin(); lcd.backlight(); lcd.clear();
  lcd.setCursor(0,0); lcd.print("EW8ZO Controller");
  lcd.setCursor(0,1); lcd.print("VER 1.76");
  delay(SPLASH_MS); lcd.clear();

  // EncButton настройки
  eb.setBtnLevel(LOW); eb.setClickTimeout(800); eb.setDebTimeout(8); eb.setHoldTimeout(3000);
  eb.setEncType(EB_STEP4_LOW); eb.setEncReverse(0); eb.setFastTimeout(10); eb.counter = 0;

  // Загрузка сохранённой позиции
  long saved = 0;
  if (loadPositionFromEEPROM(saved)) { noInterrupts(); sensor = saved; interrupts(); Serial.print("Loaded pos counts: "); Serial.println(saved); lastSavedMillis = millis(); }
  else Serial.println("No saved pos in EEPROM");

  // Инициализация eb.counter и displayAz
  noInterrupts();
  int curAz = countsToAzimuth1(sensor);
  interrupts();
  // Инициализируем displayAz как нижнее кратное 5 (304 -> 300)
  displayAz = (curAz / 5) * 5;
  if (displayAz <= 0) displayAz = 360;
  eb.counter = displayAz;

  lcdPrintFieldStr(0,0,"New:",5); lcdPrintFieldStr(0,1,"Azimuth:",8);
  lastLcdMillis = millis();

  // Информационный лог при старте
  Serial.println("Controller v1.76 started");
  Serial.print("Initial curAz="); Serial.print(curAz); Serial.print(" displayAz="); Serial.println(displayAz);
}

// ===== Main loop =====
void loop() {
  eb.tick();

  // Обработка START/STOP кнопки (debounce) с логом в Serial
  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) { // нажата
        noInterrupts(); long curSensor = sensor; interrupts();
        int curAz = countsToAzimuth1(curSensor);
        Serial.print("BUTTON START/STOP pressed -> New="); Serial.print(eb.counter);
        Serial.print(" CurAz="); Serial.print(curAz);
        Serial.print(" movementActive="); Serial.println(movementActive);

        if (movementActive) {
          stopMovement();
        } else {
          int desiredAz = eb.counter;
          if (desiredAz <= 0) desiredAz = 1;
          if (desiredAz > 360) desiredAz = ((desiredAz - 1) % 360) + 1;
          if (desiredAz != curAz) startMovementTo(desiredAz);
          else Serial.println("Start button: desired == current, nothing to do");
        }
      }
      startStopPressedHandled = true;
    }
  }

  // Вход в ADD режим при удержании энкодера
  if (eb.hold() && mode == MODE_NORMAL && !movementActive) {
    mode = MODE_ADD;
    noInterrupts(); int curAz = countsToAzimuth1(sensor); interrupts();
    addAz = curAz; Serial.println("Entered ADD mode"); suppressNextClick = false; lastLcdMillis = 0;
  }

  // Отслеживание изменений sensor
  if (sensor != temp_sensor) { temp_sensor = sensor; lastMoveMillis = millis(); movedSinceLastSave = true; }

  // Если движение активно — проверяем достижение цели
  if (movementActive) {
    noInterrupts(); long curSensor = sensor; interrupts();
    int curAz = countsToAzimuth1(curSensor);
    if (curAz == movementTargetAz) { stopMovement(); lastLcdMillis = 0; }
  }

  // Сохранение в EEPROM после задержки
  if (movedSinceLastSave && (millis() - lastMoveMillis >= SAVE_DELAY_MS)) {
    if (millis() - lastSavedMillis > SAVE_DEDUP_MS) {
      noInterrupts(); long toSave = sensor; interrupts();
      int azForLog = countsToAzimuth1(toSave);
      Serial.print("Saving counts: "); Serial.print(toSave); Serial.print("  azimuth: "); Serial.println(azForLog);
      savePositionToEEPROM(toSave); movedSinceLastSave = false; savedDisplayMillis = millis();
      int newAz = countsToAzimuth1(toSave); eb.counter = newAz; lastLcdMillis = 0;
    } else movedSinceLastSave = false;
  }

  // Обработка поворотов энкодера
  static unsigned long lastTurnMs = 0;
  if (eb.turn()) {
    unsigned long now = millis(); unsigned long dt = now - lastTurnMs; lastTurnMs = now;
    int dir = eb.dir(); // 1 или -1

    if (mode == MODE_NORMAL) {
      if (!movementActive) {
        // Обновляем displayAz ровно на 5 градусов за один шаг энкодера
        displayAz += dir * 5;
        // Нормализация 1..360
        while (displayAz <= 0) displayAz += 360;
        displayAz = ((displayAz - 1) % 360) + 1;
        // Синхронизируем New с displayAz
        eb.counter = displayAz;
        lastLcdMillis = 0;
      }
    } else if (mode == MODE_ADD) {
      // ADD режим с ускорением (как раньше)
      int baseStep = 5;
      int mult;
      if (dt < 30) mult = 20;
      else if (dt < 80) mult = 5;
      else mult = 1;
      int delta = dir * baseStep * mult;
      addAz += delta;
      while (addAz <= 0) addAz += 360;
      addAz = ((addAz - 1) % 360) + 1;
      lastLcdMillis = 0;
    }
  }

  // Подтверждение ADD кликом
  if (mode == MODE_ADD && eb.click()) {
    long newCounts = azimuthToCounts(addAz); noInterrupts(); sensor = newCounts; interrupts();
    if (millis() - lastSavedMillis > SAVE_DEDUP_MS) { savePositionToEEPROM(newCounts); Serial.print("ADD confirmed: az="); Serial.print(addAz); Serial.print("  counts="); Serial.println(newCounts); }
    else { Serial.print("ADD confirmed: az="); Serial.print(addAz); Serial.println(" (save skipped - recent)"); }
    eb.counter = addAz; mode = MODE_NORMAL; suppressNextClick = true; savedDisplayMillis = millis(); lastLcdMillis = 0;
    // Синхронизируем displayAz с новым значением
    displayAz = addAz;
  }

  // Нормальный клик энкодера: старт движения
  if (mode == MODE_NORMAL && eb.click()) {
    if (suppressNextClick) suppressNextClick = false;
    else {
      noInterrupts(); long curSensor = sensor; interrupts();
      int curAz = countsToAzimuth1(curSensor);
      int desiredAz = eb.counter;
      if (desiredAz <= 0) desiredAz = 1;
      if (desiredAz > 360) desiredAz = ((desiredAz - 1) % 360) + 1;
      if (desiredAz != curAz) startMovementTo(desiredAz);
      else Serial.println("Click: desired == current, nothing to do");
      lastLcdMillis = 0;
    }
  }

  // Обновление LCD
  if (millis() - lastLcdMillis >= LCD_UPDATE_MS) {
    lastLcdMillis = millis(); noInterrupts(); long curSensor = sensor; interrupts();
    int curAz = countsToAzimuth1(curSensor);
    if (mode == MODE_NORMAL) {
      if (millis() - savedDisplayMillis < SAVED_DISPLAY_MS) { lcdPrintFieldStr(0,0,"SAVED",5); lcdPrintFieldLong(COL_NEW_VAL,0,eb.counter,W_NEW_VAL); }
      else { lcdPrintFieldStr(0,0,"New:",5); lcdPrintFieldLong(COL_NEW_VAL,0,eb.counter,W_NEW_VAL); }
      lcdPrintFieldIntFmt(COL_CUR_AZ,1,curAz,"%03d",W_CUR_AZ);
      lcd.setCursor(13,1);
      if (movementActive) { int dir = shortestTurnDirection(curAz, movementTargetAz); if (dir > 0) lcd.print('>'); else if (dir < 0) lcd.print('<'); else lcd.print('='); }
      else lcd.print(' ');
    } else if (mode == MODE_ADD) {
      lcdPrintFieldStr(0,0,"ADD:",5); lcdPrintFieldIntFmt(COL_NEW_VAL,0,addAz,"%03d",3); lcdPrintFieldIntFmt(COL_CUR_AZ,1,curAz,"%03d",W_CUR_AZ); lcd.setCursor(13,1); lcd.print(' ');
    }
  }

  delay(2);
}
rotator_main.txt · Последнее изменение: eu8t