Sister Night NeoPixel Goggles

In this project, we're building NeoPixel goggles inspired by HBO's television series Watchmen. Control the NeoPixel LED animations with a rotary encoder. Display up to four different modes with control like speed, colors and brightness of the NeoPixels. The 3D printed parts fit over costume goggles and snap fit together!

CircuitPython Code

The code was written in Circuit Python so it makes customizing much easier. This project uses the Trinket Adafruit M0 which is one of the tiniest boards supported in CircuitPython. This makes projects easier to maintain because it works like a USB drive so all of your code and libraries are easily accessible.

Wearable Cosplay Goggles

You can actually see through them so they’re safe to wear. The 3D printed covers actually keep the light away from your eyes. An elastic headband makes it much more comfortable to wear and can be adjusted so they’ll stay nice and tight.

Adafruit Parts

Manicured hand holding Trinket M0.
The Adafruit Trinket M0 may be small, but do not be fooled by its size! It's a tiny microcontroller board, built around the Atmel ATSAMD21, a little chip with a lot...
In Stock
Adafruit LiIon/LiPoly Backpack soldered onto a Pro Trinket, plugged into a solderless breadboard.
If you have an ItsyBitsy or Pro Trinket you probably know it's the perfect little size for a portable project. This LiPoly backpack makes it really easy to do! Instead of wiring 2...
In Stock
Hand holding NeoPixel Ring with 16 x 5050 RGB LED, lit up rainbow
Round and round and round they go! 16 ultra bright smart LED NeoPixels are arranged in a circle with 1.75" (44.5mm) outer diameter. The rings are 'chainable' - connect the...
In Stock
Angled shot of Costume Goggles.
We've got some awesome LED goggle tutorials, and now you want to make your own, right? So pick up a pair of NeoPixel rings and...
Out of Stock
1 x Adafruit Trinket M0
Circuit Python Board
1 x 400mAh Battery
3.7V lipo battery
1 x Lipo Backpack
Add-on for Trinkey/ItsyBitsy
2 x Adafruit 16x NeoPixel Ring
NeoPixel ring with 16 NeoPixel RGB LEDs
1 x Rotary Encoder
rotary encoder with extras
1 x Slide Switch
mini on/off switch
1 x Costume Goggles
goggles include strap, lenses and screw-on covers
1 x Silicone Ribbon Cable - 10 Wire 1 Meter Long - 28AWG Black
Silicone Cover Stranded-Core Ribbon Cable - 10 Wire 1 Meter Long - 28AWG Black
1 x USB Cable
USB A to Micro-B - 3 foot long
1 x Heat Shrink Tubing
Multi-Colored Heat Shrink Pack - 3/32" + 1/8" + 3/16" Diameters

The diagram below provides a visual reference for wiring of the components. This diagrams was created using the software package Fritzing.

Adafruit Library for Fritzing

Use Adafruit's Fritzing parts library to create circuit diagrams for your projects. Download the library or just grab individual parts. Get the library and parts from GitHub - Adafruit Fritzing Parts.


The Trinket M0 is powered by 400mAh battery via the Lipo Backpack. This allows the USB port from the Trinket M0 to charge the 400mAh battery. A slide switch is connected to the two switch pin on the Lipo Back. 

  • BAT from Lipo Backpack to BAT on Trinket M0
  • Ground from Lipo Backpack to GND on Trinket M0
  • 5V from Lipo Backpack to USB on Trinket M0

Rotary Encoder

A rotary encoder is wired to the Trinket M0. This requires five wired connections. The rotary encoder also features a built-in button switch. Follow the circuit diagram above for the connecting the correct pins to Trinket M0.

NeoPixel Rings

The two 16x NeoPixel rings are daisy chained with three wired connections, data, voltage and ground. The first ring is connected to the Trinket M0.

  • V+ from NeoPixel Ring to BAT on Trinket M0
  • G from NeoPixel Ring to G on Trinket M0
  • IN from NeoPixel Ring to Pin #0 on Trinket M0

Setup Trinket M0 with CircuitPython

We'll need to get our board setup so we can run the CircuitPython code. Let's walk through these steps to get the latest version of CircuitPython onto your board. 

The Mu Python Editor

Mu is a simple Python editor that works with Adafruit CircuitPython hardware. It's written in Python and works on Windows, MacOS, Linux and Raspberry Pi. The serial console is built right in so you get immediate feedback from your board's serial output! While you can use any text editor with your code, Mu makes it super simple.

Installing or upgrading CircuitPython

