ws2812_esp32_rmt_driver/
lib_embedded_graphics.rs

1//! embedded-graphics draw target API.
2
3use crate::driver::color::{LedPixelColor, LedPixelColorGrb24, LedPixelColorImpl};
4use crate::driver::{Ws2812Esp32RmtDriver, Ws2812Esp32RmtDriverError};
5use core::marker::PhantomData;
6use core::ops::DerefMut;
7use embedded_graphics_core::draw_target::DrawTarget;
8use embedded_graphics_core::geometry::{OriginDimensions, Point, Size};
9use embedded_graphics_core::pixelcolor::{Rgb888, RgbColor};
10use embedded_graphics_core::Pixel;
11use esp_idf_hal::rmt::TxRmtDriver;
12
13#[cfg(not(target_vendor = "espressif"))]
14use crate::mock::esp_idf_hal;
15use esp_idf_hal::{gpio::OutputPin, peripheral::Peripheral, rmt::RmtChannel};
16
17/// LED pixel shape
18pub trait LedPixelShape {
19    /// Returns the number of pixels
20    fn pixel_len() -> usize {
21        let size = Self::size();
22        (size.width * size.height) as usize
23    }
24    /// Physical size of the LED pixel equipment.
25    fn size() -> Size;
26    /// Convert from `point` to the index.
27    /// Returns `None` if it is out of the bounds.
28    fn pixel_index(point: Point) -> Option<usize>;
29}
30
31/// LED pixel shape of `W`x`H` matrix
32pub struct LedPixelMatrix<const W: usize, const H: usize> {}
33
34impl<const W: usize, const H: usize> LedPixelMatrix<W, H> {
35    /// Physical size of the LED pixel matrix.
36    pub const SIZE: Size = Size::new(W as u32, H as u32);
37    /// The number of pixels.
38    pub const PIXEL_LEN: usize = W * H;
39}
40
41impl<const W: usize, const H: usize> LedPixelShape for LedPixelMatrix<W, H> {
42    #[inline]
43    fn size() -> Size {
44        Self::SIZE
45    }
46    #[inline]
47    fn pixel_len() -> usize {
48        Self::PIXEL_LEN
49    }
50
51    fn pixel_index(point: Point) -> Option<usize> {
52        if (0..W as i32).contains(&point.x) && (0..H as i32).contains(&point.y) {
53            Some((point.x + point.y * W as i32) as usize)
54        } else {
55            None
56        }
57    }
58}
59
60/// Default data storage type for `LedPixelDrawTarget`.
61#[cfg(feature = "std")]
62type LedPixelDrawTargetData = Vec<u8>;
63
64/// Default data storage type for `LedPixelDrawTarget`.
65#[cfg(all(not(feature = "std"), feature = "alloc"))]
66type LedPixelDrawTargetData = alloc::vec::Vec<u8>;
67
68/// Default data storage type for `LedPixelDrawTarget`.
69/// In case of heapless, allocate 256-byte capacity vector.
70#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
71type LedPixelDrawTargetData = heapless::Vec<u8, 256>;
72
73/// Target for embedded-graphics drawing operations of the LED pixels.
74///
75/// This is a generalization for the future extension.
76/// Use [`Ws2812DrawTarget`] for typical RGB LED (WS2812B/SK6812) consisting of 8-bit GRB (total 24-bit pixel).
77///
78/// * `CDraw` - color type for embedded-graphics drawing operations
79/// * `CDev` - the LED pixel color type (device dependant). It shall be convertible from `CDraw`.
80/// * `S` - the LED pixel shape
81/// * `Data` - (optional) data storage type. It shall be `Vec`-like struct.
82///
83/// [`flush()`] operation shall be required to write changes from a framebuffer to the display.
84///
85/// For non-`alloc` no_std environment, `Data` should be explicitly set to some `Vec`-like struct:
86/// e.g., `heapless::Vec<u8, PIXEL_LEN>` where `PIXEL_LEN` equals to `S::size() * CDev::BPP`.
87///
88/// [`flush()`]: #method.flush
89pub struct LedPixelDrawTarget<'d, CDraw, CDev, S, Data = LedPixelDrawTargetData>
90where
91    CDraw: RgbColor,
92    CDev: LedPixelColor + From<CDraw>,
93    S: LedPixelShape,
94    Data: DerefMut<Target = [u8]> + FromIterator<u8> + IntoIterator<Item = u8>,
95{
96    driver: Ws2812Esp32RmtDriver<'d>,
97    data: Data,
98    brightness: u8,
99    changed: bool,
100    _phantom: PhantomData<(CDraw, CDev, S, Data)>,
101}
102
103impl<'d, CDraw, CDev, S, Data> LedPixelDrawTarget<'d, CDraw, CDev, S, Data>
104where
105    CDraw: RgbColor,
106    CDev: LedPixelColor + From<CDraw>,
107    S: LedPixelShape,
108    Data: DerefMut<Target = [u8]> + FromIterator<u8> + IntoIterator<Item = u8>,
109{
110    /// Create a new draw target.
111    ///
112    /// `channel` shall be different between different `pin`.
113    pub fn new<C: RmtChannel>(
114        channel: impl Peripheral<P = C> + 'd,
115        pin: impl Peripheral<P = impl OutputPin> + 'd,
116    ) -> Result<Self, Ws2812Esp32RmtDriverError> {
117        let driver = Ws2812Esp32RmtDriver::<'d>::new(channel, pin)?;
118        Self::new_with_ws2812_driver(driver)
119    }
120
121    /// Create a new draw target with `TxRmtDriver`.
122    ///
123    /// The clock divider must be set to 1 for the `driver` configuration.
124    ///
125    /// ```
126    /// # #[cfg(not(target_vendor = "espressif"))]
127    /// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal;
128    /// #
129    /// # use esp_idf_hal::peripherals::Peripherals;
130    /// # use esp_idf_hal::rmt::config::TransmitConfig;
131    /// # use esp_idf_hal::rmt::TxRmtDriver;
132    /// #
133    /// # let peripherals = Peripherals::take().unwrap();
134    /// # let led_pin = peripherals.pins.gpio27;
135    /// # let channel = peripherals.rmt.channel0;
136    /// #
137    /// let driver_config = TransmitConfig::new()
138    ///     .clock_divider(1); // Required parameter.
139    /// let driver = TxRmtDriver::new(channel, led_pin, &driver_config).unwrap();
140    /// ```
141    pub fn new_with_rmt_driver(tx: TxRmtDriver<'d>) -> Result<Self, Ws2812Esp32RmtDriverError> {
142        let driver = Ws2812Esp32RmtDriver::<'d>::new_with_rmt_driver(tx)?;
143        Self::new_with_ws2812_driver(driver)
144    }
145
146    pub fn new_with_ws2812_driver(
147        driver: Ws2812Esp32RmtDriver<'d>,
148    ) -> Result<Self, Ws2812Esp32RmtDriverError> {
149        let data = core::iter::repeat(0)
150            .take(S::pixel_len() * CDev::BPP)
151            .collect::<Data>();
152        Ok(Self {
153            driver,
154            data,
155            brightness: u8::MAX,
156            changed: true,
157            _phantom: Default::default(),
158        })
159    }
160
161    /// Set maximum brightness.
162    /// Each channel values of the returned shall be scaled down to `(brightness + 1) / 256`.
163    #[inline]
164    pub fn set_brightness(&mut self, brightness: u8) {
165        self.brightness = brightness;
166        self.changed = true;
167    }
168
169    /// Returns maximum brightness.
170    #[inline]
171    pub fn brightness(&self) -> u8 {
172        self.brightness
173    }
174
175    /// Clear with black.
176    /// Same operation as `clear(black_color)`.
177    pub fn clear_with_black(&mut self) -> Result<(), Ws2812Esp32RmtDriverError> {
178        self.data.fill(0);
179        self.changed = true;
180        Ok(())
181    }
182
183    /// Write changes from a framebuffer to the LED pixels
184    pub fn flush(&mut self) -> Result<(), Ws2812Esp32RmtDriverError> {
185        if self.changed {
186            self.driver.write_blocking(self.data.iter().copied())?;
187            self.changed = false;
188        }
189        Ok(())
190    }
191}
192
193impl<'d, CDraw, CDev, S, Data> OriginDimensions for LedPixelDrawTarget<'d, CDraw, CDev, S, Data>
194where
195    CDraw: RgbColor,
196    CDev: LedPixelColor + From<CDraw>,
197    S: LedPixelShape,
198    Data: DerefMut<Target = [u8]> + FromIterator<u8> + IntoIterator<Item = u8>,
199{
200    #[inline]
201    fn size(&self) -> Size {
202        S::size()
203    }
204}
205
206impl<'d, CDraw, CDev, S, Data> DrawTarget for LedPixelDrawTarget<'d, CDraw, CDev, S, Data>
207where
208    CDraw: RgbColor,
209    CDev: LedPixelColor + From<CDraw>,
210    S: LedPixelShape,
211    Data: DerefMut<Target = [u8]> + FromIterator<u8> + IntoIterator<Item = u8>,
212{
213    type Color = CDraw;
214    type Error = Ws2812Esp32RmtDriverError;
215
216    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
217    where
218        I: IntoIterator<Item = Pixel<Self::Color>>,
219    {
220        for Pixel(point, color) in pixels {
221            if let Some(pixel_index) = S::pixel_index(point) {
222                let index = pixel_index * CDev::BPP;
223                let color_device = CDev::from(color).brightness(self.brightness);
224                for (offset, v) in color_device.as_ref().iter().enumerate() {
225                    self.data[index + offset] = *v;
226                }
227                self.changed = true;
228            }
229        }
230        Ok(())
231    }
232
233    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
234        let c = CDev::from(color).brightness(self.brightness);
235        for (index, v) in self.data.iter_mut().enumerate() {
236            *v = c.as_ref()[index % CDev::BPP];
237        }
238        self.changed = true;
239        Ok(())
240    }
241}
242
243impl<
244        const N: usize,
245        const R_ORDER: usize,
246        const G_ORDER: usize,
247        const B_ORDER: usize,
248        const W_ORDER: usize,
249    > From<Rgb888> for LedPixelColorImpl<N, R_ORDER, G_ORDER, B_ORDER, W_ORDER>
250{
251    fn from(x: Rgb888) -> Self {
252        Self::new_with_rgb(x.r(), x.g(), x.b())
253    }
254}
255
256/// LED pixel shape of `L`-led strip
257pub type LedPixelStrip<const L: usize> = LedPixelMatrix<L, 1>;
258
259/// 8-bit GRB (total 24-bit pixel) LED draw target, Typical RGB LED (WS2812B/SK6812) draw target
260///
261/// * `S` - the LED pixel shape
262/// * `Data` - (optional) data storage type. It shall be `Vec`-like struct.
263///
264/// [`flush()`] operation shall be required to write changes from a framebuffer to the display.
265///
266/// For non-`alloc` no_std environment, `Data` should be explicitly set to some `Vec`-like struct:
267/// e.g., `heapless::Vec<u8, PIXEL_LEN>` where `PIXEL_LEN` equals to `S::size() * LedPixelColorGrb24::BPP`.
268///
269/// [`flush()`]: #method.flush
270///
271/// # Examples
272///
273/// ```
274/// # #[cfg(not(target_vendor = "espressif"))]
275/// # use ws2812_esp32_rmt_driver::mock::esp_idf_hal;
276/// #
277/// use embedded_graphics::pixelcolor::Rgb888;
278/// use embedded_graphics::prelude::*;
279/// use embedded_graphics::primitives::{Circle, PrimitiveStyle};
280/// use esp_idf_hal::peripherals::Peripherals;
281/// use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelMatrix, Ws2812DrawTarget};
282///
283/// let peripherals = Peripherals::take().unwrap();
284/// let led_pin = peripherals.pins.gpio27;
285/// let channel = peripherals.rmt.channel0;
286/// let mut draw = Ws2812DrawTarget::<LedPixelMatrix<5, 5>>::new(channel, led_pin).unwrap();
287/// draw.set_brightness(40);
288/// draw.clear_with_black().unwrap();
289/// let mut translated_draw = draw.translated(Point::new(0, 0));
290/// Circle::new(Point::new(0, 0), 5)
291///     .into_styled(PrimitiveStyle::with_fill(Rgb888::RED))
292///     .draw(&mut translated_draw)
293///     .unwrap();
294/// draw.flush().unwrap();
295/// ```
296pub type Ws2812DrawTarget<'d, S, Data = LedPixelDrawTargetData> =
297    LedPixelDrawTarget<'d, Rgb888, LedPixelColorGrb24, S, Data>;
298
299#[cfg(test)]
300mod test {
301    use super::*;
302    use crate::mock::esp_idf_hal::peripherals::Peripherals;
303
304    #[test]
305    fn test_led_pixel_matrix() {
306        assert_eq!(LedPixelMatrix::<10, 5>::PIXEL_LEN, 50);
307        assert_eq!(LedPixelMatrix::<10, 5>::SIZE, Size::new(10, 5));
308        assert_eq!(LedPixelMatrix::<10, 5>::pixel_len(), 50);
309        assert_eq!(LedPixelMatrix::<10, 5>::size(), Size::new(10, 5));
310        assert_eq!(
311            LedPixelMatrix::<10, 5>::pixel_index(Point::new(0, 0)),
312            Some(0)
313        );
314        assert_eq!(
315            LedPixelMatrix::<10, 5>::pixel_index(Point::new(9, 4)),
316            Some(49)
317        );
318        assert_eq!(
319            LedPixelMatrix::<10, 5>::pixel_index(Point::new(-1, 0)),
320            None
321        );
322        assert_eq!(
323            LedPixelMatrix::<10, 5>::pixel_index(Point::new(0, -1)),
324            None
325        );
326        assert_eq!(
327            LedPixelMatrix::<10, 5>::pixel_index(Point::new(10, 4)),
328            None
329        );
330        assert_eq!(LedPixelMatrix::<10, 5>::pixel_index(Point::new(9, 5)), None);
331    }
332
333    #[test]
334    fn test_led_pixel_strip() {
335        assert_eq!(LedPixelStrip::<10>::PIXEL_LEN, 10);
336        assert_eq!(LedPixelStrip::<10>::SIZE, Size::new(10, 1));
337        assert_eq!(LedPixelStrip::<10>::pixel_len(), 10);
338        assert_eq!(LedPixelStrip::<10>::size(), Size::new(10, 1));
339        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(0, 0)), Some(0));
340        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(9, 0)), Some(9));
341        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(-1, 0)), None);
342        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(0, -1)), None);
343        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(10, 0)), None);
344        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(9, 1)), None);
345    }
346
347    #[test]
348    fn test_ws2812draw_target_new() {
349        let peripherals = Peripherals::take().unwrap();
350        let led_pin = peripherals.pins.gpio0;
351        let channel = peripherals.rmt.channel0;
352
353        let draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>>::new(channel, led_pin).unwrap();
354        assert_eq!(draw.changed, true);
355        assert_eq!(
356            draw.data,
357            core::iter::repeat(0).take(150).collect::<Vec<_>>()
358        );
359    }
360
361    #[test]
362    fn test_ws2812draw_target_new_with_custom_data_struct() {
363        const VEC_CAPACITY: usize = LedPixelMatrix::<10, 5>::PIXEL_LEN * LedPixelColorGrb24::BPP;
364
365        let peripherals = Peripherals::take().unwrap();
366        let led_pin = peripherals.pins.gpio0;
367        let channel = peripherals.rmt.channel0;
368
369        let draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>, heapless::Vec<u8, VEC_CAPACITY>>::new(
370            channel, led_pin,
371        )
372        .unwrap();
373        assert_eq!(draw.changed, true);
374        assert_eq!(
375            draw.data,
376            core::iter::repeat(0)
377                .take(150)
378                .collect::<heapless::Vec<_, VEC_CAPACITY>>()
379        );
380    }
381
382    #[test]
383    fn test_ws2812draw_target_draw() {
384        let peripherals = Peripherals::take().unwrap();
385        let led_pin = peripherals.pins.gpio1;
386        let channel = peripherals.rmt.channel1;
387
388        let mut draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>>::new(channel, led_pin).unwrap();
389
390        draw.draw_iter(
391            [
392                Pixel(Point::new(0, 0), Rgb888::new(0x01, 0x02, 0x03)),
393                Pixel(Point::new(9, 4), Rgb888::new(0x04, 0x05, 0x06)),
394                Pixel(Point::new(10, 5), Rgb888::new(0xFF, 0xFF, 0xFF)), // out of matrix shape
395            ]
396            .iter()
397            .cloned(),
398        )
399        .unwrap();
400        assert_eq!(draw.changed, true);
401        assert_eq!(draw.data[0..3], [0x02, 0x01, 0x03]);
402        assert_eq!(draw.data[3..147], [0x00; 144]);
403        assert_eq!(draw.data[147..150], [0x05, 0x04, 0x06]);
404        draw.changed = false;
405
406        draw.clear(Rgb888::new(0x07, 0x08, 0x0A)).unwrap();
407        assert_eq!(draw.changed, true);
408        assert_eq!(
409            draw.data,
410            core::iter::repeat([0x08, 0x07, 0x0A])
411                .take(50)
412                .flatten()
413                .collect::<Vec<_>>()
414        );
415        draw.changed = false;
416
417        draw.clear_with_black().unwrap();
418        assert_eq!(draw.changed, true);
419        assert_eq!(draw.data, [0x00; 150]);
420        draw.changed = false;
421    }
422
423    #[test]
424    fn test_ws2812draw_target_flush() {
425        let peripherals = Peripherals::take().unwrap();
426        let led_pin = peripherals.pins.gpio2;
427        let channel = peripherals.rmt.channel2;
428
429        let mut draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>>::new(channel, led_pin).unwrap();
430
431        draw.changed = true;
432        draw.data.fill(0x01);
433        draw.driver.pixel_data = None;
434        draw.flush().unwrap();
435        assert_eq!(draw.driver.pixel_data.unwrap(), draw.data);
436        assert_eq!(draw.changed, false);
437
438        draw.driver.pixel_data = None;
439        draw.flush().unwrap();
440        assert_eq!(draw.driver.pixel_data, None);
441        assert_eq!(draw.changed, false);
442    }
443}