Browse Source

Initial support for Waveshare 2in13 v2 e-ink screen

This initial support covers both the full (slow) update of the display and the
partial (fast) update.
master
Marc Poulhiès 2 years ago
parent
commit
27e367c89c
  1. 1
      Cargo.toml
  2. 167
      examples/epd2in13_v2.rs
  3. 287
      src/epd2in13_v2/command.rs
  4. 35
      src/epd2in13_v2/constants.rs
  5. 159
      src/epd2in13_v2/graphics.rs
  6. 575
      src/epd2in13_v2/mod.rs
  7. 1
      src/lib.rs
  8. 2
      src/traits.rs

1
Cargo.toml

@ -18,6 +18,7 @@ edition = "2018"
[dependencies]
embedded-graphics = { version = "0.6.1", optional = true}
embedded-hal = {version = "0.2.3", features = ["unproven"]}
bit_field = "0.10.1"
[dev-dependencies]
linux-embedded-hal = "0.3"

167
examples/epd2in13_v2.rs

@ -0,0 +1,167 @@
#![deny(warnings)]
use embedded_graphics::{
fonts::{Font12x16, Font6x8, Text},
prelude::*,
primitives::{Circle, Line},
style::PrimitiveStyle,
text_style,
};
use embedded_hal::prelude::*;
use epd_waveshare::{
color::*,
epd2in13_v2::{Display2in13, EPD2in13},
graphics::{Display, DisplayRotation},
prelude::*,
};
use linux_embedded_hal::{
spidev::{self, SpidevOptions},
sysfs_gpio::Direction,
Delay, Pin, Spidev,
};
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() -> Result<(), std::io::Error> {
// Configure SPI
// Settings are taken from
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs = Pin::new(26); //BCM7 CE0
cs.export().expect("cs export");
while !cs.is_exported() {}
cs.set_direction(Direction::Out).expect("CS Direction");
cs.set_value(1).expect("CS Value set to 1");
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
let dc = Pin::new(6); //pin 31 //bcm6
dc.export().expect("dc export");
while !dc.is_exported() {}
dc.set_direction(Direction::Out).expect("dc Direction");
dc.set_value(1).expect("dc Value set to 1");
let rst = Pin::new(16); //pin 36 //bcm16
rst.export().expect("rst export");
while !rst.is_exported() {}
rst.set_direction(Direction::Out).expect("rst Direction");
rst.set_value(1).expect("rst Value set to 1");
let mut delay = Delay {};
let mut epd2in13 =
EPD2in13::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error");
//println!("Test all the rotations");
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate0);
draw_text(&mut display, "Rotate 0!", 5, 50);
display.set_rotation(DisplayRotation::Rotate90);
draw_text(&mut display, "Rotate 90!", 5, 50);
display.set_rotation(DisplayRotation::Rotate180);
draw_text(&mut display, "Rotate 180!", 5, 50);
display.set_rotation(DisplayRotation::Rotate270);
draw_text(&mut display, "Rotate 270!", 5, 50);
epd2in13.update_frame(&mut spi, &display.buffer())?;
epd2in13
.display_frame(&mut spi)
.expect("display frame new graphics");
delay.delay_ms(5000u16);
//println!("Now test new graphics with default rotation and some special stuff:");
display.clear_buffer(Color::White);
// draw a analog clock
let _ = Circle::new(Point::new(64, 64), 40)
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let _ = Line::new(Point::new(64, 64), Point::new(30, 40))
.into_styled(PrimitiveStyle::with_stroke(Black, 4))
.draw(&mut display);
let _ = Line::new(Point::new(64, 64), Point::new(80, 40))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
// draw white on black background
let _ = Text::new("It's working-WoB!", Point::new(90, 10))
.into_styled(text_style!(
font = Font6x8,
text_color = White,
background_color = Black
))
.draw(&mut display);
// use bigger/different font
let _ = Text::new("It's working-WoB!", Point::new(90, 40))
.into_styled(text_style!(
font = Font12x16,
text_color = White,
background_color = Black
))
.draw(&mut display);
// Demonstrating how to use the partial refresh feature of the screen.
// Real animations can be used.
epd2in13
.set_refresh(&mut spi, &mut delay, RefreshLUT::QUICK)
.unwrap();
epd2in13.clear_frame(&mut spi).unwrap();
// a moving `Hello World!`
let limit = 10;
for i in 0..limit {
draw_text(&mut display, " Hello World! ", 5 + i * 12, 50);
epd2in13
.update_and_display_frame(&mut spi, &display.buffer())
.expect("display frame new graphics");
delay.delay_ms(1_000u16);
}
// Show a spinning bar without any delay between frames. Shows how «fast»
// the screen can refresh for this kind of change (small single character)
display.clear_buffer(Color::White);
epd2in13
.update_and_display_frame(&mut spi, &display.buffer())
.unwrap();
let spinner = ["|", "/", "-", "\\"];
for i in 0..10 {
display.clear_buffer(Color::White);
draw_text(&mut display, spinner[i % spinner.len()], 10, 100);
epd2in13
.update_and_display_frame(&mut spi, &display.buffer())
.unwrap();
}
println!("Finished tests - going to sleep");
epd2in13.sleep(&mut spi)
}
fn draw_text(display: &mut Display2in13, text: &str, x: i32, y: i32) {
let _ = Text::new(text, Point::new(x, y))
.into_styled(text_style!(
font = Font6x8,
text_color = Black,
background_color = White
))
.draw(display);
}

