WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 30f4d58

Browse files
authored
Re-architect to pre-calculate a buffer for each frame (#90)
👩‍🔬 🧪 💥 If we can off-load sending bits to the LEDs using hardware, then we can prepare the bits for the next frame while the current frame is being sent. This change is a big step towards making this possible. One realization to make this work is that yes [the `generic_const_exprs` feature](https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html) would make no-alloc much nicer, BUT IN THE MEANTIME we can achieve the same outcomes (with a worse user experience) by using generic constants without expressions. Since you can't do constant calculations using generics (e.g. we can't calculate the buffer size needed using other generic types we already have, such as the type of LED which has a constant function for this), the user must calculate them on their side and pass in to your type arguments. We can use all our builder pattern skills to make this as ergonomic as possible. Changes: - Change `Driver` (and `DriverAsync`) trait interface ```rust pub trait Driver { /// The error type that may be returned by the driver. type Error; /// The color type accepted by the driver. type Color; /// The word of the frame buffer. type Word; /// Encodes an update frame buffer for the LED hardware. /// /// # Type Parameters /// /// * `PIXEL_COUNT` - Number of pixels in frame /// * `FRAME_BUFFER_SIZE` - Length of encoded frame buffer, in words. /// * `Pixels` - Iterator of colors for each pixel /// * `Color` - Type of each pixel /// /// # Arguments /// /// * `pixels` - Iterator of colors for each pixel /// * `brightness` - Global brightness scaling factor (0.0 to 1.0) /// * `correction` - Color correction factors /// /// # Returns /// /// Result with frame buffer fn encode<const PIXEL_COUNT: usize, const FRAME_BUFFER_SIZE: usize, Pixels, Color>( &mut self, pixels: Pixels, brightness: f32, correction: ColorCorrection, ) -> Vec<Self::Word, FRAME_BUFFER_SIZE> where Pixels: IntoIterator<Item = Color>, Self::Color: FromColor<Color>; /// Writes frame buffer to the LED hardware. /// /// # Type Parameters /// /// * `FRAME_BUFFER_SIZE` - Length of encoded frame buffer, in words. /// /// # Arguments /// /// * `frame` - Frame buffer /// /// # Returns /// /// Result indicating success or an error fn write<const FRAME_BUFFER_SIZE: usize>( &mut self, frame: Vec<Self::Word, FRAME_BUFFER_SIZE>, brightness: f32, correction: ColorCorrection, ) -> Result<(), Self::Error>; /// Shows a frame on the LED hardware. /// /// # Type Parameters /// /// * `PIXEL_COUNT` - Number of pixels in frame /// * `FRAME_BUFFER_SIZE` - Length of encoded frame buffer, in words. /// * `Pixels` - Iterator of colors for each pixel /// * `Color` - Type of each pixel /// /// # Arguments /// /// * `pixels` - Iterator of colors for each pixel /// * `brightness` - Global brightness scaling factor (0.0 to 1.0) /// * `correction` - Color correction factors /// /// # Returns /// /// Result indicating success or an error fn show<const PIXEL_COUNT: usize, const FRAME_BUFFER_SIZE: usize, I, C>( &mut self, pixels: I, brightness: f32, correction: ColorCorrection, ) -> Result<(), Self::Error> where I: IntoIterator<Item = C>, Self::Color: FromColor<C>, { let frame_buffer = self.encode::<PIXEL_COUNT, FRAME_BUFFER_SIZE, _, _>(pixels, brightness, correction); self.write(frame_buffer, brightness, correction) } } ``` - Until [the `generic_const_exprs` feature](https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html) is stable, we aren't able to use associated constants, const functions, or expressions in the Blinksy code to calculate constants at compile-time. Instead, we must receive pre-calculated constants from the user as a generic. The best we can do is make it easy as possible by providing good types, traits, and const functions for the user to use. - To build a `Control`, you now need to provide a `FRAME_BUFFER_SIZE` constant. - So for example, to build a frame buffer to drive Ws2812 LEDs: ``` .with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>() ``` - Re-factor clockless and clocked drivers into common pattern - There is a generic driver for each type: `ClocklessDriver` and `ClockedDriver`. - You construct the generic driver by combining an Led with a Writer, both of that type. - All drivers and all writers are made through builders. - So for example: the clockless RMT driver for a WS2812 LED: ```rust let driver = ClocklessDriver::default() .with_led::<Ws2812>() .with_writer(ClocklessRmt::default() .with_led::<Ws2812>() .with_rmt_buffer_size::<{ rmt_buffer_size::<Ws2812>(Layout::PIXEL_COUNT) }>() .with_channel(/* rmt channel */) .with_pin(/* rmt pin */) .build() ); ``` - Another example: the clocked SPI driver for an APA102 LED: ```rust let driver = ClockedDriver::default() .with_led::<Apa102>() .with_writer(/* spi bus */) ``` - Clockless and clocked writers are also constructed through builders. - Move LED definitions to `crate::leds` module, remove `Led` suffix from structs.
1 parent b965c38 commit 30f4d58

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2234
-1395
lines changed

