diff --git a/Cargo.toml b/Cargo.toml index b8864fa..ac5655d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,13 @@ travis-ci = { repository = "caemor/epd-waveshare" } [features] -default = ["epd1in54", "epd2in9", "epd4in2", "graphics"] +default = ["epd1in54", "epd2in9", "epd4in2", "epd7in5", "graphics"] graphics = ["embedded-graphics"] epd1in54 = [] epd2in9 = [] epd4in2 = [] +epd7in5 = [] # offers an alternative fast full lut for type_a displays, but the refresh isnt as clean looking type_a_alternative_faster_lut = [] diff --git a/README.md b/README.md index 9c5fe4c..bec7e86 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ epd.display_frame(&mut spi)?; | Device (with Link) | Colors | Flexible Display | Partial Refresh | Supported | Tested | | :---: | --- | :---: | :---: | :---: | :---: | +| [7.5 Inch B/W (A)](https://www.waveshare.com/product/7.5inch-e-paper-hat.htm) | Black, White | ✕ | ✕ | ✔ | ✔ | | [4.2 Inch B/W (A)](https://www.waveshare.com/product/4.2inch-e-paper-module.htm) | Black, White | ✕ | Not officially [[1](#42-inch-e-ink-blackwhite)] | ✔ | ✔ | | [1.54 Inch B/W (A)](https://www.waveshare.com/1.54inch-e-Paper-Module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | | [2.13 Inch B/W (A)](https://www.waveshare.com/product/2.13inch-e-paper-hat.htm) | Black, White | ✕ | ✔ | | | diff --git a/examples/epd7in5_full/Cargo.toml b/examples/epd7in5_full/Cargo.toml new file mode 100644 index 0000000..e9e1b8e --- /dev/null +++ b/examples/epd7in5_full/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "embedded_linux_eink_example" +version = "0.1.0" +authors = [ + "Christoph Groß ", + "Jack Grigg ", +] +edition = "2018" + +[dependencies] +embedded-graphics = "0.5.2" +embedded-hal = { version = "0.2.2", features = ["unproven"] } +epd-waveshare = { path = "../../", default-features = false, features = ["epd7in5", "graphics"]} +linux-embedded-hal = "0.2.2" diff --git a/examples/epd7in5_full/src/main.rs b/examples/epd7in5_full/src/main.rs new file mode 100644 index 0000000..1a3ed2f --- /dev/null +++ b/examples/epd7in5_full/src/main.rs @@ -0,0 +1,188 @@ +#![deny(warnings)] + +use embedded_graphics::{ + coord::Coord, + fonts::{Font12x16, Font6x8}, + prelude::*, + primitives::{Circle, Line}, + Drawing, +}; +use embedded_hal::prelude::*; +use epd_waveshare::{ + epd7in5::{Display7in5, EPD7in5}, + graphics::{Display, DisplayRotation}, + prelude::*, +}; +use linux_embedded_hal::{ + spidev::{self, SpidevOptions}, + sysfs_gpio::Direction, + Delay, Pin, Spidev, +}; + +// activate spi, gpio in raspi-config +// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems +// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues + +fn main() { + if let Err(e) = run() { + eprintln!("Program exited early with error: {}", e); + } +} + +fn run() -> Result<(), std::io::Error> { + // Configure SPI + // Settings are taken from + let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory"); + let options = SpidevOptions::new() + .bits_per_word(8) + .max_speed_hz(4_000_000) + .mode(spidev::SPI_MODE_0) + .build(); + spi.configure(&options).expect("spi configuration"); + + // Configure Digital I/O Pin to be used as Chip Select for SPI + let cs = Pin::new(8); + cs.export().expect("cs export"); + while !cs.is_exported() {} + cs.set_direction(Direction::Out).expect("CS Direction"); + cs.set_value(1).expect("CS Value set to 1"); + + let busy = Pin::new(24); + busy.export().expect("busy export"); + while !busy.is_exported() {} + busy.set_direction(Direction::In).expect("busy Direction"); + + let dc = Pin::new(25); + dc.export().expect("dc export"); + while !dc.is_exported() {} + dc.set_direction(Direction::Out).expect("dc Direction"); + dc.set_value(1).expect("dc Value set to 1"); + + let rst = Pin::new(17); + rst.export().expect("rst export"); + while !rst.is_exported() {} + rst.set_direction(Direction::Out).expect("rst Direction"); + rst.set_value(1).expect("rst Value set to 1"); + + let mut delay = Delay {}; + + let mut epd7in5 = + EPD7in5::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); + + println!("Test all the rotations"); + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate0); + display.draw( + Font6x8::render_str("Rotate 0!") + .stroke(Some(Color::Black)) + .fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + + display.set_rotation(DisplayRotation::Rotate90); + display.draw( + Font6x8::render_str("Rotate 90!") + .stroke(Some(Color::Black)) + .fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + + display.set_rotation(DisplayRotation::Rotate180); + display.draw( + Font6x8::render_str("Rotate 180!") + .stroke(Some(Color::Black)) + .fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + + display.set_rotation(DisplayRotation::Rotate270); + display.draw( + Font6x8::render_str("Rotate 270!") + .stroke(Some(Color::Black)) + .fill(Some(Color::White)) + .translate(Coord::new(5, 50)) + .into_iter(), + ); + + epd7in5.update_frame(&mut spi, &display.buffer()).unwrap(); + epd7in5 + .display_frame(&mut spi) + .expect("display frame new graphics"); + delay.delay_ms(5000u16); + + println!("Now test new graphics with default rotation and some special stuff:"); + display.clear_buffer(Color::White); + + // draw a analog clock + display.draw( + Circle::new(Coord::new(64, 64), 64) + .stroke(Some(Color::Black)) + .into_iter(), + ); + display.draw( + Line::new(Coord::new(64, 64), Coord::new(0, 64)) + .stroke(Some(Color::Black)) + .into_iter(), + ); + display.draw( + Line::new(Coord::new(64, 64), Coord::new(80, 80)) + .stroke(Some(Color::Black)) + .into_iter(), + ); + + // draw white on black background + display.draw( + Font6x8::render_str("It's working-WoB!") + // Using Style here + .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(), + ); + + // use bigger/different font + display.draw( + Font12x16::render_str("It's working-BoW!") + // Using Style here + .style(Style { + fill_color: Some(Color::White), + stroke_color: Some(Color::Black), + stroke_width: 0u8, // Has no effect on fonts + }) + .translate(Coord::new(50, 200)) + .into_iter(), + ); + + // a moving `Hello World!` + let limit = 10; + for i in 0..limit { + println!("Moving Hello World. Loop {} from {}", (i + 1), limit); + + display.draw( + Font6x8::render_str(" Hello World! ") + .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 * 12, 50)) + .into_iter(), + ); + + epd7in5.update_frame(&mut spi, &display.buffer()).unwrap(); + epd7in5 + .display_frame(&mut spi) + .expect("display frame new graphics"); + + delay.delay_ms(1_000u16); + } + + println!("Finished tests - going to sleep"); + epd7in5.sleep(&mut spi) +} diff --git a/src/epd7in5/command.rs b/src/epd7in5/command.rs new file mode 100644 index 0000000..064a5ee --- /dev/null +++ b/src/epd7in5/command.rs @@ -0,0 +1,154 @@ +//! SPI Commands for the Waveshare 7.5" E-Ink Display + +use crate::traits; + +/// EPD7in5 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. + 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, + + /// After this command is issued, image process engine will find thin lines/pixels + /// from frame SRAM and update the frame SRAM for applying new gray level waveform. + /// + /// After "Image Process Command", BUSY_N signal will become "0" until image process + /// is finished. + IMAGE_PROCESS = 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 EPD7in5, but it's not documented + /// anywhere in the datasheet `¯\_(ツ)_/¯` + FLASH_MODE = 0xE5, +} + +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/epd7in5/graphics.rs b/src/epd7in5/graphics.rs new file mode 100644 index 0000000..c7c20dd --- /dev/null +++ b/src/epd7in5/graphics.rs @@ -0,0 +1,151 @@ +use crate::epd7in5::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; +use crate::graphics::{Display, DisplayRotation}; +use crate::prelude::*; +use embedded_graphics::prelude::*; + +/// Full size buffer for use with the 7in5 EPD +/// +/// Can also be manually constructed: +/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]` +pub struct Display7in5 { + buffer: [u8; WIDTH as usize * HEIGHT as usize / 8], + rotation: DisplayRotation, +} + +impl Default for Display7in5 { + fn default() -> Self { + Display7in5 { + buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); + WIDTH as usize * HEIGHT as usize / 8], + rotation: DisplayRotation::default(), + } + } +} + +impl Drawing for Display7in5 { + fn draw(&mut self, item_pixels: T) + where + T: IntoIterator>, + { + self.draw_helper(WIDTH, HEIGHT, item_pixels); + } +} + +impl Display for Display7in5 { + 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::color::Color; + use crate::epd7in5; + use crate::graphics::{Display, DisplayRotation}; + use embedded_graphics::coord::Coord; + use embedded_graphics::primitives::Line; + + // test buffer length + #[test] + fn graphics_size() { + let display = Display7in5::default(); + assert_eq!(display.buffer().len(), 30720); + } + + // test default background color on all bytes + #[test] + fn graphics_default() { + let display = Display7in5::default(); + for &byte in display.buffer() { + assert_eq!(byte, epd7in5::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_0() { + let mut display = Display7in5::default(); + display.draw( + Line::new(Coord::new(0, 0), Coord::new(7, 0)) + .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, epd7in5::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_90() { + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate90); + display.draw( + Line::new(Coord::new(0, 632), Coord::new(0, 639)) + .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, epd7in5::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_180() { + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate180); + display.draw( + Line::new(Coord::new(632, 383), Coord::new(639, 383)) + .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, epd7in5::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_270() { + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate270); + display.draw( + Line::new(Coord::new(383, 0), Coord::new(383, 7)) + .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, epd7in5::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } +} diff --git a/src/epd7in5/mod.rs b/src/epd7in5/mod.rs new file mode 100644 index 0000000..33fe716 --- /dev/null +++ b/src/epd7in5/mod.rs @@ -0,0 +1,290 @@ +//! A simple Driver for the Waveshare 7.5" E-Ink Display via SPI +//! +//! # References +//! +//! - [Datasheet](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT) +//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/702def06bcb75983c98b0f9d25d43c552c248eb0/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_7in5.c) +//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/702def06bcb75983c98b0f9d25d43c552c248eb0/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5.py) + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v1::{InputPin, OutputPin}, +}; + +use crate::color::Color; +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::Display7in5; + +pub const WIDTH: u32 = 640; +pub const HEIGHT: u32 = 384; +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; +const IS_BUSY_LOW: bool = true; + +/// EPD7in5 driver +/// +pub struct EPD7in5 { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: Color, +} + +impl InternalWiAdditions + for EPD7in5 +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); + + // Set the power settings + self.cmd_with_data(spi, Command::POWER_SETTING, &[0x37, 0x00])?; + + // Set the panel settings: + // - 600 x 448 + // - Using LUT from external flash + self.cmd_with_data(spi, Command::PANEL_SETTING, &[0xCF, 0x08])?; + + // Start the booster + self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0xC7, 0xCC, 0x28])?; + + // Power on + self.command(spi, Command::POWER_ON)?; + delay.delay_ms(5); + self.wait_until_idle(); + + // Set the clock frequency to 50Hz (default) + self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x3C])?; + + // Select internal temperature sensor (default) + self.cmd_with_data(spi, Command::TEMPERATURE_CALIBRATION, &[0x00])?; + + // Set Vcom and data interval to 10 (default), border output to white + self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x77])?; + + // Set S2G and G2S non-overlap periods to 12 (default) + self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?; + + // Set the real resolution + self.send_resolution(spi)?; + + // Set VCOM_DC to -1.5V + self.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x1E])?; + + // This is in all the Waveshare controllers for EPD7in5 + self.cmd_with_data(spi, Command::FLASH_MODE, &[0x03])?; + + self.wait_until_idle(); + Ok(()) + } +} + +impl WaveshareDisplay + for EPD7in5 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, +{ + /// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC + /// + /// This already initialises the device. That means [init()] isn't needed + /// directly afterwards. + /// + /// [init()]: InternalWiAdditions::init + /// + /// # Example + /// + /// ```ignore + /// //buffer = some image data; + /// + /// let mut epd7in5 = EPD7in5::new(spi, cs, busy, dc, rst, delay); + /// + /// epd7in5.display_and_transfer_frame(buffer, None); + /// + /// epd7in5.sleep(); + /// ``` + 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 = EPD7in5 { 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.command(spi, Command::POWER_OFF)?; + self.wait_until_idle(); + self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?; + + self.wait_until_idle(); + Ok(()) + } + + fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + self.command(spi, Command::DATA_START_TRANSMISSION_1)?; + for byte in buffer { + let mut temp = *byte; + for _ in 0..4 { + let mut data = if temp & 0x80 == 0 { 0x00 } else { 0x03 }; + data <<= 4; + temp <<= 1; + data |= if temp & 0x80 == 0 { 0x00 } else { 0x03 }; + temp <<= 1; + self.send_data(spi, &[data])?; + } + } + + self.wait_until_idle(); + 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::DISPLAY_REFRESH)?; + + self.wait_until_idle(); + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + self.send_resolution(spi)?; + + // The Waveshare controllers all implement clear using 0x33 + self.command(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface + .data_x_times(spi, 0x33, WIDTH / 8 * HEIGHT * 4)?; + + self.wait_until_idle(); + Ok(()) + } + + 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 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 EPD7in5 +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_until_idle(&mut self) { + self.interface.wait_until_idle(IS_BUSY_LOW) + } + + 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, 640); + assert_eq!(HEIGHT, 384); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } +} diff --git a/src/lib.rs b/src/lib.rs index dffacde..86b0e7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,9 @@ pub mod color; /// Interface for the physical connection between display and the controlling device mod interface; +#[cfg(feature = "epd7in5")] +pub mod epd7in5; + #[cfg(feature = "epd4in2")] pub mod epd4in2;