An hourglass measures time with sand that falls through a narrow neck. Turn it over and the sand runs the other way. Your digital hourglass does the same with light: three LEDs in the top bulb go out one by one while three LEDs in the bottom bulb light up. The tilt switch in the middle feels which way up the hourglass is, so you can flip it at any moment and the light runs back. When all the "sand" has run through, the buzzer plays a short tune.
What you'll learn
- How a tilt switch tells which way up something is
- How to keep track of something (the sand) with a single number
- How to measure time in steps without stopping the sketch
- How to ignore a wobbly switch until it has settled
What you need
- UNO R3 board and USB cable (UNO R3 Starter Kit)
- Tilt switch module (45-in-1 Sensor Kit)
- 6 LEDs and 6 resistors, 220 Ω (UNO R3 Starter Kit)
- Passive buzzer (UNO R3 Starter Kit)
- 14 long female-to-male jumper wires
- The printed parts from the 3D printing box below (hourglass), or the cardboard version described there
- Breadboard and jumper wires (UNO R3 Starter Kit)
Wiring

| From | To |
|---|---|
| Tilt switch module S | UNO pin 2 |
| Tilt switch module + and − | 5V and GND |
| UNO pins 3, 4, 5 | 220 Ω resistor each, then the long legs of the bulb A LEDs (pin 3 nearest the neck) |
| UNO pins 6, 7, 8 | 220 Ω resistor each, then the long legs of the bulb B LEDs (pin 6 nearest the neck) |
| All LED short legs | GND |
| Passive buzzer + | UNO pin 10 |
| Passive buzzer − | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
The resistors stay on the breadboard and the long wires go up to the LEDs in the hourglass. Mark which bulb is A with a pen: that is the bulb that should be at the top when the module's little ball rolls towards its pins.
The sketch
const int TILT_PIN = 2;
const int BULB_A[3] = {3, 4, 5}; // first LED is nearest the neck
const int BULB_B[3] = {6, 7, 8};
const int BUZZER_PIN = 10;
const unsigned long RUN_TIME = 60000; // one minute for all the sand
int sandInA = 3; // grains of sand in bulb A (the rest are in B)
bool aOnTop = true;
unsigned long lastGrain = 0;
bool finished = false;
// The top bulb's sand rests at the neck; the bottom bulb's sand piles up from the far end
void showBulb(const int pins[3], int grains, bool onTop) {
for (int i = 0; i < 3; i++) {
bool lit = onTop ? (i < grains) : (i >= 3 - grains);
digitalWrite(pins[i], lit ? HIGH : LOW);
}
}
void show() {
showBulb(BULB_A, sandInA, aOnTop);
showBulb(BULB_B, 3 - sandInA, !aOnTop);
}
// Read the tilt switch, but only believe it once it has stayed the same for 300 ms
bool readOrientation() {
static int lastReading = -1;
static unsigned long since = 0;
static bool settled = true;
int reading = digitalRead(TILT_PIN);
if (reading != lastReading) {
lastReading = reading;
since = millis();
}
if (millis() - since > 300) settled = (reading == HIGH);
return settled;
}
void tune() {
int notes[] = {523, 659, 784, 1047};
for (int i = 0; i < 4; i++) {
tone(BUZZER_PIN, notes[i], 150);
delay(180);
}
}
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(BULB_A[i], OUTPUT);
pinMode(BULB_B[i], OUTPUT);
}
pinMode(TILT_PIN, INPUT);
aOnTop = readOrientation();
show();
}
void loop() {
bool nowAOnTop = readOrientation();
if (nowAOnTop != aOnTop) { // turned over: the sand starts running the other way
aOnTop = nowAOnTop;
lastGrain = millis();
finished = false;
}
int grainsOnTop = aOnTop ? sandInA : 3 - sandInA;
if (grainsOnTop > 0 && millis() - lastGrain >= RUN_TIME / 3) {
lastGrain = millis();
sandInA += aOnTop ? -1 : 1; // one grain falls through the neck
}
if (grainsOnTop == 0 && !finished) {
finished = true;
show();
tune();
}
show();
// let the grain that is about to fall flicker in the last two seconds
if (grainsOnTop > 0 && millis() - lastGrain > RUN_TIME / 3 - 2000 && (millis() / 150) % 2) {
digitalWrite(aOnTop ? BULB_A[0] : BULB_B[0], LOW);
}
}
Check your tilt switch
Tilt switch modules from different makers give HIGH or LOW for the same position. If the sand runs upwards (the bottom bulb empties), change reading == HIGH to reading == LOW in readOrientation().
How it works
Inside the tilt switch is a small metal ball. When the switch points one way, the ball rolls onto two contacts and connects them; turned over, it rolls away. That is all the sketch needs to know which bulb is on top.
The whole hourglass is stored in one number, sandInA: how many of the three grains are in bulb A. The others must be in bulb B. Every 20 seconds (a third of RUN_TIME), one grain moves from the top bulb to the bottom bulb. Turning the hourglass over doesn't change the sand, only the direction it flows, exactly like a real one.
A ball rolling around rattles: for a moment the switch flicks between on and off. readOrientation() only changes its answer once the reading has stayed the same for 300 ms. The static variables inside it keep their values between calls.
Try this next
- Make it a 3-minute egg timer by changing
RUN_TIME. - Fade the grain out slowly with
analogWrite()on pins 3, 5 and 6 instead of switching it off. - Add a potentiometer to choose the time before you flip it.
Troubleshooting
- The sand runs upwards: see "Check your tilt switch" above.
-
It changes direction when you only shake it: make the 300 ms in
readOrientation()longer. - One LED never lights: it's in the wrong way round. The long leg goes to the resistor.
- 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.