Загрузка данных


#include <Wire.h>
#include <U8g2lib.h>

// Инициализация экрана 128x64 I2C (адрес 0x3C)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// ═══════════════════════════════════════
// ПИНЫ
// ═══════════════════════════════════════
#define BTN_UP 2
#define BTN_DOWN 3
#define LED_PIN 9

// ═══════════════════════════════════════
// НАСТРОЙКИ ТАЙМИНГОВ
// ══════════════════════════════════════
#define DEBOUNCE_MS 50          // Антидребезг
#define SHORT_PRESS_MAX 600     // Короткое нажатие (<0.6с)
#define MEDIUM_PRESS_MIN 800    // Среднее нажатие (0.8-2.4с)
#define LONG_PRESS_MIN 2500     // Долгое нажатие (>2.5с)
#define SIMULTANEOUS_TOL 250    // Погрешность одновременного нажатия (мс)

// ═══════════════════════════════════════
// ПЕРЕМЕННЫЕ ВРЕМЕНИ
// ═══════════════════════════════════════
byte h = 14, m = 43, s = 30;
unsigned long lastTick = 0;

// ═══════════════════════════════════════
// ПЕРЕМЕННЫЕ МЕТРОНОМА
// ══════════════════════════════════════
int bpm = 100;
bool metroRunning = false;
unsigned long lastBeat = 0;

// ═══════════════════════════════════════
// СОСТОЯНИЯ ИНТЕРФЕЙСА
// ═══════════════════════════════════════
enum State {
  SLEEP,
  SHOW_TIME,
  SET_TIME_SELECT,
  SET_TIME_HOURS,
  SET_TIME_MINS,
  METRO_SHOW,
  METRO_ADJUST
};

State currentState = SLEEP;
// ═══════════════════════════════════════
// КНОПКИ (ВСЕ ОБЪЯВЛЕНЫ ГЛОБАЛЬНО)
// ═══════════════════════════════════════
bool btnUpPressed = false;
bool btnDownPressed = false;
bool btnUpActive = false;
bool btnDownActive = false;
unsigned long btnUpPressTime = 0;
unsigned long btnDownPressTime = 0;
bool btnBothActive = false;

// ═══════════════════════════════════════
// SETUP
// ═══════════════════════════════════════
void setup() {
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  
  u8g2.begin();
  u8g2.setFontMode(1);
  lastTick = millis();
}

// ═══════════════════════════════════════
// LOOP
// ═══════════════════════════════════════
void loop() {
  updateButtons();
  handleStateLogic();
  drawScreen();
}

// ═══════════════════════════════════════
// ОБРАБОТКА КНОПОК (НАДЁЖНАЯ, БЕЗ ЗАДЕРЖЕК)
// ══════════════════════════════════════
void updateButtons() {
  bool upNow = (digitalRead(BTN_UP) == LOW);
  bool downNow = (digitalRead(BTN_DOWN) == LOW);
  unsigned long now = millis();

  // Верхняя кнопка
  if (upNow && !btnUpActive) {
    btnUpActive = true;
    btnUpPressed = true;
    btnUpPressTime = now;
  } else if (!upNow && btnUpActive) {
    btnUpActive = false;
    btnUpPressed = false;  }

  // Нижняя кнопка
  if (downNow && !btnDownActive) {
    btnDownActive = true;
    btnDownPressed = true;
    btnDownPressTime = now;
  } else if (!downNow && btnDownActive) {
    btnDownActive = false;
    btnDownPressed = false;
  }

  // Детекция одновременного нажатия
  if (btnUpPressed && btnDownPressed && !btnBothActive) {
    if (abs((long)(btnUpPressTime - btnDownPressTime)) <= SIMULTANEOUS_TOL) {
      btnBothActive = true;
      triggerBothPress();
    }
  }
  if (!btnUpPressed && !btnDownPressed) btnBothActive = false;
}

