Close
0%
0%

Ender_Clock_Duino

Arduino Clock Rabbit Hole

Similar projects worth following
Hello World! This project is an Arduino-based clock that repurposes an Ender-3 Pro printer display and rotary encoder into a permanent, appliance-style timepiece. The ST7920 LCD is driven over hardware SPI using U8g2, and the encoder is handled through interrupts for responsive and accurate input. Timekeeping is provided by a DS3231 RTC module, ensuring long-term accuracy and retention across resets and power loss, while EEPROM is used to store user settings so configuration persists without reentry. The interface uses a 12-hour time format with a clear AM and PM indicator and provides audible feedback through a piezo buzzer using tone variation rather than simulated volume control. The system is designed to be stable, predictable, and capable of running unattended for extended periods, treating reclaimed hardware as functional hardware rather than obsolete part's. This is an evolving project.

This project uses an Arduino Uno R3 board with an original Ender 3 Pro control panel LCD. The Arduino is driving the ST7920 LCD over hardware SPI and handling the integrated rotary encoder via interrupts for reliable input. Timekeeping is provided by a DS3231 RTC module to maintain accuracy across power loss, while user settings are stored in EEPROM so configuration persists without reentry. Audible feedback is implemented with a piezo buzzer using distinct tone patterns for interaction cues. 

Firmware developed collaboratively using AI-assisted code generation, with system architecture, behavior, integration, testing, and refinement by me :)

  • 1 × Elegoo Uno R3 Arduino R3 Clone
  • 1 × ST7920 Display Spare Stock Ender 3 LCD Display
  • 1 × Ender Clock-Duino Stand See links to download .stl file
  • 1 × Breadboard Electronic Components / Misc. Electronic Components
  • 1 × Assorted Jumpers

View all 6 components

  • 1Dot22Dot25

    Rhea Rae01/22/2026 at 20:39 0 comments

    Project Log — Stardate 1Dot22Dot25

    R3 Master / R4 Slave configuration is operational. Primary system objectives have been met and baseline functionality is confirmed. Minor firmware anomalies remain and are currently under evaluation.

    Network connectivity is established; however, after extended operational periods the connection becomes intermittent and may disengage unexpectedly.

    System startup sequence is presently order-dependent. The R4 Slave must be powered prior to the R3 Master for proper clock output to render on the Ender-3 Pro display.

    Automatic external temperature updates are functional but exhibit inconsistent refresh behavior, indicating a possible synchronization or timing issue between devices.

    An alpha release of both firmware components will be published shortly to support collaborative review, testing, and external input.

    Overall system status remains stable. Continued observation and incremental firmware refinement are in progress.

    -RR

  • R4 Weather Slave

    Rhea Rae01/20/2026 at 16:28 0 comments

  • Ender Clock-Duino Sketch w/ DS3231

    Rhea Rae01/19/2026 at 19:53 0 comments

    #include <Arduino.h>

    #include <U8g2lib.h>
    #include <EEPROM.h>
    #include <Wire.h>
    #include <RTClib.h>
    #include <math.h>

    // ================= LCD =================
    const uint8_t PIN_LCD_CS = 10; // CS/RS
    U8G2_ST7920_128X64_1_HW_SPI u8g2(U8G2_R0, PIN_LCD_CS, U8X8_PIN_NONE);

    // ================= ENCODER =================
    const uint8_t PIN_ENC_A   = 2;
    const uint8_t PIN_ENC_B   = 3;
    const uint8_t PIN_ENC_BTN = 4;

    // If your encoder changes 2 numbers per click, set this to 2.
    // If it changes 1 per click, leave at 4.
    const int8_t TRANSITIONS_PER_DETENT = 4;

    // ================= UI TIMING =================
    const uint16_t LONG_MS     = 700;
    const uint16_t DEBOUNCE_MS = 25;

    // ================= EEPROM =================
    const int EEPROM_SIG_ADDR    = 0;
    const int EEPROM_TIME_ADDR   = 1;                     // (legacy; not used now, kept for spacing)
    const int EEPROM_CHIME_ADDR  = EEPROM_TIME_ADDR + 4;  // uint8_t
    const int EEPROM_TONE_ADDR   = EEPROM_CHIME_ADDR + 1; // uint8_t
    const int EEPROM_FMT_ADDR    = EEPROM_TONE_ADDR + 1;  // uint8_t (0=12h,1=24h)
    const uint8_t EEPROM_SIG     = 0xA5;

    // ================= BUZZER =================
    const uint8_t PIN_BUZZER = 8;

    // Settings (editable, saved)
    bool    chime_enabled = true;
    uint8_t tone_amount   = 18;     // 0..100 (0 = silent)
    bool    fmt_24h       = false;  // false=12h, true=24h

    // ================= RTC =================
    RTC_DS3231 rtc;

    // Throttle RTC reads so we don't spam I2C
    DateTime rtc_cached;
    uint32_t last_rtc_read_ms = 0;

    // Temperature cache (DS3231 internal sensor)
    float    temp_f_cached = NAN;
    uint32_t last_temp_read_ms = 0;
    static const uint32_t TEMP_READ_PERIOD_MS = 5000;

    // ================= SET MODE FIELDS =================
    bool set_mode = false;

    // Editing fields
    uint8_t edit_hour12 = 12;  // 1..12
    bool    edit_pm     = false;
    uint8_t edit_hour24 = 0;   // 0..23
    uint8_t edit_min    = 0;   // 0..59

    bool    edit_chime_enabled = true;
    uint8_t edit_tone_amount   = 18;
    bool    edit_fmt_24h       = false;

    enum Sel : uint8_t { SEL_HOUR, SEL_MIN, SEL_AMPM, SEL_FMT, SEL_CHIME, SEL_TONE, SEL_TEST };
    Sel sel = SEL_HOUR;

    uint32_t saved_banner_until_ms = 0;
    int8_t last_chimed_h24 = -1;

    // ================= ENCODER ISR =================
    volatile int16_t enc_delta = 0;
    volatile uint8_t enc_prev  = 0;

    static const int8_t ENC_TAB[16] = {
      0, -1,  1,  0,
      1,  0,  0, -1,
     -1,  0,  0,  1,
      0,  1, -1,  0
    };

    void enc_isr() {
      uint8_t a = digitalRead(PIN_ENC_A) ? 1 : 0;
      uint8_t b = digitalRead(PIN_ENC_B) ? 1 : 0;
      uint8_t cur = (a << 1) | b;
      uint8_t idx = (enc_prev << 2) | cur;
      enc_prev = cur;
      enc_delta += ENC_TAB[idx];
    }

    // ================= BUTTON =================
    bool btn_last_raw = true;
    bool btn_stable   = true;
    uint32_t btn_change_ms = 0;
    uint32_t btn_down_ms   = 0;
    bool long_fired = false;

    static void poll_button(bool &short_press, bool &long_press) {
      short_press = false;
      long_press  = false;

      bool raw = digitalRead(PIN_ENC_BTN); // HIGH idle, LOW pressed
      uint32_t now = millis();

      if (raw != btn_last_raw) {
        btn_last_raw = raw;
        btn_change_ms = now;
      }

      if ((now - btn_change_ms) > DEBOUNCE_MS) {
        if (btn_stable != raw) {
          btn_stable = raw;
          if (btn_stable == LOW) {
            btn_down_ms = now;
            long_fired = false;
          } else {
            if (!long_fired) short_press = true;
          }
        }
      }

      if (btn_stable == LOW && !long_fired...

    Read more »

  • DS3231

    Rhea Rae01/15/2026 at 17:05 0 comments

    This DS3231 RTC module includes a charge path intended for a rechargeable LIR2032, implemented using a small series resistor and a glass diode feeding the VBAT node. Since I am using a standard CR2032 (non-rechargeable), I disabled the charging path.

    To do this, I identified the resistor located directly next to the glass diode associated with the battery circuit (both resistors on this board are marked “102”). I lifted one leg of the resistor that feeds the diode, effectively opening the VCC → VBAT charging path while leaving the battery connected to the DS3231’s VBAT pin.

    After lifting the resistor, I installed a CR2032 coin cell, set the time, unplugged the clock for several minutes, and then re-applied power. The RTC retained the correct time, confirming that VBAT backup is functioning normally and that the coin cell is no longer being charged from VCC.

    This modification prevents unintended charging of a non-rechargeable coin cell while preserving full RTC backup operation.

    -RR

  • Scrambled

    Rhea Rae01/12/2026 at 18:28 0 comments

    If screen is scrambled switch pin D13 and D11.

    -RR

  • ST7920 Pin-Out

    Rhea Rae01/12/2026 at 18:25 0 comments

    Use EXP 3 Header!

    -RR

  • Ender Clock-Duino Sketch w/o DS3231

    Rhea Rae01/12/2026 at 18:19 0 comments

    #include <Arduino.h>
    #include <U8g2lib.h>
    #include <EEPROM.h>
    #include <Wire.h>
    #include <RTClib.h>

    // ================= LCD =================
    const uint8_t PIN_LCD_CS = 10; // CS/RS
    U8G2_ST7920_128X64_1_HW_SPI u8g2(U8G2_R0, PIN_LCD_CS, U8X8_PIN_NONE);

    // ================= ENCODER =================
    const uint8_t PIN_ENC_A   = 2;
    const uint8_t PIN_ENC_B   = 3;
    const uint8_t PIN_ENC_BTN = 4;

    // If your encoder changes 2 numbers per click, set this to 2.
    // If it changes 1 per click, leave at 4.
    const int8_t TRANSITIONS_PER_DETENT = 4;

    // ================= UI TIMING =================
    const uint16_t LONG_MS     = 700;
    const uint16_t DEBOUNCE_MS = 25;

    // ================= EEPROM =================
    const int EEPROM_SIG_ADDR    = 0;
    const int EEPROM_TIME_ADDR   = 1;                  // (legacy; not used now, kept for spacing)
    const int EEPROM_CHIME_ADDR  = EEPROM_TIME_ADDR + 4; // uint8_t
    const int EEPROM_TONE_ADDR   = EEPROM_CHIME_ADDR + 1; // uint8_t
    const int EEPROM_FMT_ADDR    = EEPROM_TONE_ADDR + 1;  // uint8_t (0=12h,1=24h)
    const uint8_t EEPROM_SIG     = 0xA5;

    // ================= BUZZER =================
    const uint8_t PIN_BUZZER = 8;

    // Settings (editable, saved)
    bool    chime_enabled = true;
    uint8_t tone_amount   = 18;    // 0..100 (0 = silent)
    bool    fmt_24h       = false; // false=12h, true=24h

    // ================= RTC =================
    RTC_DS3231 rtc;

    // Throttle RTC reads so we don't spam I2C
    DateTime rtc_cached;
    uint32_t last_rtc_read_ms = 0;

    // ================= SET MODE FIELDS =================
    bool set_mode = false;

    // Editing fields
    uint8_t edit_hour12 = 12;  // 1..12
    bool    edit_pm     = false;
    uint8_t edit_hour24 = 0;   // 0..23
    uint8_t edit_min    = 0;   // 0..59

    bool    edit_chime_enabled = true;
    uint8_t edit_tone_amount   = 18;
    bool    edit_fmt_24h       = false;

    enum Sel : uint8_t { SEL_HOUR, SEL_MIN, SEL_AMPM, SEL_FMT, SEL_CHIME, SEL_TONE, SEL_TEST };
    Sel sel = SEL_HOUR;

    uint32_t saved_banner_until_ms = 0;
    int8_t last_chimed_h24 = -1;

    // ================= ENCODER ISR =================
    volatile int16_t enc_delta = 0;
    volatile uint8_t enc_prev  = 0;

    static const int8_t ENC_TAB[16] = {
      0, -1,  1,  0,
      1,  0,  0, -1,
     -1,  0,  0,  1,
      0,  1, -1,  0
    };

    void enc_isr() {
      uint8_t a = digitalRead(PIN_ENC_A) ? 1 : 0;
      uint8_t b = digitalRead(PIN_ENC_B) ? 1 : 0;
      uint8_t cur = (a << 1) | b;
      uint8_t idx = (enc_prev << 2) | cur;
      enc_prev = cur;
      enc_delta += ENC_TAB[idx];
    }

    // ================= BUTTON =================
    bool btn_last_raw = true;
    bool btn_stable   = true;
    uint32_t btn_change_ms = 0;
    uint32_t btn_down_ms   = 0;
    bool long_fired = false;

    static void poll_button(bool &short_press, bool &long_press) {
      short_press = false;
      long_press  = false;

      bool raw = digitalRead(PIN_ENC_BTN); // HIGH idle, LOW pressed
      uint32_t now = millis();

      if (raw != btn_last_raw) {
        btn_last_raw = raw;
        btn_change_ms = now;
      }

      if ((now - btn_change_ms) > DEBOUNCE_MS) {
        if (btn_stable != raw) {
          btn_stable = raw;
          if (btn_stable == LOW) {
            btn_down_ms = now;
            long_fired = false;
          } else {
            if (!long_fired) short_press = true;
          }
        }
      }

      if (btn_stable == LOW && !long_fired && (now - btn_down_ms) >= LONG_MS) {
        long_fired = true;
        long_press = true;
      }
    }

    // ================= HELPERS =================
    static const char* ampm_str(bool...

    Read more »

View all 7 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates