Inside Out memory ball with Adafruit Gemma M0 and Neopixel Jewel

My family’s Halloween costumes this year are Inside Out themed, with my wife being Joy and my daughter being Sadness. I thought it would be fun to create a flickering memory ball that can change from yellow to blue when sadness picks it up.

My build is mostly based on https://learn.adafruit.com/10-minute-neopixel-necklace/introduction, so be sure to look there for any info I might be leaving out!

Materials

Tools

  • Drill
  • Soldering iron
  • Computer
  • Wire stripper
  • Needle-nosed pliers

Assembly

  1. Attach your battery to your battery charger and insert in a USB charger or computer to charge the battery. The light on the charger should be red while it is charging, and then green when fully charged.
  2. Use masking tape on the outside of the bath bomb mold to protect the surface from overspray. In a well-ventilated area (preferably outdoors) coat the inside of the bath comb mold with frosted glass spray. Allow to dry.
  3. Pick one half of the mold to be the bottom. Center your button on the bottom, marking the positions of the four prongs with a permanent marker. Drill four small holes at the four marked spots. Insert the button through the holes, then use pliers to bend the prongs in so the button is firmly attached to the mold.
  4. Follow https://learn.adafruit.com/10-minute-neopixel-necklace/assembly to attach the Neopixel Jewel to the Gemma M0. I decided to solder three wires into the back of the Neopixel Jewel, and the place it back-to-back with the Gemma M0, using some eletrical tape to prevent short circuits. I then soldered the wires to the Gemma M0.
  5. Solder two long-ish (~4in) to diagonally opposite corners of button, and then solder one to the GND and one to the D2 contact of the Gemma M0.

Code

import time
import random
import board
import adafruit_dotstar
import neopixel
from digitalio import DigitalInOut, Direction, Pull

# Input/Output definitions for NeoPixel Jewel on pin D1
pixpin = board.D1
numpix = 7
pixels = neopixel.NeoPixel(pixpin, numpix, brightness=.3, auto_write=False)

# Input/Output definitions for built-in DotStar RGB led on Adafruit Gemma M0.
# Note that we do not set up I/O for the red LED on the Gemma, as it should be
# off by default.
led = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)

# Input/Output definitions for a tactile switch on pin D2.
switch = DigitalInOut(board.D2)
switch.direction = Direction.INPUT
switch.pull = Pull.UP

# Define the base colors for the LEDs. The built-in LED is a very different
# color than the NeoPixel Jewel, so different base colors.

# Base colors for the NeoPixel JEwel
rgb_colors = ([255, 80, 0],  # joy color
              [0, 128, 255])  # sadness color
# Base colors for the built-in DotStar LED.
rgb_colors_back = ([255, 215, 0],  # joy color
                   [0, 128, 255])  # sadness color

# Variables to be controlled by the button.
mode = 0  # 0 is joy, 1 is sadness
# We only want the color to switch once when we press the button, so we need
# to keep track of whether we already handled the most recent button press.
button_press_handled = False

# These variables track the state for each pixel. The first 7 entries
# correspond to the NeoPixel elements, and the last to the DotStar led.
mode_array = [0, 0, 0, 0, 0, 0, 0, 0]  # Tracks whether joy or sadness
val_array = [1., 1., 1., 1., 1., 1., 1., 1.]  # Tracks brightness
sat_array = [1., 1., 1., 1., 1., 1., 1., 1.]  # Tracks saturation

# This is designed so that each time we change the mode it takes 8 steps
# for the change to propagate to all elements.
def animate_mode_switch():
    # If I wasn't so lazy I'd do this with a loop.
    mode_array[6] = mode_array[5]
    mode_array[5] = mode_array[1]
    mode_array[1] = mode_array[0]
    mode_array[0] = mode_array[4]
    mode_array[4] = mode_array[2]
    mode_array[2] = mode_array[3]
    mode_array[3] = mode_array[7]
    mode_array[7] = mode

def color_adjust(val, sat, color):
    # val controls the overall brightness of the led and sat controls how
    # saturated the color is. Rather than do real HSV calculations, we blend
    # with white to approximate desaturation, and then scale by val to reduce
    # the brightness.
    return [int(val*(sat*x + (1-sat)*255)) for x in color]

def mutate_sat_and_value():
    # Controls how much the val for each pixel can vary in one time step
    varation_rate = 0.2
    for r in range(len(val_array)):
        cur_val = val_array[r]
        cur_val = cur_val + varation_rate/2 - varation_rate * random.random()
        # Clamp the val for each pixel to between 0.05 and 0.5
        cur_val = min(cur_val, 0.5)
        cur_val = max(cur_val, 0.05)
        val_array[r] = cur_val

    # Controls how much the sat for each pixel can vary in one time step
    varation_rate = 0.025
    for r in range(len(sat_array)):
        cur_val = sat_array[r]
        cur_val = cur_val + varation_rate/2 - varation_rate * random.random()
        # Clamp the sat for each pixel to between 1.0 and 0.95
        cur_val = min(cur_val, 1.0)
        cur_val = max(cur_val, 0.95)
        sat_array[r] = cur_val

def handle_button():
    global button_press_handled
    global mode
    # If the button is currently pressed (which correponds to switch.value
    # being False!), and we didn't aready handle it, then we flip modes.
    if not switch.value and not button_press_handled:
        button_press_handled = True
        mode = 1 - mode  # A nice way to switch modes
    elif switch.value and button_press_handled:
        button_press_handled = False

def update_pixel_colors():
    # Compute the color for each pixel. Start with the appropriate base color
    # based on mode, and then apply the saturation and value to it.
    
    # This part handles the NeoPixels
    for r in range(len(pixels)):
        pixels[r] = color_adjust(val_array[r], sat_array[r],
                                 rgb_colors[mode_array[r]])
    pixels.write()
    # This part handles the DotStar
    led[0] = color_adjust(val_array[7], sat_array[7],
                          rgb_colors_back[mode_array[7]])

while True:
    # This is the main loop.
    handle_button()
    animate_mode_switch()
    mutate_sat_and_value()
    update_pixel_colors()
    # Anything bigger than 0.1 here seems to make the button handling too
    # sluggish. Hopefully setting this to 0.1 will save power.
    time.sleep(0.1)

Leave a Reply to Inside Out Memory Ball with #GemmaM0 and #NeoPixelJewel « Adafruit Industries – Makers, hackers, artists, designers and engineers! Cancel reply

Your email address will not be published. Required fields are marked *