Expand description
This crate complements the smart_leds collection of crates for interacting with individually addressable
LEDs using Rust. It endeavors to provide a declarative interface to a library of ready-made animations
usable in low-memory, no_std environments, as well as a framework for creating custom animations.
This crate has been tested only with a WS2812B strip driven by an Arduino Uno R3 but should work with any hardware supported by smart_leds.
A Note About API Stability: Expect some volatility across versions.
This crate was developed by a newcomer to both Rust and embedded programming as a learning project. I learned a lot in getting it to this point, and I think it provides value as-is, but I’m sure there’s a lot of room for improvement. If folks open issues to suggest better implementations, request missing features, etc.—and I hope they do so I can keep learning!—I suspect the public interfaces of the library to change to accommodate the same.
§Features
- Makes zero heap allocations—no need for an allocator crate.
- Wraps and re-exports
smart_ledsso you don’t have to explicitly include it in your dependencies. Abstractssmart_leds’sGammaandBrightnessiterators—for those smart LED chips which support them—intoDriverconfigurations, ensuring correct usage. - Provides several ready-made
animationsto use in your projects as well as a framework for creating custom ones. - Supports running different animations on different sections of the LED strip as well as composing individual animations into a compound animation.
§Example

Pictured here is the the Halloween project that led to the creation of this library. A single, unbroken LED strip borders the marquee, so in many cases the visual effects are realized across noncontiguous pixels. The two main animations are implemented as follows:
- Broken arrow: The main building block of this animation is the
Snake; there are four of them in play. Each side of the arrow above the lettering is aSnake. These are grouped together in aParallelanimation because they need to be treated as a single unit in theSeriesanimation that represents the broken arrow as a whole. The other component in theSeriesis anArrow, which is little more than two convergingSnakes with a little extra logic to handle some edge cases. - Glitch: I have taken to calling the rectangle of pixels around the lettering a
Glitcheffect. Because I thought it too bespoke for a general-purpose library, it is implemented as a custom animation in the aforementioned project. Hopefully it’s a useful example of how downstream users might leverage theAnimateFramestrait.
§Getting Started
Add this crate to your project:
cargo add smart_leds_animations…then:
// This is simplified code from a project which uses an Arduino Uno R3 to drive WS2812B strip.
// Much has been omitted to highlight use of this library. A complete example is available
// at https://github.com/universalhandle/beetlejuice_marquee.
#![no_std]
#![no_main]
#[arduino_hal::entry]
fn main() -> ! {
use smart_leds_animations::{
animations::Snake,
harness::*,
smart_leds::RGB8,
};
use ws2812_spi::Ws2812;
// `Ws2812` is part of the `smart_leds` family of crates. Detail about setting up the device
// is intentionally omitted here; you can see the `smart_leds` documentation or the
// aforementioned example project for more about that. The important thing is that the
// returned value implements the `SmartLedsWrite` trait.
let writer = Ws2812::new(spi);
// First, set up a Driver. It will be responsible for communicating with the LED strip.
// DriverBuilder helps construct a Driver, which is a little bit complicated, since
// `smart_leds` supports different filters for different chips.
let driver = DriverBuilder::new(writer)
// For instance, only RGB8 pixels (as opposed to four-channel RGBA pixels) can be passed
// through the gamma filter. Since the Ws2812 chip works with RGB8, this method can be
// called here. IDEs won't supply the code hint (and rustc won't compile this code) for
// other types of smart pixels. The authors of `smart_leds` recommend using this filter to
// "make orange look orange," and, indeed, colors appear washed out when the filter is
// disabled. Despite the recommendation, that crate requires end-user action to enable
// the filter, so this crate follows suit.
.enable_gamma_correction(true)
// …and we're done!
.build();
// This library is built around a metaphor of animating cartoons, so you'll encounter a
// `Director`, frames (as in a filmstrip, not computer memory), and other such terminology
// along the way. The Director is responsible for orchestrating the light show;
// even the execution loop is managed by the Director. The metaphors get a little mixed
// here—would a Director ever call "action" except in a room full of flesh-and-blood
// actors?—but don't get distracted; you're almost there!
let mut director = DirectorBuilder::new(driver)
// Optionally specify a function to be called at the end of each execution loop. Useful
// for controlling the speed of animations, which depends on several factors, including
// the number of pixels on the strip and the hardware used to drive it. Sleeping here can
// be a good way to control the "frame rate" of the overall show. Because every hardware
// abstraction layer will implement sleep differently, this library purposefully does not
// attempt to implement a generic sleep function but rather provides a place for end-users
// to hook in their own.
.set_post_loop_fn(&|| {
arduino_hal::delay_ms(10);
})
.build();
// Define a strip of 100 pixels with the LEDs initialized in the "off" setting.
let mut pixels = [RGB8::default(); 100];
// Initialize a Snake animation.
let mut snake = Snake::new(
// the color of the snake
RGB8 { r: 255, g: 180, b: 47},
// if true the snake chases its tail; otherwise the snake will completely disappear
// from the strip before the animation loops
true,
// every pixel on the strip is being used for this animation (these are indexes of `pixels`)
0..=99,
// the snake moves away from the arduino rather than toward it
true,
// the snake will have a 24-pixel-long tail, plus one pixel (always) for the head,
// making for a 25-pixel-long snake
24,
);
// Lights, camera…
director.action(
// Note that all arguments are passed by reference. Because of the decision not to
// require an allocator, the library depends on end-users to satisfy the compiler's
// need for the sizes of all values to be known at compile time. Thus, `pixels` is a
// reference to a fixed-length array, and…
&mut pixels,
// …animations (the second argument) is a reference to a fixed-length array of
// references to Animate trait objects.
&mut [
&mut snake,
// Other animations could have been passed here.
],
);
}Re-exports§
pub use smart_leds;
Modules§
- animate
- Tools for developing animations, both ready-made and custom.
- animations
- Ready-made animations for end-users.
- composition
- Tools for grouping together individual animations and treating them as one.
- harness
- Tools for orchestrating animations and communicating with the LED strip.