diff --git a/src/epd3in52/command.rs b/src/epd3in52/command.rs new file mode 100644 index 00000000..77064140 --- /dev/null +++ b/src/epd3in52/command.rs @@ -0,0 +1,65 @@ +//! SPI Commands for the Waveshare 3.52" E-Ink Display + +use crate::traits; + +/// EPD3IN52 commands +/// +/// Should rarely (never?) be needed directly. +/// +/// For more infos about the addresses and what they are doing look into the pdfs +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub(crate) enum Command { + PanelSetting = 0x00, + PowerSetting = 0x01, + BoosterSoftStart = 0x06, + DataStartTransmission = 0x13, + Refresh = 0x17, + LutVcom = 0x20, + LutBlue = 0x21, + LutWhite = 0x22, + LutGray1 = 0x23, + LutGray2 = 0x24, + PllControl = 0x30, + VcomDataSetting = 0x50, + TconSetting = 0x60, + ResolutionSetting = 0x61, + VcomDcSetting = 0x82, + PowerSaving = 0xE3, + Sleep = 0x07, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::Command as CommandTrait; + + #[test] + fn command_addr() { + assert_eq!(Command::PanelSetting.address(), 0x00); + assert_eq!(Command::PowerSetting.address(), 0x01); + assert_eq!(Command::BoosterSoftStart.address(), 0x06); + assert_eq!(Command::DataStartTransmission.address(), 0x13); + assert_eq!(Command::Refresh.address(), 0x17); + assert_eq!(Command::LutVcom.address(), 0x20); + assert_eq!(Command::LutBlue.address(), 0x21); + assert_eq!(Command::LutWhite.address(), 0x22); + assert_eq!(Command::LutGray1.address(), 0x23); + assert_eq!(Command::LutGray2.address(), 0x24); + assert_eq!(Command::PllControl.address(), 0x30); + assert_eq!(Command::VcomDataSetting.address(), 0x50); + assert_eq!(Command::TconSetting.address(), 0x60); + assert_eq!(Command::ResolutionSetting.address(), 0x61); + assert_eq!(Command::VcomDcSetting.address(), 0x82); + assert_eq!(Command::PowerSaving.address(), 0xE3); + assert_eq!(Command::Sleep.address(), 0x07); + } +} diff --git a/src/epd3in52/constants.rs b/src/epd3in52/constants.rs new file mode 100644 index 00000000..6b7fe347 --- /dev/null +++ b/src/epd3in52/constants.rs @@ -0,0 +1,76 @@ +// Hardware-verified waveform LUT tables from the Waveshare Python reference driver. + +// --- Global Clear (GC) LUTs --- + +pub(crate) const LUT_R20_GC: [u8; 56] = [ + 0x01, 0x0f, 0x0f, 0x0f, 0x01, 0x01, 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, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_R21_GC: [u8; 42] = [ + 0x01, 0x4f, 0x8f, 0x0f, 0x01, 0x01, 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, +]; + +pub(crate) const LUT_R22_GC: [u8; 56] = [ + 0x01, 0x0f, 0x8f, 0x0f, 0x01, 0x01, 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, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_R23_GC: [u8; 56] = [ + 0x01, 0x4f, 0x8f, 0x4f, 0x01, 0x01, 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, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_R24_GC: [u8; 42] = [ + 0x01, 0x0f, 0x8f, 0x4f, 0x01, 0x01, 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, +]; + +// --- Differential Update (DU) LUTs --- + +#[allow(dead_code)] +pub(crate) const LUT_R20_DU: [u8; 56] = [ + 0x01, 0x0f, 0x01, 0x00, 0x00, 0x01, 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, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +#[allow(dead_code)] +pub(crate) const LUT_R21_DU: [u8; 42] = [ + 0x01, 0x0f, 0x01, 0x00, 0x00, 0x01, 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, +]; + +#[allow(dead_code)] +pub(crate) const LUT_R22_DU: [u8; 56] = [ + 0x01, 0x8f, 0x01, 0x00, 0x00, 0x01, 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, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +#[allow(dead_code)] +pub(crate) const LUT_R23_DU: [u8; 56] = [ + 0x01, 0x4f, 0x01, 0x00, 0x00, 0x01, 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, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +#[allow(dead_code)] +pub(crate) const LUT_R24_DU: [u8; 42] = [ + 0x01, 0x0f, 0x01, 0x00, 0x00, 0x01, 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, +]; diff --git a/src/epd3in52/mod.rs b/src/epd3in52/mod.rs new file mode 100644 index 00000000..e0488102 --- /dev/null +++ b/src/epd3in52/mod.rs @@ -0,0 +1,254 @@ +//! A simple Driver for the Waveshare 3.52" E-Ink Display via SPI +//! +//! +//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/3.52inch_e-Paper_HAT), + +use embedded_hal::{ + delay::DelayNs, + digital::{InputPin, OutputPin}, + spi::SpiDevice, +}; + +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 = 240; + +/// Height of the display +pub const HEIGHT: u32 = 360; + +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; + +const IS_BUSY_LOW: bool = true; + +const SINGLE_BYTE_WRITE: bool = true; + +/// Display with Fullsize buffer for use with the 3in52 EPD +#[cfg(feature = "graphics")] +pub type Display3in52 = crate::graphics::Display< + WIDTH, + HEIGHT, + false, + { buffer_len(WIDTH as usize, HEIGHT as usize) }, + Color, +>; + +/// Epd3in52 driver +pub struct Epd3in52 { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + background_color: Color, + /// Alternates waveform tables each refresh + lut_flag: bool, +} + +impl InternalWiAdditions + for Epd3in52 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + 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_with_data(spi, Command::PanelSetting, &[0xFF, 0x01])?; + self.interface.cmd_with_data( + spi, + Command::PowerSetting, + &[0x03, 0x10, 0x3F, 0x3F, 0x03], + )?; + self.interface + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x37, 0x3D, 0x3D])?; + self.interface + .cmd_with_data(spi, Command::TconSetting, &[0x22])?; + self.interface + .cmd_with_data(spi, Command::VcomDcSetting, &[0x07])?; + self.interface + .cmd_with_data(spi, Command::PllControl, &[0x09])?; + self.interface + .cmd_with_data(spi, Command::PowerSaving, &[0x88])?; + self.interface + .cmd_with_data(spi, Command::ResolutionSetting, &[0xF0, 0x01, 0x68])?; + self.interface + .cmd_with_data(spi, Command::VcomDataSetting, &[0xB7])?; + + self.lut_flag = false; + + Ok(()) + } +} + +impl WaveshareDisplay + for Epd3in52 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + type DisplayColor = Color; + + fn new( + spi: &mut SPI, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result { + let mut epd = Epd3in52 { + interface: DisplayInterface::new(busy, dc, rst, delay_us), + background_color: DEFAULT_BACKGROUND_COLOR, + lut_flag: false, + }; + + 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, &[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::DataStartTransmission, 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::LutVcom, &LUT_R20_GC)?; + self.interface + .cmd_with_data(spi, Command::LutBlue, &LUT_R21_GC)?; + self.interface + .cmd_with_data(spi, Command::LutGray2, &LUT_R24_GC)?; + + if !self.lut_flag { + self.interface + .cmd_with_data(spi, Command::LutWhite, &LUT_R22_GC)?; + self.interface + .cmd_with_data(spi, Command::LutGray1, &LUT_R23_GC[..42])?; + } else { + self.interface + .cmd_with_data(spi, Command::LutWhite, &LUT_R23_GC)?; + self.interface + .cmd_with_data(spi, Command::LutGray1, &LUT_R22_GC[..42])?; + } + + self.lut_flag = !self.lut_flag; + + self.interface + .cmd_with_data(spi, Command::Refresh, &[0xA5])?; + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + delay.delay_us(200_000); + 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> { + let color = self.background_color.get_byte_value(); + self.interface.cmd(spi, Command::DataStartTransmission)?; + self.interface.data_x_times( + spi, + color, + buffer_len(WIDTH as usize, HEIGHT as usize) as u32, + )?; + Ok(()) + } + + fn set_lut( + &mut self, + _spi: &mut SPI, + _delay: &mut DELAY, + _refresh_rate: Option, + ) -> Result<(), SPI::Error> { + // LUTs are sent during display_frame with alternating waveform tables + 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(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 240); + assert_eq!(HEIGHT, 360); + assert_eq!(buffer_len(WIDTH as usize, HEIGHT as usize), 240 / 8 * 360); + } +} diff --git a/src/lib.rs b/src/lib.rs index 3ef7291a..a478e88d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,7 @@ pub mod epd2in9_v2; pub mod epd2in9b_v4; pub mod epd2in9bc; pub mod epd2in9d; +pub mod epd3in52; pub mod epd3in7; pub mod epd4in2; pub mod epd5in65f;