Загрузка данных
#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
// ═══════════════════════════════════════
// НАСТРОЙКИ
// ═══════════════════════════════════════
#define LONG_PRESS_MS 2500
#define MEDIUM_PRESS_MS 800
#define SIMULTANEOUS_MS 300
// ═══════════════════════════════════════
// ВРЕМЯ
// ═══════════════════════════════════════
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;
unsigned long btnUpTime = 0;
unsigned long btnDownTime = 0;
bool btnUpLong = false;
bool btnDownLong = false;
bool btnBothPressed = 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.setFont(u8g2_font_profont22_tr);
u8g2.setFontMode(1);
lastTick = millis();
}
// ═══════════════════════════════════════
// LOOP
// ═══════════════════════════════════════
void loop() {
readButtons();
switch (currentState) {
case SLEEP:
handleSleep();
break;
case SHOW_TIME:
handleShowTime();
break;
case SET_TIME_SELECT:
handleSetTimeSelect();
break;
case SET_TIME_HOURS:
handleSetTimeHours();
break;
case SET_TIME_MINS:
handleSetTimeMins();
break;
case METRO_SHOW:
handleMetroShow();
break;
case METRO_ADJUST: handleMetroAdjust();
break;
}
updateLED();
}
// ═══════════════════════════════════════
// ЧТЕНИЕ КНОПОК
// ═══════════════════════════════════════
void readButtons() {
bool upNow = digitalRead(BTN_UP) == LOW;
bool downNow = digitalRead(BTN_DOWN) == LOW;
unsigned long now = millis();
// ВЕРХНЯЯ КНОПКА
if (upNow && !btnUpPressed) {
btnUpPressed = true;
btnUpTime = now;
btnUpLong = false;
} else if (!upNow && btnUpPressed) {
btnUpPressed = false;
unsigned long duration = now - btnUpTime;
if (duration >= MEDIUM_PRESS_MS) {
onBtnUpMedium();
} else {
onBtnUpShort();
}
} else if (upNow && btnUpPressed && (now - btnUpTime >= LONG_PRESS_MS) && !btnUpLong) {
btnUpLong = true;
onBtnUpLong();
}
// НИЖНЯЯ КНОПКА
if (downNow && !btnDownPressed) {
btnDownPressed = true;
btnDownTime = now;
btnDownLong = false;
} else if (!downNow && btnDownPressed) {
btnDownPressed = false;
unsigned long duration = now - btnDownTime;
if (duration >= MEDIUM_PRESS_MS) {
onBtnDownMedium();
} else {
onBtnDownShort();
}
} else if (downNow && btnDownPressed && (now - btnDownTime >= LONG_PRESS_MS) && !btnDownLong) {
btnDownLong = true;
onBtnDownLong();
}
// ОДНОВРЕМЕННОЕ НАЖАТИЕ
if (btnUpPressed && btnDownPressed && !btnBothPressed) {
if (abs((long)(btnUpTime - btnDownTime)) <= SIMULTANEOUS_MS) {
btnBothPressed = true;
onBtnBoth();
}
}
if (!btnUpPressed && !btnDownPressed) {
btnBothPressed = false;
}
}
// ═══════════════════════════════════════
// ОБРАБОТЧИКИ КНОПОК
// ═══════════════════════════════════════
void onBtnUpShort() {
if (currentState == SET_TIME_SELECT) {
currentState = SET_TIME_HOURS; // Выбрать часы
} else if (currentState == SET_TIME_HOURS) {
h = (h + 1) % 24; // +1 час
} else if (currentState == SET_TIME_MINS) {
m = (m + 1) % 60; // +1 минута
} else if (currentState == METRO_ADJUST) {
bpm = min(220, bpm + 1);
}
}
void onBtnUpMedium() {
if (currentState == SET_TIME_HOURS) {
h = (h + 10) % 24; // +10 часов
} else if (currentState == SET_TIME_MINS) {
m = (m + 10) % 60; // +10 минут
} else if (currentState == METRO_ADJUST) {
bpm = min(220, bpm + 10);
}
}
void onBtnUpLong() {
if (currentState == SHOW_TIME) {
currentState = SET_TIME_SELECT; // Вход в настройку времени
} else if (currentState == METRO_SHOW || currentState == METRO_ADJUST) {
currentState = SHOW_TIME; // Выход из метронома
metroRunning = false;
}
}
void onBtnDownShort() {
if (currentState == SLEEP) { currentState = SHOW_TIME; // Выход из сна
} else if (currentState == SET_TIME_SELECT) {
currentState = SET_TIME_MINS; // Выбрать минуты
} else if (currentState == SET_TIME_HOURS) {
h = (h + 23) % 24; // -1 час
} else if (currentState == SET_TIME_MINS) {
m = (m + 59) % 60; // -1 минута
} else if (currentState == METRO_SHOW && !metroRunning) {
metroRunning = true;
lastBeat = millis();
} else if (currentState == METRO_ADJUST) {
bpm = max(40, bpm - 1);
}
}
void onBtnDownMedium() {
if (currentState == SET_TIME_HOURS) {
h = (h + 14) % 24; // -10 часов
} else if (currentState == SET_TIME_MINS) {
m = (m + 50) % 60; // -10 минут
} else if (currentState == METRO_ADJUST) {
bpm = max(40, bpm - 10);
}
}
void onBtnDownLong() {
if (currentState == SHOW_TIME) {
currentState = METRO_SHOW; // Вход в метроном
} else if (currentState == METRO_SHOW || currentState == METRO_ADJUST) {
currentState = SHOW_TIME; // Выход из метронома
metroRunning = false;
}
}
void onBtnBoth() {
if (currentState == SET_TIME_HOURS) {
currentState = SET_TIME_SELECT; // Подтвердить часы → вернуться к выбору
} else if (currentState == SET_TIME_MINS) {
currentState = SHOW_TIME; // Подтвердить минуты → выход
} else if (currentState == METRO_SHOW) {
currentState = METRO_ADJUST; // Войти в настройку BPM
} else if (currentState == METRO_ADJUST) {
currentState = METRO_SHOW; // Подтвердить BPM → выход
}
}
// ═══════════════════════════════════════
// ОТРИСОВКА СТРЕЛОЧЕК
// ═══════════════════════════════════════
void drawArrows(int x, int y) { u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(x, y - 5, "↑");
u8g2.drawStr(x, y + 12, "↓");
}
// ═══════════════════════════════════════
// РЕЖИМЫ
// ═══════════════════════════════════════
void handleSleep() {
u8g2.clearBuffer();
u8g2.sendBuffer();
digitalWrite(LED_PIN, LOW);
}
void handleShowTime() {
if (millis() - lastTick >= 1000) {
lastTick += 1000;
s++;
if (s >= 60) { s = 0; m++; if (m >= 60) { m = 0; h++; if (h >= 24) h = 0; }}
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
char buf[16];
sprintf(buf, "%02d:%02d", h, m);
u8g2.drawStr(32, 40, 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");
u8g2.drawStr(15, 60, buf);
u8g2.sendBuffer();
}
void handleSetTimeSelect() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
// Часы
u8g2.setDrawColor(1);
char buf[3];
sprintf(buf, "%02d", h);
int wH = u8g2.getStrWidth(buf);
if (currentState == SET_TIME_SELECT) {
// Если режим выбора - подсветить активный элемент
u8g2.drawBox(28, 18, wH + 4, 24);
u8g2.setDrawColor(0); }
u8g2.drawStr(30, 38, buf);
u8g2.setDrawColor(1);
u8g2.drawStr(58, 38, ":");
// Минуты
sprintf(buf, "%02d", m);
int wM = u8g2.getStrWidth(buf);
if (currentState == SET_TIME_SELECT) {
u8g2.drawBox(68, 18, wM + 4, 24);
u8g2.setDrawColor(0);
}
u8g2.drawStr(70, 38, buf);
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(5, 12, "SELECT: UP/DOWN");
u8g2.drawStr(5, 62, "BOTH=ADJUST");
u8g2.sendBuffer();
}
void handleSetTimeHours() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
// ЧАСЫ (инверсия)
char buf[3];
sprintf(buf, "%02d", h);
int w = u8g2.getStrWidth(buf);
u8g2.drawBox(28, 18, w + 4, 24); // Белый фон
u8g2.setDrawColor(0); // Черный текст
u8g2.drawStr(30, 38, buf);
u8g2.setDrawColor(1); // Вернуть белый
u8g2.drawStr(58, 38, ":");
// МИНУТЫ (обычные) + стрелочки
sprintf(buf, "%02d", m);
u8g2.drawStr(70, 38, buf);
drawArrows(70, 38); // Стрелочки на месте минут
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(5, 12, "ADJUST HOURS");
u8g2.drawStr(5, 62, "BOTH=CONFIRM");
u8g2.sendBuffer();
}
void handleSetTimeMins() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
// ЧАСЫ (обычные) + стрелочки
char buf[3];
sprintf(buf, "%02d", h);
u8g2.drawStr(30, 38, buf);
drawArrows(30, 38); // Стрелочки на месте часов
u8g2.drawStr(58, 38, ":");
// МИНУТЫ (инверсия)
sprintf(buf, "%02d", m);
int w = u8g2.getStrWidth(buf);
u8g2.drawBox(68, 18, w + 4, 24);
u8g2.setDrawColor(0);
u8g2.drawStr(70, 38, buf);
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(5, 12, "ADJUST MINS");
u8g2.drawStr(5, 62, "BOTH=CONFIRM");
u8g2.sendBuffer();
}
void handleMetroShow() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
char buf[16];
sprintf(buf, "%d BPM", bpm);
u8g2.drawStr(15, 40, buf);
if (metroRunning) {
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(35, 60, "RUNNING");
unsigned long interval = 60000UL / bpm;
if (millis() - lastBeat >= interval) {
lastBeat += interval;
digitalWrite(LED_PIN, HIGH);
delay(50);
digitalWrite(LED_PIN, LOW);
}
} else {
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(20, 60, "DOWN=START"); }
u8g2.sendBuffer();
}
void handleMetroAdjust() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
// ИНВЕРСИЯ числа BPM
char buf[8];
sprintf(buf, "%d", bpm);
int w = u8g2.getStrWidth(buf);
int x = (128 - w) / 2;
u8g2.drawBox(x - 2, 25, w + 4, 28); // Белый фон
u8g2.setDrawColor(0); // Черный текст
u8g2.drawStr(x, 48, buf);
u8g2.setDrawColor(1); // Вернуть белый
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(35, 62, "BPM");
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(5, 12, "UP/DOWN=CHANGE");
u8g2.drawStr(5, 62, "BOTH=CONFIRM");
u8g2.sendBuffer();
}
void updateLED() {
if (currentState != METRO_SHOW || !metroRunning) {
digitalWrite(LED_PIN, LOW);
}
}