diff --git a/Cargo.lock b/Cargo.lock index 6e5e75d..4a93b48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,10 +73,66 @@ dependencies = [ "autocfg", ] +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + [[package]] name = "zel-gdeq031t10" version = "0.1.0" dependencies = [ "embedded-graphics", "embedded-hal", + "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index ac815a6..83a94c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] embedded-graphics = { version = "0.8.1", optional = true } embedded-hal = "1.0.0" +thiserror = { version = "2.0.17", default-features = false } [features] embedded-graphics = ["dep:embedded-graphics"] diff --git a/src/lib.rs b/src/lib.rs index 8394bbc..6bbc732 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ use embedded_hal::{ digital::{InputPin, OutputPin}, spi::{Operation, SpiDevice}, }; +use thiserror::Error; const EPD_WIDTH: usize = 240; const EPD_HEIGHT: usize = 320; @@ -27,25 +28,31 @@ pub struct EPaperDisplay { delay: Delay, } +#[derive(Debug, Error)] /// An error that may occur during display initialisation. pub enum InitError { /// This variant is for any error with setting the level of the reset pin. /// /// This variant may be uninhabited if the `ResetPin` type parameter for [EPaperDisplay] is one that /// doesn't produce errors. + #[error("While initialising the display, an error occurred when toggling the reset pin: {0}")] ResetError(ResetError), /// This variant is for errors when attempting to communicate with the display controller after reset. + #[error("While initialising the display, a communication error occurred: {0}")] WriteError(WriteError), } +#[derive(Debug, Error)] /// An error when trying to communicate with the display controller. pub enum WriteError { /// This variant is for any error with setting the level of the DC (Data/Command) pin. /// /// This variant may be uninhabited if the `DcPin` type parameter for [EPaperDisplay] is one that /// doesn't produce errors. + #[error("Failed to set the data/command line: {0}")] DcError(DcError), /// This variant is for errors produced by the SPI driver. + #[error("Failed to perform SPI operations: {0}")] SpiError(SpiError), } @@ -68,7 +75,7 @@ pub struct DoubleFrame { } /// The colour of a given pixel. -/// +/// /// Since the GDEQ031T10 is pure black-and-white (no greyscale), this is just a two-state enum.S pub enum PixelColour { Black = 0, @@ -76,7 +83,7 @@ pub enum PixelColour { } /// A trait for partial window strategies for use with [DoubleFrame]. -/// +/// /// This trait is sealed, so the only strategies available are [NoPartialWindow] /// and [AutomaticPartialWindow]. pub trait ApplyPartialWindow: private::Sealed { @@ -93,7 +100,7 @@ pub trait ApplyPartialWindow: private::Sealed { ); /// Add a new bounding box which must be covered by the partial window. - /// + /// /// Calls to this method accumulate, meaning all bounding boxes will be covered by the /// partial window until [ApplyPartialWindow::reset_partial_window] is called. fn update_partial_window(&mut self, min_x: u16, max_x: u16, min_y: u16, max_y: u16); @@ -165,14 +172,14 @@ impl ApplyPartialWindow for AutomaticPartialWindow { display.write_data((vred & 0xFF) as u8); display.write_data(0x00); } - + fn update_partial_window(&mut self, min_x: u16, max_x: u16, min_y: u16, max_y: u16) { self.min_x = core::cmp::min(self.min_x, min_x); self.max_x = core::cmp::max(self.max_x, max_x); self.min_y = core::cmp::min(self.min_y, min_y); self.max_y = core::cmp::max(self.max_y, max_y); } - + fn reset_partial_window(&mut self) { *self = AutomaticPartialWindow { min_x: EPD_WIDTH as u16, @@ -210,17 +217,20 @@ impl DoubleFrame { impl DoubleFrame { pub fn draw_pixel(&mut self, x: u16, y: u16, colour: PixelColour) { + if x >= EPD_WIDTH as u16 || y >= EPD_HEIGHT as u16 { + return; // pixel is offscreen so can be skipped + } self.partial_window.update_partial_window(x, x, y, y); - let x_i = x / 8; - let x_shift = x % 8; - let i = y * (EPD_WIDTH as u16 / 8) + x_i; + let x_i = x as usize / 8; + let x_shift = 7 - (x % 8); + let i = y as usize * (EPD_WIDTH / 8) + x_i; match colour { PixelColour::Black => { self.new.0[i as usize] &= !(1 << x_shift); - }, + } PixelColour::White => { self.new.0[i as usize] |= 1 << x_shift; - }, + } } } } @@ -272,6 +282,7 @@ impl(&mut self, double_frame: &mut DoubleFrame) { + pub fn draw_full_frame( + &mut self, + double_frame: &mut DoubleFrame, + ) { double_frame.partial_window.apply(self); self.write_command(0x10); for byte in double_frame.old.0 { @@ -318,6 +350,20 @@ impl