From 4cd9b26a24a79885d20d38f2297b004ee3768f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Tue, 17 Jul 2018 16:02:14 +0200 Subject: [PATCH 1/7] Started work on 2in9 waveshare device, but nothing much besides copying has yet happened --- src/epd2in9/command.rs | 169 +++++++++++++ src/epd2in9/lut.rs | 102 ++++++++ src/epd2in9/mod.rs | 560 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 831 insertions(+) create mode 100644 src/epd2in9/command.rs create mode 100644 src/epd2in9/lut.rs create mode 100644 src/epd2in9/mod.rs diff --git a/src/epd2in9/command.rs b/src/epd2in9/command.rs new file mode 100644 index 0000000..c81bd79 --- /dev/null +++ b/src/epd2in9/command.rs @@ -0,0 +1,169 @@ +//! SPI Commands for the Waveshare 4.2" E-Ink Display + +/// EPD4IN2 commands +/// +/// Should rarely (never?) be needed directly. +/// +/// For more infos about the addresses and what they are doing look into the pdfs +/// +/// The description of the single commands is mostly taken from IL0398.pdf +#[allow(dead_code)] +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +pub enum Command { + /// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift direction, booster switch, soft reset + PANEL_SETTING = 0x00, + /// selecting internal and external power + POWER_SETTING = 0x01, + /// After the Power Off command, the driver will power off following the Power Off Sequence. This command will turn off charge + /// pump, T-con, source driver, gate driver, VCOM, and temperature sensor, but register data will be kept until VDD becomes OFF. + /// Source Driver output and Vcom will remain as previous condition, which may have 2 conditions: floating. + POWER_OFF = 0x02, + /// Setting Power OFF sequence + POWER_OFF_SEQUENCE_SETTING = 0x03, + /// Turning On the Power + POWER_ON = 0x04, + /// This command enables the internal bandgap, which will be cleared by the next POF. + POWER_ON_MEASURE = 0x05, + /// Starting data transmission + BOOSTER_SOFT_START = 0x06, + /// After this command is transmitted, the chip would enter the deep-sleep mode to save power. + /// + /// The deep sleep mode would return to standby by hardware reset. + /// + /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. + DEEP_SLEEP = 0x07, + /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data + /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. + /// + /// - In B/W mode, this command writes “OLD” data to SRAM. + /// - In B/W/Red mode, this command writes “B/W” data to SRAM. + /// - In Program mode, this command writes “OTP” data to SRAM for programming. + DATA_START_TRANSMISSION_1 = 0x10, + /// Stopping data transmission + DATA_STOP = 0x11, + /// While user sent this command, driver will refresh display (data/VCOM) according to SRAM data and LUT. + /// + /// After Display Refresh command, BUSY_N signal will become “0” and the refreshing of panel starts. + DISPLAY_REFRESH = 0x12, + /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data + /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. + /// - In B/W mode, this command writes “NEW” data to SRAM. + /// - In B/W/Red mode, this command writes “RED” data to SRAM. + DATA_START_TRANSMISSION_2 = 0x13, + + /// This command stores VCOM Look-Up Table with 7 groups of data. Each group contains information for one state and is stored + /// with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LUT_FOR_VCOM = 0x20, + /// This command stores White-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LUT_WHITE_TO_WHITE = 0x21, + /// This command stores Black-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LUT_BLACK_TO_WHITE = 0x22, + /// This command stores White-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LUT_WHITE_TO_BLACK = 0x23, + /// This command stores Black-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is + /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. + /// + /// from IL0373 + LUT_BLACK_TO_BLACK = 0x24, + /// The command controls the PLL clock frequency. + PLL_CONTROL = 0x30, + /// This command reads the temperature sensed by the temperature sensor. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TEMPERATURE_SENSOR_COMMAND = 0x40, + /// Selects the Internal or External temperature sensor and offset + TEMPERATURE_SENSOR_SELECTION = 0x41, + /// Write External Temperature Sensor + TEMPERATURE_SENSOR_WRITE = 0x42, + /// Read External Temperature Sensor + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TEMPERATURE_SENSOR_READ = 0x43, + /// This command indicates the interval of Vcom and data output. When setting the vertical back porch, the total blanking will be kept (20 Hsync) + VCOM_AND_DATA_INTERVAL_SETTING = 0x50, + /// This command indicates the input power condition. Host can read this flag to learn the battery condition. + 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). + RESOLUTION_SETTING = 0x61, + /// This command defines the Fist Active Gate and First Active Source of active channels. + GSST_SETTING = 0x65, + /// The LUT_REV / Chip Revision is read from OTP address = 0x001. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + REVISION = 0x70, + /// Read Flags. This command reads the IC status + /// PTL, I2C_ERR, I2C_BUSY, DATA, PON, POF, BUSY + /// + /// Doesn't work! Waveshare doesn't connect the read pin + GET_STATUS = 0x71, + /// Automatically measure VCOM. This command reads the IC status + AUTO_MEASUREMENT_VCOM = 0x80, + /// This command gets the VCOM value + /// + /// Doesn't work! Waveshare doesn't connect the read pin + READ_VCOM_VALUE = 0x81, + /// Set VCM_DC + VCM_DC_SETTING = 0x82, + /// This command sets partial window + PARTIAL_WINDOW = 0x90, + /// This command makes the display enter partial mode + PARTIAL_IN = 0x91, + /// This command makes the display exit partial mode and enter normal mode + PARTIAL_OUT = 0x92, + /// After this command is issued, the chip would enter the program mode. + /// + /// After the programming procedure completed, a hardware reset is necessary for leaving program mode. + /// + /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. + PROGRAM_MODE = 0xA0, + /// After this command is transmitted, the programming state machine would be activated. + /// + /// The BUSY flag would fall to 0 until the programming is completed. + ACTIVE_PROGRAMMING = 0xA1, + /// The command is used for reading the content of OTP for checking the data of programming. + /// + /// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF. + READ_OTP = 0xA2, + /// This command is set for saving power during fresh period. If the output voltage of VCOM / Source is from negative to positive or + /// from positive to negative, the power saving mechanism will be activated. The active period width is defined by the following two + /// parameters. + POWER_SAVING = 0xE3, +} + + + +impl Command { + /// Returns the address of the command + pub fn addr(self) -> u8 { + self as u8 + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn command_addr() { + assert_eq!(Command::POWER_SAVING.addr(), 0xE3); + + 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/lut.rs b/src/epd2in9/lut.rs new file mode 100644 index 0000000..55c7d39 --- /dev/null +++ b/src/epd2in9/lut.rs @@ -0,0 +1,102 @@ +pub(crate) const LUT_VCOM0: [u8; 44] = [ +0x00, 0x17, 0x00, 0x00, 0x00, 0x02, +0x00, 0x17, 0x17, 0x00, 0x00, 0x02, +0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, +0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ +0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_WW: [u8; 42] =[ +0x40, 0x17, 0x00, 0x00, 0x00, 0x02, +0x90, 0x17, 0x17, 0x00, 0x00, 0x02, +0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, +0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_WW_QUICK: [u8; 42] =[ +0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + + +pub(crate) const LUT_BW: [u8; 42] =[ +0x40, 0x17, 0x00, 0x00, 0x00, 0x02, +0x90, 0x17, 0x17, 0x00, 0x00, 0x02, +0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, +0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_BW_QUICK: [u8; 42] =[ +0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + + +pub(crate) const LUT_BB: [u8; 42] =[ +0x80, 0x17, 0x00, 0x00, 0x00, 0x02, +0x90, 0x17, 0x17, 0x00, 0x00, 0x02, +0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, +0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_BB_QUICK: [u8; 42] =[ +0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + + +pub(crate) const LUT_WB: [u8; 42] =[ +0x80, 0x17, 0x00, 0x00, 0x00, 0x02, +0x90, 0x17, 0x17, 0x00, 0x00, 0x02, +0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, +0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_WB_QUICK: [u8; 42] =[ +0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; \ No newline at end of file diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs new file mode 100644 index 0000000..3f53d9a --- /dev/null +++ b/src/epd2in9/mod.rs @@ -0,0 +1,560 @@ +//! 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. + + +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + spi::{Mode, Phase, Polarity}, + digital::* +}; + +//The Lookup Tables for the Display +mod lut; +use self::lut::*; + +use drawing::color::Color; + +pub mod command; +pub use command::Command as Command; + +//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, +}; + + + + + + + + + +/// 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, + /// Width + width: u16, + /// Height + height: u16, +} + + +impl EPD4in2 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + /// Get the width of the display + pub fn get_width(&self) -> u16 { + self.width + } + + /// Get the height of the display + pub 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 + /// + /// # 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) + } + + + + /// 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> { + // reset the device + self.reset(); + + // set the power settings + self.send_command(Command::POWER_SETTING)?; + self.send_data(0x03)?; //VDS_EN, VDG_EN + self.send_data(0x00)?; //VCOM_HV, VGHL_LV[1], VGHL_LV[0] + self.send_data(0x2b)?; //VDH + self.send_data(0x2b)?; //VDL + self.send_data(0xff)?; //VDHR + + // 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 + } + + // power on + self.send_command(Command::POWER_ON)?; + self.wait_until_idle(); + + // set the panel settings + self.send_command(Command::PANEL_SETTING)?; + // 0x0F Red Mode, LUT from OTP + // 0x1F B/W Mode, LUT from OTP + // 0x2F Red Mode, LUT set by registers + // 0x3F B/W Mode, LUT set by registers + self.send_data(0x3F)?; + + // the values used by waveshare before for the panel settings + // instead of our one liner: + // SendData(0xbf); // KW-BF KWR-AF BWROTP 0f + // SendData(0x0b); + + // 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.send_command(Command::PLL_CONTROL)?; + self.send_data(0x3A)?; + + Ok(()) + } + + + + + + + /// 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::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_multiple_data(buffer)?; + + self.send_command(Command::PARTIAL_OUT) + } + + + + // 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); + + self.send_resolution()?; + + self.send_command(Command::VCM_DC_SETTING)?; + self.send_data(0x12)?; + + self.send_command(Command::VCOM_AND_DATA_INTERVAL_SETTING)?; + //TODO: this was a send_command instead of a send_data. check if it's alright and doing what it should do (setting the default values) + //oldTODO is this really a command here or shouldn't that be data? + //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.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(); + + 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) + } + + /// Displays the frame data from SRAM + pub fn display_frame(&mut self) -> Result<(), E> { + self.set_lut()?; + self.send_command(Command::DISPLAY_REFRESH)?; + + self.delay_ms(100); + self.wait_until_idle(); + Ok(()) + } + + /// 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 + pub fn display_frame_quick(&mut self) -> Result<(), E> { + self.set_lut_quick()?; + 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); + + self.send_resolution()?; + + let size = self.width / 8 * self.height; + + 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.delay_ms(2); + + self.send_command(Command::DATA_START_TRANSMISSION_2)?; + self.delay_ms(2); + for _ in 0..size { + self.send_data(reset_color.get_byte_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(()) + } + + /// 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(); + + //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); + } + + + + /// Fill the look-up table for the EPD + //TODO: make public? + fn set_lut(&mut self) -> Result<(), E> { + 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? + 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) + } + + fn set_lut_helper(&mut self, + lut_vcom: &[u8], + lut_ww: &[u8], + lut_bw: &[u8], + lut_wb: &[u8], + lut_bb: &[u8]) -> Result<(), E> + { + //vcom + self.send_command(Command::LUT_FOR_VCOM)?; + self.send_multiple_data(lut_vcom)?; + + //ww -- + self.send_command(Command::LUT_WHITE_TO_WHITE)?; + self.send_multiple_data(lut_ww)?; + + //bw r + self.send_command(Command::LUT_BLACK_TO_WHITE)?; + self.send_multiple_data(lut_bw)?; + + //wb w + self.send_command(Command::LUT_WHITE_TO_BLACK)?; + self.send_multiple_data(lut_wb)?; + + //bb b + 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); + } +} + + + + + + + + + + + + + From ceee294cda0dc0ced651f700b1ed188a26477ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Wed, 18 Jul 2018 17:03:11 +0200 Subject: [PATCH 2/7] Started some shared traits between the different epds, saving now --- src/epd2in9/command.rs | 167 +++++-------------------- src/epd2in9/constants.rs | 17 +++ src/epd2in9/lut.rs | 102 --------------- src/epd2in9/mod.rs | 214 +++++++++++++------------------- src/interface/data_interface.rs | 142 +++++++++++++++++++++ src/interface/mod.rs | 105 ++++++++++++++++ src/lib.rs | 6 + 7 files changed, 388 insertions(+), 365 deletions(-) create mode 100644 src/epd2in9/constants.rs delete mode 100644 src/epd2in9/lut.rs create mode 100644 src/interface/data_interface.rs create mode 100644 src/interface/mod.rs diff --git a/src/epd2in9/command.rs b/src/epd2in9/command.rs index c81bd79..63d4da1 100644 --- a/src/epd2in9/command.rs +++ b/src/epd2in9/command.rs @@ -1,6 +1,6 @@ -//! SPI Commands for the Waveshare 4.2" E-Ink Display +//! SPI Commands for the Waveshare 2.9" E-Ink Display -/// EPD4IN2 commands +/// EPD2IN9 commands /// /// Should rarely (never?) be needed directly. /// @@ -10,138 +10,31 @@ #[allow(dead_code)] #[allow(non_camel_case_types)] #[derive(Copy, Clone)] -pub enum Command { - /// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift direction, booster switch, soft reset - PANEL_SETTING = 0x00, - /// selecting internal and external power - POWER_SETTING = 0x01, - /// After the Power Off command, the driver will power off following the Power Off Sequence. This command will turn off charge - /// pump, T-con, source driver, gate driver, VCOM, and temperature sensor, but register data will be kept until VDD becomes OFF. - /// Source Driver output and Vcom will remain as previous condition, which may have 2 conditions: floating. - POWER_OFF = 0x02, - /// Setting Power OFF sequence - POWER_OFF_SEQUENCE_SETTING = 0x03, - /// Turning On the Power - POWER_ON = 0x04, - /// This command enables the internal bandgap, which will be cleared by the next POF. - POWER_ON_MEASURE = 0x05, - /// Starting data transmission - BOOSTER_SOFT_START = 0x06, - /// After this command is transmitted, the chip would enter the deep-sleep mode to save power. - /// - /// The deep sleep mode would return to standby by hardware reset. - /// - /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. - DEEP_SLEEP = 0x07, - /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data - /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. - /// - /// - In B/W mode, this command writes “OLD” data to SRAM. - /// - In B/W/Red mode, this command writes “B/W” data to SRAM. - /// - In Program mode, this command writes “OTP” data to SRAM for programming. - DATA_START_TRANSMISSION_1 = 0x10, - /// Stopping data transmission - DATA_STOP = 0x11, - /// While user sent this command, driver will refresh display (data/VCOM) according to SRAM data and LUT. - /// - /// After Display Refresh command, BUSY_N signal will become “0” and the refreshing of panel starts. - DISPLAY_REFRESH = 0x12, - /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data - /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. - /// - In B/W mode, this command writes “NEW” data to SRAM. - /// - In B/W/Red mode, this command writes “RED” data to SRAM. - DATA_START_TRANSMISSION_2 = 0x13, - - /// This command stores VCOM Look-Up Table with 7 groups of data. Each group contains information for one state and is stored - /// with 6 bytes, while the sixth byte indicates how many times that phase will repeat. - /// - /// from IL0373 - LUT_FOR_VCOM = 0x20, - /// This command stores White-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is - /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. - /// - /// from IL0373 - LUT_WHITE_TO_WHITE = 0x21, - /// This command stores Black-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is - /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. - /// - /// from IL0373 - LUT_BLACK_TO_WHITE = 0x22, - /// This command stores White-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is - /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. - /// - /// from IL0373 - LUT_WHITE_TO_BLACK = 0x23, - /// This command stores Black-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is - /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. - /// - /// from IL0373 - LUT_BLACK_TO_BLACK = 0x24, - /// The command controls the PLL clock frequency. - PLL_CONTROL = 0x30, - /// This command reads the temperature sensed by the temperature sensor. - /// - /// Doesn't work! Waveshare doesn't connect the read pin - TEMPERATURE_SENSOR_COMMAND = 0x40, - /// Selects the Internal or External temperature sensor and offset - TEMPERATURE_SENSOR_SELECTION = 0x41, - /// Write External Temperature Sensor - TEMPERATURE_SENSOR_WRITE = 0x42, - /// Read External Temperature Sensor - /// - /// Doesn't work! Waveshare doesn't connect the read pin - TEMPERATURE_SENSOR_READ = 0x43, - /// This command indicates the interval of Vcom and data output. When setting the vertical back porch, the total blanking will be kept (20 Hsync) - VCOM_AND_DATA_INTERVAL_SETTING = 0x50, - /// This command indicates the input power condition. Host can read this flag to learn the battery condition. - 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). - RESOLUTION_SETTING = 0x61, - /// This command defines the Fist Active Gate and First Active Source of active channels. - GSST_SETTING = 0x65, - /// The LUT_REV / Chip Revision is read from OTP address = 0x001. - /// - /// Doesn't work! Waveshare doesn't connect the read pin - REVISION = 0x70, - /// Read Flags. This command reads the IC status - /// PTL, I2C_ERR, I2C_BUSY, DATA, PON, POF, BUSY - /// - /// Doesn't work! Waveshare doesn't connect the read pin - GET_STATUS = 0x71, - /// Automatically measure VCOM. This command reads the IC status - AUTO_MEASUREMENT_VCOM = 0x80, - /// This command gets the VCOM value - /// - /// Doesn't work! Waveshare doesn't connect the read pin - READ_VCOM_VALUE = 0x81, - /// Set VCM_DC - VCM_DC_SETTING = 0x82, - /// This command sets partial window - PARTIAL_WINDOW = 0x90, - /// This command makes the display enter partial mode - PARTIAL_IN = 0x91, - /// This command makes the display exit partial mode and enter normal mode - PARTIAL_OUT = 0x92, - /// After this command is issued, the chip would enter the program mode. - /// - /// After the programming procedure completed, a hardware reset is necessary for leaving program mode. - /// - /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. - PROGRAM_MODE = 0xA0, - /// After this command is transmitted, the programming state machine would be activated. - /// - /// The BUSY flag would fall to 0 until the programming is completed. - ACTIVE_PROGRAMMING = 0xA1, - /// The command is used for reading the content of OTP for checking the data of programming. - /// - /// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF. - READ_OTP = 0xA2, - /// This command is set for saving power during fresh period. If the output voltage of VCOM / Source is from negative to positive or - /// from positive to negative, the power saving mechanism will be activated. The active period width is defined by the following two - /// parameters. - POWER_SAVING = 0xE3, +pub(crate) 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 } @@ -160,10 +53,10 @@ mod tests { #[test] fn command_addr() { - assert_eq!(Command::POWER_SAVING.addr(), 0xE3); + //assert_eq!(Command::POWER_SAVING.addr(), 0xE3); - assert_eq!(Command::PANEL_SETTING.addr(), 0x00); + //assert_eq!(Command::PANEL_SETTING.addr(), 0x00); - assert_eq!(Command::DISPLAY_REFRESH.addr(), 0x12); + //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..d9f65e0 --- /dev/null +++ b/src/epd2in9/constants.rs @@ -0,0 +1,17 @@ +pub(crate) const WIDTH: usize = 128; +pub(crate) const HEIGHT: usize = 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/lut.rs b/src/epd2in9/lut.rs deleted file mode 100644 index 55c7d39..0000000 --- a/src/epd2in9/lut.rs +++ /dev/null @@ -1,102 +0,0 @@ -pub(crate) const LUT_VCOM0: [u8; 44] = [ -0x00, 0x17, 0x00, 0x00, 0x00, 0x02, -0x00, 0x17, 0x17, 0x00, 0x00, 0x02, -0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - -pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ -0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - -pub(crate) const LUT_WW: [u8; 42] =[ -0x40, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, -0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - -pub(crate) const LUT_WW_QUICK: [u8; 42] =[ -0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - - -pub(crate) const LUT_BW: [u8; 42] =[ -0x40, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, -0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - -pub(crate) const LUT_BW_QUICK: [u8; 42] =[ -0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - - -pub(crate) const LUT_BB: [u8; 42] =[ -0x80, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - -pub(crate) const LUT_BB_QUICK: [u8; 42] =[ -0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - - -pub(crate) const LUT_WB: [u8; 42] =[ -0x80, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; - -pub(crate) const LUT_WB_QUICK: [u8; 42] =[ -0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; \ No newline at end of file diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index 3f53d9a..cb7f79c 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -57,54 +57,108 @@ use hal::{ digital::* }; -//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; -//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 interface::*; + +use interface::data_interface::DataInterface; + +/// EPD4in2 driver +/// +pub struct EPD2in9 { + /// SPI + interface: DataInterface, + /// Width + width: u32, + /// Height + height: u32, +} + + +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) -> u32 { + self.width + } + + fn get_height(&self) -> u32 { + self.height + } + fn new( + spi: SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: D + ) -> Result { + let width = WIDTH as u32; + let height = HEIGHT as u32; + let mut interface = DataInterface::new(spi, cs, busy, dc, rst, delay); + let mut epd = EPD2in9 {interface, width, height}; + epd.init()?; + + Ok(epd) + } + + + + fn init(&mut self) -> Result<(), E> { + //TODO: + Ok(()) + } + fn sleep(&mut self) -> Result<(), E> { + Ok(()) + } + fn reset(&mut self) { + //TODO: + } + fn wait_until_idle(&mut self) { + + } + fn delay_ms(&mut self, delay: u32) { + + } -/// 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, - /// Width - width: u16, - /// Height - height: u16, } -impl EPD4in2 +/* + + +impl EPD2in9 where SPI: Write, CS: OutputPin, @@ -141,8 +195,8 @@ where /// ``` 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 width = WIDTH as u16; + let height = HEIGHT as u16; let mut epd4in2 = EPD4in2 {spi, cs, busy, dc, rst, delay, width, height }; @@ -390,22 +444,7 @@ where Ok(()) } - /// 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(); - - //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); - } @@ -463,89 +502,12 @@ where 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/data_interface.rs b/src/interface/data_interface.rs new file mode 100644 index 0000000..1fd39b8 --- /dev/null +++ b/src/interface/data_interface.rs @@ -0,0 +1,142 @@ +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + spi::{Mode, Phase, Polarity}, + digital::* +}; + +use interface::Command; + +/// EPD4in2 driver +/// +pub struct DataInterface { + /// 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 DataInterface +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + D: DelayUs + DelayMs, +{ + pub fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Self { + DataInterface {spi, cs, busy, dc, rst, delay } + } + + /// 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: 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 [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); + } + + /// 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(); + + //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..548033f --- /dev/null +++ b/src/interface/mod.rs @@ -0,0 +1,105 @@ +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + spi::{Mode, Phase, Polarity}, + digital::* +}; + +pub mod data_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; +} + +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) -> u32; + fn get_height(&self) -> u32; + fn new( + spi: SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: D + ) -> Result + where Self: Sized; + fn init(&mut self) -> Result<(), E>; + fn sleep(&mut self) -> Result<(), E>; + fn reset(&mut self); + fn wait_until_idle(&mut self); + fn delay_ms(&mut self, delay: u32); + + /* + 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> + +*/ + +} + + +pub trait TestInterface +{ + fn get_width(&self) -> u32; + fn get_height(&self) -> u32; + + +} + +struct testStruct { + width: u32, + height: u32, +} + +impl TestInterface for testStruct { + fn get_width(&self) -> u32 { + self.width + } + + fn get_height(&self) -> u32 { + self.height + } + + +} + diff --git a/src/lib.rs b/src/lib.rs index ca53864..4f8851f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,6 +60,12 @@ pub mod drawing; pub mod epd4in2; use epd4in2::*; +pub mod epd2in9; + +pub mod interface; + + + //TODO: test spi mode /// SPI mode - /// For more infos see [Requirements: SPI](index.html#spi) From 80e5c0ffb110e4b1f483228ea2e75c6ac42ab3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Thu, 19 Jul 2018 11:08:26 +0200 Subject: [PATCH 3/7] improved traits, now it should be ready to convert 4.2" --- src/epd2in9/command.rs | 14 +++-- src/epd2in9/mod.rs | 42 +++++++++++---- src/interface/data_interface.rs | 25 ++++----- src/interface/mod.rs | 96 ++++++++++++++++++++++----------- 4 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/epd2in9/command.rs b/src/epd2in9/command.rs index 63d4da1..8eed1c7 100644 --- a/src/epd2in9/command.rs +++ b/src/epd2in9/command.rs @@ -1,5 +1,8 @@ //! SPI Commands for the Waveshare 2.9" E-Ink Display +use interface; + + /// EPD2IN9 commands /// /// Should rarely (never?) be needed directly. @@ -10,7 +13,7 @@ #[allow(dead_code)] #[allow(non_camel_case_types)] #[derive(Copy, Clone)] -pub(crate) enum Command { +pub enum Command { /// Driver Output control /// 3 Databytes: /// A[7:0] @@ -39,9 +42,9 @@ pub(crate) 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 } } @@ -49,11 +52,12 @@ impl Command { #[cfg(test)] mod tests { - use super::*; + use super::Command; + use interface::Command as CommandTrait; #[test] fn command_addr() { - //assert_eq!(Command::POWER_SAVING.addr(), 0xE3); + assert_eq!(Command::DRIVER_OUTPUT_CONTROL.address(), 0x01); //assert_eq!(Command::PANEL_SETTING.addr(), 0x00); diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index cb7f79c..ab8e35d 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -53,7 +53,6 @@ use hal::{ spi::Write, delay::* }, - spi::{Mode, Phase, Polarity}, digital::* }; @@ -69,7 +68,7 @@ use interface::*; use interface::data_interface::DataInterface; -/// EPD4in2 driver +/// EPD2in9 driver /// pub struct EPD2in9 { /// SPI @@ -80,7 +79,6 @@ pub struct EPD2in9 { height: u32, } - impl EPD2in9 where SPI: Write, @@ -103,6 +101,7 @@ where RST: OutputPin, D: DelayUs + DelayMs, { + fn get_width(&self) -> u32 { self.width } @@ -136,20 +135,45 @@ where fn init(&mut self) -> Result<(), E> { - //TODO: - Ok(()) + unimplemented!() } fn sleep(&mut self) -> Result<(), E> { - Ok(()) + unimplemented!() } fn reset(&mut self) { - //TODO: + self.interface.reset() } - fn wait_until_idle(&mut self) { + fn delay_ms(&mut self, delay: u16) { + self.interface.delay_ms(delay) + } + + + + fn update_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ + unimplemented!() + } + + fn update_partial_frame(&mut self, buffer: &[u8], x: u16, y: u16, width: u16, height: u16) -> Result<(), E>{ + unimplemented!() + } + + + fn display_frame(&mut self) -> Result<(), E>{ + unimplemented!() + } + + // TODO: add this abstraction function + // fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + + + fn clear_frame(&mut self) -> Result<(), E>{ + unimplemented!() } - fn delay_ms(&mut self, delay: u32) { + /// Sets the backgroundcolor for various commands like [WaveshareInterface::clear_frame()](clear_frame()) + fn set_background_color(&mut self, color: Color){ + unimplemented!() } } diff --git a/src/interface/data_interface.rs b/src/interface/data_interface.rs index 1fd39b8..2c50617 100644 --- a/src/interface/data_interface.rs +++ b/src/interface/data_interface.rs @@ -3,7 +3,6 @@ use hal::{ spi::Write, delay::* }, - spi::{Mode, Phase, Polarity}, digital::* }; @@ -11,7 +10,7 @@ use interface::Command; /// EPD4in2 driver /// -pub struct DataInterface { +pub(crate) struct DataInterface { /// SPI spi: SPI, /// CS for SPI @@ -36,7 +35,7 @@ where RST: OutputPin, D: DelayUs + DelayMs, { - pub fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Self { + pub(crate) fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Self { DataInterface {spi, cs, busy, dc, rst, delay } } @@ -45,7 +44,7 @@ where /// 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: T) -> Result<(), E> { + pub(crate) fn send_command(&mut self, command: T) -> Result<(), E> { // low for commands self.dc.set_low(); @@ -61,7 +60,7 @@ where /// /// Should rarely be needed! /// //TODO: make public? - fn send_data(&mut self, val: u8) -> Result<(), E> { + pub(crate) fn send_data(&mut self, val: u8) -> Result<(), E> { // high for data self.dc.set_high(); @@ -77,7 +76,7 @@ where /// /// Should rarely be needed! /// //TODO: make public? - fn send_multiple_data(&mut self, data: &[u8]) -> Result<(), E> { + pub(crate) fn send_multiple_data(&mut self, data: &[u8]) -> Result<(), E> { // high for data self.dc.set_high(); @@ -88,7 +87,7 @@ where } // spi write helper/abstraction function - fn with_cs(&mut self, f: F) -> Result<(), E> + pub(crate) fn with_cs(&mut self, f: F) -> Result<(), E> where F: FnOnce(&mut Self) -> Result<(), E>, { @@ -108,7 +107,7 @@ where /// 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) { + pub(crate) 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 @@ -117,17 +116,19 @@ where } - /// Abstraction of setting the delay for simpler calls - pub fn delay_ms(&mut self, delay: u16) { + /// 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. + /// 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) { + pub(crate) fn reset(&mut self) { self.rst.set_low(); //TODO: why 200ms? (besides being in the waveshare code) diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 548033f..e190cf9 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -7,6 +7,8 @@ use hal::{ digital::* }; +use drawing::color::Color; + pub mod data_interface; //TODO: test spi mode @@ -20,7 +22,7 @@ pub const SPI_MODE: Mode = Mode { use core::marker::Sized; pub(crate) trait Command { - fn address(&self) -> u8; + fn address(self) -> u8; } pub trait WaveshareInterface @@ -34,7 +36,13 @@ pub trait WaveshareInterface { /// Get the width of the display fn get_width(&self) -> u32; + + /// Get the height of the display fn get_height(&self) -> u32; + + /// 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, @@ -44,13 +52,66 @@ pub trait WaveshareInterface 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>; + + + + fn update_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + + 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); - fn wait_until_idle(&mut self); - fn delay_ms(&mut self, delay: u32); + + /// Abstraction of setting the delay for simpler calls + /// + /// maximum delay ~65 seconds (u16:max in ms) + fn delay_ms(&mut self, delay: u16); /* + -display_frame + -clear_frame + -set_full_frame + -set_partial_frame + + // + -set_quick_lut? + -set_normal_mode + + fn clear_frame(&mut self, reset_color: Option) -> Result<(), E> fn display_frame_quick(&mut self) -> Result<(), E> @@ -75,31 +136,4 @@ pub trait WaveshareInterface */ -} - - -pub trait TestInterface -{ - fn get_width(&self) -> u32; - fn get_height(&self) -> u32; - - -} - -struct testStruct { - width: u32, - height: u32, -} - -impl TestInterface for testStruct { - fn get_width(&self) -> u32 { - self.width - } - - fn get_height(&self) -> u32 { - self.height - } - - -} - +} \ No newline at end of file From 5e03c3c39a988101931601a568c70baa1c23817a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Thu, 19 Jul 2018 16:46:15 +0200 Subject: [PATCH 4/7] Stablised traits and interfaces, converted epd4in2 to it, and started real work on epd2in9 --- examples/embedded_linux/src/main.rs | 16 +- src/epd2in9/command.rs | 35 +- src/epd2in9/mod.rs | 38 +- src/epd4in2/command.rs | 13 +- src/epd4in2/{lut.rs => constants.rs} | 9 + src/epd4in2/mod.rs | 525 +++++++----------- ...a_interface.rs => connection_interface.rs} | 25 +- src/interface/mod.rs | 33 +- src/lib.rs | 3 +- 9 files changed, 323 insertions(+), 374 deletions(-) rename src/epd4in2/{lut.rs => constants.rs} (97%) rename src/interface/{data_interface.rs => connection_interface.rs} (81%) 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 index 8eed1c7..b777e95 100644 --- a/src/epd2in9/command.rs +++ b/src/epd2in9/command.rs @@ -10,7 +10,7 @@ use interface; /// 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(dead_code)] #[allow(non_camel_case_types)] #[derive(Copy, Clone)] pub enum Command { @@ -38,6 +38,39 @@ pub enum Command { /// 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_TIME = 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 } diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index ab8e35d..d1431d9 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -62,21 +62,23 @@ use self::constants::*; use drawing::color::Color; pub mod command; -pub use command::Command as Command; +pub use self::command::Command; use interface::*; -use interface::data_interface::DataInterface; +use interface::connection_interface::ConnectionInterface; /// EPD2in9 driver /// pub struct EPD2in9 { /// SPI - interface: DataInterface, + interface: ConnectionInterface, /// Width width: u32, /// Height - height: u32, + height: u32, + /// Color + color: Color, } impl EPD2in9 @@ -122,9 +124,11 @@ where let width = WIDTH as u32; let height = HEIGHT as u32; - let mut interface = DataInterface::new(spi, cs, busy, dc, rst, delay); + let interface = ConnectionInterface::new(spi, cs, busy, dc, rst, delay); - let mut epd = EPD2in9 {interface, width, height}; + let color = Color::White; + + let mut epd = EPD2in9 {interface, width, height, color}; epd.init()?; @@ -135,11 +139,26 @@ where fn init(&mut self) -> Result<(), E> { + self.reset(); + + + + unimplemented!() } + fn sleep(&mut self) -> Result<(), E> { - unimplemented!() + + 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.interface.wait_until_idle(false); + Ok(()) } + + fn reset(&mut self) { self.interface.reset() } @@ -165,6 +184,9 @@ where // TODO: add this abstraction function // fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ + unimplemented!() + } fn clear_frame(&mut self) -> Result<(), E>{ @@ -173,7 +195,7 @@ where /// Sets the backgroundcolor for various commands like [WaveshareInterface::clear_frame()](clear_frame()) fn set_background_color(&mut self, color: Color){ - unimplemented!() + self.color = color; } } diff --git a/src/epd4in2/command.rs b/src/epd4in2/command.rs index c81bd79..a73e43e 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. @@ -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 97% rename from src/epd4in2/lut.rs rename to src/epd4in2/constants.rs index 55c7d39..ea703e5 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,6 +11,7 @@ pub(crate) const LUT_VCOM0: [u8; 44] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; + pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -18,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, @@ -28,6 +33,7 @@ pub(crate) const LUT_WW: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; + pub(crate) const LUT_WW_QUICK: [u8; 42] =[ 0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -49,6 +55,7 @@ pub(crate) const LUT_BW: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; + pub(crate) const LUT_BW_QUICK: [u8; 42] =[ 0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -70,6 +77,7 @@ pub(crate) const LUT_BB: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; + pub(crate) const LUT_BB_QUICK: [u8; 42] =[ 0x50, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -91,6 +99,7 @@ pub(crate) const LUT_WB: [u8; 42] =[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; + 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 3f53d9a..c5a260b 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,82 +30,64 @@ //! 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. - 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, + width: u32, /// Height - height: u16, + height: u32, + /// Background Color + color: Color, } - -impl EPD4in2 -where +impl WaveshareInterface + for EPD4in2 +where SPI: Write, CS: OutputPin, BUSY: InputPin, @@ -113,54 +95,48 @@ where RST: OutputPin, D: DelayUs + DelayMs, { - /// Get the width of the display - pub fn get_width(&self) -> u16 { + fn get_width(&self) -> u32 { self.width } - /// Get the height of the display - pub fn get_height(&self) -> u16 { + fn get_height(&self) -> u32 { 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 - /// + /// /// # 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 u32; + let height = HEIGHT as u32; + + 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(); @@ -175,7 +151,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 @@ -198,68 +174,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()?; @@ -272,10 +226,9 @@ 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); @@ -284,73 +237,79 @@ where 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) } - /// 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 - pub fn display_frame_quick(&mut self) -> Result<(), E> { - self.set_lut_quick()?; + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ + self.update_frame(buffer)?; + self.display_frame() + } + + + 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); @@ -358,203 +317,101 @@ 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? + /// + /// Is automatically done by [EPD4in2::display_frame_quick()](EPD4in2::display_frame_quick()) + /// //TODO: make public? 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/data_interface.rs b/src/interface/connection_interface.rs similarity index 81% rename from src/interface/data_interface.rs rename to src/interface/connection_interface.rs index 2c50617..bbd7b4c 100644 --- a/src/interface/data_interface.rs +++ b/src/interface/connection_interface.rs @@ -10,7 +10,7 @@ use interface::Command; /// EPD4in2 driver /// -pub(crate) struct DataInterface { +pub(crate) struct ConnectionInterface { /// SPI spi: SPI, /// CS for SPI @@ -26,7 +26,7 @@ pub(crate) struct DataInterface { } -impl DataInterface +impl ConnectionInterface where SPI: Write, CS: OutputPin, @@ -36,12 +36,12 @@ where D: DelayUs + DelayMs, { pub(crate) fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Self { - DataInterface {spi, cs, busy, dc, rst, delay } + ConnectionInterface {spi, cs, busy, dc, rst, delay } } /// Basic function for sending [Commands](Command). /// - /// Enables direct interaction with the device with the help of [EPD4in2::send_data()](EPD4in2::send_data()) + /// 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> { @@ -56,7 +56,7 @@ where /// 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()) + /// Enables direct interaction with the device with the help of [Esend_command()](ConnectionInterface::send_command()) /// /// Should rarely be needed! /// //TODO: make public? @@ -72,7 +72,7 @@ where /// 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()) + /// Enables direct interaction with the device with the help of [send_command()](EPD4in2::send_command()) /// /// Should rarely be needed! /// //TODO: make public? @@ -107,11 +107,18 @@ where /// 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(crate) fn wait_until_idle(&mut self) { + /// + /// 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 self.busy.is_low() { + 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(10); + self.delay_ms(5); } } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index e190cf9..aab00e7 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -9,7 +9,7 @@ use hal::{ use drawing::color::Color; -pub mod data_interface; +pub mod connection_interface; //TODO: test spi mode /// SPI mode - @@ -25,6 +25,18 @@ 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, @@ -63,16 +75,26 @@ pub trait WaveshareInterface 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>; + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>; /// Clears the frame from the buffer /// @@ -102,10 +124,7 @@ pub trait WaveshareInterface fn delay_ms(&mut self, delay: u16); /* - -display_frame - -clear_frame - -set_full_frame - -set_partial_frame + // -set_quick_lut? diff --git a/src/lib.rs b/src/lib.rs index 4f8851f..5b3e1e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,8 @@ use hal::{ pub mod drawing; pub mod epd4in2; -use epd4in2::*; + + pub mod epd2in9; From 1f2c68d16d854a847eeea9a8864db3e792c5da7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Fri, 20 Jul 2018 13:25:30 +0200 Subject: [PATCH 5/7] Finished 2in9 support, now it needs to be tested --- src/epd2in9/command.rs | 2 +- src/epd2in9/constants.rs | 4 +- src/epd2in9/mod.rs | 489 +++++++------------------- src/epd4in2/mod.rs | 13 +- src/interface/connection_interface.rs | 19 + src/interface/mod.rs | 4 +- src/lib.rs | 2 + 7 files changed, 164 insertions(+), 369 deletions(-) diff --git a/src/epd2in9/command.rs b/src/epd2in9/command.rs index b777e95..016257b 100644 --- a/src/epd2in9/command.rs +++ b/src/epd2in9/command.rs @@ -58,7 +58,7 @@ pub enum Command { SET_DUMMY_LINE_PERIOD = 0x3A, - SET_GATE_TIME = 0x3B, + SET_GATE_LINE_WIDTH = 0x3B, BORDER_WAVEFORM_CONTROL = 0x3C, diff --git a/src/epd2in9/constants.rs b/src/epd2in9/constants.rs index d9f65e0..98e7b05 100644 --- a/src/epd2in9/constants.rs +++ b/src/epd2in9/constants.rs @@ -1,5 +1,5 @@ -pub(crate) const WIDTH: usize = 128; -pub(crate) const HEIGHT: usize = 296; +pub(crate) const WIDTH: u16 = 128; +pub(crate) const HEIGHT: u16 = 296; // Original Waveforms from Waveshare pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[ diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index d1431d9..49bd0df 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -46,6 +46,8 @@ //! //! BE CAREFUL! The Partial Drawing can "destroy" your display. //! It needs more testing first. +//! +//! Is initalised with slow full LUT use hal::{ @@ -68,17 +70,19 @@ use interface::*; use interface::connection_interface::ConnectionInterface; + + /// EPD2in9 driver /// pub struct EPD2in9 { /// SPI interface: ConnectionInterface, /// Width - width: u32, + width: u16, /// Height - height: u32, + height: u16, /// Color - color: Color, + background_color: Color, } impl EPD2in9 @@ -104,11 +108,11 @@ where D: DelayUs + DelayMs, { - fn get_width(&self) -> u32 { - self.width + fn get_width(&self) -> u16 { + self.width } - fn get_height(&self) -> u32 { + fn get_height(&self) -> u16 { self.height } @@ -121,14 +125,14 @@ where rst: RST, delay: D ) -> Result { - let width = WIDTH as u32; - let height = HEIGHT as u32; + 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 background_color = Color::White; - let mut epd = EPD2in9 {interface, width, height, color}; + let mut epd = EPD2in9 {interface, width, height, background_color}; epd.init()?; @@ -139,12 +143,48 @@ where 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)?; - - - unimplemented!() + // 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> { @@ -154,7 +194,7 @@ where //TODO: is 0x00 needed here? self.interface.send_data(0x00)?; - self.interface.wait_until_idle(false); + self.wait_until_idle(); Ok(()) } @@ -170,40 +210,58 @@ where fn update_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ - unimplemented!() + 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>{ - unimplemented!() + 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>{ - unimplemented!() + // 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) } - // TODO: add this abstraction function - // fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>; + fn update_and_display_frame(&mut self, buffer: &[u8]) -> Result<(), E>{ - unimplemented!() + self.update_frame(buffer)?; + self.display_frame() } fn clear_frame(&mut self) -> Result<(), E>{ - unimplemented!() + 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, color: Color){ - self.color = color; + fn set_background_color(&mut self, background_color: Color){ + self.background_color = background_color; } } - -/* - - impl EPD2in9 where SPI: Write, @@ -213,356 +271,71 @@ where RST: OutputPin, D: DelayUs + DelayMs, { - /// Get the width of the display - pub fn get_width(&self) -> u16 { - self.width - } - - /// Get the height of the display - pub fn get_height(&self) -> u16 { - self.height + fn wait_until_idle(&mut self) { + self.interface.wait_until_idle(false); } - - /// 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 - /// - /// # 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 = WIDTH as u16; - let height = HEIGHT as u16; + pub(crate) fn use_full_frame(&mut self) -> Result<(), E> { + // choose full frame/ram + self.set_ram_area(0, 0, WIDTH - 1, HEIGHT - 1)?; - let mut epd4in2 = EPD4in2 {spi, cs, busy, dc, rst, delay, width, height }; - - epd4in2.init()?; - - Ok(epd4in2) + // start from the beginning + self.set_ram_counter(0,0) } - - - - /// 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> { - // reset the device - self.reset(); - - // set the power settings - self.send_command(Command::POWER_SETTING)?; - self.send_data(0x03)?; //VDS_EN, VDG_EN - self.send_data(0x00)?; //VCOM_HV, VGHL_LV[1], VGHL_LV[0] - self.send_data(0x2b)?; //VDH - self.send_data(0x2b)?; //VDL - self.send_data(0xff)?; //VDHR - - // 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 - } - - // power on - self.send_command(Command::POWER_ON)?; - self.wait_until_idle(); - - // set the panel settings - self.send_command(Command::PANEL_SETTING)?; - // 0x0F Red Mode, LUT from OTP - // 0x1F B/W Mode, LUT from OTP - // 0x2F Red Mode, LUT set by registers - // 0x3F B/W Mode, LUT set by registers - self.send_data(0x3F)?; - - // the values used by waveshare before for the panel settings - // instead of our one liner: - // SendData(0xbf); // KW-BF KWR-AF BWROTP 0f - // SendData(0x0b); - - // 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.send_command(Command::PLL_CONTROL)?; - self.send_data(0x3A)?; - - Ok(()) - } - - - - - - - /// 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::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_multiple_data(buffer)?; - - self.send_command(Command::PARTIAL_OUT) - } - - - // 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); - - self.send_resolution()?; - - self.send_command(Command::VCM_DC_SETTING)?; - self.send_data(0x12)?; - - self.send_command(Command::VCOM_AND_DATA_INTERVAL_SETTING)?; - //TODO: this was a send_command instead of a send_data. check if it's alright and doing what it should do (setting the default values) - //oldTODO is this really a command here or shouldn't that be data? - //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.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(()) + 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) } - fn send_resolution(&mut self) -> Result<(), E> { - let w = self.get_width(); - let h = self.get_height(); + 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)?; - 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) - } - - /// Displays the frame data from SRAM - pub fn display_frame(&mut self) -> Result<(), E> { - self.set_lut()?; - self.send_command(Command::DISPLAY_REFRESH)?; + // 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.delay_ms(100); self.wait_until_idle(); Ok(()) } - /// 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 - pub fn display_frame_quick(&mut self) -> Result<(), E> { - self.set_lut_quick()?; - self.send_command(Command::DISPLAY_REFRESH)?; - - self.delay_ms(1); - 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) } - - /// 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); - - self.send_resolution()?; - - let size = self.width / 8 * self.height; - - 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.delay_ms(2); - - self.send_command(Command::DATA_START_TRANSMISSION_2)?; - self.delay_ms(2); - for _ in 0..size { - self.send_data(reset_color.get_byte_value())?; - } - Ok(()) + /// Uses the quick partial refresh + pub fn set_lut_quick(&mut self) -> Result<(), E> { + self.set_lut_helper(&LUT_PARTIAL_UPDATE) } - /// 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(()) + //TODO: assert length for LUT is exactly 30 + fn set_lut_manual(&mut self, buffer: &[u8]) -> Result<(), E> { + self.set_lut_helper(buffer) } - - - - /// Fill the look-up table for the EPD - //TODO: make public? - fn set_lut(&mut self) -> Result<(), E> { - self.set_lut_helper( - &LUT_VCOM0, - &LUT_WW, - &LUT_BW, - &LUT_WB, - &LUT_BB) + 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) } - /// 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? - 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) - } - - fn set_lut_helper(&mut self, - lut_vcom: &[u8], - lut_ww: &[u8], - lut_bw: &[u8], - lut_wb: &[u8], - lut_bb: &[u8]) -> Result<(), E> - { - //vcom - self.send_command(Command::LUT_FOR_VCOM)?; - self.send_multiple_data(lut_vcom)?; - - //ww -- - self.send_command(Command::LUT_WHITE_TO_WHITE)?; - self.send_multiple_data(lut_ww)?; - - //bw r - self.send_command(Command::LUT_BLACK_TO_WHITE)?; - self.send_multiple_data(lut_bw)?; - - //wb w - self.send_command(Command::LUT_WHITE_TO_BLACK)?; - self.send_multiple_data(lut_wb)?; - - //bb b - self.send_command(Command::LUT_BLACK_TO_BLACK)?; - self.send_multiple_data(lut_bb)?; - - Ok(()) - } - - -} - - - -*/ - - - - - - - - - - - - +} \ No newline at end of file diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index c5a260b..5bd1d5e 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -78,9 +78,9 @@ pub struct EPD4in2 { /// Connection Interface interface: ConnectionInterface, /// Width - width: u32, + width: u16, /// Height - height: u32, + height: u16, /// Background Color color: Color, } @@ -95,11 +95,11 @@ where RST: OutputPin, D: DelayUs + DelayMs, { - fn get_width(&self) -> u32 { + fn get_width(&self) -> u16 { self.width } - fn get_height(&self) -> u32 { + fn get_height(&self) -> u16 { self.height } @@ -119,8 +119,8 @@ where /// epd4in2.sleep(); /// ``` fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, rst: RST, delay: D) -> Result { - let width = WIDTH as u32; - let height = HEIGHT as u32; + let width = WIDTH as u16; + let height = HEIGHT as u16; let interface = ConnectionInterface::new(spi, cs, busy, dc, rst, delay); let color = Color::White; @@ -234,6 +234,7 @@ where self.send_command(Command::DATA_START_TRANSMISSION_2)?; //self.send_multiple_data(buffer)?; + for &elem in buffer.iter() { self.send_data(elem)?; } diff --git a/src/interface/connection_interface.rs b/src/interface/connection_interface.rs index bbd7b4c..7781685 100644 --- a/src/interface/connection_interface.rs +++ b/src/interface/connection_interface.rs @@ -70,6 +70,25 @@ where }) } + /// 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()) diff --git a/src/interface/mod.rs b/src/interface/mod.rs index aab00e7..9c23c8e 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -47,10 +47,10 @@ pub trait WaveshareInterface D: DelayUs + DelayMs, { /// Get the width of the display - fn get_width(&self) -> u32; + fn get_width(&self) -> u16; /// Get the height of the display - fn get_height(&self) -> u32; + fn get_height(&self) -> u16; /// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC /// diff --git a/src/lib.rs b/src/lib.rs index 5b3e1e3..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] From 2c7807265ccacdba4479d46499348e8e6b88c849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Mon, 23 Jul 2018 10:41:28 +0200 Subject: [PATCH 6/7] -fixed a doc test error in the interface file even though it's unknown why that was even recognized as a doc test - fixed a few documentation links and warnings --- src/epd4in2/command.rs | 2 +- src/epd4in2/mod.rs | 3 ++- src/interface/connection_interface.rs | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/epd4in2/command.rs b/src/epd4in2/command.rs index a73e43e..e22787c 100644 --- a/src/epd4in2/command.rs +++ b/src/epd4in2/command.rs @@ -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, diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index 5bd1d5e..94f6e49 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -105,7 +105,7 @@ where /// 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 /// @@ -136,6 +136,7 @@ where Ok(epd) } + /// blablabla fn init(&mut self) -> Result<(), E> { // reset the device self.reset(); diff --git a/src/interface/connection_interface.rs b/src/interface/connection_interface.rs index 7781685..8d47708 100644 --- a/src/interface/connection_interface.rs +++ b/src/interface/connection_interface.rs @@ -128,9 +128,10 @@ where /// if the device is still busy /// /// is_busy_low - /// - TRUE for epd4in2, epd1in54, epd2in13, epd2in7, epd5in83, epd7in5 /// - /// - FALSE for epd2in9 + /// - 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); From 9f5d748e07e0b69ef3a63e13ad254fa27b63ede5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gro=C3=9F?= Date: Mon, 23 Jul 2018 10:42:23 +0200 Subject: [PATCH 7/7] Forgot to remove a samll test comment line within the last commit --- src/epd4in2/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index 94f6e49..4874e34 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -136,7 +136,6 @@ where Ok(epd) } - /// blablabla fn init(&mut self) -> Result<(), E> { // reset the device self.reset();