287
src/epd2in13_v2/command.rs

@ -0,0 +1,287 @@
//! SPI Commands for the Waveshare 2.13" v2
use crate::traits;
extern crate bit_field;
use bit_field::BitField;
/// EPD2in13 v2
///
/// For more infos about the addresses and what they are doing look into the pdfs
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub(crate) enum Command {
DRIVER_OUTPUT_CONTROL = 0x01,
GATE_DRIVING_VOLTAGE_CTRL = 0x03,
SOURCE_DRIVING_VOLTAGE_CTRL = 0x04,
BOOSTER_SOFT_START_CONTROL = 0x0C,
GATE_SCAN_START_POSITION = 0x0F,
DEEP_SLEEP_MODE = 0x10,
DATA_ENTRY_MODE_SETTING = 0x11,
SW_RESET = 0x12,
HV_READY_DETECTION = 0x14,
VCI_DETECTION = 0x15,
TEMPERATURE_SENSOR_CONTROL_WRITE = 0x1A,
TEMPERATURE_SENSOR_CONTROL_READ = 0x1B,
TEMPERATURE_SENSOR_EXT_CONTROL_WRITE = 0x1C,
MASTER_ACTIVATION = 0x20,
DISPLAY_UPDATE_CONTROL_1 = 0x21,
DISPLAY_UPDATE_CONTROL_2 = 0x22,
WRITE_RAM = 0x24,
WRITE_RAM_RED = 0x26,
READ_RAM = 0x27,
VCOM_SENSE = 0x28,
VCOM_SENSE_DURATION = 0x29,
PROGRAM_VCOM_OPT = 0x2A,
WRITE_VCOM_REGISTER = 0x2C,
OTP_REGISTER_READ = 0x2D,
STATUS_BIT_READ = 0x2F,
PROGRAM_WS_OTP = 0x30,
LOAD_WS_OTP = 0x31,
WRITE_LUT_REGISTER = 0x32,
PROGRAM_OTP_SELECTION = 0x36,
WRITE_OTP_SELECTION = 0x37,
SET_DUMMY_LINE_PERIOD = 0x3A,
SET_GATE_LINE_WIDTH = 0x3B,
BORDER_WAVEFORM_CONTROL = 0x3C,
READ_RAM_OPTION = 0x41,
SET_RAM_X_ADDRESS_START_END_POSITION = 0x44,
SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45,
AUTO_WRITE_RED_RAM_REGULAR_PATTERN = 0x46,
AUTO_WRITE_BW_RAM_REGULAR_PATTERN = 0x47,
SET_RAM_X_ADDRESS_COUNTER = 0x4E,
SET_RAM_Y_ADDRESS_COUNTER = 0x4F,
SET_ANALOG_BLOCK_CONTROL = 0x74,
SET_DIGITAL_BLOCK_CONTROL = 0x7E,
NOP = 0x7F,
}
pub(crate) struct DriverOutput {
pub scan_is_linear: bool,
pub scan_g0_is_first: bool,
pub scan_dir_incr: bool,
pub width: u16,
}
impl DriverOutput {
pub fn to_bytes(&self) -> [u8; 3] {
[
self.width as u8,
(self.width >> 8) as u8,
*0u8.set_bit(0, !self.scan_dir_incr)
.set_bit(1, !self.scan_g0_is_first)
.set_bit(2, !self.scan_is_linear),
]
}
}
/// These are not directly documented, but the bitfield is easily reversed from
/// documentation and sample code
/// [7|6|5|4|3|2|1|0]
/// | | | | | | | `--- disable clock
/// | | | | | | `----- disable analog
/// | | | | | `------- display
/// | | | | `--------- undocumented and unknown use,
/// | | | | but used in waveshare reference code
/// | | | `----------- load LUT
/// | | `------------- load temp
/// | `--------------- enable clock
/// `----------------- enable analog
pub(crate) struct DisplayUpdateControl2(pub u8);
#[allow(dead_code)]
impl DisplayUpdateControl2 {
pub fn new() -> DisplayUpdateControl2 {
DisplayUpdateControl2(0x00)
}
pub fn disable_clock(mut self) -> Self {
self.0.set_bit(0, true);
self
}
pub fn disable_analog(mut self) -> Self {
self.0.set_bit(1, true);
self
}
pub fn display(mut self) -> Self {
self.0.set_bit(2, true);
self
}
pub fn load_lut(mut self) -> Self {
self.0.set_bit(4, true);
self
}
pub fn load_temp(mut self) -> Self {
self.0.set_bit(5, true);
self
}
pub fn enable_clock(mut self) -> Self {
self.0.set_bit(6, true);
self
}
pub fn enable_analog(mut self) -> Self {
self.0.set_bit(7, true);
self
}
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
pub(crate) enum DataEntryModeIncr {
X_DECR_Y_DECR = 0x0,
X_INCR_Y_DECR = 0x1,
X_DECR_Y_INCR = 0x2,
X_INCR_Y_INCR = 0x3,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
pub(crate) enum DataEntryModeDir {
X_DIR = 0x0,
Y_DIR = 0x4,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub(crate) enum BorderWaveFormVBD {
GS = 0x0,
FIX_LEVEL = 0x1,
VCOM = 0x2,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub(crate) enum BorderWaveFormFixLevel {
VSS = 0x0,
VSH1 = 0x1,
VSL = 0x2,
VSH2 = 0x3,
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub(crate) enum BorderWaveFormGS {
LUT0 = 0x0,
LUT1 = 0x1,
LUT2 = 0x2,
LUT3 = 0x3,
}
pub(crate) struct BorderWaveForm {
pub vbd: BorderWaveFormVBD,
pub fix_level: BorderWaveFormFixLevel,
pub gs_trans: BorderWaveFormGS,
}
impl BorderWaveForm {
pub fn to_u8(&self) -> u8 {
*0u8.set_bits(6..8, self.vbd as u8)
.set_bits(4..6, self.fix_level as u8)
.set_bits(0..2, self.gs_trans as u8)
}
}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub enum DeepSleepMode {
// Sleeps and keeps access to RAM and controller
NORMAL = 0x00,
// Sleeps without access to RAM/controller but keeps RAM content
MODE_1 = 0x01,
// Same as MODE_1 but RAM content is not kept
MODE_2 = 0x11,
}
pub(crate) struct GateDrivingVoltage(pub u8);
pub(crate) struct SourceDrivingVoltage(pub u8);
pub(crate) struct VCOM(pub u8);
pub(crate) trait I32Ext {
fn vcom(self) -> VCOM;
fn gate_driving_decivolt(self) -> GateDrivingVoltage;
fn source_driving_decivolt(self) -> SourceDrivingVoltage;
}
impl I32Ext for i32 {
// This is really not very nice. Until I find something better, this will be
// a placeholder.
fn vcom(self) -> VCOM {
assert!(self >= -30 && self <= -2);
let u = match -self {
2 => 0x08,
3 => 0x0B,
4 => 0x10,
5 => 0x14,
6 => 0x17,
7 => 0x1B,
8 => 0x20,
9 => 0x24,
10 => 0x28,
11 => 0x2C,
12 => 0x2F,
13 => 0x34,
14 => 0x37,
15 => 0x3C,
16 => 0x40,
17 => 0x44,
18 => 0x48,
19 => 0x4B,
20 => 0x50,
21 => 0x54,
22 => 0x58,
23 => 0x5B,
24 => 0x5F,
25 => 0x64,
26 => 0x68,
27 => 0x6C,
28 => 0x6F,
29 => 0x73,
30 => 0x78,
_ => 0,
};
VCOM(u)
}
fn gate_driving_decivolt(self) -> GateDrivingVoltage {
assert!(self >= 100 && self <= 210 && self % 5 == 0);
GateDrivingVoltage(((self - 100) / 5 + 0x03) as u8)
}
fn source_driving_decivolt(self) -> SourceDrivingVoltage {
assert!(
(self >= 24 && self <= 88)
|| (self >= 90 && self <= 180 && self % 5 == 0)
|| (self >= -180 && self <= -90 && self % 5 == 0)
);
if self >= 24 && self <= 88 {
SourceDrivingVoltage(((self - 24) + 0x8E) as u8)
} else if self >= 90 && self <= 180 {
SourceDrivingVoltage(((self - 90) / 2 + 0x23) as u8)
} else {
SourceDrivingVoltage((((-self - 90) / 5) * 2 + 0x1A) as u8)
}
}
}
impl traits::Command for Command {
/// Returns the address of the command
fn address(self) -> u8 {
self as u8
}
}

35
src/epd2in13_v2/constants.rs

@ -0,0 +1,35 @@
#[rustfmt::skip]
// Original Waveforms from Waveshare
pub(crate) const LUT_FULL_UPDATE: [u8; 70] =[
0x80,0x60,0x40,0x00,0x00,0x00,0x00, // LUT0: BB: VS 0 ~7
0x10,0x60,0x20,0x00,0x00,0x00,0x00, // LUT1: BW: VS 0 ~7
0x80,0x60,0x40,0x00,0x00,0x00,0x00, // LUT2: WB: VS 0 ~7
0x10,0x60,0x20,0x00,0x00,0x00,0x00, // LUT3: WW: VS 0 ~7
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT4: VCOM: VS 0 ~7
0x03,0x03,0x00,0x00,0x02, // TP0 A~D RP0
0x09,0x09,0x00,0x00,0x02, // TP1 A~D RP1
0x03,0x03,0x00,0x00,0x02, // TP2 A~D RP2
0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3
0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4
0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5
0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6
];
#[rustfmt::skip]
pub(crate) const LUT_PARTIAL_UPDATE: [u8; 70] =[
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT0: BB: VS 0 ~7
0x80,0x00,0x00,0x00,0x00,0x00,0x00, // LUT1: BW: VS 0 ~7
0x40,0x00,0x00,0x00,0x00,0x00,0x00, // LUT2: WB: VS 0 ~7
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT3: WW: VS 0 ~7
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT4: VCOM: VS 0 ~7
0x0A,0x00,0x00,0x00,0x00, // TP0 A~D RP0
0x00,0x00,0x00,0x00,0x00, // TP1 A~D RP1
0x00,0x00,0x00,0x00,0x00, // TP2 A~D RP2
0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3
0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4
0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5
0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6
];

159
src/epd2in13_v2/graphics.rs

@ -0,0 +1,159 @@
use crate::buffer_len;
use crate::epd2in13_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
use crate::graphics::{Display, DisplayRotation};
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
/// Full size buffer for use with the 2in13 v2 EPD
///
/// Can also be manually constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
pub struct Display2in13 {
buffer: [u8; buffer_len(WIDTH as usize, HEIGHT as usize)],
rotation: DisplayRotation,
}
impl Default for Display2in13 {
fn default() -> Self {
Display2in13 {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
buffer_len(WIDTH as usize, HEIGHT as usize)],
rotation: DisplayRotation::default(),
}
}
}
impl DrawTarget<BinaryColor> for Display2in13 {
type Error = core::convert::Infallible;
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
self.draw_helper(WIDTH, HEIGHT, pixel)
}
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Display for Display2in13 {
fn buffer(&self) -> &[u8] {
&self.buffer
}
fn get_mut_buffer(&mut self) -> &mut [u8] {
&mut self.buffer
}
fn set_rotation(&mut self, rotation: DisplayRotation) {
self.rotation = rotation;
}
fn rotation(&self) -> DisplayRotation {
self.rotation
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::{Black, Color};
use crate::epd2in13_v2;
use crate::graphics::{Display, DisplayRotation};
use embedded_graphics::{primitives::Line, style::PrimitiveStyle};
// test buffer length
#[test]
fn graphics_size() {
let display = Display2in13::default();
assert_eq!(display.buffer().len(), 4000);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let display = Display2in13::default();
for &byte in display.buffer() {
assert_eq!(byte, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_0() {
let mut display = Display2in13::default();
let _ = Line::new(Point::new(0, 0), Point::new(7, 0))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_90() {
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate90);
let _ = Line::new(
Point::new(0, (WIDTH - 8) as i32),
Point::new(0, (WIDTH - 1) as i32),
)
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_180() {
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate180);
let _ = Line::new(
Point::new((WIDTH - 8) as i32, (HEIGHT - 1) as i32),
Point::new((WIDTH - 1) as i32, (HEIGHT - 1) as i32),
)
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_270() {
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate270);
let _ = Line::new(
Point::new((HEIGHT - 1) as i32, 0),
Point::new((HEIGHT - 1) as i32, 7),
)
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
}

575
src/epd2in13_v2/mod.rs

@ -0,0 +1,575 @@
//! A Driver for the Waveshare 2.13" E-Ink Display (V2) via SPI
//!
//! # References
//!
//! - [Waveshare product page](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT)
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_2in13_V2.c)
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd2in13_V2.py)
//! - [Controller Datasheet SS1780](http://www.e-paper-display.com/download_detail/downloadsId=682.html)
//!
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::v2::{InputPin, OutputPin},
};
use crate::buffer_len;
use crate::color::Color;
use crate::interface::DisplayInterface;
use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay};
pub(crate) mod command;
use self::command::{
BorderWaveForm, BorderWaveFormFixLevel, BorderWaveFormGS, BorderWaveFormVBD, Command,
DataEntryModeDir, DataEntryModeIncr, DeepSleepMode, DisplayUpdateControl2, DriverOutput,
GateDrivingVoltage, I32Ext, SourceDrivingVoltage, VCOM,
};
pub(crate) mod constants;
use self::constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE};
#[cfg(feature = "graphics")]
mod graphics;
#[cfg(feature = "graphics")]
pub use self::graphics::Display2in13;
/// Width of the display.
pub const WIDTH: u32 = 122;
/// Height of the display
pub const HEIGHT: u32 = 250;
/// Default Background Color
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = false;
/// EPD2in13 (V2) driver
///
pub struct EPD2in13<SPI, CS, BUSY, DC, RST> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
sleep_mode: DeepSleepMode,
/// Background Color
background_color: Color,
refresh: RefreshLUT,
}
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
for EPD2in13<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
fn init<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error> {
// HW reset
self.interface.reset(delay);
if self.refresh == RefreshLUT::QUICK {
self.set_vcom_register(spi, (-9).vcom())?;
self.wait_until_idle();
self.set_lut(spi, Some(self.refresh))?;
// Python code does this, not sure why
// self.cmd_with_data(spi, Command::WRITE_OTP_SELECTION, &[0, 0, 0, 0, 0x40, 0, 0])?;
// During partial update, clock/analog are not disabled between 2
// updates.
self.set_display_update_control_2(
spi,
DisplayUpdateControl2::new().enable_analog().enable_clock(),
)?;
self.command(spi, Command::MASTER_ACTIVATION)?;
self.wait_until_idle();
self.set_border_waveform(
spi,
BorderWaveForm {
vbd: BorderWaveFormVBD::GS,
fix_level: BorderWaveFormFixLevel::VSS,
gs_trans: BorderWaveFormGS::LUT1,
},
)?;
} else {
self.wait_until_idle();
self.command(spi, Command::SW_RESET)?;
self.wait_until_idle();
self.set_driver_output(
spi,
DriverOutput {
scan_is_linear: true,
scan_g0_is_first: true,
scan_dir_incr: true,
width: (HEIGHT - 1) as u16,
},
)?;
// These 2 are the reset values
self.set_dummy_line_period(spi, 0x30)?;
self.set_gate_scan_start_position(spi, 0)?;
self.set_data_entry_mode(
spi,
DataEntryModeIncr::X_INCR_Y_INCR,
DataEntryModeDir::X_DIR,
)?;
// Use simple X/Y auto increase
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.set_border_waveform(
spi,
BorderWaveForm {
vbd: BorderWaveFormVBD::GS,
fix_level: BorderWaveFormFixLevel::VSS,
gs_trans: BorderWaveFormGS::LUT3,
},
)?;
self.set_vcom_register(spi, (-21).vcom())?;
self.set_gate_driving_voltage(spi, 190.gate_driving_decivolt())?;
self.set_source_driving_voltage(
spi,
150.source_driving_decivolt(),
50.source_driving_decivolt(),
(-150).source_driving_decivolt(),
)?;
self.set_gate_line_width(spi, 10)?;
self.set_lut(spi, Some(self.refresh))?;
}
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
for EPD2in13<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
fn new<DELAY: DelayMs<u8>>(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
let mut epd = EPD2in13 {
interface: DisplayInterface::new(cs, busy, dc, rst),
sleep_mode: DeepSleepMode::MODE_1,
background_color: DEFAULT_BACKGROUND_COLOR,
refresh: RefreshLUT::FULL,
};
epd.init(spi, delay)?;
Ok(epd)
}
fn wake_up<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error> {
self.init(spi, delay)
}
fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
self.wait_until_idle();
// All sample code enables and disables analog/clocks...
self.set_display_update_control_2(
spi,
DisplayUpdateControl2::new()
.enable_analog()
.enable_clock()
.disable_analog()
.disable_clock(),
)?;
self.command(spi, Command::MASTER_ACTIVATION)?;
self.set_sleep_mode(spi, self.sleep_mode)?;
Ok(())
}
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
assert!(buffer.len() == buffer_len(WIDTH as usize, HEIGHT as usize));
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
if self.refresh == RefreshLUT::FULL {
// Always keep the base buffer equal to current if not doing partial refresh.
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?;
}
Ok(())
}
/// Updating only a part of the frame is not supported when using the
/// partial refresh feature. The function will panic if called when set to
/// use partial refresh.
fn update_partial_frame(
&mut self,
spi: &mut SPI,
buffer: &[u8],
x: u32,
y: u32,
width: u32,
height: u32,
) -> Result<(), SPI::Error> {
assert!((width * height / 8) as usize == buffer.len());
// This should not be used when doing partial refresh. The RAM_RED must
// be updated with the last buffer having been displayed. Doing partial
// update directly in RAM makes this update impossible (we can't read
// RAM content). Using this function will most probably make the actual
// display incorrect as the controler will compare with something
// incorrect.
assert!(self.refresh == RefreshLUT::FULL);
self.set_ram_area(spi, x, y, x + width, y + height)?;
self.set_ram_address_counters(spi, x, y)?;
self.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
if self.refresh == RefreshLUT::FULL {
// Always keep the base buffer equals to current if not doing partial refresh.
self.set_ram_area(spi, x, y, x + width, y + height)?;
self.set_ram_address_counters(spi, x, y)?;
self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?;
}
Ok(())
}
/// Never use directly this function when using partial refresh, or also
/// keep the base buffer in syncd using `set_partial_base_buffer` function.
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
if self.refresh == RefreshLUT::FULL {
self.set_display_update_control_2(
spi,
DisplayUpdateControl2::new()
.enable_clock()
.enable_analog()
.display()
.disable_analog()
.disable_clock(),
)?;
} else {
self.set_display_update_control_2(spi, DisplayUpdateControl2::new().display())?;
}
self.command(spi, Command::MASTER_ACTIVATION)?;
self.wait_until_idle();
Ok(())
}
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
self.update_frame(spi, buffer)?;
self.display_frame(spi)?;
if self.refresh == RefreshLUT::QUICK {
self.set_partial_base_buffer(spi, buffer)?;
}
Ok(())
}
fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
let color = self.background_color.get_byte_value();
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.command(spi, Command::WRITE_RAM)?;
self.interface.data_x_times(
spi,
color,
buffer_len(WIDTH as usize, HEIGHT as usize) as u32,
)?;
// Always keep the base buffer equals to current if not doing partial refresh.
if self.refresh == RefreshLUT::FULL {
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.command(spi, Command::WRITE_RAM_RED)?;
self.interface.data_x_times(
spi,
color,
buffer_len(WIDTH as usize, HEIGHT as usize) as u32,
)?;
}
Ok(())
}
fn set_background_color(&mut self, background_color: Color) {
self.background_color = background_color;
}
fn background_color(&self) -> &Color {
&self.background_color
}
fn width(&self) -> u32 {
WIDTH
}
fn height(&self) -> u32 {
HEIGHT
}
fn set_lut(
&mut self,
spi: &mut SPI,
refresh_rate: Option<RefreshLUT>,
) -> Result<(), SPI::Error> {
let buffer = match refresh_rate {
Some(RefreshLUT::FULL) | None => &LUT_FULL_UPDATE,
Some(RefreshLUT::QUICK) => &LUT_PARTIAL_UPDATE,
};
self.cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST> EPD2in13<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
/// When using partial refresh, the controller uses the provided buffer for
/// comparison with new buffer.
pub fn set_partial_base_buffer(
&mut self,
spi: &mut SPI,
buffer: &[u8],
) -> Result<(), SPI::Error> {
assert!(buffer_len(WIDTH as usize, HEIGHT as usize) == buffer.len());
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?;
Ok(())
}
/// Selects which sleep mode will be used when triggering the deep sleep.
pub fn set_deep_sleep_mode(&mut self, mode: DeepSleepMode) {
self.sleep_mode = mode;
}
/// Sets the refresh mode. When changing mode, the screen will be
/// re-initialized accordingly.
pub fn set_refresh<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
refresh: RefreshLUT,
) -> Result<(), SPI::Error> {
if self.refresh != refresh {
self.refresh = refresh;
self.init(spi, delay)?;
}
Ok(())
}
fn set_gate_scan_start_position(
&mut self,
spi: &mut SPI,
start: u16,
) -> Result<(), SPI::Error> {
assert!(start <= 295);
self.cmd_with_data(
spi,
Command::GATE_SCAN_START_POSITION,
&[(start & 0xFF) as u8, ((start >> 8) & 0x1) as u8],
)
}
fn set_border_waveform(
&mut self,
spi: &mut SPI,
borderwaveform: BorderWaveForm,
) -> Result<(), SPI::Error> {
self.cmd_with_data(
spi,
Command::BORDER_WAVEFORM_CONTROL,
&[borderwaveform.to_u8()],
)
}
fn set_vcom_register(&mut self, spi: &mut SPI, vcom: VCOM) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::WRITE_VCOM_REGISTER, &[vcom.0])
}
fn set_gate_driving_voltage(
&mut self,
spi: &mut SPI,
voltage: GateDrivingVoltage,
) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::GATE_DRIVING_VOLTAGE_CTRL, &[voltage.0])
}
fn set_dummy_line_period(
&mut self,
spi: &mut SPI,
number_of_lines: u8,
) -> Result<(), SPI::Error> {
assert!(number_of_lines <= 127);
self.cmd_with_data(spi, Command::SET_DUMMY_LINE_PERIOD, &[number_of_lines])
}
fn set_gate_line_width(&mut self, spi: &mut SPI, width: u8) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::SET_GATE_LINE_WIDTH, &[width & 0x0F])
}
/// Sets the source driving voltage value
fn set_source_driving_voltage(
&mut self,
spi: &mut SPI,
vsh1: SourceDrivingVoltage,
vsh2: SourceDrivingVoltage,
vsl: SourceDrivingVoltage,
) -> Result<(), SPI::Error> {
self.cmd_with_data(
spi,
Command::SOURCE_DRIVING_VOLTAGE_CTRL,
&[vsh1.0, vsh2.0, vsl.0],
)
}
/// Prepare the actions that the next master activation command will
/// trigger.
fn set_display_update_control_2(
&mut self,
spi: &mut SPI,
value: DisplayUpdateControl2,
) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::DISPLAY_UPDATE_CONTROL_2, &[value.0])
}
/// Triggers the deep sleep mode
fn set_sleep_mode(&mut self, spi: &mut SPI, mode: DeepSleepMode) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::DEEP_SLEEP_MODE, &[mode as u8])
}
fn set_driver_output(&mut self, spi: &mut SPI, output: DriverOutput) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::DRIVER_OUTPUT_CONTROL, &output.to_bytes())
}
/// Sets the data entry mode (ie. how X and Y positions changes when writing
/// data to RAM)
fn set_data_entry_mode(
&mut self,
spi: &mut SPI,
counter_incr_mode: DataEntryModeIncr,
counter_direction: DataEntryModeDir,
) -> Result<(), SPI::Error> {
let mode = counter_incr_mode as u8 | counter_direction as u8;
self.cmd_with_data(spi, Command::DATA_ENTRY_MODE_SETTING, &[mode])
}
/// Sets both X and Y pixels ranges
fn set_ram_area(
&mut self,
spi: &mut SPI,
start_x: u32,
start_y: u32,
end_x: u32,
end_y: u32,
) -> Result<(), SPI::Error> {
self.cmd_with_data(
spi,
Command::SET_RAM_X_ADDRESS_START_END_POSITION,
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
)?;
self.cmd_with_data(
spi,
Command::SET_RAM_Y_ADDRESS_START_END_POSITION,
&[
start_y as u8,
(start_y >> 8) as u8,
end_y as u8,
(end_y >> 8) as u8,
],
)
}
/// Sets both X and Y pixels counters when writing data to RAM
fn set_ram_address_counters(
&mut self,
spi: &mut SPI,
x: u32,
y: u32,
) -> Result<(), SPI::Error> {
self.wait_until_idle();
self.cmd_with_data(spi, Command::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?;
self.cmd_with_data(
spi,
Command::SET_RAM_Y_ADDRESS_COUNTER,
&[y as u8, (y >> 8) as u8],
)?;
Ok(())
}
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
self.interface.cmd(spi, command)
}
fn cmd_with_data(
&mut self,
spi: &mut SPI,
command: Command,
data: &[u8],
) -> Result<(), SPI::Error> {
self.interface.cmd_with_data(spi, command, data)
}
fn wait_until_idle(&mut self) {
self.interface.wait_until_idle(IS_BUSY_LOW)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 122);
assert_eq!(HEIGHT, 250);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

1
src/lib.rs

@ -74,6 +74,7 @@ mod interface;
pub mod epd1in54;
pub mod epd1in54b;
pub mod epd2in13_v2;
pub mod epd2in9;
pub mod epd2in9bc;
pub mod epd4in2;

2
src/traits.rs

@ -12,7 +12,7 @@ pub(crate) trait Command {
}
/// Seperates the different LUT for the Display Refresh process
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum RefreshLUT {
/// The "normal" full Lookuptable for the Refresh-Sequence
FULL,

Loading…
Cancel
Save