diff --git a/Cargo.toml b/Cargo.toml index fb127c6..5d69b64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "ISC" name = "epd-waveshare" readme = "README.md" repository = "https://github.com/Caemor/epd-waveshare.git" -version = "0.4.0" +version = "0.5.0" edition = "2018" [badges] diff --git a/src/color.rs b/src/color.rs index a1e5ec7..ee446da 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,6 +2,8 @@ #[cfg(feature = "graphics")] use embedded_graphics::pixelcolor::BinaryColor; +#[cfg(feature = "graphics")] +use embedded_graphics::pixelcolor::PixelColor; #[cfg(feature = "graphics")] pub use BinaryColor::Off as White; @@ -28,6 +30,78 @@ pub enum TriColor { Chromatic, } +/// For the 5in65 8 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, +} + +impl From<()> for OctColor { + fn from(_: ()) -> OctColor { + OctColor::White + } +} + +#[cfg(feature = "graphics")] +impl PixelColor for OctColor { + type Raw = (); +} + +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 { + 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), + _ => Err(()) + } + } + ///Split the nibbles of a single byte and convert both to an OctColor if possible + pub fn split_byte(byte: u8) -> Result<(OctColor, OctColor), ()> { + 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), + } + } +} //TODO: Rename get_bit_value to bit() and get_byte_value to byte() ? impl Color { diff --git a/src/epd1in54/mod.rs b/src/epd1in54/mod.rs index e4f833f..66420ea 100644 --- a/src/epd1in54/mod.rs +++ b/src/epd1in54/mod.rs @@ -151,6 +151,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn width(&self) -> u32 { WIDTH } diff --git a/src/epd1in54b/mod.rs b/src/epd1in54b/mod.rs index 11fd985..c14050e 100644 --- a/src/epd1in54b/mod.rs +++ b/src/epd1in54b/mod.rs @@ -142,6 +142,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn new>( spi: &mut SPI, cs: CS, diff --git a/src/epd2in13_v2/mod.rs b/src/epd2in13_v2/mod.rs index b636c9f..704d262 100644 --- a/src/epd2in13_v2/mod.rs +++ b/src/epd2in13_v2/mod.rs @@ -166,6 +166,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn new>( spi: &mut SPI, cs: CS, diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index 46a9155..d2fbabb 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -147,6 +147,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn width(&self) -> u32 { WIDTH } diff --git a/src/epd2in9bc/mod.rs b/src/epd2in9bc/mod.rs index 9bab93b..300352c 100644 --- a/src/epd2in9bc/mod.rs +++ b/src/epd2in9bc/mod.rs @@ -197,6 +197,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn new>( spi: &mut SPI, cs: CS, diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index 8894132..a3a972b 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -148,6 +148,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn new>( spi: &mut SPI, cs: CS, diff --git a/src/epd5in65f/command.rs b/src/epd5in65f/command.rs new file mode 100644 index 0000000..54c4ac2 --- /dev/null +++ b/src/epd5in65f/command.rs @@ -0,0 +1,151 @@ +//! 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)] +#[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, + + /// Selecting internal and external power + POWER_SETTING = 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, + + /// Setting Power OFF sequence + POWER_OFF_SEQUENCE_SETTING = 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, + + /// Starting data transmission + BOOSTER_SOFT_START = 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, + + /// 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, + + /// 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, + + /// 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, + + /// Image Process Command + IMAGE_PROCESS_COMMAND = 0x13, + + /// This command builds the VCOM Look-Up Table (LUTC). + LUT_FOR_VCOM = 0x20, + /// This command builds the Black Look-Up Table (LUTB). + LUT_BLACK = 0x21, + /// This command builds the White Look-Up Table (LUTW). + LUT_WHITE = 0x22, + /// This command builds the Gray1 Look-Up Table (LUTG1). + LUT_GRAY_1 = 0x23, + /// This command builds the Gray2 Look-Up Table (LUTG2). + LUT_GRAY_2 = 0x24, + /// This command builds the Red0 Look-Up Table (LUTR0). + LUT_RED_0 = 0x25, + /// This command builds the Red1 Look-Up Table (LUTR1). + LUT_RED_1 = 0x26, + /// This command builds the Red2 Look-Up Table (LUTR2). + LUT_RED_2 = 0x27, + /// This command builds the Red3 Look-Up Table (LUTR3). + LUT_RED_3 = 0x28, + /// This command builds the XON Look-Up Table (LUTXON). + LUT_XON = 0x29, + + /// The command controls the PLL clock frequency. + PLL_CONTROL = 0x30, + + /// This command reads the temperature sensed by the temperature sensor. + TEMPERATURE_SENSOR_COMMAND = 0x40, + /// This command selects the Internal or External temperature sensor. + TEMPERATURE_CALIBRATION = 0x41, + /// This command could write data to the external temperature sensor. + TEMPERATURE_SENSOR_WRITE = 0x42, + /// This command could read data from the external temperature sensor. + TEMPERATURE_SENSOR_READ = 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, + /// This command indicates the input power condition. Host can read this flag to learn + /// the battery condition. + LOW_POWER_DETECTION = 0x51, + + /// This command defines non-overlap period of Gate and Source. + TCON_SETTING = 0x60, + /// This command defines alternative resolution and this setting is of higher priority + /// than the RES\[1:0\] in R00H (PSR). + TCON_RESOLUTION = 0x61, + /// This command defines MCU host direct access external memory mode. + //SPI_FLASH_CONTROL = 0x65, + + /// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000. + //REVISION = 0x70, + /// This command reads the IC status. + GET_STATUS = 0x71, + + /// This command implements related VCOM sensing setting. + //AUTO_MEASUREMENT_VCOM = 0x80, + /// This command gets the VCOM value. + READ_VCOM_VALUE = 0x81, + /// This command sets `VCOM_DC` value. + VCM_DC_SETTING = 0x82, + // /// This is in all the Waveshare controllers for EPD6in65f, but it's not documented + // /// anywhere in the datasheet `¯\_(ツ)_/¯` + FLASH_MODE = 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::PANEL_SETTING.address(), 0x00); + assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12); + } +} diff --git a/src/epd5in65f/graphics.rs b/src/epd5in65f/graphics.rs new file mode 100644 index 0000000..9ec124c --- /dev/null +++ b/src/epd5in65f/graphics.rs @@ -0,0 +1,173 @@ +use crate::epd5in65f::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; +use crate::graphics::{OctDisplay, DisplayRotation}; +use embedded_graphics::prelude::*; +use crate::color::OctColor; + +/// 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 Error = core::convert::Infallible; + + fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { + self.draw_helper(WIDTH, HEIGHT, pixel) + } + + 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::{OctDisplay, DisplayRotation}; + use embedded_graphics::{primitives::Line, style::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)) + ); + } + } +} diff --git a/src/epd5in65f/mod.rs b/src/epd5in65f/mod.rs new file mode 100644 index 0000000..59fa103 --- /dev/null +++ b/src/epd5in65f/mod.rs @@ -0,0 +1,247 @@ +//! 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 = false; + +/// EPD5in65f driver +/// +pub struct EPD5in65f { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: OctColor, +} + +impl InternalWiAdditions + for EPD5in65f +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, +{ + 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::PANEL_SETTING, &[0xEF, 0x08])?; + self.cmd_with_data(spi, Command::POWER_SETTING, &[0x37, 0x00, 0x23, 0x23])?; + self.cmd_with_data(spi, Command::POWER_OFF_SEQUENCE_SETTING, &[0x00])?; + self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0xC7, 0xC7, 0x1D])?; + self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x3C])?; + self.cmd_with_data(spi, Command::TEMPERATURE_SENSOR_COMMAND, &[0x00])?; + self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x37])?; + self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?; + self.send_resolution(spi)?; + + self.cmd_with_data(spi, Command::FLASH_MODE, &[0xAA])?; + + delay.delay_ms(100); + + self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x37])?; + Ok(()) + } +} + +impl WaveshareDisplay + for EPD5in65f +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, +{ + type DisplayColor = OctColor; + fn new>( + spi: &mut SPI, + cs: CS, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + ) -> Result { + 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) -> Result<(), SPI::Error> { + self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?; + Ok(()) + } + + fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + self.cmd_with_data(spi, Command::DATA_START_TRANSMISSION_1, 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) -> Result<(), SPI::Error> { + self.command(spi, Command::POWER_ON)?; + self.wait_busy_high(); + self.command(spi, Command::DISPLAY_REFRESH)?; + self.wait_busy_high(); + self.command(spi, Command::POWER_OFF)?; + self.wait_busy_low(); + 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)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + let bg = 0x77; /*clear frame */ //OctColor::colors_byte(self.color, self.color); + + self.command(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.data_x_times(spi, bg, WIDTH * HEIGHT / 2)?; + + self.display_frame(spi)?; + 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, + ) -> Result<(), SPI::Error> { + unimplemented!(); + } + + fn is_busy(&self) -> bool { + self.interface.is_busy(IS_BUSY_LOW) + } +} + +impl EPD5in65f +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, +{ + 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) { + self.interface.wait_until_idle(true) + } + fn wait_busy_low(&mut self) { + 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::TCON_RESOLUTION)?; + 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]) + } +} + +#[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); + } +} diff --git a/src/epd7in5/mod.rs b/src/epd7in5/mod.rs index cdc142e..883deb3 100644 --- a/src/epd7in5/mod.rs +++ b/src/epd7in5/mod.rs @@ -108,6 +108,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn new>( spi: &mut SPI, cs: CS, diff --git a/src/epd7in5_v2/mod.rs b/src/epd7in5_v2/mod.rs index 3c0fcc4..67f9dab 100644 --- a/src/epd7in5_v2/mod.rs +++ b/src/epd7in5_v2/mod.rs @@ -90,6 +90,7 @@ where DC: OutputPin, RST: OutputPin, { + type DisplayColor = Color; fn new>( spi: &mut SPI, cs: CS, diff --git a/src/graphics.rs b/src/graphics.rs index e378a64..524e550 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,7 +1,7 @@ //! Graphics Support for EPDs use crate::buffer_len; -use crate::color::Color; +use crate::color::{OctColor, Color}; use embedded_graphics::{pixelcolor::BinaryColor, prelude::*}; /// Displayrotation @@ -85,6 +85,64 @@ pub trait Display: DrawTarget { } } +/// 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 { + /// 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() { + *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; + + /// 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, + ) -> 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: @@ -186,9 +244,7 @@ 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 { @@ -209,7 +265,23 @@ fn find_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotat 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); + ( + nx / 2 + (width / 2) * ny, + (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), diff --git a/src/lib.rs b/src/lib.rs index 6c4b94b..4685308 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,8 @@ pub mod epd2in9bc; pub mod epd4in2; pub mod epd7in5; pub mod epd7in5_v2; +pub mod epd5in65f; + pub(crate) mod type_a; /// Includes everything important besides the chosen Display diff --git a/src/traits.rs b/src/traits.rs index 360e63c..9b5aa01 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,3 @@ -use crate::color::Color; use core::marker::Sized; use embedded_hal::{ blocking::{delay::*, spi::Write}, @@ -136,6 +135,8 @@ where DC: OutputPin, RST: OutputPin, { + /// 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. @@ -165,10 +166,10 @@ where ) -> 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;