You should ensure you have CircuitPython 5.0 or greater on your board. Plug your board in with a known good data + power cable (not the cheesy USB cable that comes with USB power packs, they are power only). You should see a new flash drive pop up.

If the drive is CIRCUITPY, then open the boot_out.txt file to ensure the version number is 5.0 or greater. 

Adafruit CircuitPython 5.0.0-alpha.5 on 2019-11-04; Adafruit Trinket M0 with samd21e18

If the version is less than 5 -or- you only get a drive named TRINKETBOOT then follow the Trinket M0 guide on installing CircuitPython.

Download the Adafruit CircuitPython Library Bundle

In order to run the code, we'll need to download a few libraries. Libraries contain code to help interface with hardware a lot easier for us.

Use the Trinket M0 page on Installing Libraries to get the library that matches the major version of CircuitPython you are using noted above.

To run the code for this project, we need the two libraries in the Required Libraries list below. Unzip the library bundle and search for the libraries. Drag and drop the files into a folder named lib on the CIRCUITPY drive (create the folder if it is not already on the Trinkey M0).

Required Libraries 

  • neopixel.mpy
  • adafruit_pixelbuf.mpy

Once we have all the files we need, a directory listing will look similar to below as far as files and directories.

Upload Code

This project offers two different sketches. Both programs can be used with either display. 

Click on the download link below to grab the main code directly from GitHub. Rename the file to and drop it onto the CIRCUITPY main (root) directory. The code will run properly when all of the files have been uploaded including libraries.

Use any text editor or favorite IDE to modify the code. We suggest using Mu as noted above.

# SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries
# SPDX-License-Identifier: MIT

# pylint: disable=import-error

# NeoPixel goggles code for CircuitPython
# With a rotary encoder attached (pins are declred in the "Initialize
# hardware" section of the code), you can select animation modes and
# configurable attributes (color, brightness, etc.). TAP the encoder
# button to switch between modes/settings, HOLD the encoder button to
# toggle between PLAY and CONFIGURE states.
# With no rotary encoder attached, you can select an animation mode
# and configure attributes in the "Configurable defaults" section
# (including an option to auto-cycle through the animation modes).
# Things to Know:
# - FancyLED library is NOT used here because it's a bit too much for the
#   Trinket M0 to handle (animation was very slow).
# - Animation modes are all monochromatic (single color, varying only in
#   brightness). More a design decision than a technical one...of course
#   NeoPixels can be individual colors, but folks like customizing and the
#   monochromatic approach makes it easier to select a color. Also keeps the
#   code a bit simpler, since Trinket space & performance is limited.
# - Animation is monotonic time driven; there are no sleep() calls. This
#   ensures that animation is constant-time regardless of the hardware or
#   CircuitPython performance over time, or other goings on (e.g. garbage
#   collection), only the frame rate (smoothness) varies; overall timing
#   remains consistent.

from math import modf, pi, sin
from random import getrandbits
from time import monotonic
from digitalio import DigitalInOut, Direction
from richbutton import RichButton
from rotaryio import IncrementalEncoder
import adafruit_dotstar
import board
import neopixel

# Configurable defaults

PIXEL_HUE = 0.0         # Red at start
PIXEL_BRIGHTNESS = 0.4  # 40% brightness at start
PIXEL_GAMMA = 2.6       # Controls brightness linearity
RING_1_OFFSET = 10      # Alignment of top pixel on 1st NeoPixel ring
RING_2_OFFSET = 10      # Alignment of top pixel on 2nd NeoPixel ring
RING_2_FLIP = True      # If True, reverse order of pixels on 2nd ring
CYCLE_INTERVAL = 0      # If >0 auto-cycle through play modes @ this interval
SPEED = 1.0             # Initial animation speed for modes that use it
XRAY_BITS = 0x0821      # Initial bit pattern for "X-ray" mode

# Things you probably don't want to change, unless adding new modes

PLAY_MODE_SPIN = 0               # Revolving pulse
PLAY_MODE_XRAY = 1               # Watchmen-inspired "X-ray goggles"
PLAY_MODE_SCAN = 2               # Scanline effect
PLAY_MODE_SPARKLE = 3            # Random dots
PLAY_MODES = 4                   # Number of PLAY modes
PLAY_MODE = PLAY_MODE_SPIN       # Initial PLAY mode

CONFIG_MODE_COLOR = 0            # Setting color (hue)
CONFIG_MODE_BRIGHTNESS = 1       # Setting brightness
CONFIG_MODE_ALIGN = 2            # Top pixel indicator
CONFIG_MODES = 3                 # Number of CONFIG modes
CONFIGURING = False              # NOT configuring at start
# CONFIG_MODE_ALIGN is only used to test the values of RING_1_OFFSET and
# RING_2_OFFSET. The single lit pixel should appear at the top of each ring.
# If it does not, adjust each of those two values (integer from 0 to 15)
# until the pixel appears at the top (or physically reposition the rings).
# Some of the animation modes rely on the two rings being aligned a certain
# way. Once adjusted, you can reduce the value of CONFIG_MODES and this
# mode will be skipped in config state.

# Initialize hardware - PIN DEFINITIONS APPEAR HERE

# Turn off onboard DotStar LED
DOTSTAR = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
DOTSTAR.brightness = 0

# Turn off onboard discrete LED
LED = DigitalInOut(board.D13)
LED.direction = Direction.OUTPUT
LED.value = 0

# Declare NeoPixels on pin D0, 32 pixels long. Set to max brightness because
# on-the-fly brightness slows down NeoPixel lib, so we'll do our own here.
PIXELS = neopixel.NeoPixel(board.D0, 32, brightness=1.0, auto_write=False)

# Declare rotary encoder on pins D4 and D3, and click button on pin D2.
# If encoder behaves backwards from what you want, swap pins here.
ENCODER = IncrementalEncoder(board.D4, board.D3)
ENCODER_BUTTON = RichButton(board.D2)

def set_pixel(pixel_num, brightness):
    """Set one pixel in both 16-pixel rings. Pass in pixel index (0 to 15)
       and relative brightness (0.0 to 1.0). Actual resulting brightness
       will be a function of global brightness and gamma correction."""
    # Clamp passed brightness to 0.0-1.0 range,
    # apply global brightness and gamma correction
    brightness = max(min(brightness, 1.0), 0.0) * PIXEL_BRIGHTNESS
    brightness = pow(brightness, PIXEL_GAMMA) * 255.0
    # local_color is adjusted brightness applied to global PIXEL_COLOR
    local_color = (
        int(PIXEL_COLOR[0] * brightness + 0.5),
        int(PIXEL_COLOR[1] * brightness + 0.5),
        int(PIXEL_COLOR[2] * brightness + 0.5))
    # Roll over pixel_num as needed to 0-15 range, then store color
    pixel_num_wrapped = (pixel_num + RING_1_OFFSET) & 15
    PIXELS[pixel_num_wrapped] = local_color
    # Determine corresponding pixel for second ring. Mirror direction if
    # configured for such, correct for any rotational difference, then
    # perform similar roll-over as above before storing color.
    if RING_2_FLIP:
        pixel_num = 15 - pixel_num
    pixel_num_wrapped = 16 + ((pixel_num + RING_2_OFFSET) & 15)
    PIXELS[pixel_num_wrapped] = local_color

def triangle_wave(pos, peak=0.5):
    """Return a brightness level (0.0 to 1.0) corresponding to a position
       (0.0 to 1.0) within a triangle wave (spanning 0.0 to 1.0) with wave's
       peak brightness at a given position (0.0 to 1.0) within its span.
       Positions outside the wave's span return 0.0."""
    if 0.0 <= pos < 1.0:
        if pos <= peak:
            return pos / peak
        return (1.0 - pos) / (1.0 - peak)
    return 0.0

def hue_to_rgb(hue):
    """Given a hue value as a float, where the fractional portion
       (0.0 to 1.0) indicates the actual hue (starting from red at 0,
       to green at 1/3, to blue at 2/3, and back to red at 1.0),
       return an RGB color as a 3-tuple with values from 0.0 to 1.0."""
    hue = modf(hue)[0]
    sixth = (hue * 6.0) % 6.0
    ramp = modf(sixth)[0]
    if sixth < 1.0:
        return (1.0, ramp, 0.0)
    if sixth < 2.0:
        return (1.0 - ramp, 1.0, 0.0)
    if sixth < 3.0:
        return (0.0, 1.0, ramp)
    if sixth < 4.0:
        return (0.0, 1.0 - ramp, 1.0)
    if sixth < 5.0:
        return (ramp, 0.0, 1.0)
    return (1.0, 0.0, 1.0 - ramp)

def random_bits():
    """Generate random bit pattern, avoiding adjacent set bits (w/wrap)"""
    pattern = getrandbits(16)
    pattern |= (pattern & 1) << 16   # Replicate bit 0 at bit 16
    return pattern & ~(pattern >> 1) # Mask out adjacent set bits

# Some last-minute state initialization

POS = 0                              # Initial swirl animation position
PIXEL_COLOR = hue_to_rgb(PIXEL_HUE)  # Initial color
ENCODER_PRIOR = ENCODER.position     # Initial encoder position
TIME_PRIOR = monotonic()             # Initial time
LAST_CYCLE_TIME = TIME_PRIOR         # For mode auto-cycling
SPARKLE_BITS_PREV = 0                # First bits for sparkle animation
SPARKLE_BITS_NEXT = 0                # Next bits for sparkle animation
PREV_WEIGHT = 2                      # Force initial sparkle refresh

# Main loop

while True:
    if ACTION is RichButton.TAP:
        # Encoder button tapped, cycle through play or config modes:
        if CONFIGURING:
            PLAY_MODE = (PLAY_MODE + 1) % PLAY_MODES
    elif ACTION is RichButton.DOUBLE_TAP:
        # DOUBLE_TAP not currently used, but this is where it would go.
    elif ACTION is RichButton.HOLD:
        # Encoder button held, toggle between PLAY and CONFIG modes:
    elif ACTION is RichButton.RELEASE:
        # RELEASE not currently used (play/config state changes when HOLD
        # is detected), but this is where it would go.

    # Process encoder input. Code always uses the ENCODER_CHANGE value
    # for relative adjustments.

    # Same idea, but for elapsed time (so time-based animation continues
    # at the next position, it doesn't jump around as when multiplying
    # monotonic() by SPEED.
    TIME_NOW = monotonic()

        # In config mode, different pixel patterns indicate which
        # adjustment is being made (e.g. alternating pixels = hue mode).
            PIXEL_HUE = modf(PIXEL_HUE + ENCODER_CHANGE * 0.01)[0]
            PIXEL_COLOR = hue_to_rgb(PIXEL_HUE)
            for i in range(0, 16):
                set_pixel(i, i & 1)  # Turn on alternating pixels
            PIXEL_BRIGHTNESS = max(min(PIXEL_BRIGHTNESS, 1.0), 0.0)
            for i in range(0, 16):
                set_pixel(i, (i & 2) >> 1)  # Turn on pixel pairs
            C = 1      # First pixel on
            for i in range(0, 16):
                set_pixel(i, C)
                C = 0  # All other pixels off
        # In play mode. Auto-cycle animations if CYCLE_INTERVAL is set.
        if CYCLE_INTERVAL > 0:
                PLAY_MODE = (PLAY_MODE + 1) % PLAY_MODES
                LAST_CYCLE_TIME = TIME_NOW

            # In XRAY mode, encoder selects random bit patterns
            if abs(ENCODER_CHANGE) > 1:
                XRAY_BITS = random_bits()
            # Unset bits pulsate ever-so-slightly
            DIM = 0.42 + sin(monotonic() * 2) * 0.08
            for i in range(16):
                if XRAY_BITS & (1 << i):
                    set_pixel(i, 1.0)
                    set_pixel(i, DIM)
            # In all other modes, encoder adjusts speed/direction
            SPEED += ENCODER_CHANGE * 0.05
            SPEED = max(min(SPEED, 4.0), -4.0)
            POS += TIME_CHANGE * SPEED
            if PLAY_MODE is PLAY_MODE_SPIN:
                for i in range(16):
                    frac = modf(POS + i / 15.0)[0]  # 0.0-1.0 around ring
                    if frac < 0:
                        frac = 1.0 + frac
                    set_pixel(i, triangle_wave(frac, 0.5 - SPEED * 0.125))
            elif PLAY_MODE is PLAY_MODE_SCAN:
                if POS >= 0:
                    S = 2.0 - modf(POS)[0] * 4.0
                    S = 2.0 - (1.0 + modf(POS)[0]) * 4.0
                for i in range(16):
                    Y = sin((i / 7.5 + 0.5) * pi)  # Pixel Y coord
                    D = 0.5 - abs(Y - S) * 0.6     # Distance to scanline
                    set_pixel(i, triangle_wave(D))
            elif PLAY_MODE is PLAY_MODE_SPARKLE:
                NEXT_WEIGHT = modf(abs(POS * 2.0))[0]
                if SPEED < 0:
                    NEXT_WEIGHT = 1.0 - NEXT_WEIGHT
                if NEXT_WEIGHT < PREV_WEIGHT:
                    while True:
                        SPARKLE_BITS_NEXT = random_bits()
                        if not SPARKLE_BITS_NEXT & SPARKLE_BITS_PREV:
                            break  # No bits in common, good!
                PREV_WEIGHT = 1.0 - NEXT_WEIGHT
                for i in range(16):
                    bit = 1 << i
                    if SPARKLE_BITS_PREV & bit:
                        result = PREV_WEIGHT
                    elif SPARKLE_BITS_NEXT & bit:
                        result = NEXT_WEIGHT
                        result = 0
                    set_pixel(i, result)
                PREV_WEIGHT = NEXT_WEIGHT

Double Check

See the directory listing above and double check that you have all the files listed to make this project function. If any are missing or in an incorrect directory, move them so they're in the right places.

The parts for this project are designed to be 3D printed with FDM based machines. STL files are oriented to print "as is". Parts require tight tolerances that might need adjustment of slice settings. Reference the suggested settings below.

CAD Files

The parts can further be separated into small pieces for fitting on printers with smaller build volumes. Note: a STEP file is included for other 3D surface modeling programs such as Onshape, Solidworks and Rhino.

Custom Supports Plugin for CURA

We suggest installing the custom supports plugin for CURA. This provides a tool for adding custom supports to models. To install the plugin, click on the Marketplace button, top right of the app window. Search for "custom supports" and download the plugin. Make sure to have the latest version of CURA installed.

TPU Flexible Parts

The goggle case and cover are designed to be printed in TPU filament. The goggle-case.stl part requires support material to print properly. The included .3mf file contains pre-configured slice settings for CURA slicing software. Support material is included and does not need to be added with the .3mf project file.

Rotary Case

The rotary-case-left.stl and rotary-case-right.stl parts requires support material as well. This part should be printed in PLA, not ninjaflex. The .3mf project file contains pre-configured slice settings and custom supports. Both parts feature mounting hole for the rotary encoder. You can choose which side to mount the rotary encoder.

Orient Rings

Rotate the rings as shown to optimize wire lengths. 

Measure the left NeoPixel Ring (input) 120mm long with a 4 wire ribbon cable.

The right NeoPixel Ring (output) wires measured 90mm long.

Separate Wires  

Carefully separate the wires so they can be hidden behind the back of the NeoPixel PCB.

Silicon ribbon wires are easy to bend and form around the ring.

Solder Input Ring

Apply solder to the 5V, ground, data Input and data out pins. Tweezers can be used to hold each wire while reheating the soldered points and  gently pushing each wire into the through pins.  

Solder Output Ring

Solder the 5V, ground and data input pins in the same manner.

Solder Ring input to output

Cut and place a small pice of heat shrink before soldering the input and output wires together from the left and right NeoPixel rings.  

Solder Y-Cable for Power

Measure a 25mm long 2-wire ribbon cable to combine both power and ground connections from both rings. This will make it easy to connect to the Trinket M0. 

Add another small piece of heat shrink to the short ribbon cable before soldering together.

Use tweezers to hold both power wires close while soldering to small wires as shown in the picture. 

Use hot air or the upper side of the solder iron to set the heat shrink over the wire connections.

Solder Y wire for ground

Solder the the ground connections in the same way, making sure to add the heat shrink before soldering all three ground wires together. 

Lipo Backpack header

Use the included header with the LiPo Backpack to connect it to the Trinket. The BAT and G pins align together but the 5V on the BackPack will need to connect to the USB pin on the Trinket. Use a small wire to connect to make the connection.

Remove the third pin on the header and solder to the Lipo Backpack with the plastic part of the header on the bottom of the Lipo Backpack.

Cut trace

Prepare the Lipo Backpack to connect the slide switch. Cut the trace between the two through holes that have the rectangle around them.

Use flush cutters to remove the trace by placing one of the blades on the mounting hole while the other blade slices through the trace.

Solder switch

Measure a two wire ribbon cable 55mm long and solder to each through hole on the Lipo Backpack. 

Trim the pins on the slide switch to make it easier to mount inside the case. We won't need the third pin, so you can remove one of the third pins on one of the sides. Solder both wires to each of the two pins.

Trinket M0 Data Wires

Solder the data wire from the NeoPixel ring to the Trinket M0. Connect the Data In wire to Pin #0.

Solder Power and Ground

Solder the power and ground wires to the positive(+) and negative(-) pads on the bottom of the Trinket M0.

Backpack USB to 5v

Measure and cut the wire to be 20mm in length. Connect it to the USB pad on the Trinket to the 5v pad on the Lipo Backpack.

Align BackPack to Trinket

Place the LipoBack over the Trinket and solder the USB wire to the 5v pin on the Lipo Backpack.

Solder Backpack

Line up the header on the LiPo Backpack and solder the header pins to the Trinket M0.

Rotary Prep

Measure out a 4-wire ribbon cable to be 145mm in length. Reference the circuit diagram and solder the pins to the rotary encoder. 

Solder Rotary to Trinket

Tin pins number 2, 3 and 4 on the Trinket M0 and reference the circuit diagram to solder the wires from the rotary encoder.

Goggle Nose connector

Remove the rubber nose connector on the goggles and replace it with the 3D printed case.

Unscrew both lens covers to reveal the slot on the side of the goggles.

Twist and pull each side of the nose connector to remove it from the goggles.

Temporally Remove Elastic Bands

Remove the elastic band. This will make it easier to mount all of the components. Start by carefully bending back the metal clasps on the ends. Pull out the elastic bands to remove them from the goggles.

Attach printed Nose connector case

Reference the photo and line up the case with goggles so it fits on the opposite side of the nose bridge.

Insert the nubs from the case into the slits on goggles. Twist to lock them into place. 

Remove Tinted Lens

The included tinted lenses stack on top of the clear lenses. Remove them to fit the 3D printed NeoPixel holders on top.

Place the 3D printed ring holders over the clear lens. The holder is held in place with the housing screwed back on.


Place side case

Fit one of the rotary cases over an eye and screw on the lens cover to fit it in place. Repeat this for the second eye.


Press fit rings

With the lens covers secured, press fit the NeoPixel rings into the 3D printed holders. If the tolerances are too tight for fitting, file down the NeoPixel Ring PCBs to remove any leftover rough areas ("mouse bites"). 

Insert Battery

Be sure to remove the support material inside the 3D printed case.

Connect the 400mah lipo battery to the JST connector on the Lipo Backpack. Carefully insert the battery at an angle.

Attach Slide Switch

The slide switch is fitted above the lipo battery inside the walls around the cutout. 

Insert Trinket 

With the battery and slide switch mounted, place the Trinket M0 above the battery with the Lipo Backpack facing up.

Prep Rotary

The rotary encoder is held in place with the included washer and hex nut. Remove the knob, washer and hex nut before panel mounting. The wires can be threaded through the slits on the case. 


Attach Rotary knob case

Line up the rotary encoder so it sits flush against the case. Fasten the washer and screw back on.

Feed the wires up and over the goggle lens and hold the side case with one hand while screwing the lens holder back on to the goggles.


Insert Lid

Press fit the case lid down to cover up the components.


Add the elastic band back to goggles by inserting them through the slits on the side of the goggles.

Using the rotary encoder (the black spinney knob), you can switch between the different play mode animations, adjust the speed of those animations, as well as change the color and brightness of the pixels.

On / off

Use the switch on the bridge to power on or off the goggles.

Play modes

There are 4 "play modes" for the goggles. These are the different animations that the goggles run.

Briefly press down the rotary encoder to toggle between each mode.

Play mode 1: pixels spin

Adjust the speed of the spinning by twisting the rotary encoder. Can speed up or slow down the rate at which the lights move around the goggles.

Play mode 2: "Watchmen" mode

Adjust location of bright pixels with encoder.

Play mode 3: pixels spin and collide

Adjust speed with rotary encoder.

Play mode 4: random pixel glow

Adjust speed with rotary encoder.

Transition between play and configuration modes

To change the color and brightness of the NeoPixels, you will have to change the configuration settings. These settings can be accessed by pressing and holding down the encoder until the configuration state is reached.

Config modes

There are 3 config modes. Switch between them the same way as the play modes are switched, by briefly pressing down the encoder.

Config mode 1: color

To change the color of the NeoPixels, adjust the rotary encoder. Whatever color you end up on will be the color saved for all modes moving forward.

Config mode 2: brightness

Use the encoder to adjust the NeoPixel brightness.

Config mode 3: NeoPixel alignment

This config mode is for verifying the alignment and placement of the NeoPixel rings. The leds should be at the top of the ring and symmetrical to each other. If they aren't, either reposition the rings or change the values of ring_1_offset and/or ring_2_offset in the code.


Putting this all together, here's an example of how to change the NeoPixel color from red (default) to green as well as increase the brightness. Then, how to go back to the play modes and watch the new color in action.

This guide was first published on Dec 11, 2019. It was last updated on Jul 17, 2024.