diff --git a/.github/auto_assign-issues.yml b/.github/auto_assign-issues.yml new file mode 100644 index 0000000..6a6a4f9 --- /dev/null +++ b/.github/auto_assign-issues.yml @@ -0,0 +1,8 @@ +# If enabled, auto-assigns users when a new issue is created +# Defaults to true, allows you to install the app globally, and disable on a per-repo basis +addAssignees: true + +# The list of users to assign to new issues. +# If empty or not provided, the repository owner is assigned +assignees: + - caemor \ No newline at end of file diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000..5c151da --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,17 @@ +# Set to true to add reviewers to pull requests +addReviewers: true + +# Set to true to add assignees to pull requests +addAssignees: true + +# A list of reviewers to be added to pull requests (GitHub user name) +reviewers: + - caemor + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +#skipKeywords: +# - wip + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +numberOfReviewers: 0 \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 18fc9a3..62031c1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,13 +1,7 @@ name: Rust -on: - push: - branches: - - main - pull_request: - branches: - - main +on: [push, pull_request] jobs: build: diff --git a/.gitignore b/.gitignore index db7de4a..1f41f71 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ Cargo.lock # vscode .vscode/* + +# intellij/clion +.idea/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5965444..0000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: rust -rust: - - stable -sudo: required -env: TARGET=x86_64-unknown-linux-gnu - -before_install: - - set -e - - rustup self update - -# install: -# - cargo install cargo-update || echo "cargo-update already installed" -# - cargo install-update -a # update outdated cached binaries -# - cargo install cross || echo "cross already installed" - - #TODO: remove -A clippy::new_ret_no_self when new version of clippy gets released! -script: - - cargo check --all-features - - cargo test --all-features - -cache: cargo -before_cache: - # Travis can't cache files that are not readable by "others" - - chmod -R a+r $HOME/.cargo - - | - if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then - cargo install cargo-tarpaulin - fi - -after_success: | - if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then - # Uncomment the following line for coveralls.io - # cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID - - # Uncomment the following two lines create and upload a report for codecov.io - cargo tarpaulin --all-features --out Xml - bash <(curl -s https://codecov.io/bash) -t "$CODECOV_TOKEN" - fi - - -branches: - only: - # release tags - - /^v\d+\.\d+\.\d+.*$/ - - main - -notifications: - email: - on_success: never diff --git a/CHANGELOG.md b/CHANGELOG.md index e6fba5f..e9745f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,59 +7,98 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +### Changed + +### Fixed + +## [v0.5.0] - 2021-11-28 + +### Added + +- Added QuickRefresh Trait and implemented it for EPD4in2 in #62 (thanks to @David-OConnor) +- Added Epd 2in7 (B) support in #60 (thanks to @pjsier) +- Added Epd 7in5 HD support (thanks to @whiite) +- Added Epd 2in9 V2 support in #73 & #74 (thanks to @andber1) +- Added Epd 2in13 (BC) support in #75 (thanks to @Irbalt) +- Added Color conversion methods in #87 & #88 (thanks to @crzysdrs) +- Provide full QuickRefresh interface for 4.2 inch display in #81 (thanks to @sirhcel) + + +### Changed + +- Updated embedded-graphics to 0.7 and switch to e-g-core #78 (@Irbalt) & #85 (@jamwaffles) +- Use specific ParseColorError instead of () +- Epd4in2: Don't set the resolution (and some more) over and over again (#48) +- Removed `#[allow(non_camel_case_types)]` to fix various issues around it +- Added Delay to QuickRefresh Trait due to #74 (thanks to @andber1) +- Write data over SPI 1 byte at a time due to #82 (thanks to @belak) +- Enable drawing in three colors for epd2in13bc in #76 (thanks to @Irbalt) + + ## [v0.4.0] - 2020-04-06 ### Added - - New supported epds: epd7in5 (thanks to @str4d), epd7in5 v2 (thanks to @asaaki), epd1in54b (thanks to @jkristell) - - Added update_and_display_frame to WaveshareDisplay trait (fixes #38) - - also improve position of busy_wait (#30) once more - - More Documenation + +- New supported epds: epd7in5 (thanks to @str4d), epd7in5 v2 (thanks to @asaaki), epd1in54b (thanks to @jkristell) +- Added update_and_display_frame to WaveshareDisplay trait (fixes #38) +- also improve position of busy_wait (#30) once more +- More Documentation ### Changed - - Update embedded-graphics to 0.6 (changes Display Trait) (and to 0.5 before thanks to @dbr) - - Remove useless Featuregates (Doesn't change size) - - Update and integrate a few important examples and remove the others - - Use Embedded_hal:digital::v2 +- Update embedded-graphics to 0.6 (changes Display Trait) (and to 0.5 before thanks to @dbr) +- Remove useless feature gates (Doesn't change size) +- Update and integrate a few important examples and remove the others +- Use Embedded_hal:digital::v2 ### Fixed - - Doc Tests +- Doc Tests ## [v0.3.2] - 2019-06-17 ### Fixed - - Added some more missing wait_until_idle calls + +- Added some more missing wait_until_idle calls ## [v0.3.1] - 2019-04-06 ### Added - - Example for epd4in2 and BluePill-Board + +- Example for epd4in2 and BluePill-Board ### Changed - - Improved CI + +- Improved CI ### Fixed - - Timing issues in display_frame function: epd1in54 and epd2in9 were both missing a necessary wait_until_idle call at the end of their display_frame function which sometimes caused invalid/ignored commands/inputs afterwards - - Some CI Targets were not tested correctly before + +- Timing issues in display_frame function: epd1in54 and epd2in9 were both missing a necessary wait_until_idle call at + the end of their display_frame function which sometimes caused invalid/ignored commands/inputs afterwards +- Some CI Targets were not tested correctly before ## [v0.3.0] - 2019-04-04 ### Added + - added eink to keywords - added reference to previous crate-name - improved readme/docs e.g. added reference to a few great arduino display libs for these epds - Added is_busy to Waveshare_Interface - Added IS_BUSY_LOW const for all supported epds - Added is_busy to DisplayInterface -- Added VarDisplay (a variable buffersize display/graphic driver) +- Added VarDisplay (a variable buffer-size display/graphic driver) - Updated and added more examples - add a feature gated alternative full lut for type_a displays ### Removed + - Removed all Buffers (Buffer1in54,...) and instead made specialised Displays (Display1in54,...) with included Buffers ### Changed + - Switch to 2018 edition - "cargo fix --edition" for the library - Use cargo fix edition-idioms and remove the internal renaming from embedded_hal to hal @@ -72,19 +111,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Initial release with Changelog ### Added + - Uses embedded-graphics now - Tested and fixed 1.54 inch, 2.9 inch and 4.2 inch display ### Removed + - Old included Graphics Library ### Changed + - Lots of internal changes - Renamed to `epd-waveshare` - [Unreleased]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.4.0...HEAD + [v0.4.0]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.3.2...v0.4.0 + [v0.3.2]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.3.1...v0.3.2 + [v0.3.1]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.3.0...v0.3.1 + [v0.3.0]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.2.0...v0.3.0 diff --git a/Cargo.toml b/Cargo.toml index b81cc72..f1dbb0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,25 +9,26 @@ license = "ISC" name = "epd-waveshare" readme = "README.md" repository = "https://github.com/Caemor/epd-waveshare.git" -version = "0.4.0" +version = "0.5.0" edition = "2018" [badges] # travis-ci = { repository = "caemor/epd-waveshare" } [dependencies] -embedded-graphics = { version = "0.6.1", optional = true} embedded-hal = "1.0.0-alpha.2" +embedded-graphics-core = { version = "0.3.2", optional = true} bit_field = "0.10.1" [dev-dependencies] +embedded-graphics = "0.7.1" linux-embedded-hal = "0.3" -embedded-hal-mock = "0.7" +embedded-hal-mock = "0.8" [features] default = ["graphics"] -graphics = ["embedded-graphics"] +graphics = ["embedded-graphics-core"] # Offers an alternative fast full lut for type_a displays, but the refreshed screen isnt as clean looking type_a_alternative_faster_lut = [] diff --git a/README.md b/README.md index 0ce9a23..ce37369 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ [![Build Status](https://travis-ci.com/caemor/epd-waveshare.svg?branch=master)](https://travis-ci.com/caemor/epd-waveshare) -This library contains a driver for E-Paper Modules from Waveshare (which are basically the same as the Dalian Good Display ones). +This library contains a driver for E-Paper Modules from Waveshare (which are basically the same as the Dalian Good +Display ones). It uses the [embedded graphics](https://crates.io/crates/embedded-graphics) library for the optional graphics support. A 2018-edition compatible version (Rust 1.31+) is needed. -Other similiar libraries with support for much more displays are [u8g2](https://github.com/olikraus/u8g2) and [GxEPD](https://github.com/ZinggJM/GxEPD) for arduino. +Other similar libraries with support for much more displays are [u8g2](https://github.com/olikraus/u8g2) +and [GxEPD](https://github.com/ZinggJM/GxEPD) for arduino. ## Examples @@ -14,43 +16,49 @@ There are multiple examples in the examples folder. Use `cargo run --example exa ```Rust // Setup the epd -let mut epd = EPD4in2::new(&mut spi, cs, busy, dc, rst, &mut delay)?; +let mut epd = Epd4in2::new( & mut spi, cs, busy, dc, rst, & mut delay) ?; // Setup the graphics -let mut display = Display4in2::default(); +let mut display = Display4in2::default (); // Draw some text display.draw( - let _ = Text::new("Hello Rust!", Point::new(x, y)) - .into_styled(text_style!( +let _ = Text::new("Hello Rust!", Point::new(x, y)) +.into_styled(text_style!( font = Font12x16, text_color = Black, background_color = White )) - .draw(display); +.draw(display); ); // Transfer the frame data to the epd and display it -epd.update_and_display_frame(&mut spi, &display.buffer())?; +epd.update_and_display_frame( & mut spi, & display.buffer()) ?; ``` ## (Supported) Devices | Device (with Link) | Colors | Flexible Display | Partial Refresh | Supported | Tested | | :---: | --- | :---: | :---: | :---: | :---: | +| [7.5 Inch B/W HD (A)](https://www.waveshare.com/product/displays/e-paper/epaper-1/7.5inch-hd-e-paper-hat.htm) | Black, White | ✕ | ✕ | ✔ | ✔ | | [7.5 Inch B/W V2 (A)](https://www.waveshare.com/product/7.5inch-e-paper-hat.htm) [[1](#1-75-inch-bw-v2-a)] | Black, White | ✕ | ✕ | ✔ | ✔ | | [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 [[2](#2-42-inch-e-ink-blackwhite---partial-refresh)] | ✔ | ✔ | | [1.54 Inch B/W (A)](https://www.waveshare.com/1.54inch-e-Paper-Module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | | [2.13 Inch B/W (A) V2](https://www.waveshare.com/product/2.13inch-e-paper-hat.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | +| [2.13 Inch B/W/R (B/C) V2](https://www.waveshare.com/product/raspberry-pi/displays/e-paper/2.13inch-e-paper-hat-b.htm) | Black, White, Red | ✕ | ✕ | ✔ | ✔ | | [2.9 Inch B/W (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | +| [2.9 Inch B/W V2 (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ | | [1.54 Inch B/W/R (B)](https://www.waveshare.com/product/modules/oleds-lcds/e-paper/1.54inch-e-paper-module-b.htm) | Black, White, Red | ✕ | ✕ | ✔ | ✔ | +| [1.54 Inch B/W/Y (C)](https://www.waveshare.com/1.54inch-e-paper-c.htm) | Black, White, Yellow | ✕ | ✕ | ✔ | ✔ | | [2.9 Inch B/W/R (B/C)](https://www.waveshare.com/product/displays/e-paper/epaper-2/2.9inch-e-paper-module-b.htm) | Black, White, Red | ✕ | ✕ | ✔ | ✔ | +| [5.65 Inch 7 Color (F)](https://www.waveshare.com/5.65inch-e-paper-module-f.htm) | Black, White, Red, Green, Blue, Yellow, Orange | ✕ | ✕ | ✔ | ✔ | +| [2.7 Inch 3 Color (B)](https://www.waveshare.com/2.7inch-e-paper-b.htm) | Black, White, Red | ✕ | ✔ | ✔ | ✔ | ### [1]: 7.5 Inch B/W V2 (A) -Since November 2019 Waveshare sells their updated version of these displays. -They should have a "V2" marking sticker on the backside of the panel. +Since November 2019 Waveshare sells their updated version of these displays. They should have a "V2" marking sticker on +the backside of the panel. Use `epd7in5_v2` instead of `epd7in5`, because the protocol changed. @@ -65,21 +73,23 @@ It's possible with this driver but might lead to ghosting / burn-in effects ther | Interface | Description | | :---: | :--- | -| VCC | 3.3V | -| GND | GND | -| DIN | SPI MOSI | -| CLK | SPI SCK | -| CS | SPI chip select (Low active) | -| DC | Data/Command control pin (High for data, and low for command) | -| RST | External reset pin (Low for reset) | -| BUSY | Busy state output pin (Low for busy) | +| VCC | 3.3V | +| GND | GND | +| DIN | SPI MOSI | +| CLK | SPI SCK | +| CS | SPI chip select (Low active) | +| DC | Data/Command control pin (High for data, and low for command) | +| RST | External reset pin (Low for reset) | +| BUSY | Busy state output pin (Low for busy) | ### Display Configs -There are two types of Display Configurations used in Wavedshare EPDs, which also needs to be set on the "new" E-Paper Driver HAT. -They are also called A and B, but you shouldn't get confused and mix it with the Type A,B,C and D of the various Displays, which just describe different types (colored variants) or new versions. In the Display Config the seperation is most likely due to included fast partial refresh of the displays. In a Tabular form: +There are two types of Display Configurations used in Waveshare EPDs, which also needs to be set on the "new" E-Paper +Driver HAT. They are also called A and B, but you shouldn't get confused and mix it with the Type A,B,C and D of the +various Displays, which just describe different types (colored variants) or new versions. In the Display Config the +separation is most likely due to included fast partial refresh of the displays. In a Tabular form: -| Type A | Tybe B | +| Type A | Type B | | :---: | :---: | | 1.54in (A) | 1.54in (B) | | 2.13in (A) | 1.54in (C) | diff --git a/examples/epd1in54_no_graphics.rs b/examples/epd1in54_no_graphics.rs index 7fd2025..4eecbcd 100644 --- a/examples/epd1in54_no_graphics.rs +++ b/examples/epd1in54_no_graphics.rs @@ -1,6 +1,7 @@ #![deny(warnings)] -use epd_waveshare::{epd1in54::EPD1in54, prelude::*}; +use embedded_hal::prelude::*; +use epd_waveshare::{epd1in54::Epd1in54, prelude::*}; use linux_embedded_hal::{ spidev::{self, SpidevOptions}, sysfs_gpio::Direction, @@ -57,25 +58,25 @@ fn main() -> Result<(), std::io::Error> { // Setup of the needed pins is finished here // Now the "real" usage of the eink-waveshare-rs crate begins - let mut epd = EPD1in54::new(&mut spi, cs_pin, busy, dc, rst, &mut delay)?; + let mut epd = Epd1in54::new(&mut spi, cs_pin, busy, dc, rst, &mut delay)?; // Clear the full screen - epd.clear_frame(&mut spi)?; - epd.display_frame(&mut spi)?; + epd.clear_frame(&mut spi, &mut delay)?; + epd.display_frame(&mut spi, &mut delay)?; // Speeddemo - epd.set_lut(&mut spi, Some(RefreshLUT::QUICK))?; + epd.set_lut(&mut spi, Some(RefreshLut::Quick))?; let small_buffer = [Color::Black.get_byte_value(); 32]; //16x16 let number_of_runs = 1; for i in 0..number_of_runs { let offset = i * 8 % 150; epd.update_partial_frame(&mut spi, &small_buffer, 25 + offset, 25 + offset, 16, 16)?; - epd.display_frame(&mut spi)?; + epd.display_frame(&mut spi, &mut delay)?; } // Clear the full screen - epd.clear_frame(&mut spi)?; - epd.display_frame(&mut spi)?; + epd.clear_frame(&mut spi, &mut delay)?; + epd.display_frame(&mut spi, &mut delay)?; // Draw some squares let small_buffer = [Color::Black.get_byte_value(); 3200]; //160x160 @@ -88,11 +89,11 @@ fn main() -> Result<(), std::io::Error> { epd.update_partial_frame(&mut spi, &small_buffer, 96, 96, 8, 8)?; // Display updated frame - epd.display_frame(&mut spi)?; + epd.display_frame(&mut spi, &mut delay)?; delay.delay_ms(5000u16); // Set the EPD to sleep - epd.sleep(&mut spi)?; + epd.sleep(&mut spi, &mut delay)?; Ok(()) } diff --git a/examples/epd2in13_v2.rs b/examples/epd2in13_v2.rs index ac509c3..be1b314 100644 --- a/examples/epd2in13_v2.rs +++ b/examples/epd2in13_v2.rs @@ -1,17 +1,16 @@ #![deny(warnings)] use embedded_graphics::{ - fonts::{Font12x16, Font6x8, Text}, + mono_font::MonoTextStyleBuilder, prelude::*, - primitives::{Circle, Line}, - style::PrimitiveStyle, - text_style, + primitives::{Circle, Line, PrimitiveStyle}, + text::{Baseline, Text, TextStyleBuilder}, }; use embedded_hal::prelude::*; use epd_waveshare::{ color::*, - epd2in13_v2::{Display2in13, EPD2in13}, - graphics::{Display, DisplayRotation}, + epd2in13_v2::{Display2in13, Epd2in13}, + graphics::DisplayRotation, prelude::*, }; use linux_embedded_hal::{ @@ -63,7 +62,7 @@ fn main() -> Result<(), std::io::Error> { let mut delay = Delay {}; let mut epd2in13 = - EPD2in13::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); + Epd2in13::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); //println!("Test all the rotations"); let mut display = Display2in13::default(); @@ -80,9 +79,9 @@ fn main() -> Result<(), std::io::Error> { display.set_rotation(DisplayRotation::Rotate270); draw_text(&mut display, "Rotate 270!", 5, 50); - epd2in13.update_frame(&mut spi, &display.buffer())?; + epd2in13.update_frame(&mut spi, display.buffer(), &mut delay)?; epd2in13 - .display_frame(&mut spi) + .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); delay.delay_ms(5000u16); @@ -90,7 +89,7 @@ fn main() -> Result<(), std::io::Error> { display.clear_buffer(Color::White); // draw a analog clock - let _ = Circle::new(Point::new(64, 64), 40) + let _ = Circle::with_center(Point::new(64, 64), 80) .into_styled(PrimitiveStyle::with_stroke(Black, 1)) .draw(&mut display); let _ = Line::new(Point::new(64, 64), Point::new(30, 40)) @@ -101,29 +100,32 @@ fn main() -> Result<(), std::io::Error> { .draw(&mut display); // draw white on black background - let _ = Text::new("It's working-WoB!", Point::new(90, 10)) - .into_styled(text_style!( - font = Font6x8, - text_color = White, - background_color = Black - )) + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(White) + .background_color(Black) + .build(); + let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); + + let _ = Text::with_text_style("It's working-WoB!", Point::new(90, 10), style, text_style) .draw(&mut display); // use bigger/different font - let _ = Text::new("It's working-WoB!", Point::new(90, 40)) - .into_styled(text_style!( - font = Font12x16, - text_color = White, - background_color = Black - )) + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_10X20) + .text_color(White) + .background_color(Black) + .build(); + + let _ = Text::with_text_style("It's working\nWoB!", Point::new(90, 40), style, text_style) .draw(&mut display); // Demonstrating how to use the partial refresh feature of the screen. // Real animations can be used. epd2in13 - .set_refresh(&mut spi, &mut delay, RefreshLUT::QUICK) + .set_refresh(&mut spi, &mut delay, RefreshLut::Quick) .unwrap(); - epd2in13.clear_frame(&mut spi).unwrap(); + epd2in13.clear_frame(&mut spi, &mut delay).unwrap(); // a moving `Hello World!` let limit = 10; @@ -131,7 +133,7 @@ fn main() -> Result<(), std::io::Error> { draw_text(&mut display, " Hello World! ", 5 + i * 12, 50); epd2in13 - .update_and_display_frame(&mut spi, &display.buffer()) + .update_and_display_frame(&mut spi, display.buffer(), &mut delay) .expect("display frame new graphics"); delay.delay_ms(1_000u16); } @@ -140,7 +142,7 @@ fn main() -> Result<(), std::io::Error> { // the screen can refresh for this kind of change (small single character) display.clear_buffer(Color::White); epd2in13 - .update_and_display_frame(&mut spi, &display.buffer()) + .update_and_display_frame(&mut spi, display.buffer(), &mut delay) .unwrap(); let spinner = ["|", "/", "-", "\\"]; @@ -148,20 +150,22 @@ fn main() -> Result<(), std::io::Error> { display.clear_buffer(Color::White); draw_text(&mut display, spinner[i % spinner.len()], 10, 100); epd2in13 - .update_and_display_frame(&mut spi, &display.buffer()) + .update_and_display_frame(&mut spi, display.buffer(), &mut delay) .unwrap(); } println!("Finished tests - going to sleep"); - epd2in13.sleep(&mut spi) + epd2in13.sleep(&mut spi, &mut delay) } fn draw_text(display: &mut Display2in13, text: &str, x: i32, y: i32) { - let _ = Text::new(text, Point::new(x, y)) - .into_styled(text_style!( - font = Font6x8, - text_color = Black, - background_color = White - )) - .draw(display); + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(White) + .background_color(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/examples/epd2in13bc.rs b/examples/epd2in13bc.rs new file mode 100644 index 0000000..ba45aab --- /dev/null +++ b/examples/epd2in13bc.rs @@ -0,0 +1,167 @@ +#![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::*, + epd2in13bc::{Display2in13bc, Epd2in13bc}, + graphics::{DisplayRotation, TriDisplay}, + 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 +// +// 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() -> Result<(), std::io::Error> { + 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"); + + 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"); + + // Configure Digital I/O Pin to be used as Chip Select for SPI + let cs = Pin::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"); + + // 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(10_000_000) + .mode(spidev::SpiModeFlags::SPI_MODE_0) + .build(); + spi.configure(&options).expect("spi configuration"); + + 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.clear_buffer(TriColor::White); + + display.set_rotation(DisplayRotation::Rotate0); + draw_text(&mut display, "Rotation 0!", 5, 50); + + display.set_rotation(DisplayRotation::Rotate90); + draw_text(&mut display, "Rotation 90!", 5, 50); + + display.set_rotation(DisplayRotation::Rotate180); + draw_text(&mut display, "Rotation 180!", 5, 50); + + display.set_rotation(DisplayRotation::Rotate270); + draw_text(&mut display, "Rotation 270!", 5, 50); + + // Since we only used black and white, we can resort to updating only + // the bw-buffer of this tri-color screen + + epd2in13 + .update_and_display_frame(&mut spi, display.bw_buffer(), &mut delay) + .expect("display frame new graphics"); + + println!("First frame done. Waiting 5s"); + delay.delay_ms(5000u16); + + println!("Now test new graphics with default rotation and three colors:"); + display.clear_buffer(TriColor::White); + + // draw a analog clock + let _ = Circle::with_center(Point::new(64, 64), 80) + .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 1)) + .draw(&mut display); + let _ = Line::new(Point::new(64, 64), Point::new(30, 40)) + .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 4)) + .draw(&mut display); + let _ = Line::new(Point::new(64, 64), Point::new(80, 40)) + .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 1)) + .draw(&mut display); + + // 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-WoB!", Point::new(90, 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("It's working\nWoB!", Point::new(90, 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, display.bw_buffer(), display.chromatic_buffer())?; + epd2in13 + .display_frame(&mut spi, &mut delay) + .expect("display frame new graphics"); + + println!("Second frame done. Waiting 5s"); + delay.delay_ms(5000u16); + + // clear both bw buffer and chromatic buffer + display.clear_buffer(TriColor::White); + epd2in13.update_color_frame(&mut spi, display.bw_buffer(), display.chromatic_buffer())?; + epd2in13.display_frame(&mut spi, &mut delay)?; + + println!("Finished tests - going to sleep"); + 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/examples/epd4in2.rs b/examples/epd4in2.rs index 04730ed..729e03c 100644 --- a/examples/epd4in2.rs +++ b/examples/epd4in2.rs @@ -1,17 +1,16 @@ #![deny(warnings)] use embedded_graphics::{ - fonts::{Font12x16, Font6x8, Text}, + mono_font::MonoTextStyleBuilder, prelude::*, - primitives::{Circle, Line}, - style::PrimitiveStyle, - text_style, + primitives::{Circle, Line, PrimitiveStyleBuilder}, + text::{Baseline, Text, TextStyleBuilder}, }; use embedded_hal::prelude::*; use epd_waveshare::{ color::*, - epd4in2::{Display4in2, EPD4in2}, - graphics::{Display, DisplayRotation}, + epd4in2::{Display4in2, Epd4in2}, + graphics::DisplayRotation, prelude::*, }; use linux_embedded_hal::{ @@ -63,9 +62,9 @@ fn main() -> Result<(), std::io::Error> { let mut delay = Delay {}; let mut epd4in2 = - EPD4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); + Epd4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); - //println!("Test all the rotations"); + println!("Test all the rotations"); let mut display = Display4in2::default(); display.set_rotation(DisplayRotation::Rotate0); @@ -80,71 +79,83 @@ fn main() -> Result<(), std::io::Error> { display.set_rotation(DisplayRotation::Rotate270); draw_text(&mut display, "Rotate 270!", 5, 50); - epd4in2.update_frame(&mut spi, &display.buffer())?; + epd4in2.update_frame(&mut spi, display.buffer(), &mut delay)?; epd4in2 - .display_frame(&mut spi) + .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); delay.delay_ms(5000u16); - //println!("Now test new graphics with default rotation and some special stuff:"); + println!("Now test new graphics with default rotation and some special stuff"); display.clear_buffer(Color::White); // draw a analog clock - let _ = Circle::new(Point::new(64, 64), 64) - .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + let style = PrimitiveStyleBuilder::new() + .stroke_color(Black) + .stroke_width(1) + .build(); + + let _ = Circle::with_center(Point::new(64, 64), 80) + .into_styled(style) .draw(&mut display); let _ = Line::new(Point::new(64, 64), Point::new(0, 64)) - .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .into_styled(style) .draw(&mut display); let _ = Line::new(Point::new(64, 64), Point::new(80, 80)) - .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .into_styled(style) .draw(&mut display); // draw white on black background - let _ = Text::new("It's working-WoB!", Point::new(175, 250)) - .into_styled(text_style!( - font = Font6x8, - text_color = White, - background_color = Black - )) + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(White) + .background_color(Black) + .build(); + let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); + + let _ = Text::with_text_style("It's working-WoB!", Point::new(175, 250), style, text_style) .draw(&mut display); // use bigger/different font - let _ = Text::new("It's working-WoB!", Point::new(50, 200)) - .into_styled(text_style!( - font = Font12x16, - text_color = White, - background_color = Black - )) + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_10X20) + .text_color(White) + .background_color(Black) + .build(); + + let _ = Text::with_text_style("It's working-WoB!", Point::new(50, 200), style, text_style) .draw(&mut display); // a moving `Hello World!` let limit = 10; - epd4in2.set_lut(&mut spi, Some(RefreshLUT::QUICK)).unwrap(); - epd4in2.clear_frame(&mut spi).unwrap(); + epd4in2.set_lut(&mut spi, Some(RefreshLut::Quick)).unwrap(); + epd4in2.clear_frame(&mut spi, &mut delay).unwrap(); for i in 0..limit { //println!("Moving Hello World. Loop {} from {}", (i + 1), limit); draw_text(&mut display, " Hello World! ", 5 + i * 12, 50); - epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); epd4in2 - .display_frame(&mut spi) + .update_frame(&mut spi, display.buffer(), &mut delay) + .unwrap(); + epd4in2 + .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); delay.delay_ms(1_000u16); } println!("Finished tests - going to sleep"); - epd4in2.sleep(&mut spi) + epd4in2.sleep(&mut spi, &mut delay) } fn draw_text(display: &mut Display4in2, text: &str, x: i32, y: i32) { - let _ = Text::new(text, Point::new(x, y)) - .into_styled(text_style!( - font = Font6x8, - text_color = Black, - background_color = White - )) - .draw(display); + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(White) + .background_color(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/examples/epd4in2_variable_size.rs b/examples/epd4in2_variable_size.rs index 6c03851..95a39ff 100644 --- a/examples/epd4in2_variable_size.rs +++ b/examples/epd4in2_variable_size.rs @@ -2,16 +2,15 @@ #![deny(warnings)] use embedded_graphics::{ - fonts::{Font12x16, Font6x8, Text}, + mono_font::MonoTextStyleBuilder, prelude::*, - primitives::{Circle, Line}, - style::PrimitiveStyle, - text_style, + primitives::{Circle, Line, PrimitiveStyleBuilder}, + text::{Baseline, Text, TextStyleBuilder}, }; use embedded_hal::prelude::*; use epd_waveshare::{ color::*, - epd4in2::{self, EPD4in2}, + epd4in2::{self, Epd4in2}, graphics::{Display, DisplayRotation, VarDisplay}, prelude::*, }; @@ -64,7 +63,7 @@ fn main() -> Result<(), std::io::Error> { let mut delay = Delay {}; let mut epd4in2 = - EPD4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); + Epd4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); println!("Test all the rotations"); @@ -85,10 +84,10 @@ fn main() -> Result<(), std::io::Error> { draw_text(&mut display, "Rotate 270!", 5, 50); epd4in2 - .update_partial_frame(&mut spi, &display.buffer(), x, y, width, height) + .update_partial_frame(&mut spi, display.buffer(), x, y, width, height) .unwrap(); epd4in2 - .display_frame(&mut spi) + .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); delay.delay_ms(5000u16); @@ -97,33 +96,40 @@ fn main() -> Result<(), std::io::Error> { display.clear_buffer(Color::White); // draw a analog clock - // draw a analog clock - let _ = Circle::new(Point::new(64, 64), 64) - .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + let style = PrimitiveStyleBuilder::new() + .stroke_color(Black) + .stroke_width(1) + .build(); + + let _ = Circle::with_center(Point::new(64, 64), 128) + .into_styled(style) .draw(&mut display); let _ = Line::new(Point::new(64, 64), Point::new(0, 64)) - .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .into_styled(style) .draw(&mut display); let _ = Line::new(Point::new(64, 64), Point::new(80, 80)) - .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .into_styled(style) .draw(&mut display); // draw white on black background - let _ = Text::new("It's working-WoB!", Point::new(175, 250)) - .into_styled(text_style!( - font = Font6x8, - text_color = White, - background_color = Black - )) + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(White) + .background_color(Black) + .build(); + let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); + + let _ = Text::with_text_style("It's working-WoB!", Point::new(175, 250), style, text_style) .draw(&mut display); // use bigger/different font - let _ = Text::new("It's working-WoB!", Point::new(50, 200)) - .into_styled(text_style!( - font = Font12x16, - text_color = White, - background_color = Black - )) + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_10X20) + .text_color(White) + .background_color(Black) + .build(); + + let _ = Text::with_text_style("It's working-WoB!", Point::new(50, 200), style, text_style) .draw(&mut display); // a moving `Hello World!` @@ -134,25 +140,27 @@ fn main() -> Result<(), std::io::Error> { draw_text(&mut display, " Hello World! ", 5 + i * 12, 50); epd4in2 - .update_partial_frame(&mut spi, &display.buffer(), x, y, width, height) + .update_partial_frame(&mut spi, display.buffer(), x, y, width, height) .unwrap(); epd4in2 - .display_frame(&mut spi) + .display_frame(&mut spi, &mut delay) .expect("display frame new graphics"); delay.delay_ms(1_000u16); } println!("Finished tests - going to sleep"); - epd4in2.sleep(&mut spi) + epd4in2.sleep(&mut spi, &mut delay) } fn draw_text(display: &mut VarDisplay, text: &str, x: i32, y: i32) { - let _ = Text::new(text, Point::new(x, y)) - .into_styled(text_style!( - font = Font6x8, - text_color = Black, - background_color = White - )) - .draw(display); + let style = MonoTextStyleBuilder::new() + .font(&embedded_graphics::mono_font::ascii::FONT_6X10) + .text_color(White) + .background_color(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/color.rs b/src/color.rs index a1e5ec7..b9bd27a 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,13 +1,30 @@ //! B/W Color for EPDs #[cfg(feature = "graphics")] -use embedded_graphics::pixelcolor::BinaryColor; +use embedded_graphics_core::pixelcolor::BinaryColor; +#[cfg(feature = "graphics")] +use embedded_graphics_core::pixelcolor::PixelColor; #[cfg(feature = "graphics")] pub use BinaryColor::Off as White; #[cfg(feature = "graphics")] pub use BinaryColor::On as Black; +/// When trying to parse u8 to one of the color types +#[derive(Debug, PartialEq, Eq)] +pub struct OutOfColorRangeParseError(u8); +impl core::fmt::Display for OutOfColorRangeParseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Outside of possible Color Range: {}", self.0) + } +} + +impl OutOfColorRangeParseError { + fn _new(size: u8) -> OutOfColorRangeParseError { + OutOfColorRangeParseError(size) + } +} + /// Only for the Black/White-Displays #[derive(Clone, Copy, PartialEq, Debug)] pub enum Color { @@ -28,6 +45,143 @@ pub enum TriColor { Chromatic, } +/// For the 5in65 7 Color Display +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum OctColor { + /// Black Color + Black = 0x00, + /// White Color + White = 0x01, + /// Green Color + Green = 0x02, + /// Blue Color + Blue = 0x03, + /// Red Color + Red = 0x04, + /// Yellow Color + Yellow = 0x05, + /// Orange Color + Orange = 0x06, + /// HiZ / Clean Color + HiZ = 0x07, +} + +impl From<()> for OctColor { + fn from(_: ()) -> OctColor { + OctColor::White + } +} + +#[cfg(feature = "graphics")] +impl From for OctColor { + fn from(b: BinaryColor) -> OctColor { + match b { + BinaryColor::On => OctColor::Black, + BinaryColor::Off => OctColor::White, + } + } +} + +#[cfg(feature = "graphics")] +impl From for embedded_graphics_core::pixelcolor::Rgb888 { + fn from(b: OctColor) -> Self { + let (r, g, b) = b.rgb(); + Self::new(r, g, b) + } +} + +#[cfg(feature = "graphics")] +impl From for OctColor { + fn from(p: embedded_graphics_core::pixelcolor::Rgb888) -> OctColor { + use embedded_graphics_core::prelude::RgbColor; + let colors = [ + OctColor::Black, + OctColor::White, + OctColor::Green, + OctColor::Blue, + OctColor::Red, + OctColor::Yellow, + OctColor::Orange, + OctColor::HiZ, + ]; + // if the user has already mapped to the right color space, it will just be in the list + if let Some(found) = colors.iter().find(|c| c.rgb() == (p.r(), p.g(), p.b())) { + return *found; + } + + // This is not ideal but just pick the nearest color + *colors + .iter() + .map(|c| (c, c.rgb())) + .map(|(c, (r, g, b))| { + let dist = (i32::from(r) - i32::from(p.r())).pow(2) + + (i32::from(g) - i32::from(p.g())).pow(2) + + (i32::from(b) - i32::from(p.b())).pow(2); + (c, dist) + }) + .min_by_key(|(_c, dist)| *dist) + .map(|(c, _)| c) + .unwrap_or(&OctColor::White) + } +} + +#[cfg(feature = "graphics")] +impl From for OctColor { + fn from(b: embedded_graphics_core::pixelcolor::raw::RawU4) -> Self { + use embedded_graphics_core::prelude::RawData; + OctColor::from_nibble(b.into_inner()).unwrap() + } +} + +#[cfg(feature = "graphics")] +impl PixelColor for OctColor { + type Raw = embedded_graphics_core::pixelcolor::raw::RawU4; +} + +impl OctColor { + /// Gets the Nibble representation of the Color as needed by the display + pub fn get_nibble(self) -> u8 { + self as u8 + } + /// Converts two colors into a single byte for the Display + pub fn colors_byte(a: OctColor, b: OctColor) -> u8 { + a.get_nibble() << 4 | b.get_nibble() + } + + ///Take the nibble (lower 4 bits) and convert to an OctColor if possible + pub fn from_nibble(nibble: u8) -> Result { + match nibble & 0xf { + 0x00 => Ok(OctColor::Black), + 0x01 => Ok(OctColor::White), + 0x02 => Ok(OctColor::Green), + 0x03 => Ok(OctColor::Blue), + 0x04 => Ok(OctColor::Red), + 0x05 => Ok(OctColor::Yellow), + 0x06 => Ok(OctColor::Orange), + 0x07 => Ok(OctColor::HiZ), + e => Err(OutOfColorRangeParseError(e)), + } + } + ///Split the nibbles of a single byte and convert both to an OctColor if possible + pub fn split_byte(byte: u8) -> Result<(OctColor, OctColor), OutOfColorRangeParseError> { + let low = OctColor::from_nibble(byte & 0xf)?; + let high = OctColor::from_nibble((byte >> 4) & 0xf)?; + Ok((high, low)) + } + /// Converts to limited range of RGB values. + pub fn rgb(self) -> (u8, u8, u8) { + match self { + OctColor::White => (0xff, 0xff, 0xff), + OctColor::Black => (0x00, 0x00, 0x00), + OctColor::Green => (0x00, 0xff, 0x00), + OctColor::Blue => (0x00, 0x00, 0xff), + OctColor::Red => (0xff, 0x00, 0x00), + OctColor::Yellow => (0xff, 0xff, 0x00), + OctColor::Orange => (0xff, 0x80, 0x00), + OctColor::HiZ => (0x80, 0x80, 0x80), /* looks greyish */ + } + } +} //TODO: Rename get_bit_value to bit() and get_byte_value to byte() ? impl Color { @@ -76,6 +230,29 @@ impl From for Color { } } +impl TriColor { + /// Get the color encoding of the color for one bit + pub fn get_bit_value(self) -> u8 { + match self { + TriColor::White => 1u8, + TriColor::Black | TriColor::Chromatic => 0u8, + } + } + + /// Gets a full byte of black or white pixels + pub fn get_byte_value(self) -> u8 { + match self { + TriColor::White => 0xff, + TriColor::Black | TriColor::Chromatic => 0x00, + } + } +} + +#[cfg(feature = "graphics")] +impl PixelColor for TriColor { + type Raw = (); +} + #[cfg(test)] mod tests { use super::*; @@ -107,4 +284,14 @@ mod tests { assert_eq!(Color::from(Color::White.get_bit_value()), Color::White); assert_eq!(Color::from(1u8).get_bit_value(), 1u8); } + + #[test] + fn test_oct() { + let left = OctColor::Red; + let right = OctColor::Green; + assert_eq!( + OctColor::split_byte(OctColor::colors_byte(left, right)), + Ok((left, right)) + ); + } } diff --git a/src/epd1in54/graphics.rs b/src/epd1in54/graphics.rs index 2cdc1d2..ec9a59d 100644 --- a/src/epd1in54/graphics.rs +++ b/src/epd1in54/graphics.rs @@ -1,7 +1,7 @@ use crate::epd1in54::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 1in54 EPD /// @@ -22,13 +22,22 @@ impl Default for Display1in54 { } } -impl DrawTarget for Display1in54 { +impl DrawTarget for Display1in54 { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display1in54 { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } @@ -57,7 +66,10 @@ mod tests { use super::*; use crate::color::{Black, Color}; use crate::graphics::{Display, DisplayRotation}; - use embedded_graphics::{primitives::Line, style::PrimitiveStyle}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; // test buffer length #[test] diff --git a/src/epd1in54/mod.rs b/src/epd1in54/mod.rs index 849edff..3bedf52 100644 --- a/src/epd1in54/mod.rs +++ b/src/epd1in54/mod.rs @@ -6,7 +6,7 @@ //!# use embedded_hal_mock::*; //!# fn main() -> Result<(), MockError> { //!use embedded_graphics::{ -//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle, +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyleBuilder}, //!}; //!use epd_waveshare::{epd1in54::*, prelude::*}; //!# @@ -20,22 +20,26 @@ //!# let mut delay = delay::MockNoop::new(); //! //!// Setup EPD -//!let mut epd = EPD1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//!let mut epd = Epd1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; //! //!// Use display graphics from embedded-graphics //!let mut display = Display1in54::default(); //! //!// Use embedded graphics for drawing a line +//!let style = PrimitiveStyleBuilder::new() +//! .stroke_color(Black) +//! .stroke_width(1) +//! .build(); //!let _ = Line::new(Point::new(0, 120), Point::new(0, 295)) -//! .into_styled(PrimitiveStyle::with_stroke(Black, 1)) +//! .into_styled(style) //! .draw(&mut display); //! //!// Display updated frame -//!epd.update_frame(&mut spi, &display.buffer())?; -//!epd.display_frame(&mut spi)?; +//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; //! //!// Set the EPD to sleep -//!epd.sleep(&mut spi)?; +//!epd.sleep(&mut spi, &mut delay)?; //!# Ok(()) //!# } //!``` @@ -61,7 +65,7 @@ use crate::type_a::{ use crate::color::Color; -use crate::traits::{RefreshLUT, WaveshareDisplay}; +use crate::traits::{RefreshLut, WaveshareDisplay}; use crate::interface::DisplayInterface; @@ -70,30 +74,26 @@ mod graphics; #[cfg(feature = "graphics")] pub use crate::epd1in54::graphics::Display1in54; -/// EPD1in54 driver -/// -pub struct EPD1in54 { +/// Epd1in54 driver +pub struct Epd1in54 { /// SPI - interface: DisplayInterface, + interface: DisplayInterface, /// Color background_color: Color, /// Refresh LUT - refresh: RefreshLUT, + refresh: RefreshLut, } -impl EPD1in54 +impl Epd1in54 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.interface.reset(delay, 10); // 3 Databytes: @@ -103,7 +103,7 @@ where // Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?) self.interface.cmd_with_data( spi, - Command::DRIVER_OUTPUT_CONTROL, + Command::DriverOutputControl, &[HEIGHT as u8, (HEIGHT >> 8) as u8, 0x00], )?; @@ -112,28 +112,25 @@ where // 1 .. B[6:0] = 0xCE | 0xD6 // 1 .. C[6:0] = 0x8D | 0x9D //TODO: test - self.interface.cmd_with_data( - spi, - Command::BOOSTER_SOFT_START_CONTROL, - &[0xD7, 0xD6, 0x9D], - )?; + self.interface + .cmd_with_data(spi, Command::BoosterSoftStartControl, &[0xD7, 0xD6, 0x9D])?; // One Databyte with value 0xA8 for 7V VCOM self.interface - .cmd_with_data(spi, Command::WRITE_VCOM_REGISTER, &[0xA8])?; + .cmd_with_data(spi, Command::WriteVcomRegister, &[0xA8])?; // One Databyte with default value 0x1A for 4 dummy lines per gate self.interface - .cmd_with_data(spi, Command::SET_DUMMY_LINE_PERIOD, &[0x1A])?; + .cmd_with_data(spi, Command::SetDummyLinePeriod, &[0x1A])?; // One Databyte with default value 0x08 for 2us per line self.interface - .cmd_with_data(spi, Command::SET_GATE_LINE_WIDTH, &[0x08])?; + .cmd_with_data(spi, Command::SetGateLineWidth, &[0x08])?; // One Databyte with default value 0x03 // -> address: x increment, y increment, address counter is updated in x direction self.interface - .cmd_with_data(spi, Command::DATA_ENTRY_MODE_SETTING, &[0x03])?; + .cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?; self.set_lut(spi, None)?; @@ -142,15 +139,17 @@ where } } -impl WaveshareDisplay - for EPD1in54 +impl WaveshareDisplay + for Epd1in54 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { + type DisplayColor = Color; fn width(&self) -> u32 { WIDTH } @@ -159,7 +158,7 @@ where HEIGHT } - fn new>( + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -169,10 +168,10 @@ where ) -> Result { let interface = DisplayInterface::new(cs, busy, dc, rst); - let mut epd = EPD1in54 { + let mut epd = Epd1in54 { interface, background_color: DEFAULT_BACKGROUND_COLOR, - refresh: RefreshLUT::FULL, + refresh: RefreshLut::Full, }; epd.init(spi, delay)?; @@ -180,28 +179,29 @@ where Ok(epd) } - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error> { + 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> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); // 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode //TODO: is 0x00 needed here or would 0x01 be even more efficient? self.interface - .cmd_with_data(spi, Command::DEEP_SLEEP_MODE, &[0x00])?; + .cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?; Ok(()) } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { self.wait_until_idle(); self.use_full_frame(spi)?; self.interface - .cmd_with_data(spi, Command::WRITE_RAM, buffer)?; + .cmd_with_data(spi, Command::WriteRam, buffer)?; Ok(()) } @@ -220,38 +220,43 @@ where self.set_ram_counter(spi, x, y)?; self.interface - .cmd_with_data(spi, Command::WRITE_RAM, buffer)?; + .cmd_with_data(spi, Command::WriteRam, buffer)?; Ok(()) } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); // enable clock signal, enable cp, display pattern -> 0xC4 (tested with the arduino version) //TODO: test control_1 or control_2 with default value 0xFF (from the datasheet) self.interface - .cmd_with_data(spi, Command::DISPLAY_UPDATE_CONTROL_2, &[0xC4])?; + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC4])?; - self.interface.cmd(spi, Command::MASTER_ACTIVATION)?; + self.interface.cmd(spi, Command::MasterActivation)?; // MASTER Activation should not be interupted to avoid currption of panel images // therefore a terminate command is send - self.interface.cmd(spi, Command::NOP)?; + self.interface.cmd(spi, Command::Nop)?; Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.display_frame(spi)?; + 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) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.use_full_frame(spi)?; // clear the ram with the background color let color = self.background_color.get_byte_value(); - self.interface.cmd(spi, Command::WRITE_RAM)?; + self.interface.cmd(spi, Command::WriteRam)?; self.interface .data_x_times(spi, color, WIDTH / 8 * HEIGHT)?; Ok(()) @@ -268,14 +273,14 @@ where fn set_lut( &mut self, spi: &mut SPI, - refresh_rate: Option, + refresh_rate: Option, ) -> Result<(), SPI::Error> { if let Some(refresh_lut) = refresh_rate { self.refresh = refresh_lut; } match self.refresh { - RefreshLUT::FULL => self.set_lut_helper(spi, &LUT_FULL_UPDATE), - RefreshLUT::QUICK => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE), + RefreshLut::Full => self.set_lut_helper(spi, &LUT_FULL_UPDATE), + RefreshLut::Quick => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE), } } @@ -284,16 +289,17 @@ where } } -impl EPD1in54 +impl Epd1in54 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW); + let _ = self.interface.wait_until_idle(IS_BUSY_LOW); } pub(crate) fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { @@ -320,14 +326,14 @@ where // aren't relevant self.interface.cmd_with_data( spi, - Command::SET_RAM_X_ADDRESS_START_END_POSITION, + Command::SetRamXAddressStartEndPosition, &[(start_x >> 3) as u8, (end_x >> 3) as u8], )?; // 2 Databytes: A[7:0] & 0..A[8] for each - start and end self.interface.cmd_with_data( spi, - Command::SET_RAM_Y_ADDRESS_START_END_POSITION, + Command::SetRamYAddressStartEndPosition, &[ start_y as u8, (start_y >> 8) as u8, @@ -348,12 +354,12 @@ where // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram // aren't relevant self.interface - .cmd_with_data(spi, Command::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?; + .cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?; // 2 Databytes: A[7:0] & 0..A[8] self.interface.cmd_with_data( spi, - Command::SET_RAM_Y_ADDRESS_COUNTER, + Command::SetRamYAddressCounter, &[y as u8, (y >> 8) as u8], )?; Ok(()) @@ -364,7 +370,7 @@ where assert!(buffer.len() == 30); self.interface - .cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)?; + .cmd_with_data(spi, Command::WriteLutRegister, buffer)?; Ok(()) } } diff --git a/src/epd1in54b/command.rs b/src/epd1in54b/command.rs index 6c150e0..fd1e35d 100644 --- a/src/epd1in54b/command.rs +++ b/src/epd1in54b/command.rs @@ -2,35 +2,34 @@ use crate::traits; #[allow(dead_code)] -#[allow(non_camel_case_types)] #[derive(Copy, Clone)] pub(crate) enum Command { - PANEL_SETTING = 0x00, + PanelSetting = 0x00, - POWER_SETTING = 0x01, - POWER_OFF = 0x02, - POWER_ON = 0x04, - BOOSTER_SOFT_START = 0x06, - DATA_START_TRANSMISSION_1 = 0x10, - DISPLAY_REFRESH = 0x12, - DATA_START_TRANSMISSION_2 = 0x13, + PowerSetting = 0x01, + PowerOff = 0x02, + PowerOn = 0x04, + BoosterSoftStart = 0x06, + DataStartTransmission1 = 0x10, + DisplayRefresh = 0x12, + DataStartTransmission2 = 0x13, - LUT_FOR_VCOM = 0x20, - LUT_WHITE_TO_WHITE = 0x21, - LUT_BLACK_TO_WHITE = 0x22, - LUT_G0 = 0x23, - LUT_G1 = 0x24, - LUT_RED_VCOM = 0x25, - LUT_RED0 = 0x26, - LUT_RED1 = 0x27, + LutForVcom = 0x20, + LutWhiteToWhite = 0x21, + LutBlackToWhite = 0x22, + LutG0 = 0x23, + LutG1 = 0x24, + LutRedVcom = 0x25, + LutRed0 = 0x26, + LutRed1 = 0x27, - PLL_CONTROL = 0x30, - TEMPERATURE_SENSOR_COMMAND = 0x40, - TEMPERATURE_SENSOR_SELECTION = 0x41, - VCOM_AND_DATA_INTERVAL_SETTING = 0x50, - RESOLUTION_SETTING = 0x61, - VCM_DC_SETTING = 0x82, - POWER_SAVING = 0xE3, + PllControl = 0x30, + TemperatureSensor = 0x40, + TemperatureSensorSelection = 0x41, + VcomAndDataIntervalSetting = 0x50, + ResolutionSetting = 0x61, + VcmDcSetting = 0x82, + PowerSaving = 0xE3, } impl traits::Command for Command { diff --git a/src/epd1in54b/graphics.rs b/src/epd1in54b/graphics.rs index 617d697..7a64472 100644 --- a/src/epd1in54b/graphics.rs +++ b/src/epd1in54b/graphics.rs @@ -1,7 +1,7 @@ use crate::epd1in54b::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 1in54 EPD /// @@ -21,13 +21,22 @@ impl Default for Display1in54b { } } -impl DrawTarget for Display1in54b { +impl DrawTarget for Display1in54b { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display1in54b { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } diff --git a/src/epd1in54b/mod.rs b/src/epd1in54b/mod.rs index 0a2a835..714ee0b 100644 --- a/src/epd1in54b/mod.rs +++ b/src/epd1in54b/mod.rs @@ -7,7 +7,7 @@ use embedded_hal::{ use crate::interface::DisplayInterface; use crate::traits::{ - InternalWiAdditions, RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay, + InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, }; //The Lookup Tables for the Display @@ -32,53 +32,50 @@ mod graphics; #[cfg(feature = "graphics")] pub use self::graphics::Display1in54b; -/// EPD1in54b driver -pub struct EPD1in54b { - interface: DisplayInterface, +/// Epd1in54b driver +pub struct Epd1in54b { + interface: DisplayInterface, color: Color, } -impl InternalWiAdditions - for EPD1in54b +impl InternalWiAdditions + for Epd1in54b 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.interface.reset(delay, 10); // set the power settings self.interface - .cmd_with_data(spi, Command::POWER_SETTING, &[0x07, 0x00, 0x08, 0x00])?; + .cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x00, 0x08, 0x00])?; // start the booster self.interface - .cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x07, 0x07, 0x07])?; + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x07, 0x07, 0x07])?; // power on - self.command(spi, Command::POWER_ON)?; + self.command(spi, Command::PowerOn)?; delay.try_delay_ms(5); self.wait_until_idle(); // set the panel settings - self.cmd_with_data(spi, Command::PANEL_SETTING, &[0xCF])?; + self.cmd_with_data(spi, Command::PanelSetting, &[0xCF])?; - self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x37])?; + self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x37])?; // PLL - self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x39])?; + self.cmd_with_data(spi, Command::PllControl, &[0x39])?; // set resolution self.send_resolution(spi)?; - self.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x0E])?; + self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0E])?; self.set_lut(spi, None)?; @@ -88,14 +85,15 @@ where } } -impl WaveshareThreeColorDisplay - for EPD1in54b +impl WaveshareThreeColorDisplay + for Epd1in54b where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { fn update_color_frame( &mut self, @@ -111,8 +109,7 @@ where self.wait_until_idle(); self.send_resolution(spi)?; - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; for b in black { let expanded = expand_bits(*b); @@ -126,23 +123,24 @@ where spi: &mut SPI, chromatic: &[u8], ) -> Result<(), SPI::Error> { - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface.data(spi, chromatic)?; Ok(()) } } -impl WaveshareDisplay - for EPD1in54b +impl WaveshareDisplay + for Epd1in54b where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { - fn new>( + type DisplayColor = Color; + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -153,38 +151,34 @@ where let interface = DisplayInterface::new(cs, busy, dc, rst); let color = DEFAULT_BACKGROUND_COLOR; - let mut epd = EPD1in54b { interface, color }; + let mut epd = Epd1in54b { interface, color }; epd.init(spi, delay)?; Ok(epd) } - fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.interface - .cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x17])?; //border floating + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating self.interface - .cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x00])?; // VCOM to 0V + .cmd_with_data(spi, Command::VcmDcSetting, &[0x00])?; // Vcom to 0V self.interface - .cmd_with_data(spi, Command::POWER_SETTING, &[0x02, 0x00, 0x00, 0x00])?; //VG&VS to 0V fast + .cmd_with_data(spi, Command::PowerSetting, &[0x02, 0x00, 0x00, 0x00])?; //VG&VS to 0V fast self.wait_until_idle(); //NOTE: The example code has a 1s delay here - self.command(spi, Command::POWER_OFF)?; + self.command(spi, Command::PowerOff)?; Ok(()) } - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error> { + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.init(spi, delay) } @@ -204,12 +198,16 @@ where HEIGHT } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { self.wait_until_idle(); self.send_resolution(spi)?; - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; for b in buffer { // Two bits per pixel @@ -223,8 +221,7 @@ where let color = self.color.get_byte_value(); let nbits = WIDTH * (HEIGHT / 8); - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface.data_x_times(spi, color, nbits)?; //NOTE: Example code has a delay here @@ -244,35 +241,38 @@ where unimplemented!() } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); - self.command(spi, Command::DISPLAY_REFRESH)?; + self.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.display_frame(spi)?; + 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) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.send_resolution(spi)?; let color = DEFAULT_BACKGROUND_COLOR.get_byte_value(); // Clear the black - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; // Uses 2 bits per pixel self.interface .data_x_times(spi, color, 2 * (WIDTH * HEIGHT / 8))?; // Clear the red - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface .data_x_times(spi, color, WIDTH * HEIGHT / 8)?; Ok(()) @@ -281,22 +281,22 @@ where fn set_lut( &mut self, spi: &mut SPI, - _refresh_rate: Option, + _refresh_rate: Option, ) -> Result<(), SPI::Error> { self.interface - .cmd_with_data(spi, Command::LUT_FOR_VCOM, LUT_VCOM0)?; + .cmd_with_data(spi, Command::LutForVcom, LUT_VCOM0)?; self.interface - .cmd_with_data(spi, Command::LUT_WHITE_TO_WHITE, LUT_WHITE_TO_WHITE)?; + .cmd_with_data(spi, Command::LutWhiteToWhite, LUT_WHITE_TO_WHITE)?; self.interface - .cmd_with_data(spi, Command::LUT_BLACK_TO_WHITE, LUT_BLACK_TO_WHITE)?; - self.interface.cmd_with_data(spi, Command::LUT_G0, LUT_G1)?; - self.interface.cmd_with_data(spi, Command::LUT_G1, LUT_G2)?; + .cmd_with_data(spi, Command::LutBlackToWhite, LUT_BLACK_TO_WHITE)?; + self.interface.cmd_with_data(spi, Command::LutG0, LUT_G1)?; + self.interface.cmd_with_data(spi, Command::LutG1, LUT_G2)?; self.interface - .cmd_with_data(spi, Command::LUT_RED_VCOM, LUT_RED_VCOM)?; + .cmd_with_data(spi, Command::LutRedVcom, LUT_RED_VCOM)?; self.interface - .cmd_with_data(spi, Command::LUT_RED0, LUT_RED0)?; + .cmd_with_data(spi, Command::LutRed0, LUT_RED0)?; self.interface - .cmd_with_data(spi, Command::LUT_RED1, LUT_RED1)?; + .cmd_with_data(spi, Command::LutRed1, LUT_RED1)?; Ok(()) } @@ -306,13 +306,14 @@ where } } -impl EPD1in54b +impl Epd1in54b 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) @@ -332,14 +333,14 @@ where } fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW) + let _ = 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::RESOLUTION_SETTING)?; + self.command(spi, Command::ResolutionSetting)?; self.send_data(spi, &[w as u8])?; self.send_data(spi, &[(h >> 8) as u8])?; diff --git a/src/epd1in54c/command.rs b/src/epd1in54c/command.rs new file mode 100644 index 0000000..5913986 --- /dev/null +++ b/src/epd1in54c/command.rs @@ -0,0 +1,38 @@ +//! SPI Commands for the Waveshare 1.54" C yellow E-Ink Display +use crate::traits; + +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum Command { + PanelSetting = 0x00, + + PowerSetting = 0x01, + PowerOff = 0x02, + PowerOn = 0x04, + BoosterSoftStart = 0x06, + DeepSleep = 0x07, + DataStartTransmission1 = 0x10, + DisplayRefresh = 0x12, + DataStartTransmission2 = 0x13, + + LutForVcom = 0x20, + LutWhiteToWhite = 0x21, + LutBlackToWhite = 0x22, + LutWhiteToBlack = 0x23, + LutBlackToBlack = 0x24, + + PllControl = 0x30, + TemperatureSensor = 0x40, + TemperatureSensorSelection = 0x41, + VcomAndDataIntervalSetting = 0x50, + ResolutionSetting = 0x61, + VcmDcSetting = 0x82, + PowerSaving = 0xE3, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} diff --git a/src/epd1in54c/graphics.rs b/src/epd1in54c/graphics.rs new file mode 100644 index 0000000..028d4fe --- /dev/null +++ b/src/epd1in54c/graphics.rs @@ -0,0 +1,60 @@ +use crate::epd1in54c::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH}; +use crate::graphics::{Display, DisplayRotation}; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; + +/// Full size buffer for use with the 1in54c EPD +/// +/// Can also be manually constructed and be used together with VarDisplay +pub struct Display1in54c { + buffer: [u8; NUM_DISPLAY_BITS as usize], + rotation: DisplayRotation, +} + +impl Default for Display1in54c { + fn default() -> Self { + Display1in54c { + buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); NUM_DISPLAY_BITS as usize], + rotation: DisplayRotation::default(), + } + } +} + +impl DrawTarget for Display1in54c { + type Color = BinaryColor; + 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(WIDTH, HEIGHT, pixel)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display1in54c { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +impl Display for Display1in54c { + 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 + } +} diff --git a/src/epd1in54c/mod.rs b/src/epd1in54c/mod.rs new file mode 100644 index 0000000..802b131 --- /dev/null +++ b/src/epd1in54c/mod.rs @@ -0,0 +1,300 @@ +//! A simple Driver for the Waveshare 1.54" (C) E-Ink Display via SPI + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::*, +}; + +use crate::interface::DisplayInterface; +use crate::traits::{ + InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, +}; + +/// Width of epd1in54 in pixels +pub const WIDTH: u32 = 152; +/// Height of epd1in54 in pixels +pub const HEIGHT: u32 = 152; +/// Default Background Color (white) +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; +const IS_BUSY_LOW: bool = true; +const NUM_DISPLAY_BITS: u32 = WIDTH * HEIGHT / 8; + +use crate::color::Color; + +pub(crate) mod command; +use self::command::Command; + +#[cfg(feature = "graphics")] +mod graphics; + +#[cfg(feature = "graphics")] +pub use self::graphics::Display1in54c; + +/// Epd1in54c driver +pub struct Epd1in54c { + interface: DisplayInterface, + color: Color, +} + +impl InternalWiAdditions + for Epd1in54c +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> { + // Based on Reference Program Code from: + // https://www.waveshare.com/w/upload/a/ac/1.54inch_e-Paper_Module_C_Specification.pdf + // and: + // https://github.com/waveshare/e-Paper/blob/master/STM32/STM32-F103ZET6/User/e-Paper/EPD_1in54c.c + self.interface.reset(delay, 2); + + // start the booster + self.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?; + + // power on + self.command(spi, Command::PowerOn)?; + delay.delay_ms(5); + self.wait_until_idle(); + + // set the panel settings + self.cmd_with_data(spi, Command::PanelSetting, &[0x0f, 0x0d])?; + + // set resolution + self.send_resolution(spi)?; + + self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x77])?; + + Ok(()) + } +} + +impl WaveshareThreeColorDisplay + for Epd1in54c +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.wait_until_idle(); + self.cmd_with_data(spi, Command::DataStartTransmission1, black)?; + + Ok(()) + } + + fn update_chromatic_frame( + &mut self, + spi: &mut SPI, + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.cmd_with_data(spi, Command::DataStartTransmission2, chromatic)?; + + Ok(()) + } +} + +impl WaveshareDisplay + for Epd1in54c +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = Color; + 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 = Epd1in54c { interface, color }; + + epd.init(spi, delay)?; + + Ok(epd) + } + + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + self.command(spi, Command::PowerOff)?; + self.wait_until_idle(); + self.cmd_with_data(spi, Command::DeepSleep, &[0xa5])?; + + 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.update_achromatic_frame(spi, buffer)?; + + // Clear the chromatic layer + let color = self.color.get_byte_value(); + + self.command(spi, Command::DataStartTransmission2)?; + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + 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(); + + 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(); + let color = DEFAULT_BACKGROUND_COLOR.get_byte_value(); + + // Clear the black + self.command(spi, Command::DataStartTransmission1)?; + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + // Clear the chromatic + self.command(spi, Command::DataStartTransmission2)?; + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + Ok(()) + } + + fn set_lut( + &mut self, + _spi: &mut SPI, + _refresh_rate: Option, + ) -> Result<(), SPI::Error> { + Ok(()) + } + + fn is_busy(&self) -> bool { + self.interface.is_busy(IS_BUSY_LOW) + } +} + +impl Epd1in54c +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 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) { + let _ = 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::ResolutionSetting)?; + + // | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + // | HRES[7:3] | 0 | 0 | 0 | + self.send_data(spi, &[(w as u8) & 0b1111_1000])?; + // | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + // | - | - | - | - | - | - | - | VRES[8] | + self.send_data(spi, &[(w >> 8) as u8])?; + // | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + // | VRES[7:0] | + // Specification shows C/D is zero while sending the last byte, + // but upstream code does not implement it like that. So for now + // we follow upstream code. + self.send_data(spi, &[h as u8]) + } +} diff --git a/src/epd2in13_v2/command.rs b/src/epd2in13_v2/command.rs index b7e1986..850fd16 100644 --- a/src/epd2in13_v2/command.rs +++ b/src/epd2in13_v2/command.rs @@ -5,57 +5,56 @@ use crate::traits; extern crate bit_field; use bit_field::BitField; -/// EPD2in13 v2 +/// Epd2in13 v2 /// /// 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 { - DRIVER_OUTPUT_CONTROL = 0x01, - GATE_DRIVING_VOLTAGE_CTRL = 0x03, - SOURCE_DRIVING_VOLTAGE_CTRL = 0x04, - BOOSTER_SOFT_START_CONTROL = 0x0C, - GATE_SCAN_START_POSITION = 0x0F, - DEEP_SLEEP_MODE = 0x10, - DATA_ENTRY_MODE_SETTING = 0x11, - SW_RESET = 0x12, - HV_READY_DETECTION = 0x14, - VCI_DETECTION = 0x15, - TEMPERATURE_SENSOR_CONTROL_WRITE = 0x1A, - TEMPERATURE_SENSOR_CONTROL_READ = 0x1B, - TEMPERATURE_SENSOR_EXT_CONTROL_WRITE = 0x1C, - MASTER_ACTIVATION = 0x20, - DISPLAY_UPDATE_CONTROL_1 = 0x21, - DISPLAY_UPDATE_CONTROL_2 = 0x22, - WRITE_RAM = 0x24, - WRITE_RAM_RED = 0x26, - READ_RAM = 0x27, - VCOM_SENSE = 0x28, - VCOM_SENSE_DURATION = 0x29, - PROGRAM_VCOM_OPT = 0x2A, - WRITE_VCOM_REGISTER = 0x2C, - OTP_REGISTER_READ = 0x2D, - STATUS_BIT_READ = 0x2F, - PROGRAM_WS_OTP = 0x30, - LOAD_WS_OTP = 0x31, - WRITE_LUT_REGISTER = 0x32, - PROGRAM_OTP_SELECTION = 0x36, - WRITE_OTP_SELECTION = 0x37, - SET_DUMMY_LINE_PERIOD = 0x3A, - SET_GATE_LINE_WIDTH = 0x3B, - BORDER_WAVEFORM_CONTROL = 0x3C, - READ_RAM_OPTION = 0x41, - SET_RAM_X_ADDRESS_START_END_POSITION = 0x44, - SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45, - AUTO_WRITE_RED_RAM_REGULAR_PATTERN = 0x46, - AUTO_WRITE_BW_RAM_REGULAR_PATTERN = 0x47, - SET_RAM_X_ADDRESS_COUNTER = 0x4E, - SET_RAM_Y_ADDRESS_COUNTER = 0x4F, - SET_ANALOG_BLOCK_CONTROL = 0x74, - SET_DIGITAL_BLOCK_CONTROL = 0x7E, - - NOP = 0x7F, + DriverOutputControl = 0x01, + GateDrivingVoltageCtrl = 0x03, + SourceDrivingVoltageCtrl = 0x04, + BoosterSoftStartControl = 0x0C, + GateScanStartPosition = 0x0F, + DeepSleepMode = 0x10, + DataEntryModeSetting = 0x11, + SwReset = 0x12, + HvReadyDetection = 0x14, + VciDetection = 0x15, + TemperatureSensorControlWrite = 0x1A, + TemperatureSensorControlRead = 0x1B, + TemperatureSensorExtControlWrite = 0x1C, + MasterActivation = 0x20, + DisplayUpdateControl1 = 0x21, + DisplayUpdateControl2 = 0x22, + WriteRam = 0x24, + WriteRamRed = 0x26, + ReadRam = 0x27, + VcomSense = 0x28, + VcomSenseDuration = 0x29, + ProgramVcomOpt = 0x2A, + WriteVcomRegister = 0x2C, + OtpRegisterRead = 0x2D, + StatusBitRead = 0x2F, + ProgramWsOtp = 0x30, + LoadWsOtp = 0x31, + WriteLutRegister = 0x32, + ProgramOtpSelection = 0x36, + WriteOtpSelection = 0x37, + SetDummyLinePeriod = 0x3A, + SetGateLineWidth = 0x3B, + BorderWaveformControl = 0x3C, + ReadRamOption = 0x41, + SetRamXAddressStartEndPosition = 0x44, + SetRamYAddressStartEndPosition = 0x45, + AutoWriteRedRamRegularPattern = 0x46, + AutoWriteBwRamRegularPattern = 0x47, + SetRamXAddressCounter = 0x4E, + SetRamYAddressCounter = 0x4F, + SetAnalogBlockControl = 0x74, + SetDigitalBlockControl = 0x7E, + + Nop = 0x7F, } pub(crate) struct DriverOutput { @@ -135,54 +134,51 @@ impl DisplayUpdateControl2 { } #[allow(dead_code)] -#[allow(non_camel_case_types)] + pub(crate) enum DataEntryModeIncr { - X_DECR_Y_DECR = 0x0, - X_INCR_Y_DECR = 0x1, - X_DECR_Y_INCR = 0x2, - X_INCR_Y_INCR = 0x3, + XDecrYDecr = 0x0, + XIncrYDecr = 0x1, + XDecrYIncr = 0x2, + XIncrYIncr = 0x3, } #[allow(dead_code)] -#[allow(non_camel_case_types)] + pub(crate) enum DataEntryModeDir { - X_DIR = 0x0, - Y_DIR = 0x4, + XDir = 0x0, + YDir = 0x4, } #[allow(dead_code)] -#[allow(non_camel_case_types)] #[derive(Copy, Clone)] -pub(crate) enum BorderWaveFormVBD { - GS = 0x0, - FIX_LEVEL = 0x1, - VCOM = 0x2, +pub(crate) enum BorderWaveFormVbd { + Gs = 0x0, + FixLevel = 0x1, + Vcom = 0x2, } #[allow(dead_code)] -#[allow(non_camel_case_types)] #[derive(Copy, Clone)] pub(crate) enum BorderWaveFormFixLevel { - VSS = 0x0, - VSH1 = 0x1, - VSL = 0x2, - VSH2 = 0x3, + Vss = 0x0, + Vsh1 = 0x1, + Vsl = 0x2, + Vsh2 = 0x3, } #[allow(dead_code)] -#[allow(non_camel_case_types)] #[derive(Copy, Clone)] -pub(crate) enum BorderWaveFormGS { - LUT0 = 0x0, - LUT1 = 0x1, - LUT2 = 0x2, - LUT3 = 0x3, +pub(crate) enum BorderWaveFormGs { + Lut0 = 0x0, + Lut1 = 0x1, + Lut2 = 0x2, + Lut3 = 0x3, } pub(crate) struct BorderWaveForm { - pub vbd: BorderWaveFormVBD, + pub vbd: BorderWaveFormVbd, pub fix_level: BorderWaveFormFixLevel, - pub gs_trans: BorderWaveFormGS, + pub gs_trans: BorderWaveFormGs, } impl BorderWaveForm { @@ -194,25 +190,24 @@ impl BorderWaveForm { } #[allow(dead_code)] -#[allow(non_camel_case_types)] #[derive(Copy, Clone)] pub enum DeepSleepMode { // Sleeps and keeps access to RAM and controller - NORMAL = 0x00, + Normal = 0x00, // Sleeps without access to RAM/controller but keeps RAM content - MODE_1 = 0x01, + Mode1 = 0x01, // Same as MODE_1 but RAM content is not kept - MODE_2 = 0x11, + Mode2 = 0x11, } pub(crate) struct GateDrivingVoltage(pub u8); pub(crate) struct SourceDrivingVoltage(pub u8); -pub(crate) struct VCOM(pub u8); +pub(crate) struct Vcom(pub u8); pub(crate) trait I32Ext { - fn vcom(self) -> VCOM; + fn vcom(self) -> Vcom; fn gate_driving_decivolt(self) -> GateDrivingVoltage; fn source_driving_decivolt(self) -> SourceDrivingVoltage; } @@ -220,8 +215,8 @@ pub(crate) trait I32Ext { impl I32Ext for i32 { // This is really not very nice. Until I find something better, this will be // a placeholder. - fn vcom(self) -> VCOM { - assert!(self >= -30 && self <= -2); + fn vcom(self) -> Vcom { + assert!((-30..=-2).contains(&self)); let u = match -self { 2 => 0x08, 3 => 0x0B, @@ -254,24 +249,20 @@ impl I32Ext for i32 { 30 => 0x78, _ => 0, }; - VCOM(u) + Vcom(u) } fn gate_driving_decivolt(self) -> GateDrivingVoltage { - assert!(self >= 100 && self <= 210 && self % 5 == 0); + assert!((100..=210).contains(&self) && self % 5 == 0); GateDrivingVoltage(((self - 100) / 5 + 0x03) as u8) } fn source_driving_decivolt(self) -> SourceDrivingVoltage { - assert!( - (self >= 24 && self <= 88) - || (self >= 90 && self <= 180 && self % 5 == 0) - || (self >= -180 && self <= -90 && self % 5 == 0) - ); + assert!((24..=88).contains(&self) || (self % 5 == 0 && (90..=180).contains(&self.abs()))); - if self >= 24 && self <= 88 { + if (24..=88).contains(&self) { SourceDrivingVoltage(((self - 24) + 0x8E) as u8) - } else if self >= 90 && self <= 180 { + } else if (90..=180).contains(&self) { SourceDrivingVoltage(((self - 90) / 2 + 0x23) as u8) } else { SourceDrivingVoltage((((-self - 90) / 5) * 2 + 0x1A) as u8) diff --git a/src/epd2in13_v2/graphics.rs b/src/epd2in13_v2/graphics.rs index 1c77a3e..0bb1e78 100644 --- a/src/epd2in13_v2/graphics.rs +++ b/src/epd2in13_v2/graphics.rs @@ -1,8 +1,8 @@ use crate::buffer_len; use crate::epd2in13_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 2in13 v2 EPD /// @@ -23,13 +23,22 @@ impl Default for Display2in13 { } } -impl DrawTarget for Display2in13 { +impl DrawTarget for Display2in13 { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display2in13 { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } @@ -59,7 +68,10 @@ mod tests { use crate::color::{Black, Color}; use crate::epd2in13_v2; use crate::graphics::{Display, DisplayRotation}; - use embedded_graphics::{primitives::Line, style::PrimitiveStyle}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; // test buffer length #[test] diff --git a/src/epd2in13_v2/mod.rs b/src/epd2in13_v2/mod.rs index 6d963a1..6f42272 100644 --- a/src/epd2in13_v2/mod.rs +++ b/src/epd2in13_v2/mod.rs @@ -16,13 +16,13 @@ use embedded_hal::{ use crate::buffer_len; use crate::color::Color; use crate::interface::DisplayInterface; -use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay}; +use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay}; pub(crate) mod command; use self::command::{ - BorderWaveForm, BorderWaveFormFixLevel, BorderWaveFormGS, BorderWaveFormVBD, Command, + BorderWaveForm, BorderWaveFormFixLevel, BorderWaveFormGs, BorderWaveFormVbd, Command, DataEntryModeDir, DataEntryModeIncr, DeepSleepMode, DisplayUpdateControl2, DriverOutput, - GateDrivingVoltage, I32Ext, SourceDrivingVoltage, VCOM, + GateDrivingVoltage, I32Ext, SourceDrivingVoltage, Vcom, }; pub(crate) mod constants; @@ -43,44 +43,41 @@ pub const HEIGHT: u32 = 250; pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; const IS_BUSY_LOW: bool = false; -/// EPD2in13 (V2) driver +/// Epd2in13 (V2) driver /// -pub struct EPD2in13 { +pub struct Epd2in13 { /// Connection Interface - interface: DisplayInterface, + interface: DisplayInterface, sleep_mode: DeepSleepMode, /// Background Color background_color: Color, - refresh: RefreshLUT, + refresh: RefreshLut, } -impl InternalWiAdditions - for EPD2in13 +impl InternalWiAdditions + for Epd2in13 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { // HW reset self.interface.reset(delay, 10); - if self.refresh == RefreshLUT::QUICK { + if self.refresh == RefreshLut::Quick { self.set_vcom_register(spi, (-9).vcom())?; self.wait_until_idle(); self.set_lut(spi, Some(self.refresh))?; // Python code does this, not sure why - // self.cmd_with_data(spi, Command::WRITE_OTP_SELECTION, &[0, 0, 0, 0, 0x40, 0, 0])?; + // self.cmd_with_data(spi, Command::WriteOtpSelection, &[0, 0, 0, 0, 0x40, 0, 0])?; // During partial update, clock/analog are not disabled between 2 // updates. @@ -88,20 +85,20 @@ where spi, DisplayUpdateControl2::new().enable_analog().enable_clock(), )?; - self.command(spi, Command::MASTER_ACTIVATION)?; + self.command(spi, Command::MasterActivation)?; self.wait_until_idle(); self.set_border_waveform( spi, BorderWaveForm { - vbd: BorderWaveFormVBD::GS, - fix_level: BorderWaveFormFixLevel::VSS, - gs_trans: BorderWaveFormGS::LUT1, + vbd: BorderWaveFormVbd::Gs, + fix_level: BorderWaveFormFixLevel::Vss, + gs_trans: BorderWaveFormGs::Lut1, }, )?; } else { self.wait_until_idle(); - self.command(spi, Command::SW_RESET)?; + self.command(spi, Command::SwReset)?; self.wait_until_idle(); self.set_driver_output( @@ -118,11 +115,7 @@ where self.set_dummy_line_period(spi, 0x30)?; self.set_gate_scan_start_position(spi, 0)?; - self.set_data_entry_mode( - spi, - DataEntryModeIncr::X_INCR_Y_INCR, - DataEntryModeDir::X_DIR, - )?; + self.set_data_entry_mode(spi, DataEntryModeIncr::XIncrYIncr, DataEntryModeDir::XDir)?; // Use simple X/Y auto increase self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; @@ -131,9 +124,9 @@ where self.set_border_waveform( spi, BorderWaveForm { - vbd: BorderWaveFormVBD::GS, - fix_level: BorderWaveFormFixLevel::VSS, - gs_trans: BorderWaveFormGS::LUT3, + vbd: BorderWaveFormVbd::Gs, + fix_level: BorderWaveFormFixLevel::Vss, + gs_trans: BorderWaveFormGs::Lut3, }, )?; @@ -157,16 +150,18 @@ where } } -impl WaveshareDisplay - for EPD2in13 +impl WaveshareDisplay + for Epd2in13 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { - fn new>( + type DisplayColor = Color; + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -174,26 +169,22 @@ where rst: RST, delay: &mut DELAY, ) -> Result { - let mut epd = EPD2in13 { + let mut epd = Epd2in13 { interface: DisplayInterface::new(cs, busy, dc, rst), - sleep_mode: DeepSleepMode::MODE_1, + sleep_mode: DeepSleepMode::Mode1, background_color: DEFAULT_BACKGROUND_COLOR, - refresh: RefreshLUT::FULL, + refresh: RefreshLut::Full, }; epd.init(spi, delay)?; Ok(epd) } - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error> { + 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> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); // All sample code enables and disables analog/clocks... @@ -205,25 +196,30 @@ where .disable_analog() .disable_clock(), )?; - self.command(spi, Command::MASTER_ACTIVATION)?; + self.command(spi, Command::MasterActivation)?; self.set_sleep_mode(spi, self.sleep_mode)?; Ok(()) } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { assert!(buffer.len() == buffer_len(WIDTH as usize, HEIGHT as usize)); self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; self.set_ram_address_counters(spi, 0, 0)?; - self.cmd_with_data(spi, Command::WRITE_RAM, buffer)?; + self.cmd_with_data(spi, Command::WriteRam, buffer)?; - if self.refresh == RefreshLUT::FULL { + if self.refresh == RefreshLut::Full { // Always keep the base buffer equal to current if not doing partial refresh. self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; self.set_ram_address_counters(spi, 0, 0)?; - self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?; + self.cmd_with_data(spi, Command::WriteRamRed, buffer)?; } Ok(()) } @@ -248,19 +244,19 @@ where // RAM content). Using this function will most probably make the actual // display incorrect as the controler will compare with something // incorrect. - assert!(self.refresh == RefreshLUT::FULL); + assert!(self.refresh == RefreshLut::Full); self.set_ram_area(spi, x, y, x + width, y + height)?; self.set_ram_address_counters(spi, x, y)?; - self.cmd_with_data(spi, Command::WRITE_RAM, buffer)?; + self.cmd_with_data(spi, Command::WriteRam, buffer)?; - if self.refresh == RefreshLUT::FULL { + if self.refresh == RefreshLut::Full { // Always keep the base buffer equals to current if not doing partial refresh. self.set_ram_area(spi, x, y, x + width, y + height)?; self.set_ram_address_counters(spi, x, y)?; - self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?; + self.cmd_with_data(spi, Command::WriteRamRed, buffer)?; } Ok(()) @@ -268,8 +264,8 @@ where /// Never use directly this function when using partial refresh, or also /// keep the base buffer in syncd using `set_partial_base_buffer` function. - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { - if self.refresh == RefreshLUT::FULL { + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + if self.refresh == RefreshLut::Full { self.set_display_update_control_2( spi, DisplayUpdateControl2::new() @@ -282,29 +278,34 @@ where } else { self.set_display_update_control_2(spi, DisplayUpdateControl2::new().display())?; } - self.command(spi, Command::MASTER_ACTIVATION)?; + self.command(spi, Command::MasterActivation)?; self.wait_until_idle(); Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.display_frame(spi)?; + 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)?; - if self.refresh == RefreshLUT::QUICK { + if self.refresh == RefreshLut::Quick { self.set_partial_base_buffer(spi, buffer)?; } Ok(()) } - fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { let color = self.background_color.get_byte_value(); self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; self.set_ram_address_counters(spi, 0, 0)?; - self.command(spi, Command::WRITE_RAM)?; + self.command(spi, Command::WriteRam)?; self.interface.data_x_times( spi, color, @@ -312,11 +313,11 @@ where )?; // Always keep the base buffer equals to current if not doing partial refresh. - if self.refresh == RefreshLUT::FULL { + if self.refresh == RefreshLut::Full { self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; self.set_ram_address_counters(spi, 0, 0)?; - self.command(spi, Command::WRITE_RAM_RED)?; + self.command(spi, Command::WriteRamRed)?; self.interface.data_x_times( spi, color, @@ -345,14 +346,14 @@ where fn set_lut( &mut self, spi: &mut SPI, - refresh_rate: Option, + refresh_rate: Option, ) -> Result<(), SPI::Error> { let buffer = match refresh_rate { - Some(RefreshLUT::FULL) | None => &LUT_FULL_UPDATE, - Some(RefreshLUT::QUICK) => &LUT_PARTIAL_UPDATE, + Some(RefreshLut::Full) | None => &LUT_FULL_UPDATE, + Some(RefreshLut::Quick) => &LUT_PARTIAL_UPDATE, }; - self.cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer) + self.cmd_with_data(spi, Command::WriteLutRegister, buffer) } fn is_busy(&self) -> bool { @@ -360,13 +361,14 @@ where } } -impl EPD2in13 +impl Epd2in13 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { /// When using partial refresh, the controller uses the provided buffer for /// comparison with new buffer. @@ -379,7 +381,7 @@ where self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; self.set_ram_address_counters(spi, 0, 0)?; - self.cmd_with_data(spi, Command::WRITE_RAM_RED, buffer)?; + self.cmd_with_data(spi, Command::WriteRamRed, buffer)?; Ok(()) } @@ -390,11 +392,11 @@ where /// Sets the refresh mode. When changing mode, the screen will be /// re-initialized accordingly. - pub fn set_refresh>( + pub fn set_refresh( &mut self, spi: &mut SPI, delay: &mut DELAY, - refresh: RefreshLUT, + refresh: RefreshLut, ) -> Result<(), SPI::Error> { if self.refresh != refresh { self.refresh = refresh; @@ -411,7 +413,7 @@ where assert!(start <= 295); self.cmd_with_data( spi, - Command::GATE_SCAN_START_POSITION, + Command::GateScanStartPosition, &[(start & 0xFF) as u8, ((start >> 8) & 0x1) as u8], ) } @@ -423,13 +425,13 @@ where ) -> Result<(), SPI::Error> { self.cmd_with_data( spi, - Command::BORDER_WAVEFORM_CONTROL, + Command::BorderWaveformControl, &[borderwaveform.to_u8()], ) } - fn set_vcom_register(&mut self, spi: &mut SPI, vcom: VCOM) -> Result<(), SPI::Error> { - self.cmd_with_data(spi, Command::WRITE_VCOM_REGISTER, &[vcom.0]) + fn set_vcom_register(&mut self, spi: &mut SPI, vcom: Vcom) -> Result<(), SPI::Error> { + self.cmd_with_data(spi, Command::WriteVcomRegister, &[vcom.0]) } fn set_gate_driving_voltage( @@ -437,7 +439,7 @@ where spi: &mut SPI, voltage: GateDrivingVoltage, ) -> Result<(), SPI::Error> { - self.cmd_with_data(spi, Command::GATE_DRIVING_VOLTAGE_CTRL, &[voltage.0]) + self.cmd_with_data(spi, Command::GateDrivingVoltageCtrl, &[voltage.0]) } fn set_dummy_line_period( @@ -446,11 +448,11 @@ where number_of_lines: u8, ) -> Result<(), SPI::Error> { assert!(number_of_lines <= 127); - self.cmd_with_data(spi, Command::SET_DUMMY_LINE_PERIOD, &[number_of_lines]) + self.cmd_with_data(spi, Command::SetDummyLinePeriod, &[number_of_lines]) } fn set_gate_line_width(&mut self, spi: &mut SPI, width: u8) -> Result<(), SPI::Error> { - self.cmd_with_data(spi, Command::SET_GATE_LINE_WIDTH, &[width & 0x0F]) + self.cmd_with_data(spi, Command::SetGateLineWidth, &[width & 0x0F]) } /// Sets the source driving voltage value @@ -463,7 +465,7 @@ where ) -> Result<(), SPI::Error> { self.cmd_with_data( spi, - Command::SOURCE_DRIVING_VOLTAGE_CTRL, + Command::SourceDrivingVoltageCtrl, &[vsh1.0, vsh2.0, vsl.0], ) } @@ -475,16 +477,16 @@ where spi: &mut SPI, value: DisplayUpdateControl2, ) -> Result<(), SPI::Error> { - self.cmd_with_data(spi, Command::DISPLAY_UPDATE_CONTROL_2, &[value.0]) + self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[value.0]) } /// Triggers the deep sleep mode fn set_sleep_mode(&mut self, spi: &mut SPI, mode: DeepSleepMode) -> Result<(), SPI::Error> { - self.cmd_with_data(spi, Command::DEEP_SLEEP_MODE, &[mode as u8]) + self.cmd_with_data(spi, Command::DeepSleepMode, &[mode as u8]) } fn set_driver_output(&mut self, spi: &mut SPI, output: DriverOutput) -> Result<(), SPI::Error> { - self.cmd_with_data(spi, Command::DRIVER_OUTPUT_CONTROL, &output.to_bytes()) + self.cmd_with_data(spi, Command::DriverOutputControl, &output.to_bytes()) } /// Sets the data entry mode (ie. how X and Y positions changes when writing @@ -496,7 +498,7 @@ where counter_direction: DataEntryModeDir, ) -> Result<(), SPI::Error> { let mode = counter_incr_mode as u8 | counter_direction as u8; - self.cmd_with_data(spi, Command::DATA_ENTRY_MODE_SETTING, &[mode]) + self.cmd_with_data(spi, Command::DataEntryModeSetting, &[mode]) } /// Sets both X and Y pixels ranges @@ -510,13 +512,13 @@ where ) -> Result<(), SPI::Error> { self.cmd_with_data( spi, - Command::SET_RAM_X_ADDRESS_START_END_POSITION, + Command::SetRamXAddressStartEndPosition, &[(start_x >> 3) as u8, (end_x >> 3) as u8], )?; self.cmd_with_data( spi, - Command::SET_RAM_Y_ADDRESS_START_END_POSITION, + Command::SetRamYAddressStartEndPosition, &[ start_y as u8, (start_y >> 8) as u8, @@ -534,11 +536,11 @@ where y: u32, ) -> Result<(), SPI::Error> { self.wait_until_idle(); - self.cmd_with_data(spi, Command::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?; + self.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?; self.cmd_with_data( spi, - Command::SET_RAM_Y_ADDRESS_COUNTER, + Command::SetRamYAddressCounter, &[y as u8, (y >> 8) as u8], )?; Ok(()) @@ -558,7 +560,7 @@ where } fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW) + let _ = self.interface.wait_until_idle(IS_BUSY_LOW); } } diff --git a/src/epd2in13bc/command.rs b/src/epd2in13bc/command.rs new file mode 100644 index 0000000..48c7541 --- /dev/null +++ b/src/epd2in13bc/command.rs @@ -0,0 +1,38 @@ +//! SPI Commands for the Waveshare 2.13" (B/C) E-Ink Display +use crate::traits; + +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum Command { + PanelSetting = 0x00, + + PowerSetting = 0x01, + PowerOff = 0x02, + PowerOn = 0x04, + BoosterSoftStart = 0x06, + DeepSleep = 0x07, + DataStartTransmission1 = 0x10, + DisplayRefresh = 0x12, + DataStartTransmission2 = 0x13, + + LutForVcom = 0x20, + LutWhiteToWhite = 0x21, + LutBlackToWhite = 0x22, + LutWhiteToBlack = 0x23, + LutBlackToBlack = 0x24, + + PllControl = 0x30, + TemperatureSensor = 0x40, + TemperatureSensorSelection = 0x41, + VcomAndDataIntervalSetting = 0x50, + ResolutionSetting = 0x61, + VcmDcSetting = 0x82, + PowerSaving = 0xE3, +} + +impl traits::Command for Command { + /// Returns the address of the command + fn address(self) -> u8 { + self as u8 + } +} diff --git a/src/epd2in13bc/graphics.rs b/src/epd2in13bc/graphics.rs new file mode 100644 index 0000000..9d29a40 --- /dev/null +++ b/src/epd2in13bc/graphics.rs @@ -0,0 +1,74 @@ +use crate::color::TriColor; +use crate::epd2in13bc::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH}; +use crate::graphics::{DisplayRotation, TriDisplay}; +use embedded_graphics_core::prelude::*; + +/// Full size buffer for use with the 2.13" b/c EPD +/// +/// Can also be manually constructed and be used together with VarDisplay +pub struct Display2in13bc { + // one buffer for both b/w and for chromatic: + // * &buffer[0..NUM_DISPLAY_BITS] for b/w buffer and + // * &buffer[NUM_DISPLAY_BITS..2*NUM_DISPLAY_BITS] for chromatic buffer + 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)?; + } + 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/mod.rs b/src/epd2in13bc/mod.rs new file mode 100644 index 0000000..6c20117 --- /dev/null +++ b/src/epd2in13bc/mod.rs @@ -0,0 +1,386 @@ +//! A simple Driver for the Waveshare 2.13" (B/C) E-Ink Display via SPI +//! More information on this display can be found at the [Waveshare Wiki](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_(B)) +//! This driver was build and tested for 212x104, 2.13inch E-Ink display HAT for Raspberry Pi, three-color, SPI interface +//! +//! # Example for the 2.13" E-Ink Display +//! +//!```rust, no_run +//!# use embedded_hal_mock::*; +//!# fn main() -> Result<(), MockError> { +//!use embedded_graphics::{prelude::*, primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder}}; +//!use epd_waveshare::{epd2in13bc::*, prelude::*}; +//!# +//!# let expectations = []; +//!# let mut spi = spi::Mock::new(&expectations); +//!# let expectations = []; +//!# let cs_pin = pin::Mock::new(&expectations); +//!# let busy_in = pin::Mock::new(&expectations); +//!# let dc = pin::Mock::new(&expectations); +//!# let rst = pin::Mock::new(&expectations); +//!# let mut delay = delay::MockNoop::new(); +//! +//!// Setup EPD +//!let mut epd = Epd2in13bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//! +//!// Use display graphics from embedded-graphics +//!// This display is for the black/white/chromatic pixels +//!let mut tricolor_display = Display2in13bc::default(); +//! +//!// Use embedded graphics for drawing a black line +//!let _ = Line::new(Point::new(0, 120), Point::new(0, 200)) +//! .into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 1)) +//! .draw(&mut tricolor_display); +//! +//!// We use `chromatic` but it will be shown as red/yellow +//!let _ = Line::new(Point::new(15, 120), Point::new(15, 200)) +//! .into_styled(PrimitiveStyle::with_stroke(TriColor::Chromatic, 1)) +//! .draw(&mut tricolor_display); +//! +//!// Display updated frame +//!epd.update_color_frame( +//! &mut spi, +//! &tricolor_display.bw_buffer(), +//! &tricolor_display.chromatic_buffer() +//!)?; +//!epd.display_frame(&mut spi, &mut delay)?; +//! +//!// Set the EPD to sleep +//!epd.sleep(&mut spi, &mut delay)?; +//!# Ok(()) +//!# } +//!``` +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::*, +}; + +use crate::interface::DisplayInterface; +use crate::traits::{ + InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, +}; + +/// Width of epd2in13bc in pixels +pub const WIDTH: u32 = 104; +/// Height of epd2in13bc in pixels +pub const HEIGHT: u32 = 212; +/// Default background color (white) of epd2in13bc display +pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White; + +/// Number of bits for b/w buffer and same for chromatic buffer +const NUM_DISPLAY_BITS: u32 = WIDTH * HEIGHT / 8; + +const IS_BUSY_LOW: bool = true; +const VCOM_DATA_INTERVAL: u8 = 0x07; +const WHITE_BORDER: u8 = 0x70; +const BLACK_BORDER: u8 = 0x30; +const CHROMATIC_BORDER: u8 = 0xb0; +const FLOATING_BORDER: u8 = 0xF0; + +use crate::color::TriColor; + +pub(crate) mod command; +use self::command::Command; + +#[cfg(feature = "graphics")] +mod graphics; +#[cfg(feature = "graphics")] +pub use self::graphics::Display2in13bc; + +/// Epd2in13bc driver +pub struct Epd2in13bc { + 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> { + // Values taken from datasheet and sample code + + self.interface.reset(delay, 10); + + // start the booster + self.interface + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?; + + // power on + self.command(spi, Command::PowerOn)?; + delay.delay_ms(5); + self.wait_until_idle(); + + // set the panel settings + self.cmd_with_data(spi, Command::PanelSetting, &[0x8F])?; + + self.cmd_with_data( + spi, + Command::VcomAndDataIntervalSetting, + &[WHITE_BORDER | VCOM_DATA_INTERVAL], + )?; + + // set resolution + self.send_resolution(spi)?; + + self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0A])?; + + self.wait_until_idle(); + + 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) + } + + /// Update only the black/white data of the display. + /// + /// Finish by calling `update_chromatic_frame`. + fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface.data(spi, black)?; + Ok(()) + } + + /// Update only chromatic data of the display. + /// + /// This data takes precedence over the black/white data. + fn update_chromatic_frame( + &mut self, + spi: &mut SPI, + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data(spi, chromatic)?; + + self.wait_until_idle(); + Ok(()) + } +} + +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 sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + // Section 8.2 from datasheet + self.interface.cmd_with_data( + spi, + Command::VcomAndDataIntervalSetting, + &[FLOATING_BORDER | VCOM_DATA_INTERVAL], + )?; + + self.command(spi, Command::PowerOff)?; + // The example STM code from Github has a wait after PowerOff + self.wait_until_idle(); + + self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; + + 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: TriColor) { + self.color = color; + } + + fn background_color(&self) -> &TriColor { + &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.interface.cmd(spi, Command::DataStartTransmission1)?; + + self.interface.data(spi, buffer)?; + + // Clear the chromatic layer + let color = self.color.get_byte_value(); + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + self.wait_until_idle(); + 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> { + Ok(()) + } + + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.command(spi, Command::DisplayRefresh)?; + + self.wait_until_idle(); + 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.send_resolution(spi)?; + + let color = DEFAULT_BACKGROUND_COLOR.get_byte_value(); + + // Clear the black + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + // Clear the chromatic + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; + + self.wait_until_idle(); + Ok(()) + } + + fn set_lut( + &mut self, + _spi: &mut SPI, + _refresh_rate: Option, + ) -> Result<(), SPI::Error> { + Ok(()) + } + + 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 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) { + let _ = 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::ResolutionSetting)?; + + self.send_data(spi, &[w as u8])?; + self.send_data(spi, &[(h >> 8) as u8])?; + self.send_data(spi, &[h as u8]) + } + + /// Set the outer border of the display to the chosen color. + pub fn set_border_color(&mut self, spi: &mut SPI, color: TriColor) -> Result<(), SPI::Error> { + let border = match color { + TriColor::Black => BLACK_BORDER, + TriColor::White => WHITE_BORDER, + TriColor::Chromatic => CHROMATIC_BORDER, + }; + self.cmd_with_data( + spi, + Command::VcomAndDataIntervalSetting, + &[border | VCOM_DATA_INTERVAL], + ) + } +} diff --git a/src/epd2in7b/command.rs b/src/epd2in7b/command.rs new file mode 100644 index 0000000..e32365c --- /dev/null +++ b/src/epd2in7b/command.rs @@ -0,0 +1,137 @@ +//! SPI Commands for the Waveshare 2.7" B 3 color E-Ink Display +use crate::traits; + +/// EPD2IN7B commands +/// +/// More information can be found in the [specification](https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf) +#[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, + PowerOff = 0x02, + /// Setting Power OFF sequence + PowerOffSequenceSetting = 0x03, + PowerOn = 0x04, + /// This command enables the internal bandgap, which will be cleared by the next POF. + PowerOnMeasure = 0x05, + /// Starting data transmission + /// + /// ```ignore + /// self.send_data(&[0x07, 0x07, 0x17])?; + /// ``` + BoosterSoftStart = 0x06, + /// After this command is transmitted, the chip would enter the deep-sleep mode to save power. + /// + /// The deep sleep mode would return to standby 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 + /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. + /// + /// - In B/W mode, this command writes “OLD” data to SRAM. + /// - In B/W/Red mode, this command writes “B/W” data to SRAM. + DataStartTransmission1 = 0x10, + /// Stopping data transmission + DataStop = 0x11, + /// After this command is issued, driver will refresh display (data/VCOM) according to SRAM data and LUT. + DisplayRefresh = 0x12, + /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data + /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. + /// - In B/W mode, this command writes “NEW” data to SRAM. + /// - In B/W/Red mode, this command writes “RED” data to SRAM. + DataStartTransmission2 = 0x13, + /// The command define as follows: The register is indicates that user start to transmit data, then write to SRAM. While data transmission + /// complete, user must send command DSP (Data transmission Stop). Then chip will start to send data/VCOM for panel. + /// + /// - In B/W mode, this command writes “OLD” data to SRAM. + /// - In B/W/Red mode, this command writes “B/W” data to SRAM. + PartialDataStartTransmission1 = 0x14, + /// The command define as follows: The register is indicates that user start to transmit data, then write to SRAM. While data transmission + /// complete, user must send command DSP (Data transmission Stop). Then chip will start to send data/VCOM for panel. + /// + /// - In B/W mode, this command writes “NEW” data to SRAM. + /// - In B/W/Red mode, this command writes “RED” data to SRAM. + PartialDataStartTransmission2 = 0x15, + /// While user sent this command, driver will refresh display (data/VCOM) base on SRAM data and LUT. + /// + /// Only the area (X,Y, W, L) would update, the others pixel output would follow VCOM LUT + PartialDisplayRefresh = 0x16, + /// This command builds the Look-up table for VCOM + LutForVcom = 0x20, + LutWhiteToWhite = 0x21, + LutBlackToWhite = 0x22, + LutWhiteToBlack = 0x23, + LutBlackToBlack = 0x24, + /// The command controls the PLL clock frequency. + PllControl = 0x30, + /// This command reads the temperature sensed by the temperature sensor. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TemperatureSensor = 0x40, + /// This command selects Internal or External temperature sensor. + TemperatureSensorCalibration = 0x41, + /// Write External Temperature Sensor + TemperatureSensorWrite = 0x42, + /// Read External Temperature Sensor + /// + /// Doesn't work! Waveshare doesn't connect the read pin + TemperatureSensorRead = 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) + 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). + ResolutionSetting = 0x61, + SourceAndGateSetting = 0x62, + /// This command reads the IC status. + /// + /// Doesn't work! Waveshare doesn't connect the read pin + GetStatus = 0x71, + /// Automatically measure VCOM. This command reads the IC status + AutoMeasurementVcom = 0x80, + /// This command gets the VCOM value + /// + /// Doesn't work! Waveshare doesn't connect the read pin + ReadVcomValue = 0x81, + /// This command sets VCOM_DC value. + VcmDcSetting = 0x82, + /// 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. + /// + /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. + ProgramMode = 0xA0, + /// After this command is issued, the chip would enter the program mode. + 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, + /// Not shown in commands table, but used in init sequence + PowerOptimization = 0xf8, +} + +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/epd2in7b/constants.rs b/src/epd2in7b/constants.rs new file mode 100644 index 0000000..f850f97 --- /dev/null +++ b/src/epd2in7b/constants.rs @@ -0,0 +1,55 @@ +#[rustfmt::skip] +pub(crate) const LUT_VCOM_DC: [u8; 44] = [ +0x00, 0x00, +0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, +0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10, +0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x00, 0x04, 0x10, 0x00, 0x00, 0x05, +0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, +0x00, 0x23, 0x00, 0x00, 0x00, 0x01, +]; + +#[rustfmt::skip] +pub(crate) const LUT_WW: [u8; 42] =[ +0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, +0x40, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, +0x80, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x00, 0x04, 0x10, 0x00, 0x00, 0x05, +0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, +0x00, 0x23, 0x00, 0x00, 0x00, 0x01, +]; + +#[rustfmt::skip] +pub(crate) const LUT_BW: [u8; 42] =[ +0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, +0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, +0x90, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, +0xB0, 0x03, 0x0E, 0x00, 0x00, 0x0A, +0xC0, 0x23, 0x00, 0x00, 0x00, 0x01, +]; + +#[rustfmt::skip] +pub(crate) const LUT_BB: [u8; 42] =[ +0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, +0x40, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, +0x80, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x00, 0x04, 0x10, 0x00, 0x00, 0x05, +0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, +0x00, 0x23, 0x00, 0x00, 0x00, 0x01, +]; + +#[rustfmt::skip] +pub(crate) const LUT_WB: [u8; 42] =[ +0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, +0x20, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, +0x10, 0x0A, 0x0A, 0x00, 0x00, 0x08, +0x00, 0x04, 0x10, 0x00, 0x00, 0x05, +0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A, +0x00, 0x23, 0x00, 0x00, 0x00, 0x01, +]; diff --git a/src/epd2in7b/graphics.rs b/src/epd2in7b/graphics.rs new file mode 100644 index 0000000..e344be7 --- /dev/null +++ b/src/epd2in7b/graphics.rs @@ -0,0 +1,175 @@ +use crate::epd2in7b::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; +use crate::graphics::{Display, DisplayRotation}; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; + +/// Full size buffer for use with the 2in7B EPD +/// +/// Can also be manuall constructed: +/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH * HEIGHT / 8]` +pub struct Display2in7b { + buffer: [u8; WIDTH as usize * HEIGHT as usize / 8], + rotation: DisplayRotation, +} + +impl Default for Display2in7b { + fn default() -> Self { + Display2in7b { + buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); + WIDTH as usize * HEIGHT as usize / 8], + rotation: DisplayRotation::default(), + } + } +} + +impl DrawTarget for Display2in7b { + type Color = BinaryColor; + 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(WIDTH, HEIGHT, pixel)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display2in7b { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +impl Display for Display2in7b { + 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::Black; + use crate::color::Color; + use crate::epd2in7b; + use crate::epd2in7b::{HEIGHT, WIDTH}; + use crate::graphics::{Display, DisplayRotation}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; + + // test buffer length + #[test] + fn graphics_size() { + let display = Display2in7b::default(); + assert_eq!(display.buffer().len(), 5808); + } + + // test default background color on all bytes + #[test] + fn graphics_default() { + let display = Display2in7b::default(); + for &byte in display.buffer() { + assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_0() { + let mut display = Display2in7b::default(); + let _ = Line::new(Point::new(0, 0), Point::new(7, 0)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_90() { + let mut display = Display2in7b::default(); + display.set_rotation(DisplayRotation::Rotate90); + let _ = Line::new( + Point::new(0, WIDTH as i32 - 8), + Point::new(0, WIDTH as i32 - 1), + ) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_180() { + let mut display = Display2in7b::default(); + display.set_rotation(DisplayRotation::Rotate180); + + let _ = Line::new( + Point::new(WIDTH as i32 - 8, HEIGHT as i32 - 1), + Point::new(WIDTH as i32 - 1, HEIGHT as i32 - 1), + ) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + extern crate std; + std::println!("{:?}", buffer); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_270() { + let mut display = Display2in7b::default(); + display.set_rotation(DisplayRotation::Rotate270); + let _ = Line::new( + Point::new(HEIGHT as i32 - 1, 0), + Point::new(HEIGHT as i32 - 1, 7), + ) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + extern crate std; + std::println!("{:?}", buffer); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd2in7b::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } +} diff --git a/src/epd2in7b/mod.rs b/src/epd2in7b/mod.rs new file mode 100644 index 0000000..972d785 --- /dev/null +++ b/src/epd2in7b/mod.rs @@ -0,0 +1,456 @@ +//! A simple Driver for the Waveshare 2.7" B Tri-Color E-Ink Display via SPI +//! +//! [Documentation](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT_(B)) + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::*, +}; + +use crate::interface::DisplayInterface; +use crate::traits::{ + InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, +}; + +// The Lookup Tables for the Display +mod constants; +use crate::epd2in7b::constants::*; + +/// Width of the display +pub const WIDTH: u32 = 176; +/// Height of the display +pub const HEIGHT: u32 = 264; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; +const IS_BUSY_LOW: bool = true; + +use crate::color::Color; + +pub(crate) mod command; +use self::command::Command; + +#[cfg(feature = "graphics")] +mod graphics; +#[cfg(feature = "graphics")] +pub use self::graphics::Display2in7b; + +/// Epd2in7b driver +pub struct Epd2in7b { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: Color, +} + +impl InternalWiAdditions + for Epd2in7b +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, 2); + + // power on + self.command(spi, Command::PowerOn)?; + delay.delay_ms(5); + self.wait_until_idle(); + + // set panel settings, 0xbf is bw, 0xaf is multi-color + self.interface + .cmd_with_data(spi, Command::PanelSetting, &[0xaf])?; + + // pll control + self.interface + .cmd_with_data(spi, Command::PllControl, &[0x3a])?; + + // set the power settings + self.interface.cmd_with_data( + spi, + Command::PowerSetting, + &[0x03, 0x00, 0x2b, 0x2b, 0x09], + )?; + + // start the booster + self.interface + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x07, 0x07, 0x17])?; + + // power optimization + self.interface + .cmd_with_data(spi, Command::PowerOptimization, &[0x60, 0xa5])?; + self.interface + .cmd_with_data(spi, Command::PowerOptimization, &[0x89, 0xa5])?; + self.interface + .cmd_with_data(spi, Command::PowerOptimization, &[0x90, 0x00])?; + self.interface + .cmd_with_data(spi, Command::PowerOptimization, &[0x93, 0x2a])?; + self.interface + .cmd_with_data(spi, Command::PowerOptimization, &[0x73, 0x41])?; + + self.interface + .cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?; + + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x87])?; + + self.set_lut(spi, None)?; + + self.interface + .cmd_with_data(spi, Command::PartialDisplayRefresh, &[0x00])?; + + self.wait_until_idle(); + Ok(()) + } +} + +impl WaveshareDisplay + for Epd2in7b +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = Color; + 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 = Epd2in7b { 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(); + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0xf7])?; + + self.command(spi, Command::PowerOff)?; + self.wait_until_idle(); + self.interface + .cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; + Ok(()) + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.send_buffer_helper(spi, buffer)?; + + // Clear chromatic layer since we won't be using it here + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, !self.color.get_byte_value(), WIDTH * HEIGHT / 8)?; + + self.interface.cmd(spi, Command::DataStop)?; + Ok(()) + } + + fn update_partial_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.interface + .cmd(spi, Command::PartialDataStartTransmission1)?; + + self.send_data(spi, &[(x >> 8) as u8])?; + self.send_data(spi, &[(x & 0xf8) as u8])?; + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[(y & 0xff) as u8])?; + self.send_data(spi, &[(width >> 8) as u8])?; + self.send_data(spi, &[(width & 0xf8) as u8])?; + self.send_data(spi, &[(height >> 8) as u8])?; + self.send_data(spi, &[(height & 0xff) as u8])?; + self.wait_until_idle(); + + self.send_buffer_helper(spi, buffer)?; + + self.interface.cmd(spi, Command::DataStop) + } + + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.command(spi, Command::DisplayRefresh)?; + self.wait_until_idle(); + 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.command(spi, Command::DisplayRefresh)?; + Ok(()) + } + + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + let color_value = self.color.get_byte_value(); + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, color_value, WIDTH * HEIGHT / 8)?; + + self.interface.cmd(spi, Command::DataStop)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, WIDTH * HEIGHT / 8)?; + self.interface.cmd(spi, Command::DataStop)?; + 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> { + self.wait_until_idle(); + self.cmd_with_data(spi, Command::LutForVcom, &LUT_VCOM_DC)?; + self.cmd_with_data(spi, Command::LutWhiteToWhite, &LUT_WW)?; + self.cmd_with_data(spi, Command::LutBlackToWhite, &LUT_BW)?; + self.cmd_with_data(spi, Command::LutWhiteToBlack, &LUT_WB)?; + self.cmd_with_data(spi, Command::LutBlackToBlack, &LUT_BB)?; + Ok(()) + } + + fn is_busy(&self) -> bool { + self.interface.is_busy(IS_BUSY_LOW) + } +} + +impl WaveshareThreeColorDisplay + for Epd2in7b +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) + } + + /// Update only the black/white data of the display. + /// + /// Finish by calling `update_chromatic_frame`. + fn update_achromatic_frame( + &mut self, + spi: &mut SPI, + achromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + self.send_buffer_helper(spi, achromatic)?; + + self.interface.cmd(spi, Command::DataStop) + } + + /// Update only chromatic data of the display. + /// + /// This data takes precedence over the black/white data. + fn update_chromatic_frame( + &mut self, + spi: &mut SPI, + chromatic: &[u8], + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission2)?; + + self.send_buffer_helper(spi, chromatic)?; + + self.interface.cmd(spi, Command::DataStop)?; + self.wait_until_idle(); + + Ok(()) + } +} + +impl Epd2in7b +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 send_buffer_helper(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + // Based on the waveshare implementation, all data for color values is flipped. This helper + // method makes that transmission easier + for b in buffer.iter() { + self.send_data(spi, &[!b])?; + } + Ok(()) + } + + 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) { + let _ = self.interface.wait_until_idle(IS_BUSY_LOW); + } + + /// Refresh display for partial frame + pub fn display_partial_frame( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.command(spi, Command::PartialDisplayRefresh)?; + self.send_data(spi, &[(x >> 8) as u8])?; + self.send_data(spi, &[(x & 0xf8) as u8])?; + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[(y & 0xff) as u8])?; + self.send_data(spi, &[(width >> 8) as u8])?; + self.send_data(spi, &[(width & 0xf8) as u8])?; + self.send_data(spi, &[(height >> 8) as u8])?; + self.send_data(spi, &[(height & 0xff) as u8])?; + self.wait_until_idle(); + Ok(()) + } + + /// Update black/achromatic frame + pub fn update_partial_achromatic_frame( + &mut self, + spi: &mut SPI, + achromatic: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.interface + .cmd(spi, Command::PartialDataStartTransmission1)?; + self.send_data(spi, &[(x >> 8) as u8])?; + self.send_data(spi, &[(x & 0xf8) as u8])?; + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[(y & 0xff) as u8])?; + self.send_data(spi, &[(width >> 8) as u8])?; + self.send_data(spi, &[(width & 0xf8) as u8])?; + self.send_data(spi, &[(height >> 8) as u8])?; + self.send_data(spi, &[(height & 0xff) as u8])?; + self.wait_until_idle(); + + for b in achromatic.iter() { + // Flipping based on waveshare implementation + self.send_data(spi, &[!b])?; + } + + Ok(()) + } + + /// Update partial chromatic/red frame + pub fn update_partial_chromatic_frame( + &mut self, + spi: &mut SPI, + chromatic: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.interface + .cmd(spi, Command::PartialDataStartTransmission2)?; + self.send_data(spi, &[(x >> 8) as u8])?; + self.send_data(spi, &[(x & 0xf8) as u8])?; + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[(y & 0xff) as u8])?; + self.send_data(spi, &[(width >> 8) as u8])?; + self.send_data(spi, &[(width & 0xf8) as u8])?; + self.send_data(spi, &[(height >> 8) as u8])?; + self.send_data(spi, &[(height & 0xff) as u8])?; + self.wait_until_idle(); + + for b in chromatic.iter() { + // Flipping based on waveshare implementation + self.send_data(spi, &[!b])?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 176); + assert_eq!(HEIGHT, 264); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } +} diff --git a/src/epd2in9/graphics.rs b/src/epd2in9/graphics.rs index fb1154e..8678c9c 100644 --- a/src/epd2in9/graphics.rs +++ b/src/epd2in9/graphics.rs @@ -1,7 +1,7 @@ use crate::epd2in9::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Display with Fullsize buffer for use with the 2in9 EPD /// @@ -22,13 +22,22 @@ impl Default for Display2in9 { } } -impl DrawTarget for Display2in9 { +impl DrawTarget for Display2in9 { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display2in9 { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } diff --git a/src/epd2in9/mod.rs b/src/epd2in9/mod.rs index fb9477d..b701e66 100644 --- a/src/epd2in9/mod.rs +++ b/src/epd2in9/mod.rs @@ -7,7 +7,7 @@ //!# use embedded_hal_mock::*; //!# fn main() -> Result<(), MockError> { //!use embedded_graphics::{ -//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle, +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, //!}; //!use epd_waveshare::{epd2in9::*, prelude::*}; //!# @@ -21,7 +21,7 @@ //!# let mut delay = delay::MockNoop::new(); //! //!// Setup EPD -//!let mut epd = EPD2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//!let mut epd = Epd2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; //! //!// Use display graphics from embedded-graphics //!let mut display = Display2in9::default(); @@ -32,11 +32,11 @@ //! .draw(&mut display); //! //! // Display updated frame -//!epd.update_frame(&mut spi, &display.buffer())?; -//!epd.display_frame(&mut spi)?; +//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; //! //!// Set the EPD to sleep -//!epd.sleep(&mut spi)?; +//!epd.sleep(&mut spi, &mut delay)?; //!# Ok(()) //!# } //!``` @@ -70,30 +70,27 @@ mod graphics; #[cfg(feature = "graphics")] pub use crate::epd2in9::graphics::Display2in9; -/// EPD2in9 driver +/// Epd2in9 driver /// -pub struct EPD2in9 { +pub struct Epd2in9 { /// SPI - interface: DisplayInterface, + interface: DisplayInterface, /// Color background_color: Color, /// Refresh LUT - refresh: RefreshLUT, + refresh: RefreshLut, } -impl EPD2in9 +impl Epd2in9 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.interface.reset(delay, 10); self.wait_until_idle(); @@ -104,49 +101,48 @@ where // 0.. B[2:0] // Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?) self.interface - .cmd_with_data(spi, Command::DRIVER_OUTPUT_CONTROL, &[0x27, 0x01, 0x00])?; + .cmd_with_data(spi, Command::DriverOutputControl, &[0x27, 0x01, 0x00])?; // 3 Databytes: (and default values from datasheet and arduino) // 1 .. A[6:0] = 0xCF | 0xD7 // 1 .. B[6:0] = 0xCE | 0xD6 // 1 .. C[6:0] = 0x8D | 0x9D //TODO: test - self.interface.cmd_with_data( - spi, - Command::BOOSTER_SOFT_START_CONTROL, - &[0xD7, 0xD6, 0x9D], - )?; + self.interface + .cmd_with_data(spi, Command::BoosterSoftStartControl, &[0xD7, 0xD6, 0x9D])?; // One Databyte with value 0xA8 for 7V VCOM self.interface - .cmd_with_data(spi, Command::WRITE_VCOM_REGISTER, &[0xA8])?; + .cmd_with_data(spi, Command::WriteVcomRegister, &[0xA8])?; // One Databyte with default value 0x1A for 4 dummy lines per gate self.interface - .cmd_with_data(spi, Command::SET_DUMMY_LINE_PERIOD, &[0x1A])?; + .cmd_with_data(spi, Command::SetDummyLinePeriod, &[0x1A])?; // One Databyte with default value 0x08 for 2us per line self.interface - .cmd_with_data(spi, Command::SET_GATE_LINE_WIDTH, &[0x08])?; + .cmd_with_data(spi, Command::SetGateLineWidth, &[0x08])?; // One Databyte with default value 0x03 // -> address: x increment, y increment, address counter is updated in x direction self.interface - .cmd_with_data(spi, Command::DATA_ENTRY_MODE_SETTING, &[0x03])?; + .cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?; self.set_lut(spi, None) } } -impl WaveshareDisplay - for EPD2in9 +impl WaveshareDisplay + for Epd2in9 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { + type DisplayColor = Color; fn width(&self) -> u32 { WIDTH } @@ -155,7 +151,7 @@ where HEIGHT } - fn new>( + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -165,10 +161,10 @@ where ) -> Result { let interface = DisplayInterface::new(cs, busy, dc, rst); - let mut epd = EPD2in9 { + let mut epd = Epd2in9 { interface, background_color: DEFAULT_BACKGROUND_COLOR, - refresh: RefreshLUT::FULL, + refresh: RefreshLut::Full, }; epd.init(spi, delay)?; @@ -176,31 +172,32 @@ where Ok(epd) } - fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); // 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode //TODO: is 0x00 needed here? (see also epd1in54) self.interface - .cmd_with_data(spi, Command::DEEP_SLEEP_MODE, &[0x00])?; + .cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?; Ok(()) } - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error> { + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.init(spi, delay)?; Ok(()) } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { self.wait_until_idle(); self.use_full_frame(spi)?; self.interface - .cmd_with_data(spi, Command::WRITE_RAM, buffer)?; + .cmd_with_data(spi, Command::WriteRam, buffer)?; Ok(()) } @@ -219,38 +216,43 @@ where self.set_ram_counter(spi, x, y)?; self.interface - .cmd_with_data(spi, Command::WRITE_RAM, buffer)?; + .cmd_with_data(spi, Command::WriteRam, buffer)?; Ok(()) } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); // enable clock signal, enable cp, display pattern -> 0xC4 (tested with the arduino version) //TODO: test control_1 or control_2 with default value 0xFF (from the datasheet) self.interface - .cmd_with_data(spi, Command::DISPLAY_UPDATE_CONTROL_2, &[0xC4])?; + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC4])?; - self.interface.cmd(spi, Command::MASTER_ACTIVATION)?; + self.interface.cmd(spi, Command::MasterActivation)?; // MASTER Activation should not be interupted to avoid currption of panel images // therefore a terminate command is send - self.interface.cmd(spi, Command::NOP)?; + self.interface.cmd(spi, Command::Nop)?; Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.display_frame(spi)?; + 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) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.use_full_frame(spi)?; // clear the ram with the background color let color = self.background_color.get_byte_value(); - self.interface.cmd(spi, Command::WRITE_RAM)?; + self.interface.cmd(spi, Command::WriteRam)?; self.interface .data_x_times(spi, color, WIDTH / 8 * HEIGHT)?; Ok(()) @@ -267,14 +269,14 @@ where fn set_lut( &mut self, spi: &mut SPI, - refresh_rate: Option, + refresh_rate: Option, ) -> Result<(), SPI::Error> { if let Some(refresh_lut) = refresh_rate { self.refresh = refresh_lut; } match self.refresh { - RefreshLUT::FULL => self.set_lut_helper(spi, &LUT_FULL_UPDATE), - RefreshLUT::QUICK => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE), + RefreshLut::Full => self.set_lut_helper(spi, &LUT_FULL_UPDATE), + RefreshLut::Quick => self.set_lut_helper(spi, &LUT_PARTIAL_UPDATE), } } @@ -283,16 +285,17 @@ where } } -impl EPD2in9 +impl Epd2in9 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW); + let _ = self.interface.wait_until_idle(IS_BUSY_LOW); } fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { @@ -318,14 +321,14 @@ where // aren't relevant self.interface.cmd_with_data( spi, - Command::SET_RAM_X_ADDRESS_START_END_POSITION, + Command::SetRamXAddressStartEndPosition, &[(start_x >> 3) as u8, (end_x >> 3) as u8], )?; // 2 Databytes: A[7:0] & 0..A[8] for each - start and end self.interface.cmd_with_data( spi, - Command::SET_RAM_Y_ADDRESS_START_END_POSITION, + Command::SetRamYAddressStartEndPosition, &[ start_y as u8, (start_y >> 8) as u8, @@ -340,12 +343,12 @@ where // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram // aren't relevant self.interface - .cmd_with_data(spi, Command::SET_RAM_X_ADDRESS_COUNTER, &[(x >> 3) as u8])?; + .cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?; // 2 Databytes: A[7:0] & 0..A[8] self.interface.cmd_with_data( spi, - Command::SET_RAM_Y_ADDRESS_COUNTER, + Command::SetRamYAddressCounter, &[y as u8, (y >> 8) as u8], )?; Ok(()) @@ -356,7 +359,7 @@ where self.wait_until_idle(); assert!(buffer.len() == 30); self.interface - .cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)?; + .cmd_with_data(spi, Command::WriteLutRegister, buffer)?; Ok(()) } } diff --git a/src/epd2in9_v2/graphics.rs b/src/epd2in9_v2/graphics.rs new file mode 100644 index 0000000..e121adb --- /dev/null +++ b/src/epd2in9_v2/graphics.rs @@ -0,0 +1,83 @@ +use crate::epd2in9::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; +use crate::graphics::{Display, DisplayRotation}; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; + +/// Display with Fullsize buffer for use with the 2in9 EPD V2 +/// +/// Can also be manuall constructed: +/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]` +pub struct Display2in9 { + buffer: [u8; WIDTH as usize * HEIGHT as usize / 8], + rotation: DisplayRotation, +} + +impl Default for Display2in9 { + fn default() -> Self { + Display2in9 { + buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); + WIDTH as usize * HEIGHT as usize / 8], + rotation: DisplayRotation::default(), + } + } +} + +impl DrawTarget for Display2in9 { + type Color = BinaryColor; + 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(WIDTH, HEIGHT, pixel)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display2in9 { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +impl Display for Display2in9 { + 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::*; + + // test buffer length + #[test] + fn graphics_size() { + let display = Display2in9::default(); + assert_eq!(display.buffer().len(), 4736); + } + + // test default background color on all bytes + #[test] + fn graphics_default() { + let display = Display2in9::default(); + for &byte in display.buffer() { + assert_eq!(byte, DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } +} diff --git a/src/epd2in9_v2/mod.rs b/src/epd2in9_v2/mod.rs new file mode 100644 index 0000000..5158627 --- /dev/null +++ b/src/epd2in9_v2/mod.rs @@ -0,0 +1,496 @@ +//! A simple Driver for the Waveshare 2.9" E-Ink Display V2 via SPI +//! +//! Specification: +//! +//! # Example for the 2.9 in E-Ink Display V2 +//! +//!```rust, no_run +//!# use embedded_hal_mock::*; +//!# fn main() -> Result<(), MockError> { +//!use embedded_graphics::{ +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, +//!}; +//!use epd_waveshare::{epd2in9_v2::*, prelude::*}; +//!# +//!# let expectations = []; +//!# let mut spi = spi::Mock::new(&expectations); +//!# let expectations = []; +//!# let cs_pin = pin::Mock::new(&expectations); +//!# let busy_in = pin::Mock::new(&expectations); +//!# let dc = pin::Mock::new(&expectations); +//!# let rst = pin::Mock::new(&expectations); +//!# let mut delay = delay::MockNoop::new(); +//! +//!// Setup EPD +//!let mut epd = Epd2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//! +//!// Use display graphics from embedded-graphics +//!let mut display = Display2in9::default(); +//! +//!// Use embedded graphics for drawing a line +//!let _ = Line::new(Point::new(0, 120), Point::new(0, 295)) +//! .into_styled(PrimitiveStyle::with_stroke(Black, 1)) +//! .draw(&mut display); +//! +//!// Display updated frame +//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; +//! +//!// Draw something new here +//! +//!// Display new image as a base image for further quick refreshes +//!epd.update_old_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; +//! +//!// Update image here +//! +//!// quick refresh of updated pixels +//!epd.update_new_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_new_frame(&mut spi, &mut delay)?; +//! +//!// Set the EPD to sleep +//!epd.sleep(&mut spi, &mut delay)?; +//!# Ok(()) +//!# } +//!``` + +/// Width of epd2in9 in pixels +pub const WIDTH: u32 = 128; +/// Height of epd2in9 in pixels +pub const HEIGHT: u32 = 296; +/// Default Background Color (white) +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; +const IS_BUSY_LOW: bool = false; + +const LUT_PARTIAL_2IN9: [u8; 153] = [ + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x0, 0x0, 0x0, +]; + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::*, +}; + +use crate::type_a::command::Command; + +use crate::color::Color; + +use crate::traits::*; + +use crate::interface::DisplayInterface; +use crate::traits::QuickRefresh; + +#[cfg(feature = "graphics")] +mod graphics; +#[cfg(feature = "graphics")] +pub use crate::epd2in9_v2::graphics::Display2in9; + +/// Epd2in9 driver +/// +pub struct Epd2in9 { + /// SPI + interface: DisplayInterface, + /// Color + background_color: Color, + /// Refresh LUT + refresh: RefreshLut, +} + +impl Epd2in9 +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> { + self.interface.reset(delay, 2); + + self.wait_until_idle(); + self.interface.cmd(spi, Command::SwReset)?; + self.wait_until_idle(); + + // 3 Databytes: + // A[7:0] + // 0.. A[8] + // 0.. B[2:0] + // Default Values: A = Height of Screen (0x127), B = 0x00 (GD, SM and TB=0?) + self.interface + .cmd_with_data(spi, Command::DriverOutputControl, &[0x27, 0x01, 0x00])?; + + // One Databyte with default value 0x03 + // -> address: x increment, y increment, address counter is updated in x direction + self.interface + .cmd_with_data(spi, Command::DataEntryModeSetting, &[0x03])?; + + self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; + + self.interface + .cmd_with_data(spi, Command::DisplayUpdateControl1, &[0x00, 0x80])?; + + self.set_ram_counter(spi, 0, 0)?; + + self.wait_until_idle(); + Ok(()) + } +} + +impl WaveshareDisplay + for Epd2in9 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = Color; + fn width(&self) -> u32 { + WIDTH + } + + fn height(&self) -> u32 { + HEIGHT + } + + 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 mut epd = Epd2in9 { + interface, + background_color: DEFAULT_BACKGROUND_COLOR, + refresh: 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(); + // 0x00 for Normal mode (Power on Reset), 0x01 for Deep Sleep Mode + self.interface + .cmd_with_data(spi, Command::DeepSleepMode, &[0x01])?; + Ok(()) + } + + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.init(spi, delay)?; + Ok(()) + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.interface.cmd_with_data(spi, Command::WriteRam, buffer) + } + + fn update_partial_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + //TODO This is copied from epd2in9 but it seems not working. Partial refresh supported by version 2? + self.wait_until_idle(); + self.set_ram_area(spi, x, y, x + width, y + height)?; + self.set_ram_counter(spi, x, y)?; + + self.interface + .cmd_with_data(spi, Command::WriteRam, buffer)?; + Ok(()) + } + + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + // Enable clock signal, Enable Analog, Load temperature value, DISPLAY with DISPLAY Mode 1, Disable Analog, Disable OSC + self.interface + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xF7])?; + self.interface.cmd(spi, Command::MasterActivation)?; + self.wait_until_idle(); + 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(); + + // clear the ram with the background color + let color = self.background_color.get_byte_value(); + + self.interface.cmd(spi, Command::WriteRam)?; + self.interface.data_x_times(spi, color, WIDTH / 8 * HEIGHT) + } + + fn set_background_color(&mut self, background_color: Color) { + self.background_color = background_color; + } + + fn background_color(&self) -> &Color { + &self.background_color + } + + fn set_lut( + &mut self, + _spi: &mut SPI, + refresh_rate: Option, + ) -> Result<(), SPI::Error> { + if let Some(refresh_lut) = refresh_rate { + self.refresh = refresh_lut; + } + Ok(()) + } + + fn is_busy(&self) -> bool { + self.interface.is_busy(IS_BUSY_LOW) + } +} + +impl Epd2in9 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + fn wait_until_idle(&mut self) { + self.interface.wait_until_idle(IS_BUSY_LOW); + } + + fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + // choose full frame/ram + self.set_ram_area(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?; + + // start from the beginning + self.set_ram_counter(spi, 0, 0) + } + + fn set_ram_area( + &mut self, + spi: &mut SPI, + start_x: u32, + start_y: u32, + end_x: u32, + end_y: u32, + ) -> Result<(), SPI::Error> { + assert!(start_x < end_x); + assert!(start_y < end_y); + + // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram + // aren't relevant + self.interface.cmd_with_data( + spi, + Command::SetRamXAddressStartEndPosition, + &[(start_x >> 3) as u8, (end_x >> 3) as u8], + )?; + + // 2 Databytes: A[7:0] & 0..A[8] for each - start and end + self.interface.cmd_with_data( + spi, + Command::SetRamYAddressStartEndPosition, + &[ + start_y as u8, + (start_y >> 8) as u8, + end_y as u8, + (end_y >> 8) as u8, + ], + ) + } + + fn set_ram_counter(&mut self, spi: &mut SPI, x: u32, y: u32) -> Result<(), SPI::Error> { + self.wait_until_idle(); + // x is positioned in bytes, so the last 3 bits which show the position inside a byte in the ram + // aren't relevant + self.interface + .cmd_with_data(spi, Command::SetRamXAddressCounter, &[x as u8])?; + + // 2 Databytes: A[7:0] & 0..A[8] + self.interface.cmd_with_data( + spi, + Command::SetRamYAddressCounter, + &[y as u8, (y >> 8) as u8], + )?; + Ok(()) + } + + /// Set your own LUT, this function is also used internally for set_lut + fn set_lut_helper(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.interface + .cmd_with_data(spi, Command::WriteLutRegister, buffer)?; + self.wait_until_idle(); + Ok(()) + } +} + +impl QuickRefresh + for Epd2in9 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + /// 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.wait_until_idle(); + self.interface + .cmd_with_data(spi, Command::WriteRam, buffer)?; + self.interface + .cmd_with_data(spi, Command::WriteRam2, buffer) + } + + /// 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.wait_until_idle(); + self.interface.reset(delay, 2); + + self.set_lut_helper(spi, &LUT_PARTIAL_2IN9)?; + self.interface.cmd_with_data( + spi, + Command::WriteOtpSelection, + &[0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00], + )?; + self.interface + .cmd_with_data(spi, Command::BorderWaveformControl, &[0x80])?; + self.interface + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xC0])?; + self.interface.cmd(spi, Command::MasterActivation)?; + + self.wait_until_idle(); + + self.use_full_frame(spi)?; + + self.interface + .cmd_with_data(spi, Command::WriteRam, buffer)?; + Ok(()) + } + + /// For a quick refresh of the new updated frame. To be used immediately after `update_new_frame` + fn display_new_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.interface + .cmd_with_data(spi, Command::DisplayUpdateControl2, &[0x0F])?; + self.interface.cmd(spi, Command::MasterActivation)?; + self.wait_until_idle(); + Ok(()) + } + + /// Updates and displays the new frame. + fn update_and_display_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_new_frame(spi, buffer, delay)?; + self.display_new_frame(spi, delay)?; + Ok(()) + } + + /// Partial quick refresh not supported yet + #[allow(unused)] + fn update_partial_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + //TODO supported by display? + unimplemented!() + } + + /// Partial quick refresh not supported yet + #[allow(unused)] + fn update_partial_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + //TODO supported by display? + unimplemented!() + } + + /// Partial quick refresh not supported yet + #[allow(unused)] + fn clear_partial_frame( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + //TODO supported by display? + unimplemented!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 128); + assert_eq!(HEIGHT, 296); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } +} diff --git a/src/epd2in9bc/command.rs b/src/epd2in9bc/command.rs index a389a6f..7121c50 100644 --- a/src/epd2in9bc/command.rs +++ b/src/epd2in9bc/command.rs @@ -2,33 +2,32 @@ use crate::traits; #[allow(dead_code)] -#[allow(non_camel_case_types)] #[derive(Copy, Clone)] pub(crate) enum Command { - PANEL_SETTING = 0x00, + PanelSetting = 0x00, - POWER_SETTING = 0x01, - POWER_OFF = 0x02, - POWER_ON = 0x04, - BOOSTER_SOFT_START = 0x06, - DEEP_SLEEP = 0x07, - DATA_START_TRANSMISSION_1 = 0x10, - DISPLAY_REFRESH = 0x12, - DATA_START_TRANSMISSION_2 = 0x13, + PowerSetting = 0x01, + PowerOff = 0x02, + PowerOn = 0x04, + BoosterSoftStart = 0x06, + DeepSleep = 0x07, + DataStartTransmission1 = 0x10, + DisplayRefresh = 0x12, + DataStartTransmission2 = 0x13, - LUT_FOR_VCOM = 0x20, - LUT_WHITE_TO_WHITE = 0x21, - LUT_BLACK_TO_WHITE = 0x22, - LUT_WHITE_TO_BLACK = 0x23, - LUT_BLACK_TO_BLACK = 0x24, + LutForVcom = 0x20, + LutWhiteToWhite = 0x21, + LutBlackToWhite = 0x22, + LutWhiteToBlack = 0x23, + LutBlackToBlack = 0x24, - PLL_CONTROL = 0x30, - TEMPERATURE_SENSOR_COMMAND = 0x40, - TEMPERATURE_SENSOR_SELECTION = 0x41, - VCOM_AND_DATA_INTERVAL_SETTING = 0x50, - RESOLUTION_SETTING = 0x61, - VCM_DC_SETTING = 0x82, - POWER_SAVING = 0xE3, + PllControl = 0x30, + TemperatureSensor = 0x40, + TemperatureSensorSelection = 0x41, + VcomAndDataIntervalSetting = 0x50, + ResolutionSetting = 0x61, + VcmDcSetting = 0x82, + PowerSaving = 0xE3, } impl traits::Command for Command { diff --git a/src/epd2in9bc/graphics.rs b/src/epd2in9bc/graphics.rs index 16d12d1..dd8ce9d 100644 --- a/src/epd2in9bc/graphics.rs +++ b/src/epd2in9bc/graphics.rs @@ -1,7 +1,7 @@ use crate::epd2in9bc::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 2in9b/c EPD /// @@ -20,13 +20,22 @@ impl Default for Display2in9bc { } } -impl DrawTarget for Display2in9bc { +impl DrawTarget for Display2in9bc { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display2in9bc { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } diff --git a/src/epd2in9bc/mod.rs b/src/epd2in9bc/mod.rs index 4616c63..91ffeff 100644 --- a/src/epd2in9bc/mod.rs +++ b/src/epd2in9bc/mod.rs @@ -6,7 +6,7 @@ //!# use embedded_hal_mock::*; //!# fn main() -> Result<(), MockError> { //!use embedded_graphics::{ -//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle, +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, //!}; //!use epd_waveshare::{epd2in9bc::*, prelude::*}; //!# @@ -20,7 +20,7 @@ //!# let mut delay = delay::MockNoop::new(); //! //!// Setup EPD -//!let mut epd = EPD2in9bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//!let mut epd = Epd2in9bc::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; //! //!// Use display graphics from embedded-graphics //!// This display is for the black/white pixels @@ -46,10 +46,10 @@ //! &mono_display.buffer(), //! &chromatic_display.buffer() //!)?; -//!epd.display_frame(&mut spi)?; +//!epd.display_frame(&mut spi, &mut delay)?; //! //!// Set the EPD to sleep -//!epd.sleep(&mut spi)?; +//!epd.sleep(&mut spi, &mut delay)?; //!# Ok(()) //!# } //!``` @@ -60,7 +60,7 @@ use embedded_hal::{ use crate::interface::DisplayInterface; use crate::traits::{ - InternalWiAdditions, RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay, + InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, }; /// Width of epd2in9bc in pixels @@ -89,52 +89,49 @@ mod graphics; #[cfg(feature = "graphics")] pub use self::graphics::Display2in9bc; -/// EPD2in9bc driver -pub struct EPD2in9bc { - interface: DisplayInterface, +/// Epd2in9bc driver +pub struct Epd2in9bc { + interface: DisplayInterface, color: Color, } -impl InternalWiAdditions - for EPD2in9bc +impl InternalWiAdditions + for Epd2in9bc 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { // Values taken from datasheet and sample code self.interface.reset(delay, 10); // start the booster self.interface - .cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x17])?; + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?; // power on - self.command(spi, Command::POWER_ON)?; + self.command(spi, Command::PowerOn)?; delay.try_delay_ms(5); self.wait_until_idle(); // set the panel settings - self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x8F])?; + self.cmd_with_data(spi, Command::PanelSetting, &[0x8F])?; self.cmd_with_data( spi, - Command::VCOM_AND_DATA_INTERVAL_SETTING, + Command::VcomAndDataIntervalSetting, &[WHITE_BORDER | VCOM_DATA_INTERVAL], )?; // set resolution self.send_resolution(spi)?; - self.cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x0A])?; + self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0A])?; self.wait_until_idle(); @@ -142,14 +139,15 @@ where } } -impl WaveshareThreeColorDisplay - for EPD2in9bc +impl WaveshareThreeColorDisplay + for Epd2in9bc where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { fn update_color_frame( &mut self, @@ -165,8 +163,7 @@ where /// /// Finish by calling `update_chromatic_frame`. fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error> { - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; self.interface.data(spi, black)?; Ok(()) } @@ -179,8 +176,7 @@ where spi: &mut SPI, chromatic: &[u8], ) -> Result<(), SPI::Error> { - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface.data(spi, chromatic)?; self.wait_until_idle(); @@ -188,16 +184,18 @@ where } } -impl WaveshareDisplay - for EPD2in9bc +impl WaveshareDisplay + for Epd2in9bc where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { - fn new>( + type DisplayColor = Color; + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -208,35 +206,31 @@ where let interface = DisplayInterface::new(cs, busy, dc, rst); let color = DEFAULT_BACKGROUND_COLOR; - let mut epd = EPD2in9bc { interface, color }; + let mut epd = Epd2in9bc { interface, color }; epd.init(spi, delay)?; Ok(epd) } - fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { // Section 8.2 from datasheet self.interface.cmd_with_data( spi, - Command::VCOM_AND_DATA_INTERVAL_SETTING, + Command::VcomAndDataIntervalSetting, &[FLOATING_BORDER | VCOM_DATA_INTERVAL], )?; - self.command(spi, Command::POWER_OFF)?; - // The example STM code from Github has a wait after POWER_OFF + self.command(spi, Command::PowerOff)?; + // The example STM code from Github has a wait after PowerOff self.wait_until_idle(); - self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?; + self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; Ok(()) } - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error> { + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { self.init(spi, delay) } @@ -256,17 +250,20 @@ where HEIGHT } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.interface.cmd(spi, Command::DataStartTransmission1)?; - self.interface.data(spi, &buffer)?; + self.interface.data(spi, buffer)?; // Clear the chromatic layer let color = self.color.get_byte_value(); - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; self.wait_until_idle(); @@ -286,33 +283,36 @@ where Ok(()) } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { - self.command(spi, Command::DISPLAY_REFRESH)?; + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.command(spi, Command::DisplayRefresh)?; self.wait_until_idle(); Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.display_frame(spi)?; + 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) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.send_resolution(spi)?; let color = DEFAULT_BACKGROUND_COLOR.get_byte_value(); // Clear the black - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; // Clear the chromatic - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?; self.wait_until_idle(); @@ -322,7 +322,7 @@ where fn set_lut( &mut self, _spi: &mut SPI, - _refresh_rate: Option, + _refresh_rate: Option, ) -> Result<(), SPI::Error> { Ok(()) } @@ -332,13 +332,14 @@ where } } -impl EPD2in9bc +impl Epd2in9bc 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) @@ -358,14 +359,14 @@ where } fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW) + let _ = 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::RESOLUTION_SETTING)?; + self.command(spi, Command::ResolutionSetting)?; self.send_data(spi, &[w as u8])?; self.send_data(spi, &[(h >> 8) as u8])?; @@ -381,7 +382,7 @@ where }; self.cmd_with_data( spi, - Command::VCOM_AND_DATA_INTERVAL_SETTING, + Command::VcomAndDataIntervalSetting, &[border | VCOM_DATA_INTERVAL], ) } diff --git a/src/epd4in2/command.rs b/src/epd4in2/command.rs index 0d220b1..4d3a9fe 100644 --- a/src/epd4in2/command.rs +++ b/src/epd4in2/command.rs @@ -8,7 +8,6 @@ use crate::traits; /// /// The description of the single commands is mostly taken from IL0398.pdf #[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 @@ -17,142 +16,142 @@ pub(crate) enum Command { /// 0x1F B/W Mode, LUT from OTP /// 0x2F Red Mode, LUT set by registers /// 0x3F B/W Mode, LUT set by registers - PANEL_SETTING = 0x00, + PanelSetting = 0x00, /// selecting internal and external power /// self.send_data(0x03)?; //VDS_EN, VDG_EN /// self.send_data(0x00)?; //VCOM_HV, VGHL_LV[1], VGHL_LV[0] /// self.send_data(0x2b)?; //VDH /// self.send_data(0x2b)?; //VDL /// self.send_data(0xff)?; //VDHR - POWER_SETTING = 0x01, + PowerSetting = 0x01, /// After the Power Off command, the driver will power off following the Power Off Sequence. 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: floating. - POWER_OFF = 0x02, + PowerOff = 0x02, /// Setting Power OFF sequence - POWER_OFF_SEQUENCE_SETTING = 0x03, + PowerOffSequenceSetting = 0x03, /// Turning On the Power - POWER_ON = 0x04, + PowerOn = 0x04, /// This command enables the internal bandgap, which will be cleared by the next POF. - POWER_ON_MEASURE = 0x05, + PowerOnMeasure = 0x05, /// Starting data transmission /// 3-times: self.send_data(0x17)?; //07 0f 17 1f 27 2F 37 2f - BOOSTER_SOFT_START = 0x06, + BoosterSoftStart = 0x06, /// After this command is transmitted, the chip would enter the deep-sleep mode to save power. /// /// The deep sleep mode would return to standby by hardware reset. /// /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. - DEEP_SLEEP = 0x07, + DeepSleep = 0x07, /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. /// /// - In B/W mode, this command writes “OLD” data to SRAM. /// - In B/W/Red mode, this command writes “B/W” data to SRAM. /// - In Program mode, this command writes “OTP” data to SRAM for programming. - DATA_START_TRANSMISSION_1 = 0x10, + DataStartTransmission1 = 0x10, /// Stopping data transmission - DATA_STOP = 0x11, + DataStop = 0x11, /// While user sent this command, driver will refresh display (data/VCOM) according to SRAM data and LUT. /// /// After Display Refresh command, BUSY_N signal will become “0” and the refreshing of panel starts. - DISPLAY_REFRESH = 0x12, + DisplayRefresh = 0x12, /// This command starts transmitting data and write them into SRAM. To complete data transmission, command DSP (Data /// transmission Stop) must be issued. Then the chip will start to send data/VCOM for panel. /// - In B/W mode, this command writes “NEW” data to SRAM. /// - In B/W/Red mode, this command writes “RED” data to SRAM. - DATA_START_TRANSMISSION_2 = 0x13, + DataStartTransmission2 = 0x13, /// This command stores VCOM Look-Up Table with 7 groups of data. Each group contains information for one state and is stored /// with 6 bytes, while the sixth byte indicates how many times that phase will repeat. /// /// from IL0373 - LUT_FOR_VCOM = 0x20, + LutForVcom = 0x20, /// This command stores White-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. /// /// from IL0373 - LUT_WHITE_TO_WHITE = 0x21, + LutWhiteToWhite = 0x21, /// This command stores Black-to-White Look-Up Table with 7 groups of data. Each group contains information for one state and is /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. /// /// from IL0373 - LUT_BLACK_TO_WHITE = 0x22, + LutBlackToWhite = 0x22, /// This command stores White-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. /// /// from IL0373 - LUT_WHITE_TO_BLACK = 0x23, + LutWhiteToBlack = 0x23, /// This command stores Black-to-Black Look-Up Table with 7 groups of data. Each group contains information for one state and is /// stored with 6 bytes, while the sixth byte indicates how many times that phase will repeat. /// /// from IL0373 - LUT_BLACK_TO_BLACK = 0x24, + LutBlackToBlack = 0x24, /// The command controls the PLL clock frequency. - PLL_CONTROL = 0x30, + PllControl = 0x30, /// This command reads the temperature sensed by the temperature sensor. /// /// Doesn't work! Waveshare doesn't connect the read pin - TEMPERATURE_SENSOR_COMMAND = 0x40, + TemperatureSensor = 0x40, /// Selects the Internal or External temperature sensor and offset - TEMPERATURE_SENSOR_SELECTION = 0x41, + TemperatureSensorSelection = 0x41, /// Write External Temperature Sensor - TEMPERATURE_SENSOR_WRITE = 0x42, + TemperatureSensorWrite = 0x42, /// Read External Temperature Sensor /// /// Doesn't work! Waveshare doesn't connect the read pin - TEMPERATURE_SENSOR_READ = 0x43, + TemperatureSensorRead = 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, + VcomAndDataIntervalSetting = 0x50, /// This command indicates the input power condition. Host can read this flag to learn the battery condition. - LOW_POWER_DETECTION = 0x51, + LowPowerDetection = 0x51, /// This command defines non-overlap period of Gate and Source. - TCON_SETTING = 0x60, + TconSetting = 0x60, /// This command defines alternative resolution and this setting is of higher priority than the RES\[1:0\] in R00H (PSR). - RESOLUTION_SETTING = 0x61, + ResolutionSetting = 0x61, /// This command defines the Fist Active Gate and First Active Source of active channels. - GSST_SETTING = 0x65, + GsstSetting = 0x65, /// The LUT_REV / Chip Revision is read from OTP address = 0x001. /// /// Doesn't work! Waveshare doesn't connect the read pin - REVISION = 0x70, + Revision = 0x70, /// Read Flags. This command reads the IC status /// PTL, I2C_ERR, I2C_BUSY, DATA, PON, POF, BUSY /// /// Doesn't work! Waveshare doesn't connect the read pin - GET_STATUS = 0x71, + GetStatus = 0x71, /// Automatically measure VCOM. This command reads the IC status - AUTO_MEASUREMENT_VCOM = 0x80, + AutoMeasurementVcom = 0x80, /// This command gets the VCOM value /// /// Doesn't work! Waveshare doesn't connect the read pin - READ_VCOM_VALUE = 0x81, + ReadVcomValue = 0x81, /// Set VCM_DC - VCM_DC_SETTING = 0x82, + VcmDcSetting = 0x82, /// This command sets partial window - PARTIAL_WINDOW = 0x90, + PartialWindow = 0x90, /// This command makes the display enter partial mode - PARTIAL_IN = 0x91, + PartialIn = 0x91, /// This command makes the display exit partial mode and enter normal mode - PARTIAL_OUT = 0x92, + 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. /// /// The only one parameter is a check code, the command would be excuted if check code = 0xA5. - PROGRAM_MODE = 0xA0, + 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. - ACTIVE_PROGRAMMING = 0xA1, + 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. - READ_OTP = 0xA2, + ReadOtp = 0xA2, /// This command is set for saving power during fresh 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. - POWER_SAVING = 0xE3, + PowerSaving = 0xE3, } impl traits::Command for Command { @@ -169,10 +168,10 @@ mod tests { #[test] fn command_addr() { - assert_eq!(Command::POWER_SAVING.address(), 0xE3); + assert_eq!(Command::PowerSaving.address(), 0xE3); - assert_eq!(Command::PANEL_SETTING.address(), 0x00); + assert_eq!(Command::PanelSetting.address(), 0x00); - assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12); + assert_eq!(Command::DisplayRefresh.address(), 0x12); } } diff --git a/src/epd4in2/constants.rs b/src/epd4in2/constants.rs index 8ae34b9..a4c8037 100644 --- a/src/epd4in2/constants.rs +++ b/src/epd4in2/constants.rs @@ -1,109 +1,115 @@ +//! This file contains look-up-tables used to set voltages used during +//! various categories of pixel refreshes. + #[rustfmt::skip] pub(crate) const LUT_VCOM0: [u8; 44] = [ -0x00, 0x17, 0x00, 0x00, 0x00, 0x02, -0x00, 0x17, 0x17, 0x00, 0x00, 0x02, -0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// The commented-out line below was used in a Ben Krasnow video explaining +// partial refreshes. +// 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_VCOM0_QUICK: [u8; 44] = [ -0x00, 0x0E, 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, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0E, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WW: [u8; 42] =[ -0x40, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, -0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WW_QUICK: [u8; 42] =[ -0xA0, 0x0E, 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, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x0E, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BW: [u8; 42] =[ -0x40, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, -0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BW_QUICK: [u8; 42] =[ -0xA0, 0x0E, 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, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x0E, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BB: [u8; 42] =[ -0x80, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_BB_QUICK: [u8; 42] =[ -0x50, 0x0E, 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, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x0E, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WB: [u8; 42] =[ -0x80, 0x17, 0x00, 0x00, 0x00, 0x02, -0x90, 0x17, 0x17, 0x00, 0x00, 0x02, -0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, -0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; #[rustfmt::skip] pub(crate) const LUT_WB_QUICK: [u8; 42] =[ -0x50, 0x0E, 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, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x0E, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; diff --git a/src/epd4in2/graphics.rs b/src/epd4in2/graphics.rs index 101501e..739efb8 100644 --- a/src/epd4in2/graphics.rs +++ b/src/epd4in2/graphics.rs @@ -1,7 +1,7 @@ use crate::epd4in2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 4in2 EPD /// @@ -22,13 +22,21 @@ impl Default for Display4in2 { } } -impl DrawTarget for Display4in2 { +impl DrawTarget for Display4in2 { + type Color = BinaryColor; type Error = core::convert::Infallible; - - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display4in2 { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } @@ -59,7 +67,10 @@ mod tests { use crate::color::Color; use crate::epd4in2; use crate::graphics::{Display, DisplayRotation}; - use embedded_graphics::{primitives::Line, style::PrimitiveStyle}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; // test buffer length #[test] diff --git a/src/epd4in2/mod.rs b/src/epd4in2/mod.rs index 388f64f..9fc49a5 100644 --- a/src/epd4in2/mod.rs +++ b/src/epd4in2/mod.rs @@ -11,7 +11,7 @@ //!# use embedded_hal_mock::*; //!# fn main() -> Result<(), MockError> { //!use embedded_graphics::{ -//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle, +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, //!}; //!use epd_waveshare::{epd4in2::*, prelude::*}; //!# @@ -25,7 +25,7 @@ //!# let mut delay = delay::MockNoop::new(); //! //!// Setup EPD -//!let mut epd = EPD4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//!let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; //! //!// Use display graphics from embedded-graphics //!let mut display = Display4in2::default(); @@ -36,11 +36,11 @@ //! .draw(&mut display); //! //! // Display updated frame -//!epd.update_frame(&mut spi, &display.buffer())?; -//!epd.display_frame(&mut spi)?; +//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; //! //!// Set the EPD to sleep -//!epd.sleep(&mut spi)?; +//!epd.sleep(&mut spi, &mut delay)?; //!# Ok(()) //!# } //!``` @@ -55,7 +55,7 @@ use embedded_hal::{ }; use crate::interface::DisplayInterface; -use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay}; +use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay}; //The Lookup Tables for the Display mod constants; @@ -79,58 +79,64 @@ mod graphics; #[cfg(feature = "graphics")] pub use self::graphics::Display4in2; -/// EPD4in2 driver +/// Epd4in2 driver /// -pub struct EPD4in2 { +pub struct Epd4in2 { /// Connection Interface - interface: DisplayInterface, + interface: DisplayInterface, /// Background Color color: Color, /// Refresh LUT - refresh: RefreshLUT, + refresh: RefreshLut, } -impl InternalWiAdditions - for EPD4in2 +impl InternalWiAdditions + for Epd4in2 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { // reset the device self.interface.reset(delay, 10); // set the power settings self.interface.cmd_with_data( spi, - Command::POWER_SETTING, + Command::PowerSetting, &[0x03, 0x00, 0x2b, 0x2b, 0xff], )?; // start the booster self.interface - .cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x17])?; + .cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x17])?; // power on - self.command(spi, Command::POWER_ON)?; + self.command(spi, Command::PowerOn)?; delay.try_delay_ms(5); self.wait_until_idle(); // set the panel settings - self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x3F])?; + self.cmd_with_data(spi, Command::PanelSetting, &[0x3F])?; // Set Frequency, 200 Hz didn't work on my board // 150Hz and 171Hz wasn't tested yet // TODO: Test these other frequencies // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ DEFAULT: 3c 50Hz - self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x3A])?; + self.cmd_with_data(spi, Command::PllControl, &[0x3A])?; + + self.send_resolution(spi)?; + + self.interface + .cmd_with_data(spi, Command::VcmDcSetting, &[0x12])?; + + //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + self.interface + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x97])?; self.set_lut(spi, None)?; @@ -139,16 +145,18 @@ where } } -impl WaveshareDisplay - for EPD4in2 +impl WaveshareDisplay + for Epd4in2 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { - fn new>( + type DisplayColor = Color; + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -159,10 +167,10 @@ where let interface = DisplayInterface::new(cs, busy, dc, rst); let color = DEFAULT_BACKGROUND_COLOR; - let mut epd = EPD4in2 { + let mut epd = Epd4in2 { interface, color, - refresh: RefreshLUT::FULL, + refresh: RefreshLut::Full, }; epd.init(spi, delay)?; @@ -170,53 +178,44 @@ where Ok(epd) } - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error> { + 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> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.interface - .cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x17])?; //border floating - self.command(spi, Command::VCM_DC_SETTING)?; // VCOM to 0V - self.command(spi, Command::PANEL_SETTING)?; + .cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating + self.command(spi, Command::VcmDcSetting)?; // VCOM to 0V + self.command(spi, Command::PanelSetting)?; - self.command(spi, Command::POWER_SETTING)?; //VG&VS to 0V fast + self.command(spi, Command::PowerSetting)?; //VG&VS to 0V fast for _ in 0..4 { self.send_data(spi, &[0x00])?; } - self.command(spi, Command::POWER_OFF)?; + self.command(spi, Command::PowerOff)?; self.wait_until_idle(); self.interface - .cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?; + .cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; Ok(()) } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { self.wait_until_idle(); let color_value = self.color.get_byte_value(); - self.send_resolution(spi)?; - - self.interface - .cmd_with_data(spi, Command::VCM_DC_SETTING, &[0x12])?; - - //VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 - self.interface - .cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x97])?; - - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; self.interface .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; self.interface - .cmd_with_data(spi, Command::DATA_START_TRANSMISSION_2, buffer)?; + .cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; Ok(()) } @@ -235,8 +234,8 @@ where //return Err("Wrong buffersize"); } - self.command(spi, Command::PARTIAL_IN)?; - self.command(spi, Command::PARTIAL_WINDOW)?; + self.command(spi, Command::PartialIn)?; + self.command(spi, Command::PartialWindow)?; self.send_data(spi, &[(x >> 8) as u8])?; let tmp = x & 0xf8; self.send_data(spi, &[tmp as u8])?; // x should be the multiple of 8, the last 3 bit will always be ignored @@ -255,42 +254,45 @@ where //TODO: handle dtm somehow let is_dtm1 = false; if is_dtm1 { - self.command(spi, Command::DATA_START_TRANSMISSION_1)? //TODO: check if data_start transmission 1 also needs "old"/background data here + self.command(spi, Command::DataStartTransmission1)? //TODO: check if data_start transmission 1 also needs "old"/background data here } else { - self.command(spi, Command::DATA_START_TRANSMISSION_2)? + self.command(spi, Command::DataStartTransmission2)? } self.send_data(spi, buffer)?; - self.command(spi, Command::PARTIAL_OUT)?; + self.command(spi, Command::PartialOut)?; Ok(()) } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); - self.command(spi, Command::DISPLAY_REFRESH)?; + self.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.command(spi, Command::DISPLAY_REFRESH)?; + 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.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.send_resolution(spi)?; let color_value = self.color.get_byte_value(); - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_1)?; + self.interface.cmd(spi, Command::DataStartTransmission1)?; self.interface .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; - self.interface - .cmd(spi, Command::DATA_START_TRANSMISSION_2)?; + self.interface.cmd(spi, Command::DataStartTransmission2)?; self.interface .data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?; Ok(()) @@ -315,16 +317,16 @@ where fn set_lut( &mut self, spi: &mut SPI, - refresh_rate: Option, + refresh_rate: Option, ) -> Result<(), SPI::Error> { if let Some(refresh_lut) = refresh_rate { self.refresh = refresh_lut; } match self.refresh { - RefreshLUT::FULL => { + RefreshLut::Full => { self.set_lut_helper(spi, &LUT_VCOM0, &LUT_WW, &LUT_BW, &LUT_WB, &LUT_BB) } - RefreshLUT::QUICK => self.set_lut_helper( + RefreshLut::Quick => self.set_lut_helper( spi, &LUT_VCOM0_QUICK, &LUT_WW_QUICK, @@ -340,13 +342,14 @@ where } } -impl EPD4in2 +impl Epd4in2 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) @@ -366,14 +369,14 @@ where } fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW) + let _ = 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::RESOLUTION_SETTING)?; + self.command(spi, Command::ResolutionSetting)?; self.send_data(spi, &[(w >> 8) as u8])?; self.send_data(spi, &[w as u8])?; self.send_data(spi, &[(h >> 8) as u8])?; @@ -391,19 +394,196 @@ where ) -> Result<(), SPI::Error> { self.wait_until_idle(); // LUT VCOM - self.cmd_with_data(spi, Command::LUT_FOR_VCOM, lut_vcom)?; + self.cmd_with_data(spi, Command::LutForVcom, lut_vcom)?; // LUT WHITE to WHITE - self.cmd_with_data(spi, Command::LUT_WHITE_TO_WHITE, lut_ww)?; + self.cmd_with_data(spi, Command::LutWhiteToWhite, lut_ww)?; // LUT BLACK to WHITE - self.cmd_with_data(spi, Command::LUT_BLACK_TO_WHITE, lut_bw)?; + self.cmd_with_data(spi, Command::LutBlackToWhite, lut_bw)?; // LUT WHITE to BLACK - self.cmd_with_data(spi, Command::LUT_WHITE_TO_BLACK, lut_wb)?; + self.cmd_with_data(spi, Command::LutWhiteToBlack, lut_wb)?; // LUT BLACK to BLACK - self.cmd_with_data(spi, Command::LUT_BLACK_TO_BLACK, lut_bb)?; + self.cmd_with_data(spi, Command::LutBlackToBlack, lut_bb)?; + Ok(()) + } + + /// Helper function. Sets up the display to send pixel data to a custom + /// starting point. + pub fn shift_display( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.send_data(spi, &[(x >> 8) as u8])?; + let tmp = x & 0xf8; + self.send_data(spi, &[tmp as u8])?; // x should be the multiple of 8, the last 3 bit will always be ignored + let tmp = tmp + width - 1; + self.send_data(spi, &[(tmp >> 8) as u8])?; + self.send_data(spi, &[(tmp | 0x07) as u8])?; + + self.send_data(spi, &[(y >> 8) as u8])?; + self.send_data(spi, &[y as u8])?; + + self.send_data(spi, &[((y + height - 1) >> 8) as u8])?; + self.send_data(spi, &[(y + height - 1) as u8])?; + + self.send_data(spi, &[0x01])?; // Gates scan both inside and outside of the partial window. (default) + + Ok(()) + } +} + +impl QuickRefresh + for Epd4in2 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + /// To be followed immediately after by `update_old_frame`. + fn update_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + self.interface.data(spi, 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.wait_until_idle(); + // self.send_resolution(spi)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + + self.interface.data(spi, buffer)?; + + Ok(()) + } + + /// This is a wrapper around `display_frame` for using this device as a true + /// `QuickRefresh` device. + fn display_new_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.display_frame(spi, delay) + } + + /// This is wrapper around `update_new_frame` and `display_frame` for using + /// this device as a true `QuickRefresh` device. + /// + /// To be used immediately after `update_old_frame`. + fn update_and_display_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.update_new_frame(spi, buffer, delay)?; + self.display_frame(spi, delay) + } + + fn update_partial_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + + if buffer.len() as u32 != width / 8 * height { + //TODO: panic!! or sth like that + //return Err("Wrong buffersize"); + } + + self.interface.cmd(spi, Command::PartialIn)?; + self.interface.cmd(spi, Command::PartialWindow)?; + + self.shift_display(spi, x, y, width, height)?; + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + + self.interface.data(spi, buffer)?; + + Ok(()) + } + + /// Always call `update_partial_old_frame` before this, with buffer-updating code + /// between the calls. + fn update_partial_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + if buffer.len() as u32 != width / 8 * height { + //TODO: panic!! or sth like that + //return Err("Wrong buffersize"); + } + + self.shift_display(spi, x, y, width, height)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + + self.interface.data(spi, buffer)?; + + self.interface.cmd(spi, Command::PartialOut)?; + Ok(()) + } + + fn clear_partial_frame( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.send_resolution(spi)?; + + let color_value = self.color.get_byte_value(); + + self.interface.cmd(spi, Command::PartialIn)?; + self.interface.cmd(spi, Command::PartialWindow)?; + + self.shift_display(spi, x, y, width, height)?; + + self.interface.cmd(spi, Command::DataStartTransmission1)?; + self.interface + .data_x_times(spi, color_value, width / 8 * height)?; + + self.interface.cmd(spi, Command::DataStartTransmission2)?; + self.interface + .data_x_times(spi, color_value, width / 8 * height)?; + + self.interface.cmd(spi, Command::PartialOut)?; Ok(()) } } diff --git a/src/epd5in65f/command.rs b/src/epd5in65f/command.rs new file mode 100644 index 0000000..23d2246 --- /dev/null +++ b/src/epd5in65f/command.rs @@ -0,0 +1,150 @@ +//! SPI Commands for the Waveshare 7.5" E-Ink Display + +use crate::traits; + +/// EPD6in65f 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 + DataStartTransmission1 = 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, + + /// Image Process Command + ImageProcess = 0x13, + + /// This command builds the VCOM Look-Up Table (LUTC). + LutForVcom = 0x20, + /// This command builds the Black Look-Up Table (LUTB). + LutBlack = 0x21, + /// This command builds the White Look-Up Table (LUTW). + LutWhite = 0x22, + /// This command builds the Gray1 Look-Up Table (LUTG1). + LutGray1 = 0x23, + /// This command builds the Gray2 Look-Up Table (LUTG2). + LutGray2 = 0x24, + /// This command builds the Red0 Look-Up Table (LUTR0). + LutRed0 = 0x25, + /// This command builds the Red1 Look-Up Table (LUTR1). + LutRed1 = 0x26, + /// This command builds the Red2 Look-Up Table (LUTR2). + LutRed2 = 0x27, + /// This command builds the Red3 Look-Up Table (LUTR3). + LutRed3 = 0x28, + /// This command builds the XON Look-Up Table (LUTXON). + LutXon = 0x29, + + /// The command controls the PLL clock frequency. + PllControl = 0x30, + + /// This command reads the temperature sensed by the temperature sensor. + TemperatureSensor = 0x40, + /// This command selects the Internal or External temperature sensor. + TemperatureCalibration = 0x41, + /// This command could write data to the external temperature sensor. + TemperatureSensorWrite = 0x42, + /// This command could read data from the external temperature sensor. + TemperatureSensorRead = 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). + 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, + /// This command defines MCU host direct access external memory mode. + //SpiFlashControl = 0x65, + + /// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000. + //Revision = 0x70, + /// This command reads the IC status. + GetStatus = 0x71, + + /// This command implements related VCOM sensing setting. + //AutoMeasurementVcom = 0x80, + /// This command gets the VCOM value. + ReadVcomValue = 0x81, + /// This command sets `VCOM_DC` value. + VcmDcSetting = 0x82, + // /// This is in all the Waveshare controllers for EPD6in65f, but it's not documented + // /// anywhere in the datasheet `¯\_(ツ)_/¯` + FlashMode = 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/epd5in65f/graphics.rs b/src/epd5in65f/graphics.rs new file mode 100644 index 0000000..d58cd31 --- /dev/null +++ b/src/epd5in65f/graphics.rs @@ -0,0 +1,263 @@ +use crate::color::OctColor; +use crate::epd5in65f::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; +use crate::graphics::{DisplayRotation, OctDisplay}; +use embedded_graphics_core::prelude::*; + +/// Full size buffer for use with the 5in65f EPD +/// +/// Can also be manually constructed: +/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 2 * HEIGHT]` +pub struct Display5in65f { + buffer: [u8; WIDTH as usize * HEIGHT as usize / 2], + rotation: DisplayRotation, +} + +impl Default for Display5in65f { + fn default() -> Self { + Display5in65f { + buffer: [OctColor::colors_byte(DEFAULT_BACKGROUND_COLOR, DEFAULT_BACKGROUND_COLOR); + WIDTH as usize * HEIGHT as usize / 2], + rotation: DisplayRotation::default(), + } + } +} + +impl DrawTarget for Display5in65f { + type Color = OctColor; + 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(WIDTH, HEIGHT, pixel)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display5in65f { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +impl OctDisplay for Display5in65f { + 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::epd5in65f; + use crate::graphics::{DisplayRotation, OctDisplay}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; + + // test buffer length + #[test] + fn graphics_size() { + let display = Display5in65f::default(); + assert_eq!(display.buffer().len(), 448 * 600 / 2); + } + + // test default background color on all bytes + #[test] + fn graphics_default() { + let display = Display5in65f::default(); + for &byte in display.buffer() { + assert_eq!( + byte, + OctColor::colors_byte( + epd5in65f::DEFAULT_BACKGROUND_COLOR, + epd5in65f::DEFAULT_BACKGROUND_COLOR, + ) + ); + } + } + + #[test] + fn graphics_rotation_0() { + let mut display = Display5in65f::default(); + + let _ = Line::new(Point::new(0, 0), Point::new(1, 0)) + .into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + for &byte in buffer.iter().take(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok((OctColor::Black, OctColor::Black)) + ); + } + + for &byte in buffer.iter().skip(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok(( + epd5in65f::DEFAULT_BACKGROUND_COLOR, + epd5in65f::DEFAULT_BACKGROUND_COLOR + )) + ); + } + } + + #[test] + fn graphics_rotation_90() { + let mut display = Display5in65f::default(); + display.set_rotation(DisplayRotation::Rotate90); + + let _ = Line::new( + Point::new(0, WIDTH as i32 - 2), + Point::new(0, WIDTH as i32 - 1), + ) + .into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + for &byte in buffer.iter().take(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok((OctColor::Black, OctColor::Black)) + ); + } + + for &byte in buffer.iter().skip(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok(( + epd5in65f::DEFAULT_BACKGROUND_COLOR, + epd5in65f::DEFAULT_BACKGROUND_COLOR + )) + ); + } + } + + #[test] + fn graphics_rotation_180() { + let mut display = Display5in65f::default(); + display.set_rotation(DisplayRotation::Rotate180); + + let _ = Line::new( + Point::new(WIDTH as i32 - 2, HEIGHT as i32 - 1), + Point::new(WIDTH as i32 - 1, HEIGHT as i32 - 1), + ) + .into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + for &byte in buffer.iter().take(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok((OctColor::Black, OctColor::Black)) + ); + } + + for &byte in buffer.iter().skip(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok(( + epd5in65f::DEFAULT_BACKGROUND_COLOR, + epd5in65f::DEFAULT_BACKGROUND_COLOR + )) + ); + } + } + + #[test] + fn graphics_rotation_270() { + let mut display = Display5in65f::default(); + display.set_rotation(DisplayRotation::Rotate270); + + let _ = Line::new( + Point::new(HEIGHT as i32 - 1, 0), + Point::new(HEIGHT as i32 - 1, 1), + ) + .into_styled(PrimitiveStyle::with_stroke(OctColor::Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + for &byte in buffer.iter().take(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok((OctColor::Black, OctColor::Black)) + ); + } + + for &byte in buffer.iter().skip(1) { + assert_eq!( + OctColor::split_byte(byte), + Ok(( + epd5in65f::DEFAULT_BACKGROUND_COLOR, + epd5in65f::DEFAULT_BACKGROUND_COLOR + )) + ); + } + } + + #[test] + fn graphics_colors() { + let mut display = Display5in65f::default(); + + const COLORS: [OctColor; 8] = [ + OctColor::HiZ, + OctColor::White, + OctColor::Black, + OctColor::Red, + OctColor::Green, + OctColor::Orange, + OctColor::Blue, + OctColor::Yellow, + ]; + for c in &COLORS { + display.clear_buffer(*c); + for b in display.buffer() { + assert_eq!(OctColor::split_byte(*b), Ok((*c, *c))); + } + } + + for (w, c) in (0..WIDTH).zip(COLORS.iter().cycle()) { + let _ = Line::new( + Point::new(w as i32, 0), + Point::new(w as i32, HEIGHT as i32 - 1), + ) + .into_styled(PrimitiveStyle::with_stroke(*c, 1)) + .draw(&mut display); + } + + COLORS + .chunks(2) + .cycle() + .take(WIDTH as usize * 2) + .cycle() + .zip(display.buffer()) + .for_each(|(window, b)| match (window, b) { + (&[c1, c2], b) => { + assert_eq!(OctColor::split_byte(*b), Ok((c1, c2))); + } + _ => panic!("unexpected pattern"), + }) + } +} diff --git a/src/epd5in65f/mod.rs b/src/epd5in65f/mod.rs new file mode 100644 index 0000000..3199e38 --- /dev/null +++ b/src/epd5in65f/mod.rs @@ -0,0 +1,261 @@ +//! A simple Driver for the Waveshare 6.65 inch (F) E-Ink Display via SPI +//! +//! # References +//! +//! - [Datasheet](https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F)) +//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_5in65f.c) +//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd5in65f.py) + +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::{InputPin, OutputPin}, +}; + +use crate::color::OctColor; +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::Display5in65f; + +/// Width of the display +pub const WIDTH: u32 = 600; +/// Height of the display +pub const HEIGHT: u32 = 448; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: OctColor = OctColor::White; +const IS_BUSY_LOW: bool = true; + +/// Epd5in65f driver +/// +pub struct Epd5in65f { + /// Connection Interface + interface: DisplayInterface, + /// Background Color + color: OctColor, +} + +impl InternalWiAdditions + for Epd5in65f +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, 2); + + self.cmd_with_data(spi, Command::PanelSetting, &[0xEF, 0x08])?; + self.cmd_with_data(spi, Command::PowerSetting, &[0x37, 0x00, 0x23, 0x23])?; + self.cmd_with_data(spi, Command::PowerOffSequenceSetting, &[0x00])?; + self.cmd_with_data(spi, Command::BoosterSoftStart, &[0xC7, 0xC7, 0x1D])?; + self.cmd_with_data(spi, Command::PllControl, &[0x3C])?; + self.cmd_with_data(spi, Command::TemperatureSensor, &[0x00])?; + self.update_vcom(spi)?; + self.cmd_with_data(spi, Command::TconSetting, &[0x22])?; + self.send_resolution(spi)?; + + self.cmd_with_data(spi, Command::FlashMode, &[0xAA])?; + + delay.delay_ms(100); + + self.update_vcom(spi)?; + Ok(()) + } +} + +impl WaveshareDisplay + for Epd5in65f +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = OctColor; + 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 = Epd5in65f { 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.cmd_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_busy_high(); + self.update_vcom(spi)?; + self.send_resolution(spi)?; + self.cmd_with_data(spi, Command::DataStartTransmission1, buffer)?; + 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, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_busy_high(); + self.command(spi, Command::PowerOn)?; + self.wait_busy_high(); + self.command(spi, Command::DisplayRefresh)?; + self.wait_busy_high(); + self.command(spi, Command::PowerOff)?; + self.wait_busy_low(); + 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> { + let bg = OctColor::colors_byte(self.color, self.color); + self.wait_busy_high(); + self.update_vcom(spi)?; + self.send_resolution(spi)?; + self.command(spi, Command::DataStartTransmission1)?; + self.interface.data_x_times(spi, bg, WIDTH * HEIGHT / 2)?; + self.display_frame(spi, delay)?; + Ok(()) + } + + fn set_background_color(&mut self, color: OctColor) { + self.color = color; + } + + fn background_color(&self) -> &OctColor { + &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 Epd5in65f +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 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_busy_high(&mut self) { + let _ = self.interface.wait_until_idle(true); + } + fn wait_busy_low(&mut self) { + let _ = self.interface.wait_until_idle(false); + } + 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, &[(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]) + } + + fn update_vcom(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + let bg_color = (self.color.get_nibble() & 0b111) << 5; + self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17 | bg_color])?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 600); + assert_eq!(HEIGHT, 448); + assert_eq!(DEFAULT_BACKGROUND_COLOR, OctColor::White); + } +} diff --git a/src/epd7in5/command.rs b/src/epd7in5/command.rs index 064a5ee..8615269 100644 --- a/src/epd7in5/command.rs +++ b/src/epd7in5/command.rs @@ -2,136 +2,135 @@ use crate::traits; -/// EPD7in5 commands +/// 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, + PanelSetting = 0x00, /// Selecting internal and external power - POWER_SETTING = 0x01, + 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. - POWER_OFF = 0x02, + PowerOff = 0x02, /// Setting Power OFF sequence - POWER_OFF_SEQUENCE_SETTING = 0x03, + 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". - POWER_ON = 0x04, + PowerOn = 0x04, /// Starting data transmission - BOOSTER_SOFT_START = 0x06, + 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. - DEEP_SLEEP = 0x07, + 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. - DATA_START_TRANSMISSION_1 = 0x10, + DataStartTransmission1 = 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, + 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. - DISPLAY_REFRESH = 0x12, + DisplayRefresh = 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, + ImageProcess = 0x13, /// This command builds the VCOM Look-Up Table (LUTC). - LUT_FOR_VCOM = 0x20, + LutForVcom = 0x20, /// This command builds the Black Look-Up Table (LUTB). - LUT_BLACK = 0x21, + LutBlack = 0x21, /// This command builds the White Look-Up Table (LUTW). - LUT_WHITE = 0x22, + LutWhite = 0x22, /// This command builds the Gray1 Look-Up Table (LUTG1). - LUT_GRAY_1 = 0x23, + LutGray1 = 0x23, /// This command builds the Gray2 Look-Up Table (LUTG2). - LUT_GRAY_2 = 0x24, + LutGray2 = 0x24, /// This command builds the Red0 Look-Up Table (LUTR0). - LUT_RED_0 = 0x25, + LutRed0 = 0x25, /// This command builds the Red1 Look-Up Table (LUTR1). - LUT_RED_1 = 0x26, + LutRed1 = 0x26, /// This command builds the Red2 Look-Up Table (LUTR2). - LUT_RED_2 = 0x27, + LutRed2 = 0x27, /// This command builds the Red3 Look-Up Table (LUTR3). - LUT_RED_3 = 0x28, + LutRed3 = 0x28, /// This command builds the XON Look-Up Table (LUTXON). - LUT_XON = 0x29, + LutXon = 0x29, /// The command controls the PLL clock frequency. - PLL_CONTROL = 0x30, + PllControl = 0x30, /// This command reads the temperature sensed by the temperature sensor. - TEMPERATURE_SENSOR_COMMAND = 0x40, + TemperatureSensor = 0x40, /// This command selects the Internal or External temperature sensor. - TEMPERATURE_CALIBRATION = 0x41, + TemperatureCalibration = 0x41, /// This command could write data to the external temperature sensor. - TEMPERATURE_SENSOR_WRITE = 0x42, + TemperatureSensorWrite = 0x42, /// This command could read data from the external temperature sensor. - TEMPERATURE_SENSOR_READ = 0x43, + TemperatureSensorRead = 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, + VcomAndDataIntervalSetting = 0x50, /// This command indicates the input power condition. Host can read this flag to learn /// the battery condition. - LOW_POWER_DETECTION = 0x51, + LowPowerDetection = 0x51, /// This command defines non-overlap period of Gate and Source. - TCON_SETTING = 0x60, + TconSetting = 0x60, /// This command defines alternative resolution and this setting is of higher priority /// than the RES\[1:0\] in R00H (PSR). - TCON_RESOLUTION = 0x61, + TconResolution = 0x61, /// This command defines MCU host direct access external memory mode. - SPI_FLASH_CONTROL = 0x65, + SpiFlashControl = 0x65, /// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000. - REVISION = 0x70, + Revision = 0x70, /// This command reads the IC status. - GET_STATUS = 0x71, + GetStatus = 0x71, /// This command implements related VCOM sensing setting. - AUTO_MEASUREMENT_VCOM = 0x80, + AutoMeasurementVcom = 0x80, /// This command gets the VCOM value. - READ_VCOM_VALUE = 0x81, + ReadVcomValue = 0x81, /// This command sets `VCOM_DC` value. - VCM_DC_SETTING = 0x82, + VcmDcSetting = 0x82, - /// This is in all the Waveshare controllers for EPD7in5, but it's not documented + /// This is in all the Waveshare controllers for Epd7in5, but it's not documented /// anywhere in the datasheet `¯\_(ツ)_/¯` - FLASH_MODE = 0xE5, + FlashMode = 0xE5, } impl traits::Command for Command { @@ -148,7 +147,7 @@ mod tests { #[test] fn command_addr() { - assert_eq!(Command::PANEL_SETTING.address(), 0x00); - assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12); + assert_eq!(Command::PanelSetting.address(), 0x00); + assert_eq!(Command::DisplayRefresh.address(), 0x12); } } diff --git a/src/epd7in5/graphics.rs b/src/epd7in5/graphics.rs index 62d1563..e087cdc 100644 --- a/src/epd7in5/graphics.rs +++ b/src/epd7in5/graphics.rs @@ -1,7 +1,7 @@ use crate::epd7in5::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 7in5 EPD /// @@ -22,13 +22,22 @@ impl Default for Display7in5 { } } -impl DrawTarget for Display7in5 { +impl DrawTarget for Display7in5 { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display7in5 { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } @@ -59,7 +68,10 @@ mod tests { use crate::color::Color; use crate::epd7in5; use crate::graphics::{Display, DisplayRotation}; - use embedded_graphics::{primitives::Line, style::PrimitiveStyle}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; // test buffer length #[test] diff --git a/src/epd7in5/mod.rs b/src/epd7in5/mod.rs index 34a19f5..3ee0bfe 100644 --- a/src/epd7in5/mod.rs +++ b/src/epd7in5/mod.rs @@ -13,7 +13,7 @@ use embedded_hal::{ use crate::color::Color; use crate::interface::DisplayInterface; -use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay}; +use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay}; pub(crate) mod command; use self::command::Command; @@ -31,84 +31,83 @@ pub const HEIGHT: u32 = 384; pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; const IS_BUSY_LOW: bool = true; -/// EPD7in5 driver +/// Epd7in5 driver /// -pub struct EPD7in5 { +pub struct Epd7in5 { /// Connection Interface - interface: DisplayInterface, + interface: DisplayInterface, /// Background Color color: Color, } -impl InternalWiAdditions - for EPD7in5 +impl InternalWiAdditions + for Epd7in5 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { // Reset the device self.interface.reset(delay, 10); // Set the power settings - self.cmd_with_data(spi, Command::POWER_SETTING, &[0x37, 0x00])?; + self.cmd_with_data(spi, Command::PowerSetting, &[0x37, 0x00])?; // Set the panel settings: // - 600 x 448 // - Using LUT from external flash - self.cmd_with_data(spi, Command::PANEL_SETTING, &[0xCF, 0x08])?; + self.cmd_with_data(spi, Command::PanelSetting, &[0xCF, 0x08])?; // Start the booster - self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0xC7, 0xCC, 0x28])?; + self.cmd_with_data(spi, Command::BoosterSoftStart, &[0xC7, 0xCC, 0x28])?; // Power on - self.command(spi, Command::POWER_ON)?; + self.command(spi, Command::PowerOn)?; delay.try_delay_ms(5); self.wait_until_idle(); // Set the clock frequency to 50Hz (default) - self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x3C])?; + self.cmd_with_data(spi, Command::PllControl, &[0x3C])?; // Select internal temperature sensor (default) - self.cmd_with_data(spi, Command::TEMPERATURE_CALIBRATION, &[0x00])?; + self.cmd_with_data(spi, Command::TemperatureCalibration, &[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])?; + self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x77])?; // Set S2G and G2S non-overlap periods to 12 (default) - self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?; + self.cmd_with_data(spi, Command::TconSetting, &[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])?; + self.cmd_with_data(spi, Command::VcmDcSetting, &[0x1E])?; - // This is in all the Waveshare controllers for EPD7in5 - self.cmd_with_data(spi, Command::FLASH_MODE, &[0x03])?; + // This is in all the Waveshare controllers for Epd7in5 + self.cmd_with_data(spi, Command::FlashMode, &[0x03])?; self.wait_until_idle(); Ok(()) } } -impl WaveshareDisplay - for EPD7in5 +impl WaveshareDisplay + for Epd7in5 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { - fn new>( + type DisplayColor = Color; + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -119,32 +118,33 @@ where let interface = DisplayInterface::new(cs, busy, dc, rst); let color = DEFAULT_BACKGROUND_COLOR; - let mut epd = EPD7in5 { interface, 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> { + 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> { + fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); - self.command(spi, Command::POWER_OFF)?; + self.command(spi, Command::PowerOff)?; self.wait_until_idle(); - self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?; + self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; Ok(()) } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { self.wait_until_idle(); - self.command(spi, Command::DATA_START_TRANSMISSION_1)?; + self.command(spi, Command::DataStartTransmission1)?; for byte in buffer { let mut temp = *byte; for _ in 0..4 { @@ -171,24 +171,29 @@ where unimplemented!(); } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn display_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); - self.command(spi, Command::DISPLAY_REFRESH)?; + self.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.command(spi, Command::DISPLAY_REFRESH)?; + 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.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { + fn clear_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> { self.wait_until_idle(); self.send_resolution(spi)?; // The Waveshare controllers all implement clear using 0x33 - self.command(spi, Command::DATA_START_TRANSMISSION_1)?; + self.command(spi, Command::DataStartTransmission1)?; self.interface .data_x_times(spi, 0x33, WIDTH / 8 * HEIGHT * 4)?; Ok(()) @@ -213,7 +218,7 @@ where fn set_lut( &mut self, _spi: &mut SPI, - _refresh_rate: Option, + _refresh_rate: Option, ) -> Result<(), SPI::Error> { unimplemented!(); } @@ -223,13 +228,14 @@ where } } -impl EPD7in5 +impl Epd7in5 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) @@ -249,14 +255,14 @@ where } fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW) + let _ = 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.command(spi, Command::TconResolution)?; self.send_data(spi, &[(w >> 8) as u8])?; self.send_data(spi, &[w as u8])?; self.send_data(spi, &[(h >> 8) as u8])?; diff --git a/src/epd7in5_hd/command.rs b/src/epd7in5_hd/command.rs new file mode 100644 index 0000000..946ace8 --- /dev/null +++ b/src/epd7in5_hd/command.rs @@ -0,0 +1,159 @@ +//! 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 { + DriverOutputControl = 0x01, + + /// Set gate driving voltage + GateDrivingVoltageControl = 0x03, + + /// Set source driving voltage + SourceDrivingVoltageControl = 0x04, + + SoftStart = 0x0C, + + /// Set the scanning start position of the gate driver. + /// The valid range is from 0 to 679. + GateScanStartPosition = 0x0F, + + /// Deep sleep mode control + DeepSleep = 0x10, + + /// Define data entry sequence + DataEntry = 0x11, + + /// resets the commands and parameters to their S/W Reset default values except R10h-Deep Sleep Mode. + /// During operation, BUSY pad will output high. + /// Note: RAM are unaffected by this command. + SwReset = 0x12, + + /// After this command initiated, HV Ready detection starts. + /// BUSY pad will output high during detection. + /// The detection result can be read from the Status Bit Read (Command 0x2F). + HvReadyDetection = 0x14, + + /// After this command initiated, VCI detection starts. + /// BUSY pad will output high during detection. + /// The detection result can be read from the Status Bit Read (Command 0x2F). + VciDetection = 0x15, + + /// Temperature Sensor Selection + TemperatureSensorControl = 0x18, + + /// Write to temperature register + TemperatureSensorWrite = 0x1A, + + /// Read from temperature register + TemperatureSensorRead = 0x1B, + + /// Write Command to External temperature sensor. + TemperatureSensorWriteExternal = 0x1C, + + /// Activate Display Update Sequence + MasterActivation = 0x20, + + /// RAM content option for Display Update + DisplayUpdateControl1 = 0x21, + + /// Display Update Sequence Option + DisplayUpdateControl2 = 0x22, + + /// After this command, data entries will be written into the BW RAM until another command is written + WriteRamBw = 0x24, + + /// After this command, data entries will be written into the RED RAM until another command is written + WriteRamRed = 0x26, + + /// Fetch data from RAM + ReadRam = 0x27, + + /// Enter VCOM sensing conditions + VcomSense = 0x28, + + /// Enter VCOM sensing conditions + VcomSenseDuration = 0x29, + + /// Program VCOM register into OTP + VcomProgramOtp = 0x2A, + + /// Reduces a glitch when ACVCOM is toggled + VcomControl = 0x2B, + + /// Write VCOM register from MCU interface + VcomWrite = 0x2C, + + /// Read Register for Display Option + OtpRead = 0x2D, + + /// CRC calculation command for OTP content validation + CrcCalculation = 0x34, + + /// CRC Status Read + CrcRead = 0x35, + + /// Program OTP Selection according to the OTP Selection Control + ProgramSelection = 0x36, + + /// Write Register for Display Option + DisplayOptionWrite = 0x37, + + /// Write register for User ID + UserIdWrite = 0x38, + + /// Select border waveform for VBD + VbdControl = 0x3C, + + /// Read RAM Option + ReadRamOption = 0x41, + + /// Specify the start/end positions of the window address in the X direction by an address unit for RAM + SetRamXStartEnd = 0x44, + + /// Specify the start/end positions of the window address in the Y direction by an address unit for RAM + SetRamYStartEnd = 0x45, + + /// Auto write RED RAM for regular pattern + AutoWriteRed = 0x46, + + /// Auto write B/W RAM for regular pattern + AutoWriteBw = 0x47, + + /// Make initial settings for the RAM X address in the address counter (AC) + SetRamXAc = 0x4E, + + /// Make initial settings for the RAM Y address in the address counter (AC) + SetRamYAc = 0x4F, + + /// This command is an empty command; it does not have any effect on the display module. + /// However, it can be used to terminate Frame Memory Write or Read Commands. + Nop = 0x7F, +} + +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::MasterActivation.address(), 0x20); + assert_eq!(Command::SwReset.address(), 0x12); + assert_eq!(Command::DisplayUpdateControl2.address(), 0x22); + } +} diff --git a/src/epd7in5_hd/graphics.rs b/src/epd7in5_hd/graphics.rs new file mode 100644 index 0000000..c731b1b --- /dev/null +++ b/src/epd7in5_hd/graphics.rs @@ -0,0 +1,161 @@ +use crate::epd7in5_hd::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; +use crate::graphics::{Display, DisplayRotation}; +use embedded_graphics_core::pixelcolor::BinaryColor; +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 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 DrawTarget for Display7in5 { + type Color = BinaryColor; + 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(WIDTH, HEIGHT, pixel)?; + } + Ok(()) + } +} + +impl OriginDimensions for Display7in5 { + fn size(&self) -> Size { + Size::new(WIDTH, HEIGHT) + } +} + +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::{Black, Color}; + use crate::epd7in5_hd; + use crate::graphics::{Display, DisplayRotation}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; + + // test buffer length + #[test] + fn graphics_size() { + let display = Display7in5::default(); + assert_eq!(display.buffer().len(), 58080); + } + + // test default background color on all bytes + #[test] + fn graphics_default() { + let display = Display7in5::default(); + for &byte in display.buffer() { + assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_0() { + let mut display = Display7in5::default(); + + let _ = Line::new(Point::new(0, 0), Point::new(7, 0)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_90() { + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate90); + + let _ = Line::new(Point::new(0, 872), Point::new(0, 879)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_180() { + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate180); + + let _ = Line::new(Point::new(872, 527), Point::new(879, 527)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } + + #[test] + fn graphics_rotation_270() { + let mut display = Display7in5::default(); + display.set_rotation(DisplayRotation::Rotate270); + + let _ = Line::new(Point::new(527, 0), Point::new(527, 7)) + .into_styled(PrimitiveStyle::with_stroke(Black, 1)) + .draw(&mut display); + + let buffer = display.buffer(); + + assert_eq!(buffer[0], Color::Black.get_byte_value()); + + for &byte in buffer.iter().skip(1) { + assert_eq!(byte, epd7in5_hd::DEFAULT_BACKGROUND_COLOR.get_byte_value()); + } + } +} diff --git a/src/epd7in5_hd/mod.rs b/src/epd7in5_hd/mod.rs new file mode 100644 index 0000000..d4f4983 --- /dev/null +++ b/src/epd7in5_hd/mod.rs @@ -0,0 +1,265 @@ +//! A simple Driver for the Waveshare 7.5" E-Ink Display (HD) via SPI +//! +//! Color values for this driver are inverted compared to the [EPD 7in5 V2 driver](crate::epd7in5_v2) +//! *EPD 7in5 HD:* White = 1/0xFF, Black = 0/0x00 +//! *EPD 7in5 V2:* White = 0/0x00, Black = 1/0xFF +//! +//! # References +//! +//! - [Datasheet](https://www.waveshare.com/w/upload/2/27/7inch_HD_e-Paper_Specification.pdf) +//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd7in5_HD.py) +//! +use embedded_hal::{ + blocking::{delay::*, spi::Write}, + digital::v2::{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; + +/// Width of the display +pub const WIDTH: u32 = 880; +/// Height of the display +pub const HEIGHT: u32 = 528; +/// Default Background Color +pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; // Inverted for HD as compared to 7in5 v2 (HD: 0xFF = White) +const IS_BUSY_LOW: bool = false; + +/// EPD7in5 (HD) 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, + DELAY: DelayMs, +{ + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + // Reset the device + self.interface.reset(delay, 2); + + // HD procedure as described here: + // https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd7in5_HD.py + // and as per specs: + // https://www.waveshare.com/w/upload/2/27/7inch_HD_e-Paper_Specification.pdf + + self.wait_until_idle(); + self.command(spi, Command::SwReset)?; + self.wait_until_idle(); + + self.cmd_with_data(spi, Command::AutoWriteRed, &[0xF7])?; + self.wait_until_idle(); + self.cmd_with_data(spi, Command::AutoWriteBw, &[0xF7])?; + self.wait_until_idle(); + + self.cmd_with_data(spi, Command::SoftStart, &[0xAE, 0xC7, 0xC3, 0xC0, 0x40])?; + + self.cmd_with_data(spi, Command::DriverOutputControl, &[0xAF, 0x02, 0x01])?; + + self.cmd_with_data(spi, Command::DataEntry, &[0x01])?; + + self.cmd_with_data(spi, Command::SetRamXStartEnd, &[0x00, 0x00, 0x6F, 0x03])?; + self.cmd_with_data(spi, Command::SetRamYStartEnd, &[0xAF, 0x02, 0x00, 0x00])?; + + self.cmd_with_data(spi, Command::VbdControl, &[0x05])?; + + self.cmd_with_data(spi, Command::TemperatureSensorControl, &[0x80])?; + + self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xB1])?; + + self.command(spi, Command::MasterActivation)?; + self.wait_until_idle(); + + self.cmd_with_data(spi, Command::SetRamXAc, &[0x00, 0x00])?; + self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?; + + Ok(()) + } +} + +impl WaveshareDisplay + for Epd7in5 +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + type DisplayColor = Color; + 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, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.cmd_with_data(spi, Command::DeepSleep, &[0x01])?; + Ok(()) + } + + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + _delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(); + self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?; + self.cmd_with_data(spi, Command::WriteRamBw, buffer)?; + self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xF7])?; + 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, _delay: &mut DELAY) -> Result<(), SPI::Error> { + self.command(spi, Command::MasterActivation)?; + self.wait_until_idle(); + 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> { + let pixel_count = WIDTH * HEIGHT / 8; + let background_color_byte = self.color.get_byte_value(); + + self.wait_until_idle(); + self.cmd_with_data(spi, Command::SetRamYAc, &[0x00, 0x00])?; + + for cmd in &[Command::WriteRamBw, Command::WriteRamRed] { + self.command(spi, *cmd)?; + self.interface + .data_x_times(spi, background_color_byte, pixel_count)?; + } + + self.cmd_with_data(spi, Command::DisplayUpdateControl2, &[0xF7])?; + self.command(spi, Command::MasterActivation)?; + 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, + DELAY: DelayMs, +{ + fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> { + self.interface.cmd(spi, command) + } + + 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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn epd_size() { + assert_eq!(WIDTH, 880); + assert_eq!(HEIGHT, 528); + assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White); + } +} diff --git a/src/epd7in5_v2/command.rs b/src/epd7in5_v2/command.rs index c239eda..95292d3 100644 --- a/src/epd7in5_v2/command.rs +++ b/src/epd7in5_v2/command.rs @@ -2,136 +2,135 @@ use crate::traits; -/// EPD7in5 commands +/// 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, + PanelSetting = 0x00, /// Selecting internal and external power - POWER_SETTING = 0x01, + 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. - POWER_OFF = 0x02, + PowerOff = 0x02, /// Setting Power OFF sequence - POWER_OFF_SEQUENCE_SETTING = 0x03, + 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". - POWER_ON = 0x04, + PowerOn = 0x04, /// Starting data transmission - BOOSTER_SOFT_START = 0x06, + 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. - DEEP_SLEEP = 0x07, + 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 - DATA_START_TRANSMISSION_1 = 0x10, + DataStartTransmission1 = 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, + 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. - DISPLAY_REFRESH = 0x12, + DisplayRefresh = 0x12, /// RED or NEW_DATA - DATA_START_TRANSMISSION_2 = 0x13, + DataStartTransmission2 = 0x13, /// Dual SPI - what for? - DUAL_SPI = 0x15, + DualSpi = 0x15, /// This command builds the VCOM Look-Up Table (LUTC). - LUT_FOR_VCOM = 0x20, + LutForVcom = 0x20, /// This command builds the Black Look-Up Table (LUTB). - LUT_BLACK = 0x21, + LutBlack = 0x21, /// This command builds the White Look-Up Table (LUTW). - LUT_WHITE = 0x22, + LutWhite = 0x22, /// This command builds the Gray1 Look-Up Table (LUTG1). - LUT_GRAY_1 = 0x23, + LutGray1 = 0x23, /// This command builds the Gray2 Look-Up Table (LUTG2). - LUT_GRAY_2 = 0x24, + LutGray2 = 0x24, /// This command builds the Red0 Look-Up Table (LUTR0). - LUT_RED_0 = 0x25, + LutRed0 = 0x25, /// This command builds the Red1 Look-Up Table (LUTR1). - LUT_RED_1 = 0x26, + LutRed1 = 0x26, /// This command builds the Red2 Look-Up Table (LUTR2). - LUT_RED_2 = 0x27, + LutRed2 = 0x27, /// This command builds the Red3 Look-Up Table (LUTR3). - LUT_RED_3 = 0x28, + LutRed3 = 0x28, /// This command builds the XON Look-Up Table (LUTXON). - LUT_XON = 0x29, + LutXon = 0x29, /// The command controls the PLL clock frequency. - PLL_CONTROL = 0x30, + PllControl = 0x30, /// This command reads the temperature sensed by the temperature sensor. - TEMPERATURE_SENSOR_COMMAND = 0x40, + TemperatureSensor = 0x40, /// This command selects the Internal or External temperature sensor. - TEMPERATURE_CALIBRATION = 0x41, + TemperatureCalibration = 0x41, /// This command could write data to the external temperature sensor. - TEMPERATURE_SENSOR_WRITE = 0x42, + TemperatureSensorWrite = 0x42, /// This command could read data from the external temperature sensor. - TEMPERATURE_SENSOR_READ = 0x43, + TemperatureSensorRead = 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, + VcomAndDataIntervalSetting = 0x50, /// This command indicates the input power condition. Host can read this flag to learn /// the battery condition. - LOW_POWER_DETECTION = 0x51, + LowPowerDetection = 0x51, /// This command defines non-overlap period of Gate and Source. - TCON_SETTING = 0x60, + TconSetting = 0x60, /// This command defines alternative resolution and this setting is of higher priority /// than the RES\[1:0\] in R00H (PSR). - TCON_RESOLUTION = 0x61, + TconResolution = 0x61, /// This command defines MCU host direct access external memory mode. - SPI_FLASH_CONTROL = 0x65, + SpiFlashControl = 0x65, /// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000. - REVISION = 0x70, + Revision = 0x70, /// This command reads the IC status. - GET_STATUS = 0x71, + GetStatus = 0x71, /// This command implements related VCOM sensing setting. - AUTO_MEASUREMENT_VCOM = 0x80, + AutoMeasurementVcom = 0x80, /// This command gets the VCOM value. - READ_VCOM_VALUE = 0x81, + ReadVcomValue = 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 + VcmDcSetting = 0x82, + // /// This is in all the Waveshare controllers for Epd7in5, but it's not documented // /// anywhere in the datasheet `¯\_(ツ)_/¯` - // FLASH_MODE = 0xE5, + // FlashMode = 0xE5, } impl traits::Command for Command { @@ -148,7 +147,7 @@ mod tests { #[test] fn command_addr() { - assert_eq!(Command::PANEL_SETTING.address(), 0x00); - assert_eq!(Command::DISPLAY_REFRESH.address(), 0x12); + assert_eq!(Command::PanelSetting.address(), 0x00); + assert_eq!(Command::DisplayRefresh.address(), 0x12); } } diff --git a/src/epd7in5_v2/graphics.rs b/src/epd7in5_v2/graphics.rs index 8c3211f..6550064 100644 --- a/src/epd7in5_v2/graphics.rs +++ b/src/epd7in5_v2/graphics.rs @@ -1,7 +1,7 @@ use crate::epd7in5_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH}; use crate::graphics::{Display, DisplayRotation}; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Full size buffer for use with the 7in5 EPD /// @@ -22,13 +22,22 @@ impl Default for Display7in5 { } } -impl DrawTarget for Display7in5 { +impl DrawTarget for Display7in5 { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(WIDTH, HEIGHT, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(WIDTH, HEIGHT, pixel)?; + } + Ok(()) } +} +impl OriginDimensions for Display7in5 { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } @@ -58,7 +67,10 @@ mod tests { use crate::color::{Black, Color}; use crate::epd7in5_v2; use crate::graphics::{Display, DisplayRotation}; - use embedded_graphics::{primitives::Line, style::PrimitiveStyle}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; // test buffer length #[test] diff --git a/src/epd7in5_v2/mod.rs b/src/epd7in5_v2/mod.rs index 3e3618e..93d527c 100644 --- a/src/epd7in5_v2/mod.rs +++ b/src/epd7in5_v2/mod.rs @@ -17,7 +17,7 @@ use embedded_hal::{ use crate::color::Color; use crate::interface::DisplayInterface; -use crate::traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay}; +use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay}; pub(crate) mod command; use self::command::Command; @@ -35,29 +35,26 @@ pub const HEIGHT: u32 = 480; pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White; const IS_BUSY_LOW: bool = true; -/// EPD7in5 (V2) driver +/// Epd7in5 (V2) driver /// -pub struct EPD7in5 { +pub struct Epd7in5 { /// Connection Interface - interface: DisplayInterface, + interface: DisplayInterface, /// Background Color color: Color, } -impl InternalWiAdditions - for EPD7in5 +impl InternalWiAdditions + for Epd7in5 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> { + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { // Reset the device self.interface.reset(delay, 2); @@ -66,31 +63,33 @@ where // and as per specs: // https://www.waveshare.com/w/upload/6/60/7.5inch_e-Paper_V2_Specification.pdf - self.cmd_with_data(spi, Command::BOOSTER_SOFT_START, &[0x17, 0x17, 0x27, 0x17])?; - self.cmd_with_data(spi, Command::POWER_SETTING, &[0x07, 0x17, 0x3F, 0x3F])?; - self.command(spi, Command::POWER_ON)?; - self.wait_until_idle(); - self.cmd_with_data(spi, Command::PANEL_SETTING, &[0x1F])?; - self.cmd_with_data(spi, Command::PLL_CONTROL, &[0x06])?; - self.cmd_with_data(spi, Command::TCON_RESOLUTION, &[0x03, 0x20, 0x01, 0xE0])?; - self.cmd_with_data(spi, Command::DUAL_SPI, &[0x00])?; - self.cmd_with_data(spi, Command::TCON_SETTING, &[0x22])?; - self.cmd_with_data(spi, Command::VCOM_AND_DATA_INTERVAL_SETTING, &[0x10, 0x07])?; - self.wait_until_idle(); + self.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x27, 0x17])?; + self.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x17, 0x3F, 0x3F])?; + self.command(spi, Command::PowerOn)?; + self.wait_until_idle(spi, delay)?; + self.cmd_with_data(spi, Command::PanelSetting, &[0x1F])?; + self.cmd_with_data(spi, Command::PllControl, &[0x06])?; + self.cmd_with_data(spi, Command::TconResolution, &[0x03, 0x20, 0x01, 0xE0])?; + self.cmd_with_data(spi, Command::DualSpi, &[0x00])?; + self.cmd_with_data(spi, Command::TconSetting, &[0x22])?; + self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x10, 0x07])?; + self.wait_until_idle(spi, delay)?; Ok(()) } } -impl WaveshareDisplay - for EPD7in5 +impl WaveshareDisplay + for Epd7in5 where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { - fn new>( + type DisplayColor = Color; + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -101,32 +100,33 @@ where let interface = DisplayInterface::new(cs, busy, dc, rst); let color = DEFAULT_BACKGROUND_COLOR; - let mut epd = EPD7in5 { interface, 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> { + 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.wait_until_idle(); - self.command(spi, Command::POWER_OFF)?; - self.wait_until_idle(); - self.cmd_with_data(spi, Command::DEEP_SLEEP, &[0xA5])?; + fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.command(spi, Command::PowerOff)?; + self.wait_until_idle(spi, delay)?; + self.cmd_with_data(spi, Command::DeepSleep, &[0xA5])?; Ok(()) } - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.wait_until_idle(); - self.cmd_with_data(spi, Command::DATA_START_TRANSMISSION_2, buffer)?; + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.cmd_with_data(spi, Command::DataStartTransmission2, buffer)?; Ok(()) } @@ -142,29 +142,34 @@ where unimplemented!(); } - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { - self.wait_until_idle(); - self.command(spi, Command::DISPLAY_REFRESH)?; + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; + self.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> { - self.update_frame(spi, buffer)?; - self.command(spi, Command::DISPLAY_REFRESH)?; + 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.command(spi, Command::DisplayRefresh)?; Ok(()) } - fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> { - self.wait_until_idle(); + fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> { + self.wait_until_idle(spi, delay)?; self.send_resolution(spi)?; - self.command(spi, Command::DATA_START_TRANSMISSION_1)?; + self.command(spi, Command::DataStartTransmission1)?; self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?; - self.command(spi, Command::DATA_START_TRANSMISSION_2)?; + self.command(spi, Command::DataStartTransmission2)?; self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?; - self.command(spi, Command::DISPLAY_REFRESH)?; + self.command(spi, Command::DisplayRefresh)?; Ok(()) } @@ -187,7 +192,7 @@ where fn set_lut( &mut self, _spi: &mut SPI, - _refresh_rate: Option, + _refresh_rate: Option, ) -> Result<(), SPI::Error> { unimplemented!(); } @@ -197,13 +202,14 @@ where } } -impl EPD7in5 +impl Epd7in5 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) @@ -222,15 +228,19 @@ where self.interface.cmd_with_data(spi, command, data) } - fn wait_until_idle(&mut self) { - self.interface.wait_until_idle(IS_BUSY_LOW) + 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::GetStatus)?; + delay.delay_ms(20); + } + Ok(()) } 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.command(spi, Command::TconResolution)?; self.send_data(spi, &[(w >> 8) as u8])?; self.send_data(spi, &[w as u8])?; self.send_data(spi, &[(h >> 8) as u8])?; diff --git a/src/graphics.rs b/src/graphics.rs index e378a64..5a19e4a 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,8 +1,9 @@ //! Graphics Support for EPDs use crate::buffer_len; -use crate::color::Color; -use embedded_graphics::{pixelcolor::BinaryColor, prelude::*}; +use crate::color::{Color, OctColor, TriColor}; +use embedded_graphics_core::pixelcolor::BinaryColor; +use embedded_graphics_core::prelude::*; /// Displayrotation #[derive(Clone, Copy)] @@ -29,7 +30,7 @@ impl Default for DisplayRotation { /// - Drawing (With the help of DrawTarget/Embedded Graphics) /// - Rotations /// - Clearing -pub trait Display: DrawTarget { +pub trait Display: DrawTarget { /// Clears the buffer of the display with the chosen background color fn clear_buffer(&mut self, background_color: Color) { for elem in self.get_mut_buffer().iter_mut() { @@ -85,6 +86,148 @@ pub trait Display: DrawTarget { } } +/// Necessary traits for all displays to implement for drawing +/// +/// Adds support for: +/// - Drawing (With the help of DrawTarget/Embedded Graphics) +/// - Rotations +/// - Clearing +pub trait TriDisplay: DrawTarget { + /// Clears the buffer of the display with the chosen background color + fn clear_buffer(&mut self, background_color: TriColor) { + for elem in self.get_mut_buffer().iter_mut() { + *elem = background_color.get_byte_value(); + } + } + + /// Returns the buffer + fn buffer(&self) -> &[u8]; + + /// Returns a mutable buffer + fn get_mut_buffer(&mut self) -> &mut [u8]; + + /// Sets the rotation of the display + fn set_rotation(&mut self, rotation: DisplayRotation); + + /// Get the current rotation of the display + fn rotation(&self) -> DisplayRotation; + + /// Get the offset into buffer where chromatic data starts + fn chromatic_offset(&self) -> usize; + + /// return the b/w part of the buffer + fn bw_buffer(&self) -> &[u8]; + + /// return the chromatic part of the buffer + fn chromatic_buffer(&self) -> &[u8]; + + /// Helperfunction for the Embedded Graphics draw trait + /// + /// Becomes uneccesary when const_generics become stablised + fn draw_helper_tri( + &mut self, + width: u32, + height: u32, + pixel: Pixel, + ) -> Result<(), Self::Error> { + let rotation = self.rotation(); + + let Pixel(point, color) = pixel; + if outside_display(point, width, height, rotation) { + return Ok(()); + } + + // Give us index inside the buffer and the bit-position in that u8 which needs to be changed + let (index, bit) = find_position(point.x as u32, point.y as u32, width, height, rotation); + let index = index as usize; + let offset = self.chromatic_offset(); + + let buffer = self.get_mut_buffer(); + + // "Draw" the Pixel on that bit + match color { + TriColor::Black => { + // clear bit in bw-buffer -> black + buffer[index] &= !bit; + // set bit in chromatic-buffer -> white + buffer[index + offset] |= bit; + } + TriColor::White => { + // set bit in bw-buffer -> white + buffer[index] |= bit; + // set bit in chromatic-buffer -> white + buffer[index + offset] |= bit; + } + TriColor::Chromatic => { + // set bit in b/w buffer (white) + buffer[index] |= bit; + // clear bit in chromatic buffer -> chromatic + buffer[index + offset] &= !bit; + } + } + Ok(()) + } +} + +/// Necessary traits for all displays to implement for drawing +/// +/// Adds support for: +/// - Drawing (With the help of DrawTarget/Embedded Graphics) +/// - Rotations +/// - Clearing +pub trait OctDisplay: DrawTarget { + /// Clears the buffer of the display with the chosen background color + fn clear_buffer(&mut self, background_color: OctColor) { + for elem in self.get_mut_buffer().iter_mut() { + *elem = OctColor::colors_byte(background_color, background_color); + } + } + + /// Returns the buffer + fn buffer(&self) -> &[u8]; + + /// Returns a mutable buffer + fn get_mut_buffer(&mut self) -> &mut [u8]; + + /// Sets the rotation of the display + fn set_rotation(&mut self, rotation: DisplayRotation); + + /// Get the current rotation of the display + fn rotation(&self) -> DisplayRotation; + + /// Helperfunction for the Embedded Graphics draw trait + /// + /// Becomes uneccesary when const_generics become stablised + fn draw_helper( + &mut self, + width: u32, + height: u32, + pixel: Pixel, + ) -> Result<(), Self::Error> { + let rotation = self.rotation(); + let buffer = self.get_mut_buffer(); + + let Pixel(point, color) = pixel; + if outside_display(point, width, height, rotation) { + return Ok(()); + } + + // Give us index inside the buffer and the bit-position in that u8 which needs to be changed + let (index, upper) = + find_oct_position(point.x as u32, point.y as u32, width, height, rotation); + let index = index as usize; + + // "Draw" the Pixel on that bit + let (mask, color_nibble) = if upper { + (0x0f, color.get_nibble() << 4) + } else { + (0xf0, color.get_nibble()) + }; + buffer[index] = (buffer[index] & mask) | color_nibble; + Ok(()) + } +} + /// A variable Display without a predefined buffer /// /// The buffer can be created as following: @@ -98,8 +241,7 @@ pub trait Display: DrawTarget { /// # use epd_waveshare::graphics::VarDisplay; /// # use epd_waveshare::color::Black; /// # use embedded_graphics::prelude::*; -/// # use embedded_graphics::primitives::{Circle, Line}; -/// # use embedded_graphics::style::PrimitiveStyle; +/// # use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle}; /// let width = 128; /// let height = 296; /// @@ -135,13 +277,22 @@ impl<'a> VarDisplay<'a> { } } -impl<'a> DrawTarget for VarDisplay<'a> { +impl<'a> DrawTarget for VarDisplay<'a> { + type Color = BinaryColor; type Error = core::convert::Infallible; - fn draw_pixel(&mut self, pixel: Pixel) -> Result<(), Self::Error> { - self.draw_helper(self.width, self.height, pixel) + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + for pixel in pixels { + self.draw_helper(self.width, self.height, pixel)?; + } + Ok(()) } +} +impl<'a> OriginDimensions for VarDisplay<'a> { fn size(&self) -> Size { Size::new(self.width, self.height) } @@ -149,7 +300,7 @@ impl<'a> DrawTarget for VarDisplay<'a> { impl<'a> Display for VarDisplay<'a> { fn buffer(&self) -> &[u8] { - &self.buffer + self.buffer } fn get_mut_buffer(&mut self) -> &mut [u8] { @@ -186,30 +337,46 @@ fn outside_display(p: Point, width: u32, height: u32, rotation: DisplayRotation) false } -#[rustfmt::skip] -//returns index position in the u8-slice and the bit-position inside that u8 -fn find_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u8) { +fn find_rotation(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u32) { let nx; let ny; match rotation { DisplayRotation::Rotate0 => { nx = x; ny = y; - }, + } DisplayRotation::Rotate90 => { nx = width - 1 - y; ny = x; - } , + } DisplayRotation::Rotate180 => { nx = width - 1 - x; ny = height - 1 - y; - }, + } DisplayRotation::Rotate270 => { nx = y; ny = height - 1 - x; - }, + } } + (nx, ny) +} +#[rustfmt::skip] +//returns index position in the u8-slice and the bit-position inside that u8 +fn find_oct_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, bool) { + let (nx, ny) = find_rotation(x, y, width, height, rotation); + ( + /* what byte address is this? */ + nx / 2 + (width / 2) * ny, + /* is this the lower nibble (within byte)? */ + (nx & 0x1) == 0, + ) +} + +#[rustfmt::skip] +//returns index position in the u8-slice and the bit-position inside that u8 +fn find_position(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u8) { + let (nx, ny) = find_rotation(x, y, width, height, rotation); ( nx / 8 + ((width + 7) / 8) * ny, 0x80 >> (nx % 8), @@ -221,7 +388,10 @@ mod tests { use super::{buffer_len, find_position, outside_display, Display, DisplayRotation, VarDisplay}; use crate::color::Black; use crate::color::Color; - use embedded_graphics::{prelude::*, primitives::Line, style::PrimitiveStyle}; + use embedded_graphics::{ + prelude::*, + primitives::{Line, PrimitiveStyle}, + }; #[test] fn buffer_clear() { diff --git a/src/interface.rs b/src/interface.rs index 70b8b47..04ab631 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -7,30 +7,34 @@ use embedded_hal::{ /// The Connection Interface of all (?) Waveshare EPD-Devices /// -pub(crate) struct DisplayInterface { +pub(crate) struct DisplayInterface { /// SPI _spi: PhantomData, + /// DELAY + _delay: PhantomData, /// CS for SPI cs: CS, /// Low for busy, Wait until display is ready! busy: BUSY, /// Data/Command Control Pin (High for data, Low for command) dc: DC, - /// Pin for Reseting + /// Pin for Resetting rst: RST, } -impl DisplayInterface +impl DisplayInterface where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { pub fn new(cs: CS, busy: BUSY, dc: DC, rst: RST) -> Self { DisplayInterface { _spi: PhantomData::default(), + _delay: PhantomData::default(), cs, busy, dc, @@ -51,13 +55,17 @@ where /// Basic function for sending an array of u8-values of data over spi /// - /// Enables direct interaction with the device with the help of [command()](EPD4in2::command()) + /// Enables direct interaction with the device with the help of [command()](Epd4in2::command()) pub(crate) fn data(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> { // high for data let _ = self.dc.try_set_high(); - // Transfer data (u8-array) over spi - self.write(spi, data) + for val in data.iter().copied() { + // Transfer data one u8 at a time over spi + self.write(spi, &[val])?; + } + + Ok(()) } /// Basic function for sending [Commands](Command) and the data belonging to it. @@ -107,7 +115,7 @@ where spi.try_write(data)?; } - // deativate spi with cs high + // deactivate spi with cs high let _ = self.cs.try_set_high(); Ok(()) @@ -127,13 +135,12 @@ where /// Most likely there was a mistake with the 2in9 busy connection /// //TODO: use the #cfg feature to make this compile the right way for the certain types pub(crate) fn wait_until_idle(&mut self, is_busy_low: bool) { - //tested: worked without the delay for all tested devices - //self.delay_ms(1); - + // //tested: worked without the delay for all tested devices + // //self.delay_ms(1); while self.is_busy(is_busy_low) { - //tested: REMOVAL of DELAY: it's only waiting for the signal anyway and should continue work asap - //old: shorten the time? it was 100 in the beginning - //self.delay_ms(5); + // //tested: REMOVAL of DELAY: it's only waiting for the signal anyway and should continue work asap + // //old: shorten the time? it was 100 in the beginning + // //self.delay_ms(5); } } @@ -157,12 +164,12 @@ where /// Resets the device. /// - /// Often used to awake the module from deep sleep. See [EPD4in2::sleep()](EPD4in2::sleep()) + /// Often used to awake the module from deep sleep. See [Epd4in2::sleep()](Epd4in2::sleep()) /// /// The timing of keeping the reset pin low seems to be important and different per device. /// Most displays seem to require keeping it low for 10ms, but the 7in5_v2 only seems to reset /// properly with 2ms - pub(crate) fn reset>(&mut self, delay: &mut DELAY, duration: u8) { + pub(crate) fn reset(&mut self, delay: &mut DELAY, duration: u8) { let _ = self.rst.try_set_high(); delay.try_delay_ms(10); diff --git a/src/lib.rs b/src/lib.rs index 6c4b94b..2fe9c5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ //!# use embedded_hal_mock::*; //!# fn main() -> Result<(), MockError> { //!use embedded_graphics::{ -//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle, +//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, //!}; //!use epd_waveshare::{epd1in54::*, prelude::*}; //!# @@ -28,22 +28,23 @@ //!# let mut delay = delay::MockNoop::new(); //! //!// Setup EPD -//!let mut epd = EPD1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +//!let mut epd = Epd1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; //! //!// Use display graphics from embedded-graphics //!let mut display = Display1in54::default(); //! //!// Use embedded graphics for drawing a line +//! //!let _ = Line::new(Point::new(0, 120), Point::new(0, 295)) //! .into_styled(PrimitiveStyle::with_stroke(Black, 1)) //! .draw(&mut display); //! //! // Display updated frame -//!epd.update_frame(&mut spi, &display.buffer())?; -//!epd.display_frame(&mut spi)?; +//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +//!epd.display_frame(&mut spi, &mut delay)?; //! //!// Set the EPD to sleep -//!epd.sleep(&mut spi)?; +//!epd.sleep(&mut spi, &mut delay)?; //!# Ok(()) //!# } //!``` @@ -74,23 +75,32 @@ mod interface; pub mod epd1in54; pub mod epd1in54b; +pub mod epd1in54c; pub mod epd2in13_v2; +pub mod epd2in13bc; +pub mod epd2in7b; pub mod epd2in9; +pub mod epd2in9_v2; pub mod epd2in9bc; pub mod epd4in2; +pub mod epd5in65f; pub mod epd7in5; +pub mod epd7in5_hd; pub mod epd7in5_v2; + pub(crate) mod type_a; /// Includes everything important besides the chosen Display pub mod prelude { - pub use crate::color::{Color, TriColor}; - pub use crate::traits::{RefreshLUT, WaveshareDisplay, WaveshareThreeColorDisplay}; + pub use crate::color::{Color, OctColor, TriColor}; + pub use crate::traits::{ + QuickRefresh, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay, + }; pub use crate::SPI_MODE; #[cfg(feature = "graphics")] - pub use crate::graphics::{Display, DisplayRotation}; + pub use crate::graphics::{Display, DisplayRotation, OctDisplay, TriDisplay}; } /// Computes the needed buffer length. Takes care of rounding up in case width @@ -99,9 +109,9 @@ pub mod prelude { /// unused /// bits width /// <----><------------------------> -/// [XXXXX210][76543210]...[76543210] ^ -/// [XXXXX210][76543210]...[76543210] | height -/// [XXXXX210][76543210]...[76543210] v +/// \[XXXXX210\]\[76543210\]...\[76543210\] ^ +/// \[XXXXX210\]\[76543210\]...\[76543210\] | height +/// \[XXXXX210\]\[76543210\]...\[76543210\] v pub const fn buffer_len(width: usize, height: usize) -> usize { (width + 7) / 8 * height } diff --git a/src/traits.rs b/src/traits.rs index e12fb9f..ad81034 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,3 @@ -use crate::color::Color; use core::marker::Sized; use embedded_hal::{ blocking::{delay::*, spi::Write}, @@ -13,27 +12,28 @@ pub(crate) trait Command { /// Seperates the different LUT for the Display Refresh process #[derive(Debug, Clone, PartialEq, Eq, Copy)] -pub enum RefreshLUT { +pub enum RefreshLut { /// The "normal" full Lookuptable for the Refresh-Sequence - FULL, + Full, /// The quick LUT where not the full refresh sequence is followed. /// This might lead to some - QUICK, + Quick, } -impl Default for RefreshLUT { +impl Default for RefreshLut { fn default() -> Self { - RefreshLUT::FULL + RefreshLut::Full } } -pub(crate) trait InternalWiAdditions +pub(crate) trait InternalWiAdditions where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { /// This initialises the EPD and powers it up /// @@ -45,22 +45,19 @@ where /// This function calls [reset](WaveshareDisplay::reset), /// so you don't need to call reset your self when trying to wake your device up /// after setting it to sleep. - fn init>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error>; + fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>; } /// Functions to interact with three color panels -pub trait WaveshareThreeColorDisplay: - WaveshareDisplay +pub trait WaveshareThreeColorDisplay: + WaveshareDisplay where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { /// Transmit data to the SRAM of the EPD /// @@ -95,7 +92,7 @@ where ///# use embedded_hal_mock::*; ///# fn main() -> Result<(), MockError> { ///use embedded_graphics::{ -/// pixelcolor::BinaryColor::On as Black, prelude::*, primitives::Line, style::PrimitiveStyle, +/// pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, ///}; ///use epd_waveshare::{epd4in2::*, prelude::*}; ///# @@ -109,37 +106,41 @@ where ///# let mut delay = delay::MockNoop::new(); /// ///// Setup EPD -///let mut epd = EPD4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +///let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; /// ///// Use display graphics from embedded-graphics ///let mut display = Display4in2::default(); /// ///// Use embedded graphics for drawing a line +/// ///let _ = Line::new(Point::new(0, 120), Point::new(0, 295)) /// .into_styled(PrimitiveStyle::with_stroke(Black, 1)) /// .draw(&mut display); /// /// // Display updated frame -///epd.update_frame(&mut spi, &display.buffer())?; -///epd.display_frame(&mut spi)?; +///epd.update_frame(&mut spi, &display.buffer(), &mut delay)?; +///epd.display_frame(&mut spi, &mut delay)?; /// ///// Set the EPD to sleep -///epd.sleep(&mut spi)?; +///epd.sleep(&mut spi, &mut delay)?; ///# Ok(()) ///# } ///``` -pub trait WaveshareDisplay +pub trait WaveshareDisplay where SPI: Write, CS: OutputPin, BUSY: InputPin, DC: OutputPin, RST: OutputPin, + DELAY: DelayMs, { + /// The Color Type used by the Display + type DisplayColor; /// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC /// /// This already initialises the device. - fn new>( + fn new( spi: &mut SPI, cs: CS, busy: BUSY, @@ -153,22 +154,18 @@ where /// Let the device enter deep-sleep mode to save power. /// /// The deep sleep mode returns to standby with a hardware reset. - fn sleep(&mut self, spi: &mut SPI) -> Result<(), SPI::Error>; + fn sleep(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>; /// Wakes the device up from sleep /// /// Also reintialises the device if necessary. - fn wake_up>( - &mut self, - spi: &mut SPI, - delay: &mut DELAY, - ) -> Result<(), SPI::Error>; + fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>; /// Sets the backgroundcolor for various commands like [clear_frame](WaveshareDisplay::clear_frame) - fn set_background_color(&mut self, color: Color); + fn set_background_color(&mut self, color: Self::DisplayColor); /// Get current background color - fn background_color(&self) -> &Color; + fn background_color(&self) -> &Self::DisplayColor; /// Get the width of the display fn width(&self) -> u32; @@ -177,7 +174,12 @@ where fn height(&self) -> u32; /// Transmit a full frame to the SRAM of the EPD - fn update_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error>; + fn update_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error>; /// Transmits partial data to the SRAM of the EPD /// @@ -197,15 +199,20 @@ where /// Displays the frame data from SRAM /// /// This function waits until the device isn`t busy anymore - fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error>; + fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>; /// Provide a combined update&display and save some time (skipping a busy check in between) - fn update_and_display_frame(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error>; + fn update_and_display_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error>; /// Clears the frame buffer on the EPD with the declared background color /// /// The background color can be changed with [`WaveshareDisplay::set_background_color`] - fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error>; + fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>; /// Trait for using various Waveforms from different LUTs /// E.g. for partial refreshes @@ -218,7 +225,7 @@ where fn set_lut( &mut self, spi: &mut SPI, - refresh_rate: Option, + refresh_rate: Option, ) -> Result<(), SPI::Error>; /// Checks if the display is busy transmitting data @@ -228,3 +235,118 @@ where /// if the device is still busy fn is_busy(&self) -> bool; } + +/// Allows quick refresh support for displays that support it; lets you send both +/// old and new frame data to support this. +/// +/// When using the quick refresh look-up table, the display must receive separate display +/// buffer data marked as old, and new. This is used to determine which pixels need to change, +/// and how they will change. This isn't required when using full refreshes. +/// +/// (todo: Example ommitted due to CI failures.) +/// Example: +///```rust, no_run +///# use embedded_hal_mock::*; +///# fn main() -> Result<(), MockError> { +///# use embedded_graphics::{ +///# pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyle}, +///# }; +///# use epd_waveshare::{epd4in2::*, prelude::*}; +///# use epd_waveshare::graphics::VarDisplay; +///# +///# let expectations = []; +///# let mut spi = spi::Mock::new(&expectations); +///# let expectations = []; +///# let cs_pin = pin::Mock::new(&expectations); +///# let busy_in = pin::Mock::new(&expectations); +///# let dc = pin::Mock::new(&expectations); +///# let rst = pin::Mock::new(&expectations); +///# let mut delay = delay::MockNoop::new(); +///# +///# // Setup EPD +///# let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?; +///let (x, y, frame_width, frame_height) = (20, 40, 80,80); +/// +///let mut buffer = [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 80 / 8 * 80]; +///let mut display = VarDisplay::new(frame_width, frame_height, &mut buffer); +/// +///epd.update_partial_old_frame(&mut spi, display.buffer(), x, y, frame_width, frame_height) +/// .ok(); +/// +///display.clear_buffer(Color::White); +///// Execute drawing commands here. +/// +///epd.update_partial_new_frame(&mut spi, display.buffer(), x, y, frame_width, frame_height) +/// .ok(); +///# Ok(()) +///# } +///``` +pub trait QuickRefresh +where + SPI: Write, + CS: OutputPin, + BUSY: InputPin, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayMs, +{ + /// Updates the old frame. + fn update_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error>; + + /// Updates the new frame. + fn update_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error>; + + /// Displays the new frame + fn display_new_frame(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error>; + + /// Updates and displays the new frame. + fn update_and_display_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + delay: &mut DELAY, + ) -> Result<(), SPI::Error>; + + /// Updates the old frame for a portion of the display. + fn update_partial_old_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error>; + + /// Updates the new frame for a portion of the display. + fn update_partial_new_frame( + &mut self, + spi: &mut SPI, + buffer: &[u8], + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error>; + + /// Clears the partial frame buffer on the EPD with the declared background color + /// The background color can be changed with [`WaveshareDisplay::set_background_color`] + fn clear_partial_frame( + &mut self, + spi: &mut SPI, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Result<(), SPI::Error>; +} diff --git a/src/type_a/command.rs b/src/type_a/command.rs index e966aa9..97af56a 100644 --- a/src/type_a/command.rs +++ b/src/type_a/command.rs @@ -2,13 +2,12 @@ use crate::traits; -/// EPD1in54 and EPD2IN9 commands +/// Epd1in54 and EPD2IN9 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 { /// Driver Output control @@ -17,58 +16,62 @@ pub(crate) enum Command { /// 0.. A[8] /// 0.. B[2:0] /// Default: Set A[8:0] = 0x127 and B[2:0] = 0x0 - DRIVER_OUTPUT_CONTROL = 0x01, + DriverOutputControl = 0x01, /// Booster Soft start control /// 3 Databytes: /// 1.. A[6:0] /// 1.. B[6:0] /// 1.. C[6:0] /// Default: A[7:0] = 0xCF, B[7:0] = 0xCE, C[7:0] = 0x8D - BOOSTER_SOFT_START_CONTROL = 0x0C, - GATE_SCAN_START_POSITION = 0x0F, + BoosterSoftStartControl = 0x0C, + GateScanStartPosition = 0x0F, //TODO: useful? - // GATE_SCAN_START_POSITION = 0x0F, + // GateScanStartPosition = 0x0F, /// Deep Sleep Mode Control /// 1 Databyte: /// 0.. A[0] /// Values: /// A[0] = 0: Normal Mode (POR) /// A[0] = 1: Enter Deep Sleep Mode - DEEP_SLEEP_MODE = 0x10, + DeepSleepMode = 0x10, // /// Data Entry mode setting - DATA_ENTRY_MODE_SETTING = 0x11, + DataEntryModeSetting = 0x11, - SW_RESET = 0x12, + SwReset = 0x12, - TEMPERATURE_SENSOR_CONTROL = 0x1A, + TemperatureSensorControl = 0x1A, - MASTER_ACTIVATION = 0x20, + MasterActivation = 0x20, - DISPLAY_UPDATE_CONTROL_1 = 0x21, + DisplayUpdateControl1 = 0x21, - DISPLAY_UPDATE_CONTROL_2 = 0x22, + DisplayUpdateControl2 = 0x22, - WRITE_RAM = 0x24, + WriteRam = 0x24, - WRITE_VCOM_REGISTER = 0x2C, + WriteRam2 = 0x26, - WRITE_LUT_REGISTER = 0x32, + WriteVcomRegister = 0x2C, - SET_DUMMY_LINE_PERIOD = 0x3A, + WriteLutRegister = 0x32, - SET_GATE_LINE_WIDTH = 0x3B, + WriteOtpSelection = 0x37, - BORDER_WAVEFORM_CONTROL = 0x3C, + SetDummyLinePeriod = 0x3A, - SET_RAM_X_ADDRESS_START_END_POSITION = 0x44, + SetGateLineWidth = 0x3B, - SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45, + BorderWaveformControl = 0x3C, - SET_RAM_X_ADDRESS_COUNTER = 0x4E, + SetRamXAddressStartEndPosition = 0x44, - SET_RAM_Y_ADDRESS_COUNTER = 0x4F, + SetRamYAddressStartEndPosition = 0x45, - NOP = 0xFF, + SetRamXAddressCounter = 0x4E, + + SetRamYAddressCounter = 0x4F, + + Nop = 0xFF, } impl traits::Command for Command { @@ -85,10 +88,10 @@ mod tests { #[test] fn command_addr() { - assert_eq!(Command::DRIVER_OUTPUT_CONTROL.address(), 0x01); + assert_eq!(Command::DriverOutputControl.address(), 0x01); - assert_eq!(Command::SET_RAM_X_ADDRESS_COUNTER.address(), 0x4E); + assert_eq!(Command::SetRamXAddressCounter.address(), 0x4E); - assert_eq!(Command::NOP.address(), 0xFF); + assert_eq!(Command::Nop.address(), 0xFF); } }