Загрузка данных
#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 // Долгое нажатие (2.5 сек)
#define MEDIUM_PRESS_MS 800 // Среднее нажатие (0.8 сек)
#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;
bool ledState = false;
// ═══════════════════════════════════════
// СОСТОЯНИЯ
// ═══════════════════════════════════════
enum State {
SLEEP, // Спящий режим (черный экран)
SHOW_TIME, // Показ времени
SET_TIME_SELECT, // Выбор: часы или минуты
SET_TIME_VALUE, // Настройка значения
METRO_SHOW, // Показ BPM
METRO_ADJUST // Настройка BPM
};
State currentState = SLEEP;
byte timeEditMode = 0; // 0=часы, 1=минуты
// ═══════════════════════════════════════
// КНОПКИ// ═══════════════════════════════════════
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_VALUE:
handleSetTimeValue();
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) {
timeEditMode = 0; // Выбрать часы
} else if (currentState == SET_TIME_VALUE) {
if (timeEditMode == 0) {
h = (h + 1) % 24;
} else {
m = (m + 1) % 60;
}
} else if (currentState == METRO_ADJUST) {
bpm = min(220, bpm + 1);
}
}
void onBtnUpMedium() {
if (currentState == SET_TIME_VALUE) {
if (timeEditMode == 0) {
h = (h + 10) % 24;
} else {
m = (m + 10) % 60;
}
} else if (currentState == METRO_ADJUST) {
bpm = min(220, bpm + 10);
}
}
void onBtnUpLong() {
if (currentState == SHOW_TIME) {
currentState = SET_TIME_SELECT;
timeEditMode = 0;
} 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) {
timeEditMode = 1; // Выбрать минуты
} else if (currentState == SET_TIME_VALUE) {
if (timeEditMode == 0) {
h = (h + 23) % 24; // -1
} else {
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_VALUE) {
if (timeEditMode == 0) {
h = (h + 14) % 24; // -10
} else {
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_SELECT) {
currentState = SET_TIME_VALUE;
} else if (currentState == SET_TIME_VALUE) {
timeEditMode = 1 - timeEditMode; // Переключить часы/минуты
if (timeEditMode == 1) {
currentState = SET_TIME_SELECT; // После настройки часов → выбор минут } else {
currentState = SHOW_TIME; // После настройки минут → выход
}
} else if (currentState == METRO_SHOW) {
currentState = METRO_ADJUST;
} else if (currentState == METRO_ADJUST) {
currentState = METRO_SHOW;
}
}
// ═══════════════════════════════════════
// РЕЖИМЫ
// ═══════════════════════════════════════
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);
sprintf(buf, "%02d:%02d:%02d %s",
(h == 0 || h == 12) ? 12 : (h > 12 ? h - 12 : h),
m, s, h < 12 ? "am" : "pm");
u8g2.drawStr(15, 60, buf);
u8g2.sendBuffer();
}
void handleSetTimeSelect() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
// Часы
if (timeEditMode == 0) {
// Инверсия: белый фон, черный текст
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); // Вернуть белый
} else {
char buf[3];
sprintf(buf, "%02d", h);
u8g2.drawStr(30, 38, buf);
}
u8g2.drawStr(58, 38, ":");
// Минуты
if (timeEditMode == 1) {
char buf[3];
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);
} else {
char buf[3];
sprintf(buf, "%02d", m);
u8g2.drawStr(70, 38, buf);
}
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(5, 12, timeEditMode == 0 ? "> HOURS <" : "> MINS <");
u8g2.sendBuffer();
}
void handleSetTimeValue() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
char buf[16];
sprintf(buf, "%02d:%02d", h, m);
// Инверсия выбранного
if (timeEditMode == 0) {
// Часы
u8g2.drawBox(28, 18, 30, 24);
u8g2.setDrawColor(0);
u8g2.drawStr(30, 38, buf);
u8g2.setDrawColor(1);
} else { // Минуты
u8g2.drawBox(62, 18, 30, 24);
u8g2.setDrawColor(0);
u8g2.drawStr(64, 38, buf);
u8g2.setDrawColor(1);
}
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(5, 12, "ADJUST");
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(25, 60, "Press DOWN to start");
}
u8g2.sendBuffer();
}
void handleMetroAdjust() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_profont22_tr);
char buf[8];
sprintf(buf, "%d", bpm);
int w = u8g2.getStrWidth(buf);
// Инверсия u8g2.drawBox((128 - w) / 2 - 2, 25, w + 4, 28);
u8g2.setDrawColor(0);
u8g2.drawStr((128 - w) / 2, 48, buf);
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(35, 62, "BPM");
u8g2.sendBuffer();
}
void updateLED() {
// LED мигает только в режиме метронома
if (currentState != METRO_SHOW || !metroRunning) {
digitalWrite(LED_PIN, LOW);
}
}