ws2812_pio/
lib.rs

1#![no_std]
2//! WS2812 PIO Driver for the RP2040
3//!
4//! This driver implements driving a WS2812 RGB LED strip from
5//! a PIO device of the RP2040 chip.
6//!
7//! You should reach to [Ws2812] if you run the main loop
8//! of your controller yourself and you want [Ws2812] to take
9//! a hold of your timer.
10//!
11//! In case you use `cortex-m-rtic` and can't afford this crate
12//! to wait blocking for you, you should try [Ws2812Direct].
13//! Bear in mind that you will have to take care of timing requirements
14//! yourself then.
15
16use embedded_hal::timer::CountDown;
17use fugit::{ExtU32, HertzU32, MicrosDurationU32};
18use rp2040_hal::{
19    gpio::AnyPin,
20    pio::{PIOExt, StateMachineIndex, Tx, UninitStateMachine, PIO},
21};
22use smart_leds_trait::SmartLedsWrite;
23use smart_leds_trait_0_2::SmartLedsWrite as SmartLedsWrite02;
24
25/// This is the WS2812 PIO Driver.
26///
27/// For blocking applications is recommended to use
28/// the [Ws2812] struct instead of this raw driver.
29///
30/// If you use this driver directly, you will need to
31/// take care of the timing expectations of the [Ws2812Direct::write]
32/// method.
33///
34/// Typical usage example:
35///```ignore
36/// use rp2040_hal::clocks::init_clocks_and_plls;
37/// let clocks = init_clocks_and_plls(...);
38/// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...);
39///
40/// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
41/// let mut ws = Ws2812Direct::new(
42///     pins.gpio4.into_mode(),
43///     &mut pio,
44///     sm0,
45///     clocks.peripheral_clock.freq(),
46/// );
47///
48/// // Then you will make sure yourself to not write too frequently:
49/// loop {
50///     use smart_leds::{SmartLedsWrite, RGB8};
51///     let color : RGB8 = (255, 0, 255).into();
52///
53///     ws.write([color].iter().copied()).unwrap();
54///     delay_for_at_least_60_microseconds();
55/// };
56///```
57pub struct Ws2812Direct<P, SM, I>
58where
59    I: AnyPin<Function = P::PinFunction>,
60    P: PIOExt,
61    SM: StateMachineIndex,
62{
63    tx: Tx<(P, SM)>,
64    _pin: I,
65}
66
67impl<P, SM, I> Ws2812Direct<P, SM, I>
68where
69    I: AnyPin<Function = P::PinFunction>,
70    P: PIOExt,
71    SM: StateMachineIndex,
72{
73    /// Creates a new instance of this driver.
74    pub fn new(
75        pin: I,
76        pio: &mut PIO<P>,
77        sm: UninitStateMachine<(P, SM)>,
78        clock_freq: fugit::HertzU32,
79    ) -> Self {
80        // prepare the PIO program
81        let side_set = pio::SideSet::new(false, 1, false);
82        let mut a = pio::Assembler::new_with_side_set(side_set);
83
84        const T1: u8 = 2; // start bit
85        const T2: u8 = 5; // data bit
86        const T3: u8 = 3; // stop bit
87        const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
88        const FREQ: HertzU32 = HertzU32::kHz(800);
89
90        let mut wrap_target = a.label();
91        let mut wrap_source = a.label();
92        let mut do_zero = a.label();
93        a.bind(&mut wrap_target);
94        // Do stop bit
95        a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
96        // Do start bit
97        a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
98        // Do data bit = 1
99        a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
100        a.bind(&mut do_zero);
101        // Do data bit = 0
102        a.nop_with_delay_and_side_set(T2 - 1, 0);
103        a.bind(&mut wrap_source);
104        let program = a.assemble_with_wrap(wrap_source, wrap_target);
105
106        // Install the program into PIO instruction memory.
107        let installed = pio.install(&program).unwrap();
108
109        // Configure the PIO state machine.
110        let bit_freq = FREQ * CYCLES_PER_BIT;
111        let mut int = clock_freq / bit_freq;
112        let rem = clock_freq - (int * bit_freq);
113        let frac = (rem * 256) / bit_freq;
114        assert!(
115            (1..=65536).contains(&int) && (int != 65536 || frac == 0),
116            "(System Clock / {}) must be within [1.0, 65536.0].",
117            bit_freq.to_kHz()
118        );
119
120        // 65536.0 is represented as 0 in the pio's clock divider
121        if int == 65536 {
122            int = 0;
123        }
124        // Using lossy conversion because range have been checked
125        let int: u16 = int as u16;
126        let frac: u8 = frac as u8;
127
128        let pin = pin.into();
129        let (mut sm, _, tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
130            // only use TX FIFO
131            .buffers(rp2040_hal::pio::Buffers::OnlyTx)
132            // Pin configuration
133            .side_set_pin_base(pin.id().num)
134            // OSR config
135            .out_shift_direction(rp2040_hal::pio::ShiftDirection::Left)
136            .autopull(true)
137            .pull_threshold(24)
138            .clock_divisor_fixed_point(int, frac)
139            .build(sm);
140
141        // Prepare pin's direction.
142        sm.set_pindirs([(pin.id().num, rp2040_hal::pio::PinDir::Output)]);
143
144        sm.start();
145
146        Self {
147            tx,
148            _pin: I::from(pin),
149        }
150    }
151}
152
153impl<P, SM, I> SmartLedsWrite for Ws2812Direct<P, SM, I>
154where
155    I: AnyPin<Function = P::PinFunction>,
156    P: PIOExt,
157    SM: StateMachineIndex,
158{
159    type Color = smart_leds_trait::RGB8;
160    type Error = ();
161    /// If you call this function, be advised that you will have to wait
162    /// at least 60 microseconds between calls of this function!
163    /// That means, either you get hold on a timer and the timing
164    /// requirements right your self, or rather use [Ws2812].
165    ///
166    /// Please bear in mind, that it still blocks when writing into the
167    /// PIO FIFO until all data has been transmitted to the LED chain.
168    fn write<T, J>(&mut self, iterator: T) -> Result<(), ()>
169    where
170        T: IntoIterator<Item = J>,
171        J: Into<Self::Color>,
172    {
173        for item in iterator {
174            let color: Self::Color = item.into();
175            let word =
176                (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8);
177
178            while !self.tx.write(word) {
179                cortex_m::asm::nop();
180            }
181        }
182        Ok(())
183    }
184}
185
186impl<P, SM, I> SmartLedsWrite02 for Ws2812Direct<P, SM, I>
187where
188    I: AnyPin<Function = P::PinFunction>,
189    P: PIOExt,
190    SM: StateMachineIndex,
191{
192    type Color = smart_leds_trait::RGB8;
193    type Error = ();
194    /// If you call this function, be advised that you will have to wait
195    /// at least 60 microseconds between calls of this function!
196    /// That means, either you get hold on a timer and the timing
197    /// requirements right your self, or rather use [Ws2812].
198    ///
199    /// Please bear in mind, that it still blocks when writing into the
200    /// PIO FIFO until all data has been transmitted to the LED chain.
201    fn write<T, J>(&mut self, iterator: T) -> Result<(), ()>
202    where
203        T: Iterator<Item = J>,
204        J: Into<Self::Color>,
205    {
206        SmartLedsWrite::write(self, iterator)
207    }
208}
209
210/// Instance of a WS2812 LED chain.
211///
212/// Use the [Ws2812::write] method to update the WS2812 LED chain.
213///
214/// Typical usage example:
215///```ignore
216/// use rp2040_hal::clocks::init_clocks_and_plls;
217/// let clocks = init_clocks_and_plls(...);
218/// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...);
219///
220/// let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
221///
222/// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
223/// let mut ws = Ws2812::new(
224///     pins.gpio4.into_mode(),
225///     &mut pio,
226///     sm0,
227///     clocks.peripheral_clock.freq(),
228///     timer.count_down(),
229/// );
230///
231/// loop {
232///     use smart_leds::{SmartLedsWrite, RGB8};
233///     let color : RGB8 = (255, 0, 255).into();
234///
235///     ws.write([color].iter().copied()).unwrap();
236///
237///     // Do other stuff here...
238/// };
239///```
240pub struct Ws2812<P, SM, C, I>
241where
242    C: CountDown,
243    I: AnyPin<Function = P::PinFunction>,
244    P: PIOExt,
245    SM: StateMachineIndex,
246{
247    driver: Ws2812Direct<P, SM, I>,
248    cd: C,
249}
250
251impl<P, SM, C, I> Ws2812<P, SM, C, I>
252where
253    C: CountDown,
254    I: AnyPin<Function = P::PinFunction>,
255    P: PIOExt,
256    SM: StateMachineIndex,
257{
258    /// Creates a new instance of this driver.
259    pub fn new(
260        pin: I,
261        pio: &mut PIO<P>,
262        sm: UninitStateMachine<(P, SM)>,
263        clock_freq: fugit::HertzU32,
264        cd: C,
265    ) -> Ws2812<P, SM, C, I> {
266        let driver = Ws2812Direct::new(pin, pio, sm, clock_freq);
267
268        Self { driver, cd }
269    }
270}
271
272impl<P, SM, I, C> SmartLedsWrite for Ws2812<P, SM, C, I>
273where
274    C: CountDown,
275    C::Time: From<MicrosDurationU32>,
276    I: AnyPin<Function = P::PinFunction>,
277    P: PIOExt,
278    SM: StateMachineIndex,
279{
280    type Color = smart_leds_trait::RGB8;
281    type Error = ();
282    fn write<T, J>(&mut self, iterator: T) -> Result<(), ()>
283    where
284        T: IntoIterator<Item = J>,
285        J: Into<Self::Color>,
286    {
287        self.driver.tx.clear_stalled_flag();
288        while !self.driver.tx.is_empty() && !self.driver.tx.has_stalled() {}
289
290        self.cd.start(60u32.micros());
291        let _ = nb::block!(self.cd.wait());
292
293        SmartLedsWrite::write(&mut self.driver, iterator)
294    }
295}
296
297impl<P, SM, I, C> SmartLedsWrite02 for Ws2812<P, SM, C, I>
298where
299    C: CountDown,
300    C::Time: From<MicrosDurationU32>,
301    I: AnyPin<Function = P::PinFunction>,
302    P: PIOExt,
303    SM: StateMachineIndex,
304{
305    type Color = smart_leds_trait::RGB8;
306    type Error = ();
307    fn write<T, J>(&mut self, iterator: T) -> Result<(), ()>
308    where
309        T: IntoIterator<Item = J>,
310        J: Into<Self::Color>,
311    {
312        SmartLedsWrite::write(self, iterator)
313    }
314}