Skip to content
Open
189 changes: 141 additions & 48 deletions examples/epd7in5_v2.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// This example tests rotations and draws analog clock, tests default fonts of embedded-graphics crate and displays an image of Ferris from examples/assets/ directory.
use std::error::Error;

// This example tests rotations and draws analog clock, tests default fonts of embedded-graphics crate, displays an image of Ferris from examples/assets/ directory and showcases partial updating with a digital clock.
use embedded_graphics::{
image::Image,
image::ImageRaw,
mono_font::ascii::*,
mono_font::MonoTextStyleBuilder,
image::{Image, ImageRaw},
mono_font::{ascii::*, MonoTextStyleBuilder},
prelude::*,
primitives::{Circle, Line, PrimitiveStyleBuilder},
text::{Baseline, Text, TextStyleBuilder},
Expand All @@ -12,48 +12,54 @@ use embedded_hal::delay::DelayNs;
#[cfg(feature = "graphics")]
use epd_waveshare::{color::Color, epd7in5_v2::*, graphics::DisplayRotation, prelude::*};
use linux_embedded_hal::{
spidev::{self, SpidevOptions},
sysfs_gpio::Direction,
Delay, SPIError, SpidevDevice, SysfsPin,
gpio_cdev::{Chip, LineRequestFlags},
spidev::{SpiModeFlags, SpidevOptions},
CdevPin, Delay, SpidevDevice,
};

fn main() -> Result<(), SPIError> {
// GPIO pin definitions (BCM numbering - no offset needed for cdev)
const EPD_RST_PIN: u32 = 17;
const EPD_DC_PIN: u32 = 25;
const EPD_BUSY_PIN: u32 = 24;
const EPD_PWR_PIN: u32 = 18;

fn main() -> Result<(), Box<dyn Error>> {
// Set up the device
let mut spi = SpidevDevice::open("/dev/spidev0.0").expect("spidev directory");
// Open the GPIO chip (usually gpiochip0 on Raspberry Pi)
let mut chip = Chip::new("/dev/gpiochip0")?;

// Get GPIO lines and configure them
let rst_line = chip.get_line(EPD_RST_PIN)?;
let rst_handle = rst_line.request(LineRequestFlags::OUTPUT, 0, "epd-rst")?;
let rst_pin = CdevPin::new(rst_handle)?;

let dc_line = chip.get_line(EPD_DC_PIN)?;
let dc_handle = dc_line.request(LineRequestFlags::OUTPUT, 0, "epd-dc")?;
let dc_pin = CdevPin::new(dc_handle)?;

let busy_line = chip.get_line(EPD_BUSY_PIN)?;
let busy_handle = busy_line.request(LineRequestFlags::INPUT, 0, "epd-busy")?;
let busy_pin = CdevPin::new(busy_handle)?;

let pwr_line = chip.get_line(EPD_PWR_PIN)?;
let _ = pwr_line.request(LineRequestFlags::OUTPUT, 1, "epd-pwr")?;

// Initialize SPI
let mut spi = SpidevDevice::open("/dev/spidev0.0")?;
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(10_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.mode(SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");

let cs = SysfsPin::new(26);
cs.export().expect("cs export");
while !cs.is_exported() {}
cs.set_direction(Direction::Out).expect("CS Direction");
cs.set_value(1).expect("CS Value set to 1");

let busy = SysfsPin::new(24);
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");

let dc = SysfsPin::new(25);
dc.export().expect("dc export");
while !dc.is_exported() {}
dc.set_direction(Direction::Out).expect("dc Direction");
dc.set_value(1).expect("dc Value set to 1");

let rst = SysfsPin::new(17);
rst.export().expect("rst export");
while !rst.is_exported() {}
rst.set_direction(Direction::Out).expect("rst Direction");
rst.set_value(1).expect("rst Value set to 1");
spi.configure(&options)?;

let mut delay = Delay {};

let mut epd7in5 = Epd7in5::new(&mut spi, busy, dc, rst, &mut delay, None).expect("epd new");
let mut epd7in5 =
Epd7in5::new(&mut spi, busy_pin, dc_pin, rst_pin, &mut delay, None).expect("epd new");
epd7in5.set_lut(&mut spi, &mut delay, Some(RefreshLut::Quick))?;
let mut display = Display7in5::default();
display.clear(Color::White);
println!("Device successfully initialized!");

// Test graphics display
Expand All @@ -77,9 +83,9 @@ fn main() -> Result<(), SPIError> {

// Draw an analog clock
println!("Draw a clock");
display.clear(Color::White).ok();
display.clear(Color::Black).ok();
let style = PrimitiveStyleBuilder::new()
.stroke_color(Color::Black)
.stroke_color(Color::White)
.stroke_width(1)
.build();

Expand All @@ -98,7 +104,7 @@ fn main() -> Result<(), SPIError> {
// Draw some text
println!("Print text in all sizes");
// Color is inverted - black means white, white means black; the output will be black text on white background
display.clear(Color::Black).ok();
display.clear(Color::White).ok();
let fonts = [
&FONT_4X6,
&FONT_5X7,
Expand Down Expand Up @@ -126,8 +132,8 @@ fn main() -> Result<(), SPIError> {
for (n, font) in fonts.iter().enumerate() {
let style = MonoTextStyleBuilder::new()
.font(font)
.text_color(Color::White)
.background_color(Color::Black)
.text_color(Color::Black)
.background_color(Color::White)
.build();
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();
let y = 10 + n * 30;
Expand All @@ -144,26 +150,113 @@ fn main() -> Result<(), SPIError> {

// Draw an image
println!("Draw Ferris");
display.clear(Color::Black).ok();
display.clear(Color::White).ok();
let data = include_bytes!("./assets/ferris.raw");
let raw_image = ImageRaw::<Color>::new(data, 460);
let image = Image::new(&raw_image, Point::zero());
image.draw(&mut display).unwrap();
epd7in5.update_and_display_frame(&mut spi, display.buffer(), &mut delay)?;

delay.delay_ms(5000);

println!("Clock Demo (partial update)");
epd7in5.clear_frame(&mut spi, &mut delay)?;

epd7in5.set_lut(&mut spi, &mut delay, Some(RefreshLut::PartialRefresh))?;

// Clock parameters - using FONT_6X10 (6 pixels wide, 10 pixels tall)
// "HH:MM:SS" = 8 characters
let char_width = 6;
let char_height = 10;
let clock_string_length = 8; // "HH:MM:SS"

// Create a buffer for the entire clock region (with padding)
let partial_frame_x = 299;
let partial_frame_y = 200;
let clock_buffer_width = (char_width * clock_string_length) as u32 + 1;
let clock_buffer_height = char_height as u32;

let mut partial_frame_buffer = vec![
0u8;
Display7in5::partial_frame_buffer_size(
partial_frame_x,
clock_buffer_width,
clock_buffer_height,
)
];
let mut partial_display = display.get_partial_frame(
&mut partial_frame_buffer,
partial_frame_x,
partial_frame_y,
clock_buffer_width,
clock_buffer_height,
);

// Time variables
let mut hours = 12u8;
let mut minutes = 34u8;
let mut seconds = 56u8;

println!("Updating clock every second for 10 iterations...");

for iteration in 0..10 {
// Format current time
let current_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);

// Clear the clock buffer (white background)
partial_display.clear(Color::White).unwrap();

// Draw the entire time string on the clock buffer
draw_text(&mut partial_display, &current_time, 1, 0);

let params = partial_display.get_update_parameters();

epd7in5
.update_partial_frame(
&mut spi,
&mut delay,
params.buffer,
params.x,
params.y,
params.width,
params.height,
)
.unwrap();

epd7in5.display_frame(&mut spi, &mut delay)?;

// Increment time
seconds += 1;
if seconds >= 60 {
seconds = 0;
minutes += 1;
if minutes >= 60 {
minutes = 0;
hours += 1;
if hours >= 24 {
hours = 0;
}
}
}

println!("[{}] Time: {}", iteration, current_time);
delay.delay_ms(1000);
}

// Clear and sleep
println!("Clear the display");
display.clear(Color::Black).ok();
epd7in5.update_and_display_frame(&mut spi, display.buffer(), &mut delay)?;
epd7in5.set_lut(&mut spi, &mut delay, Some(RefreshLut::Full))?;
epd7in5.clear_frame(&mut spi, &mut delay)?;
println!("Finished tests - going to sleep");
epd7in5.sleep(&mut spi, &mut delay)
epd7in5.sleep(&mut spi, &mut delay)?;
Ok(())
}

fn draw_text(display: &mut Display7in5, text: &str, x: i32, y: i32) {
fn draw_text<D: DrawTarget<Color = Color>>(display: &mut D, text: &str, x: i32, y: i32) {
let style = MonoTextStyleBuilder::new()
.font(&embedded_graphics::mono_font::ascii::FONT_6X10)
.text_color(Color::White)
.background_color(Color::Black)
.text_color(Color::Black)
.background_color(Color::White)
.build();

let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build();
Expand Down
4 changes: 3 additions & 1 deletion src/epd1in02/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ where
) -> Result<(), SPI::Error> {
let (white_lut, black_lut) = match refresh_rate {
Some(RefreshLut::Full) => (&LUT_FULL_UPDATE_WHITE, &LUT_FULL_UPDATE_BLACK),
Some(RefreshLut::Quick) => (&LUT_PARTIAL_UPDATE_WHITE, &LUT_PARTIAL_UPDATE_BLACK),
Some(RefreshLut::Quick | RefreshLut::PartialRefresh) => {
(&LUT_PARTIAL_UPDATE_WHITE, &LUT_PARTIAL_UPDATE_BLACK)
}
None => return Ok(()),
};

Expand Down
4 changes: 3 additions & 1 deletion src/epd1in54/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ where
}
match self.refresh {
RefreshLut::Full => self.set_lut_helper(spi, delay, &LUT_FULL_UPDATE),
RefreshLut::Quick => self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE),
RefreshLut::Quick | RefreshLut::PartialRefresh => {
self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE)
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/epd1in54_v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ where
}
match self.refresh {
RefreshLut::Full => self.set_lut_helper(spi, delay, &LUT_FULL_UPDATE),
RefreshLut::Quick => self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE),
RefreshLut::Quick | RefreshLut::PartialRefresh => {
self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE)
}
}?;

// Additional configuration required only for partial updates
Expand Down
2 changes: 1 addition & 1 deletion src/epd2in13_v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ where
) -> Result<(), SPI::Error> {
let buffer = match refresh_rate {
Some(RefreshLut::Full) | None => &LUT_FULL_UPDATE,
Some(RefreshLut::Quick) => &LUT_PARTIAL_UPDATE,
Some(RefreshLut::Quick | RefreshLut::PartialRefresh) => &LUT_PARTIAL_UPDATE,
};

self.cmd_with_data(spi, Command::WriteLutRegister, buffer)
Expand Down
4 changes: 3 additions & 1 deletion src/epd2in9/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,9 @@ where
}
match self.refresh {
RefreshLut::Full => self.set_lut_helper(spi, delay, &LUT_FULL_UPDATE),
RefreshLut::Quick => self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE),
RefreshLut::Quick | RefreshLut::PartialRefresh => {
self.set_lut_helper(spi, delay, &LUT_PARTIAL_UPDATE)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/epd3in7/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ where
) -> Result<(), SPI::Error> {
let buffer = match refresh_rate {
Some(RefreshLut::Full) | None => &LUT_1GRAY_GC,
Some(RefreshLut::Quick) => &LUT_1GRAY_DU,
Some(RefreshLut::Quick | RefreshLut::PartialRefresh) => &LUT_1GRAY_DU,
};

self.interface
Expand Down
2 changes: 1 addition & 1 deletion src/epd4in2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ where
RefreshLut::Full => {
self.set_lut_helper(spi, delay, &LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB)
}
RefreshLut::Quick => self.set_lut_helper(
RefreshLut::Quick | RefreshLut::PartialRefresh => self.set_lut_helper(
spi,
delay,
&LUT_VCOM0_QUICK,
Expand Down
47 changes: 23 additions & 24 deletions src/epd7in5_v2/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,6 @@ pub(crate) enum Command {
/// Dual SPI - what for?
DualSpi = 0x15,

/// This command builds the VCOM Look-Up Table (LUTC).
LutForVcom = 0x20,
/// This command builds the Black Look-Up Table (LUTB).
LutBlack = 0x21,
/// This command builds the White Look-Up Table (LUTW).
LutWhite = 0x22,
/// This command builds the Gray1 Look-Up Table (LUTG1).
LutGray1 = 0x23,
/// This command builds the Gray2 Look-Up Table (LUTG2).
LutGray2 = 0x24,
/// This command builds the Red0 Look-Up Table (LUTR0).
LutRed0 = 0x25,
/// This command builds the Red1 Look-Up Table (LUTR1).
LutRed1 = 0x26,
/// This command builds the Red2 Look-Up Table (LUTR2).
LutRed2 = 0x27,
/// This command builds the Red3 Look-Up Table (LUTR3).
LutRed3 = 0x28,
/// This command builds the XON Look-Up Table (LUTXON).
LutXon = 0x29,

/// The command controls the PLL clock frequency.
PllControl = 0x30,

Expand Down Expand Up @@ -128,9 +107,29 @@ pub(crate) enum Command {
ReadVcomValue = 0x81,
/// This command sets `VCOM_DC` value.
VcmDcSetting = 0x82,
// /// This is in all the Waveshare controllers for Epd7in5, but it's not documented
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
// FlashMode = 0xE5,

/// Sets window size for the partial update
PartialWindow = 0x90,
/// Sets chip into partial update mode
PartialIn = 0x91,
/// Quits partial update mode
PartialOut = 0x92,

// The following commands are part of an undocumented hack by the manufacturer to select an arbitrary LUT.
// Normally the temperature and NEW/OLD buffer settings are used to select the correct LUT (flicker table)
// for healthy operation in a variety of temperatures. By abusing this command and setting a normally
// out-of-range value in addition to putting custom LUT's in OTP memory, one can select LUT's for other use-cases, such as:
//
// - fast refresh (LUT with fewer flickers): 0x5A
// - partial refresh (LUT without flicker): 0x6E
// - 4-grayscale (complicated hack that uses OLD/NEW buffers to encode grayscale values): 0x5F
//
// USE AT OWN RISK
//
/// This command sets cascade settings (such as allowing override temperature) for controlling primary and secondary displays
CascadeSetting = 0xE0,
/// This command is used when cascading temperature between primary and secondary displays
ForceTemperature = 0xE5,
}

impl traits::Command for Command {
Expand Down
Loading
Loading