diff --git a/examples/epd2in13bc_v3.rs b/examples/epd2in13bc_v3.rs new file mode 100644 index 0000000..9005600 --- /dev/null +++ b/examples/epd2in13bc_v3.rs @@ -0,0 +1,145 @@ +#![deny(warnings)] + +use embedded_graphics::{ + mono_font::MonoTextStyleBuilder, + prelude::*, + primitives::{Circle, Line, PrimitiveStyle}, + text::{Baseline, Text, TextStyleBuilder}, +}; +use embedded_hal::prelude::*; +use epd_waveshare::{ + color::TriColor, + epd2in13bc_v3::{Display2in13bc, Epd2in13bc}, + graphics::DisplayRotation, + prelude::*, +}; +use linux_embedded_hal::{ + spidev::{self, SpidevOptions}, + sysfs_gpio::Direction, + Delay, Pin, Spidev, +}; + +// The pins in this example are for the Universal e-Paper Raw Panel Driver HAT +// 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() -> 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::SpiModeFlags::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(26); //BCM7 CE0 + 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); // GPIO 24, board J-18 + busy.export().expect("busy export"); + while !busy.is_exported() {} + busy.set_direction(Direction::In).expect("busy Direction"); + //busy.set_value(1).expect("busy Value set to 1"); + + let dc = Pin::new(25); // GPIO 25, board J-22 + 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); // GPIO 17, board J-11 + 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 epd2in13 = + Epd2in13bc::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); + + println!("Test all the rotations"); + let mut display = Display2in13bc::default(); + + display.set_rotation(DisplayRotation::Rotate0); + draw_text(&mut display, "Rotate 0!", 5, 5); + + display.set_rotation(DisplayRotation::Rotate90); + draw_text(&mut display, "Rotate 90!", 5, 5); + + display.set_rotation(DisplayRotation::Rotate180); + draw_text(&mut display, "Rotate 180!", 5, 5); + + display.set_rotation(DisplayRotation::Rotate270); + draw_text(&mut display, "Rotate 270!", 5, 5); + + epd2in13.update_frame(&mut spi, display.buffer(), &mut delay)?; + epd2in13 + .display_frame(&mut spi, &mut delay) + .expect("display frame new graphics"); + delay.delay_ms(5000u16); + + display.clear_buffer(TriColor::White); + println!("Now test new graphics with default rotation and some special stuff:"); + + // draw a analog clock + let _ = Circle::with_center(Point::new(52, 52), 80) + .into_styled(PrimitiveStyle::with_fill(TriColor::Chromatic)) + .draw(&mut display); + let _ = Circle::with_center(Point::new(52, 52), 80) + .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 3)) + .draw(&mut display); + let _ = Line::new(Point::new(52, 52), Point::new(18, 28)) + .into_styled(PrimitiveStyle::with_stroke(TriColor::White, 5)) + .draw(&mut display); + let _ = Line::new(Point::new(52, 52), Point::new(68, 28)) + .into_styled(PrimitiveStyle::with_stroke(TriColor::White, 1)) + .draw(&mut display); + + // draw white on black background + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(TriColor::White) + .background_color(TriColor::Chromatic) + .build(); + let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); + + let _ = Text::with_text_style("Hello World!", Point::new(112, 10), style, text_style) + .draw(&mut display); + + // use bigger/different font + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_10X20) + .text_color(TriColor::White) + .background_color(TriColor::Chromatic) + .build(); + + let _ = Text::with_text_style("Hello\nWorld!", Point::new(112, 40), style, text_style) + .draw(&mut display); + + epd2in13.update_color_frame(&mut spi, display.bw_buffer(), display.chromatic_buffer()).unwrap(); + epd2in13.display_frame(&mut spi, &mut delay).unwrap(); + + println!("Finished tests - going to sleep"); + epd2in13.clear_frame(&mut spi, &mut delay).unwrap(); + epd2in13.sleep(&mut spi, &mut delay) +} + +fn draw_text(display: &mut Display2in13bc, text: &str, x: i32, y: i32) { + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(TriColor::White) + .background_color(TriColor::Black) + .build(); + + let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); + + let _ = Text::with_text_style(text, Point::new(x, y), style, text_style).draw(display); +} diff --git a/src/epd2in13bc_v3/command.rs b/src/epd2in13bc_v3/command.rs new file mode 100644 index 0000000..4574a86 --- /dev/null +++ b/src/epd2in13bc_v3/command.rs @@ -0,0 +1,141 @@ + +use crate::traits; + +/// Epd7in5b_v3 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, BWR pixels, gate scan direction, source shift + /// direction, booster 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, + + /// Starting data transmission + BoosterSoftStart = 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 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 + DataStartTransmissionBlackWhite = 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. + 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. + DisplayRefresh = 0x12, + + /// RED or NEW_DATA + DataStartTransmissionChromatic = 0x13, + + /// Dual SPI - what for? + DualSpi = 0x15, + + /// This command builds the VCOM Look-Up Table (LUTC). + LutForVcom = 0x20, + /// This command builds the White to White Look-Up Table (LUTWW). + LutWhiteToWhite = 0x21, + /// This command builds the Black to White Look-Up Table (LUTKW). + LutBlackToWhite = 0x22, + /// This command builds the White to Black Look-Up Table (LUTWK). + LutWhiteToBlack = 0x23, + /// This command builds the Black to Black Look-Up Table (LUTKK). + LutBlackToBlack = 0x24, + /// This command builds the Border? Look-Up Table (LUTR0). + LutBorder = 0x25, + + /// This command builds the XON Look-Up Table (LUTXON). + LutXon = 0x2A, + + /// The command controls the PLL clock frequency. + PllControl = 0x30, + + /// This command reads the temperature sensed by the temperature sensor. + TemperatureSensor = 0x40, + + /// 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 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, + //TODO /// This command defines MCU host direct access external memory mode. + GateSourceStart = 0x65, + + /// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000. + Revision = 0x70, + + /// This command sets `VCOM_DC` value. + VcmDcSetting = 0x82, + + // TODO + ProgramMode = 0xA0, + // + // TODO + ActiveProgram = 0xA1, + + // TODO + ReadOtpData = 0xA2, + // /// This is in all the Waveshare controllers for Epd7in5, but it's not documented + // /// anywhere in the datasheet `¯\_(ツ)_/¯` + // FlashMode = 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::PanelSetting.address(), 0x00); + assert_eq!(Command::DisplayRefresh.address(), 0x12); + } +} diff --git a/src/epd2in13bc_v3/graphics.rs b/src/epd2in13bc_v3/graphics.rs new file mode 100644 index 0000000..6265c32 --- /dev/null +++ b/src/epd2in13bc_v3/graphics.rs @@ -0,0 +1,74 @@ +use crate::epd2in13bc_v3::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH, NUM_DISPLAY_BITS}; +use crate::graphics::{DisplayColorRendering, DisplayRotation, TriDisplay}; +use crate::color::TriColor; +use embedded_graphics_core::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 Display2in13bc { + buffer: [u8; 2 * NUM_DISPLAY_BITS as usize], + rotation: DisplayRotation, +} + +impl Default for Display2in13bc { + fn default() -> Self { + Display2in13bc { + buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); + 2 * NUM_DISPLAY_BITS as usize], + rotation: DisplayRotation::default(), + } + } +} + +impl DrawTarget for Display2in13bc { + type Color = TriColor; + type Error = core::convert::Infallible; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper_tri(WIDTH, HEIGHT, pixel, DisplayColorRendering::Positive)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display2in13bc { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +impl TriDisplay for Display2in13bc { + 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 + } + + fn chromatic_offset(&self) -> usize { + NUM_DISPLAY_BITS as usize + } + + fn bw_buffer(&self) -> &[u8] { + &self.buffer[0..self.chromatic_offset()] + } + + fn chromatic_buffer(&self) -> &[u8] { + &self.buffer[self.chromatic_offset()..] + } +} diff --git a/src/epd2in13bc_v3/mod.rs b/src/epd2in13bc_v3/mod.rs new file mode 100644 index 0000000..d23e36e --- /dev/null +++ b/src/epd2in13bc_v3/mod.rs @@ -0,0 +1,274 @@ +//! A simple Driver for the Waveshare 2.13" E-Ink Display (V3) via SPI +//! +//! # References +//! +//! - [Datasheet](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_(B)) +//! +//! Important note for V3: +//! Revision V3 has been released on 2019.11, the resolution is upgraded to 212×104 +//! The hardware and interface of V2 are compatible with V1, however, the related software should be updated. + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::{InputPin, OutputPin}, +}; + +use crate::color::TriColor; +use crate::interface::DisplayInterface; +use crate::prelude::{WaveshareThreeColorDisplay}; +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::Display2in13bc; + +/// Width of epd2in13bc_v3 in pixels +pub const WIDTH: u32 = 104; +/// Height of epd2in13bc_v3 in pixels +pub const HEIGHT: u32 = 212; +/// Default background color (white) of epd2in13bc_v3 display +pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White; +const IS_BUSY_LOW: bool = true; + +/// Number of bits for b/w buffer and same for chromatic buffer +const NUM_DISPLAY_BITS: u32 = WIDTH * HEIGHT / 8; + + +/// Epd2in13bc (V3) driver +/// +pub struct Epd2in13bc { + // Connection Interface + interface: DisplayInterface, + color: TriColor, +} + +impl InternalWiAdditions + for Epd2in13bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // Reset the device + self.interface.reset(delay, 10); + + self.command(spi, Command::PowerOn)?; + self.wait_until_idle(spi, delay)?; + + self.command_with_data(spi, Command::PanelSetting, &[0x0F, 0x89])?; + self.command_with_data(spi, Command::TconResolution, &[0x68, 0x00, 0xD4])?; + + self.command_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x77])?; + Ok(()) + } +} + +impl WaveshareThreeColorDisplay + for Epd2in13bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + fn update_color_frame( + &mut self, + spi: &mut SPI, + black: &[u8], + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.update_achromatic_frame(spi, black)?; + self.update_chromatic_frame(spi, chromatic) + } + + fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error> { + self.command_with_data(spi, Command::DataStartTransmissionBlackWhite, black) + } + + fn update_chromatic_frame( + &mut self, + spi: &mut SPI, + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.command_with_data(spi, Command::DataStartTransmissionChromatic, chromatic) + } +} + +impl WaveshareDisplay + for Epd2in13bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = TriColor; + + 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 = Epd2in13bc { 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, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.command_with_data(spi, Command::VcomAndDataIntervalSetting, &[0xF7])?; + + self.command(spi, Command::PowerOff)?; + self.wait_until_idle(spi, delay)?; + + self.command_with_data(spi, Command::DeepSleep, &[0xA5])?; + Ok(()) + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.command_with_data(spi, Command::DataStartTransmissionBlackWhite, buffer)?; + + let color = self.color.get_byte_value(); + self.command(spi, Command::DataStartTransmissionChromatic)?; + self.interface.data_x_times(spi, color, WIDTH * HEIGHT / 8)?; + + self.wait_until_idle(spi, delay)?; + Ok(()) + } + + #[allow(unused)] + 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, delay: &mut DELAY) -> Result<(), SPI::Error> { + 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.command(spi, Command::DataStartTransmissionBlackWhite)?; + self.interface.data_x_times(spi, 0xFF, WIDTH * HEIGHT / 8)?; + + self.command(spi, Command::DataStartTransmissionChromatic)?; + self.interface.data_x_times(spi, 0xFF, WIDTH * HEIGHT / 8)?; + + self.command(spi, Command::DisplayRefresh)?; + Ok(()) + } + + fn set_background_color(&mut self, color: TriColor) { + self.color = color; + } + + fn background_color(&self) -> &TriColor { + &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 Epd2in13bc +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + 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 command_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, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + while self.interface.is_busy(IS_BUSY_LOW) { + self.interface.cmd(spi, Command::Revision)?; + delay.delay_ms(20); + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 13281f7..2bac2b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ pub mod epd1in54b; pub mod epd1in54c; pub mod epd2in13_v2; pub mod epd2in13bc; +pub mod epd2in13bc_v3; pub mod epd2in7b; pub mod epd2in9; pub mod epd2in9_v2;