Загрузка данных
#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 DOUBLE_GAP_MS = 320;
const unsigned long DOUBLE_HOLD_MS = 180;
const unsigned long STARTUP_TIMEOUT = 5000;
// -------------------- ВРЕМЯ --------------------
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; // 0 = часы, 1 = минуты
bool editBlink = true;
unsigned long lastBlink = 0;
// -------------------- КНОПКИ --------------------
struct Button {
byte pin;
bool raw;
bool stable;
bool lastStable;
unsigned long lastChange;
unsigned long pressTime;
bool longFired;
bool shortPending;
};
Button upBtn = {BTN_UP, HIGH, HIGH, HIGH, 0, 0, false, false};
Button downBtn = {BTN_DOWN, HIGH, HIGH, HIGH, 0, 0, false, false};
bool comboArmed = false;
unsigned long comboStart = 0;
bool comboDone = false;
unsigned long startupAt = 0;
bool isPressedRaw(byte pin) {
return digitalRead(pin) == LOW;
}
void startSleep() {
state = SLEEP;
metroRunning = false;
digitalWrite(LED_PIN, LOW);
}
void gotoShowTime() {
state = SHOW_TIME;
lastSecondTick = millis();
}
bool bothPressedNow() {
return (upBtn.stable == LOW && downBtn.stable == LOW);
}
void setStable(Button &b) {
bool r = digitalRead(b.pin);
unsigned long now = millis();
if (r != b.raw) {
b.raw = r;
b.lastChange = now;
}
if ((now - b.lastChange) >= DEBOUNCE_MS && b.stable != b.raw) {
b.lastStable = b.stable;
b.stable = b.raw;
}
}
void handleShortPressUp();
void handleShortPressDown();
void handleMediumPressUp();
void handleMediumPressDown();
void handleLongPressUp();
void handleLongPressDown();
void handleComboPress();
void drawCenteredBoxText(int cx, int cy, const char *txt, int padX = 4, int padY = 4);
void drawArrowHints(int x, int y);
void updateClockTick();
void updateMetroTick();
void renderSleep();
void renderTime();
void renderTimePick();
void renderTimeEdit();
void renderMetroShow();
void renderMetroEdit();
void renderAll();
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);
u8g2.setFont(u8g2_font_profont22_tr);
startupAt = millis();
lastSecondTick = millis();
}
void loop() {
unsigned long now = millis();
setStable(upBtn);
setStable(downBtn);
if (state == SLEEP) {
if (upBtn.stable == LOW || downBtn.stable == LOW) {
gotoShowTime();
}
}
bool comboNow = bothPressedNow();
if (comboNow && !comboDone) {
if (!comboArmed) {
comboArmed = true;
comboStart = now;
} else if ((now - comboStart) >= DOUBLE_GAP_MS) {
comboDone = true;
handleComboPress();
}
}
if (!comboNow) {
comboArmed = false;
comboDone = false;
}
if (upBtn.stable == LOW && !upBtn.longFired && (now - upBtn.lastChange) >= LONG_PRESS_MS) {
upBtn.longFired = true;
handleLongPressUp();
}
if (downBtn.stable == LOW && !downBtn.longFired && (now - downBtn.lastChange) >= LONG_PRESS_MS) {
downBtn.longFired = true;
handleLongPressDown();
}
if (upBtn.stable == HIGH && upBtn.lastStable == LOW) {
unsigned long held = now - upBtn.pressTime;
if (!upBtn.longFired && !comboDone) {
if (held >= MEDIUM_PRESS_MS) handleMediumPressUp();
else handleShortPressUp();
}
upBtn.longFired = false;
}
if (downBtn.stable == HIGH && downBtn.lastStable == LOW) {
unsigned long held = now - downBtn.pressTime;
if (!downBtn.longFired && !comboDone) {
if (held >= MEDIUM_PRESS_MS) handleMediumPressDown();
else handleShortPressDown();
}
downBtn.longFired = false;
}
if (upBtn.stable == LOW && upBtn.lastStable == HIGH) {
upBtn.pressTime = now;
upBtn.longFired = false;
}
if (downBtn.stable == LOW && downBtn.lastStable == HIGH) {
downBtn.pressTime = now;
downBtn.longFired = false;
}
if (state == SHOW_TIME) updateClockTick();
if (state == METRO_SHOW && metroRunning) updateMetroTick();
if (now - lastBlink >= 350) {
lastBlink = now;
editBlink = !editBlink;
}
renderAll();
upBtn.lastStable = upBtn.stable;
downBtn.lastStable = downBtn.stable;
}
void handleShortPressUp() {
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_SHOW || state == METRO_EDIT) {
if (state == METRO_EDIT) bpm = min(220, bpm + 1);
} else if (state == SHOW_TIME) {
gotoShowTime();
}
}
void handleShortPressDown() {
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_EDIT) {
bpm = max(40, bpm - 1);
} else if (state == METRO_SHOW && !metroRunning) {
metroRunning = true;
lastBeat = millis();
}
}
void handleMediumPressUp() {
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 handleMediumPressDown() {
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 handleLongPressUp() {
if (state == SHOW_TIME) {
state = TIME_PICK;
editMode = 0;
} else if (state == METRO_SHOW || state == METRO_EDIT) {
state = SHOW_TIME;
metroRunning = false;
}
}
void handleLongPressDown() {
if (state == SHOW_TIME) {
state = METRO_SHOW;
metroRunning = false;
} else if (state == METRO_SHOW || state == METRO_EDIT) {
state = SHOW_TIME;
metroRunning = false;
}
}
void handleComboPress() {
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 updateClockTick() {
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 updateMetroTick() {
unsigned long now = millis();
unsigned long interval = 60000UL / (unsigned long)bpm;
if (ledPulse && now >= ledOffAt) {
digitalWrite(LED_PIN, LOW);
ledPulse = false;
}
if (now - lastBeat >= interval) {
lastBeat += interval;
digitalWrite(LED_PIN, HIGH);
ledPulse = true;
ledOffAt = now + 35;
}
}
void drawCenteredBoxText(int cx, int cy, const char *txt, int padX, int padY) {
int w = u8g2.getStrWidth(txt);
int hgt = u8g2.getAscent() - u8g2.getDescent();
int x = cx - w / 2 - padX;
int y = cy - hgt / 2 - padY;
u8g2.drawBox(x, y, w + padX * 2, hgt + padY * 2);
u8g2.setDrawColor(0);
u8g2.drawStr(cx - w / 2, cy + (u8g2.getAscent() / 2) - 1, txt);
u8g2.setDrawColor(1);
}
void drawArrowHints(int x, int y) {
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(x, y, "^");
u8g2.drawStr(x, y + 12, "v");
}
void renderSleep() {
u8g2.clearBuffer();
u8g2.sendBuffer();
digitalWrite(LED_PIN, LOW);
}
void renderTime() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
char buf[6];
sprintf(buf, "%02d:%02d", h, m);
u8g2.drawStr(26, 34, buf);
u8g2.setFont(u8g2_font_6x10_tr);
char buf2[16];
sprintf(buf2, "%02d:%02d:%02d", h, m, s);
u8g2.drawStr(34, 60, buf2);
u8g2.sendBuffer();
}
void renderTimePick() {
u8g2.clearBuffer();
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) {
drawCenteredBoxText(38, 36, hh);
u8g2.setFont(u8g2_font_5x8_tr);
drawArrowHints(88, 26);
u8g2.drawStr(76, 46, "MIN");
} else {
u8g2.drawStr(30, 42, hh);
u8g2.drawStr(58, 42, ":");
drawCenteredBoxText(90, 36, mm);
u8g2.setFont(u8g2_font_5x8_tr);
drawArrowHints(18, 26);
u8g2.drawStr(8, 46, "HOUR");
}
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(8, 62, "UP/DOWN select BOTH confirm");
u8g2.sendBuffer();
}
void renderTimeEdit() {
u8g2.clearBuffer();
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) {
drawCenteredBoxText(38, 36, hh);
u8g2.setFont(u8g2_font_5x8_tr);
drawArrowHints(88, 26);
u8g2.drawStr(76, 46, "MIN");
} else {
u8g2.drawStr(30, 42, hh);
u8g2.drawStr(58, 42, ":");
drawCenteredBoxText(90, 36, mm);
u8g2.setFont(u8g2_font_5x8_tr);
drawArrowHints(18, 26);
u8g2.drawStr(8, 46, "HOUR");
}
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(8, 62, "UP +1 / MED +10 BOTH next/exit");
u8g2.sendBuffer();
}
void renderMetroShow() {
u8g2.clearBuffer();
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);
if (state == METRO_SHOW && !metroRunning) {
u8g2.drawStr(34, 36, buf);
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(18, 58, "DOWN starts UP exits");
} else {
u8g2.drawStr(34, 36, buf);
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(36, 58, "RUNNING");
}
u8g2.sendBuffer();
}
void renderMetroEdit() {
u8g2.clearBuffer();
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;
int boxX = x - 6;
int boxY = y - 22;
int boxW = w + 12;
int boxH = 28;
u8g2.drawBox(boxX, boxY, boxW, boxH);
u8g2.setDrawColor(0);
u8g2.drawStr(x, y, buf);
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_5x8_tr);
drawArrowHints(18, 24);
drawArrowHints(108, 24);
u8g2.drawStr(22, 58, "UP +1 MED +10 BOTH confirm");
u8g2.sendBuffer();
}
void renderAll() {
switch (state) {
case SLEEP: renderSleep(); break;
case SHOW_TIME: renderTime(); break;
case TIME_PICK: renderTimePick(); break;
case TIME_EDIT: renderTimeEdit(); break;
case METRO_SHOW: renderMetroShow(); break;
case METRO_EDIT: renderMetroEdit(); break;
}
}