Skip to content

Instantly share code, notes, and snippets.

@schappim
Created November 6, 2025 05:17
Show Gist options
  • Save schappim/c6f91599b4c9efdba817bc5eeb39e116 to your computer and use it in GitHub Desktop.
Save schappim/c6f91599b4c9efdba817bc5eeb39e116 to your computer and use it in GitHub Desktop.

🧰 What You’ll Need

  • Raspberry Pi 5 running Raspberry Pi OS
  • Little Bird Toucan HAT (8 × APA102 LEDs)
  • Python 3
  • The spidev and time modules (standard or installable via apt/pip)

⚙️ Wiring / Setup

The Toucan plugs directly on the Pi’s 40-pin header. The APA102 uses two data lines:

Toucan Pin Function Pi GPIO Pin BCM Function
CI Clock Input Pin 23 GPIO 11 (SCLK)
DI Data Input Pin 19 GPIO 10 (MOSI)
5 V Power Pin 2 or 4 5 V
GND Ground Pin 6 GND

💡 No level-shifting is required on a Pi 5 — its 3.3 V logic is sufficient for APA102s at 5 V.


🧑‍💻 Enable SPI

  1. Open a terminal on your Pi.
  2. Run sudo raspi-config.
  3. Navigate to Interface Options → SPI → Enable.
  4. Reboot with sudo reboot.

Verify it’s active:

ls /dev/spidev*
# → /dev/spidev0.0 should exist

🐍 Demo Python Code

Save as toucan_demo.py:

#!/usr/bin/env python3
import spidev
import time

NUM_LEDS = 8  # Little Bird Toucan = 8 LEDs

# Open SPI interface (Bus 0, Device 0)
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 8000000  # 8 MHz is typical for APA102
spi.mode = 0b00

def send_frame(data):
    spi.xfer2(data)

def set_color(index, r, g, b, brightness=31):
    """Return 4 bytes for one LED (APA102 format)."""
    brightness = max(0, min(brightness, 31))
    prefix = 0b11100000 | brightness
    return [prefix, b & 0xFF, g & 0xFF, r & 0xFF]

def show(colors):
    """Send data to LEDs."""
    # Start frame (32 bits of zeros)
    send_frame([0x00, 0x00, 0x00, 0x00])

    # LED frames
    frame = []
    for color in colors:
        frame.extend(color)
    send_frame(frame)

    # End frame (at least num_leds/2 bits of 1s)
    end_bytes = [0xFF] * ((NUM_LEDS + 15) // 16)
    send_frame(end_bytes)

def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return (pos * 3, 255 - pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return (255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return (0, pos * 3, 255 - pos * 3)

try:
    print("Little Bird Toucan demo running! Press Ctrl+C to stop.")
    i = 0
    while True:
        colors = []
        for j in range(NUM_LEDS):
            r, g, b = wheel((int(i + j * 256 / NUM_LEDS)) & 255)
            colors.append(set_color(j, r, g, b))
        show(colors)
        i = (i + 1) % 256
        time.sleep(0.05)
except KeyboardInterrupt:
    show([set_color(0, 0, 0, 0)] * NUM_LEDS)
    spi.close()
    print("\nGoodbye!")

Run it:

python3 toucan_demo.py

Your Toucan HAT should now display a smooth rotating rainbow animation 🌈.


🧩 Explanation

  • SPI sends clock (CKI) and data (SDI) in sync — matching the APA102’s interface spec (page 5 Application Circuit shows chaining via SDI/SDO and CKI/CKO).
  • Each LED frame = 32 bits: 0b111xxxxx (global brightness) + Blue + Green + Red.
  • Start/End frames (zeros/ones) ensure proper latching.

⚠️ Safety

  • The APA102C runs at 5 V ± 0.5 V, typical current ~20 mA per LED.
  • Don’t stare at LEDs directly at full brightness — they’re very bright.

#!/usr/bin/env python3
import spidev
import time
NUM_LEDS = 8 # Little Bird Toucan = 8 LEDs
# Open SPI interface (Bus 0, Device 0)
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 8000000 # 8 MHz is typical for APA102
spi.mode = 0b00
def send_frame(data):
spi.xfer2(data)
def set_color(index, r, g, b, brightness=31):
"""Return 4 bytes for one LED (APA102 format)."""
brightness = max(0, min(brightness, 31))
prefix = 0b11100000 | brightness
return [prefix, b & 0xFF, g & 0xFF, r & 0xFF]
def show(colors):
"""Send data to LEDs."""
# Start frame (32 bits of zeros)
send_frame([0x00, 0x00, 0x00, 0x00])
# LED frames
frame = []
for color in colors:
frame.extend(color)
send_frame(frame)
# End frame (at least num_leds/2 bits of 1s)
end_bytes = [0xFF] * ((NUM_LEDS + 15) // 16)
send_frame(end_bytes)
def wheel(pos):
"""Generate rainbow colors across 0-255 positions."""
if pos < 85:
return (pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return (255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return (0, pos * 3, 255 - pos * 3)
try:
print("Little Bird Toucan demo running! Press Ctrl+C to stop.")
i = 0
while True:
colors = []
for j in range(NUM_LEDS):
r, g, b = wheel((int(i + j * 256 / NUM_LEDS)) & 255)
colors.append(set_color(j, r, g, b))
show(colors)
i = (i + 1) % 256
time.sleep(0.05)
except KeyboardInterrupt:
show([set_color(0, 0, 0, 0)] * NUM_LEDS)
spi.close()
print("\nGoodbye!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment