From 6415e2da5d8afcce2b32a11802e6932f6e04b320 Mon Sep 17 00:00:00 2001 From: Javier Cardona Date: Thu, 8 Dec 2022 10:58:01 +0000 Subject: [PATCH 1/2] Do enter deep sleep when epd.sleep() is invoked The documentation for WaveshareDisplay::sleep() states that the function puts the device in deep sleep. According to the documentation for this display, this requires sending a 0x1. Tested on a device, and observed a drop of standby current from ~10 uA to ~1 uA. This probably applies to all the displays that internally use the SSD1681 controller, but I have no way to test. --- src/epd1in54_v2/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/epd1in54_v2/mod.rs b/src/epd1in54_v2/mod.rs index 85f9283a..60a43d38 100644 --- a/src/epd1in54_v2/mod.rs +++ b/src/epd1in54_v2/mod.rs @@ -137,10 +137,8 @@ where fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(spi, delay)?; - // 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode - //TODO: is 0x00 needed here or would 0x01 be even more efficient? self.interface - .cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?; + .cmd_with_data(spi, Command::DeepSleepMode, &[0x01])?; Ok(()) } From 3b30736309c743e4d869727050bc996ec8235ca6 Mon Sep 17 00:00:00 2001 From: Alex Gavrisco Date: Sat, 3 Apr 2021 23:25:30 +0300 Subject: [PATCH 2/2] Add support for EPD 3in7 Matches implementation from waveshare/e-Paper code https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_3in7.c Co-Authored-By: Miguel Angel Ajo Co-Authored-By: Christoph Gross <11088935+caemor@users.noreply.github.com> --- src/epd3in7/command.rs | 88 +++++++++++++ src/epd3in7/constants.rs | 29 +++++ src/epd3in7/mod.rs | 266 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 384 insertions(+) create mode 100644 src/epd3in7/command.rs create mode 100644 src/epd3in7/constants.rs create mode 100644 src/epd3in7/mod.rs diff --git a/src/epd3in7/command.rs b/src/epd3in7/command.rs new file mode 100644 index 00000000..5aba926a --- /dev/null +++ b/src/epd3in7/command.rs @@ -0,0 +1,88 @@ +//! SPI Commands for the Waveshare 3.7" E-Ink Display + +use crate::traits; + +/// EPD3IN7 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 EDP3IN7 specification +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum Command { + /// + GateSetting = 0x01, + /// + PowerOff = 0x02, + /// + Sleep2 = 0x07, + /// + GateVoltage = 0x03, + /// + GateVoltageSource = 0x04, + /// + BoosterSoftStartControl = 0x0C, + /// After this command initiated, the chip will enter Deep Sleep Mode, + /// BUSY pad will keep output high. + /// + /// Note: To exit Deep Sleep Mode, User required to send HWRESET to the driver. + DeepSleep = 0x10, + /// + DataEntrySequence = 0x11, + /// This command resets commands and parameters to their S/W Reset default values, + /// except Deep Sleep Mode. + /// During this operation BUSY pad will keep output high. + /// + /// Note: RAM is unaffected by this command. + SwReset = 0x12, + /// This command selects the Internal or External temperature sensor and offset + TemperatureSensorSelection = 0x18, + /// Write to temperature register + TemperatureSensorWrite = 0x1A, + /// Read from temperature register + TemperatureSensorRead = 0x1B, + /// This command activates Display Update sequence. + /// The Display Update sequence option is located at R22h. + /// + /// Note: BUSY pad will output high during operation. User **should not** interrupt this operation + /// to avoid corruption of panel images. + DisplayUpdateSequence = 0x20, + /// This command sets a Display Update Sequence option. + DisplayUpdateSequenceSetting = 0x22, + /// This command will transfer its data to B/W RAM, until another command is written + WriteRam = 0x24, + /// This command writes VCOM register from MCU interface + WriteVcomRegister = 0x2C, + /// This command writes LUT register from MCU interface (105 bytes), + /// which contains the content of VS [nx-LUT], TP #[nX], RP #[n] + WriteLutRegister = 0x32, + /// + DisplayOption = 0x37, + /// + BorderWaveformControl = 0x3C, + /// This command specifies the start/end positions of the window address in the X direction, + /// by an address unit of RAM. + SetRamXAddressStartEndPosition = 0x44, + /// This command specifies the start/end positions of the window address in the Y direction, + /// by an address unit of RAM. + SetRamYAddressStartEndPosition = 0x45, + /// + AutoWriteRedRamRegularPattern = 0x46, + /// + AutoWriteBwRamRegularPattern = 0x47, + /// This command makes the initial settings for the RAM X address in the address counter (AC) + SetRamXAddressCounter = 0x4E, + /// This command makes the initial settings for the RAM Y address in the address counter (AC) + SetRamYAddressCounter = 0x4F, + /// + Sleep = 0x50, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} diff --git a/src/epd3in7/constants.rs b/src/epd3in7/constants.rs new file mode 100644 index 00000000..bbfbf0c6 --- /dev/null +++ b/src/epd3in7/constants.rs @@ -0,0 +1,29 @@ +// This LUT clears the whole display during updates. +pub(crate) const LUT_1GRAY_GC: [u8; 105] = [ + 0x2A, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //1 + 0x05, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //2 + 0x2A, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //3 + 0x05, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //5 + 0x00, 0x02, 0x03, 0x0A, 0x00, 0x02, 0x06, 0x0A, 0x05, 0x00, //6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //10 + 0x22, 0x22, 0x22, 0x22, 0x22, +]; + +// This LUT updates only the pixels that have changed. +pub(crate) const LUT_1GRAY_DU: [u8; 105] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //1 + 0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //2 + 0x0A, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //5 + 0x00, 0x00, 0x05, 0x05, 0x00, 0x05, 0x03, 0x05, 0x05, 0x00, //6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //10 + 0x22, 0x22, 0x22, 0x22, 0x22, +]; diff --git a/src/epd3in7/mod.rs b/src/epd3in7/mod.rs new file mode 100644 index 00000000..66513eb8 --- /dev/null +++ b/src/epd3in7/mod.rs @@ -0,0 +1,266 @@ +//! A simple Driver for the Waveshare 3.7" E-Ink Display via SPI +//! +//! +//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/3.7inch_e-Paper_HAT), +use embedded_hal::{ + blocking::{delay::DelayUs, spi::Write}, + digital::v2::{InputPin, OutputPin}, +}; + +pub(crate) mod command; +mod constants; + +use self::command::Command; +use self::constants::*; + +use crate::buffer_len; +use crate::color::Color; +use crate::interface::DisplayInterface; +use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay}; + +/// Width of the display. +pub const WIDTH: u32 = 280; + +/// Height of the display +pub const HEIGHT: u32 = 480; + +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; + +const IS_BUSY_LOW: bool = false; + +/// Display with Fullsize buffer for use with the 3in7 EPD +#[cfg(feature = "graphics")] +pub type Display3in7 = crate::graphics::Display< + WIDTH, + HEIGHT, + false, + { buffer_len(WIDTH as usize, HEIGHT as usize) }, + Color, +>; + +/// EPD3in7 driver +pub struct EPD3in7 { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + background_color: Color, +} + +impl InternalWiAdditions + for EPD3in7 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // reset the device + self.interface.reset(delay, 30, 10); + + self.interface.cmd(spi, Command::SwReset)?; + delay.delay_us(300000u32); + + self.interface + .cmd_with_data(spi, Command::AutoWriteRedRamRegularPattern, &[0xF7])?; + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + self.interface + .cmd_with_data(spi, Command::AutoWriteBwRamRegularPattern, &[0xF7])?; + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + + self.interface + .cmd_with_data(spi, Command::GateSetting, &[0xDF, 0x01, 0x00])?; + self.interface + .cmd_with_data(spi, Command::GateVoltage, &[0x00])?; + self.interface + .cmd_with_data(spi, Command::GateVoltageSource, &[0x41, 0xA8, 0x32])?; + + self.interface + .cmd_with_data(spi, Command::DataEntrySequence, &[0x03])?; + + self.interface + .cmd_with_data(spi, Command::BorderWaveformControl, &[0x03])?; + + self.interface.cmd_with_data( + spi, + Command::BoosterSoftStartControl, + &[0xAE, 0xC7, 0xC3, 0xC0, 0xC0], + )?; + + self.interface + .cmd_with_data(spi, Command::TemperatureSensorSelection, &[0x80])?; + + self.interface + .cmd_with_data(spi, Command::WriteVcomRegister, &[0x44])?; + + self.interface.cmd_with_data( + spi, + Command::DisplayOption, + &[0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF], + )?; + + self.interface.cmd_with_data( + spi, + Command::SetRamXAddressStartEndPosition, + &[0x00, 0x00, 0x17, 0x01], + )?; + self.interface.cmd_with_data( + spi, + Command::SetRamYAddressStartEndPosition, + &[0x00, 0x00, 0xDF, 0x01], + )?; + + self.interface + .cmd_with_data(spi, Command::DisplayUpdateSequenceSetting, &[0xCF])?; + + self.set_lut(spi, delay, Some(RefreshLut::Full))?; + Ok(()) + } +} + +impl WaveshareDisplay + for EPD3in7 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + type DisplayColor = Color; + + fn new( + spi: &mut SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result { + let mut epd = EPD3in7 { + interface: DisplayInterface::new(cs, busy, dc, rst, delay_us), + background_color: DEFAULT_BACKGROUND_COLOR, + }; + + epd.init(spi, delay)?; + Ok(epd) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.init(spi, delay) + } + + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.cmd_with_data(spi, Command::Sleep, &[0xF7])?; + self.interface.cmd(spi, Command::PowerOff)?; + self.interface + .cmd_with_data(spi, Command::Sleep2, &[0xA5])?; + Ok(()) + } + + fn set_background_color(&mut self, color: Self::DisplayColor) { + self.background_color = color; + } + + fn background_color(&self) -> &Self::DisplayColor { + &self.background_color + } + + fn width(&self) -> u32 { + WIDTH + } + + fn height(&self) -> u32 { + HEIGHT + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + assert!(buffer.len() == buffer_len(WIDTH as usize, HEIGHT as usize)); + self.interface + .cmd_with_data(spi, Command::SetRamXAddressCounter, &[0x00, 0x00])?; + self.interface + .cmd_with_data(spi, Command::SetRamYAddressCounter, &[0x00, 0x00])?; + + self.interface + .cmd_with_data(spi, Command::WriteRam, buffer)?; + + Ok(()) + } + + #[allow(unused)] + fn update_partial_frame( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + todo!() + } + + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + //self.interface + // .cmd_with_data(spi, Command::WRITE_LUT_REGISTER, &LUT_1GRAY_GC)?; + self.interface.cmd(spi, Command::DisplayUpdateSequence)?; + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + Ok(()) + } + + fn update_and_display_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_frame(spi, buffer, delay)?; + self.display_frame(spi, delay)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface + .cmd_with_data(spi, Command::SetRamXAddressCounter, &[0x00, 0x00])?; + self.interface + .cmd_with_data(spi, Command::SetRamYAddressCounter, &[0x00, 0x00])?; + + let color = self.background_color.get_byte_value(); + self.interface.cmd(spi, Command::WriteRam)?; + self.interface.data_x_times(spi, color, WIDTH * HEIGHT)?; + + Ok(()) + } + + fn set_lut( + &mut self, + spi: &mut SPI, + _delay: &mut DELAY, + refresh_rate: Option, + ) -> Result<(), SPI::Error> { + let buffer = match refresh_rate { + Some(RefreshLut::Full) | None => &LUT_1GRAY_GC, + Some(RefreshLut::Quick) => &LUT_1GRAY_DU, + }; + + self.interface + .cmd_with_data(spi, Command::WriteLutRegister, buffer)?; + Ok(()) + } + + fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index c5702802..677f16b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ pub mod epd2in7b; pub mod epd2in9; pub mod epd2in9_v2; pub mod epd2in9bc; +pub mod epd3in7; pub mod epd4in2; pub mod epd5in65f; pub mod epd5in83b_v2;