Skip to main content

device_envoy_esp/
led2d.rs

1#![cfg_attr(
2    feature = "doc-images",
3    doc = ::embed_doc_image::embed_image!("led2d1", "docs/assets/led2d1.png"),
4    doc = ::embed_doc_image::embed_image!("led2d2", "docs/assets/led2d2.png")
5)]
6//! A device abstraction for rectangular NeoPixel-style (WS2812) LED panel displays.
7//! For 1-dimensional LED strips, see the [`led_strip`](mod@crate::led_strip) module.
8//!
9//! This page provides the primary documentation and examples for programming LED panels.
10//! The device abstraction supports text, graphics, and animation.
11//!
12//! **After reading the examples below, see also:**
13//!
14//! - [`led2d!`](macro@crate::led2d) - Macro to generate an LED-panel struct type (includes syntax details).
15//! - [`Led2d`](`crate::led2d::Led2d`) - Core trait that defines the LED panel API surface.
16//! - [`Led2dGenerated`](led2d_generated::Led2dGenerated) - Sample generated panel type showing the constructor path.
17//! - [`LedLayout`] - Compile-time description of panel geometry and wiring, including dimensions (with examples)
18//! - [`Frame2d`] - 2D pixel array used for general graphics (includes examples)
19//! - [`led_strip!`](mod@crate::led_strip) - Underlying strip abstraction used by this panel API.
20//!
21//! # Example: Write Text
22//!
23//! In this example, we render text on a 12x4 panel. Here, the generated struct type is named `Led12x4`.
24//!
25//! ![LED panel preview][led2d1]
26//!
27//! ```rust,no_run
28//! # #![no_std]
29//! # #![no_main]
30//! # use core::convert::Infallible;
31//! # use esp_backtrace as _;
32//! use device_envoy_esp::{
33//!     Result, init_and_start, led2d,
34//!     led2d::{Led2d as _, Led2dFont, layout::LedLayout},
35//!     led_strip::colors,
36//! };
37//!
38//! // Tells us how the LED strip is wired up in the panel.
39//! // In this case, a common snake-like pattern.
40//! const LED_LAYOUT_12X4: LedLayout<48, 12, 4> = LedLayout::serpentine_column_major();
41//!
42//! // Generate a type named `Led12x4`.
43//! led2d! {
44//!     Led12x4 {
45//!         pin: GPIO18,                       // GPIO pin for LED data signal
46//!         len: 48,                           // Number of LEDs in the panel
47//!         led_layout: LED_LAYOUT_12X4,       // LED layout mapping (defines dimensions)
48//!         font: Led2dFont::Font3x4Trim,      // Font variant
49//!     }
50//! }
51//!
52//! # #[esp_rtos::main]
53//! # async fn main(spawner: embassy_executor::Spawner) -> ! {
54//! #     match example(spawner).await {
55//! #         Ok(infallible) => match infallible {},
56//! #         Err(error) => panic!("{error:?}"),
57//! #     }
58//! # }
59//! async fn example(spawner: embassy_executor::Spawner) -> Result<Infallible> {
60//!     init_and_start!(p, rmt80: rmt80, mode: rmt_mode::Blocking);
61//!
62//!     // Create a device abstraction for the LED panel.
63//!     // Behind the scenes, this creates a channel and background task to manage the display.
64//!     let led12x4 = Led12x4::new(p.GPIO18, rmt80.channel0, spawner)?;
65//!
66//!     // Write text to the display with per-character colors.
67//!     let colors = [colors::CYAN, colors::RED, colors::YELLOW];
68//!     // Each character takes the next color; when we run out, we start over.
69//!     led12x4.write_text("Rust", &colors);
70//!
71//!     core::future::pending().await
72//! }
73//! ```
74//!
75//! # Example: Animated Text on a Rotated Panel
76//!
77//! This example animates text on a rotated 12x8 panel built from two stacked 12x4 panels.
78//!
79//! ![LED panel preview][led2d2]
80//!
81//! ```rust,no_run
82//! # #![no_std]
83//! # #![no_main]
84//! # use core::convert::Infallible;
85//! # use esp_backtrace as _;
86//! use device_envoy_esp::{
87//!     Result, init_and_start, led2d,
88//!     led2d::{Frame2d, Led2d as _, Led2dFont, layout::LedLayout},
89//!     led_strip::{Current, Gamma, colors},
90//! };
91//! use embassy_time::Duration;
92//!
93//! // Our panel is two 12x4 panels stacked vertically and then rotated clockwise.
94//! const LED_LAYOUT_12X4: LedLayout<48, 12, 4> = LedLayout::serpentine_column_major();
95//! const LED_LAYOUT_12X8: LedLayout<96, 12, 8> = LED_LAYOUT_12X4.combine_v(LED_LAYOUT_12X4);
96//! const LED_LAYOUT_8X12_ROTATED: LedLayout<96, 8, 12> = LED_LAYOUT_12X8.rotate_cw();
97//!
98//! // Generate a type named `Led12x8Animated`.
99//! led2d! {
100//!     Led12x8Animated {
101//!         pin: GPIO18,                           // GPIO pin for LED data signal
102//!         len: 96,                               // Number of LEDs in the panel
103//!         led_layout: LED_LAYOUT_8X12_ROTATED,  // Two 12x4 panels stacked and rotated
104//!         max_current: Current::Milliamps(300), // Power budget, default is 250 mA
105//!         font: Led2dFont::Font4x6Trim,         // 4x6 font without normal padding
106//!         gamma: Gamma::Linear,                 // Color correction curve, default is Gamma::Srgb
107//!         max_frames: 2,                        // Maximum animation frames, default is 16
108//!     }
109//! }
110//!
111//! # #[esp_rtos::main]
112//! # async fn main(spawner: embassy_executor::Spawner) -> ! {
113//! #     match example(spawner).await {
114//! #         Ok(infallible) => match infallible {},
115//! #         Err(error) => panic!("{error:?}"),
116//! #     }
117//! # }
118//! async fn example(spawner: embassy_executor::Spawner) -> Result<Infallible> {
119//!     init_and_start!(p, rmt80: rmt80, mode: rmt_mode::Blocking);
120//!
121//!     // Create a device abstraction for the rotated LED panel.
122//!     let led12x8_animated = Led12x8Animated::new(p.GPIO18, rmt80.channel0, spawner)?;
123//!
124//!     // Write "Go" into an in-memory frame buffer.
125//!     let mut frame_0 = Frame2d::new();
126//!     // Empty text colors array defaults to white.
127//!     led12x8_animated.write_text_to_frame("Go", &[], &mut frame_0);
128//!
129//!     // Write "Go" into a second frame buffer with custom colors and on the 2nd line.
130//!     let mut frame_1 = Frame2d::new();
131//!     // "\n" starts a new line. Text does not wrap but rather clips.
132//!     led12x8_animated.write_text_to_frame("\nGo", &[colors::HOT_PINK, colors::LIME], &mut frame_1);
133//!
134//!     // Animate between the two frames indefinitely.
135//!     let frame_duration = Duration::from_secs(1);
136//!     led12x8_animated.animate([(frame_0, frame_duration), (frame_1, frame_duration)]);
137//!
138//!     core::future::pending().await
139//! }
140//! ```
141//!
142pub mod layout {
143    pub use device_envoy_core::led2d::layout::*;
144}
145
146pub use device_envoy_core::led2d::Led2d;
147pub use device_envoy_core::led2d::{
148    bit_matrix3x4_font, render_text_to_frame, Frame2d, Led2dFont, Led2dStripAdapter,
149    Led2dStripBacked, LedLayout, Point, Size,
150};
151pub mod led2d_generated;
152
153// Must be `pub` (not `pub(crate)`) because called by macro-generated code that
154// expands at the call site in downstream crates.
155// This is an implementation detail, not part of the user-facing API.
156#[doc(hidden)]
157pub type Led2dEsp<'a, const N: usize, S> = Led2dStripAdapter<'a, N, S>;
158
159/// Macro to generate an LED-panel struct type (includes syntax details). See [`Led2d`](`crate::led2d::Led2d`) for the shared API.
160///
161/// **See the [led2d module](mod@crate::led2d) for usage examples.**
162///
163/// **Syntax:**
164///
165/// ```text
166/// led2d! {
167///     <Name> {
168///         pin: <pin_ident>,
169///         len: <usize_expr>,
170///         led_layout: <LedLayout_expr>,
171///         font: <Led2dFont_expr>,
172///         max_current: <Current_expr>, // optional
173///         engine: Engine::Rmt|Engine::Spi, // optional
174///         gamma: <Gamma_expr>, // optional
175///         max_frames: <usize_expr>, // optional
176///     }
177/// }
178/// ```
179///
180/// # Fields
181///
182/// **Required fields:**
183///
184/// - `pin` - GPIO pin for LED data.
185/// - `len` - Number of LEDs in the generated strip.
186/// - `led_layout` - LED strip physical layout (see [`LedLayout`]); this defines panel size.
187/// - `font` - Built-in font variant (see [`Led2dFont`]), for example `Led2dFont::Font4x6Trim`.
188///
189/// The `led_layout` value must be a const so its dimensions can be derived at compile time.
190///
191/// **Optional fields:**
192///
193/// - `max_current` - Electrical current budget (default: 250 mA).
194/// - `engine` - Transport engine (`Engine::Rmt` or `Engine::Spi`, default: `Engine::Rmt`).
195/// - `gamma` - Color correction curve (default: `Gamma::Srgb`).
196/// - `max_frames` - Maximum number of animation frames (default: 16).
197///
198/// `max_frames = 0` disables animation and allocates no frame storage; `write_frame()` is still supported.
199///
200#[doc = include_str!("docs/current_limiting_and_gamma.md")]
201///
202/// # Related Macros
203///
204/// - [`led_strip!`](mod@crate::led_strip) - For 1-dimensional LED strips.
205#[doc(hidden)]
206#[macro_export]
207macro_rules! led2d {
208    ($($tt:tt)*) => { $crate::__led2d_impl! { $($tt)* } };
209}
210
211#[cfg(target_os = "none")]
212#[doc(inline)]
213pub use led2d;
214
215#[doc(hidden)]
216#[macro_export]
217macro_rules! __led2d_impl {
218    (
219        $name:ident {
220            $($fields:tt)*
221        }
222    ) => {
223        $crate::__led2d_impl! {
224            $name,
225            pin = [],
226            len = [],
227            led_layout = [],
228            max_current = [],
229            font = [],
230            engine = [],
231            gamma = [],
232            max_frames = [],
233            fields = [$($fields)*],
234        }
235    };
236
237    (
238        $name:ident,
239        pin = [$pin:ident],
240        len = [$len:expr],
241        led_layout = [$led_layout:expr],
242        max_current = [$($max_current:expr)?],
243        font = [$font:expr],
244        engine = [$($engine:tt)?],
245        gamma = [$($gamma:expr)?],
246        max_frames = [$($max_frames:expr)?],
247        fields = [],
248    ) => {
249        $crate::__led2d_dispatch_engine!(
250            $name,
251            $pin,
252            $len,
253            $led_layout,
254            $crate::__led_strip_max_current_or_default!([$($max_current)?]),
255            $font,
256            [$($engine)?],
257            [$($gamma)?],
258            [$($max_frames)?],
259        );
260    };
261    (
262        $name:ident,
263        pin = [],
264        len = [$($len:expr)?],
265        led_layout = [$($led_layout:expr)?],
266        max_current = [$($max_current:expr)?],
267        font = [$($font:expr)?],
268        engine = [$($engine:tt)?],
269        gamma = [$($gamma:expr)?],
270        max_frames = [$($max_frames:expr)?],
271        fields = [],
272    ) => {
273        compile_error!("led2d! missing required `pin` field");
274    };
275    (
276        $name:ident,
277        pin = [$pin:ident],
278        len = [],
279        led_layout = [$($led_layout:expr)?],
280        max_current = [$($max_current:expr)?],
281        font = [$($font:expr)?],
282        engine = [$($engine:tt)?],
283        gamma = [$($gamma:expr)?],
284        max_frames = [$($max_frames:expr)?],
285        fields = [],
286    ) => {
287        compile_error!("led2d! missing required `len` field");
288    };
289    (
290        $name:ident,
291        pin = [$pin:ident],
292        len = [$len:expr],
293        led_layout = [],
294        max_current = [$($max_current:expr)?],
295        font = [$($font:expr)?],
296        engine = [$($engine:tt)?],
297        gamma = [$($gamma:expr)?],
298        max_frames = [$($max_frames:expr)?],
299        fields = [],
300    ) => {
301        compile_error!("led2d! missing required `led_layout` field");
302    };
303    (
304        $name:ident,
305        pin = [$pin:ident],
306        len = [$len:expr],
307        led_layout = [$led_layout:expr],
308        max_current = [$($max_current:expr)?],
309        font = [],
310        engine = [$($engine:tt)?],
311        gamma = [$($gamma:expr)?],
312        max_frames = [$($max_frames:expr)?],
313        fields = [],
314    ) => {
315        compile_error!("led2d! missing required `font` field");
316    };
317    (
318        $name:ident,
319        pin = [],
320        len = [$($len:expr)?],
321        led_layout = [$($led_layout:expr)?],
322        max_current = [$($max_current:expr)?],
323        font = [$($font:expr)?],
324        engine = [$($engine:tt)?],
325        gamma = [$($gamma:expr)?],
326        max_frames = [$($max_frames:expr)?],
327        fields = [pin: $pin:ident $(, $($rest:tt)*)?],
328    ) => {
329        $crate::__led2d_impl!(
330            $name,
331            pin = [$pin],
332            len = [$($len)?],
333            led_layout = [$($led_layout)?],
334            max_current = [$($max_current)?],
335            font = [$($font)?],
336            engine = [$($engine)?],
337            gamma = [$($gamma)?],
338            max_frames = [$($max_frames)?],
339            fields = [$($($rest)*)?],
340        );
341    };
342    (
343        $name:ident,
344        pin = [$already_pin:ident],
345        len = [$($len:expr)?],
346        led_layout = [$($led_layout:expr)?],
347        max_current = [$($max_current:expr)?],
348        font = [$($font:expr)?],
349        engine = [$($engine:tt)?],
350        gamma = [$($gamma:expr)?],
351        max_frames = [$($max_frames:expr)?],
352        fields = [pin: $pin:ident $(, $($rest:tt)*)?],
353    ) => {
354        compile_error!("led2d! duplicate `pin` field");
355    };
356    (
357        $name:ident,
358        pin = [$($pin:ident)?],
359        len = [],
360        led_layout = [$($led_layout:expr)?],
361        max_current = [$($max_current:expr)?],
362        font = [$($font:expr)?],
363        engine = [$($engine:tt)?],
364        gamma = [$($gamma:expr)?],
365        max_frames = [$($max_frames:expr)?],
366        fields = [len: $len:expr $(, $($rest:tt)*)?],
367    ) => {
368        $crate::__led2d_impl!(
369            $name,
370            pin = [$($pin)?],
371            len = [$len],
372            led_layout = [$($led_layout)?],
373            max_current = [$($max_current)?],
374            font = [$($font)?],
375            engine = [$($engine)?],
376            gamma = [$($gamma)?],
377            max_frames = [$($max_frames)?],
378            fields = [$($($rest)*)?],
379        );
380    };
381    (
382        $name:ident,
383        pin = [$($pin:ident)?],
384        len = [$already_len:expr],
385        led_layout = [$($led_layout:expr)?],
386        max_current = [$($max_current:expr)?],
387        font = [$($font:expr)?],
388        engine = [$($engine:tt)?],
389        gamma = [$($gamma:expr)?],
390        max_frames = [$($max_frames:expr)?],
391        fields = [len: $len:expr $(, $($rest:tt)*)?],
392    ) => {
393        compile_error!("led2d! duplicate `len` field");
394    };
395    (
396        $name:ident,
397        pin = [$($pin:ident)?],
398        len = [$($len:expr)?],
399        led_layout = [],
400        max_current = [$($max_current:expr)?],
401        font = [$($font:expr)?],
402        engine = [$($engine:tt)?],
403        gamma = [$($gamma:expr)?],
404        max_frames = [$($max_frames:expr)?],
405        fields = [led_layout: $led_layout:expr $(, $($rest:tt)*)?],
406    ) => {
407        $crate::__led2d_impl!(
408            $name,
409            pin = [$($pin)?],
410            len = [$($len)?],
411            led_layout = [$led_layout],
412            max_current = [$($max_current)?],
413            font = [$($font)?],
414            engine = [$($engine)?],
415            gamma = [$($gamma)?],
416            max_frames = [$($max_frames)?],
417            fields = [$($($rest)*)?],
418        );
419    };
420    (
421        $name:ident,
422        pin = [$($pin:ident)?],
423        len = [$($len:expr)?],
424        led_layout = [$already_led_layout:expr],
425        max_current = [$($max_current:expr)?],
426        font = [$($font:expr)?],
427        engine = [$($engine:tt)?],
428        gamma = [$($gamma:expr)?],
429        max_frames = [$($max_frames:expr)?],
430        fields = [led_layout: $led_layout:expr $(, $($rest:tt)*)?],
431    ) => {
432        compile_error!("led2d! duplicate `led_layout` field");
433    };
434    (
435        $name:ident,
436        pin = [$($pin:ident)?],
437        len = [$($len:expr)?],
438        led_layout = [$($led_layout:expr)?],
439        max_current = [],
440        font = [$($font:expr)?],
441        engine = [$($engine:tt)?],
442        gamma = [$($gamma:expr)?],
443        max_frames = [$($max_frames:expr)?],
444        fields = [max_current: $max_current:expr $(, $($rest:tt)*)?],
445    ) => {
446        $crate::__led2d_impl!(
447            $name,
448            pin = [$($pin)?],
449            len = [$($len)?],
450            led_layout = [$($led_layout)?],
451            max_current = [$max_current],
452            font = [$($font)?],
453            engine = [$($engine)?],
454            gamma = [$($gamma)?],
455            max_frames = [$($max_frames)?],
456            fields = [$($($rest)*)?],
457        );
458    };
459    (
460        $name:ident,
461        pin = [$($pin:ident)?],
462        len = [$($len:expr)?],
463        led_layout = [$($led_layout:expr)?],
464        max_current = [$already_max_current:expr],
465        font = [$($font:expr)?],
466        engine = [$($engine:tt)?],
467        gamma = [$($gamma:expr)?],
468        max_frames = [$($max_frames:expr)?],
469        fields = [max_current: $max_current:expr $(, $($rest:tt)*)?],
470    ) => {
471        compile_error!("led2d! duplicate `max_current` field");
472    };
473    (
474        $name:ident,
475        pin = [$($pin:ident)?],
476        len = [$($len:expr)?],
477        led_layout = [$($led_layout:expr)?],
478        max_current = [$($max_current:expr)?],
479        font = [],
480        engine = [$($engine:tt)?],
481        gamma = [$($gamma:expr)?],
482        max_frames = [$($max_frames:expr)?],
483        fields = [font: $font:expr $(, $($rest:tt)*)?],
484    ) => {
485        $crate::__led2d_impl!(
486            $name,
487            pin = [$($pin)?],
488            len = [$($len)?],
489            led_layout = [$($led_layout)?],
490            max_current = [$($max_current)?],
491            font = [$font],
492            engine = [$($engine)?],
493            gamma = [$($gamma)?],
494            max_frames = [$($max_frames)?],
495            fields = [$($($rest)*)?],
496        );
497    };
498    (
499        $name:ident,
500        pin = [$($pin:ident)?],
501        len = [$($len:expr)?],
502        led_layout = [$($led_layout:expr)?],
503        max_current = [$($max_current:expr)?],
504        font = [$already_font:expr],
505        engine = [$($engine:tt)?],
506        gamma = [$($gamma:expr)?],
507        max_frames = [$($max_frames:expr)?],
508        fields = [font: $font:expr $(, $($rest:tt)*)?],
509    ) => {
510        compile_error!("led2d! duplicate `font` field");
511    };
512    (
513        $name:ident,
514        pin = [$($pin:ident)?],
515        len = [$($len:expr)?],
516        led_layout = [$($led_layout:expr)?],
517        max_current = [$($max_current:expr)?],
518        font = [$($font:expr)?],
519        engine = [],
520        gamma = [$($gamma:expr)?],
521        max_frames = [$($max_frames:expr)?],
522        fields = [engine: Engine::Spi $(, $($rest:tt)*)?],
523    ) => {
524        $crate::__led2d_impl!(
525            $name,
526            pin = [$($pin)?],
527            len = [$($len)?],
528            led_layout = [$($led_layout)?],
529            max_current = [$($max_current)?],
530            font = [$($font)?],
531            engine = [Spi],
532            gamma = [$($gamma)?],
533            max_frames = [$($max_frames)?],
534            fields = [$($($rest)*)?],
535        );
536    };
537    (
538        $name:ident,
539        pin = [$($pin:ident)?],
540        len = [$($len:expr)?],
541        led_layout = [$($led_layout:expr)?],
542        max_current = [$($max_current:expr)?],
543        font = [$($font:expr)?],
544        engine = [],
545        gamma = [$($gamma:expr)?],
546        max_frames = [$($max_frames:expr)?],
547        fields = [engine: $crate::led_strip::Engine::Spi $(, $($rest:tt)*)?],
548    ) => {
549        $crate::__led2d_impl!(
550            $name,
551            pin = [$($pin)?],
552            len = [$($len)?],
553            led_layout = [$($led_layout)?],
554            max_current = [$($max_current)?],
555            font = [$($font)?],
556            engine = [Spi],
557            gamma = [$($gamma)?],
558            max_frames = [$($max_frames)?],
559            fields = [$($($rest)*)?],
560        );
561    };
562    (
563        $name:ident,
564        pin = [$($pin:ident)?],
565        len = [$($len:expr)?],
566        led_layout = [$($led_layout:expr)?],
567        max_current = [$($max_current:expr)?],
568        font = [$($font:expr)?],
569        engine = [],
570        gamma = [$($gamma:expr)?],
571        max_frames = [$($max_frames:expr)?],
572        fields = [engine: device_envoy_esp::led_strip::Engine::Spi $(, $($rest:tt)*)?],
573    ) => {
574        $crate::__led2d_impl!(
575            $name,
576            pin = [$($pin)?],
577            len = [$($len)?],
578            led_layout = [$($led_layout)?],
579            max_current = [$($max_current)?],
580            font = [$($font)?],
581            engine = [Spi],
582            gamma = [$($gamma)?],
583            max_frames = [$($max_frames)?],
584            fields = [$($($rest)*)?],
585        );
586    };
587    (
588        $name:ident,
589        pin = [$($pin:ident)?],
590        len = [$($len:expr)?],
591        led_layout = [$($led_layout:expr)?],
592        max_current = [$($max_current:expr)?],
593        font = [$($font:expr)?],
594        engine = [],
595        gamma = [$($gamma:expr)?],
596        max_frames = [$($max_frames:expr)?],
597        fields = [engine: Engine::Rmt $(, $($rest:tt)*)?],
598    ) => {
599        $crate::__led2d_impl!(
600            $name,
601            pin = [$($pin)?],
602            len = [$($len)?],
603            led_layout = [$($led_layout)?],
604            max_current = [$($max_current)?],
605            font = [$($font)?],
606            engine = [Rmt],
607            gamma = [$($gamma)?],
608            max_frames = [$($max_frames)?],
609            fields = [$($($rest)*)?],
610        );
611    };
612    (
613        $name:ident,
614        pin = [$($pin:ident)?],
615        len = [$($len:expr)?],
616        led_layout = [$($led_layout:expr)?],
617        max_current = [$($max_current:expr)?],
618        font = [$($font:expr)?],
619        engine = [],
620        gamma = [$($gamma:expr)?],
621        max_frames = [$($max_frames:expr)?],
622        fields = [engine: $crate::led_strip::Engine::Rmt $(, $($rest:tt)*)?],
623    ) => {
624        $crate::__led2d_impl!(
625            $name,
626            pin = [$($pin)?],
627            len = [$($len)?],
628            led_layout = [$($led_layout)?],
629            max_current = [$($max_current)?],
630            font = [$($font)?],
631            engine = [Rmt],
632            gamma = [$($gamma)?],
633            max_frames = [$($max_frames)?],
634            fields = [$($($rest)*)?],
635        );
636    };
637    (
638        $name:ident,
639        pin = [$($pin:ident)?],
640        len = [$($len:expr)?],
641        led_layout = [$($led_layout:expr)?],
642        max_current = [$($max_current:expr)?],
643        font = [$($font:expr)?],
644        engine = [],
645        gamma = [$($gamma:expr)?],
646        max_frames = [$($max_frames:expr)?],
647        fields = [engine: device_envoy_esp::led_strip::Engine::Rmt $(, $($rest:tt)*)?],
648    ) => {
649        $crate::__led2d_impl!(
650            $name,
651            pin = [$($pin)?],
652            len = [$($len)?],
653            led_layout = [$($led_layout)?],
654            max_current = [$($max_current)?],
655            font = [$($font)?],
656            engine = [Rmt],
657            gamma = [$($gamma)?],
658            max_frames = [$($max_frames)?],
659            fields = [$($($rest)*)?],
660        );
661    };
662    (
663        $name:ident,
664        pin = [$($pin:ident)?],
665        len = [$($len:expr)?],
666        led_layout = [$($led_layout:expr)?],
667        max_current = [$($max_current:expr)?],
668        font = [$($font:expr)?],
669        engine = [$already_engine:tt],
670        gamma = [$($gamma:expr)?],
671        max_frames = [$($max_frames:expr)?],
672        fields = [engine: $ignored:path $(, $($rest:tt)*)?],
673    ) => {
674        compile_error!("led2d! duplicate `engine` field");
675    };
676    (
677        $name:ident,
678        pin = [$($pin:ident)?],
679        len = [$($len:expr)?],
680        led_layout = [$($led_layout:expr)?],
681        max_current = [$($max_current:expr)?],
682        font = [$($font:expr)?],
683        engine = [],
684        gamma = [$($gamma:expr)?],
685        max_frames = [$($max_frames:expr)?],
686        fields = [engine: $ignored:path $(, $($rest:tt)*)?],
687    ) => {
688        compile_error!("led2d! engine must be Engine::Rmt or Engine::Spi");
689    };
690    (
691        $name:ident,
692        pin = [$($pin:ident)?],
693        len = [$($len:expr)?],
694        led_layout = [$($led_layout:expr)?],
695        max_current = [$($max_current:expr)?],
696        font = [$($font:expr)?],
697        engine = [$($engine:tt)?],
698        gamma = [],
699        max_frames = [$($max_frames:expr)?],
700        fields = [gamma: $gamma:expr $(, $($rest:tt)*)?],
701    ) => {
702        $crate::__led2d_impl!(
703            $name,
704            pin = [$($pin)?],
705            len = [$($len)?],
706            led_layout = [$($led_layout)?],
707            max_current = [$($max_current)?],
708            font = [$($font)?],
709            engine = [$($engine)?],
710            gamma = [$gamma],
711            max_frames = [$($max_frames)?],
712            fields = [$($($rest)*)?],
713        );
714    };
715    (
716        $name:ident,
717        pin = [$($pin:ident)?],
718        len = [$($len:expr)?],
719        led_layout = [$($led_layout:expr)?],
720        max_current = [$($max_current:expr)?],
721        font = [$($font:expr)?],
722        engine = [$($engine:tt)?],
723        gamma = [$already_gamma:expr],
724        max_frames = [$($max_frames:expr)?],
725        fields = [gamma: $gamma:expr $(, $($rest:tt)*)?],
726    ) => {
727        compile_error!("led2d! duplicate `gamma` field");
728    };
729    (
730        $name:ident,
731        pin = [$($pin:ident)?],
732        len = [$($len:expr)?],
733        led_layout = [$($led_layout:expr)?],
734        max_current = [$($max_current:expr)?],
735        font = [$($font:expr)?],
736        engine = [$($engine:tt)?],
737        gamma = [$($gamma:expr)?],
738        max_frames = [],
739        fields = [max_frames: $max_frames:expr $(, $($rest:tt)*)?],
740    ) => {
741        $crate::__led2d_impl!(
742            $name,
743            pin = [$($pin)?],
744            len = [$($len)?],
745            led_layout = [$($led_layout)?],
746            max_current = [$($max_current)?],
747            font = [$($font)?],
748            engine = [$($engine)?],
749            gamma = [$($gamma)?],
750            max_frames = [$max_frames],
751            fields = [$($($rest)*)?],
752        );
753    };
754    (
755        $name:ident,
756        pin = [$($pin:ident)?],
757        len = [$($len:expr)?],
758        led_layout = [$($led_layout:expr)?],
759        max_current = [$($max_current:expr)?],
760        font = [$($font:expr)?],
761        engine = [$($engine:tt)?],
762        gamma = [$($gamma:expr)?],
763        max_frames = [$already_max_frames:expr],
764        fields = [max_frames: $max_frames:expr $(, $($rest:tt)*)?],
765    ) => {
766        compile_error!("led2d! duplicate `max_frames` field");
767    };
768    (
769        $name:ident,
770        pin = [$($pin:ident)?],
771        len = [$($len:expr)?],
772        led_layout = [$($led_layout:expr)?],
773        max_current = [$($max_current:expr)?],
774        font = [$($font:expr)?],
775        engine = [$($engine:tt)?],
776        gamma = [$($gamma:expr)?],
777        max_frames = [$($max_frames:expr)?],
778        fields = [$field:ident : $value:expr $(, $($rest:tt)*)?],
779    ) => {
780        compile_error!(
781            "led2d! unknown field; expected `pin`, `len`, `led_layout`, `font`, `max_current`, `engine`, `gamma`, or `max_frames`"
782        );
783    };
784}
785
786#[doc(hidden)]
787#[macro_export]
788macro_rules! __led2d_dispatch_engine {
789    (
790        $name:ident,
791        $pin:ident,
792        $len:expr,
793        $led_layout:expr,
794        $max_current:expr,
795        $font:expr,
796        [Spi],
797        [$($gamma:expr)?],
798        [$($max_frames:expr)?],
799    ) => {
800        $crate::led_strip::spi::__led_strip_spi_inner!{
801            $name,
802            $pin,
803            $len,
804            $max_current,
805            [$($gamma)?],
806            [$($max_frames)?],
807            [],
808            [$led_layout],
809            [$font],
810        }
811    };
812    (
813        $name:ident,
814        $pin:ident,
815        $len:expr,
816        $led_layout:expr,
817        $max_current:expr,
818        $font:expr,
819        [Rmt],
820        [$($gamma:expr)?],
821        [$($max_frames:expr)?],
822    ) => {
823        $crate::led_strip::__led_strip_inner!{
824            $name,
825            $pin,
826            $len,
827            $max_current,
828            [$($gamma)?],
829            [$($max_frames)?],
830            [$led_layout],
831            [$font],
832        }
833    };
834    (
835        $name:ident,
836        $pin:ident,
837        $len:expr,
838        $led_layout:expr,
839        $max_current:expr,
840        $font:expr,
841        [],
842        [$($gamma:expr)?],
843        [$($max_frames:expr)?],
844    ) => {
845        $crate::led_strip::__led_strip_inner!{
846            $name,
847            $pin,
848            $len,
849            $max_current,
850            [$($gamma)?],
851            [$($max_frames)?],
852            [$led_layout],
853            [$font],
854        }
855    };
856}