From 7c10a4b57354e0ec3a7e78fb801e1e3e150080c9 Mon Sep 17 00:00:00 2001 From: Paul Daniel Faria Date: Mon, 6 May 2024 21:33:23 -0700 Subject: [PATCH 1/2] Add support for RGBW and custom color layouts. Adds direct support for RGBW color via smart_leds::RGBA8, and allows clients to implement custom support via new ColorFormat trait. --- Cargo.toml | 2 +- src/lib.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 115 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e8abe5..4b6668f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.9.0" edition = "2021" license = "Apache-2.0" description = "Driver implementation for the WS2812 smart LED using the RP2040's PIO peripheral." -documentation = "https://docs.rs/ws2812-pio" +documentation = "https://docs.rs/ws2812-pio" repository = "https://github.com/rp-rs/ws2812-pio-rs/" [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 75ad292..e67e1a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ //! Bear in mind that you will have to take care of timing requirements //! yourself then. +use core::marker::PhantomData; use embedded_hal::timer::CountDown; use fugit::{ExtU32, HertzU32, MicrosDurationU32}; use rp2040_hal::{ @@ -54,7 +55,31 @@ use smart_leds_trait_0_2::SmartLedsWrite as SmartLedsWrite02; /// delay_for_at_least_60_microseconds(); /// }; ///``` -pub struct Ws2812Direct +/// +/// Typical RGBW usage example: +///```ignore +/// use rp2040_hal::clocks::init_clocks_and_plls; +/// let clocks = init_clocks_and_plls(...); +/// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...); +/// +/// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); +/// let mut ws = Ws2812Direct::<_, _, _, smart_leds::RGBA8>::new( +/// pins.gpio4.into_mode(), +/// &mut pio, +/// sm0, +/// clocks.peripheral_clock.freq(), +/// ); +/// +/// // Then you will make sure yourself to not write too frequently: +/// loop { +/// use smart_leds::{SmartLedsWrite, RGBA8}; +/// let color : RGBA8 = (255, 0, 255, 127).into(); +/// +/// ws.write([color].iter().copied()).unwrap(); +/// delay_for_at_least_60_microseconds(); +/// }; +///``` +pub struct Ws2812Direct where I: AnyPin, P: PIOExt, @@ -62,13 +87,15 @@ where { tx: Tx<(P, SM)>, _pin: I, + _color_format: PhantomData, } -impl Ws2812Direct +impl Ws2812Direct where I: AnyPin, P: PIOExt, SM: StateMachineIndex, + CF: ColorFormat, { /// Creates a new instance of this driver. pub fn new( @@ -134,7 +161,7 @@ where // OSR config .out_shift_direction(rp2040_hal::pio::ShiftDirection::Left) .autopull(true) - .pull_threshold(24) + .pull_threshold(::COLOR_BYTES.num_bits()) .clock_divisor_fixed_point(int, frac) .build(sm); @@ -146,17 +173,63 @@ where Self { tx, _pin: I::from(pin), + _color_format: PhantomData, } } } -impl SmartLedsWrite for Ws2812Direct +/// Specify whether to use 3 or 4 bytes per led color. +pub enum ColorBytes { + ThreeBytes, + FourBytes, +} + +impl ColorBytes { + const fn num_bits(&self) -> u8 { + match self { + ColorBytes::ThreeBytes => 24, + ColorBytes::FourBytes => 32, + } + } +} + +/// Implement this trait to support a user-defined color format. +/// +/// smart_leds::RGB8 and smart_leds::RGBA are implemented by the ws2812-pio +/// crate. +pub trait ColorFormat { + /// Select the number of bytes per led. + const COLOR_BYTES: ColorBytes; + + /// Map the color to a 32-bit word. + fn to_word(self) -> u32; +} + +impl ColorFormat for smart_leds_trait::RGB8 { + const COLOR_BYTES: ColorBytes = ColorBytes::ThreeBytes; + fn to_word(self) -> u32 { + (u32::from(self.g) << 24) | (u32::from(self.r) << 16) | (u32::from(self.b) << 8) + } +} + +impl ColorFormat for smart_leds_trait::RGBA { + const COLOR_BYTES: ColorBytes = ColorBytes::FourBytes; + fn to_word(self) -> u32 { + (u32::from(self.g) << 24) + | (u32::from(self.r) << 16) + | (u32::from(self.b) << 8) + | (u32::from(self.a)) + } +} + +impl SmartLedsWrite for Ws2812Direct where I: AnyPin, P: PIOExt, SM: StateMachineIndex, + CF: ColorFormat, { - type Color = smart_leds_trait::RGB8; + type Color = CF; type Error = (); /// If you call this function, be advised that you will have to wait /// at least 60 microseconds between calls of this function! @@ -172,8 +245,7 @@ where { for item in iterator { let color: Self::Color = item.into(); - let word = - (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8); + let word = color.to_word(); while !self.tx.write(word) { cortex_m::asm::nop(); @@ -237,23 +309,51 @@ where /// // Do other stuff here... /// }; ///``` -pub struct Ws2812 +/// +/// Typical RGBW usage example: +///```ignore +/// use rp2040_hal::clocks::init_clocks_and_plls; +/// let clocks = init_clocks_and_plls(...); +/// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...); +/// +/// let timer = Timer::new(pac.TIMER, &mut pac.RESETS); +/// +/// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); +/// let mut ws = Ws2812::<_, _, _, _, smart_leds::RGBA8>::new( +/// pins.gpio4.into_mode(), +/// &mut pio, +/// sm0, +/// clocks.peripheral_clock.freq(), +/// timer.count_down(), +/// ); +/// +/// loop { +/// use smart_leds::{SmartLedsWrite, RGBA8}; +/// let color : RGBA8 = (255, 0, 255, 127).into(); +/// +/// ws.write([color].iter().copied()).unwrap(); +/// +/// // Do other stuff here... +/// }; +///``` +pub struct Ws2812 where C: CountDown, I: AnyPin, P: PIOExt, SM: StateMachineIndex, { - driver: Ws2812Direct, + driver: Ws2812Direct, cd: C, } -impl Ws2812 +impl Ws2812 where C: CountDown, I: AnyPin, P: PIOExt, SM: StateMachineIndex, + CF: ColorFormat, { /// Creates a new instance of this driver. pub fn new( @@ -262,22 +362,23 @@ where sm: UninitStateMachine<(P, SM)>, clock_freq: fugit::HertzU32, cd: C, - ) -> Ws2812 { + ) -> Ws2812 { let driver = Ws2812Direct::new(pin, pio, sm, clock_freq); Self { driver, cd } } } -impl SmartLedsWrite for Ws2812 +impl SmartLedsWrite for Ws2812 where C: CountDown, C::Time: From, I: AnyPin, P: PIOExt, SM: StateMachineIndex, + CF: ColorFormat, { - type Color = smart_leds_trait::RGB8; + type Color = CF; type Error = (); fn write(&mut self, iterator: T) -> Result<(), ()> where From 36298b2b4613455b8788af2bbe21b24bfbea03b5 Mon Sep 17 00:00:00 2001 From: melvyn Date: Wed, 30 Jul 2025 20:09:30 -0700 Subject: [PATCH 2/2] Use RBGW instead of RGBA, make constructors concrete --- src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e67e1a8..a201d27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,14 +56,14 @@ use smart_leds_trait_0_2::SmartLedsWrite as SmartLedsWrite02; /// }; ///``` /// -/// Typical RGBW usage example: +/// Usage for RGBW devices is similar: ///```ignore /// use rp2040_hal::clocks::init_clocks_and_plls; /// let clocks = init_clocks_and_plls(...); /// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...); /// /// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); -/// let mut ws = Ws2812Direct::<_, _, _, smart_leds::RGBA8>::new( +/// let mut ws = Ws2812Direct::new_sk6812( /// pins.gpio4.into_mode(), /// &mut pio, /// sm0, @@ -72,10 +72,10 @@ use smart_leds_trait_0_2::SmartLedsWrite as SmartLedsWrite02; /// /// // Then you will make sure yourself to not write too frequently: /// loop { -/// use smart_leds::{SmartLedsWrite, RGBA8}; -/// let color : RGBA8 = (255, 0, 255, 127).into(); +/// use smart_leds::{SmartLedsWrite, RGBW, White}; +/// let color = RGBW { r: 255, g: 0, b: 255, w: White(127) }; /// -/// ws.write([color].iter().copied()).unwrap(); +/// ws.write([color]).unwrap(); /// delay_for_at_least_60_microseconds(); /// }; ///``` @@ -97,12 +97,11 @@ where SM: StateMachineIndex, CF: ColorFormat, { - /// Creates a new instance of this driver. - pub fn new( + fn new_generic( pin: I, pio: &mut PIO

, sm: UninitStateMachine<(P, SM)>, - clock_freq: fugit::HertzU32, + clock_freq: HertzU32, ) -> Self { // prepare the PIO program let side_set = pio::SideSet::new(false, 1, false); @@ -178,6 +177,40 @@ where } } +impl Ws2812Direct +where + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + /// Creates a new instance of this driver. + pub fn new( + pin: I, + pio: &mut PIO

, + sm: UninitStateMachine<(P, SM)>, + clock_freq: HertzU32, + ) -> Self { + Self::new_generic(pin, pio, sm, clock_freq) + } +} + +impl Ws2812Direct> +where + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + /// Creates a new instance of this driver. + pub fn new_sk6812( + pin: I, + pio: &mut PIO

, + sm: UninitStateMachine<(P, SM)>, + clock_freq: HertzU32, + ) -> Self { + Self::new_generic(pin, pio, sm, clock_freq) + } +} + /// Specify whether to use 3 or 4 bytes per led color. pub enum ColorBytes { ThreeBytes, @@ -212,13 +245,13 @@ impl ColorFormat for smart_leds_trait::RGB8 { } } -impl ColorFormat for smart_leds_trait::RGBA { +impl ColorFormat for smart_leds_trait::RGBW { const COLOR_BYTES: ColorBytes = ColorBytes::FourBytes; fn to_word(self) -> u32 { (u32::from(self.g) << 24) | (u32::from(self.r) << 16) | (u32::from(self.b) << 8) - | (u32::from(self.a)) + | (u32::from(self.a.0)) } } @@ -310,7 +343,7 @@ where /// }; ///``` /// -/// Typical RGBW usage example: +/// Usage for RGBW devices is similar: ///```ignore /// use rp2040_hal::clocks::init_clocks_and_plls; /// let clocks = init_clocks_and_plls(...); @@ -319,7 +352,7 @@ where /// let timer = Timer::new(pac.TIMER, &mut pac.RESETS); /// /// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); -/// let mut ws = Ws2812::<_, _, _, _, smart_leds::RGBA8>::new( +/// let mut ws = Ws2812::new_sk6812( /// pins.gpio4.into_mode(), /// &mut pio, /// sm0, @@ -328,10 +361,10 @@ where /// ); /// /// loop { -/// use smart_leds::{SmartLedsWrite, RGBA8}; -/// let color : RGBA8 = (255, 0, 255, 127).into(); +/// use smart_leds::{SmartLedsWrite, RGBW, White}; +/// let color = RGBW { r: 255, g: 0, b: 255, w: White(127) }; /// -/// ws.write([color].iter().copied()).unwrap(); +/// ws.write([color]).unwrap(); /// /// // Do other stuff here... /// }; @@ -347,28 +380,48 @@ where cd: C, } -impl Ws2812 +impl Ws2812 where C: CountDown, I: AnyPin, P: PIOExt, SM: StateMachineIndex, - CF: ColorFormat, { /// Creates a new instance of this driver. pub fn new( pin: I, pio: &mut PIO

, sm: UninitStateMachine<(P, SM)>, - clock_freq: fugit::HertzU32, + clock_freq: HertzU32, cd: C, - ) -> Ws2812 { + ) -> Ws2812 { let driver = Ws2812Direct::new(pin, pio, sm, clock_freq); Self { driver, cd } } } +impl Ws2812> +where + C: CountDown, + I: AnyPin, + P: PIOExt, + SM: StateMachineIndex, +{ + /// Creates a new instance of this driver for SK6812 devices. + pub fn new_sk6812( + pin: I, + pio: &mut PIO

, + sm: UninitStateMachine<(P, SM)>, + clock_freq: HertzU32, + cd: C, + ) -> Ws2812> { + let driver = Ws2812Direct::new_sk6812(pin, pio, sm, clock_freq); + + Self { driver, cd } + } +} + impl SmartLedsWrite for Ws2812 where C: CountDown,