WS2812B LED clock with Arduino and RTC

TomKnox 19
TomKnox
@TomKnox
Image for WS2812B LED clock with Arduino and RTC
Build a smart WS2812B LED clock with Arduino and an RTC

A 60-LED ring driven by an Arduino Nano with a DS3231 backing it up — the result is an analog-style clock that drifts less than two minutes a year, survives power outages, and lets you reskin the colour scheme in a few lines of code. Whole build is one evening of soldering and one weekend of fiddling with the look.

Finished WS2812B 60-LED ring clock glowing on a desk, showing hour, minute and second hands as coloured LEDs

What you're building

One WS2812B (a.k.a. NeoPixel) ring with 60 LEDs maps perfectly to a clock face — every LED is a minute, every fifth LED is an hour. The Arduino reads time from a DS3231 over I²C, computes which LEDs should be lit, and pushes the colours to the ring. The DS3231 has its own coin-cell battery, so the clock keeps time when you unplug everything.

This is a "smart" clock in the sense that you control exactly what each pixel does — fade tails on the second hand, different colours AM vs PM, brighter ticks at 12/3/6/9, dim the whole face after 22:00. Not "smart" in the Wi-Fi sense; we'll mention that variant at the end.

Parts list
Item AliExpress Amazon
Arduino Nano (CH340 clone is fine) AliExpress Link Amazon Link
WS2812B 60-LED ring (5 V, ~150 mm OD) AliExpress Link Amazon Link
DS3231 RTC module with AT24C32 EEPROM AliExpress Link Amazon Link
CR2032 coin cell (3 V, non-rechargeable) AliExpress Link Amazon Link
5 V 3 A power supply, 5.5 × 2.1 mm barrel AliExpress Link Amazon Link
1000 µF / 16 V electrolytic capacitor AliExpress Link Amazon Link
12 mm tactile push buttons (×2) AliExpress Link Amazon Link
Dupont jumper wires (M-F & F-F kit) AliExpress Link Amazon Link

You'll also want a 470 Ω resistor for the data line (out of any junk-box resistor pack) and a 3D-printed or laser-cut diffuser ring — see "Enclosure" near the end.

Notes on the parts

  • The DS3231 is non-negotiable. A DS1307 will work code-wise but drifts ~1–2 minutes per week. The DS3231 is temperature-compensated, rated ±2 ppm, which is roughly ±1 minute per year. The price difference is about a dollar.
  • Most cheap DS3231 modules ship with a non-rechargeable CR2032 but a charging circuit on the board. That's a fire hazard. Either remove the charging resistor (usually labelled R5 or R6, between VCC and the battery's + pin), or swap to a rechargeable LIR2032. The lastminuteengineers DS3231 write-up has the canonical photo of which resistor to lift.
  • Don't power 60 WS2812Bs from the Arduino's 5 V pin. At full white, 60 LEDs draw up to ~3.6 A (60 mA per LED at peak). The Nano's regulator gives you about 500 mA. Always feed the ring from the wall supply directly and only use the Arduino for the data signal.
  • The 1000 µF cap goes across the ring's V+ and GND, as close to the first pixel as you can get it. It absorbs the inrush spike when the ring goes from black to bright. Skip it and you'll see random pixels glitching on the first frame.

Close-up of a WS2812B 60-LED ring board

Wiring

Five connections, no surprises. The 470 Ω resistor sits in series on the data line right next to the ring's DIN pad — it dampens reflections that can corrupt the first pixel.

From To
5 V PSU + Ring V+, Arduino VIN, DS3231 VCC
5 V PSU − Ring GND, Arduino GND, DS3231 GND
Arduino D6 470 Ω → Ring DIN
Arduino A4 (SDA) DS3231 SDA
Arduino A5 (SCL) DS3231 SCL
Arduino D2 Button 1 → GND (mode)
Arduino D3 Button 3 → GND (set)

Buttons use the Arduino's internal pull-ups (INPUT_PULLUP), so the other leg goes straight to GND — no external resistor needed.

Circuit schematic showing Arduino Nano, DS3231 and WS2812B ring wired together with shared 5 V supply

A note on logic levels: WS2812B is officially happiest with a 3.5 V minimum on its data line. The Nano runs at 5 V, so the data signal is comfortably above threshold. If you ever swap to an ESP8266 or ESP32, add a 74AHCT125 level shifter — those boards drive 3.3 V, which is right on the edge.

Code

Three libraries, all installable from the Arduino IDE Library Manager:

  • Adafruit_NeoPixel — drives the WS2812B
  • RTClib (Adafruit) — talks to the DS3231
  • Wire — built in, used by RTClib

Skeleton sketch (drop in, set the time once, then comment out the rtc.adjust() line and re-flash):

#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN     6
#define NUM_LEDS    60
#define BRIGHTNESS  60   // 0–255; keep ≤80 unless you have airflow

RTC_DS3231 rtc;
Adafruit_NeoPixel ring(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Wire.begin();
  rtc.begin();
  // Run ONCE to set the clock from your computer's compile time:
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  ring.begin();
  ring.setBrightness(BRIGHTNESS);
  ring.show();
}

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

  // Dim white tick marks at 12, 3, 6, 9
  for (int i = 0; i < 60; i += 15)
    ring.setPixelColor(i, ring.Color(20, 20, 20));

  int hourLed = (now.hour() % 12) * 5 + now.minute() / 12;
  ring.setPixelColor(hourLed,   ring.Color(80,  0,  0));   // red hour
  ring.setPixelColor(now.minute(), ring.Color(0, 80,  0)); // green min
  ring.setPixelColor(now.second(), ring.Color(0,  0, 80)); // blue sec

  ring.show();
  delay(200);
}

That's the whole thing in 30-odd lines. Once it works, the fun starts:

  • Fading second-hand tail — store the last 5 second positions and dim them progressively (each one ¼ the previous brightness).
  • AM/PM hour colournow.hour() < 12 ? red : purple.
  • Night dimif (now.hour() >= 22 || now.hour() < 7) ring.setBrightness(10);
  • Modes via the buttons — cycle through analog clock / time-of-day-as-bar / rainbow.

Macro shot of a single WS2812B addressable RGB LED chip

Setting the time

The rtc.adjust(DateTime(F(__DATE__), F(__TIME__))) line uses the compile time of the sketch. Procedure:

  1. Uncomment the rtc.adjust(...) line.
  2. Click Verify, then immediately Upload (the gap matters — every second of compile + upload = a second your clock will be behind).
  3. Once it's running, comment the line out again and re-upload. Otherwise the clock resets to the old compile time every reboot.

A tidier way is to wire the buttons to nudge the time forward/backward by a minute, and set it once by hand. The DS3231 is accurate enough that you'll only do this once or twice a year.

Common mistakes
  • First pixel is always white/blue/random. You're missing the 470 Ω resistor on DIN, or the bulk capacitor across the ring's power. Both, ideally.
  • Whole ring flickers when you touch it. Power and ground aren't tied between the PSU and the Arduino. Both grounds must connect.
  • Time resets every time you unplug. The CR2032 in the DS3231 is dead, installed backwards, or you bought a module without one. Fresh CR2032s read 3.0–3.3 V on a multimeter.
  • Time is exactly 12 hours off. You set it from __TIME__ while the IDE was on a different timezone, or the DS3231 is in 12-hour mode. RTClib defaults to 24-hour; verify with a serial print.
  • Colours look washed out. Drop BRIGHTNESS to 40-ish; high values bias toward white because the LEDs saturate. A diffuser (frosted acrylic or matte 3D-printed PETG) makes 30 % brightness look like 100 %.
Enclosure

A 60-LED ring is ~150 mm OD. Two common approaches:

  • 3D print a two-part case. Back plate with standoffs for the ring and a bay for the Nano + DS3231; front diffuser printed in white PETG or PLA at 0.4 mm wall, 15 % gyroid infill — the infill itself diffuses beautifully. Cura/Bambu Studio's "ironing" setting on the front face hides layer lines.
  • Glue inside a Ribba photo frame (the IKEA 23×23 cm one is the canonical maker hack). Drop a printed paper face on top with cut-outs at the 12/3/6/9 positions for that classic word-clock look.

📷 (photo placeholder — slot ds3231-module: DS3231 RTC breakout module with CR2032 battery holder and AT24C32 EEPROM)

When to use this, and the Wi-Fi variant

This Arduino + DS3231 build is the right call if you want a standalone clock you flash once and forget — no router dependency, no cloud, runs offline forever. It's also the right starting point if you've never touched WS2812Bs before; everything stays at 5 V, no level-shifting drama.

If you want auto-DST and zero manual time-setting, swap the Nano for a Wemos D1 Mini (ESP8266) or an ESP32, drop the DS3231, and pull time from an NTP server. Same ring, same code structure (FastLED or NeoPixel libraries are identical on ESP), but you'll need a 74AHCT125 to bring the 3.3 V data signal up to the WS2812B's input threshold cleanly. Save that for after you've built this one.

Comments