diff --git a/Cargo.toml b/Cargo.toml index fc9e5d3..cb92be1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ epd4in2_fast_update = [] [dependencies] -embedded-graphics = "0.4.1" +#embedded-graphics = "0.4.1" +embedded-graphics = {git = "https://github.com/caemor/embedded-graphics", branch = "patch-1"} [dependencies.embedded-hal] diff --git a/examples/embedded_linux_epd4in2/Cargo.toml b/examples/embedded_linux_epd4in2/Cargo.toml index 5bb31a6..3940b13 100644 --- a/examples/embedded_linux_epd4in2/Cargo.toml +++ b/examples/embedded_linux_epd4in2/Cargo.toml @@ -11,4 +11,7 @@ eink_waveshare_rs = { path = "../../", default-features = false, features = ["ep linux-embedded-hal = "0.2.0" +#embedded-graphics = "0.4.1" +embedded-graphics = {git = "https://github.com/caemor/embedded-graphics", branch = "patch-1"} + embedded-hal = { version = "0.2.1", features = ["unproven"] } diff --git a/examples/embedded_linux_epd4in2/src/main.rs b/examples/embedded_linux_epd4in2/src/main.rs index 922cf67..06d9312 100644 --- a/examples/embedded_linux_epd4in2/src/main.rs +++ b/examples/embedded_linux_epd4in2/src/main.rs @@ -8,10 +8,18 @@ extern crate eink_waveshare_rs; use eink_waveshare_rs::{ EPD4in2, drawing_old::{Graphics}, + drawing::{DisplayEink42BlackWhite, Display, DisplayRotation}, color::Color, WaveshareDisplay, }; +extern crate embedded_graphics; +use embedded_graphics::coord::Coord; +use embedded_graphics::fonts::Font6x8; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{Circle, Line}; +use embedded_graphics::Drawing; + use lin_hal::spidev::{self, SpidevOptions}; use lin_hal::{Pin, Spidev}; use lin_hal::sysfs_gpio::Direction; @@ -62,7 +70,7 @@ impl<'a> InputPin for HackInputPin<'a> { * */ fn main() { - run().map_err(|e| println!("{}", e.to_string())); + run().map_err(|e| println!("{}", e.to_string())).unwrap(); } @@ -136,6 +144,8 @@ fn run() -> Result<(), std::io::Error> { epd4in2.clear_frame(&mut spi).expect("clear frame error"); epd4in2.update_frame(&mut spi, graphics.get_buffer()).expect("update frame error"); epd4in2.display_frame(&mut spi)?; + + println!("Finished basic old graphics test"); delay.delay_ms(3000u16); @@ -158,6 +168,8 @@ fn run() -> Result<(), std::io::Error> { epd4in2.update_partial_frame(&mut spi, circle_graphics.get_buffer(), 160,240, 32, 32).expect("update partial frame error"); epd4in2.display_frame(&mut spi)?; + println!("Finished partial update test"); + delay.delay_ms(3000u16); @@ -170,7 +182,112 @@ fn run() -> Result<(), std::io::Error> { epd4in2.update_frame(&mut spi, graphics.get_buffer())?; epd4in2.display_frame(&mut spi)?; + println!("Finished draw string test"); + delay.delay_ms(3000u16); + println!("Now test new graphics:"); + + println!("Now test new graphics with rotate90:"); + let mut display = DisplayEink42BlackWhite::default(); + display.set_rotation(DisplayRotation::Rotate90); + display.draw( + Font6x8::render_str("Rotate 90!") + .with_stroke(Some(Color::Black)) + .with_fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); + epd4in2.display_frame(&mut spi).expect("display frame new graphics"); + delay.delay_ms(2000u16); + + println!("Now test new graphics with rotate180:"); + let mut display = DisplayEink42BlackWhite::default(); + display.set_rotation(DisplayRotation::Rotate180); + display.draw( + Font6x8::render_str("Rotate 180!") + .with_stroke(Some(Color::Black)) + .with_fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); + epd4in2.display_frame(&mut spi).expect("display frame new graphics"); + delay.delay_ms(2000u16); + + println!("Now test new graphics with rotate270:"); + let mut display = DisplayEink42BlackWhite::default(); + display.set_rotation(DisplayRotation::Rotate270); + display.draw( + Font6x8::render_str("Rotate 270!") + .with_stroke(Some(Color::Black)) + .with_fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); + epd4in2.display_frame(&mut spi).expect("display frame new graphics"); + delay.delay_ms(2000u16); + + + println!("Now test new graphics with default rotation and some special stuff:"); + let mut display = DisplayEink42BlackWhite::default(); + display.draw( + Circle::new(Coord::new(64, 64), 64) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + display.draw( + Line::new(Coord::new(64, 64), Coord::new(0, 64)) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + display.draw( + Line::new(Coord::new(64, 64), Coord::new(80, 80)) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + display.draw( + Font6x8::render_str("It's working!") + // Using Style here + .with_style(Style { + fill_color: Some(Color::Black), + stroke_color: Some(Color::White), + stroke_width: 0u8, // Has no effect on fonts + }) + .translate(Coord::new(175, 250)) + .into_iter(), + ); + + + + let mut i = 0; + loop { + i += 1; + println!("Moving Hello World. Loop {} from 20", i); + + display.draw( + Font6x8::render_str("Hello World!") + .with_style(Style { + fill_color: Some(Color::White), + stroke_color: Some(Color::Black), + stroke_width: 0u8, // Has no effect on fonts + }) + .translate(Coord::new(5 + i*10, 50)) + .into_iter(), + ); + + epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); + epd4in2.display_frame(&mut spi).expect("display frame new graphics"); + if i > 20 { + + break; + } + delay.delay_ms(1_000u16); + } + + + println!("Finished tests - going to sleep"); epd4in2.sleep(&mut spi) -} +} diff --git a/src/drawing.rs b/src/drawing.rs index 97c3ef7..7747908 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -19,29 +19,18 @@ impl Default for DisplayRotation { } } -pub enum Display { - Eink42BlackWhite, -} -impl Display { - /// Gets the Dimensions of a dipslay in the following order: - /// - Width - /// - Height - /// - Neccessary Buffersize - pub fn get_dimensions(&self) -> (u16, u16, u16) { - match self { - Display::Eink42BlackWhite => (400, 300, 15000), - } - } +pub trait Display { + fn buffer(&self) -> &[u8]; + fn set_rotation(&mut self, rotation: DisplayRotation); + fn rotation(&self) -> DisplayRotation; } -pub trait Buffer { - fn get_buffer(&self) -> &[u8]; -} pub struct DisplayEink42BlackWhite { buffer: [u8; 400 * 300 / 8], rotation: DisplayRotation, //TODO: check embedded_graphics for orientation } + impl Default for DisplayEink42BlackWhite { fn default() -> Self { use epd4in2::constants::{DEFAULT_BACKGROUND_COLOR, WIDTH, HEIGHT}; @@ -54,15 +43,23 @@ impl Default for DisplayEink42BlackWhite { } } } -impl Buffer for DisplayEink42BlackWhite { - fn get_buffer(&self) -> &[u8] { + +impl Display for DisplayEink42BlackWhite { + fn buffer(&self) -> &[u8] { &self.buffer } + fn set_rotation(&mut self, rotation: DisplayRotation) { + self.rotation = rotation; + } + fn rotation(&self) -> DisplayRotation { + self.rotation + } } -impl Drawing for DisplayEink42BlackWhite { + +impl Drawing for DisplayEink42BlackWhite { fn draw(&mut self, item_pixels: T) where - T: Iterator> + T: Iterator> { use epd4in2::constants::{DEFAULT_BACKGROUND_COLOR, WIDTH, HEIGHT}; for Pixel(UnsignedCoord(x,y), color) in item_pixels { @@ -81,7 +78,7 @@ impl Drawing for DisplayEink42BlackWhite { return; } - match Color::from(color) { + match color { Color::Black => { self.buffer[idx] &= !bit; } @@ -93,83 +90,122 @@ impl Drawing for DisplayEink42BlackWhite { } } -// impl Drawing for DisplayRibbonLeft { -// fn draw(&mut self, item_pixels: T) -// where -// T: Iterator>, -// { -// for Pixel(UnsignedCoord(x, y), color) in item_pixels { -// if y > 127 || x > 295 { -// continue; -// } -// let cell = &mut self.0[y as usize / 8 + (295 - x as usize) * 128 / 8]; -// let bit = 7 - y % 8; -// if color != 0 { -// *cell &= !(1 << bit); -// } else { -// *cell |= 1 << bit; -// } -// } -// } -// } - - - - // /// Draw a single Pixel with `color` - // /// - // /// limited to i16::max images (buffer_size) at the moment - // pub fn draw_pixel(&mut self, x: u16, y: u16, color: &Color) { - // let (idx, bit) = match self.rotation { - // Displayorientation::Rotate0 | Displayorientation::Rotate180 => ( - // (x as usize / 8 + (self.width as usize / 8) * y as usize), - // 0x80 >> (x % 8), - // ), - // Displayorientation::Rotate90 | Displayorientation::Rotate270 => ( - // y as usize / 8 * self.width as usize + x as usize, - // 0x80 >> (y % 8), - // ), - // }; - - // if idx >= self.buffer.len() { - // return; - // } - - // match color { - // Color::Black => { - // self.buffer[idx] &= !bit; - // } - // Color::White => { - // self.buffer[idx] |= bit; - // } - // } - // } - - // /// Draw a single Pixel with `color` - // /// - // /// limited to i16::max images (buffer_size) at the moment - // #[allow(dead_code)] - // fn draw_byte(&mut self, x: u16, y: u16, filling: u8, color: &Color) { - // let idx = match self.rotation { - // Displayorientation::Rotate0 | Displayorientation::Rotate180 => { - // x as usize / 8 + (self.width as usize / 8) * y as usize - // }, - // Displayorientation::Rotate90 | Displayorientation::Rotate270 => { - // y as usize / 8 + (self.width as usize / 8) * x as usize - // }, - // }; - - // if idx >= self.buffer.len() { - // return; - // } - - // match color { - // Color::Black => { - // self.buffer[idx] = !filling; - // }, - // Color::White => { - // self.buffer[idx] = filling; - // } - // } - // } - -//TODO: write tests \ No newline at end of file +//TODO: write tests +#[cfg(test)] +mod tests { + use super::*; + use epd4in2; + use embedded_graphics::coord::Coord; + use embedded_graphics::fonts::Font6x8; + use embedded_graphics::prelude::*; + use embedded_graphics::primitives::{Circle, Line}; + + #[test] + fn from_u8() { + assert_eq!(Color::Black, Color::from(0u8)); + assert_eq!(Color::White, Color::from(1u8)); + } + + // test all values aside from 0 and 1 which all should panic + #[test] + fn from_u8_panic() { + for val in 2..=u8::max_value() { + let result = std::panic::catch_unwind(|| Color::from(val)); + assert!(result.is_err()); + } + } + + // test buffer length + #[test] + fn graphics_4in2_size() { + let display = DisplayEink42BlackWhite::default(); + assert_eq!(display.buffer().len(), 15000); + } + + // test default background color on all bytes + #[test] + fn graphics_4in2_default() { + let display = DisplayEink42BlackWhite::default(); + use epd4in2; + for &byte in display.buffer() { + assert_eq!(byte, epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_4in2_rotation_0() { + let mut display = DisplayEink42BlackWhite::default(); + display.draw( + Line::new(Coord::new(0, 0), Coord::new(7, 0)) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_4in2_rotation_90() { + let mut display = DisplayEink42BlackWhite::default(); + display.set_rotation(DisplayRotation::Rotate90); + display.draw( + Line::new(Coord::new(0, 392), Coord::new(0, 399)) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_4in2_rotation_180() { + let mut display = DisplayEink42BlackWhite::default(); + display.set_rotation(DisplayRotation::Rotate180); + display.draw( + Line::new(Coord::new(392, 299), Coord::new(399, 299)) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + + } + + #[test] + fn graphics_4in2_rotation_270() { + let mut display = DisplayEink42BlackWhite::default(); + display.set_rotation(DisplayRotation::Rotate270); + display.draw( + Line::new(Coord::new(299, 0), Coord::new(299, 0)) + .with_stroke(Some(Color::Black)) + .into_iter(), + ); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + + } +} \ No newline at end of file diff --git a/src/epd1in54/mod.rs b/src/epd1in54/mod.rs index 99732d1..a4c625f 100644 --- a/src/epd1in54/mod.rs +++ b/src/epd1in54/mod.rs @@ -29,7 +29,10 @@ use hal::{ digital::*, }; -use type_a::{command::Command, LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE}; +use type_a::{ + command::Command, + constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE} +}; use color::Color; diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index 1110d11..4fd3b2a 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -28,7 +28,10 @@ use hal::{ digital::*, }; -use type_a::{command::Command, LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE}; +use type_a::{ + command::Command, + constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE} +}; use color::Color; diff --git a/src/interface.rs b/src/interface.rs index 1b1a6b9..4e07baf 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -100,8 +100,18 @@ where { // activate spi with cs low self.cs.set_low(); + // transfer spi data - spi.write(data)?; + // Be careful!! Linux has a default limit of 4096 bytes per spi transfer + // see https://raspberrypi.stackexchange.com/questions/65595/spi-transfer-fails-with-buffer-size-greater-than-4096 + if cfg!(target_os = "linux") { + for data_chunk in data.chunks(4096) { + spi.write(data_chunk)?; + } + } else { + spi.write(data)?; + } + // deativate spi with cs high self.cs.set_high(); diff --git a/src/type_a/constants.rs b/src/type_a/constants.rs new file mode 100644 index 0000000..63de296 --- /dev/null +++ b/src/type_a/constants.rs @@ -0,0 +1,14 @@ +// Original Waveforms from Waveshare +pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[ + 0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, + 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, + 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, + 0x35, 0x51, 0x51, 0x19, 0x01, 0x00 +]; + +pub(crate) const LUT_PARTIAL_UPDATE: [u8; 30] =[ + 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +]; \ No newline at end of file diff --git a/src/type_a/mod.rs b/src/type_a/mod.rs index 987a032..11b59c7 100644 --- a/src/type_a/mod.rs +++ b/src/type_a/mod.rs @@ -1,16 +1,2 @@ pub(crate) mod command; - -// Original Waveforms from Waveshare -pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[ - 0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, - 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, - 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, - 0x35, 0x51, 0x51, 0x19, 0x01, 0x00 -]; - -pub(crate) const LUT_PARTIAL_UPDATE: [u8; 30] =[ - 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -]; +pub(crate) mod constants; \ No newline at end of file