Browse Source
This initial support covers both the full (slow) update of the display and the partial (fast) update.master
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