LED Hourglass Using Arduino

Quick Overview & Specifications

AttributeDetails
PublicationVinjo Engineering & Technology
CategoryHardware & IoT
AuthorVinay
Article TypeTechnical Tutorial & Case Study
Published2026-10-20
Last Updated2026-10-20
License / AccessOpen Educational Resource
Core Engineering Highlights:
  1. Verified hardware architecture, schematics, and pinout diagrams.
  2. Comprehensive firmware routines, protocol benchmarks, and build steps.
  3. Production design rules, tolerances, and thermal considerations.
Related Topic Taxonomy:
  • #Arduino Projects
  • #LED Matrix
  • #3D Printing
  • #DIY Electronics

LED Hourglass Using Arduino

Summary: In this project, traditional sand and glass are replaced with electronic components to create a digital hourglass using Arduino. By pairing two 8x8 LED matrices driven by MAX7219 ICs with an ADXL335 3-axis accelerometer, the system detects physical orientation and dynamically simulates falling sand particles.

Key Takeaways

  • Gravity-Aware Display: Utilizes an ADXL335 accelerometer to sense physical rotation and determine which LED matrix acts as the top or bottom bulb.
  • Multiplexed Visuals: Uses MAX7219 driver modules to control two 8x8 LED matrices, allowing complex particle animations using minimal Arduino I/O pins.
  • Portable & Compact: Powered by a 3.7 V Li-ion battery integrated with a charger and 5 V step-up booster module inside a custom 3D printed frame.

Detailed Overview

In ancient times, people relied on an hourglass as a simple yet effective tool to measure the passage of time. The hourglass is made up of two glass bulbs connected by a narrow neck, through which sand flows steadily from the upper bulb to the lower one. The duration of the sand flow was usually fixed—often about an hour—which is why it came to be known as the hourglass. This timeless device symbolises the continuous flow of time, and even today it is admired for its simplicity and elegance.

Inspired by this traditional concept, this tutorial recreates the same idea in a modern way by building a digital hourglass using Arduino, where electronic components replace sand and glass to simulate the passage of time in a creative and interactive form.

LED Hourglass Overview

Components Needed

Project Components

  • Arduino (Uno / Nano / Pro Mini) — 1
  • ADXL335 breakout board — 1
  • 8×8 LED matrix + MAX7219 module — 2
  • 3.7 V Li-ion battery — 1
  • Charger + Booster Module — 1
  • Button switch — 1
  • Connecting Wires
  • 3D printed frame parts

Component Roles & System Operation

  • Arduino (The Brain): The Arduino is the central controller for the digital hourglass. It reads data from the accelerometer, runs the "sand" falling animation algorithm, and communicates with the LED drivers. An Uno, Nano, or Pro Mini works well due to simple power requirements, digital I/O availability, and library support.
  • ADXL335 3-Axis Analog Accelerometer: Provides raw X, Y, and Z analog voltage values corresponding to the sensor’s tilt orientation. It requires simple analogRead() readings to determine orientation without needing I²C setup.
  • Two 8×8 LED Matrices (MAX7219 Driven): One matrix represents the top bulb and the other represents the bottom bulb. Daisy-chaining the two MAX7219 ICs allows full control of all 128 LEDs using only three digital control lines (DIN, CLK, CS/LOAD).
  • 3.7 V Li-ion Battery & 5 V Booster: A single-cell battery combined with a charger/booster module steps up voltage to 5 V to power both the Arduino and MAX7219 displays in a compact form factor.

How the Parts Work Together

  1. The ADXL335 detects the orientation of the project to determine which matrix is currently "up."
  2. The Arduino processes the analog accelerometer data, assigns the top and bottom roles to the LED matrices, and starts animating falling pixels.
  3. The MAX7219 ICs display the falling pixel animation. Flipping the frame reverses the animation sequence, causing simulated sand to drop into the new lower display matrix until emptied.

Deep Dive & Technical Insights

Circuit Diagram & Wiring

Circuit Diagram

Arduino → First MAX7219 LED Matrix
  • DIN → D5
  • CLK → D4
  • CS/LOAD → D6
  • VCC → 5 V
  • GND → GND
First MAX7219 Matrix → Second MAX7219 Matrix
  • DOUT → DIN (Second Matrix)
  • CLK → CLK
  • CS/LOAD → CS/LOAD
  • VCC → VCC
  • GND → GND
ADXL335 Accelerometer → Arduino
  • X → A1
  • Y → A2
  • VCC → 5 V
  • GND → GND

