From c30d213bc50e344b166f22aa666b93e72f568bfc Mon Sep 17 00:00:00 2001 From: David-OConnor Date: Thu, 21 Jan 2021 21:13:13 +0400 Subject: [PATCH 1/2] Added QuickRefresh trait, and implemented it for 4.2 display --- src/epd4in2/constants.rs | 146 +++++++++++++++++---------------- src/epd4in2/mod.rs | 170 ++++++++++++++++++++++++++++++++++++++- src/lib.rs | 4 +- src/traits.rs | 64 +++++++++++++++ 4 files changed, 312 insertions(+), 72 deletions(-) diff --git a/src/epd4in2/constants.rs b/src/epd4in2/constants.rs index 8ae34b9..a4c8037 100644 --- a/src/epd4in2/constants.rs +++ b/src/epd4in2/constants.rs @@ -1,109 +1,115 @@ +//! This file contains look-up-tables used to set voltages used during +//! various categories of pixel refreshes. + #[rustfmt::skip] pub(crate) const LUT_VCOM0: [u8; 44] = [ -0x00, 0x17, 0x00, 0x00, 0x00, 0x02, -0x00, 0x17, 0x17, 0x00, 0x00, 0x02, -0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// The commented-out line below was used in a Ben Krasnow video explaining +// partial refreshes. +// 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ -0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WW: [u8; 42] =[ -0x40, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, -0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WW_QUICK: [u8; 42] =[ -0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BW: [u8; 42] =[ -0x40, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, -0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BW_QUICK: [u8; 42] =[ -0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BB: [u8; 42] =[ -0x80, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BB_QUICK: [u8; 42] =[ -0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WB: [u8; 42] =[ -0x80, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WB_QUICK: [u8; 42] =[ -0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index a3a972b..76ab7a6 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -55,7 +55,7 @@ use embedded_hal::{ }; use crate::interface::DisplayInterface; -use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay}; +use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLUT, WaveshareDisplay}; //The Lookup Tables for the Display mod constants; @@ -407,6 +407,174 @@ where self.cmd_with_data(spi, Command::LUT_BLACK_TO_BLACK, lut_bb)?; Ok(()) } + + /// Helper function. Sets up the display to send pixel data to a custom + /// starting point. + pub fn shift_display( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.send_data(spi, &[(x >> 8) as u8])?; + let tmp = x & 0xf8; + self.send_data(spi, &[tmp as u8])?; // x should be the multiple of 8, the last 3 bit will always be ignored + let tmp = tmp + width - 1; + self.send_data(spi, &[(tmp >> 8) as u8])?; + self.send_data(spi, &[(tmp | 0x07) as u8])?; + + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[y as u8])?; + + self.send_data(spi, &[((y + height - 1) >> 8) as u8])?; + self.send_data(spi, &[(y + height - 1) as u8])?; + + self.send_data(spi, &[0x01])?; // Gates scan both inside and outside of the partial window. (default) + + Ok(()) + } +} + +impl QuickRefresh + for EPD4in2 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, +{ + /// To be followed immediately after by `update_old_frame`. + fn update_old_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + // todo: Eval if you need these 3 res setting items. + self.send_resolution(spi)?; + self.interface + .cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x12])?; + //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + self.interface + .cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x97])?; + + self.interface + .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + + self.interface.data(spi, buffer)?; + + Ok(()) + } + + /// To be used immediately after `update_old_frame`. + fn update_new_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + self.wait_until_idle(); + // self.send_resolution(spi)?; + + self.interface + .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + + self.interface.data(spi, buffer)?; + + Ok(()) + } + + fn update_partial_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + // todo: Eval if you need these 3 res setting items. + self.send_resolution(spi)?; + self.interface + .cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x12])?; + //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + self.interface + .cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x97])?; + + if buffer.len() as u32 != width / 8 * height { + //TODO: panic!! or sth like that + //return Err("Wrong buffersize"); + } + + self.interface.cmd(spi, Command::PARTIAL_IN)?; + self.interface.cmd(spi, Command::PARTIAL_WINDOW)?; + + self.shift_display(spi, x, y, width, height)?; + + self.interface + .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + + self.interface.data(spi, buffer)?; + + Ok(()) + } + + /// Always call `update_partial_old_frame` before this, with buffer-updating code + /// between the calls. + fn update_partial_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + if buffer.len() as u32 != width / 8 * height { + //TODO: panic!! or sth like that + //return Err("Wrong buffersize"); + } + + self.shift_display(spi, x, y, width, height)?; + + self.interface + .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + + self.interface.data(spi, buffer)?; + + self.interface.cmd(spi, Command::PARTIAL_OUT)?; + Ok(()) + } + + fn clear_partial_frame( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.send_resolution(spi)?; + + let color_value = self.color.get_byte_value(); + + self.interface.cmd(spi, Command::PARTIAL_IN)?; + self.interface.cmd(spi, Command::PARTIAL_WINDOW)?; + + self.shift_display(spi, x, y, width, height)?; + + self.interface + .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface + .data_x_times(spi, color_value, width / 8 * height)?; + + self.interface + .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface + .data_x_times(spi, color_value, width / 8 * height)?; + + self.interface.cmd(spi, Command::PARTIAL_OUT)?; + Ok(()) + } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 175dc01..dcbcf6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,9 @@ pub(crate) mod type_a; /// Includes everything important besides the chosen Display pub mod prelude { pub use crate::color::{Color, OctColor, TriColor}; - pub use crate::traits::{RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay}; + pub use crate::traits::{ + QuickRefresh, RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay, + }; pub use crate::SPI_MODE; diff --git a/src/traits.rs b/src/traits.rs index 9b5aa01..0478874 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -229,3 +229,67 @@ where /// if the device is still busy fn is_busy(&self) -> bool; } + +/// Allows quick refresh support for displays that support it; lets you send both +/// old and new frame data to support this. +/// +/// When using the quick refresh look-up table, the display must receive separate display +/// buffer data marked as old, and new. This is used to determine which pixels need to change, +/// and how they will change. This isn't required when using full refreshes. +/// +/// Example: +/// epd.update_partial_old_frame(spi, disp.buffer(), x, y, frame_width, frame_height) +/// .ok(); +/// +/// disp.clear_buffer(Color::White); +/// // Execute drawing commands here. +/// +/// epd.update_partial_new_frame(spi, disp.buffer(), x, y, frame_width, frame_height) +/// .ok(); +pub trait QuickRefresh +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, +{ + /// Updates the old frame. + fn update_old_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error>; + + /// Updates the new frame. + fn update_new_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error>; + + /// Updates the old frame for a portion of the display. + fn update_partial_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error>; + + /// Updates the new frame for a portion of the display. + fn update_partial_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error>; + + /// Clears the partial frame buffer on the EPD with the declared background color + /// The background color can be changed with [`WaveshareDisplay::set_background_color`] + fn clear_partial_frame( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error>; +} From 0695e9467089291076b9f596826e5c24458f7a91 Mon Sep 17 00:00:00 2001 From: David-OConnor Date: Thu, 21 Jan 2021 21:29:37 +0400 Subject: [PATCH 2/2] Removed example that broke CI --- Cargo.toml | 4 ++-- src/traits.rs | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d69b64..c340fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ edition = "2018" # travis-ci = { repository = "caemor/epd-waveshare" } [dependencies] -embedded-graphics = { version = "0.6.1", optional = true} -embedded-hal = {version = "0.2.3", features = ["unproven"]} +embedded-graphics = { version = "0.6.2", optional = true} +embedded-hal = {version = "0.2.4", features = ["unproven"]} bit_field = "0.10.1" [dev-dependencies] diff --git a/src/traits.rs b/src/traits.rs index 0478874..044185a 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -237,15 +237,7 @@ where /// buffer data marked as old, and new. This is used to determine which pixels need to change, /// and how they will change. This isn't required when using full refreshes. /// -/// Example: -/// epd.update_partial_old_frame(spi, disp.buffer(), x, y, frame_width, frame_height) -/// .ok(); -/// -/// disp.clear_buffer(Color::White); -/// // Execute drawing commands here. -/// -/// epd.update_partial_new_frame(spi, disp.buffer(), x, y, frame_width, frame_height) -/// .ok(); +/// (todo: Example ommitted due to CI failures.) pub trait QuickRefresh where SPI: Write,