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


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

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

const unsigned long DEBOUNCE_MS     = 35;
const unsigned long LONG_PRESS_MS    = 2500;
const unsigned long MEDIUM_PRESS_MS  = 1000;
const unsigned long COMBO_WINDOW_MS  = 350;
const unsigned long LED_PULSE_MS     = 35;

byte h = 16, m = 11, s = 0;
unsigned long lastSecondTick = 0;

int bpm = 100;
bool metroRunning = false;
unsigned long lastBeat = 0;
bool ledPulse = false;
unsigned long ledOffAt = 0;

enum State {
  SLEEP,
  SHOW_TIME,
  TIME_PICK,
  TIME_EDIT,
  METRO_SHOW,
  METRO_EDIT
};

State state = SLEEP;
byte editMode = 0;

struct Button {
  byte pin;
  bool stable;
  bool lastStable;
  bool raw;
  unsigned long lastChange;
  unsigned long pressStart;
  bool longDone;
};

Button upBtn   = {BTN_UP, HIGH, HIGH, HIGH, 0, 0, false};
Button downBtn  = {BTN_DOWN, HIGH, HIGH, HIGH, 0, 0, false};

bool comboActive = false;
bool comboDone = false;
unsigned long comboStart = 0;

unsigned long lastBlink = 0;
bool blinkState = true;

void gotoShowTime() {
  state = SHOW_TIME;
  lastSecondTick = millis();
}

void goSleep() {
  state = SLEEP;
  metroRunning = false;
  digitalWrite(LED_PIN, LOW);
}

void updateButton(Button &b) {
  bool nowRaw = digitalRead(b.pin);
  unsigned long now = millis();

  if (nowRaw != b.raw) {
    b.raw = nowRaw;
    b.lastChange = now;
  }

  if ((now - b.lastChange) >= DEBOUNCE_MS && b.stable != b.raw) {
    b.stable = b.raw;
  }
}

bool bothPressed() {
  return (upBtn.stable == LOW && downBtn.stable == LOW);
}

void drawInverseTextBox(int x, int y, const char *txt) {
  int w = u8g2.getStrWidth(txt);
  int h = u8g2.getAscent() - u8g2.getDescent();
  u8g2.drawBox(x, y - h + 2, w + 6, h + 4);
  u8g2.setDrawColor(0);
  u8g2.drawStr(x + 3, y, txt);
  u8g2.setDrawColor(1);
}

void drawHintArrows(int x, int y) {
  u8g2.setFont(u8g2_font_5x8_tr);
  u8g2.drawStr(x, y, "^");
  u8g2.drawStr(x, y + 12, "v");
}

void handleShortUp();
void handleShortDown();
void handleMediumUp();
void handleMediumDown();
void handleLongUp();
void handleLongDown();
void handleCombo();
void updateClock();
void updateMetro();
void renderScreen();

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);

  lastSecondTick = millis();
}

