mirror of
https://github.com/Anshorei/epd-waveshare.git
synced 2026-10-04 06:16:42 +00:00
Merge remote-tracking branch 'origin/main' into embedded-hal-1.0
This commit is contained in:
+188
-1
@@ -1,13 +1,30 @@
|
||||
//! B/W Color for EPDs
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
#[cfg(feature = "graphics")]
|
||||
use embedded_graphics_core::pixelcolor::PixelColor;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use BinaryColor::Off as White;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use BinaryColor::On as Black;
|
||||
|
||||
/// 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
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum Color {
|
||||
@@ -28,6 +45,143 @@ pub enum TriColor {
|
||||
Chromatic,
|
||||
}
|
||||
|
||||
/// For the 5in65 7 Color Display
|
||||
#[derive(Clone, Copy, PartialEq, 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,
|
||||
}
|
||||
|
||||
impl From<()> for OctColor {
|
||||
fn from(_: ()) -> OctColor {
|
||||
OctColor::White
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
@@ -76,6 +230,29 @@ impl From<u8> for Color {
|
||||
}
|
||||
}
|
||||
|
||||
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(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -107,4 +284,14 @@ mod tests {
|
||||
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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::epd1in54::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 1in54 EPD
|
||||
///
|
||||
@@ -22,13 +22,22 @@ impl Default for Display1in54 {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display1in54 {
|
||||
impl DrawTarget for Display1in54 {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display1in54 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
@@ -57,7 +66,10 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::color::{Black, Color};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{primitives::Line, style::PrimitiveStyle};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
|
||||
+68
-62
@@ -6,7 +6,7 @@
|
||||
//!# use embedded_hal_mock::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!use embedded_graphics::{
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle,
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyleBuilder},
|
||||
//!};
|
||||
//!use epd_waveshare::{epd1in54::*, prelude::*};
|
||||
//!#
|
||||
@@ -20,22 +20,26 @@
|
||||
//!# let mut delay = delay::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = EPD1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!let mut epd = Epd1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// Use display graphics from embedded-graphics
|
||||
//!let mut display = Display1in54::default();
|
||||
//!
|
||||
//!// Use embedded graphics for drawing a line
|
||||
//!let style = PrimitiveStyleBuilder::new()
|
||||
//! .stroke_color(Black)
|
||||
//! .stroke_width(1)
|
||||
//! .build();
|
||||
//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
|
||||
//! .into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
//! .into_styled(style)
|
||||
//! .draw(&mut display);
|
||||
//!
|
||||
//!// Display updated frame
|
||||
//!epd.update_frame(&mut spi, &display.buffer())?;
|
||||
//!epd.display_frame(&mut spi)?;
|
||||
//!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)?;
|
||||
//!epd.sleep(&mut spi, &mut delay)?;
|
||||
//!# Ok(())
|
||||
//!# }
|
||||
//!```
|
||||
@@ -61,7 +65,7 @@ use crate::type_a::{
|
||||
|
||||
use crate::color::Color;
|
||||
|
||||
use crate::traits::{RefreshLUT, WaveshareDisplay};
|
||||
use crate::traits::{RefreshLut, WaveshareDisplay};
|
||||
|
||||
use crate::interface::DisplayInterface;
|
||||
|
||||
@@ -70,30 +74,26 @@ mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use crate::epd1in54::graphics::Display1in54;
|
||||
|
||||
/// EPD1in54 driver
|
||||
///
|
||||
pub struct EPD1in54<SPI, CS, BUSY, DC, RST> {
|
||||
/// Epd1in54 driver
|
||||
pub struct Epd1in54<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// SPI
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Color
|
||||
background_color: Color,
|
||||
/// Refresh LUT
|
||||
refresh: RefreshLUT,
|
||||
refresh: RefreshLut,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD1in54<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.interface.reset(delay, 10);
|
||||
|
||||
// 3 Databytes:
|
||||
@@ -103,7 +103,7 @@ where
|
||||
// Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?)
|
||||
self.interface.cmd_with_data(
|
||||
spi,
|
||||
Command::DRIVER_OUTPUT_CONTROL,
|
||||
Command::DriverOutputControl,
|
||||
&[HEIGHT as u8, (HEIGHT >> 8) as u8, 0x00],
|
||||
)?;
|
||||
|
||||
@@ -112,28 +112,25 @@ where
|
||||
// 1 .. B[6:0] = 0xCE | 0xD6
|
||||
// 1 .. C[6:0] = 0x8D | 0x9D
|
||||
//TODO: test
|
||||
self.interface.cmd_with_data(
|
||||
spi,
|
||||
Command::BOOSTER_SOFT_START_CONTROL,
|
||||
&[0xD7, 0xD6, 0x9D],
|
||||
)?;
|
||||
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::WRITE_VCOM_REGISTER, &[0xA8])?;
|
||||
.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::SET_DUMMY_LINE_PERIOD, &[0x1A])?;
|
||||
.cmd_with_data(spi, Command::SetDummyLinePeriod, &[0x1A])?;
|
||||
|
||||
// One Databyte with default value 0x08 for 2us per line
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::SET_GATE_LINE_WIDTH, &[0x08])?;
|
||||
.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::DATA_ENTRY_MODE_SETTING, &[0x03])?;
|
||||
.cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?;
|
||||
|
||||
self.set_lut(spi, None)?;
|
||||
|
||||
@@ -142,15 +139,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, E> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD1in54<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, E, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8, Error = E>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = Color;
|
||||
fn width(&self) -> u32 {
|
||||
WIDTH
|
||||
}
|
||||
@@ -159,7 +158,7 @@ where
|
||||
HEIGHT
|
||||
}
|
||||
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -169,10 +168,10 @@ where
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
|
||||
let mut epd = EPD1in54 {
|
||||
let mut epd = Epd1in54 {
|
||||
interface,
|
||||
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||
refresh: RefreshLUT::FULL,
|
||||
refresh: RefreshLut::Full,
|
||||
};
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
@@ -180,28 +179,29 @@ where
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
// 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::DEEP_SLEEP_MODE, &[0x00])?;
|
||||
.cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
buffer: &[u8],
|
||||
_delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.use_full_frame(spi)?;
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
|
||||
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -220,38 +220,43 @@ where
|
||||
self.set_ram_counter(spi, x, y)?;
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
|
||||
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
// 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::DISPLAY_UPDATE_CONTROL_2, &[0xC4])?;
|
||||
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC4])?;
|
||||
|
||||
self.interface.cmd(spi, Command::MASTER_ACTIVATION)?;
|
||||
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)?;
|
||||
self.interface.cmd(spi, Command::Nop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.display_frame(spi)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.use_full_frame(spi)?;
|
||||
|
||||
// clear the ram with the background color
|
||||
let color = self.background_color.get_byte_value();
|
||||
|
||||
self.interface.cmd(spi, Command::WRITE_RAM)?;
|
||||
self.interface.cmd(spi, Command::WriteRam)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||
Ok(())
|
||||
@@ -268,14 +273,14 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
refresh_rate: Option<RefreshLUT>,
|
||||
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, &LUT_FULL_UPDATE),
|
||||
RefreshLUT::QUICK => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE),
|
||||
RefreshLut::Full => self.set_lut_helper(spi, &LUT_FULL_UPDATE),
|
||||
RefreshLut::Quick => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,16 +289,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD1in54<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
pub(crate) fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
@@ -320,14 +326,14 @@ where
|
||||
// aren't relevant
|
||||
self.interface.cmd_with_data(
|
||||
spi,
|
||||
Command::SET_RAM_X_ADDRESS_START_END_POSITION,
|
||||
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::SET_RAM_Y_ADDRESS_START_END_POSITION,
|
||||
Command::SetRamYAddressStartEndPosition,
|
||||
&[
|
||||
start_y as u8,
|
||||
(start_y >> 8) as u8,
|
||||
@@ -348,12 +354,12 @@ where
|
||||
// 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::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?;
|
||||
.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::SET_RAM_Y_ADDRESS_COUNTER,
|
||||
Command::SetRamYAddressCounter,
|
||||
&[y as u8, (y >> 8) as u8],
|
||||
)?;
|
||||
Ok(())
|
||||
@@ -364,7 +370,7 @@ where
|
||||
assert!(buffer.len() == 30);
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)?;
|
||||
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+23
-24
@@ -2,35 +2,34 @@
|
||||
use crate::traits;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
PANEL_SETTING = 0x00,
|
||||
PanelSetting = 0x00,
|
||||
|
||||
POWER_SETTING = 0x01,
|
||||
POWER_OFF = 0x02,
|
||||
POWER_ON = 0x04,
|
||||
BOOSTER_SOFT_START = 0x06,
|
||||
DATA_START_TRANSMISSION_1 = 0x10,
|
||||
DISPLAY_REFRESH = 0x12,
|
||||
DATA_START_TRANSMISSION_2 = 0x13,
|
||||
PowerSetting = 0x01,
|
||||
PowerOff = 0x02,
|
||||
PowerOn = 0x04,
|
||||
BoosterSoftStart = 0x06,
|
||||
DataStartTransmission1 = 0x10,
|
||||
DisplayRefresh = 0x12,
|
||||
DataStartTransmission2 = 0x13,
|
||||
|
||||
LUT_FOR_VCOM = 0x20,
|
||||
LUT_WHITE_TO_WHITE = 0x21,
|
||||
LUT_BLACK_TO_WHITE = 0x22,
|
||||
LUT_G0 = 0x23,
|
||||
LUT_G1 = 0x24,
|
||||
LUT_RED_VCOM = 0x25,
|
||||
LUT_RED0 = 0x26,
|
||||
LUT_RED1 = 0x27,
|
||||
LutForVcom = 0x20,
|
||||
LutWhiteToWhite = 0x21,
|
||||
LutBlackToWhite = 0x22,
|
||||
LutG0 = 0x23,
|
||||
LutG1 = 0x24,
|
||||
LutRedVcom = 0x25,
|
||||
LutRed0 = 0x26,
|
||||
LutRed1 = 0x27,
|
||||
|
||||
PLL_CONTROL = 0x30,
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40,
|
||||
TEMPERATURE_SENSOR_SELECTION = 0x41,
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
|
||||
RESOLUTION_SETTING = 0x61,
|
||||
VCM_DC_SETTING = 0x82,
|
||||
POWER_SAVING = 0xE3,
|
||||
PllControl = 0x30,
|
||||
TemperatureSensor = 0x40,
|
||||
TemperatureSensorSelection = 0x41,
|
||||
VcomAndDataIntervalSetting = 0x50,
|
||||
ResolutionSetting = 0x61,
|
||||
VcmDcSetting = 0x82,
|
||||
PowerSaving = 0xE3,
|
||||
}
|
||||
|
||||
impl traits::Command for Command {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::epd1in54b::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 1in54 EPD
|
||||
///
|
||||
@@ -21,13 +21,22 @@ impl Default for Display1in54b {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display1in54b {
|
||||
impl DrawTarget for Display1in54b {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display1in54b {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
|
||||
+66
-65
@@ -7,7 +7,7 @@ use embedded_hal::{
|
||||
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{
|
||||
InternalWiAdditions, RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||
};
|
||||
|
||||
//The Lookup Tables for the Display
|
||||
@@ -32,53 +32,50 @@ mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display1in54b;
|
||||
|
||||
/// EPD1in54b driver
|
||||
pub struct EPD1in54b<SPI, CS, BUSY, DC, RST> {
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
/// Epd1in54b driver
|
||||
pub struct Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
for EPD1in54b<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.interface.reset(delay, 10);
|
||||
|
||||
// set the power settings
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::POWER_SETTING, &[0x07, 0x00, 0x08, 0x00])?;
|
||||
.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x00, 0x08, 0x00])?;
|
||||
|
||||
// start the booster
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x07, 0x07, 0x07])?;
|
||||
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x07, 0x07, 0x07])?;
|
||||
|
||||
// power on
|
||||
self.command(spi, Command::POWER_ON)?;
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.try_delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// set the panel settings
|
||||
self.cmd_with_data(spi, Command::PANEL_SETTING, &[0xCF])?;
|
||||
self.cmd_with_data(spi, Command::PanelSetting, &[0xCF])?;
|
||||
|
||||
self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x37])?;
|
||||
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x37])?;
|
||||
|
||||
// PLL
|
||||
self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x39])?;
|
||||
self.cmd_with_data(spi, Command::PllControl, &[0x39])?;
|
||||
|
||||
// set resolution
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x0E])?;
|
||||
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0E])?;
|
||||
|
||||
self.set_lut(spi, None)?;
|
||||
|
||||
@@ -88,14 +85,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD1in54b<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn update_color_frame(
|
||||
&mut self,
|
||||
@@ -111,8 +109,7 @@ where
|
||||
self.wait_until_idle();
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
|
||||
for b in black {
|
||||
let expanded = expand_bits(*b);
|
||||
@@ -126,23 +123,24 @@ where
|
||||
spi: &mut SPI,
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data(spi, chromatic)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD1in54b<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -153,38 +151,34 @@ where
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = EPD1in54b { interface, color };
|
||||
let mut epd = Epd1in54b { interface, color };
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x17])?; //border floating
|
||||
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x00])?; // VCOM to 0V
|
||||
.cmd_with_data(spi, Command::VcmDcSetting, &[0x00])?; // Vcom to 0V
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::POWER_SETTING, &[0x02, 0x00, 0x00, 0x00])?; //VG&VS to 0V fast
|
||||
.cmd_with_data(spi, Command::PowerSetting, &[0x02, 0x00, 0x00, 0x00])?; //VG&VS to 0V fast
|
||||
|
||||
self.wait_until_idle();
|
||||
|
||||
//NOTE: The example code has a 1s delay here
|
||||
|
||||
self.command(spi, Command::POWER_OFF)?;
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.init(spi, delay)
|
||||
}
|
||||
|
||||
@@ -204,12 +198,16 @@ where
|
||||
HEIGHT
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
buffer: &[u8],
|
||||
_delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
|
||||
for b in buffer {
|
||||
// Two bits per pixel
|
||||
@@ -223,8 +221,7 @@ where
|
||||
let color = self.color.get_byte_value();
|
||||
let nbits = WIDTH * (HEIGHT / 8);
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data_x_times(spi, color, nbits)?;
|
||||
|
||||
//NOTE: Example code has a delay here
|
||||
@@ -244,35 +241,38 @@ where
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
self.command(spi, Command::DisplayRefresh)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.display_frame(spi)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
let color = DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||
|
||||
// Clear the black
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
|
||||
// Uses 2 bits per pixel
|
||||
self.interface
|
||||
.data_x_times(spi, color, 2 * (WIDTH * HEIGHT / 8))?;
|
||||
|
||||
// Clear the red
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color, WIDTH * HEIGHT / 8)?;
|
||||
Ok(())
|
||||
@@ -281,22 +281,22 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
_refresh_rate: Option<RefreshLUT>,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::LUT_FOR_VCOM, LUT_VCOM0)?;
|
||||
.cmd_with_data(spi, Command::LutForVcom, LUT_VCOM0)?;
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::LUT_WHITE_TO_WHITE, LUT_WHITE_TO_WHITE)?;
|
||||
.cmd_with_data(spi, Command::LutWhiteToWhite, LUT_WHITE_TO_WHITE)?;
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::LUT_BLACK_TO_WHITE, LUT_BLACK_TO_WHITE)?;
|
||||
self.interface.cmd_with_data(spi, Command::LUT_G0, LUT_G1)?;
|
||||
self.interface.cmd_with_data(spi, Command::LUT_G1, LUT_G2)?;
|
||||
.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::LUT_RED_VCOM, LUT_RED_VCOM)?;
|
||||
.cmd_with_data(spi, Command::LutRedVcom, LUT_RED_VCOM)?;
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::LUT_RED0, LUT_RED0)?;
|
||||
.cmd_with_data(spi, Command::LutRed0, LUT_RED0)?;
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::LUT_RED1, LUT_RED1)?;
|
||||
.cmd_with_data(spi, Command::LutRed1, LUT_RED1)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -306,13 +306,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD1in54b<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
@@ -332,14 +333,14 @@ where
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::RESOLUTION_SETTING)?;
|
||||
self.command(spi, Command::ResolutionSetting)?;
|
||||
|
||||
self.send_data(spi, &[w as u8])?;
|
||||
self.send_data(spi, &[(h >> 8) as u8])?;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::epd1in54c::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 1in54c EPD
|
||||
///
|
||||
/// Can also be manually constructed and be used together with VarDisplay
|
||||
pub struct Display1in54c {
|
||||
buffer: [u8; NUM_DISPLAY_BITS as usize],
|
||||
rotation: DisplayRotation,
|
||||
}
|
||||
|
||||
impl Default for Display1in54c {
|
||||
fn default() -> Self {
|
||||
Display1in54c {
|
||||
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); NUM_DISPLAY_BITS as usize],
|
||||
rotation: DisplayRotation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for Display1in54c {
|
||||
type Color = BinaryColor;
|
||||
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.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display1in54c {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Display1in54c {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||
self.rotation = rotation;
|
||||
}
|
||||
|
||||
fn rotation(&self) -> DisplayRotation {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
//! A simple Driver for the Waveshare 1.54" (C) E-Ink Display via SPI
|
||||
|
||||
use embedded_hal::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
digital::v2::*,
|
||||
};
|
||||
|
||||
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 * HEIGHT / 8;
|
||||
|
||||
use crate::color::Color;
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
mod graphics;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display1in54c;
|
||||
|
||||
/// Epd1in54c driver
|
||||
pub struct Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
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, 2);
|
||||
|
||||
// start the booster
|
||||
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||
|
||||
// power on
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// 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, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn update_color_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
black: &[u8],
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.update_achromatic_frame(spi, black)?;
|
||||
self.update_chromatic_frame(spi, chromatic)
|
||||
}
|
||||
|
||||
fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::DataStartTransmission1, black)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_chromatic_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::DataStartTransmission2, chromatic)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
dc: DC,
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = 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();
|
||||
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
self.wait_until_idle();
|
||||
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, 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,
|
||||
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();
|
||||
|
||||
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();
|
||||
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,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_busy(&self) -> bool {
|
||||
self.interface.is_busy(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
}
|
||||
|
||||
fn send_data(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.interface.data(spi, data)
|
||||
}
|
||||
|
||||
fn cmd_with_data(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
command: Command,
|
||||
data: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd_with_data(spi, command, data)
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::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])
|
||||
}
|
||||
}
|
||||
+79
-88
@@ -5,57 +5,56 @@ use crate::traits;
|
||||
extern crate bit_field;
|
||||
use bit_field::BitField;
|
||||
|
||||
/// EPD2in13 v2
|
||||
/// Epd2in13 v2
|
||||
///
|
||||
/// For more infos about the addresses and what they are doing look into the pdfs
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
DRIVER_OUTPUT_CONTROL = 0x01,
|
||||
GATE_DRIVING_VOLTAGE_CTRL = 0x03,
|
||||
SOURCE_DRIVING_VOLTAGE_CTRL = 0x04,
|
||||
BOOSTER_SOFT_START_CONTROL = 0x0C,
|
||||
GATE_SCAN_START_POSITION = 0x0F,
|
||||
DEEP_SLEEP_MODE = 0x10,
|
||||
DATA_ENTRY_MODE_SETTING = 0x11,
|
||||
SW_RESET = 0x12,
|
||||
HV_READY_DETECTION = 0x14,
|
||||
VCI_DETECTION = 0x15,
|
||||
TEMPERATURE_SENSOR_CONTROL_WRITE = 0x1A,
|
||||
TEMPERATURE_SENSOR_CONTROL_READ = 0x1B,
|
||||
TEMPERATURE_SENSOR_EXT_CONTROL_WRITE = 0x1C,
|
||||
MASTER_ACTIVATION = 0x20,
|
||||
DISPLAY_UPDATE_CONTROL_1 = 0x21,
|
||||
DISPLAY_UPDATE_CONTROL_2 = 0x22,
|
||||
WRITE_RAM = 0x24,
|
||||
WRITE_RAM_RED = 0x26,
|
||||
READ_RAM = 0x27,
|
||||
VCOM_SENSE = 0x28,
|
||||
VCOM_SENSE_DURATION = 0x29,
|
||||
PROGRAM_VCOM_OPT = 0x2A,
|
||||
WRITE_VCOM_REGISTER = 0x2C,
|
||||
OTP_REGISTER_READ = 0x2D,
|
||||
STATUS_BIT_READ = 0x2F,
|
||||
PROGRAM_WS_OTP = 0x30,
|
||||
LOAD_WS_OTP = 0x31,
|
||||
WRITE_LUT_REGISTER = 0x32,
|
||||
PROGRAM_OTP_SELECTION = 0x36,
|
||||
WRITE_OTP_SELECTION = 0x37,
|
||||
SET_DUMMY_LINE_PERIOD = 0x3A,
|
||||
SET_GATE_LINE_WIDTH = 0x3B,
|
||||
BORDER_WAVEFORM_CONTROL = 0x3C,
|
||||
READ_RAM_OPTION = 0x41,
|
||||
SET_RAM_X_ADDRESS_START_END_POSITION = 0x44,
|
||||
SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45,
|
||||
AUTO_WRITE_RED_RAM_REGULAR_PATTERN = 0x46,
|
||||
AUTO_WRITE_BW_RAM_REGULAR_PATTERN = 0x47,
|
||||
SET_RAM_X_ADDRESS_COUNTER = 0x4E,
|
||||
SET_RAM_Y_ADDRESS_COUNTER = 0x4F,
|
||||
SET_ANALOG_BLOCK_CONTROL = 0x74,
|
||||
SET_DIGITAL_BLOCK_CONTROL = 0x7E,
|
||||
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,
|
||||
Nop = 0x7F,
|
||||
}
|
||||
|
||||
pub(crate) struct DriverOutput {
|
||||
@@ -135,54 +134,51 @@ impl DisplayUpdateControl2 {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
|
||||
pub(crate) enum DataEntryModeIncr {
|
||||
X_DECR_Y_DECR = 0x0,
|
||||
X_INCR_Y_DECR = 0x1,
|
||||
X_DECR_Y_INCR = 0x2,
|
||||
X_INCR_Y_INCR = 0x3,
|
||||
XDecrYDecr = 0x0,
|
||||
XIncrYDecr = 0x1,
|
||||
XDecrYIncr = 0x2,
|
||||
XIncrYIncr = 0x3,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
|
||||
pub(crate) enum DataEntryModeDir {
|
||||
X_DIR = 0x0,
|
||||
Y_DIR = 0x4,
|
||||
XDir = 0x0,
|
||||
YDir = 0x4,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum BorderWaveFormVBD {
|
||||
GS = 0x0,
|
||||
FIX_LEVEL = 0x1,
|
||||
VCOM = 0x2,
|
||||
pub(crate) enum BorderWaveFormVbd {
|
||||
Gs = 0x0,
|
||||
FixLevel = 0x1,
|
||||
Vcom = 0x2,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum BorderWaveFormFixLevel {
|
||||
VSS = 0x0,
|
||||
VSH1 = 0x1,
|
||||
VSL = 0x2,
|
||||
VSH2 = 0x3,
|
||||
Vss = 0x0,
|
||||
Vsh1 = 0x1,
|
||||
Vsl = 0x2,
|
||||
Vsh2 = 0x3,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum BorderWaveFormGS {
|
||||
LUT0 = 0x0,
|
||||
LUT1 = 0x1,
|
||||
LUT2 = 0x2,
|
||||
LUT3 = 0x3,
|
||||
pub(crate) enum BorderWaveFormGs {
|
||||
Lut0 = 0x0,
|
||||
Lut1 = 0x1,
|
||||
Lut2 = 0x2,
|
||||
Lut3 = 0x3,
|
||||
}
|
||||
|
||||
pub(crate) struct BorderWaveForm {
|
||||
pub vbd: BorderWaveFormVBD,
|
||||
pub vbd: BorderWaveFormVbd,
|
||||
pub fix_level: BorderWaveFormFixLevel,
|
||||
pub gs_trans: BorderWaveFormGS,
|
||||
pub gs_trans: BorderWaveFormGs,
|
||||
}
|
||||
|
||||
impl BorderWaveForm {
|
||||
@@ -194,25 +190,24 @@ impl BorderWaveForm {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum DeepSleepMode {
|
||||
// Sleeps and keeps access to RAM and controller
|
||||
NORMAL = 0x00,
|
||||
Normal = 0x00,
|
||||
|
||||
// Sleeps without access to RAM/controller but keeps RAM content
|
||||
MODE_1 = 0x01,
|
||||
Mode1 = 0x01,
|
||||
|
||||
// Same as MODE_1 but RAM content is not kept
|
||||
MODE_2 = 0x11,
|
||||
Mode2 = 0x11,
|
||||
}
|
||||
|
||||
pub(crate) struct GateDrivingVoltage(pub u8);
|
||||
pub(crate) struct SourceDrivingVoltage(pub u8);
|
||||
pub(crate) struct VCOM(pub u8);
|
||||
pub(crate) struct Vcom(pub u8);
|
||||
|
||||
pub(crate) trait I32Ext {
|
||||
fn vcom(self) -> VCOM;
|
||||
fn vcom(self) -> Vcom;
|
||||
fn gate_driving_decivolt(self) -> GateDrivingVoltage;
|
||||
fn source_driving_decivolt(self) -> SourceDrivingVoltage;
|
||||
}
|
||||
@@ -220,8 +215,8 @@ pub(crate) trait I32Ext {
|
||||
impl I32Ext for i32 {
|
||||
// This is really not very nice. Until I find something better, this will be
|
||||
// a placeholder.
|
||||
fn vcom(self) -> VCOM {
|
||||
assert!(self >= -30 && self <= -2);
|
||||
fn vcom(self) -> Vcom {
|
||||
assert!((-30..=-2).contains(&self));
|
||||
let u = match -self {
|
||||
2 => 0x08,
|
||||
3 => 0x0B,
|
||||
@@ -254,24 +249,20 @@ impl I32Ext for i32 {
|
||||
30 => 0x78,
|
||||
_ => 0,
|
||||
};
|
||||
VCOM(u)
|
||||
Vcom(u)
|
||||
}
|
||||
|
||||
fn gate_driving_decivolt(self) -> GateDrivingVoltage {
|
||||
assert!(self >= 100 && self <= 210 && self % 5 == 0);
|
||||
assert!((100..=210).contains(&self) && self % 5 == 0);
|
||||
GateDrivingVoltage(((self - 100) / 5 + 0x03) as u8)
|
||||
}
|
||||
|
||||
fn source_driving_decivolt(self) -> SourceDrivingVoltage {
|
||||
assert!(
|
||||
(self >= 24 && self <= 88)
|
||||
|| (self >= 90 && self <= 180 && self % 5 == 0)
|
||||
|| (self >= -180 && self <= -90 && self % 5 == 0)
|
||||
);
|
||||
assert!((24..=88).contains(&self) || (self % 5 == 0 && (90..=180).contains(&self.abs())));
|
||||
|
||||
if self >= 24 && self <= 88 {
|
||||
if (24..=88).contains(&self) {
|
||||
SourceDrivingVoltage(((self - 24) + 0x8E) as u8)
|
||||
} else if self >= 90 && self <= 180 {
|
||||
} else if (90..=180).contains(&self) {
|
||||
SourceDrivingVoltage(((self - 90) / 2 + 0x23) as u8)
|
||||
} else {
|
||||
SourceDrivingVoltage((((-self - 90) / 5) * 2 + 0x1A) as u8)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::buffer_len;
|
||||
use crate::epd2in13_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 2in13 v2 EPD
|
||||
///
|
||||
@@ -23,13 +23,22 @@ impl Default for Display2in13 {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display2in13 {
|
||||
impl DrawTarget for Display2in13 {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display2in13 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
@@ -59,7 +68,10 @@ mod tests {
|
||||
use crate::color::{Black, Color};
|
||||
use crate::epd2in13_v2;
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{primitives::Line, style::PrimitiveStyle};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
|
||||
+88
-86
@@ -16,13 +16,13 @@ use embedded_hal::{
|
||||
use crate::buffer_len;
|
||||
use crate::color::Color;
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay};
|
||||
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::{
|
||||
BorderWaveForm, BorderWaveFormFixLevel, BorderWaveFormGS, BorderWaveFormVBD, Command,
|
||||
BorderWaveForm, BorderWaveFormFixLevel, BorderWaveFormGs, BorderWaveFormVbd, Command,
|
||||
DataEntryModeDir, DataEntryModeIncr, DeepSleepMode, DisplayUpdateControl2, DriverOutput,
|
||||
GateDrivingVoltage, I32Ext, SourceDrivingVoltage, VCOM,
|
||||
GateDrivingVoltage, I32Ext, SourceDrivingVoltage, Vcom,
|
||||
};
|
||||
|
||||
pub(crate) mod constants;
|
||||
@@ -43,44 +43,41 @@ pub const HEIGHT: u32 = 250;
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||
const IS_BUSY_LOW: bool = false;
|
||||
|
||||
/// EPD2in13 (V2) driver
|
||||
/// Epd2in13 (V2) driver
|
||||
///
|
||||
pub struct EPD2in13<SPI, CS, BUSY, DC, RST> {
|
||||
pub struct Epd2in13<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
|
||||
sleep_mode: DeepSleepMode,
|
||||
|
||||
/// Background Color
|
||||
background_color: Color,
|
||||
refresh: RefreshLUT,
|
||||
refresh: RefreshLut,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
for EPD2in13<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in13<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// HW reset
|
||||
self.interface.reset(delay, 10);
|
||||
|
||||
if self.refresh == RefreshLUT::QUICK {
|
||||
if self.refresh == RefreshLut::Quick {
|
||||
self.set_vcom_register(spi, (-9).vcom())?;
|
||||
self.wait_until_idle();
|
||||
|
||||
self.set_lut(spi, Some(self.refresh))?;
|
||||
|
||||
// Python code does this, not sure why
|
||||
// self.cmd_with_data(spi, Command::WRITE_OTP_SELECTION, &[0, 0, 0, 0, 0x40, 0, 0])?;
|
||||
// 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.
|
||||
@@ -88,20 +85,20 @@ where
|
||||
spi,
|
||||
DisplayUpdateControl2::new().enable_analog().enable_clock(),
|
||||
)?;
|
||||
self.command(spi, Command::MASTER_ACTIVATION)?;
|
||||
self.command(spi, Command::MasterActivation)?;
|
||||
self.wait_until_idle();
|
||||
|
||||
self.set_border_waveform(
|
||||
spi,
|
||||
BorderWaveForm {
|
||||
vbd: BorderWaveFormVBD::GS,
|
||||
fix_level: BorderWaveFormFixLevel::VSS,
|
||||
gs_trans: BorderWaveFormGS::LUT1,
|
||||
vbd: BorderWaveFormVbd::Gs,
|
||||
fix_level: BorderWaveFormFixLevel::Vss,
|
||||
gs_trans: BorderWaveFormGs::Lut1,
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::SW_RESET)?;
|
||||
self.command(spi, Command::SwReset)?;
|
||||
self.wait_until_idle();
|
||||
|
||||
self.set_driver_output(
|
||||
@@ -118,11 +115,7 @@ where
|
||||
self.set_dummy_line_period(spi, 0x30)?;
|
||||
self.set_gate_scan_start_position(spi, 0)?;
|
||||
|
||||
self.set_data_entry_mode(
|
||||
spi,
|
||||
DataEntryModeIncr::X_INCR_Y_INCR,
|
||||
DataEntryModeDir::X_DIR,
|
||||
)?;
|
||||
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)?;
|
||||
@@ -131,9 +124,9 @@ where
|
||||
self.set_border_waveform(
|
||||
spi,
|
||||
BorderWaveForm {
|
||||
vbd: BorderWaveFormVBD::GS,
|
||||
fix_level: BorderWaveFormFixLevel::VSS,
|
||||
gs_trans: BorderWaveFormGS::LUT3,
|
||||
vbd: BorderWaveFormVbd::Gs,
|
||||
fix_level: BorderWaveFormFixLevel::Vss,
|
||||
gs_trans: BorderWaveFormGs::Lut3,
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -157,16 +150,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD2in13<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in13<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -174,26 +169,22 @@ where
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let mut epd = EPD2in13 {
|
||||
let mut epd = Epd2in13 {
|
||||
interface: DisplayInterface::new(cs, busy, dc, rst),
|
||||
sleep_mode: DeepSleepMode::MODE_1,
|
||||
sleep_mode: DeepSleepMode::Mode1,
|
||||
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||
refresh: RefreshLUT::FULL,
|
||||
refresh: RefreshLut::Full,
|
||||
};
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
|
||||
// All sample code enables and disables analog/clocks...
|
||||
@@ -205,25 +196,30 @@ where
|
||||
.disable_analog()
|
||||
.disable_clock(),
|
||||
)?;
|
||||
self.command(spi, Command::MASTER_ACTIVATION)?;
|
||||
self.command(spi, Command::MasterActivation)?;
|
||||
|
||||
self.set_sleep_mode(spi, self.sleep_mode)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
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, 0, 0)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
|
||||
self.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
|
||||
if self.refresh == RefreshLUT::FULL {
|
||||
if self.refresh == RefreshLut::Full {
|
||||
// Always keep the base buffer equal to current if not doing partial refresh.
|
||||
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||
self.set_ram_address_counters(spi, 0, 0)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?;
|
||||
self.cmd_with_data(spi, Command::WriteRamRed, buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -248,19 +244,19 @@ where
|
||||
// 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);
|
||||
assert!(self.refresh == RefreshLut::Full);
|
||||
|
||||
self.set_ram_area(spi, x, y, x + width, y + height)?;
|
||||
self.set_ram_address_counters(spi, x, y)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
|
||||
self.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
|
||||
if self.refresh == RefreshLUT::FULL {
|
||||
if self.refresh == RefreshLut::Full {
|
||||
// Always keep the base buffer equals to current if not doing partial refresh.
|
||||
self.set_ram_area(spi, x, y, x + width, y + height)?;
|
||||
self.set_ram_address_counters(spi, x, y)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?;
|
||||
self.cmd_with_data(spi, Command::WriteRamRed, buffer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -268,8 +264,8 @@ where
|
||||
|
||||
/// Never use directly this function when using partial refresh, or also
|
||||
/// keep the base buffer in syncd using `set_partial_base_buffer` function.
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
if self.refresh == RefreshLUT::FULL {
|
||||
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()
|
||||
@@ -282,29 +278,34 @@ where
|
||||
} else {
|
||||
self.set_display_update_control_2(spi, DisplayUpdateControl2::new().display())?;
|
||||
}
|
||||
self.command(spi, Command::MASTER_ACTIVATION)?;
|
||||
self.command(spi, Command::MasterActivation)?;
|
||||
self.wait_until_idle();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.display_frame(spi)?;
|
||||
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 {
|
||||
if self.refresh == RefreshLut::Quick {
|
||||
self.set_partial_base_buffer(spi, buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
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, 0, 0)?;
|
||||
|
||||
self.command(spi, Command::WRITE_RAM)?;
|
||||
self.command(spi, Command::WriteRam)?;
|
||||
self.interface.data_x_times(
|
||||
spi,
|
||||
color,
|
||||
@@ -312,11 +313,11 @@ where
|
||||
)?;
|
||||
|
||||
// Always keep the base buffer equals to current if not doing partial refresh.
|
||||
if self.refresh == RefreshLUT::FULL {
|
||||
if self.refresh == RefreshLut::Full {
|
||||
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||
self.set_ram_address_counters(spi, 0, 0)?;
|
||||
|
||||
self.command(spi, Command::WRITE_RAM_RED)?;
|
||||
self.command(spi, Command::WriteRamRed)?;
|
||||
self.interface.data_x_times(
|
||||
spi,
|
||||
color,
|
||||
@@ -345,14 +346,14 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
refresh_rate: Option<RefreshLUT>,
|
||||
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,
|
||||
Some(RefreshLut::Full) | None => &LUT_FULL_UPDATE,
|
||||
Some(RefreshLut::Quick) => &LUT_PARTIAL_UPDATE,
|
||||
};
|
||||
|
||||
self.cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)
|
||||
self.cmd_with_data(spi, Command::WriteLutRegister, buffer)
|
||||
}
|
||||
|
||||
fn is_busy(&self) -> bool {
|
||||
@@ -360,13 +361,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD2in13<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in13<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// When using partial refresh, the controller uses the provided buffer for
|
||||
/// comparison with new buffer.
|
||||
@@ -379,7 +381,7 @@ where
|
||||
self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
|
||||
self.set_ram_address_counters(spi, 0, 0)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?;
|
||||
self.cmd_with_data(spi, Command::WriteRamRed, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -390,11 +392,11 @@ where
|
||||
|
||||
/// Sets the refresh mode. When changing mode, the screen will be
|
||||
/// re-initialized accordingly.
|
||||
pub fn set_refresh<DELAY: DelayMs<u8>>(
|
||||
pub fn set_refresh(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
refresh: RefreshLUT,
|
||||
refresh: RefreshLut,
|
||||
) -> Result<(), SPI::Error> {
|
||||
if self.refresh != refresh {
|
||||
self.refresh = refresh;
|
||||
@@ -411,7 +413,7 @@ where
|
||||
assert!(start <= 295);
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::GATE_SCAN_START_POSITION,
|
||||
Command::GateScanStartPosition,
|
||||
&[(start & 0xFF) as u8, ((start >> 8) & 0x1) as u8],
|
||||
)
|
||||
}
|
||||
@@ -423,13 +425,13 @@ where
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::BORDER_WAVEFORM_CONTROL,
|
||||
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::WRITE_VCOM_REGISTER, &[vcom.0])
|
||||
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(
|
||||
@@ -437,7 +439,7 @@ where
|
||||
spi: &mut SPI,
|
||||
voltage: GateDrivingVoltage,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.cmd_with_data(spi, Command::GATE_DRIVING_VOLTAGE_CTRL, &[voltage.0])
|
||||
self.cmd_with_data(spi, Command::GateDrivingVoltageCtrl, &[voltage.0])
|
||||
}
|
||||
|
||||
fn set_dummy_line_period(
|
||||
@@ -446,11 +448,11 @@ where
|
||||
number_of_lines: u8,
|
||||
) -> Result<(), SPI::Error> {
|
||||
assert!(number_of_lines <= 127);
|
||||
self.cmd_with_data(spi, Command::SET_DUMMY_LINE_PERIOD, &[number_of_lines])
|
||||
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::SET_GATE_LINE_WIDTH, &[width & 0x0F])
|
||||
self.cmd_with_data(spi, Command::SetGateLineWidth, &[width & 0x0F])
|
||||
}
|
||||
|
||||
/// Sets the source driving voltage value
|
||||
@@ -463,7 +465,7 @@ where
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::SOURCE_DRIVING_VOLTAGE_CTRL,
|
||||
Command::SourceDrivingVoltageCtrl,
|
||||
&[vsh1.0, vsh2.0, vsl.0],
|
||||
)
|
||||
}
|
||||
@@ -475,16 +477,16 @@ where
|
||||
spi: &mut SPI,
|
||||
value: DisplayUpdateControl2,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.cmd_with_data(spi, Command::DISPLAY_UPDATE_CONTROL_2, &[value.0])
|
||||
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::DEEP_SLEEP_MODE, &[mode as u8])
|
||||
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::DRIVER_OUTPUT_CONTROL, &output.to_bytes())
|
||||
self.cmd_with_data(spi, Command::DriverOutputControl, &output.to_bytes())
|
||||
}
|
||||
|
||||
/// Sets the data entry mode (ie. how X and Y positions changes when writing
|
||||
@@ -496,7 +498,7 @@ where
|
||||
counter_direction: DataEntryModeDir,
|
||||
) -> Result<(), SPI::Error> {
|
||||
let mode = counter_incr_mode as u8 | counter_direction as u8;
|
||||
self.cmd_with_data(spi, Command::DATA_ENTRY_MODE_SETTING, &[mode])
|
||||
self.cmd_with_data(spi, Command::DataEntryModeSetting, &[mode])
|
||||
}
|
||||
|
||||
/// Sets both X and Y pixels ranges
|
||||
@@ -510,13 +512,13 @@ where
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::SET_RAM_X_ADDRESS_START_END_POSITION,
|
||||
Command::SetRamXAddressStartEndPosition,
|
||||
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
|
||||
)?;
|
||||
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::SET_RAM_Y_ADDRESS_START_END_POSITION,
|
||||
Command::SetRamYAddressStartEndPosition,
|
||||
&[
|
||||
start_y as u8,
|
||||
(start_y >> 8) as u8,
|
||||
@@ -534,11 +536,11 @@ where
|
||||
y: u32,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?;
|
||||
self.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
|
||||
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::SET_RAM_Y_ADDRESS_COUNTER,
|
||||
Command::SetRamYAddressCounter,
|
||||
&[y as u8, (y >> 8) as u8],
|
||||
)?;
|
||||
Ok(())
|
||||
@@ -558,7 +560,7 @@ where
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::color::TriColor;
|
||||
use crate::epd2in13bc::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH};
|
||||
use crate::graphics::{DisplayRotation, TriDisplay};
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 2.13" b/c EPD
|
||||
///
|
||||
/// Can also be manually constructed and be used together with VarDisplay
|
||||
pub struct Display2in13bc {
|
||||
// one buffer for both b/w and for chromatic:
|
||||
// * &buffer[0..NUM_DISPLAY_BITS] for b/w buffer and
|
||||
// * &buffer[NUM_DISPLAY_BITS..2*NUM_DISPLAY_BITS] for chromatic buffer
|
||||
buffer: [u8; 2 * NUM_DISPLAY_BITS as usize],
|
||||
rotation: DisplayRotation,
|
||||
}
|
||||
|
||||
impl Default for Display2in13bc {
|
||||
fn default() -> Self {
|
||||
Display2in13bc {
|
||||
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 2 * NUM_DISPLAY_BITS as usize],
|
||||
rotation: DisplayRotation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for Display2in13bc {
|
||||
type Color = TriColor;
|
||||
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.draw_helper_tri(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display2in13bc {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
impl TriDisplay for Display2in13bc {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||
self.rotation = rotation;
|
||||
}
|
||||
|
||||
fn rotation(&self) -> DisplayRotation {
|
||||
self.rotation
|
||||
}
|
||||
|
||||
fn chromatic_offset(&self) -> usize {
|
||||
NUM_DISPLAY_BITS as usize
|
||||
}
|
||||
|
||||
fn bw_buffer(&self) -> &[u8] {
|
||||
&self.buffer[0..self.chromatic_offset()]
|
||||
}
|
||||
|
||||
fn chromatic_buffer(&self) -> &[u8] {
|
||||
&self.buffer[self.chromatic_offset()..]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
//! 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::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!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::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = Epd2in13bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// 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,
|
||||
//! &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::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
digital::v2::*,
|
||||
};
|
||||
|
||||
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 * HEIGHT / 8;
|
||||
|
||||
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;
|
||||
|
||||
use crate::color::TriColor;
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display2in13bc;
|
||||
|
||||
/// Epd2in13bc driver
|
||||
pub struct Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
color: TriColor,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
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);
|
||||
|
||||
// start the booster
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||
|
||||
// power on
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// 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();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn update_color_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
black: &[u8],
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.update_achromatic_frame(spi, black)?;
|
||||
self.update_chromatic_frame(spi, 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, 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,
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data(spi, chromatic)?;
|
||||
|
||||
self.wait_until_idle();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = TriColor;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
dc: DC,
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = 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();
|
||||
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn update_partial_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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();
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
_spi: &mut SPI,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_busy(&self) -> bool {
|
||||
self.interface.is_busy(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
}
|
||||
|
||||
fn send_data(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.interface.data(spi, data)
|
||||
}
|
||||
|
||||
fn cmd_with_data(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
command: Command,
|
||||
data: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd_with_data(spi, command, data)
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::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],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
@@ -0,0 +1,175 @@
|
||||
use crate::epd2in7b::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 2in7B EPD
|
||||
///
|
||||
/// Can also be manuall constructed:
|
||||
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH * HEIGHT / 8]`
|
||||
pub struct Display2in7b {
|
||||
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
|
||||
rotation: DisplayRotation,
|
||||
}
|
||||
|
||||
impl Default for Display2in7b {
|
||||
fn default() -> Self {
|
||||
Display2in7b {
|
||||
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||
WIDTH as usize * HEIGHT as usize / 8],
|
||||
rotation: DisplayRotation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for Display2in7b {
|
||||
type Color = BinaryColor;
|
||||
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.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display2in7b {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Display2in7b {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||
self.rotation = rotation;
|
||||
}
|
||||
|
||||
fn rotation(&self) -> DisplayRotation {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::color::Black;
|
||||
use crate::color::Color;
|
||||
use crate::epd2in7b;
|
||||
use crate::epd2in7b::{HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
fn graphics_size() {
|
||||
let display = Display2in7b::default();
|
||||
assert_eq!(display.buffer().len(), 5808);
|
||||
}
|
||||
|
||||
// test default background color on all bytes
|
||||
#[test]
|
||||
fn graphics_default() {
|
||||
let display = Display2in7b::default();
|
||||
for &byte in display.buffer() {
|
||||
assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_0() {
|
||||
let mut display = Display2in7b::default();
|
||||
let _ = Line::new(Point::new(0, 0), Point::new(7, 0))
|
||||
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_90() {
|
||||
let mut display = Display2in7b::default();
|
||||
display.set_rotation(DisplayRotation::Rotate90);
|
||||
let _ = Line::new(
|
||||
Point::new(0, WIDTH as i32 - 8),
|
||||
Point::new(0, WIDTH as i32 - 1),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_180() {
|
||||
let mut display = Display2in7b::default();
|
||||
display.set_rotation(DisplayRotation::Rotate180);
|
||||
|
||||
let _ = Line::new(
|
||||
Point::new(WIDTH as i32 - 8, HEIGHT as i32 - 1),
|
||||
Point::new(WIDTH as i32 - 1, HEIGHT as i32 - 1),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(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, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_270() {
|
||||
let mut display = Display2in7b::default();
|
||||
display.set_rotation(DisplayRotation::Rotate270);
|
||||
let _ = Line::new(
|
||||
Point::new(HEIGHT as i32 - 1, 0),
|
||||
Point::new(HEIGHT as i32 - 1, 7),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(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, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
//! 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::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
digital::v2::*,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
use crate::color::Color;
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display2in7b;
|
||||
|
||||
/// Epd2in7b driver
|
||||
pub struct Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Background Color
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// reset the device
|
||||
self.interface.reset(delay, 2);
|
||||
|
||||
// power on
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// 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, None)?;
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::PartialDisplayRefresh, &[0x00])?;
|
||||
|
||||
self.wait_until_idle();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
dc: DC,
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = 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();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0xf7])?;
|
||||
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
self.wait_until_idle();
|
||||
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 * HEIGHT / 8)?;
|
||||
|
||||
self.interface.cmd(spi, Command::DataStop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_partial_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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();
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
let color_value = self.color.get_byte_value();
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color_value, WIDTH * HEIGHT / 8)?;
|
||||
|
||||
self.interface.cmd(spi, Command::DataStop)?;
|
||||
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color_value, WIDTH * HEIGHT / 8)?;
|
||||
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,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
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 is_busy(&self) -> bool {
|
||||
self.interface.is_busy(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn update_color_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
black: &[u8],
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.update_achromatic_frame(spi, black)?;
|
||||
self.update_chromatic_frame(spi, 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,
|
||||
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,
|
||||
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();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
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)
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
/// Refresh display for partial frame
|
||||
pub fn display_partial_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update black/achromatic frame
|
||||
pub fn update_partial_achromatic_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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();
|
||||
|
||||
for b in achromatic.iter() {
|
||||
// Flipping based on waveshare implementation
|
||||
self.send_data(spi, &[!b])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update partial chromatic/red frame
|
||||
pub fn update_partial_chromatic_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+14
-5
@@ -1,7 +1,7 @@
|
||||
use crate::epd2in9::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Display with Fullsize buffer for use with the 2in9 EPD
|
||||
///
|
||||
@@ -22,13 +22,22 @@ impl Default for Display2in9 {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display2in9 {
|
||||
impl DrawTarget for Display2in9 {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display2in9 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
|
||||
+62
-59
@@ -7,7 +7,7 @@
|
||||
//!# use embedded_hal_mock::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!use embedded_graphics::{
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle,
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||
//!};
|
||||
//!use epd_waveshare::{epd2in9::*, prelude::*};
|
||||
//!#
|
||||
@@ -21,7 +21,7 @@
|
||||
//!# let mut delay = delay::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = EPD2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!let mut epd = Epd2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// Use display graphics from embedded-graphics
|
||||
//!let mut display = Display2in9::default();
|
||||
@@ -32,11 +32,11 @@
|
||||
//! .draw(&mut display);
|
||||
//!
|
||||
//! // Display updated frame
|
||||
//!epd.update_frame(&mut spi, &display.buffer())?;
|
||||
//!epd.display_frame(&mut spi)?;
|
||||
//!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)?;
|
||||
//!epd.sleep(&mut spi, &mut delay)?;
|
||||
//!# Ok(())
|
||||
//!# }
|
||||
//!```
|
||||
@@ -70,30 +70,27 @@ mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use crate::epd2in9::graphics::Display2in9;
|
||||
|
||||
/// EPD2in9 driver
|
||||
/// Epd2in9 driver
|
||||
///
|
||||
pub struct EPD2in9<SPI, CS, BUSY, DC, RST> {
|
||||
pub struct Epd2in9<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// SPI
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Color
|
||||
background_color: Color,
|
||||
/// Refresh LUT
|
||||
refresh: RefreshLUT,
|
||||
refresh: RefreshLut,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD2in9<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.interface.reset(delay, 10);
|
||||
|
||||
self.wait_until_idle();
|
||||
@@ -104,49 +101,48 @@ where
|
||||
// 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::DRIVER_OUTPUT_CONTROL, &[0x27, 0x01, 0x00])?;
|
||||
.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::BOOSTER_SOFT_START_CONTROL,
|
||||
&[0xD7, 0xD6, 0x9D],
|
||||
)?;
|
||||
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::WRITE_VCOM_REGISTER, &[0xA8])?;
|
||||
.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::SET_DUMMY_LINE_PERIOD, &[0x1A])?;
|
||||
.cmd_with_data(spi, Command::SetDummyLinePeriod, &[0x1A])?;
|
||||
|
||||
// One Databyte with default value 0x08 for 2us per line
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::SET_GATE_LINE_WIDTH, &[0x08])?;
|
||||
.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::DATA_ENTRY_MODE_SETTING, &[0x03])?;
|
||||
.cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?;
|
||||
|
||||
self.set_lut(spi, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD2in9<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = Color;
|
||||
fn width(&self) -> u32 {
|
||||
WIDTH
|
||||
}
|
||||
@@ -155,7 +151,7 @@ where
|
||||
HEIGHT
|
||||
}
|
||||
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -165,10 +161,10 @@ where
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
|
||||
let mut epd = EPD2in9 {
|
||||
let mut epd = Epd2in9 {
|
||||
interface,
|
||||
background_color: DEFAULT_BACKGROUND_COLOR,
|
||||
refresh: RefreshLUT::FULL,
|
||||
refresh: RefreshLut::Full,
|
||||
};
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
@@ -176,31 +172,32 @@ where
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
// 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::DEEP_SLEEP_MODE, &[0x00])?;
|
||||
.cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.init(spi, delay)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
buffer: &[u8],
|
||||
_delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.use_full_frame(spi)?;
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
|
||||
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -219,38 +216,43 @@ where
|
||||
self.set_ram_counter(spi, x, y)?;
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WRITE_RAM, buffer)?;
|
||||
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
// 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::DISPLAY_UPDATE_CONTROL_2, &[0xC4])?;
|
||||
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC4])?;
|
||||
|
||||
self.interface.cmd(spi, Command::MASTER_ACTIVATION)?;
|
||||
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)?;
|
||||
self.interface.cmd(spi, Command::Nop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.display_frame(spi)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.use_full_frame(spi)?;
|
||||
|
||||
// clear the ram with the background color
|
||||
let color = self.background_color.get_byte_value();
|
||||
|
||||
self.interface.cmd(spi, Command::WRITE_RAM)?;
|
||||
self.interface.cmd(spi, Command::WriteRam)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
|
||||
Ok(())
|
||||
@@ -267,14 +269,14 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
refresh_rate: Option<RefreshLUT>,
|
||||
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, &LUT_FULL_UPDATE),
|
||||
RefreshLUT::QUICK => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE),
|
||||
RefreshLut::Full => self.set_lut_helper(spi, &LUT_FULL_UPDATE),
|
||||
RefreshLut::Quick => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,16 +285,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD2in9<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
@@ -318,14 +321,14 @@ where
|
||||
// aren't relevant
|
||||
self.interface.cmd_with_data(
|
||||
spi,
|
||||
Command::SET_RAM_X_ADDRESS_START_END_POSITION,
|
||||
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::SET_RAM_Y_ADDRESS_START_END_POSITION,
|
||||
Command::SetRamYAddressStartEndPosition,
|
||||
&[
|
||||
start_y as u8,
|
||||
(start_y >> 8) as u8,
|
||||
@@ -340,12 +343,12 @@ where
|
||||
// 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::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?;
|
||||
.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::SET_RAM_Y_ADDRESS_COUNTER,
|
||||
Command::SetRamYAddressCounter,
|
||||
&[y as u8, (y >> 8) as u8],
|
||||
)?;
|
||||
Ok(())
|
||||
@@ -356,7 +359,7 @@ where
|
||||
self.wait_until_idle();
|
||||
assert!(buffer.len() == 30);
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)?;
|
||||
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::epd2in9::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Display with Fullsize buffer for use with the 2in9 EPD V2
|
||||
///
|
||||
/// Can also be manuall constructed:
|
||||
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
|
||||
pub struct Display2in9 {
|
||||
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
|
||||
rotation: DisplayRotation,
|
||||
}
|
||||
|
||||
impl Default for Display2in9 {
|
||||
fn default() -> Self {
|
||||
Display2in9 {
|
||||
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||
WIDTH as usize * HEIGHT as usize / 8],
|
||||
rotation: DisplayRotation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for Display2in9 {
|
||||
type Color = BinaryColor;
|
||||
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.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display2in9 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Display2in9 {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||
self.rotation = rotation;
|
||||
}
|
||||
|
||||
fn rotation(&self) -> DisplayRotation {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
fn graphics_size() {
|
||||
let display = Display2in9::default();
|
||||
assert_eq!(display.buffer().len(), 4736);
|
||||
}
|
||||
|
||||
// test default background color on all bytes
|
||||
#[test]
|
||||
fn graphics_default() {
|
||||
let display = Display2in9::default();
|
||||
for &byte in display.buffer() {
|
||||
assert_eq!(byte, DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,496 @@
|
||||
//! 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::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!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::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = Epd2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// 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(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 LUT_PARTIAL_2IN9: [u8; 153] = [
|
||||
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, 0x2, 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,
|
||||
];
|
||||
|
||||
use embedded_hal::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
digital::v2::*,
|
||||
};
|
||||
|
||||
use crate::type_a::command::Command;
|
||||
|
||||
use crate::color::Color;
|
||||
|
||||
use crate::traits::*;
|
||||
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::QuickRefresh;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use crate::epd2in9_v2::graphics::Display2in9;
|
||||
|
||||
/// Epd2in9 driver
|
||||
///
|
||||
pub struct Epd2in9<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// SPI
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Color
|
||||
background_color: Color,
|
||||
/// Refresh LUT
|
||||
refresh: RefreshLut,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.interface.reset(delay, 2);
|
||||
|
||||
self.wait_until_idle();
|
||||
self.interface.cmd(spi, Command::SwReset)?;
|
||||
self.wait_until_idle();
|
||||
|
||||
// 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, 0, 0)?;
|
||||
|
||||
self.wait_until_idle();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = Color;
|
||||
fn width(&self) -> u32 {
|
||||
WIDTH
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
HEIGHT
|
||||
}
|
||||
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
dc: DC,
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
|
||||
let 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();
|
||||
// 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();
|
||||
self.interface.cmd_with_data(spi, Command::WriteRam, buffer)
|
||||
}
|
||||
|
||||
fn update_partial_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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();
|
||||
self.set_ram_area(spi, x, y, x + width, y + height)?;
|
||||
self.set_ram_counter(spi, 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();
|
||||
// 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, &[0xF7])?;
|
||||
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||
self.wait_until_idle();
|
||||
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();
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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,
|
||||
refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
if let Some(refresh_lut) = refresh_rate {
|
||||
self.refresh = refresh_lut;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_busy(&self) -> bool {
|
||||
self.interface.is_busy(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn use_full_frame(&mut self, spi: &mut SPI) -> 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, 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, x: u32, y: u32) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
// 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, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
|
||||
self.wait_until_idle();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> QuickRefresh<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// 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();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::WriteRam, buffer)?;
|
||||
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();
|
||||
self.interface.reset(delay, 2);
|
||||
|
||||
self.set_lut_helper(spi, &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();
|
||||
|
||||
self.use_full_frame(spi)?;
|
||||
|
||||
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();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0x0F])?;
|
||||
self.interface.cmd(spi, Command::MasterActivation)?;
|
||||
self.wait_until_idle();
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
+21
-22
@@ -2,33 +2,32 @@
|
||||
use crate::traits;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
PANEL_SETTING = 0x00,
|
||||
PanelSetting = 0x00,
|
||||
|
||||
POWER_SETTING = 0x01,
|
||||
POWER_OFF = 0x02,
|
||||
POWER_ON = 0x04,
|
||||
BOOSTER_SOFT_START = 0x06,
|
||||
DEEP_SLEEP = 0x07,
|
||||
DATA_START_TRANSMISSION_1 = 0x10,
|
||||
DISPLAY_REFRESH = 0x12,
|
||||
DATA_START_TRANSMISSION_2 = 0x13,
|
||||
PowerSetting = 0x01,
|
||||
PowerOff = 0x02,
|
||||
PowerOn = 0x04,
|
||||
BoosterSoftStart = 0x06,
|
||||
DeepSleep = 0x07,
|
||||
DataStartTransmission1 = 0x10,
|
||||
DisplayRefresh = 0x12,
|
||||
DataStartTransmission2 = 0x13,
|
||||
|
||||
LUT_FOR_VCOM = 0x20,
|
||||
LUT_WHITE_TO_WHITE = 0x21,
|
||||
LUT_BLACK_TO_WHITE = 0x22,
|
||||
LUT_WHITE_TO_BLACK = 0x23,
|
||||
LUT_BLACK_TO_BLACK = 0x24,
|
||||
LutForVcom = 0x20,
|
||||
LutWhiteToWhite = 0x21,
|
||||
LutBlackToWhite = 0x22,
|
||||
LutWhiteToBlack = 0x23,
|
||||
LutBlackToBlack = 0x24,
|
||||
|
||||
PLL_CONTROL = 0x30,
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40,
|
||||
TEMPERATURE_SENSOR_SELECTION = 0x41,
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
|
||||
RESOLUTION_SETTING = 0x61,
|
||||
VCM_DC_SETTING = 0x82,
|
||||
POWER_SAVING = 0xE3,
|
||||
PllControl = 0x30,
|
||||
TemperatureSensor = 0x40,
|
||||
TemperatureSensorSelection = 0x41,
|
||||
VcomAndDataIntervalSetting = 0x50,
|
||||
ResolutionSetting = 0x61,
|
||||
VcmDcSetting = 0x82,
|
||||
PowerSaving = 0xE3,
|
||||
}
|
||||
|
||||
impl traits::Command for Command {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::epd2in9bc::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 2in9b/c EPD
|
||||
///
|
||||
@@ -20,13 +20,22 @@ impl Default for Display2in9bc {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display2in9bc {
|
||||
impl DrawTarget for Display2in9bc {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display2in9bc {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
|
||||
+62
-61
@@ -6,7 +6,7 @@
|
||||
//!# use embedded_hal_mock::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!use embedded_graphics::{
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle,
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||
//!};
|
||||
//!use epd_waveshare::{epd2in9bc::*, prelude::*};
|
||||
//!#
|
||||
@@ -20,7 +20,7 @@
|
||||
//!# let mut delay = delay::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = EPD2in9bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!let mut epd = Epd2in9bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// Use display graphics from embedded-graphics
|
||||
//!// This display is for the black/white pixels
|
||||
@@ -46,10 +46,10 @@
|
||||
//! &mono_display.buffer(),
|
||||
//! &chromatic_display.buffer()
|
||||
//!)?;
|
||||
//!epd.display_frame(&mut spi)?;
|
||||
//!epd.display_frame(&mut spi, &mut delay)?;
|
||||
//!
|
||||
//!// Set the EPD to sleep
|
||||
//!epd.sleep(&mut spi)?;
|
||||
//!epd.sleep(&mut spi, &mut delay)?;
|
||||
//!# Ok(())
|
||||
//!# }
|
||||
//!```
|
||||
@@ -60,7 +60,7 @@ use embedded_hal::{
|
||||
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{
|
||||
InternalWiAdditions, RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||
InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
|
||||
};
|
||||
|
||||
/// Width of epd2in9bc in pixels
|
||||
@@ -89,52 +89,49 @@ mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display2in9bc;
|
||||
|
||||
/// EPD2in9bc driver
|
||||
pub struct EPD2in9bc<SPI, CS, BUSY, DC, RST> {
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
/// Epd2in9bc driver
|
||||
pub struct Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
for EPD2in9bc<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
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);
|
||||
|
||||
// start the booster
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x17])?;
|
||||
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||
|
||||
// power on
|
||||
self.command(spi, Command::POWER_ON)?;
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.try_delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// set the panel settings
|
||||
self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x8F])?;
|
||||
self.cmd_with_data(spi, Command::PanelSetting, &[0x8F])?;
|
||||
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::VCOM_AND_DATA_INTERVAL_SETTING,
|
||||
Command::VcomAndDataIntervalSetting,
|
||||
&[WHITE_BORDER | VCOM_DATA_INTERVAL],
|
||||
)?;
|
||||
|
||||
// set resolution
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
self.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x0A])?;
|
||||
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0A])?;
|
||||
|
||||
self.wait_until_idle();
|
||||
|
||||
@@ -142,14 +139,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD2in9bc<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn update_color_frame(
|
||||
&mut self,
|
||||
@@ -165,8 +163,7 @@ where
|
||||
///
|
||||
/// Finish by calling `update_chromatic_frame`.
|
||||
fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
self.interface.data(spi, black)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -179,8 +176,7 @@ where
|
||||
spi: &mut SPI,
|
||||
chromatic: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data(spi, chromatic)?;
|
||||
|
||||
self.wait_until_idle();
|
||||
@@ -188,16 +184,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD2in9bc<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -208,35 +206,31 @@ where
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = EPD2in9bc { interface, color };
|
||||
let mut epd = Epd2in9bc { interface, color };
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
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::VCOM_AND_DATA_INTERVAL_SETTING,
|
||||
Command::VcomAndDataIntervalSetting,
|
||||
&[FLOATING_BORDER | VCOM_DATA_INTERVAL],
|
||||
)?;
|
||||
|
||||
self.command(spi, Command::POWER_OFF)?;
|
||||
// The example STM code from Github has a wait after POWER_OFF
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
// The example STM code from Github has a wait after PowerOff
|
||||
self.wait_until_idle();
|
||||
|
||||
self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?;
|
||||
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.init(spi, delay)
|
||||
}
|
||||
|
||||
@@ -256,17 +250,20 @@ where
|
||||
HEIGHT
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
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)?;
|
||||
self.interface.data(spi, buffer)?;
|
||||
|
||||
// Clear the chromatic layer
|
||||
let color = self.color.get_byte_value();
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||
|
||||
self.wait_until_idle();
|
||||
@@ -286,33 +283,36 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.command(spi, Command::DisplayRefresh)?;
|
||||
|
||||
self.wait_until_idle();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.display_frame(spi)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
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::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
|
||||
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||
|
||||
// Clear the chromatic
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
|
||||
|
||||
self.wait_until_idle();
|
||||
@@ -322,7 +322,7 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
_spi: &mut SPI,
|
||||
_refresh_rate: Option<RefreshLUT>,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -332,13 +332,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD2in9bc<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
@@ -358,14 +359,14 @@ where
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::RESOLUTION_SETTING)?;
|
||||
self.command(spi, Command::ResolutionSetting)?;
|
||||
|
||||
self.send_data(spi, &[w as u8])?;
|
||||
self.send_data(spi, &[(h >> 8) as u8])?;
|
||||
@@ -381,7 +382,7 @@ where
|
||||
};
|
||||
self.cmd_with_data(
|
||||
spi,
|
||||
Command::VCOM_AND_DATA_INTERVAL_SETTING,
|
||||
Command::VcomAndDataIntervalSetting,
|
||||
&[border | VCOM_DATA_INTERVAL],
|
||||
)
|
||||
}
|
||||
|
||||
+42
-43
@@ -8,7 +8,6 @@ use crate::traits;
|
||||
///
|
||||
/// The description of the single commands is mostly taken from IL0398.pdf
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift direction, booster switch, soft reset
|
||||
@@ -17,142 +16,142 @@ pub(crate) enum Command {
|
||||
/// 0x1F B/W Mode, LUT from OTP
|
||||
/// 0x2F Red Mode, LUT set by registers
|
||||
/// 0x3F B/W Mode, LUT set by registers
|
||||
PANEL_SETTING = 0x00,
|
||||
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
|
||||
POWER_SETTING = 0x01,
|
||||
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.
|
||||
POWER_OFF = 0x02,
|
||||
PowerOff = 0x02,
|
||||
/// Setting Power OFF sequence
|
||||
POWER_OFF_SEQUENCE_SETTING = 0x03,
|
||||
PowerOffSequenceSetting = 0x03,
|
||||
/// Turning On the Power
|
||||
POWER_ON = 0x04,
|
||||
PowerOn = 0x04,
|
||||
/// This command enables the internal bandgap, which will be cleared by the next POF.
|
||||
POWER_ON_MEASURE = 0x05,
|
||||
PowerOnMeasure = 0x05,
|
||||
/// Starting data transmission
|
||||
/// 3-times: self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f
|
||||
BOOSTER_SOFT_START = 0x06,
|
||||
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.
|
||||
DEEP_SLEEP = 0x07,
|
||||
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.
|
||||
DATA_START_TRANSMISSION_1 = 0x10,
|
||||
DataStartTransmission1 = 0x10,
|
||||
/// Stopping data transmission
|
||||
DATA_STOP = 0x11,
|
||||
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.
|
||||
DISPLAY_REFRESH = 0x12,
|
||||
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.
|
||||
DATA_START_TRANSMISSION_2 = 0x13,
|
||||
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
|
||||
LUT_FOR_VCOM = 0x20,
|
||||
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
|
||||
LUT_WHITE_TO_WHITE = 0x21,
|
||||
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
|
||||
LUT_BLACK_TO_WHITE = 0x22,
|
||||
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
|
||||
LUT_WHITE_TO_BLACK = 0x23,
|
||||
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
|
||||
LUT_BLACK_TO_BLACK = 0x24,
|
||||
LutBlackToBlack = 0x24,
|
||||
/// The command controls the PLL clock frequency.
|
||||
PLL_CONTROL = 0x30,
|
||||
PllControl = 0x30,
|
||||
/// This command reads the temperature sensed by the temperature sensor.
|
||||
///
|
||||
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40,
|
||||
TemperatureSensor = 0x40,
|
||||
/// Selects the Internal or External temperature sensor and offset
|
||||
TEMPERATURE_SENSOR_SELECTION = 0x41,
|
||||
TemperatureSensorSelection = 0x41,
|
||||
/// Write External Temperature Sensor
|
||||
TEMPERATURE_SENSOR_WRITE = 0x42,
|
||||
TemperatureSensorWrite = 0x42,
|
||||
/// Read External Temperature Sensor
|
||||
///
|
||||
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||
TEMPERATURE_SENSOR_READ = 0x43,
|
||||
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)
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
|
||||
VcomAndDataIntervalSetting = 0x50,
|
||||
/// This command indicates the input power condition. Host can read this flag to learn the battery condition.
|
||||
LOW_POWER_DETECTION = 0x51,
|
||||
LowPowerDetection = 0x51,
|
||||
/// This command defines non-overlap period of Gate and Source.
|
||||
TCON_SETTING = 0x60,
|
||||
TconSetting = 0x60,
|
||||
/// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR).
|
||||
RESOLUTION_SETTING = 0x61,
|
||||
ResolutionSetting = 0x61,
|
||||
/// This command defines the Fist Active Gate and First Active Source of active channels.
|
||||
GSST_SETTING = 0x65,
|
||||
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,
|
||||
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
|
||||
GET_STATUS = 0x71,
|
||||
GetStatus = 0x71,
|
||||
/// Automatically measure VCOM. This command reads the IC status
|
||||
AUTO_MEASUREMENT_VCOM = 0x80,
|
||||
AutoMeasurementVcom = 0x80,
|
||||
/// This command gets the VCOM value
|
||||
///
|
||||
/// Doesn't work! Waveshare doesn't connect the read pin
|
||||
READ_VCOM_VALUE = 0x81,
|
||||
ReadVcomValue = 0x81,
|
||||
/// Set VCM_DC
|
||||
VCM_DC_SETTING = 0x82,
|
||||
VcmDcSetting = 0x82,
|
||||
/// This command sets partial window
|
||||
PARTIAL_WINDOW = 0x90,
|
||||
PartialWindow = 0x90,
|
||||
/// This command makes the display enter partial mode
|
||||
PARTIAL_IN = 0x91,
|
||||
PartialIn = 0x91,
|
||||
/// This command makes the display exit partial mode and enter normal mode
|
||||
PARTIAL_OUT = 0x92,
|
||||
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.
|
||||
PROGRAM_MODE = 0xA0,
|
||||
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.
|
||||
ACTIVE_PROGRAMMING = 0xA1,
|
||||
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.
|
||||
READ_OTP = 0xA2,
|
||||
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.
|
||||
POWER_SAVING = 0xE3,
|
||||
PowerSaving = 0xE3,
|
||||
}
|
||||
|
||||
impl traits::Command for Command {
|
||||
@@ -169,10 +168,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn command_addr() {
|
||||
assert_eq!(Command::POWER_SAVING.address(), 0xE3);
|
||||
assert_eq!(Command::PowerSaving.address(), 0xE3);
|
||||
|
||||
assert_eq!(Command::PANEL_SETTING.address(), 0x00);
|
||||
assert_eq!(Command::PanelSetting.address(), 0x00);
|
||||
|
||||
assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12);
|
||||
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||
}
|
||||
}
|
||||
|
||||
+76
-70
@@ -1,109 +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] = [
|
||||
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,
|
||||
// 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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
];
|
||||
|
||||
+18
-7
@@ -1,7 +1,7 @@
|
||||
use crate::epd4in2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 4in2 EPD
|
||||
///
|
||||
@@ -22,13 +22,21 @@ impl Default for Display4in2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display4in2 {
|
||||
impl DrawTarget for Display4in2 {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display4in2 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
@@ -59,7 +67,10 @@ mod tests {
|
||||
use crate::color::Color;
|
||||
use crate::epd4in2;
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{primitives::Line, style::PrimitiveStyle};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
|
||||
+258
-78
@@ -11,7 +11,7 @@
|
||||
//!# use embedded_hal_mock::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!use embedded_graphics::{
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle,
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||
//!};
|
||||
//!use epd_waveshare::{epd4in2::*, prelude::*};
|
||||
//!#
|
||||
@@ -25,7 +25,7 @@
|
||||
//!# let mut delay = delay::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = EPD4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// Use display graphics from embedded-graphics
|
||||
//!let mut display = Display4in2::default();
|
||||
@@ -36,11 +36,11 @@
|
||||
//! .draw(&mut display);
|
||||
//!
|
||||
//! // Display updated frame
|
||||
//!epd.update_frame(&mut spi, &display.buffer())?;
|
||||
//!epd.display_frame(&mut spi)?;
|
||||
//!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)?;
|
||||
//!epd.sleep(&mut spi, &mut delay)?;
|
||||
//!# Ok(())
|
||||
//!# }
|
||||
//!```
|
||||
@@ -55,7 +55,7 @@ use embedded_hal::{
|
||||
};
|
||||
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay};
|
||||
use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay};
|
||||
|
||||
//The Lookup Tables for the Display
|
||||
mod constants;
|
||||
@@ -79,58 +79,64 @@ mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display4in2;
|
||||
|
||||
/// EPD4in2 driver
|
||||
/// Epd4in2 driver
|
||||
///
|
||||
pub struct EPD4in2<SPI, CS, BUSY, DC, RST> {
|
||||
pub struct Epd4in2<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Background Color
|
||||
color: Color,
|
||||
/// Refresh LUT
|
||||
refresh: RefreshLUT,
|
||||
refresh: RefreshLut,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
for EPD4in2<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// reset the device
|
||||
self.interface.reset(delay, 10);
|
||||
|
||||
// set the power settings
|
||||
self.interface.cmd_with_data(
|
||||
spi,
|
||||
Command::POWER_SETTING,
|
||||
Command::PowerSetting,
|
||||
&[0x03, 0x00, 0x2b, 0x2b, 0xff],
|
||||
)?;
|
||||
|
||||
// start the booster
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x17])?;
|
||||
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?;
|
||||
|
||||
// power on
|
||||
self.command(spi, Command::POWER_ON)?;
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.try_delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// set the panel settings
|
||||
self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x3F])?;
|
||||
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::PLL_CONTROL, &[0x3A])?;
|
||||
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, None)?;
|
||||
|
||||
@@ -139,16 +145,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD4in2<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -159,10 +167,10 @@ where
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = EPD4in2 {
|
||||
let mut epd = Epd4in2 {
|
||||
interface,
|
||||
color,
|
||||
refresh: RefreshLUT::FULL,
|
||||
refresh: RefreshLut::Full,
|
||||
};
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
@@ -170,53 +178,44 @@ where
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x17])?; //border floating
|
||||
self.command(spi, Command::VCM_DC_SETTING)?; // VCOM to 0V
|
||||
self.command(spi, Command::PANEL_SETTING)?;
|
||||
.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::POWER_SETTING)?; //VG&VS to 0V fast
|
||||
self.command(spi, Command::PowerSetting)?; //VG&VS to 0V fast
|
||||
for _ in 0..4 {
|
||||
self.send_data(spi, &[0x00])?;
|
||||
}
|
||||
|
||||
self.command(spi, Command::POWER_OFF)?;
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
self.wait_until_idle();
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?;
|
||||
.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
buffer: &[u8],
|
||||
_delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
let color_value = self.color.get_byte_value();
|
||||
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x12])?;
|
||||
|
||||
//VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x97])?;
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||
|
||||
self.interface
|
||||
.cmd_with_data(spi, Command::DATA_START_TRANSMISSION_2, buffer)?;
|
||||
.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -235,8 +234,8 @@ where
|
||||
//return Err("Wrong buffersize");
|
||||
}
|
||||
|
||||
self.command(spi, Command::PARTIAL_IN)?;
|
||||
self.command(spi, Command::PARTIAL_WINDOW)?;
|
||||
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
|
||||
@@ -255,42 +254,45 @@ where
|
||||
//TODO: handle dtm somehow
|
||||
let is_dtm1 = false;
|
||||
if is_dtm1 {
|
||||
self.command(spi, Command::DATA_START_TRANSMISSION_1)? //TODO: check if data_start transmission 1 also needs "old"/background data here
|
||||
self.command(spi, Command::DataStartTransmission1)? //TODO: check if data_start transmission 1 also needs "old"/background data here
|
||||
} else {
|
||||
self.command(spi, Command::DATA_START_TRANSMISSION_2)?
|
||||
self.command(spi, Command::DataStartTransmission2)?
|
||||
}
|
||||
|
||||
self.send_data(spi, buffer)?;
|
||||
|
||||
self.command(spi, Command::PARTIAL_OUT)?;
|
||||
self.command(spi, Command::PartialOut)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
self.command(spi, Command::DisplayRefresh)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
let color_value = self.color.get_byte_value();
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission1)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||
|
||||
self.interface
|
||||
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.interface.cmd(spi, Command::DataStartTransmission2)?;
|
||||
self.interface
|
||||
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
|
||||
Ok(())
|
||||
@@ -315,16 +317,16 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
refresh_rate: Option<RefreshLUT>,
|
||||
refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
if let Some(refresh_lut) = refresh_rate {
|
||||
self.refresh = refresh_lut;
|
||||
}
|
||||
match self.refresh {
|
||||
RefreshLUT::FULL => {
|
||||
RefreshLut::Full => {
|
||||
self.set_lut_helper(spi, &LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB)
|
||||
}
|
||||
RefreshLUT::QUICK => self.set_lut_helper(
|
||||
RefreshLut::Quick => self.set_lut_helper(
|
||||
spi,
|
||||
&LUT_VCOM0_QUICK,
|
||||
&LUT_WW_QUICK,
|
||||
@@ -340,13 +342,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD4in2<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
@@ -366,14 +369,14 @@ where
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::RESOLUTION_SETTING)?;
|
||||
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])?;
|
||||
@@ -391,19 +394,196 @@ where
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
// LUT VCOM
|
||||
self.cmd_with_data(spi, Command::LUT_FOR_VCOM, lut_vcom)?;
|
||||
self.cmd_with_data(spi, Command::LutForVcom, lut_vcom)?;
|
||||
|
||||
// LUT WHITE to WHITE
|
||||
self.cmd_with_data(spi, Command::LUT_WHITE_TO_WHITE, lut_ww)?;
|
||||
self.cmd_with_data(spi, Command::LutWhiteToWhite, lut_ww)?;
|
||||
|
||||
// LUT BLACK to WHITE
|
||||
self.cmd_with_data(spi, Command::LUT_BLACK_TO_WHITE, lut_bw)?;
|
||||
self.cmd_with_data(spi, Command::LutBlackToWhite, lut_bw)?;
|
||||
|
||||
// LUT WHITE to BLACK
|
||||
self.cmd_with_data(spi, Command::LUT_WHITE_TO_BLACK, lut_wb)?;
|
||||
self.cmd_with_data(spi, Command::LutWhiteToBlack, lut_wb)?;
|
||||
|
||||
// LUT BLACK to BLACK
|
||||
self.cmd_with_data(spi, Command::LUT_BLACK_TO_BLACK, lut_bb)?;
|
||||
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, CS, BUSY, DC, RST, DELAY> QuickRefresh<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// 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();
|
||||
|
||||
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();
|
||||
// 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,
|
||||
buffer: &[u8],
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
|
||||
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,
|
||||
buffer: &[u8],
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
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,
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
use crate::color::OctColor;
|
||||
use crate::epd5in65f::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{DisplayRotation, OctDisplay};
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 5in65f EPD
|
||||
///
|
||||
/// Can also be manually constructed:
|
||||
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 2 * HEIGHT]`
|
||||
pub struct Display5in65f {
|
||||
buffer: [u8; WIDTH as usize * HEIGHT as usize / 2],
|
||||
rotation: DisplayRotation,
|
||||
}
|
||||
|
||||
impl Default for Display5in65f {
|
||||
fn default() -> Self {
|
||||
Display5in65f {
|
||||
buffer: [OctColor::colors_byte(DEFAULT_BACKGROUND_COLOR, DEFAULT_BACKGROUND_COLOR);
|
||||
WIDTH as usize * HEIGHT as usize / 2],
|
||||
rotation: DisplayRotation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for Display5in65f {
|
||||
type Color = OctColor;
|
||||
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.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display5in65f {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
impl OctDisplay for Display5in65f {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||
self.rotation = rotation;
|
||||
}
|
||||
|
||||
fn rotation(&self) -> DisplayRotation {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::epd5in65f;
|
||||
use crate::graphics::{DisplayRotation, OctDisplay};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
fn graphics_size() {
|
||||
let display = Display5in65f::default();
|
||||
assert_eq!(display.buffer().len(), 448 * 600 / 2);
|
||||
}
|
||||
|
||||
// test default background color on all bytes
|
||||
#[test]
|
||||
fn graphics_default() {
|
||||
let display = Display5in65f::default();
|
||||
for &byte in display.buffer() {
|
||||
assert_eq!(
|
||||
byte,
|
||||
OctColor::colors_byte(
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR,
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_0() {
|
||||
let mut display = Display5in65f::default();
|
||||
|
||||
let _ = Line::new(Point::new(0, 0), Point::new(1, 0))
|
||||
.into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
for &byte in buffer.iter().take(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((OctColor::Black, OctColor::Black))
|
||||
);
|
||||
}
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR,
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_90() {
|
||||
let mut display = Display5in65f::default();
|
||||
display.set_rotation(DisplayRotation::Rotate90);
|
||||
|
||||
let _ = Line::new(
|
||||
Point::new(0, WIDTH as i32 - 2),
|
||||
Point::new(0, WIDTH as i32 - 1),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
for &byte in buffer.iter().take(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((OctColor::Black, OctColor::Black))
|
||||
);
|
||||
}
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR,
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_180() {
|
||||
let mut display = Display5in65f::default();
|
||||
display.set_rotation(DisplayRotation::Rotate180);
|
||||
|
||||
let _ = Line::new(
|
||||
Point::new(WIDTH as i32 - 2, HEIGHT as i32 - 1),
|
||||
Point::new(WIDTH as i32 - 1, HEIGHT as i32 - 1),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
for &byte in buffer.iter().take(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((OctColor::Black, OctColor::Black))
|
||||
);
|
||||
}
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR,
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_270() {
|
||||
let mut display = Display5in65f::default();
|
||||
display.set_rotation(DisplayRotation::Rotate270);
|
||||
|
||||
let _ = Line::new(
|
||||
Point::new(HEIGHT as i32 - 1, 0),
|
||||
Point::new(HEIGHT as i32 - 1, 1),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
for &byte in buffer.iter().take(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((OctColor::Black, OctColor::Black))
|
||||
);
|
||||
}
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(
|
||||
OctColor::split_byte(byte),
|
||||
Ok((
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR,
|
||||
epd5in65f::DEFAULT_BACKGROUND_COLOR
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_colors() {
|
||||
let mut display = Display5in65f::default();
|
||||
|
||||
const COLORS: [OctColor; 8] = [
|
||||
OctColor::HiZ,
|
||||
OctColor::White,
|
||||
OctColor::Black,
|
||||
OctColor::Red,
|
||||
OctColor::Green,
|
||||
OctColor::Orange,
|
||||
OctColor::Blue,
|
||||
OctColor::Yellow,
|
||||
];
|
||||
for c in &COLORS {
|
||||
display.clear_buffer(*c);
|
||||
for b in display.buffer() {
|
||||
assert_eq!(OctColor::split_byte(*b), Ok((*c, *c)));
|
||||
}
|
||||
}
|
||||
|
||||
for (w, c) in (0..WIDTH).zip(COLORS.iter().cycle()) {
|
||||
let _ = Line::new(
|
||||
Point::new(w as i32, 0),
|
||||
Point::new(w as i32, HEIGHT as i32 - 1),
|
||||
)
|
||||
.into_styled(PrimitiveStyle::with_stroke(*c, 1))
|
||||
.draw(&mut display);
|
||||
}
|
||||
|
||||
COLORS
|
||||
.chunks(2)
|
||||
.cycle()
|
||||
.take(WIDTH as usize * 2)
|
||||
.cycle()
|
||||
.zip(display.buffer())
|
||||
.for_each(|(window, b)| match (window, b) {
|
||||
(&[c1, c2], b) => {
|
||||
assert_eq!(OctColor::split_byte(*b), Ok((c1, c2)));
|
||||
}
|
||||
_ => panic!("unexpected pattern"),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
//! 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::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
digital::v2::{InputPin, OutputPin},
|
||||
};
|
||||
|
||||
use crate::color::OctColor;
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display5in65f;
|
||||
|
||||
/// 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;
|
||||
const IS_BUSY_LOW: bool = true;
|
||||
|
||||
/// Epd5in65f driver
|
||||
///
|
||||
pub struct Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Background Color
|
||||
color: OctColor,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// Reset the device
|
||||
self.interface.reset(delay, 2);
|
||||
|
||||
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_ms(100);
|
||||
|
||||
self.update_vcom(spi)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = OctColor;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
dc: DC,
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = 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_busy_high();
|
||||
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,
|
||||
_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_busy_high();
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
self.wait_busy_high();
|
||||
self.command(spi, Command::DisplayRefresh)?;
|
||||
self.wait_busy_high();
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
self.wait_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> {
|
||||
let bg = OctColor::colors_byte(self.color, self.color);
|
||||
self.wait_busy_high();
|
||||
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,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn is_busy(&self) -> bool {
|
||||
self.interface.is_busy(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
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_high(&mut self) {
|
||||
let _ = self.interface.wait_until_idle(true);
|
||||
}
|
||||
fn wait_busy_low(&mut self) {
|
||||
let _ = self.interface.wait_until_idle(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);
|
||||
}
|
||||
}
|
||||
+41
-42
@@ -2,136 +2,135 @@
|
||||
|
||||
use crate::traits;
|
||||
|
||||
/// EPD7in5 commands
|
||||
/// Epd7in5 commands
|
||||
///
|
||||
/// Should rarely (never?) be needed directly.
|
||||
///
|
||||
/// For more infos about the addresses and what they are doing look into the PDFs.
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||
/// direction, booster switch, soft reset.
|
||||
PANEL_SETTING = 0x00,
|
||||
PanelSetting = 0x00,
|
||||
|
||||
/// Selecting internal and external power
|
||||
POWER_SETTING = 0x01,
|
||||
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.
|
||||
POWER_OFF = 0x02,
|
||||
PowerOff = 0x02,
|
||||
|
||||
/// Setting Power OFF sequence
|
||||
POWER_OFF_SEQUENCE_SETTING = 0x03,
|
||||
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".
|
||||
POWER_ON = 0x04,
|
||||
PowerOn = 0x04,
|
||||
|
||||
/// Starting data transmission
|
||||
BOOSTER_SOFT_START = 0x06,
|
||||
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.
|
||||
DEEP_SLEEP = 0x07,
|
||||
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.
|
||||
DATA_START_TRANSMISSION_1 = 0x10,
|
||||
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.
|
||||
DATA_STOP = 0x11,
|
||||
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.
|
||||
DISPLAY_REFRESH = 0x12,
|
||||
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.
|
||||
IMAGE_PROCESS = 0x13,
|
||||
ImageProcess = 0x13,
|
||||
|
||||
/// This command builds the VCOM Look-Up Table (LUTC).
|
||||
LUT_FOR_VCOM = 0x20,
|
||||
LutForVcom = 0x20,
|
||||
/// This command builds the Black Look-Up Table (LUTB).
|
||||
LUT_BLACK = 0x21,
|
||||
LutBlack = 0x21,
|
||||
/// This command builds the White Look-Up Table (LUTW).
|
||||
LUT_WHITE = 0x22,
|
||||
LutWhite = 0x22,
|
||||
/// This command builds the Gray1 Look-Up Table (LUTG1).
|
||||
LUT_GRAY_1 = 0x23,
|
||||
LutGray1 = 0x23,
|
||||
/// This command builds the Gray2 Look-Up Table (LUTG2).
|
||||
LUT_GRAY_2 = 0x24,
|
||||
LutGray2 = 0x24,
|
||||
/// This command builds the Red0 Look-Up Table (LUTR0).
|
||||
LUT_RED_0 = 0x25,
|
||||
LutRed0 = 0x25,
|
||||
/// This command builds the Red1 Look-Up Table (LUTR1).
|
||||
LUT_RED_1 = 0x26,
|
||||
LutRed1 = 0x26,
|
||||
/// This command builds the Red2 Look-Up Table (LUTR2).
|
||||
LUT_RED_2 = 0x27,
|
||||
LutRed2 = 0x27,
|
||||
/// This command builds the Red3 Look-Up Table (LUTR3).
|
||||
LUT_RED_3 = 0x28,
|
||||
LutRed3 = 0x28,
|
||||
/// This command builds the XON Look-Up Table (LUTXON).
|
||||
LUT_XON = 0x29,
|
||||
LutXon = 0x29,
|
||||
|
||||
/// The command controls the PLL clock frequency.
|
||||
PLL_CONTROL = 0x30,
|
||||
PllControl = 0x30,
|
||||
|
||||
/// This command reads the temperature sensed by the temperature sensor.
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40,
|
||||
TemperatureSensor = 0x40,
|
||||
/// This command selects the Internal or External temperature sensor.
|
||||
TEMPERATURE_CALIBRATION = 0x41,
|
||||
TemperatureCalibration = 0x41,
|
||||
/// This command could write data to the external temperature sensor.
|
||||
TEMPERATURE_SENSOR_WRITE = 0x42,
|
||||
TemperatureSensorWrite = 0x42,
|
||||
/// This command could read data from the external temperature sensor.
|
||||
TEMPERATURE_SENSOR_READ = 0x43,
|
||||
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).
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
|
||||
VcomAndDataIntervalSetting = 0x50,
|
||||
/// This command indicates the input power condition. Host can read this flag to learn
|
||||
/// the battery condition.
|
||||
LOW_POWER_DETECTION = 0x51,
|
||||
LowPowerDetection = 0x51,
|
||||
|
||||
/// This command defines non-overlap period of Gate and Source.
|
||||
TCON_SETTING = 0x60,
|
||||
TconSetting = 0x60,
|
||||
/// This command defines alternative resolution and this setting is of higher priority
|
||||
/// than the RES\[1:0\] in R00H (PSR).
|
||||
TCON_RESOLUTION = 0x61,
|
||||
TconResolution = 0x61,
|
||||
/// This command defines MCU host direct access external memory mode.
|
||||
SPI_FLASH_CONTROL = 0x65,
|
||||
SpiFlashControl = 0x65,
|
||||
|
||||
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||
REVISION = 0x70,
|
||||
Revision = 0x70,
|
||||
/// This command reads the IC status.
|
||||
GET_STATUS = 0x71,
|
||||
GetStatus = 0x71,
|
||||
|
||||
/// This command implements related VCOM sensing setting.
|
||||
AUTO_MEASUREMENT_VCOM = 0x80,
|
||||
AutoMeasurementVcom = 0x80,
|
||||
/// This command gets the VCOM value.
|
||||
READ_VCOM_VALUE = 0x81,
|
||||
ReadVcomValue = 0x81,
|
||||
/// This command sets `VCOM_DC` value.
|
||||
VCM_DC_SETTING = 0x82,
|
||||
VcmDcSetting = 0x82,
|
||||
|
||||
/// This is in all the Waveshare controllers for EPD7in5, but it's not documented
|
||||
/// This is in all the Waveshare controllers for Epd7in5, but it's not documented
|
||||
/// anywhere in the datasheet `¯\_(ツ)_/¯`
|
||||
FLASH_MODE = 0xE5,
|
||||
FlashMode = 0xE5,
|
||||
}
|
||||
|
||||
impl traits::Command for Command {
|
||||
@@ -148,7 +147,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn command_addr() {
|
||||
assert_eq!(Command::PANEL_SETTING.address(), 0x00);
|
||||
assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12);
|
||||
assert_eq!(Command::PanelSetting.address(), 0x00);
|
||||
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-6
@@ -1,7 +1,7 @@
|
||||
use crate::epd7in5::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 7in5 EPD
|
||||
///
|
||||
@@ -22,13 +22,22 @@ impl Default for Display7in5 {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display7in5 {
|
||||
impl DrawTarget for Display7in5 {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display7in5 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
@@ -59,7 +68,10 @@ mod tests {
|
||||
use crate::color::Color;
|
||||
use crate::epd7in5;
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{primitives::Line, style::PrimitiveStyle};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
|
||||
+53
-47
@@ -13,7 +13,7 @@ use embedded_hal::{
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay};
|
||||
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
@@ -31,84 +31,83 @@ pub const HEIGHT: u32 = 384;
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||
const IS_BUSY_LOW: bool = true;
|
||||
|
||||
/// EPD7in5 driver
|
||||
/// Epd7in5 driver
|
||||
///
|
||||
pub struct EPD7in5<SPI, CS, BUSY, DC, RST> {
|
||||
pub struct Epd7in5<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Background Color
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
for EPD7in5<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// Reset the device
|
||||
self.interface.reset(delay, 10);
|
||||
|
||||
// Set the power settings
|
||||
self.cmd_with_data(spi, Command::POWER_SETTING, &[0x37, 0x00])?;
|
||||
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::PANEL_SETTING, &[0xCF, 0x08])?;
|
||||
self.cmd_with_data(spi, Command::PanelSetting, &[0xCF, 0x08])?;
|
||||
|
||||
// Start the booster
|
||||
self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0xC7, 0xCC, 0x28])?;
|
||||
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0xC7, 0xCC, 0x28])?;
|
||||
|
||||
// Power on
|
||||
self.command(spi, Command::POWER_ON)?;
|
||||
self.command(spi, Command::PowerOn)?;
|
||||
delay.try_delay_ms(5);
|
||||
self.wait_until_idle();
|
||||
|
||||
// Set the clock frequency to 50Hz (default)
|
||||
self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x3C])?;
|
||||
self.cmd_with_data(spi, Command::PllControl, &[0x3C])?;
|
||||
|
||||
// Select internal temperature sensor (default)
|
||||
self.cmd_with_data(spi, Command::TEMPERATURE_CALIBRATION, &[0x00])?;
|
||||
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::VCOM_AND_DATA_INTERVAL_SETTING, &[0x77])?;
|
||||
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x77])?;
|
||||
|
||||
// Set S2G and G2S non-overlap periods to 12 (default)
|
||||
self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?;
|
||||
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::VCM_DC_SETTING, &[0x1E])?;
|
||||
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x1E])?;
|
||||
|
||||
// This is in all the Waveshare controllers for EPD7in5
|
||||
self.cmd_with_data(spi, Command::FLASH_MODE, &[0x03])?;
|
||||
// This is in all the Waveshare controllers for Epd7in5
|
||||
self.cmd_with_data(spi, Command::FlashMode, &[0x03])?;
|
||||
|
||||
self.wait_until_idle();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD7in5<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -119,32 +118,33 @@ where
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = EPD7in5 { interface, color };
|
||||
let mut epd = Epd7in5 { interface, color };
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::POWER_OFF)?;
|
||||
self.command(spi, Command::PowerOff)?;
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?;
|
||||
self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
fn update_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
buffer: &[u8],
|
||||
_delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.command(spi, Command::DataStartTransmission1)?;
|
||||
for byte in buffer {
|
||||
let mut temp = *byte;
|
||||
for _ in 0..4 {
|
||||
@@ -171,24 +171,29 @@ where
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
self.command(spi, Command::DisplayRefresh)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.send_resolution(spi)?;
|
||||
|
||||
// The Waveshare controllers all implement clear using 0x33
|
||||
self.command(spi, Command::DATA_START_TRANSMISSION_1)?;
|
||||
self.command(spi, Command::DataStartTransmission1)?;
|
||||
self.interface
|
||||
.data_x_times(spi, 0x33, WIDTH / 8 * HEIGHT * 4)?;
|
||||
Ok(())
|
||||
@@ -213,7 +218,7 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
_spi: &mut SPI,
|
||||
_refresh_rate: Option<RefreshLUT>,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
unimplemented!();
|
||||
}
|
||||
@@ -223,13 +228,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD7in5<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
@@ -249,14 +255,14 @@ where
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::TCON_RESOLUTION)?;
|
||||
self.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])?;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
use crate::epd7in5_hd::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 7in5 EPD
|
||||
///
|
||||
/// Can also be manually constructed:
|
||||
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
|
||||
pub struct Display7in5 {
|
||||
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
|
||||
rotation: DisplayRotation,
|
||||
}
|
||||
|
||||
impl Default for Display7in5 {
|
||||
fn default() -> Self {
|
||||
Display7in5 {
|
||||
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
|
||||
WIDTH as usize * HEIGHT as usize / 8],
|
||||
rotation: DisplayRotation::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for Display7in5 {
|
||||
type Color = BinaryColor;
|
||||
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.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display7in5 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Display7in5 {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation) {
|
||||
self.rotation = rotation;
|
||||
}
|
||||
|
||||
fn rotation(&self) -> DisplayRotation {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::color::{Black, Color};
|
||||
use crate::epd7in5_hd;
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
fn graphics_size() {
|
||||
let display = Display7in5::default();
|
||||
assert_eq!(display.buffer().len(), 58080);
|
||||
}
|
||||
|
||||
// test default background color on all bytes
|
||||
#[test]
|
||||
fn graphics_default() {
|
||||
let display = Display7in5::default();
|
||||
for &byte in display.buffer() {
|
||||
assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_0() {
|
||||
let mut display = Display7in5::default();
|
||||
|
||||
let _ = Line::new(Point::new(0, 0), Point::new(7, 0))
|
||||
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_90() {
|
||||
let mut display = Display7in5::default();
|
||||
display.set_rotation(DisplayRotation::Rotate90);
|
||||
|
||||
let _ = Line::new(Point::new(0, 872), Point::new(0, 879))
|
||||
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_180() {
|
||||
let mut display = Display7in5::default();
|
||||
display.set_rotation(DisplayRotation::Rotate180);
|
||||
|
||||
let _ = Line::new(Point::new(872, 527), Point::new(879, 527))
|
||||
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_rotation_270() {
|
||||
let mut display = Display7in5::default();
|
||||
display.set_rotation(DisplayRotation::Rotate270);
|
||||
|
||||
let _ = Line::new(Point::new(527, 0), Point::new(527, 7))
|
||||
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
|
||||
.draw(&mut display);
|
||||
|
||||
let buffer = display.buffer();
|
||||
|
||||
assert_eq!(buffer[0], Color::Black.get_byte_value());
|
||||
|
||||
for &byte in buffer.iter().skip(1) {
|
||||
assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
//! 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::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
digital::v2::{InputPin, OutputPin},
|
||||
};
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
|
||||
#[cfg(feature = "graphics")]
|
||||
mod graphics;
|
||||
#[cfg(feature = "graphics")]
|
||||
pub use self::graphics::Display7in5;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// EPD7in5 (HD) driver
|
||||
///
|
||||
pub struct Epd7in5<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Background Color
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// Reset the device
|
||||
self.interface.reset(delay, 2);
|
||||
|
||||
// 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();
|
||||
self.command(spi, Command::SwReset)?;
|
||||
self.wait_until_idle();
|
||||
|
||||
self.cmd_with_data(spi, Command::AutoWriteRed, &[0xF7])?;
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::AutoWriteBw, &[0xF7])?;
|
||||
self.wait_until_idle();
|
||||
|
||||
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();
|
||||
|
||||
self.cmd_with_data(spi, Command::SetRamXAc, &[0x00, 0x00])?;
|
||||
self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
dc: DC,
|
||||
rst: RST,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<Self, SPI::Error> {
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = Epd7in5 { interface, color };
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn wake_up(&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();
|
||||
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();
|
||||
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,
|
||||
_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();
|
||||
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 * HEIGHT / 8;
|
||||
let background_color_byte = self.color.get_byte_value();
|
||||
|
||||
self.wait_until_idle();
|
||||
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();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_background_color(&mut self, color: Color) {
|
||||
self.color = color;
|
||||
}
|
||||
|
||||
fn background_color(&self) -> &Color {
|
||||
&self.color
|
||||
}
|
||||
|
||||
fn width(&self) -> u32 {
|
||||
WIDTH
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
HEIGHT
|
||||
}
|
||||
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
_spi: &mut SPI,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn is_busy(&self) -> bool {
|
||||
self.interface.is_busy(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
}
|
||||
|
||||
fn cmd_with_data(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
command: Command,
|
||||
data: &[u8],
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd_with_data(spi, command, data)
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn epd_size() {
|
||||
assert_eq!(WIDTH, 880);
|
||||
assert_eq!(HEIGHT, 528);
|
||||
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
|
||||
}
|
||||
}
|
||||
+42
-43
@@ -2,136 +2,135 @@
|
||||
|
||||
use crate::traits;
|
||||
|
||||
/// EPD7in5 commands
|
||||
/// Epd7in5 commands
|
||||
///
|
||||
/// Should rarely (never?) be needed directly.
|
||||
///
|
||||
/// For more infos about the addresses and what they are doing look into the PDFs.
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift
|
||||
/// direction, booster switch, soft reset.
|
||||
PANEL_SETTING = 0x00,
|
||||
PanelSetting = 0x00,
|
||||
|
||||
/// Selecting internal and external power
|
||||
POWER_SETTING = 0x01,
|
||||
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.
|
||||
POWER_OFF = 0x02,
|
||||
PowerOff = 0x02,
|
||||
|
||||
/// Setting Power OFF sequence
|
||||
POWER_OFF_SEQUENCE_SETTING = 0x03,
|
||||
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".
|
||||
POWER_ON = 0x04,
|
||||
PowerOn = 0x04,
|
||||
|
||||
/// Starting data transmission
|
||||
BOOSTER_SOFT_START = 0x06,
|
||||
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.
|
||||
DEEP_SLEEP = 0x07,
|
||||
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
|
||||
DATA_START_TRANSMISSION_1 = 0x10,
|
||||
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.
|
||||
DATA_STOP = 0x11,
|
||||
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.
|
||||
DISPLAY_REFRESH = 0x12,
|
||||
DisplayRefresh = 0x12,
|
||||
|
||||
/// RED or NEW_DATA
|
||||
DATA_START_TRANSMISSION_2 = 0x13,
|
||||
DataStartTransmission2 = 0x13,
|
||||
|
||||
/// Dual SPI - what for?
|
||||
DUAL_SPI = 0x15,
|
||||
DualSpi = 0x15,
|
||||
|
||||
/// This command builds the VCOM Look-Up Table (LUTC).
|
||||
LUT_FOR_VCOM = 0x20,
|
||||
LutForVcom = 0x20,
|
||||
/// This command builds the Black Look-Up Table (LUTB).
|
||||
LUT_BLACK = 0x21,
|
||||
LutBlack = 0x21,
|
||||
/// This command builds the White Look-Up Table (LUTW).
|
||||
LUT_WHITE = 0x22,
|
||||
LutWhite = 0x22,
|
||||
/// This command builds the Gray1 Look-Up Table (LUTG1).
|
||||
LUT_GRAY_1 = 0x23,
|
||||
LutGray1 = 0x23,
|
||||
/// This command builds the Gray2 Look-Up Table (LUTG2).
|
||||
LUT_GRAY_2 = 0x24,
|
||||
LutGray2 = 0x24,
|
||||
/// This command builds the Red0 Look-Up Table (LUTR0).
|
||||
LUT_RED_0 = 0x25,
|
||||
LutRed0 = 0x25,
|
||||
/// This command builds the Red1 Look-Up Table (LUTR1).
|
||||
LUT_RED_1 = 0x26,
|
||||
LutRed1 = 0x26,
|
||||
/// This command builds the Red2 Look-Up Table (LUTR2).
|
||||
LUT_RED_2 = 0x27,
|
||||
LutRed2 = 0x27,
|
||||
/// This command builds the Red3 Look-Up Table (LUTR3).
|
||||
LUT_RED_3 = 0x28,
|
||||
LutRed3 = 0x28,
|
||||
/// This command builds the XON Look-Up Table (LUTXON).
|
||||
LUT_XON = 0x29,
|
||||
LutXon = 0x29,
|
||||
|
||||
/// The command controls the PLL clock frequency.
|
||||
PLL_CONTROL = 0x30,
|
||||
PllControl = 0x30,
|
||||
|
||||
/// This command reads the temperature sensed by the temperature sensor.
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40,
|
||||
TemperatureSensor = 0x40,
|
||||
/// This command selects the Internal or External temperature sensor.
|
||||
TEMPERATURE_CALIBRATION = 0x41,
|
||||
TemperatureCalibration = 0x41,
|
||||
/// This command could write data to the external temperature sensor.
|
||||
TEMPERATURE_SENSOR_WRITE = 0x42,
|
||||
TemperatureSensorWrite = 0x42,
|
||||
/// This command could read data from the external temperature sensor.
|
||||
TEMPERATURE_SENSOR_READ = 0x43,
|
||||
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).
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50,
|
||||
VcomAndDataIntervalSetting = 0x50,
|
||||
/// This command indicates the input power condition. Host can read this flag to learn
|
||||
/// the battery condition.
|
||||
LOW_POWER_DETECTION = 0x51,
|
||||
LowPowerDetection = 0x51,
|
||||
|
||||
/// This command defines non-overlap period of Gate and Source.
|
||||
TCON_SETTING = 0x60,
|
||||
TconSetting = 0x60,
|
||||
/// This command defines alternative resolution and this setting is of higher priority
|
||||
/// than the RES\[1:0\] in R00H (PSR).
|
||||
TCON_RESOLUTION = 0x61,
|
||||
TconResolution = 0x61,
|
||||
/// This command defines MCU host direct access external memory mode.
|
||||
SPI_FLASH_CONTROL = 0x65,
|
||||
SpiFlashControl = 0x65,
|
||||
|
||||
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000.
|
||||
REVISION = 0x70,
|
||||
Revision = 0x70,
|
||||
/// This command reads the IC status.
|
||||
GET_STATUS = 0x71,
|
||||
GetStatus = 0x71,
|
||||
|
||||
/// This command implements related VCOM sensing setting.
|
||||
AUTO_MEASUREMENT_VCOM = 0x80,
|
||||
AutoMeasurementVcom = 0x80,
|
||||
/// This command gets the VCOM value.
|
||||
READ_VCOM_VALUE = 0x81,
|
||||
ReadVcomValue = 0x81,
|
||||
/// This command sets `VCOM_DC` value.
|
||||
VCM_DC_SETTING = 0x82,
|
||||
// /// This is in all the Waveshare controllers for EPD7in5, but it's not documented
|
||||
VcmDcSetting = 0x82,
|
||||
// /// This is in all the Waveshare controllers for Epd7in5, but it's not documented
|
||||
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
|
||||
// FLASH_MODE = 0xE5,
|
||||
// FlashMode = 0xE5,
|
||||
}
|
||||
|
||||
impl traits::Command for Command {
|
||||
@@ -148,7 +147,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn command_addr() {
|
||||
assert_eq!(Command::PANEL_SETTING.address(), 0x00);
|
||||
assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12);
|
||||
assert_eq!(Command::PanelSetting.address(), 0x00);
|
||||
assert_eq!(Command::DisplayRefresh.address(), 0x12);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::epd7in5_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use embedded_graphics::prelude::*;
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Full size buffer for use with the 7in5 EPD
|
||||
///
|
||||
@@ -22,13 +22,22 @@ impl Default for Display7in5 {
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget<BinaryColor> for Display7in5 {
|
||||
impl DrawTarget for Display7in5 {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(WIDTH, HEIGHT, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for Display7in5 {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(WIDTH, HEIGHT)
|
||||
}
|
||||
@@ -58,7 +67,10 @@ mod tests {
|
||||
use crate::color::{Black, Color};
|
||||
use crate::epd7in5_v2;
|
||||
use crate::graphics::{Display, DisplayRotation};
|
||||
use embedded_graphics::{primitives::Line, style::PrimitiveStyle};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
// test buffer length
|
||||
#[test]
|
||||
|
||||
+65
-55
@@ -17,7 +17,7 @@ use embedded_hal::{
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::interface::DisplayInterface;
|
||||
use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay};
|
||||
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
|
||||
|
||||
pub(crate) mod command;
|
||||
use self::command::Command;
|
||||
@@ -35,29 +35,26 @@ pub const HEIGHT: u32 = 480;
|
||||
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
|
||||
const IS_BUSY_LOW: bool = true;
|
||||
|
||||
/// EPD7in5 (V2) driver
|
||||
/// Epd7in5 (V2) driver
|
||||
///
|
||||
pub struct EPD7in5<SPI, CS, BUSY, DC, RST> {
|
||||
pub struct Epd7in5<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// Connection Interface
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
|
||||
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
|
||||
/// Background Color
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
for EPD7in5<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn init<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
// Reset the device
|
||||
self.interface.reset(delay, 2);
|
||||
|
||||
@@ -66,31 +63,33 @@ where
|
||||
// and as per specs:
|
||||
// https://www.waveshare.com/w/upload/6/60/7.5inch_e-Paper_V2_Specification.pdf
|
||||
|
||||
self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x27, 0x17])?;
|
||||
self.cmd_with_data(spi, Command::POWER_SETTING, &[0x07, 0x17, 0x3F, 0x3F])?;
|
||||
self.command(spi, Command::POWER_ON)?;
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x1F])?;
|
||||
self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x06])?;
|
||||
self.cmd_with_data(spi, Command::TCON_RESOLUTION, &[0x03, 0x20, 0x01, 0xE0])?;
|
||||
self.cmd_with_data(spi, Command::DUAL_SPI, &[0x00])?;
|
||||
self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?;
|
||||
self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x10, 0x07])?;
|
||||
self.wait_until_idle();
|
||||
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, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
for EPD7in5<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
type DisplayColor = Color;
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -101,32 +100,33 @@ where
|
||||
let interface = DisplayInterface::new(cs, busy, dc, rst);
|
||||
let color = DEFAULT_BACKGROUND_COLOR;
|
||||
|
||||
let mut epd = EPD7in5 { interface, color };
|
||||
let mut epd = Epd7in5 { interface, color };
|
||||
|
||||
epd.init(spi, delay)?;
|
||||
|
||||
Ok(epd)
|
||||
}
|
||||
|
||||
fn wake_up<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error> {
|
||||
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) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::POWER_OFF)?;
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?;
|
||||
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]) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.cmd_with_data(spi, Command::DATA_START_TRANSMISSION_2, buffer)?;
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -142,29 +142,34 @@ where
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
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]) -> Result<(), SPI::Error> {
|
||||
self.update_frame(spi, buffer)?;
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
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) -> Result<(), SPI::Error> {
|
||||
self.wait_until_idle();
|
||||
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::DATA_START_TRANSMISSION_1)?;
|
||||
self.command(spi, Command::DataStartTransmission1)?;
|
||||
self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?;
|
||||
|
||||
self.command(spi, Command::DATA_START_TRANSMISSION_2)?;
|
||||
self.command(spi, Command::DataStartTransmission2)?;
|
||||
self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?;
|
||||
|
||||
self.command(spi, Command::DISPLAY_REFRESH)?;
|
||||
self.command(spi, Command::DisplayRefresh)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -187,7 +192,7 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
_spi: &mut SPI,
|
||||
_refresh_rate: Option<RefreshLUT>,
|
||||
_refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error> {
|
||||
unimplemented!();
|
||||
}
|
||||
@@ -197,13 +202,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> EPD7in5<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
|
||||
self.interface.cmd(spi, command)
|
||||
@@ -222,15 +228,19 @@ where
|
||||
self.interface.cmd_with_data(spi, command, data)
|
||||
}
|
||||
|
||||
fn wait_until_idle(&mut self) {
|
||||
self.interface.wait_until_idle(IS_BUSY_LOW)
|
||||
fn wait_until_idle(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
|
||||
while self.interface.is_busy(IS_BUSY_LOW) {
|
||||
self.interface.cmd(spi, Command::GetStatus)?;
|
||||
delay.delay_ms(20);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
|
||||
self.command(spi, Command::TCON_RESOLUTION)?;
|
||||
self.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])?;
|
||||
|
||||
+187
-17
@@ -1,8 +1,9 @@
|
||||
//! Graphics Support for EPDs
|
||||
|
||||
use crate::buffer_len;
|
||||
use crate::color::Color;
|
||||
use embedded_graphics::{pixelcolor::BinaryColor, prelude::*};
|
||||
use crate::color::{Color, OctColor, TriColor};
|
||||
use embedded_graphics_core::pixelcolor::BinaryColor;
|
||||
use embedded_graphics_core::prelude::*;
|
||||
|
||||
/// Displayrotation
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -29,7 +30,7 @@ impl Default for DisplayRotation {
|
||||
/// - Drawing (With the help of DrawTarget/Embedded Graphics)
|
||||
/// - Rotations
|
||||
/// - Clearing
|
||||
pub trait Display: DrawTarget<BinaryColor> {
|
||||
pub trait Display: DrawTarget<Color = BinaryColor> {
|
||||
/// Clears the buffer of the display with the chosen background color
|
||||
fn clear_buffer(&mut self, background_color: Color) {
|
||||
for elem in self.get_mut_buffer().iter_mut() {
|
||||
@@ -85,6 +86,148 @@ pub trait Display: DrawTarget<BinaryColor> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Necessary traits for all displays to implement for drawing
|
||||
///
|
||||
/// Adds support for:
|
||||
/// - Drawing (With the help of DrawTarget/Embedded Graphics)
|
||||
/// - Rotations
|
||||
/// - Clearing
|
||||
pub trait TriDisplay: DrawTarget<Color = TriColor> {
|
||||
/// Clears the buffer of the display with the chosen background color
|
||||
fn clear_buffer(&mut self, background_color: TriColor) {
|
||||
for elem in self.get_mut_buffer().iter_mut() {
|
||||
*elem = background_color.get_byte_value();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the buffer
|
||||
fn buffer(&self) -> &[u8];
|
||||
|
||||
/// Returns a mutable buffer
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8];
|
||||
|
||||
/// Sets the rotation of the display
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation);
|
||||
|
||||
/// Get the current rotation of the display
|
||||
fn rotation(&self) -> DisplayRotation;
|
||||
|
||||
/// Get the offset into buffer where chromatic data starts
|
||||
fn chromatic_offset(&self) -> usize;
|
||||
|
||||
/// return the b/w part of the buffer
|
||||
fn bw_buffer(&self) -> &[u8];
|
||||
|
||||
/// return the chromatic part of the buffer
|
||||
fn chromatic_buffer(&self) -> &[u8];
|
||||
|
||||
/// Helperfunction for the Embedded Graphics draw trait
|
||||
///
|
||||
/// Becomes uneccesary when const_generics become stablised
|
||||
fn draw_helper_tri(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
pixel: Pixel<TriColor>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let rotation = self.rotation();
|
||||
|
||||
let Pixel(point, color) = pixel;
|
||||
if outside_display(point, width, height, rotation) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Give us index inside the buffer and the bit-position in that u8 which needs to be changed
|
||||
let (index, bit) = find_position(point.x as u32, point.y as u32, width, height, rotation);
|
||||
let index = index as usize;
|
||||
let offset = self.chromatic_offset();
|
||||
|
||||
let buffer = self.get_mut_buffer();
|
||||
|
||||
// "Draw" the Pixel on that bit
|
||||
match color {
|
||||
TriColor::Black => {
|
||||
// clear bit in bw-buffer -> black
|
||||
buffer[index] &= !bit;
|
||||
// set bit in chromatic-buffer -> white
|
||||
buffer[index + offset] |= bit;
|
||||
}
|
||||
TriColor::White => {
|
||||
// set bit in bw-buffer -> white
|
||||
buffer[index] |= bit;
|
||||
// set bit in chromatic-buffer -> white
|
||||
buffer[index + offset] |= bit;
|
||||
}
|
||||
TriColor::Chromatic => {
|
||||
// set bit in b/w buffer (white)
|
||||
buffer[index] |= bit;
|
||||
// clear bit in chromatic buffer -> chromatic
|
||||
buffer[index + offset] &= !bit;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Necessary traits for all displays to implement for drawing
|
||||
///
|
||||
/// Adds support for:
|
||||
/// - Drawing (With the help of DrawTarget/Embedded Graphics)
|
||||
/// - Rotations
|
||||
/// - Clearing
|
||||
pub trait OctDisplay: DrawTarget<Color = OctColor> {
|
||||
/// Clears the buffer of the display with the chosen background color
|
||||
fn clear_buffer(&mut self, background_color: OctColor) {
|
||||
for elem in self.get_mut_buffer().iter_mut() {
|
||||
*elem = OctColor::colors_byte(background_color, background_color);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the buffer
|
||||
fn buffer(&self) -> &[u8];
|
||||
|
||||
/// Returns a mutable buffer
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8];
|
||||
|
||||
/// Sets the rotation of the display
|
||||
fn set_rotation(&mut self, rotation: DisplayRotation);
|
||||
|
||||
/// Get the current rotation of the display
|
||||
fn rotation(&self) -> DisplayRotation;
|
||||
|
||||
/// Helperfunction for the Embedded Graphics draw trait
|
||||
///
|
||||
/// Becomes uneccesary when const_generics become stablised
|
||||
fn draw_helper(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
pixel: Pixel<OctColor>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let rotation = self.rotation();
|
||||
let buffer = self.get_mut_buffer();
|
||||
|
||||
let Pixel(point, color) = pixel;
|
||||
if outside_display(point, width, height, rotation) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Give us index inside the buffer and the bit-position in that u8 which needs to be changed
|
||||
let (index, upper) =
|
||||
find_oct_position(point.x as u32, point.y as u32, width, height, rotation);
|
||||
let index = index as usize;
|
||||
|
||||
// "Draw" the Pixel on that bit
|
||||
let (mask, color_nibble) = if upper {
|
||||
(0x0f, color.get_nibble() << 4)
|
||||
} else {
|
||||
(0xf0, color.get_nibble())
|
||||
};
|
||||
buffer[index] = (buffer[index] & mask) | color_nibble;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A variable Display without a predefined buffer
|
||||
///
|
||||
/// The buffer can be created as following:
|
||||
@@ -98,8 +241,7 @@ pub trait Display: DrawTarget<BinaryColor> {
|
||||
/// # use epd_waveshare::graphics::VarDisplay;
|
||||
/// # use epd_waveshare::color::Black;
|
||||
/// # use embedded_graphics::prelude::*;
|
||||
/// # use embedded_graphics::primitives::{Circle, Line};
|
||||
/// # use embedded_graphics::style::PrimitiveStyle;
|
||||
/// # use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle};
|
||||
/// let width = 128;
|
||||
/// let height = 296;
|
||||
///
|
||||
@@ -135,13 +277,22 @@ impl<'a> VarDisplay<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DrawTarget<BinaryColor> for VarDisplay<'a> {
|
||||
impl<'a> DrawTarget for VarDisplay<'a> {
|
||||
type Color = BinaryColor;
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn draw_pixel(&mut self, pixel: Pixel<BinaryColor>) -> Result<(), Self::Error> {
|
||||
self.draw_helper(self.width, self.height, pixel)
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||
{
|
||||
for pixel in pixels {
|
||||
self.draw_helper(self.width, self.height, pixel)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OriginDimensions for VarDisplay<'a> {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(self.width, self.height)
|
||||
}
|
||||
@@ -149,7 +300,7 @@ impl<'a> DrawTarget<BinaryColor> for VarDisplay<'a> {
|
||||
|
||||
impl<'a> Display for VarDisplay<'a> {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
self.buffer
|
||||
}
|
||||
|
||||
fn get_mut_buffer(&mut self) -> &mut [u8] {
|
||||
@@ -186,30 +337,46 @@ fn outside_display(p: Point, width: u32, height: u32, rotation: DisplayRotation)
|
||||
false
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
//returns index position in the u8-slice and the bit-position inside that u8
|
||||
fn find_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u8) {
|
||||
fn find_rotation(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u32) {
|
||||
let nx;
|
||||
let ny;
|
||||
match rotation {
|
||||
DisplayRotation::Rotate0 => {
|
||||
nx = x;
|
||||
ny = y;
|
||||
},
|
||||
}
|
||||
DisplayRotation::Rotate90 => {
|
||||
nx = width - 1 - y;
|
||||
ny = x;
|
||||
} ,
|
||||
}
|
||||
DisplayRotation::Rotate180 => {
|
||||
nx = width - 1 - x;
|
||||
ny = height - 1 - y;
|
||||
},
|
||||
}
|
||||
DisplayRotation::Rotate270 => {
|
||||
nx = y;
|
||||
ny = height - 1 - x;
|
||||
},
|
||||
}
|
||||
}
|
||||
(nx, ny)
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
//returns index position in the u8-slice and the bit-position inside that u8
|
||||
fn find_oct_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, bool) {
|
||||
let (nx, ny) = find_rotation(x, y, width, height, rotation);
|
||||
(
|
||||
/* what byte address is this? */
|
||||
nx / 2 + (width / 2) * ny,
|
||||
/* is this the lower nibble (within byte)? */
|
||||
(nx & 0x1) == 0,
|
||||
)
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
//returns index position in the u8-slice and the bit-position inside that u8
|
||||
fn find_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u8) {
|
||||
let (nx, ny) = find_rotation(x, y, width, height, rotation);
|
||||
(
|
||||
nx / 8 + ((width + 7) / 8) * ny,
|
||||
0x80 >> (nx % 8),
|
||||
@@ -221,7 +388,10 @@ mod tests {
|
||||
use super::{buffer_len, find_position, outside_display, Display, DisplayRotation, VarDisplay};
|
||||
use crate::color::Black;
|
||||
use crate::color::Color;
|
||||
use embedded_graphics::{prelude::*, primitives::Line, style::PrimitiveStyle};
|
||||
use embedded_graphics::{
|
||||
prelude::*,
|
||||
primitives::{Line, PrimitiveStyle},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn buffer_clear() {
|
||||
|
||||
+22
-15
@@ -7,30 +7,34 @@ use embedded_hal::{
|
||||
|
||||
/// The Connection Interface of all (?) Waveshare EPD-Devices
|
||||
///
|
||||
pub(crate) struct DisplayInterface<SPI, CS, BUSY, DC, RST> {
|
||||
pub(crate) struct DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY> {
|
||||
/// SPI
|
||||
_spi: PhantomData<SPI>,
|
||||
/// DELAY
|
||||
_delay: PhantomData<DELAY>,
|
||||
/// CS for SPI
|
||||
cs: CS,
|
||||
/// Low for busy, Wait until display is ready!
|
||||
busy: BUSY,
|
||||
/// Data/Command Control Pin (High for data, Low for command)
|
||||
dc: DC,
|
||||
/// Pin for Reseting
|
||||
/// Pin for Resetting
|
||||
rst: RST,
|
||||
}
|
||||
|
||||
impl<SPI, CS, BUSY, DC, RST> DisplayInterface<SPI, CS, BUSY, DC, RST>
|
||||
impl<SPI, CS, BUSY, DC, RST, DELAY> DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
pub fn new(cs: CS, busy: BUSY, dc: DC, rst: RST) -> Self {
|
||||
DisplayInterface {
|
||||
_spi: PhantomData::default(),
|
||||
_delay: PhantomData::default(),
|
||||
cs,
|
||||
busy,
|
||||
dc,
|
||||
@@ -51,13 +55,17 @@ where
|
||||
|
||||
/// 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())
|
||||
/// 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.try_set_high();
|
||||
|
||||
// Transfer data (u8-array) over spi
|
||||
self.write(spi, data)
|
||||
for val in data.iter().copied() {
|
||||
// Transfer data one u8 at a time over spi
|
||||
self.write(spi, &[val])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Basic function for sending [Commands](Command) and the data belonging to it.
|
||||
@@ -107,7 +115,7 @@ where
|
||||
spi.try_write(data)?;
|
||||
}
|
||||
|
||||
// deativate spi with cs high
|
||||
// deactivate spi with cs high
|
||||
let _ = self.cs.try_set_high();
|
||||
|
||||
Ok(())
|
||||
@@ -127,13 +135,12 @@ where
|
||||
/// Most likely there was a mistake with the 2in9 busy connection
|
||||
/// //TODO: use the #cfg feature to make this compile the right way for the certain types
|
||||
pub(crate) fn wait_until_idle(&mut self, is_busy_low: bool) {
|
||||
//tested: worked without the delay for all tested devices
|
||||
//self.delay_ms(1);
|
||||
|
||||
// //tested: worked without the delay for all tested devices
|
||||
// //self.delay_ms(1);
|
||||
while self.is_busy(is_busy_low) {
|
||||
//tested: REMOVAL of DELAY: it's only waiting for the signal anyway and should continue work asap
|
||||
//old: shorten the time? it was 100 in the beginning
|
||||
//self.delay_ms(5);
|
||||
// //tested: REMOVAL of DELAY: it's only waiting for the signal anyway and should continue work asap
|
||||
// //old: shorten the time? it was 100 in the beginning
|
||||
// //self.delay_ms(5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,12 +164,12 @@ where
|
||||
|
||||
/// Resets the device.
|
||||
///
|
||||
/// Often used to awake the module from deep sleep. See [EPD4in2::sleep()](EPD4in2::sleep())
|
||||
/// 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<DELAY: DelayMs<u8>>(&mut self, delay: &mut DELAY, duration: u8) {
|
||||
pub(crate) fn reset(&mut self, delay: &mut DELAY, duration: u8) {
|
||||
let _ = self.rst.try_set_high();
|
||||
delay.try_delay_ms(10);
|
||||
|
||||
|
||||
+21
-11
@@ -14,7 +14,7 @@
|
||||
//!# use embedded_hal_mock::*;
|
||||
//!# fn main() -> Result<(), MockError> {
|
||||
//!use embedded_graphics::{
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle,
|
||||
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||
//!};
|
||||
//!use epd_waveshare::{epd1in54::*, prelude::*};
|
||||
//!#
|
||||
@@ -28,22 +28,23 @@
|
||||
//!# let mut delay = delay::MockNoop::new();
|
||||
//!
|
||||
//!// Setup EPD
|
||||
//!let mut epd = EPD1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!let mut epd = Epd1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
//!
|
||||
//!// 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(Black, 1))
|
||||
//! .draw(&mut display);
|
||||
//!
|
||||
//! // Display updated frame
|
||||
//!epd.update_frame(&mut spi, &display.buffer())?;
|
||||
//!epd.display_frame(&mut spi)?;
|
||||
//!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)?;
|
||||
//!epd.sleep(&mut spi, &mut delay)?;
|
||||
//!# Ok(())
|
||||
//!# }
|
||||
//!```
|
||||
@@ -74,23 +75,32 @@ mod interface;
|
||||
|
||||
pub mod epd1in54;
|
||||
pub mod epd1in54b;
|
||||
pub mod epd1in54c;
|
||||
pub mod epd2in13_v2;
|
||||
pub mod epd2in13bc;
|
||||
pub mod epd2in7b;
|
||||
pub mod epd2in9;
|
||||
pub mod epd2in9_v2;
|
||||
pub mod epd2in9bc;
|
||||
pub mod epd4in2;
|
||||
pub mod epd5in65f;
|
||||
pub mod epd7in5;
|
||||
pub mod epd7in5_hd;
|
||||
pub mod epd7in5_v2;
|
||||
|
||||
pub(crate) mod type_a;
|
||||
|
||||
/// Includes everything important besides the chosen Display
|
||||
pub mod prelude {
|
||||
pub use crate::color::{Color, TriColor};
|
||||
pub use crate::traits::{RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay};
|
||||
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};
|
||||
pub use crate::graphics::{Display, DisplayRotation, OctDisplay, TriDisplay};
|
||||
}
|
||||
|
||||
/// Computes the needed buffer length. Takes care of rounding up in case width
|
||||
@@ -99,9 +109,9 @@ pub mod prelude {
|
||||
/// unused
|
||||
/// bits width
|
||||
/// <----><------------------------>
|
||||
/// [XXXXX210][76543210]...[76543210] ^
|
||||
/// [XXXXX210][76543210]...[76543210] | height
|
||||
/// [XXXXX210][76543210]...[76543210] v
|
||||
/// \[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
|
||||
}
|
||||
|
||||
+156
-34
@@ -1,4 +1,3 @@
|
||||
use crate::color::Color;
|
||||
use core::marker::Sized;
|
||||
use embedded_hal::{
|
||||
blocking::{delay::*, spi::Write},
|
||||
@@ -13,27 +12,28 @@ pub(crate) trait Command {
|
||||
|
||||
/// Seperates the different LUT for the Display Refresh process
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||
pub enum RefreshLUT {
|
||||
pub enum RefreshLut {
|
||||
/// The "normal" full Lookuptable for the Refresh-Sequence
|
||||
FULL,
|
||||
Full,
|
||||
/// The quick LUT where not the full refresh sequence is followed.
|
||||
/// This might lead to some
|
||||
QUICK,
|
||||
Quick,
|
||||
}
|
||||
|
||||
impl Default for RefreshLUT {
|
||||
impl Default for RefreshLut {
|
||||
fn default() -> Self {
|
||||
RefreshLUT::FULL
|
||||
RefreshLut::Full
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait InternalWiAdditions<SPI, CS, BUSY, DC, RST>
|
||||
pub(crate) trait InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// This initialises the EPD and powers it up
|
||||
///
|
||||
@@ -45,22 +45,19 @@ where
|
||||
/// 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<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error>;
|
||||
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
|
||||
}
|
||||
|
||||
/// Functions to interact with three color panels
|
||||
pub trait WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST>:
|
||||
WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
pub trait WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>:
|
||||
WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// Transmit data to the SRAM of the EPD
|
||||
///
|
||||
@@ -95,7 +92,7 @@ where
|
||||
///# use embedded_hal_mock::*;
|
||||
///# fn main() -> Result<(), MockError> {
|
||||
///use embedded_graphics::{
|
||||
/// pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle,
|
||||
/// pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle},
|
||||
///};
|
||||
///use epd_waveshare::{epd4in2::*, prelude::*};
|
||||
///#
|
||||
@@ -109,37 +106,41 @@ where
|
||||
///# let mut delay = delay::MockNoop::new();
|
||||
///
|
||||
///// Setup EPD
|
||||
///let mut epd = EPD4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
///let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
///
|
||||
///// 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(Black, 1))
|
||||
/// .draw(&mut display);
|
||||
///
|
||||
/// // Display updated frame
|
||||
///epd.update_frame(&mut spi, &display.buffer())?;
|
||||
///epd.display_frame(&mut spi)?;
|
||||
///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)?;
|
||||
///epd.sleep(&mut spi, &mut delay)?;
|
||||
///# Ok(())
|
||||
///# }
|
||||
///```
|
||||
pub trait WaveshareDisplay<SPI, CS, BUSY, DC, RST>
|
||||
pub trait WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// The Color Type used by the Display
|
||||
type DisplayColor;
|
||||
/// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC
|
||||
///
|
||||
/// This already initialises the device.
|
||||
fn new<DELAY: DelayMs<u8>>(
|
||||
fn new(
|
||||
spi: &mut SPI,
|
||||
cs: CS,
|
||||
busy: BUSY,
|
||||
@@ -153,22 +154,18 @@ where
|
||||
/// 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) -> Result<(), SPI::Error>;
|
||||
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<DELAY: DelayMs<u8>>(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
delay: &mut DELAY,
|
||||
) -> Result<(), SPI::Error>;
|
||||
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: Color);
|
||||
fn set_background_color(&mut self, color: Self::DisplayColor);
|
||||
|
||||
/// Get current background color
|
||||
fn background_color(&self) -> &Color;
|
||||
fn background_color(&self) -> &Self::DisplayColor;
|
||||
|
||||
/// Get the width of the display
|
||||
fn width(&self) -> u32;
|
||||
@@ -177,7 +174,12 @@ where
|
||||
fn height(&self) -> u32;
|
||||
|
||||
/// Transmit a full frame to the SRAM of the EPD
|
||||
fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error>;
|
||||
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
|
||||
///
|
||||
@@ -197,15 +199,20 @@ where
|
||||
/// Displays the frame data from SRAM
|
||||
///
|
||||
/// This function waits until the device isn`t busy anymore
|
||||
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error>;
|
||||
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]) -> Result<(), SPI::Error>;
|
||||
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) -> Result<(), SPI::Error>;
|
||||
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
|
||||
@@ -218,7 +225,7 @@ where
|
||||
fn set_lut(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
refresh_rate: Option<RefreshLUT>,
|
||||
refresh_rate: Option<RefreshLut>,
|
||||
) -> Result<(), SPI::Error>;
|
||||
|
||||
/// Checks if the display is busy transmitting data
|
||||
@@ -228,3 +235,118 @@ where
|
||||
/// if the device is still busy
|
||||
fn is_busy(&self) -> bool;
|
||||
}
|
||||
|
||||
/// 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::*;
|
||||
///# fn main() -> Result<(), MockError> {
|
||||
///# 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::MockNoop::new();
|
||||
///#
|
||||
///# // Setup EPD
|
||||
///# let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
|
||||
///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);
|
||||
///
|
||||
///epd.update_partial_old_frame(&mut spi, display.buffer(), x, y, frame_width, frame_height)
|
||||
/// .ok();
|
||||
///
|
||||
///display.clear_buffer(Color::White);
|
||||
///// Execute drawing commands here.
|
||||
///
|
||||
///epd.update_partial_new_frame(&mut spi, display.buffer(), x, y, frame_width, frame_height)
|
||||
/// .ok();
|
||||
///# Ok(())
|
||||
///# }
|
||||
///```
|
||||
pub trait QuickRefresh<SPI, CS, BUSY, DC, RST, DELAY>
|
||||
where
|
||||
SPI: Write<u8>,
|
||||
CS: OutputPin,
|
||||
BUSY: InputPin,
|
||||
DC: OutputPin,
|
||||
RST: OutputPin,
|
||||
DELAY: DelayMs<u8>,
|
||||
{
|
||||
/// 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.
|
||||
fn update_partial_old_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
buffer: &[u8],
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), SPI::Error>;
|
||||
|
||||
/// Updates the new frame for a portion of the display.
|
||||
fn update_partial_new_frame(
|
||||
&mut self,
|
||||
spi: &mut SPI,
|
||||
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,
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), SPI::Error>;
|
||||
}
|
||||
|
||||
+30
-27
@@ -2,13 +2,12 @@
|
||||
|
||||
use crate::traits;
|
||||
|
||||
/// EPD1in54 and EPD2IN9 commands
|
||||
/// 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)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum Command {
|
||||
/// Driver Output control
|
||||
@@ -17,58 +16,62 @@ pub(crate) enum Command {
|
||||
/// 0.. A[8]
|
||||
/// 0.. B[2:0]
|
||||
/// Default: Set A[8:0] = 0x127 and B[2:0] = 0x0
|
||||
DRIVER_OUTPUT_CONTROL = 0x01,
|
||||
DriverOutputControl = 0x01,
|
||||
/// 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
|
||||
BOOSTER_SOFT_START_CONTROL = 0x0C,
|
||||
GATE_SCAN_START_POSITION = 0x0F,
|
||||
BoosterSoftStartControl = 0x0C,
|
||||
GateScanStartPosition = 0x0F,
|
||||
//TODO: useful?
|
||||
// GATE_SCAN_START_POSITION = 0x0F,
|
||||
// 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
|
||||
DEEP_SLEEP_MODE = 0x10,
|
||||
DeepSleepMode = 0x10,
|
||||
// /// Data Entry mode setting
|
||||
DATA_ENTRY_MODE_SETTING = 0x11,
|
||||
DataEntryModeSetting = 0x11,
|
||||
|
||||
SW_RESET = 0x12,
|
||||
SwReset = 0x12,
|
||||
|
||||
TEMPERATURE_SENSOR_CONTROL = 0x1A,
|
||||
TemperatureSensorControl = 0x1A,
|
||||
|
||||
MASTER_ACTIVATION = 0x20,
|
||||
MasterActivation = 0x20,
|
||||
|
||||
DISPLAY_UPDATE_CONTROL_1 = 0x21,
|
||||
DisplayUpdateControl1 = 0x21,
|
||||
|
||||
DISPLAY_UPDATE_CONTROL_2 = 0x22,
|
||||
DisplayUpdateControl2 = 0x22,
|
||||
|
||||
WRITE_RAM = 0x24,
|
||||
WriteRam = 0x24,
|
||||
|
||||
WRITE_VCOM_REGISTER = 0x2C,
|
||||
WriteRam2 = 0x26,
|
||||
|
||||
WRITE_LUT_REGISTER = 0x32,
|
||||
WriteVcomRegister = 0x2C,
|
||||
|
||||
SET_DUMMY_LINE_PERIOD = 0x3A,
|
||||
WriteLutRegister = 0x32,
|
||||
|
||||
SET_GATE_LINE_WIDTH = 0x3B,
|
||||
WriteOtpSelection = 0x37,
|
||||
|
||||
BORDER_WAVEFORM_CONTROL = 0x3C,
|
||||
SetDummyLinePeriod = 0x3A,
|
||||
|
||||
SET_RAM_X_ADDRESS_START_END_POSITION = 0x44,
|
||||
SetGateLineWidth = 0x3B,
|
||||
|
||||
SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45,
|
||||
BorderWaveformControl = 0x3C,
|
||||
|
||||
SET_RAM_X_ADDRESS_COUNTER = 0x4E,
|
||||
SetRamXAddressStartEndPosition = 0x44,
|
||||
|
||||
SET_RAM_Y_ADDRESS_COUNTER = 0x4F,
|
||||
SetRamYAddressStartEndPosition = 0x45,
|
||||
|
||||
NOP = 0xFF,
|
||||
SetRamXAddressCounter = 0x4E,
|
||||
|
||||
SetRamYAddressCounter = 0x4F,
|
||||
|
||||
Nop = 0xFF,
|
||||
}
|
||||
|
||||
impl traits::Command for Command {
|
||||
@@ -85,10 +88,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn command_addr() {
|
||||
assert_eq!(Command::DRIVER_OUTPUT_CONTROL.address(), 0x01);
|
||||
assert_eq!(Command::DriverOutputControl.address(), 0x01);
|
||||
|
||||
assert_eq!(Command::SET_RAM_X_ADDRESS_COUNTER.address(), 0x4E);
|
||||
assert_eq!(Command::SetRamXAddressCounter.address(), 0x4E);
|
||||
|
||||
assert_eq!(Command::NOP.address(), 0xFF);
|
||||
assert_eq!(Command::Nop.address(), 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user