diff --git a/.gitignore b/.gitignore index 50281a4..db7de4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ +# Generated files +**/target +**/*.rs.bk + -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +# The library shouldn't decide about the exact versions of +# its dependencies, but let the downstream crate decide. Cargo.lock -# These are backup files generated by rustfmt -**/*.rs.bk + + +# vscode +.vscode/* diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d22cdcb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "eink_waveshare_rs" +version = "0.1.0" +authors = ["Christoph Groß "] + +[dependencies] + + +[dependencies.embedded-hal] +features = ["unproven"] +version = "0.1.2" diff --git a/src/epd4in2/command.rs b/src/epd4in2/command.rs new file mode 100644 index 0000000..c82d930 --- /dev/null +++ b/src/epd4in2/command.rs @@ -0,0 +1,154 @@ +//! 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 + } +} \ No newline at end of file diff --git a/src/epd4in2/lut.rs b/src/epd4in2/lut.rs new file mode 100644 index 0000000..55c7d39 --- /dev/null +++ b/src/epd4in2/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/epd4in2/mod.rs b/src/epd4in2/mod.rs new file mode 100644 index 0000000..9540fdf --- /dev/null +++ b/src/epd4in2/mod.rs @@ -0,0 +1,551 @@ +//! 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 +//! +//! ``` +//! 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(); +//! ``` +//! +//! +//! + + +use hal::{ + blocking::{ + spi::Write, + delay::* + }, + spi::{Mode, Phase, Polarity}, + digital::* +}; + +//The Lookup Tables for the Display +mod lut; +use self::lut::*; + +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 + /// + /// ``` + /// //buffer = some image data; + /// + /// let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay); + /// + /// epd4in2.display_and_transfer_frame(buffer, None); + /// + /// epd4in2.sleep(); + /// ``` + /// testlajfafe + 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 + /// 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)?; + 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: u8 = reset_color.unwrap_or(0xff); + + 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)?; + } + + 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)?; + } + 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 + fn delay_ms(&mut self, delay: u16) { + self.delay.delay_ms(delay); + } +} + + + + + + + + + + + + + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9df51fe --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,67 @@ +//! A simple Driver for the Waveshare E-Ink Displays via SPI +//! +//! The other Waveshare E-Ink Displays should be added later on, atm it's only the 4.2"-Display +//! +//! 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 +//! +//! ``` +//! use eink-waveshare-rs::epd4in2::EPD4in2; +//! +//! 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(); +//! ``` +//! +//! +//! +#![no_std] + + +extern crate embedded_hal as hal; + +use hal::{ + spi::{Mode, Phase, Polarity}, +}; + +pub mod epd4in2; +use epd4in2::*; + +//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, +}; \ No newline at end of file