You'll code your snow globe using CircuitPython. You'll be able to use the built-in accelerometer to detect when the snow globe is shaken, and then play a light show on the NeoPixels and play simple songs with the built-in speaker.
Get Ready!
- First, make sure you're familiar with the basics of using CircuitPython on the Circuit Playground Express. Follow this guide to familiarize yourself.
- Then, install CircuitPython on your board by following these instructions.
- The last thing to do to prepare is to install the library bundle onto your board as shown here. The libraries give us what we need to code easily with high level commands!
Download the Snow Globe Python Code
Click 'Download Project Bundle' below and copy the files over to the CIRCUITPY drive. After you've copied them over, the drive should look something like this:
# SPDX-FileCopyrightText: 2017 John Edgar Park for Adafruit Industries # # SPDX-License-Identifier: MIT """Snow Globe for Adafruit Circuit Playground express with CircuitPython """ import math import time from adafruit_circuitplayground.express import cpx ROLL_THRESHOLD = 30 # Total acceleration cpx.pixels.brightness = 0.1 # set brightness value WHITE = (65, 65, 65) RED = (220, 0, 0) GREEN = (0, 220, 0) BLUE = (0, 0, 220) SKYBLUE = (0, 20, 200) BLACK = (0, 0, 0) # Initialize the global states new_roll = False rolling = False # pick from colors defined above, e.g., RED, GREEN, BLUE, WHITE, etc. def fade_pixels(fade_color): # fade up for j in range(25): pixel_brightness = (j * 0.01) cpx.pixels.brightness = pixel_brightness for i in range(10): cpx.pixels[i] = fade_color # fade down for k in range(25): pixel_brightness = (0.25 - (k * 0.01)) cpx.pixels.brightness = pixel_brightness for i in range(10): cpx.pixels[i] = fade_color # fade in the pixels fade_pixels(GREEN) # pylint: disable=too-many-locals def play_song(song_number): # 1: Jingle bells # 2: Let It Snow # set up time signature whole_note = 1.5 # adjust this to change tempo of everything # these notes are fractions of the whole note half_note = whole_note / 2 quarter_note = whole_note / 4 dotted_quarter_note = quarter_note * 1.5 eighth_note = whole_note / 8 # pylint: disable=unused-variable # set up note values A3 = 220 Bb3 = 233 B3 = 247 C4 = 262 Db4 = 277 D4 = 294 Eb4 = 311 E4 = 330 F4 = 349 Gb4 = 370 G4 = 392 Ab4 = 415 A4 = 440 Bb4 = 466 B4 = 494 if song_number == 1: # jingle bells jingle_bells_song = [ [E4, quarter_note], [E4, quarter_note], [E4, half_note], [E4, quarter_note], [E4, quarter_note], [E4, half_note], [E4, quarter_note], [G4, quarter_note], [C4, dotted_quarter_note], [D4, eighth_note], [E4, whole_note], ] # pylint: disable=consider-using-enumerate for n in range(len(jingle_bells_song)): cpx.start_tone(jingle_bells_song[n][0]) time.sleep(jingle_bells_song[n][1]) cpx.stop_tone() if song_number == 2: # Let It Snow let_it_snow_song = [ [B4, dotted_quarter_note], [A4, eighth_note], [G4, quarter_note], [G4, dotted_quarter_note], [F4, eighth_note], [E4, quarter_note], [E4, dotted_quarter_note], [D4, eighth_note], [C4, whole_note], ] for n in range(len(let_it_snow_song)): cpx.start_tone(let_it_snow_song[n][0]) time.sleep(let_it_snow_song[n][1]) cpx.stop_tone() play_song(1) # play music on start # Loop forever while True: # check for shaking # Compute total acceleration x_total = 0 y_total = 0 z_total = 0 for count in range(10): x, y, z = cpx.acceleration x_total = x_total + x y_total = y_total + y z_total = z_total + z time.sleep(0.001) x_total = x_total / 10 y_total = y_total / 10 z_total = z_total / 10 total_accel = math.sqrt(x_total * x_total + y_total * y_total + z_total * z_total) # Check for rolling if total_accel > ROLL_THRESHOLD: roll_start_time = time.monotonic() new_roll = True rolling = True print('shaken') # Rolling momentum # Keep rolling for a period of time even after shaking stops if new_roll: if time.monotonic() - roll_start_time > 2: # seconds to run rolling = False # Light show if rolling: fade_pixels(SKYBLUE) fade_pixels(WHITE) cpx.pixels.brightness = 0.8 cpx.pixels.fill(WHITE) elif new_roll: new_roll = False # play a song! play_song(2) # return to resting color fade_pixels(GREEN) cpx.pixels.brightness = 0.05 cpx.pixels.fill(GREEN)
The board will restart once the code has been saved. You'll see the pixels fade up green, and then a song plays.
Now, you can shake the board to start the snowfall light sequence, followed by a second song. Finally, it will fade back to green, awaiting the next time it is shaken.
Here's how the code works!
Snowy Code!
There are three basic things we need for our code to do:
- Recognize when it's being shaken
- Play music
- Light lights
Using the Circuit Playground Express library
In CircuitPython on the Circuit Playground Express, we can do most of these things with high level commands, such as:
cpx.pixels.fill(255, 0, 0)
to make all of the NeoPixels turn red, or:
cpx.start_tone(440)
to play an A4 music note.
(These commands are made possible by the use of the Circuit Playground Express library which is part of the library bundle you installed.)
With the library available on the board, you can then import it into your code with this line:
from adafruit_circuitplayground.express import cpx
Now, you can use a number of commands that simplify and make consistent the ways you work with the board. Most functions, such as reading the buttons and sensors, to lighting NeoPixels, and playing tones and .wav files have a cpx
command available.
A Tour of the Code
Let's have a look at the code in small chunks before we save the entire program to the board.
First, we'll import the libraries to give us access to simpler, higher level commands that we'll need.
# Snow Globe # Circuit Playground Express from adafruit_circuitplayground.express import cpx import math import time
Next, we'll set up a variable called ROLL_THRESHOLD
that determines how hard we'll need to shake the snow globe to activate it.
We'll also set the total brightness of the NeoPixels, and create color names to control the red, green, and blue values of the LEDs without needing to write in the numerical values each time.
ROLL_THRESHOLD = 30 # Total acceleration cpx.pixels.brightness = 0.1 # set brightness value WHITE = (65, 65, 65) RED = (220, 0, 0) GREEN = (0, 220, 0) BLUE = (0, 0, 220) SKYBLUE = (0, 20, 200) BLACK = (0, 0, 0)
Fading Pixels
In order to make our code efficient, we'll create a function named fade_pixels
that controls the fade up and fade down of NeoPixels. We can call this function, along with one of our pre-defined color names, any time we need to animate the lights later on.
The contents of this function are two loops, one for fade up and a second for fade down. Let's look at the fade up loop (they both work essentially the same way).
The line for j in range(25):
is a loop that iterates the code below it that is indented in a level 25 times. Each time, it increments the value of j
by one, so j
starts at 0 and ends at 24.
The code that is iterated is an increase of the pixel_brightness
variable:
pixel_brightness = (j * 0.01)
So, this starts out as 0 and steps through to a value of 0.24
This number is is then applied to the NeoPixels overall in the next line:
cpx.pixels.brightness = pixel_brightness
Then, each of the ten NeoPixel is set to the specified fade_color
with the next loop for i in range(10):
which iterates over the line cpx.pixels[i] = fade_color
def fade_pixels(fade_color): # pick from colors defined above, e.g., RED, GREEN, BLUE, WHITE, etc. # fade up for j in range(25): pixel_brightness = (j * 0.01) cpx.pixels.brightness = pixel_brightness for i in range(10): cpx.pixels[i] = fade_color # fade down for k in range(25): pixel_brightness = (0.25 - (k * 0.01)) cpx.pixels.brightness = pixel_brightness for i in range(10): cpx.pixels[i] = fade_color
Playing a Song
The next function definition is play_song()
which is used to play one of two songs coded within, Jingle Bells or Let It Snow. You could write other songs and add them!
First, we create a variable called whole_note
to define the length of a whole note, in this case 1.5 seconds. You can adjust that to increase or decrease the tempo. All other note lengths are derived from this one variable, e.g. half_note
is a whole_note * 0.5
Similarly, we create a series of variables to define the pitches different notes, starting from A3 up to B4. This way, we can call the command cpx.start_tone()
with a note name instead of a frequency value. This makes it much easier to transcribe from standard music notation!
def play_song(song_number): # 1: Jingle bells # 2: Let It Snow # set up time signature whole_note = 1.5 # adjust this to change tempo of everything # these notes are fractions of the whole note half_note = whole_note / 2 quarter_note = whole_note / 4 dotted_quarter_note = quarter_note * 1.5 eighth_note = whole_note / 8 # set up note values A3 = 220 Bb3 = 233 B3 = 247 C4 = 262 Db4 = 277 D4 = 294 Eb4 = 311 E4 = 330 F4 = 349 Gb4 = 370 G4 = 392 Ab4 = 415 A4 = 440 Bb4 = 466 B4 = 494
Playing one note
To play one note, say a C, for a quarter note duration, we'll start the tone, sleep for a quarter note, and stop the tone. It will look like this:
cpx.start_tone(C4)
time.sleep(qN)
cpx.stop_tone()
Playing many notes
There are a couple of ways to transcribe a song using this method. The first way is very clear, but uses many lines of code:
if song_number == 1: # jingle bells for i in range(2): # repeat twice # jingle bells... cpx.stop_tone() cpx.start_tone(E4) time.sleep(qN) cpx.stop_tone() cpx.start_tone(E4) time.sleep(qN) cpx.stop_tone() cpx.start_tone(E4) time.sleep(hN) cpx.stop_tone() # jingle all the way cpx.start_tone(E4) time.sleep(qN) cpx.stop_tone() cpx.start_tone(G4) time.sleep(qN) cpx.stop_tone() cpx.start_tone(C4) time.sleep(dqN) cpx.stop_tone() cpx.start_tone(D4) time.sleep(eN) cpx.stop_tone() cpx.start_tone(E4) time.sleep(wN) cpx.stop_tone()
That's very straightforward -- other than looping the initial phrase twice, it repeats three commands over and over again for every note of the song. You can imagine that this would get really long, quickly!
The second method involves packing the entire set of notes and durations into a two dimensional array, like this:
# jingle bells jingle_bells_song = [[E4, quarter_note], [E4, quarter_note], [E4, half_note], [E4, quarter_note], [E4, quarter_note], [E4, half_note], [E4, quarter_note], [G4, quarter_note], [C4, dotted_quarter_note], [D4, eighth_note], [E4, whole_note]]
You can see how each pair in the list is a note pitch, followed by its play duration.
We can then play that song with a few lines of code that iterate through the array, playing and pausing for the values one pair at a time:
for n in range(len(jingle_bells_song)): cpx.start_tone(jingle_bells_song[n][0]) time.sleep(jingle_bells_song[n][1]) cpx.stop_tone()
Also note how the number of times needed to iterate through the loop is derived from querying the length of the jingle_bells_song
array with the len()
command. This way the number of iterations will always match the number of notes we add to or subtract from the song. If we were to instead hard code it with the number of notes like this: for n in range(11)
we would need to constantly update that value while working on the song. No fun!
Then, we'll define a second song, Let It Snow:
if song_number == 2: # Let It Snow let_it_snow_song = [[B4, dotted_quarter_note], [A4, eighth_note], [G4, quarter_note], [G4, dotted_quarter_note], [F4, eighth_note], [E4, quarter_note], [E4, dotted_quarter_note], [D4, eighth_note], [C4, whole_note]]
Once all of that has been defined, we'll play through Jingle Bells once:
play_song(1) # play music on start
Main Loop!
Now, we get to the main loop, this is what will repeat over and over again.
The first thing to do is set up some variables and math to compute total acceleration from movement on all three axes of the accelerometer.
while True: # check for shaking # Compute total acceleration x_total = 0 y_total = 0 z_total = 0 for count in range(10): x, y, z = cpx.acceleration x_total = x_total + x y_total = y_total + y z_total = z_total + z time.sleep(0.001) x_total = x_total / 10 y_total = y_total / 10 z_total = z_total / 10 total_accel = math.sqrt(x_total*x_total + y_total*y_total + z_total*z_total)
Now, we'll have the total_accel
value to compare to a threshold of 30 that we set at the top of the program called ROLL_THRESHOLD
.
# Check for rolling if total_accel > ROLL_THRESHOLD: roll_start_time = time.monotonic() new_roll = True rolling = True print('shaken') # Rolling momentum # Keep rolling for a period of time even after shaking stops if new_roll: if time.monotonic() - roll_start_time > 2: # seconds to run rolling = False
When shaking is detected, we will run the fade_pixels
function twice, first with skyblue, and then with white. We'll then fill all pixels with a bright white!
# Light show if rolling: fade_pixels(SKYBLUE) fade_pixels(WHITE) cpx.pixels.brightness = 0.8 cpx.pixels.fill(WHITE)
Lastly, when the shaking has stopped, we'll play the second song and then fade_pixels
to green.
elif new_roll: new_roll = False # play a song! play_song(2) #return to resting color fade_pixels(GREEN) cpx.pixels.brightness = 0.05 cpx.pixels.fill(GREEN)
From this point, the snow globe will stay lit green, waiting to be shaken again!
Text editor powered by tinymce.