Skip to content

ahdinosaur/blinksy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

90 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Blinksy simulation of 2D grid with noise pattern

Blinksy πŸŸ₯🟩🟦

A Rust no-std no-alloc LED control library.
For 1D, 2D, and 3D layouts.
Inspired by FastLED and WLED.

GitHub Repo stars GitHub Sponsors Chat License

How Blinksy works

  • Define your LED layout in 1D, 2D, or 3D space
  • Create your visual pattern (effect), or choose from our built-in patterns library
    • The pattern will compute colors for each LED based on its position in space
  • Setup a [driver] to send each frame of colors to your [leds]

Features

  • No-std, no-alloc: Designed for embedded targets.
  • Spatial in 1D, 2D, or 3D: Map out the shape of your LEDs in space.
  • Async support: Supports blocking or asynchronous execution.
  • Full color support: Supports modern and classic color spaces.
  • Global settings: Control overall brightness and color correction.
  • Desktop simulation: Simulate your LEDs on your desktop to play with ideas.
  • RGB+W support: Supports RGB + White color channels

LED Support

Clockless: One-wire (only data, no clock)

Clocked: Two-wire (data and clock)

  • APA102: High-FPS RGB LED, aka DotStar

If you want help to support a new LED chipset, make an issue!

Pattern (Effect) Library:

  • Rainbow: A basic scrolling rainbow
  • Noise: A flow through random noise functions

If you want help to port a pattern from FastLED / WLED to Rust, make an issue!

Microcontroller Family Support

Clocked LED support (e.g. APA102):

Micro HAL Blinksy Recommended Driver Backup Driver
ALL embedded-hal blinksy Spi Delay

Clockless LED support (e.g. WS2812)

Micro HAL Blinksy Recommended Driver Backup Driver
ALL embedded-hal blinksy - TODO Spi #12
ESP32 esp-hal blinksy-esp Rmt -
RP (2040 or 2350) rp-hal TODO TODO #36 -
STM32 stm32-hal TODO TODO #78 -
nRF nrf-hal TODO TODO #77 -
atsamd atsamd TODO TODO #67 -
AVR (Arduino) avr-hal TODO TODO #79 -
CH32 ch32-hal TODO TODO #80 -
??? - - - -

If you want help to support a new microcontroller family, make an issue!

Board Support

These are ready-to-go LED controllers with board support crates to make things even easier:

If you want help to support a new target, make an issue!

Quick Start

To quickstart a project, see:

To start using the library, see control.

Modules

CI status

Examples

For all examples, see:

Embedded Gledopto: 3D Cube with Noise Pattern

cube-demo-2025-06-27-1080p.mp4
Click to see code
#![no_std]
#![no_main]

use blinksy::{
    layout::{Layout3d, Shape3d, Vec3},
    layout3d,
    leds::Ws2812,
    patterns::noise::{noise_fns, Noise3d, NoiseParams},
    ControlBuilder,
};
use gledopto::{board, bootloader, elapsed, main, ws2812};

bootloader!();

