diff --git a/src/color.rs b/src/color.rs index 0177923c..960aed52 100644 --- a/src/color.rs +++ b/src/color.rs @@ -46,6 +46,24 @@ pub enum TriColor { Chromatic, } +/// For the 6 Color Displays +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum HexColor { + /// Black Color + Black = 0x00, + /// White Color + #[default] + White = 0x01, + /// Yellow Color + Yellow = 0x02, + /// Red Color + Red = 0x03, + /// Blue Color + Blue = 0x05, + /// Green Color + Green = 0x06, +} + /// For the 7 Color Displays #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum OctColor { @@ -123,6 +141,121 @@ impl ColorType for TriColor { } } +impl ColorType for HexColor { + const BITS_PER_PIXEL_PER_BUFFER: usize = 4; + const BUFFER_COUNT: usize = 1; + fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) { + let mask = !(0xF0 >> ((pos % 2) * 4)); + let bits = self.get_nibble() as u16; + (mask, if pos % 2 == 1 { bits } else { bits << 4 }) + } +} + +#[cfg(feature = "graphics")] +impl From for HexColor { + fn from(b: BinaryColor) -> HexColor { + match b { + BinaryColor::On => HexColor::Black, + BinaryColor::Off => HexColor::White, + } + } +} + +#[cfg(feature = "graphics")] +impl From for embedded_graphics_core::pixelcolor::Rgb888 { + fn from(b: HexColor) -> Self { + let (r, g, b) = b.rgb(); + Self::new(r, g, b) + } +} + +#[cfg(feature = "graphics")] +impl From for HexColor { + fn from(p: embedded_graphics_core::pixelcolor::Rgb888) -> HexColor { + use embedded_graphics_core::prelude::RgbColor; + let colors = [ + HexColor::Black, + HexColor::White, + HexColor::Green, + HexColor::Blue, + HexColor::Red, + HexColor::Yellow, + ]; + // 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(&HexColor::White) + } +} + +#[cfg(feature = "graphics")] +impl From for HexColor { + fn from(b: embedded_graphics_core::pixelcolor::raw::RawU4) -> Self { + use embedded_graphics_core::prelude::RawData; + HexColor::from_nibble(b.into_inner()).unwrap() + } +} + +#[cfg(feature = "graphics")] +impl PixelColor for HexColor { + type Raw = embedded_graphics_core::pixelcolor::raw::RawU4; +} + +impl HexColor { + /// 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: HexColor, b: HexColor) -> u8 { + a.get_nibble() << 4 | b.get_nibble() + } + + ///Take the nibble (lower 4 bits) and convert to an HexColor if possible + pub fn from_nibble(nibble: u8) -> Result { + match nibble & 0xf { + 0x00 => Ok(HexColor::Black), + 0x01 => Ok(HexColor::White), + 0x02 => Ok(HexColor::Yellow), + 0x03 => Ok(HexColor::Red), + 0x05 => Ok(HexColor::Blue), + 0x06 => Ok(HexColor::Green), + e => Err(OutOfColorRangeParseError(e)), + } + } + ///Split the nibbles of a single byte and convert both to an HexColor if possible + pub fn split_byte(byte: u8) -> Result<(HexColor, HexColor), OutOfColorRangeParseError> { + let low = HexColor::from_nibble(byte & 0xf)?; + let high = HexColor::from_nibble((byte >> 4) & 0xf)?; + Ok((high, low)) + } + /// Converts to limited range of RGB values. + pub fn rgb(self) -> (u8, u8, u8) { + match self { + HexColor::White => (0xff, 0xff, 0xff), + HexColor::Black => (0x00, 0x00, 0x00), + HexColor::Green => (0x00, 0xff, 0x00), + HexColor::Blue => (0x00, 0x00, 0xff), + HexColor::Red => (0xff, 0x00, 0x00), + HexColor::Yellow => (0xff, 0xff, 0x00), + } + } +} + impl ColorType for OctColor { const BITS_PER_PIXEL_PER_BUFFER: usize = 4; const BUFFER_COUNT: usize = 1; diff --git a/src/epd7in3e/command.rs b/src/epd7in3e/command.rs new file mode 100644 index 00000000..7556586d --- /dev/null +++ b/src/epd7in3e/command.rs @@ -0,0 +1,44 @@ +use crate::traits; + +#[allow(dead_code, clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug)] +pub(crate) enum Command { + Ox00 = 0x00, + Ox01 = 0x01, + + PowerOff = 0x02, + + Ox03 = 0x03, + + PowerOn = 0x04, + + Ox05 = 0x05, + Ox06 = 0x06, + + DeepSleep = 0x07, + + Ox08 = 0x08, + + DataStartTransmission = 0x10, + + DataFresh = 0x12, + + Ox30 = 0x30, + + Ox50 = 0x50, + Ox60 = 0x60, + Ox61 = 0x61, + + Ox84 = 0x84, + + CMDH = 0xAA, + + OxE3 = 0xE3, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} diff --git a/src/epd7in3e/mod.rs b/src/epd7in3e/mod.rs new file mode 100644 index 00000000..f23e33e2 --- /dev/null +++ b/src/epd7in3e/mod.rs @@ -0,0 +1,267 @@ +//! A simple Driver for the Waveshare 7.3inch e-Paper HAT (F) Display via SPI +//! +//! # References +//! +//! - [Datasheet](https://www.waveshare.com/wiki/7.3inch_e-Paper_HAT_(E)) +//! - [Waveshare C driver](https://github.com/waveshareteam/e-Paper/blob/af0c7dc2b04220b04dae75838cff47f9046d49b5/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_7in3e.c) +//! - [Waveshare Python driver](https://github.com/waveshareteam/e-Paper/blob/af0c7dc2b04220b04dae75838cff47f9046d49b5/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd7in3e.py) + +use embedded_hal::{ + delay::DelayNs, + digital::{InputPin, OutputPin}, + spi::SpiDevice, +}; + +use crate::{ + buffer_len, + color::HexColor, + interface::DisplayInterface, + traits::{InternalWiAdditions, WaveshareDisplay}, +}; + +use self::command::Command; + +mod command; + +/// Full size buffer for use with the 7in3e EPD +#[cfg(feature = "graphics")] +pub type Display7in3e = crate::graphics::Display< + WIDTH, + HEIGHT, + false, + { buffer_len(WIDTH as usize, HEIGHT as usize * 4) }, + HexColor, +>; + +/// Width of the display +pub const WIDTH: u32 = 800; +/// Height of the display +pub const HEIGHT: u32 = 480; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: HexColor = HexColor::White; +/// Default mode of writing data (single byte vs blockwise) +const SINGLE_BYTE_WRITE: bool = true; + +/// Epd57n3f driver +pub struct Epd7in3e { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: HexColor, +} + +impl InternalWiAdditions + for Epd7in3e +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), ::Error> { + self.interface.reset(delay, 20_000, 20_000); + self.wait_busy_low(delay); + delay.delay_ms(30); + + self.cmd_with_data(spi, Command::CMDH, &[0x49, 0x55, 0x20, 0x08, 0x09, 0x18])?; + self.cmd_with_data(spi, Command::Ox01, &[0x3f])?; + self.cmd_with_data(spi, Command::Ox00, &[0x5f, 0x69])?; + self.cmd_with_data(spi, Command::Ox03, &[0x00, 0x54, 0x00, 0x44])?; + self.cmd_with_data(spi, Command::Ox05, &[0x40, 0x1f, 0x1f, 0x2c])?; + self.cmd_with_data(spi, Command::Ox06, &[0x6f, 0x1f, 0x17, 0x49])?; + self.cmd_with_data(spi, Command::Ox08, &[0x6f, 0x1f, 0x1f, 0x22])?; + self.cmd_with_data(spi, Command::Ox30, &[0x03])?; + self.cmd_with_data(spi, Command::Ox50, &[0x3f])?; + self.cmd_with_data(spi, Command::Ox60, &[0x02, 0x00])?; + self.cmd_with_data(spi, Command::Ox61, &[0x03, 0x20, 0x01, 0xe0])?; + self.cmd_with_data(spi, Command::Ox84, &[0x01])?; + self.cmd_with_data(spi, Command::OxE3, &[0x2f])?; + + self.command(spi, Command::PowerOn)?; + self.wait_busy_low(delay); + + Ok(()) + } +} + +impl WaveshareDisplay + for Epd7in3e +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + type DisplayColor = HexColor; + + fn new( + spi: &mut SPI, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result::Error> + where + Self: Sized, + { + let interface = DisplayInterface::new(busy, dc, rst, delay_us); + let color = DEFAULT_BACKGROUND_COLOR; + + let mut epd = Epd7in3e { interface, color }; + + epd.init(spi, delay)?; + + Ok(epd) + } + + fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), ::Error> { + self.cmd_with_data(spi, Command::PowerOff, &[0x00])?; + self.wait_busy_low(delay); + + self.cmd_with_data(spi, Command::DeepSleep, &[0xa5])?; + Ok(()) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), ::Error> { + self.init(spi, delay) + } + + fn set_background_color(&mut self, color: Self::DisplayColor) { + self.color = color; + } + + fn background_color(&self) -> &Self::DisplayColor { + &self.color + } + + fn width(&self) -> u32 { + WIDTH + } + + fn height(&self) -> u32 { + HEIGHT + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), ::Error> { + self.wait_until_idle(spi, delay)?; + self.cmd_with_data(spi, Command::DataStartTransmission, buffer) + } + + fn update_partial_frame( + &mut self, + _spi: &mut SPI, + _delay: &mut DELAY, + _buffer: &[u8], + _x: u32, + _y: u32, + _width: u32, + _height: u32, + ) -> Result<(), ::Error> { + unimplemented!() + } + + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), ::Error> { + self.command(spi, Command::PowerOn)?; + self.wait_busy_low(delay); + + self.cmd_with_data(spi, Command::Ox06, &[0x6f, 0x1f, 0x17, 0x49])?; + + self.cmd_with_data(spi, Command::DataFresh, &[0x00])?; + self.wait_busy_low(delay); + + self.cmd_with_data(spi, Command::PowerOff, &[0x00])?; + self.wait_busy_low(delay); + + Ok(()) + } + + fn update_and_display_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), ::Error> { + self.update_frame(spi, buffer, delay)?; + self.display_frame(spi, delay) + } + + fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), ::Error> { + let bg = HexColor::colors_byte(self.color, self.color); + + self.wait_busy_low(delay); + self.command(spi, Command::DataStartTransmission)?; + self.interface.data_x_times(spi, bg, WIDTH * HEIGHT / 2)?; + + self.display_frame(spi, delay) + } + + fn set_lut( + &mut self, + _spi: &mut SPI, + _delay: &mut DELAY, + _refresh_rate: Option, + ) -> Result<(), ::Error> { + unimplemented!() + } + + fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), ::Error> { + self.wait_busy_low(delay); + Ok(()) + } +} + +impl Epd7in3e +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> { + self.interface.cmd(spi, command) + } + + fn cmd_with_data( + &mut self, + spi: &mut SPI, + command: Command, + data: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd_with_data(spi, command, data) + } + + fn wait_busy_low(&mut self, delay: &mut DELAY) { + self.interface.wait_until_idle(delay, true); + } + + /// Show 6 blocks of color, used for quick testing + pub fn show_6block(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + let color_6 = [ + HexColor::Black, + HexColor::Yellow, + HexColor::Red, + HexColor::Blue, + HexColor::Green, + HexColor::White, + ]; + + self.command(spi, Command::DataStartTransmission)?; + for color in color_6.iter() { + for _ in 0..20_000 { + self.interface + .data(spi, &[HexColor::colors_byte(*color, *color)])?; + } + } + + self.display_frame(spi, delay) + } +} diff --git a/src/lib.rs b/src/lib.rs index 3ef7291a..9ebe7071 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ pub mod epd4in2; pub mod epd5in65f; pub mod epd5in83_v2; pub mod epd5in83b_v2; +pub mod epd7in3e; pub mod epd7in3f; pub mod epd7in5; pub mod epd7in5_hd; @@ -109,7 +110,7 @@ pub(crate) mod type_a; /// Includes everything important besides the chosen Display pub mod prelude { - pub use crate::color::{Color, OctColor, TriColor}; + pub use crate::color::{Color, HexColor, OctColor, TriColor}; pub use crate::traits::{ QuickRefresh, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, };