diff --git a/README.md b/README.md index 3df65ccc..63699335 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ epd4in2.sleep(&mut spi, &mut delay) | [2.9 Inch B/W (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | | [2.9 Inch B/W V2 (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | | [2.7 Inch 3 Color (B)](https://www.waveshare.com/2.7inch-e-paper-b.htm) | Black, White, Red | ✕ | ✔ | ✔ | ✔ | +| [2.7 Inch B/W V2](https://www.waveshare.com/2.7inch-e-paper.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | | [2.66 Inch 3 Color (B)](https://www.waveshare.com/wiki/Pico-ePaper-2.66-B) | Black, White, Red | ✕ | ✕ | ✔ | ✔ | | [1.54 Inch B/W/Y (C) (Discontinued)](https://www.waveshare.com/1.54inch-e-paper-module-c.htm) | Black, White, Yellow | ✕ | ✕ | ✔ | ✔ | | [1.54 Inch B/W/R (B)](https://www.waveshare.com/1.54inch-e-Paper-B.htm) | Black, White, Red | ✕ | ✕ | ✔ | ✔ | diff --git a/src/epd2in7_v2/mod.rs b/src/epd2in7_v2/mod.rs new file mode 100644 index 00000000..19df56f4 --- /dev/null +++ b/src/epd2in7_v2/mod.rs @@ -0,0 +1,308 @@ +//! A simple Driver for the Waveshare 2.7inch v2 e-Paper HAT Display via SPI +//! +//! # References +//! +//! - [Datasheet](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT_Manual) +//! - [Waveshare C driver](https://github.com/waveshareteam/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7_V2.c) +//! - [Waveshare Python driver](https://github.com/waveshareteam/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7_V2.py) + +use embedded_hal::{ + delay::DelayNs, + digital::{InputPin, OutputPin}, + spi::SpiDevice, +}; + +use crate::{ + buffer_len, + color::Color, + interface::DisplayInterface, + traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay}, + type_a::command::Command, +}; + +/// Width of the display +pub const WIDTH: u32 = 176; +/// Height of the display +pub const HEIGHT: u32 = 264; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; + +const IS_BUSY_LOW: bool = false; +const SINGLE_BYTE_WRITE: bool = true; + +/// Full size buffer for use with the 2in7B EPD +/// TODO this should be a TriColor, but let's keep it as is at first +#[cfg(feature = "graphics")] +pub type Display2in7 = crate::graphics::Display< + WIDTH, + HEIGHT, + false, + { buffer_len(WIDTH as usize, HEIGHT as usize) }, + Color, +>; + +/// Epd2in7b driver +pub struct Epd2in7 { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: Color, + refresh: RefreshLut, +} + +impl InternalWiAdditions + for Epd2in7 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // reset the device + self.interface.reset(delay, 200_000, 2_000); + + self.wait_until_idle(spi, delay)?; + self.command(spi, Command::SwReset)?; + self.wait_until_idle(spi, delay)?; + + self.use_full_frame(spi, delay)?; + + self.interface + .cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?; + + Ok(()) + } +} + +impl WaveshareDisplay + for Epd2in7 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + type DisplayColor = Color; + fn new( + spi: &mut SPI, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result { + let interface = DisplayInterface::new(busy, dc, rst, delay_us); + let color = DEFAULT_BACKGROUND_COLOR; + + let mut epd = Epd2in7 { + 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(spi, delay)?; + self.interface + .cmd_with_data(spi, Command::DeepSleepMode, &[0x01])?; + Ok(()) + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.use_full_frame(spi, delay)?; + self.interface + .cmd_with_data(spi, Command::WriteRam, buffer)?; + 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)?; + self.set_ram_area(spi, x, y, x + width, y + height)?; + self.set_ram_counter(spi, delay, x, y)?; + + self.interface + .cmd_with_data(spi, Command::WriteRam, buffer)?; + + Ok(()) + } + + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + if self.refresh == RefreshLut::Full { + self.interface + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xF7])?; + } else if self.refresh == RefreshLut::Quick { + self.interface + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC7])?; + } + + self.interface.cmd(spi, Command::MasterActivation)?; + 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.display_frame(spi, delay)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.use_full_frame(spi, delay)?; + + let color = self.color.get_byte_value(); + + self.interface.cmd(spi, Command::WriteRam)?; + self.interface + .data_x_times(spi, color, WIDTH / 8 * HEIGHT)?; + + Ok(()) + } + + fn set_background_color(&mut self, color: Color) { + self.color = color; + } + + fn background_color(&self) -> &Color { + &self.color + } + + fn width(&self) -> u32 { + WIDTH + } + + fn height(&self) -> u32 { + HEIGHT + } + + 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; + } + Ok(()) + } + + 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 Epd2in7 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> { + self.interface.cmd(spi, command) + } + + fn set_ram_area( + &mut self, + spi: &mut SPI, + start_x: u32, + start_y: u32, + end_x: u32, + end_y: u32, + ) -> Result<(), SPI::Error> { + assert!(start_x < end_x); + assert!(start_y < end_y); + + self.interface.cmd_with_data( + spi, + Command::SetRamXAddressStartEndPosition, + &[(start_x >> 3) as u8, (end_x >> 3) as u8], + )?; + + self.interface.cmd_with_data( + spi, + Command::SetRamYAddressStartEndPosition, + &[ + (start_y & 0xFF) as u8, + ((start_y >> 8) & 0x01) as u8, + (end_y & 0xFF) as u8, + ((end_y >> 8) & 0x01) as u8, + ], + )?; + Ok(()) + } + + fn set_ram_counter( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + x: u32, + y: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.interface + .cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x & 0xFF) as u8])?; + + self.interface.cmd_with_data( + spi, + Command::SetRamYAddressCounter, + &[(y & 0xFF) as u8, ((y >> 8) & 0x01) as u8], + )?; + Ok(()) + } + + fn use_full_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // choose full frame/ram + self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; + + // start from the beginning + self.set_ram_counter(spi, delay, 0, 0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 176); + assert_eq!(HEIGHT, 264); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9b393528..6acf39ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ pub mod epd1in54c; pub mod epd2in13_v2; pub mod epd2in13bc; pub mod epd2in66b; +pub mod epd2in7_v2; pub mod epd2in7b; pub mod epd2in9; pub mod epd2in9_v2;