Overview
This compact ring puts 16 WS2812 addressable RGB LEDs in a circle you can drive from a single GPIO pin. Each LED carries its own driver chip, so all 16 take independent 24-bit colors: a rainbow halo, a spinning loader, a countdown timer, a volume dial, or one pixel quietly breathing as a status light. Once colors are latched, the ring holds them with zero attention from your code.
The 16-pixel size is the go-to for accents and interfaces — button halos, knob surrounds, eyes for robots and props, small clock faces, wearable badges — anywhere the 24-LED ring would be too large. Pads on the back carry 5V, GND, data-in (DI), and data-out (DO); DO chains into another ring or strip's DI, letting you build compound layouts (nested rings, ring-plus-strip gauges) that your code addresses as one long pixel run.
It follows the standard WS2812 recipe: 5V power sized for ~60 mA per LED at full white (just under 1 A for the ring, though sane animations draw far less), a 330-470 ohm resistor in the data line, a chunky capacitor across power, and any of the universal libraries — Adafruit NeoPixel or FastLED on Arduino/ESP32, MicroPython's neopixel on ESP32 and Pico, rpi_ws281x on the Raspberry Pi.
At a Glance
Specifications
| Parameter | Value |
| LED Type | WS2812 (WS2812B-class) integrated RGB + driver, 5050 package |
| LED Count | 16, evenly spaced around the ring |
| Supply Voltage | 5V DC (4.5 - 5.5V) |
| Current Draw | ~1 mA/LED idle; up to ~60 mA/LED at full white (~0.96 A total) |
| Data Protocol | Single-wire 800 kHz, strict timing (library-handled) |
| Color Depth | 24-bit (8 bits/channel, GRB order) |
| Pads | DI (data in), DO (data out), 5V/VCC, GND |
| Recommended Extras | 330-470 ohm on DI; 470-1000 uF across 5V/GND |
| Logic Level | 5V nominal; 3.3V data usually works (shift if unstable) |
| Angular Resolution | 22.5° per LED — ideal for dials and loaders |
Wiring Guide
Three wires: 5V, GND, and GPIO to DI through a 330-470 ohm resistor (DO stays free unless chaining). Add the capacitor across power, and always common the grounds between LED supply and controller.
Arduino Wiring
| Ring Pad | Arduino Pin | Details |
|---|---|---|
| DI | D6 via 330 ohm | Data input |
| 5V | 5V | 16 LEDs are USB-friendly at capped brightness |
| GND | GND |
ESP32 Wiring
| Ring Pad | ESP32 Pin | Details |
|---|---|---|
| DI | GPIO 13 via 330 ohm | Any output GPIO |
| 5V | VIN (USB 5V) | Power at 5V, not 3V3 |
| GND | GND |
Raspberry Pi Wiring
| Ring Pad | Pi Pin | Details |
|---|---|---|
| DI | Pin 12 (GPIO 18) via 330 ohm | PWM0 — rpi_ws281x requirement |
| 5V | Pin 2 (5V) | |
| GND | Pin 6 (GND) |
Raspberry Pi Pico Wiring
| Ring Pad | Pico Pin | Details |
|---|---|---|
| DI | GP0 (pin 1) via 330 ohm | PIO handles the timing |
| 5V | VBUS (pin 40) | USB 5V |
| GND | GND (pin 38) |
Code Examples
Each example demos patterns that suit a 16-pixel ring: a loading spinner, a battery/volume-style gauge, and a breathing status glow — all brightness-capped for USB power.
Arduino
// WS2812 16-LED Ring - Arduino Example
// DI->D6 via 330 ohm, 5V->5V, GND->GND
// Library: "Adafruit NeoPixel"
#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NUM 16
Adafruit_NeoPixel ring(NUM, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
ring.begin();
ring.setBrightness(60);
ring.show();
}
void spinner(uint32_t color, int loops) {
for (int t = 0; t < loops * NUM; t++) {
ring.clear();
ring.setPixelColor(t % NUM, color);
ring.setPixelColor((t + 1) % NUM, color); // 2-px head
ring.setPixelColor((t + NUM - 1) % NUM,
ring.Color(10, 10, 30)); // faint tail
ring.show();
delay(60);
}
}
void gauge(int percent) { // 0-100
int lit = map(percent, 0, 100, 0, NUM);
ring.clear();
for (int i = 0; i < lit; i++) {
// green -> yellow -> red as the gauge fills
uint8_t r = map(i, 0, NUM - 1, 0, 255);
uint8_t g = map(i, 0, NUM - 1, 255, 0);
ring.setPixelColor(i, r, g, 0);
}
ring.show();
}
void loop() {
spinner(ring.Color(0, 120, 255), 4);
for (int p = 0; p <= 100; p += 5) { // gauge sweep
gauge(p);
delay(80);
}
delay(600);
}
ESP32 (MicroPython)
# WS2812 16-LED Ring - ESP32 MicroPython Example
# DI->GPIO 13 via 330 ohm, 5V->VIN, GND->GND
from machine import Pin
import neopixel, time
NUM = 16
ring = neopixel.NeoPixel(Pin(13), NUM)
BRIGHT = 0.25
def scale(c):
return tuple(int(v * BRIGHT) for v in c)
def spinner(color, loops=4):
for t in range(loops * NUM):
ring.fill((0, 0, 0))
ring[t % NUM] = scale(color)
ring[(t + 1) % NUM] = scale(color)
ring[(t - 1) % NUM] = scale((10, 10, 30))
ring.write()
time.sleep_ms(60)
def gauge(percent):
lit = percent * NUM // 100
ring.fill((0, 0, 0))
for i in range(lit):
r = i * 255 // (NUM - 1)
g = 255 - r
ring[i] = scale((r, g, 0))
ring.write()
def breathe(color, cycles=2):
for _ in range(cycles):
for lv in list(range(0, 100)) + list(range(100, 0, -1)):
ring.fill(tuple(int(v * lv / 100 * BRIGHT) for v in color))
ring.write()
time.sleep_ms(10)
while True:
spinner((0, 120, 255))
for p in range(0, 101, 5):
gauge(p)
time.sleep_ms(80)
breathe((150, 0, 255))
Raspberry Pi (Python)
#!/usr/bin/env python3
# WS2812 16-LED Ring - Raspberry Pi Example (rpi_ws281x)
# DI->GPIO18 (pin 12) via 330 ohm; run with sudo
# Install: sudo pip3 install rpi_ws281x
import time
from rpi_ws281x import PixelStrip, Color
NUM = 16
strip = PixelStrip(NUM, 18, brightness=60)
strip.begin()
def clear():
for i in range(NUM):
strip.setPixelColor(i, 0)
def spinner(color, loops=4):
for t in range(loops * NUM):
clear()
strip.setPixelColor(t % NUM, color)
strip.setPixelColor((t + 1) % NUM, color)
strip.show()
time.sleep(0.06)
def gauge(percent):
lit = percent * NUM // 100
clear()
for i in range(lit):
r = i * 255 // (NUM - 1)
g = 255 - r
strip.setPixelColor(i, Color(r, g, 0))
strip.show()
try:
while True:
spinner(Color(0, 120, 255))
for p in range(0, 101, 5):
gauge(p)
time.sleep(0.08)
time.sleep(0.6)
except KeyboardInterrupt:
clear(); strip.show()
print("Stopped by user")
Raspberry Pi Pico (MicroPython)
# WS2812 16-LED Ring - Pico MicroPython Example
# DI->GP0 via 330 ohm, 5V->VBUS, GND->GND
# Countdown-timer demo: ring empties over N seconds, then flashes.
from machine import Pin
import neopixel, time
NUM = 16
ring = neopixel.NeoPixel(Pin(0), NUM)
BRIGHT = 0.25
def scale(c):
return tuple(int(v * BRIGHT) for v in c)
def countdown(seconds=16):
per_led = seconds / NUM
for lit in range(NUM, 0, -1):
ring.fill((0, 0, 0))
for i in range(lit):
color = (0, 255, 60) if lit > NUM // 4 else (255, 30, 0)
ring[i] = scale(color)
ring.write()
time.sleep(per_led)
def flash(color, times=4):
for _ in range(times):
ring.fill(scale(color)); ring.write(); time.sleep_ms(120)
ring.fill((0, 0, 0)); ring.write(); time.sleep_ms(120)
while True:
countdown(16) # 1 LED per second
flash((255, 0, 0))
time.sleep(1)