diff --git a/src/epd2in9d/command.rs b/src/epd2in9d/command.rs new file mode 100644 index 00000000..c7abc249 --- /dev/null +++ b/src/epd2in9d/command.rs @@ -0,0 +1,150 @@ +//! SPI Commands for the Waveshare 2.9" FLEXIBLE E-PAPER DISPLAY +use crate::traits; + +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum Command { + PanelSetting = 0x00, + /// selecting internal and external power + /// 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 + PowerSetting = 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. + PowerOff = 0x02, + /// Setting Power OFF sequence + PowerOffSequenceSetting = 0x03, + /// Turning On the Power + PowerOn = 0x04, + /// This command enables the internal bandgap, which will be cleared by the next POF. + PowerOnMeasure = 0x05, + /// Starting data transmission + /// 3-times: self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f + BoosterSoftStart = 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. + DeepSleep = 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. + DataStartTransmission1 = 0x10, + /// Stopping data transmission + DataStop = 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. + DisplayRefresh = 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. + DataStartTransmission2 = 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 + LutForVcom = 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 + LutWhiteToWhite = 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 + LutBlackToWhite = 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 + LutWhiteToBlack = 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 + LutBlackToBlack = 0x24, + /// The command controls the PLL clock frequency. + PllControl = 0x30, + /// This command reads the temperature sensed by the temperature sensor. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TemperatureSensor = 0x40, + /// Selects the Internal or External temperature sensor and offset + TemperatureSensorSelection = 0x41, + /// Write External Temperature Sensor + TemperatureSensorWrite = 0x42, + /// Read External Temperature Sensor + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TemperatureSensorRead = 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) + VcomAndDataIntervalSetting = 0x50, + /// This command indicates the input power condition. Host can read this flag to learn the battery condition. + LowPowerDetection = 0x51, + /// This command defines non-overlap period of Gate and Source. + TconSetting = 0x60, + /// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR). + ResolutionSetting = 0x61, + /// This command defines the Fist Active Gate and First Active Source of active channels. + // GsstSetting = 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 + GetStatus = 0x71, + /// Automatically measure VCOM. This command reads the IC status + AutoMeasurementVcom = 0x80, + /// This command gets the VCOM value + /// + /// Doesn't work! Waveshare doesn't connect the read pin + ReadVcomValue = 0x81, + /// Set VCM_DC + VcmDcSetting = 0x82, + /// This command sets partial window + PartialWindow = 0x90, + /// This command makes the display enter partial mode + PartialIn = 0x91, + /// This command makes the display exit partial mode and enter normal mode + PartialOut = 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. + ProgramMode = 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. + ActiveProgramming = 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. + ReadOtp = 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. + PowerSaving = 0xE3, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} diff --git a/src/epd2in9d/constants.rs b/src/epd2in9d/constants.rs new file mode 100644 index 00000000..6a56d2e7 --- /dev/null +++ b/src/epd2in9d/constants.rs @@ -0,0 +1,61 @@ +//! This file contains look-up-tables used to set voltages used during +//! various categories of pixel refreshes. + +/** + * partial screen update LUT +**/ +#[rustfmt::skip] +pub(crate) const LUT_VCOM1: [u8; 44] = [ + 0x00, 0x19, 0x01, 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, +]; + +#[rustfmt::skip] +pub(crate) const LUT_WW1: [u8; 42] =[ + 0x00, 0x19, 0x01, 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, +]; + +#[rustfmt::skip] +pub(crate) const LUT_BW1: [u8; 42] =[ + 0x80, 0x19, 0x01, 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, +]; + +#[rustfmt::skip] +pub(crate) const LUT_BB1: [u8; 42] =[ + 0x00, 0x19, 0x01, 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, +]; + +#[rustfmt::skip] +pub(crate) const LUT_WB1: [u8; 42] =[ + 0x40, 0x19, 0x01, 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, +]; diff --git a/src/epd2in9d/mod.rs b/src/epd2in9d/mod.rs new file mode 100644 index 00000000..87b5e8ad --- /dev/null +++ b/src/epd2in9d/mod.rs @@ -0,0 +1,407 @@ +//! A simple Driver for the Waveshare 2.9" D E-Ink Display via SPI +//! +//! +//! 参考[Waveshare](https://www.waveshare.net/wiki/2.9inch_e-Paper_HAT_%28D%29)的文档/例程进行构建 +//! +//! Specification: https://www.waveshare.net/w/upload/b/b5/2.9inch_e-Paper_%28D%29_Specification.pdf + +use core::slice::from_raw_parts; + +use embedded_hal::{ + delay::DelayUs, + digital::{InputPin, OutputPin}, + spi::SpiDevice, +}; + +use crate::interface::DisplayInterface; +use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay}; + +//The Lookup Tables for the Display +mod constants; +use crate::epd2in9d::constants::*; + +/// Width of Epd2in9d in pixels +pub const WIDTH: u32 = 128; +/// Height of Epd2in9d in pixels +pub const HEIGHT: u32 = 296; +/// EPD_ARRAY of Epd2in9d in pixels +/// WIDTH / 8 * HEIGHT +pub const EPD_ARRAY: u32 = 4736; +/// Default Background Color (white) +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::Black; +const IS_BUSY_LOW: bool = false; +const SINGLE_BYTE_WRITE: bool = true; + +use crate::color::Color; + +pub(crate) mod command; +use self::command::Command; +use crate::buffer_len; + +/// Display with Fullsize buffer for use with the 2in9 EPD D +#[cfg(feature = "graphics")] +pub type Display2in9d = crate::graphics::Display< + WIDTH, + HEIGHT, + false, + { buffer_len(WIDTH as usize, HEIGHT as usize) }, + Color, +>; + +/// Epd2in9d driver +/// +pub struct Epd2in9d<'a, SPI, BUSY, DC, RST, DELAY> { + /// SPI + interface: DisplayInterface, + /// Color + // background_color: Color, + color: Color, + /// Refresh LUT + refresh: RefreshLut, + // 存放旧数据,以供部分刷新使用 + old_data: &'a [u8], + // 标记是否局刷的状态 + is_partial_refresh: bool, +} + +impl InternalWiAdditions + for Epd2in9d<'_, SPI, BUSY, DC, RST, DELAY> +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.reset(delay, 10_000, 2_000); + + //panel setting + //LUT from OTP,KW-BF KWR-AF BWROTP 0f BWOTP 1f + self.interface + .cmd_with_data(spi, Command::PanelSetting, &[0x1f, 0x0D])?; + + //resolution setting + self.interface + .cmd_with_data(spi, Command::ResolutionSetting, &[0x80, 0x01, 0x28])?; + + self.interface.cmd(spi, Command::PowerOn)?; + self.wait_until_idle(spi, delay)?; + + //VCOM AND DATA INTERVAL SETTING + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?; + + Ok(()) + } +} + +impl WaveshareDisplay + for Epd2in9d<'_, SPI, BUSY, DC, RST, DELAY> +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + type DisplayColor = Color; + fn new( + spi: &mut SPI, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result { + let interface = DisplayInterface::new(busy, dc, rst, delay_us); + let color = DEFAULT_BACKGROUND_COLOR; + let old_data: &[u8] = &[]; + let is_partial_refresh = false; + + let mut epd = Epd2in9d { + interface, + color, + refresh: RefreshLut::Full, + old_data, + is_partial_refresh, + }; + + epd.init(spi, delay)?; + + Ok(epd) + } + + fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.is_partial_refresh = false; + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0xf7])?; + self.interface.cmd(spi, Command::PowerOff)?; + self.wait_until_idle(spi, delay)?; + delay.delay_us(100_000); + self.interface + .cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; + + Ok(()) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.init(spi, delay)?; + Ok(()) + } + + fn set_background_color(&mut self, background_color: Color) { + self.color = background_color; + } + + fn background_color(&self) -> &Color { + &self.color + } + + fn width(&self) -> u32 { + WIDTH + } + + fn height(&self) -> u32 { + HEIGHT + } + + // 对应的是Display函数 + // 用于将要显示的数据写入屏幕SRAM + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + if self.is_partial_refresh { + // 若进行全刷则修改局刷状态 + self.is_partial_refresh = false; + } + self.wait_until_idle(spi, delay)?; + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface.data_x_times(spi, 0xFF, EPD_ARRAY)?; + + self.interface + .cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + self.old_data = unsafe { from_raw_parts(buffer.as_ptr(), buffer.len()) }; + Ok(()) + } + + // 这个是DisplayPart + // Partial refresh write address and data + 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> { + if !self.is_partial_refresh { + // 仅在初次调用时初始化 + self.set_part_reg(spi, delay)?; + self.is_partial_refresh = true; + } + self.interface.cmd(spi, Command::PartialIn)?; + + self.interface.cmd(spi, Command::PartialWindow)?; + self.interface.data(spi, &[(x - x % 8) as u8])?; + self.interface + .data(spi, &[(((x - x % 8) + width - 1) - 1) as u8])?; + self.interface.data(spi, &[(y / 256) as u8])?; + self.interface.data(spi, &[(y % 256) as u8])?; + self.interface + .data(spi, &[((y + height - 1) / 256) as u8])?; + self.interface + .data(spi, &[((y + height - 1) % 256 - 1) as u8])?; + self.interface.data(spi, &[0x28])?; + + self.interface + .cmd_with_data(spi, Command::DataStartTransmission1, self.old_data)?; + + self.interface + .cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + self.old_data = unsafe { from_raw_parts(buffer.as_ptr(), buffer.len()) }; + + Ok(()) + } + + /// actually is the "Turn on Display" sequence + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DisplayRefresh)?; + delay.delay_us(1_000); + self.wait_until_idle(spi, delay)?; + Ok(()) + } + + fn update_and_display_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_frame(spi, buffer, delay)?; + self.display_frame(spi, delay)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface.data_x_times(spi, 0x00, EPD_ARRAY)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data_x_times(spi, 0xFF, EPD_ARRAY)?; + + self.display_frame(spi, delay)?; + + Ok(()) + } + + fn set_lut( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + refresh_rate: Option, + ) -> Result<(), SPI::Error> { + if let Some(refresh_lut) = refresh_rate { + self.refresh = refresh_lut; + } + self.set_lut_helper( + spi, delay, &LUT_VCOM1, &LUT_WW1, &LUT_BW1, &LUT_WB1, &LUT_BB1, + )?; + + Ok(()) + } + + fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + Ok(()) + } +} + +impl Epd2in9d<'_, SPI, BUSY, DC, RST, DELAY> +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayUs, +{ + /// 唤醒屏幕 + /// + /// 在屏幕执行sleep之后,会进入深度睡眠模式。在深度睡眠模式下若需要刷新屏幕,必须先执行awaken() + /// 唤醒屏幕 + // fn awaken(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // // reset the device + // self.interface.reset(delay, 20_000, 2_000); + // self.wait_until_idle(spi, delay)?; + + // // panel setting + // // LUT from OTP,KW-BF KWR-AF BWROTP 0f BWOTP 1f + // self.interface + // .cmd_with_data(spi, Command::PanelSetting, &[0x1f])?; + + // // resolution setting + // self.interface + // .cmd_with_data(spi, Command::ResolutionSetting, &[0x80, 0x01, 0x28])?; + + // // VCOM AND DATA INTERVAL SETTING + // self.interface + // .cmd(spi, Command::VcomAndDataIntervalSetting)?; + // Ok(()) + // } + + fn set_part_reg(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // 重置EPD驱动电路 + //TODO: 这里在微雪的例程中反复刷新了3次,后面有显示问题再进行修改 + self.interface.reset(delay, 10_000, 2_000); + + // 电源设置 + //TODO: 文档中的数据为[0x03,0x00,0x2b,0x2b,0x09] + self.interface.cmd_with_data( + spi, + Command::PowerSetting, + &[0x03, 0x00, 0x2b, 0x2b, 0x03], + )?; + + // 软启动 + self.interface + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?; + + // 面板设置 + self.interface + .cmd_with_data(spi, Command::PanelSetting, &[0xbf, 0x0D])?; + + // 设置刷新率 + // 3a 100HZ | 29 150Hz | 39 200HZ | 31 171HZ + // 例程中使用3a + self.interface + .cmd_with_data(spi, Command::PllControl, &[0x3C])?; + + // 分辨率设置 + self.interface + .cmd_with_data(spi, Command::ResolutionSetting, &[0x80, 0x01, 0x28])?; + + // vcom_DC设置 + self.interface + .cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?; + + self.set_lut(spi, delay, None)?; + + // 开启电源 + // self.interface.cmd_with_data( + // spi, + // Command::PowerOn, + // &[0x04], + // ); + self.interface.cmd(spi, Command::PowerOn)?; + + // 获取BUSY电平,高电平继续执行,低电平则等待屏幕响应 + //TODO: 这里是文档推荐的步骤,但我看其他屏幕的也没等待就先忽略了 + self.wait_until_idle(spi, delay)?; + + // vcom和数据间隔设置 + // self.interface + // .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?; + + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn set_lut_helper( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + lut_vcom: &[u8], + lut_ww: &[u8], + lut_bw: &[u8], + lut_wb: &[u8], + lut_bb: &[u8], + ) -> Result<(), SPI::Error> { + let _ = delay; + // LUT VCOM + self.interface + .cmd_with_data(spi, Command::LutForVcom, lut_vcom)?; + + // LUT WHITE to WHITE + self.interface + .cmd_with_data(spi, Command::LutWhiteToWhite, lut_ww)?; + + // LUT BLACK to WHITE + self.interface + .cmd_with_data(spi, Command::LutBlackToWhite, lut_bw)?; + + // LUT WHITE to BLACK + self.interface + .cmd_with_data(spi, Command::LutWhiteToBlack, lut_wb)?; + + // LUT BLACK to BLACK + self.interface + .cmd_with_data(spi, Command::LutBlackToBlack, lut_bb)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index bdedf055..38ce84d3 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 epd2in9d; pub mod epd3in7; pub mod epd4in2; pub mod epd5in65f;