Every clock needs three things: something that ticks, a way to count the ticks, and a display. In this lesson the UNO's own timer does the ticking, your sketch counts the seconds, and the 1602 LCD shows the time. Two buttons let you set the hour and the minute, and the same buttons set an alarm that plays on the passive buzzer.
What you'll learn
- How
millis()can count seconds without stopping the sketch - How to turn a number of seconds into hours, minutes and seconds
- How a "mode" variable lets two buttons do many jobs
- How to make a field blink while you change it
What you need
- UNO R3 board and USB cable (UNO R3 Starter Kit)
- 1602 LCD display (UNO R3 Starter Kit)
- 10 kΩ potentiometer (UNO R3 Starter Kit)
- 1 resistor, 220 Ω (UNO R3 Starter Kit)
- 2 push buttons (UNO R3 Starter Kit)
- Passive buzzer (UNO R3 Starter Kit)
- The printed parts from the 3D printing box below (clock stand), or the cardboard version described there
- Breadboard and jumper wires (UNO R3 Starter Kit)
Wiring

| From | To |
|---|---|
| LCD VSS, RW and K | GND |
| LCD VDD | 5V |
| LCD V0 | Middle pin of the potentiometer |
| LCD RS | UNO pin 12 |
| LCD E | UNO pin 11 |
| LCD D4, D5, D6, D7 | UNO pins 5, 4, 3, 2 |
| LCD A (backlight +) | 220 Ω resistor, then 5V |
| Potentiometer outer pins | 5V and GND |
| SET button, one leg | UNO pin A0 |
| PLUS button, one leg | UNO pin A1 |
| Both buttons, the diagonally opposite leg | GND |
| Passive buzzer + | UNO pin 8 |
| Passive buzzer − | GND |
The LCD has 16 connections in a row. If yours has no pins, solder 16 pins from the 40-pin male header in the kit first, so it plugs into the breadboard. Ask an adult to help if you're new to soldering.
This is the LCD wiring from the LCD lesson, plus two buttons and the buzzer.
The sketch
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int SET_PIN = A0;
const int PLUS_PIN = A1;
const int BUZZER_PIN = 8;
const long DAY = 86400; // seconds in a day
long secondsToday = 12L * 3600; // starts at 12:00:00
int alarmHour = 7, alarmMinute = 0;
bool alarmOn = false;
bool ringing = false;
int mode = 0; // 0 clock, 1 hour, 2 minute, 3 alarm hour, 4 alarm minute, 5 alarm on/off
unsigned long lastTick = 0;
unsigned long ringStart = 0;
bool pressed(int pin) {
if (digitalRead(pin) == HIGH) return false;
delay(20); // let the contacts settle
if (digitalRead(pin) == HIGH) return false;
while (digitalRead(pin) == LOW) delay(5); // wait until it is released
return true;
}
// Print a number as two digits, or two spaces when the field is blinking off
void field(int value, bool selected) {
if (selected && (millis() / 400) % 2 == 0) {
lcd.print(" ");
return;
}
if (value < 10) lcd.print('0');
lcd.print(value);
}
void show() {
int h = secondsToday / 3600;
int m = (secondsToday / 60) % 60;
int s = secondsToday % 60;
lcd.setCursor(0, 0);
lcd.print(mode == 0 ? " " : "Set ");
field(h, mode == 1);
lcd.print(':');
field(m, mode == 2);
lcd.print(':');
field(s, false);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("Alarm ");
field(alarmHour, mode == 3);
lcd.print(':');
field(alarmMinute, mode == 4);
lcd.print(' ');
if (mode == 5 && (millis() / 400) % 2 == 0) lcd.print(" ");
else lcd.print(alarmOn ? "ON " : "OFF");
}
void tick() {
while (millis() - lastTick >= 1000) { // catch up if we were busy
lastTick += 1000;
secondsToday = (secondsToday + 1) % DAY;
if (alarmOn && mode == 0 && secondsToday == alarmHour * 3600L + alarmMinute * 60L) {
ringing = true;
ringStart = millis();
}
}
}
void setup() {
lcd.begin(16, 2);
pinMode(SET_PIN, INPUT_PULLUP);
pinMode(PLUS_PIN, INPUT_PULLUP);
lastTick = millis();
}
void loop() {
tick();
if (ringing) {
if (digitalRead(SET_PIN) == LOW || digitalRead(PLUS_PIN) == LOW || millis() - ringStart > 60000) {
ringing = false; // any button (or one minute) stops the alarm
noTone(BUZZER_PIN);
while (digitalRead(SET_PIN) == LOW || digitalRead(PLUS_PIN) == LOW) delay(5);
} else {
tone(BUZZER_PIN, (millis() / 250) % 2 ? 1000 : 1500); // two-tone beep
}
} else {
if (pressed(SET_PIN)) {
mode = (mode + 1) % 6;
if (mode == 3) { // time set: restart the seconds at 0
secondsToday -= secondsToday % 60;
lastTick = millis();
}
}
if (pressed(PLUS_PIN)) {
if (mode == 1) secondsToday = (secondsToday + 3600) % DAY;
if (mode == 2) secondsToday = (secondsToday / 3600) * 3600 + ((secondsToday / 60 % 60 + 1) % 60) * 60 + secondsToday % 60;
if (mode == 3) alarmHour = (alarmHour + 1) % 24;
if (mode == 4) alarmMinute = (alarmMinute + 5) % 60; // 5-minute steps
if (mode == 5) alarmOn = !alarmOn;
}
}
show();
}
Set the clock
- Press SET once: "Set" appears and the hours blink. Press PLUS until the hour is right.
- Press SET again for the minutes, and once more to start the clock from 0 seconds.
- Keep pressing SET to set the alarm hour, the alarm minute (in 5-minute steps) and ON or OFF, then once more to go back to the clock.
- When the alarm rings, press any button to stop it.
How it works
The clock keeps the time as one number: the seconds since midnight, from 0 to 86,399. Dividing by 3600 gives the hours, dividing by 60 and keeping the remainder after 60 (% 60) gives the minutes, and % 60 on its own gives the seconds.
tick() doesn't use delay(1000). It checks how much time has passed with millis() and adds a second for every full 1000 ms, using a while loop so no second is lost if the sketch was busy. That keeps the buttons and the blinking quick.
The two buttons do many jobs thanks to mode. SET moves to the next mode, and PLUS changes whatever the current mode is about. The blinking comes from millis() / 400: it goes up by one every 400 ms, and % 2 makes it switch between even and odd.
The UNO's clock isn't perfect: it can drift by a few seconds a day. For a clock that keeps exact time, even after the power is off, the sensor kit's clock module has its own crystal and battery.
Try this next
- Show the day of the week on the second line, and make it change at midnight.
- Add a snooze: PLUS stops the alarm for five minutes, SET stops it for the day.
- Use the clock module from the 45-in-1 Sensor Kit so the time survives switching off.
Troubleshooting
- The LCD lights up but shows nothing, or only black boxes: turn the potentiometer slowly from one end to the other until the text appears.
-
The time jumps by two when you press PLUS once: the button bounces. Make the
delay(20)inpressed()a bit longer. - The buttons do nothing: each button must straddle the gap in the middle of the breadboard, with one leg to A0 or A1 and the diagonally opposite leg to GND.
- The alarm never rings: check that it says ON, and that you left the setting modes, because the alarm only rings while the clock is showing.
- A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.