commit
221a801340
@ -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