Install Libraries
Download the latest version of the Circuit Python Library Repository using the button above. Be sure the library version matches the version of CircuitPython you just installed (i.e. if you installed CircuitPython 6.x, install the 6.x version of the library files).
Copy the following files into a directory called /lib on your CIRCUITPY drive:
- adafruit_bus_device (directory)
- adafruit_circuitplayground (directory)
- adafruit_led_animation (directory)
- adafruit_lis3dh.mpy
- adafruit_thermistor.mpy
- neopixel.mpy
When you're done, your CIRCUITPY drive should look like this:
Simplified Code
This sample code demonstrates how to use a rotary encoder to control the speed and the brightness of animations from the LED Animations Library. It also uses the rotary encoder's push button to turn the lights on and off with a software toggle switch.
This code also demonstrates how to run animations on two different LED strips or pins at the same time: in this case, the lights on pin A1 and the lights on the face of the Circuit Playground board.
I've included the full chandelier code, including pixel mapping and multiple layered animations, at the bottom of this page.
# SPDX-FileCopyrightText: 2021 Erin St Blaine for Adafruit Industries # # SPDX-License-Identifier: MIT """ Crystal Chandelier with Circuit Playground BlueFruit Adafruit invests time and resources providing this open source code. Please support Adafruit and open source hardware by purchasing products from Adafruit! Written by Erin St Blaine & Limor Fried for Adafruit Industries Copyright (c) 2020-2021 Adafruit Industries Licensed under the MIT license. All text above must be included in any redistribution. """ # pylint: disable=import-error # pylint: disable=no-member import board import digitalio import rotaryio import neopixel from adafruit_circuitplayground import cp from adafruit_led_animation.group import AnimationGroup from adafruit_led_animation.animation.comet import Comet from adafruit_led_animation.animation.solid import Solid from adafruit_led_animation.color import ( BLACK, PURPLE, ) NEOPIXEL_PIN = board.A1 # NeoPixels connected here (crystals + rings) NUM_PIXELS = 92 # Number of pixels in the crystals and rings MIN_BRIGHTNESS = 1 # Minimum LED brightness as a percentage (0 to 100) MAX_BRIGHTNESS = 25 # Maximum LED brighrness as a percentage (0 to 100) MIN_FPS = 2 # Minimum animation speed in frames per second (>0) MAX_FPS = 8 # Maximum animation speed in frames per second (>0) # Set initial brightness and speed to center values BRIGHTNESS = (MIN_BRIGHTNESS + MAX_BRIGHTNESS) // 2 FPS = (MIN_FPS + MAX_FPS) // 2 SPEED = 1 / FPS # Integer frames-per-second to seconds interval LEVEL = BRIGHTNESS * 0.01 # Integer brightness percentage to 0.0-1.0 coeff. button = digitalio.DigitalInOut(board.A5) button.direction = digitalio.Direction.INPUT button.pull = digitalio.Pull.UP BUTTON_STATE = None BUTTON_VALUE = None PIXELS = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=LEVEL, auto_write=False) # NeoPixels off ASAP on startup cp.pixels.fill(0) # Onboard pixels cp.pixels.show() PIXELS.fill(0) # NeoPixel strip PIXELS.show() ENCODER = rotaryio.IncrementalEncoder(board.A2, board.A3) # LED ANIMATIONS ----------------------------------------------------------- COMET = Comet(PIXELS, speed=SPEED, tail_length=8, color=PURPLE, bounce=True) CP_COMET = Comet(cp.pixels, speed=SPEED, tail_length=8, color=PURPLE, bounce=True) DARK_RINGS = Solid(PIXELS, color=BLACK) DARK_CPB = Solid(cp.pixels, color=BLACK) DARK = AnimationGroup( DARK_RINGS, DARK_CPB, ) # Animations Playlist, reorder as desired. AnimationGroups play at same time ANIMATIONS = AnimationGroup( COMET, CP_COMET, ) # MAIN LOOP ---------------------------------------------------------------- LAST_POSITION = ENCODER.position MODE = 1 while True: POSITION = ENCODER.position if POSITION != LAST_POSITION: MOVE = POSITION - LAST_POSITION if cp.switch: FPS = max(MIN_FPS, min(FPS + MOVE, MAX_FPS)) SPEED = 1.0 / FPS COMET.speed = SPEED print("comet speed = ", SPEED) else: BRIGHTNESS = max(MIN_BRIGHTNESS, min(BRIGHTNESS + MOVE, MAX_BRIGHTNESS)) LEVEL = BRIGHTNESS * 0.1 if LEVEL > 1: LEVEL = 1 cp.pixels.brightness = LEVEL PIXELS.brightness = LEVEL print("brightness = ", LEVEL) LAST_POSITION = POSITION if not BUTTON_VALUE and BUTTON_STATE is None: BUTTON_STATE = "pressed" if BUTTON_VALUE and BUTTON_STATE == "pressed": print("Button pressed.") if MODE == 1: MODE = 2 else: MODE = 1 BUTTON_STATE = None if MODE == 1: ANIMATIONS.animate() else: DARK.animate()
Download the code.py file or copy/paste the code into your Mu editor window and save it as code.py at the root of your CIRCUITPY drive. You should see a purple comet animation running on the face of your Circuit Playground, and also on any lights you have hooked up to pin A1.
If you have your rotary encoder connected, test it out. When the tiny slide switch on the Circuit Playground is switched to the left, the speed of the comet will change when you turn the dial. If the switch is flipped to the right, the brightness will change. Press down on the knob to toggle the lights on and off.
Let's delve into the code a bit.
First, we'll import our libraries and the animations we want to use from the LED Animations Library. Check out that guide for a lot more info on using animations.
import board import digitalio import rotaryio import neopixel from adafruit_circuitplayground import cp from adafruit_led_animation.group import AnimationGroup from adafruit_led_animation.animation.comet import Comet from adafruit_led_animation.animation.solid import Solid from adafruit_led_animation.color import ( BLACK, PURPLE, )
Next, we'll set up the NeoPixel strip on pin A1. We don't need to set up the Circuit Playground's onboard pixels - the Circuit Playground library does that for us.
NEOPIXEL_PIN = board.A1 # NeoPixels connected here (crystals + rings) NUM_PIXELS = 92 # Number of pixels in the crystals and rings PIXELS = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=LEVEL, auto_write=False)
Next we'll set up the parameters for our brightness and speed controls. We're controlling speed in frames-per-second (FPS). You can play around with these numbers to define your maximum and minimum speed and brightness. Open the REPL and turn the knob to get a readout of the current brightness level or speed.
Be sure to back up your code before you mess around with these numbers too much! There's a possibility of feeding your rotary encoder a reading of 0, which will hang your board. Check the troubleshooting section below if this happens to you.
MIN_BRIGHTNESS = 1 # Minimum LED brightness as a percentage (0 to 100) MAX_BRIGHTNESS = 25 # Maximum LED brighrness as a percentage (0 to 100) MIN_FPS = 2 # Minimum animation speed in frames per second (>0) MAX_FPS = 8 # Maximum animation speed in frames per second (>0) # Set initial brightness and speed to center values BRIGHTNESS = (MIN_BRIGHTNESS + MAX_BRIGHTNESS) // 2 FPS = (MIN_FPS + MAX_FPS) // 2 SPEED = 1 / FPS # Integer frames-per-second to seconds interval LEVEL = BRIGHTNESS * 0.01 # Integer brightness percentage to 0.0-1.0 coeff.
Next we set up the push-button for our on/off toggle.
button = digitalio.DigitalInOut(board.A5) button.direction = digitalio.Direction.INPUT button.pull = digitalio.Pull.UP BUTTON_STATE = None BUTTON_VALUE = None
We set the pixels to OFF initially, then set up our encoder.
# NeoPixels off ASAP on startup cp.pixels.fill(0) # Onboard pixels cp.pixels.show() PIXELS.fill(0) # NeoPixel strip PIXELS.show() ENCODER = rotaryio.IncrementalEncoder(board.A2, board.A3)
Next we set up our LED animations. You can add as many different animation types and varietals as you'd like. Just be sure to import the animation from the LED Animations library at the top of the code.
The LED Animations library can only address one pin per animation. Since we're using two different pins: the pixels on A1 and the pixels on the face of the Circuit Playground, we need to use AnimationGroup to make them play at the same time.
I've set up the COMET
animation to play on PIXELS
(pin A1) and also on cp.pixels
(the onboard pixels). I've also set up a DARK animation on both strips, for use when we press the toggle switch to turn the pixels off.
# LED ANIMATIONS ----------------------------------------------------------- COMET = Comet(PIXELS, speed=SPEED, tail_length=8, color=PURPLE, bounce=True) CP_COMET = Comet(cp.pixels, speed=SPEED, tail_length=8, color=PURPLE, bounce=True) DARK_RINGS = Solid(PIXELS, color=BLACK) DARK_CPB = Solid(cp.pixels, color=BLACK) DARK = AnimationGroup( DARK_RINGS, DARK_CPB, ) # Animations Playlist, reorder as desired. AnimationGroups play at same time ANIMATIONS = AnimationGroup( COMET, CP_COMET, )
That's it for setup. Next comes our main loop. First the code reads the position of the rotary encoder to determine if it's been moved. If the onboard switch (cp.switch
) is set to the left, the code will adjust the speed of the COMET
animation whenever the switch is turned.
If the switch is set to the right, the code adjusts the brightness level up or down.
LAST_POSITION = ENCODER.position MODE = 1 while True: POSITION = ENCODER.position if POSITION != LAST_POSITION: MOVE = POSITION - LAST_POSITION if cp.switch: FPS = max(MIN_FPS, min(FPS + MOVE, MAX_FPS)) SPEED = 1.0 / FPS COMET.speed = SPEED print("comet speed = ", SPEED) else: BRIGHTNESS = max(MIN_BRIGHTNESS, min(BRIGHTNESS + MOVE, MAX_BRIGHTNESS)) LEVEL = BRIGHTNESS * 0.1 if LEVEL > 1: LEVEL = 1 cp.pixels.brightness = LEVEL PIXELS.brightness = LEVEL print("brightness = ", LEVEL) LAST_POSITION = POSITION
Finally, the code looks for button presses and toggles between the COMET
animation group and the DARK
animation group depending on the button's state.
if not BUTTON_VALUE and BUTTON_STATE is None: BUTTON_STATE = "pressed" if BUTTON_VALUE and BUTTON_STATE == "pressed": print("Button pressed.") if MODE == 1: MODE = 2 else: MODE = 1 BUTTON_STATE = None if MODE == 1: ANIMATIONS.animate() else: DARK.animate()
Full Code
Here's my full chandelier code. In this version I've added pixel mapping so I can address the crystals separately from the rings, plus I've added more animations and animation groups. You can learn more about these features in the LED Animations Library Guide.
This code runs a half-brightness rainbow animation on the rings and Circuit Playground face, balancing the brightness with the single-pixel crystals, while it runs a speed-controlled comet animation on the crystals themselves.
# SPDX-FileCopyrightText: 2021 Erin St Blaine for Adafruit Industries # SPDX-FileCopyrightText: 2021 Limor Fried for Adafruit Industries # # SPDX-License-Identifier: MIT """ Crystal Chandelier with Circuit Playground BlueFruit Adafruit invests time and resources providing this open source code. Please support Adafruit and open source hardware by purchasing products from Adafruit! Written by Erin St Blaine & Limor Fried for Adafruit Industries Copyright (c) 2020-2021 Adafruit Industries Licensed under the MIT license. All text above must be included in any redistribution. """ # pylint: disable=import-error # pylint: disable=no-member import board import digitalio import rotaryio import neopixel from adafruit_circuitplayground import cp from adafruit_led_animation.helper import PixelMap from adafruit_led_animation.sequence import AnimationSequence from adafruit_led_animation.group import AnimationGroup from adafruit_led_animation.animation.rainbow import Rainbow from adafruit_led_animation.animation.comet import Comet from adafruit_led_animation.animation.solid import Solid from adafruit_led_animation.color import ( BLACK, PURPLE, ) NEOPIXEL_PIN = board.A1 # NeoPixels connected here (crystals + rings) NUM_PIXELS = 92 # Number of pixels in the crystals and rings MIN_BRIGHTNESS = 1 # Minimum LED brightness as a percentage (0 to 100) MAX_BRIGHTNESS = 25 # Maximum LED brighrness as a percentage (0 to 100) MIN_FPS = 2 # Minimum animation speed in frames per second (>0) MAX_FPS = 8 # Maximum animation speed in frames per second (>0) # Set initial brightness and speed to center values BRIGHTNESS = (MIN_BRIGHTNESS + MAX_BRIGHTNESS) // 2 FPS = (MIN_FPS + MAX_FPS) // 2 SPEED = 1 / FPS # Integer frames-per-second to seconds interval LEVEL = BRIGHTNESS * 0.01 # Integer brightness percentage to 0.0-1.0 coeff. button = digitalio.DigitalInOut(board.A5) button.direction = digitalio.Direction.INPUT button.pull = digitalio.Pull.UP BUTTON_STATE = None BUTTON_VALUE = None PIXELS = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=LEVEL, auto_write=False) # NeoPixels off ASAP on startup cp.pixels.fill(0) # Onboard pixels cp.pixels.show() PIXELS.fill(0) # NeoPixel strip PIXELS.show() ENCODER = rotaryio.IncrementalEncoder(board.A2, board.A3) # PIXEL MAPS reorder pixels so animations run in different configs --------- # Crystals Only PIXEL_MAP_CRYSTALS = PixelMap(PIXELS, [ 84, 85, 86, 87, 88, 89, 90, 91 ], individual_pixels=True) # Rings Only PIXEL_MAP_RINGS = PixelMap(PIXELS, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83 ], individual_pixels=True) # LED ANIMATIONS ----------------------------------------------------------- RAINBOW = Rainbow(PIXEL_MAP_RINGS, speed=0.01, period=10, step=3) CP_RAINBOW = Rainbow(cp.pixels, speed=0.01, period=10, step=3) CRYSTAL_COMET = Comet(PIXEL_MAP_CRYSTALS, speed=SPEED, tail_length=8, color=PURPLE, bounce=True) DARK_RINGS = Solid(PIXELS, color=BLACK) DARK_CPB = Solid(cp.pixels, color=BLACK) DARK = AnimationGroup( DARK_RINGS, DARK_CPB, ) # Animations Playlist, reorder as desired. AnimationGroups play at same time ANIMATIONS = AnimationSequence( AnimationGroup( CRYSTAL_COMET, RAINBOW, CP_RAINBOW, ), auto_clear=True, auto_reset=True, ) # MAIN LOOP ---------------------------------------------------------------- LAST_POSITION = ENCODER.position MODE = 1 while True: POSITION = ENCODER.position if POSITION != LAST_POSITION: MOVE = POSITION - LAST_POSITION if cp.switch: FPS = max(MIN_FPS, min(FPS + MOVE, MAX_FPS)) SPEED = 1.0 / FPS CRYSTAL_COMET.speed = SPEED #RAINBOW.speed = SPEED #CP_RAINBOW.speed = SPEED print("crystal speed = ", SPEED) else: BRIGHTNESS = max(MIN_BRIGHTNESS, min(BRIGHTNESS + MOVE, MAX_BRIGHTNESS)) LEVEL = BRIGHTNESS * 0.1 if LEVEL > 2: LEVEL = 2 cp.pixels.brightness = LEVEL/2 PIXEL_MAP_RINGS.brightness = LEVEL/2 PIXEL_MAP_CRYSTALS.brightness = LEVEL print("ring brightness = ", LEVEL/2, "crystal brightness = ", LEVEL) LAST_POSITION = POSITION if not BUTTON_VALUE and BUTTON_STATE is None: BUTTON_STATE = "pressed" if BUTTON_VALUE and BUTTON_STATE == "pressed": print("Button pressed.") if MODE == 1: MODE = 2 else: MODE = 1 BUTTON_STATE = None if MODE == 1: ANIMATIONS.animate() else: DARK.animate()
Troubleshooting
If you're having trouble getting the code to load, head over to the Circuit Playground Bluefruit guide for more detailed instructions and things to try.
Another problem I encountered is that the rotary encoder will hang if it's fed a value of 0. If you're playing with calibrating your numbers and your board's lights turn red, your CIRCUITPY drive vanishes, and pressing the reset button has no effect, this may be what happened.
Don't worry! You can erase and recover the board but you'll need to reinstall CircuitPython and your libraries. Visit the Troubleshooting page from the CircuitPython guide and scroll way down until you find CIRCUITPY Drive Issues. Follow the instructions to wipe your board clean and start over.
Page last edited January 19, 2025
Text editor powered by tinymce.