memory_lcd_spi/
framebuffer.rs

1//! Framebuffer for memory displays
2//!
3//! Considerations:
4//! - No flip or mirror support
5//! - Rotation is needed
6//! - TODO: double buffering
7
8use core::marker::PhantomData;
9
10use embedded_graphics_core::{
11    pixelcolor::BinaryColor,
12    prelude::{DrawTarget, OriginDimensions, RawData, Size},
13    primitives::Rectangle,
14    Pixel,
15};
16use embedded_hal::spi::SpiBus;
17
18use crate::pixelcolor::Rgb111;
19
20/// Display rotation.
21#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum Rotation {
23    /// No rotation.
24    Deg0,
25    /// 90° clockwise rotation.
26    Deg90,
27    /// 180° clockwise rotation.
28    Deg180,
29    /// 270° clockwise rotation.
30    Deg270,
31}
32
33impl Rotation {
34    #[inline]
35    fn is_column_row_swap(self) -> bool {
36        matches!(self, Rotation::Deg90 | Rotation::Deg270)
37    }
38}
39
40pub(crate) mod sealed {
41    use embedded_hal::spi::SpiBus;
42
43    pub trait FramebufferSpiUpdate {
44        fn update<SPI: SpiBus>(&self, spi: &mut SPI) -> Result<(), SPI::Error>;
45    }
46
47    pub trait DriverVariant {}
48}
49
50pub struct JDI;
51pub struct Sharp;
52
53impl sealed::DriverVariant for JDI {}
54impl sealed::DriverVariant for Sharp {}
55
56pub trait FramebufferType: OriginDimensions + DrawTarget + Default + sealed::FramebufferSpiUpdate {}
57
58pub struct Framebuffer4Bit<const WIDTH: u16, const HEIGHT: u16>
59where
60    [(); WIDTH as usize * HEIGHT as usize / 2]:,
61{
62    data: [u8; WIDTH as usize * HEIGHT as usize / 2],
63    rotation: Rotation,
64}
65
66impl<const WIDTH: u16, const HEIGHT: u16> Default for Framebuffer4Bit<WIDTH, HEIGHT>
67where
68    [(); WIDTH as usize * HEIGHT as usize / 2]:,
69{
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl<const WIDTH: u16, const HEIGHT: u16> sealed::FramebufferSpiUpdate for Framebuffer4Bit<WIDTH, HEIGHT>
76where
77    [(); WIDTH as usize * HEIGHT as usize / 2]:,
78{
79    // only burst update is supported
80    fn update<SPI: SpiBus>(&self, spi: &mut SPI) -> Result<(), SPI::Error> {
81        for i in 0..HEIGHT {
82            let start = (i as usize) * WIDTH as usize / 2;
83            let end = start + WIDTH as usize / 2;
84            let line_data = &self.data[start..end];
85            // NOTE: refer to manual, gate address counter is 1-based
86            spi.write(&[crate::CMD_UPDATE_4BIT, i as u8 + 1])?;
87            spi.write(line_data)?;
88        }
89        spi.write(&[0x00, 0x00])?;
90        Ok(())
91    }
92}
93
94impl<const WIDTH: u16, const HEIGHT: u16> Framebuffer4Bit<WIDTH, HEIGHT>
95where
96    [(); WIDTH as usize * HEIGHT as usize / 2]:,
97{
98    pub fn new() -> Self {
99        Self {
100            data: [0; WIDTH as usize * HEIGHT as usize / 2],
101            rotation: Rotation::Deg0,
102        }
103    }
104
105    pub fn set_rotation(&mut self, rotation: Rotation) {
106        self.rotation = rotation;
107    }
108
109    pub fn get_rotation(&self) -> Rotation {
110        self.rotation
111    }
112
113    pub(crate) fn set_pixel(&mut self, x: u16, y: u16, color: Rgb111) {
114        if self.rotation.is_column_row_swap() {
115            if x >= HEIGHT || y >= WIDTH {
116                return;
117            }
118        } else if y >= HEIGHT || x >= WIDTH {
119            return;
120        }
121        let x = x as usize;
122        let y = y as usize;
123
124        let (x, y) = match self.rotation {
125            Rotation::Deg0 => (x, y),
126            Rotation::Deg90 => (y, HEIGHT as usize - x - 1),
127            Rotation::Deg180 => (WIDTH as usize - x - 1, HEIGHT as usize - y - 1),
128            Rotation::Deg270 => (WIDTH as usize - y - 1, x),
129        };
130
131        let index = (y * WIDTH as usize + x) / 2;
132
133        let color = color.0.into_inner();
134
135        if x % 2 == 0 {
136            self.data[index] = (self.data[index] & 0b00001111) | (color << 4);
137        } else {
138            self.data[index] = (self.data[index] & 0b11110000) | color;
139        }
140    }
141}
142
143impl<const WIDTH: u16, const HEIGHT: u16> FramebufferType for Framebuffer4Bit<WIDTH, HEIGHT> where
144    [(); WIDTH as usize * HEIGHT as usize / 2]:
145{
146}
147
148impl<const WIDTH: u16, const HEIGHT: u16> OriginDimensions for Framebuffer4Bit<WIDTH, HEIGHT>
149where
150    [(); WIDTH as usize * HEIGHT as usize / 2]:,
151{
152    fn size(&self) -> Size {
153        match self.rotation {
154            Rotation::Deg0 | Rotation::Deg180 => Size::new(WIDTH as u32, HEIGHT as u32),
155            Rotation::Deg90 | Rotation::Deg270 => Size::new(HEIGHT as u32, WIDTH as u32),
156        }
157    }
158}
159
160impl<const WIDTH: u16, const HEIGHT: u16> DrawTarget for Framebuffer4Bit<WIDTH, HEIGHT>
161where
162    [(); WIDTH as usize * HEIGHT as usize / 2]:,
163{
164    type Color = Rgb111;
165
166    type Error = core::convert::Infallible;
167
168    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
169    where
170        I: IntoIterator<Item = Pixel<Self::Color>>,
171    {
172        for Pixel(coord, color) in pixels.into_iter() {
173            self.set_pixel(coord.x as u16, coord.y as u16, color);
174        }
175        Ok(())
176    }
177
178    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
179        let raw = color.0.into_inner() << 4 | color.0.into_inner();
180        self.data.fill(raw);
181        Ok(())
182    }
183
184    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
185        if self.rotation == Rotation::Deg0 && area.top_left.x % 2 == 0 && area.size.width % 2 == 0 {
186            let w = area.size.width as usize / 2;
187            let x_off = area.top_left.x as usize / 2;
188            let raw_pix = color.0.into_inner() << 4 | color.0.into_inner();
189
190            for y in area.top_left.y..area.top_left.y + area.size.height as i32 {
191                let start = (y as usize) * WIDTH as usize / 2 + x_off;
192                let end = start + w;
193                self.data[start..end].fill(raw_pix);
194            }
195            Ok(())
196        } else {
197            self.fill_contiguous(area, core::iter::repeat(color))
198        }
199    }
200}
201
202pub struct FramebufferBW<const WIDTH: u16, const HEIGHT: u16, TYPE: sealed::DriverVariant>
203where
204    [(); WIDTH as usize * HEIGHT as usize / 8]:,
205{
206    data: [u8; WIDTH as usize * HEIGHT as usize / 8],
207    rotation: Rotation,
208    _type: PhantomData<TYPE>,
209}
210
211impl<const WIDTH: u16, const HEIGHT: u16, TYPE: sealed::DriverVariant> Default for FramebufferBW<WIDTH, HEIGHT, TYPE>
212where
213    [(); WIDTH as usize * HEIGHT as usize / 8]:,
214{
215    fn default() -> Self {
216        Self::new()
217    }
218}
219
220impl<const WIDTH: u16, const HEIGHT: u16, TYPE: sealed::DriverVariant> FramebufferBW<WIDTH, HEIGHT, TYPE>
221where
222    [(); WIDTH as usize * HEIGHT as usize / 8]:,
223{
224    pub fn new() -> Self {
225        Self {
226            data: [0; WIDTH as usize * HEIGHT as usize / 8],
227            rotation: Rotation::Deg0,
228            _type: PhantomData,
229        }
230    }
231
232    pub fn set_rotation(&mut self, rotation: Rotation) {
233        self.rotation = rotation;
234    }
235
236    pub fn get_rotation(&self) -> Rotation {
237        self.rotation
238    }
239
240    pub(crate) fn set_pixel(&mut self, x: u16, y: u16, color: BinaryColor) {
241        if self.rotation.is_column_row_swap() {
242            if x >= HEIGHT || y >= WIDTH {
243                return;
244            }
245        } else if y >= HEIGHT || x >= WIDTH {
246            return;
247        }
248
249        let x = x as usize;
250        let y = y as usize;
251
252        let (x, y) = match self.rotation {
253            Rotation::Deg0 => (x, y),
254            Rotation::Deg90 => (y, HEIGHT as usize - x - 1),
255            Rotation::Deg180 => (WIDTH as usize - x - 1, HEIGHT as usize - y - 1),
256            Rotation::Deg270 => (WIDTH as usize - y - 1, x),
257        };
258
259        if y >= HEIGHT as usize || x >= WIDTH as usize {
260            return;
261        }
262
263        let index = y * WIDTH as usize + x;
264
265        if color.is_on() {
266            self.data[index / 8] |= 1 << (8 - (index % 8) - 1);
267        } else {
268            self.data[index / 8] &= !(1 << (8 - (index % 8) - 1));
269        }
270    }
271}
272
273impl<const WIDTH: u16, const HEIGHT: u16> sealed::FramebufferSpiUpdate for FramebufferBW<WIDTH, HEIGHT, JDI>
274where
275    [(); WIDTH as usize * HEIGHT as usize / 8]:,
276{
277    fn update<SPI: SpiBus>(&self, spi: &mut SPI) -> Result<(), SPI::Error> {
278        for i in 0..HEIGHT {
279            let start = (i as usize) * WIDTH as usize / 8;
280            let end = start + WIDTH as usize / 8;
281            let gate_line = &self.data[start..end];
282            // gate address is counted from 1
283            spi.write(&[crate::CMD_UPDATE_1BIT, i as u8 + 1])?;
284            spi.write(gate_line)?;
285        }
286        spi.write(&[0x00, 0x00])?;
287        Ok(())
288    }
289}
290
291impl<const WIDTH: u16, const HEIGHT: u16> sealed::FramebufferSpiUpdate for FramebufferBW<WIDTH, HEIGHT, Sharp>
292where
293    [(); WIDTH as usize * HEIGHT as usize / 8]:,
294{
295    fn update<SPI: SpiBus>(&self, spi: &mut SPI) -> Result<(), SPI::Error> {
296        for i in 0..HEIGHT {
297            let start = (i as usize) * WIDTH as usize / 8;
298            let end = start + WIDTH as usize / 8;
299            let gate_line = &self.data[start..end];
300            // gate address is counted from 1
301            spi.write(&[crate::CMD_UPDATE_1BIT, reverse_bits(i as u8 + 1)])?;
302            spi.write(gate_line)?;
303        }
304        spi.write(&[0x00, 0x00])?;
305        Ok(())
306    }
307}
308
309impl<const WIDTH: u16, const HEIGHT: u16, TYPE: sealed::DriverVariant> FramebufferType
310    for FramebufferBW<WIDTH, HEIGHT, TYPE>
311where
312    [(); WIDTH as usize * HEIGHT as usize / 8]:,
313    FramebufferBW<WIDTH, HEIGHT, TYPE>: sealed::FramebufferSpiUpdate,
314{
315}
316
317impl<const WIDTH: u16, const HEIGHT: u16, TYPE: sealed::DriverVariant> OriginDimensions
318    for FramebufferBW<WIDTH, HEIGHT, TYPE>
319where
320    [(); WIDTH as usize * HEIGHT as usize / 8]:,
321{
322    fn size(&self) -> Size {
323        match self.rotation {
324            Rotation::Deg0 | Rotation::Deg180 => Size::new(WIDTH as u32, HEIGHT as u32),
325            Rotation::Deg90 | Rotation::Deg270 => Size::new(HEIGHT as u32, WIDTH as u32),
326        }
327    }
328}
329
330impl<const WIDTH: u16, const HEIGHT: u16, TYPE: sealed::DriverVariant> DrawTarget for FramebufferBW<WIDTH, HEIGHT, TYPE>
331where
332    [(); WIDTH as usize * HEIGHT as usize / 8]:,
333{
334    type Color = BinaryColor;
335
336    type Error = core::convert::Infallible;
337
338    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
339    where
340        I: IntoIterator<Item = Pixel<Self::Color>>,
341    {
342        for Pixel(coord, color) in pixels.into_iter() {
343            self.set_pixel(coord.x as u16, coord.y as u16, color);
344        }
345        Ok(())
346    }
347
348    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
349        if color.is_on() {
350            self.data.fill(0xFF);
351        } else {
352            self.data.fill(0x00);
353        }
354        Ok(())
355    }
356}
357
358// For Sharp's SPI interface
359fn reverse_bits(mut b: u8) -> u8 {
360    let mut r = 0;
361    for _ in 0..8 {
362        r <<= 1;
363        r |= b & 1;
364        b >>= 1;
365    }
366    r
367}