diff --git a/Cargo.toml b/Cargo.toml index dab7e96..810bd42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.8.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] @@ -12,6 +12,6 @@ embedded-hal = "0.2.5" fugit = "0.3.5" rp2040-hal = "0.10" pio = "0.2.0" -smart-leds-trait = "0.2.1" +smart-leds-trait = "0.3.0" nb = "1.0.0" cortex-m = "0.7.3" diff --git a/src/lib.rs b/src/lib.rs index acc6481..e0b1a08 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::{ @@ -53,7 +54,31 @@ use smart_leds_trait::SmartLedsWrite; /// 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, @@ -61,13 +86,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( @@ -133,7 +160,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); @@ -145,17 +172,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! @@ -166,13 +239,12 @@ where /// PIO FIFO until all data has been transmitted to the LED chain. fn write(&mut self, iterator: T) -> Result<(), ()> where - T: Iterator, + T: IntoIterator, J: Into, { 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(); @@ -212,23 +284,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( @@ -237,26 +337,27 @@ 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 - T: Iterator, + T: IntoIterator, J: Into, { self.driver.tx.clear_stalled_flag();