Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/epd3in52/command.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
76 changes: 76 additions & 0 deletions src/epd3in52/constants.rs
Original file line number Diff line number Diff line change
@@ -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,
];
254 changes: 254 additions & 0 deletions src/epd3in52/mod.rs
Original file line number Diff line number Diff line change
@@ -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<SPI, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
/// Background Color
background_color: Color,
/// Alternates waveform tables each refresh
lut_flag: bool,
}

impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
for Epd3in52<SPI, BUSY, DC, RST, DELAY>
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<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
for Epd3in52<SPI, BUSY, DC, RST, DELAY>
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<u32>,
) -> Result<Self, SPI::Error> {
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<RefreshLut>,
) -> 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);
}
}
Loading
Loading