CHANGELOG.md

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,155 @@ Migration guide (0.10 -> UNRELEASED)
1212
+ .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
1313
```
1414

15-
- `Driver::write` now expects a `const PIXEL_COUNT: usize` generic constant as the first type argument.
15+
- `ControlBuilder` now has a `with_frame_buffer_size` to provide a `FRAME_BUFFER_SIZE` constant.
16+
- So for example, to build a frame buffer to drive Ws2812 LEDs:
17+
18+
```diff
19+
+ .with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
20+
```
21+
22+
If using the `gledopto` high-level helper macros, this should be all you need to change.
23+
24+
Below are changes for lower-level interfaces:
25+
26+
- Change `Driver` (and `DriverAsync`) traits:
27+
28+
```rust
29+
pub trait Driver {
30+
/// The error type that may be returned by the driver.
31+
type Error;
32+
33+
/// The color type accepted by the driver.
34+
type Color;
35+
36+
/// The word of the frame buffer.
37+
type Word;
38+
39+
/// Encodes an update frame buffer for the LED hardware.
40+
///
41+
/// # Type Parameters
42+
///
43+
/// * `PIXEL_COUNT` - Number of pixels in frame
44+
/// * `FRAME_BUFFER_SIZE` - Length of encoded frame buffer, in words.
45+
/// * `Pixels` - Iterator of colors for each pixel
46+
/// * `Color` - Type of each pixel
47+
///
48+
/// # Arguments
49+
///
50+
/// * `pixels` - Iterator of colors for each pixel
51+
/// * `brightness` - Global brightness scaling factor (0.0 to 1.0)
52+
/// * `correction` - Color correction factors
53+
///
54+
/// # Returns
55+
///
56+
/// Result with frame buffer
57+
fn encode<const PIXEL_COUNT: usize, const FRAME_BUFFER_SIZE: usize, Pixels, Color>(
58+
&mut self,
59+
pixels: Pixels,
60+
brightness: f32,
61+
correction: ColorCorrection,
62+
) -> Vec<Self::Word, FRAME_BUFFER_SIZE>
63+
where
64+
Pixels: IntoIterator<Item = Color>,
65+
Self::Color: FromColor<Color>;
66+
67+
/// Writes frame buffer to the LED hardware.
68+
///
69+
/// # Type Parameters
70+
///
71+
/// * `FRAME_BUFFER_SIZE` - Length of encoded frame buffer, in words.
72+
///
73+
/// # Arguments
74+
///
75+
/// * `frame` - Frame buffer
76+
///
77+
/// # Returns
78+
///
79+
/// Result indicating success or an error
80+
fn write<const FRAME_BUFFER_SIZE: usize>(
81+
&mut self,
82+
frame: Vec<Self::Word, FRAME_BUFFER_SIZE>,
83+
brightness: f32,
84+
correction: ColorCorrection,
85+
) -> Result<(), Self::Error>;
86+
87+
/// Shows a frame on the LED hardware.
88+
///
89+
/// # Type Parameters
90+
///
91+
/// * `PIXEL_COUNT` - Number of pixels in frame
92+
/// * `FRAME_BUFFER_SIZE` - Length of encoded frame buffer, in words.
93+
/// * `Pixels` - Iterator of colors for each pixel
94+
/// * `Color` - Type of each pixel
95+
///
96+
/// # Arguments
97+
///
98+
/// * `pixels` - Iterator of colors for each pixel
99+
/// * `brightness` - Global brightness scaling factor (0.0 to 1.0)
100+
/// * `correction` - Color correction factors
101+
///
102+
/// # Returns
103+
///
104+
/// Result indicating success or an error
105+
fn show<const PIXEL_COUNT: usize, const FRAME_BUFFER_SIZE: usize, I, C>(
106+
&mut self,
107+
pixels: I,
108+
brightness: f32,
109+
correction: ColorCorrection,
110+
) -> Result<(), Self::Error>
111+
where
112+
I: IntoIterator<Item = C>,
113+
Self::Color: FromColor<C>,
114+
{
115+
let frame_buffer =
116+
self.encode::<PIXEL_COUNT, FRAME_BUFFER_SIZE, _, _>(pixels, brightness, correction);
117+
self.write(frame_buffer, brightness, correction)
118+
}
119+
}
120+
```
121+
122+
- Until [the `generic_const_exprs` feature](https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html) is stable, we aren't able to use associated constants, const functions, or expressions in the Blinksy code to calculate constants at compile-time. Instead, we must receive pre-calculated constants from the user as a generic. The best we can do is make it easy as possible by providing good types, traits, and const functions for the user to use.
123+
- Built-in LED drivers have been refactored:
124+
- There is a generic driver for each type: `ClocklessDriver` and `ClockedDriver`.
125+
- You construct the generic driver by combining an Led with a Writer, both of that type.
126+
- All drivers and all writers are made through the builder pattern.
127+
- So for example: the clockless RMT driver for a WS2812 LED:
128+
129+
```rust
130+
let driver = ClocklessDriver::default()
131+
.with_led::<Ws2812>()
132+
.with_writer(ClocklessRmt::default()
133+
.with_led::<Ws2812>()
134+
.with_rmt_buffer_size::<{ rmt_buffer_size::<Ws2812>(Layout::PIXEL_COUNT) }>()
135+
.with_channel(/* rmt channel */)
136+
.with_pin(/* rmt pin */)
137+
.build()
138+
);
139+
```
140+
141+
- Another example: the clocked SPI driver for an APA102 LED:
142+
143+
```rust
144+
let driver = ClockedDriver::default()
145+
.with_led::<Apa102>()
146+
.with_writer(/* spi bus */)
147+
```
148+
149+
- Clockless and clocked writers are also constructed through the builder pattern.
150+
- Move LED definitions to `blinksy::leds` module, remove `Led` suffix from structs.
151+
- `blinksy-esp` RMT driver expects `RMT_BUFFER_SIZE`.
152+
- Each memory block on the RMT driver has a size of 64, with a max of 8 memory blocks.
153+
- If async, you should use `64`.
154+
- If blocking and not too many LEDs, you should use `rmt_buffer_size::<Led>(Layout::PIXEL_COUNT)`.
155+
- If blocking and too many LEDs, you should use `64`.
16156

17157
Breaking changes:
18158

159+
- [#90](https://github.com/ahdinosaur/blinksy/pull/90): Re-architect to pre-calculate a buffer for each frame
19160
- [#82](https://github.com/ahdinosaur/blinksy/pull/82): Use pixels buffer
20161
- Write all colors from `Pattern` iterator to pixel buffer, then write pixel buffer to LEDs with `Driver`.
21162
- [#87](https://github.com/ahdinosaur/blinksy/pull/87): Refactor clocked LED drivers
22163

23-
24164
## 0.10
25165

26166
Yee haw `blinksy` now supports async!

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ For all examples, see:
179179
- [Desktop examples in `./blinksy-desktop/examples`](./blinksy-desktop/examples)
180180
- [Embedded (with Gledopto) examples in `./esp/gledopto/examples`](./esp/gledopto/examples)
181181

182-
### Embedded Gledopto: 3D Cube with Nosie Pattern
182+
### Embedded Gledopto: 3D Cube with Noise Pattern
183183

184184
https://github.com/user-attachments/assets/36a2c6ad-7ae6-4498-85b3-ed76d0b62264
185185

@@ -195,6 +195,7 @@ https://github.com/user-attachments/assets/36a2c6ad-7ae6-4498-85b3-ed76d0b62264
195195
use blinksy::{
196196
layout::{Layout3d, Shape3d, Vec3},
197197
layout3d,
198+
leds::Ws2812,
198199
patterns::noise::{noise_fns, Noise3d, NoiseParams},
199200
ControlBuilder,
200201
};
@@ -272,6 +273,7 @@ fn main() -> ! {
272273
..Default::default()
273274
})
274275
.with_driver(ws2812!(p, Layout::PIXEL_COUNT))
276+
.with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
275277
.build();
276278

277279
control.set_brightness(0.2);
@@ -327,6 +329,7 @@ fn main() {
327329
..Default::default()
328330
})
329331
.with_driver(driver)
332+
.with_frame_buffer_size::<{ PanelLayout::PIXEL_COUNT }>()
330333
.build();
331334

332335
loop {
@@ -358,21 +361,25 @@ https://github.com/user-attachments/assets/703fe31d-e7ca-4e08-ae2b-7829c0d4d52e
358361
use blinksy::{
359362
layout::Layout1d,
360363
layout1d,
364+
leds::Ws2812,
361365
patterns::rainbow::{Rainbow, RainbowParams},
362366
ControlBuilder,
363367
};
364-
use gledopto::{board, elapsed, main, ws2812};
368+
use gledopto::{board, bootloader, elapsed, main, ws2812};
369+
370+
bootloader!();
365371

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

370-
layout1d!(Layout, 60 * 5);
376+
layout1d!(Layout, 50);
371377

372378
let mut control = ControlBuilder::new_1d()
373379
.with_layout::<Layout, { Layout::PIXEL_COUNT }>()
374380
.with_pattern::<Rainbow>(RainbowParams::default())
375-
.with_driver(ws2812!(p, Layout::PIXEL_COUNT))
381+
.with_driver(ws2812!(p, Layout::PIXEL_COUNT, buffered))
382+
.with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
376383
.build();
377384

378385
control.set_brightness(0.2);
@@ -399,24 +406,28 @@ fn main() -> ! {
399406
use blinksy::{
400407
layout::Layout1d,
401408
layout1d,
409+
leds::Ws2812,
402410
patterns::rainbow::{Rainbow, RainbowParams},
403411
ControlBuilder,
404412
};
405413
use embassy_executor::Spawner;
406-
use gledopto::{board, elapsed, init_embassy, main_embassy, ws2812_async};
414+
use gledopto::{board, bootloader, elapsed, init_embassy, main_embassy, ws2812_async};
415+
416+
bootloader!();
407417

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

412422
init_embassy!(p);
413423

414-
layout1d!(Layout, 60 * 5);
424+
layout1d!(Layout, 50);
415425

416426
let mut control = ControlBuilder::new_1d_async()
417427
.with_layout::<Layout, { Layout::PIXEL_COUNT }>()
418428
.with_pattern::<Rainbow>(RainbowParams::default())
419-
.with_driver(ws2812_async!(p, Layout::PIXEL_COUNT))
429+
.with_driver(ws2812_async!(p, Layout::PIXEL_COUNT, buffered))
430+
.with_frame_buffer_size::<{ Ws2812::frame_buffer_size(Layout::PIXEL_COUNT) }>()
420431
.build();
421432

422433
control.set_brightness(0.2);
@@ -446,10 +457,11 @@ https://github.com/user-attachments/assets/1c1cf3a2-f65c-4152-b444-29834ac749ee
446457
use blinksy::{
447458
layout::{Layout2d, Shape2d, Vec2},
448459
layout2d,
460+
leds::Apa102,
449461
patterns::noise::{noise_fns, Noise2d, NoiseParams},
450462
ControlBuilder,
451463
};
452-
use gledopto::{board, bootloader, elapsed, main, ws2812};
464+
use gledopto::{apa102, board, bootloader, elapsed, main};
453465

454466
bootloader!();
455467

@@ -472,6 +484,7 @@ fn main() -> ! {
472484
.with_layout::<Layout, { Layout::PIXEL_COUNT }>()
473485
.with_pattern::<Noise2d<noise_fns::Perlin>>(NoiseParams::default())
474486
.with_driver(apa102!(p))
487+
.with_frame_buffer_size::<{ Apa102::frame_buffer_size(Layout::PIXEL_COUNT) }>()
475488
.build();
476489

477490
control.set_brightness(0.2);

blinksy-desktop/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ blinksy = { path = "../blinksy", version = "0.10" }
1717
egui = "0.28"
1818
egui-miniquad = "0.15.0"
1919
glam = { version = "0.30.1" }
20+
heapless = "0.9.1"
2021
miniquad = "0.4"

blinksy-desktop/examples/1d-rainbow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn main() {
2020
..Default::default()
2121
})
2222
.with_driver(driver)
23+
.with_frame_buffer_size::<{ StripLayout::PIXEL_COUNT }>()
2324
.build();
2425

2526
loop {

blinksy-desktop/examples/2d-noise.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fn main() {
3030
..Default::default()
3131
})
3232
.with_driver(driver)
33+
.with_frame_buffer_size::<{ PanelLayout::PIXEL_COUNT }>()
3334
.build();
3435

3536
loop {

blinksy-desktop/examples/2d-rainbow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fn main() {
3030
..Default::default()
3131
})
3232
.with_driver(driver)
33+
.with_frame_buffer_size::<{ PanelLayout::PIXEL_COUNT }>()
3334
.build();
3435

3536
loop {

blinksy-desktop/examples/3d-arcs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ fn main() {
6868
..Default::default()
6969
})
7070
.with_driver(driver)
71+
.with_frame_buffer_size::<{ TunnelLayout::PIXEL_COUNT }>()
7172
.build();
7273

7374
loop {

blinksy-desktop/examples/3d-cube-face-noise.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ fn main() {
7878
..Default::default()
7979
})
8080
.with_driver(driver)
81+
.with_frame_buffer_size::<{ CubeFaceLayout::PIXEL_COUNT }>()
8182
.build();
8283

8384
loop {

blinksy-desktop/examples/3d-cube-volume-noise.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ fn main() {
4747
position_scalar: 0.25,
4848
})
4949
.with_driver(driver)
50+
.with_frame_buffer_size::<{ CubeVolumeLayout::PIXEL_COUNT }>()
5051
.build();
5152

5253
loop {

0 commit comments

Comments
 (0)