Загрузка данных


/*
 * Скетч: Часы + Метроном на базе 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);
}