diff --git a/examples/epd4in2_variable_size.rs b/examples/epd4in2_variable_size.rs index 6da2b427..e0bf4820 100644 --- a/examples/epd4in2_variable_size.rs +++ b/examples/epd4in2_variable_size.rs @@ -70,7 +70,8 @@ fn main() -> Result<(), std::io::Error> { let (x, y, width, height) = (50, 50, 250, 250); let mut buffer = [epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value(); 62500]; //250*250 - let mut display = VarDisplay::new(width, height, &mut buffer, false).unwrap(); + let mut display = + VarDisplay::new(width, height, &mut buffer, DisplayMode::BwrBitOff as u8).unwrap(); display.set_rotation(DisplayRotation::Rotate0); draw_text(&mut display, "Rotate 0!", 5, 50); diff --git a/src/color.rs b/src/color.rs index 21e0ecdd..71b60313 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,6 +3,7 @@ //! EPD representation of multicolor with separate buffers //! for each bit makes it hard to properly represent colors here +use crate::prelude::DisplayMode; #[cfg(feature = "graphics")] use embedded_graphics_core::pixelcolor::BinaryColor; #[cfg(feature = "graphics")] @@ -77,7 +78,7 @@ pub trait ColorType: PixelColor { /// Return the data used to set a pixel color /// - /// * bwrbit is used to tell the value of the unused bit when a chromatic + /// * MODE is used to tell the value of the unused bit when a chromatic /// color is set (TriColor only as for now) /// * pos is the pixel position in the line, used to know which pixels must be set /// @@ -85,13 +86,13 @@ pub trait ColorType: PixelColor { /// * .0 is the mask used to exclude this pixel from the byte (eg: 0x7F in BiColor) /// * .1 are the bits used to set the color in the byte (eg: 0x80 in BiColor) /// this is u16 because we set 2 bytes in case of split buffer - fn bitmask(&self, bwrbit: bool, pos: u32) -> (u8, u16); + fn bitmask(&self, mode: DisplayMode, pos: u32) -> (u8, u16); } impl ColorType for Color { const BITS_PER_PIXEL_PER_BUFFER: usize = 1; const BUFFER_COUNT: usize = 1; - fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) { + fn bitmask(&self, _mode: DisplayMode, pos: u32) -> (u8, u16) { let bit = 0x80 >> (pos % 8); match self { Color::Black => (!bit, 0u16), @@ -103,27 +104,34 @@ impl ColorType for Color { impl ColorType for TriColor { const BITS_PER_PIXEL_PER_BUFFER: usize = 1; const BUFFER_COUNT: usize = 2; - fn bitmask(&self, bwrbit: bool, pos: u32) -> (u8, u16) { + + fn bitmask(&self, mode: DisplayMode, pos: u32) -> (u8, u16) { let bit = 0x80 >> (pos % 8); - match self { - TriColor::Black => (!bit, 0u16), - TriColor::White => (!bit, bit as u16), - TriColor::Chromatic => ( - !bit, - if bwrbit { - (bit as u16) << 8 - } else { - (bit as u16) << 8 | bit as u16 - }, - ), - } + let mask = match mode { + DisplayMode::BwrBitOnColorInverted => match self { + TriColor::Black => (bit as u16) << 8, + TriColor::White => (bit as u16) << 8 | bit as u16, + TriColor::Chromatic => 0u16, + }, + DisplayMode::BwrBitOn => match self { + TriColor::Black => 0u16, + TriColor::White => bit as u16, + TriColor::Chromatic => (bit as u16) << 8, + }, + DisplayMode::BwrBitOff => match self { + TriColor::Black => 0u16, + TriColor::White => bit as u16, + TriColor::Chromatic => (bit as u16) << 8 | bit as u16, + }, + }; + (!bit, mask) } } impl ColorType for OctColor { const BITS_PER_PIXEL_PER_BUFFER: usize = 4; const BUFFER_COUNT: usize = 1; - fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) { + fn bitmask(&self, _mode: DisplayMode, pos: u32) -> (u8, u16) { let mask = !(0xF0 >> (pos % 2)); let bits = self.get_nibble() as u16; (mask, if pos % 2 == 1 { bits } else { bits << 4 }) @@ -307,16 +315,16 @@ impl From for Color { impl From for Color { fn from(rgb: embedded_graphics_core::pixelcolor::Rgb888) -> Self { use embedded_graphics_core::pixelcolor::RgbColor; - if rgb == RgbColor::BLACK { - Color::Black - } else if rgb == RgbColor::WHITE { - Color::White - } else { - // choose closest color - if (rgb.r() as u16 + rgb.g() as u16 + rgb.b() as u16) > 255 * 3 / 2 { - Color::White - } else { - Color::Black + match rgb { + RgbColor::BLACK => Color::Black, + RgbColor::WHITE => Color::White, + _ => { + // choose closest color + if (rgb.r() as u16 + rgb.g() as u16 + rgb.b() as u16) > 255 * 3 / 2 { + Color::White + } else { + Color::Black + } } } } @@ -369,13 +377,11 @@ impl From for TriColor { impl From for TriColor { fn from(rgb: embedded_graphics_core::pixelcolor::Rgb888) -> Self { use embedded_graphics_core::pixelcolor::RgbColor; - if rgb == RgbColor::BLACK { - TriColor::Black - } else if rgb == RgbColor::WHITE { - TriColor::White - } else { + match rgb { + RgbColor::BLACK => TriColor::Black, + RgbColor::WHITE => TriColor::White, // there is no good approximation here since we don't know which color is 'chromatic' - TriColor::Chromatic + _ => TriColor::Chromatic, } } } diff --git a/src/epd1in54/mod.rs b/src/epd1in54/mod.rs index be6a430b..dc321213 100644 --- a/src/epd1in54/mod.rs +++ b/src/epd1in54/mod.rs @@ -75,7 +75,7 @@ use crate::interface::DisplayInterface; pub type Display1in54 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd1in54b/mod.rs b/src/epd1in54b/mod.rs index ce8c4fb4..f34df729 100644 --- a/src/epd1in54b/mod.rs +++ b/src/epd1in54b/mod.rs @@ -34,7 +34,7 @@ use crate::buffer_len; pub type Display1in54b = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd1in54c/mod.rs b/src/epd1in54c/mod.rs index 2934a827..cf06fdeb 100644 --- a/src/epd1in54c/mod.rs +++ b/src/epd1in54c/mod.rs @@ -31,7 +31,7 @@ use crate::buffer_len; pub type Display1in54c = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd2in13_v2/mod.rs b/src/epd2in13_v2/mod.rs index 0efad684..a6e441b9 100644 --- a/src/epd2in13_v2/mod.rs +++ b/src/epd2in13_v2/mod.rs @@ -33,7 +33,7 @@ use self::constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE}; pub type Display2in13 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd2in13bc/mod.rs b/src/epd2in13bc/mod.rs index e009f315..850383bf 100644 --- a/src/epd2in13bc/mod.rs +++ b/src/epd2in13bc/mod.rs @@ -88,7 +88,7 @@ use crate::buffer_len; pub type Display2in13bc = crate::graphics::Display< WIDTH, HEIGHT, - true, + { crate::graphics::DisplayMode::BwrBitOn as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize * 2) }, TriColor, >; diff --git a/src/epd2in7b/mod.rs b/src/epd2in7b/mod.rs index 6c3a49c5..9268d832 100644 --- a/src/epd2in7b/mod.rs +++ b/src/epd2in7b/mod.rs @@ -36,7 +36,7 @@ use crate::buffer_len; pub type Display2in7b = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index 6a3ee1d0..aeef223e 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -71,7 +71,7 @@ use crate::interface::DisplayInterface; pub type Display2in9 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd2in9_v2/mod.rs b/src/epd2in9_v2/mod.rs index a2d8caf2..d6a696bf 100644 --- a/src/epd2in9_v2/mod.rs +++ b/src/epd2in9_v2/mod.rs @@ -94,7 +94,7 @@ use crate::traits::QuickRefresh; pub type Display2in9 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd2in9bc/mod.rs b/src/epd2in9bc/mod.rs index 4eb980fc..d6f32f7a 100644 --- a/src/epd2in9bc/mod.rs +++ b/src/epd2in9bc/mod.rs @@ -92,7 +92,7 @@ use crate::buffer_len; pub type Display2in9bc = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd3in7/mod.rs b/src/epd3in7/mod.rs index 66513eb8..038aaa91 100644 --- a/src/epd3in7/mod.rs +++ b/src/epd3in7/mod.rs @@ -34,7 +34,7 @@ const IS_BUSY_LOW: bool = false; pub type Display3in7 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index a5e52a8e..c26ded9b 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -58,7 +58,7 @@ use crate::interface::DisplayInterface; use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay}; //The Lookup Tables for the Display -mod constants; +pub mod constants; use crate::epd4in2::constants::*; /// Width of the display @@ -80,7 +80,7 @@ use crate::buffer_len; pub type Display4in2 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd4in2bc/mod.rs b/src/epd4in2bc/mod.rs new file mode 100644 index 00000000..0c9fe126 --- /dev/null +++ b/src/epd4in2bc/mod.rs @@ -0,0 +1,667 @@ +//! 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::{ +//! 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, None)?; +//! +//!// Use display graphics from embedded-graphics +//!let mut display = Display4in2bc::default(); +//! +//!// Use embedded graphics for drawing a line +//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295)) +//! .into_styled(PrimitiveStyle::with_stroke(TriColor::Chromatic, 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; +use crate::traits::{ + InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, +}; + +use crate::color::TriColor; + +//The Lookup Tables for the Display +use crate::epd4in2::command::Command; +use crate::epd4in2::constants::*; + +use crate::epd4in2::HEIGHT; +use crate::epd4in2::WIDTH; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White; +const IS_BUSY_LOW: bool = true; + +use crate::buffer_len; + +/// Full size buffer for use with the 4in2 EPD +#[cfg(feature = "graphics")] +pub type Display4in2bc = crate::graphics::Display< + WIDTH, + HEIGHT, + { crate::graphics::DisplayMode::BwrBitOnColorInverted as u8 }, + { buffer_len(WIDTH as usize, HEIGHT as usize * 2) }, + TriColor, +>; + +/// Epd4in2bc driver +/// +pub struct Epd4in2bc { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: TriColor, + /// Refresh LUT + refresh: RefreshLut, +} + +impl InternalWiAdditions + for Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // reset the device + self.interface.reset(delay, 10_000, 10_000); + + // 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])?; + + // set the panel settings + self.cmd_with_data(spi, Command::PanelSetting, &[0x0F])?; + + // // Set Frequency, 200 Hz didn't work on my board + // // 150Hz and 171Hz wasn't tested yet + // // TODO: Test these other frequencies + // // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ DEFAULT: 3c 50Hz + self.cmd_with_data(spi, Command::PllControl, &[0x3C])?; + + self.send_resolution(spi)?; + + self.interface + .cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?; + + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x7f])?; + + self.set_lut(spi, delay, None)?; + + // power on + self.command(spi, Command::PowerOn)?; + delay.delay_us(5000); + + self.wait_until_idle(spi, delay)?; + Ok(()) + } +} + +impl WaveshareThreeColorDisplay + for Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + fn update_color_frame( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + black: &[u8], + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.update_achromatic_frame(spi, delay, black)?; + self.update_chromatic_frame(spi, delay, 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, + _delay: &mut DELAY, + 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, + delay: &mut DELAY, + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data(spi, chromatic)?; + + self.wait_until_idle(spi, delay)?; + Ok(()) + } +} + +impl WaveshareDisplay + for Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + type DisplayColor = TriColor; + + fn new( + spi: &mut SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result { + let interface = DisplayInterface::new(cs, busy, dc, rst, delay_us); + + let mut epd = Epd4in2bc { + interface, + color: DEFAULT_BACKGROUND_COLOR, + refresh: RefreshLut::Quick, + }; + + epd.init(spi, delay)?; + + Ok(epd) + } + + fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x7f])?; //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(spi, delay)?; + self.interface + .cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; + Ok(()) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.init(spi, delay) + } + + 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 update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface.data(spi, buffer)?; + + let color_value = self.color.get_byte_value(); + + self.interface + .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; + + self.interface + .cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + + self.command(spi, Command::DataStop)?; + + Ok(()) + } + + fn update_partial_frame( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + 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])?; + self.send_data(spi, &[(x & 0xf8) as u8])?; // x should be the multiple of 8, the last 3 bit will always be ignored + self.send_data(spi, &[(((x & 0xf8) + width - 1) >> 8) as u8])?; + self.send_data(spi, &[(((x & 0xf8) + width - 1) | 0x07) as u8])?; + + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[(y & 0xff) as u8])?; + self.send_data(spi, &[((y + height - 1) >> 8) as u8])?; + self.send_data(spi, &[((y + height - 1) & 0xff) as u8])?; + + self.send_data(spi, &[0x01])?; // Gates scan both inside and outside of the partial window. (default) + + delay.delay_us(2000); + self.command(spi, Command::DataStartTransmission1)?; + self.send_data(spi, buffer)?; + + let color_value = self.color.get_bit_value(); + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; + + delay.delay_us(2000); + self.command(spi, Command::PartialOut)?; + Ok(()) + } + + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.set_lut(spi, delay, None)?; + self.command(spi, Command::DisplayRefresh)?; + delay.delay_us(100_000); + self.wait_until_idle(spi, delay)?; + 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(spi, delay)?; + self.send_resolution(spi)?; + + let color_value = self.color.get_bit_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_lut( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + 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, delay, &LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB) + } + RefreshLut::Quick => self.set_lut_helper( + spi, + delay, + &LUT_VCOM0_QUICK, + &LUT_WW_QUICK, + &LUT_BW_QUICK, + &LUT_WB_QUICK, + &LUT_BB_QUICK, + ), + } + } + + fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + Ok(()) + } +} + +impl Epd4in2bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + 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 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]) + } + + #[allow(clippy::too_many_arguments)] + fn set_lut_helper( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + lut_vcom: &[u8], + lut_ww: &[u8], + lut_bw: &[u8], + lut_wb: &[u8], + lut_bb: &[u8], + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + // 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: DelayUs, +{ + /// 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(spi, delay)?; + + 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(spi, delay)?; + 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, + delay: &mut DELAY, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + + 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, + delay: &mut DELAY, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + 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, + delay: &mut DELAY, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + 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, TriColor::White); + } +} diff --git a/src/epd5in65f/mod.rs b/src/epd5in65f/mod.rs index fde52050..51a5906c 100644 --- a/src/epd5in65f/mod.rs +++ b/src/epd5in65f/mod.rs @@ -24,7 +24,7 @@ use crate::buffer_len; pub type Display5in65f = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize * 4) }, OctColor, >; diff --git a/src/epd5in83b_v2/mod.rs b/src/epd5in83b_v2/mod.rs index 0a9ac8a5..eb37d8be 100644 --- a/src/epd5in83b_v2/mod.rs +++ b/src/epd5in83b_v2/mod.rs @@ -25,7 +25,7 @@ use crate::buffer_len; pub type Display5in83 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize * 2) }, TriColor, >; diff --git a/src/epd7in5/mod.rs b/src/epd7in5/mod.rs index 52237e6e..c70ac205 100644 --- a/src/epd7in5/mod.rs +++ b/src/epd7in5/mod.rs @@ -24,7 +24,7 @@ use crate::buffer_len; pub type Display7in5 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd7in5_hd/mod.rs b/src/epd7in5_hd/mod.rs index c29d723e..e6bdf4f8 100644 --- a/src/epd7in5_hd/mod.rs +++ b/src/epd7in5_hd/mod.rs @@ -27,7 +27,7 @@ use crate::buffer_len; pub type Display7in5 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd7in5_v2/mod.rs b/src/epd7in5_v2/mod.rs index fe6867dd..eaa3967a 100644 --- a/src/epd7in5_v2/mod.rs +++ b/src/epd7in5_v2/mod.rs @@ -28,7 +28,7 @@ use crate::buffer_len; pub type Display7in5 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, Color, >; diff --git a/src/epd7in5_v3/mod.rs b/src/epd7in5_v3/mod.rs index 675ee048..42a2a467 100644 --- a/src/epd7in5_v3/mod.rs +++ b/src/epd7in5_v3/mod.rs @@ -27,7 +27,7 @@ use crate::buffer_len; pub type Display7in5 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize) }, TriColor, >; diff --git a/src/epd7in5b_v2/mod.rs b/src/epd7in5b_v2/mod.rs index cdf0186b..daabdd83 100644 --- a/src/epd7in5b_v2/mod.rs +++ b/src/epd7in5b_v2/mod.rs @@ -28,7 +28,7 @@ use crate::buffer_len; pub type Display7in5 = crate::graphics::Display< WIDTH, HEIGHT, - false, + { crate::graphics::DisplayMode::BwrBitOff as u8 }, { buffer_len(WIDTH as usize, HEIGHT as usize * 2) }, TriColor, >; diff --git a/src/graphics.rs b/src/graphics.rs index 911a9cf7..7320e73f 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -5,9 +5,10 @@ use core::marker::PhantomData; use embedded_graphics_core::prelude::*; /// Display rotation, only 90° increments supported -#[derive(Clone, Copy)] +#[derive(Default, Clone, Copy)] pub enum DisplayRotation { /// No rotation + #[default] Rotate0, /// Rotate by 90 degrees clockwise Rotate90, @@ -17,9 +18,29 @@ pub enum DisplayRotation { Rotate270, } -impl Default for DisplayRotation { - fn default() -> Self { - DisplayRotation::Rotate0 +/// DisplayMode carries modes avaiable for color representaion on TriColor displays +/// Each mode contains list of (bw, color) pairs, where bw - value for BW layer, color - value for Chromatic layer +#[repr(u8)] +pub enum DisplayMode { + /// BwrBitOn chromatic doesn't override white, white bit cleared for black, white bit set for white, both bits set for chromatic + /// Colors: White - (0xFF, 0); Black - (0, 0); Red - (0, 0xFF) + BwrBitOn = 0, + /// BwrBitOff is chromatic does override white, both bits cleared for black, white bit set for white, red bit set for black + /// Colors: White - (0xFF, 0); Black - (0, 0); Red - (0xFF, 0xFF) + BwrBitOff = 1, + /// BwrBitOnColorInverted is same as standard, but with color layer values inverted + /// Colors: White - (0xFF, 0xFF); Black - (0, 0xFF); Red - (0, 0) + BwrBitOnColorInverted = 2, +} + +impl DisplayMode { + fn from_u8(value: u8) -> DisplayMode { + match value { + 0 => DisplayMode::BwrBitOn, + 1 => DisplayMode::BwrBitOff, + 2 => DisplayMode::BwrBitOnColorInverted, + v => panic!("Unsupported DisplayMode value {v}"), + } } } @@ -34,13 +55,13 @@ const fn line_bytes(width: u32, bits_per_pixel: usize) -> usize { /// /// - WIDTH: width in pixel when display is not rotated /// - HEIGHT: height in pixel when display is not rotated -/// - BWRBIT: mandatory value of the B/W when chromatic bit is set, can be any value for non +/// - MODE: mandatory value of the B/W when chromatic bit is set, can be any value for non /// tricolor epd /// - COLOR: color type used by the target display /// - BYTECOUNT: This is redundant with prvious data and should be removed when const generic /// expressions are stabilized /// -/// More on BWRBIT: +/// More on MODE: /// /// Different chromatic displays differently treat the bits in chromatic color planes. /// Some of them ([crate::epd2in13bc]) will render a color pixel if bit is set for that pixel, @@ -49,12 +70,12 @@ const fn line_bytes(width: u32, bits_per_pixel: usize) -> usize { /// Other displays, like [crate::epd5in83b_v2] in opposite, will draw color pixel if bit is /// cleared for that pixel, which is a [DisplayColorRendering::Negative] mode. /// -/// BWRBIT=true: chromatic doesn't override white, white bit cleared for black, white bit set for white, both bits set for chromatic -/// BWRBIT=false: chromatic does override white, both bits cleared for black, white bit set for white, red bit set for black +/// MODE=true: chromatic doesn't override white, white bit cleared for black, white bit set for white, both bits set for chromatic +/// MODE=false: chromatic does override white, both bits cleared for black, white bit set for white, red bit set for black pub struct Display< const WIDTH: u32, const HEIGHT: u32, - const BWRBIT: bool, + const MODE: u8, const BYTECOUNT: usize, COLOR: ColorType, > { @@ -66,10 +87,10 @@ pub struct Display< impl< const WIDTH: u32, const HEIGHT: u32, - const BWRBIT: bool, + const MODE: u8, const BYTECOUNT: usize, COLOR: ColorType, - > Default for Display + > Default for Display { /// Initialize display with the color '0', which may not be the same on all device. /// Many devices have a bit parameter polarity that should be changed if this is not the right @@ -94,10 +115,10 @@ impl< impl< const WIDTH: u32, const HEIGHT: u32, - const BWRBIT: bool, + const MODE: u8, const BYTECOUNT: usize, COLOR: ColorType, - > DrawTarget for Display + > DrawTarget for Display { type Color = COLOR; type Error = core::convert::Infallible; @@ -117,10 +138,10 @@ impl< impl< const WIDTH: u32, const HEIGHT: u32, - const BWRBIT: bool, + const MODE: u8, const BYTECOUNT: usize, COLOR: ColorType, - > OriginDimensions for Display + > OriginDimensions for Display { fn size(&self) -> Size { match self.rotation { @@ -133,10 +154,10 @@ impl< impl< const WIDTH: u32, const HEIGHT: u32, - const BWRBIT: bool, + const MODE: u8, const BYTECOUNT: usize, COLOR: ColorType, - > Display + > Display { /// get internal buffer to use it (to draw in epd) pub fn buffer(&self) -> &[u8] { @@ -158,20 +179,13 @@ impl< /// Set a specific pixel color on this display pub fn set_pixel(&mut self, pixel: Pixel) { - set_pixel( - &mut self.buffer, - WIDTH, - HEIGHT, - self.rotation, - BWRBIT, - pixel, - ); + set_pixel(&mut self.buffer, WIDTH, HEIGHT, self.rotation, MODE, pixel); } } /// Some Tricolor specifics -impl - Display +impl + Display { /// get black/white internal buffer to use it (to draw in epd) pub fn bw_buffer(&self) -> &[u8] { @@ -190,7 +204,7 @@ impl { width: u32, height: u32, - bwrbit: bool, + mode: u8, buffer: &'a mut [u8], rotation: DisplayRotation, _color: PhantomData, @@ -237,17 +251,17 @@ impl<'a, COLOR: ColorType> VarDisplay<'a, COLOR> { /// You must allocate the buffer by yourself, it must be large enough to contain all pixels. /// /// Parameters are documented in `Display` as they are the same as the const generics there. - /// bwrbit should be false for non tricolor displays + /// MODE should be false for non tricolor displays pub fn new( width: u32, height: u32, buffer: &'a mut [u8], - bwrbit: bool, + mode: u8, ) -> Result { let myself = Self { width, height, - bwrbit, + mode, buffer, rotation: DisplayRotation::default(), _color: PhantomData, @@ -294,7 +308,7 @@ impl<'a, COLOR: ColorType> VarDisplay<'a, COLOR> { self.width, self.height, self.rotation, - self.bwrbit, + self.mode, pixel, ); } @@ -322,7 +336,7 @@ fn set_pixel( width: u32, height: u32, rotation: DisplayRotation, - bwrbit: bool, + mode: u8, pixel: Pixel, ) { let Pixel(point, color) = pixel; @@ -342,9 +356,10 @@ fn set_pixel( return; } + let display_mode = DisplayMode::from_u8(mode); let index = x as usize * COLOR::BITS_PER_PIXEL_PER_BUFFER / 8 + y as usize * line_bytes(width, COLOR::BITS_PER_PIXEL_PER_BUFFER); - let (mask, bits) = color.bitmask(bwrbit, x as u32); + let (mask, bits) = color.bitmask(display_mode, x as u32); if COLOR::BUFFER_COUNT == 2 { // split buffer is for tricolor displays that use 2 buffer for 2 bits per pixel @@ -369,14 +384,15 @@ mod tests { #[test] fn graphics_size() { // example definition taken from epd1in54 - let display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default(); + let display = Display::<200, 200, { DisplayMode::BwrBitOff as u8 } + , { 200 * 200 / 8 }, Color>::default(); assert_eq!(display.buffer().len(), 5000); } // test default background color on all bytes #[test] fn graphics_default() { - let display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default(); + let display = Display::<200, 200, { DisplayMode::BwrBitOff as u8 }, { 200 * 200 / 8 }, Color>::default(); for &byte in display.buffer() { assert_eq!(byte, 0); } @@ -384,7 +400,13 @@ mod tests { #[test] fn graphics_rotation_0() { - let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default(); + let mut display = Display::< + 200, + 200, + { DisplayMode::BwrBitOff as u8 }, + { 200 * 200 / 8 }, + Color, + >::default(); let _ = Line::new(Point::new(0, 0), Point::new(7, 0)) .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1)) .draw(&mut display); @@ -400,7 +422,13 @@ mod tests { #[test] fn graphics_rotation_90() { - let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default(); + let mut display = Display::< + 200, + 200, + { DisplayMode::BwrBitOff as u8 }, + { 200 * 200 / 8 }, + Color, + >::default(); display.set_rotation(DisplayRotation::Rotate90); let _ = Line::new(Point::new(0, 192), Point::new(0, 199)) .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1)) @@ -417,7 +445,13 @@ mod tests { #[test] fn graphics_rotation_180() { - let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default(); + let mut display = Display::< + 200, + 200, + { DisplayMode::BwrBitOff as u8 }, + { 200 * 200 / 8 }, + Color, + >::default(); display.set_rotation(DisplayRotation::Rotate180); let _ = Line::new(Point::new(192, 199), Point::new(199, 199)) .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1)) @@ -426,7 +460,7 @@ mod tests { let buffer = display.buffer(); extern crate std; - std::println!("{:?}", buffer); + std::println!("{buffer:?}"); assert_eq!(buffer[0], Color::Black.get_byte_value()); @@ -437,7 +471,13 @@ mod tests { #[test] fn graphics_rotation_270() { - let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default(); + let mut display = Display::< + 200, + 200, + { DisplayMode::BwrBitOff as u8 }, + { 200 * 200 / 8 }, + Color, + >::default(); display.set_rotation(DisplayRotation::Rotate270); let _ = Line::new(Point::new(199, 0), Point::new(199, 7)) .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1)) @@ -446,7 +486,7 @@ mod tests { let buffer = display.buffer(); extern crate std; - std::println!("{:?}", buffer); + std::println!("{buffer:?}"); assert_eq!(buffer[0], Color::Black.get_byte_value()); diff --git a/src/lib.rs b/src/lib.rs index 677f16b9..93df5a79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ pub mod epd2in9_v2; pub mod epd2in9bc; pub mod epd3in7; pub mod epd4in2; +pub mod epd4in2bc; pub mod epd5in65f; pub mod epd5in83b_v2; pub mod epd7in5; @@ -105,7 +106,7 @@ pub mod prelude { pub use crate::SPI_MODE; #[cfg(feature = "graphics")] - pub use crate::graphics::{Display, DisplayRotation}; + pub use crate::graphics::{Display, DisplayMode, DisplayRotation}; } /// Computes the needed buffer length. Takes care of rounding up in case width diff --git a/src/traits.rs b/src/traits.rs index 00cceb6a..462eca61 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -11,21 +11,16 @@ pub(crate) trait Command: Copy { } /// Seperates the different LUT for the Display Refresh process -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Copy)] pub enum RefreshLut { /// The "normal" full Lookuptable for the Refresh-Sequence + #[default] Full, /// The quick LUT where not the full refresh sequence is followed. /// This might lead to some Quick, } -impl Default for RefreshLut { - fn default() -> Self { - RefreshLut::Full - } -} - pub(crate) trait InternalWiAdditions where SPI: Write, @@ -284,7 +279,7 @@ where ///let (x, y, frame_width, frame_height) = (20, 40, 80,80); /// ///let mut buffer = [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 80 / 8 * 80]; -///let mut display = VarDisplay::new(frame_width, frame_height, &mut buffer,false).unwrap(); +///let mut display = VarDisplay::new(frame_width, frame_height, &mut buffer, DisplayMode::BwrBitOff as u8).unwrap(); /// ///epd.update_partial_old_frame(&mut spi, &mut delay, display.buffer(), x, y, frame_width, frame_height) /// .ok();