ws2812_spi/
lib.rs

1//! # Use ws2812 leds via spi
2//!
3//! - For usage with `smart-leds`
4//! - Implements the `SmartLedsWrite` trait
5//!
6//! Needs a type implementing the `spi::SpiBus` trait.
7//!
8//! The spi peripheral should run at 2MHz to 3.8 MHz
9
10// Timings for ws2812 from https://cpldcpu.files.wordpress.com/2014/01/ws2812_timing_table.png
11// Timings for sk6812 from https://cpldcpu.wordpress.com/2016/03/09/the-sk6812-another-intelligent-rgb-led/
12
13#![cfg_attr(not(feature = "std"), no_std)]
14
15use embedded_hal as hal;
16
17#[cfg(feature = "std")]
18pub mod hosted;
19pub mod prerendered;
20
21use hal::spi::{Mode, Phase, Polarity, SpiBus};
22
23use core::marker::PhantomData;
24use core::slice::from_ref;
25
26use smart_leds_trait::{SmartLedsWrite, RGB8, RGBW};
27
28/// SPI mode that can be used for this crate
29///
30/// Provided for convenience
31/// Doesn't really matter
32pub const MODE: Mode = Mode {
33    polarity: Polarity::IdleLow,
34    phase: Phase::CaptureOnFirstTransition,
35};
36
37pub mod devices {
38    pub struct Ws2812;
39    pub struct Sk6812w;
40}
41
42pub struct Ws2812<SPI, DEVICE = devices::Ws2812> {
43    spi: SPI,
44    device: PhantomData<DEVICE>,
45}
46
47impl<SPI, E> Ws2812<SPI>
48where
49    SPI: SpiBus<u8, Error = E>,
50{
51    /// Use ws2812 devices via spi
52    ///
53    /// The SPI bus should run within 2 MHz to 3.8 MHz
54    ///
55    /// You may need to look at the datasheet and your own hal to verify this.
56    ///
57    /// Please ensure that the mcu is pretty fast, otherwise weird timing
58    /// issues will occur
59    pub fn new(spi: SPI) -> Self {
60        Self {
61            spi,
62            device: PhantomData {},
63        }
64    }
65}
66
67impl<SPI, E> Ws2812<SPI, devices::Sk6812w>
68where
69    SPI: SpiBus<u8, Error = E>,
70{
71    /// Use sk6812w devices via spi
72    ///
73    /// The SPI bus should run within 2.3 MHz to 3.8 MHz at least.
74    ///
75    /// You may need to look at the datasheet and your own hal to verify this.
76    ///
77    /// Please ensure that the mcu is pretty fast, otherwise weird timing
78    /// issues will occur
79    // The spi frequencies are just the limits, the available timing data isn't
80    // complete
81    pub fn new_sk6812w(spi: SPI) -> Self {
82        Self {
83            spi,
84            device: PhantomData {},
85        }
86    }
87}
88
89impl<SPI, D, E> Ws2812<SPI, D>
90where
91    SPI: SpiBus<u8, Error = E>,
92{
93    /// Write a single byte for ws2812 devices
94    fn write_byte(&mut self, mut data: u8) -> Result<(), E> {
95        // Send two bits in one spi byte. High time first, then the low time
96        // The maximum for T0H is 500ns, the minimum for one bit 1063 ns.
97        // These result in the upper and lower spi frequency limits
98        let patterns = [0b1000_1000, 0b1000_1110, 0b11101000, 0b11101110];
99        for _ in 0..4 {
100            let bits = (data & 0b1100_0000) >> 6;
101            self.spi.write(from_ref(&patterns[bits as usize]))?;
102            data <<= 2;
103        }
104        Ok(())
105    }
106
107    fn reset(&mut self) -> Result<(), E> {
108        // Should be > 300μs, so for an SPI Freq. of 3.8MHz, we have to send at least 1140 low bits or 140 low bytes
109        for _ in 0..140 {
110            self.spi.write(from_ref(&0))?;
111        }
112        Ok(())
113    }
114}
115
116impl<SPI, E> SmartLedsWrite for Ws2812<SPI>
117where
118    SPI: SpiBus<u8, Error = E>,
119{
120    type Error = E;
121    type Color = RGB8;
122    /// Write all the items of an iterator to a ws2812 strip
123    fn write<T, I>(&mut self, iterator: T) -> Result<(), E>
124    where
125        T: IntoIterator<Item = I>,
126        I: Into<Self::Color>,
127    {
128        if cfg!(feature = "mosi_idle_high") {
129            self.reset()?;
130        }
131
132        for item in iterator {
133            let item = item.into();
134            self.write_byte(item.g)?;
135            self.write_byte(item.r)?;
136            self.write_byte(item.b)?;
137        }
138        self.reset()?;
139        Ok(())
140    }
141}
142
143impl<SPI, E> SmartLedsWrite for Ws2812<SPI, devices::Sk6812w>
144where
145    SPI: SpiBus<u8, Error = E>,
146{
147    type Error = E;
148    type Color = RGBW<u8, u8>;
149    /// Write all the items of an iterator to a ws2812 strip
150    fn write<T, I>(&mut self, iterator: T) -> Result<(), E>
151    where
152        T: IntoIterator<Item = I>,
153        I: Into<Self::Color>,
154    {
155        if cfg!(feature = "mosi_idle_high") {
156            self.reset()?;
157        }
158
159        for item in iterator {
160            let item = item.into();
161            self.write_byte(item.g)?;
162            self.write_byte(item.r)?;
163            self.write_byte(item.b)?;
164            self.write_byte(item.a.0)?;
165        }
166        self.reset()?;
167        Ok(())
168    }
169}