Browse Source

Merge pull request #36 from asaaki/epd7in5_v2

Add epd7in5_v2 support
v0.4
Chris 3 years ago committed by GitHub
parent
commit
8829f9a315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      Cargo.toml
  2. 19
      README.md
  3. 15
      examples/epd7in5_v2_full/Cargo.toml
  4. 188
      examples/epd7in5_v2_full/src/main.rs
  5. 2
      src/epd1in54/mod.rs
  6. 2
      src/epd1in54b/mod.rs
  7. 2
      src/epd2in9/mod.rs
  8. 2
      src/epd4in2/mod.rs
  9. 2
      src/epd7in5/mod.rs
  10. 154
      src/epd7in5_v2/command.rs
  11. 151
      src/epd7in5_v2/graphics.rs
  12. 280
      src/epd7in5_v2/mod.rs
  13. 21
      src/interface.rs
  14. 6
      src/lib.rs
  15. 14
      src/traits.rs

6
Cargo.toml

@ -17,9 +17,8 @@ edition = "2018"
# `branch` is optional; default is `master`
travis-ci = { repository = "caemor/epd-waveshare" }
[features]
default = ["epd1in54", "epd1in54b", "epd2in9", "epd4in2", "epd7in5", "graphics"]
default = ["epd1in54", "epd1in54b", "epd2in9", "epd4in2", "epd7in5", "epd7in5_v2", "graphics"]
graphics = ["embedded-graphics"]
epd1in54 = []
@ -27,6 +26,7 @@ epd1in54b = []
epd2in9 = []
epd4in2 = []
epd7in5 = []
epd7in5_v2 = []
# offers an alternative fast full lut for type_a displays, but the refresh isnt as clean looking
type_a_alternative_faster_lut = []
@ -36,4 +36,4 @@ version = "0.5.2"
[dependencies.embedded-hal]
features = ["unproven"]
version = "0.2.1"
version = "0.2.3"

19
README.md

