Browse Source
This initial support covers both the full (slow) update of the display and the partial (fast) update.embedded-hal-1.0
8 changed files with 1226 additions and 1 deletions
@ -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); |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
|
} |
||||||
@ -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
|
||||||
|
]; |
||||||
@ -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()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue