From 6c83ac07fe35f333104b387b51da63f368206b58 Mon Sep 17 00:00:00 2001 From: Yuri Volkov Date: Sun, 13 Oct 2024 18:26:39 +0200 Subject: [PATCH] Add support for epd1in02 --- src/epd1in02/command.rs | 162 +++++++++++ src/epd1in02/constants.rs | 27 ++ src/epd1in02/mod.rs | 558 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 748 insertions(+) create mode 100644 src/epd1in02/command.rs create mode 100644 src/epd1in02/constants.rs create mode 100644 src/epd1in02/mod.rs diff --git a/src/epd1in02/command.rs b/src/epd1in02/command.rs new file mode 100644 index 00000000..13ae805f --- /dev/null +++ b/src/epd1in02/command.rs @@ -0,0 +1,162 @@ +//! SPI Commands for the Waveshare 1.02" E-Ink Display + +use crate::traits; + +/// Epd1in02 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)] +#[derive(Copy, Clone)] +pub(crate) enum Command { + /// Set Resolution, LUT selection, gate scan direction, source shift + /// direction, charge pump switch, soft reset. + PanelSetting = 0x00, + + /// Selecting internal and external power + PowerSetting = 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. + PowerOff = 0x02, + + /// Setting Power OFF sequence + PowerOffSequenceSetting = 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". + PowerOn = 0x04, + + /// Setting charge pump time interval, driving strength and frequency + ChargePumpSetting = 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. + DeepSleep = 0x07, + + /// This command starts transmitting B/W data and write them into SRAM. To complete data + /// transmission, commands Display Refresh or Data Start Transmission2 must be issued. Then the chip will start to + /// send data/VCOM for panel. + DataStartTransmission1 = 0x10, + + /// This command starts transmitting RED data and write them into SRAM. To complete data + /// transmission, command Display refresh must be issued. Then the chip will start to + /// send data/VCOM for panel. + DataStartTransmission2 = 0x13, + + /// 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. + DataStop = 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. + /// The waiting interval from BUSY falling to the first FLG command must be longer than 200us. + DisplayRefresh = 0x12, + + /// This command stores white Look-Up Table + SetWhiteLut = 0x23, + + /// This command stores black Look-Up Table + SetBlackLut = 0x24, + + /// This command sets XON and the options of LUT. + LutOption = 0x2A, + + /// The command controls the PLL clock frequency. + PllControl = 0x30, + + /// This command reads the temperature sensed by the temperature sensor. + TemperatureSensorCalibration = 0x40, + /// This command selects temperature option. + TemperatureSensorSelection = 0x41, + + /// This command indicates the interval of Vcom and data output. When setting the + /// vertical back porch, the total blanking will be kept (20 Hsync). + VcomAndDataIntervalSetting = 0x50, + /// This command indicates the input power condition. Host can read this flag to learn + /// the battery condition. + LowPowerDetection = 0x51, + + /// This command defines non-overlap period of Gate and Source. + TconSetting = 0x60, + + /// This command defines alternative resolution and this setting is of higher priority + /// than the RES\[1:0\] in R00H (PSR). + TconResolution = 0x61, + + /// The command reads LUT revision and chip revision. + Revision = 0x70, + /// This command reads the IC status. + GetStatus = 0x71, + + /// This command reads Cyclic redundancy check (CRC) result. + /// + /// The calculation only incudes image data (DTM1 & DTM2), and don't containt DTM1(0x10) & DTM2(0x13). + /// Polynomial = x^16 + x^12 + x^5 + 1, initial value: 0xFFFF + /// + /// The result will be reset after this command. + CyclicRedundancyCheck = 0x72, + + /// This command implements related VCOM sensing setting. + AutoMeasurementVcom = 0x80, + /// This command gets the VCOM value. + ReadVcomValue = 0x81, + /// This command sets `VCOM_DC` value. + VcomDcSetting = 0x82, + + /// Sets window size for the partial update + PartialWindow = 0x90, + /// Sets chip into partial update mode + PartialIn = 0x91, + /// Quits partial update mode + PartialOut = 0x92, + + /// After this command is issued, the chip would enter the program mode. + /// After the programming procedure completed, a hardware reset is necessary for leaving program mode. + ProgramMode = 0xA0, + /// After this command is transmitted, the programming state machine would be activated. + /// The BUSY flag would fall to 0 until the programming is completed. + ActiveProgramming = 0xA1, + /// The command is used for reading the content of OTP for checking the data of programming. + /// The value of (n) is depending on the amount of programmed data, tha max address = 0xFFF. + ReadOtp = 0xA2, + + /// This command is set for saving power during refresh period. + /// If the output voltage of VCOM / Source is from negative to positive or + /// from positive to negative, the power saving mechanism will be activated. + /// The active period width is defined by the following two parameters. + PowerSaving = 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::PanelSetting.address(), 0x00); + assert_eq!(Command::DisplayRefresh.address(), 0x12); + } +} diff --git a/src/epd1in02/constants.rs b/src/epd1in02/constants.rs new file mode 100644 index 00000000..807a4e7c --- /dev/null +++ b/src/epd1in02/constants.rs @@ -0,0 +1,27 @@ +/// Epd1in02 waveforms +/// +/// from Waveshare +/// +pub(crate) const LUT_FULL_UPDATE_WHITE: [u8; 42] = [ + 0x60, 0x5A, 0x5A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_FULL_UPDATE_BLACK: [u8; 42] = [ + 0x90, 0x5A, 0x5A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_PARTIAL_UPDATE_WHITE: [u8; 42] = [ + 0x60, 0x01, 0x01, 0x00, 0x00, 0x01, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub(crate) const LUT_PARTIAL_UPDATE_BLACK: [u8; 42] = [ + 0x90, 0x01, 0x01, 0x00, 0x00, 0x01, 0x40, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; diff --git a/src/epd1in02/mod.rs b/src/epd1in02/mod.rs new file mode 100644 index 00000000..17d9566f --- /dev/null +++ b/src/epd1in02/mod.rs @@ -0,0 +1,558 @@ +//! A simple Driver for the Waveshare 1.02" E-Ink Display via SPI +//! +//! - [Datasheet](https://www.waveshare.com/product/1.02inch-e-paper.htm) +//! +//! The display controller IC is UC8175 + +use embedded_hal::{ + delay::DelayNs, + digital::{InputPin, OutputPin}, + spi::SpiDevice, +}; + +use crate::color::Color; +use crate::interface::DisplayInterface; +use crate::prelude::WaveshareDisplay; +use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut}; + +pub(crate) mod command; +use self::command::Command; +use crate::buffer_len; + +pub(crate) mod constants; +use self::constants::{ + LUT_FULL_UPDATE_BLACK, LUT_FULL_UPDATE_WHITE, LUT_PARTIAL_UPDATE_BLACK, + LUT_PARTIAL_UPDATE_WHITE, +}; + +/// Full size buffer for use with the 1in02 EPD +#[cfg(feature = "graphics")] +pub type Display1in02 = crate::graphics::Display< + WIDTH, + HEIGHT, + false, + { buffer_len(WIDTH as usize, HEIGHT as usize) }, + Color, +>; + +/// Width of the display +pub const WIDTH: u32 = 80; +/// Height of the display +pub const HEIGHT: u32 = 128; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; +/// BUSY is low active +const IS_BUSY_LOW: bool = true; +/// Number of bytes to contain values of all display pixels +const NUMBER_OF_BYTES: u32 = WIDTH * HEIGHT / 8; +const SINGLE_BYTE_WRITE: bool = true; + +/// Epd1in02 driver +/// +pub struct Epd1in02 { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: Color, + is_turned_on: bool, + refresh_mode: RefreshLut, +} + +impl WaveshareDisplay + for Epd1in02 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + type DisplayColor = Color; + + fn new( + spi: &mut SPI, + busy: BUSY, + dc: DC, + rst: RST, + delay: &mut DELAY, + delay_us: Option, + ) -> Result { + let interface = DisplayInterface::new(busy, dc, rst, delay_us); + let color = DEFAULT_BACKGROUND_COLOR; + + let mut epd = Epd1in02 { + interface, + color, + is_turned_on: false, + refresh_mode: RefreshLut::Full, + }; + + epd.init(spi, delay)?; + + Ok(epd) + } + + fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.turn_off(spi, delay)?; + self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; + + // display registers are set to default value + self.refresh_mode = RefreshLut::Full; + + Ok(()) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.init(spi, delay) + } + + fn set_background_color(&mut self, color: Color) { + self.color = color; + } + + fn background_color(&self) -> &Color { + &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<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + + self.set_full_mode(spi, delay)?; + + let color_value = self.background_color().get_byte_value(); + + self.command(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, color_value, NUMBER_OF_BYTES)?; + + self.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + Ok(()) + } + + // Implemented as quick partial update + // as it requires old frame update + fn update_partial_frame( + &mut self, + _spi: &mut SPI, + _delay: &mut DELAY, + _buffer: &[u8], + _x: u32, + _y: u32, + _width: u32, + _height: u32, + ) -> Result<(), SPI::Error> { + unimplemented!() + } + + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.turn_on_if_turned_off(spi, delay)?; + + self.command(spi, Command::DisplayRefresh)?; + self.wait_until_idle(spi, delay)?; + Ok(()) + } + + fn update_and_display_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_frame(spi, buffer, delay)?; + self.display_frame(spi, delay)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.set_full_mode(spi, delay)?; + + let color_value = self.background_color().get_byte_value(); + + self.command(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, !color_value, NUMBER_OF_BYTES)?; + + self.command(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, NUMBER_OF_BYTES)?; + + Ok(()) + } + + fn set_lut( + &mut self, + spi: &mut SPI, + _delay: &mut DELAY, + refresh_rate: Option, + ) -> 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), + None => return Ok(()), + }; + + self.cmd_with_data(spi, Command::SetWhiteLut, white_lut)?; + self.cmd_with_data(spi, Command::SetBlackLut, black_lut)?; + Ok(()) + } + + fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.interface.wait_until_idle(delay, IS_BUSY_LOW); + Ok(()) + } +} + +impl QuickRefresh + for Epd1in02 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + /// To be followed immediately by update_new_frame + fn update_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.set_partial_mode(spi, delay)?; + self.set_partial_window(spi, delay, 0, 0, WIDTH, HEIGHT)?; + + self.cmd_with_data(spi, Command::DataStartTransmission1, buffer)?; + Ok(()) + } + + /// To be used immediately after update_old_frame + fn update_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + Ok(()) + } + + fn display_new_frame(&mut self, _spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + unimplemented!() + } + + fn update_and_display_new_frame( + &mut self, + _spi: &mut SPI, + _buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + unimplemented!() + } + + /// To be followed immediately by update_partial_new_frame + /// isn't faster then full update + fn update_partial_old_frame( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + if !is_buffer_size_ok(buffer, width, height) { + panic!("Image buffer size is not correct") + } + + self.set_partial_mode(spi, delay)?; + self.set_partial_window(spi, delay, x, y, width, height)?; + + self.cmd_with_data(spi, Command::DataStartTransmission1, buffer)?; + Ok(()) + } + + /// To be used immediately after update_partial_old_frame + fn update_partial_new_frame( + &mut self, + spi: &mut SPI, + _delay: &mut DELAY, + buffer: &[u8], + _x: u32, + _y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + if !is_buffer_size_ok(buffer, width, height) { + panic!("Image buffer size is not correct") + } + + self.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; + Ok(()) + } + + /// Isn't faster then full clear + fn clear_partial_frame( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + // set full LUT as quick LUT requires old image + self.set_full_mode(spi, delay)?; + self.command(spi, Command::PartialIn)?; + self.set_partial_window(spi, delay, x, y, width, height)?; + + let color_value = self.background_color().get_byte_value(); + let number_of_bytes = buffer_len(width as usize, height as usize) as u32; + + self.command(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, !color_value, number_of_bytes)?; + + self.command(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, number_of_bytes)?; + + self.command(spi, Command::PartialOut)?; + + Ok(()) + } +} + +impl InternalWiAdditions + for Epd1in02 +where + SPI: SpiDevice, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // Reset the device + self.interface.reset(delay, 20_000, 2000); + + // Set the panel settings: LUT from register + self.cmd_with_data(spi, Command::PanelSetting, &[0x6F])?; + + // Set the power settings: VGH=16V, VGL=-16V, VDH=11V, VDL=-11V + self.cmd_with_data(spi, Command::PowerSetting, &[0x03, 0x00, 0x2b, 0x2b])?; + + // Set the charge pump settings: 50ms, Strength 4, 8kHz + self.cmd_with_data(spi, Command::ChargePumpSetting, &[0x3F])?; + + // Set LUT option: no All-Gate-ON + self.cmd_with_data(spi, Command::LutOption, &[0x00, 0x00])?; + + // Set the clock frequency: 50 Hz + self.cmd_with_data(spi, Command::PllControl, &[0x17])?; + + // Set Vcom and data interval: default + // set the border color the same as background color + let value = match self.background_color() { + Color::Black => 0x57, + Color::White => 0x97, + }; + self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[value])?; + + // Set the non-overlapping period of Gate and Source: 24us + self.cmd_with_data(spi, Command::TconSetting, &[0x22])?; + + // Set the real resolution + self.send_resolution(spi)?; + + // Set Vcom DC value: -1 V + self.cmd_with_data(spi, Command::VcomDcSetting, &[0x12])?; + + // Set pover saving settings + self.cmd_with_data(spi, Command::PowerSaving, &[0x33])?; + + self.set_lut(spi, delay, Some(self.refresh_mode))?; + + self.wait_until_idle(spi, delay)?; + Ok(()) + } +} + +impl Epd1in02 +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 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 send_resolution(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + let w = self.width(); + let h = self.height(); + + self.command(spi, Command::TconResolution)?; + self.send_data(spi, &[h as u8])?; + self.send_data(spi, &[w as u8]) + } + + /// PowerOn command + fn turn_on_if_turned_off( + &mut self, + spi: &mut SPI, + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + if !self.is_turned_on { + self.command(spi, Command::PowerOn)?; + self.wait_until_idle(spi, delay)?; + self.is_turned_on = true; + } + Ok(()) + } + + fn turn_off(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.command(spi, Command::PowerOff)?; + self.wait_until_idle(spi, delay)?; + self.is_turned_on = false; + Ok(()) + } + + fn set_full_mode(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + if self.refresh_mode != RefreshLut::Full { + self.command(spi, Command::PartialOut)?; + self.set_lut(spi, delay, Some(RefreshLut::Full))?; + self.refresh_mode = RefreshLut::Full; + } + Ok(()) + } + + fn set_partial_mode(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + if self.refresh_mode != RefreshLut::Quick { + self.command(spi, Command::PartialIn)?; + self.set_lut(spi, delay, Some(RefreshLut::Quick))?; + self.refresh_mode = RefreshLut::Quick; + } + Ok(()) + } + + fn set_partial_window( + &mut self, + spi: &mut SPI, + _delay: &mut DELAY, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + if !is_window_size_ok(x, y, width, height) { + panic!("Partial update window size is not correct") + } + + self.cmd_with_data( + spi, + Command::PartialWindow, + &[ + x as u8, + (x + width - 1) as u8, + y as u8, + (y + height - 1) as u8, + 0x00, + ], + )?; + + Ok(()) + } +} + +fn is_window_size_ok(x: u32, y: u32, width: u32, height: u32) -> bool { + // partial update window is inside the screen + x + width <= WIDTH && y + height <= HEIGHT + // 3 less significant bits are ignored + && x % 8 == 0 && width % 8 == 0 +} + +fn is_buffer_size_ok(buffer: &[u8], width: u32, height: u32) -> bool { + buffer_len(width as usize, height as usize) == buffer.len() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 80); + assert_eq!(HEIGHT, 128); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } + + #[test] + fn inside_of_screen() { + assert_eq!(is_window_size_ok(0, 0, 80, 128), true); + } + + #[test] + fn x_too_big() { + assert_eq!(is_window_size_ok(8, 8, 80, 1), false); + } + + #[test] + fn y_too_big() { + assert_eq!(is_window_size_ok(8, 8, 8, 121), false); + } + + #[test] + fn x_is_not_multiple_of_8() { + assert_eq!(is_window_size_ok(1, 0, 72, 128), false); + } + + #[test] + fn width_is_not_multiple_of_8() { + assert_eq!(is_window_size_ok(0, 0, 79, 128), false); + } + + #[test] + fn buffer_size_incorrect() { + let buf = [0u8; 10]; + assert_eq!(is_buffer_size_ok(&buf, 10, 10), false); + } + + #[test] + fn buffer_size_correct() { + let buf = [0u8; 10]; + assert_eq!(is_buffer_size_ok(&buf, 8, 10), true); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9b393528..f20d71b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,6 +73,7 @@ pub mod color; /// Interface for the physical connection between display and the controlling device mod interface; +pub mod epd1in02; pub mod epd1in54; pub mod epd1in54_v2; pub mod epd1in54b;