Learn

Chaining NeoPixel/WS2812 LEDs on a Raspberry Pi 4B

Wire a chain of NeoPixel/WS2812 addressable LEDs to a Raspberry Pi 4B, drive them with the real rpi_ws281x library, and give each unit its own color over a single data wire — daisy-chaining DOUT to DIN, with color data correctly interpreted in the real 24-bit GRB wire order.

Note: the NeoPixel (WS2812) is a VIP-tier component in the simulator — you’ll need a VIP plan to use it there.

Try this directly in the free Raspberry Pi 4B simulator — no hardware or signup required. New to the GPIO header? Start with the interactive pinout guide.

What you’ll need

  • Raspberry Pi 4B
  • 3x NeoPixel (WS2812)

Step by step

  1. Drag Raspberry Pi 4B onto the Canvas.
  2. Drag three NeoPixel (WS2812) components onto the Canvas — each one is a single, real WS2812 unit. Chaining them (like a real LED strip) is done entirely by wiring, not by placing one "strip" component.

Hover over a NeoPixel's pins to confirm the 4 labels: VDD (top-left), DIN (top-right), DOUT (bottom-left), GND (bottom-right). DIN is where data comes IN from the previous unit (or the Pi, for the first one); DOUT is where that same unit passes the REST of the data on to the next unit in the chain.

  1. Power every one of the 3 units — DIN/DOUT only ever carries data, never power, so each unit needs its own VDD/GND wired:
NeoPixel pinWire to
VDD (all 3 units)a Pi 5V pin
GND (all 3 units)a Pi GND pin
  1. Wire ONLY the first unit's DIN to a Pi GPIO pin (e.g. physical pin 12) — this is the head of the chain. The other two units never get a direct wire back to the Pi at all.
  2. Chain the rest by wiring each unit's DOUT to the NEXT unit's DIN: first unit's DOUT → second unit's DIN, second unit's DOUT → third unit's DIN.
  3. Go to the Code tab and write a script using the real rpi_ws281x library:
from rpi_ws281x import PixelStrip, Color
 
LED_COUNT = 3 # how many chained units you actually wired
LED_PIN = 18 # a BCM pin number — see the note below
 
strip = PixelStrip(LED_COUNT, LED_PIN)
strip.begin()
 
strip.setPixelColor(0, Color(255, 0, 0)) # first unit (the one wired to the Pi): red
strip.setPixelColor(1, Color(0, 255, 0)) # second unit: green
strip.setPixelColor(2, Color(0, 0, 255)) # third unit: blue
strip.show()

The LED_PIN argument is a real gotcha worth knowing: rpi_ws281x doesn't go through GPIO.setmode(GPIO.BOARD)/RPi.GPIO at all — on real hardware it drives the Pi's PWM/DMA hardware directly, and its pin argument is always a BCM GPIO number (18 is the standard default, the PWM0-capable pin), never the physical/BOARD pin number you use everywhere else in this app. If you wired the first unit's DIN to physical pin 12, that's BCM18 — the two numbers refer to the exact same physical pin, just in two different numbering systems.

  1. Click Start.

The first unit lights solid red, the second green, the third blue — one call to show() transmits all three colors down the single data wire at once, and each unit peels off its own color before passing the rest along. Color(255, 0, 0) genuinely renders red (not swapped with green) — this simulator decodes the real WS2812 wire order (green, then red, then blue) correctly under the hood, so setPixelColor() behaves exactly like it would on real hardware.

  1. Try a simple chase effect to see the chain update live:
import asyncio
from rpi_ws281x import PixelStrip, Color
 
LED_COUNT = 3
LED_PIN = 18
 
strip = PixelStrip(LED_COUNT, LED_PIN)
strip.begin()
 
colors = [Color(255, 0, 0), Color(0, 255, 0), Color(0, 0, 255)]
position = 0
 
while not should_stop():
for i in range(LED_COUNT):
strip.setPixelColor(i, colors[(i + position) % len(colors)])
strip.show()
position += 1
await asyncio.sleep(0.4)

The colors visibly rotate down the chain — the same PixelStrip.show() call redraws all 3 units together on every loop iteration.

What “working correctly” looks like

  • Calling show() after setPixelColor() lights each unit with its own real color — Color(255, 0, 0) shows red, Color(0, 255, 0) shows green, Color(0, 0, 255) shows blue, never swapped.
  • Only the FIRST unit in the chain needs its DIN wired to the Pi — every later unit gets its data purely through the previous unit's DOUT, no separate wire back to the Pi needed.
  • Every unit still needs its own VDD/GND wired for power — a chained unit with data reaching it but no power stays dark.
  • Declaring fewer pixels in PixelStrip(n, pin) than you've actually wired leaves the extra chained units exactly as they were before (usually off) — a shorter frame simply never reaches them, matching real WS2812 behavior.
  • The whole chain goes dark on Stop and starts fresh (all off) on the next Start — matching every other GPIO-adjacent simulated buffer in this app.

If something’s wrong

  • Nothing lights at all → confirm VDD/GND are wired on EVERY unit in the chain, not just the first one — an unpowered unit stays dark even if data reaches it correctly.
  • Only the first unit lights, the rest stay dark → confirm DOUT of one unit is wired to DIN of the next — data only reaches downstream units through this daisy chain, never through a second wire back to the Pi.
  • Colors look swapped or wrong (e.g. what should be red shows as green) → double-check you're passing red/green/blue to Color() in that exact order (Color(red, green, blue)) — this simulator itself correctly handles the real GRB wire-order conversion internally, so a swapped result almost always means the arguments were swapped in the script, not a simulator bug.
  • The color never updates → confirm you're calling strip.show() after setPixelColor() — setPixelColor() only writes to a local buffer in your script; show() is the one call that actually sends it to the LEDs.
  • A pin/PixelStrip-related error → confirm LED_PIN is a BCM number (18 for the standard wiring above), not the physical/BOARD pin number — rpi_ws281x never uses GPIO.setmode(GPIO.BOARD)'s numbering.