// ═══════════════════════════════════════
// ЛОГИКА СОСТОЯНИЙ
// ═══════════════════════════════════════
void handleStateLogic() {
  unsigned long now = millis();
  unsigned long upDur = now - btnUpPressTime;
  unsigned long downDur = now - btnDownPressTime;

  switch (currentState) {
    case SLEEP:
      if (btnDownPressed) { // Любое нажатие будит
        currentState = SHOW_TIME;
        btnDownPressed = false;
      }
      break;

    case SHOW_TIME:
      // Обновление времени
      if (now - lastTick >= 1000) {
        lastTick += 1000;
        s++;
        if (s >= 60) { s = 0; m++; if (m >= 60) { m = 0; h++; if (h >= 24) h = 0; } }
      }
      // Долгое нажатие ВВЕРХ → Настройка времени
      if (btnUpActive && upDur >= LONG_PRESS_MIN) {
        currentState = SET_TIME_SELECT;
        btnUpActive = false; // Сброс чтобы не сработало дважды
      }      // Долгое нажатие ВНИЗ → Метроном
      if (btnDownActive && downDur >= LONG_PRESS_MIN) {
        currentState = METRO_SHOW;
        btnDownActive = false;
      }
      break;

    case SET_TIME_SELECT:
      if (btnUpPressed) { currentState = SET_TIME_HOURS; btnUpPressed = false; }
      if (btnDownPressed) { currentState = SET_TIME_MINS; btnDownPressed = false; }
      break;

    case SET_TIME_HOURS:
      handleTimeAdjust(true);
      if (btnBothActive) { currentState = SET_TIME_SELECT; btnBothActive = false; }
      break;

    case SET_TIME_MINS:
      handleTimeAdjust(false);
      if (btnBothActive) { currentState = SHOW_TIME; btnBothActive = false; }
      break;

    case METRO_SHOW:
      // Любая короткая кнопка → старт/стоп
      if (btnUpPressed || btnDownPressed) {
        metroRunning = !metroRunning;
        if (metroRunning) lastBeat = now;
        btnUpPressed = false; btnDownPressed = false;
      }
      // Обе кнопки → настройка BPM
      if (btnBothActive) { currentState = METRO_ADJUST; btnBothActive = false; }
      // Долгое нажатие любой → выход
      if ((btnUpActive && upDur >= LONG_PRESS_MIN) || (btnDownActive && downDur >= LONG_PRESS_MIN)) {
        currentState = SHOW_TIME;
        metroRunning = false;
        btnUpActive = false; btnDownActive = false;
      }
      // Тик метронома
      if (metroRunning) {
        unsigned long interval = 60000UL / bpm;
        if (now - lastBeat >= interval) {
          lastBeat += interval;
          digitalWrite(LED_PIN, HIGH);
          // Кратковременный импульс без delay()
          static unsigned long ledOffTime = 0;
          ledOffTime = now + 60;
          // Логика выключения в drawScreen или отдельном таймере
        }
        if (now > (lastBeat - 50) && now < (lastBeat + 10)) { // Импульс ~60мс
             digitalWrite(LED_PIN, HIGH);        } else {
             digitalWrite(LED_PIN, LOW);
        }
      } else {
        digitalWrite(LED_PIN, LOW);
      }
      break;

    case METRO_ADJUST:
      // Изменение BPM
      unsigned long upDurAdj = now - btnUpPressTime;
      unsigned long downDurAdj = now - btnDownPressTime;
      
      if (btnUpPressed) { bpm = min(220, bpm + 1); btnUpPressed = false; }
      if (btnDownPressed) { bpm = max(40, bpm - 1); btnDownPressed = false; }
      
      if (btnUpActive && upDurAdj >= MEDIUM_PRESS_MIN && upDurAdj < LONG_PRESS_MIN) {
        bpm = min(220, bpm + 10);
        btnUpActive = false; // Сброс среднего нажатия
      }
      if (btnDownActive && downDurAdj >= MEDIUM_PRESS_MIN && downDurAdj < LONG_PRESS_MIN) {
        bpm = max(40, bpm - 10);
        btnDownActive = false;
      }

      if (btnBothActive) { currentState = METRO_SHOW; btnBothActive = false; }
      if ((btnUpActive && upDurAdj >= LONG_PRESS_MIN) || (btnDownActive && downDurAdj >= LONG_PRESS_MIN)) {
        currentState = METRO_SHOW;
        btnUpActive = false; btnDownActive = false;
      }
      break;
  }
}

void handleTimeAdjust(bool isHours) {
  unsigned long now = millis();
  unsigned long upDur = now - btnUpPressTime;
  unsigned long downDur = now - btnDownPressTime;

  if (btnUpPressed) {
    if (isHours) h = (h + 1) % 24; else m = (m + 1) % 60;
    btnUpPressed = false;
  }
  if (btnDownPressed) {
    if (isHours) h = (h + 23) % 24; else m = (m + 59) % 60;
    btnDownPressed = false;
  }

  if (btnUpActive && upDur >= MEDIUM_PRESS_MIN && upDur < LONG_PRESS_MIN) {
    if (isHours) h = (h + 10) % 24; else m = (m + 10) % 60;    btnUpActive = false;
  }
  if (btnDownActive && downDur >= MEDIUM_PRESS_MIN && downDur < LONG_PRESS_MIN) {
    if (isHours) h = (h + 14) % 24; else m = (m + 50) % 60;
    btnDownActive = false;
  }
}

void triggerBothPress() {
  // Обработка уже в handleStateLogic для чистоты
}