Soldering

Soldering Circuit Step 1 Soldering Circuit Step 2 Soldering Circuit Step 3

  1. Connect and solder the X and Y output pins of the ADXL335 accelerometer to analog pins A1 and A2 on the Arduino Nano. Connect VCC and GND lines to 5 V and GND.
  2. Daisy-chain the two 8x8 MAX7219 LED matrices together. Solder control lines DIN, CLK, and CS to digital pins D5, D4, and D6 respectively.
  3. Connect a 3.7 V Li-Po battery to the IP5306 charger/booster module. Solder its 5 V output and ground connections to power the Arduino Nano and components.

3D Printed Enclosure

3D Enclosure Design 3D Printed Enclosure

The housing frame was designed in Tinkercad and printed in PLA using a BambuLab A1 3D printer.


Arduino Source Code

#include "Arduino.h"
#include "LedControl.h"
#include "Delay.h"

#define MATRIX_A 1
#define MATRIX_B 0

// Values are 260/330/400
#define ACC_THRESHOLD_LOW 300
#define ACC_THRESHOLD_HIGH 360

// Matrix
#define PIN_DATAIN 5
#define PIN_CLK 4
#define PIN_LOAD 6

// Accelerometer
#define PIN_X A1
#define PIN_Y A2

#define PIN_BUZZER 14

// This takes into account how the matrixes are mounted
#define ROTATION_OFFSET 90

#define DELAY_FRAME 100

#define DEBUG_OUTPUT 1

#define MODE_HOURGLASS 0

byte delayHours = 0;
byte delayMinutes = 1;
int mode = MODE_HOURGLASS;
int gravity;
LedControl lc = LedControl(PIN_DATAIN, PIN_CLK, PIN_LOAD, 2);
NonBlockDelay d;
int resetCounter = 0;
bool alarmWentOff = false;


/**
 * Get delay between particle drops (in seconds)
 */
long getDelayDrop() {
  return delayMinutes + delayHours * 60;
}


#if DEBUG_OUTPUT
void printmatrix() {
  Serial.println(" 0123-4567 ");
  for (int y = 0; y<8; y++) {
    if (y == 4) {
      Serial.println("|----|----|");
    }
    Serial.print(y);
    for (int x = 0; x<8; x++) {
      if (x == 4) {
        Serial.print("|");
      }
      Serial.print(lc.getXY(0,x,y) ? "X" :" ");
    }
    Serial.println("|");
  }
  Serial.println("-----------");
}
#endif


coord getDown(int x, int y) {
  coord xy;
  xy.x = x-1;
  xy.y = y+1;
  return xy;
}
coord getLeft(int x, int y) {
  coord xy;
  xy.x = x-1;
  xy.y = y;
  return xy;
}
coord getRight(int x, int y) {
  coord xy;
  xy.x = x;
  xy.y = y+1;
  return xy;
}

bool canGoLeft(int addr, int x, int y) {
  if (x == 0) return false;
  return !lc.getXY(addr, getLeft(x, y));
}
bool canGoRight(int addr, int x, int y) {
  if (y == 7) return false;
  return !lc.getXY(addr, getRight(x, y));
}
bool canGoDown(int addr, int x, int y) {
  if (y == 7) return false;
  if (x == 0) return false;
  if (!canGoLeft(addr, x, y)) return false;
  if (!canGoRight(addr, x, y)) return false;
  return !lc.getXY(addr, getDown(x, y));
}

void goDown(int addr, int x, int y) {
  lc.setXY(addr, x, y, false);
  lc.setXY(addr, getDown(x,y), true);
}
void goLeft(int addr, int x, int y) {
  lc.setXY(addr, x, y, false);
  lc.setXY(addr, getLeft(x,y), true);
}
void goRight(int addr, int x, int y) {
  lc.setXY(addr, x, y, false);
  lc.setXY(addr, getRight(x,y), true);
}

int countParticles(int addr) {
  int c = 0;
  for (byte y=0; y<8; y++) {
    for (byte x=0; x<8; x++) {
      if (lc.getXY(addr, x, y)) {
        c++;
      }
    }
  }
  return c;
}

