1 unstable release
new 0.1.0 | May 24, 2025 |
---|
#305 in Hardware support
64KB
798 lines
Smart LEDs Animations
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
.
[!IMPORTANT] 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_leds
so you don't have to explicitly include it in your dependencies. Abstractssmart_leds
'sGamma
andBrightness
iterators—for those smart LED chips which support them—intoDriver
configurations, ensuring correct usage. - Provides several ready-made
animations
to 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 aParallel
animation because they need to be treated as a single unit in theSeries
animation that represents the broken arrow as a whole. The other component in theSeries
is anArrow
, which is little more than two convergingSnake
s with a little extra logic to handle some edge cases. - Glitch: I have taken to calling the rectangle of pixels around the lettering a
Glitch
effect. 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 use theAnimateFrames
trait.
Basic Usage
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.
],
);
}
For more detail, see the documentation. For a real-world example which makes use of most of the features of this crate, see the Halloween project that started it all.
License
smart_leds_animations
is distributed under the terms of both the MIT license
and the Apache License (Version 2.0).
Any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Contributing
Issues, pull requests, feature requests, and constructive criticism are welcome.
Dependencies
~315KB