Addressable LEDs with Raspberry Pi Zero: Complete Guide
What Are Addressable LEDs?
Addressable LEDs (WS2812B/NeoPixel) are RGB LEDs where each pixel is individually controllable via a single data line. Perfect for:
- Ambient lighting
- Status indicators
- Decorative displays
- Motion detection lighting
- Smart home automation
Hardware Requirements
Shopping List
| Item | Qty | Cost | Notes |
|---|---|---|---|
| Pi Zero 2W | 1 | $15 | With 40-pin header |
| WS2812B LED Strip | 1m | $10 | 30 LEDs/meter |
| 5V Power Supply | 1 | $8 | 2A minimum for 30 LEDs |
| 470Ω Resistor | 1 | $0.10 | Data line protection |
| Capacitor 1000µF | 1 | $0.50 | Power smoothing |
| Breadboard | 1 | $3 | For prototyping |
| Jumper Wires | - | $5 | Various sizes |
Total: ~$40
Wiring Diagram
Pi Zero GPIO 18 (PWM) → 470Ω Resistor → WS2812B Data (DIN)
Pi Zero 5V → Capacitor → WS2812B Power (5V)
Pi Zero GND → WS2812B GND (GND)
Power Supply:
5V Power → LED Strip Power (5V)
GND Power → LED Strip GND + Pi Zero GND (shared ground)
Pinout Reference
Pi Zero 40-pin Header:
PIN 1: 3.3V
PIN 2: 5V ← Use for capacitor positive
PIN 6: GND ← Shared ground
PIN 12: GPIO 18 ← Data line (PWM)
PIN 14: GND
PIN 39: GND
PIN 40: GPIO 21
Installation and Setup
1. Install Required Libraries
sudo apt update
sudo apt install python3-pip
# Install Adafruit NeoPixel library
pip3 install adafruit-circuitpython-neopixel
# Or for more control
pip3 install rpi_ws281x
pip3 install adafruit-blinka
2. Enable SPI (for some libraries)
sudo raspi-config
# Interfacing Options → SPI → Enable
3. Test Setup
#!/usr/bin/env python3
import board
import neopixel
import time
# Configure LED strip (30 pixels on GPIO 18)
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.5)
# Test: Red
pixels.fill((255, 0, 0))
pixels.show()
time.sleep(1)
# Test: Green
pixels.fill((0, 255, 0))
pixels.show()
time.sleep(1)
# Test: Blue
pixels.fill((0, 0, 255))
pixels.show()
Run it:
python3 test_leds.py
Basic Animations
Rainbow Effect
#!/usr/bin/env python3
import board
import neopixel
import time
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.8)
def wheel(pos):
"""Generate rainbow colors"""
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, 0, pos * 3)
elif pos < 170:
pos -= 85
return (0, pos * 3, 255 - pos * 3)
else:
pos -= 170
return (pos * 3, 255 - pos * 3, 0)
def rainbow_cycle(wait):
"""Rainbow animation"""
for j in range(256):
for i in range(len(pixels)):
rc_index = (i * 256 // len(pixels)) + j
pixels[i] = wheel(rc_index & 255)
pixels.show()
time.sleep(wait)
# Run animation
try:
while True:
rainbow_cycle(0.02)
except KeyboardInterrupt:
pixels.fill((0, 0, 0))
pixels.show()
Chase Effect
def chase(color, wait):
"""Chase animation"""
for i in range(len(pixels)):
pixels[i] = color
pixels.show()
time.sleep(wait)
pixels[i] = (0, 0, 0)
while True:
chase((255, 0, 0), 0.05)
chase((0, 255, 0), 0.05)
chase((0, 0, 255), 0.05)
Breathing Effect
import math
def breathing(color, cycle_time=1.0):
"""Smooth breathing animation"""
start_time = time.time()
while True:
elapsed = (time.time() - start_time) % cycle_time
brightness = (math.sin(elapsed / cycle_time * 2 * math.pi) + 1) / 2
r, g, b = color
pixels.fill((
int(r * brightness),
int(g * brightness),
int(b * brightness)
))
pixels.show()
time.sleep(0.02)
breathing((0, 255, 0), cycle_time=2.0)
Motion-Activated Lights
PIR Sensor Integration
#!/usr/bin/env python3
import board
import neopixel
import RPi.GPIO as GPIO
import time
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.6)
pir_pin = 27
GPIO.setmode(GPIO.BCM)
GPIO.setup(pir_pin, GPIO.IN)
def light_on(color=(255, 255, 255)):
"""Turn lights on"""
pixels.fill(color)
pixels.show()
def light_off():
"""Turn lights off"""
pixels.fill((0, 0, 0))
pixels.show()
try:
while True:
if GPIO.input(pir_pin):
print("Motion detected!")
light_on((100, 150, 255)) # Cool white
time.sleep(30) # Stay on for 30 seconds
light_off()
time.sleep(0.5)
except KeyboardInterrupt:
light_off()
GPIO.cleanup()
Temperature-Based Color Change
Using Temperature Sensor
#!/usr/bin/env python3
import board
import neopixel
import adafruit_dht
import time
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.7)
sensor = adafruit_dht.DHT22(board.D4)
def temp_to_color(temp):
"""Map temperature to color"""
if temp < 15:
return (0, 0, 255) # Blue (cold)
elif temp < 20:
return (0, 255, 255) # Cyan
elif temp < 25:
return (0, 255, 0) # Green (comfortable)
elif temp < 30:
return (255, 255, 0) # Yellow
else:
return (255, 0, 0) # Red (hot)
try:
while True:
temp = sensor.temperature
color = temp_to_color(temp)
pixels.fill(color)
pixels.show()
print(f"Temperature: {temp:.1f}°C → Color: {color}")
time.sleep(60)
except Exception as err:
print(f"Error: {err}")
pixels.fill((0, 0, 0))
pixels.show()
Status Indicators
Multi-Color Status
#!/usr/bin/env python3
import board
import neopixel
import subprocess
import time
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.8)
def get_system_status():
"""Check system health"""
# CPU temperature
result = subprocess.run(['vcgencmd', 'measure_temp'],
capture_output=True, text=True)
temp = float(result.stdout.split('=')[1].split("'")[0])
# Disk usage
result = subprocess.run(['df', '/'], capture_output=True, text=True)
lines = result.stdout.split('\n')
disk_usage = int(lines[1].split()[4].rstrip('%'))
return {
'temp': temp,
'disk': disk_usage,
}
def status_to_color(status):
"""Convert status to color"""
if status['temp'] > 80:
return (255, 0, 0) # Red: Overheating
elif status['disk'] > 90:
return (255, 165, 0) # Orange: Disk full
elif status['temp'] > 70:
return (255, 255, 0) # Yellow: Warm
else:
return (0, 255, 0) # Green: OK
try:
while True:
status = get_system_status()
color = status_to_color(status)
pixels.fill(color)
pixels.show()
print(f"Temp: {status['temp']}°C, Disk: {status['disk']}%")
time.sleep(30)
except KeyboardInterrupt:
pixels.fill((0, 0, 0))
pixels.show()
Music Reactive Lights
Audio Visualization
#!/usr/bin/env python3
import board
import neopixel
import numpy as np
from pyaudio import PyAudio
import time
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.7)
# Audio setup
p = PyAudio()
stream = p.open(format=8,
channels=1,
rate=44100,
input=True,
frames_per_buffer=1024)
try:
while True:
# Read audio data
data = np.frombuffer(stream.read(1024), dtype=np.uint8)
# Calculate frequency bands
fft = np.fft.fft(data)
magnitude = np.abs(fft[:512])
# Map frequencies to LED positions
for i in range(30):
freq_band = int(magnitude[i * 17] / 256 * 255)
# Create color from frequency
hue = int(i * 12 % 360) # Spectrum of colors
pixels[i] = hsv_to_rgb(hue, 255, freq_band)
pixels.show()
except KeyboardInterrupt:
stream.stop_stream()
stream.close()
p.terminate()
pixels.fill((0, 0, 0))
pixels.show()
def hsv_to_rgb(h, s, v):
"""Convert HSV to RGB"""
import colorsys
r, g, b = colorsys.hsv_to_rgb(h/360, s/255, v/255)
return (int(r*255), int(g*255), int(b*255))
Web Interface for Control
Flask App
#!/usr/bin/env python3
from flask import Flask, render_template, request, jsonify
import board
import neopixel
import threading
app = Flask(__name__)
pixels = neopixel.NeoPixel(board.D18, 30, brightness=0.8)
current_color = [255, 255, 255]
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/color', methods=['GET', 'POST'])
def color():
global current_color
if request.method == 'POST':
data = request.json
current_color = [data['r'], data['g'], data['b']]
pixels.fill(tuple(current_color))
pixels.show()
return jsonify({'status': 'ok'})
return jsonify({'color': current_color})
@app.route('/api/brightness', methods=['POST'])
def brightness():
data = request.json
pixels.brightness = data['value'] / 100
pixels.show()
return jsonify({'status': 'ok'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
HTML Template
<!DOCTYPE html>
<html>
<head>
<title>LED Control</title>
<style>
body { font-family: Arial; max-width: 600px; margin: 50px auto; }
.control { margin: 20px 0; }
input[type="color"] { width: 200px; height: 50px; }
input[type="range"] { width: 100%; }
</style>
</head>
<body>
<h1>LED Control Panel</h1>
<div class="control">
<label>Color:</label>
<input type="color" id="colorPicker" value="#ffffff">
</div>
<div class="control">
<label>Brightness:</label>
<input type="range" id="brightness" min="0" max="100" value="80">
<span id="brightnessValue">80%</span>
</div>
<script>
document.getElementById('colorPicker').addEventListener('change', (e) => {
const hex = e.target.value;
const r = parseInt(hex.substr(1,2), 16);
const g = parseInt(hex.substr(3,2), 16);
const b = parseInt(hex.substr(5,2), 16);
fetch('/api/color', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({r, g, b})
});
});
document.getElementById('brightness').addEventListener('input', (e) => {
const value = e.target.value;
document.getElementById('brightnessValue').textContent = value + '%';
fetch('/api/brightness', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({value})
});
});
</script>
</body>
</html>
Troubleshooting
LEDs Not Lighting
# Check GPIO permissions
ls -la /dev/gpiomem
sudo usermod -a -G gpio pi
# Verify power connection
# Use multimeter: should read ~5V across power pins
Colors Look Wrong
- Red appears green: Check data line polarity
- Dim colors: Increase brightness or check power supply capacity
- Flickering: Add capacitor across power pins
Noise/Interference
- Use shielded cable for data line
- Keep power and data lines separate
- Add pull-up resistor (10kΩ) on data line to 3.3V
Performance Tips
# Avoid repeated operations
# BAD: inside loop
import time
# GOOD: move outside
start = time.time()
# Batch updates
pixels[0:10] = [(255, 0, 0)] * 10 # Set multiple at once
pixels.show() # Only update once
Power Calculation
Each LED maximum: 60mA (at full white)
30 LEDs: 30 × 60mA = 1.8A
Safe: 2A power supply minimum
Recommended: 3A+ for brightness headroom
Advanced Projects
- Spectrum Analyzer: Sync with music
- Room Occupancy Indicator: Motion → color
- Weather Display: Forecast → LED color
- Gaming Light Sync: Response to game events
- Clock Display: Time represented as colors
Conclusion
Addressable LEDs on Raspberry Pi Zero create amazing visual effects with minimal components. Start simple with solid colors and progress to complex animations.