CircuitPython Code

GEMMA M0 boards can run CircuitPython — a different approach to programming compared to Arduino sketches. In fact, CircuitPython comes factory pre-loaded on GEMMA M0. If you’ve overwritten it with an Arduino sketch, or just want to learn the basics of setting up and using CircuitPython, this is explained in the Adafruit GEMMA M0 guide.

These directions are specific to the “M0” GEMMA board. The original GEMMA with an 8-bit AVR microcontroller doesn’t run CircuitPython…for those boards, use the Arduino sketch on the “Arduino code” page of this guide.

Below is CircuitPython code that works similarly (though not exactly the same) as the Arduino sketch shown on a prior page. To use this, plug the GEMMA M0 into USB…it should show up on your computer as a small flash drive…then edit the file “main.py” with your text editor of choice. Select and copy the code below and paste it into that file, entirely replacing its contents (don’t mix it in with lingering bits of old code). When you save the file, the code should start running almost immediately (if not, see notes at the bottom of this page).

If GEMMA M0 doesn’t show up as a drive, follow the GEMMA M0 guide link above to prepare the board for CircuitPython.

# Gemma "Firewalker Lite" sneakers
#  - Uses the following Adafruit parts (X2 for two shoes):
#    * Gemma M0 3V microcontroller (#3501)
#    * 150 mAh LiPoly battery (#1317) or larger
#    * Medium vibration sensor switch (#2384)
#    * 60/m NeoPixel RGB LED strip (#1138 or #1461)
#    * LiPoly charger such as #1304
#
# - originally written by Phil Burgess for Gemma using Arduino
#   * https://learn.adafruit.com/gemma-led-sneakers

import board
import digitalio
import neopixel

try:
    import urandom as random
except ImportError:
    import random

# Declare a NeoPixel object on led_pin with num_leds as pixels
# No auto-write.
led_pin = board.D1  # Which pin your pixels are connected to
num_leds = 40  # How many LEDs you have
circumference = 40  # Shoe circumference, in pixels, may be > NUM_LEDS
frames_per_second = 50  # Animation frames per second
brightness = 0  # Current wave height
strip = neopixel.NeoPixel(led_pin, num_leds, brightness=1, auto_write=False)
offset = 0

# vibration sensor
motion_pin = board.D0  # Pin where vibration switch is connected
pin = digitalio.DigitalInOut(motion_pin)
pin.direction = digitalio.Direction.INPUT
pin.pull = digitalio.Pull.UP
ramping_up = False

center = 0  # Center point of wave in fixed-point space (0 - 255)
speed = 1  # Distance to move between frames (-128 - +127)
width = 2  # Width from peak to bottom of triangle wave (0 - 128)
hue = 3  # Current wave hue (color) see comments later
hue_target = 4  # Final hue we're aiming for
red = 5  # LED RGB color calculated from hue
green = 6  # LED RGB color calculated from hue
blue = 7  # LED RGB color calculated from hue

y = 0
brightness = 0
count = 0

# Gemma can animate 3 of these on 40 LEDs at 50 FPS
# More LEDs and/or more waves will need lower
wave = [0] * 8, [0] * 8, [0] * 8

# Note that the speeds of each wave are different prime numbers.
# This avoids repetition as the waves move around the
# perimeter...if they were even numbers or multiples of each
# other, there'd be obvious repetition in the pattern of motion...
# beat frequencies.
n_waves = len(wave)

# 90 distinct hues (0-89) around color wheel
hue_table = [255, 255, 255, 255, 255, 255, 255, 255, 237, 203,
             169, 135, 101, 67, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             0, 0, 0, 0, 0, 0, 18, 52, 86, 120, 154, 188, 222,
             255, 255, 255, 255, 255, 255, 255, 255]

# Gamma-correction table
gammas = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
    2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
    5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
    10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
    17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
    25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
    37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
    51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
    69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
    90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110,
    112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133,
    135, 137, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158,
    160, 162, 164, 167, 169, 171, 173, 175, 177, 180, 182, 184, 186,
    189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218,
    220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252,
    255
]


def h2rgb(colour_hue):
    colour_hue %= 90
    h = hue_table[colour_hue >> 1]

    if colour_hue & 1:
        ret = h & 15
    else:
        ret = (h >> 4)

    return ret * 17


# pylint: disable=global-statement
def wave_setup():
    global wave

    wave = [[0, 3, 60, 0, 0, 0, 0, 0],
            [0, -5, 45, 0, 0, 0, 0, 0],
            [0, 7, 30, 0, 0, 0, 0, 0]]

    # assign random starting colors to waves
    for wave_index in range(n_waves):
        current_wave = wave[wave_index]
        random_offset = random.randint(0, 90)

        current_wave[hue] = current_wave[hue_target] = 90 + random_offset
        current_wave[red] = h2rgb(current_wave[hue] - 30)
        current_wave[green] = h2rgb(current_wave[hue])
        current_wave[blue] = h2rgb(current_wave[hue] + 30)


def vibration_detector():
    while True:
        if not pin.value:
            return True