#[main]
fn main() -> ! {
    let p = board!();

    layout3d!(
        Layout,
        [
            // bottom face
            Shape3d::Grid {
                start: Vec3::new(1., -1., 1.),           // right bottom front
                horizontal_end: Vec3::new(-1., -1., 1.), // left bottom front
                vertical_end: Vec3::new(1., -1., -1.),   // right bottom back
                horizontal_pixel_count: 16,
                vertical_pixel_count: 16,
                serpentine: true,
            },
            // back face
            Shape3d::Grid {
                start: Vec3::new(-1., -1., -1.),         // left bottom back
                horizontal_end: Vec3::new(-1., 1., -1.), // left top back
                vertical_end: Vec3::new(1., -1., -1.),   // right bottom back
                horizontal_pixel_count: 16,
                vertical_pixel_count: 16,
                serpentine: true,
            },
            // right face
            Shape3d::Grid {
                start: Vec3::new(1., 1., -1.),         // right top back
                horizontal_end: Vec3::new(1., 1., 1.), // right top front
                vertical_end: Vec3::new(1., -1., -1.), // right bottom back
                horizontal_pixel_count: 16,
                vertical_pixel_count: 16,
                serpentine: true,
            },
            // front face
            Shape3d::Grid {
                start: Vec3::new(-1., -1., 1.),         // left bottom front
                horizontal_end: Vec3::new(1., -1., 1.), // right bottom front
                vertical_end: Vec3::new(-1., 1., 1.),   // left top front
                horizontal_pixel_count: 16,
                vertical_pixel_count: 16,
                serpentine: true,
            },
            // left face
            Shape3d::Grid {
                start: Vec3::new(-1., 1., -1.),           // left top back
                horizontal_end: Vec3::new(-1., -1., -1.), // left bottom back
                vertical_end: Vec3::new(-1., 1., 1.),     // left top front
                horizontal_pixel_count: 16,
                vertical_pixel_count: 16,
                serpentine: true,
            },
            // top face
            Shape3d::Grid {
                start: Vec3::new(1., 1., 1.),           // right top front
                horizontal_end: Vec3::new(1., 1., -1.), // right top back
                vertical_end: Vec3::new(-1., 1., 1.),   // left top front
                horizontal_pixel_count: 16,
                vertical_pixel_count: 16,
                serpentine: true,
            }
        ]
    );

    let mut control = ControlBuilder::new_3d()
        .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
        .with_pattern::<Noise3d<noise_fns::Perlin>>(NoiseParams {
            ..Default::default()
        })
        .with_driver(ws2812!(p, Layout::PIXEL_COUNT))
        .with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
        .build();

    control.set_brightness(0.2);

    loop {
        let elapsed_in_ms = elapsed().as_millis();
        control.tick(elapsed_in_ms).unwrap();
    }
}

Desktop Simulation: 2D Grid with Noise Pattern

Screencast.2025-05-19.11.59.44.avi.mp4
Click to see code
use blinksy::{
    layout::{Layout2d, Shape2d, Vec2},
    layout2d,
    patterns::noise::{noise_fns, Noise2d, NoiseParams},
    ControlBuilder,
};
use blinksy_desktop::{
    driver::{Desktop, DesktopError},
    time::elapsed_in_ms,
};
use std::{thread::sleep, time::Duration};

layout2d!(
    PanelLayout,
    [Shape2d::Grid {
        start: Vec2::new(-1., -1.),
        horizontal_end: Vec2::new(1., -1.),
        vertical_end: Vec2::new(-1., 1.),
        horizontal_pixel_count: 16,
        vertical_pixel_count: 16,
        serpentine: true,
    }]
);

fn main() {
    Desktop::new_2d::<PanelLayout>().start(|driver| {
        let mut control = ControlBuilder::new_2d()
            .with_layout::<PanelLayout, { PanelLayout::PIXEL_COUNT }>()
            .with_pattern::<Noise2d<noise_fns::Perlin>>(NoiseParams {
                ..Default::default()
            })
            .with_driver(driver)
            .with_frame_buffer_size::<{ PanelLayout::PIXEL_COUNT }>()
            .build();

        loop {
            if let Err(DesktopError::WindowClosed) = control.tick(elapsed_in_ms()) {
                break;
            }

            sleep(Duration::from_millis(16));
        }
    });
}

Embedded Gledopto: 1D WS2812 Strip with Rainbow Pattern

VID20250327160955.mp4
Click to see code (Blocking)
#![no_std]
#![no_main]

use blinksy::{
    layout::Layout1d,
    layout1d,
    leds::Ws2812,
    patterns::rainbow::{Rainbow, RainbowParams},
    ControlBuilder,
};
use gledopto::{board, bootloader, elapsed, main, ws2812};

bootloader!();

#[main]
fn main() -> ! {
    let p = board!();

    layout1d!(Layout, 50);

    let mut control = ControlBuilder::new_1d()
        .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
        .with_pattern::<Rainbow>(RainbowParams::default())
        .with_driver(ws2812!(p, Layout::PIXEL_COUNT, buffered))
        .with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
        .build();

    control.set_brightness(0.2);

    loop {
        let elapsed_in_ms = elapsed().as_millis();
        control.tick(elapsed_in_ms).unwrap();
    }
}
Click to see code (Async)
#![no_std]
#![no_main]
#![feature(impl_trait_in_assoc_type)]

use blinksy::{
    layout::Layout1d,
    layout1d,
    leds::Ws2812,
    patterns::rainbow::{Rainbow, RainbowParams},
    ControlBuilder,
};
use embassy_executor::Spawner;
use gledopto::{board, bootloader, elapsed, init_embassy, main_embassy, ws2812_async};

bootloader!();

#[main_embassy]
async fn main(_spawner: Spawner) {
    let p = board!();

    init_embassy!(p);

    layout1d!(Layout, 50);

    let mut control = ControlBuilder::new_1d_async()
        .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
        .with_pattern::<Rainbow>(RainbowParams::default())
        .with_driver(ws2812_async!(p, Layout::PIXEL_COUNT, buffered))
        .with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
        .build();

    control.set_brightness(0.2);

    loop {
        let elapsed_in_ms = elapsed().as_millis();
        control.tick(elapsed_in_ms).await.unwrap();
    }
}

Embedded Gledopto: 2D APA102 Grid with Noise Pattern

VID20250401221726.mp4
Click to see code (Blocking)
#![no_std]
#![no_main]

use blinksy::{
    layout::{Layout2d, Shape2d, Vec2},
    layout2d,
    leds::Apa102,
    patterns::noise::{noise_fns, Noise2d, NoiseParams},
    ControlBuilder,
};
use gledopto::{apa102, board, bootloader, elapsed, main};

bootloader!();

#[main]
fn main() -> ! {
    let p = board!();

    layout2d!(
        Layout,
        [Shape2d::Grid {
            start: Vec2::new(-1., -1.),
            horizontal_end: Vec2::new(1., -1.),
            vertical_end: Vec2::new(-1., 1.),
            horizontal_pixel_count: 16,
            vertical_pixel_count: 16,
            serpentine: true,
        }]
    );
    let mut control = ControlBuilder::new_2d()
        .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
        .with_pattern::<Noise2d<noise_fns::Perlin>>(NoiseParams::default())
        .with_driver(apa102!(p))
        .with_frame_buffer_size::<{ Apa102::frame_buffer_size(Layout::PIXEL_COUNT) }>()
        .build();

    control.set_brightness(0.2);

    loop {
        let elapsed_in_ms = elapsed().as_millis();
        control.tick(elapsed_in_ms).unwrap();
    }
}
Click to see code (Async)
#![no_std]
#![no_main]
#![feature(impl_trait_in_assoc_type)]

use blinksy::{
    layout::{Layout2d, Shape2d, Vec2},
    layout2d,
    patterns::noise::{noise_fns, Noise2d, NoiseParams},
    ControlBuilder,
};
use embassy_executor::Spawner;
use gledopto::{apa102_async, board, bootloader, elapsed, main_embassy};

bootloader!();

#[main_embassy]
async fn main(_spawner: Spawner) {
    let p = board!();

    layout2d!(
        Layout,
        [Shape2d::Grid {
            start: Vec2::new(-1., -1.),
            horizontal_end: Vec2::new(1., -1.),
            vertical_end: Vec2::new(-1., 1.),
            horizontal_pixel_count: 16,
            vertical_pixel_count: 16,
            serpentine: true,
        }]
    );
    let mut control = ControlBuilder::new_2d_async()
        .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
        .with_pattern::<Noise2d<noise_fns::Perlin>>(NoiseParams::default())
        .with_driver(apa102_async!(p))
        .build();

    control.set_brightness(0.2);

    loop {
        let elapsed_in_ms = elapsed().as_millis();
        control.tick(elapsed_in_ms).await.unwrap();
    }
}

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

If you want to help, the best thing to do is use Blinksy for your own LED project, and share about your adventures.

License

Blinksy is licensed under the European Union Public License (EUPL).

You are free to use, modify, and share Blinksy freely. Whether for personal projects, art installations, or commercial products.

Only once you start distributing something based on changes to Blinksy, you must share any improvements back with the community by releasing your source code.

Unlike more viral copyleft licenses, you will not be required to release the source code for your entire project, only changes to Blinksy.

About

Rust no-std, no-alloc LED control for spatial layouts πŸŸ₯🟩🟦

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors 3

  •  
  •  
  •