caemor / epd-waveshare

Drivers for various EPDs from Waveshare and others
ISC License
227 stars 137 forks source link

Add support for EPD 2.13" (b) V4 #208

Closed monorkin closed 4 months ago

monorkin commented 4 months ago

This PR adds support for 2.13" (b) V4 e-ink displays.

DEMO:

https://github.com/user-attachments/assets/84bb8b7b-d999-4b4d-b3e2-1623fc8fc593

Demo source code ```rust use embedded_graphics::{ mono_font::MonoTextStyleBuilder, prelude::*, primitives::{Circle, Line, PrimitiveStyle}, text::{Baseline, Text, TextStyleBuilder}, }; use embedded_hal::delay::DelayNs; use epd_waveshare::{ color::*, epd2in13b_v4::{Display2in13bV4, Epd2in13bV4}, graphics::DisplayRotation, prelude::*, }; use linux_embedded_hal::Delay; use rppal::gpio::Gpio; use rppal::spi::{Bus, Mode, SimpleHalSpiDevice, SlaveSelect, Spi}; // 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 // // This example first setups SPI communication using the pin layout found // at https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_(B). This example uses the layout for the // Raspberry Pi Zero (RPI Zero). The Chip Select (CS) was taken from the ep2in9 example since CE0 (GPIO8) did // not seem to work on RPI Zero with 2.13" HAT // // The first frame is filled with four texts at different rotations (black on white) // The second frame uses a buffer for black/white and a seperate buffer for chromatic/white (i.e. red or yellow) // This example draws a sample clock in black on white and two texts using white on red. // // after finishing, put the display to sleep fn main() { let mut pwr = Gpio::new().unwrap().get(18).unwrap().into_output(); pwr.set_high(); // let busy = SysfsPin::new(24); // GPIO 24, board J-18 // busy.export().expect("busy export"); // while !busy.is_exported() {} // busy.set_direction(Direction::In).expect("busy Direction"); let busy = Gpio::new().unwrap().get(24).unwrap().into_input(); // let dc = SysfsPin::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 dc = Gpio::new().unwrap().get(25).unwrap().into_output(); // let rst = SysfsPin::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 rst = Gpio::new().unwrap().get(17).unwrap().into_output(); rst.set_low(); // Configure Digital I/O Pin to be used as Chip Select for SPI // let cs = SysfsPin::new(26); // CE0, board J-24, GPIO 8 -> doesn work. use this from 2in19 example which works // 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 mut cs = Gpio::new().unwrap().get(8).unwrap().into_output(); cs.set_low(); // let pwr = Gpio::new().unwrap().get(18).unwrap().into_output(); // cs.set_high(); // Configure SPI // Settings are taken from let bus = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 10_000_000, Mode::Mode0).unwrap(); bus.set_bits_per_word(8).unwrap(); bus.set_ss_polarity(rppal::spi::Polarity::ActiveLow) .unwrap(); let mut spi = SimpleHalSpiDevice::new(bus); // let mut spi = SpidevDevice::open("/dev/spidev0.0").expect("spidev directory"); // let options = SpidevOptions::new() // .bits_per_word(8) // .max_speed_hz(10_000_000) // .mode(spidev::SpiModeFlags::SPI_MODE_0) // .build(); // spi.configure(&options).expect("spi configuration"); let mut delay = Delay {}; let mut epd2in13 = Epd2in13bV4::new(&mut spi, busy, dc, rst, &mut delay, None).expect("eink initalize error"); println!("Test all the rotations"); let mut display = Display2in13bV4::default(); display.clear(TriColor::White).ok(); display.set_rotation(DisplayRotation::Rotate0); draw_text(&mut display, "Rotation 0!", 0, 0); display.set_rotation(DisplayRotation::Rotate90); draw_text(&mut display, "Rotation 90!", 0, 0); display.set_rotation(DisplayRotation::Rotate180); draw_text(&mut display, "Rotation 180!", 0, 0); display.set_rotation(DisplayRotation::Rotate270); draw_text(&mut display, "Rotation 270!", 0, 0); epd2in13 .update_and_display_frame(&mut spi, display.bw_buffer(), &mut delay) .expect("display frame new graphics"); println!("Waiting 5s"); delay.delay_ms(5000); println!("Drawing an analog clock and some text"); display.set_rotation(DisplayRotation::Rotate0); display.clear(TriColor::White).ok(); // draw a analog clock let _ = Circle::with_center(Point::new(60, 60), 120) .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 2)) .draw(&mut display); let _ = Line::new(Point::new(60, 60), Point::new(76, 28)) .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 4)) .draw(&mut display); let _ = Line::new(Point::new(60, 60), Point::new(31, 19)) .into_styled(PrimitiveStyle::with_stroke(TriColor::Chromatic, 2)) .draw(&mut display); epd2in13 .update_color_frame( &mut spi, &mut delay, display.bw_buffer(), display.chromatic_buffer(), ) .unwrap(); epd2in13 .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); println!("Waiting 5s"); delay.delay_ms(5000); println!("Testing diferent fonts and colors"); display.clear(TriColor::White).ok(); // draw text white on Red background by using the chromatic buffer 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("It's working", Point::new(15, 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::Chromatic) .background_color(TriColor::Black) .build(); let _ = Text::with_text_style("It's working", Point::new(0, 40), style, text_style) .draw(&mut display); // we used three colors, so we need to update both bw-buffer and chromatic-buffer epd2in13 .update_color_frame( &mut spi, &mut delay, display.bw_buffer(), display.chromatic_buffer(), ) .unwrap(); epd2in13 .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); println!("Waiting 5s"); delay.delay_ms(5000); // clear both bw buffer and chromatic buffer println!("Clearing screen"); display.clear(TriColor::White).ok(); epd2in13 .update_color_frame( &mut spi, &mut delay, display.bw_buffer(), display.chromatic_buffer(), ) .unwrap(); epd2in13.display_frame(&mut spi, &mut delay).unwrap(); println!("Finished tests - going to sleep"); epd2in13.sleep(&mut spi, &mut delay).unwrap(); } fn draw_text(display: &mut Display2in13bV4, 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); } ```

I didn't look into how other screens are implemented and based my code mostly off the bc version, so there might be superfluous code here. I'd appreciate some pointers on what can be removed or improved.

This screen renders color differently from the bc version. Instead of red pixels being represented with a 1, they are represented with a zero. I couldn't find a way to define this on the display level, so I ended up just inverting the bits before I send them off to the screen.

monorkin commented 4 months ago

Ah, NVM. Somehow I missed #145 it looks to be a more thorough implementation.