Skip to main content

device_envoy/
led_strip.rs

1#![cfg_attr(
2    feature = "doc-images",
3    doc = ::embed_doc_image::embed_image!(
4        "led_strip_simple",
5        "docs/assets/led_strip_simple.png"
6    ),
7    doc = ::embed_doc_image::embed_image!(
8        "led_strip_gpio0",
9        "docs/assets/led_strip_gpio0.png"
10    ),
11    doc = ::embed_doc_image::embed_image!(
12        "led_strip_gogo",
13        "docs/assets/led2d2.png"
14    ),
15    doc = ::embed_doc_image::embed_image!(
16        "led_strip_animated",
17        "docs/assets/led_strip_animated.png"
18    )
19)]
20//! A device abstraction for 1-dimensional NeoPixel-style (WS2812) LED strips. For 2-dimensional
21//! panels, see the [`led2d`](mod@crate::led2d) module.
22//!
23//! This page provides the primary documentation and examples for programming LED strips.
24//! The device abstraction supports pixel patterns and animation on the LED strip.
25//!
26//! **After reading the examples below, see also:**
27//!
28//! - [`led_strip!`](macro@crate::led_strip) — Macro to generate an LED strip struct type (includes syntax details). See [`LedStripGenerated`](led_strip_generated::LedStripGenerated) for a sample of a generated type.
29//! - [`LedStripGenerated`](led_strip_generated::LedStripGenerated) — Sample struct type showing all methods and associated constants.
30//! - [`Frame1d`] — 1D pixel array used to describe LED strip patterns.
31//! - [`led_strips!`](crate::led_strips) — Alternative macro to share a PIO resource with other strips or panels (includes examples).
32//!
33//! # Example: Write a Single 1-Dimensional Frame
34//!
35//! In this example, we set every other LED to blue and gray. Here, the generated struct type is
36//! named `LedStripSimple`.
37//!
38//! ![LED strip preview][led_strip_simple]
39//!
40//! ```rust,no_run
41//! # #![no_std]
42//! # #![no_main]
43//! # use panic_probe as _;
44//! # use core::convert::Infallible;
45//! # use core::default::Default;
46//! # use core::result::Result::Ok;
47//! use device_envoy::{Result, led_strip::{Frame1d, colors}};
48//! use device_envoy::led_strip;
49//!
50//! // Define LedStripSimple, a struct type for an 8-LED strip on PIN_0.
51//! led_strip! {
52//!     LedStripSimple {
53//!         pin: PIN_0,  // GPIO pin for LED data
54//!         len: 8,      // 8 LEDs
55//!         // other inputs set to their defaults
56//!     }
57//! }
58//!
59//! # #[embassy_executor::main]
60//! # async fn main(spawner: embassy_executor::Spawner) -> ! {
61//! #     let err = example(spawner).await.unwrap_err();
62//! #     core::panic!("{err}");
63//! # }
64//! async fn example(spawner: embassy_executor::Spawner) -> Result<Infallible> {
65//!     let p = embassy_rp::init(Default::default());
66//!     // Create a LedStripSimple instance.
67//!     let led_strip_simple = LedStripSimple::new(p.PIN_0, p.PIO0, p.DMA_CH0, spawner)?;
68//!
69//!     // Create and write a frame with alternating blue and gray pixels.
70//!     let mut frame = Frame1d::new();
71//!     for pixel_index in 0..LedStripSimple::LEN {
72//!         // Directly index into the frame buffer.
73//!         frame[pixel_index] = [colors::BLUE, colors::GRAY][pixel_index % 2];
74//!     }
75//!
76//!     // Display the frame on the LED strip (until replaced).
77//!     led_strip_simple.write_frame(frame)?;
78//!
79//!     core::future::pending().await // run forever
80//! }
81//! ```
82//!
83//! # Example: Animate a Sequence
84//!
85//! This example animates a 96-LED strip through red, green, and blue frames, cycling continuously.
86//! Here, the generated struct type is named `LedStripAnimated`.
87//!
88//! ![LED strip preview][led_strip_animated]
89//!
90//! ```rust,no_run
91//! # #![no_std]
92//! # #![no_main]
93//! # use panic_probe as _;
94//! # use core::convert::Infallible;
95//! # use core::default::Default;
96//! # use core::result::Result::Ok;
97//! use device_envoy::{Result, led_strip::{Current, Frame1d, Gamma, colors}};
98//! use device_envoy::led_strip;
99//!
100//! // Define LedStripAnimated, a struct type for a 96-LED strip on PIN_4.
101//! // We change some defaults including setting a 1A power budget and disabling gamma correction.
102//! led_strip! {
103//!     pub(self) LedStripAnimated {               // Can provide a visibility modifier
104//!         pin: PIN_4,                            // GPIO pin for LED data
105//!         len: 96,                               // 96 LEDs
106//!         pio: PIO1,                             // Use PIO resource 1
107//!         dma: DMA_CH3,                          // Use DMA channel 3
108//!         max_current: Current::Milliamps(1000), // 1A power budget
109//!         gamma: Gamma::Linear,                  // No color correction
110//!         max_frames: 3,                         // Up to 3 animation frames
111//!     }
112//! }
113//!
114//! # #[embassy_executor::main]
115//! # async fn main(spawner: embassy_executor::Spawner) -> ! {
116//! #     let err = example(spawner).await.unwrap_err();
117//! #     core::panic!("{err}");
118//! # }
119//! async fn example(spawner: embassy_executor::Spawner) -> Result<Infallible> {
120//!     let p = embassy_rp::init(Default::default());
121//!     let led_strip_animated = LedStripAnimated::new(p.PIN_4, p.PIO1, p.DMA_CH3, spawner)?;
122//!
123//!     // Create a sequence of frames and durations and then animate them (looping, until replaced).
124//!     let frame_duration = embassy_time::Duration::from_millis(300);
125//!     led_strip_animated.animate([
126//!         (Frame1d::filled(colors::RED), frame_duration),
127//!         (Frame1d::filled(colors::GREEN), frame_duration),
128//!         (Frame1d::filled(colors::BLUE), frame_duration),
129//!     ])?;
130//!
131//!     core::future::pending().await // run forever
132//! }
133//! ```
134
135/// 8-bit-per-channel RGB color re-exported from the
136/// [`smart_leds`](https://docs.rs/smart-leds/latest/smart_leds/index.html) crate.
137///
138/// Used in [`Frame1d`] and [Frame2d](crate::led2d::Frame2d) for pixel colors.
139///
140/// See [`colors`] for the predefined color list.
141///
142/// Conversion to [`Rgb888`] via [`ToRgb888::to_rgb888`].
143///
144/// # [`smart_leds`](https://docs.rs/smart-leds/latest/smart_leds/type.RGB8.html) Documentation:
145#[doc(inline)]
146pub use smart_leds::RGB8;
147
148/// Module containing predefined [`RGB8`] color constants, re-exported from the
149/// [`smart_leds`](https://docs.rs/smart-leds/latest/smart_leds/index.html) crate.
150///
151/// These constants follow CSS/Web color names. In particular, `GREEN` is
152/// `(0, 128, 0)` and `LIME` is `(0, 255, 0)`. If you want "full green," use
153/// `LIME`.
154///
155/// All examples in this crate use `smart_leds::colors::*` as the single source
156/// of named colors; when an embedded-graphics API needs [`Rgb888`], convert with
157/// [`ToRgb888::to_rgb888`].
158#[doc(inline)]
159pub use smart_leds::colors;
160
161/// 8-bit-per-channel RGB color re-exported from the
162/// [`embedded-graphics`](https://docs.rs/embedded-graphics) crate.
163///
164/// See [Frame2d](crate::led2d::Frame2d) for usage examples of [`Rgb888`] and [`RGB8`].
165///
166/// Get named colors from [`colors`] and convert to `Rgb888` with
167/// [`ToRgb888::to_rgb888`].
168///
169/// Conversion to [`RGB8`] via [`ToRgb8::to_rgb8`].
170///
171/// # [`embedded-graphics`](https://docs.rs/embedded-graphics/latest/embedded_graphics/pixelcolor/struct.Rgb888.html) Documentation:
172#[doc(inline)]
173pub use embedded_graphics::pixelcolor::Rgb888;
174
175/// Convert colors to [`RGB8`] for LED strip rendering.
176///
177/// # Example
178///
179/// ```rust,no_run
180/// # #![no_std]
181/// # #![no_main]
182/// # use panic_probe as _;
183/// # use core::assert_eq;
184/// use device_envoy::led_strip::{Rgb888, ToRgb8, RGB8};
185/// # fn main() {
186/// let rgb8 = RGB8::new(16, 32, 48).to_rgb8();
187/// let rgb888 = Rgb888::new(16, 32, 48);
188/// let converted = rgb888.to_rgb8();
189///
190/// assert_eq!(rgb8, converted);
191/// # }
192/// ```
193pub trait ToRgb8 {
194    /// Convert this color to [`RGB8`].
195    ///
196    /// See the [`ToRgb8`](Self) trait docs for a usage example.
197    #[must_use]
198    fn to_rgb8(self) -> RGB8;
199}
200
201impl ToRgb8 for RGB8 {
202    #[inline(always)]
203    fn to_rgb8(self) -> RGB8 {
204        self
205    }
206}
207
208impl ToRgb8 for Rgb888 {
209    #[inline(always)]
210    fn to_rgb8(self) -> RGB8 {
211        RGB8::new(self.r(), self.g(), self.b())
212    }
213}
214
215/// Convert colors to [`Rgb888`] for embedded-graphics rendering.
216///
217/// # Example
218///
219/// ```rust,no_run
220/// # #![no_std]
221/// # #![no_main]
222/// # use panic_probe as _;
223/// # use core::assert_eq;
224/// use device_envoy::led_strip::{Rgb888, ToRgb888, RGB8};
225/// # fn main() {
226/// let rgb8 = RGB8::new(16, 32, 48);
227/// let rgb888 = rgb8.to_rgb888();
228/// let already_rgb888 = Rgb888::new(16, 32, 48).to_rgb888();
229///
230/// assert_eq!(rgb888, already_rgb888);
231/// # }
232/// ```
233pub trait ToRgb888 {
234    /// Convert this color to [`Rgb888`].
235    ///
236    /// See the [`ToRgb888`](Self) trait docs for a usage example.
237    #[must_use]
238    fn to_rgb888(self) -> Rgb888;
239}
240
241impl ToRgb888 for RGB8 {
242    #[inline(always)]
243    fn to_rgb888(self) -> Rgb888 {
244        Rgb888::new(self.r, self.g, self.b)
245    }
246}
247
248impl ToRgb888 for Rgb888 {
249    #[inline(always)]
250    fn to_rgb888(self) -> Rgb888 {
251        self
252    }
253}
254
255#[cfg(not(feature = "host"))]
256use core::borrow::Borrow;
257use core::ops::{Deref, DerefMut};
258use embedded_graphics::prelude::RgbColor;
259
260// ============================================================================
261// Gamma Correction
262// ============================================================================
263
264/// Gamma correction configuration for LED strips.
265///
266/// See the [`led_strip!`](macro@crate::led_strip), [`led_strips!`](crate::led_strips),
267/// and [`led2d!`](mod@crate::led2d) macro docs for usage and context.
268///
269/// For background on gamma correction, see the
270/// [Wikipedia article on gamma correction](https://en.wikipedia.org/wiki/Gamma_correction).
271///
272/// This is not display-calibrated color management; it is a simple LED brightness curve.
273#[derive(Clone, Copy, Debug, Eq, PartialEq)]
274pub enum Gamma {
275    /// No correction; raw LED PWM values.
276    Linear,
277    /// Perceptual sRGB semantics (gamma 2.2).
278    ///
279    /// This preserves the intent of the named color constants.
280    Srgb,
281    /// Compatibility with the historical `smart_leds::gamma()` curve (2.8).
282    SmartLeds,
283}
284
285impl Default for Gamma {
286    fn default() -> Self {
287        Self::Srgb
288    }
289}
290
291// Public so led_strip!/led_strips! expansions in downstream crates can reference it.
292#[doc(hidden)]
293/// Default gamma correction curve for generated LED devices (`Gamma::Srgb`).
294pub const GAMMA_DEFAULT: Gamma = Gamma::Srgb;
295
296/// Gamma 2.2 lookup table for 8-bit values.
297/// Pre-computed to avoid floating point math: corrected = (value/255)^2.2 * 255
298pub(crate) const GAMMA_SRGB_TABLE: [u8; 256] = [
299    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
300    3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11,
301    11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23,
302    23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39,
303    40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
304    62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 81, 82, 83, 84, 85, 87, 88,
305    89, 90, 91, 93, 94, 95, 97, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 111, 113, 114, 116,
306    117, 119, 120, 121, 123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 141, 143, 145,
307    146, 148, 149, 151, 153, 154, 156, 158, 159, 161, 163, 165, 166, 168, 170, 172, 173, 175, 177,
308    179, 181, 182, 184, 186, 188, 190, 192, 194, 196, 197, 199, 201, 203, 205, 207, 209, 211, 213,
309    215, 217, 219, 221, 223, 225, 227, 229, 231, 234, 236, 238, 240, 242, 244, 246, 248, 251, 253,
310    255,
311];
312
313/// Gamma 2.8 lookup table for 8-bit values.
314/// Matches `smart_leds::gamma()` behavior.
315pub(crate) const GAMMA_SMARTLEDS_TABLE: [u8; 256] = [
316    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
317    1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
318    5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14,
319    14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27,
320    27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46,
321    47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72,
322    73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104,
323    105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137,
324    138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
325    177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220,
326    223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255,
327];
328
329/// Linear lookup table (identity function).
330const LINEAR_TABLE: [u8; 256] = [
331    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
332    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
333    50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
334    74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97,
335    98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
336    117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135,
337    136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
338    155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173,
339    174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192,
340    193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
341    212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230,
342    231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249,
343    250, 251, 252, 253, 254, 255,
344];
345
346/// Generate a combined gamma correction and brightness scaling lookup table.
347///
348/// This combines two operations into a single table lookup for efficiency:
349/// 1. Apply gamma correction based on the `gamma` parameter
350/// 2. Scale by `max_brightness` for electrical current limiting
351///
352/// The result is a table where `combo_table[input_value]` gives the final output value.
353#[doc(hidden)] // Implementation detail used by macro-generated strip types
354#[must_use]
355pub const fn generate_combo_table(gamma: Gamma, max_brightness: u8) -> [u8; 256] {
356    let gamma_table = match gamma {
357        Gamma::Linear => &LINEAR_TABLE,
358        Gamma::Srgb => &GAMMA_SRGB_TABLE,
359        Gamma::SmartLeds => &GAMMA_SMARTLEDS_TABLE,
360    };
361    let mut result = [0u8; 256];
362    let mut index = 0;
363    while index < 256 {
364        let gamma_corrected = gamma_table[index];
365        // Apply brightness scaling: (value * brightness) / 255
366        let scaled = ((gamma_corrected as u16 * max_brightness as u16) / 255) as u8;
367        result[index] = scaled;
368        index += 1;
369    }
370    result
371}
372
373#[cfg(not(feature = "host"))]
374use core::cell::RefCell;
375#[cfg(not(feature = "host"))]
376use embassy_futures::select::{Either, select};
377#[cfg(not(feature = "host"))]
378use embassy_rp::pio::{Common, Instance};
379#[cfg(not(feature = "host"))]
380use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
381#[cfg(not(feature = "host"))]
382use embassy_sync::blocking_mutex::Mutex;
383#[cfg(not(feature = "host"))]
384use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
385#[cfg(not(feature = "host"))]
386#[cfg(not(feature = "host"))]
387use embassy_sync::once_lock::OnceLock;
388#[cfg(not(feature = "host"))]
389use embassy_sync::signal::Signal;
390#[cfg(not(feature = "host"))]
391use embassy_time::{Duration, Timer};
392#[cfg(not(feature = "host"))]
393use heapless::Vec;
394
395#[cfg(not(feature = "host"))]
396use crate::Result;
397
398// ============================================================================
399// Submodules
400// ============================================================================
401
402pub mod led_strip_generated;
403
404/// 1D pixel array used to describe LED strip patterns.
405///
406/// See the [led_strip module documentation](mod@crate::led_strip) for usage examples.
407///
408/// Frames deref to `[RGB8; N]`, so you can mutate pixels directly before passing them to the generated strip's `write_frame` method.
409#[derive(Clone, Copy, Debug)]
410pub struct Frame1d<const N: usize>(pub [RGB8; N]);
411
412impl<const N: usize> Frame1d<N> {
413    /// Number of LEDs in this frame.
414    pub const LEN: usize = N;
415
416    /// Create a new blank (all black) frame.
417    ///
418    /// See the [led_strip module documentation](mod@crate::led_strip) for usage examples.
419    #[must_use]
420    pub const fn new() -> Self {
421        Self([RGB8::new(0, 0, 0); N])
422    }
423
424    /// Create a frame filled with a single color.
425    ///
426    /// See the [led_strip module documentation](mod@crate::led_strip) for usage examples.
427    #[must_use]
428    pub const fn filled(color: RGB8) -> Self {
429        Self([color; N])
430    }
431}
432
433impl<const N: usize> Deref for Frame1d<N> {
434    type Target = [RGB8; N];
435
436    fn deref(&self) -> &Self::Target {
437        &self.0
438    }
439}
440
441impl<const N: usize> DerefMut for Frame1d<N> {
442    fn deref_mut(&mut self) -> &mut Self::Target {
443        &mut self.0
444    }
445}
446
447impl<const N: usize> From<[RGB8; N]> for Frame1d<N> {
448    fn from(array: [RGB8; N]) -> Self {
449        Self(array)
450    }
451}
452
453impl<const N: usize> From<Frame1d<N>> for [RGB8; N] {
454    fn from(frame: Frame1d<N>) -> Self {
455        frame.0
456    }
457}
458
459impl<const N: usize> Default for Frame1d<N> {
460    fn default() -> Self {
461        Self::new()
462    }
463}
464
465// ============================================================================
466// PIO Bus - Shared PIO resource for multiple LED strips
467// ============================================================================
468
469/// Trait for PIO peripherals that can be used with LED strips.
470///
471/// This trait is implemented for any PIO resource that implements
472/// [`crate::pio_irqs::PioIrqMap`].
473#[cfg(not(feature = "host"))]
474#[doc(hidden)] // Required pub for macro expansion in downstream crates
475pub trait LedStripPio: crate::pio_irqs::PioIrqMap {}
476
477#[cfg(not(feature = "host"))]
478impl<PioResource: crate::pio_irqs::PioIrqMap> LedStripPio for PioResource {}
479/// A state machine bundled with its PIO bus.
480///
481/// This is returned by `pio_split!` and passed to strip constructors.
482#[cfg(not(feature = "host"))]
483#[doc(hidden)] // Support type for macro-generated strip types; not intended as surface API
484pub struct PioBusStateMachine<PIO: Instance + 'static, const SM: usize> {
485    pio_bus: &'static PioBus<'static, PIO>,
486    state_machine: embassy_rp::pio::StateMachine<'static, PIO, SM>,
487}
488
489#[cfg(not(feature = "host"))]
490impl<PIO: Instance + 'static, const SM: usize> PioBusStateMachine<PIO, SM> {
491    #[doc(hidden)]
492    pub fn new(
493        pio_bus: &'static PioBus<'static, PIO>,
494        state_machine: embassy_rp::pio::StateMachine<'static, PIO, SM>,
495    ) -> Self {
496        Self {
497            pio_bus,
498            state_machine,
499        }
500    }
501
502    #[doc(hidden)]
503    pub fn pio_bus(&self) -> &'static PioBus<'static, PIO> {
504        self.pio_bus
505    }
506
507    #[doc(hidden)]
508    pub fn into_parts(
509        self,
510    ) -> (
511        &'static PioBus<'static, PIO>,
512        embassy_rp::pio::StateMachine<'static, PIO, SM>,
513    ) {
514        (self.pio_bus, self.state_machine)
515    }
516}
517
518/// Shared PIO bus that manages the Common resource and WS2812 program.
519#[cfg(not(feature = "host"))]
520#[doc(hidden)] // Support type for macro-generated strip types; not intended as surface API
521pub struct PioBus<'d, PIO: Instance> {
522    common: Mutex<CriticalSectionRawMutex, RefCell<Common<'d, PIO>>>,
523    ws2812_program: OnceLock<PioWs2812Program<'d, PIO>>,
524}
525
526#[cfg(not(feature = "host"))]
527impl<'d, PIO: Instance> PioBus<'d, PIO> {
528    /// Create a new PIO bus with the given Common resource
529    pub fn new(common: Common<'d, PIO>) -> Self {
530        Self {
531            common: Mutex::new(RefCell::new(common)),
532            ws2812_program: OnceLock::new(),
533        }
534    }
535
536    /// Get or initialize the WS2812 program (only loaded once)
537    pub fn get_program(&'static self) -> &'static PioWs2812Program<'d, PIO> {
538        self.ws2812_program.get_or_init(|| {
539            self.common.lock(|common_cell: &RefCell<Common<'d, PIO>>| {
540                let mut common = common_cell.borrow_mut();
541                PioWs2812Program::new(&mut *common)
542            })
543        })
544    }
545
546    /// Access the common resource for initializing a driver
547    pub fn with_common<F, R>(&self, f: F) -> R
548    where
549        F: FnOnce(&mut Common<'d, PIO>) -> R,
550    {
551        self.common.lock(|common_cell: &RefCell<Common<'d, PIO>>| {
552            let mut common = common_cell.borrow_mut();
553            f(&mut *common)
554        })
555    }
556}
557
558// ============================================================================
559// LED Strip Command Channel and Static
560// ============================================================================
561
562#[cfg(not(feature = "host"))]
563#[cfg(not(feature = "host"))]
564#[doc(hidden)] // Required pub for macro expansion in downstream crates
565pub type LedStripCommandSignal<const N: usize, const MAX_FRAMES: usize> =
566    Signal<CriticalSectionRawMutex, Command<N, MAX_FRAMES>>;
567
568#[cfg(not(feature = "host"))]
569#[cfg(not(feature = "host"))]
570#[doc(hidden)]
571// Command for the LED strip animation loop.
572#[derive(Clone)]
573pub enum Command<const N: usize, const MAX_FRAMES: usize> {
574    DisplayStatic(Frame1d<N>),
575    Animate(Vec<(Frame1d<N>, Duration), MAX_FRAMES>),
576}
577
578/// Static used to construct LED strip instances with animation support.
579#[cfg(not(feature = "host"))]
580#[doc(hidden)] // Must be pub for method signatures and macro expansion in downstream crates
581pub struct LedStripStatic<const N: usize, const MAX_FRAMES: usize> {
582    command_signal: LedStripCommandSignal<N, MAX_FRAMES>,
583}
584
585#[cfg(not(feature = "host"))]
586impl<const N: usize, const MAX_FRAMES: usize> LedStripStatic<N, MAX_FRAMES> {
587    /// Creates static resources.
588    #[must_use]
589    #[doc(hidden)]
590    pub const fn new_static() -> Self {
591        Self {
592            command_signal: Signal::new(),
593        }
594    }
595
596    #[doc(hidden)]
597    pub fn command_signal(&'static self) -> &'static LedStripCommandSignal<N, MAX_FRAMES> {
598        &self.command_signal
599    }
600}
601
602// Public so macro-generated types can deref to it; hidden from docs.
603#[cfg(not(feature = "host"))]
604#[doc(hidden)]
605/// Internal deref target for generated LED strip types.
606///
607/// All LED strip methods are available through macro-generated types.
608/// See [`led_strip!`] macro documentation for usage.
609pub struct LedStrip<const N: usize, const MAX_FRAMES: usize> {
610    command_signal: &'static LedStripCommandSignal<N, MAX_FRAMES>,
611}
612
613#[cfg(not(feature = "host"))]
614impl<const N: usize, const MAX_FRAMES: usize> LedStrip<N, MAX_FRAMES> {
615    /// Creates LED strip resources.
616    #[must_use]
617    #[doc(hidden)]
618    pub const fn new_static() -> LedStripStatic<N, MAX_FRAMES> {
619        LedStripStatic::new_static()
620    }
621
622    /// Creates a new LED strip controller bound to the given static resources.
623    pub fn new(led_strip_static: &'static LedStripStatic<N, MAX_FRAMES>) -> Result<Self> {
624        Ok(Self {
625            command_signal: led_strip_static.command_signal(),
626        })
627    }
628
629    /// Writes a full frame to the LED strip. It remains displayed until another command
630    /// replaces it.
631    ///
632    /// See the [led_strip module documentation](mod@crate::led_strip) for example usage.
633    pub fn write_frame(&self, frame: Frame1d<N>) -> Result<()> {
634        self.command_signal.signal(Command::DisplayStatic(frame));
635        Ok(())
636    }
637
638    /// Loop forever through a sequence of animation frames.
639    /// They remain displayed until another command replaces them.
640    ///
641    /// Each frame is a tuple of `(Frame1d, Duration)`. Accepts arrays, `Vec`s, or any
642    /// iterator that produces `(Frame1d, Duration)` tuples.
643    ///
644    /// Returns immediately; the animation runs in the background until interrupted
645    /// by a new `animate` call or `write_frame`.
646    ///
647    /// See the [led_strip module documentation](mod@crate::led_strip) for example usage.
648    pub fn animate<I>(&self, frames: I) -> Result<()>
649    where
650        I: IntoIterator,
651        I::Item: Borrow<(Frame1d<N>, Duration)>,
652    {
653        if MAX_FRAMES == 0 {
654            return Err(crate::Error::AnimationDisabled(MAX_FRAMES));
655        }
656        let mut sequence: Vec<(Frame1d<N>, Duration), MAX_FRAMES> = Vec::new();
657        for frame in frames {
658            let (frame, duration) = *frame.borrow();
659            assert!(
660                duration.as_micros() > 0,
661                "animation frame duration must be positive"
662            );
663            sequence
664                .push((frame, duration))
665                .expect("animation sequence fits within MAX_FRAMES");
666        }
667        self.animate_frames(sequence)
668    }
669
670    pub(crate) fn animate_frames(
671        &self,
672        sequence: Vec<(Frame1d<N>, Duration), MAX_FRAMES>,
673    ) -> Result<()> {
674        if MAX_FRAMES == 0 {
675            return Err(crate::Error::AnimationDisabled(MAX_FRAMES));
676        }
677        assert!(
678            !sequence.is_empty(),
679            "animation requires at least one frame"
680        );
681        self.command_signal.signal(Command::Animate(sequence));
682        Ok(())
683    }
684}
685
686#[cfg(not(feature = "host"))]
687#[doc(hidden)] // Required pub for macro expansion in downstream crates
688pub async fn led_strip_device_loop<
689    PIO,
690    const SM: usize,
691    const N: usize,
692    const MAX_FRAMES: usize,
693    ORDER,
694>(
695    mut driver: PioWs2812<'static, PIO, SM, N, ORDER>,
696    command_signal: &'static LedStripCommandSignal<N, MAX_FRAMES>,
697    combo_table: &'static [u8; 256],
698) -> !
699where
700    PIO: Instance,
701    ORDER: embassy_rp::pio_programs::ws2812::RgbColorOrder,
702{
703    loop {
704        let mut command = command_signal.wait().await;
705        command_signal.reset();
706
707        loop {
708            match command {
709                Command::DisplayStatic(mut frame) => {
710                    apply_correction(&mut frame, combo_table);
711                    driver.write(&frame).await;
712                    break;
713                }
714                Command::Animate(frames) => {
715                    command =
716                        run_frame_animation(&mut driver, frames, command_signal, combo_table).await;
717                }
718            }
719        }
720    }
721}
722
723#[cfg(not(feature = "host"))]
724async fn run_frame_animation<PIO, const SM: usize, const N: usize, const MAX_FRAMES: usize, ORDER>(
725    driver: &mut PioWs2812<'static, PIO, SM, N, ORDER>,
726    mut frames: Vec<(Frame1d<N>, Duration), MAX_FRAMES>,
727    command_signal: &'static LedStripCommandSignal<N, MAX_FRAMES>,
728    combo_table: &'static [u8; 256],
729) -> Command<N, MAX_FRAMES>
730where
731    PIO: Instance,
732    ORDER: embassy_rp::pio_programs::ws2812::RgbColorOrder,
733{
734    frames
735        .iter_mut()
736        .for_each(|(frame, _)| apply_correction(frame, combo_table));
737
738    loop {
739        for (frame, duration) in &frames {
740            driver.write(frame).await;
741
742            match select(command_signal.wait(), Timer::after(*duration)).await {
743                Either::First(new_command) => {
744                    command_signal.reset();
745                    return new_command;
746                }
747                Either::Second(()) => continue,
748            }
749        }
750    }
751}
752
753#[cfg(not(feature = "host"))]
754fn apply_correction<const N: usize>(frame: &mut Frame1d<N>, combo_table: &[u8; 256]) {
755    frame.iter_mut().for_each(|pixel| {
756        pixel.r = combo_table[pixel.r as usize];
757        pixel.g = combo_table[pixel.g as usize];
758        pixel.b = combo_table[pixel.b as usize];
759    });
760}
761/// Macro to generate multiple LED strip and panel struct types that share a single
762/// [PIO resource](crate#glossary) (includes syntax details).
763///
764/// This page provides the primary documentation and examples for configuring strip/panel
765/// groups that share a PIO resource.
766///
767/// **Syntax:**
768///
769/// ```text
770/// led_strips! {
771///     pio: <pio_ident>, // optional
772///     [<visibility>] <GroupName> {
773///         <MemberName>: {
774///             pin: <pin_ident>,
775///             len: <usize_expr>,
776///             max_current: <Current_expr>,
777///             gamma: <Gamma_expr>,          // optional
778///             max_frames: <usize_expr>,     // optional
779///             dma: <dma_ident>,             // optional
780///             led2d: {                      // optional (panel mode)
781///                 led_layout: <LedLayout_expr>,
782///                 font: <Led2dFont_expr>,
783///             }
784///         },
785///         // ...more members...
786///     }
787/// }
788/// ```
789///
790/// **After reading the examples below, see also:**
791///
792/// - [`LedStripGenerated`](led_strip_generated::LedStripGenerated) — Sample LED **strip** type showing all methods and associated constants
793/// - [`Led2dGenerated`](crate::led2d::led2d_generated::Led2dGenerated) — Sample LED **panel** type showing all methods and associated constants
794/// - [`led_strip!`](macro@crate::led_strip) — Alternative macro to generate a single LED strip type. Consumes a PIO resource.
795/// - [`led2d!`](mod@crate::led2d) — Alternative macro to generate a single LED panel type. Consumes a PIO resource.
796///
797/// Use this macro when your project has multiple LED strips or panels
798/// that should share a single PIO resource.
799/// If you only need a single strip or panel, prefer [`led_strip!`](macro@crate::led_strip)
800/// or [`led2d!`](macro@crate::led2d) for simpler configuration.
801///
802/// We’ll start with a complete example below, then describe the required and
803/// optional fields in detail.
804///
805/// # Example: Connect Three LED Strips/Panels to One PIO Resource
806///
807/// This example creates three LED strips/panels on GPIO0, GPIO3, and GPIO4,
808/// all sharing PIO0. It demonstrates showing a pattern on the first two strips
809/// and animating text on the 2D panel.
810///
811/// ![GPIO0 strip preview][led_strip_gpio0]
812///
813/// ![GPIO3 strip preview][led_strip_simple]
814///
815/// ![GPIO4 panel preview][led_strip_gogo]
816///
817/// ```rust,no_run
818/// # #![no_std]
819/// # #![no_main]
820/// # use panic_probe as _;
821/// # use core::convert::Infallible;
822/// # use core::future;
823/// # use defmt_rtt as _;
824/// # use embassy_executor::Spawner;
825/// # use defmt::info;
826/// use device_envoy::{Result, led2d::Frame2d, led2d::Led2dFont, led2d::layout::LedLayout, led_strip::{Current, Frame1d, Gamma, colors, led_strips}};
827/// use embassy_time::Duration;
828///
829/// // Our 2D panel is two 12x4 panels stacked vertically.
830/// const LED_LAYOUT_12X4: LedLayout<48, 12, 4> = LedLayout::serpentine_column_major();
831/// const LED_LAYOUT_12X8: LedLayout<96, 12, 8> = LED_LAYOUT_12X4.combine_v(LED_LAYOUT_12X4);
832/// const LED_LAYOUT_12X8_ROTATED: LedLayout<96, 8, 12> = LED_LAYOUT_12X8.rotate_cw();
833///
834/// led_strips! {
835///     pio: PIO0,                          // Optional; defaults to PIO0.
836///     LedStrips0 {                        // Name for this group of LED strips/panels. Can provide visibility modifier
837///         // 1. a 8-LED strip on GPIO0
838///         Gpio0LedStrip: {                // Exact struct name for this strip.
839///             pin: PIN_0,                 // GPIO pin for LED data signal.
840///             len: 8,                     // 8 LEDs on this strip.
841///             max_current: Current::Milliamps(25), // Every strip/panel requires an electrical current budget.
842///         },
843///         // 2. a 48-LED strip on GPIO3
844///         Gpio3LedStrip: {
845///             pin: PIN_3,
846///             len: 48,
847///             max_current: Current::Milliamps(75),
848///             gamma: Gamma::Srgb,        // Optional; color correction (default, Gamma::Srgb).
849///             max_frames: 1,              // Optional; default 16 frames.
850///             dma: DMA_CH11,              // Optional; auto-assigned by strip order.
851///         },
852///         // 3. a 96-LED 2D panel on GPIO4
853///         Gpio4Led2d: {
854///             pin: PIN_4,
855///             len: 96,
856///             max_current: Current::Milliamps(250),
857///             max_frames: 2,
858///             led2d: {                    // Optional panel configuration for 2D displays.
859///                 led_layout: LED_LAYOUT_12X8_ROTATED, // Two 12×4 panels stacked and rotated.
860///                 font: Led2dFont::Font4x6Trim, // 4x6 pixel font without the usual 1 pixel spacing.
861///             }
862///         },
863///     }
864/// }
865///
866/// # #[embassy_executor::main]
867/// # async fn main(spawner: Spawner) -> ! {
868/// #     let _ = example(spawner).await;
869/// #     core::panic!("done");
870/// # }
871/// async fn example(spawner: Spawner) -> Result<Infallible> {
872///     let p = embassy_rp::init(Default::default());
873///
874///     // Create instances of two LED strips and one panel.
875///     let (gpio0_led_strip, gpio3_led_strip, gpio4_led2d) = LedStrips0::new(
876///         p.PIO0, p.PIN_0, p.DMA_CH0, p.PIN_3, p.DMA_CH11, p.PIN_4, p.DMA_CH2, spawner,
877///     )?;
878///
879///     info!("Setting GPIO0 to white, GPIO3 to alternating blue/gray, GPIO4 to Go Go animation");
880///
881///     // Turn on all-white on GPIO0 strip.
882///     let frame_gpio0 = Frame1d::filled(colors::WHITE);
883///     gpio0_led_strip.write_frame(frame_gpio0)?; // Display the frame (until replaced)
884///
885///     // Alternate blue/gray on GPIO3 strip.
886///     let mut frame_gpio3 = Frame1d::new();
887///     for pixel_index in 0..Gpio3LedStrip::LEN {
888///         frame_gpio3[pixel_index] = [colors::BLUE, colors::GRAY][pixel_index % 2];
889///     }
890///     gpio3_led_strip.write_frame(frame_gpio3)?;  // Display the frame (until replaced)
891///
892///     // Animate "Go Go" text on GPIO4 2D panel.
893///     let mut frame_go_top = Frame2d::new();
894///     gpio4_led2d.write_text_to_frame("Go", &[], &mut frame_go_top)?;
895///
896///     let mut frame_go_bottom = Frame2d::new();
897///     gpio4_led2d.write_text_to_frame(
898///         "\nGo",
899///         &[colors::HOT_PINK, colors::LIME],
900///         &mut frame_go_bottom,
901///     )?;
902///
903///     let frame_duration = Duration::from_secs(1);
904///     gpio4_led2d
905///         .animate([
906///             (frame_go_top, frame_duration),
907///             (frame_go_bottom, frame_duration),
908///         ])?; // Loop animation (until replaced)
909///
910///     future::pending::<Result<Infallible>>().await // Run forever
911/// }
912/// ```
913///
914/// # Configuration
915///
916/// ## Shared PIO Resource
917///
918/// - `pio` — PIO peripheral to use (default: `PIO0`). This consumes one PIO resource for the
919///   group and is shared across all strips/panels in the macro invocation.
920///
921/// ## Required Fields per Strip/Panel
922///
923/// - `pin` — GPIO pin for LED data
924/// - `len` — Number of LEDs (pixels)
925/// - `max_current` — Electrical current budget per strip (required; no default)
926///
927/// ## Optional Fields per Strip/Panel
928///
929/// - `dma` — DMA channel (default: auto-assigned by strip order)
930/// - `gamma` — Gamma correction curve (default: `Gamma::Srgb`)
931/// - `max_frames` — Maximum number of animation frames (default: 16 frames)
932///
933/// `max_frames = 0` disables animation and allocates no frame storage; `write_frame()` is still supported.
934/// - `led2d` — Marks this strip as a 2D LED panel and enables 2D rendering support (optional, see below).
935///    Detailed 2D rendering, examples, and animation support are documented
936///    in the [`led2d` module](mod@crate::led2d).
937///
938/// ## 2D Panel Configuration (`led2d`)
939///
940/// If a strip represents a rectangular LED panel rather than a linear strip,
941/// add a `led2d` configuration block to describe its geometry.
942///
943/// Required fields:
944/// - `led_layout` — Physical layout mapping (defines the panel size). See [`LedLayout`](crate::led2d::layout::LedLayout) for details.
945/// - `font` — Built-in font for text rendering. See [`Led2dFont`](crate::led2d::Led2dFont) for available fonts.
946///   Bring `Led2dFont` into scope or use a full path like `device_envoy::led2d::Led2dFont::Font4x6Trim`.
947///
948/// The `led_layout` value must be a const so its dimensions can be derived at compile time.
949///
950/// # Capacity and Board Capabilities
951///
952/// The `led_strips!` macro is designed to **fully utilize the PIO resources**
953/// of supported Pico boards.
954///
955/// Each `led_strips!` invocation can drive up to **4 LED strips or panels**
956/// while sharing a single PIO resource. This lets you consolidate multiple
957/// LED outputs efficiently instead of consuming one PIO per strip.
958///
959/// Each invocation consumes exactly one PIO resource.
960///
961/// On supported boards, this enables the maximum practical LED capacity:
962///
963/// - **Pico 1** provides **2 PIO resources**, allowing up to **8 LED strips or panels**
964/// - **Pico 2** provides **3 PIO resources**, allowing up to **12 LED strips or panels**
965///
966#[doc = include_str!("docs/current_limiting_and_gamma.md")]
967///
968/// # Related Macros
969///
970/// - [`led_strip!`](macro@crate::led_strip) — For a single 1-dimensional LED strip (includes examples)
971/// - [`led2d!`](mod@crate::led2d) — For 2-dimensional LED panels
972#[cfg_attr(
973    feature = "doc-images",
974    doc = ::embed_doc_image::embed_image!(
975        "led_strip_gpio0",
976        "docs/assets/led_strip_gpio0.png"
977    )
978)]
979#[cfg_attr(
980    feature = "doc-images",
981    doc = ::embed_doc_image::embed_image!(
982        "led_strip_simple",
983        "docs/assets/led_strip_simple.png"
984    )
985)]
986#[cfg_attr(
987    feature = "doc-images",
988    doc = ::embed_doc_image::embed_image!(
989        "led_strip_gogo",
990        "docs/assets/led2d2.png"
991    )
992)]
993#[cfg(not(feature = "host"))]
994#[doc(hidden)]
995#[macro_export]
996macro_rules! led_strips {
997    ($($tt:tt)*) => { $crate::__led_strips_impl! { $($tt)* } };
998}
999
1000/// Implementation macro. Not part of the public API; use [`led_strips!`] instead.
1001#[cfg(not(feature = "host"))]
1002#[doc(hidden)]
1003#[macro_export]
1004macro_rules! __led_strips_impl {
1005    // Internal: full expansion with all fields specified
1006    (@__expand
1007        frame_alias: $frame_alias:tt,
1008        pio: $pio:ident,
1009        vis: $vis:vis,
1010        group: $group:ident,
1011        strips: [
1012            $(
1013                $label:ident {
1014                    sm: $sm_index:expr,
1015                    dma: $dma:ident,
1016                    pin: $pin:ident,
1017                    len: $len:expr,
1018                    max_current: $max_current:expr,
1019                    gamma: $gamma:expr,
1020                    max_frames: $max_frames:expr
1021                    $(,
1022                        led2d: {
1023                            led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1024                            $(max_frames: $led2d_max_frames:expr,)?
1025                            font: $led2d_font:expr $(,)?
1026                        }
1027                    )?
1028                }
1029            ),+ $(,)?
1030        ]
1031    ) => {
1032        // Use crate-level PIO interrupt bindings (Pio0Irqs, Pio1Irqs, Pio2Irqs)
1033        paste::paste! {
1034            // Create the PIO bus
1035            #[allow(non_upper_case_globals)]
1036            static [<$pio _BUS>]: ::static_cell::StaticCell<
1037                $crate::led_strip::PioBus<'static, ::embassy_rp::peripherals::$pio>
1038            > = ::static_cell::StaticCell::new();
1039
1040            /// Split the PIO into bus and state machines.
1041            ///
1042            /// Returns 4 StateMachines (one for each SM)
1043            #[allow(dead_code)]
1044            pub fn [<$pio:lower _split>](
1045                pio: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pio>,
1046            ) -> (
1047                $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, 0>,
1048                $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, 1>,
1049                $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, 2>,
1050                $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, 3>,
1051            ) {
1052                let ::embassy_rp::pio::Pio { common, sm0, sm1, sm2, sm3, .. } =
1053                    ::embassy_rp::pio::Pio::new(
1054                        pio,
1055                        <::embassy_rp::peripherals::$pio as $crate::pio_irqs::PioIrqMap>::irqs(),
1056                    );
1057                let pio_bus = [<$pio _BUS>].init_with(|| {
1058                    $crate::led_strip::PioBus::new(common)
1059                });
1060                (
1061                    $crate::led_strip::PioBusStateMachine::new(pio_bus, sm0),
1062                    $crate::led_strip::PioBusStateMachine::new(pio_bus, sm1),
1063                    $crate::led_strip::PioBusStateMachine::new(pio_bus, sm2),
1064                    $crate::led_strip::PioBusStateMachine::new(pio_bus, sm3),
1065                )
1066                }
1067
1068            // Create strip types
1069        $(
1070            $crate::__led_strips_impl!(
1071                @__define_strip
1072                vis: $vis,
1073                group: $group,
1074                pio: $pio,
1075                label: $label,
1076                sm: $sm_index,
1077                dma: $dma,
1078                pin: $pin,
1079                len: $len,
1080                max_current: $max_current,
1081                gamma: $gamma,
1082                max_frames: $max_frames
1083                $(,
1084                    led2d: {
1085                        led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1086                        $(max_frames: $led2d_max_frames,)?
1087                        font: $led2d_font,
1088                    }
1089                )?
1090            );
1091        )+
1092
1093            // Generate the group marker struct with new() constructor
1094            #[allow(missing_docs)]
1095            $vis struct $group;
1096
1097            #[allow(missing_docs)]
1098            impl $group {
1099                #[allow(clippy::too_many_arguments)]
1100                pub fn new(
1101                    pio: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pio>>,
1102                    $(
1103                        [<$label:snake _pin>]: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>>,
1104                        [<$label:snake _dma>]: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>>,
1105                    )+
1106                    spawner: ::embassy_executor::Spawner,
1107                ) -> $crate::Result<(
1108                    $(
1109                        $crate::__led_strips_impl!(
1110                            @__strip_return_type
1111                            $label
1112                            $(,
1113                                led2d: {
1114                                    led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1115                                    font: $led2d_font,
1116                                }
1117                            )?
1118                        ),
1119                    )+
1120                )> {
1121                    // Inline PIO splitting
1122                    let pio_peri = pio.into();
1123                    let ::embassy_rp::pio::Pio { common, sm0, sm1, sm2, sm3, .. } =
1124                        ::embassy_rp::pio::Pio::new(
1125                            pio_peri,
1126                            <::embassy_rp::peripherals::$pio as $crate::pio_irqs::PioIrqMap>::irqs(),
1127                        );
1128                    let pio_bus = [<$pio _BUS>].init_with(|| {
1129                        $crate::led_strip::PioBus::new(common)
1130                    });
1131
1132                    // Create individual state machine wrappers
1133                    #[allow(unused_variables)]
1134                    let sm0_wrapped = $crate::led_strip::PioBusStateMachine::new(pio_bus, sm0);
1135                    #[allow(unused_variables)]
1136                    let sm1_wrapped = $crate::led_strip::PioBusStateMachine::new(pio_bus, sm1);
1137                    #[allow(unused_variables)]
1138                    let sm2_wrapped = $crate::led_strip::PioBusStateMachine::new(pio_bus, sm2);
1139                    #[allow(unused_variables)]
1140                    let sm3_wrapped = $crate::led_strip::PioBusStateMachine::new(pio_bus, sm3);
1141
1142                    // Construct each strip with the appropriate SM
1143                    Ok((
1144                        $(
1145                            $crate::__led_strips_impl!(
1146                                @__strip_return_value
1147                                label: $label,
1148                                state_machine: $crate::__led_strips_impl!(
1149                                    @__select_sm
1150                                    $sm_index,
1151                                    sm0_wrapped,
1152                                    sm1_wrapped,
1153                                    sm2_wrapped,
1154                                    sm3_wrapped
1155                                ),
1156                                pin: [<$label:snake _pin>],
1157                                dma: [<$label:snake _dma>],
1158                                spawner: spawner
1159                                $(,
1160                                    led2d: {
1161                                        led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1162                                        font: $led2d_font,
1163                                    }
1164                                )?
1165                            ),
1166                        )+
1167                    ))
1168                }
1169            }
1170        }
1171    };
1172
1173    // Helper to select the right SM based on index
1174    (@__select_sm 0, $sm0:ident, $sm1:ident, $sm2:ident, $sm3:ident) => { $sm0 };
1175    (@__select_sm 1, $sm0:ident, $sm1:ident, $sm2:ident, $sm3:ident) => { $sm1 };
1176    (@__select_sm 2, $sm0:ident, $sm1:ident, $sm2:ident, $sm3:ident) => { $sm2 };
1177    (@__select_sm 3, $sm0:ident, $sm1:ident, $sm2:ident, $sm3:ident) => { $sm3 };
1178
1179    (@__define_strip
1180        vis: $vis:vis,
1181        group: $group:ident,
1182        pio: $pio:ident,
1183        label: $label:ident,
1184        sm: $sm_index:expr,
1185        dma: $dma:ident,
1186        pin: $pin:ident,
1187        len: $len:expr,
1188        max_current: $max_current:expr,
1189        gamma: $gamma:expr,
1190        max_frames: $max_frames:expr
1191    ) => {
1192        paste::paste! {
1193            #[doc = concat!(
1194                "LED strip wrapper generated by [`led_strips!`].\n\n",
1195                "Derefs to provide all LED control methods. ",
1196                "Created with [`", stringify!($group), "::new`]. ",
1197                "See the [led_strip module documentation](mod@crate::led_strip) for a similar example."
1198            )]
1199            $vis struct $label {
1200                strip: $crate::led_strip::LedStrip<{ $len }, { $max_frames }>,
1201            }
1202
1203            #[allow(missing_docs)]
1204            impl $label {
1205                pub const LEN: usize = $len;
1206                pub const MAX_FRAMES: usize = $max_frames;
1207
1208                // Calculate max brightness from current budget
1209                // Each WS2812B LED draws ~60mA at full brightness
1210                const WORST_CASE_MA: u32 = ($len as u32) * 60;
1211                pub const MAX_BRIGHTNESS: u8 =
1212                    $max_current.max_brightness(Self::WORST_CASE_MA);
1213
1214                // Combined gamma correction and brightness scaling table
1215                const COMBO_TABLE: [u8; 256] = $crate::led_strip::generate_combo_table($gamma, Self::MAX_BRIGHTNESS);
1216
1217                pub(crate) const fn new_static() -> $crate::led_strip::LedStripStatic<{ $len }, { $max_frames }> {
1218                    $crate::led_strip::LedStrip::new_static()
1219                }
1220
1221                pub fn new(
1222                    state_machine: $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, $sm_index>,
1223                    pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>>,
1224                    dma: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>>,
1225                    spawner: ::embassy_executor::Spawner,
1226                ) -> $crate::Result<&'static Self> {
1227                    static STRIP_STATIC: $crate::led_strip::LedStripStatic<{ $len }, { $max_frames }> =
1228                        $label::new_static();
1229                    static STRIP_CELL: ::static_cell::StaticCell<$label> = ::static_cell::StaticCell::new();
1230                    let pin = pin.into();
1231                    let dma = dma.into();
1232
1233                    let (bus, sm) = state_machine.into_parts();
1234                    let token = [<$label:snake _device_task>](
1235                        bus,
1236                        sm,
1237                        dma,
1238                        pin,
1239                        STRIP_STATIC.command_signal(),
1240                    );
1241                    spawner.spawn(token).map_err($crate::Error::TaskSpawn)?;
1242                    let strip = $crate::led_strip::LedStrip::new(&STRIP_STATIC)?;
1243                    let instance = STRIP_CELL.init(Self { strip });
1244                    Ok(instance)
1245                }
1246            }
1247
1248            impl ::core::ops::Deref for $label {
1249                type Target = $crate::led_strip::LedStrip<{ $len }, { $max_frames }>;
1250
1251                fn deref(&self) -> &Self::Target {
1252                    &self.strip
1253                }
1254            }
1255
1256            #[cfg(not(feature = "host"))]
1257            impl AsRef<$crate::led_strip::LedStrip<{ $len }, { $max_frames }>> for $label {
1258                fn as_ref(&self) -> &$crate::led_strip::LedStrip<{ $len }, { $max_frames }> {
1259                    &self.strip
1260                }
1261            }
1262
1263            #[::embassy_executor::task]
1264            async fn [<$label:snake _device_task>](
1265                bus: &'static $crate::led_strip::PioBus<'static, ::embassy_rp::peripherals::$pio>,
1266                sm: ::embassy_rp::pio::StateMachine<'static, ::embassy_rp::peripherals::$pio, $sm_index>,
1267                dma: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>,
1268                pin: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>,
1269                command_signal: &'static $crate::led_strip::LedStripCommandSignal<{ $len }, { $max_frames }>,
1270            ) -> ! {
1271                let program = bus.get_program();
1272                let driver = bus.with_common(|common| {
1273                    ::embassy_rp::pio_programs::ws2812::PioWs2812::<
1274                        ::embassy_rp::peripherals::$pio,
1275                        $sm_index,
1276                        { $len },
1277                        _
1278                    >::new(common, sm, dma, pin, program)
1279                });
1280                $crate::led_strip::led_strip_device_loop::<
1281                    ::embassy_rp::peripherals::$pio,
1282                    $sm_index,
1283                    { $len },
1284                    { $max_frames },
1285                    _
1286                >(driver, command_signal, &$label::COMBO_TABLE).await
1287            }
1288        }
1289    };
1290
1291    (@__define_strip
1292        vis: $vis:vis,
1293        group: $group:ident,
1294        pio: $pio:ident,
1295        label: $label:ident,
1296        sm: $sm_index:expr,
1297        dma: $dma:ident,
1298        pin: $pin:ident,
1299        len: $len:expr,
1300        max_current: $max_current:expr,
1301        gamma: $gamma:expr,
1302        max_frames: $max_frames:expr,
1303        led2d: {
1304            led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1305            $(max_frames: $led2d_max_frames:expr,)?
1306            font: $led2d_font:expr $(,)?
1307        }
1308    ) => {
1309        paste::paste! {
1310            #[doc = concat!(
1311                "LED strip wrapper generated by [`led_strips!`].\n\n",
1312                "Derefs to provide all LED control methods. ",
1313                "Created with [`", stringify!($group), "::new`]. ",
1314                "See the [led_strip module documentation](mod@crate::led_strip) for a similar example."
1315            )]
1316            #[allow(missing_docs)]
1317            struct [<$label:camel LedStrip>] {
1318                strip: $crate::led_strip::LedStrip<{ $len }, { $max_frames }>,
1319            }
1320
1321            #[allow(missing_docs)]
1322            impl [<$label:camel LedStrip>] {
1323                pub const LEN: usize = $len;
1324                pub const MAX_FRAMES: usize = $max_frames;
1325
1326                // Calculate max brightness from current budget
1327                // Each WS2812B LED draws ~60mA at full brightness
1328                const WORST_CASE_MA: u32 = ($len as u32) * 60;
1329                pub const MAX_BRIGHTNESS: u8 =
1330                    $max_current.max_brightness(Self::WORST_CASE_MA);
1331
1332                // Combined gamma correction and brightness scaling table
1333                const COMBO_TABLE: [u8; 256] = $crate::led_strip::generate_combo_table($gamma, Self::MAX_BRIGHTNESS);
1334
1335                pub(crate) const fn new_static() -> $crate::led_strip::LedStripStatic<{ $len }, { $max_frames }> {
1336                    $crate::led_strip::LedStrip::new_static()
1337                }
1338
1339                pub fn new(
1340                    state_machine: $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, $sm_index>,
1341                    pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>>,
1342                    dma: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>>,
1343                    spawner: ::embassy_executor::Spawner,
1344                ) -> $crate::Result<&'static Self> {
1345                    static STRIP_STATIC: $crate::led_strip::LedStripStatic<{ $len }, { $max_frames }> =
1346                        [<$label:camel LedStrip>]::new_static();
1347                    static STRIP_CELL: ::static_cell::StaticCell<[<$label:camel LedStrip>]> = ::static_cell::StaticCell::new();
1348                    let pin = pin.into();
1349                    let dma = dma.into();
1350
1351                    let (bus, sm) = state_machine.into_parts();
1352                    let token = [<$label:snake _led_strip _device_task>](
1353                        bus,
1354                        sm,
1355                        dma,
1356                        pin,
1357                        STRIP_STATIC.command_signal(),
1358                    );
1359                    spawner.spawn(token).map_err($crate::Error::TaskSpawn)?;
1360                    let strip = $crate::led_strip::LedStrip::new(&STRIP_STATIC)?;
1361                    let instance = STRIP_CELL.init(Self { strip });
1362                    Ok(instance)
1363                }
1364            }
1365
1366            impl ::core::ops::Deref for [<$label:camel LedStrip>] {
1367                type Target = $crate::led_strip::LedStrip<{ $len }, { $max_frames }>;
1368
1369                fn deref(&self) -> &Self::Target {
1370                    &self.strip
1371                }
1372            }
1373
1374            #[cfg(not(feature = "host"))]
1375            impl AsRef<$crate::led_strip::LedStrip<{ $len }, { $max_frames }>> for [<$label:camel LedStrip>] {
1376                fn as_ref(&self) -> &$crate::led_strip::LedStrip<{ $len }, { $max_frames }> {
1377                    &self.strip
1378                }
1379            }
1380
1381            #[::embassy_executor::task]
1382            async fn [<$label:snake _led_strip _device_task>](
1383                bus: &'static $crate::led_strip::PioBus<'static, ::embassy_rp::peripherals::$pio>,
1384                sm: ::embassy_rp::pio::StateMachine<'static, ::embassy_rp::peripherals::$pio, $sm_index>,
1385                dma: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>,
1386                pin: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>,
1387                command_signal: &'static $crate::led_strip::LedStripCommandSignal<{ $len }, { $max_frames }>,
1388            ) -> ! {
1389                let program = bus.get_program();
1390                let driver = bus.with_common(|common| {
1391                    ::embassy_rp::pio_programs::ws2812::PioWs2812::<
1392                        ::embassy_rp::peripherals::$pio,
1393                        $sm_index,
1394                        { $len },
1395                        _
1396                    >::new(common, sm, dma, pin, program)
1397                });
1398                $crate::led_strip::led_strip_device_loop::<
1399                    ::embassy_rp::peripherals::$pio,
1400                    $sm_index,
1401                    { $len },
1402                    { $max_frames },
1403                    _
1404                >(driver, command_signal, &[<$label:camel LedStrip>]::COMBO_TABLE).await
1405            }
1406
1407            #[cfg(not(feature = "host"))]
1408            $crate::led2d::led2d_from_strip! {
1409                $vis $label,
1410                strip_type: [<$label:camel LedStrip>],
1411                width: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?.width(),
1412                height: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?.height(),
1413                led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1414                font: $led2d_font,
1415            }
1416
1417            #[cfg(not(feature = "host"))]
1418            impl [<$label:camel LedStrip>] {
1419                pub fn new_led2d(
1420                    state_machine: $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, $sm_index>,
1421                    pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>>,
1422                    dma: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>>,
1423                    spawner: ::embassy_executor::Spawner,
1424                ) -> $crate::Result<$label> {
1425                    let strip = Self::new(state_machine, pin, dma, spawner)?;
1426                    $label::from_strip(strip)
1427                }
1428            }
1429        }
1430    };
1431
1432    (@__strip_return_type $label:ident) => {
1433        &'static $label
1434    };
1435    (@__strip_return_type $label:ident, led2d: { $($led2d_fields:tt)* }) => {
1436        $label
1437    };
1438
1439
1440    (@__strip_return_value
1441        label: $label:ident,
1442        state_machine: $state_machine:expr,
1443        pin: $pin:ident,
1444        dma: $dma:ident,
1445        spawner: $spawner:ident
1446    ) => {
1447        $label::new($state_machine, $pin, $dma, $spawner)?
1448    };
1449    (@__strip_return_value
1450        label: $label:ident,
1451        state_machine: $state_machine:expr,
1452        pin: $pin:ident,
1453        dma: $dma:ident,
1454        spawner: $spawner:ident,
1455        led2d: {
1456            led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1457            $(max_frames: $led2d_max_frames:expr,)?
1458            font: $led2d_font:expr $(,)?
1459        }
1460    ) => {
1461        paste::paste! {{
1462            let [<$label:snake _led_strip>] =
1463                [<$label:camel LedStrip>]::new($state_machine, $pin, $dma, $spawner)?;
1464            $label::from_strip([<$label:snake _led_strip>])?
1465        }}
1466    };
1467
1468    // Entry point with explicit pio and group syntax (internal use by led2d)
1469    (@__with_frame_alias
1470        frame_alias: $frame_alias:tt,
1471        pio: $pio:ident,
1472        vis: $vis:vis,
1473        $group:ident {
1474            $( $label:ident: { $($fields:tt)* } ),+ $(,)?
1475        }
1476    ) => {
1477        $crate::__led_strips_impl! {
1478            @__with_defaults
1479            frame_alias: $frame_alias,
1480            pio: $pio,
1481            vis: $vis,
1482            group: $group,
1483            sm_counter: 0,
1484            strips_out: [],
1485            strips_in: [ $( $label: { $($fields)* } ),+ ]
1486        }
1487    };
1488
1489    // Entry point with visibility and pio
1490    (
1491        pio: $pio:ident,
1492        $vis:vis $group:ident {
1493            $( $label:ident: { $($fields:tt)* } ),+ $(,)?
1494        }
1495    ) => {
1496        $crate::__led_strips_impl! {
1497            @__with_defaults
1498            frame_alias: __WITH_FRAME_ALIAS__,
1499            pio: $pio,
1500            vis: $vis,
1501            group: $group,
1502            sm_counter: 0,
1503            strips_out: [],
1504            strips_in: [ $( $label: { $($fields)* } ),+ ]
1505        }
1506    };
1507
1508    // Entry point with pio, defaults to pub visibility - REMOVED (visibility required)
1509
1510    // Entry point without pio (defaults to PIO0) with group syntax - REMOVED (visibility required)
1511
1512    // Entry point with visibility, defaults to PIO0
1513    (
1514        $vis:vis $group:ident {
1515            $( $label:ident: { $($fields:tt)* } ),+ $(,)?
1516        }
1517    ) => {
1518        $crate::__led_strips_impl! {
1519            @__with_defaults
1520            frame_alias: __WITH_FRAME_ALIAS__,
1521            pio: PIO0,
1522            vis: $vis,
1523            group: $group,
1524            sm_counter: 0,
1525            strips_out: [],
1526            strips_in: [ $( $label: { $($fields)* } ),+ ]
1527        }
1528    };
1529
1530    // Entry point without visibility, defaults to pub and PIO0 - REMOVED (visibility required)
1531
1532    // Process strips one at a time, adding defaults
1533    (@__with_defaults
1534        frame_alias: $frame_alias:tt,
1535        pio: $pio:ident,
1536        vis: $vis:vis,
1537        group: $group:ident,
1538        sm_counter: $sm:tt,
1539        strips_out: [ $($out:tt)* ],
1540        strips_in: [ $label:ident: { $($fields:tt)* } $(, $($rest:tt)* )? ]
1541    ) => {
1542        $crate::__led_strips_impl! {
1543            @__fill_strip_defaults
1544            frame_alias: $frame_alias,
1545            pio: $pio,
1546            vis: $vis,
1547            sm_counter: $sm,
1548            strips_out: [ $($out)* ],
1549            strips_remaining: [ $($($rest)*)? ],
1550            label: $label,
1551            group: $group,
1552            pin: __MISSING_PIN__,
1553            dma: __DEFAULT_DMA__,
1554            len: __MISSING_LEN__,
1555            max_current: __MISSING_MAX_CURRENT__,
1556            gamma: $crate::led_strip::GAMMA_DEFAULT,
1557            max_frames: $crate::led_strip::MAX_FRAMES_DEFAULT,
1558            led2d: __NONE__,
1559            fields: [ $($fields)* ]
1560        }
1561    };
1562
1563    // All strips processed, call the main implementation
1564    (@__with_defaults
1565        frame_alias: $frame_alias:tt,
1566        pio: $pio:ident,
1567        vis: $vis:vis,
1568        group: $group:ident,
1569        sm_counter: $sm:tt,
1570        strips_out: [ $($out:tt)* ],
1571        strips_in: []
1572    ) => {
1573        $crate::__led_strips_impl! {
1574            @__resolve_default_dma
1575            frame_alias: $frame_alias,
1576            pio: $pio,
1577            vis: $vis,
1578            group: $group,
1579            strips_out: [],
1580            strips_in: [ $($out)* ]
1581        }
1582    };
1583
1584    // Resolve any __DEFAULT_DMA__ placeholders before expansion.
1585    (@__resolve_default_dma
1586        frame_alias: $frame_alias:tt,
1587        pio: $pio:ident,
1588        vis: $vis:vis,
1589        group: $group:ident,
1590        strips_out: [ $($out:tt)* ],
1591        strips_in: []
1592    ) => {
1593        $crate::__led_strips_impl! {
1594            @__expand
1595            frame_alias: $frame_alias,
1596            pio: $pio,
1597            vis: $vis,
1598            group: $group,
1599            strips: [ $($out)* ]
1600        }
1601    };
1602
1603    (@__resolve_default_dma
1604        frame_alias: $frame_alias:tt,
1605        pio: $pio:ident,
1606        vis: $vis:vis,
1607        group: $group:ident,
1608        strips_out: [ $($out:tt)* ],
1609        strips_in: [
1610            $label:ident {
1611                sm: 0,
1612                dma: __DEFAULT_DMA__,
1613                pin: $pin:ident,
1614                len: $len:expr,
1615                max_current: $max_current:expr,
1616                gamma: $gamma:expr,
1617                max_frames: $max_frames:expr
1618                $(,
1619                    led2d: {
1620                        led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1621                        $(max_frames: $led2d_max_frames:expr,)?
1622                        font: $led2d_font:expr $(,)?
1623                    }
1624                )?
1625            }
1626            $(, $($rest:tt)* )?
1627        ]
1628    ) => {
1629        $crate::__led_strips_impl! {
1630            @__resolve_default_dma
1631            frame_alias: $frame_alias,
1632            pio: $pio,
1633            vis: $vis,
1634            group: $group,
1635            strips_out: [
1636                $($out)*
1637                $label {
1638                    sm: 0,
1639                    dma: DMA_CH0,
1640                    pin: $pin,
1641                    len: $len,
1642                    max_current: $max_current,
1643                    gamma: $gamma,
1644                    max_frames: $max_frames
1645                    $(,
1646                        led2d: {
1647                            led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1648                            font: $led2d_font,
1649                        }
1650                    )?
1651                },
1652            ],
1653            strips_in: [ $($($rest)*)? ]
1654        }
1655    };
1656
1657    // SM 0 with led2d but no explicit max_frames (use strip-level max_frames)
1658    (@__resolve_default_dma
1659        frame_alias: $frame_alias:tt,
1660        pio: $pio:ident,
1661        vis: $vis:vis,
1662        group: $group:ident,
1663        strips_out: [ $($out:tt)* ],
1664        strips_in: [
1665            $label:ident {
1666                sm: 0,
1667                dma: __DEFAULT_DMA__,
1668                pin: $pin:ident,
1669                len: $len:expr,
1670                max_current: $max_current:expr,
1671                gamma: $gamma:expr,
1672                max_frames: $max_frames:expr
1673                ,
1674                led2d: {
1675                    led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1676                    font: $led2d_font:expr $(,)?
1677                }
1678            }
1679            $(, $($rest:tt)* )?
1680        ]
1681    ) => {
1682        $crate::__led_strips_impl! {
1683            @__resolve_default_dma
1684            frame_alias: $frame_alias,
1685            pio: $pio,
1686            vis: $vis,
1687            group: $group,
1688            strips_out: [
1689                $($out)*
1690                $label {
1691                    sm: 0,
1692                    dma: DMA_CH0,
1693                    pin: $pin,
1694                    len: $len,
1695                    max_current: $max_current,
1696                    gamma: $gamma,
1697                    max_frames: $max_frames
1698                    ,
1699                    led2d: {
1700                        led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1701                        max_frames: $max_frames,
1702                        font: $led2d_font,
1703                    }
1704                },
1705            ],
1706            strips_in: [ $($($rest)*)? ]
1707        }
1708    };
1709
1710    // SM 1 with led2d but no explicit max_frames (use strip-level max_frames)
1711    (@__resolve_default_dma
1712        frame_alias: $frame_alias:tt,
1713        pio: $pio:ident,
1714        vis: $vis:vis,
1715        group: $group:ident,
1716        strips_out: [ $($out:tt)* ],
1717        strips_in: [
1718            $label:ident {
1719                sm: 1,
1720                dma: __DEFAULT_DMA__,
1721                pin: $pin:ident,
1722                len: $len:expr,
1723                max_current: $max_current:expr,
1724                gamma: $gamma:expr,
1725                max_frames: $max_frames:expr
1726                ,
1727                led2d: {
1728                    led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1729                    font: $led2d_font:expr $(,)?
1730                }
1731            }
1732            $(, $($rest:tt)* )?
1733        ]
1734    ) => {
1735        $crate::__led_strips_impl! {
1736            @__resolve_default_dma
1737            frame_alias: $frame_alias,
1738            pio: $pio,
1739            vis: $vis,
1740            group: $group,
1741            strips_out: [
1742                $($out)*
1743                $label {
1744                    sm: 1,
1745                    dma: DMA_CH1,
1746                    pin: $pin,
1747                    len: $len,
1748                    max_current: $max_current,
1749                    gamma: $gamma,
1750                    max_frames: $max_frames
1751                    ,
1752                    led2d: {
1753                        led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1754                        max_frames: $max_frames,
1755                        font: $led2d_font,
1756                    }
1757                },
1758            ],
1759            strips_in: [ $($($rest)*)? ]
1760        }
1761    };
1762
1763    // SM 1 with led2d and explicit max_frames (original)
1764    (@__resolve_default_dma
1765        frame_alias: $frame_alias:tt,
1766        pio: $pio:ident,
1767        vis: $vis:vis,
1768        group: $group:ident,
1769        strips_out: [ $($out:tt)* ],
1770        strips_in: [
1771            $label:ident {
1772                sm: 1,
1773                dma: __DEFAULT_DMA__,
1774                pin: $pin:ident,
1775                len: $len:expr,
1776                max_current: $max_current:expr,
1777                gamma: $gamma:expr,
1778                max_frames: $max_frames:expr
1779                $(,
1780                    led2d: {
1781                        led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1782                        $(max_frames: $led2d_max_frames:expr,)?
1783                        font: $led2d_font:expr $(,)?
1784                    }
1785                )?
1786            }
1787            $(, $($rest:tt)* )?
1788        ]
1789    ) => {
1790        $crate::__led_strips_impl! {
1791            @__resolve_default_dma
1792            frame_alias: $frame_alias,
1793            pio: $pio,
1794            vis: $vis,
1795            group: $group,
1796            strips_out: [
1797                $($out)*
1798                $label {
1799                    sm: 1,
1800                    dma: DMA_CH1,
1801                    pin: $pin,
1802                    len: $len,
1803                    max_current: $max_current,
1804                    gamma: $gamma,
1805                    max_frames: $max_frames
1806                    $(,
1807                        led2d: {
1808                            led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1809                            font: $led2d_font,
1810                        }
1811                    )?
1812                },
1813            ],
1814            strips_in: [ $($($rest)*)? ]
1815        }
1816    };
1817
1818    // SM 2 with led2d but no explicit max_frames (use strip-level max_frames)
1819    (@__resolve_default_dma
1820        frame_alias: $frame_alias:tt,
1821        pio: $pio:ident,
1822        vis: $vis:vis,
1823        group: $group:ident,
1824        strips_out: [ $($out:tt)* ],
1825        strips_in: [
1826            $label:ident {
1827                sm: 2,
1828                dma: __DEFAULT_DMA__,
1829                pin: $pin:ident,
1830                len: $len:expr,
1831                max_current: $max_current:expr,
1832                gamma: $gamma:expr,
1833                max_frames: $max_frames:expr
1834                ,
1835                led2d: {
1836                    led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1837                    font: $led2d_font:expr $(,)?
1838                }
1839            }
1840            $(, $($rest:tt)* )?
1841        ]
1842    ) => {
1843        $crate::__led_strips_impl! {
1844            @__resolve_default_dma
1845            frame_alias: $frame_alias,
1846            pio: $pio,
1847            vis: $vis,
1848            group: $group,
1849            strips_out: [
1850                $($out)*
1851                $label {
1852                    sm: 2,
1853                    dma: DMA_CH2,
1854                    pin: $pin,
1855                    len: $len,
1856                    max_current: $max_current,
1857                    gamma: $gamma,
1858                    max_frames: $max_frames
1859                    ,
1860                    led2d: {
1861                        led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1862                        max_frames: $max_frames,
1863                        font: $led2d_font,
1864                    }
1865                },
1866            ],
1867            strips_in: [ $($($rest)*)? ]
1868        }
1869    };
1870
1871    // SM 3 with led2d but no explicit max_frames (use strip-level max_frames)
1872    (@__resolve_default_dma
1873        frame_alias: $frame_alias:tt,
1874        pio: $pio:ident,
1875        vis: $vis:vis,
1876        group: $group:ident,
1877        strips_out: [ $($out:tt)* ],
1878        strips_in: [
1879            $label:ident {
1880                sm: 3,
1881                dma: __DEFAULT_DMA__,
1882                pin: $pin:ident,
1883                len: $len:expr,
1884                max_current: $max_current:expr,
1885                gamma: $gamma:expr,
1886                max_frames: $max_frames:expr
1887                ,
1888                led2d: {
1889                    led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1890                    font: $led2d_font:expr $(,)?
1891                }
1892            }
1893            $(, $($rest:tt)* )?
1894        ]
1895    ) => {
1896        $crate::__led_strips_impl! {
1897            @__resolve_default_dma
1898            frame_alias: $frame_alias,
1899            pio: $pio,
1900            vis: $vis,
1901            group: $group,
1902            strips_out: [
1903                $($out)*
1904                $label {
1905                    sm: 3,
1906                    dma: DMA_CH3,
1907                    pin: $pin,
1908                    len: $len,
1909                    max_current: $max_current,
1910                    gamma: $gamma,
1911                    max_frames: $max_frames
1912                    ,
1913                    led2d: {
1914                        led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1915                        max_frames: $max_frames,
1916                        font: $led2d_font,
1917                    }
1918                },
1919            ],
1920            strips_in: [ $($($rest)*)? ]
1921        }
1922    };
1923
1924    (@__resolve_default_dma
1925        frame_alias: $frame_alias:tt,
1926        pio: $pio:ident,
1927        vis: $vis:vis,
1928        group: $group:ident,
1929        strips_out: [ $($out:tt)* ],
1930        strips_in: [
1931            $label:ident {
1932                sm: 2,
1933                dma: __DEFAULT_DMA__,
1934                pin: $pin:ident,
1935                len: $len:expr,
1936                max_current: $max_current:expr,
1937                gamma: $gamma:expr,
1938                max_frames: $max_frames:expr
1939                $(,
1940                    led2d: {
1941                        led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1942                        $(max_frames: $led2d_max_frames:expr,)?
1943                        font: $led2d_font:expr $(,)?
1944                    }
1945                )?
1946            }
1947            $(, $($rest:tt)* )?
1948        ]
1949    ) => {
1950        $crate::__led_strips_impl! {
1951            @__resolve_default_dma
1952            frame_alias: $frame_alias,
1953            pio: $pio,
1954            vis: $vis,
1955            group: $group,
1956            strips_out: [
1957                $($out)*
1958                $label {
1959                    sm: 2,
1960                    dma: DMA_CH2,
1961                    pin: $pin,
1962                    len: $len,
1963                    max_current: $max_current,
1964                    gamma: $gamma,
1965                    max_frames: $max_frames
1966                    $(,
1967                        led2d: {
1968                            led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
1969                            font: $led2d_font,
1970                        }
1971                    )?
1972                },
1973            ],
1974            strips_in: [ $($($rest)*)? ]
1975        }
1976    };
1977
1978    (@__resolve_default_dma
1979        frame_alias: $frame_alias:tt,
1980        pio: $pio:ident,
1981        vis: $vis:vis,
1982        group: $group:ident,
1983        strips_out: [ $($out:tt)* ],
1984        strips_in: [
1985            $label:ident {
1986                sm: 3,
1987                dma: __DEFAULT_DMA__,
1988                pin: $pin:ident,
1989                len: $len:expr,
1990                max_current: $max_current:expr,
1991                gamma: $gamma:expr,
1992                max_frames: $max_frames:expr
1993                $(,
1994                    led2d: {
1995                        led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
1996                        $(max_frames: $led2d_max_frames:expr,)?
1997                        font: $led2d_font:expr $(,)?
1998                    }
1999                )?
2000            }
2001            $(, $($rest:tt)* )?
2002        ]
2003    ) => {
2004        $crate::__led_strips_impl! {
2005            @__resolve_default_dma
2006            frame_alias: $frame_alias,
2007            pio: $pio,
2008            vis: $vis,
2009            group: $group,
2010            strips_out: [
2011                $($out)*
2012                $label {
2013                    sm: 3,
2014                    dma: DMA_CH3,
2015                    pin: $pin,
2016                    len: $len,
2017                    max_current: $max_current,
2018                    gamma: $gamma,
2019                    max_frames: $max_frames
2020                    $(,
2021                        led2d: {
2022                            led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
2023                            font: $led2d_font,
2024                        }
2025                    )?
2026                },
2027            ],
2028            strips_in: [ $($($rest)*)? ]
2029        }
2030    };
2031
2032    (@__resolve_default_dma
2033        frame_alias: $frame_alias:tt,
2034        pio: $pio:ident,
2035        vis: $vis:vis,
2036        group: $group:ident,
2037        strips_out: [ $($out:tt)* ],
2038        strips_in: [
2039            $label:ident {
2040                sm: $sm_index:expr,
2041                dma: $dma:ident,
2042                pin: $pin:ident,
2043                len: $len:expr,
2044                max_current: $max_current:expr,
2045                gamma: $gamma:expr,
2046                max_frames: $max_frames:expr
2047                $(,
2048                    led2d: {
2049                        led_layout: $led2d_led_layout:ident $( ( $($led2d_led_layout_args:tt)* ) )?,
2050                        $(max_frames: $led2d_max_frames:expr,)?
2051                        font: $led2d_font:expr $(,)?
2052                    }
2053                )?
2054            }
2055            $(, $($rest:tt)* )?
2056        ]
2057    ) => {
2058        $crate::__led_strips_impl! {
2059            @__resolve_default_dma
2060            frame_alias: $frame_alias,
2061            pio: $pio,
2062            vis: $vis,
2063            group: $group,
2064            strips_out: [
2065                $($out)*
2066                $label {
2067                    sm: $sm_index,
2068                    dma: $dma,
2069                    pin: $pin,
2070                    len: $len,
2071                    max_current: $max_current,
2072                    gamma: $gamma,
2073                    max_frames: $max_frames
2074                    $(,
2075                        led2d: {
2076                            led_layout: $led2d_led_layout $( ( $($led2d_led_layout_args)* ) )?,
2077                            font: $led2d_font,
2078                        }
2079                    )?
2080                },
2081            ],
2082            strips_in: [ $($($rest)*)? ]
2083        }
2084    };
2085
2086
2087    // Parse fields for a single strip
2088    (@__fill_strip_defaults
2089        frame_alias: $frame_alias:tt,
2090        pio: $pio:ident,
2091        vis: $vis:vis,
2092        sm_counter: $sm:tt,
2093        strips_out: [ $($out:tt)* ],
2094        strips_remaining: [ $($remaining:tt)* ],
2095        label: $label:ident,
2096        group: $group:ident,
2097        pin: $pin:tt,
2098        dma: $dma:ident,
2099        len: $len:tt,
2100        max_current: $max_current:expr,
2101        gamma: $gamma:expr,
2102        max_frames: $max_frames:expr,
2103        led2d: $led2d:tt,
2104        fields: [ pin: $new_pin:ident $(, $($rest:tt)* )? ]
2105    ) => {
2106        $crate::__led_strips_impl! {
2107            @__fill_strip_defaults
2108            frame_alias: $frame_alias,
2109            pio: $pio,
2110            vis: $vis,
2111            sm_counter: $sm,
2112            strips_out: [ $($out)* ],
2113            strips_remaining: [ $($remaining)* ],
2114            label: $label,
2115            group: $group,
2116            pin: $new_pin,
2117            dma: $dma,
2118            len: $len,
2119            max_current: $max_current,
2120            gamma: $gamma,
2121            max_frames: $max_frames,
2122            led2d: $led2d,
2123            fields: [ $($($rest)*)? ]
2124        }
2125    };
2126
2127    (@__fill_strip_defaults
2128        frame_alias: $frame_alias:tt,
2129        pio: $pio:ident,
2130        vis: $vis:vis,
2131        sm_counter: $sm:tt,
2132        strips_out: [ $($out:tt)* ],
2133        strips_remaining: [ $($remaining:tt)* ],
2134        label: $label:ident,
2135        group: $group:ident,
2136        pin: $pin:tt,
2137        dma: $dma:ident,
2138        len: $len:tt,
2139        max_current: $max_current:expr,
2140        gamma: $gamma:expr,
2141        max_frames: $max_frames:expr,
2142        led2d: $led2d:tt,
2143        fields: [ dma: $new_dma:ident $(, $($rest:tt)* )? ]
2144    ) => {
2145        $crate::__led_strips_impl! {
2146            @__fill_strip_defaults
2147            frame_alias: $frame_alias,
2148            pio: $pio,
2149            vis: $vis,
2150            sm_counter: $sm,
2151            strips_out: [ $($out)* ],
2152            strips_remaining: [ $($remaining)* ],
2153            label: $label,
2154            group: $group,
2155            pin: $pin,
2156            dma: $new_dma,
2157            len: $len,
2158            max_current: $max_current,
2159            gamma: $gamma,
2160            max_frames: $max_frames,
2161            led2d: $led2d,
2162            fields: [ $($($rest)*)? ]
2163        }
2164    };
2165
2166    (@__fill_strip_defaults
2167        frame_alias: $frame_alias:tt,
2168        pio: $pio:ident,
2169        vis: $vis:vis,
2170        sm_counter: $sm:tt,
2171        strips_out: [ $($out:tt)* ],
2172        strips_remaining: [ $($remaining:tt)* ],
2173        label: $label:ident,
2174        group: $group:ident,
2175        pin: $pin:tt,
2176        dma: $dma:ident,
2177        len: $len:tt,
2178        max_current: $max_current:expr,
2179        gamma: $gamma:expr,
2180        max_frames: $max_frames:expr,
2181        led2d: $led2d:tt,
2182        fields: [ len: $new_len:expr $(, $($rest:tt)* )? ]
2183    ) => {
2184        $crate::__led_strips_impl! {
2185            @__fill_strip_defaults
2186            frame_alias: $frame_alias,
2187            pio: $pio,
2188            vis: $vis,
2189            sm_counter: $sm,
2190            strips_out: [ $($out)* ],
2191            strips_remaining: [ $($remaining)* ],
2192            label: $label,
2193            group: $group,
2194            pin: $pin,
2195            dma: $dma,
2196            len: $new_len,
2197            max_current: $max_current,
2198            gamma: $gamma,
2199            max_frames: $max_frames,
2200            led2d: $led2d,
2201            fields: [ $($($rest)*)? ]
2202        }
2203    };
2204
2205    (@__fill_strip_defaults
2206        frame_alias: $frame_alias:tt,
2207        pio: $pio:ident,
2208        vis: $vis:vis,
2209        sm_counter: $sm:tt,
2210        strips_out: [ $($out:tt)* ],
2211        strips_remaining: [ $($remaining:tt)* ],
2212        label: $label:ident,
2213        group: $group:ident,
2214        pin: $pin:tt,
2215        dma: $dma:ident,
2216        len: $len:tt,
2217        max_current: $max_current:expr,
2218        gamma: $gamma:expr,
2219        max_frames: $max_frames:expr,
2220        led2d: $led2d:tt,
2221        fields: [ max_current: $new_max_current:expr $(, $($rest:tt)* )? ]
2222    ) => {
2223        $crate::__led_strips_impl! {
2224            @__fill_strip_defaults
2225            frame_alias: $frame_alias,
2226            pio: $pio,
2227            vis: $vis,
2228            sm_counter: $sm,
2229            strips_out: [ $($out)* ],
2230            strips_remaining: [ $($remaining)* ],
2231            label: $label,
2232            group: $group,
2233            pin: $pin,
2234            dma: $dma,
2235            len: $len,
2236            max_current: $new_max_current,
2237            gamma: $gamma,
2238            max_frames: $max_frames,
2239            led2d: $led2d,
2240            fields: [ $($($rest)*)? ]
2241        }
2242    };
2243
2244    (@__fill_strip_defaults
2245        frame_alias: $frame_alias:tt,
2246        pio: $pio:ident,
2247        vis: $vis:vis,
2248        sm_counter: $sm:tt,
2249        strips_out: [ $($out:tt)* ],
2250        strips_remaining: [ $($remaining:tt)* ],
2251        label: $label:ident,
2252        group: $group:ident,
2253        pin: $pin:tt,
2254        dma: $dma:ident,
2255        len: $len:tt,
2256        max_current: $max_current:expr,
2257        gamma: $gamma:expr,
2258        max_frames: $max_frames:expr,
2259        led2d: $led2d:tt,
2260        fields: [ gamma: $new_gamma:expr $(, $($rest:tt)* )? ]
2261    ) => {
2262        $crate::__led_strips_impl! {
2263            @__fill_strip_defaults
2264            frame_alias: $frame_alias,
2265            pio: $pio,
2266            vis: $vis,
2267            sm_counter: $sm,
2268            strips_out: [ $($out)* ],
2269            strips_remaining: [ $($remaining)* ],
2270            label: $label,
2271            group: $group,
2272            pin: $pin,
2273            dma: $dma,
2274            len: $len,
2275            max_current: $max_current,
2276            gamma: $new_gamma,
2277            max_frames: $max_frames,
2278            led2d: $led2d,
2279            fields: [ $($($rest)*)? ]
2280        }
2281    };
2282
2283    (@__fill_strip_defaults
2284        frame_alias: $frame_alias:tt,
2285        pio: $pio:ident,
2286        vis: $vis:vis,
2287        sm_counter: $sm:tt,
2288        strips_out: [ $($out:tt)* ],
2289        strips_remaining: [ $($remaining:tt)* ],
2290        label: $label:ident,
2291        group: $group:ident,
2292        pin: $pin:tt,
2293        dma: $dma:ident,
2294        len: $len:tt,
2295        max_current: $max_current:expr,
2296        gamma: $gamma:expr,
2297        max_frames: $max_frames:expr,
2298        led2d: $led2d:tt,
2299        fields: [ max_frames: $new_max_frames:expr $(, $($rest:tt)* )? ]
2300    ) => {
2301        $crate::__led_strips_impl! {
2302            @__fill_strip_defaults
2303            frame_alias: $frame_alias,
2304            pio: $pio,
2305            vis: $vis,
2306            sm_counter: $sm,
2307            strips_out: [ $($out)* ],
2308            strips_remaining: [ $($remaining)* ],
2309            label: $label,
2310            group: $group,
2311            pin: $pin,
2312            dma: $dma,
2313            len: $len,
2314            max_current: $max_current,
2315            gamma: $gamma,
2316            max_frames: $new_max_frames,
2317            led2d: $led2d,
2318            fields: [ $($($rest)*)? ]
2319        }
2320    };
2321
2322    (@__fill_strip_defaults
2323        frame_alias: $frame_alias:tt,
2324        pio: $pio:ident,
2325        vis: $vis:vis,
2326        sm_counter: $sm:tt,
2327        strips_out: [ $($out:tt)* ],
2328        strips_remaining: [ $($remaining:tt)* ],
2329        label: $label:ident,
2330        group: $group:ident,
2331        pin: $pin:tt,
2332        dma: $dma:ident,
2333        len: $len:tt,
2334        max_current: $max_current:expr,
2335        gamma: $gamma:expr,
2336        max_frames: $max_frames:expr,
2337        led2d: __NONE__,
2338        fields: [ led2d: { $($led2d_fields:tt)* } $(, $($rest:tt)* )? ]
2339    ) => {
2340        $crate::__led_strips_impl! {
2341            @__fill_strip_defaults
2342            frame_alias: $frame_alias,
2343            pio: $pio,
2344            vis: $vis,
2345            sm_counter: $sm,
2346            strips_out: [ $($out)* ],
2347            strips_remaining: [ $($remaining)* ],
2348            label: $label,
2349            group: $group,
2350            pin: $pin,
2351            dma: $dma,
2352            len: $len,
2353            max_current: $max_current,
2354            gamma: $gamma,
2355            max_frames: $max_frames,
2356            led2d: __HAS_LED2D__ { $($led2d_fields)* },
2357            fields: [ $($($rest)*)? ]
2358        }
2359    };
2360
2361    // Done parsing fields, add strip to output and continue
2362    // Special case: convert __DEFAULT_DMA__ to actual DMA channel based on sm
2363    (@__fill_strip_defaults
2364        frame_alias: $frame_alias:tt,
2365        pio: $pio:ident,
2366        vis: $vis:vis,
2367        sm_counter: 0,
2368        strips_out: [ $($out:tt)* ],
2369        strips_remaining: [ $($remaining:tt)* ],
2370        label: $label:ident,
2371        group: $group:ident,
2372        pin: $pin:ident,
2373        dma: __DEFAULT_DMA__,
2374        len: $len:expr,
2375        max_current: $max_current:expr,
2376        gamma: $gamma:expr,
2377        max_frames: $max_frames:expr,
2378        led2d: $led2d:tt,
2379        fields: []
2380    ) => {
2381        $crate::__led_strips_impl! {
2382            @__fill_strip_defaults
2383            frame_alias: $frame_alias,
2384            pio: $pio,
2385            vis: $vis,
2386            sm_counter: 0,
2387            strips_out: [ $($out)* ],
2388            strips_remaining: [ $($remaining)* ],
2389            label: $label,
2390            group: $group,
2391            pin: $pin,
2392            dma: DMA_CH0,
2393            len: $len,
2394            max_current: $max_current,
2395            gamma: $gamma,
2396            max_frames: $max_frames,
2397            led2d: $led2d,
2398            fields: []
2399        }
2400    };
2401    (@__fill_strip_defaults
2402        frame_alias: $frame_alias:tt,
2403        pio: $pio:ident,
2404        vis: $vis:vis,
2405        sm_counter: 1,
2406        strips_out: [ $($out:tt)* ],
2407        strips_remaining: [ $($remaining:tt)* ],
2408        label: $label:ident,
2409        group: $group:ident,
2410        pin: $pin:ident,
2411        dma: __DEFAULT_DMA__,
2412        len: $len:expr,
2413        max_current: $max_current:expr,
2414        gamma: $gamma:expr,
2415        max_frames: $max_frames:expr,
2416        led2d: $led2d:tt,
2417        fields: []
2418    ) => {
2419        $crate::__led_strips_impl! {
2420            @__fill_strip_defaults
2421            frame_alias: $frame_alias,
2422            pio: $pio,
2423            vis: $vis,
2424            sm_counter: 1,
2425            strips_out: [ $($out)* ],
2426            strips_remaining: [ $($remaining)* ],
2427            label: $label,
2428            group: $group,
2429            pin: $pin,
2430            dma: DMA_CH1,
2431            len: $len,
2432            max_current: $max_current,
2433            gamma: $gamma,
2434            max_frames: $max_frames,
2435            led2d: $led2d,
2436            fields: []
2437        }
2438    };
2439    (@__fill_strip_defaults
2440        frame_alias: $frame_alias:tt,
2441        pio: $pio:ident,
2442        vis: $vis:vis,
2443        sm_counter: 2,
2444        strips_out: [ $($out:tt)* ],
2445        strips_remaining: [ $($remaining:tt)* ],
2446        label: $label:ident,
2447        group: $group:ident,
2448        pin: $pin:ident,
2449        dma: __DEFAULT_DMA__,
2450        len: $len:expr,
2451        max_current: $max_current:expr,
2452        gamma: $gamma:expr,
2453        max_frames: $max_frames:expr,
2454        led2d: $led2d:tt,
2455        fields: []
2456    ) => {
2457        $crate::__led_strips_impl! {
2458            @__fill_strip_defaults
2459            frame_alias: $frame_alias,
2460            pio: $pio,
2461            vis: $vis,
2462            sm_counter: 2,
2463            strips_out: [ $($out)* ],
2464            strips_remaining: [ $($remaining)* ],
2465            label: $label,
2466            group: $group,
2467            pin: $pin,
2468            dma: DMA_CH2,
2469            len: $len,
2470            max_current: $max_current,
2471            gamma: $gamma,
2472            max_frames: $max_frames,
2473            led2d: $led2d,
2474            fields: []
2475        }
2476    };
2477    (@__fill_strip_defaults
2478        frame_alias: $frame_alias:tt,
2479        pio: $pio:ident,
2480        vis: $vis:vis,
2481        sm_counter: 3,
2482        strips_out: [ $($out:tt)* ],
2483        strips_remaining: [ $($remaining:tt)* ],
2484        label: $label:ident,
2485        group: $group:ident,
2486        pin: $pin:ident,
2487        dma: __DEFAULT_DMA__,
2488        len: $len:expr,
2489        max_current: $max_current:expr,
2490        gamma: $gamma:expr,
2491        max_frames: $max_frames:expr,
2492        led2d: $led2d:tt,
2493        fields: []
2494    ) => {
2495        $crate::__led_strips_impl! {
2496            @__fill_strip_defaults
2497            frame_alias: $frame_alias,
2498            pio: $pio,
2499            vis: $vis,
2500            sm_counter: 3,
2501            strips_out: [ $($out)* ],
2502            strips_remaining: [ $($remaining)* ],
2503            label: $label,
2504            group: $group,
2505            pin: $pin,
2506            dma: DMA_CH3,
2507            len: $len,
2508            max_current: $max_current,
2509            gamma: $gamma,
2510            max_frames: $max_frames,
2511            led2d: $led2d,
2512            fields: []
2513        }
2514    };
2515
2516    // Done parsing fields, add strip to output and continue
2517    (@__fill_strip_defaults
2518        frame_alias: $frame_alias:tt,
2519        pio: $pio:ident,
2520        vis: $vis:vis,
2521        sm_counter: $sm:tt,
2522        strips_out: [ $($out:tt)* ],
2523        strips_remaining: [ $($remaining:tt)* ],
2524        label: $label:ident,
2525        group: $group:ident,
2526        pin: $pin:ident,
2527        dma: $dma:ident,
2528        len: $len:expr,
2529        max_current: __MISSING_MAX_CURRENT__,
2530        gamma: $gamma:expr,
2531        max_frames: $max_frames:expr,
2532        led2d: __NONE__,
2533        fields: []
2534    ) => {
2535        compile_error!("led_strips!: max_current is required for every strip");
2536    };
2537
2538    (@__fill_strip_defaults
2539        frame_alias: $frame_alias:tt,
2540        pio: $pio:ident,
2541        vis: $vis:vis,
2542        sm_counter: $sm:tt,
2543        strips_out: [ $($out:tt)* ],
2544        strips_remaining: [ $($remaining:tt)* ],
2545        label: $label:ident,
2546        group: $group:ident,
2547        pin: $pin:ident,
2548        dma: $dma:ident,
2549        len: $len:expr,
2550        max_current: __MISSING_MAX_CURRENT__,
2551        gamma: $gamma:expr,
2552        max_frames: $max_frames:expr,
2553        led2d: __HAS_LED2D__ { $($led2d_fields:tt)* },
2554        fields: []
2555    ) => {
2556        compile_error!("led_strips!: max_current is required for every strip");
2557    };
2558
2559    (@__fill_strip_defaults
2560        frame_alias: $frame_alias:tt,
2561        pio: $pio:ident,
2562        vis: $vis:vis,
2563        sm_counter: $sm:tt,
2564        strips_out: [ $($out:tt)* ],
2565        strips_remaining: [ $($remaining:tt)* ],
2566        label: $label:ident,
2567        group: $group:ident,
2568        pin: $pin:ident,
2569        dma: $dma:ident,
2570        len: $len:expr,
2571        max_current: $max_current:expr,
2572        gamma: $gamma:expr,
2573        max_frames: $max_frames:expr,
2574        led2d: __NONE__,
2575        fields: []
2576    ) => {
2577        $crate::__led_strips_impl! {
2578            @__inc_counter
2579            frame_alias: $frame_alias,
2580            pio: $pio,
2581            vis: $vis,
2582            group: $group,
2583            sm: $sm,
2584            strips_out: [
2585                $($out)*
2586                $label {
2587                    sm: $sm,
2588                    dma: $dma,
2589                    pin: $pin,
2590                    len: $len,
2591                    max_current: $max_current,
2592                    gamma: $gamma,
2593                    max_frames: $max_frames
2594                },
2595            ],
2596            strips_in: [ $($remaining)* ]
2597        }
2598    };
2599
2600    (@__fill_strip_defaults
2601        frame_alias: $frame_alias:tt,
2602        pio: $pio:ident,
2603        vis: $vis:vis,
2604        sm_counter: $sm:tt,
2605        strips_out: [ $($out:tt)* ],
2606        strips_remaining: [ $($remaining:tt)* ],
2607        label: $label:ident,
2608        group: $group:ident,
2609        pin: $pin:ident,
2610        dma: $dma:ident,
2611        len: $len:expr,
2612        max_current: $max_current:expr,
2613        gamma: $gamma:expr,
2614        max_frames: $max_frames:expr,
2615        led2d: __HAS_LED2D__ { $($led2d_fields:tt)* },
2616        fields: []
2617    ) => {
2618        $crate::__led_strips_impl! {
2619            @__inc_counter
2620            frame_alias: $frame_alias,
2621            pio: $pio,
2622            vis: $vis,
2623            group: $group,
2624            sm: $sm,
2625            strips_out: [
2626                $($out)*
2627                $label {
2628                    sm: $sm,
2629                    dma: $dma,
2630                    pin: $pin,
2631                    len: $len,
2632                    max_current: $max_current,
2633                    gamma: $gamma,
2634                    max_frames: $max_frames,
2635                    led2d: { $($led2d_fields)* }
2636                },
2637            ],
2638            strips_in: [ $($remaining)* ]
2639        }
2640    };
2641    // Increment counter by expanding to literal numbers
2642    (@__inc_counter frame_alias: $frame_alias:tt, pio: $pio:ident, vis: $vis:vis, group: $group:ident, sm: 0, strips_out: [$($out:tt)*], strips_in: [$($in:tt)*]) => {
2643        $crate::__led_strips_impl! { @__with_defaults frame_alias: $frame_alias, pio: $pio, vis: $vis, group: $group, sm_counter: 1, strips_out: [$($out)*], strips_in: [$($in)*] }
2644    };
2645    (@__inc_counter frame_alias: $frame_alias:tt, pio: $pio:ident, vis: $vis:vis, group: $group:ident, sm: 1, strips_out: [$($out:tt)*], strips_in: [$($in:tt)*]) => {
2646        $crate::__led_strips_impl! { @__with_defaults frame_alias: $frame_alias, pio: $pio, vis: $vis, group: $group, sm_counter: 2, strips_out: [$($out)*], strips_in: [$($in)*] }
2647    };
2648    (@__inc_counter frame_alias: $frame_alias:tt, pio: $pio:ident, vis: $vis:vis, group: $group:ident, sm: 2, strips_out: [$($out:tt)*], strips_in: [$($in:tt)*]) => {
2649        $crate::__led_strips_impl! { @__with_defaults frame_alias: $frame_alias, pio: $pio, vis: $vis, group: $group, sm_counter: 3, strips_out: [$($out)*], strips_in: [$($in)*] }
2650    };
2651    (@__inc_counter frame_alias: $frame_alias:tt, pio: $pio:ident, vis: $vis:vis, group: $group:ident, sm: 3, strips_out: [$($out:tt)*], strips_in: [$($in:tt)*]) => {
2652        $crate::__led_strips_impl! { @__with_defaults frame_alias: $frame_alias, pio: $pio, vis: $vis, group: $group, sm_counter: 4, strips_out: [$($out)*], strips_in: [$($in)*] }
2653    };
2654}
2655
2656/// Macro to generate an LED-strip struct type (includes syntax details). See
2657/// [`LedStripGenerated`](led_strip_generated::LedStripGenerated) for a sample of a generated type.
2658///
2659/// **See the [led_strip module documentation](mod@crate::led_strip) for usage examples.**
2660///
2661/// **Syntax:**
2662///
2663/// ```text
2664/// led_strip! {
2665///     [<visibility>] <Name> {
2666///         pin: <pin_ident>,
2667///         len: <usize_expr>,
2668///         pio: <pio_ident>,               // optional
2669///         dma: <dma_ident>,               // optional
2670///         max_current: <Current_expr>,    // optional
2671///         gamma: <Gamma_expr>,            // optional
2672///         max_frames: <usize_expr>,       // optional
2673///     }
2674/// }
2675/// ```
2676///
2677/// **Required fields:**
2678///
2679/// - `pin` — GPIO pin for LED data
2680/// - `len` — Number of LEDs
2681///
2682/// **Optional fields:**
2683///
2684/// - `pio` — PIO resource (default: `PIO0`)
2685/// - `dma` — DMA channel (default: `DMA_CH0`)
2686/// - `max_current` — Electrical current budget (default: 250 mA)
2687/// - `gamma` — Color curve (default: `Gamma::Srgb`)
2688/// - `max_frames` — Maximum number of animation frames (default: 16 frames)
2689///
2690/// `max_frames = 0` disables animation and allocates no frame storage; `write_frame()` is still supported.
2691///
2692#[doc = include_str!("docs/current_limiting_and_gamma.md")]
2693///
2694/// # Related Macros
2695///
2696/// - [`led_strips!`](crate::led_strips) — Alternative macro to share a PIO resource with other strips or panels (includes examples)
2697/// - [`led2d!`](mod@crate::led2d) — For 2-dimensional LED panels
2698///
2699#[cfg(not(feature = "host"))]
2700#[doc(hidden)]
2701#[macro_export]
2702macro_rules! led_strip {
2703    ($($tt:tt)*) => { $crate::__led_strip_impl! { $($tt)* } };
2704}
2705
2706/// Implementation macro. Not part of the public API; use [`led_strip!`] instead.
2707#[cfg(not(feature = "host"))]
2708#[doc(hidden)]
2709#[macro_export]
2710macro_rules! __led_strip_impl {
2711    // Entry point - name without visibility defaults to public
2712    (
2713        $name:ident {
2714            $($fields:tt)*
2715        }
2716    ) => {
2717        $crate::__led_strip_impl! {
2718            @__fill_defaults
2719            vis: pub(self),
2720            pio: PIO0,
2721            name: $name,
2722            pin: _UNSET_,
2723            dma: DMA_CH0,
2724            len: _UNSET_,
2725            max_current: _UNSET_,
2726            gamma: $crate::led_strip::GAMMA_DEFAULT,
2727            max_frames: $crate::led_strip::MAX_FRAMES_DEFAULT,
2728            fields: [ $($fields)* ]
2729        }
2730    };
2731
2732    // Entry point - name with explicit visibility
2733    (
2734        $vis:vis $name:ident {
2735            $($fields:tt)*
2736        }
2737    ) => {
2738        $crate::__led_strip_impl! {
2739            @__fill_defaults
2740            vis: $vis,
2741            pio: PIO0,
2742            name: $name,
2743            pin: _UNSET_,
2744            dma: DMA_CH0,
2745            len: _UNSET_,
2746            max_current: _UNSET_,
2747            gamma: $crate::led_strip::GAMMA_DEFAULT,
2748            max_frames: $crate::led_strip::MAX_FRAMES_DEFAULT,
2749            fields: [ $($fields)* ]
2750        }
2751    };
2752
2753    // Fill defaults: pio
2754    (@__fill_defaults
2755        vis: $vis:vis,
2756        pio: $pio:ident,
2757        name: $name:ident,
2758        pin: $pin:tt,
2759        dma: $dma:ident,
2760        len: $len:tt,
2761        max_current: $max_current:tt,
2762        gamma: $gamma:expr,
2763        max_frames: $max_frames:expr,
2764        fields: [ pio: $new_pio:ident $(, $($rest:tt)* )? ]
2765    ) => {
2766        $crate::__led_strip_impl! {
2767            @__fill_defaults
2768            vis: $vis,
2769            pio: $new_pio,
2770            name: $name,
2771            pin: $pin,
2772            dma: $dma,
2773            len: $len,
2774            max_current: $max_current,
2775            gamma: $gamma,
2776            max_frames: $max_frames,
2777            fields: [ $($($rest)*)? ]
2778        }
2779    };
2780
2781    // Fill defaults: pin
2782    (@__fill_defaults
2783        vis: $vis:vis,
2784        pio: $pio:ident,
2785        name: $name:ident,
2786        pin: $pin:tt,
2787        dma: $dma:ident,
2788        len: $len:tt,
2789        max_current: $max_current:tt,
2790        gamma: $gamma:expr,
2791        max_frames: $max_frames:expr,
2792        fields: [ pin: $new_pin:ident $(, $($rest:tt)* )? ]
2793    ) => {
2794        $crate::__led_strip_impl! {
2795            @__fill_defaults
2796            vis: $vis,
2797            pio: $pio,
2798            name: $name,
2799            pin: $new_pin,
2800            dma: $dma,
2801            len: $len,
2802            max_current: $max_current,
2803            gamma: $gamma,
2804            max_frames: $max_frames,
2805            fields: [ $($($rest)*)? ]
2806        }
2807    };
2808
2809    // Fill defaults: dma
2810    (@__fill_defaults
2811        vis: $vis:vis,
2812        pio: $pio:ident,
2813        name: $name:ident,
2814        pin: $pin:tt,
2815        dma: $dma:ident,
2816        len: $len:tt,
2817        max_current: $max_current:tt,
2818        gamma: $gamma:expr,
2819        max_frames: $max_frames:expr,
2820        fields: [ dma: $new_dma:ident $(, $($rest:tt)* )? ]
2821    ) => {
2822        $crate::__led_strip_impl! {
2823            @__fill_defaults
2824            vis: $vis,
2825            pio: $pio,
2826            name: $name,
2827            pin: $pin,
2828            dma: $new_dma,
2829            len: $len,
2830            max_current: $max_current,
2831            gamma: $gamma,
2832            max_frames: $max_frames,
2833            fields: [ $($($rest)*)? ]
2834        }
2835    };
2836
2837    // Fill defaults: len (expression in braces)
2838    (@__fill_defaults
2839        vis: $vis:vis,
2840        pio: $pio:ident,
2841        name: $name:ident,
2842        pin: $pin:tt,
2843        dma: $dma:ident,
2844        len: $len:tt,
2845        max_current: $max_current:tt,
2846        gamma: $gamma:expr,
2847        max_frames: $max_frames:expr,
2848        fields: [ len: { $new_len:expr } $(, $($rest:tt)* )? ]
2849    ) => {
2850        $crate::__led_strip_impl! {
2851            @__fill_defaults
2852            vis: $vis,
2853            pio: $pio,
2854            name: $name,
2855            pin: $pin,
2856            dma: $dma,
2857            len: { $new_len },
2858            max_current: $max_current,
2859            gamma: $gamma,
2860            max_frames: $max_frames,
2861            fields: [ $($($rest)*)? ]
2862        }
2863    };
2864
2865    // Fill defaults: len (plain expression)
2866    (@__fill_defaults
2867        vis: $vis:vis,
2868        pio: $pio:ident,
2869        name: $name:ident,
2870        pin: $pin:tt,
2871        dma: $dma:ident,
2872        len: $len:tt,
2873        max_current: $max_current:tt,
2874        gamma: $gamma:expr,
2875        max_frames: $max_frames:expr,
2876        fields: [ len: $new_len:expr $(, $($rest:tt)* )? ]
2877    ) => {
2878        $crate::__led_strip_impl! {
2879            @__fill_defaults
2880            vis: $vis,
2881            pio: $pio,
2882            name: $name,
2883            pin: $pin,
2884            dma: $dma,
2885            len: $new_len,
2886            max_current: $max_current,
2887            gamma: $gamma,
2888            max_frames: $max_frames,
2889            fields: [ $($($rest)*)? ]
2890        }
2891    };
2892
2893    // Fill defaults: max_current
2894    (@__fill_defaults
2895        vis: $vis:vis,
2896        pio: $pio:ident,
2897        name: $name:ident,
2898        pin: $pin:tt,
2899        dma: $dma:ident,
2900        len: $len:tt,
2901        max_current: $max_current:tt,
2902        gamma: $gamma:expr,
2903        max_frames: $max_frames:expr,
2904        fields: [ max_current: $new_max_current:expr $(, $($rest:tt)* )? ]
2905    ) => {
2906        $crate::__led_strip_impl! {
2907            @__fill_defaults
2908            vis: $vis,
2909            pio: $pio,
2910            name: $name,
2911            pin: $pin,
2912            dma: $dma,
2913            len: $len,
2914            max_current: $new_max_current,
2915            gamma: $gamma,
2916            max_frames: $max_frames,
2917            fields: [ $($($rest)*)? ]
2918        }
2919    };
2920
2921    // Fill defaults: gamma
2922    (@__fill_defaults
2923        vis: $vis:vis,
2924        pio: $pio:ident,
2925        name: $name:ident,
2926        pin: $pin:tt,
2927        dma: $dma:ident,
2928        len: $len:tt,
2929        max_current: $max_current:tt,
2930        gamma: $gamma:expr,
2931        max_frames: $max_frames:expr,
2932        fields: [ gamma: $new_gamma:expr $(, $($rest:tt)* )? ]
2933    ) => {
2934        $crate::__led_strip_impl! {
2935            @__fill_defaults
2936            vis: $vis,
2937            pio: $pio,
2938            name: $name,
2939            pin: $pin,
2940            dma: $dma,
2941            len: $len,
2942            max_current: $max_current,
2943            gamma: $new_gamma,
2944            max_frames: $max_frames,
2945            fields: [ $($($rest)*)? ]
2946        }
2947    };
2948
2949    // Fill defaults: max_frames
2950    (@__fill_defaults
2951        vis: $vis:vis,
2952        pio: $pio:ident,
2953        name: $name:ident,
2954        pin: $pin:tt,
2955        dma: $dma:ident,
2956        len: $len:tt,
2957        max_current: $max_current:tt,
2958        gamma: $gamma:expr,
2959        max_frames: $max_frames:expr,
2960        fields: [ max_frames: $new_max_frames:expr $(, $($rest:tt)* )? ]
2961    ) => {
2962        $crate::__led_strip_impl! {
2963            @__fill_defaults
2964            vis: $vis,
2965            pio: $pio,
2966            name: $name,
2967            pin: $pin,
2968            dma: $dma,
2969            len: $len,
2970            max_current: $max_current,
2971            gamma: $gamma,
2972            max_frames: $new_max_frames,
2973            fields: [ $($($rest)*)? ]
2974        }
2975    };
2976
2977    // Fill default max_current if still unset
2978    (@__fill_defaults
2979        vis: $vis:vis,
2980        pio: $pio:ident,
2981        name: $name:ident,
2982        pin: $pin:ident,
2983        dma: $dma:ident,
2984        len: $len:expr,
2985        max_current: _UNSET_,
2986        gamma: $gamma:expr,
2987        max_frames: $max_frames:expr,
2988        fields: []
2989    ) => {
2990        $crate::__led_strip_impl! {
2991            @__fill_defaults
2992            vis: $vis,
2993            pio: $pio,
2994            name: $name,
2995            pin: $pin,
2996            dma: $dma,
2997            len: $len,
2998            max_current: $crate::led_strip::MAX_CURRENT_DEFAULT,
2999            gamma: $gamma,
3000            max_frames: $max_frames,
3001            fields: []
3002        }
3003    };
3004
3005    // All fields processed - expand the type
3006    (@__fill_defaults
3007        vis: $vis:vis,
3008        pio: $pio:ident,
3009        name: $name:ident,
3010        pin: $pin:ident,
3011        dma: $dma:ident,
3012        len: $len:expr,
3013        max_current: $max_current:expr,
3014        gamma: $gamma:expr,
3015        max_frames: $max_frames:expr,
3016        fields: []
3017    ) => {
3018        ::paste::paste! {
3019            // Create the PIO bus (shared across all SM0 strips using this PIO)
3020            #[allow(non_upper_case_globals)]
3021            static [<$name:snake _ $pio _BUS>]: ::static_cell::StaticCell<
3022                $crate::led_strip::PioBus<'static, ::embassy_rp::peripherals::$pio>
3023            > = ::static_cell::StaticCell::new();
3024
3025            /// Split the PIO into bus and state machines.
3026            ///
3027            /// Returns SM0 only for single-strip usage.
3028            #[allow(dead_code)]
3029            fn [<$name:snake _split_sm0>](
3030                pio: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pio>,
3031            ) -> $crate::led_strip::PioBusStateMachine<::embassy_rp::peripherals::$pio, 0> {
3032                let ::embassy_rp::pio::Pio { common, sm0, .. } =
3033                    ::embassy_rp::pio::Pio::new(
3034                        pio,
3035                        <::embassy_rp::peripherals::$pio as $crate::pio_irqs::PioIrqMap>::irqs(),
3036                    );
3037                let pio_bus = [<$name:snake _ $pio _BUS>].init_with(|| {
3038                    $crate::led_strip::PioBus::new(common)
3039                });
3040                $crate::led_strip::PioBusStateMachine::new(pio_bus, sm0)
3041            }
3042
3043            #[doc = concat!(
3044                "LED strip generated by [`led_strip!`] or [`led_strips!`](crate::led_strips!).\n\n",
3045                "See the [led_strip module documentation](mod@crate::led_strip) for usage and examples."
3046            )]
3047            $vis struct $name {
3048                strip: $crate::led_strip::LedStrip<{ $len }, { $max_frames }>,
3049            }
3050
3051            impl $name {
3052                /// The number of LEDs in this strip (determined by the `len` parameter in [`led_strip!`] or [`led_strips!`](crate::led_strips!)).
3053                pub const LEN: usize = $len;
3054                /// Maximum number of animation frames (determined by `max_frames` parameter).
3055                pub const MAX_FRAMES: usize = $max_frames;
3056
3057                // Calculate max brightness from current budget
3058                const WORST_CASE_MA: u32 = ($len as u32) * 60;
3059                /// Maximum brightness level, automatically limited by the power budget specified in `max_current`.
3060                /// We assume, each LED draws 60 mA at full brightness.
3061                pub const MAX_BRIGHTNESS: u8 =
3062                    $max_current.max_brightness(Self::WORST_CASE_MA);
3063
3064                // Combined gamma correction and brightness scaling table
3065                const COMBO_TABLE: [u8; 256] = $crate::led_strip::generate_combo_table($gamma, Self::MAX_BRIGHTNESS);
3066
3067                /// Create a new LED strip instance of the struct type
3068                /// defined by [`led_strip!`] or [`led_strips!`](crate::led_strips!).
3069                ///
3070                /// See the [led_strip module documentation](mod@crate::led_strip) for example usage.
3071                ///
3072                /// The `pin`, `pio`, and `dma` parameters must correspond to the
3073                /// GPIO pin, PIO resource, and DMA channel specified in the macro.
3074                ///
3075                /// - The [`led_strip!`] macro defaults to `PIO0` and `DMA_CH0` if not specified.
3076                /// - The [`led_strips!`](crate::led_strips!) macro defaults to `PIO0` and
3077                /// automatically assigns consecutive DMA channels to each strip, starting at `DMA_CH0`.
3078                ///
3079                /// # Parameters
3080                ///
3081                /// - `pin`: GPIO pin for LED data signal
3082                /// - `pio`: PIO resource
3083                /// - `dma`: DMA channel for LED data transfer
3084                /// - `spawner`: Task spawner for background operations
3085                pub fn new(
3086                    pin: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>>,
3087                    pio: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pio>,
3088                    dma: impl Into<::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>>,
3089                    spawner: ::embassy_executor::Spawner,
3090                ) -> $crate::Result<&'static Self> {
3091                    static STRIP_STATIC: $crate::led_strip::LedStripStatic<{ $len }, { $max_frames }> =
3092                        $crate::led_strip::LedStrip::new_static();
3093                    static STRIP_CELL: ::static_cell::StaticCell<$name> = ::static_cell::StaticCell::new();
3094
3095                    let pin = pin.into();
3096                    let dma = dma.into();
3097
3098                    let sm0 = [<$name:snake _split_sm0>](pio);
3099                    let (bus, sm) = sm0.into_parts();
3100
3101                    let token = [<$name:snake _device_task>](
3102                        bus,
3103                        sm,
3104                        dma,
3105                        pin,
3106                        STRIP_STATIC.command_signal(),
3107                    );
3108                    spawner.spawn(token).map_err($crate::Error::TaskSpawn)?;
3109
3110                    let strip = $crate::led_strip::LedStrip::new(&STRIP_STATIC)?;
3111                    let instance = STRIP_CELL.init($name { strip });
3112                    Ok(instance)
3113                }
3114            }
3115
3116            impl ::core::ops::Deref for $name {
3117                type Target = $crate::led_strip::LedStrip<{ $len }, { $max_frames }>;
3118
3119                fn deref(&self) -> &Self::Target {
3120                    &self.strip
3121                }
3122            }
3123
3124            #[cfg(not(feature = "host"))]
3125            impl AsRef<$crate::led_strip::LedStrip<{ $len }, { $max_frames }>> for $name {
3126                fn as_ref(&self) -> &$crate::led_strip::LedStrip<{ $len }, { $max_frames }> {
3127                    &self.strip
3128                }
3129            }
3130
3131            #[::embassy_executor::task]
3132            async fn [<$name:snake _device_task>](
3133                bus: &'static $crate::led_strip::PioBus<'static, ::embassy_rp::peripherals::$pio>,
3134                sm: ::embassy_rp::pio::StateMachine<'static, ::embassy_rp::peripherals::$pio, 0>,
3135                dma: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$dma>,
3136                pin: ::embassy_rp::Peri<'static, ::embassy_rp::peripherals::$pin>,
3137                command_signal: &'static $crate::led_strip::LedStripCommandSignal<{ $len }, { $max_frames }>,
3138            ) -> ! {
3139                let program = bus.get_program();
3140                let driver = bus.with_common(|common| {
3141                    ::embassy_rp::pio_programs::ws2812::PioWs2812::<
3142                        ::embassy_rp::peripherals::$pio,
3143                        0,
3144                        { $len },
3145                        _
3146                    >::new(common, sm, dma, pin, program)
3147                });
3148                $crate::led_strip::led_strip_device_loop
3149                ::<
3150                    ::embassy_rp::peripherals::$pio,
3151                    0,
3152                    { $len },
3153                    { $max_frames },
3154                    _
3155                >(driver, command_signal, &$name::COMBO_TABLE).await
3156            }
3157        }
3158    };
3159}
3160
3161/// Macro for advanced PIO splitting (rarely needed).
3162///
3163/// **See the module docs.** Most users should use the group constructor instead.
3164#[doc(hidden)]
3165#[cfg(not(feature = "host"))]
3166#[macro_export]
3167macro_rules! pio_split {
3168    ($p:ident . PIO0) => {
3169        pio0_split($p.PIO0)
3170    };
3171    ($p:ident . PIO1) => {
3172        pio1_split($p.PIO1)
3173    };
3174    ($p:ident . PIO2) => {
3175        pio2_split($p.PIO2)
3176    };
3177}
3178
3179#[cfg(not(feature = "host"))]
3180pub use pio_split;
3181
3182#[cfg(not(feature = "host"))]
3183#[doc(inline)]
3184pub use led_strip;
3185#[cfg(not(feature = "host"))]
3186#[doc(inline)]
3187pub use led_strips;
3188
3189/// Electrical current budget configuration for LED strips.
3190///
3191/// See the [`led_strip!`](macro@crate::led_strip), [`led_strips!`](crate::led_strips),
3192/// and [`led2d!`](mod@crate::led2d) macro docs for usage and context.
3193#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3194pub enum Current {
3195    /// Limit brightness to stay within a specific milliamp budget.
3196    ///
3197    /// The `max_brightness` is automatically calculated to ensure the worst-case electrical current
3198    /// (all LEDs at full brightness) does not exceed this limit. For example, a 16-LED strip
3199    /// draws 960 mA at full brightness (assuming 60 mA per LED); with the default electrical current
3200    /// limit, brightness is capped at ~26%.
3201    ///
3202    /// See the [`led_strip!`](macro@crate::led_strip), [`led_strips!`](crate::led_strips),
3203    /// and [`led2d!`](mod@crate::led2d) macro docs for usage and context.
3204    Milliamps(u16),
3205    /// No limit — brightness stays at 100% (subject to practical hardware constraints like
3206    /// USB power delivery and the Pico's circuitry).
3207    ///
3208    /// See the [`led_strip!`](macro@crate::led_strip), [`led_strips!`](crate::led_strips),
3209    /// and [`led2d!`](mod@crate::led2d) macro docs for usage and context.
3210    Unlimited,
3211}
3212
3213impl Default for Current {
3214    fn default() -> Self {
3215        Self::Milliamps(250)
3216    }
3217}
3218
3219// Public so led_strip!/led_strips! expansions in downstream crates can reference it.
3220#[doc(hidden)]
3221/// Default electrical current budget for generated LED devices (`Current::Milliamps(250)`).
3222pub const MAX_CURRENT_DEFAULT: Current = Current::Milliamps(250);
3223
3224// Public so led_strip!/led_strips! expansions in downstream crates can reference it.
3225#[doc(hidden)]
3226/// Default maximum animation frames for generated LED devices (`16`).
3227pub const MAX_FRAMES_DEFAULT: usize = 16;
3228
3229impl Current {
3230    /// Calculate maximum brightness based on electrical current budget and worst-case electrical current draw.
3231    ///
3232    /// Returns 255 (full brightness) for Unlimited, or a scaled value for Milliamps.
3233    #[doc(hidden)] // Called by macro-generated code; not part of public API
3234    #[must_use]
3235    pub const fn max_brightness(self, worst_case_ma: u32) -> u8 {
3236        assert!(worst_case_ma > 0, "worst_case_ma must be positive");
3237        match self {
3238            Self::Milliamps(ma) => {
3239                let scale = (ma as u32 * 255) / worst_case_ma;
3240                if scale > 255 { 255 } else { scale as u8 }
3241            }
3242            Self::Unlimited => 255,
3243        }
3244    }
3245}