This guide uses fairly simple CircuitPython code. Most of the heavy lifting is already done once you've created your graphics.
This code is based on this BMP Animation sample code by Phil Burgess. The code has a lot of possibility -- this is just one way to use it, so check that page out for a lot more ideas.
The ideas have been used in other projects already, so rather than reiterate we’ll simply point you to these other tutorials: this guide on “sprite sheet” animation is how the matrix animation works, and this one on bitmap-to-NeoPixel animation for the rings (the EyeLights LEDs aren’t NeoPixels, but the idea is the same). The images sizes here are different, that’s really the only change: 18x5 pixels per frame for the matrix, and 48 pixels tall for the rings.
Usage
Save your sprite animations into the /images folder on your CIRCUITPY drive and then tilt your head up to cycle between animations. Control the brightness using the onboard momentary switch button, so you can wear these either during the daytime or at night without blinding people.
Source Code and Example Images
CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory LED_Glasses_With_Sprites/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
Your CIRCUITPY
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries # # SPDX-License-Identifier: MIT # modified from original # 2024 Carter Nelson """ Modifed from original project code here: https://learn.adafruit.com/adafruit-eyelights-led-glasses-and-driver/bmp-animation This version only uses the matrix part of the glasses. The ring LEDs are not used. BMP image files should be properly formatted for the matrix (18x5 sprites) and placed in the /images folder. Current animation can be changed by tilting head back. Brightness can be changed by pressing the user button. """ import os import time import board from busio import I2C import digitalio import adafruit_lis3dh import adafruit_is31fl3741 from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses from eyelights_anim import EyeLightsAnim # --| User Config |------------------------------ ANIM_DELAY = 0.07 BRIGHT_LEVELS = (0, 10, 20, 40) # --| User Config |------------------------------ # use all BMPs found in /images dir ANIM_FILES = [ "/images/" + f for f in os.listdir("/images") if f.endswith(".bmp") and not f.startswith("._") ] # HARDWARE SETUP ----------------------- i2c = I2C(board.SCL, board.SDA, frequency=1000000) lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c) button = digitalio.DigitalInOut(board.SWITCH) button.switch_to_input(digitalio.Pull.UP) # Initialize the IS31 LED driver, buffered for smoother animation glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER) glasses.show() # Clear any residue on startup glasses.global_current = 20 # Just middlin' bright, please # ANIMATION SETUP ---------------------- # Two indexed-color BMP filenames are specified: first is for the LED matrix # portion, second is for the LED rings -- or pass None for one or the other # if not animating that part. The two elements, matrix and rings, share a # few LEDs in common...by default the rings appear "on top" of the matrix, # or you can optionally pass a third argument of False to have the rings # underneath. There's that one odd unaligned pixel between the two though, # so this may only rarely be desirable. anim = EyeLightsAnim(glasses, ANIM_FILES[0], None) # MAIN LOOP ---------------------------- # This example just runs through a repeating cycle. If you need something # else, like ping-pong animation, or frames based on a specific time, the # anim.frame() function can optionally accept two arguments: an index for # the matrix animation, and an index for the rings. _, filtered_y, _ = lis3dh.acceleration looking_up = filtered_y < -5 anim_index = 0 bright_index = 0 while True: # read accelo and check if looking up _, y, _ = lis3dh.acceleration filtered_y = filtered_y * 0.85 + y * 0.15 if looking_up: if filtered_y > -3.5: looking_up = False else: if filtered_y < -5: looking_up = True anim_index = (anim_index + 1) % len(ANIM_FILES) print(ANIM_FILES[anim_index]) anim.matrix_filename = ANIM_FILES[anim_index] # check for button press if not button.value: bright_index = (bright_index + 1) % len(BRIGHT_LEVELS) print(BRIGHT_LEVELS[bright_index]) glasses.global_current = BRIGHT_LEVELS[bright_index] while not button.value: pass anim.frame() # Advance matrix and rings by 1 frame and wrap around glasses.show() # Update LED matrix time.sleep(ANIM_DELAY) # Pause briefly
Code Walkthrough
Let's take a look at what's happening in the code.
First we import all the necessary libraries.
import os import time import board from busio import I2C import digitalio import adafruit_lis3dh import adafruit_is31fl3741 from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses from eyelights_anim import EyeLightsAnim
The next section allows you to control the speed of the animation and the brightness levels. A higher ANIM_DELAY
number will make your animation run more slowly.
We've kept the brightness levels fairly low, since these glasses are pretty intense at full brightness, but you can adjust your numbers here. Each press of the button will cycle through to the next brightness level.
# --| User Config |------------------------------ ANIM_DELAY = 0.07 BRIGHT_LEVELS = (0, 10, 20, 40) # --| User Config |------------------------------
Next, the code looks in the /images folder for your .bmp images. Place as many images into this folder as you'd like, and the code will automatically cycle through them.
# use all BMPs found in /images dir ANIM_FILES = [ "/images/" + f for f in os.listdir("/images") if f.endswith(".bmp") and not f.startswith("._") ]
Hardware setup is next. This code defines the ports and sensors on the board and sets the initial brightness.
# HARDWARE SETUP ----------------------- i2c = I2C(board.SCL, board.SDA, frequency=1000000) lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c) button = digitalio.DigitalInOut(board.SWITCH) button.switch_to_input(digitalio.Pull.UP) # Initialize the IS31 LED driver, buffered for smoother animation glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER) glasses.show() # Clear any residue on startup glasses.global_current = 20 # Just middlin' bright, please
Next is our Animation Setup area. If you want to turn the rings back on, change "None" to the filename of the image you'd like to show on the rings.
# ANIMATION SETUP ---------------------- # Two indexed-color BMP filenames are specified: first is for the LED matrix # portion, second is for the LED rings -- or pass None for one or the other # if not animating that part. The two elements, matrix and rings, share a # few LEDs in common...by default the rings appear "on top" of the matrix, # or you can optionally pass a third argument of False to have the rings # underneath. There's that one odd unaligned pixel between the two though, # so this may only rarely be desirable. anim = EyeLightsAnim(glasses, ANIM_FILES[0], None)
Finally, the main loop is where the action happens. If you'd like to change the head-tilt direction, change it here.
Check out this guide about the lis3hd accelerometer for more info.
# MAIN LOOP ---------------------------- # This example just runs through a repeating cycle. If you need something # else, like ping-pong animation, or frames based on a specific time, the # anim.frame() function can optionally accept two arguments: an index for # the matrix animation, and an index for the rings. _, filtered_y, _ = lis3dh.acceleration looking_up = filtered_y < -5 anim_index = 0 bright_index = 0 while True: # read accelo and check if looking up _, y, _ = lis3dh.acceleration filtered_y = filtered_y * 0.85 + y * 0.15 if looking_up: if filtered_y > -3.5: looking_up = False else: if filtered_y < -5: looking_up = True anim_index = (anim_index + 1) % len(ANIM_FILES) print(ANIM_FILES[anim_index]) anim.matrix_filename = ANIM_FILES[anim_index] # check for button press if not button.value: bright_index = (bright_index + 1) % len(BRIGHT_LEVELS) print(BRIGHT_LEVELS[bright_index]) glasses.global_current = BRIGHT_LEVELS[bright_index] while not button.value: pass anim.frame() # Advance matrix and rings by 1 frame and wrap around glasses.show() # Update LED matrix time.sleep(ANIM_DELAY) # Pause briefly
Troubleshooting
If your code doesn't run, here are a few things to try:
- Be sure your .bmp images are saved in the /images folder on your CIRCUITPY drive and not the root
- If our images work and yours do not, you may have the wrong type of .bmp file. Try a different conversion tool, and make sure you have saved as a 4- or 8-bit file.
- If your images are misaligned, go back to your sprite sheet generator and try centering them within the 18x5 grid in a different way
- If no images appear, open your serial monitor and see what kind of feedback the code editor can give you. More info on how to do this can be found here.
Text editor powered by tinymce.