void loop() {
  unsigned long now = millis();

  updateButton(upBtn);
  updateButton(downBtn);

  if (state == SLEEP) {
    if (upBtn.stable == LOW || downBtn.stable == LOW) {
      gotoShowTime();
    }
  }

  if (upBtn.stable == LOW && upBtn.lastStable == HIGH) {
    upBtn.pressStart = now;
    upBtn.longDone = false;
  }
  if (downBtn.stable == LOW && downBtn.lastStable == HIGH) {
    downBtn.pressStart = now;
    downBtn.longDone = false;
  }

  if (upBtn.stable == LOW && !upBtn.longDone && (now - upBtn.pressStart) >= LONG_PRESS_MS) {
    upBtn.longDone = true;
    handleLongUp();
  }
  if (downBtn.stable == LOW && !downBtn.longDone && (now - downBtn.pressStart) >= LONG_PRESS_MS) {
    downBtn.longDone = true;
    handleLongDown();
  }

  if (bothPressed()) {
    if (!comboActive) {
      comboActive = true;
      comboStart = now;
      comboDone = false;
    } else if (!comboDone && (now - comboStart) >= COMBO_WINDOW_MS) {
      comboDone = true;
      handleCombo();
    }
  } else {
    comboActive = false;
    comboDone = false;
  }

  if (upBtn.stable == HIGH && upBtn.lastStable == LOW) {
    unsigned long held = now - upBtn.pressStart;
    if (!upBtn.longDone && !comboDone) {
      if (held >= MEDIUM_PRESS_MS) handleMediumUp();
      else handleShortUp();
    }
  }

  if (downBtn.stable == HIGH && downBtn.lastStable == LOW) {
    unsigned long held = now - downBtn.pressStart;
    if (!downBtn.longDone && !comboDone) {
      if (held >= MEDIUM_PRESS_MS) handleMediumDown();
      else handleShortDown();
    }
  }

  if (state == SHOW_TIME) updateClock();
  if (state == METRO_SHOW && metroRunning) updateMetro();

  if (now - lastBlink >= 350) {
    lastBlink = now;
    blinkState = !blinkState;
  }

  renderScreen();

  upBtn.lastStable = upBtn.stable;
  downBtn.lastStable = downBtn.stable;
}

void handleShortUp() {
  if (state == TIME_PICK) {
    editMode = 0;
  } else if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 1) % 24;
    else m = (m + 1) % 60;
  } else if (state == METRO_EDIT) {
    bpm = min(220, bpm + 1);
  }
}

void handleShortDown() {
  if (state == SLEEP) {
    gotoShowTime();
  } else if (state == TIME_PICK) {
    editMode = 1;
  } else if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 23) % 24;
    else m = (m + 59) % 60;
  } else if (state == METRO_SHOW && !metroRunning) {
    metroRunning = true;
    lastBeat = millis();
  } else if (state == METRO_EDIT) {
    bpm = max(40, bpm - 1);
  }
}

void handleMediumUp() {
  if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 10) % 24;
    else m = (m + 10) % 60;
  } else if (state == METRO_EDIT) {
    bpm = min(220, bpm + 10);
  }
}

void handleMediumDown() {
  if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 14) % 24;
    else m = (m + 50) % 60;
  } else if (state == METRO_EDIT) {
    bpm = max(40, bpm - 10);
  }
}

void handleLongUp() {
  if (state == SHOW_TIME) {
    state = TIME_PICK;
    editMode = 0;
  } else if (state == METRO_SHOW || state == METRO_EDIT) {
    gotoShowTime();
    metroRunning = false;
  }
}

void handleLongDown() {
  if (state == SHOW_TIME) {
    state = METRO_SHOW;
    metroRunning = false;
  } else if (state == METRO_SHOW || state == METRO_EDIT) {
    gotoShowTime();
    metroRunning = false;
  }
}

void handleCombo() {
  if (state == TIME_PICK) {
    state = TIME_EDIT;
  } else if (state == TIME_EDIT) {
    if (editMode == 0) {
      editMode = 1;
      state = TIME_PICK;
    } else {
      state = SHOW_TIME;
    }
  } else if (state == METRO_SHOW) {
    state = METRO_EDIT;
  } else if (state == METRO_EDIT) {
    state = METRO_SHOW;
  }
}

void updateClock() {
  unsigned long now = millis();
  while (now - lastSecondTick >= 1000) {
    lastSecondTick += 1000;
    s++;
    if (s >= 60) {
      s = 0;
      m++;
      if (m >= 60) {
        m = 0;
        h = (h + 1) % 24;
      }
    }
  }
}

void updateMetro() {
  unsigned long now = millis();

  if (ledPulse && now >= ledOffAt) {
    digitalWrite(LED_PIN, LOW);
    ledPulse = false;
  }

  unsigned long interval = 60000UL / (unsigned long)bpm;
  if (now - lastBeat >= interval) {
    lastBeat += interval;
    digitalWrite(LED_PIN, HIGH);
    ledPulse = true;
    ledOffAt = now + LED_PULSE_MS;
  }
}

