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.

Revisions

  1. schappim created this gist Nov 6, 2025.
    134 changes: 134 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    ## 🧰 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:

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

    ---

    ## 🐍 Demo Python Code

    Save as `toucan_demo.py`:

    ```python
    #!/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:

    ```bash
    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**.

    ---
    62 changes: 62 additions & 0 deletions toucan_demo.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    #!/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!")