Documentation

WS2812 24-Bit RGB LED Ring 90mm for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual
Documentation / WS2812 24-Bit RGB LED Ring 90mm for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual

WS2812 24-Bit RGB LED Ring 90mm for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual

Overview

This 90mm ring carries 24 WS2812 addressable RGB LEDs — each one an independent pixel with its own tiny controller chip baked inside. One data wire from your microcontroller sets the color and brightness of every LED individually: whole-ring rainbows, spinning comets, clock faces, audio meters, or a single status pixel — all from one GPIO pin. No multiplexing, no driver ICs, no PWM juggling; the pixels latch their colors and hold them until told otherwise.

The 24-pixel count and 90mm diameter hit a sweet spot: big enough to read as a proper dial or halo (clocks, gauges, speaker surrounds, camera ring lights, infinity mirrors), small enough to run from a modest 5V supply. Solder pads on the back bring out power (5V/GND) and the data chain — DI in, DO out — so rings can be daisy-chained into longer runs by wiring one ring's DO to the next ring's DI.

Library support is universal: Adafruit NeoPixel and FastLED on Arduino/ESP32, the built-in neopixel module in MicroPython on ESP32 and Pico, and rpi_ws281x on the Raspberry Pi. The only discipline WS2812s demand is electrical: a 330-470 ohm resistor in the data line, a big capacitor across power, and a supply sized for the worst case — about 60 mA per LED at full white, or ~1.4 A for the whole ring.

At a Glance

LEDs
24x WS2812 RGB pixels
Ring Size
~90 mm outer diameter
Control
1 data pin (800 kHz)
Supply Voltage
5V DC
Max Current
~1.4 A (all white, full)
Chainable
Yes — DO feeds next DI

Specifications

Parameter Value
LED Type WS2812 (WS2812B-class) integrated RGB + driver, 5050 package
LED Count 24, evenly spaced on the ring
Outer Diameter ~90 mm
Supply Voltage 5V DC (4.5 - 5.5V)
Current Draw ~1 mA idle per LED; up to ~60 mA per LED at full white (~1.4 A total)
Data Protocol Single-wire, 800 kHz, strict timing (library-handled)
Color Depth 24-bit (8 bits per channel, GRB order)
Pads DI (data in), DO (data out), 5V/VCC, GND
Recommended Extras 330-470 ohm resistor on DI; 470-1000 uF capacitor across 5V/GND
Logic Level 5V nominal; 3.3V data usually works (level shifter for reliability)
Viewing Individually addressable — any LED any color at any time

Wiring Guide

Three connections do everything: 5V and GND to the power pads, and your GPIO to DI (data flows one way around the ring — into DI, out of DO). Put a 330-470 ohm resistor close to DI and a 470-1000 uF electrolytic across the power pads. Always connect grounds between the LED supply and the microcontroller.

Arduino Wiring

Ring Pad Arduino Pin Details
DI D6 via 330 ohm Data input
5V 5V (demos) / external 5V 2A (full brightness) 1000 uF cap across supply
GND GND Common with external supply
Warning: The board's 5V pin can feed the ring only at modest brightness. All 24 LEDs at full white pull ~1.4 A — that needs an external 5V supply (grounds joined), or cap brightness in code as the examples do.

ESP32 Wiring

Ring Pad ESP32 Pin Details
DI GPIO 13 via 330 ohm Any output GPIO works
5V VIN (USB 5V) or external 5V Not 3V3 — LEDs want 5V power
GND GND
Note: The ESP32's 3.3V data into 5V-powered pixels is slightly out of spec but works on the vast majority of rings. If you ever see random flicker, add a 74AHCT125 level shifter on DI — or power the ring through a 1N4007 diode (~4.3V), which brings the logic threshold down.

Raspberry Pi Wiring

Ring Pad Pi Pin Details
DI Pin 12 (GPIO 18) via 330 ohm PWM0 — required by rpi_ws281x
5V Pin 2 (5V) for demos / external 5V 2A 1000 uF cap recommended
GND Pin 6 (GND)
Tip: The rpi_ws281x library drives GPIO 18 with DMA and needs sudo. It also conflicts with onboard audio — add dtparam=audio=off to /boot/firmware/config.txt (or blacklist snd_bcm2835) and reboot before first use.

Raspberry Pi Pico Wiring

Ring Pad Pico Pin Details
DI GP0 (pin 1) via 330 ohm PIO drives the timing
5V VBUS (pin 40) or external 5V USB 5V handles medium brightness
GND GND (pin 38)

Code Examples

Each example runs the same three-scene demo — a spinning comet chase, a full-ring rainbow, and a "clock hand" sweep — with brightness capped to stay USB-friendly. Raise the brightness only on an external supply.

Arduino

ring24_arduino.ino
// WS2812 24-LED Ring - Arduino Example
// DI->D6 via 330 ohm, 5V->5V, GND->GND
// Library: "Adafruit NeoPixel" (Library Manager)

#include <Adafruit_NeoPixel.h>

#define PIN        6
#define NUM_LEDS   24

Adafruit_NeoPixel ring(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  ring.begin();
  ring.setBrightness(60);            // 0-255; keep low on USB power
  ring.show();
}

void comet(uint32_t color, int loops) {
  for (int t = 0; t < loops * NUM_LEDS; t++) {
    ring.clear();
    for (int tail = 0; tail < 6; tail++) {
      int i = (t - tail + NUM_LEDS * 8) % NUM_LEDS;
      uint8_t fade = 255 >> tail;
      uint8_t r = (uint8_t)(color >> 16) * fade / 255;
      uint8_t g = (uint8_t)(color >> 8)  * fade / 255;
      uint8_t b = (uint8_t)(color)       * fade / 255;
      ring.setPixelColor(i, r, g, b);
    }
    ring.show();
    delay(40);
  }
}

void rainbow(int loops) {
  for (int j = 0; j < 256 * loops; j++) {
    for (int i = 0; i < NUM_LEDS; i++) {
      ring.setPixelColor(i, ring.gamma32(
        ring.ColorHSV((i * 65536L / NUM_LEDS + j * 256) & 0xFFFF)));
    }
    ring.show();
    delay(10);
  }
}

void clockSweep(int loops) {
  for (int t = 0; t < loops * NUM_LEDS; t++) {
    ring.clear();
    ring.setPixelColor(t % NUM_LEDS, 255, 40, 0);      // hand
    ring.setPixelColor(0, 40, 40, 40);                 // 12 o'clock mark
    ring.show();
    delay(120);
  }
}

void loop() {
  comet(ring.Color(0, 120, 255), 3);
  rainbow(2);
  clockSweep(2);
}

ESP32 (MicroPython)

ring24_esp32.py
# WS2812 24-LED Ring - ESP32 MicroPython Example
# DI->GPIO 13 via 330 ohm, 5V->VIN, GND->GND

from machine import Pin
import neopixel, time

NUM = 24
ring = neopixel.NeoPixel(Pin(13), NUM)
BRIGHT = 0.25                      # global brightness cap

def scale(c):
    return tuple(int(x * BRIGHT) for x in c)

def wheel(pos):
    pos %= 256
    if pos < 85:  return (255 - pos * 3, pos * 3, 0)
    if pos < 170: pos -= 85; return (0, 255 - pos * 3, pos * 3)
    pos -= 170;   return (pos * 3, 0, 255 - pos * 3)

def comet(color, loops=3):
    for t in range(loops * NUM):
        ring.fill((0, 0, 0))
        for tail in range(6):
            i = (t - tail) % NUM
            fade = 1 / (tail + 1)
            ring[i] = scale(tuple(int(x * fade) for x in color))
        ring.write()
        time.sleep_ms(40)