while True:

    # wait for vibration sensor to trigger
    if not ramping_up:
        ramping_up = vibration_detector()
        wave_setup()

    # But it's not just a straight shot that it ramps up.
    # This is a low-pass filter...it makes the brightness
    # value decelerate as it approaches a target (200 in
    # this case).  207 is used here because integers round
    # down on division and we'd never reach the target;
    # it's an ersatz ceil() function: ((199*7)+200+7)/8 = 200;
    brightness = int(((brightness * 7) + 207) / 8)
    count += 1

    if count == (circumference + num_leds + 5):
        ramping_up = False
        count = 0

    # Wave positions and colors are updated...
    for w in range(n_waves):
        # Move wave; wraps around ends, is OK!
        wave[w][center] += wave[w][speed]

        # Hue not currently changing?
        if wave[w][hue] == wave[w][hue_target]:

            # There's a tiny random chance of picking a new hue...
            if not random.randint(frames_per_second * 4, 255):
                # Within 1/3 color wheel
                wave[w][hue_target] = random.randint(
                    wave[w][hue] - 30, wave[w][hue] + 30)

        # This wave's hue is currently shifting...
        else:

            if wave[w][hue] < wave[w][hue_target]:
                wave[w][hue] += 1  # Move up or
            else:
                wave[w][hue] -= 1  # down as needed

            # Reached destination?
            if wave[w][hue] == wave[w][hue_target]:
                wave[w][hue] = 90 + wave[w][hue] % 90  # Clamp to 90-180 range
                wave[w][hue_target] = wave[w][hue]  # Copy to target

            wave[w][red] = h2rgb(wave[w][hue] - 30)
            wave[w][green] = h2rgb(wave[w][hue])
            wave[w][blue] = h2rgb(wave[w][hue] + 30)

        # Now render the LED strip using the current
        # brightness & wave states.
        # Each LED in strip is visited just once...
        for i in range(num_leds):

            # Transform 'i' (LED number in pixel space) to the
            # equivalent point in 8-bit fixed-point space (0-255)
            # "* 256" because that would be
            # the start of the (N+1)th pixel
            # "+ 127" to get pixel center.
            x = (i * 256 + 127) / circumference

            # LED assumed off, but wave colors will add up here
            r = g = b = 0

            # For each item in wave[] array...
            for w_index in range(n_waves):
                # Calculate distance from pixel center to wave
                # center point, using both signed and unsigned
                # 8-bit integers...
                d1 = int(abs(x - wave[w_index][center]))
                d2 = int(abs(x - wave[w_index][center]))

                # Then take the lesser of the two, resulting in
                # a distance (0-128)
                # that 'wraps around' the ends of the strip as
                # necessary...it's a contiguous ring, and waves
                # can move smoothly across the gap.
                if d2 < d1:
                    d1 = d2  # d1 is pixel-to-wave-center distance

                # d2 distance, relative to wave width, is then
                # proportional to the wave's brightness at this
                # pixel (basic linear y=mx+b stuff).
                # Is distance within wave's influence?
                # d2 is opposite; distance to wave's end
                if d1 < wave[w_index][width]:
                    d2 = wave[w_index][width] - d1
                    y = int(brightness * d2 / wave[w_index][width])  # 0 to 200

                    # y is a brightness scale value --
                    # proportional to, but not exactly equal
                    # to, the resulting RGB value.
                    if y < 128:  # Fade black to RGB color
                        # In HSV colorspace, this would be
                        # tweaking 'value'
                        n = int(y * 2 + 1)  # 1-256
                        r += (wave[w_index][red] * n) >> 8  # More fixed-point math
                        # Wave color is scaled by 'n'
                        g += (wave[w_index][green] * n) >> 8
                        b += (wave[w_index][blue] * n) >> 8  # >>8 is equiv to /256
                    else:  # Fade RGB color to white
                        # In HSV colorspace, this tweaks 'saturation'
                        n = int((y - 128) * 2)  # 0-255 affects white level
                        m = 256 * n
                        n = 256 - n  # 1-256 affects RGB level
                        r += (m + wave[w_index][red] * n) >> 8
                        g += (m + wave[w_index][green] * n) >> 8
                        b += (m + wave[w_index][blue] * n) >> 8

            # r,g,b are 16-bit types that accumulate brightness
            # from all waves that affect this pixel; may exceed
            # 255.  Now clip to 0-255 range:
            if r > 255:
                r = 255
            if g > 255:
                g = 255
            if b > 255:
                b = 255

            # Store resulting RGB value and we're done with
            # this pixel!
            strip[i] = (r, g, b)

        # Once rendering is complete, a second pass is made
        # through pixel data applying gamma correction, for
        # more perceptually linear colors.
        # https://learn.adafruit.com/led-tricks-gamma-correction
        for j in range(num_leds):
            (red_gamma, green_gamma, blue_gamma) = strip[j]
            red_gamma = gammas[red_gamma]
            green_gamma = gammas[green_gamma]
            blue_gamma = gammas[blue_gamma]
            strip[j] = (red_gamma, green_gamma, blue_gamma)

        strip.show()

This code requires the neopixel.py library. A factory-fresh board will have this already installed. If you’ve just reloaded the board with CircuitPython, create the “lib” directory and then download neopixel.py from Github.

This guide was first published on Dec 02, 2015. It was last updated on Dec 02, 2015. This page (CircuitPython Code) was last updated on Nov 21, 2019.