sharp_memory_display/
lib.rs

1/*  This program is free software: you can redistribute it and/or modify
2    it under the terms of the GNU General Public License as published by
3    the Free Software Foundation, either version 3 of the License, or
4    (at your option) any later version.
5
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9    GNU General Public License for more details.
10
11    You should have received a copy of the GNU General Public License
12    along with this program.  If not, see <http://www.gnu.org/licenses/>.
13*/
14
15//! Support for SHARP memory-in-pixel display devices via [`embedded_graphics`].
16//!
17//! # Usage                                                                                  
18//! Create a new [`MemoryDisplay`] and simply use it as an [`embedded_graphics`]
19//! [`embedded_graphics::draw_target::DrawTarget`].
20//! You must flush the framebuffer with [`MemoryDisplay::flush_buffer`] for the buffer to be written to the
21//! screen.
22//!
23//! Please specify one of the supported displays via the Cargo `feature` flag. This sets
24//! appropriate buffer and target sizes for the device at compile time.
25#![no_std]
26extern crate bitvec;
27extern crate embedded_graphics;
28extern crate embedded_hal as hal;
29
30use bitvec::prelude::*;
31use core::ops::{BitOr, Not};
32use embedded_graphics::draw_target::DrawTarget;
33use embedded_graphics::pixelcolor::BinaryColor;
34use embedded_graphics::prelude::{OriginDimensions, Size};
35use embedded_graphics::Pixel;
36use hal::blocking::spi::Write;
37use hal::digital::v2::OutputPin;
38use hal::spi::Mode;
39
40#[cfg(not(any(
41    feature = "ls027b7dh01",
42    feature = "ls012b7dd06",
43    feature = "ls010b7dh04",
44    feature = "ls013b7dh05",
45    feature = "ls011b7dh03",
46)))]
47compile_error!("Please specify a display type via the feature flag");
48
49// Pull in the appropriate set of constants for the particular model of display
50#[cfg_attr(feature = "ls027b7dh01", path = "ls027b7dh01.rs")]
51#[cfg_attr(feature = "ls012b7dd06", path = "ls012b7dd06.rs")]
52#[cfg_attr(feature = "ls010b7dh04", path = "ls010b7dh04.rs")]
53#[cfg_attr(feature = "ls013b7dh05", path = "ls013b7dh05.rs")]
54#[cfg_attr(feature = "ls011b7dh03", path = "ls011b7dh03.rs")]
55mod display;
56
57const DUMMY_DATA: u8 = 0x00; // This can really be anything, but the spec sheet recommends 0s
58
59#[derive(Clone, Copy, PartialEq, Eq)]
60enum Vcom {
61    // For details see the document https://www.sharpsde.com/fileadmin/products/Displays/2016_SDE_App_Note_for_Memory_LCD_programming_V1.3.pdf
62    Lo = 0x00, // 0b_0______ M1 == 0
63    Hi = 0x40, // 0b_1______ M1 == 1
64}
65
66impl Not for Vcom {
67    type Output = Self;
68
69    fn not(self) -> Self::Output {
70        match self {
71            Vcom::Lo => Vcom::Hi,
72            Vcom::Hi => Vcom::Lo,
73        }
74    }
75}
76
77impl BitOr<Command> for Vcom {
78    type Output = u8;
79
80    fn bitor(self, rhs: Command) -> Self::Output {
81        (self as u8) | (rhs as u8)
82    }
83}
84
85#[derive(Clone, Copy, PartialEq, Eq)]
86enum Command {
87    // For details see the document https://www.sharpsde.com/fileadmin/products/Displays/2016_SDE_App_Note_for_Memory_LCD_programming_V1.3.pdf
88    Nop = 0x00,         // 0b0_0_____ M0 == 0, M2 == 0
89    ClearMemory = 0x20, // 0b0_1_____ M0 == 0, M2 == 1
90    WriteLine = 0x80,   // 0b1_0_____ M0 == 1, M2 == 0
91}
92
93impl BitOr<Vcom> for Command {
94    type Output = u8;
95
96    fn bitor(self, rhs: Vcom) -> Self::Output {
97        (self as u8) | (rhs as u8)
98    }
99}
100
101/// Mode to configure the SPI device in in order to communicate with the display.
102pub const MODE: Mode = display::MODE;
103
104// Local write buffer size for a line: line number, then data (e.g. 400px / 8 bits = 50 bytes), followed by 8-bit trailer
105const WRITE_BUFFER_SIZE: usize = (display::WIDTH / 8) + 2;
106
107pub struct MemoryDisplay<SPI, CS, DISP> {
108    spi: SPI,
109    cs: CS,
110    disp: DISP,
111    buffer: [BitArr!(for display::WIDTH, in u8, Lsb0); display::HEIGHT],
112    touched: BitArr!(for display::HEIGHT, in u8, Lsb0),
113    vcom: Vcom,
114    clear_state: BinaryColor,
115}
116
117impl<SPI, CS, DISP> OriginDimensions for MemoryDisplay<SPI, CS, DISP> {
118    fn size(&self) -> Size {
119        Size::new(display::WIDTH as u32, display::HEIGHT as u32)
120    }
121}
122
123impl<SPI, CS, DISP, E> DrawTarget for MemoryDisplay<SPI, CS, DISP>
124where
125    SPI: Write<u8, Error = E>,
126    CS: OutputPin,
127    DISP: OutputPin,
128{
129    type Color = BinaryColor;
130    type Error = E;
131
132    fn draw_iter<T>(&mut self, item_pixels: T) -> Result<(), E>
133    where
134        T: IntoIterator<Item = Pixel<Self::Color>>,
135    {
136        for Pixel(coord, color) in item_pixels {
137            if coord.x < 0 || coord.x >= (display::WIDTH as i32) || coord.y < 0 || coord.y >= (display::HEIGHT as i32) {
138                // Ignore attempts to draw outside of display bounds, continue to next pixel
139                continue
140            } else {
141                unsafe { self.set_pixel(coord.x as u32, coord.y as u32, color) };
142            }
143        }
144        Ok(())
145    }
146}
147
148impl<SPI, CS, DISP, E> MemoryDisplay<SPI, CS, DISP>
149where
150    SPI: Write<u8, Error = E>,
151    CS: OutputPin,
152    DISP: OutputPin,
153{
154    /// Create a new instance of `MemoryDisplay`.
155    ///
156    /// Please issue a `clear` before drawing to the display.
157    pub fn new(spi: SPI, mut cs: CS, mut disp: DISP) -> Self {
158        let _ = disp.set_low();
159        let _ = cs.set_low();
160
161        // The framebuffer: a byte-array for every line
162        let buffer = [bitarr![u8, Lsb0; 0; display::WIDTH]; display::HEIGHT];
163        let touched = bitarr![u8, Lsb0; 0; display::HEIGHT];
164
165        Self {
166            spi,
167            cs,
168            disp,
169            buffer,
170            touched,
171            vcom: Vcom::Hi,
172            clear_state: BinaryColor::On,
173        }
174    }
175
176    /// Set the value that screen buffer should be set to when issuing a clear command.
177    /// Note that this might be different from the state the hardware will set itself to.
178    /// You'll need to execute a flush_buffer following the call to clear if the
179    /// desired state differs from the default one in the hardware.
180    pub fn set_clear_state(&mut self, clear_state: BinaryColor) {
181        self.clear_state = clear_state;
182    }
183
184    /// Enable the LCD by driving the display pin high.
185    pub fn enable(&mut self) {
186        let _ = self.disp.set_high();
187    }
188
189    /// Disable the LCD.
190    pub fn disable(&mut self) {
191        let _ = self.disp.set_low();
192    }
193
194    /// Sets a single pixel value in the internal framebuffer.
195    ///
196    /// N.B. This function does no bounds checking! Attempting to draw
197    /// to a location outside the bounds of the display will result in
198    /// a panic.
199    pub unsafe fn set_pixel(&mut self, x: u32, y: u32, val: BinaryColor) {
200        let line_buffer = &mut self.buffer[y as usize];
201        line_buffer.set(x as usize, val.is_on());
202        self.touched.set(y as usize, true);
203    }
204
205    /// Draw all lines of the buffer to the screen which have changed since last calling this
206    /// function.
207    pub fn flush_buffer(&mut self) {
208        let _ = self.cs.set_high();
209
210        self.vcom = !self.vcom;
211        let _ = self.spi.write(&[Command::WriteLine | self.vcom]);
212
213        // Pack buffer into byte form and send
214        for y in self.touched.iter_ones() {
215            // Known problem with BitArr where if it's length isn't exactly divisible by the underlying storage size
216            // it will return indexes greater than its length. Break loop early if we've exceeded the size of buffer.
217            // https://github.com/bitvecto-rs/bitvec/issues/159 for details.
218            if y >= self.buffer.len() {
219                break;
220            }
221            // Write line number (starting at 1)
222            let line_no = (y + 1) as u8;
223            defmt::trace!("Writing line {}", line_no);
224            let line_no_bits_msb = BitSlice::<u8, Lsb0>::from_element(&line_no);
225            let line_no_bits = Self::swap(line_no_bits_msb);
226
227            let line_buffer_msb = self.buffer[y as usize];
228
229            let mut write_buffer = [0u8; WRITE_BUFFER_SIZE];
230            write_buffer[0] = line_no_bits;
231
232            let mut chunks = line_buffer_msb.chunks(8);
233            (1..(write_buffer.len() - 1)).for_each(|x| {
234                write_buffer[x] = Self::swap(chunks.next().unwrap());
235            });
236            // Technically this is supposed to be part of the address of the following line, but we'll just send it here because it's easier
237            write_buffer[write_buffer.len() - 1] = DUMMY_DATA;
238            let _ = self.spi.write(&write_buffer);
239        }
240
241        // Write the 16-bit frame trailer (first 8 bits come from the end of the last line written)
242        let _ = self.spi.write(&[DUMMY_DATA]);
243
244        let _ = self.cs.set_low();
245
246        self.touched.fill(false);
247    }
248
249    /// Contrary to the MSB order most SPI devices use, the memory-in-pixel displays use LSB byte
250    /// order. This function swaps the order of a single byte (viewed via a `BitSlice`) and converts it to `u8`.
251    pub fn swap(byte: &BitSlice<u8, Lsb0>) -> u8 {
252        // View slice with most-significant bit first (inverted)
253        let mut local_buffer = bitarr!(u8, Msb0; 0; 8);
254        for (i, bit) in byte.iter().by_ref().enumerate() {
255            local_buffer.set(i, *bit);
256        }
257        local_buffer.load::<u8>()
258    }
259
260    /// Clear just the internal framebuffer, without writing changes to the display.
261    pub fn clear_buffer(&mut self) {
262        for y in 0..(self.size().height as usize) {
263            let line_buffer = &mut self.buffer[y];
264            line_buffer.fill(self.clear_state.is_on());
265        }
266        self.touched.fill(true);
267    }
268
269    /// Clear the screen and the internal framebuffer.
270    pub fn clear(&mut self) {
271        self.clear_buffer();
272        self.vcom = !self.vcom;
273        self.write_spi(&[Command::ClearMemory | self.vcom, DUMMY_DATA]);
274    }
275
276    /// Puts the display into power saving mode. This can also be used to send
277    /// the VCOM signal which Sharp recommends sending at least once a second.
278    /// No actual harm seems to come from failing to do so however.
279    pub fn display_mode(&mut self) {
280        self.vcom = !self.vcom;
281        self.write_spi(&[Command::Nop | self.vcom, DUMMY_DATA]);
282    }
283
284    /// Internal function for handling the chip select
285    fn write_spi(&mut self, data: &[u8]) {
286        let _ = self.cs.set_high();
287
288        let _ = self.spi.write(data);
289
290        let _ = self.cs.set_low();
291    }
292}