Lab 12: Rainbow Cycle
Pixel says...
What if the whole rainbow could spin around the strip? We'll shift every color over by one pixel, again and again. This rainbow is going places!
Program file: 12-rainbow-cycle.py
What you'll learn
- How an
offsetslides the rainbow one pixel at a time - How the modulo operator
%wraps a pixel number back to the start of the strip - Why the rainbow needs no erasing when the loop repaints every pixel
- How the
sleep()value sets the speed of the spin
What you'll need
- Your base kit: a Pico, a breadboard, and the 30-pixel LED strip, wired as shown in the Kit User's Guide
- The
config.pyfile saved on the Pico (see Getting Code onto the Kit) - Thonny open and connected to your Pico
- The rainbow from Lab 11: Rainbow, because this lab builds on it
The program
This program draws the rainbow over and over, and it starts each new drawing one pixel further along the strip.
# Lab 12: Rainbow Cycle
# Filename: 12-rainbow-cycle.py
# Version: 1.0.0
#
# The rainbow rotates around the strip.
from machine import Pin
from neopixel import NeoPixel
from utime import sleep
from urandom import randint
import config
# hardware settings from config.py
NEOPIXEL_PIN = config.NEOPIXEL_PIN
NUMBER_PIXELS = config.NUMBER_PIXELS
strip = NeoPixel(Pin(NEOPIXEL_PIN), NUMBER_PIXELS)
# we have each pixel step through the color wheel
color_wheel_step = int(256 / NUMBER_PIXELS)
def wheel(pos):
# Input a value 0 to 255 to get a color value.
# The colors are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
current_color = 0
offset = 0
print('Drawing rainbow in', NUMBER_PIXELS, 'steps with step size of: ', color_wheel_step)
while True:
for i in range(0,NUMBER_PIXELS):
strip[(i+offset) % NUMBER_PIXELS] = wheel(current_color)
current_color = current_color + color_wheel_step
strip.write()
sleep(.001)
current_color = 0
offset += 1
Run it. The whole rainbow rotates around the strip, and the red end moves toward the higher pixel numbers, away from the end where your wires connect. This program keeps going until you press Stop.
How it works
What stays the same
The wheel() function and the step size are the same as in Lab 11. Each pixel in the rainbow sits 8 positions further around the wheel than the pixel before it.
Two counters
These two lines set up the numbers that change as the program runs.
current_color = 0
offset = 0
current_color is the wheel position for the next pixel, as in Lab 11. offset is new. It tells the program how many pixels to slide the rainbow along the strip. It starts at 0, so the first rainbow looks like the one in Lab 11.
Paint the strip again and again
This loop paints all 30 pixels, and then it makes one small change and paints them again.
while True:
for i in range(0,NUMBER_PIXELS):
strip[(i+offset) % NUMBER_PIXELS] = wheel(current_color)
current_color = current_color + color_wheel_step
strip.write()
sleep(.001)
current_color = 0
offset += 1
The for loop makes one pass, painting each of the 30 colors once. When the pass ends, two lines get ready for the next one. current_color = 0 sends the wheel position back to red. offset += 1 adds 1 to offset, so the next pass starts one pixel further along.
Every pass repaints all 30 pixels. Each pixel gets a new color on top of its old one, so nothing needs to be erased.
Wrap around with modulo
The strip has pixels 0 to 29. What happens when i + offset reaches 30 or more? That is a pixel that does not exist. The modulo operator % fixes it. It gives the remainder after dividing. So 30 % 30 is 0, 31 % 30 is 1, and 5 % 30 is 5.
The expression (i+offset) % NUMBER_PIXELS therefore always lands between 0 and 29. A color that slides off the end of the strip comes back in at pixel 0.
offset |
Where the red pixel goes | Where the last color goes (i is 29) |
|---|---|---|
| 0 | pixel 0 | pixel 29 |
| 1 | pixel 1 | pixel 0 |
| 2 | pixel 2 | pixel 1 |
See it in action in the Modulo Wrap-Around MicroSim. Each pass moves the rainbow by one pixel, so 30 passes bring it all the way around.
How fast is it?
Each pass paints 30 pixels, and each one waits sleep(.001). So the waiting alone takes 30 × .001 = .03 seconds per pass. Each strip.write() also takes a little time. A full trip around the strip needs 30 passes, so it takes at least 30 × .03 = .9 seconds.
Power check
This program keeps all 30 pixels lit. Every wheel color adds up to 255, so each pixel draws about 20 mA. That makes about 600 mA for the strip, and a USB port supplies about 500 mA. See How Bright Can You Go? for the details. To cut the total to about 150 mA, divide each color number by 4.
Replace the line that starts with strip[(i+offset) % NUMBER_PIXELS] = with these two lines to dim the rainbow.
red, green, blue = wheel(current_color)
strip[(i+offset) % NUMBER_PIXELS] = (red // 4, green // 4, blue // 4)
The first line splits the three numbers from wheel() into red, green, and blue. The // sign divides and drops any leftover decimal.
Try it yourself
- Change
offset += 1tooffset -= 1. Which way does the rainbow spin now? - Change
sleep(.001)tosleep(.01). How much slower is the spin? Work out the new minimum time for one trip around the strip.
Check your understanding
- What is
31 % 30? What is29 % 30? - What does
offsetchange about the picture on the strip? - Why does the program set
current_color = 0after every pass? - How many passes does the rainbow need to travel once around a 30-pixel strip?
Lab complete!
Your rainbow is on the move! The % trick lets colors wrap around the strip like a loop, and you'll meet it again in later labs.
What's next: In Lab 13: Moving Rainbow with the Color Wheel, a short rainbow slides along the strip and leaves the rest dark.