Загрузка данных
/*
* Скетч: Часы + Метроном на базе State Machine для ESP32 Super Mini
* Версия для Arduino Core 3.3.x
*
* Добавлен пассивный баззер с регулировкой громкости
*
* Библиотеки для установки:
* - U8g2 (by oliver)
* - ESP32Time (by fbiego)
*
* Аппаратная конфигурация:
* - OLED 0.96" I2C: SCL=4, SDA=3
* - Кнопки: UP=1, DOWN=0 (INPUT_PULLUP, активный LOW)
* - Баззер: PIN 10 (пассивный)
*/
#include <Wire.h>
#include <U8g2lib.h>
#include <ESP32Time.h>
// ================== НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ ==================
#define TILTED_CLOCK true
// ------------------ Пины ------------------
#define PIN_BTN_UP 1
#define PIN_BTN_DOWN 0
#define PIN_LED 2
#define PIN_BUZZER 10
// ------------------ Настройка баззера ------------------
#define BUZZER_FREQ 800 // Частота писка в Гц (ниже = тише/глуше, выше = звонче)
#define BUZZER_DUTY 64 // Скважность ШИМ (0-128). 64 = ~50% громкости. 128 = макс. 32 = тихо.
// ------------------ Тайминги ------------------
#define SHORT_PRESS_MAX 1000
#define LONG_PRESS_MIN 2000
#define BOTH_PRESS_WINDOW 200
#define SLEEP_TIMEOUT 5000
#define SLEEP_ANIM_FRAMES 5
#define SLEEP_ANIM_DELAY 15
#define HAZARD_FADE_SPEED 35
// ------------------ Метроном ------------------
#define DEFAULT_BPM 100
#define MIN_BPM 30
#define MAX_BPM 250
// ------------------ Константы позиционирования ------------------
#define TEXT_Y_CENTER 48
#define HELP_TEXT_Y 62
// ------------------ Параметры инверсного квадрата ------------------
#define BOX_PAD_X 3
#define BOX_PAD_Y 2
#define BOX_OFFSET_Y 3
// ------------------ Дисплей ------------------
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* clock=*/ 4, /* data=*/ 3);
ESP32Time rtc;
// ================== КОНЕЧНЫЙ АВТОМАТ ==================
enum DeviceState : uint8_t {
SHOW_TIME,
SHOW_TIME_SLEEP,
TIME_PICK,
TIME_EDIT,
METRO_SHOW,
METRO_EDIT
} currentState = SHOW_TIME;
// ================== ПЕРЕМЕННЫЕ КНОПОК ==================
struct ButtonState {
bool pressed = false;
unsigned long pressStartTime = 0;
bool longPressTriggered = false;
bool shortPressReady = false;
};
ButtonState btnUp, btnDown;
unsigned long lastActivityTime = 0;
// ================== ПЕРЕМЕННЫЕ ВРЕМЕНИ ==================
int currentHour = 12, currentMinute = 0;
bool editMode = 0;
// ================== ПЕРЕМЕННЫЕ МЕТРОНОМА ==================
volatile bool metroRunning = false;
volatile bool metroBeatFlag = false;
volatile int hazardGlow = 0;
unsigned long lastHazardUpdate = 0;
int metroBPM = DEFAULT_BPM;
int editBPM = DEFAULT_BPM;
hw_timer_t *metroTimer = NULL;
bool screenWokeUpFromMetro = false;
// Флаги
bool showUpArrow = false;
bool showDownArrow = false;
volatile bool buzzerActive = false; // Флаг для обработки баззера в loop
// ================== ПРЕРЫВАНИЕ МЕТРОНОМА ==================
void IRAM_ATTR onMetronomeBeat() {
if (metroRunning) {
metroBeatFlag = true;
hazardGlow = 4;
buzzerActive = true; // Взводим флаг баззера
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
}
}
void updateMetronomeTimer() {
if (metroTimer) {
unsigned long period = (60 * 1000000UL) / metroBPM;
if (period < 1000) period = 1000;
timerAlarm(metroTimer, period, true, 0);
}
}
// ================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==================
void beepBuzzer() {
// Короткий тоновый сигнал для пассивного баззера
// Частота BUZZER_FREQ Гц, период = 1/freq секунд
int halfPeriod = 500000 / BUZZER_FREQ; // Половина периода в микросекундах
for (int i = 0; i < 20; i++) { // 20 циклов = короткий писк
digitalWrite(PIN_BUZZER, HIGH);
delayMicroseconds(halfPeriod);
digitalWrite(PIN_BUZZER, LOW);
delayMicroseconds(halfPeriod);
}
}
// ... (остальные функции без изменений: drawHelperText, drawUpArrow, drawDownArrow, drawCenteredSelection, drawHazardLamps, drawMainClock, playSleepAnimation, resetActivityTimer, enterSleepMode, saveTimeToRTC)
// ... (обработчики кнопок: onLongPress, onShortPress, onHoldPress, onBothHold)
// ... (handleButtons)
// ... (drawUI)
// ================== SETUP ==================
void setup() {
Serial.begin(115200);
Wire.begin(3, 4);
u8g2.begin();
u8g2.setFontMode(1);
u8g2.setContrast(255);
pinMode(PIN_BTN_UP, INPUT_PULLUP);
pinMode(PIN_BTN_DOWN, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
digitalWrite(PIN_LED, HIGH);
digitalWrite(PIN_BUZZER, LOW);
rtc.setTime(0, 0, 12, 1, 1, 2024);
metroTimer = timerBegin(1000000);
timerAttachInterrupt(metroTimer, &onMetronomeBeat);
timerAlarm(metroTimer, (60 * 1000000UL) / DEFAULT_BPM, true, 0);
lastActivityTime = millis();
lastHazardUpdate = millis();
}
// ================== LOOP ==================
void loop() {
handleButtons();
// Обработка баззера (вне прерывания!)
if (buzzerActive) {
buzzerActive = false;
beepBuzzer();
}
if (metroRunning) {
unsigned long now = millis();
if (now - lastHazardUpdate >= HAZARD_FADE_SPEED) {
lastHazardUpdate = now;
if (hazardGlow > 0) {
hazardGlow--;
}
}
}
unsigned long now = millis();
if ((currentState == SHOW_TIME) &&
(now - lastActivityTime > SLEEP_TIMEOUT)) {
enterSleepMode();
}
if (metroBeatFlag) {
metroBeatFlag = false;
}
if (currentState != SHOW_TIME_SLEEP) {
drawUI();
}
delay(10);
}