For boards like Feather RP2040 SCORPIO, use the adafruit_neopxl8 library to control the LEDs in CircuitPython, rather than the standard neopixel library.

The library works much like the standard neopixel library, but it can control from 1 to 8 LED strands. Just import it, and then create an instance of the NeoPXL8 class:

import board
from adafruit_neopxl8 import NeoPxl8

# Customize for your strands here
num_strands = 8
strand_length = 30
first_led_pin = board.NEOPIXEL0

num_pixels = num_strands * strand_length
pixels = NeoPxl8(
    first_led_pin,
    num_pixels,
    num_strands=num_strands,
    auto_write=False,
    brightness=0.50,
)

"Background" writing

The adafruit_neopxl8 library is fast for two reasons. Not only does it send the data for up to 8 strands at once, it also sends the data "in the background", while your CircuitPython code can do things like check for button presses & update animations. With care in coding the rest of your CircuitPython program, you can get some really high update rates for your LED displays.

About pixel numbering

In CircuitPython's NeoPXL8, the pixels from all the strands are interleaved. In other words, say you have a 8 strands of 30 LEDs. The first 30 pixels are the pixels in the first strand; the next 30 pixels are the second pixel from each strand; and so on. Or, to put it another way, the pixels of the first strand are the numbers in range(0,30), the pixels of the second strand are range(30, 60), etc.

You can use the PixelMap feature of the LED Animation Library to build an object that lets you address each strand individually:

from adafruit_led_animation.helper import PixelMap

def strand(n):
    return PixelMap(
        pixels,
        range(n * strand_length, (n + 1) * strand_length),
        individual_pixels=True,
    )

strands = [strand(i) for i in range(num_strands)]

Now, you can refer to strands[S][P] to mean `strand #S, pixel #P`, or use `strand[S]` to refer to the whole strand—for instance, to call the fill method or to use with an LED animation.  Note that you want to call the strand function just once per strand and store the result. You can store them all in a list, like this example does; or you could give them names—for instance, if you you have 4 strands on the 4 sides of a window you might want to write top=strand(0) and bottom=strand(1), for instance. Any of the other pixel mapping helpers from the LED Animation Library work equally well with NeoPXL8.

Using Animations with NeoPXL8

import rainbowio
from adafruit_led_animation.animation.comet import Comet
from adafruit_led_animation.group import AnimationGroup

# For each strand, create a comet animation of a different color
animations = [
    Comet(strand, 0.02, rainbowio.colorwheel(3 * 32 * i), ring=True)
    for i, strand in enumerate(strands)
]

# Group them so we can run them all at once
animations = AnimationGroup(*animations)

while True:
    animations.animate()

Here's the full example code. Use the 'download project bundle' link to get all the files you need to run it:

File
# SPDX-FileCopyrightText: 2022 Jeff Epler
#
# SPDX-License-Identifier: Unlicense

import board
import rainbowio
import adafruit_ticks
from adafruit_led_animation.animation.comet import Comet
from adafruit_led_animation.group import AnimationGroup
from adafruit_led_animation.helper import PixelMap
from adafruit_neopxl8 import NeoPxl8

# Customize for your strands here
num_strands = 8
strand_length = 30
first_led_pin = board.NEOPIXEL0

num_pixels = num_strands * strand_length

# Make the object to control the pixels
pixels = NeoPxl8(
    first_led_pin,
    num_pixels,
    num_strands=num_strands,
    auto_write=False,
    brightness=0.50,
)


def strand(n):
    return PixelMap(
        pixels,
        range(n * strand_length, (n + 1) * strand_length),
        individual_pixels=True,
    )


# Create the 8 virtual strands
strands = [strand(i) for i in range(num_strands)]

# For each strand, create a comet animation of a different color
animations = [
    Comet(strand, 0.02, rainbowio.colorwheel(3 * 32 * i), ring=True)
    for i, strand in enumerate(strands)
]

# Advance the animations by varying amounts so that they become staggered
for i, animation in enumerate(animations):
    animation._tail_start = 30 * 5 * i // 8  # pylint: disable=protected-access

# Group them so we can run them all at once
animations = AnimationGroup(*animations)

# Run the animations and report on the speed in frame per secodn
t0 = adafruit_ticks.ticks_ms()
frame_count = 0
while True:
    animations.animate()
    frame_count += 1
    t1 = adafruit_ticks.ticks_ms()
    dt = adafruit_ticks.ticks_diff(t1, t0)
    if dt > 1000:
        print(f"{frame_count * 1000/dt:.1f}fps")
        t0 = t1
        frame_count = 0

This guide was first published on Dec 27, 2022. It was last updated on Mar 28, 2024.

This page (Using adafruit_neopxl8) was last updated on Mar 27, 2024.

Text editor powered by tinymce.