Skip to content

Instantly share code, notes, and snippets.

@tdicola
Created January 28, 2018 07:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tdicola/cf81c7c8475ed4b936f443d7a27bb1cc to your computer and use it in GitHub Desktop.
Save tdicola/cf81c7c8475ed4b936f443d7a27bb1cc to your computer and use it in GitHub Desktop.
Cyber Flower digital valentine with Gemma M0 and CircuitPython.
# Cyber Flower Digital Valentine
#
# 'Roses are red,
# Violets are blue,
# This flower changes color,
# To show its love for you.'
#
# Load this on a Gemma M0 running CircuitPython and it will smoothly animate
# the DotStar LED between different color hues. Touch the D0 pad and it will
# cause the pixel to pulse like a heart beat. Try connecting aluminum foil or
# an exposed wire to the pad so you can trigger the heart beat by holding the
# flower stem!
#
# Note on power up the flower will wait 5 seconds flashing the red LED on and
# off before starting. This is to give you time to stop touching the flower
# so its touch sensing is calibrated well. Power on the flower, put it down
# so that you're not touching it, wait 5 seconds and it will start. When you
# pick it up or touch it the capacitive sensing should trigger a heart beat.
# You might need to touch with more pressure, closer to the board, etc. in
# some cases to trigger!
#
# Author: Tony DiCola
# License: Public Domain
import math
import time
import board
import digitalio
import touchio
import adafruit_dotstar
# Variables that control the code. Try changing these to modify speed, color,
# etc.
START_DELAY = 5.0 # How many seconds to wait after power up before
# jumping into the animation and initializing the
# touch input. This gives you time to take move your
# fingers off the flower so the capacitive touch
# sensing is better calibrated. During the delay
# the small red LED on the board will flash.
TOUCH_PIN = board.D0 # The board pin to listen for touches and trigger the
# heart beat animation. You can change this to any
# other pin like board.D2 or board.D1. Make sure not
# to touch this pin as the board powers on or the
# capacitive sensing will get confused (just reset
# the board and try again).
BRIGHTNESS = 1.0 # The brightness of the colors. Set this to a value
# anywhere within 0 and 1.0, where 1.0 is full bright.
# For example 0.5 would be half brightness.
RAINBOW_PERIOD_S = 18.0 # How many seconds it takes for the default rainbow
# cycle animation to perform a full cycle. Increase
# this to slow down the animation or decrease to speed
# it up.
HEARTBEAT_BPM = 60.0 # Heartbeat animation beats per minute. Increase to
# speed up the heartbeat, and decrease to slow down.
HEARTBEAT_HUE = 300.0 # The color hue to use when animating the heartbeat
# animation. Pick a value in the range of 0 to 359
# degrees, see the hue spectrum here:
# https://en.wikipedia.org/wiki/Hue
# A value of 300 is a nice pink color.
# First initialize the DotStar LED and turn it off.
dotstar = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
dotstar[0] = (0, 0, 0) # Set the pixel to RGB color 0, 0, 0 or off.
# Also make sure the on-board red LED is turned off.
red_led = digitalio.DigitalInOut(board.L)
red_led.switch_to_output(value=False)
# Wait the startup delay period while flashing the red LED. This gives time
# to move your hand away from the flower/stem so the capacitive touch sensing
# is initialized and calibrated with a good non-touch starting state.
start = time.monotonic()
while time.monotonic() - start <= START_DELAY:
# Blink the red LED on and off every half second.
red_led.value = True
time.sleep(0.5)
red_led.value = False
time.sleep(0.5)
# Setup the touch input.
touch = touchio.TouchIn(TOUCH_PIN)
# Convert periods to frequencies that are used later in animations.
rainbow_freq = 1.0/RAINBOW_PERIOD_S
# Calculcate periods and values used by the heartbeat animation.
beat_period = 60.0/HEARTBEAT_BPM
beat_quarter_period = beat_period/4.0 # Quarter period controls the speed of
# the heartbeat drop-off (using an
# exponential decay function).
beat_phase = beat_period/5.0 # Phase controls how long in-between
# the two parts of the heart beat
# (the 'ba-boom' of the beat).
# Define a gamma correction lookup table to make colors more accurate.
# See this guide for more background on gamma correction:
# https://learn.adafruit.com/led-tricks-gamma-correction/
gamma8 = bytearray(256)
for i in range(len(gamma8)):
gamma8[i] = int(math.pow(i/255.0, 2.8)*255.0+0.5) & 0xFF
# Define a function to convert from HSV (hue, saturation, value) color to
# RGB colors that DotStar LEDs speak. The HSV color space is a nicer for
# animations because you can easily change the hue and value (brightness)
# vs. RGB colors. Pass in a hue (in degrees from 0-360) and saturation and
# value that range from 0 to 1.0. This will also use the gamma correction
# table above to get the most accurate color. Adapted from C/C++ code here:
# https://www.cs.rit.edu/~ncs/color/t_convert.html
def HSV_to_RGB(h, s, v):
r = 0
g = 0
b = 0
if s == 0.0:
r = v
g = v
b = v
else:
h /= 60.0 # sector 0 to 5
i = int(math.floor(h))
f = h - i # factorial part of h
p = v * (1.0 - s)
q = v * (1.0 - s * f)
t = v * (1.0 - s * (1.0 - f))
if i == 0:
r = v
g = t
b = p
elif i == 1:
r = q
g = v
b = p
elif i == 2:
r = p
g = v
b = t
elif i == 3:
r = p
g = q
b = v
elif i == 4:
r = t
g = p
b = v
else:
r = v
g = p
b = q
r = gamma8[int(255.0*r)]
g = gamma8[int(255.0*g)]
b = gamma8[int(255.0*b)]
return (r, g, b)
# Another handy function for linear interpolation of a value. Pass in a value
# x that's within the range x0...x1 and a range y0...y1 to get an output value
# y that's proportionally within y0...y1 based on x within x0...x1. Handy for
# transforming a value in one range to a value in another (like Arduino's map
# function).
def lerp(x, x0, x1, y0, y1):
return y0+(x-x0)*((y1-y0)/(x1-x0))
# Main loop below will run forever:
while True:
# Get the current time at the start of the animation update.
current = time.monotonic()
# Now check if the touch input is being touched and choose a different
# animation to run, either a rainbow cycle or heartbeat.
if touch.value:
# The touch input is being touched, so figure out the color using
# a heartbeat animation.
# This works using exponential decay of the color value (brightness)
# over time:
# https://en.wikipedia.org/wiki/Exponential_decay
# A heart beat is made of two sub-beats (the 'ba-boom') so two decay
# functions are calculated using the same fall-off period but slightly
# out of phase so one occurs a little bit after the other.
t0 = current % beat_period
t1 = (current + beat_phase) % beat_period
x0 = math.pow(math.e, -t0/beat_quarter_period)
x1 = math.pow(math.e, -t1/beat_quarter_period)
# After calculating both exponential decay values pick the biggest one
# as the secondary one will occur after the first. Scale each by
# the global brightness and then convert to RGB color using the fixed
# hue but modulating the color value (brightness). Luckily the result
# of the exponential decay is a value that goes from 1.0 to 0.0 just
# like we expect for full bright to zero brightness with HSV color
# (i.e. no interpolation is necessary).
val = max(x0, x1) * BRIGHTNESS
dotstar[0] = HSV_to_RGB(HEARTBEAT_HUE, 1.0, val)
else:
# The touch input is not being touched (touch.value is False) so
# compute the hue with a smooth cycle over time.
# First use the sine function to smoothly generate a value that goes
# from -1.0 to 1.0 at a certain frequency to match the rainbow period.
x = math.sin(2.0*math.pi*rainbow_freq*current)
# Then compute the hue by converting the sine wave value from something
# that goes from -1.0 to 1.0 to instead go from 0 to 359 degrees.
hue = lerp(x, -1.0, 1.0, 0.0, 359.0)
# Finally update the DotStar LED by converting the HSV color at the
# specified hue to a RGB color the LED understands.
dotstar[0] = HSV_to_RGB(hue, 1.0, BRIGHTNESS)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment