Compare commits

...

6 Commits

Author SHA1 Message Date
97ca331f34 Fix keypad 2024-09-26 16:09:27 +02:00
b5da325946 Keypad mapping 2024-09-26 16:08:49 +02:00
2fb089fd03 Create keypad handler 2024-09-26 16:08:48 +02:00
91e75d37a0 New screen 2024-09-26 06:54:01 +02:00
e2d322c9eb Waveshare 2024-09-23 17:07:33 +02:00
b76727a01e Waveshare 2024-09-23 17:07:30 +02:00
65 changed files with 13408 additions and 180 deletions

17
Cargo.lock generated
View File

@ -90,6 +90,12 @@ dependencies = [
"serde",
]
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitfield"
version = "0.16.1"
@ -380,6 +386,15 @@ dependencies = [
"syn 2.0.77",
]
[[package]]
name = "epd-waveshare"
version = "0.5.0"
dependencies = [
"bit_field",
"embedded-graphics-core",
"embedded-hal 1.0.0",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -883,7 +898,9 @@ version = "0.1.0"
dependencies = [
"display-interface-spi",
"embedded-graphics",
"embedded-hal 1.0.0",
"embedded-hal-bus",
"epd-waveshare",
"esp-backtrace",
"esp-hal",
"esp-println",

View File

@ -21,6 +21,8 @@ shared = { path = "./shared", features = ['trng'] }
maze = { path = "./maze" }
strum = { version = "0.26.3", default-features = false, features = ["derive"] }
keypad = "0.2.2"
epd-waveshare = { path = "./epd-waveshare" }
embedded-hal = "1.0.0"
[profile.dev]
# Rust debug is too slow.

16
epd-waveshare/.gitignore vendored Normal file
View 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
View 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
View 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
View 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))
);
}
}

View 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);
}
}

View 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,
];

View 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);
}
}

View 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
}
}

View 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,
];

View 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);
}
}

View 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
}
}

View 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])
}
}

View 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
}
}

View 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
];

View 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);
}
}

View 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
}
}

View 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],
)
}
}

View 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,
}

View 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)
}
}

View 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);
}
}

View 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,
];

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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
}
}

View 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],
)
}
}

View 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
}
}

View 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,
];

View 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 OTPKW-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 OTPKW-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(())
}
}

View 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
}
}

View 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,
];

View 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(())
}
}

View 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);
}
}

View 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,
];

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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
}
}

View 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)
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}
}

View 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
View 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
View 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>;
}

View 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);
}
}

View 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,
];

View File

@ -0,0 +1,2 @@
pub(crate) mod command;
pub(crate) mod constants;

View File

