Compare commits

...

194 Commits
v0.2.0 ... main

Author SHA1 Message Date
Chris f49b6e98f5
Merge pull request #92 from akashihi/5in83
Added support for 5.83" Black/Red/White Display
2021-12-28 10:57:15 +01:00
Denis Chapligin 02c1e7d1b0 Fixed rotation tests for 5in83. 2021-12-26 13:40:47 +00:00
Denis Chaplygin 68d55463ab Color specific test fixes 2021-12-26 13:14:27 +00:00
Denis Chaplygin 4b0195f686 Fixed TriColor enum import in tests 2021-12-26 13:14:27 +00:00
Denis Chaplygin 271922a8b1 Added documentation and changelog entries. 2021-12-26 13:14:27 +00:00
Denis Chaplygin 132cae0e45 Inverted red channel for the partial update 2021-12-26 13:14:27 +00:00
Denis Chaplygin afddb07cc6 Fixed CI errors 2021-12-26 13:14:27 +00:00
Denis Chaplygin 9b20bae100 Added support for 5.83" Black/Red/White Display 2021-12-26 13:14:27 +00:00
Chris 2cb56b2ac1
Merge pull request #93 from caemor/Needless-borrow
Update graphics.rs to fix needless borrow
2021-12-15 21:36:25 +01:00
Chris abf3bba966
Update graphics.rs
Fix needless borrow
2021-12-15 21:31:30 +01:00
Chris 2f589c5ad8
Merge pull request #91 from ole-treichel/main
Fix pins for epd2in13_v2 example (#90)
2021-12-02 23:08:45 +01:00
Ole Treichel 8e7844c2e0 Add comment on pin layout for HAT 2021-12-02 21:32:21 +01:00
Ole Treichel f56c4c4aaa Fix pins for epd2in13_v2 example 2021-12-02 20:50:09 +01:00
Caemor bf810dc1dc Update Changelog for 0.5 2021-11-28 23:20:43 +01:00
Chris b1830d16c5
Merge pull request #83 from belak/aggressive-write
Write data over SPI 1 byte at a time
2021-11-28 23:03:12 +01:00
Chris 34a0d81b75
Merge pull request #88 from crzysdrs/main
Transposed color channels
2021-08-24 22:00:01 +02:00
Mitch Souders f6f8f3d1f5 Transposed color channels 2021-08-24 11:56:42 -07:00
Chris b3f9bc810a
Merge pull request #87 from crzysdrs/main
Add Color Conversions, Allow Changing Display Background Color thanks to @crzysdrs
2021-08-24 20:46:49 +02:00
Mitch Souders 965989638a Convert to signed types before subtraction 2021-08-24 10:48:43 -07:00
Mitch Souders e89c51cb84 Switch to embedded graphics core 2021-08-24 10:37:47 -07:00
Chris e80670f66a
Merge pull request #85 from jamwaffles/e-g-core-only
Make embedded-graphics a dev dependency thanks to @jamwaffles
2021-08-24 07:40:15 +02:00
Mitch Souders 875cc0dae3 Update color options, format 2021-08-23 22:31:15 -07:00
dependabot[bot] b8ba9fea93
Update embedded-hal-mock requirement from 0.7 to 0.8 (#84)
Updates the requirements on [embedded-hal-mock](https://github.com/dbrgn/embedded-hal-mock) to permit the latest version.
- [Release notes](https://github.com/dbrgn/embedded-hal-mock/releases)
- [Changelog](https://github.com/dbrgn/embedded-hal-mock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dbrgn/embedded-hal-mock/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: embedded-hal-mock
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-21 11:29:29 +02:00
Chris 493cbd9394
Fix clippy warning (#86)
* Fix clippy improvements warnings
2021-08-21 11:23:00 +02:00
James Waples a92eac02a4 Make embedded-graphics a dev dependency
e-g-core should be the only e-g dependency required for the driver
itself, so this PR changes a few of the imports to reflect that.
2021-08-20 18:10:59 +01:00
Kaleb Elwert f3e7dad83d Address PR feedback 2021-08-13 07:55:58 +01:00
Kaleb Elwert 247a78acb7 Write data over SPI 1 byte at a time
Fixes #82
2021-08-13 01:17:15 +01:00
Chris 2f31f4bebc
Merge pull request #81 from sirhcel/quickrefresh-interface-epd4in2
Provide full QuickRefresh interface for 4.2 inch display and some doc improvements thanks to @sirhcel
2021-07-15 07:57:15 +02:00
Chris b845319771
Update src/epd4in2/mod.rs 2021-07-15 07:54:13 +02:00
Christian Meusel 1a09b81ab6 Clean up some warnings from 'cargo doc' 2021-07-14 23:19:28 +02:00
Christian Meusel 3edeac413d Provide full QuickRefresh interface for 4in2 too
This display needs no special commands for displaying the new frame. But
it will come in handy to support the full QuickRefresh trait when it
comes to supporting different quick refresh capable displays from an
application.
2021-07-14 23:06:29 +02:00
Chris 239691e8d3
Merge pull request #78 from lrbalt/update-to-eg070
Migrate to embedded-graphics 0.7.0 (thanks to @lrbalt )
2021-07-01 16:35:31 +02:00
Reinier Balt 6a10e0cb96 Migrate remaining drivers 2021-06-16 17:36:12 +02:00
Reinier Balt feac908558 fix example for epd 2.13"
* use Circle::with_center
* the large text does not fit on this screen, add newline
2021-06-16 16:48:00 +02:00
Reinier Balt 5622627c1f fix remaining PrimitiveStyleBuilder conversions 2021-06-16 14:53:12 +02:00
Reinier Balt dd9a09aff4 use PrimitiveStyle::with_stroke, not PrimitiveStyleBuilder 2021-06-16 14:17:18 +02:00
Reinier Balt 21428ff82f fix fmt after rebase 2021-06-16 13:49:59 +02:00
Reinier Balt 78ffda673e migrate minimum set of drivers to make tests pass 2021-06-16 13:49:33 +02:00
Chris 253cc101fd
Merge pull request #76 from lrbalt/tri-color
Enable drawing in three colors for epd2in13 with TriColor thanks to @lrbalt
2021-06-15 10:46:40 +02:00
Reinier Balt 35ac132530 fix fmt 2021-06-09 11:46:59 +02:00
Reinier Balt 076b3a2d07 Update example to use TriColor 2021-06-09 11:41:25 +02:00
Reinier Balt a55a9970a5 cargo fmt 2021-05-30 14:28:09 +02:00
Reinier Balt c25d9f18d3 Enable drawing in three colors for epd2in13
Move from BinaryColor to TriColor:  use one Display fo drawing
2021-05-30 14:20:02 +02:00
Chris 401858c456
Merge pull request #75 from lrbalt/epd2in13bc
add support for 2in13bc tri-color e-ink display
2021-05-25 07:57:16 +02:00
Reinier Balt f7d86d8bfe add epd2in13bc to README and add some comments to example 2021-05-24 09:58:42 +02:00
Reinier Balt a4b8f5768e run cargo fmt 2021-05-24 09:58:42 +02:00
Reinier Balt 199c5ed298 add example for epd2in13bc using chromatic color besides black and white 2021-05-24 09:58:42 +02:00
Reinier Balt c552111ae8 add support for Waveshare e-Paper 2.13 inch tri-color screen
datasheet is same as the 2.9" version, except resolution
2021-05-24 09:58:42 +02:00
Reinier Balt 9dedf76c57 bring copy into 2in13bc namespace 2021-05-24 09:58:42 +02:00
Reinier Balt e1aada3b38 use epd2in9bc as base for implementing support epd2in13bc 2021-05-24 09:58:41 +02:00
Chris 6679869bc3
Update rust.yml
Fix ci only running for main-branch stuff
2021-05-16 14:26:51 +02:00
Chris d9254a68a2
Update CHANGELOG.md 2021-04-27 21:09:40 +02:00
Chris 56219e0d4b
Merge pull request #74 from andber1/epd-2in9-v2
Fixed epd2in9 quick refresh by @andber1
2021-04-27 21:02:01 +02:00
Andreas dc93687a86 Fixed epd2in9 quick refresh 2021-04-27 17:35:15 +02:00
Chris 89ec95d8c9
Merge pull request #73 from andber1/epd-2in9-v2
Added epd2in9_v2 support by @andber1
2021-04-21 09:03:34 +02:00
Andreas ae25129ada Merge branch 'fix-70' 2021-04-19 18:00:41 +02:00
Andreas 1cd1c3921d fixed documentation and formatting 2021-04-19 17:58:32 +02:00
Andreas 64e1b6ded0 Added epd2in9_v2 support 2021-04-19 17:58:32 +02:00
Caemor 8ef83eb88a Fix missing parts of merge of #71 2021-04-18 18:49:23 +02:00
Caemor 93b1a21cdb Merge branch 'olback-fix-70' 2021-04-18 18:15:43 +02:00
Caemor 980b2b9667 Merge branch 'fix-70' of https://github.com/olback/epd-waveshare into olback-fix-70 2021-04-18 18:13:40 +02:00
Chris ef1d2b97e9
Merge pull request #67 from whiite/main
Added support for the 7.5 inch HD display by @whiite
2021-04-15 12:57:54 +02:00
pi 3141865b62 epd7in5 HD: Fixed graphics tests 2021-04-15 10:49:39 +01:00
pi c94f6c4434 epd7in5 HD: Renamed epd7in5 struct for Clippy 2021-04-15 10:27:30 +01:00
pi 989a236fb1 epd7in5 HD: Rename Command enums for Clippy check 2021-04-15 10:20:52 +01:00
pi a1e9c17b53 epd7in5 HD: Corrected import of `RefreshLut` trait 2021-04-14 15:30:41 +01:00
pi f52ff0f1ca Merge branch 'main' of https://github.com/whiite/epd-waveshare into main 2021-04-14 15:22:36 +01:00
pi 72e6b82b2d epd7in5 HD: Formatted with cargo fmt 2021-04-14 15:22:33 +01:00
Liam 434f1bcd20
Merge branch 'main' into main 2021-04-14 15:15:06 +01:00
pi ba1243137d EPD 7in5 HD: Added more documentation
Updated README to include the driver.
Updated CHANGELOG.
Added note about the default background color of the HD driver
2021-04-14 14:59:23 +01:00
Chris ff6bf92d1c
Merge pull request #72 from caemor/Fix-CamelCase-Issue
Fix camel caseand other lowercase/acronym issues issue
2021-04-11 21:26:22 +02:00
Caemor 15e557951f Further improvements around acronym lowercases,... 2021-04-11 21:21:39 +02:00
Caemor 9d95eeb64b cargo fmt 2021-04-11 20:38:03 +02:00
Caemor ba1b90e2f3 Removed #[allow(non_camel_case_types)] to fix issues 2021-04-11 20:31:24 +02:00
Caemor 0567da80c0 Small impros to changelog and readme 2021-04-11 20:28:46 +02:00
Edwin Svensson c3a4cde815
skip closure/task 2021-04-11 16:57:28 +02:00
Edwin Svensson 04476e6671
fix tests 2021-04-04 22:26:50 +02:00
Edwin Svensson 9a1575b2ec
API change to partially fix #70 2021-04-04 22:10:50 +02:00
pi 73c98d86e7 Added back tests for epd7in5_hd commands 2021-03-31 11:09:58 +01:00
pi ba432ccf91 Removed unused code 2021-03-31 11:09:32 +01:00
pi c27ffd507f Added support for the 7.5 inch HD display 2021-03-31 10:39:29 +01:00
Chris a252d074de
Merge pull request #66 from BuggStream/feature/epd1in54c
Add support for epd1in54c
2021-02-20 16:58:21 +01:00
Chris 181010f366
Merge pull request #64 from caemor/Further4in2Improvements
Further 4in2 improvements
2021-02-20 16:56:26 +01:00
BuggStream b479edd108 Fix last rustfmt issue 2021-02-08 00:40:07 +01:00
BuggStream b49dd6cd1f Update documentation for 1in54c 2021-02-08 00:26:31 +01:00
BuggStream 6129723c41 Clean up and simplify code 2021-02-07 23:30:44 +01:00
BuggStream 6c8ac87c7d Ensure device is ready to receive data when updating frame 2021-02-07 01:19:30 +01:00
BuggStream d1b0ac1854 Add support for 1in54c EPD 2021-02-06 17:17:04 +01:00
Caemor b4c3722ece Updated Changelog 2021-01-28 12:08:13 +01:00
Chris 87037c6f52
Merge pull request #60 from pjsier/feat/epd2in7b
Add support for epd2in7b
2021-01-28 00:07:27 +01:00
Chris ae9ef59c21
Merge pull request #65 from caemor/assign_tests
Auto Assign tests
2021-01-27 21:32:28 +01:00
Caemor ce95d3d93f add auto_assign ymls 2021-01-27 21:26:13 +01:00
Caemor f9a09d9018 Update Changelog 2021-01-27 21:00:52 +01:00
Caemor 788ddcab98 Apply #48 to QuickRefresh as well 2021-01-27 20:53:24 +01:00
Caemor bcbef1e805 "EPD4in2: Don't set the resolution (and some more) over and over again.
If the resolution ever changes, something has horribly gone wrong." From @mgottschlag #48
2021-01-27 20:29:23 +01:00
Caemor ad9574f355 Add David & the changes to Changelog 2021-01-27 17:35:03 +01:00
Caemor 4ccbea5607 Add Example to quick refresh trait 2021-01-27 17:34:39 +01:00
Chris 4d2b6a5685
Merge pull request #62 from David-OConnor/partial-refresh
Added QuickRefresh trait, and implemented it for 4.2 display
2021-01-27 17:08:57 +01:00
Chris 2657b867be
Remove Travis and Codecov 2021-01-23 17:37:19 +01:00
Caemor 463afa52c6 delete travis + codecov 2021-01-23 17:01:11 +01:00
Caemor d572ef0f5d test it 2021-01-23 16:50:55 +01:00
Caemor ca3eebecbe Try with codecov.yml 2021-01-23 16:45:31 +01:00
Caemor 1a612c92d3 Fix missing Eq for OutofColorRangeParseError 2021-01-23 16:23:36 +01:00
Caemor bccfc81b13 Improve Stuff mentioned by clippy:
- use range.contains instead of >=  && <=
- Use better error type
2021-01-23 14:55:03 +01:00
David-OConnor 0695e94670 Removed example that broke CI 2021-01-21 21:29:37 +04:00
David-OConnor c30d213bc5 Added QuickRefresh trait, and implemented it for 4.2 display 2021-01-21 21:13:13 +04:00
pjsier 49cc823ccc fix epd2in7b reset delay 2021-01-18 13:20:58 -06:00
pjsier 8630869186 add epd2in7b to readme 2021-01-07 12:44:16 -05:00
pjsier fb25c40fe2 add support for epd2in7b 2021-01-01 10:59:12 -06:00
Chris 87e63741ef
Merge pull request #59 from crzysdrs/main
Add Support for 5.65 Inch 7 Color (F) Display
2020-11-20 10:01:40 +01:00
Mitch Souders e49e144a34 Minor fixups 2020-11-19 19:22:41 -08:00
Mitch Souders e225464b62 Cargo fmt 2020-11-15 18:11:58 -08:00
Mitch Souders 147eaf655d Updated README 2020-11-15 18:01:45 -08:00
Mitch Souders 231cc63009 Minor cleanup, added HiZ as color 2020-11-15 17:57:40 -08:00
Mitch Souders c7d32ca86e Added some busy checks and seem to be avoiding missing pictures 2020-11-15 05:32:17 -08:00
Mitch Souders 96d505647d Add support for ep5in65f 2020-11-15 03:25:10 -08:00
Caemor 82f0fe2d15 Fix CI 2020-10-20 12:41:28 +02:00
Chris 43aba4ddb1
Merge pull request #55 from icewind1991/reset-timing
Make reset timing device specific
2020-10-19 19:46:49 +02:00
Robin Appelman 674b88e327 use correct 7in5_v2 reset delay, not the b/c variant 2020-10-19 16:53:17 +02:00
Robin Appelman 5cb7de5929 make reset timing device specific
reset timings seem to be device specific, trying to reset the 7in5_v2 with timings from other devices does not reset the display.

Timings taken from https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd
2020-10-17 17:13:38 +02:00
Chris ac97aac745
Create dependabot.yml 2020-10-07 11:41:32 +02:00
Chris d8a41c7afb
Merge pull request #53 from dkm/patch-1
Add 2in13 to the list of supported screens
2020-10-05 23:04:57 +02:00
Marc Poulhiès e5cc087933 Add 2in13 V2 to the list of supported screens
Add 2in13 V2 as supported and tested device.
2020-10-05 22:43:37 +02:00
Chris 221a801340
Merge pull request #51 from dkm/master
Support for Waveshare 2in13 v2 e-ink screen
2020-10-05 21:01:55 +02:00
Marc Poulhiès 27e367c89c Initial support for Waveshare 2in13 v2 e-ink screen
This initial support covers both the full (slow) update of the display and the
partial (fast) update.
2020-10-05 20:48:02 +02:00
Chris 7b8834700e
Merge pull request #52 from dkm/pr/strange_screen_size
Better handling for screen size not divisible by 8
Added buffer_len function
2020-10-05 18:59:23 +02:00
Marc Poulhiès 82b8c98538 Better handling for screen size not divisible by 8
Screen with width not divisible by 8 require special care when allocating the
buffer and when handling rotation.

Define a function for getting the needed buffer size (round up to next byte).
Change how rotation is done by changing coordinates instead of direct pixel
access.
2020-09-27 19:13:35 +02:00
Chris 22f16eaa05
Merge pull request #44 from caemor/v0.4
Release v.0.4.0
2020-04-06 15:59:05 +02:00
caemor f9f2a9ad87 v.0.4 2020-04-06 15:51:54 +02:00
Chris 79c2524c1d
Merge pull request #41 from michaelbeaumont/epd2in9b
Add epd2in9bc support
2020-03-27 22:31:54 +01:00
Michael Beaumont 9167c643f9
Add epd2in9bc to README, add example 2020-03-27 21:46:32 +01:00
Michael Beaumont b5e647b0d0 Rename red to chromatic, rename epd2in9b to epd2in9bc 2020-03-27 19:09:12 +01:00
Michael Beaumont 7a5472f73a
Update epd2in9b after embedded-graphics update 2020-03-27 18:50:33 +01:00
Michael Beaumont 71d539eeb7
Merge remote-tracking branch 'upstream/master' into epd2in9b 2020-03-27 18:47:13 +01:00
Chris c62ee89960
Update Readme for examples 2020-03-27 10:13:22 +01:00
Chris 8776211536
Update Readme example 2020-03-27 10:12:12 +01:00
Chris 1a670ef8c6
Update README.md 2020-03-27 10:11:06 +01:00
Caemor 5ba408d5a7 Fixed Readme Example 2020-03-26 20:49:15 +01:00
Chris 3871c1672e
Merge pull request #40 from caemor/nidus
Improvements for 0.4
2020-03-26 20:46:15 +01:00
Caemor 85ad681229 Refine Changelog 2020-03-26 20:43:04 +01:00
Caemor 2fcf788673 don't reinstall tarpaulin every time 2020-03-26 20:33:51 +01:00
Caemor 08c46952f7 Further shorten work of travis 2020-03-26 20:07:55 +01:00
Caemor 5c5c04d1fb Speed Up Travis, since most of the stuff switched to github ci 2020-03-26 20:01:05 +01:00
Caemor 0ab1e7db35 Fix changelog 2020-03-26 19:55:53 +01:00
Caemor 409676423d Mainly improved Documenation 2020-03-26 19:55:17 +01:00
Caemor 1dfea538b7 Update changelog and ci 2020-03-26 17:33:54 +01:00
Caemor dcc264c2b0 Replace tabs with spaces 2020-03-26 17:19:55 +01:00
Michael Beaumont 20a01a25bf
Add epd2in9b support 2020-03-25 22:56:34 +01:00
Caemor ec72357b55 Update/Include new update_and_display trait 2020-03-22 00:42:20 +01:00
Caemor 8da294dd5e Update embedded-graphics,
remove useless Featuregates (Doesn't change size)

Update and integrate a few important examples and remove the others
2020-03-21 23:37:27 +01:00
Caemor dcaed6fb3e Update embedded-graphics and update epd4in2-blue-pill-exampleg 2020-03-18 14:30:40 +01:00
Chris 8829f9a315
Merge pull request #36 from asaaki/epd7in5_v2
Add epd7in5_v2 support
2020-02-14 16:09:08 +01:00
Christoph Grabo d0e1d55e77
Update readme to include the V2 2020-01-11 13:17:47 +01:00
Christoph Grabo 4ecee29641
Apply cargo fmt 2020-01-11 13:17:47 +01:00
Christoph Grabo 80b3aa2c7c
Add example for epd7in5_v2 2020-01-11 13:17:46 +01:00
Christoph Grabo 35291fe639
Add V2 version of epd7in5 2020-01-11 13:17:46 +01:00
Christoph Grabo f17cae8e49
Add new optional extention trait 2020-01-11 10:21:51 +01:00
Christoph Grabo a9f27e51ae
Use embedded_hal::digital::v2 (v1 is deprecated) 2020-01-11 10:12:15 +01:00
Chris 636b31437a
Merge pull request #35 from jkristell/rfc-1in54b-support
Add support for the epd1in54b display
2019-12-28 11:17:21 +01:00
Johan Kristell 8f89ade010 cargo fmt 2019-12-28 09:13:40 +01:00
Johan Kristell 435cfd9a1c Rename trait and method and doc update 2019-12-28 09:12:53 +01:00
Johan Kristell b170e34028 Add documentation 2019-12-26 12:44:42 +01:00
Johan Kristell d0b7f162fe Add support for the epd1in54b display 2019-12-13 10:08:19 +01:00
Chris 6520bbffd7
Merge pull request #33 from str4d/epd7in5
EPD7in5 support
2019-11-21 12:53:06 +01:00
Jack Grigg 3bd62b3057
Add EPD7in5 to README 2019-11-20 15:25:03 +00:00
Jack Grigg a280a12170
EPD7in5 example 2019-11-15 23:58:08 +00:00
Jack Grigg c049b0a9f4
EPD7in5 support 2019-11-15 23:58:07 +00:00
Chris 9390f9aa49
Merge pull request #32 from caemor/CI-Tests
- make clippy failable in CI
- test code coverage
2019-09-02 15:21:25 +02:00
Christoph 59df02320e add codecov token 2019-08-21 15:40:53 +02:00
Christoph 8b1cacc48c ci update 2019-08-21 14:43:53 +02:00
Chris d452c9744c
Merge pull request #31 from dbr/master
* Update for embedded-graphics 0.5.2

* draw_helper takes IntoIterator
* embedded-graphics 0.5.0 removed the with_ prefix from some methods

with_stroke() to stroke()
with_fill() to fill()
with_style() to style()

* Update examples to embedded-graphics="0.5.2" also
2019-08-21 14:34:27 +02:00
Christoph d3cc0a5044 test code coverage 2019-08-21 14:29:58 +02:00
dbr 3d05130bc0 Fix copypaste error 2019-08-21 21:41:46 +09:30
dbr 044c798a8b Update examples to embedded-graphics="0.5.2" also 2019-08-18 17:58:48 +09:30
dbr 0b047b211b Trivial tidyup 2019-08-18 17:54:35 +09:30
dbr 3e56353cb6 embedded-graphics 0.5.0 removed the with_ prefix from some methods
with_stroke() to stroke()
with_fill() to fill()
with_style() to style()
2019-08-17 09:31:58 +09:30
dbr 99b4cff362 draw_helper takes IntoIterator
Update other display types
2019-08-16 23:59:58 +09:30
dbr f96caeb419 Update for embedded-graphics 0.5.2 2019-08-11 11:01:12 +09:30
Chris 7b4a7f0578
v0.3.2 - some timing fixes (#29)
* Fixed some missing wait_until_idle calls
* prepared release of 0.3.2
* cargo fmt

This is known to fail on travis because it uses the deprecated old digital v1 embedded hal pins
2019-06-17 10:33:20 +02:00
Chris 20fc874766
Finalize changelog for v.0.3.1 before release 2019-04-06 09:12:57 +02:00
Chris 60f48a8273
Add Bluepill example & some other minor improvements (#28)
- added bluepill example
- improved ci config
- travis: run fmt only once
- travis: fixed target issues
2019-04-06 09:09:56 +02:00
Chris 34c03c5486 Fixed forgotten fmt 2019-04-06 08:56:48 +02:00
Chris e8744dcb34 Release 0.3.1 2019-04-06 08:53:12 +02:00
Chris 63321f7e2c Fix: Timing Issues after display_frame
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.
2019-04-06 08:52:42 +02:00
Chris 9b6c7ada16 release 0.3.0 2019-04-04 23:37:38 +02:00
Chris 47af2fc664
Make a few small updates to support TeXitoi rusty clock (#27)
- add a feature gated alternative full lut for type_a displays
- remove pub from set_lut_helper function
- fix behaviour of set_lut for epd2in9. it always sets the LUT now!
-  better comments
2019-04-04 23:23:48 +02:00
Chris 5c0744ff01
Various improvements (#25)
- Added is_busy to Waveshare_Interface
- Added IS_BUSY_LOW const for all supported epds
- Added is_busy to DisplayInterface
- moved width, height and default_background_color directly to epd4in2 module
- Added VarDisplay (a variable buffersize display/graphic driver)
- Removed all Buffers (Buffer1in54,...) and instead made specialised Displays (Display1in54,...) with included Buffers
- Updated and added more examples
- Cargo fmt/clippy
- Improved Docs/Readmes
2019-03-06 11:48:05 +01:00
Christoph Groß 80384f031c Improve keywords,description and Readme
- added eink to keywords
- added reference to previous crate-name
- improved readme with reference to a few great arduino display libs
2019-02-22 14:51:29 +01:00
Chris d7f3cdca97
Edition 2018 (#24)
* "cargo fix --edition" for the library

* Switch to 2018 edition

* Use cargo fix edition-idioms and remove the internal renaming from embedded_hal to hal

* Updated Readme

* run cargo fmt
2019-02-22 11:23:28 +01:00
Chris 91b723d30e
Merge pull request #23 from caemor/test-codecov
test codecov
2019-01-15 13:40:05 +01:00
Chris e379477866 ci update 2019-01-15 13:06:26 +01:00
Chris 619e6124c5 try coveralls instead 2019-01-15 11:35:10 +01:00
Chris 42ee1773cb ci: fix codecov dir name 2019-01-15 11:21:27 +01:00
Chris 74dc5171bf test codecov 2019-01-15 10:56:34 +01:00
78 changed files with 10433 additions and 1699 deletions

8
.github/auto_assign-issues.yml vendored Normal file
View File

@ -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

17
.github/auto_assign.yml vendored Normal file
View File

@ -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

13
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
assignees:
- "caemor"

31
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Rust
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
steps:
- uses: actions/checkout@v1
- name: Install ARM toolchain
run: rustup target add thumbv7em-none-eabihf
- name: Check Fmt
run: cargo fmt --all -- --check
- name: Build lib
run: cargo check --all-targets --verbose
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings -A clippy::new_ret_no_self
- name: Build examples
run: cargo build --examples --all-targets --verbose
- name: Run tests
run: cargo test --verbose
- name: Build docs
run: cargo doc --all-features

3
.gitignore vendored
View File

@ -11,3 +11,6 @@ Cargo.lock
# vscode
.vscode/*
# intellij/clion
.idea/

View File

@ -1,135 +0,0 @@
# Based on the "trust" template v0.1.2
# https://github.com/japaric/trust/tree/v0.1.2
dist: trusty
language: rust
rust:
- stable
- beta
- nightly
sudo: required
# TODO Rust builds on stable by default, this can be
# overridden on a case by case basis down below.
matrix:
allow_failures:
# - nightly
# TODO These are all the build jobs. Adjust as necessary. Comment out what you
# don't need
include:
# Android
#- env: TARGET=aarch64-linux-android DISABLE_TESTS=1
#- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
#- env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1
#- env: TARGET=i686-linux-android DISABLE_TESTS=1
#- env: TARGET=x86_64-linux-android DISABLE_TESTS=1
# iOS
#- env: TARGET=aarch64-apple-ios DISABLE_TESTS=1
# os: osx
#- env: TARGET=armv7-apple-ios DISABLE_TESTS=1
# os: osx
#- env: TARGET=armv7s-apple-ios DISABLE_TESTS=1
# os: osx
#- env: TARGET=i386-apple-ios DISABLE_TESTS=1
# os: osx
#- env: TARGET=x86_64-apple-ios DISABLE_TESTS=1
# os: osx
# Linux
#- env: TARGET=aarch64-unknown-linux-gnu
# Raspberry Pi
- env: TARGET=arm-unknown-linux-gnueabi DISABLE_EXAMPLES=1
# Raspberry Pi 3...
- env: TARGET=armv7-unknown-linux-gnueabihf DISABLE_EXAMPLES=1
#- env: TARGET=i686-unknown-linux-gnu
#- env: TARGET=i686-unknown-linux-musl
#- env: TARGET=mips-unknown-linux-gnu
#- env: TARGET=mips64-unknown-linux-gnuabi64
#- env: TARGET=mips64el-unknown-linux-gnuabi64
#- env: TARGET=mipsel-unknown-linux-gnu
#- env: TARGET=powerpc-unknown-linux-gnu
#- env: TARGET=powerpc64-unknown-linux-gnu
#- env: TARGET=powerpc64le-unknown-linux-gnu
#- env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1
- env: TARGET=x86_64-unknown-linux-gnu
- env: TARGET=x86_64-unknown-linux-musl
# OSX
#- env: TARGET=i686-apple-darwin
# os: osx
#- env: TARGET=x86_64-apple-darwin
# os: osx
# *BSD
#- env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
#- env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
#- env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
# Windows
#- env: TARGET=x86_64-pc-windows-gnu
# Bare metal
# These targets don't support std and as such are likely not suitable for
# most crates.
- env: TARGET=thumbv6m-none-eabi
- env: TARGET=thumbv7em-none-eabi
- env: TARGET=thumbv7em-none-eabihf
- env: TARGET=thumbv7m-none-eabi
# Testing other channels
#- env: TARGET=x86_64-unknown-linux-gnu
# rust: nightly
#- env: TARGET=x86_64-apple-darwin
# os: osx
# rust: nightly
#- env: TEST_DIR=examples/embedded_linux
#- env: TEST_DIR=examples/f3_stm32f30x
before_install:
- set -e
- rustup self update
install:
- cargo install cargo-travis || true
- rustup override set nightly
- rustup target add thumbv7m-none-eabi
- rustup component add clippy-preview
- rustup component add rustfmt-preview
#TODO: remove -A clippy::new_ret_no_self when new version of clippy gets released!
script:
- cargo fmt --all -- --check
- cargo clippy --all-targets --all-features -- -D warnings -A clippy::new_ret_no_self
- cargo check
- cargo test --all-features --release
- cargo doc --all-features --release
- cd examples/embedded_linux_epd4in2 && cargo fmt --all -- --check && cargo check && cd ../../
- cd examples/embedded_linux_epd1in54 && cargo fmt --all -- --check && cargo check && cd ../../
- cd examples/embedded_linux_epd2in9 && cargo fmt --all -- --check && cargo check && cd ../../
# - cd examples/stm32f3discovery && cargo check --target thumbv7m-none-eabi && cd ../../
#- cd ../f3_stm32f30x && cargo build
after_success:
#- cargo doc-upload
cache: cargo
before_cache:
# Travis can't cache files that are not readable by "others"
- chmod -R a+r $HOME/.cargo
branches:
only:
# release tags
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
email:
on_success: never

View File

@ -7,23 +7,132 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
<!-- ## [v0.2.1] - 2018-12-06 -->
### Added
- Added support for positive and negatives modes of rendering in TriColor display in #92 (thanks to @akashihi)
- Added Epd 5in83 V2 (B) support in #92 (thanks to @akashihi)
### 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 Documentation
### Changed
- 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
## [v0.3.2] - 2019-06-17
### Fixed
- Added some more missing wait_until_idle calls
## [v0.3.1] - 2019-04-06
### Added
- Example for epd4in2 and BluePill-Board
### Changed
- 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
## [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 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
- moved width, height and default_background_color directly to epd4in2 module
- remove pub from set_lut_helper function
- fix behaviour of set_lut for epd2in9. it always sets the LUT now!
## v0.2.0 - 2018-10-30
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
[Unreleased]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.2.0...HEAD
[v0.2.1]: https://github.com/Caemor/eink-waveshare-rs/compare/v0.2.0...v0.2.1
[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

View File

@ -1,34 +1,34 @@
[package]
authors = ["Christoph Groß <caemor@mailbox.org>"]
categories = ["embedded", "hardware-support", "no-std"]
description = "An embedded-hal based driver for ePaper displays from Waveshare"
description = "An embedded-hal based driver for ePaper displays from Waveshare formerly published as eink-waveshare-rs"
documentation = "https://docs.rs/epd-waveshare"
homepage = "https://github.com/caemor/epd-waveshare"
keywords = ["ePaper", "Display", "epd"]
keywords = ["ePaper", "Display", "epd", "eink"]
license = "ISC"
name = "epd-waveshare"
readme = "README.md"
repository = "https://github.com/Caemor/epd-waveshare.git"
version = "0.2.0"
version = "0.5.0"
edition = "2018"
[badges]
# Travis CI: `repository` in format "<user>/<project>" is required.
# `branch` is optional; default is `master`
travis-ci = { repository = "caemor/epd-waveshare" }
# travis-ci = { repository = "caemor/epd-waveshare" }
[dependencies]
embedded-graphics-core = { version = "0.3.2", optional = true}
embedded-hal = {version = "0.2.4", features = ["unproven"]}
bit_field = "0.10.1"
[dev-dependencies]
embedded-graphics = "0.7.1"
linux-embedded-hal = "0.3"
embedded-hal-mock = "0.8"
[features]
default = ["epd1in54", "epd2in9", "epd4in2", "graphics"]
default = ["graphics"]
graphics = ["embedded-graphics"]
epd1in54 = []
epd2in9 = []
epd4in2 = []
graphics = ["embedded-graphics-core"]
[dependencies.embedded-graphics]
optional = true
version = "0.4.3"
[dependencies.embedded-hal]
features = ["unproven"]
version = "0.2.1"
# Offers an alternative fast full lut for type_a displays, but the refreshed screen isnt as clean looking
type_a_alternative_faster_lut = []

View File

@ -1,77 +1,96 @@
[![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.
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 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
There are multiple examples in the examples folder. For more infos about the examples see the seperate Readme [there](/examples/Readme.md).
There are multiple examples in the examples folder. Use `cargo run --example example_name` to try them.
```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 buffer = Buffer4in2::default();
let mut display = Display::new(epd.width(), epd.height(), &mut buffer.buffer);
let mut display = Display4in2::default ();
// Draw some text
display.draw(
Font12x16::render_str("Hello Rust!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
let _ = Text::new("Hello Rust!", Point::new(x, y))
.into_styled(text_style!(
font = Font12x16,
text_color = Black,
background_color = White
))
.draw(display);
);
// Transfer the frame data to the epd
epd.update_frame(&mut spi, &display.buffer())?;
// Display the frame on the epd
epd.display_frame(&mut spi)?;
// Transfer the frame data to the epd and display it
epd.update_and_display_frame( & mut spi, & display.buffer()) ?;
```
## (Supported) Devices
| Device (with Link) | Colors | Flexible Display | Partial Refresh | Supported | Tested |
| :---: | --- | :---: | :---: | :---: | :---: |
| [4.2 Inch B/W (A)](https://www.waveshare.com/product/4.2inch-e-paper-module.htm) | Black, White | ✕ | Not officially [[1](#42-inch-e-ink-blackwhite)] | ✔ | ✔ |
| [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 | ✕ | ✕ | ✔ | ✔ |
| [5.83 Inch B/W/R (b)](https://www.waveshare.com/5.83inch-e-Paper-B.htm) | Black, White, Red | ✕ | Not officially | ✔ | ✔ |
| [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)](https://www.waveshare.com/product/2.13inch-e-paper-hat.htm) | Black, White | ✕ | ✔ | | |
| [2.9 Inch B/W (A)](https://www.waveshare.com/product/2.9inch-e-paper-module.htm) | Black, White | ✕ | ✔ | ✔ | ✔ [[2](#2-29-inch-e-ink-blackwhite---tests)] |
| [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)
### [1]: 4.2 Inch E-Ink Black/White - Partial Refresh
Since November 2019 Waveshare sells their updated version of these displays. They should have a "V2" marking sticker on
the backside of the panel.
Out of the Box the original driver from Waveshare only supports full updates.
Use `epd7in5_v2` instead of `epd7in5`, because the protocol changed.
### [2]: 4.2 Inch E-Ink Black/White - Partial Refresh
Out of the Box the original driver from Waveshare only supports full updates.
That means: Be careful with the quick refresh updates: <br>
It's possible with this driver but might lead to ghosting / burn-in effects therefore it's hidden behind a feature.
### [2]: 2.9 Inch E-Ink Black/White - Tests
Since my 2.9 Inch Display has some blurring issues I am not absolutly sure if everything was working correctly as it should :-)
### Interface
| 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) |
@ -87,13 +106,3 @@ They are also called A and B, but you shouldn't get confused and mix it with the
| | 7.5in (A) |
| | 7.5in (B) |
| | 7.5in (C) |
## TODO's
- [ ] improve the partial drawing/check the timings/timing improvements/....

View File

@ -1,23 +0,0 @@
# This script takes care of packaging the build artifacts that will go in the
# release zipfile
$SRC_DIR = $PWD.Path
$STAGE = [System.Guid]::NewGuid().ToString()
Set-Location $ENV:Temp
New-Item -Type Directory -Name $STAGE
Set-Location $STAGE
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"
# TODO Update this to package the right artifacts
Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\hello.exe" '.\'
7z a "$ZIP" *
Push-AppveyorArtifact "$ZIP"
Remove-Item *.* -Force
Set-Location ..
Remove-Item $STAGE
Set-Location $SRC_DIR

View File

@ -1,33 +0,0 @@
# This script takes care of building your crate and packaging it for release
set -ex
main() {
local src=$(pwd) \
stage=
case $TRAVIS_OS_NAME in
linux)
stage=$(mktemp -d)
;;
osx)
stage=$(mktemp -d -t tmp)
;;
esac
test -f Cargo.lock || cargo generate-lockfile
# TODO Update this to build the artifacts that matter to you
cross rustc --bin hello --target $TARGET --release -- -C lto
# TODO Update this to package the right artifacts
cp target/$TARGET/release/hello $stage/
cd $stage
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
cd $src
rm -rf $stage
}
main

View File

@ -1,47 +0,0 @@
set -ex
main() {
local target=
if [ $TRAVIS_OS_NAME = linux ]; then
target=x86_64-unknown-linux-musl
sort=sort
else
target=x86_64-apple-darwin
sort=gsort # for `sort --sort-version`, from brew's coreutils.
fi
# Builds for iOS are done on OSX, but require the specific target to be
# installed.
case $TARGET in
aarch64-apple-ios)
rustup target install aarch64-apple-ios
;;
armv7-apple-ios)
rustup target install armv7-apple-ios
;;
armv7s-apple-ios)
rustup target install armv7s-apple-ios
;;
i386-apple-ios)
rustup target install i386-apple-ios
;;
x86_64-apple-ios)
rustup target install x86_64-apple-ios
;;
esac
# This fetches latest stable release
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
| cut -d/ -f3 \
| grep -E '^v[0.1.0-9.]+$' \
| $sort --version-sort \
| tail -n1)
curl -LSfs https://japaric.github.io/trust/install.sh | \
sh -s -- \
--force \
--git japaric/cross \
--tag $tag \
--target $target
}
main

View File

@ -1,24 +0,0 @@
# This script takes care of testing your crate
set -ex
# TODO This is the "test phase", tweak it as you see fit
main() {
cross build --target $TARGET
cross build --target $TARGET --release
if [ ! -z $DISABLE_TESTS ]; then
return
fi
cross test --target $TARGET
cross test --target $TARGET --release
cross run --target $TARGET
cross run --target $TARGET --release
}
# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
main
fi

View File

@ -1,12 +0,0 @@
# Examples:
### embedded_linux_epd4in2
Basic example of using a Raspberry Pi with the 4in2 Waveshare E-Ink Display.
It showcases most of what can be done with the crate.
### embedded_linux_epd1in54
This example doesn't use the graphics feature of the crate and shows the fast update rate of the 1in54 EPD.

View File

@ -1,14 +0,0 @@
[package]
name = "embedded_linux_eink_example"
version = "0.1.0"
authors = ["Christoph Groß <christoph-gross@mailbox.org>"]
[dependencies]
epd-waveshare = { path = "../../", default-features = false, features = ["epd1in54", "graphics"]}
linux-embedded-hal = "0.2.1"
embedded-graphics = "0.4.3"
embedded-hal = { version = "0.2.1", features = ["unproven"] }

View File

@ -1,161 +0,0 @@
// the library for the embedded linux device
extern crate linux_embedded_hal as lin_hal;
use lin_hal::spidev::{self, SpidevOptions};
use lin_hal::sysfs_gpio::Direction;
use lin_hal::Delay;
use lin_hal::{Pin, Spidev};
// the eink library
extern crate epd_waveshare;
use epd_waveshare::{
epd1in54::{Buffer1in54, EPD1in54},
graphics::{Display, DisplayRotation},
prelude::*,
};
// Graphics
extern crate embedded_graphics;
use embedded_graphics::coord::Coord;
use embedded_graphics::fonts::Font6x8;
use embedded_graphics::prelude::*;
//use embedded_graphics::primitives::{Circle, Line};
use embedded_graphics::Drawing;
// HAL (Traits)
extern crate embedded_hal;
use embedded_hal::prelude::*;
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() {
run().unwrap();
}
fn run() -> Result<(), std::io::Error> {
// Configure SPI
// SPI settings are from eink-waveshare-rs documenation
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs_pin = Pin::new(26); //BCM7 CE0
cs_pin.export().expect("cs_pin export");
while !cs_pin.is_exported() {}
cs_pin
.set_direction(Direction::Out)
.expect("cs_pin Direction");
cs_pin.set_value(1).expect("cs_pin Value set to 1");
// Configure Busy Input Pin
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
// Configure Data/Command OutputPin
let dc = Pin::new(6); //pin 31 //bcm6
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");
// Configure Reset OutputPin
let rst = Pin::new(16); //pin 36 //bcm16
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 Delay
let mut delay = Delay {};
// 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)?;
// Clear the full screen
epd.clear_frame(&mut spi).expect("clear frame 1");
epd.display_frame(&mut spi).expect("disp 1");
println!("Test all the rotations");
let mut buffer = Buffer1in54::default();
let mut display = Display::new(epd.width(), epd.height(), &mut buffer.buffer);
display.set_rotation(DisplayRotation::Rotate0);
display.draw(
Font6x8::render_str("Rotate 0!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Font6x8::render_str("Rotate 90!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Font6x8::render_str("Rotate 180!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Font6x8::render_str("Rotate 270!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
// Display updated frame
epd.update_frame(&mut spi, &display.buffer()).unwrap();
epd.display_frame(&mut spi)
.expect("display frame new graphics");
delay.delay_ms(5000u16);
// a quickly moving `Hello World!`
display.set_rotation(DisplayRotation::Rotate0);
epd.set_lut(&mut spi, Some(RefreshLUT::QUICK))
.expect("SET LUT QUICK error");
let limit = 20;
for i in 0..limit {
println!("Moving Hello World. Loop {} from {}", (i + 1), limit);
display.draw(
Font6x8::render_str(" Hello World! ")
.with_style(Style {
fill_color: Some(Color::White),
stroke_color: Some(Color::Black),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(5 + i * 6, 50))
.into_iter(),
);
epd.update_frame(&mut spi, &display.buffer()).unwrap();
epd.display_frame(&mut spi)
.expect("display frame new graphics");
}
// Set the EPD to sleep
epd.sleep(&mut spi).expect("sleep");
Ok(())
}

View File

@ -1,14 +0,0 @@
[package]
name = "embedded_linux_eink_example"
version = "0.1.0"
authors = ["Christoph Groß <christoph-gross@mailbox.org>"]
[dependencies]
epd-waveshare = { path = "../../", default-features = false, features = ["epd2in9", "graphics"]}
linux-embedded-hal = "0.2.1"
embedded-graphics = "0.4.3"
embedded-hal = { version = "0.2.1", features = ["unproven"] }

View File

@ -1,165 +0,0 @@
// the library for the embedded linux device
extern crate linux_embedded_hal as lin_hal;
use lin_hal::spidev::{self, SpidevOptions};
use lin_hal::sysfs_gpio::Direction;
use lin_hal::Delay;
use lin_hal::{Pin, Spidev};
// the eink library
extern crate epd_waveshare;
use epd_waveshare::{
epd2in9::{Buffer2in9, EPD2in9},
graphics::{Display, DisplayRotation},
prelude::*,
};
// Graphics
extern crate embedded_graphics;
use embedded_graphics::coord::Coord;
use embedded_graphics::fonts::Font6x8;
use embedded_graphics::prelude::*;
//use embedded_graphics::primitives::{Circle, Line};
use embedded_graphics::Drawing;
// HAL (Traits)
extern crate embedded_hal;
use embedded_hal::prelude::*;
// 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
//TODO: Test this implemenation with a new display
fn main() {
run().unwrap();
}
fn run() -> Result<(), std::io::Error> {
// Configure SPI
// SPI settings are from eink-waveshare-rs documenation
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs_pin = Pin::new(26); //BCM7 CE0
cs_pin.export().expect("cs_pin export");
while !cs_pin.is_exported() {}
cs_pin
.set_direction(Direction::Out)
.expect("cs_pin Direction");
cs_pin.set_value(1).expect("cs_pin Value set to 1");
// Configure Busy Input Pin
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
// Configure Data/Command OutputPin
let dc = Pin::new(6); //pin 31 //bcm6
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");
// Configure Reset OutputPin
let rst = Pin::new(16); //pin 36 //bcm16
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 Delay
let mut delay = Delay {};
// Setup of the needed pins is finished here
// Now the "real" usage of the eink-waveshare-rs crate begins
let mut epd = EPD2in9::new(&mut spi, cs_pin, busy, dc, rst, &mut delay)?;
// Clear the full screen
epd.clear_frame(&mut spi).expect("clear frame 1");
epd.display_frame(&mut spi).expect("disp 1");
println!("Test all the rotations");
let mut buffer = Buffer2in9::default();
let mut display = Display::new(epd.width(), epd.height(), &mut buffer.buffer);
epd.update_frame(&mut spi, display.buffer()).unwrap();
epd.display_frame(&mut spi).expect("display frame x03");
display.set_rotation(DisplayRotation::Rotate0);
display.draw(
Font6x8::render_str("Rotate 0!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Font6x8::render_str("Rotate 90!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Font6x8::render_str("Rotate 180!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Font6x8::render_str("Rotate 270!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
// Display updated frame
epd.update_frame(&mut spi, &display.buffer()).unwrap();
epd.display_frame(&mut spi)
.expect("display frame new graphics");
delay.delay_ms(5000u16);
// a quickly moving `Hello World!`
display.set_rotation(DisplayRotation::Rotate0);
epd.set_lut(&mut spi, Some(RefreshLUT::QUICK))
.expect("SET LUT QUICK error");
let limit = 20;
for i in 0..limit {
println!("Moving Hello World. Loop {} from {}", (i + 1), limit);
display.draw(
Font6x8::render_str(" Hello World! ")
.with_style(Style {
fill_color: Some(Color::White),
stroke_color: Some(Color::Black),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(5 + i * 6, 50))
.into_iter(),
);
epd.update_frame(&mut spi, &display.buffer()).unwrap();
epd.display_frame(&mut spi)
.expect("display frame new graphics");
}
// Set the EPD to sleep
epd.sleep(&mut spi).expect("sleep");
Ok(())
}

View File

@ -1,16 +0,0 @@
[package]
name = "embedded_linux_eink_example"
version = "0.1.0"
authors = ["Christoph Groß <christoph-gross@mailbox.org>"]
[dependencies]
## The Only difference between this one and the one without default features sizewise seems to be a different .d-file Size (dependencies-file)
#epd_waveshare = { path = "../../"}
epd-waveshare = { path = "../../", default-features = false, features = ["epd4in2", "graphics"]}
linux-embedded-hal = "0.2.1"
embedded-graphics = "0.4.3"
embedded-hal = { version = "0.2.1", features = ["unproven"] }

View File

@ -1,194 +0,0 @@
// the library for the embedded linux device
extern crate linux_embedded_hal as lin_hal;
use lin_hal::spidev::{self, SpidevOptions};
use lin_hal::sysfs_gpio::Direction;
use lin_hal::Delay;
use lin_hal::{Pin, Spidev};
// the eink library
extern crate epd_waveshare;
use epd_waveshare::{
epd4in2::{Buffer4in2, EPD4in2},
graphics::{Display, DisplayRotation},
prelude::*,
};
// Graphics
extern crate embedded_graphics;
use embedded_graphics::coord::Coord;
use embedded_graphics::fonts::{Font12x16, Font6x8};
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Circle, Line};
use embedded_graphics::Drawing;
// HAL (Traits)
extern crate embedded_hal;
use embedded_hal::prelude::*;
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() {
run().map_err(|e| println!("{}", e.to_string())).unwrap();
}
fn run() -> Result<(), std::io::Error> {
// Configure SPI
// Settings are taken from
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs = Pin::new(26); //BCM7 CE0
cs.export().expect("cs export");
while !cs.is_exported() {}
cs.set_direction(Direction::Out).expect("CS Direction");
cs.set_value(1).expect("CS Value set to 1");
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
let dc = Pin::new(6); //pin 31 //bcm6
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(16); //pin 36 //bcm16
rst.export().expect("rst export");
while !rst.is_exported() {}
rst.set_direction(Direction::Out).expect("rst Direction");
rst.set_value(1).expect("rst Value set to 1");
let mut delay = Delay {};
let mut epd4in2 =
EPD4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error");
println!("Test all the rotations");
let mut buffer = Buffer4in2::default();
let mut display = Display::new(epd4in2.width(), epd4in2.height(), &mut buffer.buffer);
display.set_rotation(DisplayRotation::Rotate0);
display.draw(
Font6x8::render_str("Rotate 0!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Font6x8::render_str("Rotate 90!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Font6x8::render_str("Rotate 180!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Font6x8::render_str("Rotate 270!")
.with_stroke(Some(Color::Black))
.with_fill(Some(Color::White))
.translate(Coord::new(5, 50))
.into_iter(),
);
epd4in2.update_frame(&mut spi, &display.buffer()).unwrap();
epd4in2
.display_frame(&mut spi)
.expect("display frame new graphics");
delay.delay_ms(5000u16);
println!("Now test new graphics with default rotation and some special stuff:");
display.clear_buffer(Color::White);
// draw a analog clock
display.draw(
Circle::new(Coord::new(64, 64), 64)
.with_stroke(Some(Color::Black))
.into_iter(),
);
display.draw(
Line::new(Coord::new(64, 64), Coord::new(0, 64))
.with_stroke(Some(Color::Black))
.into_iter(),
);
display.draw(
Line::new(Coord::new(64, 64), Coord::new(80, 80))
.with_stroke(Some(Color::Black))
.into_iter(),
);
// draw white on black background
display.draw(
Font6x8::render_str("It's working-WoB!")
// Using Style here
.with_style(Style {
fill_color: Some(Color::Black),
stroke_color: Some(Color::White),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(175, 250))
.into_iter(),
);
// use bigger/different font
display.draw(
Font12x16::render_str("It's working-BoW!")
// Using Style here
.with_style(Style {
fill_color: Some(Color::White),
stroke_color: Some(Color::Black),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(50, 200))
.into_iter(),
);
// a moving `Hello World!`
let limit = 10;
for i in 0..limit {
println!("Moving Hello World. Loop {} from {}", (i + 1), limit);
display.draw(
Font6x8::render_str(" Hello World! ")
.with_style(Style {
fill_color: Some(Color::White),
stroke_color: Some(Color::Black),
stroke_width: 0u8, // Has no effect on fonts
})
.translate(Coord::new(5 + i * 12, 50))
.into_iter(),
);
epd4in2.update_frame(&mut spi, &display.buffer()).unwrap();
epd4in2
.display_frame(&mut spi)
.expect("display frame new graphics");
delay.delay_ms(1_000u16);
}
println!("Finished tests - going to sleep");
epd4in2.sleep(&mut spi)
}

View File

@ -0,0 +1,99 @@
#![deny(warnings)]
use embedded_hal::prelude::*;
use epd_waveshare::{epd1in54::Epd1in54, prelude::*};
use linux_embedded_hal::{
spidev::{self, SpidevOptions},
sysfs_gpio::Direction,
Delay, Pin, Spidev,
};
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() -> Result<(), std::io::Error> {
// Configure SPI
// SPI settings are from eink-waveshare-rs documenation
let mut spi = Spidev::open("/dev/spidev0.0")?;
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs_pin = Pin::new(26); //BCM7 CE0
cs_pin.export().expect("cs_pin export");
while !cs_pin.is_exported() {}
cs_pin
.set_direction(Direction::Out)
.expect("cs_pin Direction");
cs_pin.set_value(1).expect("cs_pin Value set to 1");
// Configure Busy Input Pin
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
// Configure Data/Command OutputPin
let dc = Pin::new(6); //pin 31 //bcm6
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");
// Configure Reset OutputPin
let rst = Pin::new(16); //pin 36 //bcm16
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 Delay
let mut delay = Delay {};
// 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)?;
// Clear the full screen
epd.clear_frame(&mut spi, &mut delay)?;
epd.display_frame(&mut spi, &mut delay)?;
// Speeddemo
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, &mut delay)?;
}
// Clear the full screen
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
epd.update_partial_frame(&mut spi, &small_buffer, 20, 20, 160, 160)?;
let small_buffer = [Color::White.get_byte_value(); 800]; //80x80
epd.update_partial_frame(&mut spi, &small_buffer, 60, 60, 80, 80)?;
let small_buffer = [Color::Black.get_byte_value(); 8]; //8x8
epd.update_partial_frame(&mut spi, &small_buffer, 96, 96, 8, 8)?;
// Display updated frame
epd.display_frame(&mut spi, &mut delay)?;
delay.delay_ms(5000u16);
// Set the EPD to sleep
epd.sleep(&mut spi, &mut delay)?;
Ok(())
}

172
examples/epd2in13_v2.rs Normal file
View File

@ -0,0 +1,172 @@
#![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::*,
epd2in13_v2::{Display2in13, Epd2in13},
graphics::DisplayRotation,
prelude::*,
};
use linux_embedded_hal::{
spidev::{self, SpidevOptions},
sysfs_gpio::Direction,
Delay, Pin, Spidev,
};
// The pins in this example are for the Universal e-Paper Raw Panel Driver HAT
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() -> Result<(), std::io::Error> {
// Configure SPI
// Settings are taken from
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs = Pin::new(26); //BCM7 CE0
cs.export().expect("cs export");
while !cs.is_exported() {}
cs.set_direction(Direction::Out).expect("CS Direction");
cs.set_value(1).expect("CS Value set to 1");
let busy = Pin::new(24); // GPIO 24, board J-18
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
let dc = Pin::new(25); // GPIO 25, board J-22
dc.export().expect("dc export");
while !dc.is_exported() {}
dc.set_direction(Direction::Out).expect("dc Direction");
dc.set_value(1).expect("dc Value set to 1");
let rst = Pin::new(17); // GPIO 17, board J-11
rst.export().expect("rst export");
while !rst.is_exported() {}
rst.set_direction(Direction::Out).expect("rst Direction");
rst.set_value(1).expect("rst Value set to 1");
let mut delay = Delay {};
let mut epd2in13 =
Epd2in13::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error");
//println!("Test all the rotations");
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate0);
draw_text(&mut display, "Rotate 0!", 5, 50);
display.set_rotation(DisplayRotation::Rotate90);
draw_text(&mut display, "Rotate 90!", 5, 50);
display.set_rotation(DisplayRotation::Rotate180);
draw_text(&mut display, "Rotate 180!", 5, 50);
display.set_rotation(DisplayRotation::Rotate270);
draw_text(&mut display, "Rotate 270!", 5, 50);
epd2in13.update_frame(&mut spi, display.buffer(), &mut delay)?;
epd2in13
.display_frame(&mut spi, &mut delay)
.expect("display frame new graphics");
delay.delay_ms(5000u16);
//println!("Now test new graphics with default rotation and some special stuff:");
display.clear_buffer(Color::White);
// draw a analog clock
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))
.into_styled(PrimitiveStyle::with_stroke(Black, 4))
.draw(&mut display);
let _ = Line::new(Point::new(64, 64), Point::new(80, 40))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
// draw white on black background
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 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)
.unwrap();
epd2in13.clear_frame(&mut spi, &mut delay).unwrap();
// a moving `Hello World!`
let limit = 10;
for i in 0..limit {
draw_text(&mut display, " Hello World! ", 5 + i * 12, 50);
epd2in13
.update_and_display_frame(&mut spi, display.buffer(), &mut delay)
.expect("display frame new graphics");
delay.delay_ms(1_000u16);
}
// Show a spinning bar without any delay between frames. Shows how «fast»
// 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(), &mut delay)
.unwrap();
let spinner = ["|", "/", "-", "\\"];
for i in 0..10 {
display.clear_buffer(Color::White);
draw_text(&mut display, spinner[i % spinner.len()], 10, 100);
epd2in13
.update_and_display_frame(&mut spi, display.buffer(), &mut delay)
.unwrap();
}
println!("Finished tests - going to sleep");
epd2in13.sleep(&mut spi, &mut delay)
}
fn draw_text(display: &mut Display2in13, text: &str, x: i32, y: i32) {
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);
}

167
examples/epd2in13bc.rs Normal file
View File

@ -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);
}

161
examples/epd4in2.rs Normal file
View File

@ -0,0 +1,161 @@
#![deny(warnings)]
use embedded_graphics::{
mono_font::MonoTextStyleBuilder,
prelude::*,
primitives::{Circle, Line, PrimitiveStyleBuilder},
text::{Baseline, Text, TextStyleBuilder},
};
use embedded_hal::prelude::*;
use epd_waveshare::{
color::*,
epd4in2::{Display4in2, Epd4in2},
graphics::DisplayRotation,
prelude::*,
};
use linux_embedded_hal::{
spidev::{self, SpidevOptions},
sysfs_gpio::Direction,
Delay, Pin, Spidev,
};
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() -> Result<(), std::io::Error> {
// Configure SPI
// Settings are taken from
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs = Pin::new(26); //BCM7 CE0
cs.export().expect("cs export");
while !cs.is_exported() {}
cs.set_direction(Direction::Out).expect("CS Direction");
cs.set_value(1).expect("CS Value set to 1");
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
let dc = Pin::new(6); //pin 31 //bcm6
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(16); //pin 36 //bcm16
rst.export().expect("rst export");
while !rst.is_exported() {}
rst.set_direction(Direction::Out).expect("rst Direction");
rst.set_value(1).expect("rst Value set to 1");
let mut delay = Delay {};
let mut epd4in2 =
Epd4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error");
println!("Test all the rotations");
let mut display = Display4in2::default();
display.set_rotation(DisplayRotation::Rotate0);
draw_text(&mut display, "Rotate 0!", 5, 50);
display.set_rotation(DisplayRotation::Rotate90);
draw_text(&mut display, "Rotate 90!", 5, 50);
display.set_rotation(DisplayRotation::Rotate180);
draw_text(&mut display, "Rotate 180!", 5, 50);
display.set_rotation(DisplayRotation::Rotate270);
draw_text(&mut display, "Rotate 270!", 5, 50);
epd4in2.update_frame(&mut spi, display.buffer(), &mut delay)?;
epd4in2
.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");
display.clear_buffer(Color::White);
// draw a analog clock
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(style)
.draw(&mut display);
let _ = Line::new(Point::new(64, 64), Point::new(80, 80))
.into_styled(style)
.draw(&mut display);
// draw white on black background
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 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, &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(), &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, &mut delay)
}
fn draw_text(display: &mut Display4in2, text: &str, x: i32, y: i32) {
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);
}

View File

@ -0,0 +1,166 @@
#![deny(warnings)]
#![deny(warnings)]
use embedded_graphics::{
mono_font::MonoTextStyleBuilder,
prelude::*,
primitives::{Circle, Line, PrimitiveStyleBuilder},
text::{Baseline, Text, TextStyleBuilder},
};
use embedded_hal::prelude::*;
use epd_waveshare::{
color::*,
epd4in2::{self, Epd4in2},
graphics::{Display, DisplayRotation, VarDisplay},
prelude::*,
};
use linux_embedded_hal::{
spidev::{self, SpidevOptions},
sysfs_gpio::Direction,
Delay, Pin, Spidev,
};
// activate spi, gpio in raspi-config
// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems
// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues
fn main() -> Result<(), std::io::Error> {
// Configure SPI
// Settings are taken from
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory");
let options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(4_000_000)
.mode(spidev::SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&options).expect("spi configuration");
// Configure Digital I/O Pin to be used as Chip Select for SPI
let cs = Pin::new(26); //BCM7 CE0
cs.export().expect("cs export");
while !cs.is_exported() {}
cs.set_direction(Direction::Out).expect("CS Direction");
cs.set_value(1).expect("CS Value set to 1");
let busy = Pin::new(5); //pin 29
busy.export().expect("busy export");
while !busy.is_exported() {}
busy.set_direction(Direction::In).expect("busy Direction");
//busy.set_value(1).expect("busy Value set to 1");
let dc = Pin::new(6); //pin 31 //bcm6
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(16); //pin 36 //bcm16
rst.export().expect("rst export");
while !rst.is_exported() {}
rst.set_direction(Direction::Out).expect("rst Direction");
rst.set_value(1).expect("rst Value set to 1");
let mut delay = Delay {};
let mut epd4in2 =
Epd4in2::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error");
println!("Test all the rotations");
let (x, y, width, height) = (50, 50, 250, 250);
let mut buffer = [epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value(); 62500]; //250*250
let mut display = VarDisplay::new(width, height, &mut buffer);
display.set_rotation(DisplayRotation::Rotate0);
draw_text(&mut display, "Rotate 0!", 5, 50);
display.set_rotation(DisplayRotation::Rotate90);
draw_text(&mut display, "Rotate 90!", 5, 50);
display.set_rotation(DisplayRotation::Rotate180);
draw_text(&mut display, "Rotate 180!", 5, 50);
display.set_rotation(DisplayRotation::Rotate270);
draw_text(&mut display, "Rotate 270!", 5, 50);
epd4in2
.update_partial_frame(&mut spi, display.buffer(), x, y, width, height)
.unwrap();
epd4in2
.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:");
display.set_rotation(DisplayRotation::Rotate0);
display.clear_buffer(Color::White);
// draw a analog clock
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(style)
.draw(&mut display);
let _ = Line::new(Point::new(64, 64), Point::new(80, 80))
.into_styled(style)
.draw(&mut display);
// draw white on black background
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 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;
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_partial_frame(&mut spi, display.buffer(), x, y, width, height)
.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, &mut delay)
}
fn draw_text(display: &mut VarDisplay, text: &str, x: i32, y: i32) {
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);
}

View File

@ -1,5 +1,30 @@
//! B/W Color for EPDs
#[cfg(feature = "graphics")]
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 {
@ -9,6 +34,154 @@ pub enum Color {
White,
}
/// Only for the Black/White/Color-Displays
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TriColor {
/// Black color
Black,
/// White color
White,
/// Chromatic color
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<BinaryColor> for OctColor {
fn from(b: BinaryColor) -> OctColor {
match b {
BinaryColor::On => OctColor::Black,
BinaryColor::Off => OctColor::White,
}
}
}
#[cfg(feature = "graphics")]
impl From<OctColor> 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<embedded_graphics_core::pixelcolor::Rgb888> 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<embedded_graphics_core::pixelcolor::raw::RawU4> 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<OctColor, OutOfColorRangeParseError> {
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 {
@ -51,17 +224,35 @@ impl Color {
}
}
#[cfg(feature = "graphics")]
use embedded_graphics::prelude::*;
#[cfg(feature = "graphics")]
impl PixelColor for Color {}
impl From<u8> for Color {
fn from(value: u8) -> Self {
Color::from_u8(value)
}
}
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::*;
@ -93,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))
);
}
}

View File

@ -1,44 +1,87 @@
use epd1in54::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
use crate::epd1in54::{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 1in54 EPD
///
/// Can also be manuall constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
pub struct Buffer1in54BlackWhite {
pub buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
pub struct Display1in54 {
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation,
}
impl Default for Buffer1in54BlackWhite {
impl Default for Display1in54 {
fn default() -> Self {
Buffer1in54BlackWhite {
Display1in54 {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation::default(),
}
}
}
impl DrawTarget for Display1in54 {
type Color = BinaryColor;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper(WIDTH, HEIGHT, pixel)?;
}
Ok(())
}
}
impl OriginDimensions for Display1in54 {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Display for Display1in54 {
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 color::Color;
use embedded_graphics::coord::Coord;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::Line;
use graphics::{Display, DisplayRotation};
use crate::color::{Black, Color};
use crate::graphics::{Display, DisplayRotation};
use embedded_graphics::{
prelude::*,
primitives::{Line, PrimitiveStyle},
};
// test buffer length
#[test]
fn graphics_size() {
let mut display1in54 = Buffer1in54BlackWhite::default();
let display = Display::new(WIDTH, HEIGHT, &mut display1in54.buffer);
let display = Display1in54::default();
assert_eq!(display.buffer().len(), 5000);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let mut display1in54 = Buffer1in54BlackWhite::default();
let display = Display::new(WIDTH, HEIGHT, &mut display1in54.buffer);
let display = Display1in54::default();
for &byte in display.buffer() {
assert_eq!(byte, DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
@ -46,13 +89,10 @@ mod tests {
#[test]
fn graphics_rotation_0() {
let mut display1in54 = Buffer1in54BlackWhite::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display1in54.buffer);
display.draw(
Line::new(Coord::new(0, 0), Coord::new(7, 0))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let mut display = Display1in54::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();
@ -65,14 +105,11 @@ mod tests {
#[test]
fn graphics_rotation_90() {
let mut display1in54 = Buffer1in54BlackWhite::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display1in54.buffer);
let mut display = Display1in54::default();
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Line::new(Coord::new(0, 192), Coord::new(0, 199))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(0, 192), Point::new(0, 199))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
@ -85,14 +122,11 @@ mod tests {
#[test]
fn graphics_rotation_180() {
let mut display1in54 = Buffer1in54BlackWhite::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display1in54.buffer);
let mut display = Display1in54::default();
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Line::new(Coord::new(192, 199), Coord::new(199, 199))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(192, 199), Point::new(199, 199))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
@ -108,14 +142,11 @@ mod tests {
#[test]
fn graphics_rotation_270() {
let mut display1in54 = Buffer1in54BlackWhite::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display1in54.buffer);
let mut display = Display1in54::default();
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Line::new(Coord::new(199, 0), Coord::new(199, 7))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(199, 0), Point::new(199, 7))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();

View File

@ -2,86 +2,99 @@
//!
//! # Example for the 1.54 in E-Ink Display
//!
//! ```ignore
//! use epd_waveshare::{
//! epd1in54::{EPD1in54, Buffer1in54},
//! graphics::{Display, DisplayRotation},
//! prelude::*,
//! };
//!```rust, no_run
//!# use embedded_hal_mock::*;
//!# fn main() -> Result<(), MockError> {
//!use embedded_graphics::{
//! pixelcolor::BinaryColor::On as Black, prelude::*, primitives::{Line, PrimitiveStyleBuilder},
//!};
//!use epd_waveshare::{epd1in54::*, 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 = EPD1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
//!// Setup EPD
//!let mut epd = Epd1in54::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
//!
//! // Use display graphics
//! let mut buffer = Buffer1in54::default();
//! let mut display = Display::new(epd.width(), epd.height(), &mut buffer.buffer);
//!// Use display graphics from embedded-graphics
//!let mut display = Display1in54::default();
//!
//! // Write some hello world in the screenbuffer
//! display.draw(
//! Font6x8::render_str("Hello World!")
//! .with_stroke(Some(Color::Black))
//! .with_fill(Some(Color::White))
//! .translate(Coord::new(5, 50))
//! .into_iter(),
//! );
//!// 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(style)
//! .draw(&mut display);
//!
//! // Display updated frame
//! epd.update_frame(&mut spi, &display.buffer()).unwrap();
//! epd.display_frame(&mut spi).expect("display frame new graphics");
//!// Display updated frame
//!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).expect("sleep");
//! ```
//!// Set the EPD to sleep
//!epd.sleep(&mut spi, &mut delay)?;
//!# Ok(())
//!# }
//!```
/// Width of the display
pub const WIDTH: u32 = 200;
/// Height of the display
pub const HEIGHT: u32 = 200;
//const DPI: u16 = 184;
/// Default Background Color
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
//const DPI: u16 = 184;
const IS_BUSY_LOW: bool = false;
use hal::{
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use type_a::{
use crate::type_a::{
command::Command,
constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE},
};
use color::Color;
use crate::color::Color;
use traits::{RefreshLUT, WaveshareDisplay};
use crate::traits::{RefreshLut, WaveshareDisplay};
use interface::DisplayInterface;
use crate::interface::DisplayInterface;
#[cfg(feature = "graphics")]
mod graphics;
pub use epd1in54::graphics::Buffer1in54BlackWhite as Buffer1in54;
#[cfg(feature = "graphics")]
pub use crate::epd1in54::graphics::Display1in54;
/// EPD1in54 driver
///
pub struct EPD1in54<SPI, CS, BUSY, DC, RST> {
/// Epd1in54 driver
pub struct Epd1in54<SPI, CS, BUSY, DC, RST, DELAY> {
/// SPI
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Color
background_color: Color,
/// Refresh LUT
refresh: RefreshLUT,
refresh: RefreshLut,
}
impl<SPI, CS, BUSY, DC, RST> EPD1in54<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn init<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error> {
self.interface.reset(delay);
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
self.interface.reset(delay, 10);
// 3 Databytes:
// A[7:0]
@ -90,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],
)?;
@ -99,42 +112,44 @@ 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)
self.set_lut(spi, None)?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST, E> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
for EPD1in54<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, E, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8, Error = E>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn width(&self) -> u32 {
WIDTH
}
@ -143,7 +158,7 @@ where
HEIGHT
}
fn new<DELAY: DelayMs<u8>>(
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
@ -153,10 +168,10 @@ where
) -> Result<Self, SPI::Error> {
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)?;
@ -164,28 +179,30 @@ where
Ok(epd)
}
fn wake_up<DELAY: DelayMs<u8>>(
&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])?;
self.wait_until_idle();
.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(())
}
//TODO: update description: last 3 bits will be ignored for width and x_pos
@ -198,33 +215,51 @@ where
width: u32,
height: u32,
) -> Result<(), SPI::Error> {
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::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 clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
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();
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.data_x_times(spi, color, WIDTH / 8 * HEIGHT)
self.interface.cmd(spi, Command::WriteRam)?;
self.interface
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
Ok(())
}
fn set_background_color(&mut self, background_color: Color) {
@ -238,28 +273,33 @@ where
fn set_lut(
&mut self,
spi: &mut SPI,
refresh_rate: Option<RefreshLUT>,
refresh_rate: Option<RefreshLut>,
) -> 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),
}
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST> EPD1in54<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn wait_until_idle(&mut self) {
self.interface.wait_until_idle(false);
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
}
pub(crate) fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
@ -278,6 +318,7 @@ where
end_x: u32,
end_y: u32,
) -> Result<(), SPI::Error> {
self.wait_until_idle();
assert!(start_x < end_x);
assert!(start_y < end_y);
@ -285,21 +326,22 @@ 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,
end_y as u8,
(end_y >> 8) as u8,
],
)
)?;
Ok(())
}
pub(crate) fn set_ram_counter(
@ -308,26 +350,28 @@ where
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::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],
)?;
self.wait_until_idle();
Ok(())
}
fn set_lut_helper(&mut self, spi: &mut SPI, buffer: &[u8]) -> Result<(), SPI::Error> {
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(())
}
}

40
src/epd1in54b/command.rs Normal file
View File

@ -0,0 +1,40 @@
//! SPI Commands for the Waveshare 1.54" red 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,
DataStartTransmission1 = 0x10,
DisplayRefresh = 0x12,
DataStartTransmission2 = 0x13,
LutForVcom = 0x20,
LutWhiteToWhite = 0x21,
LutBlackToWhite = 0x22,
LutG0 = 0x23,
LutG1 = 0x24,
LutRedVcom = 0x25,
LutRed0 = 0x26,
LutRed1 = 0x27,
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
}
}

View File

@ -0,0 +1,31 @@
pub(crate) const LUT_VCOM0: &[u8] = &[
0x0E, 0x14, 0x01, 0x0A, 0x06, 0x04, 0x0A, 0x0A, 0x0F, 0x03, 0x03, 0x0C, 0x06, 0x0A, 0x00,
];
pub(crate) const LUT_WHITE_TO_WHITE: &[u8] = &[
0x0E, 0x14, 0x01, 0x0A, 0x46, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x86, 0x0A, 0x04,
];
pub(crate) const LUT_BLACK_TO_WHITE: &[u8] = &[
0x0E, 0x14, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x4A, 0x04,
];
pub(crate) const LUT_G1: &[u8] = &[
0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04,
];
pub(crate) const LUT_G2: &[u8] = &[
0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04,
];
pub(crate) const LUT_RED_VCOM: &[u8] = &[
0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const LUT_RED0: &[u8] = &[
0x83, 0x5D, 0x01, 0x81, 0x48, 0x23, 0x77, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
pub(crate) const LUT_RED1: &[u8] = &[
0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];

61
src/epd1in54b/graphics.rs Normal file
View File

@ -0,0 +1,61 @@
use crate::epd1in54b::{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 1in54 EPD
///
/// Can also be manually constructed and be used together with VarDisplay
pub struct Display1in54b {
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation,
}
impl Default for Display1in54b {
fn default() -> Self {
Display1in54b {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation::default(),
}
}
}
impl DrawTarget for Display1in54b {
type Color = BinaryColor;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper(WIDTH, HEIGHT, pixel)?;
}
Ok(())
}
}
impl OriginDimensions for Display1in54b {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Display for Display1in54b {
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
}
}

372
src/epd1in54b/mod.rs Normal file
View File

@ -0,0 +1,372 @@
//! A simple Driver for the Waveshare 1.54" (B) 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,
};
//The Lookup Tables for the Display
mod constants;
use crate::epd1in54b::constants::*;
/// Width of epd1in54 in pixels
pub const WIDTH: u32 = 200;
/// Height of epd1in54 in pixels
pub const HEIGHT: u32 = 200;
/// Default Background Color (white)
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::Display1in54b;
/// Epd1in54b driver
pub struct Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY> {
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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::PowerSetting, &[0x07, 0x00, 0x08, 0x00])?;
// start the booster
self.interface
.cmd_with_data(spi, Command::BoosterSoftStart, &[0x07, 0x07, 0x07])?;
// 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, &[0xCF])?;
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x37])?;
// PLL
self.cmd_with_data(spi, Command::PllControl, &[0x39])?;
// set resolution
self.send_resolution(spi)?;
self.cmd_with_data(spi, Command::VcmDcSetting, &[0x0E])?;
self.set_lut(spi, None)?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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.send_resolution(spi)?;
self.interface.cmd(spi, Command::DataStartTransmission1)?;
for b in black {
let expanded = expand_bits(*b);
self.interface.data(spi, &expanded)?;
}
Ok(())
}
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)?;
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
let interface = DisplayInterface::new(cs, busy, dc, rst);
let color = DEFAULT_BACKGROUND_COLOR;
let mut epd = Epd1in54b { 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.interface
.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x17])?; //border floating
self.interface
.cmd_with_data(spi, Command::VcmDcSetting, &[0x00])?; // Vcom to 0V
self.interface
.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::PowerOff)?;
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.wait_until_idle();
self.send_resolution(spi)?;
self.interface.cmd(spi, Command::DataStartTransmission1)?;
for b in buffer {
// Two bits per pixel
let expanded = expand_bits(*b);
self.interface.data(spi, &expanded)?;
}
//NOTE: Example code has a delay here
// Clear the read layer
let color = self.color.get_byte_value();
let nbits = WIDTH * (HEIGHT / 8);
self.interface.cmd(spi, Command::DataStartTransmission2)?;
self.interface.data_x_times(spi, color, nbits)?;
//NOTE: Example code has a delay here
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.wait_until_idle();
self.command(spi, Command::DisplayRefresh)?;
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();
self.send_resolution(spi)?;
let color = DEFAULT_BACKGROUND_COLOR.get_byte_value();
// Clear the black
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::DataStartTransmission2)?;
self.interface
.data_x_times(spi, color, WIDTH * HEIGHT / 8)?;
Ok(())
}
fn set_lut(
&mut self,
spi: &mut SPI,
_refresh_rate: Option<RefreshLut>,
) -> Result<(), SPI::Error> {
self.interface
.cmd_with_data(spi, Command::LutForVcom, LUT_VCOM0)?;
self.interface
.cmd_with_data(spi, Command::LutWhiteToWhite, LUT_WHITE_TO_WHITE)?;
self.interface
.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::LutRedVcom, LUT_RED_VCOM)?;
self.interface
.cmd_with_data(spi, Command::LutRed0, LUT_RED0)?;
self.interface
.cmd_with_data(spi, Command::LutRed1, LUT_RED1)?;
Ok(())
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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])
}
}
fn expand_bits(bits: u8) -> [u8; 2] {
let mut x = bits as u16;
x = (x | (x << 4)) & 0x0F0F;
x = (x | (x << 2)) & 0x3333;
x = (x | (x << 1)) & 0x5555;
x = x | (x << 1);
[(x >> 8) as u8, (x & 0xFF) as u8]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 200);
assert_eq!(HEIGHT, 200);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

38
src/epd1in54c/command.rs Normal file
View File

@ -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
}
}

60
src/epd1in54c/graphics.rs Normal file
View File

@ -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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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
}
}

300
src/epd1in54c/mod.rs Normal file
View File

@ -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<SPI, CS, BUSY, DC, RST, DELAY> {
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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<RefreshLut>,
) -> Result<(), SPI::Error> {
Ok(())
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd1in54c<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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])
}
}

278
src/epd2in13_v2/command.rs Normal file
View File

@ -0,0 +1,278 @@
//! SPI Commands for the Waveshare 2.13" v2
use crate::traits;
extern crate bit_field;
use bit_field::BitField;
/// Epd2in13 v2
///
/// 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 {
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 {
pub scan_is_linear: bool,
pub scan_g0_is_first: bool,
pub scan_dir_incr: bool,
pub width: u16,
}
impl DriverOutput {
pub fn to_bytes(&self) -> [u8; 3] {
[
self.width as u8,
(self.width >> 8) as u8,
*0u8.set_bit(0, !self.scan_dir_incr)
.set_bit(1, !self.scan_g0_is_first)
.set_bit(2, !self.scan_is_linear),
]
}
}
/// These are not directly documented, but the bitfield is easily reversed from
/// documentation and sample code
/// [7|6|5|4|3|2|1|0]
/// | | | | | | | `--- disable clock
/// | | | | | | `----- disable analog
/// | | | | | `------- display
/// | | | | `--------- undocumented and unknown use,
/// | | | | but used in waveshare reference code
/// | | | `----------- load LUT
/// | | `------------- load temp
/// | `--------------- enable clock
/// `----------------- enable analog
pub(crate) struct DisplayUpdateControl2(pub u8);
#[allow(dead_code)]
impl DisplayUpdateControl2 {
pub fn new() -> DisplayUpdateControl2 {
DisplayUpdateControl2(0x00)
}
pub fn disable_clock(mut self) -> Self {
self.0.set_bit(0, true);
self
}
pub fn disable_analog(mut self) -> Self {
self.0.set_bit(1, true);
self
}
pub fn display(mut self) -> Self {
self.0.set_bit(2, true);
self
}
pub fn load_lut(mut self) -> Self {
self.0.set_bit(4, true);
self
}
pub fn load_temp(mut self) -> Self {
self.0.set_bit(5, true);
self
}
pub fn enable_clock(mut self) -> Self {
self.0.set_bit(6, true);
self
}
pub fn enable_analog(mut self) -> Self {
self.0.set_bit(7, true);
self
}
}
#[allow(dead_code)]
pub(crate) enum DataEntryModeIncr {
XDecrYDecr = 0x0,
XIncrYDecr = 0x1,
XDecrYIncr = 0x2,
XIncrYIncr = 0x3,
}
#[allow(dead_code)]
pub(crate) enum DataEntryModeDir {
XDir = 0x0,
YDir = 0x4,
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub(crate) enum BorderWaveFormVbd {
Gs = 0x0,
FixLevel = 0x1,
Vcom = 0x2,
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub(crate) enum BorderWaveFormFixLevel {
Vss = 0x0,
Vsh1 = 0x1,
Vsl = 0x2,
Vsh2 = 0x3,
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub(crate) enum BorderWaveFormGs {
Lut0 = 0x0,
Lut1 = 0x1,
Lut2 = 0x2,
Lut3 = 0x3,
}
pub(crate) struct BorderWaveForm {
pub vbd: BorderWaveFormVbd,
pub fix_level: BorderWaveFormFixLevel,
pub gs_trans: BorderWaveFormGs,
}
impl BorderWaveForm {
pub fn to_u8(&self) -> u8 {
*0u8.set_bits(6..8, self.vbd as u8)
.set_bits(4..6, self.fix_level as u8)
.set_bits(0..2, self.gs_trans as u8)
}
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum DeepSleepMode {
// Sleeps and keeps access to RAM and controller
Normal = 0x00,
// Sleeps without access to RAM/controller but keeps RAM content
Mode1 = 0x01,
// Same as MODE_1 but RAM content is not kept
Mode2 = 0x11,
}
pub(crate) struct GateDrivingVoltage(pub u8);
pub(crate) struct SourceDrivingVoltage(pub u8);
pub(crate) struct Vcom(pub u8);
pub(crate) trait I32Ext {
fn vcom(self) -> Vcom;
fn gate_driving_decivolt(self) -> GateDrivingVoltage;
fn source_driving_decivolt(self) -> SourceDrivingVoltage;
}
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!((-30..=-2).contains(&self));
let u = match -self {
2 => 0x08,
3 => 0x0B,
4 => 0x10,
5 => 0x14,
6 => 0x17,
7 => 0x1B,
8 => 0x20,
9 => 0x24,
10 => 0x28,
11 => 0x2C,
12 => 0x2F,
13 => 0x34,
14 => 0x37,
15 => 0x3C,
16 => 0x40,
17 => 0x44,
18 => 0x48,
19 => 0x4B,
20 => 0x50,
21 => 0x54,
22 => 0x58,
23 => 0x5B,
24 => 0x5F,
25 => 0x64,
26 => 0x68,
27 => 0x6C,
28 => 0x6F,
29 => 0x73,
30 => 0x78,
_ => 0,
};
Vcom(u)
}
fn gate_driving_decivolt(self) -> GateDrivingVoltage {
assert!((100..=210).contains(&self) && self % 5 == 0);
GateDrivingVoltage(((self - 100) / 5 + 0x03) as u8)
}
fn source_driving_decivolt(self) -> SourceDrivingVoltage {
assert!((24..=88).contains(&self) || (self % 5 == 0 && (90..=180).contains(&self.abs())));
if (24..=88).contains(&self) {
SourceDrivingVoltage(((self - 24) + 0x8E) as u8)
} else if (90..=180).contains(&self) {
SourceDrivingVoltage(((self - 90) / 2 + 0x23) as u8)
} else {
SourceDrivingVoltage((((-self - 90) / 5) * 2 + 0x1A) as u8)
}
}
}
impl traits::Command for Command {
/// Returns the address of the command
fn address(self) -> u8 {
self as u8
}
}

View File

@ -0,0 +1,35 @@
#[rustfmt::skip]
// Original Waveforms from Waveshare
pub(crate) const LUT_FULL_UPDATE: [u8; 70] =[
0x80,0x60,0x40,0x00,0x00,0x00,0x00, // LUT0: BB: VS 0 ~7
0x10,0x60,0x20,0x00,0x00,0x00,0x00, // LUT1: BW: VS 0 ~7
0x80,0x60,0x40,0x00,0x00,0x00,0x00, // LUT2: WB: VS 0 ~7
0x10,0x60,0x20,0x00,0x00,0x00,0x00, // LUT3: WW: VS 0 ~7
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT4: VCOM: VS 0 ~7
0x03,0x03,0x00,0x00,0x02, // TP0 A~D RP0
0x09,0x09,0x00,0x00,0x02, // TP1 A~D RP1
0x03,0x03,0x00,0x00,0x02, // TP2 A~D RP2
0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3
0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4
0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5
0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6
];
#[rustfmt::skip]
pub(crate) const LUT_PARTIAL_UPDATE: [u8; 70] =[
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT0: BB: VS 0 ~7
0x80,0x00,0x00,0x00,0x00,0x00,0x00, // LUT1: BW: VS 0 ~7
0x40,0x00,0x00,0x00,0x00,0x00,0x00, // LUT2: WB: VS 0 ~7
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT3: WW: VS 0 ~7
0x00,0x00,0x00,0x00,0x00,0x00,0x00, // LUT4: VCOM: VS 0 ~7
0x0A,0x00,0x00,0x00,0x00, // TP0 A~D RP0
0x00,0x00,0x00,0x00,0x00, // TP1 A~D RP1
0x00,0x00,0x00,0x00,0x00, // TP2 A~D RP2
0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3
0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4
0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5
0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6
];

171
src/epd2in13_v2/graphics.rs Normal file
View File

@ -0,0 +1,171 @@
use crate::buffer_len;
use crate::epd2in13_v2::{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 2in13 v2 EPD
///
/// Can also be manually constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
pub struct Display2in13 {
buffer: [u8; buffer_len(WIDTH as usize, HEIGHT as usize)],
rotation: DisplayRotation,
}
impl Default for Display2in13 {
fn default() -> Self {
Display2in13 {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
buffer_len(WIDTH as usize, HEIGHT as usize)],
rotation: DisplayRotation::default(),
}
}
}
impl DrawTarget for Display2in13 {
type Color = BinaryColor;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper(WIDTH, HEIGHT, pixel)?;
}
Ok(())
}
}
impl OriginDimensions for Display2in13 {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Display for Display2in13 {
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::epd2in13_v2;
use crate::graphics::{Display, DisplayRotation};
use embedded_graphics::{
prelude::*,
primitives::{Line, PrimitiveStyle},
};
// test buffer length
#[test]
fn graphics_size() {
let display = Display2in13::default();
assert_eq!(display.buffer().len(), 4000);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let display = Display2in13::default();
for &byte in display.buffer() {
assert_eq!(byte, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_0() {
let mut display = Display2in13::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, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_90() {
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate90);
let _ = Line::new(
Point::new(0, (WIDTH - 8) as i32),
Point::new(0, (WIDTH - 1) as i32),
)
.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, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_180() {
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate180);
let _ = Line::new(
Point::new((WIDTH - 8) as i32, (HEIGHT - 1) as i32),
Point::new((WIDTH - 1) as i32, (HEIGHT - 1) as i32),
)
.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, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_270() {
let mut display = Display2in13::default();
display.set_rotation(DisplayRotation::Rotate270);
let _ = Line::new(
Point::new((HEIGHT - 1) as i32, 0),
Point::new((HEIGHT - 1) as i32, 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, epd2in13_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
}

577
src/epd2in13_v2/mod.rs Normal file
View File

@ -0,0 +1,577 @@
//! A Driver for the Waveshare 2.13" E-Ink Display (V2) via SPI
//!
//! # References
//!
//! - [Waveshare product page](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT)
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_2in13_V2.c)
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd2in13_V2.py)
//! - [Controller Datasheet SS1780](http://www.e-paper-display.com/download_detail/downloadsId=682.html)
//!
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::v2::{InputPin, OutputPin},
};
use crate::buffer_len;
use crate::color::Color;
use crate::interface::DisplayInterface;
use crate::traits::{InternalWiAdditions, RefreshLut, WaveshareDisplay};
pub(crate) mod command;
use self::command::{
BorderWaveForm, BorderWaveFormFixLevel, BorderWaveFormGs, BorderWaveFormVbd, Command,
DataEntryModeDir, DataEntryModeIncr, DeepSleepMode, DisplayUpdateControl2, DriverOutput,
GateDrivingVoltage, I32Ext, SourceDrivingVoltage, Vcom,
};
pub(crate) mod constants;
use self::constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE};
#[cfg(feature = "graphics")]
mod graphics;
#[cfg(feature = "graphics")]
pub use self::graphics::Display2in13;
/// Width of the display.
pub const WIDTH: u32 = 122;
/// Height of the display
pub const HEIGHT: u32 = 250;
/// Default Background Color
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = false;
/// Epd2in13 (V2) driver
///
pub struct Epd2in13<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
sleep_mode: DeepSleepMode,
/// Background Color
background_color: Color,
refresh: RefreshLut,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in13<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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 {
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::WriteOtpSelection, &[0, 0, 0, 0, 0x40, 0, 0])?;
// During partial update, clock/analog are not disabled between 2
// updates.
self.set_display_update_control_2(
spi,
DisplayUpdateControl2::new().enable_analog().enable_clock(),
)?;
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,
},
)?;
} else {
self.wait_until_idle();
self.command(spi, Command::SwReset)?;
self.wait_until_idle();
self.set_driver_output(
spi,
DriverOutput {
scan_is_linear: true,
scan_g0_is_first: true,
scan_dir_incr: true,
width: (HEIGHT - 1) as u16,
},
)?;
// These 2 are the reset values
self.set_dummy_line_period(spi, 0x30)?;
self.set_gate_scan_start_position(spi, 0)?;
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)?;
self.set_ram_address_counters(spi, 0, 0)?;
self.set_border_waveform(
spi,
BorderWaveForm {
vbd: BorderWaveFormVbd::Gs,
fix_level: BorderWaveFormFixLevel::Vss,
gs_trans: BorderWaveFormGs::Lut3,
},
)?;
self.set_vcom_register(spi, (-21).vcom())?;
self.set_gate_driving_voltage(spi, 190.gate_driving_decivolt())?;
self.set_source_driving_voltage(
spi,
150.source_driving_decivolt(),
50.source_driving_decivolt(),
(-150).source_driving_decivolt(),
)?;
self.set_gate_line_width(spi, 10)?;
self.set_lut(spi, Some(self.refresh))?;
}
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in13<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
let mut epd = Epd2in13 {
interface: DisplayInterface::new(cs, busy, dc, rst),
sleep_mode: DeepSleepMode::Mode1,
background_color: DEFAULT_BACKGROUND_COLOR,
refresh: RefreshLut::Full,
};
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();
// All sample code enables and disables analog/clocks...
self.set_display_update_control_2(
spi,
DisplayUpdateControl2::new()
.enable_analog()
.enable_clock()
.disable_analog()
.disable_clock(),
)?;
self.command(spi, Command::MasterActivation)?;
self.set_sleep_mode(spi, self.sleep_mode)?;
Ok(())
}
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::WriteRam, buffer)?;
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::WriteRamRed, buffer)?;
}
Ok(())
}
/// Updating only a part of the frame is not supported when using the
/// partial refresh feature. The function will panic if called when set to
/// use partial refresh.
fn update_partial_frame(
&mut self,
spi: &mut SPI,
buffer: &[u8],
x: u32,
y: u32,
width: u32,
height: u32,
) -> Result<(), SPI::Error> {
assert!((width * height / 8) as usize == buffer.len());
// This should not be used when doing partial refresh. The RAM_RED must
// be updated with the last buffer having been displayed. Doing partial
// update directly in RAM makes this update impossible (we can't read
// 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);
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::WriteRam, buffer)?;
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::WriteRamRed, buffer)?;
}
Ok(())
}
/// 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, _delay: &mut DELAY) -> Result<(), SPI::Error> {
if self.refresh == RefreshLut::Full {
self.set_display_update_control_2(
spi,
DisplayUpdateControl2::new()
.enable_clock()
.enable_analog()
.display()
.disable_analog()
.disable_clock(),
)?;
} else {
self.set_display_update_control_2(spi, DisplayUpdateControl2::new().display())?;
}
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)?;
if self.refresh == RefreshLut::Quick {
self.set_partial_base_buffer(spi, buffer)?;
}
Ok(())
}
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::WriteRam)?;
self.interface.data_x_times(
spi,
color,
buffer_len(WIDTH as usize, HEIGHT as usize) as u32,
)?;
// Always keep the base buffer equals to current if not doing partial refresh.
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::WriteRamRed)?;
self.interface.data_x_times(
spi,
color,
buffer_len(WIDTH as usize, HEIGHT as usize) as u32,
)?;
}
Ok(())
}
fn set_background_color(&mut self, background_color: Color) {
self.background_color = background_color;
}
fn background_color(&self) -> &Color {
&self.background_color
}
fn width(&self) -> u32 {
WIDTH
}
fn height(&self) -> u32 {
HEIGHT
}
fn set_lut(
&mut self,
spi: &mut SPI,
refresh_rate: Option<RefreshLut>,
) -> Result<(), SPI::Error> {
let buffer = match refresh_rate {
Some(RefreshLut::Full) | None => &LUT_FULL_UPDATE,
Some(RefreshLut::Quick) => &LUT_PARTIAL_UPDATE,
};
self.cmd_with_data(spi, Command::WriteLutRegister, buffer)
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in13<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// When using partial refresh, the controller uses the provided buffer for
/// comparison with new buffer.
pub fn set_partial_base_buffer(
&mut self,
spi: &mut SPI,
buffer: &[u8],
) -> Result<(), SPI::Error> {
assert!(buffer_len(WIDTH as usize, HEIGHT as usize) == buffer.len());
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::WriteRamRed, buffer)?;
Ok(())
}
/// Selects which sleep mode will be used when triggering the deep sleep.
pub fn set_deep_sleep_mode(&mut self, mode: DeepSleepMode) {
self.sleep_mode = mode;
}
/// Sets the refresh mode. When changing mode, the screen will be
/// re-initialized accordingly.
pub fn set_refresh(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
refresh: RefreshLut,
) -> Result<(), SPI::Error> {
if self.refresh != refresh {
self.refresh = refresh;
self.init(spi, delay)?;
}
Ok(())
}
fn set_gate_scan_start_position(
&mut self,
spi: &mut SPI,
start: u16,
) -> Result<(), SPI::Error> {
assert!(start <= 295);
self.cmd_with_data(
spi,
Command::GateScanStartPosition,
&[(start & 0xFF) as u8, ((start >> 8) & 0x1) as u8],
)
}
fn set_border_waveform(
&mut self,
spi: &mut SPI,
borderwaveform: BorderWaveForm,
) -> Result<(), SPI::Error> {
self.cmd_with_data(
spi,
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::WriteVcomRegister, &[vcom.0])
}
fn set_gate_driving_voltage(
&mut self,
spi: &mut SPI,
voltage: GateDrivingVoltage,
) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::GateDrivingVoltageCtrl, &[voltage.0])
}
fn set_dummy_line_period(
&mut self,
spi: &mut SPI,
number_of_lines: u8,
) -> Result<(), SPI::Error> {
assert!(number_of_lines <= 127);
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::SetGateLineWidth, &[width & 0x0F])
}
/// Sets the source driving voltage value
fn set_source_driving_voltage(
&mut self,
spi: &mut SPI,
vsh1: SourceDrivingVoltage,
vsh2: SourceDrivingVoltage,
vsl: SourceDrivingVoltage,
) -> Result<(), SPI::Error> {
self.cmd_with_data(
spi,
Command::SourceDrivingVoltageCtrl,
&[vsh1.0, vsh2.0, vsl.0],
)
}
/// Prepare the actions that the next master activation command will
/// trigger.
fn set_display_update_control_2(
&mut self,
spi: &mut SPI,
value: DisplayUpdateControl2,
) -> Result<(), SPI::Error> {
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::DeepSleepMode, &[mode as u8])
}
fn set_driver_output(&mut self, spi: &mut SPI, output: DriverOutput) -> Result<(), SPI::Error> {
self.cmd_with_data(spi, Command::DriverOutputControl, &output.to_bytes())
}
/// Sets the data entry mode (ie. how X and Y positions changes when writing
/// data to RAM)
fn set_data_entry_mode(
&mut self,
spi: &mut SPI,
counter_incr_mode: DataEntryModeIncr,
counter_direction: DataEntryModeDir,
) -> Result<(), SPI::Error> {
let mode = counter_incr_mode as u8 | counter_direction as u8;
self.cmd_with_data(spi, Command::DataEntryModeSetting, &[mode])
}
/// Sets both X and Y pixels ranges
fn set_ram_area(
&mut self,
spi: &mut SPI,
start_x: u32,
start_y: u32,
end_x: u32,
end_y: u32,
) -> Result<(), SPI::Error> {
self.cmd_with_data(
spi,
Command::SetRamXAddressStartEndPosition,
&[(start_x >> 3) as u8, (end_x >> 3) as u8],
)?;
self.cmd_with_data(
spi,
Command::SetRamYAddressStartEndPosition,
&[
start_y as u8,
(start_y >> 8) as u8,
end_y as u8,
(end_y >> 8) as u8,
],
)
}
/// Sets both X and Y pixels counters when writing data to RAM
fn set_ram_address_counters(
&mut self,
spi: &mut SPI,
x: u32,
y: u32,
) -> Result<(), SPI::Error> {
self.wait_until_idle();
self.cmd_with_data(spi, Command::SetRamXAddressCounter, &[(x >> 3) as u8])?;
self.cmd_with_data(
spi,
Command::SetRamYAddressCounter,
&[y as u8, (y >> 8) as u8],
)?;
Ok(())
}
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) {
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 122);
assert_eq!(HEIGHT, 250);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

38
src/epd2in13bc/command.rs Normal file
View File

@ -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
}
}

View File

@ -0,0 +1,74 @@
use crate::color::TriColor;
use crate::epd2in13bc::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH};
use crate::graphics::{DisplayColorRendering, 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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper_tri(WIDTH, HEIGHT, pixel, DisplayColorRendering::Positive)?;
}
Ok(())
}
}
impl OriginDimensions for Display2in13bc {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl TriDisplay for Display2in13bc {
fn buffer(&self) -> &[u8] {
&self.buffer
}
fn get_mut_buffer(&mut self) -> &mut [u8] {
&mut self.buffer
}
fn set_rotation(&mut self, rotation: DisplayRotation) {
self.rotation = rotation;
}
fn rotation(&self) -> DisplayRotation {
self.rotation
}
fn chromatic_offset(&self) -> usize {
NUM_DISPLAY_BITS as usize
}
fn bw_buffer(&self) -> &[u8] {
&self.buffer[0..self.chromatic_offset()]
}
fn chromatic_buffer(&self) -> &[u8] {
&self.buffer[self.chromatic_offset()..]
}
}

386
src/epd2in13bc/mod.rs Normal file
View File

@ -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<SPI, CS, BUSY, DC, RST, DELAY> {
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
color: TriColor,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = TriColor;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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<RefreshLut>,
) -> Result<(), SPI::Error> {
Ok(())
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in13bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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],
)
}
}

137
src/epd2in7b/command.rs Normal file
View File

@ -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);
}
}

55
src/epd2in7b/constants.rs Normal file
View File

@ -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,
];

175
src/epd2in7b/graphics.rs Normal file
View File

@ -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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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());
}
}
}

456
src/epd2in7b/mod.rs Normal file
View File

@ -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<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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<RefreshLut>,
) -> 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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> Epd2in7b<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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);
}
}

View File

@ -1,40 +1,81 @@
use epd2in9::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
use crate::epd2in9::{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 2in9 EPD
/// Display with Fullsize buffer for use with the 2in9 EPD
///
/// Can also be manuall constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
pub struct Buffer2in9 {
pub buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
pub struct Display2in9 {
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation,
}
impl Default for Buffer2in9 {
impl Default for Display2in9 {
fn default() -> Self {
Buffer2in9 {
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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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::*;
use graphics::Display;
// test buffer length
#[test]
fn graphics_size() {
let mut buffer = Buffer2in9::default();
let display = Display::new(WIDTH, HEIGHT, &mut buffer.buffer);
let display = Display2in9::default();
assert_eq!(display.buffer().len(), 4736);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let mut buffer = Buffer2in9::default();
let display = Display::new(WIDTH, HEIGHT, &mut buffer.buffer);
let display = Display2in9::default();
for &byte in display.buffer() {
assert_eq!(byte, DEFAULT_BACKGROUND_COLOR.get_byte_value());
}

View File

@ -1,88 +1,99 @@
//! A simple Driver for the Waveshare 2.9" E-Ink Display via SPI
//!
//! Untested!
//!
//! # Example for the 2.9 in E-Ink Display
//!
//! ```ignore
//! use epd_waveshare::{
//! epd2in9::{EPD2in9, Buffer2in9},
//! graphics::{Display, DisplayRotation},
//! prelude::*,
//! };
//!```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::*, 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)?;
//!// Setup EPD
//!let mut epd = Epd2in9::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
//!
//! // Use display graphics
//! let mut buffer = Buffer2in9::default();
//! let mut display = Display::new(epd.width(), epd.height(), &mut buffer.buffer);
//!// Use display graphics from embedded-graphics
//!let mut display = Display2in9::default();
//!
//! // Write some hello world in the screenbuffer
//! display.draw(
//! Font6x8::render_str("Hello World!")
//! .with_stroke(Some(Color::Black))
//! .with_fill(Some(Color::White))
//! .translate(Coord::new(5, 50))
//! .into_iter(),
//! );
//!// 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()).unwrap();
//! epd.display_frame(&mut spi).expect("display frame new graphics");
//! // Display updated frame
//!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).expect("sleep");
//! ```
//!// 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;
use hal::{
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use type_a::{
use crate::type_a::{
command::Command,
constants::{LUT_FULL_UPDATE, LUT_PARTIAL_UPDATE},
};
use color::Color;
use crate::color::Color;
use traits::*;
use crate::traits::*;
use interface::DisplayInterface;
use crate::interface::DisplayInterface;
#[cfg(feature = "graphics")]
mod graphics;
pub use epd2in9::graphics::Buffer2in9;
#[cfg(feature = "graphics")]
pub use crate::epd2in9::graphics::Display2in9;
/// EPD2in9 driver
/// Epd2in9 driver
///
pub struct EPD2in9<SPI, CS, BUSY, DC, RST> {
pub struct Epd2in9<SPI, CS, BUSY, DC, RST, DELAY> {
/// SPI
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Color
background_color: Color,
/// Refresh LUT
refresh: RefreshLUT,
refresh: RefreshLut,
}
impl<SPI, CS, BUSY, DC, RST> EPD2in9<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn init<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error> {
self.interface.reset(delay);
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
self.interface.reset(delay, 10);
self.wait_until_idle();
// 3 Databytes:
// A[7:0]
@ -90,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<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
for EPD2in9<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn width(&self) -> u32 {
WIDTH
}
@ -141,7 +151,7 @@ where
HEIGHT
}
fn new<DELAY: DelayMs<u8>>(
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
@ -151,10 +161,10 @@ where
) -> Result<Self, SPI::Error> {
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)?;
@ -162,29 +172,33 @@ 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])?;
self.wait_until_idle();
.cmd_with_data(spi, Command::DeepSleepMode, &[0x00])?;
Ok(())
}
fn wake_up<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error> {
self.init(spi, delay)
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(())
}
//TODO: update description: last 3 bits will be ignored for width and x_pos
@ -197,33 +211,51 @@ where
width: u32,
height: u32,
) -> Result<(), SPI::Error> {
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::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 clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
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();
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.data_x_times(spi, color, WIDTH / 8 * HEIGHT)
self.interface.cmd(spi, Command::WriteRam)?;
self.interface
.data_x_times(spi, color, WIDTH / 8 * HEIGHT)?;
Ok(())
}
fn set_background_color(&mut self, background_color: Color) {
@ -237,28 +269,33 @@ where
fn set_lut(
&mut self,
spi: &mut SPI,
refresh_rate: Option<RefreshLUT>,
refresh_rate: Option<RefreshLut>,
) -> 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),
}
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST> EPD2in9<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn wait_until_idle(&mut self) {
self.interface.wait_until_idle(false);
let _ = self.interface.wait_until_idle(IS_BUSY_LOW);
}
fn use_full_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
@ -284,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,
@ -302,26 +339,28 @@ where
}
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::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],
)?;
self.wait_until_idle();
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();
assert!(buffer.len() == 30);
self.interface
.cmd_with_data(spi, Command::WRITE_LUT_REGISTER, buffer)
.cmd_with_data(spi, Command::WriteLutRegister, buffer)?;
Ok(())
}
}

View File

@ -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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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());
}
}
}

496
src/epd2in9_v2/mod.rs Normal file
View File

@ -0,0 +1,496 @@
//! A simple Driver for the Waveshare 2.9" E-Ink Display V2 via SPI
//!
//! Specification: <https://www.waveshare.com/w/upload/7/79/2.9inch-e-paper-v2-specification.pdf>
//!
//! # 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, CS, BUSY, DC, RST, DELAY> {
/// SPI
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Color
background_color: Color,
/// Refresh LUT
refresh: RefreshLut,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<Self, SPI::Error> {
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<RefreshLut>,
) -> 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<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> QuickRefresh<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in9<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// 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);
}
}

38
src/epd2in9bc/command.rs Normal file
View File

@ -0,0 +1,38 @@
//! SPI Commands for the Waveshare 2.9" (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
}
}

60
src/epd2in9bc/graphics.rs Normal file
View File

@ -0,0 +1,60 @@
use crate::epd2in9bc::{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 2in9b/c EPD
///
/// Can also be manually constructed and be used together with VarDisplay
pub struct Display2in9bc {
buffer: [u8; NUM_DISPLAY_BITS as usize],
rotation: DisplayRotation,
}
impl Default for Display2in9bc {
fn default() -> Self {
Display2in9bc {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); NUM_DISPLAY_BITS as usize],
rotation: DisplayRotation::default(),
}
}
}
impl DrawTarget for Display2in9bc {
type Color = BinaryColor;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper(WIDTH, HEIGHT, pixel)?;
}
Ok(())
}
}
impl OriginDimensions for Display2in9bc {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Display for Display2in9bc {
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
}
}

389
src/epd2in9bc/mod.rs Normal file
View File

@ -0,0 +1,389 @@
//! A simple Driver for the Waveshare 2.9" (B/C) E-Ink Display via SPI
//!
//! # Example for the 2.9" E-Ink Display
//!
//!```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::{epd2in9bc::*, 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 = 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
//!let mut mono_display = Display2in9bc::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(Black, 1))
//! .draw(&mut mono_display);
//!
//!// Use a second display for red/yellow
//!let mut chromatic_display = Display2in9bc::default();
//!
//!// We use `Black` but it will be shown as red/yellow
//!let _ = Line::new(Point::new(15, 120), Point::new(15, 200))
//! .into_styled(PrimitiveStyle::with_stroke(Black, 1))
//! .draw(&mut chromatic_display);
//!
//!// Display updated frame
//!epd.update_color_frame(
//! &mut spi,
//! &mono_display.buffer(),
//! &chromatic_display.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 epd2in9bc in pixels
pub const WIDTH: u32 = 128;
/// Height of epd2in9bc in pixels
pub const HEIGHT: u32 = 296;
/// Default background color (white) of epd2in9bc display
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
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::{Color, TriColor};
pub(crate) mod command;
use self::command::Command;
#[cfg(feature = "graphics")]
mod graphics;
#[cfg(feature = "graphics")]
pub use self::graphics::Display2in9bc;
/// Epd2in9bc driver
pub struct Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY> {
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
let interface = DisplayInterface::new(cs, busy, dc, rst);
let color = DEFAULT_BACKGROUND_COLOR;
let mut epd = Epd2in9bc { 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: 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.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<RefreshLut>,
) -> Result<(), SPI::Error> {
Ok(())
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd2in9bc<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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],
)
}
}

View File

@ -1,5 +1,5 @@
//! SPI Commands for the Waveshare 4.2" E-Ink Display
use traits;
use crate::traits;
/// EPD4IN2 commands
///
/// Should rarely (never?) be needed directly.
@ -8,7 +8,6 @@ use 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 {
@ -165,14 +164,14 @@ impl traits::Command for Command {
#[cfg(test)]
mod tests {
use super::*;
use traits::Command as CommandTrait;
use crate::traits::Command as CommandTrait;
#[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);
}
}

View File

@ -1,115 +1,115 @@
use color::Color;
pub const WIDTH: u32 = 400;
pub const HEIGHT: u32 = 300;
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
//! 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,
];

View File

@ -1,109 +1,134 @@
use epd4in2::constants::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH};
use crate::epd4in2::{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 4in2 EPD
///
/// Can also be manuall constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]`
pub struct Buffer4in2 {
pub buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
pub struct Display4in2 {
buffer: [u8; WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation,
}
impl Default for Buffer4in2 {
impl Default for Display4in2 {
fn default() -> Self {
Buffer4in2 {
Display4in2 {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value();
WIDTH as usize * HEIGHT as usize / 8],
rotation: DisplayRotation::default(),
}
}
}
impl DrawTarget for Display4in2 {
type Color = BinaryColor;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper(WIDTH, HEIGHT, pixel)?;
}
Ok(())
}
}
impl OriginDimensions for Display4in2 {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Display for Display4in2 {
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 color::Color;
use embedded_graphics::coord::Coord;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::Line;
use epd4in2;
use graphics::{Display, DisplayRotation};
use crate::color::Black;
use crate::color::Color;
use crate::epd4in2;
use crate::graphics::{Display, DisplayRotation};
use embedded_graphics::{
prelude::*,
primitives::{Line, PrimitiveStyle},
};
// test buffer length
#[test]
fn graphics_size() {
let mut display4in2 = Buffer4in2::default();
let display = Display::new(WIDTH, HEIGHT, &mut display4in2.buffer);
let display = Display4in2::default();
assert_eq!(display.buffer().len(), 15000);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let mut display4in2 = Buffer4in2::default();
let display = Display::new(WIDTH, HEIGHT, &mut display4in2.buffer);
use epd4in2;
let display = Display4in2::default();
for &byte in display.buffer() {
assert_eq!(
byte,
epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_0() {
let mut display4in2 = Buffer4in2::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display4in2.buffer);
display.draw(
Line::new(Coord::new(0, 0), Coord::new(7, 0))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let mut display = Display4in2::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,
epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_90() {
let mut display4in2 = Buffer4in2::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display4in2.buffer);
let mut display = Display4in2::default();
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Line::new(Coord::new(0, 392), Coord::new(0, 399))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(0, 392), Point::new(0, 399))
.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,
epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_180() {
let mut display4in2 = Buffer4in2::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display4in2.buffer);
let mut display = Display4in2::default();
display.set_rotation(DisplayRotation::Rotate180);
display.draw(
Line::new(Coord::new(392, 299), Coord::new(399, 299))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(392, 299), Point::new(399, 299))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
@ -113,23 +138,17 @@ mod tests {
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(
byte,
epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_270() {
let mut display4in2 = Buffer4in2::default();
let mut display = Display::new(WIDTH, HEIGHT, &mut display4in2.buffer);
let mut display = Display4in2::default();
display.set_rotation(DisplayRotation::Rotate270);
display.draw(
Line::new(Coord::new(299, 0), Coord::new(299, 7))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(299, 0), Point::new(299, 7))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();
@ -139,10 +158,7 @@ mod tests {
assert_eq!(buffer[0], Color::Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(
byte,
epd4in2::constants::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
assert_eq!(byte, epd4in2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
}

View File

@ -1,155 +1,162 @@
//! A simple Driver for the Waveshare 4.2" E-Ink Display via SPI
//!
//! The other Waveshare E-Ink Displays should be added later on
//!
//! Build with the help of documentation/code from [Waveshare](https://www.waveshare.com/wiki/4.2inch_e-Paper_Module),
//! [Ben Krasnows partial Refresh tips](https://benkrasnow.blogspot.de/2017/10/fast-partial-refresh-on-42-e-paper.html) and
//! the driver documents in the `pdfs`-folder as orientation.
//!
//! This driver was built using [`embedded-hal`] traits.
//!
//! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.1
//!
//! # Requirements
//!
//! ### SPI
//!
//! - MISO is not connected/available
//! - SPI_MODE_0 is used (CPHL = 0, CPOL = 0)
//! - 8 bits per word, MSB first
//! - Max. Speed tested was 8Mhz but more should be possible
//!
//! ### Other....
//!
//! - Buffersize: Wherever a buffer is used it always needs to be of the size: `width / 8 * length`,
//! where width and length being either the full e-ink size or the partial update window size
//!
//! # Examples
//!
//! ```ignore
//! let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay).unwrap();
//!```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::*};
//!#
//!# 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();
//!
//! let mut buffer = [0u8, epd4in2.get_width() / 8 * epd4in2.get_height()];
//!// Setup EPD
//!let mut epd = Epd4in2::new(&mut spi, cs_pin, busy_in, dc, rst, &mut delay)?;
//!
//! // draw something into the buffer
//!// Use display graphics from embedded-graphics
//!let mut display = Display4in2::default();
//!
//! epd4in2.display_and_transfer_buffer(buffer, None);
//!// 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);
//!
//! // wait and look at the image
//! // Display updated frame
//!epd.update_frame(&mut spi, &display.buffer(), &mut delay)?;
//!epd.display_frame(&mut spi, &mut delay)?;
//!
//! epd4in2.clear_frame(None);
//!
//! epd4in2.sleep();
//! ```
//!// Set the EPD to sleep
//!epd.sleep(&mut spi, &mut delay)?;
//!# Ok(())
//!# }
//!```
//!
//!
//!
//! BE CAREFUL! The screen can get ghosting/burn-ins through the Partial Fast Update Drawing.
use hal::{
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use interface::DisplayInterface;
use traits::{InternalWiAdditions, RefreshLUT, WaveshareDisplay};
use crate::interface::DisplayInterface;
use crate::traits::{InternalWiAdditions, QuickRefresh, RefreshLut, WaveshareDisplay};
//The Lookup Tables for the Display
mod constants;
pub use self::constants::*;
use crate::epd4in2::constants::*;
use color::Color;
/// Width of the display
pub const WIDTH: u32 = 400;
/// Height of the display
pub const HEIGHT: u32 = 300;
/// 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;
pub use self::graphics::Buffer4in2;
#[cfg(feature = "graphics")]
pub use self::graphics::Display4in2;
/// EPD4in2 driver
/// Epd4in2 driver
///
pub struct EPD4in2<SPI, CS, BUSY, DC, RST> {
pub struct Epd4in2<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST>,
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: Color,
/// Refresh LUT
refresh: RefreshLUT,
refresh: RefreshLut,
}
impl<SPI, CS, BUSY, DC, RST> InternalWiAdditions<SPI, CS, BUSY, DC, RST>
for EPD4in2<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn init<DELAY: DelayMs<u8>>(
&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);
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.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)?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST> WaveshareDisplay<SPI, CS, BUSY, DC, RST>
for EPD4in2<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// Creates a new driver from a SPI peripheral, CS Pin, Busy InputPin, DC
///
/// This already initialises the device. That means [init()](init()) isn't needed directly afterwards
///
/// # Example
///
/// ```ignore
/// //buffer = some image data;
///
/// let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay);
///
/// epd4in2.display_and_transfer_frame(buffer, None);
///
/// epd4in2.sleep();
/// ```
fn new<DELAY: DelayMs<u8>>(
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
@ -160,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)?;
@ -171,50 +178,45 @@ where
Ok(epd)
}
fn wake_up<DELAY: DelayMs<u8>>(
&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(())
}
fn update_partial_frame(
@ -226,13 +228,14 @@ where
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.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
@ -251,37 +254,48 @@ 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)
}
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error> {
self.command(spi, Command::DISPLAY_REFRESH)?;
self.wait_until_idle();
self.command(spi, Command::PartialOut)?;
Ok(())
}
fn clear_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::DisplayRefresh)?;
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();
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::DataStartTransmission2)?;
self.interface
.cmd(spi, Command::DATA_START_TRANSMISSION_2)?;
self.interface
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)
.data_x_times(spi, color_value, WIDTH / 8 * HEIGHT)?;
Ok(())
}
fn set_background_color(&mut self, color: Color) {
@ -303,16 +317,16 @@ where
fn set_lut(
&mut self,
spi: &mut SPI,
refresh_rate: Option<RefreshLUT>,
refresh_rate: Option<RefreshLut>,
) -> 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,
@ -322,15 +336,20 @@ where
),
}
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST> EPD4in2<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn command(&mut self, spi: &mut SPI, command: Command) -> Result<(), SPI::Error> {
self.interface.cmd(spi, command)
@ -350,14 +369,14 @@ where
}
fn wait_until_idle(&mut self) {
self.interface.wait_until_idle(true)
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])?;
@ -373,20 +392,199 @@ where
lut_wb: &[u8],
lut_bb: &[u8],
) -> 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<SPI, CS, BUSY, DC, RST, DELAY> QuickRefresh<SPI, CS, BUSY, DC, RST, DELAY>
for Epd4in2<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// 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(())
}
}

150
src/epd5in65f/command.rs Normal file
View File

@ -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);
}
}

263
src/epd5in65f/graphics.rs Normal file
View File

@ -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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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"),
})
}
}

261
src/epd5in65f/mod.rs Normal file
View File

@ -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<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: OctColor,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = OctColor;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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<RefreshLut>,
) -> Result<(), SPI::Error> {
unimplemented!();
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd5in65f<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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);
}
}

134
src/epd5in83b_v2/command.rs Normal file
View File

@ -0,0 +1,134 @@
//! SPI Commands for the Waveshare 5.83" E-Ink Display
use crate::traits;
/// Epd5in83 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 B/W data and write them into SRAM. To complete data
/// transmission, commands Display Refresh or Data Start Transmission2 must be issued. Then the chip will start to
/// send data/VCOM for panel.
DataStartTransmission1 = 0x10,
/// This command starts transmitting RED data and write them into SRAM. To complete data
/// transmission, command Display refresh must be issued. Then the chip will start to
/// send data/VCOM for panel.
DataStartTransmission2 = 0x13,
/// 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,
/// Enables or disables Dual SPI mode
DualSPI = 0x15,
/// The command controls the PLL clock frequency.
PllControl = 0x30,
/// This command reads the temperature sensed by the temperature sensor.
TemperatureSensorCalibration = 0x40,
/// This command selects the Internal or External temperature sensor.
TemperatureSensorSelection = 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,
/// 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,
/// Sets window size for the partial update
PartialWindow = 0x90,
/// Sets chip into partial update mode
PartialIn = 0x91,
/// Quits partial update mode
PartialOut = 0x92,
}
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);
}
}

View File

@ -0,0 +1,191 @@
use crate::color::TriColor;
use crate::epd5in83b_v2::{DEFAULT_BACKGROUND_COLOR, HEIGHT, NUM_DISPLAY_BITS, WIDTH};
use crate::graphics::{DisplayColorRendering, DisplayRotation};
use crate::prelude::TriDisplay;
use embedded_graphics_core::prelude::*;
/// Full size buffer for use with the 5in83 EPD
///
/// Can also be manually constructed:
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 2 * NUM_DISPLAY_BITS as usize]`
pub struct Display5in83 {
buffer: [u8; 2 * NUM_DISPLAY_BITS as usize],
rotation: DisplayRotation,
}
impl Default for Display5in83 {
fn default() -> Self {
let mut display = Display5in83 {
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 2 * NUM_DISPLAY_BITS as usize],
rotation: DisplayRotation::default(),
};
// We need to invert chromatic part to black so it will be render white
let offset = display.chromatic_offset();
display.buffer[offset..].fill(0x00);
display
}
}
impl DrawTarget for Display5in83 {
type Color = TriColor;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels {
self.draw_helper_tri(WIDTH, HEIGHT, pixel, DisplayColorRendering::Negative)?;
}
Ok(())
}
}
impl OriginDimensions for Display5in83 {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl TriDisplay for Display5in83 {
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()..]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::TriColor::Black;
use crate::epd5in83b_v2;
use crate::graphics::DisplayRotation;
use embedded_graphics::{
prelude::*,
primitives::{Line, PrimitiveStyle},
};
// test buffer length
#[test]
fn graphics_size() {
let display = Display5in83::default();
assert_eq!(display.buffer().len(), 77760); // (77760 = 648 * 480/8) * 2
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let display = Display5in83::default();
for &byte in display.bw_buffer() {
assert_eq!(
byte,
epd5in83b_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
}
for &byte in display.chromatic_buffer() {
assert_eq!(byte, 0x00);
}
}
#[test]
fn graphics_rotation_0() {
let mut display = Display5in83::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.bw_buffer();
assert_eq!(buffer[0], Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(
byte,
epd5in83b_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
}
}
#[test]
fn graphics_rotation_90() {
let mut display = Display5in83::default();
display.set_rotation(DisplayRotation::Rotate90);
let _ = Line::new(Point::new(0, 640), Point::new(0, 647))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.bw_buffer();
assert_eq!(buffer[0], Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(
byte,
epd5in83b_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
}
}
#[test]
fn graphics_rotation_180() {
let mut display = Display5in83::default();
display.set_rotation(DisplayRotation::Rotate180);
let _ = Line::new(Point::new(640, 479), Point::new(647, 479))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.bw_buffer();
assert_eq!(buffer[0], Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(
byte,
epd5in83b_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
}
}
#[test]
fn graphics_rotation_270() {
let mut display = Display5in83::default();
display.set_rotation(DisplayRotation::Rotate270);
let _ = Line::new(Point::new(479, 0), Point::new(479, 7))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.bw_buffer();
assert_eq!(buffer[0], Black.get_byte_value());
for &byte in buffer.iter().skip(1) {
assert_eq!(
byte,
epd5in83b_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value()
);
}
}
}

342
src/epd5in83b_v2/mod.rs Normal file
View File

@ -0,0 +1,342 @@
//! A simple Driver for the Waveshare 5.83" (B) v2 E-Ink Display via SPI
//!
//! # References
//!
//! - [Datasheet](https://www.waveshare.com/5.83inch-e-Paper-B.htm)
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_5in83b_V2.c)
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd5in83b_V2.py)
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::v2::{InputPin, OutputPin},
};
use crate::color::Color;
use crate::interface::DisplayInterface;
use crate::prelude::{TriColor, WaveshareDisplay, WaveshareThreeColorDisplay};
use crate::traits::{InternalWiAdditions, RefreshLut};
pub(crate) mod command;
use self::command::Command;
#[cfg(feature = "graphics")]
mod graphics;
#[cfg(feature = "graphics")]
pub use self::graphics::Display5in83;
/// Width of the display
pub const WIDTH: u32 = 648;
/// Height of the display
pub const HEIGHT: u32 = 480;
/// Default Background Color
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = true;
const NUM_DISPLAY_BITS: u32 = WIDTH * HEIGHT / 8;
/// Epd7in5 driver
///
pub struct Epd5in83<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd5in83<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
// Reset the device
self.interface.reset(delay, 10);
// Start the booster
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x1e, 0x17])?;
// Set the power settings: VGH=20V,VGL=-20V,VDH=15V,VDL=-15V
self.cmd_with_data(spi, Command::PowerSetting, &[0x07, 0x07, 0x3F, 0x3F])?;
// Power on
self.command(spi, Command::PowerOn)?;
delay.delay_ms(5);
self.wait_until_idle();
// Set the panel settings: BWROTP
self.cmd_with_data(spi, Command::PanelSetting, &[0x0F])?;
// Set the real resolution
self.send_resolution(spi)?;
// Disable dual SPI
self.cmd_with_data(spi, Command::DualSPI, &[0x00])?;
// Set Vcom and data interval
self.cmd_with_data(spi, Command::VcomAndDataIntervalSetting, &[0x11, 0x07])?;
// Set S2G and G2S non-overlap periods to 12 (default)
self.cmd_with_data(spi, Command::TconSetting, &[0x22])?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd5in83<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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)?;
Ok(())
}
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd5in83<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
let interface = DisplayInterface::new(cs, busy, dc, rst);
let color = DEFAULT_BACKGROUND_COLOR;
let mut epd = Epd5in83 { 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.wait_until_idle();
self.update_achromatic_frame(spi, buffer)?;
let color = self.color.get_byte_value();
self.command(spi, Command::DataStartTransmission2)?;
self.interface.data_x_times(spi, color, NUM_DISPLAY_BITS)?;
Ok(())
}
fn update_partial_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 error
}
let hrst_upper = (x / 8) as u8 >> 6;
let hrst_lower = ((x / 8) << 3) as u8;
let hred_upper = ((x + width) / 8) as u8 >> 6;
let hred_lower = (((x + width) / 8) << 3) as u8 & 0b111;
let vrst_upper = (y >> 8) as u8;
let vrst_lower = y as u8;
let vred_upper = ((y + height) >> 8) as u8;
let vred_lower = (y + height) as u8;
let pt_scan = 0x01; // Gates scan both inside and outside of the partial window. (default)
self.command(spi, Command::PartialIn)?;
self.command(spi, Command::PartialWindow)?;
self.send_data(
spi,
&[
hrst_upper, hrst_lower, hred_upper, hred_lower, vrst_upper, vrst_lower, vred_upper,
vred_lower, pt_scan,
],
)?;
self.command(spi, Command::DataStartTransmission1)?;
self.send_data(spi, buffer)?;
let color = TriColor::Black.get_byte_value(); //We need it black, so red channel will be rendered transparent
self.command(spi, Command::DataStartTransmission2)?;
self.interface
.data_x_times(spi, color, width * height / 8)?;
self.command(spi, Command::DisplayRefresh)?;
self.wait_until_idle();
self.command(spi, Command::PartialOut)?;
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.wait_until_idle();
// The Waveshare controllers all implement clear using 0x33
self.command(spi, Command::DataStartTransmission1)?;
self.interface.data_x_times(spi, 0xFF, NUM_DISPLAY_BITS)?;
self.command(spi, Command::DataStartTransmission2)?;
self.interface.data_x_times(spi, 0x00, NUM_DISPLAY_BITS)?;
Ok(())
}
fn set_lut(
&mut self,
_spi: &mut SPI,
_refresh_rate: Option<RefreshLut>,
) -> Result<(), SPI::Error> {
unimplemented!();
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd5in83<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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::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])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 648);
assert_eq!(HEIGHT, 480);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

153
src/epd7in5/command.rs Normal file
View File

@ -0,0 +1,153 @@
//! 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)]
#[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.
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,
/// 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.
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 Epd7in5, but it's not documented
/// anywhere in the datasheet `¯\_(ツ)_/¯`
FlashMode = 0xE5,
}
impl traits::Command for Command {
/// Returns the address of the command
fn address(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Command as CommandTrait;
#[test]
fn command_addr() {
assert_eq!(Command::PanelSetting.address(), 0x00);
assert_eq!(Command::DisplayRefresh.address(), 0x12);
}
}

158
src/epd7in5/graphics.rs Normal file
View File

@ -0,0 +1,158 @@
use crate::epd7in5::{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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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;
use crate::color::Color;
use crate::epd7in5;
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(), 30720);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let display = Display7in5::default();
for &byte in display.buffer() {
assert_eq!(byte, epd7in5::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
#[test]
fn graphics_rotation_0() {
let mut display = Display7in5::default();
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::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, 632), Point::new(0, 639))
.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::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(632, 383), Point::new(639, 383))
.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::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(383, 0), Point::new(383, 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::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
}

283
src/epd7in5/mod.rs Normal file
View File

@ -0,0 +1,283 @@
//! A simple Driver for the Waveshare 7.5" E-Ink Display via SPI
//!
//! # References
//!
//! - [Datasheet](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT)
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/702def06bcb75983c98b0f9d25d43c552c248eb0/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_7in5.c)
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/702def06bcb75983c98b0f9d25d43c552c248eb0/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5.py)
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::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 = 640;
/// Height of the display
pub const HEIGHT: u32 = 384;
/// Default Background Color
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = true;
/// Epd7in5 driver
///
pub struct Epd7in5<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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::PowerSetting, &[0x37, 0x00])?;
// Set the panel settings:
// - 600 x 448
// - Using LUT from external flash
self.cmd_with_data(spi, Command::PanelSetting, &[0xCF, 0x08])?;
// Start the booster
self.cmd_with_data(spi, Command::BoosterSoftStart, &[0xC7, 0xCC, 0x28])?;
// Power on
self.command(spi, Command::PowerOn)?;
delay.delay_ms(5);
self.wait_until_idle();
// Set the clock frequency to 50Hz (default)
self.cmd_with_data(spi, Command::PllControl, &[0x3C])?;
// Select internal temperature sensor (default)
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::VcomAndDataIntervalSetting, &[0x77])?;
// Set S2G and G2S non-overlap periods to 12 (default)
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::VcmDcSetting, &[0x1E])?;
// This is in all the Waveshare controllers for Epd7in5
self.cmd_with_data(spi, Command::FlashMode, &[0x03])?;
self.wait_until_idle();
Ok(())
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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.command(spi, Command::PowerOff)?;
self.wait_until_idle();
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_until_idle();
self.command(spi, Command::DataStartTransmission1)?;
for byte in buffer {
let mut temp = *byte;
for _ in 0..4 {
let mut data = if temp & 0x80 == 0 { 0x00 } else { 0x03 };
data <<= 4;
temp <<= 1;
data |= if temp & 0x80 == 0 { 0x00 } else { 0x03 };
temp <<= 1;
self.send_data(spi, &[data])?;
}
}
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_until_idle();
self.command(spi, Command::DisplayRefresh)?;
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();
self.send_resolution(spi)?;
// The Waveshare controllers all implement clear using 0x33
self.command(spi, Command::DataStartTransmission1)?;
self.interface
.data_x_times(spi, 0x33, WIDTH / 8 * HEIGHT * 4)?;
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<RefreshLut>,
) -> Result<(), SPI::Error> {
unimplemented!();
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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::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])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 640);
assert_eq!(HEIGHT, 384);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

159
src/epd7in5_hd/command.rs Normal file
View File

@ -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);
}
}

161
src/epd7in5_hd/graphics.rs Normal file
View File

@ -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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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());
}
}
}

265
src/epd7in5_hd/mod.rs Normal file
View File

@ -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<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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<RefreshLut>,
) -> Result<(), SPI::Error> {
unimplemented!();
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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);
}
}

153
src/epd7in5_v2/command.rs Normal file
View File

@ -0,0 +1,153 @@
//! 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)]
#[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,
/// RED or NEW_DATA
DataStartTransmission2 = 0x13,
/// Dual SPI - what for?
DualSpi = 0x15,
/// 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 Epd7in5, but it's not documented
// /// anywhere in the datasheet `¯\_(ツ)_/¯`
// FlashMode = 0xE5,
}
impl traits::Command for Command {
/// Returns the address of the command
fn address(self) -> u8 {
self as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Command as CommandTrait;
#[test]
fn command_addr() {
assert_eq!(Command::PanelSetting.address(), 0x00);
assert_eq!(Command::DisplayRefresh.address(), 0x12);
}
}

161
src/epd7in5_v2/graphics.rs Normal file
View File

@ -0,0 +1,161 @@
use crate::epd7in5_v2::{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<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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_v2;
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(), 48000);
}
// test default background color on all bytes
#[test]
fn graphics_default() {
let display = Display7in5::default();
for &byte in display.buffer() {
assert_eq!(byte, epd7in5_v2::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_v2::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, 792), Point::new(0, 799))
.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_v2::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(792, 479), Point::new(799, 479))
.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_v2::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(479, 0), Point::new(479, 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_v2::DEFAULT_BACKGROUND_COLOR.get_byte_value());
}
}
}

261
src/epd7in5_v2/mod.rs Normal file
View File

@ -0,0 +1,261 @@
//! A simple Driver for the Waveshare 7.5" E-Ink Display (V2) via SPI
//!
//! # References
//!
//! - [Datasheet](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT)
//! - [Waveshare C driver](https://github.com/waveshare/e-Paper/blob/702def0/RaspberryPi%26JetsonNano/c/lib/e-Paper/EPD_7in5_V2.c)
//! - [Waveshare Python driver](https://github.com/waveshare/e-Paper/blob/702def0/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5_V2.py)
//!
//! Important note for V2:
//! Revision V2 has been released on 2019.11, the resolution is upgraded to 800×480, from 640×384 of V1.
//! The hardware and interface of V2 are compatible with V1, however, the related software should be updated.
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::v2::{InputPin, OutputPin},
};
use crate::color::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 = 800;
/// Height of the display
pub const HEIGHT: u32 = 480;
/// Default Background Color
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::White;
const IS_BUSY_LOW: bool = true;
/// Epd7in5 (V2) driver
///
pub struct Epd7in5<SPI, CS, BUSY, DC, RST, DELAY> {
/// Connection Interface
interface: DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>,
/// Background Color
color: Color,
}
impl<SPI, CS, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
// Reset the device
self.interface.reset(delay, 2);
// V2 procedure as described here:
// https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5bc_V2.py
// 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::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<SPI, CS, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
for Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
type DisplayColor = Color;
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
dc: DC,
rst: RST,
delay: &mut DELAY,
) -> Result<Self, SPI::Error> {
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(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],
delay: &mut DELAY,
) -> Result<(), SPI::Error> {
self.wait_until_idle(spi, delay)?;
self.cmd_with_data(spi, Command::DataStartTransmission2, 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_until_idle(spi, delay)?;
self.command(spi, Command::DisplayRefresh)?;
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(spi, delay)?;
self.send_resolution(spi)?;
self.command(spi, Command::DataStartTransmission1)?;
self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?;
self.command(spi, Command::DataStartTransmission2)?;
self.interface.data_x_times(spi, 0x00, WIDTH * HEIGHT / 8)?;
self.command(spi, Command::DisplayRefresh)?;
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<RefreshLut>,
) -> Result<(), SPI::Error> {
unimplemented!();
}
fn is_busy(&self) -> bool {
self.interface.is_busy(IS_BUSY_LOW)
}
}
impl<SPI, CS, BUSY, DC, RST, DELAY> Epd7in5<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
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, 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::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])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epd_size() {
assert_eq!(WIDTH, 800);
assert_eq!(HEIGHT, 480);
assert_eq!(DEFAULT_BACKGROUND_COLOR, Color::White);
}
}

View File

@ -1,7 +1,9 @@
//! Graphics Support for EPDs
use color::Color;
use embedded_graphics::prelude::*;
use crate::buffer_len;
use crate::color::{Color, OctColor, TriColor};
use embedded_graphics_core::pixelcolor::BinaryColor;
use embedded_graphics_core::prelude::*;
/// Displayrotation
#[derive(Clone, Copy)]
@ -22,72 +24,357 @@ impl Default for DisplayRotation {
}
}
pub struct Display<'a> {
/// Display specific pixel output configuration
///
/// Different chromatic displays differently treat the bits in chromatic color planes.
/// Some of them ([crate::epd2in13bc]) will render a color pixel if bit is set for that pixel,
/// which is a [DisplayColorRendering::Positive] mode.
///
/// Other displays, like [crate::epd5in83b_v2] in opposite, will draw color pixel if bit is
/// cleared for that pixel, which is a [DisplayColorRendering::Negative] mode.
///
#[derive(Clone, Copy)]
pub enum DisplayColorRendering {
/// Positive: chromatic doesn't override white, white bit cleared for black, white bit set for white, both bits set for chromatic
Positive,
/// Negative: chromatic does override white, both bits cleared for black, white bit set for white, red bit set for black
Negative,
}
/// Necessary traits for all displays to implement for drawing
///
/// Adds support for:
/// - Drawing (With the help of DrawTarget/Embedded Graphics)
/// - Rotations
/// - Clearing
pub trait Display: DrawTarget<Color = BinaryColor> {
/// 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() {
*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;
/// 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<BinaryColor>,
) -> 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, bit) = find_position(point.x as u32, point.y as u32, width, height, rotation);
let index = index as usize;
// "Draw" the Pixel on that bit
match color {
// Black
BinaryColor::On => {
buffer[index] &= !bit;
}
// White
BinaryColor::Off => {
buffer[index] |= 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 TriDisplay: DrawTarget<Color = TriColor> {
/// 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
///
/// # Arguments
///
/// * `width` - Screen width in pixels
/// * `height` - Screen height in pixels
/// * `pixel` - Pixel to draw
/// * `rendering` - Chooses rendering mode for the color plane,
/// whether it is positive or negative. Check [DisplayColorRendering] for details.
/// This is a hardware defined setting, that needs to be checked from the datasheet.
fn draw_helper_tri(
&mut self,
width: u32,
height: u32,
pixel: Pixel<TriColor>,
rendering: DisplayColorRendering,
) -> 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;
match rendering {
DisplayColorRendering::Positive => {
// set bit in chromatic-buffer -> white
buffer[index + offset] |= bit;
}
DisplayColorRendering::Negative => {
// clear bit in chromatic-buffer -> white
buffer[index + offset] &= !bit;
}
}
}
TriColor::White => {
// set bit in bw-buffer -> white
buffer[index] |= bit;
match rendering {
DisplayColorRendering::Positive => {
// set bit in chromatic-buffer -> white
buffer[index + offset] |= bit;
}
DisplayColorRendering::Negative => {
// clear bit in chromatic-buffer -> white
buffer[index + offset] &= !bit;
}
}
}
TriColor::Chromatic => {
match rendering {
DisplayColorRendering::Positive => {
// set bit in b/w buffer (white)
buffer[index] |= bit;
// clear bit in chromatic buffer -> chromatic
buffer[index + offset] &= !bit;
}
DisplayColorRendering::Negative => {
// set bit in b/w buffer (white)
buffer[index] |= bit;
// set 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<Color = OctColor> {
/// 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<OctColor>,
) -> 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:
/// buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]
/// If WIDTH is not a multiple of 8, don't forget to round it up (ie. (WIDTH + 7) / 8)
///
/// Example:
/// ```rust,no_run
/// # use epd_waveshare::epd2in9::DEFAULT_BACKGROUND_COLOR;
/// # use epd_waveshare::prelude::*;
/// # use epd_waveshare::graphics::VarDisplay;
/// # use epd_waveshare::color::Black;
/// # use embedded_graphics::prelude::*;
/// # use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle};
/// let width = 128;
/// let height = 296;
///
/// let mut buffer = [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 128 / 8 * 296];
/// let mut display = VarDisplay::new(width, height, &mut buffer);
///
/// display.set_rotation(DisplayRotation::Rotate90);
///
/// let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
/// .into_styled(PrimitiveStyle::with_stroke(Black, 1))
/// .draw(&mut display);
/// ```
pub struct VarDisplay<'a> {
width: u32,
height: u32,
rotation: DisplayRotation,
buffer: &'a mut [u8], //buffer: Box<u8>//[u8; 15000]
}
impl<'a> Display<'a> {
pub fn new(width: u32, height: u32, buffer: &'a mut [u8]) -> Display<'a> {
impl<'a> VarDisplay<'a> {
/// Create a new variable sized display.
///
/// Buffersize must be at least (width + 7) / 8 * height bytes.
pub fn new(width: u32, height: u32, buffer: &'a mut [u8]) -> VarDisplay<'a> {
let len = buffer.len() as u32;
assert!(width / 8 * height >= len);
Display {
assert!(buffer_len(width as usize, height as usize) >= len as usize);
VarDisplay {
width,
height,
rotation: DisplayRotation::default(),
buffer,
}
}
}
pub fn buffer(&self) -> &[u8] {
&self.buffer
}
impl<'a> DrawTarget for VarDisplay<'a> {
type Color = BinaryColor;
type Error = core::convert::Infallible;
pub fn clear_buffer(&mut self, background_color: Color) {
for elem in &mut self.buffer.iter_mut() {
*elem = background_color.get_byte_value();
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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)
}
}
impl<'a> Display for VarDisplay<'a> {
fn buffer(&self) -> &[u8] {
self.buffer
}
pub fn set_rotation(&mut self, rotation: DisplayRotation) {
fn get_mut_buffer(&mut self) -> &mut [u8] {
self.buffer
}
fn set_rotation(&mut self, rotation: DisplayRotation) {
self.rotation = rotation;
}
pub fn rotation(&self) -> DisplayRotation {
fn rotation(&self) -> DisplayRotation {
self.rotation
}
}
impl<'a> Drawing<Color> for Display<'a> {
fn draw<T>(&mut self, item_pixels: T)
where
T: Iterator<Item = Pixel<Color>>,
{
for Pixel(UnsignedCoord(x, y), color) in item_pixels {
if outside_display(x, y, self.width, self.height, self.rotation) {
continue;
}
// Give us index inside the buffer and the bit-position in that u8 which needs to be changed
let (index, bit) = rotation(x, y, self.width, self.height, self.rotation);
let index = index as usize;
// "Draw" the Pixel on that bit
match color {
Color::Black => {
self.buffer[index] &= !bit;
}
Color::White => {
self.buffer[index] |= bit;
}
}
}
// Checks if a pos is outside the defined display
fn outside_display(p: Point, width: u32, height: u32, rotation: DisplayRotation) -> bool {
if p.x < 0 || p.y < 0 {
return true;
}
}
fn outside_display(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> bool {
let (x, y) = (p.x as u32, p.y as u32);
match rotation {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
if x >= width || y >= height {
@ -103,43 +390,69 @@ fn outside_display(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRot
false
}
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 rotation(x: u32, y: u32, width: u32, height: u32, rotation: DisplayRotation) -> (u32, u8) {
match rotation {
DisplayRotation::Rotate0 => (
x / 8 + (width / 8) * y,
0x80 >> (x % 8),
),
DisplayRotation::Rotate90 => (
(width - 1 - y) / 8 + (width / 8) * x,
0x01 << (y % 8),
),
DisplayRotation::Rotate180 => (
((width / 8) * height - 1) - (x / 8 + (width / 8) * y),
0x01 << (x % 8),
),
DisplayRotation::Rotate270 => (
y / 8 + (height - 1 - x) * (width / 8),
0x80 >> (y % 8),
),
}
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),
)
}
#[cfg(test)]
mod tests {
use super::{outside_display, rotation, Display, DisplayRotation};
use color::Color;
use embedded_graphics::coord::Coord;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::Line;
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, PrimitiveStyle},
};
#[test]
fn buffer_clear() {
use epd4in2::{HEIGHT, WIDTH};
use crate::epd4in2::{HEIGHT, WIDTH};
let mut buffer = [Color::Black.get_byte_value(); WIDTH as usize / 8 * HEIGHT as usize];
let mut display = Display::new(WIDTH, HEIGHT, &mut buffer);
let mut buffer =
[Color::Black.get_byte_value(); buffer_len(WIDTH as usize, HEIGHT as usize)];
let mut display = VarDisplay::new(WIDTH, HEIGHT, &mut buffer);
for &byte in display.buffer.iter() {
assert_eq!(byte, Color::Black.get_byte_value());
@ -154,7 +467,7 @@ mod tests {
#[test]
fn rotation_overflow() {
use epd4in2::{HEIGHT, WIDTH};
use crate::epd4in2::{HEIGHT, WIDTH};
let width = WIDTH as u32;
let height = HEIGHT as u32;
test_rotation_overflow(width, height, DisplayRotation::Rotate0);
@ -168,10 +481,10 @@ mod tests {
for x in 0..(width + height) {
//limit x because it runs too long
for y in 0..(u32::max_value()) {
if outside_display(x, y, width, height, rotation2) {
if outside_display(Point::new(x as i32, y as i32), width, height, rotation2) {
break;
} else {
let (idx, _) = rotation(x, y, width, height, rotation2);
let (idx, _) = find_position(x, y, width, height, rotation2);
assert!(idx < max_value);
}
}
@ -180,18 +493,16 @@ mod tests {
#[test]
fn graphics_rotation_0() {
use epd2in9::DEFAULT_BACKGROUND_COLOR;
use crate::epd2in9::DEFAULT_BACKGROUND_COLOR;
let width = 128;
let height = 296;
let mut buffer = [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 128 / 8 * 296];
let mut display = Display::new(width, height, &mut buffer);
let mut display = VarDisplay::new(width, height, &mut buffer);
display.draw(
Line::new(Coord::new(0, 0), Coord::new(7, 0))
.with_stroke(Some(Color::Black))
.into_iter(),
);
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();
@ -204,20 +515,18 @@ mod tests {
#[test]
fn graphics_rotation_90() {
use epd2in9::DEFAULT_BACKGROUND_COLOR;
use crate::epd2in9::DEFAULT_BACKGROUND_COLOR;
let width = 128;
let height = 296;
let mut buffer = [DEFAULT_BACKGROUND_COLOR.get_byte_value(); 128 / 8 * 296];
let mut display = Display::new(width, height, &mut buffer);
let mut display = VarDisplay::new(width, height, &mut buffer);
display.set_rotation(DisplayRotation::Rotate90);
display.draw(
Line::new(Coord::new(0, 120), Coord::new(0, 295))
.with_stroke(Some(Color::Black))
.into_iter(),
);
let _ = Line::new(Point::new(0, 120), Point::new(0, 295))
.into_styled(PrimitiveStyle::with_stroke(Black, 1))
.draw(&mut display);
let buffer = display.buffer();

View File

@ -1,36 +1,40 @@
use crate::traits::Command;
use core::marker::PhantomData;
use hal::{
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
use traits::Command;
/// The Connection Interface of all (?) Waveshare EPD-Devices
///
pub(crate) struct DisplayInterface<SPI, CS, BUSY, DC, RST> {
pub(crate) struct DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY> {
/// SPI
_spi: PhantomData<SPI>,
/// DELAY
_delay: PhantomData<DELAY>,
/// 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<SPI, CS, BUSY, DC, RST> DisplayInterface<SPI, CS, BUSY, DC, RST>
impl<SPI, CS, BUSY, DC, RST, DELAY> DisplayInterface<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
pub fn new(cs: CS, busy: BUSY, dc: DC, rst: RST) -> Self {
DisplayInterface {
_spi: PhantomData::default(),
_delay: PhantomData::default(),
cs,
busy,
dc,
@ -43,7 +47,7 @@ where
/// Enables direct interaction with the device with the help of [data()](DisplayInterface::data())
pub(crate) fn cmd<T: Command>(&mut self, spi: &mut SPI, command: T) -> Result<(), SPI::Error> {
// low for commands
self.dc.set_low();
let _ = self.dc.set_low();
// Transfer the command over spi
self.write(spi, &[command.address()])
@ -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
self.dc.set_high();
let _ = self.dc.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.
@ -83,7 +91,7 @@ where
repetitions: u32,
) -> Result<(), SPI::Error> {
// high for data
self.dc.set_high();
let _ = self.dc.set_high();
// Transfer data (u8) over spi
for _ in 0..repetitions {
self.write(spi, &[val])?;
@ -94,7 +102,7 @@ where
// spi write helper/abstraction function
fn write(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), SPI::Error> {
// activate spi with cs low
self.cs.set_low();
let _ = self.cs.set_low();
// transfer spi data
// Be careful!! Linux has a default limit of 4096 bytes per spi transfer
@ -107,8 +115,8 @@ where
spi.write(data)?;
}
// deativate spi with cs high
self.cs.set_high();
// deactivate spi with cs high
let _ = self.cs.set_high();
Ok(())
}
@ -127,27 +135,49 @@ 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) {
// TODO: removal of delay. TEST!
//self.delay_ms(1);
//low: busy, high: idle
while (is_busy_low && self.busy.is_low()) || (!is_busy_low && self.busy.is_high()) {
//TODO: 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: 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);
}
}
/// Checks if device is still busy
///
/// This is normally handled by the more complicated commands themselves,
/// but in the case you send data and commands directly you might need to check
/// if the device is still busy
///
/// is_busy_low
///
/// - TRUE for epd4in2, epd2in13, epd2in7, epd5in83, epd7in5
/// - FALSE for epd2in9, epd1in54 (for all Display Type A ones?)
///
/// 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 is_busy(&self, is_busy_low: bool) -> bool {
(is_busy_low && self.busy.is_low().unwrap_or(false))
|| (!is_busy_low && self.busy.is_high().unwrap_or(false))
}
/// 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())
///
/// TODO: Takes at least 400ms of delay alone, can it be shortened?
pub(crate) fn reset<DELAY: DelayMs<u8>>(&mut self, delay: &mut DELAY) {
self.rst.set_low();
//TODO: why 200ms? (besides being in the arduino version)
delay.delay_ms(200);
self.rst.set_high();
//TODO: same as 3 lines above
/// 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) {
let _ = self.rst.set_high();
delay.delay_ms(10);
let _ = self.rst.set_low();
delay.delay_ms(duration);
let _ = self.rst.set_high();
//TODO: the upstream libraries always sleep for 200ms here
// 10ms works fine with just for the 7in5_v2 but this needs to be validated for other devices
delay.delay_ms(200);
}
}

View File

@ -1,48 +1,67 @@
//! A simple Driver for the Waveshare E-Ink Displays via SPI
//! A simple Driver for the [Waveshare](https://github.com/waveshare/e-Paper) E-Ink Displays via SPI
//!
//! This driver was built using [`embedded-hal`] traits.
//! - Built using [`embedded-hal`] traits.
//! - Graphics support is added through [`embedded-graphics`]
//!
//! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.1
//! [`embedded-graphics`]: https://docs.rs/embedded-graphics/
//! [`embedded-hal`]: https://docs.rs/embedded-hal
//!
//! # Requirements
//!
//! ### SPI
//! # Example
//!
//! - MISO is not connected/available
//! - SPI_MODE_0 is used (CPHL = 0, CPOL = 0)
//! - 8 bits per word, MSB first
//! - Max. Speed tested by myself was 8Mhz but more should be possible (Ben Krasnow used 18Mhz with his implemenation)
//!```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::{epd1in54::*, 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();
//!
//! ### Other....
//!// Setup EPD
//!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(), &mut delay)?;
//!epd.display_frame(&mut spi, &mut delay)?;
//!
//!// Set the EPD to sleep
//!epd.sleep(&mut spi, &mut delay)?;
//!# Ok(())
//!# }
//!```
//!
//! # Other information and requirements
//!
//! - Buffersize: Wherever a buffer is used it always needs to be of the size: `width / 8 * length`,
//! where width and length being either the full e-ink size or the partial update window size
//!
//! # Examples
//! ### SPI
//!
//! ```ignore
//! use eink-waveshare-rs::epd4in2::EPD4in2;
//!
//! let mut epd4in2 = EPD4in2::new(spi, cs, busy, dc, rst, delay).unwrap();
//!
//! let mut buffer = [0u8, epd4in2.get_width() / 8 * epd4in2.get_height()];
//!
//! // draw something into the buffer
//!
//! epd4in2.display_and_transfer_buffer(buffer, None);
//!
//! // wait and look at the image
//!
//! epd4in2.clear_frame(None);
//!
//! epd4in2.sleep();
//! ```
//! MISO is not connected/available. SPI_MODE_0 is used (CPHL = 0, CPOL = 0) with 8 bits per word, MSB first.
//!
//! Maximum speed tested by myself was 8Mhz but more should be possible (Ben Krasnow used 18Mhz with his implemenation)
//!
#![no_std]
#[cfg(feature = "graphics")]
extern crate embedded_graphics;
#![deny(missing_docs)]
#[cfg(feature = "graphics")]
pub mod graphics;
@ -54,26 +73,51 @@ pub mod color;
/// Interface for the physical connection between display and the controlling device
mod interface;
#[cfg(feature = "epd4in2")]
pub mod epd4in2;
#[cfg(feature = "epd1in54")]
pub mod epd1in54;
#[cfg(feature = "epd2in9")]
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 epd5in83b_v2;
pub mod epd7in5;
pub mod epd7in5_hd;
pub mod epd7in5_v2;
#[cfg(any(feature = "epd1in54", feature = "epd2in9"))]
pub(crate) mod type_a;
/// Includes everything important besides the chosen Display
pub mod prelude {
pub use color::Color;
pub use traits::{RefreshLUT, WaveshareDisplay};
pub use SPI_MODE;
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, OctDisplay, TriDisplay};
}
extern crate embedded_hal as hal;
use hal::spi::{Mode, Phase, Polarity};
/// Computes the needed buffer length. Takes care of rounding up in case width
/// is not divisible by 8.
///
/// unused
/// bits width
/// <----><------------------------>
/// \[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
}
use embedded_hal::spi::{Mode, Phase, Polarity};
/// SPI mode -
/// For more infos see [Requirements: SPI](index.html#spi)

View File

@ -1,8 +1,7 @@
use color::Color;
use core::marker::Sized;
use hal::{
use embedded_hal::{
blocking::{delay::*, spi::Write},
digital::*,
digital::v2::*,
};
/// All commands need to have this trait which gives the address of the command
@ -12,60 +11,136 @@ pub(crate) trait Command {
}
/// Seperates the different LUT for the Display Refresh process
pub enum RefreshLUT {
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
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<SPI, CS, BUSY, DC, RST>
pub(crate) trait InternalWiAdditions<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// This initialises the EPD and powers it up
///
/// This function is already called from
/// - [new()](WaveshareInterface::new())
/// - [new()](WaveshareDisplay::new())
/// - [`wake_up`]
///
///
/// This function calls [reset()](WaveshareInterface::reset()),
/// 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<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error>;
fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
}
/// All the functions to interact with the EPDs
///
/// This trait includes all public functions to use the EPDS
pub trait WaveshareDisplay<SPI, CS, BUSY, DC, RST>
/// Functions to interact with three color panels
pub trait WaveshareThreeColorDisplay<SPI, CS, BUSY, DC, RST, DELAY>:
WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// Transmit data to the SRAM of the EPD
///
/// Updates both the black and the secondary color layers
fn update_color_frame(
&mut self,
spi: &mut SPI,
black: &[u8],
chromatic: &[u8],
) -> Result<(), SPI::Error>;
/// Update only the black/white data of the display.
///
/// This must be finished by calling `update_chromatic_frame`.
fn update_achromatic_frame(&mut self, spi: &mut SPI, black: &[u8]) -> Result<(), SPI::Error>;
/// Update only the chromatic data of the display.
///
/// This should be preceded by a call to `update_achromatic_frame`.
/// This data takes precedence over the black/white data.
fn update_chromatic_frame(&mut self, spi: &mut SPI, chromatic: &[u8])
-> Result<(), SPI::Error>;
}
/// All the functions to interact with the EPDs
///
/// This trait includes all public functions to use the EPDs
///
/// # 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::*};
///#
///# 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)?;
///
///// 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(), &mut delay)?;
///epd.display_frame(&mut spi, &mut delay)?;
///
///// Set the EPD to sleep
///epd.sleep(&mut spi, &mut delay)?;
///# Ok(())
///# }
///```
pub trait WaveshareDisplay<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// 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. That means [init()](WaveshareInterface::init()) isn't needed directly afterwards
fn new<DELAY: DelayMs<u8>>(
/// This already initialises the device.
fn new(
spi: &mut SPI,
cs: CS,
busy: BUSY,
@ -79,23 +154,18 @@ where
/// Let the device enter deep-sleep mode to save power.
///
/// The deep sleep mode returns to standby with a hardware reset.
/// But you can also use [wake_up()](WaveshareInterface::wake_up()) to awaken.
/// But as you need to power it up once more anyway you can also just directly use [new()](WaveshareInterface::new()) for resetting
/// and initialising which already contains the 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
fn wake_up<DELAY: DelayMs<u8>>(
&mut self,
spi: &mut SPI,
delay: &mut DELAY,
) -> Result<(), SPI::Error>;
///
/// Also reintialises the device if necessary.
fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error>;
/// Sets the backgroundcolor for various commands like [clear_frame()](WaveshareInterface::clear_frame())
fn set_background_color(&mut self, color: Color);
/// Sets the backgroundcolor for various commands like [clear_frame](WaveshareDisplay::clear_frame)
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;
@ -104,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
///
@ -122,11 +197,22 @@ where
) -> Result<(), SPI::Error>;
/// Displays the frame data from SRAM
fn display_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error>;
///
/// This function waits until the device isn`t busy anymore
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],
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 [`set_background_color`]
fn clear_frame(&mut self, spi: &mut SPI) -> Result<(), SPI::Error>;
///
/// The background color can be changed with [`WaveshareDisplay::set_background_color`]
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
@ -139,6 +225,128 @@ where
fn set_lut(
&mut self,
spi: &mut SPI,
refresh_rate: Option<RefreshLUT>,
refresh_rate: Option<RefreshLut>,
) -> Result<(), SPI::Error>;
/// Checks if the display is busy transmitting data
///
/// This is normally handled by the more complicated commands themselves,
/// but in the case you send data and commands directly you might need to check
/// 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<SPI, CS, BUSY, DC, RST, DELAY>
where
SPI: Write<u8>,
CS: OutputPin,
BUSY: InputPin,
DC: OutputPin,
RST: OutputPin,
DELAY: DelayMs<u8>,
{
/// 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>;
}

View File

@ -1,74 +1,77 @@
//! SPI Commands for the Waveshare 2.9" and 1.54" E-Ink Display
use traits;
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
/// 3 Databytes:
/// A[7:0]
/// 0.. A[8]
/// 0.. B[2:0]
/// Default: Set A[8:0] = 0x127 and B[2:0] = 0x0
DRIVER_OUTPUT_CONTROL = 0x01,
/// 3 Databytes:
/// A[7:0]
/// 0.. A[8]
/// 0.. B[2:0]
/// Default: Set A[8:0] = 0x127 and B[2:0] = 0x0
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,
/// 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
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,
/// 1 Databyte:
/// 0.. A[0]
/// Values:
/// A[0] = 0: Normal Mode (POR)
/// A[0] = 1: Enter Deep Sleep Mode
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 {
@ -81,14 +84,14 @@ impl traits::Command for Command {
#[cfg(test)]
mod tests {
use super::Command;
use traits::Command as CommandTrait;
use crate::traits::Command as CommandTrait;
#[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);
}
}

View File

@ -1,5 +1,6 @@
// Original Waveforms from Waveshare
#[cfg(not(any(feature = "type_a_alternative_faster_lut")))]
#[rustfmt::skip]
// Original Waveforms from Waveshare
pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[
0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22,
0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88,
@ -14,3 +15,11 @@ pub(crate) const LUT_PARTIAL_UPDATE: [u8; 30] =[
0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
#[cfg(feature = "type_a_alternative_faster_lut")]
#[rustfmt::skip]
// Waveform from TeXiToi/il3820
pub(crate) const LUT_FULL_UPDATE: [u8; 30] =[
0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];