Created
March 13, 2026 00:10
-
-
Save maliqq/738f8e7205adb631ef1289d1eafbdbbc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Kitchen Pomodoro Timer — Arduino Nano (3-Button Version) | |
| * | |
| * Hardware: | |
| * - 4-digit 7-segment LED display (TM1637) → CLK=D2, DIO=D3 | |
| * - Piezo buzzer → D8 | |
| * - BTN_M (Mode) → D4 (INPUT_PULLUP) | |
| * - BTN_PLUS (+) → D5 (INPUT_PULLUP) | |
| * - BTN_MINUS (−) → D6 (INPUT_PULLUP) | |
| * | |
| * 3-Button Logic: | |
| * M cycles: READY → PRESET → MANUAL → READY → ... | |
| * | |
| * READY state: [+] = Start [−] = (no action) | |
| * PRESET state: [+] = Next [−] = Prev (5/15/30/60 min) | |
| * MANUAL state: [+] = +5 min [−] = −5 min | |
| * RUNNING: [+] = Pause [−] = Stop → READY | |
| * PAUSED: [+] = Resume [−] = Stop → READY | |
| * | |
| * Install library: "TM1637Display" by Avishay Orpaz (Library Manager) | |
| */ | |
| #include <TM1637Display.h> | |
| // ── Pins ───────────────────────────────────────────────────── | |
| #define CLK 2 | |
| #define DIO 3 | |
| #define BTN_M 4 | |
| #define BTN_PLUS 5 | |
| #define BTN_MINUS 6 | |
| #define BUZZER 8 | |
| // ── Presets (seconds) ──────────────────────────────────────── | |
| const uint16_t PRESETS[] = { 300, 900, 1800, 3600 }; | |
| const uint8_t NUM_PRESETS = 4; | |
| // ── State Machine ──────────────────────────────────────────── | |
| enum State { READY, PRESET, MANUAL, RUNNING, PAUSED }; | |
| TM1637Display display(CLK, DIO); | |
| State state = READY; | |
| uint8_t presetIndex = 0; | |
| uint16_t totalSeconds = PRESETS[0]; | |
| uint16_t remaining = 0; | |
| unsigned long prevMillis = 0; | |
| // Debounce | |
| unsigned long lastPress[3] = {0, 0, 0}; | |
| const unsigned long DEBOUNCE = 200; | |
| // Blink control for edit modes | |
| unsigned long lastBlink = 0; | |
| bool blinkOn = true; | |
| // ── Helpers ────────────────────────────────────────────────── | |
| bool pressed(uint8_t pin, uint8_t idx) { | |
| if (digitalRead(pin) == LOW) { | |
| unsigned long now = millis(); | |
| if (now - lastPress[idx] > DEBOUNCE) { | |
| lastPress[idx] = now; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void showTime(uint16_t secs, bool colon) { | |
| uint8_t mm = secs / 60; | |
| uint8_t ss = secs % 60; | |
| uint8_t data[] = { | |
| display.encodeDigit(mm / 10), | |
| display.encodeDigit(mm % 10), | |
| display.encodeDigit(ss / 10), | |
| display.encodeDigit(ss % 10) | |
| }; | |
| if (colon) data[1] |= 0x80; | |
| display.setSegments(data); | |
| } | |
| void showBlank() { | |
| uint8_t blank[] = { 0, 0, 0, 0 }; | |
| display.setSegments(blank); | |
| } | |
| void beepDone() { | |
| for (uint8_t i = 0; i < 3; i++) { | |
| tone(BUZZER, 2000, 400); | |
| delay(600); | |
| } | |
| noTone(BUZZER); | |
| } | |
| void beepClick() { | |
| tone(BUZZER, 4000, 30); | |
| } | |
| void beepMode() { | |
| tone(BUZZER, 3000, 60); | |
| } | |
| // ── Setup ──────────────────────────────────────────────────── | |
| void setup() { | |
| pinMode(BTN_M, INPUT_PULLUP); | |
| pinMode(BTN_PLUS, INPUT_PULLUP); | |
| pinMode(BTN_MINUS, INPUT_PULLUP); | |
| pinMode(BUZZER, OUTPUT); | |
| display.setBrightness(5); | |
| showTime(totalSeconds, true); | |
| } | |
| // ── Main Loop ──────────────────────────────────────────────── | |
| void loop() { | |
| bool pM = pressed(BTN_M, 0); | |
| bool pPlus = pressed(BTN_PLUS, 1); | |
| bool pMinus = pressed(BTN_MINUS, 2); | |
| switch (state) { | |
| // ·············· READY — display set time, + starts ········ | |
| case READY: | |
| showTime(totalSeconds, true); | |
| if (pM) { | |
| beepMode(); | |
| state = PRESET; | |
| lastBlink = millis(); | |
| blinkOn = true; | |
| } | |
| if (pPlus) { // START | |
| beepClick(); | |
| remaining = totalSeconds; | |
| prevMillis = millis(); | |
| state = RUNNING; | |
| } | |
| break; | |
| // ·············· PRESET — cycle 5/15/30/60 with +/− ······· | |
| case PRESET: | |
| // Blink display to indicate edit mode | |
| if (millis() - lastBlink > 300) { | |
| lastBlink = millis(); | |
| blinkOn = !blinkOn; | |
| } | |
| if (blinkOn) showTime(PRESETS[presetIndex], true); | |
| else showBlank(); | |
| if (pM) { // → MANUAL mode | |
| beepMode(); | |
| totalSeconds = PRESETS[presetIndex]; | |
| state = MANUAL; | |
| lastBlink = millis(); | |
| blinkOn = true; | |
| } | |
| if (pPlus) { // next preset | |
| beepClick(); | |
| presetIndex = (presetIndex + 1) % NUM_PRESETS; | |
| totalSeconds = PRESETS[presetIndex]; | |
| blinkOn = true; lastBlink = millis(); | |
| } | |
| if (pMinus) { // prev preset | |
| beepClick(); | |
| presetIndex = (presetIndex + NUM_PRESETS - 1) % NUM_PRESETS; | |
| totalSeconds = PRESETS[presetIndex]; | |
| blinkOn = true; lastBlink = millis(); | |
| } | |
| break; | |
| // ·············· MANUAL — fine-tune ±5 min ················· | |
| case MANUAL: | |
| // Slower blink to distinguish from PRESET mode | |
| if (millis() - lastBlink > 500) { | |
| lastBlink = millis(); | |
| blinkOn = !blinkOn; | |
| } | |
| if (blinkOn) showTime(totalSeconds, false); // no colon = visual cue | |
| else showBlank(); | |
| if (pM) { // → back to READY | |
| beepMode(); | |
| state = READY; | |
| } | |
| if (pPlus) { // +5 min | |
| beepClick(); | |
| if (totalSeconds <= 5695) totalSeconds += 300; | |
| blinkOn = true; lastBlink = millis(); | |
| } | |
| if (pMinus) { // −5 min | |
| beepClick(); | |
| if (totalSeconds > 300) totalSeconds -= 300; | |
| blinkOn = true; lastBlink = millis(); | |
| } | |
| break; | |
| // ·············· RUNNING ··································· | |
| case RUNNING: | |
| if (millis() - prevMillis >= 1000) { | |
| prevMillis += 1000; | |
| if (remaining > 0) { | |
| remaining--; | |
| showTime(remaining, true); | |
| } | |
| if (remaining == 0) { | |
| showTime(0, true); | |
| beepDone(); | |
| state = READY; | |
| showTime(totalSeconds, true); | |
| } | |
| } | |
| if (pPlus) { // PAUSE | |
| beepClick(); | |
| state = PAUSED; | |
| } | |
| if (pMinus) { // STOP → READY | |
| beepClick(); | |
| state = READY; | |
| showTime(totalSeconds, true); | |
| } | |
| break; | |
| // ·············· PAUSED — blinking colon ··················· | |
| case PAUSED: { | |
| bool colonOn = (millis() / 500) % 2; | |
| showTime(remaining, colonOn); | |
| if (pPlus) { // RESUME | |
| beepClick(); | |
| prevMillis = millis(); | |
| state = RUNNING; | |
| } | |
| if (pMinus) { // STOP → READY | |
| beepClick(); | |
| state = READY; | |
| showTime(totalSeconds, true); | |
| } | |
| break; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment