diff --git a/src/epd4in2bc/command.rs b/src/epd4in2bc/command.rs new file mode 100644 index 00000000..4d3a9fe0 --- /dev/null +++ b/src/epd4in2bc/command.rs @@ -0,0 +1,177 @@ +//! SPI Commands for the Waveshare 4.2" E-Ink Display +use crate::traits; +/// EPD4IN2 commands +/// +/// Should rarely (never?) be needed directly. +/// +/// For more infos about the addresses and what they are doing look into the pdfs +/// +/// The description of the single commands is mostly taken from IL0398.pdf +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum Command { + /// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift direction, booster switch, soft reset + /// One Byte of Data: + /// 0x0F Red Mode, LUT from OTP + /// 0x1F B/W Mode, LUT from OTP + /// 0x2F Red Mode, LUT set by registers + /// 0x3F B/W Mode, LUT set by registers + PanelSetting = 0x00, + /// selecting internal and external power + /// self.send_data(0x03)?; //VDS_EN, VDG_EN + /// self.send_data(0x00)?; //VCOM_HV, VGHL_LV[1], VGHL_LV[0] + /// self.send_data(0x2b)?; //VDH + /// self.send_data(0x2b)?; //VDL + /// self.send_data(0xff)?; //VDHR + PowerSetting = 0x01, + /// After the Power Off command, the driver will power off following the Power Off Sequence. This command will turn off charge + /// pump, T-con, source driver, gate driver, VCOM, and temperature sensor, but register data will be kept until VDD becomes OFF. + /// Source Driver output and Vcom will remain as previous condition, which may have 2 conditions: floating. + PowerOff = 0x02, + /// Setting Power OFF sequence + PowerOffSequenceSetting = 0x03, + /// Turning On the Power + PowerOn = 0x04, + /// This command enables the internal bandgap, which will be cleared by the next POF. + PowerOnMeasure = 0x05, + /// Starting data transmission + /// 3-times: self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f + BoosterSoftStart = 0x06, + /// After this command is transmitted, the chip would enter the deep-sleep mode to save power. + /// + /// The deep sleep mode would return to standby by hardware reset. + /// + /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. + DeepSleep = 0x07, + /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data + /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. + /// + /// - In B/W mode, this command writes “OLD” data to SRAM. + /// - In B/W/Red mode, this command writes “B/W” data to SRAM. + /// - In Program mode, this command writes “OTP” data to SRAM for programming. + DataStartTransmission1 = 0x10, + /// Stopping data transmission + DataStop = 0x11, + /// While user sent this command, driver will refresh display (data/VCOM) according to SRAM data and LUT. + /// + /// After Display Refresh command, BUSY_N signal will become “0” and the refreshing of panel starts. + DisplayRefresh = 0x12, + /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data + /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. + /// - In B/W mode, this command writes “NEW” data to SRAM. + /// - In B/W/Red mode, this command writes “RED” data to SRAM. + DataStartTransmission2 = 0x13, + + /// This command stores VCOM Look-Up Table with 7 groups of data. Each group contains information for one state and is stored + /// with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LutForVcom = 0x20, + /// This command stores White-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LutWhiteToWhite = 0x21, + /// This command stores Black-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LutBlackToWhite = 0x22, + /// This command stores White-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LutWhiteToBlack = 0x23, + /// This command stores Black-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LutBlackToBlack = 0x24, + /// The command controls the PLL clock frequency. + PllControl = 0x30, + /// This command reads the temperature sensed by the temperature sensor. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TemperatureSensor = 0x40, + /// Selects the Internal or External temperature sensor and offset + TemperatureSensorSelection = 0x41, + /// Write External Temperature Sensor + TemperatureSensorWrite = 0x42, + /// Read External Temperature Sensor + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TemperatureSensorRead = 0x43, + /// This command indicates the interval of Vcom and data output. When setting the vertical back porch, the total blanking will be kept (20 Hsync) + VcomAndDataIntervalSetting = 0x50, + /// This command indicates the input power condition. Host can read this flag to learn the battery condition. + LowPowerDetection = 0x51, + /// This command defines non-overlap period of Gate and Source. + TconSetting = 0x60, + /// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR). + ResolutionSetting = 0x61, + /// This command defines the Fist Active Gate and First Active Source of active channels. + GsstSetting = 0x65, + /// The LUT_REV / Chip Revision is read from OTP address = 0x001. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + Revision = 0x70, + /// Read Flags. This command reads the IC status + /// PTL, I2C_ERR, I2C_BUSY, DATA, PON, POF, BUSY + /// + /// Doesn't work! Waveshare doesn't connect the read pin + GetStatus = 0x71, + /// Automatically measure VCOM. This command reads the IC status + AutoMeasurementVcom = 0x80, + /// This command gets the VCOM value + /// + /// Doesn't work! Waveshare doesn't connect the read pin + ReadVcomValue = 0x81, + /// Set VCM_DC + VcmDcSetting = 0x82, + /// This command sets partial window + PartialWindow = 0x90, + /// This command makes the display enter partial mode + PartialIn = 0x91, + /// This command makes the display exit partial mode and enter normal mode + PartialOut = 0x92, + /// After this command is issued, the chip would enter the program mode. + /// + /// After the programming procedure completed, a hardware reset is necessary for leaving program mode. + /// + /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. + ProgramMode = 0xA0, + /// After this command is transmitted, the programming state machine would be activated. + /// + /// The BUSY flag would fall to 0 until the programming is completed. + ActiveProgramming = 0xA1, + /// The command is used for reading the content of OTP for checking the data of programming. + /// + /// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF. + ReadOtp = 0xA2, + /// This command is set for saving power during fresh period. If the output voltage of VCOM / Source is from negative to positive or + /// from positive to negative, the power saving mechanism will be activated. The active period width is defined by the following two + /// parameters. + PowerSaving = 0xE3, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::Command as CommandTrait; + + #[test] + fn command_addr() { + assert_eq!(Command::PowerSaving.address(), 0xE3); + + assert_eq!(Command::PanelSetting.address(), 0x00); + + assert_eq!(Command::DisplayRefresh.address(), 0x12); + } +} diff --git a/src/epd4in2bc/constants.rs b/src/epd4in2bc/constants.rs new file mode 100644 index 00000000..a4c8037c --- /dev/null +++ b/src/epd4in2bc/constants.rs @@ -0,0 +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] = [ +// 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, +]; + +#[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, +]; + +#[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, +]; + +#[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, +]; + +#[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, +]; + +#[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, +]; + +#[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, +]; + +#[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, +]; + +#[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, +]; diff --git a/src/epd4in2bc/graphics.rs b/src/epd4in2bc/graphics.rs new file mode 100644 index 00000000..9869aa5a --- /dev/null +++ b/src/epd4in2bc/graphics.rs @@ -0,0 +1,176 @@ +use crate::color::TriColor; +use crate::epd4in2bc::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH}; +use crate::graphics::{DisplayRotation, DisplayColorRendering}; +use crate::graphics::TriDisplay; +use embedded_graphics_core::prelude::*; + +/// Full size buffer for use with the 4in2 EPD +/// +/// Can also be manuall constructed: +/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]` +pub struct Display4in2bc { + buffer: [u8; 2 * NUM_DISPLAY_BITS as usize], + rotation: DisplayRotation, +} + +impl Default for Display4in2bc { + fn default() -> Self { + Display4in2bc { + buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 2 * NUM_DISPLAY_BITS as usize], + rotation: DisplayRotation::default(), + } + } +} + +impl DrawTarget for Display4in2bc { + type Color = TriColor; + type Error = core::convert::Infallible; + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper_tri(WIDTH, HEIGHT, pixel, DisplayColorRendering::Positive)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display4in2bc { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +impl TriDisplay for Display4in2bc { + fn buffer(&self) -> &[u8] { + &self.buffer + } + + fn get_mut_buffer(&mut self) -> &mut [u8] { + &mut self.buffer + } + + fn set_rotation(&mut self, rotation: DisplayRotation) { + self.rotation = rotation; + } + + fn rotation(&self) -> DisplayRotation { + self.rotation + } + + fn chromatic_offset(&self) -> usize { + NUM_DISPLAY_BITS as usize + } + + fn bw_buffer(&self) -> &[u8] { + &self.buffer[0..self.chromatic_offset()] + } + + fn chromatic_buffer(&self) -> &[u8] { + &self.buffer[self.chromatic_offset()..] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::color::Black; + use crate::color::Color; + use crate::epd4in2bc; + use crate::graphics::{Display, DisplayRotation}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; + + // test buffer length + #[test] + fn graphics_size() { + let display = Display4in2bc::default(); + assert_eq!(display.buffer().len(), 15000); + } + + // test default background color on all bytes + #[test] + fn graphics_default() { + let display = Display4in2bc::default(); + for &byte in display.buffer() { + assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_0() { + let mut display = Display4in2bc::default(); + let _ = Line::new(Point::new(0, 0), Point::new(7, 0)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_90() { + let mut display = Display4in2bc::default(); + display.set_rotation(DisplayRotation::Rotate90); + let _ = Line::new(Point::new(0, 392), Point::new(0, 399)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_180() { + let mut display = Display4in2bc::default(); + display.set_rotation(DisplayRotation::Rotate180); + + let _ = Line::new(Point::new(392, 299), Point::new(399, 299)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + extern crate std; + std::println!("{:?}", buffer); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_270() { + let mut display = Display4in2bc::default(); + display.set_rotation(DisplayRotation::Rotate270); + let _ = Line::new(Point::new(299, 0), Point::new(299, 7)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + extern crate std; + std::println!("{:?}", buffer); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } +} diff --git a/src/epd4in2bc/mod.rs b/src/epd4in2bc/mod.rs new file mode 100644 index 00000000..e2c3d29f --- /dev/null +++ b/src/epd4in2bc/mod.rs @@ -0,0 +1,669 @@ +//! A simple Driver for the Waveshare 4.2" E-Ink Display via SPI +//! +//! +//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/4.2inch_e-Paper_Module), +//! [Ben Krasnows partial Refresh tips](https://benkrasnow.blogspot.de/2017/10/fast-partial-refresh-on-42-e-paper.html) and +//! the driver documents in the `pdfs`-folder as orientation. +//! +//! # Examples +//! +//!```rust, no_run +//!# use embedded_hal_mock::*; +//!# fn main() -> Result<(), MockError> { +//!use embedded_graphics::{ +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, +//!}; +//!use epd_waveshare::{epd4in2bc::*, prelude::*}; +//!# +//!# let expectations = []; +//!# let mut spi = spi::Mock::new(&expectations); +//!# let expectations = []; +//!# let cs_pin = pin::Mock::new(&expectations); +//!# let busy_in = pin::Mock::new(&expectations); +//!# let dc = pin::Mock::new(&expectations); +//!# let rst = pin::Mock::new(&expectations); +//!# let mut delay = delay::MockNoop::new(); +//! +//!// Setup EPD +//!let mut epd = Epd4in2bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//! +//!// Use display graphics from embedded-graphics +//!let mut display = Display4in2::default(); +//! +//!// Use embedded graphics for drawing a line +//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295)) +//! .into_styled(PrimitiveStyle::with_stroke(Black, 1)) +//! .draw(&mut display); +//! +//! // Display updated frame +//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; +//! +//!// Set the EPD to sleep +//!epd.sleep(&mut spi, &mut delay)?; +//!# Ok(()) +//!# } +//!``` +//! +//! +//! +//! BE CAREFUL! The screen can get ghosting/burn-ins through the Partial Fast Update Drawing. + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::*, +}; + +use crate::{interface::DisplayInterface, traits::WaveshareThreeColorDisplay}; +use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay}; + +//The Lookup Tables for the Display +mod constants; +use crate::epd4in2bc::constants::*; + +/// Width of the display +pub const WIDTH: u32 = 400; +/// Height of the display +pub const HEIGHT: u32 = 300; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White; + +/// Number of bits for b/w buffer and same for chromatic buffer +const NUM_DISPLAY_BITS: u32 = WIDTH * HEIGHT / 8; + +const IS_BUSY_LOW: bool = true; + +use crate::color::TriColor; + +pub(crate) mod command; +use self::command::Command; + +#[cfg(feature = "graphics")] +mod graphics; +#[cfg(feature = "graphics")] +pub use self::graphics::Display4in2bc; + +/// Epd4in2bc driver +/// +pub struct Epd4in2bc { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: TriColor, + /// Refresh LUT + refresh: RefreshLut, +} + +// 3A 100HZ 29 150Hz 39 200HZ 31 171HZ DEFAULT: 3c 50Hz +#[repr(u8)] +enum RefreshFrequency { + _50hzDefault = 0x3C, + _100Hz = 0x3A, + _150Hz = 0x29, + _171Hz = 0x31, + _200Hz = 0x39, +} + +impl InternalWiAdditions + for Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // reset the device + self.interface.reset(delay, 10); + + // set the power settings + self.interface.cmd_with_data( + spi, + Command::PowerSetting, + &[0x03, 0x00, 0x2b, 0x2b, 0xff], + )?; + + // start the booster + self.interface + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?; + + // power on + self.command(spi, Command::PowerOn)?; + delay.delay_ms(5); + self.wait_until_idle(); + + // set the panel settings + self.cmd_with_data(spi, Command::PanelSetting, &[0x3F])?; + + // Set Frequency, 200 Hz didn't work on my board + self.cmd_with_data(spi, Command::PllControl, &[RefreshFrequency::_150Hz as u8])?; + + self.send_resolution(spi)?; + + self.interface + .cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?; + + //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?; + + self.set_lut(spi, None)?; + + self.wait_until_idle(); + Ok(()) + } +} + +impl WaveshareThreeColorDisplay + for Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + fn update_color_frame( + &mut self, + spi: &mut SPI, + black: &[u8], + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.update_achromatic_frame(spi, black)?; + self.update_chromatic_frame(spi, chromatic) + } + + /// Update only the black/white data of the display. + /// + /// Finish by calling `update_chromatic_frame`. + fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface.data(spi, black)?; + Ok(()) + } + + /// Update only chromatic data of the display. + /// + /// This data takes precedence over the black/white data. + fn update_chromatic_frame( + &mut self, + spi: &mut SPI, + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data(spi, chromatic)?; + + self.wait_until_idle(); + Ok(()) + } +} + +impl WaveshareDisplay + for Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = TriColor; + fn new( + spi: &mut SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + ) -> Result { + let interface = DisplayInterface::new(cs, busy, dc, rst); + let color = DEFAULT_BACKGROUND_COLOR; + + let mut epd = Epd4in2bc { + interface, + color, + refresh: RefreshLut::Full, + }; + + epd.init(spi, delay)?; + + Ok(epd) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.init(spi, delay) + } + + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating + self.command(spi, Command::VcmDcSetting)?; // VCOM to 0V + self.command(spi, Command::PanelSetting)?; + + self.command(spi, Command::PowerSetting)?; //VG&VS to 0V fast + for _ in 0..4 { + self.send_data(spi, &[0x00])?; + } + + self.command(spi, Command::PowerOff)?; + self.wait_until_idle(); + self.interface + .cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; + Ok(()) + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + // self.wait_until_idle(); + // let color_value = self.color.get_byte_value(); + + // self.interface.cmd(spi, Command::DataStartTransmission1)?; + // self.interface + // .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; + + // self.interface + // .cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + // Ok(()) + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + self.interface.data(spi, buffer)?; + + // Clear the chromatic layer + let color = self.color.get_byte_value(); + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + self.wait_until_idle(); + Ok(()) + } + + fn update_partial_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.command(spi, Command::PartialIn)?; + self.command(spi, Command::PartialWindow)?; + 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) + + //TODO: handle dtm somehow + let is_dtm1 = false; + if is_dtm1 { + self.command(spi, Command::DataStartTransmission1)? //TODO: check if data_start transmission 1 also needs "old"/background data here + } else { + self.command(spi, Command::DataStartTransmission2)? + } + + self.send_data(spi, buffer)?; + + self.command(spi, Command::PartialOut)?; + Ok(()) + } + + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.command(spi, Command::DisplayRefresh)?; + Ok(()) + } + + fn update_and_display_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_frame(spi, buffer, delay)?; + self.command(spi, Command::DisplayRefresh)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.send_resolution(spi)?; + + let color_value = self.color.get_byte_value(); + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; + Ok(()) + } + + fn set_background_color(&mut self, color: TriColor) { + self.color = color; + } + + fn background_color(&self) -> &TriColor { + &self.color + } + + fn width(&self) -> u32 { + WIDTH + } + + fn height(&self) -> u32 { + HEIGHT + } + + fn set_lut( + &mut self, + spi: &mut SPI, + refresh_rate: Option, + ) -> Result<(), SPI::Error> { + if let Some(refresh_lut) = refresh_rate { + self.refresh = refresh_lut; + } + match self.refresh { + RefreshLut::Full => { + self.set_lut_helper(spi, &LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB) + } + RefreshLut::Quick => self.set_lut_helper( + spi, + &LUT_VCOM0_QUICK, + &LUT_WW_QUICK, + &LUT_BW_QUICK, + &LUT_WB_QUICK, + &LUT_BB_QUICK, + ), + } + } + + fn is_busy(&self) -> bool { + self.interface.is_busy(IS_BUSY_LOW) + } +} + +impl Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> { + self.interface.cmd(spi, command) + } + + fn send_data(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> { + self.interface.data(spi, data) + } + + fn cmd_with_data( + &mut self, + spi: &mut SPI, + command: Command, + data: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd_with_data(spi, command, data) + } + + fn wait_until_idle(&mut self) { + let _ = self.interface.wait_until_idle(IS_BUSY_LOW); + } + + fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + let w = self.width(); + let h = self.height(); + + self.command(spi, Command::ResolutionSetting)?; + self.send_data(spi, &[(w >> 8) as u8])?; + self.send_data(spi, &[w as u8])?; + self.send_data(spi, &[(h >> 8) as u8])?; + self.send_data(spi, &[h as u8]) + } + + fn set_lut_helper( + &mut self, + spi: &mut SPI, + lut_vcom: &[u8], + lut_ww: &[u8], + lut_bw: &[u8], + lut_wb: &[u8], + lut_bb: &[u8], + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + // LUT VCOM + self.cmd_with_data(spi, Command::LutForVcom, lut_vcom)?; + + // LUT WHITE to WHITE + self.cmd_with_data(spi, Command::LutWhiteToWhite, lut_ww)?; + + // LUT BLACK to WHITE + self.cmd_with_data(spi, Command::LutBlackToWhite, lut_bw)?; + + // LUT WHITE to BLACK + self.cmd_with_data(spi, Command::LutWhiteToBlack, lut_wb)?; + + // LUT BLACK to BLACK + self.cmd_with_data(spi, Command::LutBlackToBlack, 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 Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + /// To be followed immediately after by `update_old_frame`. + fn update_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + 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], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + // self.send_resolution(spi)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + + self.interface.data(spi, buffer)?; + + Ok(()) + } + + /// This is a wrapper around `display_frame` for using this device as a true + /// `QuickRefresh` device. + fn display_new_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.display_frame(spi, delay) + } + + /// This is wrapper around `update_new_frame` and `display_frame` for using + /// this device as a true `QuickRefresh` device. + /// + /// To be used immediately after `update_old_frame`. + fn update_and_display_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_new_frame(spi, buffer, delay)?; + self.display_frame(spi, delay) + } + + 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(); + + if buffer.len() as u32 != width / 8 * height { + //TODO: panic!! or sth like that + //return Err("Wrong buffersize"); + } + + self.interface.cmd(spi, Command::PartialIn)?; + self.interface.cmd(spi, Command::PartialWindow)?; + + self.shift_display(spi, x, y, width, height)?; + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + 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::DataStartTransmission2)?; + + self.interface.data(spi, buffer)?; + + self.interface.cmd(spi, Command::PartialOut)?; + 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::PartialIn)?; + self.interface.cmd(spi, Command::PartialWindow)?; + + self.shift_display(spi, x, y, width, height)?; + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, color_value, width / 8 * height)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, width / 8 * height)?; + + self.interface.cmd(spi, Command::PartialOut)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 400); + assert_eq!(HEIGHT, 300); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } +} diff --git a/src/lib.rs b/src/lib.rs index 02205a73..72a17c1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ pub mod epd2in9; pub mod epd2in9_v2; pub mod epd2in9bc; pub mod epd4in2; +pub mod epd4in2bc; pub mod epd5in65f; pub mod epd5in83b_v2; pub mod epd7in5;