bool moveParticle(int addr, int x, int y) {
  if (!lc.getXY(addr,x,y)) {
    return false;
  }

  bool can_GoLeft = canGoLeft(addr, x, y);
  bool can_GoRight = canGoRight(addr, x, y);

  if (!can_GoLeft && !can_GoRight) {
    return false;
  }

  bool can_GoDown = canGoDown(addr, x, y);

  if (can_GoDown) {
    goDown(addr, x, y);
  } else if (can_GoLeft && !can_GoRight) {
    goLeft(addr, x, y);
  } else if (can_GoRight && !can_GoLeft) {
    goRight(addr, x, y);
  } else if (random(2) == 1) {
    goLeft(addr, x, y);
  } else {
    goRight(addr, x, y);
  }
  return true;
}

void fill(int addr, int maxcount) {
  int n = 8;
  byte x,y;
  int count = 0;
  for (byte slice = 0; slice < 2*n-1; ++slice) {
    byte z = slice<n ? 0 : slice-n + 1;
    for (byte j = z; j <= slice-z; ++j) {
      y = 7-j;
      x = (slice-j);
      lc.setXY(addr, x, y, (++count <= maxcount));
    }
  }
}

int getGravity() {
  int x = analogRead(PIN_X);
  int y = analogRead(PIN_Y);
  if (y < ACC_THRESHOLD_LOW)  { return 0;   }
  if (x > ACC_THRESHOLD_HIGH) { return 90;  }
  if (y > ACC_THRESHOLD_HIGH) { return 180; }
  if (x < ACC_THRESHOLD_LOW)  { return 270; }
}

int getTopMatrix() {
  return (getGravity() == 90) ? MATRIX_A : MATRIX_B;
}
int getBottomMatrix() {
  return (getGravity() != 90) ? MATRIX_A : MATRIX_B;
}

void resetTime() {
  for (byte i=0; i<2; i++) {
    lc.clearDisplay(i);
  }
  fill(getTopMatrix(), 60);
  d.Delay(getDelayDrop() * 1000);
}

bool updateMatrix() {
  int n = 8;
  bool somethingMoved = false;
  byte x,y;
  bool direction;
  for (byte slice = 0; slice < 2*n-1; ++slice) {
    direction = (random(2) == 1);
    byte z = slice<n ? 0 : slice-n + 1;
    for (byte j = z; j <= slice-z; ++j) {
      y = direction ? (7-j) : (7-(slice-j));
      x = direction ? (slice-j) : j;
      if (moveParticle(MATRIX_B, x, y)) {
        somethingMoved = true;
      };
      if (moveParticle(MATRIX_A, x, y)) {
        somethingMoved = true;
      }
    }
  }
  return somethingMoved;
}

boolean dropParticle() {
  if (d.Timeout()) {
    d.Delay(getDelayDrop() * 1000);
    if (gravity == 0 || gravity == 180) {
      if ((lc.getRawXY(MATRIX_A, 0, 0) && !lc.getRawXY(MATRIX_B, 7, 7)) ||
          (!lc.getRawXY(MATRIX_A, 0, 0) && lc.getRawXY(MATRIX_B, 7, 7))
      ) {
        lc.invertRawXY(MATRIX_A, 0, 0);
        lc.invertRawXY(MATRIX_B, 7, 7);
        tone(PIN_BUZZER, 440, 10);
        return true;
      }
    }
  }
  return false;
}

void alarm() {
  for (int i=0; i<5; i++) {
    tone(PIN_BUZZER, 440, 200);
    delay(1000);
  }
}

/**
 * Setup
 */
void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(A0));

  for (byte i=0; i<2; i++) {
    lc.shutdown(i,false);
    lc.setIntensity(i,1);
  }

  resetTime();
}

/**
 * Main loop
 */
void loop() {
  delay(DELAY_FRAME);

  gravity = getGravity();
  lc.setRotation((ROTATION_OFFSET + gravity) % 360);

  bool moved = updateMatrix();
  bool dropped = dropParticle();

  if (!moved && !dropped && !alarmWentOff && (countParticles(getTopMatrix()) == 0)) {
    alarmWentOff = true;
    alarm();
  }
  if (dropped) {
    alarmWentOff = false;
  }
}

Assembly

Assembly Step 1 Assembly Step 2

After uploading the code, place the soldered circuit carefully inside the custom 3D enclosure to complete the project assembly.

Final Assembled Hourglass View 1 Final Assembled Hourglass View 2 Final Assembled Hourglass View 3


Conclusion & Resources

  • The digital LED hourglass blends traditional concepts with modern microcontroller logic to build an interactive, gravity-sensing timer project.
  • Schematics, 3D design files, and complete Arduino source code are accessible on the GitHub Repository.