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        let data = core::iter::repeat(0)
119            .take(S::pixel_len() * CDev::BPP)
120            .collect::<Data>();
121        Ok(Self {
122            driver,
123            data,
124            brightness: u8::MAX,
125            changed: true,
126            _phantom: Default::default(),
127        })
128    }
129
130    /// Create a new draw target.
131    pub fn new_with_rmt_driver(
132        tx: TxRmtDriver<'d>,
133    ) -> Result<Self, Ws2812Esp32RmtDriverError> {
134        let driver = Ws2812Esp32RmtDriver::<'d>::new_with_rmt_driver(tx)?;
135        let data = core::iter::repeat(0)
136            .take(S::pixel_len() * CDev::BPP)
137            .collect::<Data>();
138        Ok(Self {
139            driver,
140            data,
141            brightness: u8::MAX,
142            changed: true,
143            _phantom: Default::default(),
144        })
145    }
146
147    /// Set maximum brightness.
148    /// Each channel values of the returned shall be scaled down to `(brightness + 1) / 256`.
149    #[inline]
150    pub fn set_brightness(&mut self, brightness: u8) {
151        self.brightness = brightness;
152        self.changed = true;
153    }
154
155    /// Returns maximum brightness.
156    #[inline]
157    pub fn brightness(&self) -> u8 {
158        self.brightness
159    }
160
161    /// Clear with black.
162    /// Same operation as `clear(black_color)`.
163    pub fn clear_with_black(&mut self) -> Result<(), Ws2812Esp32RmtDriverError> {
164        self.data.fill(0);
165        self.changed = true;
166        Ok(())
167    }
168
169    /// Write changes from a framebuffer to the LED pixels
170    pub fn flush(&mut self) -> Result<(), Ws2812Esp32RmtDriverError> {
171        if self.changed {
172            self.driver.write_blocking(self.data.iter().copied())?;
173            self.changed = false;
174        }
175        Ok(())
176    }
177}
178
179impl<'d, CDraw, CDev, S, Data> OriginDimensions for LedPixelDrawTarget<'d, CDraw, CDev, S, Data>
180where
181    CDraw: RgbColor,
182    CDev: LedPixelColor + From<CDraw>,
183    S: LedPixelShape,
184    Data: DerefMut<Target = [u8]> + FromIterator<u8> + IntoIterator<Item = u8>,
185{
186    #[inline]
187    fn size(&self) -> Size {
188        S::size()
189    }
190}
191
192impl<'d, CDraw, CDev, S, Data> DrawTarget for LedPixelDrawTarget<'d, CDraw, CDev, S, Data>
193where
194    CDraw: RgbColor,
195    CDev: LedPixelColor + From<CDraw>,
196    S: LedPixelShape,
197    Data: DerefMut<Target = [u8]> + FromIterator<u8> + IntoIterator<Item = u8>,
198{
199    type Color = CDraw;
200    type Error = Ws2812Esp32RmtDriverError;
201
202    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
203    where
204        I: IntoIterator<Item = Pixel<Self::Color>>,
205    {
206        for Pixel(point, color) in pixels {
207            if let Some(pixel_index) = S::pixel_index(point) {
208                let index = pixel_index * CDev::BPP;
209                let color_device = CDev::from(color).brightness(self.brightness);
210                for (offset, v) in color_device.as_ref().iter().enumerate() {
211                    self.data[index + offset] = *v;
212                }
213                self.changed = true;
214            }
215        }
216        Ok(())
217    }
218
219    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
220        let c = CDev::from(color).brightness(self.brightness);
221        for (index, v) in self.data.iter_mut().enumerate() {
222            *v = c.as_ref()[index % CDev::BPP];
223        }
224        self.changed = true;
225        Ok(())
226    }
227}
228
229impl<
230        const N: usize,
231        const R_ORDER: usize,
232        const G_ORDER: usize,
233        const B_ORDER: usize,
234        const W_ORDER: usize,
235    > From<Rgb888> for LedPixelColorImpl<N, R_ORDER, G_ORDER, B_ORDER, W_ORDER>
236{
237    fn from(x: Rgb888) -> Self {
238        Self::new_with_rgb(x.r(), x.g(), x.b())
239    }
240}
241
242/// LED pixel shape of `L`-led strip
243pub type LedPixelStrip<const L: usize> = LedPixelMatrix<L, 1>;
244
245/// 8-bit GRB (total 24-bit pixel) LED draw target, Typical RGB LED (WS2812B/SK6812) draw target
246///
247/// * `S` - the LED pixel shape
248/// * `Data` - (optional) data storage type. It shall be `Vec`-like struct.
249///
250/// [`flush()`] operation shall be required to write changes from a framebuffer to the display.
251///
252/// For non-`alloc` no_std environment, `Data` should be explicitly set to some `Vec`-like struct:
253/// e.g., `heapless::Vec<u8, PIXEL_LEN>` where `PIXEL_LEN` equals to `S::size() * LedPixelColorGrb24::BPP`.
254///
255/// [`flush()`]: #method.flush
256///
257/// # Examples
258///
259/// ```
260/// #[cfg(not(target_vendor = "espressif"))]
261/// use ws2812_esp32_rmt_driver::mock::esp_idf_hal;
262///
263/// use embedded_graphics::pixelcolor::Rgb888;
264/// use embedded_graphics::prelude::*;
265/// use embedded_graphics::primitives::{Circle, PrimitiveStyle};
266/// use esp_idf_hal::peripherals::Peripherals;
267/// use ws2812_esp32_rmt_driver::lib_embedded_graphics::{LedPixelMatrix, Ws2812DrawTarget};
268///
269/// let peripherals = Peripherals::take().unwrap();
270/// let led_pin = peripherals.pins.gpio27;
271/// let channel = peripherals.rmt.channel0;
272/// let mut draw = Ws2812DrawTarget::<LedPixelMatrix<5, 5>>::new(channel, led_pin).unwrap();
273/// draw.set_brightness(40);
274/// draw.clear_with_black().unwrap();
275/// let mut translated_draw = draw.translated(Point::new(0, 0));
276/// Circle::new(Point::new(0, 0), 5)
277///     .into_styled(PrimitiveStyle::with_fill(Rgb888::RED))
278///     .draw(&mut translated_draw)
279///     .unwrap();
280/// draw.flush().unwrap();
281/// ```
282pub type Ws2812DrawTarget<'d, S, Data = LedPixelDrawTargetData> =
283    LedPixelDrawTarget<'d, Rgb888, LedPixelColorGrb24, S, Data>;
284
285#[cfg(test)]
286mod test {
287    use super::*;
288    use crate::mock::esp_idf_hal::peripherals::Peripherals;
289
290    #[test]
291    fn test_led_pixel_matrix() {
292        assert_eq!(LedPixelMatrix::<10, 5>::PIXEL_LEN, 50);
293        assert_eq!(LedPixelMatrix::<10, 5>::SIZE, Size::new(10, 5));
294        assert_eq!(LedPixelMatrix::<10, 5>::pixel_len(), 50);
295        assert_eq!(LedPixelMatrix::<10, 5>::size(), Size::new(10, 5));
296        assert_eq!(
297            LedPixelMatrix::<10, 5>::pixel_index(Point::new(0, 0)),
298            Some(0)
299        );
300        assert_eq!(
301            LedPixelMatrix::<10, 5>::pixel_index(Point::new(9, 4)),
302            Some(49)
303        );
304        assert_eq!(
305            LedPixelMatrix::<10, 5>::pixel_index(Point::new(-1, 0)),
306            None
307        );
308        assert_eq!(
309            LedPixelMatrix::<10, 5>::pixel_index(Point::new(0, -1)),
310            None
311        );
312        assert_eq!(
313            LedPixelMatrix::<10, 5>::pixel_index(Point::new(10, 4)),
314            None
315        );
316        assert_eq!(LedPixelMatrix::<10, 5>::pixel_index(Point::new(9, 5)), None);
317    }
318
319    #[test]
320    fn test_led_pixel_strip() {
321        assert_eq!(LedPixelStrip::<10>::PIXEL_LEN, 10);
322        assert_eq!(LedPixelStrip::<10>::SIZE, Size::new(10, 1));
323        assert_eq!(LedPixelStrip::<10>::pixel_len(), 10);
324        assert_eq!(LedPixelStrip::<10>::size(), Size::new(10, 1));
325        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(0, 0)), Some(0));
326        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(9, 0)), Some(9));
327        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(-1, 0)), None);
328        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(0, -1)), None);
329        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(10, 0)), None);
330        assert_eq!(LedPixelStrip::<10>::pixel_index(Point::new(9, 1)), None);
331    }
332
333    #[test]
334    fn test_ws2812draw_target_new() {
335        let peripherals = Peripherals::take().unwrap();
336        let led_pin = peripherals.pins.gpio0;
337        let channel = peripherals.rmt.channel0;
338
339        let draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>>::new(channel, led_pin).unwrap();
340        assert_eq!(draw.changed, true);
341        assert_eq!(
342            draw.data,
343            core::iter::repeat(0).take(150).collect::<Vec<_>>()
344        );
345    }
346
347    #[test]
348    fn test_ws2812draw_target_new_with_custom_data_struct() {
349        const VEC_CAPACITY: usize = LedPixelMatrix::<10, 5>::PIXEL_LEN * LedPixelColorGrb24::BPP;
350
351        let peripherals = Peripherals::take().unwrap();
352        let led_pin = peripherals.pins.gpio0;
353        let channel = peripherals.rmt.channel0;
354
355        let draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>, heapless::Vec<u8, VEC_CAPACITY>>::new(
356            channel, led_pin,
357        )
358        .unwrap();
359        assert_eq!(draw.changed, true);
360        assert_eq!(
361            draw.data,
362            core::iter::repeat(0)
363                .take(150)
364                .collect::<heapless::Vec<_, VEC_CAPACITY>>()
365        );
366    }
367
368    #[test]
369    fn test_ws2812draw_target_draw() {
370        let peripherals = Peripherals::take().unwrap();
371        let led_pin = peripherals.pins.gpio1;
372        let channel = peripherals.rmt.channel1;
373
374        let mut draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>>::new(channel, led_pin).unwrap();
375
376        draw.draw_iter(
377            [
378                Pixel(Point::new(0, 0), Rgb888::new(0x01, 0x02, 0x03)),
379                Pixel(Point::new(9, 4), Rgb888::new(0x04, 0x05, 0x06)),
380                Pixel(Point::new(10, 5), Rgb888::new(0xFF, 0xFF, 0xFF)), // out of matrix shape
381            ]
382            .iter()
383            .cloned(),
384        )
385        .unwrap();
386        assert_eq!(draw.changed, true);
387        assert_eq!(draw.data[0..3], [0x02, 0x01, 0x03]);
388        assert_eq!(draw.data[3..147], [0x00; 144]);
389        assert_eq!(draw.data[147..150], [0x05, 0x04, 0x06]);
390        draw.changed = false;
391
392        draw.clear(Rgb888::new(0x07, 0x08, 0x0A)).unwrap();
393        assert_eq!(draw.changed, true);
394        assert_eq!(
395            draw.data,
396            core::iter::repeat([0x08, 0x07, 0x0A])
397                .take(50)
398                .flatten()
399                .collect::<Vec<_>>()
400        );
401        draw.changed = false;
402
403        draw.clear_with_black().unwrap();
404        assert_eq!(draw.changed, true);
405        assert_eq!(draw.data, [0x00; 150]);
406        draw.changed = false;
407    }
408
409    #[test]
410    fn test_ws2812draw_target_flush() {
411        let peripherals = Peripherals::take().unwrap();
412        let led_pin = peripherals.pins.gpio2;
413        let channel = peripherals.rmt.channel2;
414
415        let mut draw = Ws2812DrawTarget::<LedPixelMatrix<10, 5>>::new(channel, led_pin).unwrap();
416
417        draw.changed = true;
418        draw.data.fill(0x01);
419        draw.driver.pixel_data = None;
420        draw.flush().unwrap();
421        assert_eq!(draw.driver.pixel_data.unwrap(), draw.data);
422        assert_eq!(draw.changed, false);
423
424        draw.driver.pixel_data = None;
425        draw.flush().unwrap();
426        assert_eq!(draw.driver.pixel_data, None);
427        assert_eq!(draw.changed, false);
428    }
429}