@ -40,22 +40,29 @@ epd.display_frame(&mut spi)?;
| Device (with Link) | Colors | Flexible Display | Partial Refresh | Supported | Tested |
| :---: | --- | :---: | :---: | :---: | :---: |
| [7.5 Inch B/W V2 (A)](https://www.waveshare.com/product/7.5inch-e-paper-hat.htm) [[1](#1-75-inch-bw-v2-a)] | Black, White | ✕ | ✕ | ✔ | ✔ |
| [7.5 Inch B/W (A)](https://www.waveshare.com/product/7.5inch-e-paper-hat.htm) | Black, White | ✕ | ✕ | ✔ | ✔ |
| [4.2 Inch B/W (A)](https://www.waveshare.com/product/4.2inch-e-paper-module.htm) | Black, White | ✕ | Not officially [[1](#42-inch-e-ink-blackwhite)] | ✔ | ✔ |
| [4.2 Inch B/W (A)](https://www.waveshare.com/product/4.2inch-e-paper-module.htm) | Black, White | ✕ | Not officially [[2](#2-42-inch-e-ink-blackwhite---partial-refresh)] | ✔ | ✔ |
| [1.54 Inch B/W (A)](https://www.waveshare.com/1.54inch-e-Paper-Module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ |
| [2.13 Inch B/W (A)](https://www.waveshare.com/product/2.13inch-e-paper-hat.htm) | Black, White | ✕ | ✔ | | |
| [2.9 Inch B/W (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ [[2](#2-29-inch-e-ink-blackwhite---tests)] |
| [2.9 Inch B/W (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ [[3](#3-29-inch-e-ink-blackwhite---tests)] |
| [1.54 Inch B/W/R (B)](https://www.waveshare.com/product/modules/oleds-lcds/e-paper/1.54inch-e-paper-module-b.htm) | Black, White, Red | ✕ | ✕ | ✔ | ✔ |
### [1]: 7.5 Inch B/W V2 (A)
### [1]: 4.2 Inch E-Ink Black/White - Partial Refresh
Since November 2019 Waveshare sells their updated version of these displays.
They should have a "V2" marking sticker on the backside of the panel.
Out of the Box the original driver from Waveshare only supports full updates.
Use `epd7in5_v2` instead of `epd7in5`, because the protocol changed.
### [2]: 4.2 Inch E-Ink Black/White - Partial Refresh
Out of the Box the original driver from Waveshare only supports full updates.
That means: Be careful with the quick refresh updates: <br>
It's possible with this driver but might lead to ghosting / burn-in effects therefore it's hidden behind a feature.
### [2]: 2.9 Inch E-Ink Black/White - Tests
### [3]: 2.9 Inch E-Ink Black/White - Tests
Since my 2.9 Inch Display has some blurring issues I am not absolutly sure if everything was working correctly as it should :-)
@ -75,7 +82,7 @@ Since my 2.9 Inch Display has some blurring issues I am not absolutly sure if ev
### Display Configs
There are two types of Display Configurations used in Wavedshare EPDs, which also needs to be set on the "new" E-Paper Driver HAT.
They are also called A and B, but you shouldn't get confused and mix it with the Type A,B,C and D of the various Displays, which just describe different types (colored variants) or new versions. In the Display Config the seperation is most likely due to included fast partial refresh of the displays. In a Tabular form:
They are also called A and B, but you shouldn't get confused and mix it with the Type A,B,C and D of the various Displays, which just describe different types (colored variants) or new versions. In the Display Config the seperation is most likely due to included fast partial refresh of the displays. In a Tabular form:
| Type A | Tybe B |
| :---: | :---: |

15
examples/epd7in5_v2_full/Cargo.toml

@ -0,0 +1,15 @@
[package]
name = "embedded_linux_eink_example"
version = "0.1.0"
authors = [
"Christoph Groß <christoph-gross@mailbox.org>",
"Jack Grigg <thestr4d@gmail.com>",
"Christoph Grabo <asaaki@mannaz.cc>",
]
edition = "2018"
[dependencies]
embedded-graphics = "0.5.2"
embedded-hal = { version = "0.2.3", features = ["unproven"] }
epd-waveshare = { path = "../../", default-features = false, features = ["epd7in5_v2", "graphics"]}
linux-embedded-hal = "0.3.0"

188
examples/epd7in5_v2_full/src/main.rs

@ -0,0 +1,188 @@
#![deny(warnings)]
use embedded_graphics::{
coord::Coord,
fonts::{Font12x16, Font6x8},
prelude::*,
primitives::{Circle, Line},
Drawing,
};
use embedded_hal::prelude::*;
use epd_waveshare::{
epd7in5_v2::{Display7in5, EPD7in5},
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() {
if let Err(e) = run() {
eprintln!("Program exited early with error: {}", e);
}
}
fn run() -> 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(8);
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(24);
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
let dc = Pin::new(25);
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(17);
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 epd7in5 =
EPD7in5::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error");
println!("Test all the rotations");
let mut display = Display7in5::default();
display.set_rotation(DisplayRotation::Rotate0);
display.draw(
Font6x8::render_str("Rotate 0!")
.stroke(Some(Color::Black))
.fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Font6x8::render_str("Rotate 90!")
.stroke(Some(Color::Black))
.fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Font6x8::render_str("Rotate 180!")
.stroke(Some(Color::Black))
.fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Font6x8::render_str("Rotate 270!")
.stroke(Some(Color::Black))
.fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
epd7in5.update_frame(&mut spi, &display.buffer()).unwrap();
epd7in5
.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
display.draw(
Circle::new(Coord::new(64, 64), 64)
.stroke(Some(Color::Black))
.into_iter(),
);
display.draw(
Line::new(Coord::new(64, 64), Coord::new(0, 64))
.stroke(Some(Color::Black))
.into_iter(),
);
display.draw(
Line::new(Coord::new(64, 64), Coord::new(80, 80))
.stroke(Some(Color::Black))
.into_iter(),
);
// draw white on black background
display.draw(
Font6x8::render_str("It's working-WoB!")
// Using Style here
.style(Style {
fill_color: Some(Color::Black),
stroke_color: Some(Color::White),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(175, 250))
.into_iter(),
);
// use bigger/different font
display.draw(
Font12x16::render_str("It's working-BoW!")
// Using Style here
.style(Style {
fill_color: Some(Color::White),
stroke_color: Some(Color::Black),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(50, 200))
.into_iter(),
);
// a moving `Hello World!`
let limit = 10;
for i in 0..limit {
println!("Moving Hello World. Loop {} from {}", (i + 1), limit);
display.draw(
Font6x8::render_str(" Hello World! ")
.style(Style {
fill_color: Some(Color::White),
stroke_color: Some(Color::Black),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(5 + i * 12, 50))
.into_iter(),
);
epd7in5.update_frame(&mut spi, &display.buffer()).unwrap();
epd7in5
.display_frame(&mut spi)
.expect("display frame new graphics");
delay.delay_ms(1_000u16);
}
println!("Finished tests - going to sleep");
epd7in5.sleep(&mut spi)
}

2
src/epd1in54/mod.rs

@ -41,7 +41,7 @@ const IS_BUSY_LOW: bool = false;
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use crate::type_a::{

2
src/epd1in54b/mod.rs

@ -2,7 +2,7 @@
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use crate::interface::DisplayInterface;

2
src/epd2in9/mod.rs

@ -42,7 +42,7 @@ const IS_BUSY_LOW: bool = false;
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use crate::type_a::{

2
src/epd4in2/mod.rs

@ -48,7 +48,7 @@
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use crate::interface::DisplayInterface;

2
src/epd7in5/mod.rs

@ -8,7 +8,7 @@
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::v1::{InputPin, OutputPin},
digital::v2::{InputPin, OutputPin},
};
use crate::color::Color;

154
src/epd7in5_v2/command.rs

@ -0,0 +1,154 @@
//! SPI Commands for the Waveshare 7.5" E-Ink Display
use crate::traits;
/// EPD7in5 commands
///
/// Should rarely (never?) be needed directly.
///
/// For more infos about the addresses and what they are doing look into the PDFs.
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub(crate) enum Command {
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
/// direction, booster switch, soft reset.
PANEL_SETTING = 0x00,
/// Selecting internal and external power
POWER_SETTING = 0x01,
/// After the Power Off command, the driver will power off following the Power Off
/// Sequence; BUSY signal will become "0". This command will turn off charge pump,
/// T-con, source driver, gate driver, VCOM, and temperature sensor, but register
/// data will be kept until VDD becomes OFF. Source Driver output and Vcom will remain
/// as previous condition, which may have 2 conditions: 0V or floating.
POWER_OFF = 0x02,
/// Setting Power OFF sequence
POWER_OFF_SEQUENCE_SETTING = 0x03,
/// Turning On the Power
///
/// After the Power ON command, the driver will power on following the Power ON
/// sequence. Once complete, the BUSY signal will become "1".
POWER_ON = 0x04,
/// Starting data transmission
BOOSTER_SOFT_START = 0x06,
/// This command makes the chip enter the deep-sleep mode to save power.
///
/// The deep sleep mode would return to stand-by by hardware reset.
///
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
DEEP_SLEEP = 0x07,
/// This command starts transmitting data and write them into SRAM. To complete data
/// transmission, command DSP (Data Stop) must be issued. Then the chip will start to
/// send data/VCOM for panel.
///
/// BLACK/WHITE or OLD_DATA
DATA_START_TRANSMISSION_1 = 0x10,
/// To stop data transmission, this command must be issued to check the `data_flag`.
///
/// After this command, BUSY signal will become "0" until the display update is
/// finished.
DATA_STOP = 0x11,
/// After this command is issued, driver will refresh display (data/VCOM) according to
/// SRAM data and LUT.
///
/// After Display Refresh command, BUSY signal will become "0" until the display
/// update is finished.
DISPLAY_REFRESH = 0x12,
/// RED or NEW_DATA
DATA_START_TRANSMISSION_2 = 0x13,
/// Dual SPI - what for?
DUAL_SPI = 0x15,
/// This command builds the VCOM Look-Up Table (LUTC).
LUT_FOR_VCOM = 0x20,
/// This command builds the Black Look-Up Table (LUTB).
LUT_BLACK = 0x21,
/// This command builds the White Look-Up Table (LUTW).
LUT_WHITE = 0x22,
/// This command builds the Gray1 Look-Up Table (LUTG1).
LUT_GRAY_1 = 0x23,
/// This command builds the Gray2 Look-Up Table (LUTG2).
LUT_GRAY_2 = 0x24,
/// This command builds the Red0 Look-Up Table (LUTR0).
LUT_RED_0 = 0x25,
/// This command builds the Red1 Look-Up Table (LUTR1).
LUT_RED_1 = 0x26,
/// This command builds the Red2 Look-Up Table (LUTR2).
LUT_RED_2 = 0x27,
/// This command builds the Red3 Look-Up Table (LUTR3).
LUT_RED_3 = 0x28,
/// This command builds the XON Look-Up Table (LUTXON).
LUT_XON = 0x29,
/// The command controls the PLL clock frequency.
PLL_CONTROL = 0x30,
/// This command reads the temperature sensed by the temperature sensor.
TEMPERATURE_SENSOR_COMMAND = 0x40,
/// This command selects the Internal or External temperature sensor.
TEMPERATURE_CALIBRATION = 0x41,
/// This command could write data to the external temperature sensor.
TEMPERATURE_SENSOR_WRITE = 0x42,
/// This command could read data from the external temperature sensor.
TEMPERATURE_SENSOR_READ = 0x43,
/// This command indicates the interval of Vcom and data output. When setting the
/// vertical back porch, the total blanking will be kept (20 Hsync).
VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
/// This command indicates the input power condition. Host can read this flag to learn
/// the battery condition.
LOW_POWER_DETECTION = 0x51,
/// This command defines non-overlap period of Gate and Source.
TCON_SETTING = 0x60,
/// This command defines alternative resolution and this setting is of higher priority
/// than the RES\[1:0\] in R00H (PSR).
TCON_RESOLUTION = 0x61,
/// This command defines MCU host direct access external memory mode.
SPI_FLASH_CONTROL = 0x65,
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
REVISION = 0x70,
/// This command reads the IC status.
GET_STATUS = 0x71,
/// This command implements related VCOM sensing setting.
AUTO_MEASUREMENT_VCOM = 0x80,
/// This command gets the VCOM value.
READ_VCOM_VALUE = 0x81,
/// This command sets `VCOM_DC` value.
VCM_DC_SETTING = 0x82,
// /// This is in all the Waveshare controllers for EPD7in5, but it's not documented
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
// FLASH_MODE = 0xE5,
}
impl traits::Command for Command {
/// Returns the address of the command
fn address(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Command as CommandTrait;
#[test]
fn command_addr() {
assert_eq!(Command::PANEL_SETTING.address(), 0x00);
assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12);
}
}

151
src/epd7in5_v2/graphics.rs

@ -0,0 +1,151 @@
use crate::epd7in5_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
use crate::graphics::{Display, DisplayRotation};
use crate::prelude::*;
use embedded_graphics::prelude::*;
/// Full size buffer for use with the 7in5 EPD
///
/// Can also be manually constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
pub struct Display7in5 {
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation,
}
impl Default for Display7in5 {
fn default() -> Self {
Display7in5 {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation::default(),
}
}
}
impl Drawing<Color> for Display7in5 {
fn draw<T>(&mut self, item_pixels: T)
where
T: IntoIterator<Item = Pixel<Color>>,
{
self.draw_helper(WIDTH, HEIGHT, item_pixels);
}
}
impl Display for Display7in5 {
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::Color;
use crate::epd7in5_v2;
use crate::graphics::{Display, DisplayRotation};
use embedded_graphics::coord::Coord;
use embedded_graphics::primitives::Line;
// test buffer length
#[test]
fn graphics_size() {
let display = Display7in5::default();
assert_eq!(display.buffer().len(), 48000);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let display = Display7in5::default();
for &byte in display.buffer() {
assert_eq!(byte, epd7in5_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_0() {
let mut display = Display7in5::default();
display.draw(
Line::new(Coord::new(0, 0), Coord::new(7, 0))
.stroke(Some(Color::Black))
.into_iter(),
);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd7in5_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_90() {
let mut display = Display7in5::default();
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Line::new(Coord::new(0, 792), Coord::new(0, 799))
.stroke(Some(Color::Black))
.into_iter(),
);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd7in5_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_180() {
let mut display = Display7in5::default();
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Line::new(Coord::new(792, 479), Coord::new(799, 479))
.stroke(Some(Color::Black))
.into_iter(),
);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd7in5_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_270() {
let mut display = Display7in5::default();
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Line::new(Coord::new(479, 0), Coord::new(479, 7))
.stroke(Some(Color::Black))
.into_iter(),
);
let buffer = display.buffer();
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(byte, epd7in5_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
}

280
src/epd7in5_v2/mod.rs

@ -0,0 +1,280 @@
//! A simple Driver for the Waveshare 7.5" E-Ink Display (V2) via SPI
//!
//! # References
//!
//! - [Datasheet](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT)
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/702def0/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_7in5_V2.c)
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/702def0/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5_V2.py)
//!
//! Important note for V2:
//! Revision V2 has been released on 2019.11, the resolution is upgraded to 800×480, from 640×384 of V1.
//! The hardware and interface of V2 are compatible with V1, however, the related software should be updated.
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::v2::{InputPin, OutputPin},
};
use crate::color::Color;
use crate::interface::DisplayInterface;
use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay, WaveshareDisplayExt};
pub(crate) mod command;
use self::command::Command;
#[cfg(feature = "graphics")]
mod graphics;
#[cfg(feature = "graphics")]
pub use self::graphics::Display7in5;
pub const WIDTH: u32 = 800;
pub const HEIGHT: u32 = 480;
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = true;
/// EPD7in5 (V2) driver
///
pub struct EPD7in5<SPI, CS, BUSY, DC, RST> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
/// Background Color
color: Color,
}
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
for EPD7in5<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> {
// Reset the device
self.interface.reset(delay);
// V2 procedure as described here:
// https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5bc_V2.py
// and as per specs:
// https://www.waveshare.com/w/upload/6/60/7.5inch_e-Paper_V2_Specification.pdf
self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x27, 0x17])?;
self.cmd_with_data(spi, Command::POWER_SETTING, &[0x07, 0x17, 0x3F, 0x3F])?;
self.command(spi, Command::POWER_ON)?;
self.wait_until_idle();
self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x1F])?;
self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x06])?;
self.cmd_with_data(spi, Command::TCON_RESOLUTION, &[0x03, 0x20, 0x01, 0xE0])?;
self.cmd_with_data(spi, Command::DUAL_SPI, &[0x00])?;
self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?;
self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x10, 0x07])?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
for EPD7in5<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
/// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC
///
/// This already initialises the device. That means [init()] isn't needed
/// directly afterwards.
///
/// [init()]: InternalWiAdditions::init
///
/// # Example
///
/// ```rust,ignore
/// //buffer = some image data;
///
/// let mut epd7in5 = EPD7in5::new(spi, cs, busy, dc, rst, delay);
///
/// epd7in5.update_and_display_frame(&mut spi, &buffer);
///
/// epd7in5.sleep();
/// ```
fn new<DELAY: DelayMs<u8>>(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
let interface = DisplayInterface::new(cs, busy, dc, rst);
let color = DEFAULT_BACKGROUND_COLOR;
let mut epd = EPD7in5 { interface, color };
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.command(spi, Command::POWER_OFF)?;
self.wait_until_idle();
self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?;
Ok(())
}
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
self.command(spi, Command::DATA_START_TRANSMISSION_2)?;
self.send_data(spi, buffer)?;
self.wait_until_idle();
Ok(())
}
fn update_partial_frame(
&mut self,
_spi: &mut SPI,
_buffer: &[u8],
_x: u32,
_y: u32,
_width: u32,
_height: u32,
) -> Result<(), SPI::Error> {
unimplemented!();
}
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
self.command(spi, Command::DISPLAY_REFRESH)?;
self.wait_until_idle();
Ok(())
}
fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
self.send_resolution(spi)?;
self.command(spi, Command::DATA_START_TRANSMISSION_1)?;
self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?;
self.command(spi, Command::DATA_START_TRANSMISSION_2)?;
self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?;
self.command(spi, Command::DISPLAY_REFRESH)?;
self.wait_until_idle();
Ok(())
}
fn set_background_color(&mut self, color: Color) {
self.color = color;
}
fn background_color(&self) -> &Color {
&self.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> {
unimplemented!();
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplayExt<SPI, CS, BUSY, DC, RST>
for EPD7in5<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
self.command(spi, Command::DATA_START_TRANSMISSION_2)?;
for b in buffer {
self.send_data(spi, &[255 - b])?;
}
self.command(spi, Command::DISPLAY_REFRESH)?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST> EPD7in5<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
self.interface.cmd(spi, command)
}
fn send_data(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
self.interface.data(spi, data)
}
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)
}
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
let w = self.width();
let h = self.height();
self.command(spi, Command::TCON_RESOLUTION)?;
self.send_data(spi, &[(w >> 8) as u8])?;
self.send_data(spi, &[w as u8])?;
self.send_data(spi, &[(h >> 8) as u8])?;
self.send_data(spi, &[h as u8])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 800);
assert_eq!(HEIGHT, 480);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

21
src/interface.rs

@ -2,7 +2,7 @@ use crate::traits::Command;
use core::marker::PhantomData;
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
/// The Connection Interface of all (?) Waveshare EPD-Devices
@ -43,7 +43,7 @@ where
/// Enables direct interaction with the device with the help of [data()](DisplayInterface::data())
pub(crate) fn cmd<T: Command>(&mut self, spi: &mut SPI, command: T) -> Result<(), SPI::Error> {
// low for commands
self.dc.set_low();
let _ = self.dc.set_low();
// Transfer the command over spi
self.write(spi, &[command.address()])
@ -54,7 +54,7 @@ where
/// Enables direct interaction with the device with the help of [command()](EPD4in2::command())
pub(crate) fn data(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
// high for data
self.dc.set_high();
let _ = self.dc.set_high();
// Transfer data (u8-array) over spi
self.write(spi, data)
@ -83,7 +83,7 @@ where
repetitions: u32,
) -> Result<(), SPI::Error> {
// high for data
self.dc.set_high();
let _ = self.dc.set_high();
// Transfer data (u8) over spi
for _ in 0..repetitions {
self.write(spi, &[val])?;
@ -94,7 +94,7 @@ where
// spi write helper/abstraction function
fn write(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
// activate spi with cs low
self.cs.set_low();
let _ = self.cs.set_low();
// transfer spi data
// Be careful!! Linux has a default limit of 4096 bytes per spi transfer
@ -108,7 +108,7 @@ where
}
// deativate spi with cs high
self.cs.set_high();
let _ = self.cs.set_high();
Ok(())
}
@ -138,7 +138,7 @@ where
}
/// Checks if device is still busy
///
///
/// This is normally handled by the more complicated commands themselves,
/// but in the case you send data and commands directly you might need to check
/// if the device is still busy
@ -151,7 +151,8 @@ where
/// Most likely there was a mistake with the 2in9 busy connection
/// //TODO: use the #cfg feature to make this compile the right way for the certain types
pub(crate) fn is_busy(&self, is_busy_low: bool) -> bool {
(is_busy_low && self.busy.is_low()) || (!is_busy_low && self.busy.is_high())
(is_busy_low && self.busy.is_low().unwrap_or(false))
|| (!is_busy_low && self.busy.is_high().unwrap_or(false))
}
/// Resets the device.
@ -160,10 +161,10 @@ where
///
/// TODO: Takes at least 400ms of delay alone, can it be shortened?
pub(crate) fn reset<DELAY: DelayMs<u8>>(&mut self, delay: &mut DELAY) {
self.rst.set_low();
let _ = self.rst.set_low();
//TODO: why 200ms? (besides being in the arduino version)
delay.delay_ms(200);
self.rst.set_high();
let _ = self.rst.set_high();
//TODO: same as 3 lines above
delay.delay_ms(200);
}

6
src/lib.rs

@ -66,6 +66,8 @@ mod interface;
#[cfg(feature = "epd7in5")]
pub mod epd7in5;
#[cfg(feature = "epd7in5_v2")]
pub mod epd7in5_v2;
#[cfg(feature = "epd4in2")]
pub mod epd4in2;
@ -85,6 +87,10 @@ pub(crate) mod type_a;
pub mod prelude {
pub use crate::color::Color;
pub use crate::traits::{RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay};
#[cfg(feature = "epd7in5_v2")]
pub use crate::traits::WaveshareDisplayExt;
pub use crate::SPI_MODE;
#[cfg(feature = "graphics")]

14
src/traits.rs

@ -2,7 +2,7 @@ use crate::color::Color;
use core::marker::Sized;
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
/// All commands need to have this trait which gives the address of the command
@ -174,3 +174,15 @@ where
/// if the device is still busy
fn is_busy(&self) -> bool;
}
/// Tiny optional extension trait
pub trait WaveshareDisplayExt<SPI, CS, BUSY, DC, RST>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
{
// provide a combined update&display and save some time (skipping a busy check in between)
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error>;
}

Loading…
Cancel
Save