From c1260e275e70e31c311834cf2ad3485b99baabdb Mon Sep 17 00:00:00 2001 From: cogwheel886 Date: Sat, 11 Apr 2026 01:53:35 -0400 Subject: [PATCH 1/3] feat: add support for Waveshare 3.52" EPD (EPD3IN52) - 240x360 resolution, UC8179 controller - Hardware-verified LUT waveform tables (GC and DU) - Alternating R22/R23 waveform tables in display_frame() - IS_BUSY_LOW: true, verified on Raspberry Pi 5 with lgpio - Includes Display3in52 type alias for graphics feature - 56 unit tests passing (54 original + 2 new) --- src/epd3in52/command.rs | 65 ++++++++++ src/epd3in52/constants.rs | 112 +++++++++++++++++ src/epd3in52/mod.rs | 251 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 429 insertions(+) create mode 100644 src/epd3in52/command.rs create mode 100644 src/epd3in52/constants.rs create mode 100644 src/epd3in52/mod.rs diff --git a/src/epd3in52/command.rs b/src/epd3in52/command.rs new file mode 100644 index 00000000..b9f6ac57 --- /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 = 0x12, + 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(), 0x12); + 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..bdeba2ab --- /dev/null +++ b/src/epd3in52/constants.rs @@ -0,0 +1,112 @@ +// 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..0a19c215 --- /dev/null +++ b/src/epd3in52/mod.rs @@ -0,0 +1,251 @@ +//! 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(spi, Command::Refresh)?; + 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> { + let color = self.background_color.get_byte_value(); + self.interface + .cmd(spi, Command::DataStartTransmission)?; + 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> { + // 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; From 9b66389c38ff81a76bd90fdd0343afef807b8005 Mon Sep 17 00:00:00 2001 From: cogwheel886 Date: Sat, 11 Apr 2026 02:00:19 -0400 Subject: [PATCH 2/3] style: apply cargo fmt --- src/epd3in52/constants.rs | 108 +++++++++++++------------------------- src/epd3in52/mod.rs | 21 ++++---- 2 files changed, 45 insertions(+), 84 deletions(-) diff --git a/src/epd3in52/constants.rs b/src/epd3in52/constants.rs index bdeba2ab..6b7fe347 100644 --- a/src/epd3in52/constants.rs +++ b/src/epd3in52/constants.rs @@ -3,110 +3,74 @@ // --- 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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 index 0a19c215..f7ded5f3 100644 --- a/src/epd3in52/mod.rs +++ b/src/epd3in52/mod.rs @@ -68,8 +68,11 @@ where 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::PowerSetting, + &[0x03, 0x10, 0x3F, 0x3F, 0x03], + )?; self.interface .cmd_with_data(spi, Command::BoosterSoftStart, &[0x37, 0x3D, 0x3D])?; self.interface @@ -125,8 +128,7 @@ where } fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { - self.interface - .cmd_with_data(spi, Command::Sleep, &[0xA5])?; + self.interface.cmd_with_data(spi, Command::Sleep, &[0xA5])?; Ok(()) } @@ -212,10 +214,8 @@ where 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, WIDTH * HEIGHT)?; + self.interface.cmd(spi, Command::DataStartTransmission)?; + self.interface.data_x_times(spi, color, WIDTH * HEIGHT)?; Ok(()) } @@ -243,9 +243,6 @@ mod tests { fn epd_size() { assert_eq!(WIDTH, 240); assert_eq!(HEIGHT, 360); - assert_eq!( - buffer_len(WIDTH as usize, HEIGHT as usize), - 240 / 8 * 360 - ); + assert_eq!(buffer_len(WIDTH as usize, HEIGHT as usize), 240 / 8 * 360); } } From 765bf1c73027252e73d8f99eea781c88ec63ee28 Mon Sep 17 00:00:00 2001 From: cogwheel886 Date: Sat, 11 Apr 2026 19:21:52 -0400 Subject: [PATCH 3/3] fix: epd3in52 bugs found during epd2in13_v4 review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix clear_frame() sending 8x too many bytes (WIDTH*HEIGHT → buffer_len) - Fix refresh command: 0x12 → 0x17 + 0xA5 matching Python reference - Rename EPD3in52 → Epd3in52 (naming convention) --- src/epd3in52/command.rs | 4 ++-- src/epd3in52/mod.rs | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/epd3in52/command.rs b/src/epd3in52/command.rs index b9f6ac57..77064140 100644 --- a/src/epd3in52/command.rs +++ b/src/epd3in52/command.rs @@ -15,7 +15,7 @@ pub(crate) enum Command { PowerSetting = 0x01, BoosterSoftStart = 0x06, DataStartTransmission = 0x13, - Refresh = 0x12, + Refresh = 0x17, LutVcom = 0x20, LutBlue = 0x21, LutWhite = 0x22, @@ -48,7 +48,7 @@ mod tests { assert_eq!(Command::PowerSetting.address(), 0x01); assert_eq!(Command::BoosterSoftStart.address(), 0x06); assert_eq!(Command::DataStartTransmission.address(), 0x13); - assert_eq!(Command::Refresh.address(), 0x12); + assert_eq!(Command::Refresh.address(), 0x17); assert_eq!(Command::LutVcom.address(), 0x20); assert_eq!(Command::LutBlue.address(), 0x21); assert_eq!(Command::LutWhite.address(), 0x22); diff --git a/src/epd3in52/mod.rs b/src/epd3in52/mod.rs index f7ded5f3..e0488102 100644 --- a/src/epd3in52/mod.rs +++ b/src/epd3in52/mod.rs @@ -43,8 +43,8 @@ pub type Display3in52 = crate::graphics::Display< Color, >; -/// EPD3in52 driver -pub struct EPD3in52 { +/// Epd3in52 driver +pub struct Epd3in52 { /// Connection Interface interface: DisplayInterface, /// Background Color @@ -54,7 +54,7 @@ pub struct EPD3in52 { } impl InternalWiAdditions - for EPD3in52 + for Epd3in52 where SPI: SpiDevice, BUSY: InputPin, @@ -95,7 +95,7 @@ where } impl WaveshareDisplay - for EPD3in52 + for Epd3in52 where SPI: SpiDevice, BUSY: InputPin, @@ -113,7 +113,7 @@ where delay: &mut DELAY, delay_us: Option, ) -> Result { - let mut epd = EPD3in52 { + let mut epd = Epd3in52 { interface: DisplayInterface::new(busy, dc, rst, delay_us), background_color: DEFAULT_BACKGROUND_COLOR, lut_flag: false, @@ -196,8 +196,10 @@ where self.lut_flag = !self.lut_flag; - self.interface.cmd(spi, Command::Refresh)?; + self.interface + .cmd_with_data(spi, Command::Refresh, &[0xA5])?; self.interface.wait_until_idle(delay, IS_BUSY_LOW); + delay.delay_us(200_000); Ok(()) } @@ -215,7 +217,11 @@ where 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, WIDTH * HEIGHT)?; + self.interface.data_x_times( + spi, + color, + buffer_len(WIDTH as usize, HEIGHT as usize) as u32, + )?; Ok(()) }