@ -25,7 +25,8 @@ impl App for MazeGame {
BinaryMapVisitor.format(&mut grid, &mut self.map.0);
}
fn draw(&self, display: &mut Display290TriColor) {
fn draw(&self, ctx: &mut Context) {
/*
let wall_style = PrimitiveStyleBuilder::new()
.stroke_color(TriColor::Black)
.stroke_width(3)
@ -60,15 +61,16 @@ impl App for MazeGame {
),
Size::new(1, 1),
);
p.draw_styled(&player_style, display).unwrap();
p.draw_styled(&player_style, ctx.display).unwrap();
}
*/
}
fn update(&mut self, ctx: &Context) -> Action {
fn update(&mut self, ctx: &mut Context) -> Option<Action> {
let Some(button) = ctx.button_pressed else {
return Action::Noop;
return None;
};
match button {
Some(match button {
Button::Up if self.map.can_move(self.player, Direction::North) => {
self.player.1 -= 1;
}
@ -81,9 +83,9 @@ impl App for MazeGame {
Button::Right if self.map.can_move(self.player, Direction::East) => {
self.player.0 += 1;
}
Button::Back => return Action::GoToMenu,
Button::Back => {}
_ => {}
};
Action::Noop
});
None
}
}

View File

@ -42,33 +42,35 @@ impl Menu {
impl App for Menu {
fn start(&mut self, _trng: &mut Trng) {}
fn update(&mut self, ctx: &Context) -> Action {
fn update(&mut self, ctx: &mut Context) -> Option<Action> {
let Some(button) = ctx.button_pressed else {
return Action::Noop;
return None;
};
match button {
Button::Up => match self.selected {
MenuEntry::Labirynth => return Action::Noop,
MenuEntry::Labirynth => return None,
MenuEntry::Pairs => {
self.selected = MenuEntry::Labirynth;
None
}
},
Button::Down => match self.selected {
MenuEntry::Labirynth => {
self.selected = MenuEntry::Pairs;
None
}
MenuEntry::Pairs => return Action::Noop,
MenuEntry::Pairs => None,
},
Button::Circle => match self.selected {
MenuEntry::Labirynth => return Action::StartMaze,
MenuEntry::Pairs => return Action::StartPairs,
MenuEntry::Labirynth => return Some(Action::StartMaze),
MenuEntry::Pairs => return Some(Action::StartPairs),
},
_ => return Action::Noop,
};
Action::Noop
_ => None,
}
}
fn draw(&self, display: &mut Display290TriColor) {
fn draw(&self, ctx: &mut Context) {
/*
let style = MonoTextStyle::new(&PROFONT_18_POINT, TriColor::Black);
let selected_style = MonoTextStyle::new(&PROFONT_18_POINT, TriColor::Red);
@ -97,5 +99,6 @@ impl App for Menu {
.draw(display)
.unwrap();
});
*/
}
}

View File

@ -17,7 +17,6 @@ pub mod maze_game;
pub mod menu;
pub enum Action {
Noop,
GoToMenu,
StartMaze,
StartPairs,
@ -29,10 +28,12 @@ pub enum Application {
}
impl Application {
pub fn update(&mut self, ctx: &Context, trng: &mut Trng) {
let action = match self {
pub fn update(&mut self, ctx: &mut Context, trng: &mut Trng) {
let Some(action) = (match self {
Self::Menu(menu) => menu.update(ctx),
Self::Maze(maze) => maze.update(ctx),
}) else {
return;
};
match action {
Action::StartMaze => {
@ -45,15 +46,14 @@ impl Application {
menu.start(trng);
*self = Application::Menu(menu);
}
Action::Noop => {}
Action::StartPairs => {}
}
}
pub fn draw(&self, display: &mut Display290TriColor) {
pub fn draw(&self, ctx: &mut Context) {
match self {
Self::Menu(menu) => menu.draw(display),
Self::Maze(maze) => maze.draw(display),
Self::Menu(menu) => menu.draw(ctx),
Self::Maze(maze) => maze.draw(ctx),
}
}
}
@ -61,7 +61,13 @@ impl Application {
pub trait App {
fn start(&mut self, trng: &mut Trng);
fn draw(&self, display: &mut Display290TriColor);
fn draw(&self, display: &mut Context);
fn update(&mut self, ctx: &Context) -> Action;
fn update(&mut self, ctx: &mut Context) -> Option<Action>;
fn draw_and_update(&mut self, ctx: &mut Context) -> Option<Action> {
let action = self.update(ctx);
self.draw(ctx);
action
}
}

351
src/keypad.rs Normal file
View File

@ -0,0 +1,351 @@
#[doc(hidden)]
pub extern crate core as _core;
use core::cell::RefCell;
use esp_hal::gpio::{Input, InputPin, OutputOpenDrain, OutputPin};
/// A virtual `embedded-hal` input pin representing one key of the keypad.
///
/// A `KeypadInput` stores references to one row and one column pin. When you
/// read from it with `.is_low()` or `.is_high()`, it secretly sets the column
/// pin low, reads from the row pin, and then sets the column pin high again.
/// The column pin is actually stored inside a `RefCell` in the keypad struct,
/// so that multiple `KeypadInput`s can mutate the column pin's state as needed,
/// even though they only have a shared/immutable reference to it.
///
/// This has several implications.
///
/// 1) Reading from `KeypadInput`s is not reentrant. If we were in the middle
/// of reading a `KeypadInput` and entered an interrupt service routine that
/// read any `KeypadInput` of the same keypad, we might read an incorrect value
/// or cause a `panic`.
///
/// 2) Reading from a `KeypadInput` is slower than reading from a real input
/// pin, because it needs to change the output pin state twice for every read.
pub struct KeypadInput<'a> {
row: &'a RefCell<Input<'a, InputPin>>,
col: &'a RefCell<OutputOpenDrain<'a, dyn InputPin + OutputPin>>,
}
impl<'a, E> KeypadInput<'a, E> {
/// Create a new `KeypadInput`. For use in macros.
pub fn new(row: &'a dyn InputPin, col: &'a RefCell<dyn OutputPin>) -> Self {
Self { row, col }
}
}
impl<'a, E> InputPin for KeypadInput<'a, E> {
type Error = E;
/// Read the state of the key at this row and column. Not reentrant.
fn is_high(&self) -> Result<bool, E> {
Ok(!self.is_low()?)
}
/// Read the state of the key at this row and column. Not reentrant.
fn is_low(&self) -> Result<bool, E> {
self.col.borrow_mut().set_low()?;
let out = self.row.is_low()?;
self.col.borrow_mut().set_high()?;
Ok(out)
}
}
/// Define a new struct representing your keypad matrix circuit.
///
/// Every pin has a unique type, depending on its pin number and its current
/// mode. This struct is where you specify which pin types will be used in the
/// rows and columns of the keypad matrix. All the row pins must implement the
/// `InputPin` trait, and the column pins must implement the `OutputPin` trait.
/// The associated `Error` type of the `InputPin` and `OutputPin` traits must be
/// the same for every row and column pin, and you must specify it after your
/// struct name with `<Error = ...>`
///
/// You can specify the visibility of the struct (eg. `pub`) as usual, and add
/// doc comments using the `#[doc="..."]` attribute.
///
/// Don't access or modify the struct's fields directly. Instead, use
/// the methods implemented by this macro, documented here:
/// [`example_generated::ExampleKeypad`](./example_generated/struct.ExampleKeypad.html)
///
/// # Example
///
/// ```
/// # #![cfg_attr(docs_rs_workaround, feature(macro_vis_matcher))]
/// #[macro_use]
/// extern crate keypad;
///
/// use keypad::mock_hal::{self, Input, OpenDrain, Output, PullUp};
/// use core::convert::Infallible;
///
/// keypad_struct! {
/// #[doc="My super-special keypad."]
/// pub struct ExampleKeypad<Error = Infallible> {
/// rows: (
/// mock_hal::gpioa::PA0<Input<PullUp>>,
/// mock_hal::gpioa::PA1<Input<PullUp>>,
/// mock_hal::gpioa::PA2<Input<PullUp>>,
/// mock_hal::gpioa::PA3<Input<PullUp>>,
/// ),
/// columns: (
/// mock_hal::gpioa::PA4<Output<OpenDrain>>,
/// mock_hal::gpioa::PA5<Output<OpenDrain>>,
/// mock_hal::gpioa::PA6<Output<OpenDrain>>,
/// mock_hal::gpioa::PA7<Output<OpenDrain>>,
/// mock_hal::gpioa::PA8<Output<OpenDrain>>,
/// ),
/// }
/// }
///
/// # fn main() {
/// # }
/// ```
///
/// # Safety
///
/// This macro uses `unsafe` to create an array with uninitialized memory, which
/// is then immediately initialized in a loop. This is fine as long as there is
/// not a bug in how the macro calculates the dimensions of the array.
// There are two reasons why this big, scary macro is necessary:
//
// 1) Every single pin has a unique type, and we don't know which pins will be used. We know that
// they all implement a certain trait, but that doesn't help much because it doesn't tell us the
// size of the type, so we can't directly stick it into the struct. If we could use dynamic
// allocation, we could just store pins on the heap as boxed trait objects. But this crate needs
// to work on embedded platforms without an allocator! So, we use this macro to generate a
// struct containing the exact, concrete pin types the user provides.
//
// 2) We don't know how many pins there will be, because the keypad could have any number of rows
// and columns. That makes it hard to implement `decompose()`, which needs to iterate over the
// row and column pins. We can't store pins in arrays because they all have unique types, but we
// can store them in tuples instead. The problem is that you can't actually iterate over a tuple,
// and even indexing into arbitrary fields of a tuple using a macro is stupidly hard. The best
// approach I could come up with was to repeatedly destructure the tuple with different patterns,
// like this:
//
// let tuple = (0, 1, 2);
// let array = [
// {
// let (ref x, ..) = tuple;
// x
// },
// {
// let (_, ref x, ..) = tuple;
// x
// },
// {
// let (_, _, ref x, ..) = tuple;
// x
// },
// ];
// for reference in array.into_iter() {
// // ...
// }
//
// So that's how the `keypad_struct!()` macro iterates over tuples of pins. It counts the length of
// the tuple (also tricky!), creates patterns with increasing numbers of underscores, and uses them
// to build up a temporary array of references that it can iterate over. Luckily this code only
// needs to be run once, in `decompose()`, and not every time we read from a pin.
//
// I can't think of any simpler design that still has a convenient API and allows the keypad struct
// to own the row and column pins. If they weren't owned, the crate would be less convenient to use
// but could provide a generic Keypad struct like this:
//
// pub struct Keypad<'a, E, const R: usize, const C: usize> {
// rows: [&'a dyn InputPin<Error = E>; R],
// columns: [&'a RefCell<dyn OutputPin<Error = E>>; C],
// }
//
#[macro_export]
macro_rules! keypad_struct {
(
$(#[$attributes:meta])* $visibility:vis struct $struct_name:ident {
rows: ( $($row_type:ty),* $(,)* ),
columns: ( $($col_type:ty),* $(,)* ),
}
) => {
compile_error!("You must specify the associated `Error` type of the row and column pins'\
`InputPin` and `OutputPin` traits.\n\
Example: `struct MyStruct <Error = Infallible> { ... }`");
};
(
$(#[$attributes:meta])* $visibility:vis struct $struct_name:ident <Error = $error_type:ty> {
rows: ( $($row_type:ty),* $(,)* ),
columns: ( $($col_type:ty),* $(,)* ),
}
) => {
$(#[$attributes])* $visibility struct $struct_name {
/// The input pins used for reading each row.
rows: ($($row_type),* ,),
/// The output pins used for scanning through each column. They're
/// wrapped in RefCells so that we can change their state even if we
/// only have shared/immutable reference to them. This lets us
/// actively scan the matrix when reading the state of a virtual
/// `KeypadInput` pin.
columns: ($($crate::_core::cell::RefCell<$col_type>),* ,),
}
impl $struct_name {
/// Get a 2d array of embedded-hal input pins, each representing one
/// key in the keypad matrix.
#[allow(dead_code)]
$visibility fn decompose<'a>(&'a self) ->
keypad_struct!(
@array2d_type
$crate::KeypadInput<'a, $error_type>,
($($row_type),*)
($($crate::_core::cell::RefCell<$col_type>),*)
)
{
let rows: [
&dyn $crate::embedded_hal::digital::v2::InputPin<Error = $error_type>;
keypad_struct!(@count $($row_type)*)
]
= keypad_struct!(@tuple self.rows, ($($row_type),*));
let columns: [
&$crate::_core::cell::RefCell<dyn $crate::embedded_hal::digital::v2::OutputPin<Error = $error_type>>;
keypad_struct!(@count $($col_type)*)
]
= keypad_struct!(@tuple self.columns, ($($col_type),*));
// Create an uninitialized 2d array of MaybeUninit.
let mut out: keypad_struct!(
@array2d_type
$crate::_core::mem::MaybeUninit<$crate::KeypadInput<'a, $error_type>>,
($($row_type),*)
($($crate::_core::cell::RefCell<$col_type>),*)
) = unsafe {
$crate::_core::mem::MaybeUninit::uninit().assume_init()
};
// Initialize each element with a KeypadInput struct
for r in 0..rows.len() {
for c in 0..columns.len() {
out[r][c].write($crate::KeypadInput::new(rows[r], columns[c]));
}
}
// All elements are initialized. Transmute the array to the initialized type.
unsafe { $crate::_core::mem::transmute::<_, _>(out) }
}
/// Give back ownership of the row and column pins.
///
/// This consumes the keypad struct. All references to its virtual
/// `KeypadInput` pins must have gone out of scope before you try to
/// call `.release()`, or it will fail to compile.
///
/// The column pins will be returned inside of `RefCell`s (because
/// macros are hard). You can use `.into_inner()` to extract
/// each column pin from its `RefCell`.
#[allow(dead_code)]
$visibility fn release(self) ->(($($row_type),* ,), ($($crate::_core::cell::RefCell<$col_type>),* ,)) {
(self.rows, self.columns)
}
}
};
(@array2d_type $element_type:ty, ($($row:ty),*) ($($col:ty),*) ) => {
[keypad_struct!(@array1d_type $element_type, ($($col),*)) ; keypad_struct!(@count $($row)*)]
};
(@array1d_type $element_type:ty, ($($col:ty),*)) => {
[$element_type ; keypad_struct!(@count $($col)*)]
};
(@count $($token_trees:tt)*) => {
0usize $(+ keypad_struct!(@replace $token_trees 1usize))*
};
(@replace $_t:tt $sub:expr) => {
$sub
};
(@underscore $unused:tt) => {
_
};
(@destructure_ref $tuple:expr, ($($repeat_n:ty),*)) => {
{
let (
$(keypad_struct!(@underscore $repeat_n),)*
ref nth, ..) = $tuple;
nth
}
};
(@tuple_helper $tuple:expr, ($head:ty), ($($result:expr),* $(,)*)) => {
[
keypad_struct!(@destructure_ref $tuple, ()),
$($result),*
]
};
(@tuple_helper $tuple:expr, ($head:ty $(,$repeats:ty)* $(,)*), ($($result:expr),* $(,)*)) => {
keypad_struct!(
@tuple_helper $tuple, ($($repeats),*),
(
keypad_struct!(@destructure_ref $tuple, ($($repeats),*)),
$($result),*
)
)
};
(@tuple $tuple:expr, ($($repeats:ty),*)) => {
keypad_struct!(@tuple_helper $tuple, ($($repeats),*) , ())
};
}
/// Create an instance of the struct you defined with the `keypad_struct!()` macro..
///
/// The pin numbers and modes will need to match the ones you specified with `keypad_struct!()`.
///
/// ```
/// # #![cfg_attr(docs_rs_workaround, feature(macro_vis_matcher))]
/// # #[macro_use]
/// # extern crate keypad;
/// # use core::convert::Infallible;
/// # use keypad::mock_hal::{self, Input, OpenDrain, Output, PullUp};
/// # use keypad::mock_hal::{GpioExt, GPIOA};
/// # keypad_struct!{
/// # pub struct ExampleKeypad<Error = Infallible>{
/// # rows: (
/// # mock_hal::gpioa::PA0<Input<PullUp>>,
/// # mock_hal::gpioa::PA1<Input<PullUp>>,
/// # mock_hal::gpioa::PA2<Input<PullUp>>,
/// # mock_hal::gpioa::PA3<Input<PullUp>>,
/// # ),
/// # columns: (
/// # mock_hal::gpioa::PA4<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA5<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA6<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA7<Output<OpenDrain>>,
/// # mock_hal::gpioa::PA8<Output<OpenDrain>>,
/// # ),
/// # }
/// # }
/// # fn main() {
/// let pins = GPIOA::split();
///
/// let keypad = keypad_new!(ExampleKeypad {
/// rows: (
/// pins.pa0.into_pull_up_input(),
/// pins.pa1.into_pull_up_input(),
/// pins.pa2.into_pull_up_input(),
/// pins.pa3.into_pull_up_input(),
/// ),
/// columns: (
/// pins.pa4.into_open_drain_output(),
/// pins.pa5.into_open_drain_output(),
/// pins.pa6.into_open_drain_output(),
/// pins.pa7.into_open_drain_output(),
/// pins.pa8.into_open_drain_output(),
/// ),
/// });
/// # }
/// ```
#[macro_export]
macro_rules! keypad_new {
( $struct_name:ident {
rows: ( $($row_val:expr),* $(,)* ),
columns: ( $($col_val:expr),* $(,)* ),
}) => {
$struct_name {
rows: ($($row_val),* ,),
columns: ($($crate::_core::cell::RefCell::new($col_val)),* ,),
}
};
}

View File

@ -5,40 +5,48 @@ use apps::{menu::Menu, Application};
use display_interface_spi::SPIInterface;
use embedded_graphics::{
geometry::Point,
mono_font::MonoTextStyle,
text::{Text, TextStyle},
mono_font::{MonoTextStyle, MonoTextStyleBuilder},
text::{Baseline, Text, TextStyle, TextStyleBuilder},
Drawable,
};
use embedded_hal::delay::DelayNs;
use embedded_hal_bus::spi::ExclusiveDevice;
use epd_waveshare::epd3in7::*;
use epd_waveshare::prelude::*;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
delay::Delay,
gpio::{
Gpio10, Gpio2, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, Input, InputPin, Io, Level,
Output, OutputPin, Pull, NO_PIN,
Gpio10, Gpio2, Gpio20, Gpio21, Gpio22, Gpio23, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8,
Input, Io, Level, Output, OutputOpenDrain, Pull, NO_PIN,
},
peripherals::Peripherals,
prelude::*,
rng::Trng,
spi::{master::Spi, SpiMode},
spi::{master::Spi, FullDuplexMode, SpiMode},
system::SystemControl,
};
use esp_println::println;
use log::LevelFilter;
use profont::PROFONT_24_POINT;
use weact_studio_epd::{graphics::Display290TriColor, TriColor};
use weact_studio_epd::{graphics::DisplayRotation, WeActStudio290TriColorDriver};
mod apps;
type CS<'b> = Output<'b, Gpio20>;
type BUS<'b> = Input<'b, Gpio23>;
type RST<'b> = Output<'b, Gpio22>;
type DC<'b> = Output<'b, Gpio21>;
type SpiBus<'b> = Spi<'b, esp_hal::peripherals::SPI2, FullDuplexMode>;
type SPI<'b> = ExclusiveDevice<SpiBus<'b>, CS<'b>, Delay>;
type Driver<'b> = EPD3in7<SPI<'b>, BUS<'b>, DC<'b>, RST<'b>, Delay>;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let clocks = ClockControl::max(system.clock_control).freeze();
let delay = Delay::new(&clocks);
let mut delay = Delay::new(&clocks);
esp_println::logger::init_logger(LevelFilter::Trace);
// esp_println::logger::init_logger_from_env();
@ -46,6 +54,8 @@ fn main() -> ! {
println!("init");
log::info!("Intializing SPI Bus...");
// Configure SPI
// Settings are taken from
// Pins for WeAct
let mosi /* sda */ = io.pins.gpio18; // D10 / GPIO18
@ -55,7 +65,8 @@ fn main() -> ! {
let rst = io.pins.gpio22; // D4 / GPIO22
let busy = io.pins.gpio23; // D5 / GPIO23
let spi_bus = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0, &clocks).with_pins(
let spi_bus: SpiBus = Spi::new(peripherals.SPI2, 4_000.kHz(), SpiMode::Mode0, &clocks)
.with_pins(
Some(sclk),
Some(mosi),
NO_PIN,
@ -69,78 +80,90 @@ fn main() -> ! {
DC: OutputPin,
RST: OutputPin,
*/
let cs = Output::new(cs, Level::High);
let busy = Input::new(busy, esp_hal::gpio::Pull::Up);
let dc = Output::new(dc, Level::Low);
let cs: CS = Output::new(cs, Level::High);
let busy: BUS = Input::new(busy, esp_hal::gpio::Pull::Up);
let dc: DC = Output::new(dc, Level::Low);
let rst = Output::new(rst, Level::High);
log::info!("Intializing SPI Device...");
let spi_device = ExclusiveDevice::new(spi_bus, cs, delay).expect("SPI device initialize error");
let spi_interface = SPIInterface::new(spi_device, dc);
log::info!("Intializing SPI Device (DONE)");
let mut spi: SPI =
ExclusiveDevice::new(spi_bus, cs, delay).expect("SPI device initialize error");
// let spi_interface = SPIInterface::new(spi_device, dc);
let mut driver =
EPD3in7::new(&mut spi, busy, dc, rst, &mut delay, None).expect("eink initalize error");
// Setup EPD
log::info!("Intializing EPD...");
let mut driver = WeActStudio290TriColorDriver::new(spi_interface, busy, rst, delay);
let mut display = Display290TriColor::new();
let mut display = Display3in7::default();
display.set_rotation(DisplayRotation::Rotate90);
log::info!("Driver init...");
driver.init().unwrap();
log::info!("Intializing EPD (DONE)");
display.clear(TriColor::White);
log::info!("Printing welcome msg...");
let style = MonoTextStyle::new(&PROFONT_24_POINT, TriColor::Red);
let _ = Text::with_text_style("Witamy", Point::new(8, 68), style, TextStyle::default())
.draw(&mut display);
log::info!("Printing welcome msg (DONE)");
// draw white on black background
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_6X10)
.text_color(Color::White)
.background_color(Color::Black)
.build();
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();
let _ =
Text::with_text_style("Witamy!", Point::new(90, 10), style, text_style).draw(&mut display);
driver
.update_and_display_frame(&mut spi, display.buffer(), &mut delay)
.expect("display frame new graphics");
delay.delay_ms(500);
log::info!("Full update...");
let _ = driver.full_update(&display);
log::info!("Full update (DONE)");
// epd2in13
// .set_refresh(&mut spi, &mut delay, RefreshLut::Quick)
// .unwrap();
driver.clear_frame(&mut spi, &mut delay).unwrap();
log::info!("Sleeping for 0.5s...");
delay.delay(500.millis());
let mut kbd = Keyboard {
rows: Rows {
gpio2: Input::new(io.pins.gpio2, Pull::Up),
gpio3: Input::new(io.pins.gpio3, Pull::Up),
gpio4: Input::new(io.pins.gpio4, Pull::Up),
gpio5: Input::new(io.pins.gpio5, Pull::Up),
},
cols: Cols {
gpio6: Output::new(io.pins.gpio6, Level::Low),
gpio7: Output::new(io.pins.gpio7, Level::Low),
gpio8: Output::new(io.pins.gpio8, Level::Low),
gpio10: Output::new(io.pins.gpio10, Level::Low),
},
let mut kbd = Keypad {
i1: Input::new(io.pins.gpio2, Pull::Up),
i2: Input::new(io.pins.gpio3, Pull::Up),
i3: Input::new(io.pins.gpio4, Pull::Up),
i4: Input::new(io.pins.gpio5, Pull::Up),
//---------------------------
o1: OutputOpenDrain::new(io.pins.gpio6, Level::Low, Pull::Up),
o2: OutputOpenDrain::new(io.pins.gpio7, Level::Low, Pull::Up),
o3: OutputOpenDrain::new(io.pins.gpio8, Level::Low, Pull::Up),
o4: OutputOpenDrain::new(io.pins.gpio10, Level::Low, Pull::Up),
};
let mut ctx = Context {
button_pressed: None,
driver,
display,
spi,
delay,
};
let mut app = Application::Menu(Menu::new());
let mut trng = Trng::new(peripherals.RNG, peripherals.ADC1);
display.clear(TriColor::White);
app.draw(&mut display);
driver.full_update(&display).unwrap();
driver.sleep().unwrap();
//app.draw(&mut display);
//driver.full_update(&display).unwrap();
//driver.sleep().unwrap();
ctx.driver.clear_frame(&mut ctx.spi, &mut delay).unwrap();
ctx.driver
.update_and_display_frame(&mut ctx.spi, ctx.display.buffer(), &mut delay)
.expect("display frame new graphics");
let _ = ctx.driver.sleep(&mut ctx.spi, &mut delay);
loop {
ctx.button_pressed = kbd.pressed();
if !ctx.button_pressed.is_none() {
log::info!("Wake up!");
driver.wake_up().unwrap();
display.clear(TriColor::White);
app.update(&ctx, &mut trng);
app.draw(&mut display);
driver.full_update(&display).unwrap();
ctx.driver.wake_up(&mut ctx.spi, &mut delay).unwrap();
//display.clear(TriColor::White);
//app.update(&ctx, &mut trng);
// app.draw(&mut display);
//driver.full_update(&display).unwrap();
ctx.driver.clear_frame(&mut ctx.spi, &mut delay).unwrap();
ctx.driver
.update_and_display_frame(&mut ctx.spi, ctx.display.buffer(), &mut delay)
.expect("display frame new graphics");
let _ = ctx.driver.sleep(&mut ctx.spi, &mut delay);
}
log::info!("Sleeping for 100ms...");
driver.sleep().unwrap();
//driver.sleep().unwrap();
delay.delay(300.millis());
}
}
@ -177,103 +200,69 @@ pub enum Button {
Back,
}
pub struct Context {
button_pressed: Option<Button>,
pub struct Context<'d> {
pub button_pressed: Option<Button>,
pub display: Display3in7,
pub driver: Driver<'d>,
pub spi: SPI<'d>,
pub delay: Delay,
}
struct Rows<'d> {
gpio2: Input<'d, Gpio2>, //
gpio3: Input<'d, Gpio3>, //
gpio4: Input<'d, Gpio4>, //
gpio5: Input<'d, Gpio5>, //
struct Keypad<'d> {
o1: OutputOpenDrain<'d, Gpio6>, //
o2: OutputOpenDrain<'d, Gpio7>, //
o3: OutputOpenDrain<'d, Gpio8>, //
o4: OutputOpenDrain<'d, Gpio10>, //
// --------------------------------------
i1: Input<'d, Gpio2>, //
i2: Input<'d, Gpio3>, //
i3: Input<'d, Gpio4>, //
i4: Input<'d, Gpio5>, //
}
struct Cols<'d> {
gpio6: Output<'d, Gpio6>, //
gpio7: Output<'d, Gpio7>, //
gpio8: Output<'d, Gpio8>, //
gpio10: Output<'d, Gpio10>, //
}
struct Keyboard<'d> {
rows: Rows<'d>,
cols: Cols<'d>,
}
impl<'d> Keyboard<'d> {
impl<'d> Keypad<'d> {
pub fn pressed(&mut self) -> Option<Button> {
self.cols.gpio6.set_high();
let col1 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio6.set_high();
let res: [[bool; 4]; 4] = core::array::from_fn(|x| {
core::array::from_fn(|y| {
self.set_high(x as u8);
let v = self.is_low(y as u8);
self.set_low(x as u8);
v
})
});
self.cols.gpio7.set_high();
let col2 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio7.set_low();
self.cols.gpio8.set_high();
let col3 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio8.set_low();
self.cols.gpio10.set_high();
let col4 = [
self.rows.gpio2.is_high(),
self.rows.gpio3.is_high(),
self.rows.gpio4.is_high(),
self.rows.gpio5.is_high(),
];
self.cols.gpio10.set_low();
if res.iter().any(|a| a.iter().any(|b| !b)) {
println!("***************************************");
println!("col1 {col1:?}");
println!("col2 {col2:?}");
println!("col3 {col3:?}");
println!("col4 {col4:?}");
for a in res {
println!("col {a:?}");
}
}
None
}
}
/*
struct Keyboard<'d>(
[(
core::cell::RefCell<dyn InputPin + 'd>,
core::cell::RefCell<dyn OutputPin + 'd>,
); 16],
);
impl<'d> Keyboard<'d> {
pub fn new(cols: [impl InputPin; 4], rows: [impl OutputPin; 4]) {
core::array::from_fn(|x| )
for (x, c) in cols.into_iter().enumerate() {
for (y, r) in rows.into_iter().enumerate() {
;
fn set_high(&mut self, o: u8) {
match o {
0 => self.o1.set_high(),
1 => self.o2.set_high(),
2 => self.o3.set_high(),
3 => self.o4.set_high(),
_ => {}
};
}
fn set_low(&mut self, o: u8) {
match o {
0 => self.o1.set_low(),
1 => self.o2.set_low(),
2 => self.o3.set_low(),
3 => self.o4.set_low(),
_ => {}
};
}
fn is_low(&mut self, o: u8) -> bool {
match o {
0 => self.i1.is_low(),
1 => self.i2.is_low(),
2 => self.i3.is_low(),
3 => self.i4.is_low(),
_ => false,
}
}
}
}
trait KeyboardInput<'d> {
fn is_low(&mut self) -> bool;
fn is_high(&mut self) -> bool {
!self.is_low()
}
}
impl<'d, C: InputPin, R: OutputPin> KeyboardInput<'d> for (Input<'d, C>, Output<'d, R>) {
fn is_low(&mut self) -> bool {
self.1.set_low();
let s = self.0.is_low();
self.1.set_high();
s
}
}*/