Unicorn-Pi: a MicroPython animation launcher for Raspberry Pi Pico and the Pimoroni Unicorn Pack
I built Unicorn-Pi to make a Raspberry Pi Pico and a Pimoroni Unicorn Pack feel like one small, self-contained LED device instead of a pile of separate demo scripts. The goal was simple: load a bunch of effects onto the board, switch between them with the onboard buttons, and come back to the same view after reboot without having to rework anything each time.
What this project actually is
Unicorn-Pi is a collection of LED effects and animations for the Raspberry Pi Pico plus the Pimoroni Unicorn Pack. On the surface, it looks like a small gallery of visual modes: a digital clock, digital rain, a DVD-style bouncer, fire, fireworks, lightning, snowfall, wave patterns, a flashlight mode, an SOS flasher, and more.
What I like about it is that it is not trying to be a giant framework. It is a practical launcher around a set of self-contained views. I can power it on, tap the hardware buttons, and move through the effects without reflashing the board or manually opening a different script every time.
The result feels more like a finished little device than a code sample. That was the part I cared about most.
Why I structured it this way
The interesting part of Unicorn-Pi is not just the animations. It is the way the project is organized. I kept the core flow very small and let each effect stay in its own file. That makes the project easier to reason about, easier to test, and much easier to extend later.
I did not want a single huge script full of conditionals and shared state. I wanted each effect to feel independent. In practice, that means every view exposes the same basic entry point and the launcher handles the boring parts like startup, navigation, clearing the display, and remembering the current selection.
That separation is what keeps the project easy to work on.
How the software works
The main entry point lives in scripts/main.py. That file
imports every view module, creates the Unicorn Pack graphics objects,
registers the views in an ordered list, loads the saved current view, and
starts the active animation task.
From there, the flow is straightforward:
-
main.pybuilds an ordered registry of view names and theirrun()functions. -
The current view key is loaded from
/current_view.json. If there is no saved file yet, the project falls back toRainbow. - The selected view starts as an async task.
-
A separate button listener watches the
AandXbuttons, debounces presses, and figures out the previous or next view. - The view manager cancels the current task, clears the matrix, saves the new view key, and starts the replacement task.
I like this design because the switching logic lives in one place. The views do not need to know about the rest of the application. They just draw.
Each effect is its own small program
Every animation lives under scripts/views. That is where the
project gets its personality. Some views are only a few lines long. Others
are a bit more algorithmic. But they all follow the same general shape,
which keeps the codebase approachable.
A few examples show the range pretty well:
-
Digital Clock scrolls the current time across the
display and already uses the
BandYbuttons locally to cycle color and brightness. - Digital Rain creates falling green dots with trailing and fading behavior, which gives the tiny matrix a convincing low-resolution Matrix feel.
- DVD Bouncer turns the panel into a simple bouncing logo toy, changing color when it hits the edges.
- SOS strips the whole thing back to timing and rhythm, using full-panel flashes to blink Morse code.
- Flashlight Torch does the opposite of an animation and just turns the whole display into a solid white light.
That mix is what makes the project fun. Some modes are decorative. Some are playful. Some are actually useful.
I also like that the project leaves room for per-view controls. Navigation
belongs to A and X, while B and
Y can be used inside an effect when that effect needs its own
interaction model.
What I borrowed, and why that matters
Not every effect starts from zero. Some of the visual ideas in this
project build on code from Pimoroni's Pico examples, and I kept that
explicit in the file headers and in the CREDITS file. I
prefer being clear about where something came from, especially in hobby
hardware projects where good examples are often part of how people learn.
That also says something about the role of Unicorn-Pi. It is not pretending to invent every animation style from scratch. It is a curated, button-driven collection that brings original views and adapted examples together in one consistent runtime.
Building and running it on real hardware
Setup is simple. This is one of the things I appreciate about small MicroPython projects.
- Flash the Pico with the Pimoroni Unicorn Pack software by following the official Pimoroni guide.
- Install Thonny and connect the board over USB.
-
Copy the contents of the repository's
scriptsfolder to the root of the Pico. - Reconnect the board so
main.pystarts automatically.
That is it. Once the files are on the board, Unicorn-Pi boots straight into the saved effect and the hardware buttons take over from there.
For local development, I kept the tooling minimal: Black, Flake8, and pre-commit. There is also a lightweight GitHub Actions lint workflow, which is enough to keep a project like this tidy without turning it into a whole process.
python3 -m pip install -r requirements.txt
pre-commit install
python3 -m black scripts/
python3 -m flake8 --show-source --ignore E501 scripts/
How I would extend it
Unicorn-Pi is easy to grow because the extension path is obvious. To add a
new effect, I would create another file in scripts/views,
give it an async run(picoUnicorn, graphics) function, import
it in main.py, and register it in the ordered view list.
That is the main idea behind the project. A new effect should not require rewriting the app. It should only need one new module and one registration line.
I also like that many of the view files include a tiny
__main__ block for direct testing. That makes iteration in
Thonny much nicer because I can run one effect in isolation before wiring
it into the full launcher.
Why this project works as a developer project
There is a specific kind of satisfaction in hardware projects that do one thing well. Unicorn-Pi fits that category for me. It is small, readable, and honest about what it is. It does not need cloud services, a database, or a complicated deployment story. It just turns a Pico and an LED matrix into something tactile and fun.
More importantly, the code organization respects the kind of tinkering this hardware invites. I can open a single view, experiment with timing, color, or motion, run it on the device, and keep going. That is exactly how I want an LED project to feel.
Anyone digging through the repository will find a project that is easy to understand, easy to customize, and easy to use as a base for new ideas. That is the part I am happiest with. It stays out of the way and lets the animations be the point.
