Skip to main content

epd_waveshare/epd2in9_v2/
mod.rs

1//! A simple Driver for the Waveshare 2.9" E-Ink Display V2 via SPI
2//!
3//! Specification: <https://www.waveshare.com/w/upload/7/79/2.9inch-e-paper-v2-specification.pdf>
4//!
5//! # Example for the 2.9 in E-Ink Display V2
6//!
7//!```rust, no_run
8//!# use embedded_hal_mock::eh1::*;
9//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
10//!use embedded_graphics::{
11//!    pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
12//!};
13//!use epd_waveshare::{epd2in9_v2::*, prelude::*};
14//!#
15//!# let expectations = [];
16//!# let mut spi = spi::Mock::new(&expectations);
17//!# let expectations = [];
18//!# let cs_pin = digital::Mock::new(&expectations);
19//!# let busy_in = digital::Mock::new(&expectations);
20//!# let dc = digital::Mock::new(&expectations);
21//!# let rst = digital::Mock::new(&expectations);
22//!# let mut delay = delay::NoopDelay::new();
23//!
24//!// Setup EPD
25//!let mut epd = Epd2in9::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
26//!
27//!// Use display graphics from embedded-graphics
28//!let mut display = Display2in9::default();
29//!
30//!// Use embedded graphics for drawing a line
31//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
32//!    .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
33//!    .draw(&mut display);
34//!
35//!// Display updated frame
36//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
37//!epd.display_frame(&mut spi, &mut delay)?;
38//!
39//!// Draw something new here
40//!
41//!// Display new image as a base image for further quick refreshes
42//!epd.update_old_frame(&mut spi, &display.buffer(), &mut delay)?;
43//!epd.display_frame(&mut spi, &mut delay)?;
44//!
45//!// Update image here
46//!
47//!// quick refresh of updated pixels
48//!epd.update_new_frame(&mut spi, &display.buffer(), &mut delay)?;
49//!epd.display_new_frame(&mut spi, &mut delay)?;
50//!
51//!// Set the EPD to sleep
52//!epd.sleep(&mut spi, &mut delay)?;
53//!# Ok(())
54//!# }
55//!```
56
57/// Width of epd2in9 in pixels
58pub const WIDTH: u32 = 128;
59/// Height of epd2in9 in pixels
60pub const HEIGHT: u32 = 296;
61/// Default Background Color (white)
62pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
63const IS_BUSY_LOW: bool = false;
64const SINGLE_BYTE_WRITE: bool = true;
65
66const LUT_PARTIAL_2IN9: [u8; 159] = [
67    0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0,
68    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
69    0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
70    0x0, 0x0, 0x0, 0x0, 0x0, 0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
71    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
72    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
73    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
74    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22,
75    0x22, 0x0, 0x0, 0x0, 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36,
76];
77
78const WS_20_30: [u8; 159] = [
79    0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x10, 0x66, 0x0, 0x0, 0x0, 0x0,
80    0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
81    0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
82    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0xA, 0xA, 0x0, 0xA, 0xA, 0x0,
83    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
84    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
85    0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x8, 0x0, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
86    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x44, 0x44, 0x44,
87    0x44, 0x44, 0x0, 0x0, 0x0, 0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
88];
89
90use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
91
92use crate::type_a::command::Command;
93
94use crate::color::Color;
95
96use crate::traits::*;
97
98use crate::buffer_len;
99use crate::interface::DisplayInterface;
100use crate::traits::QuickRefresh;
101
102/// Display with Fullsize buffer for use with the 2in9 EPD V2
103#[cfg(feature = "graphics")]
104pub type Display2in9 = crate::graphics::Display<
105    WIDTH,
106    HEIGHT,
107    false,
108    { buffer_len(WIDTH as usize, HEIGHT as usize) },
109    Color,
110>;
111
112/// Epd2in9 driver
113///
114pub struct Epd2in9<SPI, BUSY, DC, RST, DELAY> {
115    /// SPI
116    interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
117    /// Color
118    background_color: Color,
119    /// Refresh LUT
120    refresh: RefreshLut,
121}
122
123impl<SPI, BUSY, DC, RST, DELAY> Epd2in9<SPI, BUSY, DC, RST, DELAY>
124where
125    SPI: SpiDevice,
126    BUSY: InputPin,
127    DC: OutputPin,
128    RST: OutputPin,
129    DELAY: DelayNs,
130{
131    fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
132        self.interface.reset(delay, 10_000, 2_000);
133
134        self.wait_until_idle(spi, delay)?;
135        self.interface.cmd(spi, Command::SwReset)?;
136        self.wait_until_idle(spi, delay)?;
137
138        // 3 Databytes:
139        // A[7:0]
140        // 0.. A[8]
141        // 0.. B[2:0]
142        // Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?)
143        self.interface
144            .cmd_with_data(spi, Command::DriverOutputControl, &[0x27, 0x01, 0x00])?;
145
146        // One Databyte with default value 0x03
147        //  -> address: x increment, y increment, address counter is updated in x direction
148        self.interface
149            .cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?;
150
151        self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
152
153        self.interface
154            .cmd_with_data(spi, Command::DisplayUpdateControl1, &[0x00, 0x80])?;
155
156        self.set_ram_counter(spi, delay, 0, 0)?;
157
158        self.wait_until_idle(spi, delay)?;
159
160        // set LUT by host
161        self.set_lut_helper(spi, delay, &WS_20_30[0..153])?;
162        self.interface
163            .cmd_with_data(spi, Command::WriteLutRegisterEnd, &WS_20_30[153..154])?;
164        self.interface
165            .cmd_with_data(spi, Command::GateDrivingVoltage, &WS_20_30[154..155])?;
166        self.interface
167            .cmd_with_data(spi, Command::SourceDrivingVoltage, &WS_20_30[155..158])?;
168        self.interface
169            .cmd_with_data(spi, Command::WriteVcomRegister, &WS_20_30[158..159])?;
170
171        Ok(())
172    }
173}
174
175impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
176    for Epd2in9<SPI, BUSY, DC, RST, DELAY>
177where
178    SPI: SpiDevice,
179    BUSY: InputPin,
180    DC: OutputPin,
181    RST: OutputPin,
182    DELAY: DelayNs,
183{
184    type DisplayColor = Color;
185    fn width(&self) -> u32 {
186        WIDTH
187    }
188
189    fn height(&self) -> u32 {
190        HEIGHT
191    }
192
193    fn new(
194        spi: &mut SPI,
195        busy: BUSY,
196        dc: DC,
197        rst: RST,
198        delay: &mut DELAY,
199        delay_us: Option<u32>,
200    ) -> Result<Self, SPI::Error> {
201        let interface = DisplayInterface::new(busy, dc, rst, delay_us);
202
203        let mut epd = Epd2in9 {
204            interface,
205            background_color: DEFAULT_BACKGROUND_COLOR,
206            refresh: RefreshLut::Full,
207        };
208
209        epd.init(spi, delay)?;
210
211        Ok(epd)
212    }
213
214    fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
215        self.wait_until_idle(spi, delay)?;
216        // 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode
217        self.interface
218            .cmd_with_data(spi, Command::DeepSleepMode, &[0x01])?;
219        Ok(())
220    }
221
222    fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
223        self.init(spi, delay)?;
224        Ok(())
225    }
226
227    fn update_frame(
228        &mut self,
229        spi: &mut SPI,
230        buffer: &[u8],
231        delay: &mut DELAY,
232    ) -> Result<(), SPI::Error> {
233        self.wait_until_idle(spi, delay)?;
234        self.interface.cmd_with_data(spi, Command::WriteRam, buffer)
235    }
236
237    fn update_partial_frame(
238        &mut self,
239        spi: &mut SPI,
240        delay: &mut DELAY,
241        buffer: &[u8],
242        x: u32,
243        y: u32,
244        width: u32,
245        height: u32,
246    ) -> Result<(), SPI::Error> {
247        //TODO This is copied from epd2in9 but it seems not working. Partial refresh supported by version 2?
248        self.wait_until_idle(spi, delay)?;
249        self.set_ram_area(spi, x, y, x + width, y + height)?;
250        self.set_ram_counter(spi, delay, x, y)?;
251
252        self.interface
253            .cmd_with_data(spi, Command::WriteRam, buffer)?;
254        Ok(())
255    }
256
257    /// actually is the "Turn on Display" sequence
258    fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
259        self.wait_until_idle(spi, delay)?;
260        // Enable clock signal, Enable Analog, Load temperature value, DISPLAY with DISPLAY Mode 1, Disable Analog, Disable OSC
261        self.interface
262            .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC7])?;
263        self.interface.cmd(spi, Command::MasterActivation)?;
264        self.wait_until_idle(spi, delay)?;
265        Ok(())
266    }
267
268    fn update_and_display_frame(
269        &mut self,
270        spi: &mut SPI,
271        buffer: &[u8],
272        delay: &mut DELAY,
273    ) -> Result<(), SPI::Error> {
274        self.update_frame(spi, buffer, delay)?;
275        self.display_frame(spi, delay)?;
276        Ok(())
277    }
278
279    fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
280        self.wait_until_idle(spi, delay)?;
281
282        // clear the ram with the background color
283        let color = self.background_color.get_byte_value();
284
285        self.interface.cmd(spi, Command::WriteRam)?;
286        self.interface
287            .data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
288        self.interface.cmd(spi, Command::WriteRam2)?;
289        self.interface.data_x_times(spi, color, WIDTH / 8 * HEIGHT)
290    }
291
292    fn set_background_color(&mut self, background_color: Color) {
293        self.background_color = background_color;
294    }
295
296    fn background_color(&self) -> &Color {
297        &self.background_color
298    }
299
300    fn set_lut(
301        &mut self,
302        _spi: &mut SPI,
303        _delay: &mut DELAY,
304        refresh_rate: Option<RefreshLut>,
305    ) -> Result<(), SPI::Error> {
306        if let Some(refresh_lut) = refresh_rate {
307            self.refresh = refresh_lut;
308        }
309        Ok(())
310    }
311
312    fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
313        self.interface.wait_until_idle(delay, IS_BUSY_LOW);
314        Ok(())
315    }
316}
317
318impl<SPI, BUSY, DC, RST, DELAY> Epd2in9<SPI, BUSY, DC, RST, DELAY>
319where
320    SPI: SpiDevice,
321    BUSY: InputPin,
322    DC: OutputPin,
323    RST: OutputPin,
324    DELAY: DelayNs,
325{
326    fn use_full_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
327        // choose full frame/ram
328        self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
329
330        // start from the beginning
331        self.set_ram_counter(spi, delay, 0, 0)
332    }
333
334    fn set_ram_area(
335        &mut self,
336        spi: &mut SPI,
337        start_x: u32,
338        start_y: u32,
339        end_x: u32,
340        end_y: u32,
341    ) -> Result<(), SPI::Error> {
342        assert!(start_x < end_x);
343        assert!(start_y < end_y);
344
345        // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
346        // aren't relevant
347        self.interface.cmd_with_data(
348            spi,
349            Command::SetRamXAddressStartEndPosition,
350            &[(start_x >> 3) as u8, (end_x >> 3) as u8],
351        )?;
352
353        // 2 Databytes: A[7:0] & 0..A[8] for each - start and end
354        self.interface.cmd_with_data(
355            spi,
356            Command::SetRamYAddressStartEndPosition,
357            &[
358                start_y as u8,
359                (start_y >> 8) as u8,
360                end_y as u8,
361                (end_y >> 8) as u8,
362            ],
363        )
364    }
365
366    fn set_ram_counter(
367        &mut self,
368        spi: &mut SPI,
369        delay: &mut DELAY,
370        x: u32,
371        y: u32,
372    ) -> Result<(), SPI::Error> {
373        self.wait_until_idle(spi, delay)?;
374        // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
375        // aren't relevant
376        self.interface
377            .cmd_with_data(spi, Command::SetRamXAddressCounter, &[x as u8])?;
378
379        // 2 Databytes: A[7:0] & 0..A[8]
380        self.interface.cmd_with_data(
381            spi,
382            Command::SetRamYAddressCounter,
383            &[y as u8, (y >> 8) as u8],
384        )?;
385        Ok(())
386    }
387
388    /// Set your own LUT, this function is also used internally for set_lut
389    fn set_lut_helper(
390        &mut self,
391        spi: &mut SPI,
392        delay: &mut DELAY,
393        buffer: &[u8],
394    ) -> Result<(), SPI::Error> {
395        self.wait_until_idle(spi, delay)?;
396        self.interface
397            .cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
398        self.wait_until_idle(spi, delay)?;
399        Ok(())
400    }
401}
402
403impl<SPI, BUSY, DC, RST, DELAY> QuickRefresh<SPI, BUSY, DC, RST, DELAY>
404    for Epd2in9<SPI, BUSY, DC, RST, DELAY>
405where
406    SPI: SpiDevice,
407    BUSY: InputPin,
408    DC: OutputPin,
409    RST: OutputPin,
410    DELAY: DelayNs,
411{
412    /// To be followed immediately by `update_new_frame`.
413    fn update_old_frame(
414        &mut self,
415        spi: &mut SPI,
416        buffer: &[u8],
417        delay: &mut DELAY,
418    ) -> Result<(), SPI::Error> {
419        self.wait_until_idle(spi, delay)?;
420        self.interface
421            .cmd_with_data(spi, Command::WriteRam2, buffer)
422    }
423
424    /// To be used immediately after `update_old_frame`.
425    fn update_new_frame(
426        &mut self,
427        spi: &mut SPI,
428        buffer: &[u8],
429        delay: &mut DELAY,
430    ) -> Result<(), SPI::Error> {
431        self.wait_until_idle(spi, delay)?;
432        self.interface.reset(delay, 10_000, 2_000);
433
434        self.set_lut_helper(spi, delay, &LUT_PARTIAL_2IN9)?;
435        self.interface.cmd_with_data(
436            spi,
437            Command::WriteOtpSelection,
438            &[0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00],
439        )?;
440        self.interface
441            .cmd_with_data(spi, Command::BorderWaveformControl, &[0x80])?;
442        self.interface
443            .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC0])?;
444        self.interface.cmd(spi, Command::MasterActivation)?;
445
446        self.wait_until_idle(spi, delay)?;
447
448        self.use_full_frame(spi, delay)?;
449
450        self.interface
451            .cmd_with_data(spi, Command::WriteRam, buffer)?;
452        Ok(())
453    }
454
455    /// For a quick refresh of the new updated frame. To be used immediately after `update_new_frame`
456    fn display_new_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
457        self.wait_until_idle(spi, delay)?;
458        self.interface
459            .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0x0F])?;
460        self.interface.cmd(spi, Command::MasterActivation)?;
461        self.wait_until_idle(spi, delay)?;
462        Ok(())
463    }
464
465    /// Updates and displays the new frame.
466    fn update_and_display_new_frame(
467        &mut self,
468        spi: &mut SPI,
469        buffer: &[u8],
470        delay: &mut DELAY,
471    ) -> Result<(), SPI::Error> {
472        self.update_new_frame(spi, buffer, delay)?;
473        self.display_new_frame(spi, delay)?;
474        Ok(())
475    }
476
477    /// Partial quick refresh not supported yet
478    #[allow(unused)]
479    fn update_partial_old_frame(
480        &mut self,
481        spi: &mut SPI,
482        delay: &mut DELAY,
483        buffer: &[u8],
484        x: u32,
485        y: u32,
486        width: u32,
487        height: u32,
488    ) -> Result<(), SPI::Error> {
489        //TODO supported by display?
490        unimplemented!()
491    }
492
493    /// Partial quick refresh not supported yet
494    #[allow(unused)]
495    fn update_partial_new_frame(
496        &mut self,
497        spi: &mut SPI,
498        delay: &mut DELAY,
499        buffer: &[u8],
500        x: u32,
501        y: u32,
502        width: u32,
503        height: u32,
504    ) -> Result<(), SPI::Error> {
505        //TODO supported by display?
506        unimplemented!()
507    }
508
509    /// Partial quick refresh not supported yet
510    #[allow(unused)]
511    fn clear_partial_frame(
512        &mut self,
513        spi: &mut SPI,
514        delay: &mut DELAY,
515        x: u32,
516        y: u32,
517        width: u32,
518        height: u32,
519    ) -> Result<(), SPI::Error> {
520        //TODO supported by display?
521        unimplemented!()
522    }
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528
529    #[test]
530    fn epd_size() {
531        assert_eq!(WIDTH, 128);
532        assert_eq!(HEIGHT, 296);
533        assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
534    }
535}