Basic LED Animation Patterns
Summary
Implements the first wave of animation effects—blink, rainbow, moving rainbow, rainbow cycle, twinkle, candle flicker, random colors, and clock display—using frame-based animation, position tracking, direction variables, bounce logic, and modulo wrapping.
Concepts Covered
This chapter covers the following 21 concepts from the learning graph:
- Blink Pattern
- Rainbow Static Pattern
- Moving Rainbow Pattern
- Rainbow Cycle
- Twinkle Effect
- Candle Flicker Animation
- Random Colors Animation
- Clock Display Animation
- Frame-Based Animation
- Animation Delay
- Position Tracking Variable
- Direction Variable
- Bounce Logic
- Modulo Wrapping
- Pixel Offset Calculation
- Sequential Pixel Lighting
- Parallel Pixel Update
- Timing with utime Module
- Flicker Randomness
- Step Size in Animation
- Time-Based Animation
Prerequisites
This chapter builds on concepts from:
- Chapter 1: Introduction and Computational Thinking Foundations
- Chapter 2: Python Basics: Variables, Data Types, and Operators
- Chapter 3: Python Functions, Modules, and Programming Best Practices
- Chapter 4: Python Control Flow, Loops, and Error Handling
- Chapter 6: MicroPython APIs, GPIO Control, and Electrical Fundamentals
- Chapter 7: Color Theory: The RGB Color Model and Color Mixing
- Chapter 9: NeoPixel Programming: Pixels, Colors, and the NeoPixel Library
- Chapter 11: Mathematics for LED Programming
Pixel says...
Welcome to Chapter 12! This is where the lights really come on. You've got all the building blocks — variables, loops, functions, the NeoPixel library, and the math. Now you'll combine them into real, beautiful animations. Eight patterns in one chapter. Let's light this up!
What You'll Learn
By the end of this chapter, you'll be able to:
- Explain what frame-based animation means
- Implement the blink, rainbow, moving rainbow, and rainbow cycle patterns
- Build twinkle and candle flicker effects using randomness
- Create a random color animation
- Display a clock using the
localtime()function - Explain position tracking, direction variables, and bounce logic
What You'll Need
- Raspberry Pi Pico with NeoPixel strip connected
- Thonny IDE connected
config.pyon the Pico with correctNEOPIXEL_PINandNUMBER_PIXELS
Frame-Based Animation
Every animation in this chapter uses frame-based animation: a loop that runs repeatedly, updating the LED strip on each iteration. Each iteration is one frame. The speed of the animation depends on the animation delay — how long the program waits between frames.
Before looking at any pattern, here's the standard frame structure all animations follow:
# Standard animation loop structure
while True:
# 1. Calculate what the LEDs should look like for this frame
# 2. Set pixel colors
# 3. Call strip.write() to push colors to hardware
# 4. Wait (the animation delay)
utime.sleep_ms(DELAY_MS)
Two types of time control appear in animations:
- Timing with
utime.sleep_ms()— pauses for a fixed number of milliseconds. Simple and predictable. - Time-based animation using
ticks_ms()— tracks elapsed time so the animation speed doesn't depend on how long the calculation takes.
For the patterns in this chapter, sleep_ms() is sufficient.
Pattern 1: Blink
The Blink Pattern turns the entire strip on, waits, then turns it off, then repeats.
This pattern introduces two key concepts: parallel pixel update (setting all pixels at once in a loop) and the simplest possible animation delay.
import neopixel, machine, utime, config
strip = neopixel.NeoPixel(machine.Pin(config.NEOPIXEL_PIN), config.NUMBER_PIXELS)
ON_COLOR = (0, 150, 255) # blue-white
OFF_COLOR = (0, 0, 0)
while True:
for i in range(config.NUMBER_PIXELS):
strip[i] = ON_COLOR # all pixels on
strip.write()
utime.sleep_ms(500)
for i in range(config.NUMBER_PIXELS):
strip[i] = OFF_COLOR # all pixels off
strip.write()
utime.sleep_ms(500)
You should see the strip blink on and off once per second.
Pattern 2: Rainbow Static Pattern
The Rainbow Static Pattern spreads a full spectrum of colors across the strip simultaneously. Each pixel gets a different hue.
Before the code, here's the idea: divide 360 hue degrees evenly among all pixels. Pixel 0 gets hue 0 (red), the middle pixel gets hue 180 (cyan), and the last pixel gets hue 359 (almost-red again).
import neopixel, machine, utime, config
import math
strip = neopixel.NeoPixel(machine.Pin(config.NEOPIXEL_PIN), config.NUMBER_PIXELS)
def hsv_to_rgb(h, s, v):
if s == 0:
val = int(v * 255)
return (val, val, val)
h = h / 60.0
i = int(h)
f = h - i
p, q, t = v*(1-s), v*(1-s*f), v*(1-s*(1-f))
pairs = [(v,t,p),(q,v,p),(p,v,t),(p,q,v),(t,p,v),(v,p,q)]
r, g, b = pairs[i % 6]
return (int(r*255), int(g*255), int(b*255))
for i in range(config.NUMBER_PIXELS):
hue = (i / config.NUMBER_PIXELS) * 360
strip[i] = hsv_to_rgb(hue, 1.0, 0.5)
strip.write()
You should see a full rainbow spread across the strip and stay static.
Pattern 3: Moving Rainbow Pattern
The Moving Rainbow Pattern is the same rainbow, but it scrolls. A position offset shifts all the hues together on each frame, creating the illusion of movement.
Two new concepts appear here:
- Pixel Offset Calculation — adding an offset to each pixel's hue shifts the entire rainbow
- Modulo Wrapping — % 360 keeps the hue in the valid range when the offset grows large
offset = 0 # tracks how far the rainbow has scrolled
STEP = 3 # degrees to shift per frame (step size)
while True:
for i in range(config.NUMBER_PIXELS):
hue = ((i / config.NUMBER_PIXELS) * 360 + offset) % 360
strip[i] = hsv_to_rgb(hue, 1.0, 0.5)
strip.write()
offset = (offset + STEP) % 360 # advance offset, wrap at 360
utime.sleep_ms(20)
You should see the rainbow smoothly scroll across the strip. Increase STEP to scroll faster.
Pattern 4: Rainbow Cycle
The Rainbow Cycle is different from the moving rainbow. Instead of the rainbow being spread across the strip, each individual pixel cycles through the full spectrum at its own speed. The offset between pixels determines the visual pattern.
offset = 0
while True:
for i in range(config.NUMBER_PIXELS):
hue = (offset + i * 10) % 360 # each pixel offset by 10 degrees
strip[i] = hsv_to_rgb(hue, 1.0, 0.5)
strip.write()
offset = (offset + 2) % 360
utime.sleep_ms(15)
You should see a cycling, spinning pattern — similar to the moving rainbow but with a different relationship between adjacent pixels.
Pixel thinks...
The difference between Moving Rainbow and Rainbow Cycle is in how the offset is applied. Moving Rainbow: all pixels shift together (same offset for all). Rainbow Cycle: each pixel gets a different offset based on its index. Two patterns, one math tool.
Pattern 5: Twinkle Effect
The Twinkle Effect randomly picks a few pixels and flashes them bright, creating a starfield look.
Flicker randomness is the key technique — using urandom to pick both which pixels light up and what color they use.
Before the code, here's the algorithm: 1. Each frame, dim all pixels slightly (so previous twinkles fade) 2. Pick 3 random pixels and set them to bright colors 3. Write and wait
import urandom
# Start with all pixels off
for i in range(config.NUMBER_PIXELS):
strip[i] = (0, 0, 0)
while True:
# Dim all pixels by multiplying by 0.8
for i in range(config.NUMBER_PIXELS):
r, g, b = strip[i]
strip[i] = (int(r * 0.8), int(g * 0.8), int(b * 0.8))
# Twinkle: light 3 random pixels
for _ in range(3):
pixel = urandom.randint(0, config.NUMBER_PIXELS - 1)
r = urandom.randint(100, 255)
g = urandom.randint(100, 255)
b = urandom.randint(100, 255)
strip[pixel] = (r, g, b)
strip.write()
utime.sleep_ms(50)
You should see random pixels sparkling with random colors, each briefly bright before fading.
Pattern 6: Candle Flicker Animation
The Candle Flicker Animation simulates a flame: mostly warm yellow-orange with random dips to red or orange, and random brightness variation.
The key ideas: warm colors (no blue), and random variation in brightness on every frame.
import urandom
while True:
for i in range(config.NUMBER_PIXELS):
# Flame colors: orange to yellow, randomized
r = urandom.randint(150, 255)
g = urandom.randint(50, 130)
b = 0 # no blue in a candle!
strip[i] = (r, g, b)
strip.write()
utime.sleep_ms(urandom.randint(30, 80)) # random delay = irregular flicker
You should see the strip rippling with warm orange-yellow flickers. The random delay (30–80 ms) makes the rhythm irregular — just like a real candle.
Pattern 7: Random Colors Animation
The Random Colors Animation assigns a new random color to every pixel on every frame. It's energetic and chaotic — good for party effects.
import urandom
while True:
for i in range(config.NUMBER_PIXELS):
strip[i] = (
urandom.randint(0, 200),
urandom.randint(0, 200),
urandom.randint(0, 200)
)
strip.write()
utime.sleep_ms(100)
You should see the entire strip flash with new random colors every 100 ms.
Diagram: Animation Pattern Comparison
Run Animation Pattern Comparison Fullscreen
Interactive comparison: six animation patterns side by side
Type: interactive-infographic sim-id: animation-pattern-comparison Library: p5.js Status: Specified
Six rows of small LED strip simulations (10 pixels each), each labeled with its pattern name: 1. Blink — alternates all-on / all-off every 500 ms 2. Rainbow Static — shows fixed rainbow 3. Moving Rainbow — rainbow scrolls left at adjustable speed 4. Twinkle — random pixels flash bright on a dark background 5. Candle Flicker — warm colors randomly varying brightness 6. Random Colors — all pixels flash random colors each frame
A global Play/Pause button runs or pauses all six simultaneously. A Speed slider (slow/medium/fast) adjusts all animation speeds together. Each row has a label and a brief description on the right. Canvas: 700 × 500 px. Responds to window resize.
Learning objective: Analyzing — the student can visually match a running LED pattern to its name and algorithm.
Pattern 8: Clock Display Animation
The Clock Display Animation uses utime.localtime() to read the current time and map it to pixels on the strip. This turns your LED strip into a clock!
localtime() returns a tuple. The relevant fields are:
- t[4] — hours (0–23)
- t[5] — minutes (0–59)
- t[6] — seconds (0–59)
Before the code, here's the mapping scheme: divide the strip into three segments. The first third shows hours, the second shows minutes, the third shows seconds.
import utime
N = config.NUMBER_PIXELS
THIRD = N // 3 # pixels per segment
while True:
t = utime.localtime() # get current time
hours = t[4] # 0–23
minutes = t[5] # 0–59
seconds = t[6] # 0–59
# Map time values to pixel counts within each segment
hour_pixels = int((hours / 24) * THIRD)
min_pixels = int((minutes / 60) * THIRD)
sec_pixels = int((seconds / 60) * THIRD)
for i in range(N):
if i < THIRD:
# Hours segment: red
strip[i] = (150, 0, 0) if i < hour_pixels else (0, 0, 0)
elif i < 2 * THIRD:
# Minutes segment: green
j = i - THIRD
strip[i] = (0, 150, 0) if j < min_pixels else (0, 0, 0)
else:
# Seconds segment: blue
j = i - 2 * THIRD
strip[i] = (0, 0, 150) if j < sec_pixels else (0, 0, 0)
strip.write()
utime.sleep(1) # update once per second
You should see three segments of red, green, and blue that grow and shrink like clock hands. The blue segment resets every 60 seconds, the green every hour.
Watch out!
The Pico doesn't have a battery-backed real-time clock (RTC). When you unplug it, localtime() resets to a default time. For the clock animation to show the correct time, you need to set it first in Thonny with: import utime; utime.set_time(...) or use an RTC module. For this course, the animation pattern itself is the learning goal — exact time accuracy is optional.
Try It Yourself
-
Color blink: Modify the blink pattern to cycle through three colors instead of just on/off: red blink, green blink, blue blink.
-
Fast rainbow: Increase the moving rainbow
STEPvalue from 3 to 20. How does the visual feel change? -
Gentle twinkle: Change the twinkle pattern to use only white pixels
(200, 200, 200)instead of random colors. This makes a starfield effect. -
Clock minutes: Modify the clock display to use the full strip just for minutes — 60 pixels all green, with the lit portion showing how many minutes have passed this hour.
Check Your Understanding
- What is frame-based animation? What does each "frame" do?
- In the moving rainbow, what is the role of the
offsetvariable? - How does twinkle use randomness differently from random colors?
- What makes the candle flicker look natural? (Name two sources of randomness.)
- What does
utime.localtime()return? - What is the step size in an animation, and how does it affect speed?
- What is the difference between sequential and parallel pixel updates?
Chapter complete!
Eight patterns! Blink, static rainbow, moving rainbow, rainbow cycle, twinkle, candle flicker, random colors, and a clock display. You built every one of them from scratch. Your strip is doing real things now. These patterns are the foundation for everything more complex coming up in the next two chapters!
What's Next
In Chapter 13, you'll build more complex animations — fade in/out, theater chase, ripple, bounce, and moving bands — and learn how to control animation speed, trail fading, and multi-parameter patterns.