diff --git a/examples/embedded_linux/src/main.rs b/examples/embedded_linux/src/main.rs index 388b535..ced4d9d 100644 --- a/examples/embedded_linux/src/main.rs +++ b/examples/embedded_linux/src/main.rs @@ -5,7 +5,7 @@ extern crate linux_embedded_hal as lin_hal; extern crate eink_waveshare_rs; -use eink_waveshare_rs::{epd4in2::EPD4in2, drawing::{Graphics, color::Color}}; +use eink_waveshare_rs::{epd4in2::EPD4in2, drawing::{Graphics, color::Color}, interface::WaveshareInterface}; use lin_hal::spidev::{self, SpidevOptions}; use lin_hal::{Pin, Spidev}; @@ -120,27 +120,27 @@ fn main() { graphics.draw_vertical_line(200, 50, 200, &Color::Black); - epd4in2.display_and_transfer_frame(graphics.get_buffer(), None).expect("display and transfer error"); + epd4in2.update_and_display_frame(graphics.get_buffer()).expect("display and transfer error"); epd4in2.delay_ms(3000); - epd4in2.clear_frame(None).expect("clear frame error"); + epd4in2.clear_frame().expect("clear frame error"); //Test fast updating a bit more let mut small_buffer = [0x00; 128]; let mut circle_graphics = Graphics::new(32,32, &mut small_buffer); circle_graphics.draw_circle(16,16, 10, &Color::Black); - epd4in2.set_partial_window(circle_graphics.get_buffer(), 16,16, 32, 32, false).expect("Partial Window Error"); + epd4in2.update_partial_frame(circle_graphics.get_buffer(), 16,16, 32, 32).expect("Partial Window Error"); epd4in2.display_frame().expect("Display Frame Error"); - epd4in2.set_partial_window(circle_graphics.get_buffer(), 128,64, 32, 32, false).expect("Partial Window Error"); + epd4in2.update_partial_frame(circle_graphics.get_buffer(), 128,64, 32, 32).expect("Partial Window Error"); epd4in2.display_frame().expect("Display Frame Error"); - epd4in2.set_partial_window(circle_graphics.get_buffer(), 320,24, 32, 32, false).expect("Partial Window Error"); + epd4in2.update_partial_frame(circle_graphics.get_buffer(), 320,24, 32, 32).expect("Partial Window Error"); epd4in2.display_frame().expect("Display Frame Error"); - epd4in2.set_partial_window(circle_graphics.get_buffer(), 160,240, 32, 32, false).expect("Partial Window Error"); + epd4in2.update_partial_frame(circle_graphics.get_buffer(), 160,240, 32, 32).expect("Partial Window Error"); epd4in2.display_frame().expect("Display Frame Error"); epd4in2.delay_ms(3000); @@ -152,7 +152,7 @@ fn main() { graphics.draw_string_8x8(16, 16, "hello", &Color::Black); graphics.draw_char_8x8(250, 250, '#', &Color::Black); graphics.draw_char_8x8(300, 16, '7', &Color::Black); - epd4in2.display_and_transfer_frame(graphics.get_buffer(), None).expect("display and transfer error"); + epd4in2.update_and_display_frame(graphics.get_buffer()).expect("display and transfer error"); epd4in2.delay_ms(3000); diff --git a/src/epd2in9/command.rs b/src/epd2in9/command.rs new file mode 100644 index 0000000..016257b --- /dev/null +++ b/src/epd2in9/command.rs @@ -0,0 +1,99 @@ +//! SPI Commands for the Waveshare 2.9" E-Ink Display + +use interface; + + +/// EPD2IN9 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)] +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +pub enum Command { + /// Driver Output control + /// 3 Databytes: + /// A[7:0] + /// 0.. A[8] + /// 0.. B[2:0] + /// Default: Set A[8:0] = 0x127 and B[2:0] = 0x0 + DRIVER_OUTPUT_CONTROL = 0x01, + /// Booster Soft start control + /// 3 Databytes: + /// 1.. A[6:0] + /// 1.. B[6:0] + /// 1.. C[6:0] + /// Default: A[7:0] = 0xCF, B[7:0] = 0xCE, C[7:0] = 0x8D + BOOSTER_SOFT_START_CONTROL = 0x0C, + //TODO: useful? + // GATE_SCAN_START_POSITION = 0x0F, + /// Deep Sleep Mode Control + /// 1 Databyte: + /// 0.. A[0] + /// Values: + /// A[0] = 0: Normal Mode (POR) + /// A[0] = 1: Enter Deep Sleep Mode + DEEP_SLEEP_MODE = 0x10, + // /// Data Entry mode setting + DATA_ENTRY_MODE_SETTING = 0x11, + + SW_RESET = 0x12, + + TEMPERATURE_SENSOR_CONTROL = 0x1A, + + MASTER_ACTIVATION = 0x20, + + DISPLAY_UPDATE_CONTROL_1 = 0x21, + + DISPLAY_UPDATE_CONTROL_2 = 0x22, + + WRITE_RAM = 0x24, + + WRITE_VCOM_REGISTER = 0x2C, + + WRITE_LUT_REGISTER = 0x32, + + SET_DUMMY_LINE_PERIOD = 0x3A, + + SET_GATE_LINE_WIDTH = 0x3B, + + BORDER_WAVEFORM_CONTROL = 0x3C, + + SET_RAM_X_ADDRESS_START_END_POSITION = 0x44, + + SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45, + + SET_RAM_X_ADDRESS_COUNTER = 0x4E, + + SET_RAM_Y_ADDRESS_COUNTER = 0x4F, + + TERMINATE_COMMANDS_AND_FRAME_WRITE = 0xFF +} + + + +impl interface::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} + + +#[cfg(test)] +mod tests { + use super::Command; + use interface::Command as CommandTrait; + + #[test] + fn command_addr() { + assert_eq!(Command::DRIVER_OUTPUT_CONTROL.address(), 0x01); + + //assert_eq!(Command::PANEL_SETTING.addr(), 0x00); + + //assert_eq!(Command::DISPLAY_REFRESH.addr(), 0x12); + } +} \ No newline at end of file diff --git a/src/epd2in9/constants.rs b/src/epd2in9/constants.rs new file mode 100644 index 0000000..98e7b05 --- /dev/null +++ b/src/epd2in9/constants.rs @@ -0,0 +1,17 @@ +pub(crate) const WIDTH: u16 = 128; +pub(crate) const HEIGHT: u16 = 296; + +// Original Waveforms from Waveshare +pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[ + 0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, + 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, + 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, + 0x35, 0x51, 0x51, 0x19, 0x01, 0x00 +]; + +pub(crate) const LUT_PARTIAL_UPDATE: [u8; 30] =[ + 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +]; \ No newline at end of file diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs new file mode 100644 index 0000000..49bd0df --- /dev/null +++ b/src/epd2in9/mod.rs @@ -0,0 +1,341 @@ +//! A simple Driver for the Waveshare 4.2" E-Ink Display via SPI +//! +//! The other Waveshare E-Ink Displays should be added later on +//! +//! 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. +//! +//! This driver was built using [`embedded-hal`] traits. +//! +//! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.1 +//! +//! # Requirements +//! +//! ### SPI +//! +//! - MISO is not connected/available +//! - SPI_MODE_0 is used (CPHL = 0, CPOL = 0) +//! - 8 bits per word, MSB first +//! - Max. Speed tested was 8Mhz but more should be possible +//! +//! ### Other.... +//! +//! - Buffersize: Wherever a buffer is used it always needs to be of the size: `width / 8 * length`, +//! where width and length being either the full e-ink size or the partial update window size +//! +//! # Examples +//! +//! ```ignore +//! let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay).unwrap(); +//! +//! let mut buffer = [0u8, epd4in2.get_width() / 8 * epd4in2.get_height()]; +//! +//! // draw something into the buffer +//! +//! epd4in2.display_and_transfer_buffer(buffer, None); +//! +//! // wait and look at the image +//! +//! epd4in2.clear_frame(None); +//! +//! epd4in2.sleep(); +//! ``` +//! +//! +//! +//! BE CAREFUL! The Partial Drawing can "destroy" your display. +//! It needs more testing first. +//! +//! Is initalised with slow full LUT + + +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + digital::* +}; + +mod constants; +use self::constants::*; + +use drawing::color::Color; + +pub mod command; +pub use self::command::Command; + +use interface::*; + +use interface::connection_interface::ConnectionInterface; + + + +/// EPD2in9 driver +/// +pub struct EPD2in9 { + /// SPI + interface: ConnectionInterface, + /// Width + width: u16, + /// Height + height: u16, + /// Color + background_color: Color, +} + +impl EPD2in9 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs +{ + +} + + +impl WaveshareInterface for EPD2in9 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + + fn get_width(&self) -> u16 { + self.width + } + + fn get_height(&self) -> u16 { + self.height + } + + + fn new( + spi: SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: D + ) -> Result { + let width = WIDTH as u16; + let height = HEIGHT as u16; + + let interface = ConnectionInterface::new(spi, cs, busy, dc, rst, delay); + + let background_color = Color::White; + + let mut epd = EPD2in9 {interface, width, height, background_color}; + + + epd.init()?; + + Ok(epd) + } + + + + fn init(&mut self) -> Result<(), E> { + + + self.reset(); + + // 3 Databytes: + // A[7:0] + // 0.. A[8] + // 0.. B[2:0] + // Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?) + self.interface.send_command(Command::DRIVER_OUTPUT_CONTROL)?; + self.interface.send_data(HEIGHT as u8)?; + self.interface.send_data((HEIGHT >> 8) as u8)?; + self.interface.send_data(0x00)?; + + // 3 Databytes: (and default values from datasheet and arduino) + // 1 .. A[6:0] = 0xCF | 0xD7 + // 1 .. B[6:0] = 0xCE | 0xD6 + // 1 .. C[6:0] = 0x8D | 0x9D + //TODO: test + self.interface.send_command(Command::BOOSTER_SOFT_START_CONTROL)?; + self.interface.send_data(0xD7)?; + self.interface.send_data(0xD6)?; + self.interface.send_data(0x9D)?; + + // One Databyte with value 0xA8 for 7V VCOM + self.interface.send_command(Command::WRITE_VCOM_REGISTER)?; + self.interface.send_data(0xA8)?; + + // One Databyte with default value 0x1A for 4 dummy lines per gate + self.interface.send_command(Command::SET_DUMMY_LINE_PERIOD)?; + self.interface.send_data(0x1A)?; + + // One Databyte with default value 0x08 for 2us per line + self.interface.send_command(Command::SET_GATE_LINE_WIDTH)?; + self.interface.send_data(0x08)?; + + // One Databyte with default value 0x03 + // -> address: x increment, y increment, address counter is updated in x direction + self.interface.send_command(Command::DATA_ENTRY_MODE_SETTING)?; + self.interface.send_data(0x03)?; + + self.set_lut() + } + + fn sleep(&mut self) -> Result<(), E> { + + self.interface.send_command(Command::DEEP_SLEEP_MODE)?; + // 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode + //TODO: is 0x00 needed here? + self.interface.send_data(0x00)?; + + self.wait_until_idle(); + Ok(()) + } + + + fn reset(&mut self) { + self.interface.reset() + } + + fn delay_ms(&mut self, delay: u16) { + self.interface.delay_ms(delay) + } + + + + fn update_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ + self.use_full_frame()?; + + self.interface.send_command(Command::WRITE_RAM)?; + self.interface.send_multiple_data(buffer) + } + + //TODO: update description: last 3 bits will be ignored for width and x_pos + fn update_partial_frame(&mut self, buffer: &[u8], x: u16, y: u16, width: u16, height: u16) -> Result<(), E>{ + self.set_ram_area(x, y, x + width, y + height)?; + self.set_ram_counter(x, y)?; + + self.interface.send_command(Command::WRITE_RAM)?; + self.interface.send_multiple_data(buffer) + } + + + fn display_frame(&mut self) -> Result<(), E>{ + // enable clock signal, enable cp, display pattern -> 0xC4 (tested with the arduino version) + //TODO: test control_1 or control_2 with default value 0xFF (from the datasheet) + self.interface.send_command(Command::DISPLAY_UPDATE_CONTROL_2)?; + self.interface.send_data(0xC4)?; + + self.interface.send_command(Command::MASTER_ACTIVATION)?; + // MASTER Activation should not be interupted to avoid currption of panel images + // therefore a terminate command is send + self.interface.send_command(Command::TERMINATE_COMMANDS_AND_FRAME_WRITE) + } + + + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ + self.update_frame(buffer)?; + self.display_frame() + } + + + fn clear_frame(&mut self) -> Result<(), E>{ + self.use_full_frame()?; + + // clear the ram with the background color + let color = self.background_color.get_byte_value(); + + self.interface.send_command(Command::WRITE_RAM)?; + self.interface.send_data_x_times(color, WIDTH / 8 * HEIGHT) + } + + /// Sets the backgroundcolor for various commands like [WaveshareInterface::clear_frame()](clear_frame()) + fn set_background_color(&mut self, background_color: Color){ + self.background_color = background_color; + } + +} + +impl EPD2in9 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + fn wait_until_idle(&mut self) { + self.interface.wait_until_idle(false); + } + + pub(crate) fn use_full_frame(&mut self) -> Result<(), E> { + // choose full frame/ram + self.set_ram_area(0, 0, WIDTH - 1, HEIGHT - 1)?; + + // start from the beginning + self.set_ram_counter(0,0) + } + + pub(crate) fn set_ram_area(&mut self, start_x: u16, start_y: u16, end_x: u16, end_y: u16) -> Result<(), E> { + assert!(start_x < end_x); + assert!(start_y < end_y); + + // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram + // aren't relevant + self.interface.send_command(Command::SET_RAM_X_ADDRESS_START_END_POSITION)?; + self.interface.send_data((start_x >> 3) as u8)?; + self.interface.send_data((end_x >> 3) as u8)?; + + // 2 Databytes: A[7:0] & 0..A[8] for each - start and end + self.interface.send_command(Command::SET_RAM_Y_ADDRESS_START_END_POSITION)?; + self.interface.send_data(start_y as u8)?; + self.interface.send_data((start_y >> 8) as u8)?; + self.interface.send_data(end_y as u8)?; + self.interface.send_data((end_y >> 8) as u8) + } + + pub(crate) fn set_ram_counter(&mut self, x: u16, y: u16) -> Result<(), E> { + // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram + // aren't relevant + self.interface.send_command(Command::SET_RAM_X_ADDRESS_COUNTER)?; + self.interface.send_data((x >> 3) as u8)?; + + // 2 Databytes: A[7:0] & 0..A[8] + self.interface.send_command(Command::SET_RAM_Y_ADDRESS_COUNTER)?; + self.interface.send_data(y as u8)?; + self.interface.send_data((y >> 8) as u8)?; + + self.wait_until_idle(); + Ok(()) + } + + /// Uses the slower full update + pub fn set_lut(&mut self) -> Result<(), E> { + self.set_lut_helper(&LUT_FULL_UPDATE) + } + + /// Uses the quick partial refresh + pub fn set_lut_quick(&mut self) -> Result<(), E> { + self.set_lut_helper(&LUT_PARTIAL_UPDATE) + } + + //TODO: assert length for LUT is exactly 30 + fn set_lut_manual(&mut self, buffer: &[u8]) -> Result<(), E> { + self.set_lut_helper(buffer) + } + + + fn set_lut_helper(&mut self, buffer: &[u8]) -> Result<(), E> { + assert!(buffer.len() == 30); + self.interface.send_command(Command::WRITE_LUT_REGISTER)?; + self.interface.send_multiple_data(buffer) + } + +} \ No newline at end of file diff --git a/src/epd4in2/command.rs b/src/epd4in2/command.rs index c81bd79..e22787c 100644 --- a/src/epd4in2/command.rs +++ b/src/epd4in2/command.rs @@ -1,5 +1,5 @@ //! SPI Commands for the Waveshare 4.2" E-Ink Display - +use interface; /// EPD4IN2 commands /// /// Should rarely (never?) be needed directly. @@ -97,7 +97,7 @@ pub enum Command { LOW_POWER_DETECTION = 0x51, /// This command defines non-overlap period of Gate and Source. TCON_SETTING = 0x60, - /// This command defines alternative resolution and this setting is of higher priority than the RES[1:0] in R00H (PSR). + /// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR). RESOLUTION_SETTING = 0x61, /// This command defines the Fist Active Gate and First Active Source of active channels. GSST_SETTING = 0x65, @@ -146,9 +146,9 @@ pub enum Command { -impl Command { +impl interface::Command for Command { /// Returns the address of the command - pub fn addr(self) -> u8 { + fn address(self) -> u8 { self as u8 } } @@ -157,13 +157,14 @@ impl Command { #[cfg(test)] mod tests { use super::*; + use interface::Command as CommandTrait; #[test] fn command_addr() { - assert_eq!(Command::POWER_SAVING.addr(), 0xE3); + assert_eq!(Command::POWER_SAVING.address(), 0xE3); - assert_eq!(Command::PANEL_SETTING.addr(), 0x00); + assert_eq!(Command::PANEL_SETTING.address(), 0x00); - assert_eq!(Command::DISPLAY_REFRESH.addr(), 0x12); + assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12); } } \ No newline at end of file diff --git a/src/epd4in2/lut.rs b/src/epd4in2/constants.rs similarity index 91% rename from src/epd4in2/lut.rs rename to src/epd4in2/constants.rs index ab032a5..190181f 100644 --- a/src/epd4in2/lut.rs +++ b/src/epd4in2/constants.rs @@ -1,3 +1,6 @@ +pub(crate) const WIDTH: usize = 400; +pub(crate) const HEIGHT: usize = 300; + pub(crate) const LUT_VCOM0: [u8; 44] = [ 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, @@ -8,7 +11,7 @@ pub(crate) const LUT_VCOM0: [u8; 44] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -#[cfg(feature = "epd4in2_fast_update")] +#[cfg(feature = "epd4in2_fast_update")] pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -19,6 +22,7 @@ pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; + pub(crate) const LUT_WW: [u8; 42] =[ 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, @@ -29,7 +33,7 @@ pub(crate) const LUT_WW: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -#[cfg(feature = "epd4in2_fast_update")] +#[cfg(feature = "epd4in2_fast_update")] pub(crate) const LUT_WW_QUICK: [u8; 42] =[ 0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -51,7 +55,7 @@ pub(crate) const LUT_BW: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -#[cfg(feature = "epd4in2_fast_update")] +#[cfg(feature = "epd4in2_fast_update")] pub(crate) const LUT_BW_QUICK: [u8; 42] =[ 0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -73,7 +77,7 @@ pub(crate) const LUT_BB: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -#[cfg(feature = "epd4in2_fast_update")] +#[cfg(feature = "epd4in2_fast_update")] pub(crate) const LUT_BB_QUICK: [u8; 42] =[ 0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -95,7 +99,7 @@ pub(crate) const LUT_WB: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; -#[cfg(feature = "epd4in2_fast_update")] +#[cfg(feature = "epd4in2_fast_update")] pub(crate) const LUT_WB_QUICK: [u8; 42] =[ 0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index 89bf400..0f5ee76 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -1,27 +1,27 @@ //! A simple Driver for the Waveshare 4.2" E-Ink Display via SPI -//! +//! //! The other Waveshare E-Ink Displays should be added later on -//! -//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/4.2inch_e-Paper_Module), +//! +//! 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. //! //! This driver was built using [`embedded-hal`] traits. //! //! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.1 -//! +//! //! # Requirements -//! +//! //! ### SPI -//! +//! //! - MISO is not connected/available //! - SPI_MODE_0 is used (CPHL = 0, CPOL = 0) //! - 8 bits per word, MSB first //! - Max. Speed tested was 8Mhz but more should be possible -//! +//! //! ### Other.... -//! -//! - Buffersize: Wherever a buffer is used it always needs to be of the size: `width / 8 * length`, +//! +//! - Buffersize: Wherever a buffer is used it always needs to be of the size: `width / 8 * length`, //! where width and length being either the full e-ink size or the partial update window size //! //! # Examples @@ -30,81 +30,63 @@ //! let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay).unwrap(); //! //! let mut buffer = [0u8, epd4in2.get_width() / 8 * epd4in2.get_height()]; -//! +//! //! // draw something into the buffer -//! +//! //! epd4in2.display_and_transfer_buffer(buffer, None); -//! +//! //! // wait and look at the image -//! +//! //! epd4in2.clear_frame(None); -//! +//! //! epd4in2.sleep(); //! ``` -//! -//! +//! +//! //! //! BE CAREFUL! The screen can get ghosting/burn-ins through the Partial Fast Update Drawing. - use hal::{ - blocking::{ - spi::Write, - delay::* - }, + blocking::{delay::*, spi::Write}, + digital::*, spi::{Mode, Phase, Polarity}, - digital::* }; +use interface::{connection_interface::ConnectionInterface, WaveshareInterface}; + //The Lookup Tables for the Display -mod lut; -use self::lut::*; +mod constants; +use self::constants::*; use drawing::color::Color; pub mod command; -pub use command::Command as Command; +pub use self::command::Command; //TODO: test spi mode -/// SPI mode - +/// SPI mode - /// For more infos see [Requirements: SPI](index.html#spi) pub const SPI_MODE: Mode = Mode { phase: Phase::CaptureOnFirstTransition, polarity: Polarity::IdleLow, }; - - - - - - - - /// EPD4in2 driver /// pub struct EPD4in2 { - /// SPI - spi: SPI, - /// CS for SPI - cs: CS, - /// Low for busy, Wait until display is ready! - busy: BUSY, - /// Data/Command Control Pin (High for data, Low for command) - dc: DC, - /// Pin for Reseting - rst: RST, - /// The concrete Delay implementation - delay: D, + /// Connection Interface + interface: ConnectionInterface, /// Width width: u16, /// Height - height: u16, + height: u16, + /// Background Color + color: Color, } - -impl EPD4in2 -where +impl WaveshareInterface + for EPD4in2 +where SPI: Write, CS: OutputPin, BUSY: InputPin, @@ -112,54 +94,48 @@ where RST: OutputPin, D: DelayUs + DelayMs, { - /// Get the width of the display - pub fn get_width(&self) -> u16 { + fn get_width(&self) -> u16 { self.width } - /// Get the height of the display - pub fn get_height(&self) -> u16 { + fn get_height(&self) -> u16 { self.height } - - + /// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC - /// - /// This already initialises the device. That means [EPD4in2::init()](EPD4in2::init()) isn't needed directly afterwards - /// + /// + /// This already initialises the device. That means [init()](init()) isn't needed directly afterwards + /// /// # Example - /// + /// /// ```ignore /// //buffer = some image data; - /// + /// /// let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay); - /// + /// /// epd4in2.display_and_transfer_frame(buffer, None); - /// + /// /// epd4in2.sleep(); /// ``` - pub fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Result { - //TODO: width und height anpassbar machen? - let width = 400; - let height = 300; - - let mut epd4in2 = EPD4in2 {spi, cs, busy, dc, rst, delay, width, height }; - - epd4in2.init()?; - - Ok(epd4in2) + fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Result { + let width = WIDTH as u16; + let height = HEIGHT as u16; + + let interface = ConnectionInterface::new(spi, cs, busy, dc, rst, delay); + let color = Color::White; + let mut epd = EPD4in2 { + interface, + width, + height, + color, + }; + + epd.init()?; + + Ok(epd) } - - - /// This initialises the EPD and powers it up - /// - /// This function is already called from [EPD4in2::new()](EPD4in2::new()) - /// - /// This function calls [EPD4in2::reset()](EPD4in2::reset()), - /// so you don't need to call reset your self when trying to wake your device up - /// after setting it to sleep. - pub fn init(&mut self) -> Result<(), E> { + fn init(&mut self) -> Result<(), E> { // reset the device self.reset(); @@ -174,7 +150,7 @@ where // start the booster self.send_command(Command::BOOSTER_SOFT_START)?; for _ in 0..3 { - self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f + self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f } // power on @@ -197,68 +173,46 @@ where // 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 + // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ DEFAULT: 3c 50Hz self.send_command(Command::PLL_CONTROL)?; self.send_data(0x3A)?; + self.set_lut()?; + Ok(()) } + fn sleep(&mut self) -> Result<(), E> { + self.send_command(Command::VCOM_AND_DATA_INTERVAL_SETTING)?; + self.send_data(0x17)?; //border floating + self.send_command(Command::VCM_DC_SETTING)?; // VCOM to 0V + self.send_command(Command::PANEL_SETTING)?; + self.delay_ms(100); - - - - - /// Transmit partial data to the SRAM of the EPD, - /// the final parameter dtm chooses between the 2 - /// internal buffers - /// - /// Normally it should be dtm2, so use false - /// - /// BUFFER needs to be of size: w / 8 * l ! - pub fn set_partial_window(&mut self, buffer: &[u8], x: u16, y: u16, w: u16, l: u16, is_dtm1: bool) -> Result<(), E> { - if buffer.len() as u16 != w / 8 * l { - //TODO: panic!! or sth like that - //return Err("Wrong buffersize"); + self.send_command(Command::POWER_SETTING)?; //VG&VS to 0V fast + for _ in 0..4 { + self.send_data(0x00)?; } + self.delay_ms(100); - self.send_command(Command::PARTIAL_IN)?; - self.send_command(Command::PARTIAL_WINDOW)?; - self.send_data((x >> 8) as u8)?; - let tmp = x & 0xf8; - self.send_data(tmp as u8)?; // x should be the multiple of 8, the last 3 bit will always be ignored - let tmp = tmp + w - 1; - self.send_data((tmp >> 8) as u8)?; - self.send_data((tmp | 0x07) as u8)?; - - self.send_data((y >> 8) as u8)?; - self.send_data(y as u8)?; - - self.send_data(((y + l - 1) >> 8) as u8)?; - self.send_data((y + l - 1) as u8)?; - - self.send_data(0x01)?; // Gates scan both inside and outside of the partial window. (default) - - if is_dtm1 { - self.send_command(Command::DATA_START_TRANSMISSION_1)? - } else { - self.send_command(Command::DATA_START_TRANSMISSION_2)? - } + self.send_command(Command::POWER_OFF)?; + self.wait_until_idle(); + self.send_command(Command::DEEP_SLEEP)?; + self.send_data(0xA5)?; - self.send_multiple_data(buffer)?; + Ok(()) + } - self.send_command(Command::PARTIAL_OUT) + fn reset(&mut self) { + self.interface.reset() } - + fn delay_ms(&mut self, delay: u16) { + self.interface.delay_ms(delay) + } - // void DisplayFrame(const unsigned char* frame_buffer); - /// Display the frame data from SRAM - /// Uses the SLOW!! full update/refresh - /// Default color: 0xff - /// - pub fn display_and_transfer_frame(&mut self, buffer: &[u8], color: Option) -> Result<(), E>{ - let color = color.unwrap_or(0xff); + fn update_frame(&mut self, buffer: &[u8]) -> Result<(), E> { + let color_value = self.color.get_byte_value(); self.send_resolution()?; @@ -270,86 +224,91 @@ where //self.send_command_u8(0x97)?; //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 self.send_data(0x97)?; - self.send_command(Command::DATA_START_TRANSMISSION_1)?; for _ in 0..(buffer.len()) { - self.send_data(color)?; + self.send_data(color_value)?; } self.delay_ms(2); self.send_command(Command::DATA_START_TRANSMISSION_2)?; //self.send_multiple_data(buffer)?; + for &elem in buffer.iter() { self.send_data(elem)?; } - self.delay_ms(2); - - self.set_lut()?; - - self.send_command(Command::DISPLAY_REFRESH)?; - //TODO: adapt time, is this long delay really needed? - self.delay_ms(10); - self.wait_until_idle(); Ok(()) } - fn send_resolution(&mut self) -> Result<(), E> { - let w = self.get_width(); - let h = self.get_height(); + fn update_partial_frame( + &mut self, + buffer: &[u8], + x: u16, + y: u16, + width: u16, + height: u16, + ) -> Result<(), E> { + if buffer.len() as u16 != width / 8 * height { + //TODO: panic!! or sth like that + //return Err("Wrong buffersize"); + } - self.send_command(Command::RESOLUTION_SETTING)?; - self.send_data((w >> 8) as u8)?; - self.send_data(w as u8)?; - self.send_data((h >> 8) as u8)?; - self.send_data(h as u8) - } + self.send_command(Command::PARTIAL_IN)?; + self.send_command(Command::PARTIAL_WINDOW)?; + self.send_data((x >> 8) as u8)?; + let tmp = x & 0xf8; + self.send_data(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((tmp >> 8) as u8)?; + self.send_data((tmp | 0x07) as u8)?; - /// Displays the frame data from SRAM - pub fn display_frame(&mut self) -> Result<(), E> { - self.set_lut()?; - self.send_command(Command::DISPLAY_REFRESH)?; + self.send_data((y >> 8) as u8)?; + self.send_data(y as u8)?; - self.delay_ms(100); - self.wait_until_idle(); - Ok(()) + self.send_data(((y + height - 1) >> 8) as u8)?; + self.send_data((y + height - 1) as u8)?; + + self.send_data(0x01)?; // Gates scan both inside and outside of the partial window. (default) + + //TODO: handle dtm somehow + let is_dtm1 = false; + if is_dtm1 { + self.send_command(Command::DATA_START_TRANSMISSION_1)? + } else { + self.send_command(Command::DATA_START_TRANSMISSION_2)? + } + + self.send_multiple_data(buffer)?; + + self.send_command(Command::PARTIAL_OUT) + } + + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ + self.update_frame(buffer)?; + self.display_frame() } - /// Same as display_frame(), but with nearly no delay - /// and uses the fast/partial refresh LUT - /// needs more testing!!! - /// maybe delay can be fully removed as wait_until_idle should do - /// the necessary stuff - /// TODO: check delay!!! - /// Displays the frame data from SRAM - #[cfg(feature = "epd4in2_fast_update")] - pub fn display_frame_quick(&mut self) -> Result<(), E> { - self.set_lut_quick()?; + + fn display_frame(&mut self) -> Result<(), E> { self.send_command(Command::DISPLAY_REFRESH)?; - self.delay_ms(1); self.wait_until_idle(); Ok(()) } - - /// Clears the frame from the buffer - /// - /// Set a reset_color if you want a different from the default 0xff - /// - /// TODO: should that option be removed? E.g. the struct contains an additional default background value - /// which is settable? - pub fn clear_frame(&mut self, reset_color: Option) -> Result<(), E> { - let reset_color: Color = reset_color.unwrap_or(Color::White); + // TODO: add this abstraction function + // fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + fn clear_frame(&mut self) -> Result<(), E> { self.send_resolution()?; let size = self.width / 8 * self.height; + let color_value = self.color.get_byte_value(); self.send_command(Command::DATA_START_TRANSMISSION_1)?; self.delay_ms(2); for _ in 0..size { - self.send_data(reset_color.get_byte_value())?; + self.send_data(color_value)?; } self.delay_ms(2); @@ -357,204 +316,102 @@ where self.send_command(Command::DATA_START_TRANSMISSION_2)?; self.delay_ms(2); for _ in 0..size { - self.send_data(reset_color.get_byte_value())?; + self.send_data(color_value)?; } Ok(()) } - /// Let the device enter deep-sleep mode to save power. - /// - /// The deep sleep mode returns to standby with a hardware reset. - /// But you can also use [EPD4in2::reset()](EPD4in2::reset()) to awaken. - /// But as you need to power it up once more anyway you can also just directly use [EPD4in2::init()](EPD4in2::init()) for resetting - /// and initialising which already contains the reset - pub fn sleep(&mut self) -> Result<(), E> { - self.send_command(Command::VCOM_AND_DATA_INTERVAL_SETTING)?; - self.send_data(0x17)?; //border floating - self.send_command(Command::VCM_DC_SETTING)?; // VCOM to 0V - self.send_command(Command::PANEL_SETTING)?; - self.delay_ms(100); - - self.send_command(Command::POWER_SETTING)?; //VG&VS to 0V fast - for _ in 0..4 { - self.send_data(0x00)?; - } - self.delay_ms(100); - - self.send_command(Command::POWER_OFF)?; - self.wait_until_idle(); - self.send_command(Command::DEEP_SLEEP)?; - self.send_data(0xA5)?; - - Ok(()) + /// Sets the backgroundcolor for various commands like [WaveshareInterface::clear_frame()](clear_frame()) + fn set_background_color(&mut self, color: Color) { + self.color = color; } +} - /// Resets the device. - /// - /// Often used to awake the module from deep sleep. See [EPD4in2::sleep()](EPD4in2::sleep()) - /// - /// TODO: Takes at least 400ms of delay alone, can it be shortened? - pub fn reset(&mut self) { - self.rst.set_low(); +impl EPD4in2 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + fn send_command(&mut self, command: Command) -> Result<(), E> { + self.interface.send_command(command) + } - //TODO: why 200ms? (besides being in the waveshare code) - self.delay_ms(200); + fn send_data(&mut self, val: u8) -> Result<(), E> { + self.interface.send_data(val) + } - self.rst.set_high(); + fn send_multiple_data(&mut self, data: &[u8]) -> Result<(), E> { + self.interface.send_multiple_data(data) + } - //TODO: same as 3 lines above - self.delay_ms(200); + fn wait_until_idle(&mut self) { + self.interface.wait_until_idle(true) } + fn send_resolution(&mut self) -> Result<(), E> { + let w = self.get_width(); + let h = self.get_height(); + self.send_command(Command::RESOLUTION_SETTING)?; + self.send_data((w >> 8) as u8)?; + self.send_data(w as u8)?; + self.send_data((h >> 8) as u8)?; + self.send_data(h as u8) + } /// Fill the look-up table for the EPD - //TODO: make public? + //TODO: make public? fn set_lut(&mut self) -> Result<(), E> { - self.set_lut_helper( - &LUT_VCOM0, - &LUT_WW, - &LUT_BW, - &LUT_WB, - &LUT_BB) + self.set_lut_helper(&LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB) } /// Fill the look-up table for a quick display (partial refresh) /// /// Is automatically done by [EPD4in2::display_frame_quick()](EPD4in2::display_frame_quick()) /// //TODO: make public? - #[cfg(feature = "epd4in2_fast_update")] + #[cfg(feature = "epd4in2_fast_update")] fn set_lut_quick(&mut self) -> Result<(), E> { self.set_lut_helper( &LUT_VCOM0_QUICK, &LUT_WW_QUICK, &LUT_BW_QUICK, &LUT_WB_QUICK, - &LUT_BB_QUICK) + &LUT_BB_QUICK, + ) } - fn set_lut_helper(&mut self, - lut_vcom: &[u8], - lut_ww: &[u8], - lut_bw: &[u8], - lut_wb: &[u8], - lut_bb: &[u8]) -> Result<(), E> - { - //vcom + fn set_lut_helper( + &mut self, + lut_vcom: &[u8], + lut_ww: &[u8], + lut_bw: &[u8], + lut_wb: &[u8], + lut_bb: &[u8], + ) -> Result<(), E> { + // LUT VCOM self.send_command(Command::LUT_FOR_VCOM)?; self.send_multiple_data(lut_vcom)?; - //ww -- + // LUT WHITE to WHITE self.send_command(Command::LUT_WHITE_TO_WHITE)?; self.send_multiple_data(lut_ww)?; - //bw r + // LUT BLACK to WHITE self.send_command(Command::LUT_BLACK_TO_WHITE)?; self.send_multiple_data(lut_bw)?; - //wb w + // LUT WHITE to BLACK self.send_command(Command::LUT_WHITE_TO_BLACK)?; self.send_multiple_data(lut_wb)?; - //bb b + // LUT BLACK to BLACK self.send_command(Command::LUT_BLACK_TO_BLACK)?; self.send_multiple_data(lut_bb)?; Ok(()) } - - /// Basic function for sending [Commands](Command). - /// - /// Enables direct interaction with the device with the help of [EPD4in2::send_data()](EPD4in2::send_data()) - /// Should rarely be needed! - /// //TODO: make public? - fn send_command(&mut self, command: Command) -> Result<(), E> { - // low for commands - self.dc.set_low(); - - // Transfer the command over spi - self.with_cs(|epd| { - epd.spi.write(&[command.addr()]) - }) - } - - /// Basic function for sending a single u8 of data over spi - /// - /// Enables direct interaction with the device with the help of [EPD4in2::send_command()](EPD4in2::send_command()) - /// - /// Should rarely be needed! - /// //TODO: make public? - fn send_data(&mut self, val: u8) -> Result<(), E> { - // high for data - self.dc.set_high(); - - // Transfer data (u8) over spi - self.with_cs(|epd| { - epd.spi.write(&[val]) - }) - } - - /// Basic function for sending an array of u8-values of data over spi - /// - /// Enables direct interaction with the device with the help of [EPD4in2::send_command()](EPD4in2::send_command()) - /// - /// Should rarely be needed! - /// //TODO: make public? - fn send_multiple_data(&mut self, data: &[u8]) -> Result<(), E> { - // high for data - self.dc.set_high(); - - // Transfer data (u8-array) over spi - self.with_cs(|epd| { - epd.spi.write(data) - }) - } - - // spi write helper/abstraction function - fn with_cs(&mut self, f: F) -> Result<(), E> - where - F: FnOnce(&mut Self) -> Result<(), E>, - { - // activate spi with cs low - self.cs.set_low(); - // transfer spi data - let result = f(self); - // deativate spi with cs high - self.cs.set_high(); - // return result - result - } - - - /// Waits until device isn't busy anymore (busy == HIGH) - /// - /// This is normally handled by the more complicated commands themselves, - /// but in the case you send data and commands directly you might need to check - /// if the device is still busy - pub fn wait_until_idle(&mut self) { - //low: busy, high: idle - while self.busy.is_low() { - //TODO: shorten the time? it was 100 in the beginning - self.delay_ms(10); - } - } - - - /// Abstraction of setting the delay for simpler calls - pub fn delay_ms(&mut self, delay: u16) { - self.delay.delay_ms(delay); - } } - - - - - - - - - - - - - diff --git a/src/interface/connection_interface.rs b/src/interface/connection_interface.rs new file mode 100644 index 0000000..8d47708 --- /dev/null +++ b/src/interface/connection_interface.rs @@ -0,0 +1,170 @@ +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + digital::* +}; + +use interface::Command; + +/// EPD4in2 driver +/// +pub(crate) struct ConnectionInterface { + /// SPI + spi: SPI, + /// CS for SPI + cs: CS, + /// Low for busy, Wait until display is ready! + busy: BUSY, + /// Data/Command Control Pin (High for data, Low for command) + dc: DC, + /// Pin for Reseting + rst: RST, + /// The concrete Delay implementation + delay: D, +} + + +impl ConnectionInterface +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + pub(crate) fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Self { + ConnectionInterface {spi, cs, busy, dc, rst, delay } + } + + /// Basic function for sending [Commands](Command). + /// + /// Enables direct interaction with the device with the help of [send_data()](ConnectionInterface::send_data()) + /// Should rarely be needed! + /// //TODO: make public? + pub(crate) fn send_command(&mut self, command: T) -> Result<(), E> { + // low for commands + self.dc.set_low(); + + // Transfer the command over spi + self.with_cs(|epd| { + epd.spi.write(&[command.address()]) + }) + } + + /// Basic function for sending a single u8 of data over spi + /// + /// Enables direct interaction with the device with the help of [Esend_command()](ConnectionInterface::send_command()) + /// + /// Should rarely be needed! + /// //TODO: make public? + pub(crate) fn send_data(&mut self, val: u8) -> Result<(), E> { + // high for data + self.dc.set_high(); + + // Transfer data (u8) over spi + self.with_cs(|epd| { + epd.spi.write(&[val]) + }) + } + + /// Basic function for sending a single u8 of data over spi + /// + /// Enables direct interaction with the device with the help of [Esend_command()](ConnectionInterface::send_command()) + /// + /// Should rarely be needed! + /// //TODO: make public? + pub(crate) fn send_data_x_times(&mut self, val: u8, repetitions: u16) -> Result<(), E> { + // high for data + self.dc.set_high(); + + // Transfer data (u8) over spi + self.with_cs(|epd| { + for _ in 0..repetitions { + epd.spi.write(&[val])?; + } + Ok(()) + }) + } + + /// Basic function for sending an array of u8-values of data over spi + /// + /// Enables direct interaction with the device with the help of [send_command()](EPD4in2::send_command()) + /// + /// Should rarely be needed! + /// //TODO: make public? + pub(crate) fn send_multiple_data(&mut self, data: &[u8]) -> Result<(), E> { + // high for data + self.dc.set_high(); + + // Transfer data (u8-array) over spi + self.with_cs(|epd| { + epd.spi.write(data) + }) + } + + // spi write helper/abstraction function + pub(crate) fn with_cs(&mut self, f: F) -> Result<(), E> + where + F: FnOnce(&mut Self) -> Result<(), E>, + { + // activate spi with cs low + self.cs.set_low(); + // transfer spi data + let result = f(self); + // deativate spi with cs high + self.cs.set_high(); + // return result + result + } + + + /// Waits until device isn't busy anymore (busy == HIGH) + /// + /// This is normally handled by the more complicated commands themselves, + /// but in the case you send data and commands directly you might need to check + /// if the device is still busy + /// + /// is_busy_low + /// + /// - TRUE for epd4in2, epd1in54, epd2in13, epd2in7, epd5in83, epd7in5 + /// - FALSE for epd2in9 + /// + /// Most likely there was a mistake with the 2in9 busy connection + pub(crate) fn wait_until_idle(&mut self, is_busy_low: bool) { + self.delay_ms(1); + //low: busy, high: idle + while (is_busy_low && self.busy.is_low()) || (!is_busy_low && self.busy.is_high()) { + //TODO: shorten the time? it was 100 in the beginning + self.delay_ms(5); + } + } + + + /// Abstraction of setting the delay for simpler calls + /// + /// maximum delay ~65 seconds (u16:max in ms) + pub(crate) fn delay_ms(&mut self, delay: u16) { + self.delay.delay_ms(delay); + } + + /// Resets the device. + /// + /// Often used to awake the module from deep sleep. See [EPD4in2::sleep()](EPD4in2::sleep()) + /// + /// TODO: Takes at least 400ms of delay alone, can it be shortened? + pub(crate) fn reset(&mut self) { + self.rst.set_low(); + + //TODO: why 200ms? (besides being in the waveshare code) + self.delay_ms(200); + + self.rst.set_high(); + + //TODO: same as 3 lines above + self.delay_ms(200); + } + +} \ No newline at end of file diff --git a/src/interface/mod.rs b/src/interface/mod.rs new file mode 100644 index 0000000..9c23c8e --- /dev/null +++ b/src/interface/mod.rs @@ -0,0 +1,158 @@ +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + spi::{Mode, Phase, Polarity}, + digital::* +}; + +use drawing::color::Color; + +pub mod connection_interface; + +//TODO: test spi mode +/// SPI mode - +/// For more infos see [Requirements: SPI](index.html#spi) +pub const SPI_MODE: Mode = Mode { + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, +}; + +use core::marker::Sized; + +pub(crate) trait Command { + fn address(self) -> u8; +} + + +//TODO add LUT trait with set_fast_lut +// and set_manual_lut and set_normal_lut or sth like that +// for partial updates + +pub trait LUTSupport { + fn set_lut(&mut self) -> Result<(), Error>; + fn set_lut_quick(&mut self) -> Result<(), Error>; + fn set_lut_manual(&mut self, data: &[u8]) -> Result<(), Error>; +} + + +pub trait WaveshareInterface + where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + /// Get the width of the display + fn get_width(&self) -> u16; + + /// Get the height of the display + fn get_height(&self) -> u16; + + /// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC + /// + /// This already initialises the device. That means [init()](WaveshareInterface::init()) isn't needed directly afterwards + fn new( + spi: SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: D + ) -> Result + where Self: Sized; + + /// This initialises the EPD and powers it up + /// + /// This function is already called from [new()](WaveshareInterface::new()) + /// + /// This function calls [reset()](WaveshareInterface::reset()), + /// so you don't need to call reset your self when trying to wake your device up + /// after setting it to sleep. + fn init(&mut self) -> Result<(), E>; + + + // void DisplayFrame(const unsigned char* frame_buffer); + /// Transmit a full frame to the SRAM of the DPD + /// + fn update_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + + //TODO: is dtm always used? + /// Transmit partial data to the SRAM of the EPD, + /// the final parameter dtm chooses between the 2 + /// internal buffers + /// + /// Normally it should be dtm2, so use false + /// + /// BUFFER needs to be of size: w / 8 * l ! + fn update_partial_frame(&mut self, buffer: &[u8], x: u16, y: u16, width: u16, height: u16) -> Result<(), E>; + + /// Displays the frame data from SRAM + fn display_frame(&mut self) -> Result<(), E>; + + // TODO: add this abstraction function + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + + /// Clears the frame from the buffer + /// + /// Uses the chosen background color + fn clear_frame(&mut self) -> Result<(), E>; + + /// Sets the backgroundcolor for various commands like [clear_frame()](WaveshareInterface::clear_frame()) + fn set_background_color(&mut self, color: Color); + + + /// Let the device enter deep-sleep mode to save power. + /// + /// The deep sleep mode returns to standby with a hardware reset. + /// But you can also use [reset()](WaveshareInterface::reset()) to awaken. + /// But as you need to power it up once more anyway you can also just directly use [init()](WaveshareInterface::init()) for resetting + /// and initialising which already contains the reset + fn sleep(&mut self) -> Result<(), E>; + + /// Resets the device. + /// + /// Often used to awake the module from deep sleep. See [sleep()](WaveshareInterface::sleep()) + fn reset(&mut self); + + /// Abstraction of setting the delay for simpler calls + /// + /// maximum delay ~65 seconds (u16:max in ms) + fn delay_ms(&mut self, delay: u16); + + /* + + + // + -set_quick_lut? + -set_normal_mode + + + fn clear_frame(&mut self, reset_color: Option) -> Result<(), E> + + fn display_frame_quick(&mut self) -> Result<(), E> + + fn display_frame(&mut self) -> Result<(), E> + + pub fn display_and_transfer_frame( + &mut self, + buffer: &[u8], + color: Option +) -> Result<(), E> + + pub fn set_partial_window( + &mut self, + buffer: &[u8], + x: u16, + y: u16, + w: u16, + l: u16, + is_dtm1: bool +) -> Result<(), E> + +*/ + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ca53864..0dd8b57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,8 @@ //! //! BE CAREFUL! The Partial Drawing can "destroy" your display. //! It needs more testing first. +//! +//! TODO: Make more assertions about buffersizes? #![no_std] @@ -58,7 +60,14 @@ use hal::{ pub mod drawing; pub mod epd4in2; -use epd4in2::*; + + + +pub mod epd2in9; + +pub mod interface; + + //TODO: test spi mode /// SPI mode -