void renderScreen() {
  u8g2.clearBuffer();

  switch (state) {
    case SLEEP:
      break;

    case SHOW_TIME: {
      u8g2.setFont(u8g2_font_profont22_tr);
      char buf1[6];
      sprintf(buf1, "%02d:%02d", h, m);
      u8g2.drawStr(26, 34, buf1);

      u8g2.setFont(u8g2_font_6x10_tr);
      char buf2[16];
      sprintf(buf2, "%02d:%02d:%02d", h, m, s);
      u8g2.drawStr(28, 60, buf2);
      break;
    }

    case TIME_PICK: {
      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(4, 10, "TIME SET");

      u8g2.setFont(u8g2_font_profont22_tr);
      char hh[3], mm[3];
      sprintf(hh, "%02d", h);
      sprintf(mm, "%02d", m);

      if (editMode == 0) {
        drawInverseTextBox(30, 36, hh);
        u8g2.setFont(u8g2_font_5x8_tr);
        drawHintArrows(94, 25);
        u8g2.drawStr(80, 44, "MIN");
      } else {
        u8g2.drawStr(30, 42, hh);
        u8g2.drawStr(58, 42, ":");
        drawInverseTextBox(78, 36, mm);
        u8g2.setFont(u8g2_font_5x8_tr);
        drawHintArrows(18, 25);
        u8g2.drawStr(8, 44, "HOUR");
      }

      u8g2.setFont(u8g2_font_5x8_tr);
      u8g2.drawStr(6, 62, "BOTH confirm   LONG UP exit");
      break;
    }

    case TIME_EDIT: {
      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(4, 10, "ADJUST");

      u8g2.setFont(u8g2_font_profont22_tr);
      char hh[3], mm[3];
      sprintf(hh, "%02d", h);
      sprintf(mm, "%02d", m);

      if (editMode == 0) {
        drawInverseTextBox(30, 36, hh);
        u8g2.setFont(u8g2_font_5x8_tr);
        drawHintArrows(94, 25);
        u8g2.drawStr(80, 44, "MIN");
      } else {
        u8g2.drawStr(30, 42, hh);
        u8g2.drawStr(58, 42, ":");
        drawInverseTextBox(78, 36, mm);
        u8g2.setFont(u8g2_font_5x8_tr);
        drawHintArrows(18, 25);
        u8g2.drawStr(8, 44, "HOUR");
      }

      u8g2.setFont(u8g2_font_5x8_tr);
      u8g2.drawStr(6, 62, "UP +1  MED +10  BOTH next");
      break;
    }

    case METRO_SHOW: {
      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(4, 10, "METRONOME");

      u8g2.setFont(u8g2_font_profont22_tr);
      char buf[12];
      sprintf(buf, "%d BPM", bpm);
      u8g2.drawStr(30, 36, buf);

      u8g2.setFont(u8g2_font_5x8_tr);
      if (metroRunning) {
        u8g2.drawStr(36, 58, "RUNNING");
      } else {
        u8g2.drawStr(18, 58, "DOWN start   LONG UP exit");
      }
      break;
    }

    case METRO_EDIT: {
      u8g2.setFont(u8g2_font_6x10_tr);
      u8g2.drawStr(4, 10, "BPM SET");

      u8g2.setFont(u8g2_font_profont22_tr);
      char buf[8];
      sprintf(buf, "%d", bpm);
      int w = u8g2.getStrWidth(buf);
      int x = (128 - w) / 2;
      int y = 36;
      u8g2.drawBox(x - 6, y - 22, w + 12, 28);
      u8g2.setDrawColor(0);
      u8g2.drawStr(x, y, buf);
      u8g2.setDrawColor(1);

      u8g2.setFont(u8g2_font_5x8_tr);
      drawHintArrows(18, 25);
      drawHintArrows(108, 25);
      u8g2.drawStr(6, 62, "UP +1  MED +10  BOTH confirm");
      break;
    }
  }

  u8g2.sendBuffer();
}