Waveshare
This commit is contained in:
parent
b76727a01e
commit
e2d322c9eb
16
epd-waveshare/.gitignore
vendored
Normal file
16
epd-waveshare/.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Generated files
|
||||||
|
**/target
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
|
||||||
|
# The library shouldn't decide about the exact versions of
|
||||||
|
# its dependencies, but let the downstream crate decide.
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode/*
|
||||||
|
|
||||||
|
# intellij/clion
|
||||||
|
.idea/
|
62
epd-waveshare/Cargo.toml
Normal file
62
epd-waveshare/Cargo.toml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Christoph Groß <caemor@mailbox.org>"]
|
||||||
|
categories = ["embedded", "hardware-support", "no-std"]
|
||||||
|
description = "An embedded-hal based driver for ePaper displays from Waveshare formerly published as eink-waveshare-rs"
|
||||||
|
documentation = "https://docs.rs/epd-waveshare"
|
||||||
|
homepage = "https://github.com/caemor/epd-waveshare"
|
||||||
|
keywords = ["ePaper", "Display", "epd", "eink"]
|
||||||
|
license = "ISC"
|
||||||
|
name = "epd-waveshare"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/Caemor/epd-waveshare.git"
|
||||||
|
version = "0.5.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
# travis-ci = { repository = "caemor/epd-waveshare" }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embedded-graphics-core = { version = "0.4", optional = true }
|
||||||
|
embedded-hal = "1.0.0"
|
||||||
|
bit_field = "0.10.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
embedded-graphics = "0.8"
|
||||||
|
embedded-hal-mock = { version = "=0.10.0", default-features = false, features = [
|
||||||
|
"eh1",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
|
linux-embedded-hal = "0.4.0"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "epd1in54_no_graphics"
|
||||||
|
required-features = ["linux-dev"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "epd2in13_v2"
|
||||||
|
required-features = ["linux-dev"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "epd2in13bc"
|
||||||
|
required-features = ["linux-dev"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "epd4in2_variable_size"
|
||||||
|
required-features = ["linux-dev"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "epd4in2"
|
||||||
|
required-features = ["linux-dev"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Remove the linux-dev feature to build the tests on non unix systems
|
||||||
|
default = ["graphics", "linux-dev", "epd2in13_v3"]
|
||||||
|
|
||||||
|
graphics = ["embedded-graphics-core"]
|
||||||
|
epd2in13_v2 = []
|
||||||
|
epd2in13_v3 = []
|
||||||
|
linux-dev = []
|
||||||
|
|
||||||
|
# Offers an alternative fast full lut for type_a displays, but the refreshed screen isnt as clean looking
|
||||||
|
type_a_alternative_faster_lut = []
|
15
epd-waveshare/License.md
Normal file
15
epd-waveshare/License.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2018, Christoph Groß, morituri2@mailbox.org
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
436
epd-waveshare/src/color.rs
Normal file
436
epd-waveshare/src/color.rs
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
//! B/W Color for EPDs
|
||||||
|
//!
|
||||||
|
//! EPD representation of multicolor with separate buffers
|
||||||
|
//! for each bit makes it hard to properly represent colors here
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
use embedded_graphics_core::pixelcolor::PixelColor;
|
||||||
|
|
||||||
|
/// When trying to parse u8 to one of the color types
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct OutOfColorRangeParseError(u8);
|
||||||
|
impl core::fmt::Display for OutOfColorRangeParseError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
write!(f, "Outside of possible Color Range: {}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutOfColorRangeParseError {
|
||||||
|
fn _new(size: u8) -> OutOfColorRangeParseError {
|
||||||
|
OutOfColorRangeParseError(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only for the Black/White-Displays
|
||||||
|
// TODO : 'color' is not a good name for black and white, rename it to BiColor/BWColor ?
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Color {
|
||||||
|
/// Black color
|
||||||
|
Black,
|
||||||
|
/// White color
|
||||||
|
White,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only for the Black/White/Color-Displays
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum TriColor {
|
||||||
|
/// Black color
|
||||||
|
Black,
|
||||||
|
/// White color
|
||||||
|
White,
|
||||||
|
/// Chromatic color
|
||||||
|
Chromatic,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For the 7 Color Displays
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum OctColor {
|
||||||
|
/// Black Color
|
||||||
|
Black = 0x00,
|
||||||
|
/// White Color
|
||||||
|
White = 0x01,
|
||||||
|
/// Green Color
|
||||||
|
Green = 0x02,
|
||||||
|
/// Blue Color
|
||||||
|
Blue = 0x03,
|
||||||
|
/// Red Color
|
||||||
|
Red = 0x04,
|
||||||
|
/// Yellow Color
|
||||||
|
Yellow = 0x05,
|
||||||
|
/// Orange Color
|
||||||
|
Orange = 0x06,
|
||||||
|
/// HiZ / Clean Color
|
||||||
|
HiZ = 0x07,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Color trait for use in `Display`s
|
||||||
|
pub trait ColorType {
|
||||||
|
/// Number of bit used to represent this color type in a single buffer.
|
||||||
|
/// To get the real number of bits per pixel you should multiply this by `BUFFER_COUNT`
|
||||||
|
const BITS_PER_PIXEL_PER_BUFFER: usize;
|
||||||
|
|
||||||
|
/// Number of buffer used to represent this color type
|
||||||
|
/// splitted buffer like tricolo is 2, otherwise this should be 1.
|
||||||
|
const BUFFER_COUNT: usize;
|
||||||
|
|
||||||
|
/// Return the data used to set a pixel color
|
||||||
|
///
|
||||||
|
/// * bwrbit is used to tell the value of the unused bit when a chromatic
|
||||||
|
/// color is set (TriColor only as for now)
|
||||||
|
/// * pos is the pixel position in the line, used to know which pixels must be set
|
||||||
|
///
|
||||||
|
/// Return values are :
|
||||||
|
/// * .0 is the mask used to exclude this pixel from the byte (eg: 0x7F in BiColor)
|
||||||
|
/// * .1 are the bits used to set the color in the byte (eg: 0x80 in BiColor)
|
||||||
|
/// this is u16 because we set 2 bytes in case of split buffer
|
||||||
|
fn bitmask(&self, bwrbit: bool, pos: u32) -> (u8, u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorType for Color {
|
||||||
|
const BITS_PER_PIXEL_PER_BUFFER: usize = 1;
|
||||||
|
const BUFFER_COUNT: usize = 1;
|
||||||
|
fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) {
|
||||||
|
let bit = 0x80 >> (pos % 8);
|
||||||
|
match self {
|
||||||
|
Color::Black => (!bit, 0u16),
|
||||||
|
Color::White => (!bit, bit as u16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorType for TriColor {
|
||||||
|
const BITS_PER_PIXEL_PER_BUFFER: usize = 1;
|
||||||
|
const BUFFER_COUNT: usize = 2;
|
||||||
|
fn bitmask(&self, bwrbit: bool, pos: u32) -> (u8, u16) {
|
||||||
|
let bit = 0x80 >> (pos % 8);
|
||||||
|
match self {
|
||||||
|
TriColor::Black => (!bit, 0u16),
|
||||||
|
TriColor::White => (!bit, bit as u16),
|
||||||
|
TriColor::Chromatic => (
|
||||||
|
!bit,
|
||||||
|
if bwrbit {
|
||||||
|
(bit as u16) << 8
|
||||||
|
} else {
|
||||||
|
(bit as u16) << 8 | bit as u16
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorType for OctColor {
|
||||||
|
const BITS_PER_PIXEL_PER_BUFFER: usize = 4;
|
||||||
|
const BUFFER_COUNT: usize = 1;
|
||||||
|
fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) {
|
||||||
|
let mask = !(0xF0 >> (pos % 2));
|
||||||
|
let bits = self.get_nibble() as u16;
|
||||||
|
(mask, if pos % 2 == 1 { bits } else { bits << 4 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<BinaryColor> for OctColor {
|
||||||
|
fn from(b: BinaryColor) -> OctColor {
|
||||||
|
match b {
|
||||||
|
BinaryColor::On => OctColor::Black,
|
||||||
|
BinaryColor::Off => OctColor::White,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<OctColor> for embedded_graphics_core::pixelcolor::Rgb888 {
|
||||||
|
fn from(b: OctColor) -> Self {
|
||||||
|
let (r, g, b) = b.rgb();
|
||||||
|
Self::new(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<embedded_graphics_core::pixelcolor::Rgb888> for OctColor {
|
||||||
|
fn from(p: embedded_graphics_core::pixelcolor::Rgb888) -> OctColor {
|
||||||
|
use embedded_graphics_core::prelude::RgbColor;
|
||||||
|
let colors = [
|
||||||
|
OctColor::Black,
|
||||||
|
OctColor::White,
|
||||||
|
OctColor::Green,
|
||||||
|
OctColor::Blue,
|
||||||
|
OctColor::Red,
|
||||||
|
OctColor::Yellow,
|
||||||
|
OctColor::Orange,
|
||||||
|
OctColor::HiZ,
|
||||||
|
];
|
||||||
|
// if the user has already mapped to the right color space, it will just be in the list
|
||||||
|
if let Some(found) = colors.iter().find(|c| c.rgb() == (p.r(), p.g(), p.b())) {
|
||||||
|
return *found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not ideal but just pick the nearest color
|
||||||
|
*colors
|
||||||
|
.iter()
|
||||||
|
.map(|c| (c, c.rgb()))
|
||||||
|
.map(|(c, (r, g, b))| {
|
||||||
|
let dist = (i32::from(r) - i32::from(p.r())).pow(2)
|
||||||
|
+ (i32::from(g) - i32::from(p.g())).pow(2)
|
||||||
|
+ (i32::from(b) - i32::from(p.b())).pow(2);
|
||||||
|
(c, dist)
|
||||||
|
})
|
||||||
|
.min_by_key(|(_c, dist)| *dist)
|
||||||
|
.map(|(c, _)| c)
|
||||||
|
.unwrap_or(&OctColor::White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<embedded_graphics_core::pixelcolor::raw::RawU4> for OctColor {
|
||||||
|
fn from(b: embedded_graphics_core::pixelcolor::raw::RawU4) -> Self {
|
||||||
|
use embedded_graphics_core::prelude::RawData;
|
||||||
|
OctColor::from_nibble(b.into_inner()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl PixelColor for OctColor {
|
||||||
|
type Raw = embedded_graphics_core::pixelcolor::raw::RawU4;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OctColor {
|
||||||
|
/// Gets the Nibble representation of the Color as needed by the display
|
||||||
|
pub fn get_nibble(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
/// Converts two colors into a single byte for the Display
|
||||||
|
pub fn colors_byte(a: OctColor, b: OctColor) -> u8 {
|
||||||
|
a.get_nibble() << 4 | b.get_nibble()
|
||||||
|
}
|
||||||
|
|
||||||
|
///Take the nibble (lower 4 bits) and convert to an OctColor if possible
|
||||||
|
pub fn from_nibble(nibble: u8) -> Result<OctColor, OutOfColorRangeParseError> {
|
||||||
|
match nibble & 0xf {
|
||||||
|
0x00 => Ok(OctColor::Black),
|
||||||
|
0x01 => Ok(OctColor::White),
|
||||||
|
0x02 => Ok(OctColor::Green),
|
||||||
|
0x03 => Ok(OctColor::Blue),
|
||||||
|
0x04 => Ok(OctColor::Red),
|
||||||
|
0x05 => Ok(OctColor::Yellow),
|
||||||
|
0x06 => Ok(OctColor::Orange),
|
||||||
|
0x07 => Ok(OctColor::HiZ),
|
||||||
|
e => Err(OutOfColorRangeParseError(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///Split the nibbles of a single byte and convert both to an OctColor if possible
|
||||||
|
pub fn split_byte(byte: u8) -> Result<(OctColor, OctColor), OutOfColorRangeParseError> {
|
||||||
|
let low = OctColor::from_nibble(byte & 0xf)?;
|
||||||
|
let high = OctColor::from_nibble((byte >> 4) & 0xf)?;
|
||||||
|
Ok((high, low))
|
||||||
|
}
|
||||||
|
/// Converts to limited range of RGB values.
|
||||||
|
pub fn rgb(self) -> (u8, u8, u8) {
|
||||||
|
match self {
|
||||||
|
OctColor::White => (0xff, 0xff, 0xff),
|
||||||
|
OctColor::Black => (0x00, 0x00, 0x00),
|
||||||
|
OctColor::Green => (0x00, 0xff, 0x00),
|
||||||
|
OctColor::Blue => (0x00, 0x00, 0xff),
|
||||||
|
OctColor::Red => (0xff, 0x00, 0x00),
|
||||||
|
OctColor::Yellow => (0xff, 0xff, 0x00),
|
||||||
|
OctColor::Orange => (0xff, 0x80, 0x00),
|
||||||
|
OctColor::HiZ => (0x80, 0x80, 0x80), /* looks greyish */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO: Rename get_bit_value to bit() and get_byte_value to byte() ?
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
/// Get the color encoding of the color for one bit
|
||||||
|
pub fn get_bit_value(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Color::White => 1u8,
|
||||||
|
Color::Black => 0u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a full byte of black or white pixels
|
||||||
|
pub fn get_byte_value(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Color::White => 0xff,
|
||||||
|
Color::Black => 0x00,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses from u8 to Color
|
||||||
|
fn from_u8(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
0 => Color::Black,
|
||||||
|
1 => Color::White,
|
||||||
|
e => panic!(
|
||||||
|
"DisplayColor only parses 0 and 1 (Black and White) and not `{}`",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inverse of the given color.
|
||||||
|
///
|
||||||
|
/// Black returns White and White returns Black
|
||||||
|
pub fn inverse(self) -> Color {
|
||||||
|
match self {
|
||||||
|
Color::White => Color::Black,
|
||||||
|
Color::Black => Color::White,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Color {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
Color::from_u8(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl PixelColor for Color {
|
||||||
|
type Raw = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<BinaryColor> for Color {
|
||||||
|
fn from(b: BinaryColor) -> Color {
|
||||||
|
match b {
|
||||||
|
BinaryColor::On => Color::Black,
|
||||||
|
BinaryColor::Off => Color::White,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<embedded_graphics_core::pixelcolor::Rgb888> for Color {
|
||||||
|
fn from(rgb: embedded_graphics_core::pixelcolor::Rgb888) -> Self {
|
||||||
|
use embedded_graphics_core::pixelcolor::RgbColor;
|
||||||
|
if rgb == RgbColor::BLACK {
|
||||||
|
Color::Black
|
||||||
|
} else if rgb == RgbColor::WHITE {
|
||||||
|
Color::White
|
||||||
|
} else {
|
||||||
|
// choose closest color
|
||||||
|
if (rgb.r() as u16 + rgb.g() as u16 + rgb.b() as u16) > 255 * 3 / 2 {
|
||||||
|
Color::White
|
||||||
|
} else {
|
||||||
|
Color::Black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<Color> for embedded_graphics_core::pixelcolor::Rgb888 {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
use embedded_graphics_core::pixelcolor::RgbColor;
|
||||||
|
match color {
|
||||||
|
Color::Black => embedded_graphics_core::pixelcolor::Rgb888::BLACK,
|
||||||
|
Color::White => embedded_graphics_core::pixelcolor::Rgb888::WHITE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriColor {
|
||||||
|
/// Get the color encoding of the color for one bit
|
||||||
|
pub fn get_bit_value(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
TriColor::White => 1u8,
|
||||||
|
TriColor::Black | TriColor::Chromatic => 0u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a full byte of black or white pixels
|
||||||
|
pub fn get_byte_value(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
TriColor::White => 0xff,
|
||||||
|
TriColor::Black | TriColor::Chromatic => 0x00,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl PixelColor for TriColor {
|
||||||
|
type Raw = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<BinaryColor> for TriColor {
|
||||||
|
fn from(b: BinaryColor) -> TriColor {
|
||||||
|
match b {
|
||||||
|
BinaryColor::On => TriColor::Black,
|
||||||
|
BinaryColor::Off => TriColor::White,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<embedded_graphics_core::pixelcolor::Rgb888> for TriColor {
|
||||||
|
fn from(rgb: embedded_graphics_core::pixelcolor::Rgb888) -> Self {
|
||||||
|
use embedded_graphics_core::pixelcolor::RgbColor;
|
||||||
|
if rgb == RgbColor::BLACK {
|
||||||
|
TriColor::Black
|
||||||
|
} else if rgb == RgbColor::WHITE {
|
||||||
|
TriColor::White
|
||||||
|
} else {
|
||||||
|
// there is no good approximation here since we don't know which color is 'chromatic'
|
||||||
|
TriColor::Chromatic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
impl From<TriColor> for embedded_graphics_core::pixelcolor::Rgb888 {
|
||||||
|
fn from(tri_color: TriColor) -> Self {
|
||||||
|
use embedded_graphics_core::pixelcolor::RgbColor;
|
||||||
|
match tri_color {
|
||||||
|
TriColor::Black => embedded_graphics_core::pixelcolor::Rgb888::BLACK,
|
||||||
|
TriColor::White => embedded_graphics_core::pixelcolor::Rgb888::WHITE,
|
||||||
|
// assume chromatic is red
|
||||||
|
TriColor::Chromatic => embedded_graphics_core::pixelcolor::Rgb888::new(255, 0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_u8() {
|
||||||
|
assert_eq!(Color::Black, Color::from(0u8));
|
||||||
|
assert_eq!(Color::White, Color::from(1u8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// test all values aside from 0 and 1 which all should panic
|
||||||
|
#[test]
|
||||||
|
fn from_u8_panic() {
|
||||||
|
for val in 2..=u8::max_value() {
|
||||||
|
extern crate std;
|
||||||
|
let result = std::panic::catch_unwind(|| Color::from(val));
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn u8_conversion_black() {
|
||||||
|
assert_eq!(Color::from(Color::Black.get_bit_value()), Color::Black);
|
||||||
|
assert_eq!(Color::from(0u8).get_bit_value(), 0u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn u8_conversion_white() {
|
||||||
|
assert_eq!(Color::from(Color::White.get_bit_value()), Color::White);
|
||||||
|
assert_eq!(Color::from(1u8).get_bit_value(), 1u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_oct() {
|
||||||
|
let left = OctColor::Red;
|
||||||
|
let right = OctColor::Green;
|
||||||
|
assert_eq!(
|
||||||
|
OctColor::split_byte(OctColor::colors_byte(left, right)),
|
||||||
|
Ok((left, right))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
399
epd-waveshare/src/epd1in54/mod.rs
Normal file
399
epd-waveshare/src/epd1in54/mod.rs
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
//! A simple Driver for the Waveshare 1.54" E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! # Example for the 1.54 in E-Ink Display
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{
|
||||||
|
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyleBuilder},
|
||||||
|
//!};
|
||||||
|
//!use epd_waveshare::{epd1in54::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd1in54::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!let mut display = Display1in54::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing a line
|
||||||
|
//!let style = PrimitiveStyleBuilder::new()
|
||||||
|
//! .stroke_color(Color::Black)
|
||||||
|
//! .stroke_width(1)
|
||||||
|
//! .build();
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||||
|
//! .into_styled(style)
|
||||||
|
//! .draw(&mut display);
|
||||||
|
//!
|
||||||
|
//!// Display updated frame
|
||||||
|
//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 200;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 200;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
//const DPI: u16 = 184;
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::type_a::{
|
||||||
|
command::Command,
|
||||||
|
constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
use crate::traits::{RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
use crate::buffer_len;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 1in54b EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display1in54 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd1in54 driver
|
||||||
|
pub struct Epd1in54<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// SPI
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Color
|
||||||
|
background_color: Color,
|
||||||
|
/// Refresh LUT
|
||||||
|
refresh: RefreshLut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd1in54<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// 3 Databytes:
|
||||||
|
// A[7:0]
|
||||||
|
// 0.. A[8]
|
||||||
|
// 0.. B[2:0]
|
||||||
|
// Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?)
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DriverOutputControl,
|
||||||
|
&[HEIGHT as u8, (HEIGHT >> 8) as u8, 0x00],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 3 Databytes: (and default values from datasheet and arduino)
|
||||||
|
// 1 .. A[6:0] = 0xCF | 0xD7
|
||||||
|
// 1 .. B[6:0] = 0xCE | 0xD6
|
||||||
|
// 1 .. C[6:0] = 0x8D | 0x9D
|
||||||
|
//TODO: test
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStartControl, &[0xD7, 0xD6, 0x9D])?;
|
||||||
|
|
||||||
|
// One Databyte with value 0xA8 for 7V VCOM
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteVcomRegister, &[0xA8])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x1A for 4 dummy lines per gate
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetDummyLinePeriod, &[0x1A])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x08 for 2us per line
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetGateLineWidth, &[0x08])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x03
|
||||||
|
// -> address: x increment, y increment, address counter is updated in x direction
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, None)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
|
||||||
|
let mut epd = Epd1in54 {
|
||||||
|
interface,
|
||||||
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode
|
||||||
|
//TODO: is 0x00 needed here or would 0x01 be even more efficient?
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: update description: last 3 bits will be ignored for width and x_pos
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.set_ram_area(spi, delay, x, y, x + width, y + height)?;
|
||||||
|
self.set_ram_counter(spi, delay, x, y)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// enable clock signal, enable cp, display pattern -> 0xC4 (tested with the arduino version)
|
||||||
|
//TODO: test control_1 or control_2 with default value 0xFF (from the datasheet)
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC4])?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
// MASTER Activation should not be interupted to avoid currption of panel images
|
||||||
|
// therefore a terminate command is send
|
||||||
|
self.interface.cmd(spi, Command::Nop)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
|
||||||
|
// clear the ram with the background color
|
||||||
|
let color = self.background_color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::WriteRam)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, background_color: Color) {
|
||||||
|
self.background_color = background_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Color {
|
||||||
|
&self.background_color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if let Some(refresh_lut) = refresh_rate {
|
||||||
|
self.refresh = refresh_lut;
|
||||||
|
}
|
||||||
|
match self.refresh {
|
||||||
|
RefreshLut::Full => self.set_lut_helper(spi, delay, &LUT_FULL_UPDATE),
|
||||||
|
RefreshLut::Quick => self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd1in54<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
pub(crate) fn use_full_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
// choose full frame/ram
|
||||||
|
self.set_ram_area(spi, delay, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
|
||||||
|
// start from the beginning
|
||||||
|
self.set_ram_counter(spi, delay, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_ram_area(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
start_x: u32,
|
||||||
|
start_y: u32,
|
||||||
|
end_x: u32,
|
||||||
|
end_y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
assert!(start_x < end_x);
|
||||||
|
assert!(start_y < end_y);
|
||||||
|
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamXAddressStartEndPosition,
|
||||||
|
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8] for each - start and end
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressStartEndPosition,
|
||||||
|
&[
|
||||||
|
start_y as u8,
|
||||||
|
(start_y >> 8) as u8,
|
||||||
|
end_y as u8,
|
||||||
|
(end_y >> 8) as u8,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_ram_counter(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8]
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressCounter,
|
||||||
|
&[y as u8, (y >> 8) as u8],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut_helper(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
assert!(buffer.len() == 30);
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 200);
|
||||||
|
assert_eq!(HEIGHT, 200);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
46
epd-waveshare/src/epd1in54_v2/constants.rs
Normal file
46
epd-waveshare/src/epd1in54_v2/constants.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#[rustfmt::skip]
|
||||||
|
// Original Waveforms from Waveshare for 1in54_V2
|
||||||
|
pub(crate) const LUT_FULL_UPDATE: [u8; 159] =[
|
||||||
|
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x8, 0x1, 0x0, 0x8, 0x1, 0x0, 0x2,
|
||||||
|
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0x0, 0x32, 0x20
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_PARTIAL_UPDATE: [u8; 159] =[
|
||||||
|
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x02, 0x17, 0x41, 0xB0, 0x32, 0x28,
|
||||||
|
];
|
383
epd-waveshare/src/epd1in54_v2/mod.rs
Normal file
383
epd-waveshare/src/epd1in54_v2/mod.rs
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
//! A simple Driver for the Waveshare 1.54" E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! GDEH0154D67
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 200;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 200;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::type_a::command::Command;
|
||||||
|
|
||||||
|
mod constants;
|
||||||
|
use crate::epd1in54_v2::constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
use crate::traits::{RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub use crate::epd1in54::Display1in54;
|
||||||
|
|
||||||
|
/// Epd1in54 driver
|
||||||
|
pub struct Epd1in54<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// SPI
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Color
|
||||||
|
background_color: Color,
|
||||||
|
|
||||||
|
/// Refresh LUT
|
||||||
|
refresh: RefreshLut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd1in54<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface.cmd(spi, Command::SwReset)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// 3 Databytes:
|
||||||
|
// A[7:0]
|
||||||
|
// 0.. A[8]
|
||||||
|
// 0.. B[2:0]
|
||||||
|
// Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?)
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DriverOutputControl,
|
||||||
|
&[(HEIGHT - 1) as u8, 0x0, 0x00],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataEntryModeSetting, &[0x3])?;
|
||||||
|
|
||||||
|
self.set_ram_area(spi, delay, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::TemperatureSensorSelection,
|
||||||
|
&[0x80], // 0x80: internal temperature sensor
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::TemperatureSensorControl, &[0xB1, 0x20])?;
|
||||||
|
|
||||||
|
self.set_ram_counter(spi, delay, 0, 0)?;
|
||||||
|
|
||||||
|
//Initialize the lookup table with a refresh waveform
|
||||||
|
self.set_lut(spi, delay, None)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
|
||||||
|
let mut epd = Epd1in54 {
|
||||||
|
interface,
|
||||||
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleepMode, &[0x01])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: update description: last 3 bits will be ignored for width and x_pos
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.set_ram_area(spi, delay, x, y, x + width, y + height)?;
|
||||||
|
self.set_ram_counter(spi, delay, x, y)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
if self.refresh == RefreshLut::Full {
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC7])?;
|
||||||
|
} else if self.refresh == RefreshLut::Quick {
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xCF])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
// MASTER Activation should not be interupted to avoid currption of panel images
|
||||||
|
// therefore a terminate command is send
|
||||||
|
self.interface.cmd(spi, Command::Nop)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
|
||||||
|
// clear the ram with the background color
|
||||||
|
let color = self.background_color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::WriteRam)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||||
|
self.interface.cmd(spi, Command::WriteRam2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, background_color: Color) {
|
||||||
|
self.background_color = background_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Color {
|
||||||
|
&self.background_color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if let Some(refresh_lut) = refresh_rate {
|
||||||
|
self.refresh = refresh_lut;
|
||||||
|
}
|
||||||
|
match self.refresh {
|
||||||
|
RefreshLut::Full => self.set_lut_helper(spi, delay, &LUT_FULL_UPDATE),
|
||||||
|
RefreshLut::Quick => self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Additional configuration required only for partial updates
|
||||||
|
if self.refresh == RefreshLut::Quick {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::WriteOtpSelection,
|
||||||
|
&[0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0],
|
||||||
|
)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BorderWaveformControl, &[0x80])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xc0])?;
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
// MASTER Activation should not be interupted to avoid currption of panel images
|
||||||
|
// therefore a terminate command is send
|
||||||
|
self.interface.cmd(spi, Command::Nop)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd1in54<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
pub(crate) fn use_full_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
// choose full frame/ram
|
||||||
|
self.set_ram_area(spi, delay, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
|
||||||
|
// start from the beginning
|
||||||
|
self.set_ram_counter(spi, delay, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_ram_area(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
start_x: u32,
|
||||||
|
start_y: u32,
|
||||||
|
end_x: u32,
|
||||||
|
end_y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
assert!(start_x < end_x);
|
||||||
|
assert!(start_y < end_y);
|
||||||
|
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamXAddressStartEndPosition,
|
||||||
|
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8] for each - start and end
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressStartEndPosition,
|
||||||
|
&[
|
||||||
|
start_y as u8,
|
||||||
|
(start_y >> 8) as u8,
|
||||||
|
end_y as u8,
|
||||||
|
(end_y >> 8) as u8,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_ram_counter(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8]
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressCounter,
|
||||||
|
&[y as u8, (y >> 8) as u8],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut_helper(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
assert!(buffer.len() == 159);
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegister, &buffer[0..153])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegisterEnd, &[buffer[153]])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::GateDrivingVoltage, &[buffer[154]])?;
|
||||||
|
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SourceDrivingVoltage,
|
||||||
|
&[buffer[155], buffer[156], buffer[157]],
|
||||||
|
)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteVcomRegister, &[buffer[158]])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 200);
|
||||||
|
assert_eq!(HEIGHT, 200);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
40
epd-waveshare/src/epd1in54b/command.rs
Normal file
40
epd-waveshare/src/epd1in54b/command.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//! SPI Commands for the Waveshare 1.54" red E-Ink Display
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
PowerOff = 0x02,
|
||||||
|
PowerOn = 0x04,
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
LutG0 = 0x23,
|
||||||
|
LutG1 = 0x24,
|
||||||
|
LutRedVcom = 0x25,
|
||||||
|
LutRed0 = 0x26,
|
||||||
|
LutRed1 = 0x27,
|
||||||
|
|
||||||
|
PllControl = 0x30,
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
PowerSaving = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
31
epd-waveshare/src/epd1in54b/constants.rs
Normal file
31
epd-waveshare/src/epd1in54b/constants.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
pub(crate) const LUT_VCOM0: &[u8] = &[
|
||||||
|
0x0E, 0x14, 0x01, 0x0A, 0x06, 0x04, 0x0A, 0x0A, 0x0F, 0x03, 0x03, 0x0C, 0x06, 0x0A, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_WHITE_TO_WHITE: &[u8] = &[
|
||||||
|
0x0E, 0x14, 0x01, 0x0A, 0x46, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x86, 0x0A, 0x04,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_BLACK_TO_WHITE: &[u8] = &[
|
||||||
|
0x0E, 0x14, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x4A, 0x04,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_G1: &[u8] = &[
|
||||||
|
0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_G2: &[u8] = &[
|
||||||
|
0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_RED_VCOM: &[u8] = &[
|
||||||
|
0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_RED0: &[u8] = &[
|
||||||
|
0x83, 0x5D, 0x01, 0x81, 0x48, 0x23, 0x77, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) const LUT_RED1: &[u8] = &[
|
||||||
|
0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
379
epd-waveshare/src/epd1in54b/mod.rs
Normal file
379
epd-waveshare/src/epd1in54b/mod.rs
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
//! A simple Driver for the Waveshare 1.54" (B) E-Ink Display via SPI
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
//The Lookup Tables for the Display
|
||||||
|
mod constants;
|
||||||
|
use crate::epd1in54b::constants::*;
|
||||||
|
|
||||||
|
/// Width of epd1in54 in pixels
|
||||||
|
pub const WIDTH: u32 = 200;
|
||||||
|
/// Height of epd1in54 in pixels
|
||||||
|
pub const HEIGHT: u32 = 200;
|
||||||
|
/// Default Background Color (white)
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 1in54b EPD
|
||||||
|
/// TODO this should be a TriColor, but let's keep it as is at first
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display1in54b = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd1in54b driver
|
||||||
|
pub struct Epd1in54b<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// set the power settings
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x00, 0x08, 0x00])?;
|
||||||
|
|
||||||
|
// start the booster
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x07, 0x07, 0x07])?;
|
||||||
|
|
||||||
|
// power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set the panel settings
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0xCF])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x37])?;
|
||||||
|
|
||||||
|
// PLL
|
||||||
|
self.cmd_with_data(spi, Command::PllControl, &[0x39])?;
|
||||||
|
|
||||||
|
// set resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0E])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, None)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
for b in black {
|
||||||
|
let expanded = expand_bits(*b);
|
||||||
|
self.interface.data(spi, &expanded)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data(spi, chromatic)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd1in54b { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcmDcSetting, &[0x00])?; // Vcom to 0V
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerSetting, &[0x02, 0x00, 0x00, 0x00])?; //VG&VS to 0V fast
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
//NOTE: The example code has a 1s delay here
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
for b in buffer {
|
||||||
|
// Two bits per pixel
|
||||||
|
let expanded = expand_bits(*b);
|
||||||
|
self.interface.data(spi, &expanded)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE: Example code has a delay here
|
||||||
|
|
||||||
|
// Clear the read layer
|
||||||
|
let color = self.color.get_byte_value();
|
||||||
|
let nbits = WIDTH * (HEIGHT / 8);
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, nbits)?;
|
||||||
|
|
||||||
|
//NOTE: Example code has a delay here
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||||
|
|
||||||
|
// Clear the black
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
// Uses 2 bits per pixel
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, 2 * (WIDTH / 8 * HEIGHT))?;
|
||||||
|
|
||||||
|
// Clear the red
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutForVcom, LUT_VCOM0)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutWhiteToWhite, LUT_WHITE_TO_WHITE)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutBlackToWhite, LUT_BLACK_TO_WHITE)?;
|
||||||
|
self.interface.cmd_with_data(spi, Command::LutG0, LUT_G1)?;
|
||||||
|
self.interface.cmd_with_data(spi, Command::LutG1, LUT_G2)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutRedVcom, LUT_RED_VCOM)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutRed0, LUT_RED0)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutRed1, LUT_RED1)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd1in54b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::ResolutionSetting)?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[w as u8])?;
|
||||||
|
self.send_data(spi, &[(h >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[h as u8])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_bits(bits: u8) -> [u8; 2] {
|
||||||
|
let mut x = bits as u16;
|
||||||
|
|
||||||
|
x = (x | (x << 4)) & 0x0F0F;
|
||||||
|
x = (x | (x << 2)) & 0x3333;
|
||||||
|
x = (x | (x << 1)) & 0x5555;
|
||||||
|
x = x | (x << 1);
|
||||||
|
|
||||||
|
[(x >> 8) as u8, (x & 0xFF) as u8]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 200);
|
||||||
|
assert_eq!(HEIGHT, 200);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
38
epd-waveshare/src/epd1in54c/command.rs
Normal file
38
epd-waveshare/src/epd1in54c/command.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//! SPI Commands for the Waveshare 1.54" C yellow E-Ink Display
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
PowerOff = 0x02,
|
||||||
|
PowerOn = 0x04,
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
LutWhiteToBlack = 0x23,
|
||||||
|
LutBlackToBlack = 0x24,
|
||||||
|
|
||||||
|
PllControl = 0x30,
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
PowerSaving = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
306
epd-waveshare/src/epd1in54c/mod.rs
Normal file
306
epd-waveshare/src/epd1in54c/mod.rs
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
//! A simple Driver for the Waveshare 1.54" (C) E-Ink Display via SPI
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Width of epd1in54 in pixels
|
||||||
|
pub const WIDTH: u32 = 152;
|
||||||
|
/// Height of epd1in54 in pixels
|
||||||
|
pub const HEIGHT: u32 = 152;
|
||||||
|
/// Default Background Color (white)
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const NUM_DISPLAY_BITS: u32 = WIDTH / 8 * HEIGHT;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 1in54c EPD
|
||||||
|
/// TODO this should be a TriColor, but let's keep it as is at first
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display1in54c = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd1in54c driver
|
||||||
|
pub struct Epd1in54c<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54c<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Based on Reference Program Code from:
|
||||||
|
// https://www.waveshare.com/w/upload/a/ac/1.54inch_e-Paper_Module_C_Specification.pdf
|
||||||
|
// and:
|
||||||
|
// https://github.com/waveshare/e-Paper/blob/master/STM32/STM32-F103ZET6/User/e-Paper/EPD_1in54c.c
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
// start the booster
|
||||||
|
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||||
|
|
||||||
|
// power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set the panel settings
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x0f, 0x0d])?;
|
||||||
|
|
||||||
|
// set resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x77])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54c<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission1, black)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission2, chromatic)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd1in54c<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd1in54c { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xa5])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, buffer)?;
|
||||||
|
|
||||||
|
// Clear the chromatic layer
|
||||||
|
let color = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||||
|
|
||||||
|
// Clear the black
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
// Clear the chromatic
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd1in54c<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::ResolutionSetting)?;
|
||||||
|
|
||||||
|
// | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|
||||||
|
// | HRES[7:3] | 0 | 0 | 0 |
|
||||||
|
self.send_data(spi, &[(w as u8) & 0b1111_1000])?;
|
||||||
|
// | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|
||||||
|
// | - | - | - | - | - | - | - | VRES[8] |
|
||||||
|
self.send_data(spi, &[(w >> 8) as u8])?;
|
||||||
|
// | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|
||||||
|
// | VRES[7:0] |
|
||||||
|
// Specification shows C/D is zero while sending the last byte,
|
||||||
|
// but upstream code does not implement it like that. So for now
|
||||||
|
// we follow upstream code.
|
||||||
|
self.send_data(spi, &[h as u8])
|
||||||
|
}
|
||||||
|
}
|
276
epd-waveshare/src/epd2in13_v2/command.rs
Normal file
276
epd-waveshare/src/epd2in13_v2/command.rs
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
//! 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
DriverOutputControl = 0x01,
|
||||||
|
GateDrivingVoltageCtrl = 0x03,
|
||||||
|
SourceDrivingVoltageCtrl = 0x04,
|
||||||
|
BoosterSoftStartControl = 0x0C,
|
||||||
|
GateScanStartPosition = 0x0F,
|
||||||
|
DeepSleepMode = 0x10,
|
||||||
|
DataEntryModeSetting = 0x11,
|
||||||
|
SwReset = 0x12,
|
||||||
|
HvReadyDetection = 0x14,
|
||||||
|
VciDetection = 0x15,
|
||||||
|
TemperatureSensorControlWrite = 0x1A,
|
||||||
|
TemperatureSensorControlRead = 0x1B,
|
||||||
|
TemperatureSensorExtControlWrite = 0x1C,
|
||||||
|
MasterActivation = 0x20,
|
||||||
|
DisplayUpdateControl1 = 0x21,
|
||||||
|
DisplayUpdateControl2 = 0x22,
|
||||||
|
WriteRam = 0x24,
|
||||||
|
WriteRamRed = 0x26,
|
||||||
|
ReadRam = 0x27,
|
||||||
|
VcomSense = 0x28,
|
||||||
|
VcomSenseDuration = 0x29,
|
||||||
|
ProgramVcomOpt = 0x2A,
|
||||||
|
WriteVcomRegister = 0x2C,
|
||||||
|
OtpRegisterRead = 0x2D,
|
||||||
|
StatusBitRead = 0x2F,
|
||||||
|
ProgramWsOtp = 0x30,
|
||||||
|
LoadWsOtp = 0x31,
|
||||||
|
WriteLutRegister = 0x32,
|
||||||
|
ProgramOtpSelection = 0x36,
|
||||||
|
WriteOtpSelection = 0x37,
|
||||||
|
SetDummyLinePeriod = 0x3A,
|
||||||
|
SetGateLineWidth = 0x3B,
|
||||||
|
BorderWaveformControl = 0x3C,
|
||||||
|
ReadRamOption = 0x41,
|
||||||
|
SetRamXAddressStartEndPosition = 0x44,
|
||||||
|
SetRamYAddressStartEndPosition = 0x45,
|
||||||
|
AutoWriteRedRamRegularPattern = 0x46,
|
||||||
|
AutoWriteBwRamRegularPattern = 0x47,
|
||||||
|
SetRamXAddressCounter = 0x4E,
|
||||||
|
SetRamYAddressCounter = 0x4F,
|
||||||
|
SetAnalogBlockControl = 0x74,
|
||||||
|
SetDigitalBlockControl = 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, clippy::enum_variant_names)]
|
||||||
|
pub(crate) enum DataEntryModeIncr {
|
||||||
|
XDecrYDecr = 0x0,
|
||||||
|
XIncrYDecr = 0x1,
|
||||||
|
XDecrYIncr = 0x2,
|
||||||
|
XIncrYIncr = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) enum DataEntryModeDir {
|
||||||
|
XDir = 0x0,
|
||||||
|
YDir = 0x4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum BorderWaveFormVbd {
|
||||||
|
Gs = 0x0,
|
||||||
|
FixLevel = 0x1,
|
||||||
|
Vcom = 0x2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum BorderWaveFormFixLevel {
|
||||||
|
Vss = 0x0,
|
||||||
|
Vsh1 = 0x1,
|
||||||
|
Vsl = 0x2,
|
||||||
|
Vsh2 = 0x3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[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)]
|
||||||
|
#[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
|
||||||
|
Mode1 = 0x01,
|
||||||
|
|
||||||
|
// Same as MODE_1 but RAM content is not kept
|
||||||
|
Mode2 = 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!((-30..=-2).contains(&self));
|
||||||
|
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!((100..=210).contains(&self) && self % 5 == 0);
|
||||||
|
GateDrivingVoltage(((self - 100) / 5 + 0x03) as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_driving_decivolt(self) -> SourceDrivingVoltage {
|
||||||
|
assert!((24..=88).contains(&self) || (self % 5 == 0 && (90..=180).contains(&self.abs())));
|
||||||
|
|
||||||
|
if (24..=88).contains(&self) {
|
||||||
|
SourceDrivingVoltage(((self - 24) + 0x8E) as u8)
|
||||||
|
} else if (90..=180).contains(&self) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
86
epd-waveshare/src/epd2in13_v2/constants.rs
Normal file
86
epd-waveshare/src/epd2in13_v2/constants.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#[rustfmt::skip]
|
||||||
|
|
||||||
|
#[cfg(feature = "epd2in13_v2")]
|
||||||
|
// 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
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(feature = "epd2in13_v2")]
|
||||||
|
#[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
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(feature = "epd2in13_v3")]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
// Original Waveforms from Waveshare
|
||||||
|
pub(crate) const LUT_PARTIAL_UPDATE: [u8; 159] =[
|
||||||
|
0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x14,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x1,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x1,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
|
||||||
|
0x22,0x17,0x41,0x00,0x32,0x36,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(feature = "epd2in13_v3")]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_FULL_UPDATE: [u8; 159] =[
|
||||||
|
0x80,0x4A,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x40,0x4A,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x80,0x4A,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x40,0x4A,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0xF,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0xF,0x0,0x0,0xF,0x0,0x0,0x2,
|
||||||
|
0xF,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x1,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x0,0x0,0x0,0x0,0x0,0x0,0x0,
|
||||||
|
0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
|
||||||
|
0x22,0x17,0x41,0x0,0x32,0x36
|
||||||
|
];
|
599
epd-waveshare/src/epd2in13_v2/mod.rs
Normal file
599
epd-waveshare/src/epd2in13_v2/mod.rs
Normal file
@ -0,0 +1,599 @@
|
|||||||
|
//! A Driver for the Waveshare 2.13" E-Ink Display (V2 and V3) via SPI
|
||||||
|
//!
|
||||||
|
//! # References V2
|
||||||
|
//!
|
||||||
|
//! - [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)
|
||||||
|
//!
|
||||||
|
//! # References V3
|
||||||
|
//!
|
||||||
|
//! - [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_JetsonNano/c/lib/e-Paper/EPD_2in13_V3.c)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in9b_V3.py)
|
||||||
|
//! - [Controller Datasheet SS1780](http://www.e-paper-display.com/download_detail/downloadsId=682.html)
|
||||||
|
//!
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
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(all(feature = "epd2in13_v2", feature = "epd2in13_v3"))]
|
||||||
|
compile_error!(
|
||||||
|
"feature \"epd2in13_v2\" and feature \"epd2in13_v3\" cannot be enabled at the same time"
|
||||||
|
);
|
||||||
|
#[cfg(not(any(feature = "epd2in13_v2", feature = "epd2in13_v3")))]
|
||||||
|
compile_error!(
|
||||||
|
"One of feature \"epd2in13_v2\" and feature \"epd2in13_v3\" needs to be enabled as a feature"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 2in13 v2 and v3 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in13 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// Epd2in13 (V2 & V3) driver
|
||||||
|
///
|
||||||
|
/// To use this driver for V2 of the display, feature \"epd2in13_v3\" needs to be disabled and feature \"epd2in13_v2\" enabled.
|
||||||
|
pub struct Epd2in13<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
|
||||||
|
sleep_mode: DeepSleepMode,
|
||||||
|
|
||||||
|
/// Background Color
|
||||||
|
background_color: Color,
|
||||||
|
refresh: RefreshLut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in13<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// HW reset
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
if self.refresh == RefreshLut::Quick {
|
||||||
|
self.set_vcom_register(spi, (-9).vcom())?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, Some(self.refresh))?;
|
||||||
|
|
||||||
|
// Python code does this, not sure why
|
||||||
|
// self.cmd_with_data(spi, Command::WriteOtpSelection, &[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::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.set_border_waveform(
|
||||||
|
spi,
|
||||||
|
BorderWaveForm {
|
||||||
|
vbd: BorderWaveFormVbd::Gs,
|
||||||
|
fix_level: BorderWaveFormFixLevel::Vss,
|
||||||
|
gs_trans: BorderWaveFormGs::Lut1,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::SwReset)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
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::XIncrYIncr, DataEntryModeDir::XDir)?;
|
||||||
|
|
||||||
|
// Use simple X/Y auto increase
|
||||||
|
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
self.set_ram_address_counters(spi, delay, 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, delay, Some(self.refresh))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in13<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let mut epd = Epd2in13 {
|
||||||
|
interface: DisplayInterface::new(busy, dc, rst, delay_us),
|
||||||
|
sleep_mode: DeepSleepMode::Mode1,
|
||||||
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// 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::MasterActivation)?;
|
||||||
|
|
||||||
|
self.set_sleep_mode(spi, self.sleep_mode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> 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, delay, 0, 0)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::WriteRam, 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, delay, 0, 0)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::WriteRamRed, 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,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
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, delay, x, y)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::WriteRam, 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, delay, x, y)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::WriteRamRed, 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, delay: &mut DELAY) -> 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::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
|
||||||
|
if self.refresh == RefreshLut::Quick {
|
||||||
|
self.set_partial_base_buffer(spi, delay, buffer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> 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, delay, 0, 0)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::WriteRam)?;
|
||||||
|
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, delay, 0, 0)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::WriteRamRed)?;
|
||||||
|
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,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
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::WriteLutRegister, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in13<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// 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,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
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, delay, 0, 0)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::WriteRamRed, 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(
|
||||||
|
&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::GateScanStartPosition,
|
||||||
|
&[(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::BorderWaveformControl,
|
||||||
|
&[borderwaveform.to_u8()],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vcom_register(&mut self, spi: &mut SPI, vcom: Vcom) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd_with_data(spi, Command::WriteVcomRegister, &[vcom.0])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_gate_driving_voltage(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
voltage: GateDrivingVoltage,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd_with_data(spi, Command::GateDrivingVoltageCtrl, &[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::SetDummyLinePeriod, &[number_of_lines])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_gate_line_width(&mut self, spi: &mut SPI, width: u8) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd_with_data(spi, Command::SetGateLineWidth, &[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::SourceDrivingVoltageCtrl,
|
||||||
|
&[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::DisplayUpdateControl2, &[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::DeepSleepMode, &[mode as u8])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_driver_output(&mut self, spi: &mut SPI, output: DriverOutput) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd_with_data(spi, Command::DriverOutputControl, &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::DataEntryModeSetting, &[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::SetRamXAddressStartEndPosition,
|
||||||
|
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressStartEndPosition,
|
||||||
|
&[
|
||||||
|
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,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressCounter,
|
||||||
|
&[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
38
epd-waveshare/src/epd2in13bc/command.rs
Normal file
38
epd-waveshare/src/epd2in13bc/command.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//! SPI Commands for the Waveshare 2.13" (B/C) E-Ink Display
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
PowerOff = 0x02,
|
||||||
|
PowerOn = 0x04,
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
LutWhiteToBlack = 0x23,
|
||||||
|
LutBlackToBlack = 0x24,
|
||||||
|
|
||||||
|
PllControl = 0x30,
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
PowerSaving = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
393
epd-waveshare/src/epd2in13bc/mod.rs
Normal file
393
epd-waveshare/src/epd2in13bc/mod.rs
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
//! A simple Driver for the Waveshare 2.13" (B/C) E-Ink Display via SPI
|
||||||
|
//! More information on this display can be found at the [Waveshare Wiki](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_(B))
|
||||||
|
//! This driver was build and tested for 212x104, 2.13inch E-Ink display HAT for Raspberry Pi, three-color, SPI interface
|
||||||
|
//!
|
||||||
|
//! # Example for the 2.13" E-Ink Display
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{prelude::*, primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder}};
|
||||||
|
//!use epd_waveshare::{epd2in13bc::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd2in13bc::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!// This display is for the black/white/chromatic pixels
|
||||||
|
//!let mut tricolor_display = Display2in13bc::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing a black line
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 200))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 1))
|
||||||
|
//! .draw(&mut tricolor_display);
|
||||||
|
//!
|
||||||
|
//!// We use `chromatic` but it will be shown as red/yellow
|
||||||
|
//!let _ = Line::new(Point::new(15, 120), Point::new(15, 200))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(TriColor::Chromatic, 1))
|
||||||
|
//! .draw(&mut tricolor_display);
|
||||||
|
//!
|
||||||
|
//!// Display updated frame
|
||||||
|
//!epd.update_color_frame(
|
||||||
|
//! &mut spi,
|
||||||
|
//! &mut delay,
|
||||||
|
//! &tricolor_display.bw_buffer(),
|
||||||
|
//! &tricolor_display.chromatic_buffer()
|
||||||
|
//!)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Width of epd2in13bc in pixels
|
||||||
|
pub const WIDTH: u32 = 104;
|
||||||
|
/// Height of epd2in13bc in pixels
|
||||||
|
pub const HEIGHT: u32 = 212;
|
||||||
|
/// Default background color (white) of epd2in13bc display
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White;
|
||||||
|
|
||||||
|
/// Number of bits for b/w buffer and same for chromatic buffer
|
||||||
|
const NUM_DISPLAY_BITS: u32 = WIDTH / 8 * HEIGHT;
|
||||||
|
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const VCOM_DATA_INTERVAL: u8 = 0x07;
|
||||||
|
const WHITE_BORDER: u8 = 0x70;
|
||||||
|
const BLACK_BORDER: u8 = 0x30;
|
||||||
|
const CHROMATIC_BORDER: u8 = 0xb0;
|
||||||
|
const FLOATING_BORDER: u8 = 0xF0;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::TriColor;
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 2.13" b/c EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in13bc = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
true,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize * 2) },
|
||||||
|
TriColor,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd2in13bc driver
|
||||||
|
pub struct Epd2in13bc<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
color: TriColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in13bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Values taken from datasheet and sample code
|
||||||
|
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// start the booster
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||||
|
|
||||||
|
// power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set the panel settings
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x8F])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::VcomAndDataIntervalSetting,
|
||||||
|
&[WHITE_BORDER | VCOM_DATA_INTERVAL],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// set resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0A])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in13bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only the black/white data of the display.
|
||||||
|
///
|
||||||
|
/// Finish by calling `update_chromatic_frame`.
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data(spi, black)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only chromatic data of the display.
|
||||||
|
///
|
||||||
|
/// This data takes precedence over the black/white data.
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data(spi, chromatic)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in13bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = TriColor;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd2in13bc { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Section 8.2 from datasheet
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::VcomAndDataIntervalSetting,
|
||||||
|
&[FLOATING_BORDER | VCOM_DATA_INTERVAL],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
// The example STM code from Github has a wait after PowerOff
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, color: TriColor) {
|
||||||
|
self.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &TriColor {
|
||||||
|
&self.color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.interface.data(spi, buffer)?;
|
||||||
|
|
||||||
|
// Clear the chromatic layer
|
||||||
|
let color = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||||
|
|
||||||
|
// Clear the black
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
// Clear the chromatic
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in13bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::ResolutionSetting)?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[w as u8])?;
|
||||||
|
self.send_data(spi, &[(h >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[h as u8])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the outer border of the display to the chosen color.
|
||||||
|
pub fn set_border_color(&mut self, spi: &mut SPI, color: TriColor) -> Result<(), SPI::Error> {
|
||||||
|
let border = match color {
|
||||||
|
TriColor::Black => BLACK_BORDER,
|
||||||
|
TriColor::White => WHITE_BORDER,
|
||||||
|
TriColor::Chromatic => CHROMATIC_BORDER,
|
||||||
|
};
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::VcomAndDataIntervalSetting,
|
||||||
|
&[border | VCOM_DATA_INTERVAL],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
117
epd-waveshare/src/epd2in66b/command.rs
Normal file
117
epd-waveshare/src/epd2in66b/command.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
//! SPI Commands for the SSD1675B driver chip
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
DriverOutputControl = 0x01,
|
||||||
|
GateDrivingVoltageControl = 0x02,
|
||||||
|
SourceDrivingVoltageControl = 0x04,
|
||||||
|
ProgramOTPInitialCodeSetting = 0x08,
|
||||||
|
WriteRegisterForInitialCodeSetting = 0x09,
|
||||||
|
ReadRegisterForInitiaslCodeSetting = 0x0a,
|
||||||
|
BoosterSoftstartControl = 0x0c,
|
||||||
|
GateScanStartPosition = 0x0f,
|
||||||
|
DeepSleepMode = 0x10,
|
||||||
|
DataEntryMode = 0x11,
|
||||||
|
Reset = 0x12,
|
||||||
|
HVReadyDetection = 0x14,
|
||||||
|
VCIDetection = 0x15,
|
||||||
|
TemperatureSensorSelection = 0x18,
|
||||||
|
WriteTemperatureRegister = 0x1a,
|
||||||
|
ReadTemperatureRegister = 0x1b,
|
||||||
|
ExternalTemperatureSensorWrite = 0x1c,
|
||||||
|
MasterActivation = 0x20,
|
||||||
|
DisplayUpdateControl1 = 0x21,
|
||||||
|
DisplayUpdateControl2 = 0x22,
|
||||||
|
WriteBlackWhiteRAM = 0x24,
|
||||||
|
WriteRedRAM = 0x26,
|
||||||
|
ReadRAM = 0x27,
|
||||||
|
SenseVCOM = 0x28,
|
||||||
|
VCOMSenseDuration = 0x29,
|
||||||
|
ProgramOTPVCOM = 0x2a,
|
||||||
|
WriteRegisterForVCOMControl = 0x2b,
|
||||||
|
WriteVCOMRegister = 0x2c,
|
||||||
|
ReadOTPDisplayOptions = 0x2d,
|
||||||
|
ReadOTPUserId = 0x2e,
|
||||||
|
ReadStatusBits = 0x2f,
|
||||||
|
ProgramOTPWaveformSetting = 0x30,
|
||||||
|
LoadOTPWaveformSetting = 0x31,
|
||||||
|
WriteLUTRegister = 0x32,
|
||||||
|
CalculateCRC = 0x34,
|
||||||
|
ReadCRC = 0x35,
|
||||||
|
ProgramOTPSelection = 0x36,
|
||||||
|
WriteRegisterForDisplayOption = 0x37,
|
||||||
|
WriteRegisterForUserID = 0x38,
|
||||||
|
OTPProgramMode = 0x39,
|
||||||
|
SetDummyLinePeriod = 0x3a,
|
||||||
|
SetGateLineWidth = 0x3b,
|
||||||
|
BorderWaveformControl = 0x3c,
|
||||||
|
RAMReadOption = 0x41,
|
||||||
|
SetXAddressRange = 0x44,
|
||||||
|
SetYAddressRange = 0x45,
|
||||||
|
RedRAMTestPattern = 0x46,
|
||||||
|
BlackWhiteRAMTestPattern = 0x47,
|
||||||
|
SetXAddressCounter = 0x4e,
|
||||||
|
SetYAddressCounter = 0x4f,
|
||||||
|
SetAnalogBlockControl = 0x74,
|
||||||
|
SetDigitalBlockControl = 0x7e,
|
||||||
|
Nop = 0x7f,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum DataEntrySign {
|
||||||
|
DecYDecX = 0b00,
|
||||||
|
DecYIncX = 0b01,
|
||||||
|
IncYDecX = 0b10,
|
||||||
|
IncYIncX = 0b11,
|
||||||
|
}
|
||||||
|
pub(crate) enum DataEntryRow {
|
||||||
|
XMinor = 0b000,
|
||||||
|
YMinor = 0b100,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum WriteMode {
|
||||||
|
Normal = 0b0000,
|
||||||
|
ForceZero = 0b0100,
|
||||||
|
Invert = 0b1000,
|
||||||
|
}
|
||||||
|
pub(crate) enum OutputSource {
|
||||||
|
S0ToS175 = 0x00,
|
||||||
|
S8ToS167 = 0x80,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum DeepSleep {
|
||||||
|
Awake = 0b00,
|
||||||
|
SleepKeepingRAM = 0b01,
|
||||||
|
SleepLosingRAM = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum PatH {
|
||||||
|
H8 = 0b000_0000,
|
||||||
|
H16 = 0b001_0000,
|
||||||
|
H32 = 0b010_0000,
|
||||||
|
H64 = 0b011_0000,
|
||||||
|
H128 = 0b100_0000,
|
||||||
|
H256 = 0b101_0000,
|
||||||
|
H296 = 0b110_0000,
|
||||||
|
}
|
||||||
|
pub(crate) enum PatW {
|
||||||
|
W8 = 0b000,
|
||||||
|
W16 = 0b001,
|
||||||
|
W32 = 0b010,
|
||||||
|
W64 = 0b011,
|
||||||
|
W128 = 0b100,
|
||||||
|
W160 = 0b101,
|
||||||
|
}
|
||||||
|
pub(crate) enum StartWith {
|
||||||
|
Zero = 0x00,
|
||||||
|
One = 0x80,
|
||||||
|
}
|
517
epd-waveshare/src/epd2in66b/mod.rs
Normal file
517
epd-waveshare/src/epd2in66b/mod.rs
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
//! A driver for the Waveshare three-color E-ink Pi Pico hat 'Pico-ePaper-2.66-B' B/W/R.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! This driver was built and tested for this 296x152, 2.66inch three-color E-Ink display hat for the Pi Pico, it is expected to work for
|
||||||
|
//! other boards too, but that might depend on how the OTP memory in the display is programmed by the factory.
|
||||||
|
//!
|
||||||
|
//! The driver embedded in the display of this board is the SSD1675B, [documented by cursedhardware](https://cursedhardware.github.io/epd-driver-ic/SSD1675B.pdf).
|
||||||
|
//!
|
||||||
|
//! The pin assigments are shown on the Waveshare wiki [schematic](https://www.waveshare.com/w/upload/8/8d/Pico-ePaper-2.66.pdf).
|
||||||
|
//!
|
||||||
|
//! Information on this display/hat can be found at the [Waveshare Wiki](https://www.waveshare.com/wiki/Pico-ePaper-2.66-B).
|
||||||
|
//! Do read this documentation, in particular to understand how often this display both should and should not be updated.
|
||||||
|
//!
|
||||||
|
//! # Example for the 'Pico-ePaper-2.66-B' B/W/R Pi Pico Hat E-Ink Display
|
||||||
|
//! This example was created in an environment using the [Knurling](https://github.com/knurling-rs) ```flip-link```, ```defmt``` and ```probe-run``` tools - you will
|
||||||
|
//! need to adjust for your preferred setup.
|
||||||
|
//!```ignore
|
||||||
|
//!#![no_std]
|
||||||
|
//!#![no_main]
|
||||||
|
//!use epd_waveshare::{epd2in66b::*, prelude::*};
|
||||||
|
//!
|
||||||
|
//!use cortex_m_rt::entry;
|
||||||
|
//!//use defmt::*;
|
||||||
|
//!use defmt_rtt as _;
|
||||||
|
//!use panic_probe as _;
|
||||||
|
//!
|
||||||
|
//!// Use embedded-graphics to create a bitmap to show
|
||||||
|
//!fn drawing() -> Display2in66b {
|
||||||
|
//! use embedded_graphics::{
|
||||||
|
//! mono_font::{ascii::FONT_10X20, MonoTextStyle},
|
||||||
|
//! prelude::*,
|
||||||
|
//! primitives::PrimitiveStyle,
|
||||||
|
//! text::{Alignment, Text},
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // Create a Display buffer to draw on, specific for this ePaper
|
||||||
|
//! let mut display = Display2in66b::default();
|
||||||
|
//!
|
||||||
|
//! // Landscape mode, USB plug to the right
|
||||||
|
//! display.set_rotation(DisplayRotation::Rotate270);
|
||||||
|
//!
|
||||||
|
//! // Change the background from the default black to white
|
||||||
|
//! let _ = display
|
||||||
|
//! .bounding_box()
|
||||||
|
//! .into_styled(PrimitiveStyle::with_fill(TriColor::White))
|
||||||
|
//! .draw(&mut display);
|
||||||
|
//!
|
||||||
|
//! // Draw some text on the buffer
|
||||||
|
//! let text = "Pico-ePaper-2.66 B/W/R";
|
||||||
|
//! Text::with_alignment(
|
||||||
|
//! text,
|
||||||
|
//! display.bounding_box().center() + Point::new(1, 0),
|
||||||
|
//! MonoTextStyle::new(&FONT_10X20, TriColor::Black),
|
||||||
|
//! Alignment::Center,
|
||||||
|
//! )
|
||||||
|
//! .draw(&mut display)
|
||||||
|
//! .unwrap();
|
||||||
|
//! Text::with_alignment(
|
||||||
|
//! text,
|
||||||
|
//! display.bounding_box().center() + Point::new(0, 1),
|
||||||
|
//! MonoTextStyle::new(&FONT_10X20, TriColor::Chromatic),
|
||||||
|
//! Alignment::Center,
|
||||||
|
//! )
|
||||||
|
//! .draw(&mut display)
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! display
|
||||||
|
//!}
|
||||||
|
//!
|
||||||
|
//!#[entry]
|
||||||
|
//!fn main() -> ! {
|
||||||
|
//! use fugit::RateExtU32;
|
||||||
|
//! use rp_pico::hal::{
|
||||||
|
//! self,
|
||||||
|
//! clocks::{init_clocks_and_plls, Clock},
|
||||||
|
//! gpio::{FunctionSpi, PinState, Pins},
|
||||||
|
//! pac,
|
||||||
|
//! sio::Sio,
|
||||||
|
//! watchdog::Watchdog,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // Boilerplate to access the peripherals
|
||||||
|
//! let mut pac = pac::Peripherals::take().unwrap();
|
||||||
|
//! let core = pac::CorePeripherals::take().unwrap();
|
||||||
|
//! let mut watchdog = Watchdog::new(pac.WATCHDOG);
|
||||||
|
//! let external_xtal_freq_hz = 12_000_000u32;
|
||||||
|
//! let clocks = init_clocks_and_plls(
|
||||||
|
//! external_xtal_freq_hz,
|
||||||
|
//! pac.XOSC,
|
||||||
|
//! pac.CLOCKS,
|
||||||
|
//! pac.PLL_SYS,
|
||||||
|
//! pac.PLL_USB,
|
||||||
|
//! &mut pac.RESETS,
|
||||||
|
//! &mut watchdog,
|
||||||
|
//! )
|
||||||
|
//! .ok()
|
||||||
|
//! .unwrap();
|
||||||
|
//! let sio = Sio::new(pac.SIO);
|
||||||
|
//! let pins = Pins::new(
|
||||||
|
//! pac.IO_BANK0,
|
||||||
|
//! pac.PADS_BANK0,
|
||||||
|
//! sio.gpio_bank0,
|
||||||
|
//! &mut pac.RESETS,
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! // Pin assignments of the Pi Pico-ePaper-2.66 Hat
|
||||||
|
//! let _ = pins.gpio10.into_mode::<FunctionSpi>();
|
||||||
|
//! let _ = pins.gpio11.into_mode::<FunctionSpi>();
|
||||||
|
//! let chip_select_pin = pins.gpio9.into_push_pull_output_in_state(PinState::High);
|
||||||
|
//! let is_busy_pin = pins.gpio13.into_floating_input();
|
||||||
|
//! let data_or_command_pin = pins.gpio8.into_push_pull_output_in_state(PinState::High);
|
||||||
|
//! let reset_pin = pins.gpio12.into_push_pull_output_in_state(PinState::High);
|
||||||
|
//!
|
||||||
|
//! // SPI
|
||||||
|
//! let spi = hal::Spi::<_, _, 8>::new(pac.SPI1);
|
||||||
|
//! let mut spi = spi.init(
|
||||||
|
//! &mut pac.RESETS,
|
||||||
|
//! clocks.peripheral_clock.freq(),
|
||||||
|
//! 20_000_000u32.Hz(), // The SSD1675B docs say 20MHz max
|
||||||
|
//! &SPI_MODE,
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! // Delay
|
||||||
|
//! let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
||||||
|
//!
|
||||||
|
//! // Setup the EPD driver
|
||||||
|
//! let mut e_paper = Epd2in66b::new(
|
||||||
|
//! &mut spi,
|
||||||
|
//! chip_select_pin,
|
||||||
|
//! is_busy_pin,
|
||||||
|
//! data_or_command_pin,
|
||||||
|
//! reset_pin,
|
||||||
|
//! &mut delay,
|
||||||
|
//! None,
|
||||||
|
//! )
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // Create and fill a Display buffer
|
||||||
|
//! let display = drawing();
|
||||||
|
//!
|
||||||
|
//! // Send the Display buffer to the ePaper RAM
|
||||||
|
//! e_paper
|
||||||
|
//! .update_color_frame(
|
||||||
|
//! &mut spi,
|
||||||
|
//! &mut delay,
|
||||||
|
//! &display.bw_buffer(),
|
||||||
|
//! &display.chromatic_buffer(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // Render the ePaper RAM - takes time.
|
||||||
|
//! e_paper.display_frame(&mut spi, &mut delay).unwrap();
|
||||||
|
//!
|
||||||
|
//! // Always turn off your EPD as much as possible - ePaper wears out while powered on.
|
||||||
|
//! e_paper.sleep(&mut spi, &mut delay).unwrap();
|
||||||
|
//!
|
||||||
|
//! delay.delay_ms(60 * 1000);
|
||||||
|
//!
|
||||||
|
//! // Set the display all-white before storing your ePaper long-term.
|
||||||
|
//! e_paper.wake_up(&mut spi, &mut delay).unwrap();
|
||||||
|
//! e_paper.clear_frame(&mut spi, &mut delay).unwrap();
|
||||||
|
//! e_paper.display_frame(&mut spi, &mut delay).unwrap();
|
||||||
|
//! e_paper.sleep(&mut spi, &mut delay).unwrap();
|
||||||
|
//!
|
||||||
|
//! loop {}
|
||||||
|
//!}
|
||||||
|
//!```
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::TriColor;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::*;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Display height in pixels.
|
||||||
|
pub const WIDTH: u32 = 152;
|
||||||
|
/// Display width in pixels
|
||||||
|
pub const HEIGHT: u32 = 296;
|
||||||
|
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// White, display this during long-term storage
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White;
|
||||||
|
|
||||||
|
/// A Display buffer configured with our extent and color depth.
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in66b = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) * 2 },
|
||||||
|
TriColor,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// The EPD 2in66-B driver.
|
||||||
|
pub struct Epd2in66b<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
background: TriColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in66b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// We follow the sequence of the Pi-Pico hat example code.
|
||||||
|
self.hw_reset(delay)?;
|
||||||
|
self.sw_reset(spi, delay)?;
|
||||||
|
self.data_entry_mode(spi, DataEntryRow::XMinor, DataEntrySign::IncYIncX)?;
|
||||||
|
self.set_display_window(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
self.update_control1(
|
||||||
|
spi,
|
||||||
|
WriteMode::Normal,
|
||||||
|
WriteMode::Normal,
|
||||||
|
OutputSource::S8ToS167,
|
||||||
|
)?;
|
||||||
|
self.set_cursor(spi, 0, 0)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in66b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.set_cursor(spi, 0, 0)?;
|
||||||
|
self.interface.cmd(spi, Command::WriteBlackWhiteRAM)?;
|
||||||
|
self.interface.data(spi, black)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.set_cursor(spi, 0, 0)?;
|
||||||
|
self.interface.cmd(spi, Command::WriteRedRAM)?;
|
||||||
|
self.interface.data(spi, chromatic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in66b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = TriColor;
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let mut epd = Self {
|
||||||
|
interface: DisplayInterface::new(busy, dc, rst, delay_us),
|
||||||
|
background: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
};
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DeepSleepMode,
|
||||||
|
&[DeepSleep::SleepLosingRAM as u8],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, color: Self::DisplayColor) {
|
||||||
|
self.background = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Self::DisplayColor {
|
||||||
|
&self.background
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.set_cursor(spi, 0, 0)?;
|
||||||
|
self.update_achromatic_frame(spi, delay, buffer)?;
|
||||||
|
self.red_pattern(spi, delay, PatW::W160, PatH::H296, StartWith::Zero) // do NOT consider background here since red overrides other colors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.set_display_window(spi, x, y, x + width, y + height)?;
|
||||||
|
self.set_cursor(spi, x, y)?;
|
||||||
|
self.update_achromatic_frame(spi, delay, buffer)?;
|
||||||
|
self.set_display_window(spi, 0, 0, WIDTH, HEIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
self.wait_until_idle(delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
let (white, red) = match self.background {
|
||||||
|
TriColor::Black => (StartWith::Zero, StartWith::Zero),
|
||||||
|
TriColor::White => (StartWith::One, StartWith::Zero),
|
||||||
|
TriColor::Chromatic => (StartWith::Zero, StartWith::One),
|
||||||
|
};
|
||||||
|
self.black_white_pattern(spi, delay, PatW::W160, PatH::H296, white)?;
|
||||||
|
self.red_pattern(spi, delay, PatW::W160, PatH::H296, red)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions that enforce some type and value constraints. Meant to help with code readability. They caught some of my silly errors -> yay rust!.
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in66b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn wait_until_idle(&mut self, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, false);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn hw_reset(&mut self, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// The initial delay is taken from other code here, the 2 ms comes from the SSD1675B datasheet.
|
||||||
|
self.interface.reset(delay, 20_000, 2_000);
|
||||||
|
self.wait_until_idle(delay)
|
||||||
|
}
|
||||||
|
fn sw_reset(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::Reset)?;
|
||||||
|
self.wait_until_idle(delay)
|
||||||
|
}
|
||||||
|
fn data_entry_mode(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
row: DataEntryRow,
|
||||||
|
sign: DataEntrySign,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataEntryMode, &[row as u8 | sign as u8])
|
||||||
|
}
|
||||||
|
fn set_display_window(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
xstart: u32,
|
||||||
|
ystart: u32,
|
||||||
|
xend: u32,
|
||||||
|
yend: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetXAddressRange,
|
||||||
|
&[(((xstart >> 3) & 0x1f) as u8), (((xend >> 3) & 0x1f) as u8)],
|
||||||
|
)?;
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetYAddressRange,
|
||||||
|
&[
|
||||||
|
((ystart & 0xff) as u8),
|
||||||
|
(((ystart >> 8) & 0x01) as u8),
|
||||||
|
((yend & 0xff) as u8),
|
||||||
|
(((yend >> 8) & 0x01) as u8),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn update_control1(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
red_mode: WriteMode,
|
||||||
|
bw_mode: WriteMode,
|
||||||
|
source: OutputSource,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DisplayUpdateControl1,
|
||||||
|
&[((red_mode as u8) << 4 | bw_mode as u8), (source as u8)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor(&mut self, spi: &mut SPI, x: u32, y: u32) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetXAddressCounter,
|
||||||
|
&[((x >> 3) & 0x1f) as u8],
|
||||||
|
)?;
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetYAddressCounter,
|
||||||
|
&[((y & 0xff) as u8), (((y >> 8) & 0x01) as u8)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn black_white_pattern(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
w: PatW,
|
||||||
|
h: PatH,
|
||||||
|
phase: StartWith,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::BlackWhiteRAMTestPattern,
|
||||||
|
&[phase as u8 | h as u8 | w as u8],
|
||||||
|
)?;
|
||||||
|
self.wait_until_idle(delay)
|
||||||
|
}
|
||||||
|
fn red_pattern(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
w: PatW,
|
||||||
|
h: PatH,
|
||||||
|
phase: StartWith,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::RedRAMTestPattern,
|
||||||
|
&[phase as u8 | h as u8 | w as u8],
|
||||||
|
)?;
|
||||||
|
self.wait_until_idle(delay)
|
||||||
|
}
|
||||||
|
}
|
137
epd-waveshare/src/epd2in7b/command.rs
Normal file
137
epd-waveshare/src/epd2in7b/command.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
//! SPI Commands for the Waveshare 2.7" B 3 color E-Ink Display
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
/// EPD2IN7B commands
|
||||||
|
///
|
||||||
|
/// More information can be found in the [specification](https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift direction, booster switch, soft reset
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
PowerOff = 0x02,
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 0x03,
|
||||||
|
PowerOn = 0x04,
|
||||||
|
/// This command enables the internal bandgap, which will be cleared by the next POF.
|
||||||
|
PowerOnMeasure = 0x05,
|
||||||
|
/// Starting data transmission
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// self.send_data(&[0x07, 0x07, 0x17])?;
|
||||||
|
/// ```
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
/// After this command is transmitted, the chip would enter the deep-sleep mode to save power.
|
||||||
|
///
|
||||||
|
/// The deep sleep mode would return to standby by hardware reset.
|
||||||
|
///
|
||||||
|
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
/// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data
|
||||||
|
/// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel.
|
||||||
|
///
|
||||||
|
/// - In B/W mode, this command writes “OLD” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “B/W” data to SRAM.
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
/// Stopping data transmission
|
||||||
|
DataStop = 0x11,
|
||||||
|
/// After this command is issued, driver will refresh display (data/VCOM) according to SRAM data and LUT.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
/// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data
|
||||||
|
/// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel.
|
||||||
|
/// - In B/W mode, this command writes “NEW” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “RED” data to SRAM.
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
/// The command define as follows: The register is indicates that user start to transmit data, then write to SRAM. While data transmission
|
||||||
|
/// complete, user must send command DSP (Data transmission Stop). Then chip will start to send data/VCOM for panel.
|
||||||
|
///
|
||||||
|
/// - In B/W mode, this command writes “OLD” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “B/W” data to SRAM.
|
||||||
|
PartialDataStartTransmission1 = 0x14,
|
||||||
|
/// The command define as follows: The register is indicates that user start to transmit data, then write to SRAM. While data transmission
|
||||||
|
/// complete, user must send command DSP (Data transmission Stop). Then chip will start to send data/VCOM for panel.
|
||||||
|
///
|
||||||
|
/// - In B/W mode, this command writes “NEW” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “RED” data to SRAM.
|
||||||
|
PartialDataStartTransmission2 = 0x15,
|
||||||
|
/// While user sent this command, driver will refresh display (data/VCOM) base on SRAM data and LUT.
|
||||||
|
///
|
||||||
|
/// Only the area (X,Y, W, L) would update, the others pixel output would follow VCOM LUT
|
||||||
|
PartialDisplayRefresh = 0x16,
|
||||||
|
/// This command builds the Look-up table for VCOM
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
LutWhiteToBlack = 0x23,
|
||||||
|
LutBlackToBlack = 0x24,
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// This command selects Internal or External temperature sensor.
|
||||||
|
TemperatureSensorCalibration = 0x41,
|
||||||
|
/// Write External Temperature Sensor
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// Read External Temperature Sensor
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
TemperatureSensorRead = 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)
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR).
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
SourceAndGateSetting = 0x62,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
GetStatus = 0x71,
|
||||||
|
/// Automatically measure VCOM. This command reads the IC status
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets VCOM_DC value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
/// After this command is issued, the chip would enter the program mode.
|
||||||
|
///
|
||||||
|
/// After the programming procedure completed, a hardware reset is necessary for leaving program mode.
|
||||||
|
///
|
||||||
|
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
|
||||||
|
ProgramMode = 0xA0,
|
||||||
|
/// After this command is issued, the chip would enter the program mode.
|
||||||
|
ActiveProgramming = 0xA1,
|
||||||
|
/// The command is used for reading the content of OTP for checking the data of programming.
|
||||||
|
///
|
||||||
|
/// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF.
|
||||||
|
ReadOtp = 0xA2,
|
||||||
|
/// Not shown in commands table, but used in init sequence
|
||||||
|
PowerOptimization = 0xf8,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
55
epd-waveshare/src/epd2in7b/constants.rs
Normal file
55
epd-waveshare/src/epd2in7b/constants.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_VCOM_DC: [u8; 44] = [
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10,
|
||||||
|
0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
|
||||||
|
0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
|
||||||
|
0x00, 0x23, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WW: [u8; 42] =[
|
||||||
|
0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01,
|
||||||
|
0x40, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
|
||||||
|
0x80, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
|
||||||
|
0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
|
||||||
|
0x00, 0x23, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BW: [u8; 42] =[
|
||||||
|
0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
|
||||||
|
0x90, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0xB0, 0x04, 0x10, 0x00, 0x00, 0x05,
|
||||||
|
0xB0, 0x03, 0x0E, 0x00, 0x00, 0x0A,
|
||||||
|
0xC0, 0x23, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BB: [u8; 42] =[
|
||||||
|
0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01,
|
||||||
|
0x40, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
|
||||||
|
0x80, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
|
||||||
|
0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
|
||||||
|
0x00, 0x23, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WB: [u8; 42] =[
|
||||||
|
0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01,
|
||||||
|
0x20, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
|
||||||
|
0x10, 0x0A, 0x0A, 0x00, 0x00, 0x08,
|
||||||
|
0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
|
||||||
|
0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
|
||||||
|
0x00, 0x23, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
464
epd-waveshare/src/epd2in7b/mod.rs
Normal file
464
epd-waveshare/src/epd2in7b/mod.rs
Normal file
@ -0,0 +1,464 @@
|
|||||||
|
//! A simple Driver for the Waveshare 2.7" B Tri-Color E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! [Documentation](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT_(B))
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Lookup Tables for the Display
|
||||||
|
mod constants;
|
||||||
|
use crate::epd2in7b::constants::*;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 176;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 264;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 2in7B EPD
|
||||||
|
/// TODO this should be a TriColor, but let's keep it as is at first
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in7b = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd2in7b driver
|
||||||
|
pub struct Epd2in7b<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in7b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
// power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set panel settings, 0xbf is bw, 0xaf is multi-color
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PanelSetting, &[0xaf])?;
|
||||||
|
|
||||||
|
// pll control
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PllControl, &[0x3a])?;
|
||||||
|
|
||||||
|
// set the power settings
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::PowerSetting,
|
||||||
|
&[0x03, 0x00, 0x2b, 0x2b, 0x09],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// start the booster
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x07, 0x07, 0x17])?;
|
||||||
|
|
||||||
|
// power optimization
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerOptimization, &[0x60, 0xa5])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerOptimization, &[0x89, 0xa5])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerOptimization, &[0x90, 0x00])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerOptimization, &[0x93, 0x2a])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PowerOptimization, &[0x73, 0x41])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x87])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, None)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PartialDisplayRefresh, &[0x00])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in7b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd2in7b { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0xf7])?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.send_buffer_helper(spi, buffer)?;
|
||||||
|
|
||||||
|
// Clear chromatic layer since we won't be using it here
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, !self.color.get_byte_value(), WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.cmd(spi, Command::PartialDataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[(x >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(x & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y & 0xff) as u8])?;
|
||||||
|
self.send_data(spi, &[(width >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(width & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height & 0xff) as u8])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.send_buffer_helper(spi, buffer)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
let color_value = self.color.get_byte_value();
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
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,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::LutForVcom, &LUT_VCOM_DC)?;
|
||||||
|
self.cmd_with_data(spi, Command::LutWhiteToWhite, &LUT_WW)?;
|
||||||
|
self.cmd_with_data(spi, Command::LutBlackToWhite, &LUT_BW)?;
|
||||||
|
self.cmd_with_data(spi, Command::LutWhiteToBlack, &LUT_WB)?;
|
||||||
|
self.cmd_with_data(spi, Command::LutBlackToBlack, &LUT_BB)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in7b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only the black/white data of the display.
|
||||||
|
///
|
||||||
|
/// Finish by calling `update_chromatic_frame`.
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
achromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.send_buffer_helper(spi, achromatic)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only chromatic data of the display.
|
||||||
|
///
|
||||||
|
/// This data takes precedence over the black/white data.
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
|
||||||
|
self.send_buffer_helper(spi, chromatic)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in7b<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_buffer_helper(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||||
|
// Based on the waveshare implementation, all data for color values is flipped. This helper
|
||||||
|
// method makes that transmission easier
|
||||||
|
for b in buffer.iter() {
|
||||||
|
self.send_data(spi, &[!b])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_with_data(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
command: Command,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(spi, command, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh display for partial frame
|
||||||
|
pub fn display_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::PartialDisplayRefresh)?;
|
||||||
|
self.send_data(spi, &[(x >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(x & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y & 0xff) as u8])?;
|
||||||
|
self.send_data(spi, &[(width >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(width & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height & 0xff) as u8])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update black/achromatic frame
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn update_partial_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
achromatic: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.cmd(spi, Command::PartialDataStartTransmission1)?;
|
||||||
|
self.send_data(spi, &[(x >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(x & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y & 0xff) as u8])?;
|
||||||
|
self.send_data(spi, &[(width >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(width & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height & 0xff) as u8])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
for b in achromatic.iter() {
|
||||||
|
// Flipping based on waveshare implementation
|
||||||
|
self.send_data(spi, &[!b])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update partial chromatic/red frame
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn update_partial_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.cmd(spi, Command::PartialDataStartTransmission2)?;
|
||||||
|
self.send_data(spi, &[(x >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(x & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y & 0xff) as u8])?;
|
||||||
|
self.send_data(spi, &[(width >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(width & 0xf8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(height & 0xff) as u8])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
for b in chromatic.iter() {
|
||||||
|
// Flipping based on waveshare implementation
|
||||||
|
self.send_data(spi, &[!b])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 176);
|
||||||
|
assert_eq!(HEIGHT, 264);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
388
epd-waveshare/src/epd2in9/mod.rs
Normal file
388
epd-waveshare/src/epd2in9/mod.rs
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
//! A simple Driver for the Waveshare 2.9" E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! # Example for the 2.9 in E-Ink Display
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{
|
||||||
|
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
//!};
|
||||||
|
//!use epd_waveshare::{epd2in9::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd2in9::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!let mut display = Display2in9::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing a line
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
//! .draw(&mut display);
|
||||||
|
//!
|
||||||
|
//! // Display updated frame
|
||||||
|
//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
|
||||||
|
/// Width of epd2in9 in pixels
|
||||||
|
pub const WIDTH: u32 = 128;
|
||||||
|
/// Height of epd2in9 in pixels
|
||||||
|
pub const HEIGHT: u32 = 296;
|
||||||
|
/// Default Background Color (white)
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::type_a::{
|
||||||
|
command::Command,
|
||||||
|
constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
use crate::traits::*;
|
||||||
|
|
||||||
|
use crate::buffer_len;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
|
||||||
|
/// Display with Fullsize buffer for use with the 2in9 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in9 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd2in9 driver
|
||||||
|
///
|
||||||
|
pub struct Epd2in9<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// SPI
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Color
|
||||||
|
background_color: Color,
|
||||||
|
/// Refresh LUT
|
||||||
|
refresh: RefreshLut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// 3 Databytes:
|
||||||
|
// A[7:0]
|
||||||
|
// 0.. A[8]
|
||||||
|
// 0.. B[2:0]
|
||||||
|
// Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?)
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DriverOutputControl, &[0x27, 0x01, 0x00])?;
|
||||||
|
|
||||||
|
// 3 Databytes: (and default values from datasheet and arduino)
|
||||||
|
// 1 .. A[6:0] = 0xCF | 0xD7
|
||||||
|
// 1 .. B[6:0] = 0xCE | 0xD6
|
||||||
|
// 1 .. C[6:0] = 0x8D | 0x9D
|
||||||
|
//TODO: test
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStartControl, &[0xD7, 0xD6, 0x9D])?;
|
||||||
|
|
||||||
|
// One Databyte with value 0xA8 for 7V VCOM
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteVcomRegister, &[0xA8])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x1A for 4 dummy lines per gate
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetDummyLinePeriod, &[0x1A])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x08 for 2us per line
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetGateLineWidth, &[0x08])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x03
|
||||||
|
// -> address: x increment, y increment, address counter is updated in x direction
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
|
||||||
|
let mut epd = Epd2in9 {
|
||||||
|
interface,
|
||||||
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode
|
||||||
|
//TODO: is 0x00 needed here? (see also epd1in54)
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.init(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: update description: last 3 bits will be ignored for width and x_pos
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.set_ram_area(spi, x, y, x + width, y + height)?;
|
||||||
|
self.set_ram_counter(spi, delay, x, y)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// enable clock signal, enable cp, display pattern -> 0xC4 (tested with the arduino version)
|
||||||
|
//TODO: test control_1 or control_2 with default value 0xFF (from the datasheet)
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC4])?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
// MASTER Activation should not be interupted to avoid currption of panel images
|
||||||
|
// therefore a terminate command is send
|
||||||
|
self.interface.cmd(spi, Command::Nop)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
|
||||||
|
// clear the ram with the background color
|
||||||
|
let color = self.background_color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::WriteRam)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, background_color: Color) {
|
||||||
|
self.background_color = background_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Color {
|
||||||
|
&self.background_color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if let Some(refresh_lut) = refresh_rate {
|
||||||
|
self.refresh = refresh_lut;
|
||||||
|
}
|
||||||
|
match self.refresh {
|
||||||
|
RefreshLut::Full => self.set_lut_helper(spi, delay, &LUT_FULL_UPDATE),
|
||||||
|
RefreshLut::Quick => self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn use_full_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// choose full frame/ram
|
||||||
|
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
|
||||||
|
// start from the beginning
|
||||||
|
self.set_ram_counter(spi, delay, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ram_area(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
start_x: u32,
|
||||||
|
start_y: u32,
|
||||||
|
end_x: u32,
|
||||||
|
end_y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
assert!(start_x < end_x);
|
||||||
|
assert!(start_y < end_y);
|
||||||
|
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamXAddressStartEndPosition,
|
||||||
|
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8] for each - start and end
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressStartEndPosition,
|
||||||
|
&[
|
||||||
|
start_y as u8,
|
||||||
|
(start_y >> 8) as u8,
|
||||||
|
end_y as u8,
|
||||||
|
(end_y >> 8) as u8,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ram_counter(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8]
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressCounter,
|
||||||
|
&[y as u8, (y >> 8) as u8],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set your own LUT, this function is also used internally for set_lut
|
||||||
|
fn set_lut_helper(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
assert!(buffer.len() == 30);
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 128);
|
||||||
|
assert_eq!(HEIGHT, 296);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
535
epd-waveshare/src/epd2in9_v2/mod.rs
Normal file
535
epd-waveshare/src/epd2in9_v2/mod.rs
Normal file
@ -0,0 +1,535 @@
|
|||||||
|
//! A simple Driver for the Waveshare 2.9" E-Ink Display V2 via SPI
|
||||||
|
//!
|
||||||
|
//! Specification: <https://www.waveshare.com/w/upload/7/79/2.9inch-e-paper-v2-specification.pdf>
|
||||||
|
//!
|
||||||
|
//! # Example for the 2.9 in E-Ink Display V2
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{
|
||||||
|
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
//!};
|
||||||
|
//!use epd_waveshare::{epd2in9_v2::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd2in9::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!let mut display = Display2in9::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing a line
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
//! .draw(&mut display);
|
||||||
|
//!
|
||||||
|
//!// Display updated frame
|
||||||
|
//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Draw something new here
|
||||||
|
//!
|
||||||
|
//!// Display new image as a base image for further quick refreshes
|
||||||
|
//!epd.update_old_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Update image here
|
||||||
|
//!
|
||||||
|
//!// quick refresh of updated pixels
|
||||||
|
//!epd.update_new_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_new_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
|
||||||
|
/// Width of epd2in9 in pixels
|
||||||
|
pub const WIDTH: u32 = 128;
|
||||||
|
/// Height of epd2in9 in pixels
|
||||||
|
pub const HEIGHT: u32 = 296;
|
||||||
|
/// Default Background Color (white)
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
const LUT_PARTIAL_2IN9: [u8; 159] = [
|
||||||
|
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||||
|
0x22, 0x0, 0x0, 0x0, 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36,
|
||||||
|
];
|
||||||
|
|
||||||
|
const WS_20_30: [u8; 159] = [
|
||||||
|
0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x10, 0x66, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
|
||||||
|
0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1, 0xA, 0xA, 0x0, 0xA, 0xA, 0x0,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x8, 0x0, 0x1, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x44, 0x44, 0x44,
|
||||||
|
0x44, 0x44, 0x0, 0x0, 0x0, 0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
|
||||||
|
];
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::type_a::command::Command;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
use crate::traits::*;
|
||||||
|
|
||||||
|
use crate::buffer_len;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::QuickRefresh;
|
||||||
|
|
||||||
|
/// Display with Fullsize buffer for use with the 2in9 EPD V2
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in9 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd2in9 driver
|
||||||
|
///
|
||||||
|
pub struct Epd2in9<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// SPI
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Color
|
||||||
|
background_color: Color,
|
||||||
|
/// Refresh LUT
|
||||||
|
refresh: RefreshLut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface.cmd(spi, Command::SwReset)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// 3 Databytes:
|
||||||
|
// A[7:0]
|
||||||
|
// 0.. A[8]
|
||||||
|
// 0.. B[2:0]
|
||||||
|
// Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?)
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DriverOutputControl, &[0x27, 0x01, 0x00])?;
|
||||||
|
|
||||||
|
// One Databyte with default value 0x03
|
||||||
|
// -> address: x increment, y increment, address counter is updated in x direction
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?;
|
||||||
|
|
||||||
|
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl1, &[0x00, 0x80])?;
|
||||||
|
|
||||||
|
self.set_ram_counter(spi, delay, 0, 0)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set LUT by host
|
||||||
|
self.set_lut_helper(spi, delay, &WS_20_30[0..153])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegisterEnd, &WS_20_30[153..154])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::GateDrivingVoltage, &WS_20_30[154..155])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SourceDrivingVoltage, &WS_20_30[155..158])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteVcomRegister, &WS_20_30[158..159])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
|
||||||
|
let mut epd = Epd2in9 {
|
||||||
|
interface,
|
||||||
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleepMode, &[0x01])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface.cmd_with_data(spi, Command::WriteRam, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
//TODO This is copied from epd2in9 but it seems not working. Partial refresh supported by version 2?
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.set_ram_area(spi, x, y, x + width, y + height)?;
|
||||||
|
self.set_ram_counter(spi, delay, x, y)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// actually is the "Turn on Display" sequence
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// Enable clock signal, Enable Analog, Load temperature value, DISPLAY with DISPLAY Mode 1, Disable Analog, Disable OSC
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC7])?;
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// clear the ram with the background color
|
||||||
|
let color = self.background_color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::WriteRam)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||||
|
self.interface.cmd(spi, Command::WriteRam2)?;
|
||||||
|
self.interface.data_x_times(spi, color, WIDTH / 8 * HEIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, background_color: Color) {
|
||||||
|
self.background_color = background_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Color {
|
||||||
|
&self.background_color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if let Some(refresh_lut) = refresh_rate {
|
||||||
|
self.refresh = refresh_lut;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn use_full_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// choose full frame/ram
|
||||||
|
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||||
|
|
||||||
|
// start from the beginning
|
||||||
|
self.set_ram_counter(spi, delay, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ram_area(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
start_x: u32,
|
||||||
|
start_y: u32,
|
||||||
|
end_x: u32,
|
||||||
|
end_y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
assert!(start_x < end_x);
|
||||||
|
assert!(start_y < end_y);
|
||||||
|
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamXAddressStartEndPosition,
|
||||||
|
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8] for each - start and end
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressStartEndPosition,
|
||||||
|
&[
|
||||||
|
start_y as u8,
|
||||||
|
(start_y >> 8) as u8,
|
||||||
|
end_y as u8,
|
||||||
|
(end_y >> 8) as u8,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ram_counter(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram
|
||||||
|
// aren't relevant
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[x as u8])?;
|
||||||
|
|
||||||
|
// 2 Databytes: A[7:0] & 0..A[8]
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressCounter,
|
||||||
|
&[y as u8, (y >> 8) as u8],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set your own LUT, this function is also used internally for set_lut
|
||||||
|
fn set_lut_helper(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> QuickRefresh<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// To be followed immediately by `update_new_frame`.
|
||||||
|
fn update_old_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam2, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To be used immediately after `update_old_frame`.
|
||||||
|
fn update_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
self.set_lut_helper(spi, delay, &LUT_PARTIAL_2IN9)?;
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::WriteOtpSelection,
|
||||||
|
&[0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00],
|
||||||
|
)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BorderWaveformControl, &[0x80])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC0])?;
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.use_full_frame(spi, delay)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a quick refresh of the new updated frame. To be used immediately after `update_new_frame`
|
||||||
|
fn display_new_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0x0F])?;
|
||||||
|
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates and displays the new frame.
|
||||||
|
fn update_and_display_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_new_frame(spi, buffer, delay)?;
|
||||||
|
self.display_new_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Partial quick refresh not supported yet
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_old_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
//TODO supported by display?
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Partial quick refresh not supported yet
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
//TODO supported by display?
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Partial quick refresh not supported yet
|
||||||
|
#[allow(unused)]
|
||||||
|
fn clear_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
//TODO supported by display?
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 128);
|
||||||
|
assert_eq!(HEIGHT, 296);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
38
epd-waveshare/src/epd2in9bc/command.rs
Normal file
38
epd-waveshare/src/epd2in9bc/command.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//! SPI Commands for the Waveshare 2.9" (B/C) E-Ink Display
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
PowerOff = 0x02,
|
||||||
|
PowerOn = 0x04,
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
LutWhiteToBlack = 0x23,
|
||||||
|
LutBlackToBlack = 0x24,
|
||||||
|
|
||||||
|
PllControl = 0x30,
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
PowerSaving = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
397
epd-waveshare/src/epd2in9bc/mod.rs
Normal file
397
epd-waveshare/src/epd2in9bc/mod.rs
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
//! A simple Driver for the Waveshare 2.9" (B/C) E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! # Example for the 2.9" E-Ink Display
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{
|
||||||
|
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
//!};
|
||||||
|
//!use epd_waveshare::{epd2in9bc::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd2in9bc::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!// This display is for the black/white pixels
|
||||||
|
//!let mut mono_display = Display2in9bc::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing
|
||||||
|
//!// A black line
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 200))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
//! .draw(&mut mono_display);
|
||||||
|
//!
|
||||||
|
//!// Use a second display for red/yellow
|
||||||
|
//!let mut chromatic_display = Display2in9bc::default();
|
||||||
|
//!
|
||||||
|
//!// We use `Black` but it will be shown as red/yellow
|
||||||
|
//!let _ = Line::new(Point::new(15, 120), Point::new(15, 200))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
//! .draw(&mut chromatic_display);
|
||||||
|
//!
|
||||||
|
//!// Display updated frame
|
||||||
|
//!epd.update_color_frame(
|
||||||
|
//! &mut spi,
|
||||||
|
//! &mut delay,
|
||||||
|
//! &mono_display.buffer(),
|
||||||
|
//! &chromatic_display.buffer()
|
||||||
|
//!)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Width of epd2in9bc in pixels
|
||||||
|
pub const WIDTH: u32 = 128;
|
||||||
|
/// Height of epd2in9bc in pixels
|
||||||
|
pub const HEIGHT: u32 = 296;
|
||||||
|
/// Default background color (white) of epd2in9bc display
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
|
||||||
|
const NUM_DISPLAY_BITS: u32 = WIDTH / 8 * HEIGHT;
|
||||||
|
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const VCOM_DATA_INTERVAL: u8 = 0x07;
|
||||||
|
const WHITE_BORDER: u8 = 0x70;
|
||||||
|
const BLACK_BORDER: u8 = 0x30;
|
||||||
|
const CHROMATIC_BORDER: u8 = 0xb0;
|
||||||
|
const FLOATING_BORDER: u8 = 0xF0;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::{Color, TriColor};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 2in9b/c EPD
|
||||||
|
/// TODO this should be a TriColor, but let's keep it as is at first
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in9bc = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd2in9bc driver
|
||||||
|
pub struct Epd2in9bc<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Values taken from datasheet and sample code
|
||||||
|
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// start the booster
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||||
|
|
||||||
|
// power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set the panel settings
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x8F])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::VcomAndDataIntervalSetting,
|
||||||
|
&[WHITE_BORDER | VCOM_DATA_INTERVAL],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// set resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0A])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only the black/white data of the display.
|
||||||
|
///
|
||||||
|
/// Finish by calling `update_chromatic_frame`.
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data(spi, black)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only chromatic data of the display.
|
||||||
|
///
|
||||||
|
/// This data takes precedence over the black/white data.
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data(spi, chromatic)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd2in9bc { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Section 8.2 from datasheet
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::VcomAndDataIntervalSetting,
|
||||||
|
&[FLOATING_BORDER | VCOM_DATA_INTERVAL],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
// The example STM code from Github has a wait after PowerOff
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.interface.data(spi, buffer)?;
|
||||||
|
|
||||||
|
// Clear the chromatic layer
|
||||||
|
let color = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||||
|
|
||||||
|
// Clear the black
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
// Clear the chromatic
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in9bc<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::ResolutionSetting)?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[w as u8])?;
|
||||||
|
self.send_data(spi, &[(h >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[h as u8])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the outer border of the display to the chosen color.
|
||||||
|
pub fn set_border_color(&mut self, spi: &mut SPI, color: TriColor) -> Result<(), SPI::Error> {
|
||||||
|
let border = match color {
|
||||||
|
TriColor::Black => BLACK_BORDER,
|
||||||
|
TriColor::White => WHITE_BORDER,
|
||||||
|
TriColor::Chromatic => CHROMATIC_BORDER,
|
||||||
|
};
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::VcomAndDataIntervalSetting,
|
||||||
|
&[border | VCOM_DATA_INTERVAL],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
150
epd-waveshare/src/epd2in9d/command.rs
Normal file
150
epd-waveshare/src/epd2in9d/command.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
//! SPI Commands for the Waveshare 2.9" FLEXIBLE E-PAPER DISPLAY
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
/// selecting internal and external power
|
||||||
|
/// self.send_data(0x03)?; //VDS_EN, VDG_EN
|
||||||
|
/// self.send_data(0x00)?; //VCOM_HV, VGHL_LV[1], VGHL_LV[0]
|
||||||
|
/// self.send_data(0x2b)?; //VDH
|
||||||
|
/// self.send_data(0x2b)?; //VDL
|
||||||
|
/// self.send_data(0xff)?; //VDHR
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
/// After the Power Off command, the driver will power off following the Power Off Sequence. 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: floating.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 0x03,
|
||||||
|
/// Turning On the Power
|
||||||
|
PowerOn = 0x04,
|
||||||
|
/// This command enables the internal bandgap, which will be cleared by the next POF.
|
||||||
|
PowerOnMeasure = 0x05,
|
||||||
|
/// Starting data transmission
|
||||||
|
/// 3-times: self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
/// After this command is transmitted, the chip would enter the deep-sleep mode to save power.
|
||||||
|
///
|
||||||
|
/// The deep sleep mode would return to standby by hardware reset.
|
||||||
|
///
|
||||||
|
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
/// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data
|
||||||
|
/// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel.
|
||||||
|
///
|
||||||
|
/// - In B/W mode, this command writes “OLD” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “B/W” data to SRAM.
|
||||||
|
/// - In Program mode, this command writes “OTP” data to SRAM for programming.
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
/// Stopping data transmission
|
||||||
|
DataStop = 0x11,
|
||||||
|
/// While user sent this command, driver will refresh display (data/VCOM) according to SRAM data and LUT.
|
||||||
|
///
|
||||||
|
/// After Display Refresh command, BUSY_N signal will become “0” and the refreshing of panel starts.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
/// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data
|
||||||
|
/// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel.
|
||||||
|
/// - In B/W mode, this command writes “NEW” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “RED” data to SRAM.
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
/// This command stores VCOM Look-Up Table with 7 groups of data. Each group contains information for one state and is stored
|
||||||
|
/// with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
/// This command stores White-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
/// This command stores Black-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
/// This command stores White-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutWhiteToBlack = 0x23,
|
||||||
|
/// This command stores Black-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutBlackToBlack = 0x24,
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// Selects the Internal or External temperature sensor and offset
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
/// Write External Temperature Sensor
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// Read External Temperature Sensor
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
TemperatureSensorRead = 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)
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR).
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
/// This command defines the Fist Active Gate and First Active Source of active channels.
|
||||||
|
// GsstSetting = 0x65,
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 0x001.
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
// Revision = 0x70,
|
||||||
|
/// Read Flags. This command reads the IC status
|
||||||
|
/// PTL, I2C_ERR, I2C_BUSY, DATA, PON, POF, BUSY
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
GetStatus = 0x71,
|
||||||
|
/// Automatically measure VCOM. This command reads the IC status
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// Set VCM_DC
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
/// This command sets partial window
|
||||||
|
PartialWindow = 0x90,
|
||||||
|
/// This command makes the display enter partial mode
|
||||||
|
PartialIn = 0x91,
|
||||||
|
/// This command makes the display exit partial mode and enter normal mode
|
||||||
|
PartialOut = 0x92,
|
||||||
|
/// After this command is issued, the chip would enter the program mode.
|
||||||
|
///
|
||||||
|
/// After the programming procedure completed, a hardware reset is necessary for leaving program mode.
|
||||||
|
///
|
||||||
|
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
|
||||||
|
ProgramMode = 0xA0,
|
||||||
|
/// After this command is transmitted, the programming state machine would be activated.
|
||||||
|
///
|
||||||
|
/// The BUSY flag would fall to 0 until the programming is completed.
|
||||||
|
ActiveProgramming = 0xA1,
|
||||||
|
/// The command is used for reading the content of OTP for checking the data of programming.
|
||||||
|
///
|
||||||
|
/// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF.
|
||||||
|
ReadOtp = 0xA2,
|
||||||
|
/// This command is set for saving power during fresh period. If the output voltage of VCOM / Source is from negative to positive or
|
||||||
|
/// from positive to negative, the power saving mechanism will be activated. The active period width is defined by the following two
|
||||||
|
/// parameters.
|
||||||
|
PowerSaving = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
61
epd-waveshare/src/epd2in9d/constants.rs
Normal file
61
epd-waveshare/src/epd2in9d/constants.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//! This file contains look-up-tables used to set voltages used during
|
||||||
|
//! various categories of pixel refreshes.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* partial screen update LUT
|
||||||
|
**/
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_VCOM1: [u8; 44] = [
|
||||||
|
0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WW1: [u8; 42] =[
|
||||||
|
0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BW1: [u8; 42] =[
|
||||||
|
0x80, 0x19, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BB1: [u8; 42] =[
|
||||||
|
0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WB1: [u8; 42] =[
|
||||||
|
0x40, 0x19, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
407
epd-waveshare/src/epd2in9d/mod.rs
Normal file
407
epd-waveshare/src/epd2in9d/mod.rs
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
//! A simple Driver for the Waveshare 2.9" D E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! 参考[Waveshare](https://www.waveshare.net/wiki/2.9inch_e-Paper_HAT_%28D%29)的文档/例程进行构建
|
||||||
|
//!
|
||||||
|
//! Specification: https://www.waveshare.net/w/upload/b/b5/2.9inch_e-Paper_%28D%29_Specification.pdf
|
||||||
|
|
||||||
|
use core::slice::from_raw_parts;
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
//The Lookup Tables for the Display
|
||||||
|
mod constants;
|
||||||
|
use crate::epd2in9d::constants::*;
|
||||||
|
|
||||||
|
/// Width of Epd2in9d in pixels
|
||||||
|
pub const WIDTH: u32 = 128;
|
||||||
|
/// Height of Epd2in9d in pixels
|
||||||
|
pub const HEIGHT: u32 = 296;
|
||||||
|
/// EPD_ARRAY of Epd2in9d in pixels
|
||||||
|
/// WIDTH / 8 * HEIGHT
|
||||||
|
pub const EPD_ARRAY: u32 = 4736;
|
||||||
|
/// Default Background Color (white)
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::Black;
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Display with Fullsize buffer for use with the 2in9 EPD D
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display2in9d = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd2in9d driver
|
||||||
|
///
|
||||||
|
pub struct Epd2in9d<'a, SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// SPI
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Color
|
||||||
|
// background_color: Color,
|
||||||
|
color: Color,
|
||||||
|
/// Refresh LUT
|
||||||
|
refresh: RefreshLut,
|
||||||
|
// Storing old data for partial refreshes
|
||||||
|
old_data: &'a [u8],
|
||||||
|
// 标记是否局刷的状态
|
||||||
|
is_partial_refresh: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9d<'_, SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
//panel setting
|
||||||
|
//LUT from OTP,KW-BF KWR-AF BWROTP 0f BWOTP 1f
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PanelSetting, &[0x1f, 0x0D])?;
|
||||||
|
|
||||||
|
//resolution setting
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::ResolutionSetting, &[0x80, 0x01, 0x28])?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::PowerOn)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
//VCOM AND DATA INTERVAL SETTING
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd2in9d<'_, SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
let old_data: &[u8] = &[];
|
||||||
|
let is_partial_refresh = false;
|
||||||
|
|
||||||
|
let mut epd = Epd2in9d {
|
||||||
|
interface,
|
||||||
|
color,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
old_data,
|
||||||
|
is_partial_refresh,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.is_partial_refresh = false;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0xf7])?;
|
||||||
|
self.interface.cmd(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
delay.delay_us(100_000);
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, background_color: Color) {
|
||||||
|
self.color = background_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Color {
|
||||||
|
&self.color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Corresponds to the Display function.
|
||||||
|
// Used to write the data to be displayed to the screen SRAM.
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if self.is_partial_refresh {
|
||||||
|
// Modify local refresh status if full refresh is performed.
|
||||||
|
self.is_partial_refresh = false;
|
||||||
|
}
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, 0xFF, EPD_ARRAY)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?;
|
||||||
|
self.old_data = unsafe { from_raw_parts(buffer.as_ptr(), buffer.len()) };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这个是DisplayPart
|
||||||
|
// Partial refresh write address and data
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if !self.is_partial_refresh {
|
||||||
|
// Initialize only on first call
|
||||||
|
self.set_part_reg(spi, delay)?;
|
||||||
|
self.is_partial_refresh = true;
|
||||||
|
}
|
||||||
|
self.interface.cmd(spi, Command::PartialIn)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::PartialWindow)?;
|
||||||
|
self.interface.data(spi, &[(x - x % 8) as u8])?;
|
||||||
|
self.interface
|
||||||
|
.data(spi, &[(((x - x % 8) + width - 1) - 1) as u8])?;
|
||||||
|
self.interface.data(spi, &[(y / 256) as u8])?;
|
||||||
|
self.interface.data(spi, &[(y % 256) as u8])?;
|
||||||
|
self.interface
|
||||||
|
.data(spi, &[((y + height - 1) / 256) as u8])?;
|
||||||
|
self.interface
|
||||||
|
.data(spi, &[((y + height - 1) % 256 - 1) as u8])?;
|
||||||
|
self.interface.data(spi, &[0x28])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataStartTransmission1, self.old_data)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?;
|
||||||
|
self.old_data = unsafe { from_raw_parts(buffer.as_ptr(), buffer.len()) };
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// actually is the "Turn on Display" sequence
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DisplayRefresh)?;
|
||||||
|
delay.delay_us(1_000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, 0x00, EPD_ARRAY)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, 0xFF, EPD_ARRAY)?;
|
||||||
|
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if let Some(refresh_lut) = refresh_rate {
|
||||||
|
self.refresh = refresh_lut;
|
||||||
|
}
|
||||||
|
self.set_lut_helper(
|
||||||
|
spi, delay, &LUT_VCOM1, &LUT_WW1, &LUT_BW1, &LUT_WB1, &LUT_BB1,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd2in9d<'_, SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// Wake Up Screen
|
||||||
|
///
|
||||||
|
/// After the screen sleeps, it enters deep sleep mode. If you need to refresh the screen while in deep sleep mode, you must first execute awaken().
|
||||||
|
/// Wake the screen.
|
||||||
|
// fn awaken(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// // reset the device
|
||||||
|
// self.interface.reset(delay, 20_000, 2_000);
|
||||||
|
// self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// // panel setting
|
||||||
|
// // LUT from OTP,KW-BF KWR-AF BWROTP 0f BWOTP 1f
|
||||||
|
// self.interface
|
||||||
|
// .cmd_with_data(spi, Command::PanelSetting, &[0x1f])?;
|
||||||
|
|
||||||
|
// // resolution setting
|
||||||
|
// self.interface
|
||||||
|
// .cmd_with_data(spi, Command::ResolutionSetting, &[0x80, 0x01, 0x28])?;
|
||||||
|
|
||||||
|
// // VCOM AND DATA INTERVAL SETTING
|
||||||
|
// self.interface
|
||||||
|
// .cmd(spi, Command::VcomAndDataIntervalSetting)?;
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn set_part_reg(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the EPD driver circuit
|
||||||
|
//TODO: 这里在微雪的例程中反复刷新了3次,后面有显示问题再进行修改
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
// Power settings
|
||||||
|
//TODO: The data in the document is [0x03,0x00,0x2b,0x2b,0x09].
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::PowerSetting,
|
||||||
|
&[0x03, 0x00, 0x2b, 0x2b, 0x03],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Soft start
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||||
|
|
||||||
|
// Panel settings
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PanelSetting, &[0xbf, 0x0D])?;
|
||||||
|
|
||||||
|
// Setting the refresh rate
|
||||||
|
// 3a 100HZ | 29 150Hz | 39 200HZ | 31 171HZ
|
||||||
|
// 3a is used in the example
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::PllControl, &[0x3C])?;
|
||||||
|
|
||||||
|
// Resolution Settings
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::ResolutionSetting, &[0x80, 0x01, 0x28])?;
|
||||||
|
|
||||||
|
// vcom_DC settings
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, None)?;
|
||||||
|
|
||||||
|
// Power on
|
||||||
|
// self.interface.cmd_with_data(
|
||||||
|
// spi,
|
||||||
|
// Command::PowerOn,
|
||||||
|
// &[0x04],
|
||||||
|
// );
|
||||||
|
self.interface.cmd(spi, Command::PowerOn)?;
|
||||||
|
|
||||||
|
// Get the BUSY level, high to continue, low to wait for the screen to respond.
|
||||||
|
//TODO: This is the recommended step in the documentation, but I've ignored it since I've seen other screens that don't wait.
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// vcom and data interval settings
|
||||||
|
// self.interface
|
||||||
|
// .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn set_lut_helper(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
lut_vcom: &[u8],
|
||||||
|
lut_ww: &[u8],
|
||||||
|
lut_bw: &[u8],
|
||||||
|
lut_wb: &[u8],
|
||||||
|
lut_bb: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
let _ = delay;
|
||||||
|
// LUT VCOM
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutForVcom, lut_vcom)?;
|
||||||
|
|
||||||
|
// LUT WHITE to WHITE
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutWhiteToWhite, lut_ww)?;
|
||||||
|
|
||||||
|
// LUT BLACK to WHITE
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutBlackToWhite, lut_bw)?;
|
||||||
|
|
||||||
|
// LUT WHITE to BLACK
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutWhiteToBlack, lut_wb)?;
|
||||||
|
|
||||||
|
// LUT BLACK to BLACK
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::LutBlackToBlack, lut_bb)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
88
epd-waveshare/src/epd3in7/command.rs
Normal file
88
epd-waveshare/src/epd3in7/command.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
//! SPI Commands for the Waveshare 3.7" E-Ink Display
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
/// EPD3IN7 commands
|
||||||
|
///
|
||||||
|
/// Should rarely (never?) be needed directly.
|
||||||
|
///
|
||||||
|
/// For more infos about the addresses and what they are doing look into the pdfs
|
||||||
|
///
|
||||||
|
/// The description of the single commands is mostly taken from EDP3IN7 specification
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
///
|
||||||
|
GateSetting = 0x01,
|
||||||
|
///
|
||||||
|
PowerOff = 0x02,
|
||||||
|
///
|
||||||
|
Sleep2 = 0x07,
|
||||||
|
///
|
||||||
|
GateVoltage = 0x03,
|
||||||
|
///
|
||||||
|
GateVoltageSource = 0x04,
|
||||||
|
///
|
||||||
|
BoosterSoftStartControl = 0x0C,
|
||||||
|
/// After this command initiated, the chip will enter Deep Sleep Mode,
|
||||||
|
/// BUSY pad will keep output high.
|
||||||
|
///
|
||||||
|
/// Note: To exit Deep Sleep Mode, User required to send HWRESET to the driver.
|
||||||
|
DeepSleep = 0x10,
|
||||||
|
///
|
||||||
|
DataEntrySequence = 0x11,
|
||||||
|
/// This command resets commands and parameters to their S/W Reset default values,
|
||||||
|
/// except Deep Sleep Mode.
|
||||||
|
/// During this operation BUSY pad will keep output high.
|
||||||
|
///
|
||||||
|
/// Note: RAM is unaffected by this command.
|
||||||
|
SwReset = 0x12,
|
||||||
|
/// This command selects the Internal or External temperature sensor and offset
|
||||||
|
TemperatureSensorSelection = 0x18,
|
||||||
|
/// Write to temperature register
|
||||||
|
TemperatureSensorWrite = 0x1A,
|
||||||
|
/// Read from temperature register
|
||||||
|
TemperatureSensorRead = 0x1B,
|
||||||
|
/// This command activates Display Update sequence.
|
||||||
|
/// The Display Update sequence option is located at R22h.
|
||||||
|
///
|
||||||
|
/// Note: BUSY pad will output high during operation. User **should not** interrupt this operation
|
||||||
|
/// to avoid corruption of panel images.
|
||||||
|
DisplayUpdateSequence = 0x20,
|
||||||
|
/// This command sets a Display Update Sequence option.
|
||||||
|
DisplayUpdateSequenceSetting = 0x22,
|
||||||
|
/// This command will transfer its data to B/W RAM, until another command is written
|
||||||
|
WriteRam = 0x24,
|
||||||
|
/// This command writes VCOM register from MCU interface
|
||||||
|
WriteVcomRegister = 0x2C,
|
||||||
|
/// This command writes LUT register from MCU interface (105 bytes),
|
||||||
|
/// which contains the content of VS [nx-LUT], TP #[nX], RP #[n]
|
||||||
|
WriteLutRegister = 0x32,
|
||||||
|
///
|
||||||
|
DisplayOption = 0x37,
|
||||||
|
///
|
||||||
|
BorderWaveformControl = 0x3C,
|
||||||
|
/// This command specifies the start/end positions of the window address in the X direction,
|
||||||
|
/// by an address unit of RAM.
|
||||||
|
SetRamXAddressStartEndPosition = 0x44,
|
||||||
|
/// This command specifies the start/end positions of the window address in the Y direction,
|
||||||
|
/// by an address unit of RAM.
|
||||||
|
SetRamYAddressStartEndPosition = 0x45,
|
||||||
|
///
|
||||||
|
AutoWriteRedRamRegularPattern = 0x46,
|
||||||
|
///
|
||||||
|
AutoWriteBwRamRegularPattern = 0x47,
|
||||||
|
/// This command makes the initial settings for the RAM X address in the address counter (AC)
|
||||||
|
SetRamXAddressCounter = 0x4E,
|
||||||
|
/// This command makes the initial settings for the RAM Y address in the address counter (AC)
|
||||||
|
SetRamYAddressCounter = 0x4F,
|
||||||
|
///
|
||||||
|
Sleep = 0x50,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
29
epd-waveshare/src/epd3in7/constants.rs
Normal file
29
epd-waveshare/src/epd3in7/constants.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// This LUT clears the whole display during updates.
|
||||||
|
pub(crate) const LUT_1GRAY_GC: [u8; 105] = [
|
||||||
|
0x2A, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //1
|
||||||
|
0x05, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //2
|
||||||
|
0x2A, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //3
|
||||||
|
0x05, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //4
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //5
|
||||||
|
0x00, 0x02, 0x03, 0x0A, 0x00, 0x02, 0x06, 0x0A, 0x05, 0x00, //6
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //7
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //8
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //9
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //10
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22,
|
||||||
|
];
|
||||||
|
|
||||||
|
// This LUT updates only the pixels that have changed.
|
||||||
|
pub(crate) const LUT_1GRAY_DU: [u8; 105] = [
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //1
|
||||||
|
0x01, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //2
|
||||||
|
0x0A, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //3
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //4
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //5
|
||||||
|
0x00, 0x00, 0x05, 0x05, 0x00, 0x05, 0x03, 0x05, 0x05, 0x00, //6
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //7
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //8
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //9
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //10
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22,
|
||||||
|
];
|
266
epd-waveshare/src/epd3in7/mod.rs
Normal file
266
epd-waveshare/src/epd3in7/mod.rs
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
//! A simple Driver for the Waveshare 3.7" E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/3.7inch_e-Paper_HAT),
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
mod constants;
|
||||||
|
|
||||||
|
use self::command::Command;
|
||||||
|
use self::constants::*;
|
||||||
|
|
||||||
|
use crate::buffer_len;
|
||||||
|
use crate::color::Color;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
/// Width of the display.
|
||||||
|
pub const WIDTH: u32 = 280;
|
||||||
|
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 480;
|
||||||
|
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// Display with Fullsize buffer for use with the 3in7 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display3in7 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// EPD3in7 driver
|
||||||
|
pub struct EPD3in7<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
background_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for EPD3in7<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// reset the device
|
||||||
|
self.interface.reset(delay, 30, 10);
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::SwReset)?;
|
||||||
|
delay.delay_us(300000u32);
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::AutoWriteRedRamRegularPattern, &[0xF7])?;
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::AutoWriteBwRamRegularPattern, &[0xF7])?;
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::GateSetting, &[0xDF, 0x01, 0x00])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::GateVoltage, &[0x00])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::GateVoltageSource, &[0x41, 0xA8, 0x32])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataEntrySequence, &[0x03])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BorderWaveformControl, &[0x03])?;
|
||||||
|
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::BoosterSoftStartControl,
|
||||||
|
&[0xAE, 0xC7, 0xC3, 0xC0, 0xC0],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::TemperatureSensorSelection, &[0x80])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteVcomRegister, &[0x44])?;
|
||||||
|
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DisplayOption,
|
||||||
|
&[0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamXAddressStartEndPosition,
|
||||||
|
&[0x00, 0x00, 0x17, 0x01],
|
||||||
|
)?;
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::SetRamYAddressStartEndPosition,
|
||||||
|
&[0x00, 0x00, 0xDF, 0x01],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DisplayUpdateSequenceSetting, &[0xCF])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, Some(RefreshLut::Full))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for EPD3in7<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let mut epd = EPD3in7 {
|
||||||
|
interface: DisplayInterface::new(busy, dc, rst, delay_us),
|
||||||
|
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd_with_data(spi, Command::Sleep, &[0xF7])?;
|
||||||
|
self.interface.cmd(spi, Command::PowerOff)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::Sleep2, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, color: Self::DisplayColor) {
|
||||||
|
self.background_color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Self::DisplayColor {
|
||||||
|
&self.background_color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
assert!(buffer.len() == buffer_len(WIDTH as usize, HEIGHT as usize));
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[0x00, 0x00])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamYAddressCounter, &[0x00, 0x00])?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
//self.interface
|
||||||
|
// .cmd_with_data(spi, Command::WRITE_LUT_REGISTER, &LUT_1GRAY_GC)?;
|
||||||
|
self.interface.cmd(spi, Command::DisplayUpdateSequence)?;
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamXAddressCounter, &[0x00, 0x00])?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::SetRamYAddressCounter, &[0x00, 0x00])?;
|
||||||
|
|
||||||
|
let color = self.background_color.get_byte_value();
|
||||||
|
self.interface.cmd(spi, Command::WriteRam)?;
|
||||||
|
self.interface.data_x_times(spi, color, WIDTH * HEIGHT)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
let buffer = match refresh_rate {
|
||||||
|
Some(RefreshLut::Full) | None => &LUT_1GRAY_GC,
|
||||||
|
Some(RefreshLut::Quick) => &LUT_1GRAY_DU,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
177
epd-waveshare/src/epd4in2/command.rs
Normal file
177
epd-waveshare/src/epd4in2/command.rs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
//! SPI Commands for the Waveshare 4.2" E-Ink Display
|
||||||
|
use crate::traits;
|
||||||
|
/// EPD4IN2 commands
|
||||||
|
///
|
||||||
|
/// Should rarely (never?) be needed directly.
|
||||||
|
///
|
||||||
|
/// For more infos about the addresses and what they are doing look into the pdfs
|
||||||
|
///
|
||||||
|
/// The description of the single commands is mostly taken from IL0398.pdf
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift direction, booster switch, soft reset
|
||||||
|
/// One Byte of Data:
|
||||||
|
/// 0x0F Red Mode, LUT from OTP
|
||||||
|
/// 0x1F B/W Mode, LUT from OTP
|
||||||
|
/// 0x2F Red Mode, LUT set by registers
|
||||||
|
/// 0x3F B/W Mode, LUT set by registers
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
/// selecting internal and external power
|
||||||
|
/// self.send_data(0x03)?; //VDS_EN, VDG_EN
|
||||||
|
/// self.send_data(0x00)?; //VCOM_HV, VGHL_LV[1], VGHL_LV[0]
|
||||||
|
/// self.send_data(0x2b)?; //VDH
|
||||||
|
/// self.send_data(0x2b)?; //VDL
|
||||||
|
/// self.send_data(0xff)?; //VDHR
|
||||||
|
PowerSetting = 0x01,
|
||||||
|
/// After the Power Off command, the driver will power off following the Power Off Sequence. 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: floating.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 0x03,
|
||||||
|
/// Turning On the Power
|
||||||
|
PowerOn = 0x04,
|
||||||
|
/// This command enables the internal bandgap, which will be cleared by the next POF.
|
||||||
|
PowerOnMeasure = 0x05,
|
||||||
|
/// Starting data transmission
|
||||||
|
/// 3-times: self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f
|
||||||
|
BoosterSoftStart = 0x06,
|
||||||
|
/// After this command is transmitted, the chip would enter the deep-sleep mode to save power.
|
||||||
|
///
|
||||||
|
/// The deep sleep mode would return to standby by hardware reset.
|
||||||
|
///
|
||||||
|
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
/// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data
|
||||||
|
/// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel.
|
||||||
|
///
|
||||||
|
/// - In B/W mode, this command writes “OLD” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “B/W” data to SRAM.
|
||||||
|
/// - In Program mode, this command writes “OTP” data to SRAM for programming.
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
/// Stopping data transmission
|
||||||
|
DataStop = 0x11,
|
||||||
|
/// While user sent this command, driver will refresh display (data/VCOM) according to SRAM data and LUT.
|
||||||
|
///
|
||||||
|
/// After Display Refresh command, BUSY_N signal will become “0” and the refreshing of panel starts.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
/// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data
|
||||||
|
/// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel.
|
||||||
|
/// - In B/W mode, this command writes “NEW” data to SRAM.
|
||||||
|
/// - In B/W/Red mode, this command writes “RED” data to SRAM.
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
/// This command stores VCOM Look-Up Table with 7 groups of data. Each group contains information for one state and is stored
|
||||||
|
/// with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
/// This command stores White-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutWhiteToWhite = 0x21,
|
||||||
|
/// This command stores Black-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutBlackToWhite = 0x22,
|
||||||
|
/// This command stores White-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutWhiteToBlack = 0x23,
|
||||||
|
/// This command stores Black-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is
|
||||||
|
/// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat.
|
||||||
|
///
|
||||||
|
/// from IL0373
|
||||||
|
LutBlackToBlack = 0x24,
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// Selects the Internal or External temperature sensor and offset
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
/// Write External Temperature Sensor
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// Read External Temperature Sensor
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
TemperatureSensorRead = 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)
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR).
|
||||||
|
ResolutionSetting = 0x61,
|
||||||
|
/// This command defines the Fist Active Gate and First Active Source of active channels.
|
||||||
|
GsstSetting = 0x65,
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 0x001.
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
Revision = 0x70,
|
||||||
|
/// Read Flags. This command reads the IC status
|
||||||
|
/// PTL, I2C_ERR, I2C_BUSY, DATA, PON, POF, BUSY
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
GetStatus = 0x71,
|
||||||
|
/// Automatically measure VCOM. This command reads the IC status
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value
|
||||||
|
///
|
||||||
|
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// Set VCM_DC
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
/// This command sets partial window
|
||||||
|
PartialWindow = 0x90,
|
||||||
|
/// This command makes the display enter partial mode
|
||||||
|
PartialIn = 0x91,
|
||||||
|
/// This command makes the display exit partial mode and enter normal mode
|
||||||
|
PartialOut = 0x92,
|
||||||
|
/// After this command is issued, the chip would enter the program mode.
|
||||||
|
///
|
||||||
|
/// After the programming procedure completed, a hardware reset is necessary for leaving program mode.
|
||||||
|
///
|
||||||
|
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5.
|
||||||
|
ProgramMode = 0xA0,
|
||||||
|
/// After this command is transmitted, the programming state machine would be activated.
|
||||||
|
///
|
||||||
|
/// The BUSY flag would fall to 0 until the programming is completed.
|
||||||
|
ActiveProgramming = 0xA1,
|
||||||
|
/// The command is used for reading the content of OTP for checking the data of programming.
|
||||||
|
///
|
||||||
|
/// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF.
|
||||||
|
ReadOtp = 0xA2,
|
||||||
|
/// This command is set for saving power during fresh period. If the output voltage of VCOM / Source is from negative to positive or
|
||||||
|
/// from positive to negative, the power saving mechanism will be activated. The active period width is defined by the following two
|
||||||
|
/// parameters.
|
||||||
|
PowerSaving = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PowerSaving.address(), 0xE3);
|
||||||
|
|
||||||
|
assert_eq!(Command::PanelSetting.address(), 0x00);
|
||||||
|
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
115
epd-waveshare/src/epd4in2/constants.rs
Normal file
115
epd-waveshare/src/epd4in2/constants.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
//! This file contains look-up-tables used to set voltages used during
|
||||||
|
//! various categories of pixel refreshes.
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_VCOM0: [u8; 44] = [
|
||||||
|
// The commented-out line below was used in a Ben Krasnow video explaining
|
||||||
|
// partial refreshes.
|
||||||
|
// 0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [
|
||||||
|
0x00, 0x0E, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WW: [u8; 42] =[
|
||||||
|
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||||
|
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WW_QUICK: [u8; 42] =[
|
||||||
|
0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BW: [u8; 42] =[
|
||||||
|
0x40, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||||
|
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BW_QUICK: [u8; 42] =[
|
||||||
|
0xA0, 0x0E, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BB: [u8; 42] =[
|
||||||
|
0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||||
|
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_BB_QUICK: [u8; 42] =[
|
||||||
|
0x50, 0x0E, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WB: [u8; 42] =[
|
||||||
|
0x80, 0x17, 0x00, 0x00, 0x00, 0x02,
|
||||||
|
0x90, 0x17, 0x17, 0x00, 0x00, 0x02,
|
||||||
|
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01,
|
||||||
|
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_WB_QUICK: [u8; 42] =[
|
||||||
|
0x50, 0x0E, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
606
epd-waveshare/src/epd4in2/mod.rs
Normal file
606
epd-waveshare/src/epd4in2/mod.rs
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
//! A simple Driver for the Waveshare 4.2" E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/4.2inch_e-Paper_Module),
|
||||||
|
//! [Ben Krasnows partial Refresh tips](https://benkrasnow.blogspot.de/2017/10/fast-partial-refresh-on-42-e-paper.html) and
|
||||||
|
//! the driver documents in the `pdfs`-folder as orientation.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{
|
||||||
|
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
//!};
|
||||||
|
//!use epd_waveshare::{epd4in2::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd4in2::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!let mut display = Display4in2::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing a line
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
//! .draw(&mut display);
|
||||||
|
//!
|
||||||
|
//! // Display updated frame
|
||||||
|
//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! BE CAREFUL! The screen can get ghosting/burn-ins through the Partial Fast Update Drawing.
|
||||||
|
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
//The Lookup Tables for the Display
|
||||||
|
mod constants;
|
||||||
|
use crate::epd4in2::constants::*;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 400;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 300;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 4in2 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display4in2 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Epd4in2 driver
|
||||||
|
///
|
||||||
|
pub struct Epd4in2<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
/// Refresh LUT
|
||||||
|
refresh: RefreshLut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd4in2<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// set the power settings
|
||||||
|
self.interface.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::PowerSetting,
|
||||||
|
&[0x03, 0x00, 0x2b, 0x2b, 0xff],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// start the booster
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||||
|
|
||||||
|
// power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// set the panel settings
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x3F])?;
|
||||||
|
|
||||||
|
// Set Frequency, 200 Hz didn't work on my board
|
||||||
|
// 150Hz and 171Hz wasn't tested yet
|
||||||
|
// TODO: Test these other frequencies
|
||||||
|
// 3A 100HZ 29 150Hz 39 200HZ 31 171HZ DEFAULT: 3c 50Hz
|
||||||
|
self.cmd_with_data(spi, Command::PllControl, &[0x3A])?;
|
||||||
|
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?;
|
||||||
|
|
||||||
|
//VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?;
|
||||||
|
|
||||||
|
self.set_lut(spi, delay, None)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd4in2<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd4in2 {
|
||||||
|
interface,
|
||||||
|
color,
|
||||||
|
refresh: RefreshLut::Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating
|
||||||
|
self.command(spi, Command::VcmDcSetting)?; // VCOM to 0V
|
||||||
|
self.command(spi, Command::PanelSetting)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerSetting)?; //VG&VS to 0V fast
|
||||||
|
for _ in 0..4 {
|
||||||
|
self.send_data(spi, &[0x00])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
let color_value = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
if buffer.len() as u32 != width / 8 * height {
|
||||||
|
//TODO: panic!! or sth like that
|
||||||
|
//return Err("Wrong buffersize");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.command(spi, Command::PartialIn)?;
|
||||||
|
self.command(spi, Command::PartialWindow)?;
|
||||||
|
self.send_data(spi, &[(x >> 8) as u8])?;
|
||||||
|
let tmp = x & 0xf8;
|
||||||
|
self.send_data(spi, &[tmp as u8])?; // x should be the multiple of 8, the last 3 bit will always be ignored
|
||||||
|
let tmp = tmp + width - 1;
|
||||||
|
self.send_data(spi, &[(tmp >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(tmp | 0x07) as u8])?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[(y >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[y as u8])?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[((y + height - 1) >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y + height - 1) as u8])?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[0x01])?; // Gates scan both inside and outside of the partial window. (default)
|
||||||
|
|
||||||
|
//TODO: handle dtm somehow
|
||||||
|
let is_dtm1 = false;
|
||||||
|
if is_dtm1 {
|
||||||
|
self.command(spi, Command::DataStartTransmission1)? //TODO: check if data_start transmission 1 also needs "old"/background data here
|
||||||
|
} else {
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_data(spi, buffer)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PartialOut)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
let color_value = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
if let Some(refresh_lut) = refresh_rate {
|
||||||
|
self.refresh = refresh_lut;
|
||||||
|
}
|
||||||
|
match self.refresh {
|
||||||
|
RefreshLut::Full => {
|
||||||
|
self.set_lut_helper(spi, delay, &LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB)
|
||||||
|
}
|
||||||
|
RefreshLut::Quick => self.set_lut_helper(
|
||||||
|
spi,
|
||||||
|
delay,
|
||||||
|
&LUT_VCOM0_QUICK,
|
||||||
|
&LUT_WW_QUICK,
|
||||||
|
&LUT_BW_QUICK,
|
||||||
|
&LUT_WB_QUICK,
|
||||||
|
&LUT_BB_QUICK,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd4in2<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::ResolutionSetting)?;
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn set_lut_helper(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
lut_vcom: &[u8],
|
||||||
|
lut_ww: &[u8],
|
||||||
|
lut_bw: &[u8],
|
||||||
|
lut_wb: &[u8],
|
||||||
|
lut_bb: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// LUT VCOM
|
||||||
|
self.cmd_with_data(spi, Command::LutForVcom, lut_vcom)?;
|
||||||
|
|
||||||
|
// LUT WHITE to WHITE
|
||||||
|
self.cmd_with_data(spi, Command::LutWhiteToWhite, lut_ww)?;
|
||||||
|
|
||||||
|
// LUT BLACK to WHITE
|
||||||
|
self.cmd_with_data(spi, Command::LutBlackToWhite, lut_bw)?;
|
||||||
|
|
||||||
|
// LUT WHITE to BLACK
|
||||||
|
self.cmd_with_data(spi, Command::LutWhiteToBlack, lut_wb)?;
|
||||||
|
|
||||||
|
// LUT BLACK to BLACK
|
||||||
|
self.cmd_with_data(spi, Command::LutBlackToBlack, lut_bb)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function. Sets up the display to send pixel data to a custom
|
||||||
|
/// starting point.
|
||||||
|
pub fn shift_display(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.send_data(spi, &[(x >> 8) as u8])?;
|
||||||
|
let tmp = x & 0xf8;
|
||||||
|
self.send_data(spi, &[tmp as u8])?; // x should be the multiple of 8, the last 3 bit will always be ignored
|
||||||
|
let tmp = tmp + width - 1;
|
||||||
|
self.send_data(spi, &[(tmp >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(tmp | 0x07) as u8])?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[(y >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[y as u8])?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[((y + height - 1) >> 8) as u8])?;
|
||||||
|
self.send_data(spi, &[(y + height - 1) as u8])?;
|
||||||
|
|
||||||
|
self.send_data(spi, &[0x01])?; // Gates scan both inside and outside of the partial window. (default)
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> QuickRefresh<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd4in2<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// To be followed immediately after by `update_old_frame`.
|
||||||
|
fn update_old_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.interface.data(spi, buffer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To be used immediately after `update_old_frame`.
|
||||||
|
fn update_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
|
||||||
|
self.interface.data(spi, buffer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a wrapper around `display_frame` for using this device as a true
|
||||||
|
/// `QuickRefresh` device.
|
||||||
|
fn display_new_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.display_frame(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is wrapper around `update_new_frame` and `display_frame` for using
|
||||||
|
/// this device as a true `QuickRefresh` device.
|
||||||
|
///
|
||||||
|
/// To be used immediately after `update_old_frame`.
|
||||||
|
fn update_and_display_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_new_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_old_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
if buffer.len() as u32 != width / 8 * height {
|
||||||
|
//TODO: panic!! or sth like that
|
||||||
|
//return Err("Wrong buffersize");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::PartialIn)?;
|
||||||
|
self.interface.cmd(spi, Command::PartialWindow)?;
|
||||||
|
|
||||||
|
self.shift_display(spi, x, y, width, height)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
|
||||||
|
self.interface.data(spi, buffer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Always call `update_partial_old_frame` before this, with buffer-updating code
|
||||||
|
/// between the calls.
|
||||||
|
fn update_partial_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
if buffer.len() as u32 != width / 8 * height {
|
||||||
|
//TODO: panic!! or sth like that
|
||||||
|
//return Err("Wrong buffersize");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.shift_display(spi, x, y, width, height)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
|
||||||
|
self.interface.data(spi, buffer)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::PartialOut)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
let color_value = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::PartialIn)?;
|
||||||
|
self.interface.cmd(spi, Command::PartialWindow)?;
|
||||||
|
|
||||||
|
self.shift_display(spi, x, y, width, height)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, width / 8 * height)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, width / 8 * height)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::PartialOut)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 400);
|
||||||
|
assert_eq!(HEIGHT, 300);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
150
epd-waveshare/src/epd5in65f/command.rs
Normal file
150
epd-waveshare/src/epd5in65f/command.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
//! SPI Commands for the Waveshare 7.5" E-Ink Display
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
/// EPD6in65f 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||||
|
/// direction, booster switch, soft reset.
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 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.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 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".
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
/// Starting data transmission
|
||||||
|
BoosterSoftStart = 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.
|
||||||
|
DeepSleep = 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
|
||||||
|
DataStartTransmission1 = 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.
|
||||||
|
DataStop = 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.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
|
||||||
|
/// Image Process Command
|
||||||
|
ImageProcess = 0x13,
|
||||||
|
|
||||||
|
/// This command builds the VCOM Look-Up Table (LUTC).
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
/// This command builds the Black Look-Up Table (LUTB).
|
||||||
|
LutBlack = 0x21,
|
||||||
|
/// This command builds the White Look-Up Table (LUTW).
|
||||||
|
LutWhite = 0x22,
|
||||||
|
/// This command builds the Gray1 Look-Up Table (LUTG1).
|
||||||
|
LutGray1 = 0x23,
|
||||||
|
/// This command builds the Gray2 Look-Up Table (LUTG2).
|
||||||
|
LutGray2 = 0x24,
|
||||||
|
/// This command builds the Red0 Look-Up Table (LUTR0).
|
||||||
|
LutRed0 = 0x25,
|
||||||
|
/// This command builds the Red1 Look-Up Table (LUTR1).
|
||||||
|
LutRed1 = 0x26,
|
||||||
|
/// This command builds the Red2 Look-Up Table (LUTR2).
|
||||||
|
LutRed2 = 0x27,
|
||||||
|
/// This command builds the Red3 Look-Up Table (LUTR3).
|
||||||
|
LutRed3 = 0x28,
|
||||||
|
/// This command builds the XON Look-Up Table (LUTXON).
|
||||||
|
LutXon = 0x29,
|
||||||
|
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// This command selects the Internal or External temperature sensor.
|
||||||
|
TemperatureCalibration = 0x41,
|
||||||
|
/// This command could write data to the external temperature sensor.
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// This command could read data from the external temperature sensor.
|
||||||
|
TemperatureSensorRead = 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).
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn
|
||||||
|
/// the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority
|
||||||
|
/// than the RES\[1:0\] in R00H (PSR).
|
||||||
|
TconResolution = 0x61,
|
||||||
|
/// This command defines MCU host direct access external memory mode.
|
||||||
|
//SpiFlashControl = 0x65,
|
||||||
|
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||||
|
//Revision = 0x70,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
GetStatus = 0x71,
|
||||||
|
|
||||||
|
/// This command implements related VCOM sensing setting.
|
||||||
|
//AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value.
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets `VCOM_DC` value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
// /// This is in all the Waveshare controllers for EPD6in65f, but it's not documented
|
||||||
|
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
|
||||||
|
FlashMode = 0xE3,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
266
epd-waveshare/src/epd5in65f/mod.rs
Normal file
266
epd-waveshare/src/epd5in65f/mod.rs
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
//! A simple Driver for the Waveshare 6.65 inch (F) E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! # References
|
||||||
|
//!
|
||||||
|
//! - [Datasheet](https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F))
|
||||||
|
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_5in65f.c)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd5in65f.py)
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::OctColor;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 5in65f EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display5in65f = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize * 4) },
|
||||||
|
OctColor,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 600;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 448;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: OctColor = OctColor::White;
|
||||||
|
/// Default mode of writing data (single byte vs blockwise)
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// Epd5in65f driver
|
||||||
|
///
|
||||||
|
pub struct Epd5in65f<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: OctColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in65f<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0xEF, 0x08])?;
|
||||||
|
self.cmd_with_data(spi, Command::PowerSetting, &[0x37, 0x00, 0x23, 0x23])?;
|
||||||
|
self.cmd_with_data(spi, Command::PowerOffSequenceSetting, &[0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0xC7, 0xC7, 0x1D])?;
|
||||||
|
self.cmd_with_data(spi, Command::PllControl, &[0x3C])?;
|
||||||
|
self.cmd_with_data(spi, Command::TemperatureSensor, &[0x00])?;
|
||||||
|
self.update_vcom(spi)?;
|
||||||
|
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::FlashMode, &[0xAA])?;
|
||||||
|
|
||||||
|
delay.delay_us(100_000);
|
||||||
|
|
||||||
|
self.update_vcom(spi)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in65f<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = OctColor;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd5in65f { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.update_vcom(spi)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission1, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
let bg = OctColor::colors_byte(self.color, self.color);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.update_vcom(spi)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, bg, WIDTH * HEIGHT / 2)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, color: OctColor) {
|
||||||
|
self.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &OctColor {
|
||||||
|
&self.color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, true);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd5in65f<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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_busy_low(&mut self, delay: &mut DELAY) {
|
||||||
|
self.interface.wait_until_idle(delay, false);
|
||||||
|
}
|
||||||
|
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::TconResolution)?;
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_vcom(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let bg_color = (self.color.get_nibble() & 0b111) << 5;
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17 | bg_color])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 600);
|
||||||
|
assert_eq!(HEIGHT, 448);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, OctColor::White);
|
||||||
|
}
|
||||||
|
}
|
134
epd-waveshare/src/epd5in83_v2/command.rs
Normal file
134
epd-waveshare/src/epd5in83_v2/command.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//! SPI Commands for the Waveshare 5.83" E-Ink Display
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
/// Epd5in83 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||||
|
/// direction, booster switch, soft reset.
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 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.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 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".
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
/// Starting data transmission
|
||||||
|
BoosterSoftStart = 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.
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
|
||||||
|
/// This command starts transmitting B/W data and write them into SRAM. To complete data
|
||||||
|
/// transmission, commands Display Refresh or Data Start Transmission2 must be issued. Then the chip will start to
|
||||||
|
/// send data/VCOM for panel.
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
|
||||||
|
/// This command starts transmitting RED data and write them into SRAM. To complete data
|
||||||
|
/// transmission, command Display refresh must be issued. Then the chip will start to
|
||||||
|
/// send data/VCOM for panel.
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
DataStop = 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.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
|
||||||
|
/// Enables or disables Dual SPI mode
|
||||||
|
DualSPI = 0x15,
|
||||||
|
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
TemperatureSensorCalibration = 0x40,
|
||||||
|
/// This command selects the Internal or External temperature sensor.
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
/// This command could write data to the external temperature sensor.
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// This command could read data from the external temperature sensor.
|
||||||
|
TemperatureSensorRead = 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).
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn
|
||||||
|
/// the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority
|
||||||
|
/// than the RES\[1:0\] in R00H (PSR).
|
||||||
|
TconResolution = 0x61,
|
||||||
|
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||||
|
Revision = 0x70,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
GetStatus = 0x71,
|
||||||
|
|
||||||
|
/// This command implements related VCOM sensing setting.
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value.
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets `VCOM_DC` value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
|
||||||
|
/// Sets window size for the partial update
|
||||||
|
PartialWindow = 0x90,
|
||||||
|
/// Sets chip into partial update mode
|
||||||
|
PartialIn = 0x91,
|
||||||
|
/// Quits partial update mode
|
||||||
|
PartialOut = 0x92,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
272
epd-waveshare/src/epd5in83_v2/mod.rs
Normal file
272
epd-waveshare/src/epd5in83_v2/mod.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
//! A simple Driver for the Waveshare 5.83" v2 E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! # References
|
||||||
|
//!
|
||||||
|
//! - [Datasheet](https://www.waveshare.com/5.83inch-e-paper-hat.htm)
|
||||||
|
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_5in83_V2.c)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd5in83_V2.py)
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::prelude::WaveshareDisplay;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 5in83 v2 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display5in83 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 648;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 480;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const NUM_DISPLAY_BITS: u32 = WIDTH * HEIGHT / 8;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// Epd5in83 driver
|
||||||
|
///
|
||||||
|
pub struct Epd5in83<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
self.interface.reset(delay, 2000, 50);
|
||||||
|
|
||||||
|
// Set the power settings: VGH=20V,VGL=-20V,VDH=15V,VDL=-15V
|
||||||
|
self.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x07, 0x3F, 0x3F])?;
|
||||||
|
|
||||||
|
// Power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// Set the panel settings: BWOTP
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x1F])?;
|
||||||
|
|
||||||
|
// Set the real resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
// Disable dual SPI
|
||||||
|
self.cmd_with_data(spi, Command::DualSPI, &[0x00])?;
|
||||||
|
|
||||||
|
// Set Vcom and data interval
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x10, 0x07])?;
|
||||||
|
|
||||||
|
// Set S2G and G2S non-overlap periods to 12 (default)
|
||||||
|
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd5in83 { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
let color_value = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.interface
|
||||||
|
.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, 0xFF, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, 0x00, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::TconResolution)?;
|
||||||
|
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, 648);
|
||||||
|
assert_eq!(HEIGHT, 480);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
134
epd-waveshare/src/epd5in83b_v2/command.rs
Normal file
134
epd-waveshare/src/epd5in83b_v2/command.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//! SPI Commands for the Waveshare 5.83" E-Ink Display
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
/// Epd5in83 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||||
|
/// direction, booster switch, soft reset.
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 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.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 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".
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
/// Starting data transmission
|
||||||
|
BoosterSoftStart = 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.
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
|
||||||
|
/// This command starts transmitting B/W data and write them into SRAM. To complete data
|
||||||
|
/// transmission, commands Display Refresh or Data Start Transmission2 must be issued. Then the chip will start to
|
||||||
|
/// send data/VCOM for panel.
|
||||||
|
DataStartTransmission1 = 0x10,
|
||||||
|
|
||||||
|
/// This command starts transmitting RED data and write them into SRAM. To complete data
|
||||||
|
/// transmission, command Display refresh must be issued. Then the chip will start to
|
||||||
|
/// send data/VCOM for panel.
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
DataStop = 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.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
|
||||||
|
/// Enables or disables Dual SPI mode
|
||||||
|
DualSPI = 0x15,
|
||||||
|
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
TemperatureSensorCalibration = 0x40,
|
||||||
|
/// This command selects the Internal or External temperature sensor.
|
||||||
|
TemperatureSensorSelection = 0x41,
|
||||||
|
/// This command could write data to the external temperature sensor.
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// This command could read data from the external temperature sensor.
|
||||||
|
TemperatureSensorRead = 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).
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn
|
||||||
|
/// the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority
|
||||||
|
/// than the RES\[1:0\] in R00H (PSR).
|
||||||
|
TconResolution = 0x61,
|
||||||
|
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||||
|
Revision = 0x70,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
GetStatus = 0x71,
|
||||||
|
|
||||||
|
/// This command implements related VCOM sensing setting.
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value.
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets `VCOM_DC` value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
|
||||||
|
/// Sets window size for the partial update
|
||||||
|
PartialWindow = 0x90,
|
||||||
|
/// Sets chip into partial update mode
|
||||||
|
PartialIn = 0x91,
|
||||||
|
/// Quits partial update mode
|
||||||
|
PartialOut = 0x92,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
352
epd-waveshare/src/epd5in83b_v2/mod.rs
Normal file
352
epd-waveshare/src/epd5in83b_v2/mod.rs
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
//! A simple Driver for the Waveshare 5.83" (B) v2 E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! # References
|
||||||
|
//!
|
||||||
|
//! - [Datasheet](https://www.waveshare.com/5.83inch-e-Paper-B.htm)
|
||||||
|
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_5in83b_V2.c)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd5in83b_V2.py)
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::prelude::{TriColor, WaveshareDisplay, WaveshareThreeColorDisplay};
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 5in83b v2 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display5in83 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize * 2) },
|
||||||
|
TriColor,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 648;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 480;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const NUM_DISPLAY_BITS: u32 = WIDTH / 8 * HEIGHT;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// Epd7in5 driver
|
||||||
|
///
|
||||||
|
pub struct Epd5in83<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// Start the booster
|
||||||
|
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x1e, 0x17])?;
|
||||||
|
|
||||||
|
// Set the power settings: VGH=20V,VGL=-20V,VDH=15V,VDL=-15V
|
||||||
|
self.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x07, 0x3F, 0x3F])?;
|
||||||
|
|
||||||
|
// Power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// Set the panel settings: BWROTP
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x0F])?;
|
||||||
|
|
||||||
|
// Set the real resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
// Disable dual SPI
|
||||||
|
self.cmd_with_data(spi, Command::DualSPI, &[0x00])?;
|
||||||
|
|
||||||
|
// Set Vcom and data interval
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x11, 0x07])?;
|
||||||
|
|
||||||
|
// Set S2G and G2S non-overlap periods to 12 (default)
|
||||||
|
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission1, black)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission2, chromatic)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd5in83 { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.update_achromatic_frame(spi, delay, buffer)?;
|
||||||
|
let color = self.color.get_byte_value();
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
if buffer.len() as u32 != width / 8 * height {
|
||||||
|
//TODO panic or error
|
||||||
|
}
|
||||||
|
|
||||||
|
let hrst_upper = (x / 8) as u8 >> 6;
|
||||||
|
let hrst_lower = ((x / 8) << 3) as u8;
|
||||||
|
let hred_upper = ((x + width) / 8) as u8 >> 6;
|
||||||
|
let hred_lower = (((x + width) / 8) << 3) as u8 & 0b111;
|
||||||
|
let vrst_upper = (y >> 8) as u8;
|
||||||
|
let vrst_lower = y as u8;
|
||||||
|
let vred_upper = ((y + height) >> 8) as u8;
|
||||||
|
let vred_lower = (y + height) as u8;
|
||||||
|
let pt_scan = 0x01; // Gates scan both inside and outside of the partial window. (default)
|
||||||
|
|
||||||
|
self.command(spi, Command::PartialIn)?;
|
||||||
|
self.command(spi, Command::PartialWindow)?;
|
||||||
|
self.send_data(
|
||||||
|
spi,
|
||||||
|
&[
|
||||||
|
hrst_upper, hrst_lower, hred_upper, hred_lower, vrst_upper, vrst_lower, vred_upper,
|
||||||
|
vred_lower, pt_scan,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.send_data(spi, buffer)?;
|
||||||
|
|
||||||
|
let color = TriColor::Black.get_byte_value(); //We need it black, so red channel will be rendered transparent
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, color, width * height / 8)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PartialOut)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// The Waveshare controllers all implement clear using 0x33
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, 0xFF, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, 0x00, NUM_DISPLAY_BITS)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd5in83<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::TconResolution)?;
|
||||||
|
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, 648);
|
||||||
|
assert_eq!(HEIGHT, 480);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
55
epd-waveshare/src/epd7in3f/command.rs
Normal file
55
epd-waveshare/src/epd7in3f/command.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
#[allow(dead_code, clippy::upper_case_acronyms)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
Ox00 = 0x00,
|
||||||
|
Ox01 = 0x01,
|
||||||
|
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
Ox03 = 0x03,
|
||||||
|
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
Ox05 = 0x05,
|
||||||
|
Ox06 = 0x06,
|
||||||
|
|
||||||
|
DeepSleep = 0x07,
|
||||||
|
|
||||||
|
Ox08 = 0x08,
|
||||||
|
|
||||||
|
DataStartTransmission = 0x10,
|
||||||
|
|
||||||
|
DataFresh = 0x12,
|
||||||
|
|
||||||
|
IPC = 0x13,
|
||||||
|
|
||||||
|
Ox30 = 0x30,
|
||||||
|
|
||||||
|
TSE = 0x41,
|
||||||
|
|
||||||
|
Ox50 = 0x50,
|
||||||
|
Ox60 = 0x60,
|
||||||
|
Ox61 = 0x61,
|
||||||
|
|
||||||
|
Ox82 = 0x82,
|
||||||
|
Ox84 = 0x84,
|
||||||
|
|
||||||
|
CMDH = 0xAA,
|
||||||
|
|
||||||
|
AGID = 0x86,
|
||||||
|
|
||||||
|
CCSET = 0xE0,
|
||||||
|
|
||||||
|
OxE3 = 0xE3,
|
||||||
|
|
||||||
|
TSSET = 0xE6,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
275
epd-waveshare/src/epd7in3f/mod.rs
Normal file
275
epd-waveshare/src/epd7in3f/mod.rs
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
//! A simple Driver for the Waveshare 7.3inch e-Paper HAT (F) Display via SPI
|
||||||
|
//!
|
||||||
|
//! # References
|
||||||
|
//!
|
||||||
|
//! - [Datasheet](https://www.waveshare.com/wiki/7.3inch_e-Paper_HAT_(F))
|
||||||
|
//! - [Waveshare C driver](https://github.com/waveshareteam/e-Paper/blob/8be47b27f1a6808fd82ea9ceeac04c172e4ee9a8/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_7in3f.c)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshareteam/e-Paper/blob/8be47b27f1a6808fd82ea9ceeac04c172e4ee9a8/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd7in3f.py)
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
buffer_len,
|
||||||
|
color::OctColor,
|
||||||
|
interface::DisplayInterface,
|
||||||
|
traits::{InternalWiAdditions, WaveshareDisplay},
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::command::Command;
|
||||||
|
|
||||||
|
mod command;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 7in3f EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display7in3f = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize * 4) },
|
||||||
|
OctColor,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 800;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 480;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: OctColor = OctColor::White;
|
||||||
|
/// Default mode of writing data (single byte vs blockwise)
|
||||||
|
const SINGLE_BYTE_WRITE: bool = true;
|
||||||
|
|
||||||
|
/// Epd57n3f driver
|
||||||
|
pub struct Epd7in3f<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: OctColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in3f<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), <SPI>::Error> {
|
||||||
|
self.interface.reset(delay, 20_000, 2_000);
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
delay.delay_ms(30);
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::CMDH, &[0x49, 0x55, 0x20, 0x08, 0x09, 0x18])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox01, &[0x3F, 0x00, 0x32, 0x2A, 0x0E, 0x2A])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox00, &[0x5F, 0x69])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox03, &[0x00, 0x54, 0x00, 0x44])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox05, &[0x40, 0x1F, 0x1F, 0x2C])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox06, &[0x6F, 0x1F, 0x1F, 0x22])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox08, &[0x6F, 0x1F, 0x1F, 0x22])?;
|
||||||
|
self.cmd_with_data(spi, Command::IPC, &[0x00, 0x04])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox30, &[0x3C])?;
|
||||||
|
self.cmd_with_data(spi, Command::TSE, &[0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox50, &[0x3F])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox60, &[0x02, 0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox61, &[0x03, 0x20, 0x01, 0xE0])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox82, &[0x1E])?;
|
||||||
|
self.cmd_with_data(spi, Command::Ox84, &[0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::AGID, &[0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::OxE3, &[0x2F])?;
|
||||||
|
self.cmd_with_data(spi, Command::CCSET, &[0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::TSSET, &[0x00])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in3f<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = OctColor;
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, <SPI>::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd7in3f { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), <SPI>::Error> {
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), <SPI>::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, color: Self::DisplayColor) {
|
||||||
|
self.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Self::DisplayColor {
|
||||||
|
&self.color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), <SPI>::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), <SPI>::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), <SPI>::Error> {
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DataFresh, &[0x00])?;
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::PowerOff, &[0x00])?;
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), <SPI>::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), <SPI>::Error> {
|
||||||
|
let bg = OctColor::colors_byte(self.color, self.color);
|
||||||
|
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
self.command(spi, Command::DataStartTransmission)?;
|
||||||
|
self.interface.data_x_times(spi, bg, WIDTH * HEIGHT / 2)?;
|
||||||
|
|
||||||
|
self.display_frame(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<crate::traits::RefreshLut>,
|
||||||
|
) -> Result<(), <SPI>::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), <SPI>::Error> {
|
||||||
|
self.wait_busy_low(delay);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd7in3f<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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_busy_low(&mut self, delay: &mut DELAY) {
|
||||||
|
self.interface.wait_until_idle(delay, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show 7 blocks of color, used for quick testing
|
||||||
|
pub fn show_7block(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
let color_7 = [
|
||||||
|
OctColor::Black,
|
||||||
|
OctColor::White,
|
||||||
|
OctColor::Green,
|
||||||
|
OctColor::Blue,
|
||||||
|
OctColor::Red,
|
||||||
|
OctColor::Yellow,
|
||||||
|
OctColor::Orange,
|
||||||
|
OctColor::White,
|
||||||
|
];
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission)?;
|
||||||
|
for _ in 0..240 {
|
||||||
|
for color in color_7.iter().take(4) {
|
||||||
|
for _ in 0..100 {
|
||||||
|
self.interface
|
||||||
|
.data(spi, &[OctColor::colors_byte(*color, *color)])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..240 {
|
||||||
|
for color in color_7.iter().skip(4) {
|
||||||
|
for _ in 0..100 {
|
||||||
|
self.interface
|
||||||
|
.data(spi, &[OctColor::colors_byte(*color, *color)])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.display_frame(spi, delay)
|
||||||
|
}
|
||||||
|
}
|
153
epd-waveshare/src/epd7in5/command.rs
Normal file
153
epd-waveshare/src/epd7in5/command.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
//! 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||||
|
/// direction, booster switch, soft reset.
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 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.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 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".
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
/// Starting data transmission
|
||||||
|
BoosterSoftStart = 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.
|
||||||
|
DeepSleep = 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.
|
||||||
|
DataStartTransmission1 = 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.
|
||||||
|
DataStop = 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.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
|
||||||
|
/// After this command is issued, image process engine will find thin lines/pixels
|
||||||
|
/// from frame SRAM and update the frame SRAM for applying new gray level waveform.
|
||||||
|
///
|
||||||
|
/// After "Image Process Command", BUSY_N signal will become "0" until image process
|
||||||
|
/// is finished.
|
||||||
|
ImageProcess = 0x13,
|
||||||
|
|
||||||
|
/// This command builds the VCOM Look-Up Table (LUTC).
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
/// This command builds the Black Look-Up Table (LUTB).
|
||||||
|
LutBlack = 0x21,
|
||||||
|
/// This command builds the White Look-Up Table (LUTW).
|
||||||
|
LutWhite = 0x22,
|
||||||
|
/// This command builds the Gray1 Look-Up Table (LUTG1).
|
||||||
|
LutGray1 = 0x23,
|
||||||
|
/// This command builds the Gray2 Look-Up Table (LUTG2).
|
||||||
|
LutGray2 = 0x24,
|
||||||
|
/// This command builds the Red0 Look-Up Table (LUTR0).
|
||||||
|
LutRed0 = 0x25,
|
||||||
|
/// This command builds the Red1 Look-Up Table (LUTR1).
|
||||||
|
LutRed1 = 0x26,
|
||||||
|
/// This command builds the Red2 Look-Up Table (LUTR2).
|
||||||
|
LutRed2 = 0x27,
|
||||||
|
/// This command builds the Red3 Look-Up Table (LUTR3).
|
||||||
|
LutRed3 = 0x28,
|
||||||
|
/// This command builds the XON Look-Up Table (LUTXON).
|
||||||
|
LutXon = 0x29,
|
||||||
|
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// This command selects the Internal or External temperature sensor.
|
||||||
|
TemperatureCalibration = 0x41,
|
||||||
|
/// This command could write data to the external temperature sensor.
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// This command could read data from the external temperature sensor.
|
||||||
|
TemperatureSensorRead = 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).
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn
|
||||||
|
/// the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority
|
||||||
|
/// than the RES\[1:0\] in R00H (PSR).
|
||||||
|
TconResolution = 0x61,
|
||||||
|
/// This command defines MCU host direct access external memory mode.
|
||||||
|
SpiFlashControl = 0x65,
|
||||||
|
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||||
|
Revision = 0x70,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
GetStatus = 0x71,
|
||||||
|
|
||||||
|
/// This command implements related VCOM sensing setting.
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value.
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets `VCOM_DC` value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
|
||||||
|
/// This is in all the Waveshare controllers for Epd7in5, but it's not documented
|
||||||
|
/// anywhere in the datasheet `¯\_(ツ)_/¯`
|
||||||
|
FlashMode = 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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
287
epd-waveshare/src/epd7in5/mod.rs
Normal file
287
epd-waveshare/src/epd7in5/mod.rs
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
//! A simple Driver for the Waveshare 7.5" E-Ink Display via SPI
|
||||||
|
//!
|
||||||
|
//! # References
|
||||||
|
//!
|
||||||
|
//! - [Datasheet](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT)
|
||||||
|
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/702def06bcb75983c98b0f9d25d43c552c248eb0/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_7in5.c)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/702def06bcb75983c98b0f9d25d43c552c248eb0/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5.py)
|
||||||
|
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 7in5 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display7in5 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 640;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 384;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = false;
|
||||||
|
|
||||||
|
/// Epd7in5 driver
|
||||||
|
///
|
||||||
|
pub struct Epd7in5<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 10_000);
|
||||||
|
|
||||||
|
// Set the power settings
|
||||||
|
self.cmd_with_data(spi, Command::PowerSetting, &[0x37, 0x00])?;
|
||||||
|
|
||||||
|
// Set the panel settings:
|
||||||
|
// - 600 x 448
|
||||||
|
// - Using LUT from external flash
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0xCF, 0x08])?;
|
||||||
|
|
||||||
|
// Start the booster
|
||||||
|
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0xC7, 0xCC, 0x28])?;
|
||||||
|
|
||||||
|
// Power on
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
delay.delay_us(5000);
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
// Set the clock frequency to 50Hz (default)
|
||||||
|
self.cmd_with_data(spi, Command::PllControl, &[0x3C])?;
|
||||||
|
|
||||||
|
// Select internal temperature sensor (default)
|
||||||
|
self.cmd_with_data(spi, Command::TemperatureCalibration, &[0x00])?;
|
||||||
|
|
||||||
|
// Set Vcom and data interval to 10 (default), border output to white
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x77])?;
|
||||||
|
|
||||||
|
// Set S2G and G2S non-overlap periods to 12 (default)
|
||||||
|
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
|
||||||
|
|
||||||
|
// Set the real resolution
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
// Set VCOM_DC to -1.5V
|
||||||
|
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x1E])?;
|
||||||
|
|
||||||
|
// This is in all the Waveshare controllers for Epd7in5
|
||||||
|
self.cmd_with_data(spi, Command::FlashMode, &[0x03])?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd7in5 { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
for byte in buffer {
|
||||||
|
let mut temp = *byte;
|
||||||
|
for _ in 0..4 {
|
||||||
|
let mut data = if temp & 0x80 == 0 { 0x00 } else { 0x03 };
|
||||||
|
data <<= 4;
|
||||||
|
temp <<= 1;
|
||||||
|
data |= if temp & 0x80 == 0 { 0x00 } else { 0x03 };
|
||||||
|
temp <<= 1;
|
||||||
|
self.send_data(spi, &[data])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
// The Waveshare controllers all implement clear using 0x33
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, 0x33, WIDTH / 8 * HEIGHT * 4)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::TconResolution)?;
|
||||||
|
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, 640);
|
||||||
|
assert_eq!(HEIGHT, 384);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
159
epd-waveshare/src/epd7in5_hd/command.rs
Normal file
159
epd-waveshare/src/epd7in5_hd/command.rs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
//! 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 {
|
||||||
|
DriverOutputControl = 0x01,
|
||||||
|
|
||||||
|
/// Set gate driving voltage
|
||||||
|
GateDrivingVoltageControl = 0x03,
|
||||||
|
|
||||||
|
/// Set source driving voltage
|
||||||
|
SourceDrivingVoltageControl = 0x04,
|
||||||
|
|
||||||
|
SoftStart = 0x0C,
|
||||||
|
|
||||||
|
/// Set the scanning start position of the gate driver.
|
||||||
|
/// The valid range is from 0 to 679.
|
||||||
|
GateScanStartPosition = 0x0F,
|
||||||
|
|
||||||
|
/// Deep sleep mode control
|
||||||
|
DeepSleep = 0x10,
|
||||||
|
|
||||||
|
/// Define data entry sequence
|
||||||
|
DataEntry = 0x11,
|
||||||
|
|
||||||
|
/// resets the commands and parameters to their S/W Reset default values except R10h-Deep Sleep Mode.
|
||||||
|
/// During operation, BUSY pad will output high.
|
||||||
|
/// Note: RAM are unaffected by this command.
|
||||||
|
SwReset = 0x12,
|
||||||
|
|
||||||
|
/// After this command initiated, HV Ready detection starts.
|
||||||
|
/// BUSY pad will output high during detection.
|
||||||
|
/// The detection result can be read from the Status Bit Read (Command 0x2F).
|
||||||
|
HvReadyDetection = 0x14,
|
||||||
|
|
||||||
|
/// After this command initiated, VCI detection starts.
|
||||||
|
/// BUSY pad will output high during detection.
|
||||||
|
/// The detection result can be read from the Status Bit Read (Command 0x2F).
|
||||||
|
VciDetection = 0x15,
|
||||||
|
|
||||||
|
/// Temperature Sensor Selection
|
||||||
|
TemperatureSensorControl = 0x18,
|
||||||
|
|
||||||
|
/// Write to temperature register
|
||||||
|
TemperatureSensorWrite = 0x1A,
|
||||||
|
|
||||||
|
/// Read from temperature register
|
||||||
|
TemperatureSensorRead = 0x1B,
|
||||||
|
|
||||||
|
/// Write Command to External temperature sensor.
|
||||||
|
TemperatureSensorWriteExternal = 0x1C,
|
||||||
|
|
||||||
|
/// Activate Display Update Sequence
|
||||||
|
MasterActivation = 0x20,
|
||||||
|
|
||||||
|
/// RAM content option for Display Update
|
||||||
|
DisplayUpdateControl1 = 0x21,
|
||||||
|
|
||||||
|
/// Display Update Sequence Option
|
||||||
|
DisplayUpdateControl2 = 0x22,
|
||||||
|
|
||||||
|
/// After this command, data entries will be written into the BW RAM until another command is written
|
||||||
|
WriteRamBw = 0x24,
|
||||||
|
|
||||||
|
/// After this command, data entries will be written into the RED RAM until another command is written
|
||||||
|
WriteRamRed = 0x26,
|
||||||
|
|
||||||
|
/// Fetch data from RAM
|
||||||
|
ReadRam = 0x27,
|
||||||
|
|
||||||
|
/// Enter VCOM sensing conditions
|
||||||
|
VcomSense = 0x28,
|
||||||
|
|
||||||
|
/// Enter VCOM sensing conditions
|
||||||
|
VcomSenseDuration = 0x29,
|
||||||
|
|
||||||
|
/// Program VCOM register into OTP
|
||||||
|
VcomProgramOtp = 0x2A,
|
||||||
|
|
||||||
|
/// Reduces a glitch when ACVCOM is toggled
|
||||||
|
VcomControl = 0x2B,
|
||||||
|
|
||||||
|
/// Write VCOM register from MCU interface
|
||||||
|
VcomWrite = 0x2C,
|
||||||
|
|
||||||
|
/// Read Register for Display Option
|
||||||
|
OtpRead = 0x2D,
|
||||||
|
|
||||||
|
/// CRC calculation command for OTP content validation
|
||||||
|
CrcCalculation = 0x34,
|
||||||
|
|
||||||
|
/// CRC Status Read
|
||||||
|
CrcRead = 0x35,
|
||||||
|
|
||||||
|
/// Program OTP Selection according to the OTP Selection Control
|
||||||
|
ProgramSelection = 0x36,
|
||||||
|
|
||||||
|
/// Write Register for Display Option
|
||||||
|
DisplayOptionWrite = 0x37,
|
||||||
|
|
||||||
|
/// Write register for User ID
|
||||||
|
UserIdWrite = 0x38,
|
||||||
|
|
||||||
|
/// Select border waveform for VBD
|
||||||
|
VbdControl = 0x3C,
|
||||||
|
|
||||||
|
/// Read RAM Option
|
||||||
|
ReadRamOption = 0x41,
|
||||||
|
|
||||||
|
/// Specify the start/end positions of the window address in the X direction by an address unit for RAM
|
||||||
|
SetRamXStartEnd = 0x44,
|
||||||
|
|
||||||
|
/// Specify the start/end positions of the window address in the Y direction by an address unit for RAM
|
||||||
|
SetRamYStartEnd = 0x45,
|
||||||
|
|
||||||
|
/// Auto write RED RAM for regular pattern
|
||||||
|
AutoWriteRed = 0x46,
|
||||||
|
|
||||||
|
/// Auto write B/W RAM for regular pattern
|
||||||
|
AutoWriteBw = 0x47,
|
||||||
|
|
||||||
|
/// Make initial settings for the RAM X address in the address counter (AC)
|
||||||
|
SetRamXAc = 0x4E,
|
||||||
|
|
||||||
|
/// Make initial settings for the RAM Y address in the address counter (AC)
|
||||||
|
SetRamYAc = 0x4F,
|
||||||
|
|
||||||
|
/// This command is an empty command; it does not have any effect on the display module.
|
||||||
|
/// However, it can be used to terminate Frame Memory Write or Read Commands.
|
||||||
|
Nop = 0x7F,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::MasterActivation.address(), 0x20);
|
||||||
|
assert_eq!(Command::SwReset.address(), 0x12);
|
||||||
|
assert_eq!(Command::DisplayUpdateControl2.address(), 0x22);
|
||||||
|
}
|
||||||
|
}
|
269
epd-waveshare/src/epd7in5_hd/mod.rs
Normal file
269
epd-waveshare/src/epd7in5_hd/mod.rs
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
//! A simple Driver for the Waveshare 7.5" E-Ink Display (HD) via SPI
|
||||||
|
//!
|
||||||
|
//! Color values for this driver are inverted compared to the [EPD 7in5 V2 driver](crate::epd7in5_v2)
|
||||||
|
//! *EPD 7in5 HD:* White = 1/0xFF, Black = 0/0x00
|
||||||
|
//! *EPD 7in5 V2:* White = 0/0x00, Black = 1/0xFF
|
||||||
|
//!
|
||||||
|
//! # References
|
||||||
|
//!
|
||||||
|
//! - [Datasheet](https://www.waveshare.com/w/upload/2/27/7inch_HD_e-Paper_Specification.pdf)
|
||||||
|
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd7in5_HD.py)
|
||||||
|
//!
|
||||||
|
use embedded_hal::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 7in5 HD EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display7in5 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 880;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 528;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; // Inverted for HD as compared to 7in5 v2 (HD: 0xFF = White)
|
||||||
|
const IS_BUSY_LOW: bool = false;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = false;
|
||||||
|
|
||||||
|
/// EPD7in5 (HD) driver
|
||||||
|
///
|
||||||
|
pub struct Epd7in5<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
// HD procedure as described here:
|
||||||
|
// https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd7in5_HD.py
|
||||||
|
// and as per specs:
|
||||||
|
// https://www.waveshare.com/w/upload/2/27/7inch_HD_e-Paper_Specification.pdf
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::SwReset)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::AutoWriteRed, &[0xF7])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::AutoWriteBw, &[0xF7])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::SoftStart, &[0xAE, 0xC7, 0xC3, 0xC0, 0x40])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DriverOutputControl, &[0xAF, 0x02, 0x01])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DataEntry, &[0x01])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::SetRamXStartEnd, &[0x00, 0x00, 0x6F, 0x03])?;
|
||||||
|
self.cmd_with_data(spi, Command::SetRamYStartEnd, &[0xAF, 0x02, 0x00, 0x00])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::VbdControl, &[0x05])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::TemperatureSensorControl, &[0x80])?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xB1])?;
|
||||||
|
|
||||||
|
self.command(spi, Command::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::SetRamXAc, &[0x00, 0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd7in5 { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0x01])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::WriteRamBw, buffer)?;
|
||||||
|
self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xF7])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.command(spi, Command::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.display_frame(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
let pixel_count = WIDTH / 8 * HEIGHT;
|
||||||
|
let background_color_byte = self.color.get_byte_value();
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?;
|
||||||
|
|
||||||
|
for cmd in &[Command::WriteRamBw, Command::WriteRamRed] {
|
||||||
|
self.command(spi, *cmd)?;
|
||||||
|
self.interface
|
||||||
|
.data_x_times(spi, background_color_byte, pixel_count)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xF7])?;
|
||||||
|
self.command(spi, Command::MasterActivation)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
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,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.wait_until_idle(delay, IS_BUSY_LOW);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epd_size() {
|
||||||
|
assert_eq!(WIDTH, 880);
|
||||||
|
assert_eq!(HEIGHT, 528);
|
||||||
|
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||||
|
}
|
||||||
|
}
|
153
epd-waveshare/src/epd7in5_v2/command.rs
Normal file
153
epd-waveshare/src/epd7in5_v2/command.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
//! 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||||
|
/// direction, booster switch, soft reset.
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 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.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 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".
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
/// Starting data transmission
|
||||||
|
BoosterSoftStart = 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.
|
||||||
|
DeepSleep = 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
|
||||||
|
DataStartTransmission1 = 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.
|
||||||
|
DataStop = 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.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
|
||||||
|
/// RED or NEW_DATA
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
/// Dual SPI - what for?
|
||||||
|
DualSpi = 0x15,
|
||||||
|
|
||||||
|
/// This command builds the VCOM Look-Up Table (LUTC).
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
/// This command builds the Black Look-Up Table (LUTB).
|
||||||
|
LutBlack = 0x21,
|
||||||
|
/// This command builds the White Look-Up Table (LUTW).
|
||||||
|
LutWhite = 0x22,
|
||||||
|
/// This command builds the Gray1 Look-Up Table (LUTG1).
|
||||||
|
LutGray1 = 0x23,
|
||||||
|
/// This command builds the Gray2 Look-Up Table (LUTG2).
|
||||||
|
LutGray2 = 0x24,
|
||||||
|
/// This command builds the Red0 Look-Up Table (LUTR0).
|
||||||
|
LutRed0 = 0x25,
|
||||||
|
/// This command builds the Red1 Look-Up Table (LUTR1).
|
||||||
|
LutRed1 = 0x26,
|
||||||
|
/// This command builds the Red2 Look-Up Table (LUTR2).
|
||||||
|
LutRed2 = 0x27,
|
||||||
|
/// This command builds the Red3 Look-Up Table (LUTR3).
|
||||||
|
LutRed3 = 0x28,
|
||||||
|
/// This command builds the XON Look-Up Table (LUTXON).
|
||||||
|
LutXon = 0x29,
|
||||||
|
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// This command selects the Internal or External temperature sensor.
|
||||||
|
TemperatureCalibration = 0x41,
|
||||||
|
/// This command could write data to the external temperature sensor.
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// This command could read data from the external temperature sensor.
|
||||||
|
TemperatureSensorRead = 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).
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn
|
||||||
|
/// the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority
|
||||||
|
/// than the RES\[1:0\] in R00H (PSR).
|
||||||
|
TconResolution = 0x61,
|
||||||
|
/// This command defines MCU host direct access external memory mode.
|
||||||
|
SpiFlashControl = 0x65,
|
||||||
|
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||||
|
Revision = 0x70,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
GetStatus = 0x71,
|
||||||
|
|
||||||
|
/// This command implements related VCOM sensing setting.
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value.
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets `VCOM_DC` value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
// /// This is in all the Waveshare controllers for Epd7in5, but it's not documented
|
||||||
|
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
|
||||||
|
// FlashMode = 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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
261
epd-waveshare/src/epd7in5_v2/mod.rs
Normal file
261
epd-waveshare/src/epd7in5_v2/mod.rs
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
//! 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::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::Color;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 7in5 v2 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display7in5 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize) },
|
||||||
|
Color,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 800;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 480;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = false;
|
||||||
|
|
||||||
|
/// Epd7in5 (V2) driver
|
||||||
|
///
|
||||||
|
pub struct Epd7in5<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
self.interface.reset(delay, 10_000, 2_000);
|
||||||
|
|
||||||
|
// 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::BoosterSoftStart, &[0x17, 0x17, 0x27, 0x17])?;
|
||||||
|
self.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x17, 0x3F, 0x3F])?;
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x1F])?;
|
||||||
|
self.cmd_with_data(spi, Command::PllControl, &[0x06])?;
|
||||||
|
self.cmd_with_data(spi, Command::TconResolution, &[0x03, 0x20, 0x01, 0xE0])?;
|
||||||
|
self.cmd_with_data(spi, Command::DualSpi, &[0x00])?;
|
||||||
|
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x10, 0x07])?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = Color;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd7in5 { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, 0x00, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, 0x00, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
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,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_until_idle(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.wait_until_idle_with_cmd(spi, delay, IS_BUSY_LOW, Command::GetStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::TconResolution)?;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
166
epd-waveshare/src/epd7in5b_v2/command.rs
Normal file
166
epd-waveshare/src/epd7in5b_v2/command.rs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
//! SPI Commands for the Waveshare 7.5"(B) V2 and V3 -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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||||
|
/// direction, booster switch, soft reset.
|
||||||
|
PanelSetting = 0x00,
|
||||||
|
|
||||||
|
/// Selecting internal and external power
|
||||||
|
PowerSetting = 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.
|
||||||
|
PowerOff = 0x02,
|
||||||
|
|
||||||
|
/// Setting Power OFF sequence
|
||||||
|
PowerOffSequenceSetting = 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".
|
||||||
|
PowerOn = 0x04,
|
||||||
|
|
||||||
|
/// Starting data transmission
|
||||||
|
BoosterSoftStart = 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.
|
||||||
|
DeepSleep = 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
|
||||||
|
DataStartTransmission1 = 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.
|
||||||
|
DataStop = 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.
|
||||||
|
DisplayRefresh = 0x12,
|
||||||
|
|
||||||
|
/// RED or NEW_DATA
|
||||||
|
DataStartTransmission2 = 0x13,
|
||||||
|
|
||||||
|
/// Dual SPI - what for?
|
||||||
|
DualSpi = 0x15,
|
||||||
|
|
||||||
|
/// This command builds the VCOM Look-Up Table (LUTC).
|
||||||
|
LutForVcom = 0x20,
|
||||||
|
/// This command builds the Black Look-Up Table (LUTB).
|
||||||
|
LutBlack = 0x21,
|
||||||
|
/// This command builds the White Look-Up Table (LUTW).
|
||||||
|
LutWhite = 0x22,
|
||||||
|
/// This command builds the Gray1 Look-Up Table (LUTG1).
|
||||||
|
LutGray1 = 0x23,
|
||||||
|
/// This command builds the Gray2 Look-Up Table (LUTG2).
|
||||||
|
LutGray2 = 0x24,
|
||||||
|
/// This command builds the Red0 Look-Up Table (LUTR0).
|
||||||
|
LutRed0 = 0x25,
|
||||||
|
/// This command builds the Red1 Look-Up Table (LUTR1).
|
||||||
|
LutRed1 = 0x26,
|
||||||
|
/// This command builds the Red2 Look-Up Table (LUTR2).
|
||||||
|
LutRed2 = 0x27,
|
||||||
|
/// This command builds the Red3 Look-Up Table (LUTR3).
|
||||||
|
LutRed3 = 0x28,
|
||||||
|
/// This command builds the XON Look-Up Table (LUTXON).
|
||||||
|
LutXon = 0x29,
|
||||||
|
|
||||||
|
/// LUT Option
|
||||||
|
LutOpt = 0x2A,
|
||||||
|
/// KW LUT Option
|
||||||
|
KWLutOpt = 0x2B,
|
||||||
|
|
||||||
|
/// The command controls the PLL clock frequency.
|
||||||
|
PllControl = 0x30,
|
||||||
|
|
||||||
|
/// This command reads the temperature sensed by the temperature sensor.
|
||||||
|
TemperatureSensor = 0x40,
|
||||||
|
/// This command selects the Internal or External temperature sensor.
|
||||||
|
TemperatureCalibration = 0x41,
|
||||||
|
/// This command could write data to the external temperature sensor.
|
||||||
|
TemperatureSensorWrite = 0x42,
|
||||||
|
/// This command could read data from the external temperature sensor.
|
||||||
|
TemperatureSensorRead = 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).
|
||||||
|
VcomAndDataIntervalSetting = 0x50,
|
||||||
|
/// This command indicates the input power condition. Host can read this flag to learn
|
||||||
|
/// the battery condition.
|
||||||
|
LowPowerDetection = 0x51,
|
||||||
|
|
||||||
|
/// This command defines non-overlap period of Gate and Source.
|
||||||
|
TconSetting = 0x60,
|
||||||
|
/// This command defines alternative resolution and this setting is of higher priority
|
||||||
|
/// than the RES\[1:0\] in R00H (PSR).
|
||||||
|
TconResolution = 0x61,
|
||||||
|
/// This command defines MCU host direct access external memory mode.
|
||||||
|
SpiFlashControl = 0x65,
|
||||||
|
|
||||||
|
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||||
|
Revision = 0x70,
|
||||||
|
/// This command reads the IC status.
|
||||||
|
GetStatus = 0x71,
|
||||||
|
|
||||||
|
/// This command implements related VCOM sensing setting.
|
||||||
|
AutoMeasurementVcom = 0x80,
|
||||||
|
/// This command gets the VCOM value.
|
||||||
|
ReadVcomValue = 0x81,
|
||||||
|
/// This command sets `VCOM_DC` value.
|
||||||
|
VcmDcSetting = 0x82,
|
||||||
|
// /// This is in all the Waveshare controllers for Epd7in5, but it's not documented
|
||||||
|
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
|
||||||
|
// FlashMode = 0xE5,
|
||||||
|
/// Sets window size for the partial update
|
||||||
|
PartialWindow = 0x90,
|
||||||
|
/// Sets chip into partial update mode
|
||||||
|
PartialIn = 0x91,
|
||||||
|
/// Quits partial update mode
|
||||||
|
PartialOut = 0x92,
|
||||||
|
/// Read OTP
|
||||||
|
ReadOTP = 0xA2,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::PanelSetting.address(), 0x00);
|
||||||
|
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||||
|
}
|
||||||
|
}
|
397
epd-waveshare/src/epd7in5b_v2/mod.rs
Normal file
397
epd-waveshare/src/epd7in5b_v2/mod.rs
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
//! A simple Driver for the Waveshare 7.5" (B) 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::{
|
||||||
|
delay::DelayNs,
|
||||||
|
digital::{InputPin, OutputPin},
|
||||||
|
spi::SpiDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::color::TriColor;
|
||||||
|
use crate::interface::DisplayInterface;
|
||||||
|
use crate::traits::{
|
||||||
|
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) mod command;
|
||||||
|
use self::command::Command;
|
||||||
|
use crate::buffer_len;
|
||||||
|
|
||||||
|
/// Full size buffer for use with the 7in5b v2 EPD
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub type Display7in5 = crate::graphics::Display<
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
false,
|
||||||
|
{ buffer_len(WIDTH as usize, HEIGHT as usize * 2) },
|
||||||
|
TriColor,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// Width of the display
|
||||||
|
pub const WIDTH: u32 = 800;
|
||||||
|
/// Height of the display
|
||||||
|
pub const HEIGHT: u32 = 480;
|
||||||
|
/// Default Background Color
|
||||||
|
pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White;
|
||||||
|
|
||||||
|
/// Number of bytes for b/w buffer and same for chromatic buffer bits
|
||||||
|
const NUM_DISPLAY_BITS: usize = WIDTH as usize / 8 * HEIGHT as usize;
|
||||||
|
const IS_BUSY_LOW: bool = true;
|
||||||
|
const SINGLE_BYTE_WRITE: bool = false;
|
||||||
|
|
||||||
|
/// Epd7in5 (V2) driver
|
||||||
|
///
|
||||||
|
pub struct Epd7in5<SPI, BUSY, DC, RST, DELAY> {
|
||||||
|
/// Connection Interface
|
||||||
|
interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
|
||||||
|
/// Background Color
|
||||||
|
color: TriColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
// Reset the device
|
||||||
|
// C driver does 200/2 original rust driver does 10/2
|
||||||
|
self.interface.reset(delay, 200_000, 2_000);
|
||||||
|
|
||||||
|
// 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::PowerSetting, &[0x07, 0x07, 0x3F, 0x3F])?;
|
||||||
|
self.command(spi, Command::PowerOn)?;
|
||||||
|
// C driver adds a static 100ms delay here
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// Done, but this is also the default
|
||||||
|
// 0x1F = B/W mode ? doesnt seem to work
|
||||||
|
self.cmd_with_data(spi, Command::PanelSetting, &[0x0F])?;
|
||||||
|
// Not done in C driver, this is the default
|
||||||
|
//self.cmd_with_data(spi, Command::PllControl, &[0x06])?;
|
||||||
|
self.cmd_with_data(spi, Command::TconResolution, &[0x03, 0x20, 0x01, 0xE0])?;
|
||||||
|
// Documentation removed in v3 but done in v2 and works in v3
|
||||||
|
self.cmd_with_data(spi, Command::DualSpi, &[0x00])?;
|
||||||
|
// 0x10 in BW mode (Work ?) V
|
||||||
|
// 0x12 in BW mode to disable new/old thing
|
||||||
|
// 0x01 -> Black border
|
||||||
|
// 0x11 -> White norder
|
||||||
|
// 0x21 -> Red border
|
||||||
|
// 0x31 -> don't touch border
|
||||||
|
// the second nibble can change polarity (may be easier for default
|
||||||
|
// display initialization) V
|
||||||
|
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x11, 0x07])?;
|
||||||
|
// This is the default
|
||||||
|
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
|
||||||
|
self.cmd_with_data(spi, Command::SpiFlashControl, &[0x00, 0x00, 0x00, 0x00])?;
|
||||||
|
// Not in C driver
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_achromatic_frame(spi, delay, black)?;
|
||||||
|
self.update_chromatic_frame(spi, delay, chromatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only the black/white data of the display.
|
||||||
|
///
|
||||||
|
/// Finish by calling `update_chromatic_frame`.
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data(spi, black)?;
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update only chromatic data of the display.
|
||||||
|
///
|
||||||
|
/// This data takes precedence over the black/white data.
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data(spi, chromatic)?;
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
for Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
type DisplayColor = TriColor;
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error> {
|
||||||
|
let interface = DisplayInterface::new(busy, dc, rst, delay_us);
|
||||||
|
let color = DEFAULT_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
let mut epd = Epd7in5 { interface, color };
|
||||||
|
|
||||||
|
epd.init(spi, delay)?;
|
||||||
|
|
||||||
|
Ok(epd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.init(spi, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::PowerOff)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
// (B) version sends one buffer for black and one for red
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DataStartTransmission1,
|
||||||
|
&buffer[..NUM_DISPLAY_BITS],
|
||||||
|
)?;
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::DataStartTransmission2,
|
||||||
|
&buffer[NUM_DISPLAY_BITS..],
|
||||||
|
)?;
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_buffer: &[u8],
|
||||||
|
_x: u32,
|
||||||
|
_y: u32,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_and_display_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.update_frame(spi, buffer, delay)?;
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
self.send_resolution(spi)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission1)?;
|
||||||
|
self.interface.data_x_times(spi, 0xFF, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DataStartTransmission2)?;
|
||||||
|
self.interface.data_x_times(spi, 0x00, WIDTH / 8 * HEIGHT)?;
|
||||||
|
|
||||||
|
self.interface.cmd(spi, Command::DataStop)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_background_color(&mut self, color: Self::DisplayColor) {
|
||||||
|
self.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_color(&self) -> &Self::DisplayColor {
|
||||||
|
&self.color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
WIDTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
_spi: &mut SPI,
|
||||||
|
_delay: &mut DELAY,
|
||||||
|
_refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// wait
|
||||||
|
fn wait_until_idle(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||||
|
self.interface
|
||||||
|
.wait_until_idle_with_cmd(spi, delay, IS_BUSY_LOW, Command::GetStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY> Epd7in5<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// temporary replacement for missing delay in the trait to call wait_until_idle
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn update_partial_frame2(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
if buffer.len() as u32 != width / 8 * height {
|
||||||
|
//TODO panic or error
|
||||||
|
}
|
||||||
|
|
||||||
|
let hrst_upper = (x / 8) as u8 >> 5;
|
||||||
|
let hrst_lower = ((x / 8) << 3) as u8;
|
||||||
|
let hred_upper = ((x + width) / 8 - 1) as u8 >> 5;
|
||||||
|
let hred_lower = (((x + width) / 8 - 1) << 3) as u8 | 0b111;
|
||||||
|
let vrst_upper = (y >> 8) as u8;
|
||||||
|
let vrst_lower = y as u8;
|
||||||
|
let vred_upper = ((y + height - 1) >> 8) as u8;
|
||||||
|
let vred_lower = (y + height - 1) as u8;
|
||||||
|
let pt_scan = 0x01; // Gates scan both inside and outside of the partial window. (default)
|
||||||
|
|
||||||
|
self.command(spi, Command::PartialIn)?;
|
||||||
|
self.cmd_with_data(
|
||||||
|
spi,
|
||||||
|
Command::PartialWindow,
|
||||||
|
&[
|
||||||
|
hrst_upper, hrst_lower, hred_upper, hred_lower, vrst_upper, vrst_lower, vred_upper,
|
||||||
|
vred_lower, pt_scan,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
let half = buffer.len() / 2;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission1, &buffer[..half])?;
|
||||||
|
self.cmd_with_data(spi, Command::DataStartTransmission2, &buffer[half..])?;
|
||||||
|
|
||||||
|
self.command(spi, Command::DisplayRefresh)?;
|
||||||
|
self.wait_until_idle(spi, delay)?;
|
||||||
|
|
||||||
|
self.command(spi, Command::PartialOut)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||||
|
let w = self.width();
|
||||||
|
let h = self.height();
|
||||||
|
|
||||||
|
self.command(spi, Command::TconResolution)?;
|
||||||
|
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, TriColor::White);
|
||||||
|
}
|
||||||
|
}
|
452
epd-waveshare/src/graphics.rs
Normal file
452
epd-waveshare/src/graphics.rs
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
//! Graphics Support for EPDs
|
||||||
|
|
||||||
|
use crate::color::{ColorType, TriColor};
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use embedded_graphics_core::prelude::*;
|
||||||
|
|
||||||
|
/// Display rotation, only 90° increments supported
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub enum DisplayRotation {
|
||||||
|
/// No rotation
|
||||||
|
#[default]
|
||||||
|
Rotate0,
|
||||||
|
/// Rotate by 90 degrees clockwise
|
||||||
|
Rotate90,
|
||||||
|
/// Rotate by 180 degrees clockwise
|
||||||
|
Rotate180,
|
||||||
|
/// Rotate 270 degrees clockwise
|
||||||
|
Rotate270,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// count the number of bytes per line knowing that it may contains padding bits
|
||||||
|
const fn line_bytes(width: u32, bits_per_pixel: usize) -> usize {
|
||||||
|
// round to upper 8 bit count
|
||||||
|
(width as usize * bits_per_pixel + 7) / 8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display bffer used for drawing with embedded graphics
|
||||||
|
/// This can be rendered on EPD using ...
|
||||||
|
///
|
||||||
|
/// - WIDTH: width in pixel when display is not rotated
|
||||||
|
/// - HEIGHT: height in pixel when display is not rotated
|
||||||
|
/// - BWRBIT: mandatory value of the B/W when chromatic bit is set, can be any value for non
|
||||||
|
/// tricolor epd
|
||||||
|
/// - COLOR: color type used by the target display
|
||||||
|
/// - BYTECOUNT: This is redundant with prvious data and should be removed when const generic
|
||||||
|
/// expressions are stabilized
|
||||||
|
///
|
||||||
|
/// More on BWRBIT:
|
||||||
|
///
|
||||||
|
/// Different chromatic displays differently treat the bits in chromatic color planes.
|
||||||
|
/// Some of them ([crate::epd2in13bc]) will render a color pixel if bit is set for that pixel,
|
||||||
|
/// which is a [DisplayColorRendering::Positive] mode.
|
||||||
|
///
|
||||||
|
/// Other displays, like [crate::epd5in83b_v2] in opposite, will draw color pixel if bit is
|
||||||
|
/// cleared for that pixel, which is a [DisplayColorRendering::Negative] mode.
|
||||||
|
///
|
||||||
|
/// BWRBIT=true: chromatic doesn't override white, white bit cleared for black, white bit set for white, both bits set for chromatic
|
||||||
|
/// BWRBIT=false: chromatic does override white, both bits cleared for black, white bit set for white, red bit set for black
|
||||||
|
pub struct Display<
|
||||||
|
const WIDTH: u32,
|
||||||
|
const HEIGHT: u32,
|
||||||
|
const BWRBIT: bool,
|
||||||
|
const BYTECOUNT: usize,
|
||||||
|
COLOR: ColorType + PixelColor,
|
||||||
|
> {
|
||||||
|
buffer: [u8; BYTECOUNT],
|
||||||
|
rotation: DisplayRotation,
|
||||||
|
_color: PhantomData<COLOR>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const WIDTH: u32,
|
||||||
|
const HEIGHT: u32,
|
||||||
|
const BWRBIT: bool,
|
||||||
|
const BYTECOUNT: usize,
|
||||||
|
COLOR: ColorType + PixelColor,
|
||||||
|
> Default for Display<WIDTH, HEIGHT, BWRBIT, BYTECOUNT, COLOR>
|
||||||
|
{
|
||||||
|
/// Initialize display with the color '0', which may not be the same on all device.
|
||||||
|
/// Many devices have a bit parameter polarity that should be changed if this is not the right
|
||||||
|
/// one.
|
||||||
|
/// However, every device driver should implement a DEFAULT_COLOR constant to indicate which
|
||||||
|
/// color this represents (TODO)
|
||||||
|
///
|
||||||
|
/// If you want a specific default color, you can still call clear() to set one.
|
||||||
|
// inline is necessary here to allow heap allocation via Box on stack limited programs
|
||||||
|
#[inline(always)]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
// default color must be 0 for every bit in a pixel to make this work everywere
|
||||||
|
buffer: [0u8; BYTECOUNT],
|
||||||
|
rotation: DisplayRotation::default(),
|
||||||
|
_color: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For use with embedded_grahics
|
||||||
|
impl<
|
||||||
|
const WIDTH: u32,
|
||||||
|
const HEIGHT: u32,
|
||||||
|
const BWRBIT: bool,
|
||||||
|
const BYTECOUNT: usize,
|
||||||
|
COLOR: ColorType + PixelColor,
|
||||||
|
> DrawTarget for Display<WIDTH, HEIGHT, BWRBIT, BYTECOUNT, COLOR>
|
||||||
|
{
|
||||||
|
type Color = COLOR;
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
for pixel in pixels {
|
||||||
|
self.set_pixel(pixel);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For use with embedded_grahics
|
||||||
|
impl<
|
||||||
|
const WIDTH: u32,
|
||||||
|
const HEIGHT: u32,
|
||||||
|
const BWRBIT: bool,
|
||||||
|
const BYTECOUNT: usize,
|
||||||
|
COLOR: ColorType + PixelColor,
|
||||||
|
> OriginDimensions for Display<WIDTH, HEIGHT, BWRBIT, BYTECOUNT, COLOR>
|
||||||
|
{
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
match self.rotation {
|
||||||
|
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => Size::new(WIDTH, HEIGHT),
|
||||||
|
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => Size::new(HEIGHT, WIDTH),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const WIDTH: u32,
|
||||||
|
const HEIGHT: u32,
|
||||||
|
const BWRBIT: bool,
|
||||||
|
const BYTECOUNT: usize,
|
||||||
|
COLOR: ColorType + PixelColor,
|
||||||
|
> Display<WIDTH, HEIGHT, BWRBIT, BYTECOUNT, COLOR>
|
||||||
|
{
|
||||||
|
/// get internal buffer to use it (to draw in epd)
|
||||||
|
pub fn buffer(&self) -> &[u8] {
|
||||||
|
&self.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the display rotation.
|
||||||
|
///
|
||||||
|
/// This only concerns future drawing made to it. Anything aready drawn
|
||||||
|
/// stays as it is in the buffer.
|
||||||
|
pub fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||||
|
self.rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current rotation
|
||||||
|
pub fn rotation(&self) -> DisplayRotation {
|
||||||
|
self.rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a specific pixel color on this display
|
||||||
|
pub fn set_pixel(&mut self, pixel: Pixel<COLOR>) {
|
||||||
|
set_pixel(
|
||||||
|
&mut self.buffer,
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
self.rotation,
|
||||||
|
BWRBIT,
|
||||||
|
pixel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some Tricolor specifics
|
||||||
|
impl<const WIDTH: u32, const HEIGHT: u32, const BWRBIT: bool, const BYTECOUNT: usize>
|
||||||
|
Display<WIDTH, HEIGHT, BWRBIT, BYTECOUNT, TriColor>
|
||||||
|
{
|
||||||
|
/// get black/white internal buffer to use it (to draw in epd)
|
||||||
|
pub fn bw_buffer(&self) -> &[u8] {
|
||||||
|
&self.buffer[..self.buffer.len() / 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get chromatic internal buffer to use it (to draw in epd)
|
||||||
|
pub fn chromatic_buffer(&self) -> &[u8] {
|
||||||
|
&self.buffer[self.buffer.len() / 2..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `Display`, except that its characteristics are defined at runtime.
|
||||||
|
/// See display for documentation as everything is the same except that default
|
||||||
|
/// is replaced by a `new` method.
|
||||||
|
pub struct VarDisplay<'a, COLOR: ColorType + PixelColor> {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
bwrbit: bool,
|
||||||
|
buffer: &'a mut [u8],
|
||||||
|
rotation: DisplayRotation,
|
||||||
|
_color: PhantomData<COLOR>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For use with embedded_grahics
|
||||||
|
impl<'a, COLOR: ColorType + PixelColor> DrawTarget for VarDisplay<'a, COLOR> {
|
||||||
|
type Color = COLOR;
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
for pixel in pixels {
|
||||||
|
self.set_pixel(pixel);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For use with embedded_grahics
|
||||||
|
impl<'a, COLOR: ColorType + PixelColor> OriginDimensions for VarDisplay<'a, COLOR> {
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
match self.rotation {
|
||||||
|
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
|
||||||
|
Size::new(self.width, self.height)
|
||||||
|
}
|
||||||
|
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
|
||||||
|
Size::new(self.height, self.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error found during usage of VarDisplay
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VarDisplayError {
|
||||||
|
/// The provided buffer was too small
|
||||||
|
BufferTooSmall,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, COLOR: ColorType + PixelColor> VarDisplay<'a, COLOR> {
|
||||||
|
/// You must allocate the buffer by yourself, it must be large enough to contain all pixels.
|
||||||
|
///
|
||||||
|
/// Parameters are documented in `Display` as they are the same as the const generics there.
|
||||||
|
/// bwrbit should be false for non tricolor displays
|
||||||
|
pub fn new(
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
buffer: &'a mut [u8],
|
||||||
|
bwrbit: bool,
|
||||||
|
) -> Result<Self, VarDisplayError> {
|
||||||
|
let myself = Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bwrbit,
|
||||||
|
buffer,
|
||||||
|
rotation: DisplayRotation::default(),
|
||||||
|
_color: PhantomData,
|
||||||
|
};
|
||||||
|
// enfore some constraints dynamicly
|
||||||
|
if myself.buffer_size() > myself.buffer.len() {
|
||||||
|
return Err(VarDisplayError::BufferTooSmall);
|
||||||
|
}
|
||||||
|
Ok(myself)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the number of used bytes in the buffer
|
||||||
|
fn buffer_size(&self) -> usize {
|
||||||
|
self.height as usize
|
||||||
|
* line_bytes(
|
||||||
|
self.width,
|
||||||
|
COLOR::BITS_PER_PIXEL_PER_BUFFER * COLOR::BUFFER_COUNT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get internal buffer to use it (to draw in epd)
|
||||||
|
pub fn buffer(&self) -> &[u8] {
|
||||||
|
&self.buffer[..self.buffer_size()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the display rotation.
|
||||||
|
///
|
||||||
|
/// This only concerns future drawing made to it. Anything aready drawn
|
||||||
|
/// stays as it is in the buffer.
|
||||||
|
pub fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||||
|
self.rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current rotation
|
||||||
|
pub fn rotation(&self) -> DisplayRotation {
|
||||||
|
self.rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a specific pixel color on this display
|
||||||
|
pub fn set_pixel(&mut self, pixel: Pixel<COLOR>) {
|
||||||
|
let size = self.buffer_size();
|
||||||
|
set_pixel(
|
||||||
|
&mut self.buffer[..size],
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
self.rotation,
|
||||||
|
self.bwrbit,
|
||||||
|
pixel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some Tricolor specifics
|
||||||
|
impl<'a> VarDisplay<'a, TriColor> {
|
||||||
|
/// get black/white internal buffer to use it (to draw in epd)
|
||||||
|
pub fn bw_buffer(&self) -> &[u8] {
|
||||||
|
&self.buffer[..self.buffer_size() / 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get chromatic internal buffer to use it (to draw in epd)
|
||||||
|
pub fn chromatic_buffer(&self) -> &[u8] {
|
||||||
|
&self.buffer[self.buffer_size() / 2..self.buffer_size()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a function to share code between `Display` and `VarDisplay`
|
||||||
|
// It sets a specific pixel in a buffer to a given color.
|
||||||
|
// The big number of parameters is due to the fact that it is an internal function to both
|
||||||
|
// strctures.
|
||||||
|
fn set_pixel<COLOR: ColorType + PixelColor>(
|
||||||
|
buffer: &mut [u8],
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
rotation: DisplayRotation,
|
||||||
|
bwrbit: bool,
|
||||||
|
pixel: Pixel<COLOR>,
|
||||||
|
) {
|
||||||
|
let Pixel(point, color) = pixel;
|
||||||
|
|
||||||
|
// final coordinates
|
||||||
|
let (x, y) = match rotation {
|
||||||
|
// as i32 = never use more than 2 billion pixel per line or per column
|
||||||
|
DisplayRotation::Rotate0 => (point.x, point.y),
|
||||||
|
DisplayRotation::Rotate90 => (width as i32 - 1 - point.y, point.x),
|
||||||
|
DisplayRotation::Rotate180 => (width as i32 - 1 - point.x, height as i32 - 1 - point.y),
|
||||||
|
DisplayRotation::Rotate270 => (point.y, height as i32 - 1 - point.x),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Out of range check
|
||||||
|
if (x < 0) || (x >= width as i32) || (y < 0) || (y >= height as i32) {
|
||||||
|
// don't do anything in case of out of range
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = x as usize * COLOR::BITS_PER_PIXEL_PER_BUFFER / 8
|
||||||
|
+ y as usize * line_bytes(width, COLOR::BITS_PER_PIXEL_PER_BUFFER);
|
||||||
|
let (mask, bits) = color.bitmask(bwrbit, x as u32);
|
||||||
|
|
||||||
|
if COLOR::BUFFER_COUNT == 2 {
|
||||||
|
// split buffer is for tricolor displays that use 2 buffer for 2 bits per pixel
|
||||||
|
buffer[index] = buffer[index] & mask | (bits & 0xFF) as u8;
|
||||||
|
let index = index + buffer.len() / 2;
|
||||||
|
buffer[index] = buffer[index] & mask | (bits >> 8) as u8;
|
||||||
|
} else {
|
||||||
|
buffer[index] = buffer[index] & mask | bits as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::color::*;
|
||||||
|
use embedded_graphics::{
|
||||||
|
prelude::*,
|
||||||
|
primitives::{Line, PrimitiveStyle},
|
||||||
|
};
|
||||||
|
|
||||||
|
// test buffer length
|
||||||
|
#[test]
|
||||||
|
fn graphics_size() {
|
||||||
|
// example definition taken from epd1in54
|
||||||
|
let display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default();
|
||||||
|
assert_eq!(display.buffer().len(), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test default background color on all bytes
|
||||||
|
#[test]
|
||||||
|
fn graphics_default() {
|
||||||
|
let display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default();
|
||||||
|
for &byte in display.buffer() {
|
||||||
|
assert_eq!(byte, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphics_rotation_0() {
|
||||||
|
let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default();
|
||||||
|
let _ = Line::new(Point::new(0, 0), Point::new(7, 0))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(Color::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, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphics_rotation_90() {
|
||||||
|
let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default();
|
||||||
|
display.set_rotation(DisplayRotation::Rotate90);
|
||||||
|
let _ = Line::new(Point::new(0, 192), Point::new(0, 199))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(Color::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, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphics_rotation_180() {
|
||||||
|
let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default();
|
||||||
|
display.set_rotation(DisplayRotation::Rotate180);
|
||||||
|
let _ = Line::new(Point::new(192, 199), Point::new(199, 199))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
.draw(&mut display);
|
||||||
|
|
||||||
|
let buffer = display.buffer();
|
||||||
|
|
||||||
|
extern crate std;
|
||||||
|
std::println!("{:?}", buffer);
|
||||||
|
|
||||||
|
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||||
|
|
||||||
|
for &byte in buffer.iter().skip(1) {
|
||||||
|
assert_eq!(byte, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphics_rotation_270() {
|
||||||
|
let mut display = Display::<200, 200, false, { 200 * 200 / 8 }, Color>::default();
|
||||||
|
display.set_rotation(DisplayRotation::Rotate270);
|
||||||
|
let _ = Line::new(Point::new(199, 0), Point::new(199, 7))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
.draw(&mut display);
|
||||||
|
|
||||||
|
let buffer = display.buffer();
|
||||||
|
|
||||||
|
extern crate std;
|
||||||
|
std::println!("{:?}", buffer);
|
||||||
|
|
||||||
|
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||||
|
|
||||||
|
for &byte in buffer.iter().skip(1) {
|
||||||
|
assert_eq!(byte, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
208
epd-waveshare/src/interface.rs
Normal file
208
epd-waveshare/src/interface.rs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
use crate::traits::Command;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
/// The Connection Interface of all (?) Waveshare EPD-Devices
|
||||||
|
///
|
||||||
|
/// SINGLE_BYTE_WRITE defines if a data block is written bytewise
|
||||||
|
/// or blockwise to the spi device
|
||||||
|
pub(crate) struct DisplayInterface<SPI, BUSY, DC, RST, DELAY, const SINGLE_BYTE_WRITE: bool> {
|
||||||
|
/// SPI
|
||||||
|
_spi: PhantomData<SPI>,
|
||||||
|
/// DELAY
|
||||||
|
_delay: PhantomData<DELAY>,
|
||||||
|
/// Low for busy, Wait until display is ready!
|
||||||
|
busy: BUSY,
|
||||||
|
/// Data/Command Control Pin (High for data, Low for command)
|
||||||
|
dc: DC,
|
||||||
|
/// Pin for Resetting
|
||||||
|
rst: RST,
|
||||||
|
/// number of ms the idle loop should sleep on
|
||||||
|
delay_us: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, BUSY, DC, RST, DELAY, const SINGLE_BYTE_WRITE: bool>
|
||||||
|
DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// Creates a new `DisplayInterface` struct
|
||||||
|
///
|
||||||
|
/// If no delay is given, a default delay of 10ms is used.
|
||||||
|
pub fn new(busy: BUSY, dc: DC, rst: RST, delay_us: Option<u32>) -> Self {
|
||||||
|
// default delay of 10ms
|
||||||
|
let delay_us = delay_us.unwrap_or(10_000);
|
||||||
|
DisplayInterface {
|
||||||
|
_spi: PhantomData,
|
||||||
|
_delay: PhantomData,
|
||||||
|
busy,
|
||||||
|
dc,
|
||||||
|
rst,
|
||||||
|
delay_us,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic function for sending [Commands](Command).
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
let _ = self.dc.set_low();
|
||||||
|
|
||||||
|
// Transfer the command over spi
|
||||||
|
self.write(spi, &[command.address()])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic function for sending an array of u8-values of data over spi
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
let _ = self.dc.set_high();
|
||||||
|
|
||||||
|
if SINGLE_BYTE_WRITE {
|
||||||
|
for val in data.iter().copied() {
|
||||||
|
// Transfer data one u8 at a time over spi
|
||||||
|
self.write(spi, &[val])?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.write(spi, data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic function for sending [Commands](Command) and the data belonging to it.
|
||||||
|
///
|
||||||
|
/// TODO: directly use ::write? cs wouldn't needed to be changed twice than
|
||||||
|
pub(crate) fn cmd_with_data<T: Command>(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
command: T,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd(spi, command)?;
|
||||||
|
self.data(spi, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic function for sending the same byte of data (one u8) multiple times over spi
|
||||||
|
///
|
||||||
|
/// Enables direct interaction with the device with the help of [command()](ConnectionInterface::command())
|
||||||
|
pub(crate) fn data_x_times(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
val: u8,
|
||||||
|
repetitions: u32,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
// high for data
|
||||||
|
let _ = self.dc.set_high();
|
||||||
|
// Transfer data (u8) over spi
|
||||||
|
for _ in 0..repetitions {
|
||||||
|
self.write(spi, &[val])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// spi write helper/abstraction function
|
||||||
|
fn write(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
|
||||||
|
// transfer spi data
|
||||||
|
// Be careful!! Linux has a default limit of 4096 bytes per spi transfer
|
||||||
|
// see https://raspberrypi.stackexchange.com/questions/65595/spi-transfer-fails-with-buffer-size-greater-than-4096
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
for data_chunk in data.chunks(4096) {
|
||||||
|
spi.write(data_chunk)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
spi.write(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits until device isn't busy anymore (busy == HIGH)
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// is_busy_low
|
||||||
|
///
|
||||||
|
/// - TRUE for epd4in2, epd2in13, epd2in7, epd5in83, epd7in5
|
||||||
|
/// - FALSE for epd2in9, epd1in54 (for all Display Type A ones?)
|
||||||
|
///
|
||||||
|
/// Most likely there was a mistake with the 2in9 busy connection
|
||||||
|
pub(crate) fn wait_until_idle(&mut self, delay: &mut DELAY, is_busy_low: bool) {
|
||||||
|
while self.is_busy(is_busy_low) {
|
||||||
|
// This has been removed and added many time :
|
||||||
|
// - it is faster to not have it
|
||||||
|
// - it is complicated to pass the delay everywhere all the time
|
||||||
|
// - busy waiting can consume more power that delaying
|
||||||
|
// - delay waiting enables task switching on realtime OS
|
||||||
|
// -> keep it and leave the decision to the user
|
||||||
|
if self.delay_us > 0 {
|
||||||
|
delay.delay_us(self.delay_us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `wait_until_idle` for device needing a command to probe Busy pin
|
||||||
|
pub(crate) fn wait_until_idle_with_cmd<T: Command>(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
is_busy_low: bool,
|
||||||
|
status_command: T,
|
||||||
|
) -> Result<(), SPI::Error> {
|
||||||
|
self.cmd(spi, status_command)?;
|
||||||
|
if self.delay_us > 0 {
|
||||||
|
delay.delay_us(self.delay_us);
|
||||||
|
}
|
||||||
|
while self.is_busy(is_busy_low) {
|
||||||
|
self.cmd(spi, status_command)?;
|
||||||
|
if self.delay_us > 0 {
|
||||||
|
delay.delay_us(self.delay_us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
/// is_busy_low
|
||||||
|
///
|
||||||
|
/// - TRUE for epd4in2, epd2in13, epd2in7, epd5in83, epd7in5
|
||||||
|
/// - FALSE for epd2in9, epd1in54 (for all Display Type A ones?)
|
||||||
|
///
|
||||||
|
/// 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(&mut self, is_busy_low: bool) -> bool {
|
||||||
|
(is_busy_low && self.busy.is_low().unwrap_or(false))
|
||||||
|
|| (!is_busy_low && self.busy.is_high().unwrap_or(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the device.
|
||||||
|
///
|
||||||
|
/// Often used to awake the module from deep sleep. See [Epd4in2::sleep()](Epd4in2::sleep())
|
||||||
|
///
|
||||||
|
/// The timing of keeping the reset pin low seems to be important and different per device.
|
||||||
|
/// Most displays seem to require keeping it low for 10ms, but the 7in5_v2 only seems to reset
|
||||||
|
/// properly with 2ms
|
||||||
|
pub(crate) fn reset(&mut self, delay: &mut DELAY, initial_delay: u32, duration: u32) {
|
||||||
|
let _ = self.rst.set_high();
|
||||||
|
delay.delay_us(initial_delay);
|
||||||
|
|
||||||
|
let _ = self.rst.set_low();
|
||||||
|
delay.delay_us(duration);
|
||||||
|
let _ = self.rst.set_high();
|
||||||
|
//TODO: the upstream libraries always sleep for 200ms here
|
||||||
|
// 10ms works fine with just for the 7in5_v2 but this needs to be validated for other devices
|
||||||
|
delay.delay_us(200_000);
|
||||||
|
}
|
||||||
|
}
|
135
epd-waveshare/src/lib.rs
Normal file
135
epd-waveshare/src/lib.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//! A simple Driver for the [Waveshare](https://github.com/waveshare/e-Paper) E-Ink Displays via SPI
|
||||||
|
//!
|
||||||
|
//! - Built using [`embedded-hal`] traits.
|
||||||
|
//! - Graphics support is added through [`embedded-graphics`]
|
||||||
|
//!
|
||||||
|
//! [`embedded-graphics`]: https://docs.rs/embedded-graphics/
|
||||||
|
//! [`embedded-hal`]: https://docs.rs/embedded-hal
|
||||||
|
//!
|
||||||
|
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//!```rust, no_run
|
||||||
|
//!# use embedded_hal_mock::eh1::*;
|
||||||
|
//!# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
//!use embedded_graphics::{
|
||||||
|
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
//!};
|
||||||
|
//!use epd_waveshare::{epd1in54::*, prelude::*};
|
||||||
|
//!#
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
//!# let expectations = [];
|
||||||
|
//!# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
//!# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
//!# let dc = pin::Mock::new(&expectations);
|
||||||
|
//!# let rst = pin::Mock::new(&expectations);
|
||||||
|
//!# let mut delay = delay::NoopDelay::new();
|
||||||
|
//!
|
||||||
|
//!// Setup EPD
|
||||||
|
//!let mut epd = Epd1in54::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
//!
|
||||||
|
//!// Use display graphics from embedded-graphics
|
||||||
|
//!let mut display = Display1in54::default();
|
||||||
|
//!
|
||||||
|
//!// Use embedded graphics for drawing a line
|
||||||
|
//!
|
||||||
|
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||||
|
//! .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
//! .draw(&mut display);
|
||||||
|
//!
|
||||||
|
//! // Display updated frame
|
||||||
|
//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
//!
|
||||||
|
//!// Set the EPD to sleep
|
||||||
|
//!epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
//!# Ok(())
|
||||||
|
//!# }
|
||||||
|
//!```
|
||||||
|
//!
|
||||||
|
//! # Other information and requirements
|
||||||
|
//!
|
||||||
|
//! - Buffersize: Wherever a buffer is used it always needs to be of the size: `width / 8 * length`,
|
||||||
|
//! where width and length being either the full e-ink size or the partial update window size
|
||||||
|
//!
|
||||||
|
//! ### SPI
|
||||||
|
//!
|
||||||
|
//! MISO is not connected/available. SPI_MODE_0 is used (CPHL = 0, CPOL = 0) with 8 bits per word, MSB first.
|
||||||
|
//!
|
||||||
|
//! Maximum speed tested by myself was 8Mhz but more should be possible (Ben Krasnow used 18Mhz with his implemenation)
|
||||||
|
//!
|
||||||
|
#![no_std]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub mod graphics;
|
||||||
|
|
||||||
|
mod traits;
|
||||||
|
|
||||||
|
pub mod color;
|
||||||
|
|
||||||
|
/// Interface for the physical connection between display and the controlling device
|
||||||
|
mod interface;
|
||||||
|
|
||||||
|
pub mod epd1in54;
|
||||||
|
pub mod epd1in54_v2;
|
||||||
|
pub mod epd1in54b;
|
||||||
|
pub mod epd1in54c;
|
||||||
|
pub mod epd2in13_v2;
|
||||||
|
pub mod epd2in13bc;
|
||||||
|
pub mod epd2in66b;
|
||||||
|
pub mod epd2in7b;
|
||||||
|
pub mod epd2in9;
|
||||||
|
pub mod epd2in9_v2;
|
||||||
|
pub mod epd2in9bc;
|
||||||
|
pub mod epd2in9d;
|
||||||
|
pub mod epd3in7;
|
||||||
|
pub mod epd4in2;
|
||||||
|
pub mod epd5in65f;
|
||||||
|
pub mod epd5in83_v2;
|
||||||
|
pub mod epd5in83b_v2;
|
||||||
|
pub mod epd7in3f;
|
||||||
|
pub mod epd7in5;
|
||||||
|
pub mod epd7in5_hd;
|
||||||
|
pub mod epd7in5_v2;
|
||||||
|
pub mod epd7in5b_v2;
|
||||||
|
pub use epd7in5b_v2 as epd7in5b_v3;
|
||||||
|
|
||||||
|
pub(crate) mod type_a;
|
||||||
|
|
||||||
|
/// Includes everything important besides the chosen Display
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::color::{Color, OctColor, TriColor};
|
||||||
|
pub use crate::traits::{
|
||||||
|
QuickRefresh, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use crate::SPI_MODE;
|
||||||
|
|
||||||
|
#[cfg(feature = "graphics")]
|
||||||
|
pub use crate::graphics::{Display, DisplayRotation};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the needed buffer length. Takes care of rounding up in case width
|
||||||
|
/// is not divisible by 8.
|
||||||
|
///
|
||||||
|
/// unused
|
||||||
|
/// bits width
|
||||||
|
/// <----><------------------------>
|
||||||
|
/// \[XXXXX210\]\[76543210\]...\[76543210\] ^
|
||||||
|
/// \[XXXXX210\]\[76543210\]...\[76543210\] | height
|
||||||
|
/// \[XXXXX210\]\[76543210\]...\[76543210\] v
|
||||||
|
pub const fn buffer_len(width: usize, height: usize) -> usize {
|
||||||
|
(width + 7) / 8 * height
|
||||||
|
}
|
||||||
|
|
||||||
|
use embedded_hal::spi::{Mode, Phase, Polarity};
|
||||||
|
|
||||||
|
/// SPI mode -
|
||||||
|
/// For more infos see [Requirements: SPI](index.html#spi)
|
||||||
|
pub const SPI_MODE: Mode = Mode {
|
||||||
|
phase: Phase::CaptureOnFirstTransition,
|
||||||
|
polarity: Polarity::IdleLow,
|
||||||
|
};
|
360
epd-waveshare/src/traits.rs
Normal file
360
epd-waveshare/src/traits.rs
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
use core::marker::Sized;
|
||||||
|
use embedded_hal::{delay::*, digital::*, spi::SpiDevice};
|
||||||
|
|
||||||
|
/// All commands need to have this trait which gives the address of the command
|
||||||
|
/// which needs to be send via SPI with activated CommandsPin (Data/Command Pin in CommandMode)
|
||||||
|
pub(crate) trait Command: Copy {
|
||||||
|
fn address(self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seperates the different LUT for the Display Refresh process
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, Default)]
|
||||||
|
pub enum RefreshLut {
|
||||||
|
/// The "normal" full Lookuptable for the Refresh-Sequence
|
||||||
|
#[default]
|
||||||
|
Full,
|
||||||
|
/// The quick LUT where not the full refresh sequence is followed.
|
||||||
|
/// This might lead to some
|
||||||
|
Quick,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// This initialises the EPD and powers it up
|
||||||
|
///
|
||||||
|
/// This function is already called from
|
||||||
|
/// - [new()](WaveshareDisplay::new())
|
||||||
|
/// - [`wake_up`]
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// This function calls [reset](WaveshareDisplay::reset),
|
||||||
|
/// so you don't need to call reset your self when trying to wake your device up
|
||||||
|
/// after setting it to sleep.
|
||||||
|
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Functions to interact with three color panels
|
||||||
|
pub trait WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>:
|
||||||
|
WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// Transmit data to the SRAM of the EPD
|
||||||
|
///
|
||||||
|
/// Updates both the black and the secondary color layers
|
||||||
|
fn update_color_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Update only the black/white data of the display.
|
||||||
|
///
|
||||||
|
/// This must be finished by calling `update_chromatic_frame`.
|
||||||
|
fn update_achromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
black: &[u8],
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Update only the chromatic data of the display.
|
||||||
|
///
|
||||||
|
/// This should be preceded by a call to `update_achromatic_frame`.
|
||||||
|
/// This data takes precedence over the black/white data.
|
||||||
|
fn update_chromatic_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
chromatic: &[u8],
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All the functions to interact with the EPDs
|
||||||
|
///
|
||||||
|
/// This trait includes all public functions to use the EPDs
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
///```rust, no_run
|
||||||
|
///# use embedded_hal_mock::eh1::*;
|
||||||
|
///# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
///use embedded_graphics::{
|
||||||
|
/// pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
///};
|
||||||
|
///use epd_waveshare::{epd4in2::*, prelude::*};
|
||||||
|
///#
|
||||||
|
///# let expectations = [];
|
||||||
|
///# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
///# let expectations = [];
|
||||||
|
///# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
///# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
///# let dc = pin::Mock::new(&expectations);
|
||||||
|
///# let rst = pin::Mock::new(&expectations);
|
||||||
|
///# let mut delay = delay::NoopDelay::new();
|
||||||
|
///
|
||||||
|
///// Setup EPD
|
||||||
|
///let mut epd = Epd4in2::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
///
|
||||||
|
///// Use display graphics from embedded-graphics
|
||||||
|
///let mut display = Display4in2::default();
|
||||||
|
///
|
||||||
|
///// Use embedded graphics for drawing a line
|
||||||
|
///
|
||||||
|
///let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||||
|
/// .into_styled(PrimitiveStyle::with_stroke(Color::Black, 1))
|
||||||
|
/// .draw(&mut display);
|
||||||
|
///
|
||||||
|
/// // Display updated frame
|
||||||
|
///epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
|
||||||
|
///epd.display_frame(&mut spi, &mut delay)?;
|
||||||
|
///
|
||||||
|
///// Set the EPD to sleep
|
||||||
|
///epd.sleep(&mut spi, &mut delay)?;
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
///```
|
||||||
|
pub trait WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// The Color Type used by the Display
|
||||||
|
type DisplayColor;
|
||||||
|
/// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC
|
||||||
|
///
|
||||||
|
/// `delay_us` is the number of us the idle loop should sleep on.
|
||||||
|
/// Setting it to 0 implies busy waiting.
|
||||||
|
/// Setting it to None means a default value is used.
|
||||||
|
///
|
||||||
|
/// This already initialises the device.
|
||||||
|
fn new(
|
||||||
|
spi: &mut SPI,
|
||||||
|
busy: BUSY,
|
||||||
|
dc: DC,
|
||||||
|
rst: RST,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
delay_us: Option<u32>,
|
||||||
|
) -> Result<Self, SPI::Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
/// Let the device enter deep-sleep mode to save power.
|
||||||
|
///
|
||||||
|
/// The deep sleep mode returns to standby with a hardware reset.
|
||||||
|
fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Wakes the device up from sleep
|
||||||
|
///
|
||||||
|
/// Also reintialises the device if necessary.
|
||||||
|
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Sets the backgroundcolor for various commands like [clear_frame](WaveshareDisplay::clear_frame)
|
||||||
|
fn set_background_color(&mut self, color: Self::DisplayColor);
|
||||||
|
|
||||||
|
/// Get current background color
|
||||||
|
fn background_color(&self) -> &Self::DisplayColor;
|
||||||
|
|
||||||
|
/// Get the width of the display
|
||||||
|
fn width(&self) -> u32;
|
||||||
|
|
||||||
|
/// Get the height of the display
|
||||||
|
fn height(&self) -> u32;
|
||||||
|
|
||||||
|
/// Transmit a full frame to the SRAM of the EPD
|
||||||
|
fn update_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Transmits partial data to the SRAM of the EPD
|
||||||
|
///
|
||||||
|
/// (x,y) is the top left corner
|
||||||
|
///
|
||||||
|
/// BUFFER needs to be of size: width / 8 * height !
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn update_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Displays the frame data from SRAM
|
||||||
|
///
|
||||||
|
/// This function waits until the device isn`t busy anymore
|
||||||
|
fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// 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],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Clears the frame buffer on the EPD with the declared background color
|
||||||
|
///
|
||||||
|
/// The background color can be changed with [`WaveshareDisplay::set_background_color`]
|
||||||
|
fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Trait for using various Waveforms from different LUTs
|
||||||
|
/// E.g. for partial refreshes
|
||||||
|
///
|
||||||
|
/// A full refresh is needed after a certain amount of quick refreshes!
|
||||||
|
///
|
||||||
|
/// WARNING: Quick Refresh might lead to ghosting-effects/problems with your display. Especially for the 4.2in Display!
|
||||||
|
///
|
||||||
|
/// If None is used the old value will be loaded on the LUTs once more
|
||||||
|
fn set_lut(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
refresh_rate: Option<RefreshLut>,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Wait until the display has stopped processing data
|
||||||
|
///
|
||||||
|
/// You can call this to make sure a frame is displayed before goin further
|
||||||
|
fn wait_until_idle(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows quick refresh support for displays that support it; lets you send both
|
||||||
|
/// old and new frame data to support this.
|
||||||
|
///
|
||||||
|
/// When using the quick refresh look-up table, the display must receive separate display
|
||||||
|
/// buffer data marked as old, and new. This is used to determine which pixels need to change,
|
||||||
|
/// and how they will change. This isn't required when using full refreshes.
|
||||||
|
///
|
||||||
|
/// (todo: Example ommitted due to CI failures.)
|
||||||
|
/// Example:
|
||||||
|
///```rust, no_run
|
||||||
|
///# use embedded_hal_mock::eh1::*;
|
||||||
|
///# fn main() -> Result<(), embedded_hal::spi::ErrorKind> {
|
||||||
|
///# use embedded_graphics::{
|
||||||
|
///# pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||||
|
///# };
|
||||||
|
///# use epd_waveshare::{epd4in2::*, prelude::*};
|
||||||
|
///# use epd_waveshare::graphics::VarDisplay;
|
||||||
|
///#
|
||||||
|
///# let expectations = [];
|
||||||
|
///# let mut spi = spi::Mock::new(&expectations);
|
||||||
|
///# let expectations = [];
|
||||||
|
///# let cs_pin = pin::Mock::new(&expectations);
|
||||||
|
///# let busy_in = pin::Mock::new(&expectations);
|
||||||
|
///# let dc = pin::Mock::new(&expectations);
|
||||||
|
///# let rst = pin::Mock::new(&expectations);
|
||||||
|
///# let mut delay = delay::NoopDelay::new();
|
||||||
|
///#
|
||||||
|
///# // Setup EPD
|
||||||
|
///# let mut epd = Epd4in2::new(&mut spi, busy_in, dc, rst, &mut delay, None)?;
|
||||||
|
///let (x, y, frame_width, frame_height) = (20, 40, 80,80);
|
||||||
|
///
|
||||||
|
///let mut buffer = [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 80 / 8 * 80];
|
||||||
|
///let mut display = VarDisplay::new(frame_width, frame_height, &mut buffer,false).unwrap();
|
||||||
|
///
|
||||||
|
///epd.update_partial_old_frame(&mut spi, &mut delay, display.buffer(), x, y, frame_width, frame_height)
|
||||||
|
/// .ok();
|
||||||
|
///
|
||||||
|
///display.clear(Color::White).ok();
|
||||||
|
///// Execute drawing commands here.
|
||||||
|
///
|
||||||
|
///epd.update_partial_new_frame(&mut spi, &mut delay, display.buffer(), x, y, frame_width, frame_height)
|
||||||
|
/// .ok();
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
///```
|
||||||
|
pub trait QuickRefresh<SPI, BUSY, DC, RST, DELAY>
|
||||||
|
where
|
||||||
|
SPI: SpiDevice,
|
||||||
|
BUSY: InputPin,
|
||||||
|
DC: OutputPin,
|
||||||
|
RST: OutputPin,
|
||||||
|
DELAY: DelayNs,
|
||||||
|
{
|
||||||
|
/// Updates the old frame.
|
||||||
|
fn update_old_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Updates the new frame.
|
||||||
|
fn update_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Displays the new frame
|
||||||
|
fn display_new_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Updates and displays the new frame.
|
||||||
|
fn update_and_display_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
buffer: &[u8],
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Updates the old frame for a portion of the display.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn update_partial_old_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Updates the new frame for a portion of the display.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn update_partial_new_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
buffer: &[u8],
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
|
||||||
|
/// Clears the partial frame buffer on the EPD with the declared background color
|
||||||
|
/// The background color can be changed with [`WaveshareDisplay::set_background_color`]
|
||||||
|
fn clear_partial_frame(
|
||||||
|
&mut self,
|
||||||
|
spi: &mut SPI,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), SPI::Error>;
|
||||||
|
}
|
103
epd-waveshare/src/type_a/command.rs
Normal file
103
epd-waveshare/src/type_a/command.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
//! SPI Commands for the Waveshare 2.9" and 1.54" E-Ink Display
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
|
/// Epd1in54 and EPD2IN9 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)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Driver Output control
|
||||||
|
/// 3 Databytes:
|
||||||
|
/// A[7:0]
|
||||||
|
/// 0.. A[8]
|
||||||
|
/// 0.. B[2:0]
|
||||||
|
/// Default: Set A[8:0] = 0x127 and B[2:0] = 0x0
|
||||||
|
DriverOutputControl = 0x01,
|
||||||
|
GateDrivingVoltage = 0x03,
|
||||||
|
SourceDrivingVoltage = 0x04,
|
||||||
|
/// Booster Soft start control
|
||||||
|
/// 3 Databytes:
|
||||||
|
/// 1.. A[6:0]
|
||||||
|
/// 1.. B[6:0]
|
||||||
|
/// 1.. C[6:0]
|
||||||
|
/// Default: A[7:0] = 0xCF, B[7:0] = 0xCE, C[7:0] = 0x8D
|
||||||
|
BoosterSoftStartControl = 0x0C,
|
||||||
|
GateScanStartPosition = 0x0F,
|
||||||
|
//TODO: useful?
|
||||||
|
// GateScanStartPosition = 0x0F,
|
||||||
|
/// Deep Sleep Mode Control
|
||||||
|
/// 1 Databyte:
|
||||||
|
/// 0.. A[0]
|
||||||
|
/// Values:
|
||||||
|
/// A[0] = 0: Normal Mode (POR)
|
||||||
|
/// A[0] = 1: Enter Deep Sleep Mode
|
||||||
|
DeepSleepMode = 0x10,
|
||||||
|
// /// Data Entry mode setting
|
||||||
|
DataEntryModeSetting = 0x11,
|
||||||
|
|
||||||
|
SwReset = 0x12,
|
||||||
|
|
||||||
|
TemperatureSensorSelection = 0x18,
|
||||||
|
|
||||||
|
TemperatureSensorControl = 0x1A,
|
||||||
|
|
||||||
|
MasterActivation = 0x20,
|
||||||
|
|
||||||
|
DisplayUpdateControl1 = 0x21,
|
||||||
|
|
||||||
|
DisplayUpdateControl2 = 0x22,
|
||||||
|
|
||||||
|
WriteRam = 0x24,
|
||||||
|
|
||||||
|
WriteRam2 = 0x26,
|
||||||
|
|
||||||
|
WriteVcomRegister = 0x2C,
|
||||||
|
|
||||||
|
WriteLutRegister = 0x32,
|
||||||
|
|
||||||
|
WriteOtpSelection = 0x37,
|
||||||
|
|
||||||
|
SetDummyLinePeriod = 0x3A,
|
||||||
|
|
||||||
|
SetGateLineWidth = 0x3B,
|
||||||
|
|
||||||
|
BorderWaveformControl = 0x3C,
|
||||||
|
|
||||||
|
WriteLutRegisterEnd = 0x3f,
|
||||||
|
|
||||||
|
SetRamXAddressStartEndPosition = 0x44,
|
||||||
|
|
||||||
|
SetRamYAddressStartEndPosition = 0x45,
|
||||||
|
|
||||||
|
SetRamXAddressCounter = 0x4E,
|
||||||
|
|
||||||
|
SetRamYAddressCounter = 0x4F,
|
||||||
|
|
||||||
|
Nop = 0xFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl traits::Command for Command {
|
||||||
|
/// Returns the address of the command
|
||||||
|
fn address(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Command;
|
||||||
|
use crate::traits::Command as CommandTrait;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn command_addr() {
|
||||||
|
assert_eq!(Command::DriverOutputControl.address(), 0x01);
|
||||||
|
|
||||||
|
assert_eq!(Command::SetRamXAddressCounter.address(), 0x4E);
|
||||||
|
|
||||||
|
assert_eq!(Command::Nop.address(), 0xFF);
|
||||||
|
}
|
||||||
|
}
|
25
epd-waveshare/src/type_a/constants.rs
Normal file
25
epd-waveshare/src/type_a/constants.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#[cfg(not(any(feature = "type_a_alternative_faster_lut")))]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
// Original Waveforms from Waveshare
|
||||||
|
pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[
|
||||||
|
0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22,
|
||||||
|
0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51,
|
||||||
|
0x35, 0x51, 0x51, 0x19, 0x01, 0x00
|
||||||
|
];
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const LUT_PARTIAL_UPDATE: [u8; 30] =[
|
||||||
|
0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(feature = "type_a_alternative_faster_lut")]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
// Waveform from TeXiToi/il3820
|
||||||
|
pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[
|
||||||
|
0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
2
epd-waveshare/src/type_a/mod.rs
Normal file
2
epd-waveshare/src/type_a/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) mod command;
|
||||||
|
pub(crate) mod constants;
|
Loading…
Reference in New Issue
Block a user