Загрузка данных
#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();
}