CircuitPython is Python that runs on microcontrollers, including your Gemma board! To learn more about CircuitPython, check out the Welcome to CircuitPython and CircuitPython Essentials guides. This guide uses CircuitPython and Gemma.
This project gets super fancy! We're using the LED and the touch pads, like we do in the Red light, green light blue light project. However, we've added some very different functionality to this CircuitPython program. We'll have solid colors like the first one, but we also have some Python-colored blinky fun and an awesome rainbow! As well, you'll be able to change the speed of the blinking and the rainbow, and change the brightness of the LED. We're going to use helper functions, generators, dictionaries, and state machines to make this happen, all while keeping the code non-blocking.
All of the concepts used in this program are introduced and explained in the Hacking Ikea Lamps with Circuit Playground Express Guide. Each concept is linked to the applicable section of the guide. If you'd like more detailed explanations of everything we use here, check it out!
We have three different inputs, the three touch pads on your Gemma board. These inputs will control different modes, speeds, brightness.
We'll use:
- Touch pad A0 to change color modes
- Touch pad A1 to change speeds
- Touch pad A2 to change brightness
The five different modes that touch pad A0 will cycle through are:
- a smooth rainbow cycle
- a blinking blue and yellow Python-colored blinking mode
- three static solid colors: red, green and blue.
Let's take a look at the code!
# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries # # SPDX-License-Identifier: MIT """Interactive light show using built-in LED and capacitive touch""" import time from rainbowio import colorwheel import adafruit_dotstar import board import touchio led = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1) touch_A0 = touchio.TouchIn(board.A0) touch_A1 = touchio.TouchIn(board.A1) touch_A2 = touchio.TouchIn(board.A2) def cycle_sequence(seq): """Allows other generators to iterate infinitely""" while True: for elem in seq: yield elem def rainbow_cycle(seq): """Rainbow cycle generator""" rainbow_sequence = cycle_sequence(seq) while True: # pylint: disable=stop-iteration-return led[0] = (colorwheel(next(rainbow_sequence))) yield def brightness_cycle(): """Allows cycling through brightness levels""" brightness_value = cycle_sequence([1, 0.8, 0.6, 0.4, 0.2]) while True: # pylint: disable=stop-iteration-return led.brightness = next(brightness_value) yield color_sequences = cycle_sequence( [ range(256), # rainbow_cycle [50, 160], # Python colors! [0], # red [85], # green [170], # blue ] ) cycle_speeds = cycle_sequence([0.1, 0.3, 0.5]) brightness = brightness_cycle() CYCLE_SPEED_INITIAL = 0.3 cycle_speed_start = time.monotonic() cycle_speed = cycle_speed_start + CYCLE_SPEED_INITIAL rainbow = None touch_A0_state = None touch_A1_state = None touch_A2_state = None while True: now = time.monotonic() if not touch_A0.value and touch_A0_state is None: touch_A0_state = "ready" if touch_A0.value and touch_A0_state == "ready" or rainbow is None: rainbow = rainbow_cycle(next(color_sequences)) touch_A0_state = None if now >= cycle_speed: next(rainbow) cycle_speed_start = now cycle_speed = cycle_speed_start + CYCLE_SPEED_INITIAL if not touch_A1.value and touch_A1_state is None: touch_A1_state = "ready" if touch_A1.value and touch_A1_state == "ready": CYCLE_SPEED_INITIAL = next(cycle_speeds) cycle_speed_start = now cycle_speed = cycle_speed_start + CYCLE_SPEED_INITIAL touch_A1_state = None if not touch_A2.value and touch_A2_state is None: touch_A2_state = "ready" if touch_A2.value and touch_A2_state == "ready": next(brightness) touch_A2_state = None
import time import adafruit_dotstar import touchio import board
Next, we create the LED and touch objects.
DotStar LEDs use SPI, but the DotStar on the Gemma has its own unique pin assignments. So we assign led = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
to tell it which pins to use. The 1
tells the code that we are using a single LED.
led = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
If you look at your board, you'll see three pads that have A0, A1 and A2 next to them. Those are the touch pads on your Gemma. We have three touch pads, so we create three touch objects. To create a touch object, you need to provide the pin you plan to use. We start with touch_A0 = touchio.TouchIn(board.A0)
and then use the same concept for touch_A1
and touch_A2
.
touch_A0 = touchio.TouchIn(board.A0) touch_A1 = touchio.TouchIn(board.A1) touch_A2 = touchio.TouchIn(board.A2)
Helper Function
To learn more about wheel()
, check out this page.
def wheel(pos): """ Input a value 0 to 255 to get a color value. The colours are a transition r - g - b - back to r.""" if pos < 0 or pos > 255: return 0, 0, 0 if pos < 85: return int(255 - pos * 3), int(pos * 3), 0 if pos < 170: pos -= 85 return 0, int(255 - pos * 3), int(pos * 3) pos -= 170 return int(pos * 3), 0, int(255 - (pos * 3))
Our helper function is called wheel()
, and is necessary for the rainbow cycle to work. wheel()
allows us to specify any color using a single number by requiring a position, (pos)
. Since we're already using it for the rainbow, we'll also be using it for all of our colors.
Generators
To learn more about these generators, check out this page.
Our first generator is called cycle_sequence()
and is designed to allow the other generators to iterate infinitely instead of stopping after the initial set of values.
def cycle_sequence(seq): """Allows other generators to iterate infinitely""" while True: for elem in seq: yield elem
Next we have rainbow_cycle()
which iterates through the entire set of rainbow colors and starts again to create our rainbow cycle. Normally the rainbow cycle must complete before any changes can be made, but the generator allows us to provide input at any time. That way we can change the mode or the brightness at any time without waiting until the cycle is complete!
def rainbow_cycle(seq): """Rainbow cycle generator""" rainbow = cycle_sequence(seq) while True: led[0] = (wheel(next(rainbow))) yield
The brightness_cycle()
generator iterates though the different brightness levels. Brightness is assigned using any number 0
through 1
, which represents 0-100%. So, a brightness level of 0.3
is 30% brightness, and a brightness of 0.07
is 7% brightness. We've included brightness levels of 0.2
, 0.4
, 0.6
, 0.8
, and 1
, beginning with 1
and decreasing each time.
def brightness_cycle(): """Allows cycling through brightness levels""" brightness_value = cycle_sequence([1, 0.8, 0.6, 0.4, 0.2]) while True: led.brightness = next(brightness_value) yield
The next two generators use cycle_sequence
to iterate through a list of values. The first, color_sequences
, is a list containing the different (pos)
position values that will be provided to wheel
. The second generator, cycle_speeds
, contains the speed of our modes in seconds. To be clear, this is not the speed to cycle between modes - that will be done with user input. This affects the speed of the rainbow and Python-color blink mode.
color_sequences = cycle_sequence( [ range(256), # rainbow_cycle [50, 160], # Python colors! [0], # red [85], # green [170], # blue ] ) cycle_speeds = cycle_sequence([0.1, 0.3, 0.5])
We assign brightness = brightness_cycle()
so we can use the brightness_cycle()
generator later.
Time
To learn more about non-blocking time code, check out this page.
cycle_speed_initial = 0.3 cycle_speed_start = time.monotonic() cycle_speed = cycle_speed_start + cycle_speed_initial
Here is where we begin our non-blocking time code. time.monotonic()
is a time in seconds since your board was last power cycled. To use time.monotonic()
, you must assign it to variables so you can compare different points in time to figure out when to allow the code to continue. So, you find the time delta by subtracting a start time from a current time. Here we have cycle_speed_initial
, the initial cycle speed constant of 0.3
seconds. We have cycle_speed_start
, the start speed assigned to time.monotonic()
, and a cycle_speed
that subtracts the initial speed from the start speed to create a delay of 0.3 seconds in a non-blocking variable available for use later.
Variables
We have four variables we initialise for use later.
rainbow = None touch_A0_state = None touch_A1_state = None touch_A2_state = None
rainbow
is so we can call the rainbow_cycle
generator. The three touch_state
variables are for use in the state machines that we'll use to keep the touch pads from spamming touch values when you touch and hold on any given pad.
Main Loop
We begin our main loop with while True:
.
First we create a current time for comparison by assigning now = time.monotonic()
.
In the first project, we learned that we can touch and hold on the touch pad and they will continue to respond. For this project, that won't work. We are cycling through different modes and would like to be able to control which mode at which we would like to stop. So, we're going to create a state machine that causes the touch pad to respond once each time we touch it. That way we can cycle through one mode at a time, regardless of how long we touch the touch pad.
Our first state machine utilises touch pad A0.
if not touch_A0.value and touch_A0_state is None: touch_A0_state = "ready"
We're going to use our touch_A0_state variable. Remember, we assigned it to None before our loop. Here we're saying, "if we haven't touched touch pad A0, and touch_A0_state is None
, then assign touch_A0_state = "ready"
."
if touch_A0.value and touch_A0_state == "ready" or rainbow is None: rainbow = rainbow_cycle(next(color_sequences)) touch_A0_state = None
Then we use that state to determine that we're ready to accept touch input. After we accept the single touch input, we call our rainbow generator,. Then we reassign touch_A0_state = None
so we can begin again.
Inside our state machine, we called the rainbow generator. Generators are used by calling next
. The way the rainbow_cycle
generator works is by calling next
on the color_sequences
generator, which provides rainbow_cycle
with wheel()
positions.
Our rainbow_cycle generator accepts ranges, static colors and groups of colors, by utilising wheel(). wheel() allows for a position of range(256) which cycles through all available colors, single numbers, which returns static colors, and groups of colors, which allows for blinking.
We use the next section of code to determine the speed at which we are calling next
on rainbow
.
if now >= cycle_speed: next(rainbow) cycle_speed_start = now cycle_speed = cycle_speed_start + cycle_speed_initial
This is where we set the speed of each color mode. Solid colors don't care about speed and simply aren't affected. This speed is important to the rainbow and blink modes.
Next, we have another state machine, with exactly the same syntax and concept as the first, used with touch pad A1.
if not touch_A1.value and touch_A1_state is None: touch_A1_state = "ready" if touch_A1.value and touch_A1_state == "ready":
Touch pad A1 is used to change the speed of the blinking and the rainbow. So we have a similar set of time
code as we had in our setup.
cycle_speed_initial = next(cycle_speeds) cycle_speed_start = now cycle_speed = cycle_speed_start + cycle_speed_initial
Then we assign touch_A1_state = None
so the state machine is ready to begin again.
The last bit of code is the third state machine used with touch pad A2.
if not touch_A2.value and touch_A2_state is None: touch_A2_state = "ready" if touch_A2.value and touch_A2_state == "ready":
We use touch pad A2 to cycle through the brightness levels by calling next
on the brightness
generator.
next(brightness)
And with that, we reach the end of our program! Put it all together, and you get a fancy interactive tough light using your new Gemma!
While you're welcome to edit any part of the program, there's a few things in here that you can easily change up for different effects. Let's take a look!
Change it up!
Colors and Blinks
You can change the blink modes or the static colors by adding more data to the list contained within the color_sequences
generator.
To add another static color mode, you would add another line similar to the red, green or blue lines. For example, if you would like to include orange, you would add another line containing [10],
, which is the wheel()
position that returns orange. Adding orange might look like the following example.
color_sequences = cycle_sequence( [ range(256), # rainbow_cycle [50, 160], # Python colors! [0], # red [10], # orange [85], # green [170], # blue ] )
The code on this page has seven different static colors. Check it out to get some ideas of what colors are in different positions in wheel()
!
To add another blinking mode, you would provide another line similar to the Python colors blink line. For example, if you would like to have a cyan and purple blinking mode, you would add another line containing [137, 213],
, which includes the two wheel()
positions that return cyan and purple. It may look like the following.
color_sequences = cycle_sequence( [ range(256), # rainbow_cycle [50, 160], # Python colors! [137, 213] # Cyan and purple [0], # red [85], # green [170], # blue ] )
You can blink as many different colors in one mode as you'd like. To add a mode that blinks seven different colors, you'd add a line containing [0, 10, 30, 85, 137, 170, 213],
. Adding it may look like the following.
color_sequences = cycle_sequence( [ range(256), # rainbow_cycle [50, 160], # Python colors! [0], # red [85], # green [170], # blue [0, 10, 30, 85, 137, 170, 213], # Party mode! ] )
It's really easy to add all kinds of different modes to your Gemma light!
Brightness
You can change the available brightness levels. Remember, brightness is assigned by using a number from 0-1 to represent 0-100%. So to change the brightness levels that the code cycles through, you simply need to add to or remove some of the brightness levels in brightness_cycle
generator.
For example, if you would like to have a dimmer option than is already available, you could add 0.1,
into the the list in the line reading brightness_value = cycle_sequence([0.1, 1, 0.8, 0.6, 0.4, 0.2])
.
If you would rather have less brightness options, remove some of the numbers in the list. For example, [0.1, 0.5, 1]
cycles between 10%, 50% and 100% without all the steps in between.
As well, if you would like to change the order, you can move the numbers around in the list so it cycles in a different order. That might look like [0,2, 0.4, 0.6, 0.8, 1]
. You can do it however you'd like!
Speeds
You can change the available speeds. The speed generator works exactly like the brightness generator in terms of making alterations to it. You will add to, remove from, or change the order of the current list of numbers in the line reading cycle_speeds = cycle_sequence([0.1, 0.3, 0.5])
. The current available speeds are 0.1 seconds, 0.3 seconds and 0.5 seconds. Remember, these are the delays between changes in the modes that change, such as the rainbow mode and the blink mode.
For example, if you'd like to add a slower option, your new list might look like [0.1, 0.3, 0.5, 0.7]
. If you wanted to add a faster option, your new list might look like [0.05, 0.1, 0.3, 0.5]
. If you wanted to change the order that the speeds cycle, your new list might look like [0.5, 0.3, 0.1]
.
You can also change the speed it starts at by changing CYCLE_SPEED_INITIAL = 0.3
to be a different number of seconds. If you'd like it to start out faster, try 0.1
seconds. If you'd like it to start out slower, try 0.5
. You can change it to any number of seconds you'd like. It doesn't have to be a number within the cycle_speeds
list.
It's super simple to customise the speed options!
Page last edited January 21, 2025
Text editor powered by tinymce.