Projects / Clock

Precision Alarm Clock

A desk clock that keeps time to a minute a year, with a rotary-knob alarm.

Beginner⏱ 1.5 hours5 ElectroTrical boards

Overview

The ET-A11 DS3231 is a temperature-compensated real-time clock accurate to about ±2 ppm — roughly a minute a year — and its coin cell keeps time when the power is off. The ET-D01 shows the time on a bright 0.56" 4-digit display.

Turn the ET-A15 knob to set the alarm, press it to switch modes, and the ET-A16 buzzer plays a wake-up tune. Everything except the knob and buzzer connects with Qwiic cables, and the alarm time is saved in flash.

  • ±2 ppm clock with battery backup
  • Bright 4-digit LED display with blinking colon
  • Set the alarm with one knob
  • Alarm saved through power cuts

What you need

ElectroTrical boards

ET-M02ESP32-S3 Dev BoardController with built-in Qwiic port
Coming soon
ET-A11DS3231 Precision RTCReal-time clock with coin-cell backup
Coming soon
ET-D01HT16K33 4-Digit 7-Segment DisplayTime display
Coming soon
ET-A15Rotary Encoder BreakoutKnob for setting the alarm
Coming soon
ET-A16Passive Buzzer BoardAlarm sound
Coming soon

You'll also need

  • CR1220 coin cell for the RTC (if not fitted)
  • 2 Qwiic cables
  • USB-C cable and 5 V USB charger
  • Jumper wires

Wiring

I²C addresses: DS3231 0x68, HT16K33 0x70. The display is a little dimmer at 3.3 V from the Qwiic port; that's normal for a bedside clock.

Qwiic chain

FromToNote
ET-M02 QwiicET-A11 QwiicCarries 3.3 V, GND, SDA, SCL
ET-A11 QwiicET-D01 Qwiic

ET-A15 knob → ET-M02

FromToNote
3V3VINSets the pull-up voltage
GNDGND
IO4A
IO5B
IO6SWLow when pressed

ET-A16 buzzer → ET-M02

FromToNote
VBUSVIN5 V makes it louder
GNDGND
IO7SIG

Build it

  1. 01

    Fit the coin cell

    Put a CR1220 cell in the ET-A11 holder, + side up. Without it the time resets at every power cut.

  2. 02

    Connect the boards

    Chain ET-M02 → ET-A11 → ET-D01 with Qwiic cables, then wire the knob and buzzer to the ET-M02 header as in the tables.

  3. 03

    Set up the Arduino IDE

    Install Arduino IDE 2 and the Espressif ESP32 package. Select “ESP32S3 Dev Module” and set USB CDC On Boot to Enabled. Install the libraries listed above the sketch.

  4. 04

    Upload once to set the time

    On the first upload the clock takes the time from your computer. After that it keeps time on its own, even when unplugged.

  5. 05

    Use the knob

    Press to step through: clock → alarm hour → alarm minute → alarm on/off. Turn to change the value. Any press stops a ringing alarm.

Arduino code

Written for the ET-M02 ESP32-S3 Dev Board. Install these libraries from the Arduino Library Manager:

  • RTClib by Adafruit
  • Adafruit LED Backpack Library
  • Adafruit GFX Library
alarm_clock.ino
#include <Wire.h>
#include <Preferences.h>
#include <RTClib.h>
#include <Adafruit_LEDBackpack.h>

const int ENC_A = 4, ENC_B = 5, ENC_SW = 6, BUZZER = 7;

RTC_DS3231 rtc;
Adafruit_7segment disp;
Preferences prefs;

int alarmH = 7, alarmM = 0;
bool alarmOn = true, ringing = false;
int mode = 0;                       // 0 clock, 1 hour, 2 minute, 3 on/off
int lastAB = 0, steps = 0;

void showHHMM(int h, int m, bool colon) {
  disp.writeDigitNum(0, h / 10);
  disp.writeDigitNum(1, h % 10);
  disp.writeDigitNum(3, m / 10);
  disp.writeDigitNum(4, m % 10);
  disp.drawColon(colon);
  disp.writeDisplay();
}

int readKnob() {                    // returns +1 / -1 per detent
  static const int8_t table[16] = {0,-1,1,0, 1,0,0,-1, -1,0,0,1, 0,1,-1,0};
  int ab = (digitalRead(ENC_A) << 1) | digitalRead(ENC_B);
  steps += table[(lastAB << 2) | ab];
  lastAB = ab;
  if (steps >= 4)  { steps = 0; return 1; }
  if (steps <= -4) { steps = 0; return -1; }
  return 0;
}

void setup() {
  Serial.begin(115200);
  Wire.begin(8, 9);
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  pinMode(ENC_SW, INPUT_PULLUP);

  if (!rtc.begin()) Serial.println("DS3231 not found (0x68)");
  if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  disp.begin(0x70);
  disp.setBrightness(10);

  prefs.begin("alarm");
  alarmH = prefs.getInt("h", 7);
  alarmM = prefs.getInt("m", 0);
  alarmOn = prefs.getBool("on", true);
}

void loop() {
  DateTime now = rtc.now();

  // knob press: stop alarm, or step through the modes
  static bool lastSw = HIGH;
  bool sw = digitalRead(ENC_SW);
  if (sw == LOW && lastSw == HIGH) {
    if (ringing) { ringing = false; noTone(BUZZER); }
    else {
      mode = (mode + 1) % 4;
      if (mode == 0) { prefs.putInt("h", alarmH); prefs.putInt("m", alarmM); prefs.putBool("on", alarmOn); }
    }
    delay(30);
  }
  lastSw = sw;

  int turn = readKnob();
  if (mode == 1) alarmH = (alarmH + turn + 24) % 24;
  if (mode == 2) alarmM = (alarmM + turn + 60) % 60;
  if (mode == 3 && turn) alarmOn = !alarmOn;

  // display
  bool blink = (millis() / 500) % 2;
  if (mode == 0)      showHHMM(now.hour(), now.minute(), now.second() % 2);
  else if (mode == 3) { disp.print(alarmOn ? 1 : 0, DEC); disp.writeDisplay(); }
  else                showHHMM(alarmH, alarmM, blink);

  // alarm
  if (alarmOn && mode == 0 && now.hour() == alarmH && now.minute() == alarmM && now.second() == 0) ringing = true;
  if (ringing) {
    int t = millis() % 1000;
    if (t < 150) tone(BUZZER, 2000); else if (t < 300) noTone(BUZZER);
    else if (t < 450) tone(BUZZER, 2000); else noTone(BUZZER);
  }
  delay(1);
}

Troubleshooting

Time resets after unplugging

The coin cell is missing, flat or upside down. Fit a fresh CR1220 with + facing up, then re-upload once to set the time.

Knob changes by 2 or skips

Some encoders give 2 steps per click instead of 4. Change the 4 and -4 in readKnob() to 2 and -2.

Display shows nothing

Check the Qwiic cables are fully seated and run an I²C scanner: you should see 0x68 and 0x70.

Take it further

  • Sync the time from the internet over Wi-Fi (NTP) once a day
  • Add the ET-S70 sensor and alternate time with room temperature
  • Dim the display at night with the ET-S69 light sensor

Turning this into a product?

We design custom boards that combine these sensors on one PCB, with firmware and production.