def rainbow(loops=2):
    for j in range(256 * loops):
        for i in range(NUM):
            ring[i] = scale(wheel(i * 256 // NUM + j))
        ring.write()
        time.sleep_ms(10)

def clock_sweep(loops=2):
    for t in range(loops * NUM):
        ring.fill((0, 0, 0))
        ring[t % NUM] = scale((255, 40, 0))
        ring[0] = scale((40, 40, 40))
        ring.write()
        time.sleep_ms(120)

while True:
    comet((0, 120, 255))
    rainbow()
    clock_sweep()

Raspberry Pi (Python)

ring24_rpi.py
#!/usr/bin/env python3
# WS2812 24-LED Ring - Raspberry Pi Example (rpi_ws281x)
# DI->GPIO18 (pin 12) via 330 ohm; run with: sudo python3 ring24_rpi.py
# Install: sudo pip3 install rpi_ws281x  (and disable onboard audio)

import time
from rpi_ws281x import PixelStrip, Color

NUM = 24
strip = PixelStrip(NUM, 18, brightness=60)   # GPIO 18, brightness 0-255
strip.begin()

def comet(color, loops=3):
    for t in range(loops * NUM):
        for i in range(NUM):
            strip.setPixelColor(i, 0)
        for tail in range(6):
            i = (t - tail) % NUM
            fade = 255 // (tail + 1)
            r = ((color >> 16) & 0xFF) * fade // 255
            g = ((color >> 8) & 0xFF) * fade // 255
            b = (color & 0xFF) * fade // 255
            strip.setPixelColor(i, Color(r, g, b))
        strip.show()
        time.sleep(0.04)

def wheel(pos):
    pos %= 256
    if pos < 85:  return Color(255 - pos * 3, pos * 3, 0)
    if pos < 170: pos -= 85; return Color(0, 255 - pos * 3, pos * 3)
    pos -= 170;   return Color(pos * 3, 0, 255 - pos * 3)

def rainbow(loops=2):
    for j in range(256 * loops):
        for i in range(NUM):
            strip.setPixelColor(i, wheel(i * 256 // NUM + j))
        strip.show()
        time.sleep(0.01)

try:
    while True:
        comet(0x0078FF)
        rainbow()
except KeyboardInterrupt:
    for i in range(NUM):
        strip.setPixelColor(i, 0)
    strip.show()
    print("Stopped by user")

Raspberry Pi Pico (MicroPython)

ring24_pico.py
# WS2812 24-LED Ring - Pico MicroPython Example
# DI->GP0 via 330 ohm, 5V->VBUS, GND->GND

from machine import Pin
import neopixel, time

NUM = 24
ring = neopixel.NeoPixel(Pin(0), NUM)
BRIGHT = 0.25

def scale(c):
    return tuple(int(x * BRIGHT) for x in c)

def breathe(color, cycles=3):
    for _ in range(cycles):
        for level in list(range(0, 100)) + list(range(100, 0, -1)):
            ring.fill(tuple(int(x * level / 100 * BRIGHT) for x in color))
            ring.write()
            time.sleep_ms(8)

def spinner(color, loops=4):
    for t in range(loops * NUM):
        ring.fill((0, 0, 0))
        for k in range(3):                      # 3 arms
            ring[(t + k * NUM // 3) % NUM] = scale(color)
        ring.write()
        time.sleep_ms(50)

while True:
    spinner((0, 255, 80))
    breathe((120, 0, 255))

Frequently Asked Questions

Nothing lights up at all. Where do I start?
Confirm you're feeding DI, not DO — data only flows one direction, and the two pads look identical at a glance. Then check 5V is actually reaching the ring (measure at the pads), that grounds are common, and that your code's pin number and LED count match reality. A single dead-looking ring with correct wiring is almost always the DI/DO mix-up.
The first LED flickers or shows the wrong color and the rest work.
Classic marginal-data symptom: the first pixel takes the brunt of reflections and level issues. The 330-470 ohm resistor right at DI fixes most cases; keep the data wire short (<30 cm ideally). If it persists on a 3.3V controller, that's your cue for a level shifter or the diode-drop trick on the ring's supply.
Random white flashes or the whole ring glitches when many LEDs are bright.
Supply sag. Full-white spikes pull far more current than the average animation, the 5V rail dips, and pixels reset or latch garbage. Fixes in order: the 470-1000 uF capacitor across the ring's power pads, thicker/shorter power wires, an external 5V 2A supply, and a brightness cap in software (all the examples ship with one).
Colors come out wrong — red shows green, etc.
WS2812s take data in GRB order, and every library has a setting for it: NEO_GRB in Adafruit NeoPixel (the default in the example), GRB in FastLED's addLeds template, and the Pi/MicroPython libraries default correctly. If a plain red fill shows green, flip the color-order constant — nothing is broken.
Can I chain this ring with other WS2812 strips and rings?
Yes — wire this ring's DO to the next device's DI, share 5V and GND, and address the whole chain as one long strip (this ring is pixels 0-23, the next device continues at 24). Inject 5V power at every 50-100 LEDs along big chains so the far end doesn't brown out and turn orange-ish white.
How do I make it a clock?
24 LEDs maps beautifully: one LED per hour, or use LED = minute*24/60 for a minute hand and blend colors where hands overlap. Add an RTC module (DS3231) or pull time over WiFi/NTP on an ESP32, mark LED 0 as 12 o'clock, and dim everything at night with a brightness schedule. The clockSweep() demo in the Arduino example is the skeleton.
How much power supply do I really need?
Budget 60 mA per LED at absolute full white: 1.44 A for the ring, so a 5V 2A supply covers it with margin. Real animations average far less — a comet chase at 25% brightness draws well under 200 mA. If you enforce a brightness cap of ~50% and avoid full-white fills, USB power runs the ring happily; the cap-and-cap advice (capacitor + current cap) prevents the classic failure modes.

Related Tutorials