Projects / Clock
Precision Alarm Clock
A desk clock that keeps time to a minute a year, with a rotary-knob alarm.
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
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
| From | To | Note |
|---|---|---|
| ET-M02 Qwiic | ET-A11 Qwiic | Carries 3.3 V, GND, SDA, SCL |
| ET-A11 Qwiic | ET-D01 Qwiic |
ET-A15 knob → ET-M02
| From | To | Note |
|---|---|---|
| 3V3 | VIN | Sets the pull-up voltage |
| GND | GND | |
| IO4 | A | |
| IO5 | B | |
| IO6 | SW | Low when pressed |
ET-A16 buzzer → ET-M02
| From | To | Note |
|---|---|---|
| VBUS | VIN | 5 V makes it louder |
| GND | GND | |
| IO7 | SIG |
Build it
- 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.
- 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.
- 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.
- 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.
- 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
#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.