#smart-leds #animation #no-alloc #led-animation

no-std smart_leds_animations

Library for building animations with smart LEDs

1 unstable release

new 0.1.0 May 24, 2025

#305 in Hardware support

MIT/Apache

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. Abstracts smart_leds's Gamma and Brightness iterators—for those smart LED chips which support them—into Driver 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

Example implementation of the library; marquee reads: Beetleguese Beetlegeuse

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 a Snake. These are grouped together in a Parallel animation because they need to be treated as a single unit in the Series animation that represents the broken arrow as a whole. The other component in the Series is an Arrow, which is little more than two converging Snakes 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 the AnimateFrames 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