// ═══════════════════════════════════════
// ОТРИСОВКА
// ═══════════════════════════════════════
void drawScreen() {
  u8g2.clearBuffer();

  switch (currentState) {
    case SLEEP:
      // Черный экран
      break;

    case SHOW_TIME: {
      // Основное время (увеличенное)
      u8g2.setFont(u8g2_font_profont29_tr);
      char buf[8];
      sprintf(buf, "%02d:%02d", h, m);
      int w = u8g2.getStrWidth(buf);
      u8g2.drawStr((128 - w) / 2, 42, buf);

      // Второстепенная строка
      u8g2.setFont(u8g2_font_6x10_tr);
      byte h12 = (h == 0 || h == 12) ? 12 : (h > 12 ? h - 12 : h);
      sprintf(buf, "%02d:%02d:%02d %s", h12, m, s, h < 12 ? "AM" : "PM");
      w = u8g2.getStrWidth(buf);
      u8g2.drawStr((128 - w) / 2, 60, buf);
      break;
    }

    case SET_TIME_SELECT: {
      u8g2.setFont(u8g2_font_profont22_tr);
      char buf[4];
      
      // Часы
      sprintf(buf, "%02d", h);
      int wH = u8g2.getStrWidth(buf);
      u8g2.drawBox(24, 18, wH + 6, 26);
      u8g2.setDrawColor(0);
      u8g2.drawStr(27, 38, buf);      u8g2.setDrawColor(1);

      u8g2.drawStr(56, 38, ":");

      // Минуты
      sprintf(buf, "%02d", m);
      int wM = u8g2.getStrWidth(buf);
      u8g2.drawBox(68, 18, wM + 6, 26);
      u8g2.setDrawColor(0);
      u8g2.drawStr(71, 38, buf);
      u8g2.setDrawColor(1);

      u8g2.setFont(u8g2_font_5x8_tr);
      u8g2.drawStr(8, 10, "UP: HOURS   DOWN: MINS");
      u8g2.drawStr(25, 60, "BOTH: ADJUST");
      break;
    }

    case SET_TIME_HOURS: {
      u8g2.setFont(u8g2_font_profont22_tr);
      char buf[4];

      // Часы (Инверсия)
      sprintf(buf, "%02d", h);
      int w = u8g2.getStrWidth(buf);
      u8g2.drawBox(24, 18, w + 6, 26);
      u8g2.setDrawColor(0);
      u8g2.drawStr(27, 38, buf);
      u8g2.setDrawColor(1);

      u8g2.drawStr(56, 38, ":");

      // Минуты + Стрелки
      sprintf(buf, "%02d", m);
      u8g2.drawStr(71, 38, buf);
      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(98, 28, "↑");
      u8g2.drawStr(98, 44, "↓");

      u8g2.setFont(u8g2_font_5x8_tr);
      u8g2.drawStr(8, 10, "ADJUSTING HOURS");
      u8g2.drawStr(20, 60, "BOTH: BACK");
      break;
    }

    case SET_TIME_MINS: {
      u8g2.setFont(u8g2_font_profont22_tr);
      char buf[4];

      // Часы + Стрелки      sprintf(buf, "%02d", h);
      u8g2.drawStr(27, 38, buf);
      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(10, 28, "↑");
      u8g2.drawStr(10, 44, "↓");

      u8g2.setFont(u8g2_font_profont22_tr);
      u8g2.drawStr(56, 38, ":");

      // Минуты (Инверсия)
      sprintf(buf, "%02d", m);
      int w = u8g2.getStrWidth(buf);
      u8g2.drawBox(68, 18, w + 6, 26);
      u8g2.setDrawColor(0);
      u8g2.drawStr(71, 38, buf);
      u8g2.setDrawColor(1);

      u8g2.setFont(u8g2_font_5x8_tr);
      u8g2.drawStr(8, 10, "ADJUSTING MINS");
      u8g2.drawStr(20, 60, "BOTH: CONFIRM & EXIT";
      break;
    }

    case METRO_SHOW: {
      u8g2.setFont(u8g2_font_profont29_tr);
      char buf[10];
      sprintf(buf, "%d BPM", bpm);
      int w = u8g2.getStrWidth(buf);
      u8g2.drawStr((128 - w) / 2, 42, buf);

      u8g2.setFont(u8g2_font_6x10_tr);
      if (metroRunning) {
        u8g2.drawStr(30, 60, "● RUNNING");
      } else {
        u8g2.drawStr(15, 60, "PUSH ANY TO START");
      }
      u8g2.drawStr(45, 10, "BOTH: ADJUST");
      break;
    }

    case METRO_ADJUST: {
      u8g2.setFont(u8g2_font_profont29_tr);
      char buf[6];
      sprintf(buf, "%d", bpm);
      int w = u8g2.getStrWidth(buf);
      int x = (128 - w) / 2;

      // Инверсия (белый квадрат, черный текст)
      u8g2.drawBox(x - 4, 15, w + 8, 38);
      u8g2.setDrawColor(0);      u8g2.drawStr(x, 48, buf);
      u8g2.setDrawColor(1);

      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(45, 60, "BPM");
      
      u8g2.setFont(u8g2_font_5x8_tr);
      u8g2.drawStr(5, 10, "UP/DOWN: CHANGE");
      u8g2.drawStr(25, 60, "BOTH: CONFIRM";
      break;
    }
  }

  u8g2.sendBuffer();
}