Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 1 commit into from

Conversation

monorkin
Copy link

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

DEMO:

epd_2in13b_v4_demo_compressed.mp4
Demo source code
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.

@auto-assign auto-assign bot requested a review from caemor July 25, 2024 10:21
@monorkin
Copy link
Author

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

@monorkin monorkin closed this Jul 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants