Update epdiy library
- ESP32 Platform from 2025.07.31 to 2025.08.30, Framework (Arduino Core) from v3.1.3.250712 to v3.1.3.250808 and IDF from v5.3.3.250707 to v5.3.3.250801 (#23778) - Epdiy library from v1.0.0 to v2.0.0
This commit is contained in:
parent
7ebb747dc3
commit
2deb34e856
@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file.
|
|||||||
### Breaking Changed
|
### Breaking Changed
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- ESP32 Platform from 2025.07.31 to 2025.08.30, Framework (Arduino Core) from v3.1.3.250712 to v3.1.3.250808 and IDF from v5.3.3.250707 to v5.3.3.250801 (#23778)
|
||||||
|
- Epdiy library from v1.0.0 to v2.0.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Syslog RFC5424 compliance (#23509)
|
- Syslog RFC5424 compliance (#23509)
|
||||||
|
|||||||
@ -132,8 +132,9 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
|
|||||||
### Breaking Changed
|
### Breaking Changed
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- ESP32 Platform from 2025.05.30 to 2025.07.31, Framework (Arduino Core) from v3.1.3.250504 to v3.1.3.250712 and IDF from v5.3.3.250501 to v5.3.3.250707 [#23685](https://github.com/arendst/Tasmota/issues/23685)
|
|
||||||
- ESP8266 platform update from 2025.05.00 to 2025.07.00 [#23700](https://github.com/arendst/Tasmota/issues/23700)
|
- ESP8266 platform update from 2025.05.00 to 2025.07.00 [#23700](https://github.com/arendst/Tasmota/issues/23700)
|
||||||
|
- ESP32 Platform from 2025.05.30 to 2025.08.30, Framework (Arduino Core) from v3.1.3.250504 to v3.1.3.250808 and IDF from v5.3.3.250501 to v5.3.3.250801 [#23778](https://github.com/arendst/Tasmota/issues/23778)
|
||||||
|
- Epdiy library from v1.0.0 to v2.0.0
|
||||||
- OpenTherm library from v0.9.0 to v1.1.5 [#23704](https://github.com/arendst/Tasmota/issues/23704)
|
- OpenTherm library from v0.9.0 to v1.1.5 [#23704](https://github.com/arendst/Tasmota/issues/23704)
|
||||||
- Library names [#23560](https://github.com/arendst/Tasmota/issues/23560)
|
- Library names [#23560](https://github.com/arendst/Tasmota/issues/23560)
|
||||||
- CSS uses named colors variables [#23597](https://github.com/arendst/Tasmota/issues/23597)
|
- CSS uses named colors variables [#23597](https://github.com/arendst/Tasmota/issues/23597)
|
||||||
|
|||||||
11
lib/libesp32_eink/epdiy/.clang-format
Normal file
11
lib/libesp32_eink/epdiy/.clang-format
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
BasedOnStyle: chromium
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 100
|
||||||
|
AlignAfterOpenBracket: BlockIndent
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
BreakBeforeBinaryOperators: All
|
||||||
|
Cpp11BracedListStyle: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
SortIncludes: false
|
||||||
10
lib/libesp32_eink/epdiy/.gitignore
vendored
10
lib/libesp32_eink/epdiy/.gitignore
vendored
@ -1,6 +1,9 @@
|
|||||||
.pio
|
.pio
|
||||||
|
.vscode
|
||||||
build/
|
build/
|
||||||
|
build.clang
|
||||||
sdkconfig.old
|
sdkconfig.old
|
||||||
|
sdkconfig
|
||||||
**/build/
|
**/build/
|
||||||
.ccls-cache
|
.ccls-cache
|
||||||
doc/source/xml/
|
doc/source/xml/
|
||||||
@ -16,3 +19,10 @@ fp-info-cache
|
|||||||
__pycache__
|
__pycache__
|
||||||
examples/weather/components
|
examples/weather/components
|
||||||
sdkconfig
|
sdkconfig
|
||||||
|
managed_components/
|
||||||
|
epaper-breakout-backups/
|
||||||
|
dependencies.lock
|
||||||
|
ED*.h
|
||||||
|
ES*.h
|
||||||
|
examples/private_*/
|
||||||
|
*.code-workspace
|
||||||
|
|||||||
6
lib/libesp32_eink/epdiy/.gitmodules
vendored
6
lib/libesp32_eink/epdiy/.gitmodules
vendored
@ -1,6 +0,0 @@
|
|||||||
[submodule "hardware/epaper-breakout/esp32-wrover-kicad"]
|
|
||||||
path = hardware/epaper-breakout/esp32-wrover-kicad
|
|
||||||
url = https://github.com/aliafshar/esp32-wrover-kicad
|
|
||||||
[submodule "hardware/epaper-breakout/tp4056"]
|
|
||||||
path = hardware/epaper-breakout/tp4056
|
|
||||||
url = https://github.com/alltheworld/tp4056/
|
|
||||||
@ -12,8 +12,12 @@ sphinx:
|
|||||||
# Optionally build your docs in additional formats such as PDF and ePub
|
# Optionally build your docs in additional formats such as PDF and ePub
|
||||||
formats: all
|
formats: all
|
||||||
|
|
||||||
|
build:
|
||||||
|
os: ubuntu-22.04
|
||||||
|
tools:
|
||||||
|
python: "3.11"
|
||||||
|
|
||||||
# Optionally set the version of Python and requirements required to build your docs
|
# Optionally set the version of Python and requirements required to build your docs
|
||||||
python:
|
python:
|
||||||
version: 3.7
|
|
||||||
install:
|
install:
|
||||||
- requirements: doc/requirements.txt
|
- requirements: doc/requirements.txt
|
||||||
|
|||||||
165
lib/libesp32_eink/epdiy/LICENSE
Normal file
165
lib/libesp32_eink/epdiy/LICENSE
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
@ -8,27 +8,41 @@ EXPORTED_MODES ?= 1,2,5,16,17
|
|||||||
# Generate waveforms in room temperature range
|
# Generate waveforms in room temperature range
|
||||||
EXPORT_TEMPERATURE_RANGE ?= 15,35
|
EXPORT_TEMPERATURE_RANGE ?= 15,35
|
||||||
|
|
||||||
|
FORMATTED_FILES := $(shell find ./ -regex '.*\.\(c\|cpp\|h\|ino\)$$' \
|
||||||
|
-not -regex '.*/\(.ccls-cache\|.cache\|waveforms\|\components\|build\)/.*' \
|
||||||
|
-not -regex '.*/img_.*.h' \
|
||||||
|
-not -regex '.*/build.*' \
|
||||||
|
-not -regex '.*/\(firasans_.*.h\|opensans.*.h\|amiri.h\|alexandria.h\|dragon.h\)' \
|
||||||
|
-not -regex '.*E[DS][0-9]*[A-Za-z]*[0-9].h')
|
||||||
|
|
||||||
# the default headers that should come with the distribution
|
# the default headers that should come with the distribution
|
||||||
default: \
|
default: \
|
||||||
$(patsubst %,src/epd_driver/waveforms/epdiy_%.h,$(SUPPORTRED_DISPLAYS))
|
$(patsubst %,src/waveforms/epdiy_%.h,$(SUPPORTRED_DISPLAYS))
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm src/epd_driver/waveforms/epdiy_*.h
|
rm src/waveforms/epdiy_*.h
|
||||||
rm src/epd_driver/waveforms/eink_*.h
|
rm src/waveforms/eink_*.h
|
||||||
|
|
||||||
src/epd_driver/waveforms/epdiy_%.h: src/epd_driver/waveforms/epdiy_%.json
|
format:
|
||||||
|
clang-format --style=file -i $(FORMATTED_FILES)
|
||||||
|
|
||||||
|
format-check:
|
||||||
|
clang-format --style=file --dry-run -Werror $(FORMATTED_FILES)
|
||||||
|
|
||||||
|
|
||||||
|
src/waveforms/epdiy_%.h: src/waveforms/epdiy_%.json
|
||||||
python3 scripts/waveform_hdrgen.py \
|
python3 scripts/waveform_hdrgen.py \
|
||||||
--export-modes $(EXPORTED_MODES) \
|
--export-modes $(EXPORTED_MODES) \
|
||||||
--temperature-range $(EXPORT_TEMPERATURE_RANGE) \
|
--temperature-range $(EXPORT_TEMPERATURE_RANGE) \
|
||||||
epdiy_$* < $< > $@
|
epdiy_$* < $< > $@
|
||||||
|
|
||||||
src/epd_driver/waveforms/eink_%.h: src/epd_driver/waveforms/eink_%.json
|
src/waveforms/eink_%.h: src/waveforms/eink_%.json
|
||||||
python3 scripts/waveform_hdrgen.py \
|
python3 scripts/waveform_hdrgen.py \
|
||||||
--export-modes $(EXPORTED_MODES) \
|
--export-modes $(EXPORTED_MODES) \
|
||||||
--temperature-range $(EXPORT_TEMPERATURE_RANGE) \
|
--temperature-range $(EXPORT_TEMPERATURE_RANGE) \
|
||||||
eink_$* < $< > $@
|
eink_$* < $< > $@
|
||||||
|
|
||||||
src/epd_driver/waveforms/epdiy_%.json:
|
src/waveforms/epdiy_%.json:
|
||||||
python3 scripts/epdiy_waveform_gen.py $* > $@
|
python3 scripts/epdiy_waveform_gen.py $* > $@
|
||||||
|
|
||||||
.PHONY: default
|
.PHONY: default format
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
[](https://epdiy.readthedocs.io/en/latest/?badge=latest) [](https://matrix.to/#/!GUXWriqsBKkWyXzsBK:matrix.vroland.de?via=matrix.vroland.de) [](https://join.slack.com/t/epdiy/shared_invite/zt-mw3iat5g-6jRylNrK2g79HSxeznvmPg)
|
[](https://epdiy.readthedocs.io/en/latest/?badge=latest) [](https://matrix.to/#/!GUXWriqsBKkWyXzsBK:matrix.vroland.de?via=matrix.vroland.de) [](https://join.slack.com/t/epdiy/shared_invite/zt-189eo7328-bs94cfB~eXPbLYAD1rKQcg)
|
||||||
|
|
||||||
EPDiy E-Paper Driver
|
EPDiy E-Paper Driver
|
||||||
=======================================
|
=======================================
|
||||||
@ -9,35 +9,53 @@ EPDiy is a driver board which talks to affordable E-Paper (or E-Ink) screens, wh
|
|||||||
* No power consumption when not updating
|
* No power consumption when not updating
|
||||||
* Sunlight-readable
|
* Sunlight-readable
|
||||||
|
|
||||||
Ready-made DIY modules for this size and with 4bpp (16 Grayscale) color support are currently quite expensive. This project uses Kindle replacement screens, which are available for 20$ (small) / 30$ (large) on ebay!
|
Ready-made DIY modules for this size and with 4bpp (16 Grayscale) color support are currently quite expensive and / or slow.
|
||||||
|
The EPDiy controller can drive the bare display modules, e.g. from old e-Readers, which are available for 20$ (small) / 30$ (large) on ebay!
|
||||||
|
Additionally, since it is based on the ESP32S3 (V7) / ESP32 (V2-V6) microcontroller, it features WiFi and Bluetooth connectivity.
|
||||||
|
|
||||||
The EPDiy driver board targets multiple E-Paper displays. As the driving method for all matrix-based E-ink displays seems to be more or less the same, only the right connector and timings are needed. The EPDiy PCB v5 features 33pin, 34pin and a 39pin connectors, which allow to drive the following display types: ED097OC4, ED060SC4, ED097TC2, ED060SC7. For the full list of supported displays, refer to the table below.
|
The EPDiy driver board targets a range of E-Paper displays, as shown in the table below.
|
||||||
|
As the driving method for all matrix-based E-ink displays is more or less the same, only the right connector and timings are needed.
|
||||||
|
The current V7 board has three different display connectors, other display will require an adapter board.
|
||||||
|
|
||||||
Revision 5 of the board is optimized for the use with LiPo batteries, featuring a LiPo charger and ultra-low deep sleep current.
|
The controller is optimized for the use with LiPo batteries, featuring a LiPo charger and ultra-low deep sleep current.
|
||||||
|
|
||||||
This project supports a driver for the ESP-IDF and Arduino. For installation instructions, please refer to the [documentation](https://epdiy.readthedocs.io/en/latest/getting_started.html#getting-your-board).
|
This project supports a driver for the ESP-IDF and Arduino. For installation instructions, please refer to the [documentation](https://epdiy.readthedocs.io/en/latest/getting_started.html#getting-your-board).
|
||||||
|
Note that for epdiy V7, update speeds are significantly lower when using the Arduino IDE, because it does not allow to change
|
||||||
|
the sub-optimal cache configuration.
|
||||||
|
|
||||||
|
Get Inspired
|
||||||
|
------------
|
||||||
|
|
||||||
|
The `examples` directory contains some example applications like a weather station or a screen diagnostic test.
|
||||||
|
If you want to build something more useful though, how about:
|
||||||
|
|
||||||
|
- A serial terminal for connecting to a raspberry pi: [video](https://cdn.hackaday.io/files/1681937195969312/terminal_demo.mp4) [repository](https://github.com/vroland/epdiy-terminal)]
|
||||||
|
- A Music Player Daemon (MPD) dashboard: [repository](https://github.com/vroland/epdiy-mpd)]
|
||||||
|
- An e-Paper picture frame: [video](https://www.youtube.com/watch?v=r7AcNQsSZUw)
|
||||||
|
- And more to come!
|
||||||
|
|
||||||
Building It
|
Building It
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
If you want to build a board right now, there are two possible routes:
|
On the [EPDiy Hardware Page](https://vroland.github.io/epdiy-hardware/), you'll find a list of all boards and variants, adapters, and helpers.
|
||||||
|
Next to each board, there are manufacturing files (gerbers), Bill of Materials (BoM), part placement files,
|
||||||
|
and 3D models ready to use!
|
||||||
|
|
||||||
- Use the new v5 PCB (`hardware/epaper-breakout/gerbers_v5.zip`).
|

|
||||||
**So far, I only tested a prototype of it. The newest gerbers should work, but are untested!**
|
|
||||||
**If you have tested them, please let me know!**
|
|
||||||
The BOM is available at (`hardware/epaper-breakout/BOM.csv`).
|
|
||||||
Positioning files for SMT assembly are available at (`hardware/epaper-breakout/gerbers/epaper-breakout-top-pos.csv`).
|
|
||||||
Please double check the part positioning and Rotation with your assembly service!
|
|
||||||
More information on the order process and where to find parts is in the [documentation](https://epdiy.readthedocs.io/en/latest/getting_started.html#getting-your-board).
|
|
||||||
|
|
||||||
Make sure to select the `V5` board revision in `idf.py menuconfig` when building the examples.
|
For ordering from JLCPCB for example, ordering is as easy as downloading the zipped gerbers, BoM, and placement file
|
||||||
|
and uploading them. The process is very similar for other manufacturers, check your vendor's documentation for details.
|
||||||
|
Don't forget to oder adapters if the board doesn't have connectors for your specific display.
|
||||||
|
|
||||||
- Use the old v4 PCB (`hardware/epaper-breakout/gerbers_v4.zip`). This is a bit more fresh, but should work.
|
The current latest version is epdiy V7, beased on the ESP32S3.
|
||||||
The BOM is available at (`hardware/epaper-breakout/BOM.csv`).
|
Older versions are also available on the hardware page.
|
||||||
Positioning files for SMT assembly are available at (`hardware/epaper-breakout/gerbers/epaper-breakout-top-pos.csv`).
|
|
||||||
Please double check the part positioning and Rotation with your assembly service!
|
|
||||||
|
#### Contributing Hardware
|
||||||
|
|
||||||
|
Want to contribute your own board variant or adapter?
|
||||||
|
Check out the [epdiy-hardware repository](https://github.com/vroland/epdiy-hardware) for instructions.
|
||||||
|
|
||||||
Make sure to select the `V4` board revision in `idf.py menuconfig` when building the examples.
|
|
||||||
|
|
||||||
Gettings Started
|
Gettings Started
|
||||||
----------------
|
----------------
|
||||||
@ -48,37 +66,40 @@ Join the Discussion
|
|||||||
----------------
|
----------------
|
||||||
|
|
||||||
- [](https://matrix.to/#/!GUXWriqsBKkWyXzsBK:matrix.vroland.de?via=matrix.vroland.de) Matrix Community: +epdiy:matrix.vroland.de
|
- [](https://matrix.to/#/!GUXWriqsBKkWyXzsBK:matrix.vroland.de?via=matrix.vroland.de) Matrix Community: +epdiy:matrix.vroland.de
|
||||||
- [](https://join.slack.com/t/epdiy/shared_invite/zt-mw3iat5g-6jRylNrK2g79HSxeznvmPg)
|
- Slack: See badge
|
||||||
|
|
||||||
Displays
|
Displays
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|Name|Size|Resolution|Compatible|Connector|Pin count|Compatible since pcb version|Notes
|
|Name |Size |Resolution|Compatible|Connector|Pin count|Compatible since pcb version|Notes
|
||||||
| --: | --: | --: | --: | --: | --: |--: |--: |
|
| --: | --: | --: | --: | --: | --: | --: | --: |
|
||||||
|ED060SC4|6"|800 x 600|yes, tested|FH26W-39S-0.3SHW(60)|39|v2|
|
| ED060SC4 | 6" | 800 x 600<br>167 PPI | yes, tested | FH26W-39S-0.3SHW(60) | 39 | v2 | |
|
||||||
|ED097OC4|9.7"|1200 x 825|yes, tested|XF2M-3315-1A|33|V2|Cheap, inferior contrast
|
|ED097OC4|9.7"|1200 x 825<br>150 PPI|yes, tested|XF2M-3315-1A|33|v2|Cheap, inferior contrast
|
||||||
|ED097TC2|9.7"|1200 x 825|yes, tested|XF2M-3315-1A|33|V2|Slightly higher price, better contrast
|
|ED097TC2|9.7"|1200 x 825<br>150 PPI|yes, tested|XF2M-3315-1A|33|v2|Slightly higher price, better contrast
|
||||||
|ED097OC1|9.7"|1200 x 825|yes (should work)|XF2M-3315-1A|33|V2|Cheap, inferior performance
|
|ED097OC1|9.7"|1200 x 825<br>150 PPI|yes (should work)|XF2M-3315-1A|33|v2|Cheap, inferior performance
|
||||||
|ED047TC1|4.7"|960 x 540|yes, tested|40-pin|40|LILYGO 4.7" EPD|Supported only by 4.7" e-paper board by LILYGO
|
|ED047TC1|4.7"|960 x 540<br>234 PPI|yes, tested|40-pin|40|LILYGO 4.7" EPD|Supported only by 4.7" e-paper board by LILYGO
|
||||||
|ED133UT2|13.3"|1600 x 1200|yes, tested|adapter board|39|V2|Adapter Board required, also PENG133D
|
| ED050SC5 | 5" | 600 x 800<br>200 PPI | yes, tested | THD0510-33CL-GF | 33 | v5 |
|
||||||
|ED060XC3|6"|758 x 1024|yes, tested|THD0515-34CL-SN|34|V5|Cheapest, good contrast and resolution
|
| ED050SC3 | 5" | 600 x 800<br>200 PPI | yes (should work) | THD0510-33CL-GF | 33 | v5 |
|
||||||
|ED060XD4|6"|758 x 1024|yes, tested|THD0515-34CL-SN|34|V5|
|
| ED052TC4 | 5.2" | 1280 x 780<br>??? PPI | yes (should work) | WP27D-P050VA3 | 50 | v5 |
|
||||||
|ED060XC5|6"|758 x 1024|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|
|
| ED133UT2 | 13.3" | 1600 x 1200<br>150 PPI | yes, tested | adapter board | 39 | v2 | Adapter Board required, also PENG133D
|
||||||
|ED060XD6|6"|758 x 1024|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|
|
| ED060XC3 | 6" | 758 x 1024<br>212 PPI | yes, tested | THD0515-34CL-SN | 34 | v5 | Cheapest, good contrast and resolution
|
||||||
|ED060XH2|6"|758 x 1024|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|
|
| ED060XD4 | 6" | 758 x 1024<br>212 PPI | yes, tested | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060XC9|6"|758 x 1024|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|
|
| ED060XC5 | 6" | 758 x 1024<br>212 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060KD1|6"|1072 x 1448|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|
|
| ED060XD6 | 6" | 758 x 1024<br>212 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060KC1|6"|1072 x 1448|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|
|
| ED060XH2 | 6" | 758 x 1024<br>212 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060SCF|6"|600 x 800|yes, tested|THD0515-34CL-SN|34|V5|Different flex cable shape
|
| ED060XC9 | 6" | 758 x 1024<br>212 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060SCN|6"|600 x 800|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|Different flex cable shape
|
| ED060KD1 | 6" | 1072 x 1448<br>300 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060SCP|6"|600 x 800|yes (should work as ED060XC3)|THD0515-34CL-SN|34|V5|Different flex cable shape
|
| ED060KC1 | 6" | 1072 x 1448<br>300 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 |
|
||||||
|ED060SC7|6"|600 x 800|yes (should work) |AXT434124|34|v5|
|
| ED060SCF | 6" | 600 x 800<br>167 PPI | yes, tested | THD0515-34CL-SN | 34 | v5 | Different flex cable shape
|
||||||
|ED060SCG|6"|600 x 800|yes (should work) |AXT434124|34|v5|
|
| ED060SCN | 6" | 600 x 800<br>167 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 | Different flex cable shape
|
||||||
| ED060SCE | 6" | 600 x 800 | yes (should work) | AXT434124 | 34 | v5 |
|
| ED060SCP | 6" | 600 x 800<br>167 PPI | yes (should work as ED060XC3) | THD0515-34CL-SN | 34 | v5 | Different flex cable shape
|
||||||
| ED060SCM | 6" | 600 x 800 | yes (should work) | AXT434124 | 34 | v5 |
|
| ED060SC7 | 6" | 600 x 800<br>167 PPI | yes (should work) | AXT334124 | 34 | v5 | connector dropped in v6
|
||||||
| ED060SCT | 6" | 600 x 800 | yes, tested | AXT434124 | 34 | v5 |
|
| ED060SCG | 6" | 600 x 800<br>167 PPI | yes (should work) | AXT334124 | 34 | v5 | connector dropped in v6
|
||||||
|
| ED060SCE | 6" | 600 x 800<br>167 PPI | yes (should work) | AXT334124 | 34 | v5 | connector dropped in v6
|
||||||
|
| ED060SCM | 6" | 600 x 800<br>167 PPI | yes (should work) | AXT334124 | 34 | v5 | connector dropped in v6
|
||||||
|
| ED060SCT | 6" | 600 x 800<br>167 PPI | yes, tested | AXT334124 | 34 | v5 | connector dropped in v6
|
||||||
|
| ED078KC1 | 7.8" | 1872 x 1404<br>300 PPI | yes, tested | FH12-40S-0.5SH | 40 | v7 | 16 data lines
|
||||||
|
|
||||||
**Please note that board revision v5 is still in prototype stage!**
|
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
@ -88,6 +109,25 @@ The following list is compiled from past experiences and GitHub issues:
|
|||||||
* **The existing image fades / darkens when updating a partial screen region.** Make sure the VCOM voltage is [calibrated](https://epdiy.readthedocs.io/en/latest/getting_started.html#calibrate-vcom) for your specific display.
|
* **The existing image fades / darkens when updating a partial screen region.** Make sure the VCOM voltage is [calibrated](https://epdiy.readthedocs.io/en/latest/getting_started.html#calibrate-vcom) for your specific display.
|
||||||
* **The second third of the image is replaced with the last third.** This seems to be a timing issue we could not yet quite figure out the reason for. For a workarround or suggestions please [join the discussion](https://github.com/vroland/epdiy/issues/15).
|
* **The second third of the image is replaced with the last third.** This seems to be a timing issue we could not yet quite figure out the reason for. For a workarround or suggestions please [join the discussion](https://github.com/vroland/epdiy/issues/15).
|
||||||
* **The ESP does not boot correctly when external periperals are connected.** Make sure not to pull GPIO12 high during boot, as it is a strapping pin internal voltage selection (https://github.com/vroland/epdiy/issues/17).
|
* **The ESP does not boot correctly when external periperals are connected.** Make sure not to pull GPIO12 high during boot, as it is a strapping pin internal voltage selection (https://github.com/vroland/epdiy/issues/17).
|
||||||
|
* **The ESP power consumption in deep sleep is too high.** Add `rtc_gpio_isolate(GPIO_NUM_12);` to your solution. See also [Configuring IOs (Deep-sleep Only)](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html?highlight=rtc_gpio_isolate#configuring-ios-deep-sleep-only).
|
||||||
|
|
||||||
|
LilyGo Boards
|
||||||
|
---------------
|
||||||
|
There are several differences with these boards.
|
||||||
|
One particular one is the way the LilyGo handles power to the display the official lilygo code has two states.
|
||||||
|
This is now handled in epdiy in a different way to the lilygo code.
|
||||||
|
**epd_poweroff()** completely turns the power off to the display and the other peripherals of the lilygo.
|
||||||
|
The new function **epd_powerdown()** keeps the peripherals on (this allows the touch functions to continue to work).
|
||||||
|
**epd_poweroff() should allways be called before sleeping the system**
|
||||||
|
You can still use touch to wake the screen with the following.
|
||||||
|
In Arduino it works like this.
|
||||||
|
`epd_poweroff();`
|
||||||
|
|
||||||
|
`epd_deinit();`
|
||||||
|
|
||||||
|
`esp_sleep_enable_ext1_wakeup(GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH);`
|
||||||
|
|
||||||
|
`esp_deep_sleep_start();`
|
||||||
|
|
||||||
More on E-Paper Displays
|
More on E-Paper Displays
|
||||||
------------------------
|
------------------------
|
||||||
@ -106,8 +146,7 @@ Hackaday Project
|
|||||||
For more details, see the project page on Hackaday: https://hackaday.io/project/168193-epdiy-976-e-paper-controller
|
For more details, see the project page on Hackaday: https://hackaday.io/project/168193-epdiy-976-e-paper-controller
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
Licenses
|
Licenses
|
||||||
--------
|
--------
|
||||||
@ -116,6 +155,5 @@ The weather example is Copyright (c) David Bird 2018 (except for minor modificat
|
|||||||
|
|
||||||
The board and schematic are licensed under a <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /></a> <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
|
The board and schematic are licensed under a <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /></a> <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
|
||||||
|
|
||||||
Firmware and remaining examples are licensed under the terms of the GNU GPL version 3.
|
Firmware and remaining examples are licensed under the terms of the GNU Lesser GPL version 3.
|
||||||
Utilities are licensed under the terms of the MIT license.
|
Utilities are licensed under the terms of the MIT license.
|
||||||
|
|
||||||
|
|||||||
@ -1,452 +0,0 @@
|
|||||||
############################## Revision 5 Board ##############################
|
|
||||||
epdiy_v5.name=EPDIY Board Revision 5
|
|
||||||
|
|
||||||
epdiy_v5.upload.tool=esp32:esptool_py
|
|
||||||
epdiy_v5.upload.maximum_size=1310720
|
|
||||||
epdiy_v5.upload.maximum_data_size=327680
|
|
||||||
epdiy_v5.upload.wait_for_upload_port=true
|
|
||||||
|
|
||||||
epdiy_v5.serial.disableDTR=true
|
|
||||||
epdiy_v5.serial.disableRTS=true
|
|
||||||
|
|
||||||
epdiy_v5.build.mcu=esp32
|
|
||||||
epdiy_v5.build.core=esp32:esp32
|
|
||||||
epdiy_v5.build.variant=esp32
|
|
||||||
epdiy_v5.build.board=ESP32_EPDIY
|
|
||||||
|
|
||||||
epdiy_v5.build.f_cpu=240000000L
|
|
||||||
epdiy_v5.build.flash_size=4MB
|
|
||||||
epdiy_v5.build.flash_freq=40m
|
|
||||||
epdiy_v5.build.flash_mode=dio
|
|
||||||
epdiy_v5.build.boot=dio
|
|
||||||
epdiy_v5.build.partitions=default
|
|
||||||
epdiy_v5.build.defines=
|
|
||||||
|
|
||||||
epdiy_v5.menu.DisplayType.default=ED097OC4
|
|
||||||
epdiy_v5.menu.DisplayType.default.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V5
|
|
||||||
epdiy_v5.menu.DisplayType.ed097oc4_lq=ED097OC4 Low Quality
|
|
||||||
epdiy_v5.menu.DisplayType.ed097oc4_lq.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V5
|
|
||||||
epdiy_v5.menu.DisplayType.ed060sc4=ED060SC4
|
|
||||||
epdiy_v5.menu.DisplayType.ed060sc4.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED060SC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V5
|
|
||||||
epdiy_v5.menu.DisplayType.ed097tc2=ED097TC2
|
|
||||||
epdiy_v5.menu.DisplayType.ed097tc2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097TC2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V5
|
|
||||||
epdiy_v5.menu.DisplayType.ed047tc1=ED047TC1 (LILYGO 4.7 inch)
|
|
||||||
epdiy_v5.menu.DisplayType.ed047tc1.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED047TC1 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V5
|
|
||||||
epdiy_v5.menu.DisplayType.ed133ut2=ED133UT2
|
|
||||||
epdiy_v5.menu.DisplayType.ed133ut2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED133UT2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V5
|
|
||||||
|
|
||||||
epdiy_v5.menu.PartitionScheme.default=Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.default.build.partitions=default
|
|
||||||
epdiy_v5.menu.PartitionScheme.defaultffat=Default 4MB with ffat (1.2MB APP/1.5MB FATFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.defaultffat.build.partitions=default_ffat
|
|
||||||
epdiy_v5.menu.PartitionScheme.default_8MB=8M Flash (3MB APP/1.5MB FAT)
|
|
||||||
epdiy_v5.menu.PartitionScheme.default_8MB.build.partitions=default_8MB
|
|
||||||
epdiy_v5.menu.PartitionScheme.minimal=Minimal (1.3MB APP/700KB SPIFFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.minimal.build.partitions=minimal
|
|
||||||
epdiy_v5.menu.PartitionScheme.no_ota=No OTA (2MB APP/2MB SPIFFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.no_ota.build.partitions=no_ota
|
|
||||||
epdiy_v5.menu.PartitionScheme.no_ota.upload.maximum_size=2097152
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_3g=No OTA (1MB APP/3MB SPIFFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_3g.build.partitions=noota_3g
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_3g.upload.maximum_size=1048576
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_ffat=No OTA (2MB APP/2MB FATFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_ffat.build.partitions=noota_ffat
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_ffat.upload.maximum_size=2097152
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_3gffat=No OTA (1MB APP/3MB FATFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_3gffat.build.partitions=noota_3gffat
|
|
||||||
epdiy_v5.menu.PartitionScheme.noota_3gffat.upload.maximum_size=1048576
|
|
||||||
epdiy_v5.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.huge_app.build.partitions=huge_app
|
|
||||||
epdiy_v5.menu.PartitionScheme.huge_app.upload.maximum_size=3145728
|
|
||||||
epdiy_v5.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
|
|
||||||
epdiy_v5.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs
|
|
||||||
epdiy_v5.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080
|
|
||||||
epdiy_v5.menu.PartitionScheme.fatflash=16M Flash (2MB APP/12.5MB FAT)
|
|
||||||
epdiy_v5.menu.PartitionScheme.fatflash.build.partitions=ffat
|
|
||||||
|
|
||||||
epdiy_v5.menu.FlashMode.qio=QIO
|
|
||||||
epdiy_v5.menu.FlashMode.qio.build.flash_mode=dio
|
|
||||||
epdiy_v5.menu.FlashMode.qio.build.boot=qio
|
|
||||||
epdiy_v5.menu.FlashMode.dio=DIO
|
|
||||||
epdiy_v5.menu.FlashMode.dio.build.flash_mode=dio
|
|
||||||
epdiy_v5.menu.FlashMode.dio.build.boot=dio
|
|
||||||
epdiy_v5.menu.FlashMode.qout=QOUT
|
|
||||||
epdiy_v5.menu.FlashMode.qout.build.flash_mode=dout
|
|
||||||
epdiy_v5.menu.FlashMode.qout.build.boot=qout
|
|
||||||
epdiy_v5.menu.FlashMode.dout=DOUT
|
|
||||||
epdiy_v5.menu.FlashMode.dout.build.flash_mode=dout
|
|
||||||
epdiy_v5.menu.FlashMode.dout.build.boot=dout
|
|
||||||
|
|
||||||
epdiy_v5.menu.FlashFreq.80=80MHz
|
|
||||||
epdiy_v5.menu.FlashFreq.80.build.flash_freq=80m
|
|
||||||
epdiy_v5.menu.FlashFreq.40=40MHz
|
|
||||||
epdiy_v5.menu.FlashFreq.40.build.flash_freq=40m
|
|
||||||
|
|
||||||
epdiy_v5.menu.UploadSpeed.921600=921600
|
|
||||||
epdiy_v5.menu.UploadSpeed.921600.upload.speed=921600
|
|
||||||
epdiy_v5.menu.UploadSpeed.115200=115200
|
|
||||||
epdiy_v5.menu.UploadSpeed.115200.upload.speed=115200
|
|
||||||
epdiy_v5.menu.UploadSpeed.256000.windows=256000
|
|
||||||
epdiy_v5.menu.UploadSpeed.256000.upload.speed=256000
|
|
||||||
epdiy_v5.menu.UploadSpeed.230400.windows.upload.speed=256000
|
|
||||||
epdiy_v5.menu.UploadSpeed.230400=230400
|
|
||||||
epdiy_v5.menu.UploadSpeed.230400.upload.speed=230400
|
|
||||||
epdiy_v5.menu.UploadSpeed.460800.linux=460800
|
|
||||||
epdiy_v5.menu.UploadSpeed.460800.macosx=460800
|
|
||||||
epdiy_v5.menu.UploadSpeed.460800.upload.speed=460800
|
|
||||||
epdiy_v5.menu.UploadSpeed.512000.windows=512000
|
|
||||||
epdiy_v5.menu.UploadSpeed.512000.upload.speed=512000
|
|
||||||
|
|
||||||
epdiy_v5.menu.DebugLevel.none=None
|
|
||||||
epdiy_v5.menu.DebugLevel.none.build.code_debug=0
|
|
||||||
epdiy_v5.menu.DebugLevel.error=Error
|
|
||||||
epdiy_v5.menu.DebugLevel.error.build.code_debug=1
|
|
||||||
epdiy_v5.menu.DebugLevel.warn=Warn
|
|
||||||
epdiy_v5.menu.DebugLevel.warn.build.code_debug=2
|
|
||||||
epdiy_v5.menu.DebugLevel.info=Info
|
|
||||||
epdiy_v5.menu.DebugLevel.info.build.code_debug=3
|
|
||||||
epdiy_v5.menu.DebugLevel.debug=Debug
|
|
||||||
epdiy_v5.menu.DebugLevel.debug.build.code_debug=4
|
|
||||||
epdiy_v5.menu.DebugLevel.verbose=Verbose
|
|
||||||
epdiy_v5.menu.DebugLevel.verbose.build.code_debug=5
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##############################################################
|
|
||||||
|
|
||||||
menu.DisplayType=DisplayType
|
|
||||||
|
|
||||||
epdiy_v2.name=EPDIY Board Revision 2/3
|
|
||||||
|
|
||||||
epdiy_v2.upload.tool=esptool_py
|
|
||||||
epdiy_v2.upload.maximum_size=1310720
|
|
||||||
epdiy_v2.upload.maximum_data_size=327680
|
|
||||||
epdiy_v2.upload.wait_for_upload_port=true
|
|
||||||
|
|
||||||
epdiy_v2.serial.disableDTR=true
|
|
||||||
epdiy_v2.serial.disableRTS=true
|
|
||||||
|
|
||||||
epdiy_v2.build.mcu=esp32
|
|
||||||
epdiy_v2.build.core=esp32:esp32
|
|
||||||
epdiy_v2.build.variant=esp32
|
|
||||||
epdiy_v2.build.board=ESP32_EPDIY
|
|
||||||
|
|
||||||
epdiy_v2.build.f_cpu=240000000L
|
|
||||||
epdiy_v2.build.flash_size=4MB
|
|
||||||
epdiy_v2.build.flash_freq=40m
|
|
||||||
epdiy_v2.build.flash_mode=dio
|
|
||||||
epdiy_v2.build.boot=dio
|
|
||||||
epdiy_v2.build.partitions=default
|
|
||||||
|
|
||||||
epdiy_v2.menu.DisplayType.default=ED097OC4
|
|
||||||
epdiy_v2.menu.DisplayType.default.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V2_V3
|
|
||||||
epdiy_v2.menu.DisplayType.ed097oc4_lq=ED097OC4 Low Quality
|
|
||||||
epdiy_v2.menu.DisplayType.ed097oc4_lq.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V2_V3
|
|
||||||
epdiy_v2.menu.DisplayType.ed060sc4=ED060SC4
|
|
||||||
epdiy_v2.menu.DisplayType.ed060sc4.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED060SC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V2_V3
|
|
||||||
epdiy_v2.menu.DisplayType.ed097tc2=ED097TC2
|
|
||||||
epdiy_v2.menu.DisplayType.ed097tc2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097TC2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V2_V3
|
|
||||||
epdiy_v2.menu.DisplayType.ed047tc1=ED047TC1 (LILYGO 4.7 inch)
|
|
||||||
epdiy_v2.menu.DisplayType.ed047tc1.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED047TC1 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V2_V3
|
|
||||||
epdiy_v2.menu.DisplayType.ed133ut2=ED133UT2
|
|
||||||
epdiy_v2.menu.DisplayType.ed133ut2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED133UT2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V2_V3
|
|
||||||
|
|
||||||
epdiy_v2.menu.PartitionScheme.default=Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.default.build.partitions=default
|
|
||||||
epdiy_v2.menu.PartitionScheme.defaultffat=Default 4MB with ffat (1.2MB APP/1.5MB FATFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.defaultffat.build.partitions=default_ffat
|
|
||||||
epdiy_v2.menu.PartitionScheme.default_8MB=8M Flash (3MB APP/1.5MB FAT)
|
|
||||||
epdiy_v2.menu.PartitionScheme.default_8MB.build.partitions=default_8MB
|
|
||||||
epdiy_v2.menu.PartitionScheme.minimal=Minimal (1.3MB APP/700KB SPIFFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.minimal.build.partitions=minimal
|
|
||||||
epdiy_v2.menu.PartitionScheme.no_ota=No OTA (2MB APP/2MB SPIFFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.no_ota.build.partitions=no_ota
|
|
||||||
epdiy_v2.menu.PartitionScheme.no_ota.upload.maximum_size=2097152
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_3g=No OTA (1MB APP/3MB SPIFFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_3g.build.partitions=noota_3g
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_3g.upload.maximum_size=1048576
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_ffat=No OTA (2MB APP/2MB FATFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_ffat.build.partitions=noota_ffat
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_ffat.upload.maximum_size=2097152
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_3gffat=No OTA (1MB APP/3MB FATFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_3gffat.build.partitions=noota_3gffat
|
|
||||||
epdiy_v2.menu.PartitionScheme.noota_3gffat.upload.maximum_size=1048576
|
|
||||||
epdiy_v2.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.huge_app.build.partitions=huge_app
|
|
||||||
epdiy_v2.menu.PartitionScheme.huge_app.upload.maximum_size=3145728
|
|
||||||
epdiy_v2.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
|
|
||||||
epdiy_v2.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs
|
|
||||||
epdiy_v2.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080
|
|
||||||
epdiy_v2.menu.PartitionScheme.fatflash=16M Flash (2MB APP/12.5MB FAT)
|
|
||||||
epdiy_v2.menu.PartitionScheme.fatflash.build.partitions=ffat
|
|
||||||
|
|
||||||
epdiy_v2.menu.FlashMode.qio=QIO
|
|
||||||
epdiy_v2.menu.FlashMode.qio.build.flash_mode=dio
|
|
||||||
epdiy_v2.menu.FlashMode.qio.build.boot=qio
|
|
||||||
epdiy_v2.menu.FlashMode.dio=DIO
|
|
||||||
epdiy_v2.menu.FlashMode.dio.build.flash_mode=dio
|
|
||||||
epdiy_v2.menu.FlashMode.dio.build.boot=dio
|
|
||||||
epdiy_v2.menu.FlashMode.qout=QOUT
|
|
||||||
epdiy_v2.menu.FlashMode.qout.build.flash_mode=dout
|
|
||||||
epdiy_v2.menu.FlashMode.qout.build.boot=qout
|
|
||||||
epdiy_v2.menu.FlashMode.dout=DOUT
|
|
||||||
epdiy_v2.menu.FlashMode.dout.build.flash_mode=dout
|
|
||||||
epdiy_v2.menu.FlashMode.dout.build.boot=dout
|
|
||||||
|
|
||||||
epdiy_v2.menu.FlashFreq.80=80MHz
|
|
||||||
epdiy_v2.menu.FlashFreq.80.build.flash_freq=80m
|
|
||||||
epdiy_v2.menu.FlashFreq.40=40MHz
|
|
||||||
epdiy_v2.menu.FlashFreq.40.build.flash_freq=40m
|
|
||||||
|
|
||||||
epdiy_v2.menu.UploadSpeed.921600=921600
|
|
||||||
epdiy_v2.menu.UploadSpeed.921600.upload.speed=921600
|
|
||||||
epdiy_v2.menu.UploadSpeed.115200=115200
|
|
||||||
epdiy_v2.menu.UploadSpeed.115200.upload.speed=115200
|
|
||||||
epdiy_v2.menu.UploadSpeed.256000.windows=256000
|
|
||||||
epdiy_v2.menu.UploadSpeed.256000.upload.speed=256000
|
|
||||||
epdiy_v2.menu.UploadSpeed.230400.windows.upload.speed=256000
|
|
||||||
epdiy_v2.menu.UploadSpeed.230400=230400
|
|
||||||
epdiy_v2.menu.UploadSpeed.230400.upload.speed=230400
|
|
||||||
epdiy_v2.menu.UploadSpeed.460800.linux=460800
|
|
||||||
epdiy_v2.menu.UploadSpeed.460800.macosx=460800
|
|
||||||
epdiy_v2.menu.UploadSpeed.460800.upload.speed=460800
|
|
||||||
epdiy_v2.menu.UploadSpeed.512000.windows=512000
|
|
||||||
epdiy_v2.menu.UploadSpeed.512000.upload.speed=512000
|
|
||||||
|
|
||||||
epdiy_v2.menu.DebugLevel.none=None
|
|
||||||
epdiy_v2.menu.DebugLevel.none.build.code_debug=0
|
|
||||||
epdiy_v2.menu.DebugLevel.error=Error
|
|
||||||
epdiy_v2.menu.DebugLevel.error.build.code_debug=1
|
|
||||||
epdiy_v2.menu.DebugLevel.warn=Warn
|
|
||||||
epdiy_v2.menu.DebugLevel.warn.build.code_debug=2
|
|
||||||
epdiy_v2.menu.DebugLevel.info=Info
|
|
||||||
epdiy_v2.menu.DebugLevel.info.build.code_debug=3
|
|
||||||
epdiy_v2.menu.DebugLevel.debug=Debug
|
|
||||||
epdiy_v2.menu.DebugLevel.debug.build.code_debug=4
|
|
||||||
epdiy_v2.menu.DebugLevel.verbose=Verbose
|
|
||||||
epdiy_v2.menu.DebugLevel.verbose.build.code_debug=5
|
|
||||||
|
|
||||||
############################## Revision 4 Board ##############################
|
|
||||||
epdiy_v4.name=EPDIY Board Revision 4
|
|
||||||
|
|
||||||
epdiy_v4.upload.tool=esp32:esptool_py
|
|
||||||
epdiy_v4.upload.maximum_size=1310720
|
|
||||||
epdiy_v4.upload.maximum_data_size=327680
|
|
||||||
epdiy_v4.upload.wait_for_upload_port=true
|
|
||||||
|
|
||||||
epdiy_v4.serial.disableDTR=true
|
|
||||||
epdiy_v4.serial.disableRTS=true
|
|
||||||
|
|
||||||
epdiy_v4.build.mcu=esp32
|
|
||||||
epdiy_v4.build.core=esp32:esp32
|
|
||||||
epdiy_v4.build.variant=esp32
|
|
||||||
epdiy_v4.build.board=ESP32_EPDIY
|
|
||||||
|
|
||||||
epdiy_v4.build.f_cpu=240000000L
|
|
||||||
epdiy_v4.build.flash_size=4MB
|
|
||||||
epdiy_v4.build.flash_freq=40m
|
|
||||||
epdiy_v4.build.flash_mode=dio
|
|
||||||
epdiy_v4.build.boot=dio
|
|
||||||
epdiy_v4.build.partitions=default
|
|
||||||
epdiy_v4.build.defines=
|
|
||||||
|
|
||||||
epdiy_v4.menu.DisplayType.default=ED097OC4
|
|
||||||
epdiy_v4.menu.DisplayType.default.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V4
|
|
||||||
epdiy_v4.menu.DisplayType.ed097oc4_lq=ED097OC4 Low Quality
|
|
||||||
epdiy_v4.menu.DisplayType.ed097oc4_lq.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V4
|
|
||||||
epdiy_v4.menu.DisplayType.ed060sc4=ED060SC4
|
|
||||||
epdiy_v4.menu.DisplayType.ed060sc4.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED060SC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V4
|
|
||||||
epdiy_v4.menu.DisplayType.ed097tc2=ED097TC2
|
|
||||||
epdiy_v4.menu.DisplayType.ed097tc2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097TC2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V4
|
|
||||||
epdiy_v4.menu.DisplayType.ed047tc1=ED047TC1 (LILYGO 4.7 inch)
|
|
||||||
epdiy_v4.menu.DisplayType.ed047tc1.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED047TC1 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V4
|
|
||||||
epdiy_v4.menu.DisplayType.ed133ut2=ED133UT2
|
|
||||||
epdiy_v4.menu.DisplayType.ed133ut2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED133UT2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_V4
|
|
||||||
|
|
||||||
epdiy_v4.menu.PartitionScheme.default=Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.default.build.partitions=default
|
|
||||||
epdiy_v4.menu.PartitionScheme.defaultffat=Default 4MB with ffat (1.2MB APP/1.5MB FATFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.defaultffat.build.partitions=default_ffat
|
|
||||||
epdiy_v4.menu.PartitionScheme.default_8MB=8M Flash (3MB APP/1.5MB FAT)
|
|
||||||
epdiy_v4.menu.PartitionScheme.default_8MB.build.partitions=default_8MB
|
|
||||||
epdiy_v4.menu.PartitionScheme.minimal=Minimal (1.3MB APP/700KB SPIFFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.minimal.build.partitions=minimal
|
|
||||||
epdiy_v4.menu.PartitionScheme.no_ota=No OTA (2MB APP/2MB SPIFFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.no_ota.build.partitions=no_ota
|
|
||||||
epdiy_v4.menu.PartitionScheme.no_ota.upload.maximum_size=2097152
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_3g=No OTA (1MB APP/3MB SPIFFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_3g.build.partitions=noota_3g
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_3g.upload.maximum_size=1048576
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_ffat=No OTA (2MB APP/2MB FATFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_ffat.build.partitions=noota_ffat
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_ffat.upload.maximum_size=2097152
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_3gffat=No OTA (1MB APP/3MB FATFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_3gffat.build.partitions=noota_3gffat
|
|
||||||
epdiy_v4.menu.PartitionScheme.noota_3gffat.upload.maximum_size=1048576
|
|
||||||
epdiy_v4.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.huge_app.build.partitions=huge_app
|
|
||||||
epdiy_v4.menu.PartitionScheme.huge_app.upload.maximum_size=3145728
|
|
||||||
epdiy_v4.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
|
|
||||||
epdiy_v4.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs
|
|
||||||
epdiy_v4.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080
|
|
||||||
epdiy_v4.menu.PartitionScheme.fatflash=16M Flash (2MB APP/12.5MB FAT)
|
|
||||||
epdiy_v4.menu.PartitionScheme.fatflash.build.partitions=ffat
|
|
||||||
|
|
||||||
epdiy_v4.menu.FlashMode.qio=QIO
|
|
||||||
epdiy_v4.menu.FlashMode.qio.build.flash_mode=dio
|
|
||||||
epdiy_v4.menu.FlashMode.qio.build.boot=qio
|
|
||||||
epdiy_v4.menu.FlashMode.dio=DIO
|
|
||||||
epdiy_v4.menu.FlashMode.dio.build.flash_mode=dio
|
|
||||||
epdiy_v4.menu.FlashMode.dio.build.boot=dio
|
|
||||||
epdiy_v4.menu.FlashMode.qout=QOUT
|
|
||||||
epdiy_v4.menu.FlashMode.qout.build.flash_mode=dout
|
|
||||||
epdiy_v4.menu.FlashMode.qout.build.boot=qout
|
|
||||||
epdiy_v4.menu.FlashMode.dout=DOUT
|
|
||||||
epdiy_v4.menu.FlashMode.dout.build.flash_mode=dout
|
|
||||||
epdiy_v4.menu.FlashMode.dout.build.boot=dout
|
|
||||||
|
|
||||||
epdiy_v4.menu.FlashFreq.80=80MHz
|
|
||||||
epdiy_v4.menu.FlashFreq.80.build.flash_freq=80m
|
|
||||||
epdiy_v4.menu.FlashFreq.40=40MHz
|
|
||||||
epdiy_v4.menu.FlashFreq.40.build.flash_freq=40m
|
|
||||||
|
|
||||||
epdiy_v4.menu.UploadSpeed.921600=921600
|
|
||||||
epdiy_v4.menu.UploadSpeed.921600.upload.speed=921600
|
|
||||||
epdiy_v4.menu.UploadSpeed.115200=115200
|
|
||||||
epdiy_v4.menu.UploadSpeed.115200.upload.speed=115200
|
|
||||||
epdiy_v4.menu.UploadSpeed.256000.windows=256000
|
|
||||||
epdiy_v4.menu.UploadSpeed.256000.upload.speed=256000
|
|
||||||
epdiy_v4.menu.UploadSpeed.230400.windows.upload.speed=256000
|
|
||||||
epdiy_v4.menu.UploadSpeed.230400=230400
|
|
||||||
epdiy_v4.menu.UploadSpeed.230400.upload.speed=230400
|
|
||||||
epdiy_v4.menu.UploadSpeed.460800.linux=460800
|
|
||||||
epdiy_v4.menu.UploadSpeed.460800.macosx=460800
|
|
||||||
epdiy_v4.menu.UploadSpeed.460800.upload.speed=460800
|
|
||||||
epdiy_v4.menu.UploadSpeed.512000.windows=512000
|
|
||||||
epdiy_v4.menu.UploadSpeed.512000.upload.speed=512000
|
|
||||||
|
|
||||||
epdiy_v4.menu.DebugLevel.none=None
|
|
||||||
epdiy_v4.menu.DebugLevel.none.build.code_debug=0
|
|
||||||
epdiy_v4.menu.DebugLevel.error=Error
|
|
||||||
epdiy_v4.menu.DebugLevel.error.build.code_debug=1
|
|
||||||
epdiy_v4.menu.DebugLevel.warn=Warn
|
|
||||||
epdiy_v4.menu.DebugLevel.warn.build.code_debug=2
|
|
||||||
epdiy_v4.menu.DebugLevel.info=Info
|
|
||||||
epdiy_v4.menu.DebugLevel.info.build.code_debug=3
|
|
||||||
epdiy_v4.menu.DebugLevel.debug=Debug
|
|
||||||
epdiy_v4.menu.DebugLevel.debug.build.code_debug=4
|
|
||||||
epdiy_v4.menu.DebugLevel.verbose=Verbose
|
|
||||||
epdiy_v4.menu.DebugLevel.verbose.build.code_debug=5
|
|
||||||
|
|
||||||
|
|
||||||
############################## Revision 4 Board ##############################
|
|
||||||
lilygo_t5_47.name=LILYGO T5-4.7 inch e-paper
|
|
||||||
|
|
||||||
lilygo_t5_47.upload.tool=esp32:esptool_py
|
|
||||||
lilygo_t5_47.upload.maximum_size=1310720
|
|
||||||
lilygo_t5_47.upload.maximum_data_size=327680
|
|
||||||
lilygo_t5_47.upload.wait_for_upload_port=true
|
|
||||||
|
|
||||||
lilygo_t5_47.serial.disableDTR=true
|
|
||||||
lilygo_t5_47.serial.disableRTS=true
|
|
||||||
|
|
||||||
lilygo_t5_47.build.mcu=esp32
|
|
||||||
lilygo_t5_47.build.core=esp32:esp32
|
|
||||||
lilygo_t5_47.build.variant=esp32
|
|
||||||
lilygo_t5_47.build.board=ESP32_EPDIY
|
|
||||||
|
|
||||||
lilygo_t5_47.build.f_cpu=240000000L
|
|
||||||
lilygo_t5_47.build.flash_size=4MB
|
|
||||||
lilygo_t5_47.build.flash_freq=40m
|
|
||||||
lilygo_t5_47.build.flash_mode=dio
|
|
||||||
lilygo_t5_47.build.boot=dio
|
|
||||||
lilygo_t5_47.build.partitions=default
|
|
||||||
lilygo_t5_47.build.defines=-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
|
|
||||||
lilygo_t5_47.menu.DisplayType.default=ED097OC4
|
|
||||||
lilygo_t5_47.menu.DisplayType.default.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed097oc4_lq=ED097OC4 Low Quality
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed097oc4_lq.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed060sc4=ED060SC4
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed060sc4.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED060SC4 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed097tc2=ED097TC2
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed097tc2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED097TC2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed047tc1=ED047TC1 (LILYGO 4.7 inch)
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed047tc1.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED047TC1 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed133ut2=ED133UT2
|
|
||||||
lilygo_t5_47.menu.DisplayType.ed133ut2.build.defines=-DCONFIG_EPD_DISPLAY_TYPE_ED133UT2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.default=Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.default.build.partitions=default
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.defaultffat=Default 4MB with ffat (1.2MB APP/1.5MB FATFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.defaultffat.build.partitions=default_ffat
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.default_8MB=8M Flash (3MB APP/1.5MB FAT)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.default_8MB.build.partitions=default_8MB
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.minimal=Minimal (1.3MB APP/700KB SPIFFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.minimal.build.partitions=minimal
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.no_ota=No OTA (2MB APP/2MB SPIFFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.no_ota.build.partitions=no_ota
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.no_ota.upload.maximum_size=2097152
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_3g=No OTA (1MB APP/3MB SPIFFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_3g.build.partitions=noota_3g
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_3g.upload.maximum_size=1048576
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_ffat=No OTA (2MB APP/2MB FATFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_ffat.build.partitions=noota_ffat
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_ffat.upload.maximum_size=2097152
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_3gffat=No OTA (1MB APP/3MB FATFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_3gffat.build.partitions=noota_3gffat
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.noota_3gffat.upload.maximum_size=1048576
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.huge_app.build.partitions=huge_app
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.huge_app.upload.maximum_size=3145728
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.fatflash=16M Flash (2MB APP/12.5MB FAT)
|
|
||||||
lilygo_t5_47.menu.PartitionScheme.fatflash.build.partitions=ffat
|
|
||||||
|
|
||||||
lilygo_t5_47.menu.FlashMode.qio=QIO
|
|
||||||
lilygo_t5_47.menu.FlashMode.qio.build.flash_mode=dio
|
|
||||||
lilygo_t5_47.menu.FlashMode.qio.build.boot=qio
|
|
||||||
lilygo_t5_47.menu.FlashMode.dio=DIO
|
|
||||||
lilygo_t5_47.menu.FlashMode.dio.build.flash_mode=dio
|
|
||||||
lilygo_t5_47.menu.FlashMode.dio.build.boot=dio
|
|
||||||
lilygo_t5_47.menu.FlashMode.qout=QOUT
|
|
||||||
lilygo_t5_47.menu.FlashMode.qout.build.flash_mode=dout
|
|
||||||
lilygo_t5_47.menu.FlashMode.qout.build.boot=qout
|
|
||||||
lilygo_t5_47.menu.FlashMode.dout=DOUT
|
|
||||||
lilygo_t5_47.menu.FlashMode.dout.build.flash_mode=dout
|
|
||||||
lilygo_t5_47.menu.FlashMode.dout.build.boot=dout
|
|
||||||
|
|
||||||
lilygo_t5_47.menu.FlashFreq.80=80MHz
|
|
||||||
lilygo_t5_47.menu.FlashFreq.80.build.flash_freq=80m
|
|
||||||
lilygo_t5_47.menu.FlashFreq.40=40MHz
|
|
||||||
lilygo_t5_47.menu.FlashFreq.40.build.flash_freq=40m
|
|
||||||
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.921600=921600
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.921600.upload.speed=921600
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.115200=115200
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.115200.upload.speed=115200
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.256000.windows=256000
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.256000.upload.speed=256000
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.230400.windows.upload.speed=256000
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.230400=230400
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.230400.upload.speed=230400
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.460800.linux=460800
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.460800.macosx=460800
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.460800.upload.speed=460800
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.512000.windows=512000
|
|
||||||
lilygo_t5_47.menu.UploadSpeed.512000.upload.speed=512000
|
|
||||||
|
|
||||||
lilygo_t5_47.menu.DebugLevel.none=None
|
|
||||||
lilygo_t5_47.menu.DebugLevel.none.build.code_debug=0
|
|
||||||
lilygo_t5_47.menu.DebugLevel.error=Error
|
|
||||||
lilygo_t5_47.menu.DebugLevel.error.build.code_debug=1
|
|
||||||
lilygo_t5_47.menu.DebugLevel.warn=Warn
|
|
||||||
lilygo_t5_47.menu.DebugLevel.warn.build.code_debug=2
|
|
||||||
lilygo_t5_47.menu.DebugLevel.info=Info
|
|
||||||
lilygo_t5_47.menu.DebugLevel.info.build.code_debug=3
|
|
||||||
lilygo_t5_47.menu.DebugLevel.debug=Debug
|
|
||||||
lilygo_t5_47.menu.DebugLevel.debug.build.code_debug=4
|
|
||||||
lilygo_t5_47.menu.DebugLevel.verbose=Verbose
|
|
||||||
lilygo_t5_47.menu.DebugLevel.verbose.build.code_debug=5
|
|
||||||
4
lib/libesp32_eink/epdiy/idf_component.yml
Normal file
4
lib/libesp32_eink/epdiy/idf_component.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
version: "2.0.0"
|
||||||
|
description: "Drive parallel e-Paper displays with epdiy-based boards."
|
||||||
|
url: "https://github.com/vroland/epdiy"
|
||||||
|
license: LGPL-3.0-or-later
|
||||||
32
lib/libesp32_eink/epdiy/library.json
Normal file
32
lib/libesp32_eink/epdiy/library.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "epdiy",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Drive parallel e-Paper displays with epdiy-based boards.",
|
||||||
|
"keywords": "epd, driver, e-ink",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/vroland/epdiy.git"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Valentin Roland",
|
||||||
|
"email": "github@vroland.de",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0",
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"platforms": "espressif32",
|
||||||
|
"export": {
|
||||||
|
"exclude": [
|
||||||
|
"hardware"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"includeDir": "include",
|
||||||
|
"srcDir": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
name=epdiy
|
name=epdiy
|
||||||
version=1.0.0
|
version=2.0.0
|
||||||
author=Valentin Roland <github@vroland.de>
|
author=Valentin Roland <github@vroland.de>
|
||||||
maintainer=Valentin Roland <github@vroland.de>
|
maintainer=Valentin Roland <github@vroland.de>
|
||||||
sentence=Drive parallel e-Paper displays with epdiy-based boards.
|
sentence=Drive parallel e-Paper displays with epdiy-based boards.
|
||||||
paragraph=See https://github.com/vroland/epdiy for details.
|
paragraph=See https://github.com/vroland/epdiy for details.
|
||||||
architectures=esp32
|
architectures=esp32,esp32s3
|
||||||
url=https://github.com/vroland/epdiy
|
url=https://github.com/vroland/epdiy
|
||||||
category=Display
|
category=Display
|
||||||
|
|||||||
101
lib/libesp32_eink/epdiy/scripts/README.md
Normal file
101
lib/libesp32_eink/epdiy/scripts/README.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
## Scripts in this folder are for adding addtional capabilities to epdiy.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## imgconvert.py
|
||||||
|
|
||||||
|
#### usage:
|
||||||
|
|
||||||
|
python3 imgconvert.py [-h] -i INPUTFILE -n NAME -o OUTPUTFILE [-maxw MAX_WIDTH]
|
||||||
|
[-maxh MAX_HEIGHT]
|
||||||
|
|
||||||
|
**optional arguments:**
|
||||||
|
|
||||||
|
* **-h, --help** show this help message and exit
|
||||||
|
|
||||||
|
* **-i INPUTFILE**
|
||||||
|
|
||||||
|
* **-n NAME**
|
||||||
|
|
||||||
|
* **-o OUTPUTFILE**
|
||||||
|
|
||||||
|
* **-maxw MAX_WIDTH**
|
||||||
|
|
||||||
|
* **-maxh MAX_HEIGHT**
|
||||||
|
|
||||||
|
|
||||||
|
==========================================================
|
||||||
|
|
||||||
|
## fontconvert.py
|
||||||
|
|
||||||
|
#### usage:
|
||||||
|
|
||||||
|
python3 fontconvert.py [-h] [--compress] [--additional-intervals ADDITIONAL_INTERVALS]
|
||||||
|
[--string STRING]
|
||||||
|
name size fontstack [fontstack ...]
|
||||||
|
|
||||||
|
Generate a header file from a font to be used with epdiy.
|
||||||
|
|
||||||
|
**positional arguments:**
|
||||||
|
|
||||||
|
* **name** name of the font to be used in epdiy.
|
||||||
|
* **size** font size to use.
|
||||||
|
* **fontstack** list of font files, ordered by descending priority. This is not actually implemented as yet. Please just use one file for now.
|
||||||
|
|
||||||
|
**optional arguments:**
|
||||||
|
|
||||||
|
* **-h**, --help show this help message and exit
|
||||||
|
|
||||||
|
* **--compress** compress glyph bitmaps.
|
||||||
|
|
||||||
|
* **--additional-intervals** ADDITIONAL_INTERVALS
|
||||||
|
|
||||||
|
Additional code point intervals to export as min,max. This argument
|
||||||
|
can be repeated.
|
||||||
|
|
||||||
|
* **--string STRING** A quoted string of all required characters. The intervals are will be made from these characters if they exist in the ttf file. Missing characters will warn about their abscence.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####example:
|
||||||
|
1. Download a ttf from where you like to a directory. As in: "~/Downloads/any_old_ttf.ttf"
|
||||||
|
in the download directory
|
||||||
|
|
||||||
|
2. Run
|
||||||
|
|
||||||
|
`python3 fontconvert.py my_font 30 ~/Downloads/any_old_ttf.ttf --string '/0123456789:;@ABCDEFGH[\]^_`abcdefgh\{|}~¡¢£¤¥¦§¨©ª' > fonts.h`
|
||||||
|
|
||||||
|
* you will need to use special escapes for characters like ' or " This is system dependant though.
|
||||||
|
|
||||||
|
3. copy fonts.h into your app folder or where ever your app can find it.
|
||||||
|
4. include it into your project with
|
||||||
|
`#include fonts.h`
|
||||||
|
Then use it just like any other font file in epdiy.
|
||||||
|
|
||||||
|
**To run this script the freetype module needs to be installed. This can be done with `pip install freetype-py` You will be warned if it is not accessible by the script.**
|
||||||
|
|
||||||
|
==========================================================
|
||||||
|
|
||||||
|
##waveform_hdrgen.py
|
||||||
|
|
||||||
|
####usage:
|
||||||
|
|
||||||
|
waveform_hdrgen.py [-h] [--list-modes] [--temperature-range TEMPERATURE_RANGE]
|
||||||
|
[--export-modes EXPORT_MODES]
|
||||||
|
name
|
||||||
|
|
||||||
|
**positional arguments:**
|
||||||
|
name name of the waveform object.
|
||||||
|
|
||||||
|
**optional arguments:**
|
||||||
|
|
||||||
|
* **-h, --help** show this help message and exit
|
||||||
|
|
||||||
|
* **--list-modes** list the available modes for tis file.
|
||||||
|
|
||||||
|
* **--temperature-range TEMPERATURE_RANGE**
|
||||||
|
only export waveforms in the temperature range of min,max °C.
|
||||||
|
|
||||||
|
* **--export-modes EXPORT_MODES**
|
||||||
|
comma-separated list of waveform mode IDs to export.
|
||||||
|
|
||||||
@ -1,29 +1,11 @@
|
|||||||
#!python3
|
#!python3
|
||||||
import freetype
|
|
||||||
import zlib
|
|
||||||
import sys
|
import sys
|
||||||
import re
|
|
||||||
import math
|
|
||||||
import argparse
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
|
|
||||||
parser.add_argument("name", action="store", help="name of the font.")
|
|
||||||
parser.add_argument("size", type=int, help="font size to use.")
|
|
||||||
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
|
|
||||||
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
|
|
||||||
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
|
|
||||||
|
|
||||||
font_stack = [freetype.Face(f) for f in args.fontstack]
|
|
||||||
compress = args.compress
|
|
||||||
size = args.size
|
|
||||||
font_name = args.name
|
|
||||||
|
|
||||||
# inclusive unicode code point intervals
|
# inclusive unicode code point intervals
|
||||||
# must not overlap and be in ascending order
|
# must not overlap and be in ascending order
|
||||||
|
# modify intervals here
|
||||||
|
# however if the "string" command line argument is used these are ignored
|
||||||
|
|
||||||
intervals = [
|
intervals = [
|
||||||
(32, 126),
|
(32, 126),
|
||||||
(160, 255),
|
(160, 255),
|
||||||
@ -48,12 +30,107 @@ intervals = [
|
|||||||
#(0x1F600, 0x1F680),
|
#(0x1F600, 0x1F680),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import freetype
|
||||||
|
except ImportError as error:
|
||||||
|
sys.exit("To run this script the freetype module needs to be installed.\nThis can be done using:\npip install freetype-py")
|
||||||
|
import zlib
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import math
|
||||||
|
import argparse
|
||||||
|
from collections import namedtuple
|
||||||
|
#see https://freetype-py.readthedocs.io/en/latest/ for documentation
|
||||||
|
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
|
||||||
|
parser.add_argument("name", action="store", help="name of the font.")
|
||||||
|
parser.add_argument("size", type=int, help="font size to use.")
|
||||||
|
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority. This is not actually implemented please just use one file for now.")
|
||||||
|
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
|
||||||
|
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
|
||||||
|
parser.add_argument("--string", action="store", help="A string of all required characters. intervals are made up of this" )
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
command_line = ""
|
||||||
|
prev_arg = ""
|
||||||
|
for arg in sys.argv:
|
||||||
|
# ~ if prev_arg == "--string":
|
||||||
|
# ~ command_line = command_line + " '" + arg +"'"
|
||||||
|
# ~ else:
|
||||||
|
command_line = command_line + " " + arg
|
||||||
|
# ~ prev_arg = arg
|
||||||
|
|
||||||
|
# ~ print (command_line)
|
||||||
|
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
|
||||||
|
|
||||||
|
font_stack = [freetype.Face(f) for f in args.fontstack]
|
||||||
|
font_files = args.fontstack
|
||||||
|
face_index = 0
|
||||||
|
font_file = font_files[face_index]
|
||||||
|
compress = args.compress
|
||||||
|
size = args.size
|
||||||
|
font_name = args.name
|
||||||
|
|
||||||
|
for face in font_stack:
|
||||||
|
# shift by 6 bytes, because sizes are given as 6-bit fractions
|
||||||
|
# the display has about 150 dpi.
|
||||||
|
face.set_char_size(size << 6, size << 6, 150, 150)
|
||||||
|
|
||||||
|
|
||||||
|
# assign intervals from argument parrameters ie. handle the string arg
|
||||||
|
|
||||||
|
if args.string != None:
|
||||||
|
font_file = font_files[face_index]
|
||||||
|
string = " " + args.string # always add space to the string it is easily forgotten
|
||||||
|
chars = sorted(set(string))
|
||||||
|
#make array of code pointscode_ponts.append(ord(char))
|
||||||
|
code_points = list()
|
||||||
|
intervals = [] # empty the intevals array NB. if you want to allways add default characters comment out this line
|
||||||
|
# go through the sorted characters and make the intervals
|
||||||
|
for char in chars:
|
||||||
|
if( face.get_char_index(ord(char)) != 0 ):
|
||||||
|
# this character is in the font file so add it to the new string.
|
||||||
|
code_points.append(ord(char))
|
||||||
|
else:
|
||||||
|
print("The character ", char, " is not available in ", font_file, file=sys.stderr)
|
||||||
|
lower = code_points[0]
|
||||||
|
len_x = len(code_points)
|
||||||
|
x = 0
|
||||||
|
while x < len_x:
|
||||||
|
# ~ print ("loop value x = ", x , file=sys.stderr)
|
||||||
|
a = code_points[x];
|
||||||
|
b = a;
|
||||||
|
if( x < len_x - 1):
|
||||||
|
b = code_points[x + 1];
|
||||||
|
|
||||||
|
if( a == b - 1 ):
|
||||||
|
# ~ print("sequential", a, b, file=sys.stderr)
|
||||||
|
if( lower == -1):
|
||||||
|
lower = a
|
||||||
|
else:
|
||||||
|
# ~ print("non sequential", a, b , file=sys.stderr)
|
||||||
|
if( lower == -1):
|
||||||
|
# ~ print("single character")
|
||||||
|
interval = (a , a)
|
||||||
|
else:
|
||||||
|
interval = (lower, a)
|
||||||
|
# ~ print("interval", interval , file=sys.stderr)
|
||||||
|
intervals.append(interval)
|
||||||
|
lower = -1
|
||||||
|
x = x + 1
|
||||||
|
|
||||||
|
|
||||||
|
# base intervals are assigned dditional intervals from arguments
|
||||||
add_ints = []
|
add_ints = []
|
||||||
if args.additional_intervals:
|
if args.additional_intervals != None:
|
||||||
add_ints = [tuple([int(n, base=0) for n in i.split(",")]) for i in args.additional_intervals]
|
add_ints = [tuple([int(n, base=0) for n in i.split(",")]) for i in args.additional_intervals]
|
||||||
|
|
||||||
intervals = sorted(intervals + add_ints)
|
intervals = sorted(intervals + add_ints)
|
||||||
|
|
||||||
|
# ~ print("Intervals are now: ", intervals, file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def norm_floor(val):
|
def norm_floor(val):
|
||||||
return int(math.floor(val / (1 << 6)))
|
return int(math.floor(val / (1 << 6)))
|
||||||
|
|
||||||
@ -73,21 +150,48 @@ total_size = 0
|
|||||||
total_packed = 0
|
total_packed = 0
|
||||||
all_glyphs = []
|
all_glyphs = []
|
||||||
|
|
||||||
|
# new globals
|
||||||
|
total_chars = 0
|
||||||
|
ascender = 0
|
||||||
|
descender = 100
|
||||||
|
f_height = 0
|
||||||
|
|
||||||
def load_glyph(code_point):
|
def load_glyph(code_point):
|
||||||
|
global face_index
|
||||||
face_index = 0
|
face_index = 0
|
||||||
while face_index < len(font_stack):
|
while face_index < len(font_stack):
|
||||||
face = font_stack[face_index]
|
face = font_stack[face_index]
|
||||||
glyph_index = face.get_char_index(code_point)
|
glyph_index = face.get_char_index(code_point)
|
||||||
if glyph_index > 0:
|
if glyph_index > 0:
|
||||||
face.load_glyph(glyph_index, freetype.FT_LOAD_RENDER)
|
face.load_glyph(glyph_index, freetype.FT_LOAD_RENDER)
|
||||||
|
#count characters found and find bounds of characters
|
||||||
|
global ascender
|
||||||
|
if ascender < face.size.ascender:
|
||||||
|
ascender = face.size.ascender
|
||||||
|
global descender
|
||||||
|
if descender > face.size.descender:
|
||||||
|
descender = face.size.descender
|
||||||
|
global f_height
|
||||||
|
if f_height < face.size.height:
|
||||||
|
f_height = face.size.height
|
||||||
|
global total_chars
|
||||||
|
total_chars += 1
|
||||||
return face
|
return face
|
||||||
break
|
break
|
||||||
face_index += 1
|
face_index += 1
|
||||||
|
# this needs work
|
||||||
|
# this needs to be handled better to show failed character and continue not just die a questionable death
|
||||||
|
# this appears to have been designed to combine several font files
|
||||||
|
# but that is not clear to the end user and this then looks like a bug
|
||||||
print (f"falling back to font {face_index} for {chr(code_point)}.", file=sys.stderr)
|
print (f"falling back to font {face_index} for {chr(code_point)}.", file=sys.stderr)
|
||||||
raise ValueError(f"code point {code_point} not found in font stack!")
|
raise ValueError(f"code point {code_point} not found in font stack!")
|
||||||
|
|
||||||
for i_start, i_end in intervals:
|
for i_start, i_end in intervals:
|
||||||
for code_point in range(i_start, i_end + 1):
|
for code_point in range(i_start, i_end + 1):
|
||||||
|
# handle missing characters in font file
|
||||||
|
if( face.get_char_index(code_point) == 0 ):
|
||||||
|
print("Character ", chr(code_point), "(", code_point, ") is not in ", font_file, file=sys.stderr)
|
||||||
|
continue
|
||||||
face = load_glyph(code_point)
|
face = load_glyph(code_point)
|
||||||
bitmap = face.glyph.bitmap
|
bitmap = face.glyph.bitmap
|
||||||
pixels = []
|
pixels = []
|
||||||
@ -126,7 +230,8 @@ for i_start, i_end in intervals:
|
|||||||
all_glyphs.append((glyph, compressed))
|
all_glyphs.append((glyph, compressed))
|
||||||
|
|
||||||
# pipe seems to be a good heuristic for the "real" descender
|
# pipe seems to be a good heuristic for the "real" descender
|
||||||
face = load_glyph(ord('|'))
|
# face = load_glyph(ord('|'))
|
||||||
|
# removed as max descender and assender are handled above
|
||||||
|
|
||||||
glyph_data = []
|
glyph_data = []
|
||||||
glyph_props = []
|
glyph_props = []
|
||||||
@ -134,23 +239,38 @@ for index, glyph in enumerate(all_glyphs):
|
|||||||
props, compressed = glyph
|
props, compressed = glyph
|
||||||
glyph_data.extend([b for b in compressed])
|
glyph_data.extend([b for b in compressed])
|
||||||
glyph_props.append(props)
|
glyph_props.append(props)
|
||||||
|
print("", file=sys.stderr)
|
||||||
|
print(f"Original font file {font_file} as {font_name} using {total_chars} characters", file=sys.stderr)
|
||||||
|
|
||||||
print("total", total_packed, file=sys.stderr)
|
print("total", total_packed, file=sys.stderr)
|
||||||
print("compressed", total_size, file=sys.stderr)
|
print("compressed", total_size, file=sys.stderr)
|
||||||
|
|
||||||
print("#pragma once")
|
print("#pragma once")
|
||||||
print("#include \"epd_driver.h\"")
|
print("#include \"epdiy.h\"")
|
||||||
print(f"const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
|
||||||
|
# add font file origin and characters at the head of the output file
|
||||||
|
print("/*")
|
||||||
|
print ( "Created with")
|
||||||
|
print(command_line)
|
||||||
|
print(f"As '{font_name}' with available {total_chars} characters")
|
||||||
|
for i, g in enumerate(glyph_props):
|
||||||
|
print (f"{chr(g.code_point)}", end ="" )
|
||||||
|
print("")
|
||||||
|
print("*/")
|
||||||
|
|
||||||
|
print(f"const uint8_t {font_name}_Bitmaps[{len(glyph_data)}] = {{")
|
||||||
for c in chunks(glyph_data, 16):
|
for c in chunks(glyph_data, 16):
|
||||||
print (" " + " ".join(f"0x{b:02X}," for b in c))
|
print (" " + " ".join(f"0x{b:02X}," for b in c))
|
||||||
print ("};");
|
print ("};");
|
||||||
|
|
||||||
print(f"const EpdGlyph {font_name}Glyphs[] = {{")
|
|
||||||
|
print ('// GlyphProps[width, height, advance_x, left, top, compressed_size, data_offset, code_point]')
|
||||||
|
print(f"const EpdGlyph {font_name}_Glyphs[] = {{")
|
||||||
for i, g in enumerate(glyph_props):
|
for i, g in enumerate(glyph_props):
|
||||||
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// {chr(g.code_point) if g.code_point != 92 else '<backslash>'}")
|
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// '{chr(g.code_point) if g.code_point != 92 else '<backslash>'}'")
|
||||||
print ("};");
|
print ("};");
|
||||||
|
|
||||||
print(f"const EpdUnicodeInterval {font_name}Intervals[] = {{")
|
print(f"const EpdUnicodeInterval {font_name}_Intervals[] = {{")
|
||||||
offset = 0
|
offset = 0
|
||||||
for i_start, i_end in intervals:
|
for i_start, i_end in intervals:
|
||||||
print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},")
|
print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},")
|
||||||
@ -158,12 +278,22 @@ for i_start, i_end in intervals:
|
|||||||
print ("};");
|
print ("};");
|
||||||
|
|
||||||
print(f"const EpdFont {font_name} = {{")
|
print(f"const EpdFont {font_name} = {{")
|
||||||
print(f" {font_name}Bitmaps,")
|
print(f" {font_name}_Bitmaps, // (*bitmap) Glyph bitmap pointer, all concatenated together")
|
||||||
print(f" {font_name}Glyphs,")
|
print(f" {font_name}_Glyphs, // glyphs Glyph array")
|
||||||
print(f" {font_name}Intervals,")
|
print(f" {font_name}_Intervals, // intervals Valid unicode intervals for this font")
|
||||||
print(f" {len(intervals)},")
|
print(f" {len(intervals)}, // interval_count Number of unicode intervals.intervals")
|
||||||
print(f" {1 if compress else 0},")
|
print(f" {1 if compress else 0}, // compressed Does this font use compressed glyph bitmaps?")
|
||||||
print(f" {norm_ceil(face.size.height)},")
|
print(f" {norm_ceil(f_height)}, // advance_y Newline distance (y axis)")
|
||||||
print(f" {norm_ceil(face.size.ascender)},")
|
print(f" {norm_ceil(ascender)}, // ascender Maximal height of a glyph above the base line")
|
||||||
print(f" {norm_floor(face.size.descender)},")
|
print(f" {norm_floor(descender)}, // descender Maximal height of a glyph below the base line")
|
||||||
print("};")
|
print("};")
|
||||||
|
print("/*")
|
||||||
|
print("Included intervals")
|
||||||
|
for i_start, i_end in intervals:
|
||||||
|
print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'")
|
||||||
|
print("Included intervals", file=sys.stderr)
|
||||||
|
for i_start, i_end in intervals:
|
||||||
|
print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'", file=sys.stderr)
|
||||||
|
print("")
|
||||||
|
print("*/")
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ args = parser.parse_args()
|
|||||||
im = Image.open(args.inputfile)
|
im = Image.open(args.inputfile)
|
||||||
# convert to grayscale
|
# convert to grayscale
|
||||||
im = im.convert(mode='L')
|
im = im.convert(mode='L')
|
||||||
im.thumbnail((args.max_width, args.max_height), Image.ANTIALIAS)
|
im.thumbnail((args.max_width, args.max_height), Image.LANCZOS)
|
||||||
|
|
||||||
# Write out the output file.
|
# Write out the output file.
|
||||||
with open(args.outputfile, 'w') as f:
|
with open(args.outputfile, 'w') as f:
|
||||||
|
|||||||
@ -18,24 +18,36 @@ waveforms = json.load(sys.stdin);
|
|||||||
|
|
||||||
total_size = 0
|
total_size = 0
|
||||||
|
|
||||||
def phase_to_c(phase):
|
def phase_to_c(phase,bits_per_pixel_c=4):
|
||||||
"""
|
|
||||||
Convert a 5 bit phase to a 4 bit C LUT.
|
N1 = len(phase)
|
||||||
"""
|
N2 = len(phase[0])
|
||||||
global total_size
|
N = 2**bits_per_pixel_c
|
||||||
|
|
||||||
|
if N1%N != 0:
|
||||||
|
raise ValueError(f"first dimension of phases is {N1}. Allowed are multiples of {N}")
|
||||||
|
|
||||||
|
step1 = int(N1/N)
|
||||||
|
|
||||||
|
if N2%N != 0:
|
||||||
|
raise ValueError(f"second dimension of phases is {N2}. Allowed are multiples of {N}")
|
||||||
|
|
||||||
|
step2 = int(N2/N)
|
||||||
|
|
||||||
targets = []
|
targets = []
|
||||||
for t in range(0, 32, 2):
|
for t in range(0, N1, step1):
|
||||||
chunk = 0
|
chunk = 0
|
||||||
line = []
|
line = []
|
||||||
for f in range(0, 32, 2):
|
i = 0
|
||||||
|
for f in range(0, N2, step2):
|
||||||
fr = phase[t][f]
|
fr = phase[t][f]
|
||||||
chunk = (chunk << 2) | fr
|
chunk = (chunk << 2) | fr
|
||||||
if f and f % 8 == 6:
|
i += 1
|
||||||
|
if i == 4:
|
||||||
|
i = 0
|
||||||
line.append(chunk)
|
line.append(chunk)
|
||||||
chunk = 0
|
chunk = 0
|
||||||
targets.append(line)
|
targets.append(line)
|
||||||
total_size += len(line)
|
|
||||||
|
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@ -61,7 +73,7 @@ if args.temperature_range:
|
|||||||
|
|
||||||
modes = []
|
modes = []
|
||||||
|
|
||||||
mode_filter = list(range(len(waveforms["modes"])))
|
mode_filter = [wm["mode"] for wm in waveforms["modes"]]
|
||||||
|
|
||||||
if args.export_modes:
|
if args.export_modes:
|
||||||
mode_filter = list(map(int, args.export_modes.split(",")))
|
mode_filter = list(map(int, args.export_modes.split(",")))
|
||||||
@ -72,6 +84,8 @@ num_modes = len(mode_filter)
|
|||||||
|
|
||||||
temp_intervals = []
|
temp_intervals = []
|
||||||
for bounds in waveforms["temperature_ranges"]["range_bounds"]:
|
for bounds in waveforms["temperature_ranges"]["range_bounds"]:
|
||||||
|
if bounds["to"] < tmin or bounds["from"] > tmax:
|
||||||
|
continue
|
||||||
temp_intervals.append(f"{{ .min = {bounds['from']}, .max = {bounds['to']} }}")
|
temp_intervals.append(f"{{ .min = {bounds['from']}, .max = {bounds['to']} }}")
|
||||||
|
|
||||||
modes = []
|
modes = []
|
||||||
@ -84,7 +98,7 @@ for m_index, mode in enumerate(waveforms["modes"]):
|
|||||||
ranges = []
|
ranges = []
|
||||||
for i, r in enumerate(mode["ranges"]):
|
for i, r in enumerate(mode["ranges"]):
|
||||||
bounds = waveforms["temperature_ranges"]["range_bounds"][i]
|
bounds = waveforms["temperature_ranges"]["range_bounds"][i]
|
||||||
if bounds["from"] < tmin or bounds["from"] > tmax:
|
if bounds["to"] < tmin or bounds["from"] > tmax:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
phases = []
|
phases = []
|
||||||
|
|||||||
0
lib/libesp32_eink/epdiy/src/epd_driver/Makefile → lib/libesp32_eink/epdiy/src/Makefile
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/Makefile → lib/libesp32_eink/epdiy/src/Makefile
Executable file → Normal file
77
lib/libesp32_eink/epdiy/src/board/epd_board.c
Normal file
77
lib/libesp32_eink/epdiy/src/board/epd_board.c
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include "epd_board.h"
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The board's display control pin state.
|
||||||
|
*/
|
||||||
|
static epd_ctrl_state_t ctrl_state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EPDIY board in use.
|
||||||
|
*/
|
||||||
|
const EpdBoardDefinition* epd_board = NULL;
|
||||||
|
|
||||||
|
void IRAM_ATTR epd_busy_delay(uint32_t cycles) {
|
||||||
|
volatile unsigned long counts = XTHAL_GET_CCOUNT() + cycles;
|
||||||
|
while (XTHAL_GET_CCOUNT() < counts) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_set_board(const EpdBoardDefinition* board_definition) {
|
||||||
|
if (epd_board == NULL) {
|
||||||
|
epd_board = board_definition;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW("epdiy", "EPD board can only be set once!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition* epd_current_board() {
|
||||||
|
return epd_board;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_set_mode(bool state) {
|
||||||
|
ctrl_state.ep_output_enable = state;
|
||||||
|
ctrl_state.ep_mode = state;
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
};
|
||||||
|
epd_board->set_ctrl(&ctrl_state, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
epd_ctrl_state_t* epd_ctrl_state() {
|
||||||
|
return &ctrl_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_control_reg_init() {
|
||||||
|
ctrl_state.ep_latch_enable = false;
|
||||||
|
ctrl_state.ep_output_enable = false;
|
||||||
|
ctrl_state.ep_sth = true;
|
||||||
|
ctrl_state.ep_mode = false;
|
||||||
|
ctrl_state.ep_stv = true;
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_latch_enable = true,
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_sth = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd_board->set_ctrl(&ctrl_state, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_control_reg_deinit() {
|
||||||
|
ctrl_state.ep_output_enable = false;
|
||||||
|
ctrl_state.ep_mode = false;
|
||||||
|
ctrl_state.ep_stv = false;
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
epd_board->set_ctrl(&ctrl_state, &mask);
|
||||||
|
}
|
||||||
34
lib/libesp32_eink/epdiy/src/board/epd_board_common.c
Normal file
34
lib/libesp32_eink/epdiy/src/board/epd_board_common.c
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "driver/adc.h"
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "esp_adc_cal.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const adc1_channel_t channel = ADC1_CHANNEL_7;
|
||||||
|
static esp_adc_cal_characteristics_t adc_chars;
|
||||||
|
|
||||||
|
#define NUMBER_OF_SAMPLES 100
|
||||||
|
|
||||||
|
void epd_board_temperature_init_v2() {
|
||||||
|
esp_adc_cal_value_t val_type
|
||||||
|
= esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, &adc_chars);
|
||||||
|
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
|
||||||
|
ESP_LOGI("epd_temperature", "Characterized using Two Point Value\n");
|
||||||
|
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
|
||||||
|
ESP_LOGI("esp_temperature", "Characterized using eFuse Vref\n");
|
||||||
|
} else {
|
||||||
|
ESP_LOGI("esp_temperature", "Characterized using Default Vref\n");
|
||||||
|
}
|
||||||
|
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||||
|
adc1_config_channel_atten(channel, ADC_ATTEN_DB_6);
|
||||||
|
}
|
||||||
|
|
||||||
|
float epd_board_ambient_temperature_v2() {
|
||||||
|
uint32_t value = 0;
|
||||||
|
for (int i = 0; i < NUMBER_OF_SAMPLES; i++) {
|
||||||
|
value += adc1_get_raw(channel);
|
||||||
|
}
|
||||||
|
value /= NUMBER_OF_SAMPLES;
|
||||||
|
// voltage in mV
|
||||||
|
float voltage = esp_adc_cal_raw_to_voltage(value, &adc_chars);
|
||||||
|
return (voltage - 500.0) / 10.0;
|
||||||
|
}
|
||||||
8
lib/libesp32_eink/epdiy/src/board/epd_board_common.h
Normal file
8
lib/libesp32_eink/epdiy/src/board/epd_board_common.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @file "epd_board_common.h"
|
||||||
|
* @brief Common board functions shared between boards.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void epd_board_temperature_init_v2();
|
||||||
|
float epd_board_ambient_temperature_v2();
|
||||||
227
lib/libesp32_eink/epdiy/src/board/epd_board_lilygo_t5_47.c
Normal file
227
lib/libesp32_eink/epdiy/src/board/epd_board_lilygo_t5_47.c
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#include "epd_board.h"
|
||||||
|
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#include "../output_i2s/i2s_data_bus.h"
|
||||||
|
#include "../output_i2s/render_i2s.h"
|
||||||
|
#include "../output_i2s/rmt_pulse.h"
|
||||||
|
|
||||||
|
// Make this compile on the S3 to avoid long ifdefs
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define GPIO_NUM_22 0
|
||||||
|
#define GPIO_NUM_23 0
|
||||||
|
#define GPIO_NUM_24 0
|
||||||
|
#define GPIO_NUM_25 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_DATA GPIO_NUM_23
|
||||||
|
#define CFG_CLK GPIO_NUM_18
|
||||||
|
#define CFG_STR GPIO_NUM_0
|
||||||
|
#define D7 GPIO_NUM_22
|
||||||
|
#define D6 GPIO_NUM_21
|
||||||
|
#define D5 GPIO_NUM_27
|
||||||
|
#define D4 GPIO_NUM_2
|
||||||
|
#define D3 GPIO_NUM_19
|
||||||
|
#define D2 GPIO_NUM_4
|
||||||
|
#define D1 GPIO_NUM_32
|
||||||
|
#define D0 GPIO_NUM_33
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_25
|
||||||
|
#define STH GPIO_NUM_26
|
||||||
|
|
||||||
|
#define V4_LATCH_ENABLE GPIO_NUM_15
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_5
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool power_disable : 1;
|
||||||
|
bool pos_power_enable : 1;
|
||||||
|
bool neg_power_enable : 1;
|
||||||
|
bool ep_scan_direction : 1;
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
static i2s_bus_config i2s_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.data_0 = D0,
|
||||||
|
.data_1 = D1,
|
||||||
|
.data_2 = D2,
|
||||||
|
.data_3 = D3,
|
||||||
|
.data_4 = D4,
|
||||||
|
.data_5 = D5,
|
||||||
|
.data_6 = D6,
|
||||||
|
.data_7 = D7,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void IRAM_ATTR push_cfg_bit(bool bit) {
|
||||||
|
gpio_set_level(CFG_CLK, 0);
|
||||||
|
gpio_set_level(CFG_DATA, bit);
|
||||||
|
gpio_set_level(CFG_CLK, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
/* Power Control Output/Off */
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_DATA], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_CLK], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_STR], PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT);
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
config_reg.neg_power_enable = false;
|
||||||
|
config_reg.ep_scan_direction = true;
|
||||||
|
|
||||||
|
// Setup I2S
|
||||||
|
// add an offset off dummy bytes to allow for enough timing headroom
|
||||||
|
i2s_bus_init(&i2s_config, epd_row_width + 32);
|
||||||
|
|
||||||
|
rmt_pulse_init(CKV);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
if (state->ep_sth) {
|
||||||
|
fast_gpio_set_hi(STH);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(STH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv || mask->ep_latch_enable) {
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
// push config bits in reverse order
|
||||||
|
push_cfg_bit(state->ep_output_enable);
|
||||||
|
push_cfg_bit(state->ep_mode);
|
||||||
|
push_cfg_bit(config_reg.ep_scan_direction);
|
||||||
|
push_cfg_bit(state->ep_stv);
|
||||||
|
|
||||||
|
push_cfg_bit(config_reg.neg_power_enable);
|
||||||
|
push_cfg_bit(config_reg.pos_power_enable);
|
||||||
|
push_cfg_bit(config_reg.power_disable);
|
||||||
|
push_cfg_bit(state->ep_latch_enable);
|
||||||
|
|
||||||
|
fast_gpio_set_hi(CFG_STR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
i2s_gpio_attach(&i2s_config);
|
||||||
|
|
||||||
|
// This was re-purposed as power enable.
|
||||||
|
config_reg.ep_scan_direction = true;
|
||||||
|
|
||||||
|
// POWERON
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.power_disable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
config_reg.neg_power_enable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.pos_power_enable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask.ep_sth = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
// END POWERON
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_powerdown_lilygo_t5_47() {
|
||||||
|
epd_ctrl_state_t* state = epd_ctrl_state();
|
||||||
|
|
||||||
|
// This was re-purposed as power enable however it also disables the touch.
|
||||||
|
// this workaround may still leave power on to epd and as such may cause other
|
||||||
|
// problems such as grey screen.
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(10 * 240);
|
||||||
|
|
||||||
|
config_reg.neg_power_enable = false;
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
|
||||||
|
state->ep_stv = false;
|
||||||
|
mask.ep_stv = true;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
i2s_gpio_detach(&i2s_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff_common(epd_ctrl_state_t* state) {
|
||||||
|
// POWEROFF
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(10 * 240);
|
||||||
|
|
||||||
|
config_reg.neg_power_enable = false;
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
|
||||||
|
state->ep_stv = false;
|
||||||
|
mask.ep_stv = true;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
i2s_gpio_detach(&i2s_config);
|
||||||
|
// END POWEROFF
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
// This was re-purposed as power enable.
|
||||||
|
config_reg.ep_scan_direction = false;
|
||||||
|
epd_board_poweroff_common(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff_touch(epd_ctrl_state_t* state) {
|
||||||
|
// This was re-purposed as power enable.
|
||||||
|
config_reg.ep_scan_direction = true;
|
||||||
|
epd_board_poweroff_common(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_lilygo_t5_47 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = NULL,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
.get_temperature = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_lilygo_t5_47_touch = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = NULL,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff_touch,
|
||||||
|
.get_temperature = NULL,
|
||||||
|
.set_vcom = NULL,
|
||||||
|
};
|
||||||
172
lib/libesp32_eink/epdiy/src/board/epd_board_v2_v3.c
Normal file
172
lib/libesp32_eink/epdiy/src/board/epd_board_v2_v3.c
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#include "epd_board.h"
|
||||||
|
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#include "../output_i2s/i2s_data_bus.h"
|
||||||
|
#include "../output_i2s/render_i2s.h"
|
||||||
|
#include "../output_i2s/rmt_pulse.h"
|
||||||
|
#include "epd_board_common.h"
|
||||||
|
|
||||||
|
// Make this compile on the S3 to avoid long ifdefs
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define GPIO_NUM_22 0
|
||||||
|
#define GPIO_NUM_23 0
|
||||||
|
#define GPIO_NUM_24 0
|
||||||
|
#define GPIO_NUM_25 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_DATA GPIO_NUM_23
|
||||||
|
#define CFG_CLK GPIO_NUM_18
|
||||||
|
#define CFG_STR GPIO_NUM_19
|
||||||
|
#define D7 GPIO_NUM_22
|
||||||
|
#define D6 GPIO_NUM_21
|
||||||
|
#define D5 GPIO_NUM_27
|
||||||
|
#define D4 GPIO_NUM_2
|
||||||
|
#define D3 GPIO_NUM_0
|
||||||
|
#define D2 GPIO_NUM_4
|
||||||
|
#define D1 GPIO_NUM_32
|
||||||
|
#define D0 GPIO_NUM_33
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_25
|
||||||
|
#define STH GPIO_NUM_26
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_5
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool power_disable : 1;
|
||||||
|
bool pos_power_enable : 1;
|
||||||
|
bool neg_power_enable : 1;
|
||||||
|
bool ep_scan_direction : 1;
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
static i2s_bus_config i2s_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.data_0 = D0,
|
||||||
|
.data_1 = D1,
|
||||||
|
.data_2 = D2,
|
||||||
|
.data_3 = D3,
|
||||||
|
.data_4 = D4,
|
||||||
|
.data_5 = D5,
|
||||||
|
.data_6 = D6,
|
||||||
|
.data_7 = D7,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void IRAM_ATTR push_cfg_bit(bool bit) {
|
||||||
|
gpio_set_level(CFG_CLK, 0);
|
||||||
|
gpio_set_level(CFG_DATA, bit);
|
||||||
|
gpio_set_level(CFG_CLK, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
/* Power Control Output/Off */
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_DATA], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_CLK], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_STR], PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT);
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
config_reg.neg_power_enable = false;
|
||||||
|
config_reg.ep_scan_direction = true;
|
||||||
|
|
||||||
|
// Setup I2S
|
||||||
|
// add an offset off dummy bytes to allow for enough timing headroom
|
||||||
|
i2s_bus_init(&i2s_config, epd_row_width + 32);
|
||||||
|
|
||||||
|
rmt_pulse_init(CKV);
|
||||||
|
|
||||||
|
epd_board_temperature_init_v2();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
if (state->ep_sth) {
|
||||||
|
fast_gpio_set_hi(STH);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(STH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv || mask->ep_latch_enable) {
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
// push config bits in reverse order
|
||||||
|
push_cfg_bit(state->ep_output_enable);
|
||||||
|
push_cfg_bit(state->ep_mode);
|
||||||
|
push_cfg_bit(config_reg.ep_scan_direction);
|
||||||
|
push_cfg_bit(state->ep_stv);
|
||||||
|
|
||||||
|
push_cfg_bit(config_reg.neg_power_enable);
|
||||||
|
push_cfg_bit(config_reg.pos_power_enable);
|
||||||
|
push_cfg_bit(config_reg.power_disable);
|
||||||
|
push_cfg_bit(state->ep_latch_enable);
|
||||||
|
|
||||||
|
fast_gpio_set_hi(CFG_STR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
// POWERON
|
||||||
|
i2s_gpio_attach(&i2s_config);
|
||||||
|
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.power_disable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
config_reg.neg_power_enable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.pos_power_enable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask.ep_sth = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
// END POWERON
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
// POWEROFF
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(10 * 240);
|
||||||
|
|
||||||
|
config_reg.neg_power_enable = false;
|
||||||
|
config_reg.pos_power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
i2s_gpio_detach(&i2s_config);
|
||||||
|
// END POWEROFF
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_v2_v3 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = NULL,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
.get_temperature = epd_board_ambient_temperature_v2,
|
||||||
|
.set_vcom = NULL,
|
||||||
|
};
|
||||||
194
lib/libesp32_eink/epdiy/src/board/epd_board_v4.c
Normal file
194
lib/libesp32_eink/epdiy/src/board/epd_board_v4.c
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#include "epd_board.h"
|
||||||
|
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#include "../output_i2s/i2s_data_bus.h"
|
||||||
|
#include "../output_i2s/render_i2s.h"
|
||||||
|
#include "../output_i2s/rmt_pulse.h"
|
||||||
|
#include "epd_board_common.h"
|
||||||
|
|
||||||
|
// Make this compile on the S3 to avoid long ifdefs
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define GPIO_NUM_22 0
|
||||||
|
#define GPIO_NUM_23 0
|
||||||
|
#define GPIO_NUM_24 0
|
||||||
|
#define GPIO_NUM_25 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_DATA GPIO_NUM_23
|
||||||
|
#define CFG_CLK GPIO_NUM_18
|
||||||
|
#define CFG_STR GPIO_NUM_19
|
||||||
|
#define D7 GPIO_NUM_22
|
||||||
|
#define D6 GPIO_NUM_21
|
||||||
|
#define D5 GPIO_NUM_27
|
||||||
|
#define D4 GPIO_NUM_2
|
||||||
|
#define D3 GPIO_NUM_0
|
||||||
|
#define D2 GPIO_NUM_4
|
||||||
|
#define D1 GPIO_NUM_32
|
||||||
|
#define D0 GPIO_NUM_33
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_25
|
||||||
|
#define STH GPIO_NUM_26
|
||||||
|
|
||||||
|
#define V4_LATCH_ENABLE GPIO_NUM_15
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_5
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool power_disable : 1;
|
||||||
|
bool power_enable_vpos : 1;
|
||||||
|
bool power_enable_vneg : 1;
|
||||||
|
bool power_enable_gl : 1;
|
||||||
|
bool power_enable_gh : 1;
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
static i2s_bus_config i2s_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.data_0 = D0,
|
||||||
|
.data_1 = D1,
|
||||||
|
.data_2 = D2,
|
||||||
|
.data_3 = D3,
|
||||||
|
.data_4 = D4,
|
||||||
|
.data_5 = D5,
|
||||||
|
.data_6 = D6,
|
||||||
|
.data_7 = D7,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void IRAM_ATTR push_cfg_bit(bool bit) {
|
||||||
|
gpio_set_level(CFG_CLK, 0);
|
||||||
|
gpio_set_level(CFG_DATA, bit);
|
||||||
|
gpio_set_level(CFG_CLK, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
/* Power Control Output/Off */
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_DATA], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_CLK], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_STR], PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT);
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
config_reg.power_enable_vpos = false;
|
||||||
|
config_reg.power_enable_vneg = false;
|
||||||
|
config_reg.power_enable_gl = false;
|
||||||
|
config_reg.power_enable_gh = false;
|
||||||
|
|
||||||
|
// use latch pin as GPIO
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[V4_LATCH_ENABLE], PIN_FUNC_GPIO);
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(V4_LATCH_ENABLE, GPIO_MODE_OUTPUT));
|
||||||
|
gpio_set_level(V4_LATCH_ENABLE, 0);
|
||||||
|
|
||||||
|
// Setup I2S
|
||||||
|
// add an offset off dummy bytes to allow for enough timing headroom
|
||||||
|
i2s_bus_init(&i2s_config, epd_row_width + 32);
|
||||||
|
|
||||||
|
rmt_pulse_init(CKV);
|
||||||
|
|
||||||
|
epd_board_temperature_init_v2();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
if (state->ep_sth) {
|
||||||
|
fast_gpio_set_hi(STH);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(STH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv) {
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
// push config bits in reverse order
|
||||||
|
push_cfg_bit(state->ep_output_enable);
|
||||||
|
push_cfg_bit(state->ep_mode);
|
||||||
|
push_cfg_bit(config_reg.power_enable_gh);
|
||||||
|
push_cfg_bit(state->ep_stv);
|
||||||
|
|
||||||
|
push_cfg_bit(config_reg.power_enable_gl);
|
||||||
|
push_cfg_bit(config_reg.power_enable_vneg);
|
||||||
|
push_cfg_bit(config_reg.power_enable_vpos);
|
||||||
|
push_cfg_bit(config_reg.power_disable);
|
||||||
|
|
||||||
|
fast_gpio_set_hi(CFG_STR);
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->ep_latch_enable) {
|
||||||
|
fast_gpio_set_hi(V4_LATCH_ENABLE);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(V4_LATCH_ENABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
// POWERON
|
||||||
|
i2s_gpio_attach(&i2s_config);
|
||||||
|
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.power_disable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
config_reg.power_enable_gl = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.power_enable_vneg = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.power_enable_gh = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.power_enable_vpos = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask.ep_sth = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
// END POWERON
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
// POWEROFF
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.power_enable_gh = false;
|
||||||
|
config_reg.power_enable_vpos = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(10 * 240);
|
||||||
|
config_reg.power_enable_gl = false;
|
||||||
|
config_reg.power_enable_vneg = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
config_reg.power_disable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
i2s_gpio_detach(&i2s_config);
|
||||||
|
// END POWEROFF
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_v4 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = NULL,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
.get_temperature = epd_board_ambient_temperature_v2,
|
||||||
|
.set_vcom = NULL,
|
||||||
|
};
|
||||||
196
lib/libesp32_eink/epdiy/src/board/epd_board_v5.c
Normal file
196
lib/libesp32_eink/epdiy/src/board/epd_board_v5.c
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
#include "epd_board.h"
|
||||||
|
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#include "epd_board_common.h"
|
||||||
|
|
||||||
|
#include "../output_i2s/i2s_data_bus.h"
|
||||||
|
#include "../output_i2s/render_i2s.h"
|
||||||
|
#include "../output_i2s/rmt_pulse.h"
|
||||||
|
#include "driver/rtc_io.h"
|
||||||
|
|
||||||
|
// Make this compile on the S3 to avoid long ifdefs
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define GPIO_NUM_22 0
|
||||||
|
#define GPIO_NUM_23 0
|
||||||
|
#define GPIO_NUM_24 0
|
||||||
|
#define GPIO_NUM_25 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_DATA GPIO_NUM_33
|
||||||
|
#define CFG_CLK GPIO_NUM_32
|
||||||
|
#define CFG_STR GPIO_NUM_0
|
||||||
|
#define D7 GPIO_NUM_23
|
||||||
|
#define D6 GPIO_NUM_22
|
||||||
|
#define D5 GPIO_NUM_21
|
||||||
|
#define D4 GPIO_NUM_19
|
||||||
|
#define D3 GPIO_NUM_18
|
||||||
|
#define D2 GPIO_NUM_5
|
||||||
|
#define D1 GPIO_NUM_4
|
||||||
|
#define D0 GPIO_NUM_25
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_26
|
||||||
|
#define STH GPIO_NUM_27
|
||||||
|
|
||||||
|
#define V4_LATCH_ENABLE GPIO_NUM_2
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_15
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool power_enable : 1;
|
||||||
|
bool power_enable_vpos : 1;
|
||||||
|
bool power_enable_vneg : 1;
|
||||||
|
bool power_enable_gl : 1;
|
||||||
|
bool power_enable_gh : 1;
|
||||||
|
} epd_config_register_t;
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static i2s_bus_config i2s_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.data_0 = D0,
|
||||||
|
.data_1 = D1,
|
||||||
|
.data_2 = D2,
|
||||||
|
.data_3 = D3,
|
||||||
|
.data_4 = D4,
|
||||||
|
.data_5 = D5,
|
||||||
|
.data_6 = D6,
|
||||||
|
.data_7 = D7,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void IRAM_ATTR push_cfg_bit(bool bit) {
|
||||||
|
gpio_set_level(CFG_CLK, 0);
|
||||||
|
gpio_set_level(CFG_DATA, bit);
|
||||||
|
gpio_set_level(CFG_CLK, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
/* Power Control Output/Off */
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_DATA], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_CLK], PIN_FUNC_GPIO);
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_STR], PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT);
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
config_reg.power_enable = false;
|
||||||
|
config_reg.power_enable_vpos = false;
|
||||||
|
config_reg.power_enable_vneg = false;
|
||||||
|
config_reg.power_enable_gl = false;
|
||||||
|
config_reg.power_enable_gh = false;
|
||||||
|
|
||||||
|
// use latch pin as GPIO
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[V4_LATCH_ENABLE], PIN_FUNC_GPIO);
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(V4_LATCH_ENABLE, GPIO_MODE_OUTPUT));
|
||||||
|
gpio_set_level(V4_LATCH_ENABLE, 0);
|
||||||
|
|
||||||
|
// Setup I2S
|
||||||
|
// add an offset off dummy bytes to allow for enough timing headroom
|
||||||
|
i2s_bus_init(&i2s_config, epd_row_width + 32);
|
||||||
|
|
||||||
|
rmt_pulse_init(CKV);
|
||||||
|
|
||||||
|
epd_board_temperature_init_v2();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
if (state->ep_sth) {
|
||||||
|
fast_gpio_set_hi(STH);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(STH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv) {
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
|
||||||
|
// push config bits in reverse order
|
||||||
|
push_cfg_bit(state->ep_output_enable);
|
||||||
|
push_cfg_bit(state->ep_mode);
|
||||||
|
push_cfg_bit(config_reg.power_enable_gh);
|
||||||
|
push_cfg_bit(state->ep_stv);
|
||||||
|
|
||||||
|
push_cfg_bit(config_reg.power_enable_gl);
|
||||||
|
push_cfg_bit(config_reg.power_enable_vneg);
|
||||||
|
push_cfg_bit(config_reg.power_enable_vpos);
|
||||||
|
push_cfg_bit(config_reg.power_enable);
|
||||||
|
|
||||||
|
fast_gpio_set_hi(CFG_STR);
|
||||||
|
fast_gpio_set_lo(CFG_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->ep_latch_enable) {
|
||||||
|
fast_gpio_set_hi(V4_LATCH_ENABLE);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(V4_LATCH_ENABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
// POWERON
|
||||||
|
i2s_gpio_attach(&i2s_config);
|
||||||
|
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.power_enable = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
config_reg.power_enable_gl = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.power_enable_vneg = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.power_enable_gh = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(500 * 240);
|
||||||
|
config_reg.power_enable_vpos = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask.ep_sth = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
// END POWERON
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
// POWEROFF
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
// Trigger output to shift register
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
config_reg.power_enable_gh = false;
|
||||||
|
config_reg.power_enable_vpos = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(10 * 240);
|
||||||
|
config_reg.power_enable_gl = false;
|
||||||
|
config_reg.power_enable_vneg = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
epd_busy_delay(100 * 240);
|
||||||
|
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
config_reg.power_enable = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
i2s_gpio_detach(&i2s_config);
|
||||||
|
// END POWEROFF
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_v5 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = NULL,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
.get_temperature = epd_board_ambient_temperature_v2,
|
||||||
|
.set_vcom = NULL,
|
||||||
|
};
|
||||||
359
lib/libesp32_eink/epdiy/src/board/epd_board_v6.c
Normal file
359
lib/libesp32_eink/epdiy/src/board/epd_board_v6.c
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "epd_board.h"
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "../output_i2s/i2s_data_bus.h"
|
||||||
|
#include "../output_i2s/render_i2s.h"
|
||||||
|
#include "../output_i2s/rmt_pulse.h"
|
||||||
|
#include "pca9555.h"
|
||||||
|
#include "tps65185.h"
|
||||||
|
|
||||||
|
// Make this compile on the S3 to avoid long ifdefs
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define GPIO_NUM_22 0
|
||||||
|
#define GPIO_NUM_23 0
|
||||||
|
#define GPIO_NUM_24 0
|
||||||
|
#define GPIO_NUM_25 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_SCL GPIO_NUM_33
|
||||||
|
#define CFG_SDA GPIO_NUM_32
|
||||||
|
#define CFG_INTR GPIO_NUM_35
|
||||||
|
#define EPDIY_I2C_PORT I2C_NUM_0
|
||||||
|
#define CFG_PIN_OE (PCA_PIN_PC10 >> 8)
|
||||||
|
#define CFG_PIN_MODE (PCA_PIN_PC11 >> 8)
|
||||||
|
#define CFG_PIN_STV (PCA_PIN_PC12 >> 8)
|
||||||
|
#define CFG_PIN_PWRUP (PCA_PIN_PC13 >> 8)
|
||||||
|
#define CFG_PIN_VCOM_CTRL (PCA_PIN_PC14 >> 8)
|
||||||
|
#define CFG_PIN_WAKEUP (PCA_PIN_PC15 >> 8)
|
||||||
|
#define CFG_PIN_PWRGOOD (PCA_PIN_PC16 >> 8)
|
||||||
|
#define CFG_PIN_INT (PCA_PIN_PC17 >> 8)
|
||||||
|
#define D7 GPIO_NUM_23
|
||||||
|
#define D6 GPIO_NUM_22
|
||||||
|
#define D5 GPIO_NUM_21
|
||||||
|
#define D4 GPIO_NUM_19
|
||||||
|
#define D3 GPIO_NUM_18
|
||||||
|
#define D2 GPIO_NUM_5
|
||||||
|
#define D1 GPIO_NUM_4
|
||||||
|
#define D0 GPIO_NUM_25
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_26
|
||||||
|
#define STH GPIO_NUM_27
|
||||||
|
|
||||||
|
#define V4_LATCH_ENABLE GPIO_NUM_2
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_15
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
i2c_port_t port;
|
||||||
|
bool pwrup;
|
||||||
|
bool vcom_ctrl;
|
||||||
|
bool wakeup;
|
||||||
|
bool others[8];
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
static i2s_bus_config i2s_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.data_0 = D0,
|
||||||
|
.data_1 = D1,
|
||||||
|
.data_2 = D2,
|
||||||
|
.data_3 = D3,
|
||||||
|
.data_4 = D4,
|
||||||
|
.data_5 = D5,
|
||||||
|
.data_6 = D6,
|
||||||
|
.data_7 = D7,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The VCOM voltage to use. */
|
||||||
|
static int vcom = 1600;
|
||||||
|
|
||||||
|
static bool interrupt_done = false;
|
||||||
|
|
||||||
|
static void IRAM_ATTR interrupt_handler(void* arg) {
|
||||||
|
interrupt_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
gpio_hold_dis(CKH); // free CKH after wakeup
|
||||||
|
|
||||||
|
i2c_config_t conf;
|
||||||
|
conf.mode = I2C_MODE_MASTER;
|
||||||
|
conf.sda_io_num = CFG_SDA;
|
||||||
|
conf.scl_io_num = CFG_SCL;
|
||||||
|
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.master.clk_speed = 400000;
|
||||||
|
conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL;
|
||||||
|
ESP_ERROR_CHECK(i2c_param_config(EPDIY_I2C_PORT, &conf));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_driver_install(EPDIY_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0));
|
||||||
|
|
||||||
|
config_reg.port = EPDIY_I2C_PORT;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
config_reg.others[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_set_direction(CFG_INTR, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_intr_type(CFG_INTR, GPIO_INTR_NEGEDGE);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_EDGE));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CFG_INTR, interrupt_handler, (void*)CFG_INTR));
|
||||||
|
|
||||||
|
// set all epdiy lines to output except TPS interrupt + PWR good
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_config(config_reg.port, CFG_PIN_PWRGOOD | CFG_PIN_INT, 1));
|
||||||
|
|
||||||
|
// use latch pin as GPIO
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[V4_LATCH_ENABLE], PIN_FUNC_GPIO);
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(V4_LATCH_ENABLE, GPIO_MODE_OUTPUT));
|
||||||
|
gpio_set_level(V4_LATCH_ENABLE, 0);
|
||||||
|
|
||||||
|
// Setup I2S
|
||||||
|
// add an offset off dummy bytes to allow for enough timing headroom
|
||||||
|
i2s_bus_init(&i2s_config, epd_row_width + 32);
|
||||||
|
|
||||||
|
rmt_pulse_init(CKV);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_deinit() {
|
||||||
|
// gpio_reset_pin(CFG_INTR);
|
||||||
|
// rtc_gpio_isolate(CFG_INTR);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_config(
|
||||||
|
config_reg.port, CFG_PIN_PWRGOOD | CFG_PIN_INT | CFG_PIN_VCOM_CTRL | CFG_PIN_PWRUP, 1
|
||||||
|
));
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((pca9555_read_input(config_reg.port, 1) & 0xC0) == 0x80)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE("epdiy", "failed to shut down TPS65185!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
printf("%X\n", pca9555_read_input(config_reg.port, 1));
|
||||||
|
}
|
||||||
|
// Not sure why we need this delay, but the TPS65185 seems to generate an interrupt after some
|
||||||
|
// time that needs to be cleared.
|
||||||
|
vTaskDelay(500);
|
||||||
|
pca9555_read_input(config_reg.port, 0);
|
||||||
|
pca9555_read_input(config_reg.port, 1);
|
||||||
|
i2c_driver_delete(EPDIY_I2C_PORT);
|
||||||
|
gpio_isr_handler_remove(CFG_INTR);
|
||||||
|
gpio_uninstall_isr_service();
|
||||||
|
gpio_reset_pin(CFG_INTR);
|
||||||
|
gpio_reset_pin(V4_LATCH_ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
uint8_t value = 0x00;
|
||||||
|
if (state->ep_sth) {
|
||||||
|
fast_gpio_set_hi(STH);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(STH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv) {
|
||||||
|
if (state->ep_output_enable)
|
||||||
|
value |= CFG_PIN_OE;
|
||||||
|
if (state->ep_mode)
|
||||||
|
value |= CFG_PIN_MODE;
|
||||||
|
if (state->ep_stv)
|
||||||
|
value |= CFG_PIN_STV;
|
||||||
|
if (config_reg.pwrup)
|
||||||
|
value |= CFG_PIN_PWRUP;
|
||||||
|
if (config_reg.vcom_ctrl)
|
||||||
|
value |= CFG_PIN_VCOM_CTRL;
|
||||||
|
if (config_reg.wakeup)
|
||||||
|
value |= CFG_PIN_WAKEUP;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_value(config_reg.port, value, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->ep_latch_enable) {
|
||||||
|
fast_gpio_set_hi(V4_LATCH_ENABLE);
|
||||||
|
} else {
|
||||||
|
fast_gpio_set_lo(V4_LATCH_ENABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
i2s_gpio_attach(&i2s_config);
|
||||||
|
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
state->ep_stv = true;
|
||||||
|
config_reg.wakeup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.pwrup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.vcom_ctrl = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
// give the IC time to powerup and set lines
|
||||||
|
vTaskDelay(1);
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!(pca9555_read_input(config_reg.port, 1) & CFG_PIN_PWRGOOD)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! INT status: 0x%X 0x%X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_INT1),
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_INT2)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(tps_write_register(config_reg.port, TPS_REG_ENABLE, 0x3F));
|
||||||
|
|
||||||
|
tps_set_vcom(config_reg.port, vcom);
|
||||||
|
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
while (!((tps_read_register(config_reg.port, TPS_REG_PG) & 0xFA) == 0xFA)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! PG status: %X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_PG)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_measure_vcom(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
state->ep_output_enable = true;
|
||||||
|
config_reg.wakeup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.pwrup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
// give the IC time to powerup and set lines
|
||||||
|
vTaskDelay(1);
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
while (!(pca9555_read_input(config_reg.port, 1) & CFG_PIN_PWRGOOD)) {
|
||||||
|
}
|
||||||
|
ESP_LOGI("epdiy", "Power rails enabled");
|
||||||
|
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((tps_read_register(config_reg.port, TPS_REG_PG) & 0xFA) == 0xFA)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! PG status: %X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_PG)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_stv = true,
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
};
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
state->ep_mode = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
vTaskDelay(1);
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
i2s_gpio_detach(&i2s_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float epd_board_ambient_temperature() {
|
||||||
|
return tps_read_thermistor(EPDIY_I2C_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t gpio_set_direction_v6(int pin, bool make_input) {
|
||||||
|
static uint8_t direction = 0;
|
||||||
|
uint8_t mask = ~(1 << pin);
|
||||||
|
direction = (direction & mask) | (make_input << pin);
|
||||||
|
|
||||||
|
return pca9555_set_config(EPDIY_I2C_PORT, direction, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the input level of the broken-out GPIO extender port.
|
||||||
|
*/
|
||||||
|
static bool gpio_get_level_v6(int pin) {
|
||||||
|
return (pca9555_read_input(EPDIY_I2C_PORT, 0) >> pin) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the input level of the broken-out GPIO extender port.
|
||||||
|
*/
|
||||||
|
static esp_err_t gpio_set_value_v6(int pin, bool value) {
|
||||||
|
static uint8_t state = 0;
|
||||||
|
uint8_t mask = ~(1 << pin);
|
||||||
|
state = (state & mask) | (value << pin);
|
||||||
|
|
||||||
|
return pca9555_set_value(EPDIY_I2C_PORT, state, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_vcom(int value) {
|
||||||
|
vcom = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_v6 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = epd_board_deinit,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
.measure_vcom = epd_board_measure_vcom,
|
||||||
|
.get_temperature = epd_board_ambient_temperature,
|
||||||
|
.set_vcom = set_vcom,
|
||||||
|
|
||||||
|
.gpio_set_direction = gpio_set_direction_v6,
|
||||||
|
.gpio_read = gpio_get_level_v6,
|
||||||
|
.gpio_write = gpio_set_value_v6,
|
||||||
|
};
|
||||||
338
lib/libesp32_eink/epdiy/src/board/epd_board_v7.c
Normal file
338
lib/libesp32_eink/epdiy/src/board/epd_board_v7.c
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include "../output_common/render_method.h"
|
||||||
|
#include "../output_lcd/lcd_driver.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "pca9555.h"
|
||||||
|
#include "tps65185.h"
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
|
||||||
|
// Make this compile von the ESP32 without ifdefing the whole file
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define GPIO_NUM_40 -1
|
||||||
|
#define GPIO_NUM_41 -1
|
||||||
|
#define GPIO_NUM_42 -1
|
||||||
|
#define GPIO_NUM_43 -1
|
||||||
|
#define GPIO_NUM_44 -1
|
||||||
|
#define GPIO_NUM_45 -1
|
||||||
|
#define GPIO_NUM_46 -1
|
||||||
|
#define GPIO_NUM_47 -1
|
||||||
|
#define GPIO_NUM_48 -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_SCL GPIO_NUM_40
|
||||||
|
#define CFG_SDA GPIO_NUM_39
|
||||||
|
#define CFG_INTR GPIO_NUM_38
|
||||||
|
#define EPDIY_I2C_PORT I2C_NUM_0
|
||||||
|
|
||||||
|
#define CFG_PIN_OE (PCA_PIN_PC10 >> 8)
|
||||||
|
#define CFG_PIN_MODE (PCA_PIN_PC11 >> 8)
|
||||||
|
#define __CFG_PIN_STV (PCA_PIN_PC12 >> 8)
|
||||||
|
#define CFG_PIN_PWRUP (PCA_PIN_PC13 >> 8)
|
||||||
|
#define CFG_PIN_VCOM_CTRL (PCA_PIN_PC14 >> 8)
|
||||||
|
#define CFG_PIN_WAKEUP (PCA_PIN_PC15 >> 8)
|
||||||
|
#define CFG_PIN_PWRGOOD (PCA_PIN_PC16 >> 8)
|
||||||
|
#define CFG_PIN_INT (PCA_PIN_PC17 >> 8)
|
||||||
|
|
||||||
|
#define D15 GPIO_NUM_47
|
||||||
|
#define D14 GPIO_NUM_21
|
||||||
|
#define D13 GPIO_NUM_14
|
||||||
|
#define D12 GPIO_NUM_13
|
||||||
|
#define D11 GPIO_NUM_12
|
||||||
|
#define D10 GPIO_NUM_11
|
||||||
|
#define D9 GPIO_NUM_10
|
||||||
|
#define D8 GPIO_NUM_9
|
||||||
|
|
||||||
|
#define D7 GPIO_NUM_8
|
||||||
|
#define D6 GPIO_NUM_18
|
||||||
|
#define D5 GPIO_NUM_17
|
||||||
|
#define D4 GPIO_NUM_16
|
||||||
|
#define D3 GPIO_NUM_15
|
||||||
|
#define D2 GPIO_NUM_7
|
||||||
|
#define D1 GPIO_NUM_6
|
||||||
|
#define D0 GPIO_NUM_5
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_48
|
||||||
|
#define STH GPIO_NUM_41
|
||||||
|
#define LEH GPIO_NUM_42
|
||||||
|
#define STV GPIO_NUM_45
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
i2c_port_t port;
|
||||||
|
bool pwrup;
|
||||||
|
bool vcom_ctrl;
|
||||||
|
bool wakeup;
|
||||||
|
bool others[8];
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
/** The VCOM voltage to use. */
|
||||||
|
static int vcom = 1600;
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static bool interrupt_done = false;
|
||||||
|
|
||||||
|
static void IRAM_ATTR interrupt_handler(void* arg) {
|
||||||
|
interrupt_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lcd_bus_config_t lcd_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.ckv = CKV,
|
||||||
|
.leh = LEH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.stv = STV,
|
||||||
|
.data[0] = D0,
|
||||||
|
.data[1] = D1,
|
||||||
|
.data[2] = D2,
|
||||||
|
.data[3] = D3,
|
||||||
|
.data[4] = D4,
|
||||||
|
.data[5] = D5,
|
||||||
|
.data[6] = D6,
|
||||||
|
.data[7] = D7,
|
||||||
|
.data[8] = D8,
|
||||||
|
.data[9] = D9,
|
||||||
|
.data[10] = D10,
|
||||||
|
.data[11] = D11,
|
||||||
|
.data[12] = D12,
|
||||||
|
.data[13] = D13,
|
||||||
|
.data[14] = D14,
|
||||||
|
.data[15] = D15,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
gpio_hold_dis(CKH); // free CKH after wakeup
|
||||||
|
|
||||||
|
i2c_config_t conf;
|
||||||
|
conf.mode = I2C_MODE_MASTER;
|
||||||
|
conf.sda_io_num = CFG_SDA;
|
||||||
|
conf.scl_io_num = CFG_SCL;
|
||||||
|
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.master.clk_speed = 100000;
|
||||||
|
conf.clk_flags = 0;
|
||||||
|
ESP_ERROR_CHECK(i2c_param_config(EPDIY_I2C_PORT, &conf));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_driver_install(EPDIY_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0));
|
||||||
|
|
||||||
|
config_reg.port = EPDIY_I2C_PORT;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
config_reg.others[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_set_direction(CFG_INTR, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_intr_type(CFG_INTR, GPIO_INTR_NEGEDGE);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_EDGE));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CFG_INTR, interrupt_handler, (void*)CFG_INTR));
|
||||||
|
|
||||||
|
// set all epdiy lines to output except TPS interrupt + PWR good
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_config(config_reg.port, CFG_PIN_PWRGOOD | CFG_PIN_INT, 1));
|
||||||
|
|
||||||
|
const EpdDisplay_t* display = epd_get_display();
|
||||||
|
|
||||||
|
LcdEpdConfig_t config = {
|
||||||
|
.pixel_clock = display->bus_speed * 1000 * 1000,
|
||||||
|
.ckv_high_time = 60,
|
||||||
|
.line_front_porch = 4,
|
||||||
|
.le_high_time = 4,
|
||||||
|
.bus_width = display->bus_width,
|
||||||
|
.bus = lcd_config,
|
||||||
|
};
|
||||||
|
epd_lcd_init(&config, display->width, display->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_deinit() {
|
||||||
|
epd_lcd_deinit();
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_config(
|
||||||
|
config_reg.port, CFG_PIN_PWRGOOD | CFG_PIN_INT | CFG_PIN_VCOM_CTRL | CFG_PIN_PWRUP, 1
|
||||||
|
));
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((pca9555_read_input(config_reg.port, 1) & 0xC0) == 0x80)) {
|
||||||
|
if (tries >= 50) {
|
||||||
|
ESP_LOGE("epdiy", "failed to shut down TPS65185!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not sure why we need this delay, but the TPS65185 seems to generate an interrupt after some
|
||||||
|
// time that needs to be cleared.
|
||||||
|
vTaskDelay(50);
|
||||||
|
pca9555_read_input(config_reg.port, 0);
|
||||||
|
pca9555_read_input(config_reg.port, 1);
|
||||||
|
i2c_driver_delete(EPDIY_I2C_PORT);
|
||||||
|
|
||||||
|
gpio_uninstall_isr_service();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
uint8_t value = 0x00;
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv) {
|
||||||
|
if (state->ep_output_enable)
|
||||||
|
value |= CFG_PIN_OE;
|
||||||
|
if (state->ep_mode)
|
||||||
|
value |= CFG_PIN_MODE;
|
||||||
|
// if (state->ep_stv) value |= CFG_PIN_STV;
|
||||||
|
if (config_reg.pwrup)
|
||||||
|
value |= CFG_PIN_PWRUP;
|
||||||
|
if (config_reg.vcom_ctrl)
|
||||||
|
value |= CFG_PIN_VCOM_CTRL;
|
||||||
|
if (config_reg.wakeup)
|
||||||
|
value |= CFG_PIN_WAKEUP;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_value(config_reg.port, value, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
state->ep_output_enable = true;
|
||||||
|
config_reg.wakeup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.pwrup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.vcom_ctrl = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
// give the IC time to powerup and set lines
|
||||||
|
vTaskDelay(1);
|
||||||
|
|
||||||
|
while (!(pca9555_read_input(config_reg.port, 1) & CFG_PIN_PWRGOOD)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(tps_write_register(config_reg.port, TPS_REG_ENABLE, 0x3F));
|
||||||
|
|
||||||
|
tps_set_vcom(config_reg.port, vcom);
|
||||||
|
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((tps_read_register(config_reg.port, TPS_REG_PG) & 0xFA) == 0xFA)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! PG status: %X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_PG)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_measure_vcom(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
state->ep_output_enable = true;
|
||||||
|
config_reg.wakeup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.pwrup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
// give the IC time to powerup and set lines
|
||||||
|
vTaskDelay(1);
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
while (!(pca9555_read_input(config_reg.port, 1) & CFG_PIN_PWRGOOD)) {
|
||||||
|
}
|
||||||
|
ESP_LOGI("epdiy", "Power rails enabled");
|
||||||
|
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((tps_read_register(config_reg.port, TPS_REG_PG) & 0xFA) == 0xFA)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! PG status: %X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_PG)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_stv = true,
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
};
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
state->ep_mode = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
vTaskDelay(1);
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float epd_board_ambient_temperature() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_vcom(int value) {
|
||||||
|
vcom = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_v7 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = epd_board_deinit,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
|
||||||
|
.measure_vcom = epd_board_measure_vcom,
|
||||||
|
.get_temperature = epd_board_ambient_temperature,
|
||||||
|
.set_vcom = set_vcom,
|
||||||
|
|
||||||
|
// unimplemented for now, but shares v6 implementation
|
||||||
|
.gpio_set_direction = NULL,
|
||||||
|
.gpio_read = NULL,
|
||||||
|
.gpio_write = NULL,
|
||||||
|
};
|
||||||
288
lib/libesp32_eink/epdiy/src/board/epd_board_v7_raw.c
Normal file
288
lib/libesp32_eink/epdiy/src/board/epd_board_v7_raw.c
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/**
|
||||||
|
* @file epd_board_v7_raw.c
|
||||||
|
* @author Martin Fasani www.fasani.de
|
||||||
|
* @brief Small v7 board without IO expander targeted only to 8-bit einks
|
||||||
|
* @date 2025-02-10
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include "../output_common/render_method.h"
|
||||||
|
#include "../output_lcd/lcd_driver.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "tps65185.h"
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
|
||||||
|
// Make this compile on the ESP32 without ifdefing the whole file
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define GPIO_NUM_40 -1
|
||||||
|
#define GPIO_NUM_41 -1
|
||||||
|
#define GPIO_NUM_42 -1
|
||||||
|
#define GPIO_NUM_43 -1
|
||||||
|
#define GPIO_NUM_44 -1
|
||||||
|
#define GPIO_NUM_45 -1
|
||||||
|
#define GPIO_NUM_46 -1
|
||||||
|
#define GPIO_NUM_47 -1
|
||||||
|
#define GPIO_NUM_48 -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_SCL GPIO_NUM_40
|
||||||
|
#define CFG_SDA GPIO_NUM_39
|
||||||
|
#define EPDIY_I2C_PORT I2C_NUM_0
|
||||||
|
|
||||||
|
#define CFG_PIN_OE GPIO_NUM_9
|
||||||
|
#define CFG_PIN_MODE GPIO_NUM_10
|
||||||
|
#define __CFG_PIN_STV GPIO_NUM_45
|
||||||
|
#define CFG_PIN_PWRUP GPIO_NUM_11
|
||||||
|
#define CFG_PIN_VCOM_CTRL GPIO_NUM_12
|
||||||
|
#define CFG_PIN_WAKEUP GPIO_NUM_14
|
||||||
|
#define CFG_PIN_PWRGOOD GPIO_NUM_47
|
||||||
|
#define CFG_PIN_INT GPIO_NUM_13 // TPS65185 INT
|
||||||
|
|
||||||
|
#define D7 GPIO_NUM_8
|
||||||
|
#define D6 GPIO_NUM_18
|
||||||
|
#define D5 GPIO_NUM_17
|
||||||
|
#define D4 GPIO_NUM_16
|
||||||
|
#define D3 GPIO_NUM_15
|
||||||
|
#define D2 GPIO_NUM_7
|
||||||
|
#define D1 GPIO_NUM_6
|
||||||
|
#define D0 GPIO_NUM_5
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_48
|
||||||
|
#define STH GPIO_NUM_41
|
||||||
|
#define LEH GPIO_NUM_42
|
||||||
|
#define STV GPIO_NUM_45
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
i2c_port_t port;
|
||||||
|
bool pwrup;
|
||||||
|
bool vcom_ctrl;
|
||||||
|
bool wakeup;
|
||||||
|
bool others[8];
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
/** The VCOM voltage to use. */
|
||||||
|
static int vcom = 1900;
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static bool interrupt_done = false;
|
||||||
|
|
||||||
|
static void IRAM_ATTR interrupt_handler(void* arg) {
|
||||||
|
interrupt_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lcd_bus_config_t lcd_config = { .clock = CKH,
|
||||||
|
.ckv = CKV,
|
||||||
|
.leh = LEH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.stv = STV,
|
||||||
|
.data[0] = D0,
|
||||||
|
.data[1] = D1,
|
||||||
|
.data[2] = D2,
|
||||||
|
.data[3] = D3,
|
||||||
|
.data[4] = D4,
|
||||||
|
.data[5] = D5,
|
||||||
|
.data[6] = D6,
|
||||||
|
.data[7] = D7 };
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
gpio_hold_dis(CKH); // free CKH after wakeup
|
||||||
|
|
||||||
|
i2c_config_t conf;
|
||||||
|
conf.mode = I2C_MODE_MASTER;
|
||||||
|
conf.sda_io_num = CFG_SDA;
|
||||||
|
conf.scl_io_num = CFG_SCL;
|
||||||
|
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.master.clk_speed = 100000;
|
||||||
|
conf.clk_flags = 0;
|
||||||
|
ESP_ERROR_CHECK(i2c_param_config(EPDIY_I2C_PORT, &conf));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_driver_install(EPDIY_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0));
|
||||||
|
|
||||||
|
config_reg.port = EPDIY_I2C_PORT;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
config_reg.others[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_set_direction(CFG_PIN_INT, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_intr_type(CFG_PIN_INT, GPIO_INTR_NEGEDGE);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_EDGE));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CFG_PIN_INT, interrupt_handler, (void*)CFG_PIN_INT));
|
||||||
|
|
||||||
|
// set all epdiy lines to output except TPS interrupt + PWR good
|
||||||
|
gpio_set_direction(CFG_PIN_PWRGOOD, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_pull_mode(CFG_PIN_PWRGOOD, GPIO_PULLUP_ONLY);
|
||||||
|
|
||||||
|
gpio_set_direction(CFG_PIN_WAKEUP, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_PIN_PWRUP, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_PIN_VCOM_CTRL, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_PIN_OE, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(CFG_PIN_MODE, GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
const EpdDisplay_t* display = epd_get_display();
|
||||||
|
if (display->bus_width > 8) {
|
||||||
|
ESP_LOGE("v7_RAW", "displays > 8 bit width are not supported in this board");
|
||||||
|
}
|
||||||
|
|
||||||
|
LcdEpdConfig_t config = {
|
||||||
|
.pixel_clock = display->bus_speed * 1000 * 1000,
|
||||||
|
.ckv_high_time = 60,
|
||||||
|
.line_front_porch = 4,
|
||||||
|
.le_high_time = 4,
|
||||||
|
.bus_width = display->bus_width,
|
||||||
|
.bus = lcd_config,
|
||||||
|
};
|
||||||
|
|
||||||
|
epd_lcd_init(&config, display->width, display->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_deinit() {
|
||||||
|
epd_lcd_deinit();
|
||||||
|
gpio_set_level(CFG_PIN_PWRUP, false);
|
||||||
|
|
||||||
|
// Not sure why we need this delay, but the TPS65185 seems to generate an interrupt after some
|
||||||
|
// time that needs to be cleared.
|
||||||
|
vTaskDelay(50);
|
||||||
|
i2c_driver_delete(EPDIY_I2C_PORT);
|
||||||
|
|
||||||
|
gpio_uninstall_isr_service();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
if (state->ep_output_enable) {
|
||||||
|
gpio_set_level(CFG_PIN_OE, 1);
|
||||||
|
} else {
|
||||||
|
gpio_set_level(CFG_PIN_OE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->ep_mode) {
|
||||||
|
gpio_set_level(CFG_PIN_MODE, 1);
|
||||||
|
} else {
|
||||||
|
gpio_set_level(CFG_PIN_MODE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_reg.pwrup && !gpio_get_level(CFG_PIN_PWRUP)) {
|
||||||
|
gpio_set_level(CFG_PIN_PWRUP, 1);
|
||||||
|
} else {
|
||||||
|
gpio_set_level(CFG_PIN_PWRUP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_reg.vcom_ctrl && !gpio_get_level(CFG_PIN_VCOM_CTRL)) {
|
||||||
|
gpio_set_level(CFG_PIN_VCOM_CTRL, 1);
|
||||||
|
} else {
|
||||||
|
gpio_set_level(CFG_PIN_VCOM_CTRL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_reg.wakeup && !gpio_get_level(CFG_PIN_WAKEUP)) {
|
||||||
|
gpio_set_level(CFG_PIN_WAKEUP, 1);
|
||||||
|
} else {
|
||||||
|
gpio_set_level(CFG_PIN_WAKEUP, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_mode = true;
|
||||||
|
state->ep_output_enable = true;
|
||||||
|
config_reg.wakeup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
config_reg.pwrup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
config_reg.vcom_ctrl = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
// give the IC time to powerup and set lines
|
||||||
|
vTaskDelay(1);
|
||||||
|
|
||||||
|
// Check if PWRs lines are up
|
||||||
|
int timeout_counter = 0;
|
||||||
|
const int timeout_limit = 20; // 20 * 100ms = 2 seconds
|
||||||
|
while (gpio_get_level(CFG_PIN_PWRGOOD) == 0) {
|
||||||
|
if (timeout_counter >= timeout_limit) {
|
||||||
|
ESP_LOGE("v7_RAW", "Timeout waiting for PWRGOOD signal");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
timeout_counter++;
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
ESP_LOGI("v7_RAW", "PWRGOOD OK");
|
||||||
|
|
||||||
|
tps_set_vcom(config_reg.port, vcom);
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((tps_read_register(config_reg.port, TPS_REG_PG) & 0xFA) == 0xFA)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! PG status: %X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_PG)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_stv = true,
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
};
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
state->ep_mode = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
vTaskDelay(1);
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float epd_board_ambient_temperature() {
|
||||||
|
return tps_read_thermistor(EPDIY_I2C_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_vcom(int value) {
|
||||||
|
vcom = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition epd_board_v7_raw = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = epd_board_deinit,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
|
||||||
|
.get_temperature = epd_board_ambient_temperature,
|
||||||
|
.set_vcom = set_vcom,
|
||||||
|
|
||||||
|
// unimplemented for now, but shares v6 implementation
|
||||||
|
.gpio_set_direction = NULL,
|
||||||
|
.gpio_read = NULL,
|
||||||
|
.gpio_write = NULL,
|
||||||
|
};
|
||||||
273
lib/libesp32_eink/epdiy/src/board/lilygo_board_s3.c
Normal file
273
lib/libesp32_eink/epdiy/src/board/lilygo_board_s3.c
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include "../output_common/render_method.h"
|
||||||
|
#include "../output_lcd/lcd_driver.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "pca9555.h"
|
||||||
|
#include "tps65185.h"
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
|
||||||
|
// Make this compile von the ESP32 without ifdefing the whole file
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define GPIO_NUM_40 -1
|
||||||
|
#define GPIO_NUM_41 -1
|
||||||
|
#define GPIO_NUM_42 -1
|
||||||
|
#define GPIO_NUM_43 -1
|
||||||
|
#define GPIO_NUM_44 -1
|
||||||
|
#define GPIO_NUM_45 -1
|
||||||
|
#define GPIO_NUM_46 -1
|
||||||
|
#define GPIO_NUM_47 -1
|
||||||
|
#define GPIO_NUM_48 -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_SCL GPIO_NUM_40
|
||||||
|
#define CFG_SDA GPIO_NUM_39
|
||||||
|
#define CFG_INTR GPIO_NUM_38
|
||||||
|
#define EPDIY_I2C_PORT I2C_NUM_0
|
||||||
|
|
||||||
|
#define CFG_PIN_OE (PCA_PIN_PC10 >> 8)
|
||||||
|
#define CFG_PIN_MODE (PCA_PIN_PC11 >> 8)
|
||||||
|
#define __CFG_PIN_STV (PCA_PIN_PC12 >> 8)
|
||||||
|
#define CFG_PIN_PWRUP (PCA_PIN_PC13 >> 8)
|
||||||
|
#define CFG_PIN_VCOM_CTRL (PCA_PIN_PC14 >> 8)
|
||||||
|
#define CFG_PIN_WAKEUP (PCA_PIN_PC15 >> 8)
|
||||||
|
#define CFG_PIN_PWRGOOD (PCA_PIN_PC16 >> 8)
|
||||||
|
#define CFG_PIN_INT (PCA_PIN_PC17 >> 8)
|
||||||
|
|
||||||
|
#define D7 GPIO_NUM_8
|
||||||
|
#define D6 GPIO_NUM_18
|
||||||
|
#define D5 GPIO_NUM_17
|
||||||
|
#define D4 GPIO_NUM_16
|
||||||
|
#define D3 GPIO_NUM_15
|
||||||
|
#define D2 GPIO_NUM_7
|
||||||
|
#define D1 GPIO_NUM_6
|
||||||
|
#define D0 GPIO_NUM_5
|
||||||
|
|
||||||
|
/* Control Lines */
|
||||||
|
#define CKV GPIO_NUM_48
|
||||||
|
#define STH GPIO_NUM_41
|
||||||
|
#define LEH GPIO_NUM_42
|
||||||
|
#define STV GPIO_NUM_45
|
||||||
|
|
||||||
|
/* Edges */
|
||||||
|
#define CKH GPIO_NUM_4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
i2c_port_t port;
|
||||||
|
bool pwrup;
|
||||||
|
bool vcom_ctrl;
|
||||||
|
bool wakeup;
|
||||||
|
bool others[8];
|
||||||
|
} epd_config_register_t;
|
||||||
|
|
||||||
|
/** The VCOM voltage to use. */
|
||||||
|
static int vcom = 1600;
|
||||||
|
|
||||||
|
static epd_config_register_t config_reg;
|
||||||
|
|
||||||
|
static bool interrupt_done = false;
|
||||||
|
|
||||||
|
static void IRAM_ATTR interrupt_handler(void* arg) {
|
||||||
|
interrupt_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lcd_bus_config_t lcd_config = {
|
||||||
|
.clock = CKH,
|
||||||
|
.ckv = CKV,
|
||||||
|
.leh = LEH,
|
||||||
|
.start_pulse = STH,
|
||||||
|
.stv = STV,
|
||||||
|
.data[0] = D0,
|
||||||
|
.data[1] = D1,
|
||||||
|
.data[2] = D2,
|
||||||
|
.data[3] = D3,
|
||||||
|
.data[4] = D4,
|
||||||
|
.data[5] = D5,
|
||||||
|
.data[6] = D6,
|
||||||
|
.data[7] = D7,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void epd_board_init(uint32_t epd_row_width) {
|
||||||
|
gpio_hold_dis(CKH); // free CKH after wakeup
|
||||||
|
|
||||||
|
i2c_config_t conf;
|
||||||
|
conf.mode = I2C_MODE_MASTER;
|
||||||
|
conf.sda_io_num = CFG_SDA;
|
||||||
|
conf.scl_io_num = CFG_SCL;
|
||||||
|
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.master.clk_speed = 100000;
|
||||||
|
conf.clk_flags = 0;
|
||||||
|
ESP_ERROR_CHECK(i2c_param_config(EPDIY_I2C_PORT, &conf));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_driver_install(EPDIY_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0));
|
||||||
|
|
||||||
|
config_reg.port = EPDIY_I2C_PORT;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
config_reg.others[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_set_direction(CFG_INTR, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_intr_type(CFG_INTR, GPIO_INTR_NEGEDGE);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_EDGE));
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_isr_handler_add(CFG_INTR, interrupt_handler, (void*)CFG_INTR));
|
||||||
|
|
||||||
|
// set all epdiy lines to output except TPS interrupt + PWR good
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_config(config_reg.port, CFG_PIN_PWRGOOD | CFG_PIN_INT, 1));
|
||||||
|
|
||||||
|
const EpdDisplay_t* display = epd_get_display();
|
||||||
|
|
||||||
|
LcdEpdConfig_t config = {
|
||||||
|
.pixel_clock = display->bus_speed * 1000 * 1000,
|
||||||
|
.ckv_high_time = 60,
|
||||||
|
.line_front_porch = 4,
|
||||||
|
.le_high_time = 4,
|
||||||
|
.bus_width = display->bus_width,
|
||||||
|
.bus = lcd_config,
|
||||||
|
};
|
||||||
|
epd_lcd_init(&config, display->width, display->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_deinit() {
|
||||||
|
epd_lcd_deinit();
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_config(
|
||||||
|
config_reg.port, CFG_PIN_PWRGOOD | CFG_PIN_INT | CFG_PIN_VCOM_CTRL | CFG_PIN_PWRUP, 1
|
||||||
|
));
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((pca9555_read_input(config_reg.port, 1) & 0xC0) == 0x80)) {
|
||||||
|
if (tries >= 50) {
|
||||||
|
ESP_LOGE("epdiy", "failed to shut down TPS65185!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not sure why we need this delay, but the TPS65185 seems to generate an interrupt after some
|
||||||
|
// time that needs to be cleared.
|
||||||
|
vTaskDelay(50);
|
||||||
|
pca9555_read_input(config_reg.port, 0);
|
||||||
|
pca9555_read_input(config_reg.port, 1);
|
||||||
|
i2c_driver_delete(EPDIY_I2C_PORT);
|
||||||
|
|
||||||
|
gpio_uninstall_isr_service();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_set_ctrl(epd_ctrl_state_t* state, const epd_ctrl_state_t* const mask) {
|
||||||
|
uint8_t value = 0x00;
|
||||||
|
if (mask->ep_output_enable || mask->ep_mode || mask->ep_stv) {
|
||||||
|
if (state->ep_output_enable)
|
||||||
|
value |= CFG_PIN_OE;
|
||||||
|
if (state->ep_mode)
|
||||||
|
value |= CFG_PIN_MODE;
|
||||||
|
// if (state->ep_stv) value |= CFG_PIN_STV;
|
||||||
|
if (config_reg.pwrup)
|
||||||
|
value |= CFG_PIN_PWRUP;
|
||||||
|
if (config_reg.vcom_ctrl)
|
||||||
|
value |= CFG_PIN_VCOM_CTRL;
|
||||||
|
if (config_reg.wakeup)
|
||||||
|
value |= CFG_PIN_WAKEUP;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(pca9555_set_value(config_reg.port, value, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweron(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
.ep_stv = true,
|
||||||
|
};
|
||||||
|
state->ep_stv = true;
|
||||||
|
state->ep_mode = false;
|
||||||
|
state->ep_output_enable = true;
|
||||||
|
config_reg.wakeup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.pwrup = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
config_reg.vcom_ctrl = true;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
// give the IC time to powerup and set lines
|
||||||
|
vTaskDelay(1);
|
||||||
|
|
||||||
|
while (!(pca9555_read_input(config_reg.port, 1) & CFG_PIN_PWRGOOD)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(tps_write_register(config_reg.port, TPS_REG_ENABLE, 0x3F));
|
||||||
|
|
||||||
|
tps_set_vcom(config_reg.port, vcom);
|
||||||
|
|
||||||
|
state->ep_sth = true;
|
||||||
|
mask = (const epd_ctrl_state_t){
|
||||||
|
.ep_sth = true,
|
||||||
|
};
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
while (!((tps_read_register(config_reg.port, TPS_REG_PG) & 0xFA) == 0xFA)) {
|
||||||
|
if (tries >= 500) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Power enable failed! PG status: %X",
|
||||||
|
tps_read_register(config_reg.port, TPS_REG_PG)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_board_poweroff(epd_ctrl_state_t* state) {
|
||||||
|
epd_ctrl_state_t mask = {
|
||||||
|
.ep_stv = true,
|
||||||
|
.ep_output_enable = true,
|
||||||
|
.ep_mode = true,
|
||||||
|
};
|
||||||
|
config_reg.vcom_ctrl = false;
|
||||||
|
config_reg.pwrup = false;
|
||||||
|
state->ep_stv = false;
|
||||||
|
state->ep_output_enable = false;
|
||||||
|
state->ep_mode = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
vTaskDelay(1);
|
||||||
|
config_reg.wakeup = false;
|
||||||
|
epd_board_set_ctrl(state, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float epd_board_ambient_temperature() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_vcom(int value) {
|
||||||
|
vcom = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdBoardDefinition lilygo_board_s3 = {
|
||||||
|
.init = epd_board_init,
|
||||||
|
.deinit = epd_board_deinit,
|
||||||
|
.set_ctrl = epd_board_set_ctrl,
|
||||||
|
.poweron = epd_board_poweron,
|
||||||
|
.poweroff = epd_board_poweroff,
|
||||||
|
|
||||||
|
.get_temperature = epd_board_ambient_temperature,
|
||||||
|
.set_vcom = set_vcom,
|
||||||
|
|
||||||
|
// unimplemented for now, but shares v6 implementation
|
||||||
|
.gpio_set_direction = NULL,
|
||||||
|
.gpio_read = NULL,
|
||||||
|
.gpio_write = NULL,
|
||||||
|
};
|
||||||
107
lib/libesp32_eink/epdiy/src/board/pca9555.c
Normal file
107
lib/libesp32_eink/epdiy/src/board/pca9555.c
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#include "pca9555.h"
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define REG_INPUT_PORT0 0
|
||||||
|
#define REG_INPUT_PORT1 1
|
||||||
|
|
||||||
|
#define REG_OUTPUT_PORT0 2
|
||||||
|
#define REG_OUTPUT_PORT1 3
|
||||||
|
|
||||||
|
#define REG_INVERT_PORT0 4
|
||||||
|
#define REG_INVERT_PORT1 5
|
||||||
|
|
||||||
|
#define REG_CONFIG_PORT0 6
|
||||||
|
#define REG_CONFIG_PORT1 7
|
||||||
|
|
||||||
|
static esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t* data_rd, size_t size, int reg) {
|
||||||
|
if (size == 0) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||||
|
if (cmd == NULL) {
|
||||||
|
ESP_LOGE("epdiy", "insufficient memory for I2C transaction");
|
||||||
|
}
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
i2c_master_write_byte(cmd, (EPDIY_PCA9555_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||||
|
i2c_master_write_byte(cmd, reg, true);
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
|
||||||
|
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
|
||||||
|
cmd = i2c_cmd_link_create();
|
||||||
|
if (cmd == NULL) {
|
||||||
|
ESP_LOGE("epdiy", "insufficient memory for I2C transaction");
|
||||||
|
}
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
i2c_master_write_byte(cmd, (EPDIY_PCA9555_ADDR << 1) | I2C_MASTER_READ, true);
|
||||||
|
if (size > 1) {
|
||||||
|
i2c_master_read(cmd, data_rd, size - 1, I2C_MASTER_ACK);
|
||||||
|
}
|
||||||
|
i2c_master_read_byte(cmd, data_rd + size - 1, I2C_MASTER_NACK);
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
|
||||||
|
ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t i2c_master_write_slave(
|
||||||
|
i2c_port_t i2c_num, uint8_t ctrl, uint8_t* data_wr, size_t size
|
||||||
|
) {
|
||||||
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||||
|
if (cmd == NULL) {
|
||||||
|
ESP_LOGE("epdiy", "insufficient memory for I2C transaction");
|
||||||
|
}
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
i2c_master_write_byte(cmd, (EPDIY_PCA9555_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||||
|
i2c_master_write_byte(cmd, ctrl, true);
|
||||||
|
|
||||||
|
i2c_master_write(cmd, data_wr, size, true);
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t pca9555_write_single(i2c_port_t port, int reg, uint8_t value) {
|
||||||
|
uint8_t w_data[1] = { value };
|
||||||
|
return i2c_master_write_slave(port, reg, w_data, sizeof(w_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t pca9555_set_config(i2c_port_t port, uint8_t config_value, int high_port) {
|
||||||
|
return pca9555_write_single(port, REG_CONFIG_PORT0 + high_port, config_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t pca9555_set_inversion(i2c_port_t port, uint8_t config_value, int high_port) {
|
||||||
|
return pca9555_write_single(port, REG_INVERT_PORT0 + high_port, config_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t pca9555_set_value(i2c_port_t port, uint8_t config_value, int high_port) {
|
||||||
|
return pca9555_write_single(port, REG_OUTPUT_PORT0 + high_port, config_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pca9555_read_input(i2c_port_t i2c_port, int high_port) {
|
||||||
|
esp_err_t err;
|
||||||
|
uint8_t r_data[1];
|
||||||
|
|
||||||
|
err = i2c_master_read_slave(i2c_port, r_data, 1, REG_INPUT_PORT0 + high_port);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE("PCA9555", "%s failed", __func__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r_data[0];
|
||||||
|
}
|
||||||
35
lib/libesp32_eink/epdiy/src/board/pca9555.h
Normal file
35
lib/libesp32_eink/epdiy/src/board/pca9555.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef PCA9555_H
|
||||||
|
#define PCA9555_H
|
||||||
|
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
|
||||||
|
#define PCA_PIN_P00 0x0001
|
||||||
|
#define PCA_PIN_P01 0x0002
|
||||||
|
#define PCA_PIN_P02 0x0004
|
||||||
|
#define PCA_PIN_P03 0x0008
|
||||||
|
#define PCA_PIN_P04 0x0010
|
||||||
|
#define PCA_PIN_P05 0x0020
|
||||||
|
#define PCA_PIN_P06 0x0040
|
||||||
|
#define PCA_PIN_P07 0x0080
|
||||||
|
#define PCA_PIN_PC10 0x0100
|
||||||
|
#define PCA_PIN_PC11 0x0200
|
||||||
|
#define PCA_PIN_PC12 0x0400
|
||||||
|
#define PCA_PIN_PC13 0x0800
|
||||||
|
#define PCA_PIN_PC14 0x1000
|
||||||
|
#define PCA_PIN_PC15 0x2000
|
||||||
|
#define PCA_PIN_PC16 0x4000
|
||||||
|
#define PCA_PIN_PC17 0x8000
|
||||||
|
#define PCA_PIN_P_ALL 0x00FF
|
||||||
|
#define PCA_PIN_PC_ALL 0xFF00
|
||||||
|
#define PCA_PIN_ALL 0xFFFF
|
||||||
|
#define PCA_PIN_NULL 0x0000
|
||||||
|
|
||||||
|
static const int EPDIY_PCA9555_ADDR = 0x20;
|
||||||
|
|
||||||
|
uint8_t pca9555_read_input(i2c_port_t port, int high_port);
|
||||||
|
esp_err_t pca9555_set_value(i2c_port_t port, uint8_t config_value, int high_port);
|
||||||
|
esp_err_t pca9555_set_inversion(i2c_port_t port, uint8_t config_value, int high_port);
|
||||||
|
esp_err_t pca9555_set_config(i2c_port_t port, uint8_t config_value, int high_port);
|
||||||
|
|
||||||
|
#endif // PCA9555_H
|
||||||
128
lib/libesp32_eink/epdiy/src/board/tps65185.c
Normal file
128
lib/libesp32_eink/epdiy/src/board/tps65185.c
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
#include "tps65185.h"
|
||||||
|
#include "pca9555.h"
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static const int EPDIY_TPS_ADDR = 0x68;
|
||||||
|
|
||||||
|
static uint8_t i2c_master_read_slave(i2c_port_t i2c_num, int reg) {
|
||||||
|
uint8_t r_data[1];
|
||||||
|
|
||||||
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
i2c_master_write_byte(cmd, (EPDIY_TPS_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||||
|
i2c_master_write_byte(cmd, reg, true);
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS));
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
|
||||||
|
cmd = i2c_cmd_link_create();
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
i2c_master_write_byte(cmd, (EPDIY_TPS_ADDR << 1) | I2C_MASTER_READ, true);
|
||||||
|
/*
|
||||||
|
if (size > 1) {
|
||||||
|
i2c_master_read(cmd, data_rd, size - 1, I2C_MASTER_ACK);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
i2c_master_read_byte(cmd, r_data, I2C_MASTER_NACK);
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS));
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
|
||||||
|
return r_data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t i2c_master_write_slave(
|
||||||
|
i2c_port_t i2c_num, uint8_t ctrl, uint8_t* data_wr, size_t size
|
||||||
|
) {
|
||||||
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
i2c_master_write_byte(cmd, (EPDIY_TPS_ADDR << 1) | I2C_MASTER_WRITE, true);
|
||||||
|
i2c_master_write_byte(cmd, ctrl, true);
|
||||||
|
|
||||||
|
i2c_master_write(cmd, data_wr, size, true);
|
||||||
|
i2c_master_stop(cmd);
|
||||||
|
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_PERIOD_MS);
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t tps_write_register(i2c_port_t port, int reg, uint8_t value) {
|
||||||
|
uint8_t w_data[1];
|
||||||
|
esp_err_t err;
|
||||||
|
|
||||||
|
w_data[0] = value;
|
||||||
|
|
||||||
|
err = i2c_master_write_slave(port, reg, w_data, 1);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tps_read_register(i2c_port_t i2c_num, int reg) {
|
||||||
|
return i2c_master_read_slave(i2c_num, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tps_set_vcom(i2c_port_t i2c_num, unsigned vcom_mV) {
|
||||||
|
unsigned val = vcom_mV / 10;
|
||||||
|
ESP_ERROR_CHECK(tps_write_register(i2c_num, 4, (val & 0x100) >> 8));
|
||||||
|
ESP_ERROR_CHECK(tps_write_register(i2c_num, 3, val & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t tps_read_thermistor(i2c_port_t i2c_num) {
|
||||||
|
tps_write_register(i2c_num, TPS_REG_TMST1, 0x80);
|
||||||
|
int tries = 0;
|
||||||
|
while (true) {
|
||||||
|
uint8_t val = tps_read_register(i2c_num, TPS_REG_TMST1);
|
||||||
|
// temperature conversion done
|
||||||
|
if (val & 0x20) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
|
|
||||||
|
if (tries >= 100) {
|
||||||
|
ESP_LOGE("epdiy", "thermistor read timeout!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (int8_t)tps_read_register(i2c_num, TPS_REG_TMST_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tps_vcom_kickback() {
|
||||||
|
printf("VCOM Kickback test\n");
|
||||||
|
// pull the WAKEUP pin and the PWRUP pin high to enable all output rails.
|
||||||
|
epd_current_board()->measure_vcom(epd_ctrl_state());
|
||||||
|
// set the HiZ bit in the VCOM2 register (BIT 5) 0x20
|
||||||
|
// this puts the VCOM pin in a high-impedance state.
|
||||||
|
// bit 3 & 4 Number of acquisitions that is averaged to a single kick-back V. measurement
|
||||||
|
tps_write_register(I2C_NUM_0, 4, 0x38);
|
||||||
|
vTaskDelay(1);
|
||||||
|
|
||||||
|
uint8_t int1reg = tps_read_register(I2C_NUM_0, TPS_REG_INT1);
|
||||||
|
uint8_t vcomreg = tps_read_register(I2C_NUM_0, TPS_REG_VCOM2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tps_vcom_kickback_start() {
|
||||||
|
uint8_t int1reg = tps_read_register(I2C_NUM_0, TPS_REG_INT1);
|
||||||
|
// set the ACQ bit in the VCOM2 register to 1 (BIT 7)
|
||||||
|
tps_write_register(I2C_NUM_0, TPS_REG_VCOM2, 0xA0);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned tps_vcom_kickback_rdy() {
|
||||||
|
uint8_t int1reg = tps_read_register(I2C_NUM_0, TPS_REG_INT1);
|
||||||
|
|
||||||
|
if (int1reg == 0x02) {
|
||||||
|
uint8_t lsb = tps_read_register(I2C_NUM_0, 3);
|
||||||
|
uint8_t msb = tps_read_register(I2C_NUM_0, 4);
|
||||||
|
int u16Value = (lsb | (msb << 8)) & 0x1ff;
|
||||||
|
ESP_LOGI("vcom", "raw value:%d temperature:%d C", u16Value, tps_read_thermistor(I2C_NUM_0));
|
||||||
|
return u16Value * 10;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/libesp32_eink/epdiy/src/board/tps65185.h
Normal file
56
lib/libesp32_eink/epdiy/src/board/tps65185.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#ifndef TPS65185_H
|
||||||
|
#define TPS65185_H
|
||||||
|
|
||||||
|
#include <driver/i2c.h>
|
||||||
|
|
||||||
|
#define TPS_REG_TMST_VALUE 0x00
|
||||||
|
#define TPS_REG_ENABLE 0x01
|
||||||
|
#define TPS_REG_VADJ 0x02
|
||||||
|
#define TPS_REG_VCOM1 0x03
|
||||||
|
#define TPS_REG_VCOM2 0x04
|
||||||
|
#define TPS_REG_INT_EN1 0x05
|
||||||
|
#define TPS_REG_INT_EN2 0x06
|
||||||
|
#define TPS_REG_INT1 0x07
|
||||||
|
#define TPS_REG_INT2 0x08
|
||||||
|
#define TPS_REG_UPSEQ0 0x09
|
||||||
|
#define TPS_REG_UPSEQ1 0x0A
|
||||||
|
#define TPS_REG_DWNSEQ0 0x0B
|
||||||
|
#define TPS_REG_DWNSEQ1 0x0C
|
||||||
|
#define TPS_REG_TMST1 0x0D
|
||||||
|
#define TPS_REG_TMST2 0x0E
|
||||||
|
#define TPS_REG_PG 0x0F
|
||||||
|
#define TPS_REG_REVID 0x10
|
||||||
|
|
||||||
|
esp_err_t tps_write_register(i2c_port_t port, int reg, uint8_t value);
|
||||||
|
uint8_t tps_read_register(i2c_port_t i2c_num, int reg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the VCOM voltage in positive milivolts: 1600 -> -1.6V
|
||||||
|
*/
|
||||||
|
void tps_set_vcom(i2c_port_t i2c_num, unsigned vcom_mV);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Please read datasheet section 8.3.7.1 Kick-Back Voltage Measurement
|
||||||
|
* 1 Device enters ACTIVE mode
|
||||||
|
* 2 All power rails are up except VCOM
|
||||||
|
* VCOM pin is in HiZ state
|
||||||
|
*/
|
||||||
|
void tps_vcom_kickback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief start VCOM kick-back voltage measurements
|
||||||
|
*/
|
||||||
|
void tps_vcom_kickback_start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VCOM kick-back ACQC (Acquisition Complete) bit in the INT1 register is set
|
||||||
|
* @return unsigned: 0 is not read
|
||||||
|
*/
|
||||||
|
unsigned tps_vcom_kickback_rdy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the temperature via the on-board thermistor.
|
||||||
|
*/
|
||||||
|
int8_t tps_read_thermistor(i2c_port_t i2c_num);
|
||||||
|
|
||||||
|
#endif // TPS65185_H
|
||||||
5
lib/libesp32_eink/epdiy/src/board_specific.c
Normal file
5
lib/libesp32_eink/epdiy/src/board_specific.c
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include "epd_board_specific.h"
|
||||||
|
|
||||||
|
void epd_powerdown() {
|
||||||
|
epd_powerdown_lilygo_t5_47();
|
||||||
|
}
|
||||||
11
lib/libesp32_eink/epdiy/src/epd_driver/builtin_waveforms.c → lib/libesp32_eink/epdiy/src/builtin_waveforms.c
Executable file → Normal file
11
lib/libesp32_eink/epdiy/src/epd_driver/builtin_waveforms.c → lib/libesp32_eink/epdiy/src/builtin_waveforms.c
Executable file → Normal file
@ -1,10 +1,15 @@
|
|||||||
#include "epd_driver.h"
|
#include "epdiy.h"
|
||||||
|
|
||||||
#include "waveforms/ED047TC2.h"
|
|
||||||
#include "waveforms/epdiy_ED047TC1.h"
|
#include "waveforms/epdiy_ED047TC1.h"
|
||||||
|
|
||||||
|
// Note: Alternative Waveform added by Lilygo on Oct 2021, size: 266 Kb (ED047TC1 is 37 Kb, 7 times
|
||||||
|
// smaller)
|
||||||
|
#include "waveforms/epdiy_ED047TC2.h"
|
||||||
|
|
||||||
#include "waveforms/epdiy_ED060SC4.h"
|
#include "waveforms/epdiy_ED060SC4.h"
|
||||||
#include "waveforms/epdiy_ED060XC3.h"
|
|
||||||
#include "waveforms/epdiy_ED060SCT.h"
|
#include "waveforms/epdiy_ED060SCT.h"
|
||||||
|
#include "waveforms/epdiy_ED060XC3.h"
|
||||||
#include "waveforms/epdiy_ED097OC4.h"
|
#include "waveforms/epdiy_ED097OC4.h"
|
||||||
#include "waveforms/epdiy_ED097TC2.h"
|
#include "waveforms/epdiy_ED097TC2.h"
|
||||||
#include "waveforms/epdiy_ED133UT2.h"
|
#include "waveforms/epdiy_ED133UT2.h"
|
||||||
|
#include "waveforms/epdiy_NULL.h"
|
||||||
159
lib/libesp32_eink/epdiy/src/diff.S
Normal file
159
lib/libesp32_eink/epdiy/src/diff.S
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#include <xtensa/config/core-isa.h>
|
||||||
|
#include <xtensa/config/core-matmap.h>
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
.text
|
||||||
|
.align 4
|
||||||
|
.global epd_interlace_4bpp_line_VE
|
||||||
|
.type epd_interlace_4bpp_line_VE,@function
|
||||||
|
|
||||||
|
// // CRASH AND BURN for debugging
|
||||||
|
// EE.MOVI.32.A q3, a2, 0
|
||||||
|
// EE.MOVI.32.A q3, a3, 1
|
||||||
|
// EE.MOVI.32.A q3, a4, 2
|
||||||
|
// EE.MOVI.32.A q3, a5, 3
|
||||||
|
// l8ui a10, a10, 0
|
||||||
|
|
||||||
|
// bool interlace_line(
|
||||||
|
// const uint8_t *to,
|
||||||
|
// const uint8_t *from,
|
||||||
|
// uint8_t *col_dirtyness;
|
||||||
|
// uint8_t *interlaced,
|
||||||
|
// int fb_width
|
||||||
|
// )
|
||||||
|
epd_interlace_4bpp_line_VE:
|
||||||
|
// to - a2
|
||||||
|
// from - a3
|
||||||
|
// interlaced - a4
|
||||||
|
// col_dirtyness - a5
|
||||||
|
// fb_width - a6
|
||||||
|
|
||||||
|
entry a1, 32
|
||||||
|
|
||||||
|
// divide by 32 for loop count
|
||||||
|
srli a11, a6, 5
|
||||||
|
|
||||||
|
movi.n a10, 0xF0F0F0F0;
|
||||||
|
EE.MOVI.32.Q q6,a10,0
|
||||||
|
EE.MOVI.32.Q q6,a10,1
|
||||||
|
EE.MOVI.32.Q q6,a10,2
|
||||||
|
EE.MOVI.32.Q q6,a10,3
|
||||||
|
|
||||||
|
movi.n a10, 0x0F0F0F0F
|
||||||
|
EE.MOVI.32.Q q7,a10,0
|
||||||
|
EE.MOVI.32.Q q7,a10,1
|
||||||
|
EE.MOVI.32.Q q7,a10,2
|
||||||
|
EE.MOVI.32.Q q7,a10,3
|
||||||
|
|
||||||
|
// put 4 into shift amount
|
||||||
|
movi.n a10, 4
|
||||||
|
WSR.SAR a10
|
||||||
|
|
||||||
|
// "dirtyness" register
|
||||||
|
EE.ZERO.Q q5
|
||||||
|
|
||||||
|
// Instructions sometimes are in an unexpected order
|
||||||
|
// for best pipeline utilization
|
||||||
|
loopnez a11, .loop_end_difference
|
||||||
|
|
||||||
|
EE.VLD.128.IP q0, a2, 16
|
||||||
|
EE.VLD.128.IP q1, a3, 16
|
||||||
|
|
||||||
|
// load column dirtyness
|
||||||
|
EE.VLD.128.IP q3, a5, 0
|
||||||
|
|
||||||
|
// update dirtyness
|
||||||
|
EE.XORQ q4, q1, q0
|
||||||
|
|
||||||
|
// line dirtyness accumulator
|
||||||
|
EE.ORQ q5, q5, q4
|
||||||
|
// column dirtyness
|
||||||
|
EE.ORQ q3, q3, q4
|
||||||
|
|
||||||
|
// store column dirtyness
|
||||||
|
EE.VST.128.IP q3, a5, 16
|
||||||
|
|
||||||
|
// mask out every second value
|
||||||
|
EE.ANDQ q2, q0, q7
|
||||||
|
EE.ANDQ q0, q0, q6
|
||||||
|
EE.ANDQ q3, q1, q7
|
||||||
|
EE.ANDQ q1, q1, q6
|
||||||
|
|
||||||
|
// shift vectors to align
|
||||||
|
EE.VSL.32 q2, q2
|
||||||
|
EE.VSR.32 q1, q1
|
||||||
|
|
||||||
|
// the right shift sign-extends,
|
||||||
|
// so we make sure the resulting shift is logical by masking again
|
||||||
|
EE.ANDQ q1, q1, q7
|
||||||
|
|
||||||
|
// Combine "from" and "to" nibble
|
||||||
|
EE.ORQ q2, q2, q3
|
||||||
|
EE.ORQ q0, q0, q1
|
||||||
|
|
||||||
|
// Zip masked out values together
|
||||||
|
EE.VZIP.8 q2, q0
|
||||||
|
|
||||||
|
// store interlaced buffer data
|
||||||
|
EE.VST.128.IP q2, a4, 16
|
||||||
|
EE.VST.128.IP q0, a4, 16
|
||||||
|
|
||||||
|
.loop_end_difference:
|
||||||
|
|
||||||
|
EE.MOVI.32.A q5, a2, 0
|
||||||
|
EE.MOVI.32.A q5, a3, 1
|
||||||
|
EE.MOVI.32.A q5, a4, 2
|
||||||
|
EE.MOVI.32.A q5, a5, 3
|
||||||
|
or a2, a2, a3
|
||||||
|
or a2, a2, a4
|
||||||
|
or a2, a2, a5
|
||||||
|
|
||||||
|
//movi.n a2, 1 // return "true"
|
||||||
|
|
||||||
|
// CRASH AND BURN for debugging
|
||||||
|
//EE.MOVI.32.A q5, a2, 0
|
||||||
|
//EE.MOVI.32.A q5, a3, 1
|
||||||
|
//EE.MOVI.32.A q5, a4, 2
|
||||||
|
//EE.MOVI.32.A q5, a5, 3
|
||||||
|
//movi.n a10, 0
|
||||||
|
//l8ui a10, a10, 0
|
||||||
|
|
||||||
|
retw.n
|
||||||
|
|
||||||
|
|
||||||
|
.global epd_apply_line_mask_VE
|
||||||
|
.type epd_apply_line_mask_VE,@function
|
||||||
|
|
||||||
|
// void epd_apply_line_mask_VE(
|
||||||
|
// uint8_t *line,
|
||||||
|
// const uint8_t *mask,
|
||||||
|
// int mask_len
|
||||||
|
// )
|
||||||
|
epd_apply_line_mask_VE:
|
||||||
|
// line - a2
|
||||||
|
// mask - a3
|
||||||
|
// mask_len - a4
|
||||||
|
|
||||||
|
entry a1, 32
|
||||||
|
|
||||||
|
// divide by 16 for loop count
|
||||||
|
srli a4, a4, 4
|
||||||
|
|
||||||
|
// Instructions sometimes are in an unexpected order
|
||||||
|
// for best pipeline utilization
|
||||||
|
loopnez a4, .loop_end_mask
|
||||||
|
|
||||||
|
EE.VLD.128.IP q0, a2, 0
|
||||||
|
EE.VLD.128.IP q1, a3, 16
|
||||||
|
|
||||||
|
EE.ANDQ q0, q0, q1
|
||||||
|
|
||||||
|
EE.VST.128.IP q0, a2, 16
|
||||||
|
|
||||||
|
.loop_end_mask:
|
||||||
|
|
||||||
|
retw.n
|
||||||
|
|
||||||
|
#endif
|
||||||
83
lib/libesp32_eink/epdiy/src/displays.c
Normal file
83
lib/libesp32_eink/epdiy/src/displays.c
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#include "epd_display.h"
|
||||||
|
|
||||||
|
const EpdDisplay_t ED060SCT = {
|
||||||
|
.width = 800,
|
||||||
|
.height = 600,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 20,
|
||||||
|
.default_waveform = &epdiy_ED060SCT,
|
||||||
|
.display_type = DISPLAY_TYPE_GENERIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED060XC3 = {
|
||||||
|
.width = 1024,
|
||||||
|
.height = 768,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 20,
|
||||||
|
.default_waveform = &epdiy_ED060XC3,
|
||||||
|
.display_type = DISPLAY_TYPE_GENERIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED097OC4 = {
|
||||||
|
.width = 1200,
|
||||||
|
.height = 825,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 15,
|
||||||
|
.default_waveform = &epdiy_ED097OC4,
|
||||||
|
.display_type = DISPLAY_TYPE_GENERIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED097TC2 = {
|
||||||
|
.width = 1200,
|
||||||
|
.height = 825,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 22,
|
||||||
|
.default_waveform = &epdiy_ED097TC2,
|
||||||
|
.display_type = DISPLAY_TYPE_ED097TC2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED133UT2 = {
|
||||||
|
.width = 1600,
|
||||||
|
.height = 1200,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 20,
|
||||||
|
.default_waveform = &epdiy_ED097TC2,
|
||||||
|
.display_type = DISPLAY_TYPE_ED097TC2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED047TC1 = {
|
||||||
|
.width = 960,
|
||||||
|
.height = 540,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 20,
|
||||||
|
.default_waveform = &epdiy_ED047TC1,
|
||||||
|
.display_type = DISPLAY_TYPE_GENERIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED047TC2 = {
|
||||||
|
.width = 960,
|
||||||
|
.height = 540,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 20,
|
||||||
|
.default_waveform = &epdiy_ED047TC2,
|
||||||
|
.display_type = DISPLAY_TYPE_GENERIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdDisplay_t ED078KC1 = {
|
||||||
|
.width = 1872,
|
||||||
|
.height = 1404,
|
||||||
|
.bus_width = 16,
|
||||||
|
.bus_speed = 11,
|
||||||
|
.default_waveform = &epdiy_ED047TC2,
|
||||||
|
.display_type = DISPLAY_TYPE_GENERIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attention is by default horizontal rows mirrored
|
||||||
|
const EpdDisplay_t ED052TC4 = {
|
||||||
|
.width = 1280,
|
||||||
|
.height = 720,
|
||||||
|
.bus_width = 8,
|
||||||
|
.bus_speed = 22,
|
||||||
|
.default_waveform = &epdiy_ED097TC2,
|
||||||
|
.display_type = DISPLAY_TYPE_ED097TC2,
|
||||||
|
};
|
||||||
@ -37,7 +37,7 @@
|
|||||||
#include <esp_types.h>
|
#include <esp_types.h>
|
||||||
#include <xtensa/core-macros.h>
|
#include <xtensa/core-macros.h>
|
||||||
#include <driver/gpio.h>
|
#include <driver/gpio.h>
|
||||||
#include "epd_driver.h"
|
//#include "epd_driver.h"
|
||||||
#include "epd_highlevel.h"
|
#include "epd_highlevel.h"
|
||||||
|
|
||||||
#define WAVEFORM EPD_BUILTIN_WAVEFORM
|
#define WAVEFORM EPD_BUILTIN_WAVEFORM
|
||||||
@ -58,8 +58,8 @@ Epd47::Epd47(int16_t dwidth, int16_t dheight) : Renderer(dwidth, dheight) {
|
|||||||
disp_bpp = 4;
|
disp_bpp = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t Epd47::Init(void) {
|
int Epd47::Init(void) {
|
||||||
epd_init(EPD_LUT_1K);
|
epd_init(&epd_board_lilygo_t5_47, &ED097TC2, EPD_LUT_1K);
|
||||||
hl = epd_hl_init(WAVEFORM);
|
hl = epd_hl_init(WAVEFORM);
|
||||||
epd47_buffer = epd_hl_get_framebuffer(&hl);
|
epd47_buffer = epd_hl_get_framebuffer(&hl);
|
||||||
framebuffer = epd47_buffer;
|
framebuffer = epd47_buffer;
|
||||||
|
|||||||
133
lib/libesp32_eink/epdiy/src/epd_board.h
Normal file
133
lib/libesp32_eink/epdiy/src/epd_board.h
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* @file "epd_board.h"
|
||||||
|
* @brief Board-definitions provided by epdiy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <xtensa/core-macros.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State of display control pins.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
bool ep_latch_enable : 1;
|
||||||
|
bool ep_output_enable : 1;
|
||||||
|
bool ep_sth : 1;
|
||||||
|
bool ep_mode : 1;
|
||||||
|
bool ep_stv : 1;
|
||||||
|
} epd_ctrl_state_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operations available on an epdiy board.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/**
|
||||||
|
* Initialize the board.
|
||||||
|
*/
|
||||||
|
void (*init)(uint32_t epd_row_width);
|
||||||
|
/**
|
||||||
|
* Clean up resources and peripherals used by the board.
|
||||||
|
*/
|
||||||
|
void (*deinit)(void);
|
||||||
|
/**
|
||||||
|
* Set display line state
|
||||||
|
*/
|
||||||
|
void (*set_ctrl)(epd_ctrl_state_t*, const epd_ctrl_state_t* const);
|
||||||
|
/**
|
||||||
|
* Enable power to the display.
|
||||||
|
*/
|
||||||
|
void (*poweron)(epd_ctrl_state_t*);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure VCOM kick-back. Only in v6 & v7 boards!
|
||||||
|
*/
|
||||||
|
void (*measure_vcom)(epd_ctrl_state_t* state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable power to the display.
|
||||||
|
*/
|
||||||
|
void (*poweroff)(epd_ctrl_state_t*);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the display common voltage if supported.
|
||||||
|
*
|
||||||
|
* Voltage is set as absolute value in millivolts.
|
||||||
|
* Although VCOM is negative, this function takes a positive (absolute) value.
|
||||||
|
*/
|
||||||
|
void (*set_vcom)(int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current temperature if supported by the board.
|
||||||
|
*/
|
||||||
|
float (*get_temperature)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set GPIO direction of the broken-out GPIO extender port,
|
||||||
|
* if available.
|
||||||
|
* Setting `make_input` to `1` corresponds to input, `0` corresponds to output.
|
||||||
|
*/
|
||||||
|
esp_err_t (*gpio_set_direction)(int pin, bool make_input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the input level of a GPIO extender pin, if available.
|
||||||
|
*/
|
||||||
|
bool (*gpio_read)(int pin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the output level of a GPIO extender, if available.
|
||||||
|
*/
|
||||||
|
esp_err_t (*gpio_write)(int pin, bool value);
|
||||||
|
} EpdBoardDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current board.
|
||||||
|
*/
|
||||||
|
const EpdBoardDefinition* epd_current_board();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the board hardware definition. This must be called before epd_init()
|
||||||
|
*
|
||||||
|
* The implementation of this method is in board/epd_board.c.
|
||||||
|
**/
|
||||||
|
void epd_set_board(const EpdBoardDefinition* board);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the board's current control register state.
|
||||||
|
*/
|
||||||
|
epd_ctrl_state_t* epd_ctrl_state();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the display mode pin.
|
||||||
|
*/
|
||||||
|
void epd_set_mode(bool state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the control register
|
||||||
|
*/
|
||||||
|
void epd_control_reg_init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the control register into the state of lowest power consumption.
|
||||||
|
*/
|
||||||
|
void epd_control_reg_deinit();
|
||||||
|
|
||||||
|
// Built in board definitions
|
||||||
|
extern const EpdBoardDefinition epd_board_lilygo_t5_47;
|
||||||
|
extern const EpdBoardDefinition epd_board_lilygo_t5_47_touch;
|
||||||
|
extern const EpdBoardDefinition lilygo_board_s3;
|
||||||
|
extern const EpdBoardDefinition epd_board_v2_v3;
|
||||||
|
extern const EpdBoardDefinition epd_board_v4;
|
||||||
|
extern const EpdBoardDefinition epd_board_v5;
|
||||||
|
extern const EpdBoardDefinition epd_board_v6;
|
||||||
|
extern const EpdBoardDefinition epd_board_v7;
|
||||||
|
extern const EpdBoardDefinition epd_board_v7_raw;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for short, precise delays.
|
||||||
|
*/
|
||||||
|
void epd_busy_delay(uint32_t cycles);
|
||||||
32
lib/libesp32_eink/epdiy/src/epd_board_specific.h
Normal file
32
lib/libesp32_eink/epdiy/src/epd_board_specific.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @file "epd_board_specific.h"
|
||||||
|
* @brief Board-specific functions that are only conditionally defined.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** This is a Lilygo47 specific function
|
||||||
|
|
||||||
|
This is a work around a hardware issue with the Lilygo47 epd_poweroff() turns off the epaper
|
||||||
|
completely however the hardware of the Lilygo47 is different than the official boards. Which means
|
||||||
|
that on the Lilygo47 this disables power to the touchscreen.
|
||||||
|
|
||||||
|
This is a workaround to allow to disable display power but not the touch screen.
|
||||||
|
On the Lilygo the epd power flag was re-purposed as power enable
|
||||||
|
for everything. This is a hardware thing.
|
||||||
|
\warning This workaround may still leave power on to epd and as such may cause other problems such
|
||||||
|
as grey screen.
|
||||||
|
|
||||||
|
Please use epd_poweroff() and epd_deinit() whenever you sleep the system.
|
||||||
|
The following code can be used to sleep the lilygo and power down the peripherals and wake the
|
||||||
|
unit on touch. However is should be noted that the touch controller is not powered and as such the
|
||||||
|
touch coordinates will not be captured. Arduino specific code: \code{.c} epd_poweroff();
|
||||||
|
epd_deinit();
|
||||||
|
esp_sleep_enable_ext1_wakeup(GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||||
|
esp_deep_sleep_start();
|
||||||
|
\endcode
|
||||||
|
*/
|
||||||
|
void epd_powerdown_lilygo_t5_47();
|
||||||
|
void epd_powerdown() __attribute__((deprecated));
|
||||||
43
lib/libesp32_eink/epdiy/src/epd_display.h
Normal file
43
lib/libesp32_eink/epdiy/src/epd_display.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "epd_internals.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display type as "compatibility classes",
|
||||||
|
* Grouping displays by workarounds needed.
|
||||||
|
*/
|
||||||
|
enum EpdDisplayType {
|
||||||
|
/// A generic EPD, assume default config.
|
||||||
|
DISPLAY_TYPE_GENERIC,
|
||||||
|
/// Fast display where we can get away with low hold times.
|
||||||
|
DISPLAY_TYPE_ED097TC2,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/// Width of the display in pixels.
|
||||||
|
int width;
|
||||||
|
/// Height of the display in pixels.
|
||||||
|
int height;
|
||||||
|
|
||||||
|
/// Width of the data bus in bits.
|
||||||
|
uint8_t bus_width;
|
||||||
|
/// Speed of the data bus in MHz, if configurable.
|
||||||
|
/// (Only used by the LCD based renderer in V7+)
|
||||||
|
int bus_speed;
|
||||||
|
|
||||||
|
/// Default waveform to use.
|
||||||
|
const EpdWaveform* default_waveform;
|
||||||
|
/// Display type
|
||||||
|
enum EpdDisplayType display_type;
|
||||||
|
} EpdDisplay_t;
|
||||||
|
|
||||||
|
extern const EpdDisplay_t ED060SCT;
|
||||||
|
extern const EpdDisplay_t ED060XC3;
|
||||||
|
extern const EpdDisplay_t ED097OC4;
|
||||||
|
extern const EpdDisplay_t ED097TC2;
|
||||||
|
extern const EpdDisplay_t ED133UT2;
|
||||||
|
extern const EpdDisplay_t ED047TC1;
|
||||||
|
extern const EpdDisplay_t ED047TC2;
|
||||||
|
extern const EpdDisplay_t ED078KC1;
|
||||||
|
extern const EpdDisplay_t ED052TC4;
|
||||||
@ -1,4 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
// This file is only used in the Arduino IDE
|
|
||||||
// and just includes the IDF component header.
|
|
||||||
#include "epd_driver/include/epd_driver.h"
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
set(app_sources "epd_driver.c"
|
|
||||||
"render.c"
|
|
||||||
"display_ops.c"
|
|
||||||
"font.c"
|
|
||||||
"lut.c"
|
|
||||||
"builtin_waveforms.c"
|
|
||||||
"i2s_data_bus.c"
|
|
||||||
"rmt_pulse.c"
|
|
||||||
"highlevel.c"
|
|
||||||
"epd_temperature.c")
|
|
||||||
|
|
||||||
|
|
||||||
idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "include" REQUIRES esp_adc_cal)
|
|
||||||
|
|
||||||
set_source_files_properties("lut.c" PROPERTIES COMPILE_OPTIONS -mno-fix-esp32-psram-cache-issue)
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
menu "E-Paper Driver"
|
|
||||||
choice EPD_DRIVER_DISPLAY_TYPE
|
|
||||||
prompt "Display Type"
|
|
||||||
default EPD_DISPLAY_TYPE_ED097OC4
|
|
||||||
help
|
|
||||||
This option sets the display type to drive.
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED097OC4
|
|
||||||
bool "ED097OC4"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED060SC4
|
|
||||||
bool "ED060SC4"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED060XC3
|
|
||||||
bool "ED060XC3"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED060SCT
|
|
||||||
bool "ED060SCT"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED097TC2
|
|
||||||
bool "ED097TC2"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED133UT2
|
|
||||||
bool "ED133UT2"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED047TC1
|
|
||||||
bool "ED047TC1 (LILYGO 4.7 inch)"
|
|
||||||
|
|
||||||
config EPD_DISPLAY_TYPE_ED097OC4_LQ
|
|
||||||
bool "ED097OC4 Low Quality"
|
|
||||||
endchoice
|
|
||||||
|
|
||||||
|
|
||||||
choice EPD_DRIVER_BOARD_REVISION
|
|
||||||
prompt "Board / Board Revision"
|
|
||||||
default EPD_BOARD_REVISION_V2_V3
|
|
||||||
help
|
|
||||||
The board revision to compile for.
|
|
||||||
|
|
||||||
config EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
bool "LILYGO T5-4.7 inch e-paper"
|
|
||||||
|
|
||||||
config EPD_BOARD_REVISION_V2_V3
|
|
||||||
bool "epdiy v2 / v3"
|
|
||||||
|
|
||||||
config EPD_BOARD_REVISION_V4
|
|
||||||
bool "epdiy v4"
|
|
||||||
|
|
||||||
config EPD_BOARD_REVISION_V5
|
|
||||||
bool "epdiy v5"
|
|
||||||
endchoice
|
|
||||||
endmenu
|
|
||||||
@ -1,674 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
#include "display_ops.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool ep_latch_enable : 1;
|
|
||||||
bool power_disable : 1;
|
|
||||||
bool pos_power_enable : 1;
|
|
||||||
bool neg_power_enable : 1;
|
|
||||||
bool ep_stv : 1;
|
|
||||||
bool ep_scan_direction : 1;
|
|
||||||
bool ep_mode : 1;
|
|
||||||
bool ep_output_enable : 1;
|
|
||||||
} epd_config_register_t;
|
|
||||||
|
|
||||||
static void config_reg_init(epd_config_register_t *cfg) {
|
|
||||||
cfg->ep_latch_enable = false;
|
|
||||||
cfg->power_disable = true;
|
|
||||||
cfg->pos_power_enable = false;
|
|
||||||
cfg->neg_power_enable = false;
|
|
||||||
cfg->ep_stv = true;
|
|
||||||
cfg->ep_scan_direction = true;
|
|
||||||
cfg->ep_mode = false;
|
|
||||||
cfg->ep_output_enable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void IRAM_ATTR push_cfg_bit(bool bit);
|
|
||||||
void IRAM_ATTR busy_delay(uint32_t cycles);
|
|
||||||
inline static void fast_gpio_set_hi(gpio_num_t gpio_num);
|
|
||||||
inline static void fast_gpio_set_lo(gpio_num_t gpio_num);
|
|
||||||
|
|
||||||
static void IRAM_ATTR push_cfg(const epd_config_register_t *cfg) {
|
|
||||||
fast_gpio_set_lo(CFG_STR);
|
|
||||||
|
|
||||||
// push config bits in reverse order
|
|
||||||
push_cfg_bit(cfg->ep_output_enable);
|
|
||||||
push_cfg_bit(cfg->ep_mode);
|
|
||||||
push_cfg_bit(cfg->ep_scan_direction);
|
|
||||||
push_cfg_bit(cfg->ep_stv);
|
|
||||||
|
|
||||||
push_cfg_bit(cfg->neg_power_enable);
|
|
||||||
push_cfg_bit(cfg->pos_power_enable);
|
|
||||||
push_cfg_bit(cfg->power_disable);
|
|
||||||
push_cfg_bit(cfg->ep_latch_enable);
|
|
||||||
|
|
||||||
fast_gpio_set_hi(CFG_STR);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cfg_poweron(epd_config_register_t *cfg) {
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47)
|
|
||||||
// This was re-purposed as power enable.
|
|
||||||
cfg->ep_scan_direction = true;
|
|
||||||
#endif
|
|
||||||
// POWERON
|
|
||||||
cfg->power_disable = false;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(100 * 240);
|
|
||||||
cfg->neg_power_enable = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(500 * 240);
|
|
||||||
cfg->pos_power_enable = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(100 * 240);
|
|
||||||
cfg->ep_stv = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
fast_gpio_set_hi(STH);
|
|
||||||
// END POWERON
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cfg_poweroff(epd_config_register_t *cfg) {
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47)
|
|
||||||
// This was re-purposed as power enable.
|
|
||||||
cfg->ep_scan_direction = false;
|
|
||||||
#endif
|
|
||||||
// POWEROFF
|
|
||||||
cfg->pos_power_enable = false;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(10 * 240);
|
|
||||||
cfg->neg_power_enable = false;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(100 * 240);
|
|
||||||
cfg->power_disable = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
// END POWEROFF
|
|
||||||
}
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
#include "display_ops.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
bool power_enable : 1;
|
|
||||||
#else
|
|
||||||
bool power_disable : 1;
|
|
||||||
#endif
|
|
||||||
bool power_enable_vpos : 1;
|
|
||||||
bool power_enable_vneg : 1;
|
|
||||||
bool power_enable_gl : 1;
|
|
||||||
bool ep_stv : 1;
|
|
||||||
bool power_enable_gh : 1;
|
|
||||||
bool ep_mode : 1;
|
|
||||||
bool ep_output_enable : 1;
|
|
||||||
} epd_config_register_t;
|
|
||||||
|
|
||||||
static void config_reg_init(epd_config_register_t *cfg) {
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
cfg->power_enable = false;
|
|
||||||
#else
|
|
||||||
cfg->power_disable = true;
|
|
||||||
#endif
|
|
||||||
cfg->power_enable_vpos = false;
|
|
||||||
cfg->power_enable_vneg = false;
|
|
||||||
cfg->power_enable_gl = false;
|
|
||||||
cfg->ep_stv = true;
|
|
||||||
cfg->power_enable_gh = false;
|
|
||||||
cfg->ep_mode = false;
|
|
||||||
cfg->ep_output_enable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void IRAM_ATTR push_cfg_bit(bool bit);
|
|
||||||
void IRAM_ATTR busy_delay(uint32_t cycles);
|
|
||||||
inline static void fast_gpio_set_hi(gpio_num_t gpio_num);
|
|
||||||
inline static void fast_gpio_set_lo(gpio_num_t gpio_num);
|
|
||||||
|
|
||||||
static void IRAM_ATTR push_cfg(const epd_config_register_t *cfg) {
|
|
||||||
fast_gpio_set_lo(CFG_STR);
|
|
||||||
|
|
||||||
// push config bits in reverse order
|
|
||||||
push_cfg_bit(cfg->ep_output_enable);
|
|
||||||
push_cfg_bit(cfg->ep_mode);
|
|
||||||
push_cfg_bit(cfg->power_enable_gh);
|
|
||||||
push_cfg_bit(cfg->ep_stv);
|
|
||||||
|
|
||||||
push_cfg_bit(cfg->power_enable_gl);
|
|
||||||
push_cfg_bit(cfg->power_enable_vneg);
|
|
||||||
push_cfg_bit(cfg->power_enable_vpos);
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
push_cfg_bit(cfg->power_enable);
|
|
||||||
#else
|
|
||||||
push_cfg_bit(cfg->power_disable);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
fast_gpio_set_hi(CFG_STR);
|
|
||||||
fast_gpio_set_lo(CFG_STR);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cfg_poweron(epd_config_register_t *cfg) {
|
|
||||||
// POWERON
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
cfg->power_enable = true;
|
|
||||||
#else
|
|
||||||
cfg->power_disable = false;
|
|
||||||
#endif
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(100 * 240);
|
|
||||||
cfg->power_enable_gl = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(500 * 240);
|
|
||||||
cfg->power_enable_vneg = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(500 * 240);
|
|
||||||
cfg->power_enable_gh = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(500 * 240);
|
|
||||||
cfg->power_enable_vpos = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(100 * 240);
|
|
||||||
cfg->ep_stv = true;
|
|
||||||
push_cfg(cfg);
|
|
||||||
fast_gpio_set_hi(STH);
|
|
||||||
// END POWERON
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cfg_poweroff(epd_config_register_t *cfg) {
|
|
||||||
// POWEROFF
|
|
||||||
cfg->power_enable_gh = false;
|
|
||||||
cfg->power_enable_vpos = false;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(10 * 240);
|
|
||||||
cfg->power_enable_gl = false;
|
|
||||||
cfg->power_enable_vneg = false;
|
|
||||||
push_cfg(cfg);
|
|
||||||
busy_delay(100 * 240);
|
|
||||||
|
|
||||||
cfg->ep_stv = false;
|
|
||||||
cfg->ep_output_enable = false;
|
|
||||||
cfg->ep_mode = false;
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
cfg->power_enable = false;
|
|
||||||
#else
|
|
||||||
cfg->power_disable = true;
|
|
||||||
#endif
|
|
||||||
push_cfg(cfg);
|
|
||||||
// END POWEROFF
|
|
||||||
}
|
|
||||||
@ -1,195 +0,0 @@
|
|||||||
#include "display_ops.h"
|
|
||||||
#include "esp_timer.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "i2s_data_bus.h"
|
|
||||||
#include "rmt_pulse.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
|
|
||||||
#include "xtensa/core-macros.h"
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V2_V3) || defined(CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47)
|
|
||||||
#include "config_reg_v2.h"
|
|
||||||
#else
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V4) || defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
#include "config_reg_v4.h"
|
|
||||||
#else
|
|
||||||
#error "unknown revision"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static epd_config_register_t config_reg;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Write bits directly using the registers.
|
|
||||||
* Won't work for some pins (>= 32).
|
|
||||||
*/
|
|
||||||
inline static void fast_gpio_set_hi(gpio_num_t gpio_num) {
|
|
||||||
GPIO.out_w1ts = (1 << gpio_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void fast_gpio_set_lo(gpio_num_t gpio_num) {
|
|
||||||
GPIO.out_w1tc = (1 << gpio_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR busy_delay(uint32_t cycles) {
|
|
||||||
volatile unsigned long counts = XTHAL_GET_CCOUNT() + cycles;
|
|
||||||
while (XTHAL_GET_CCOUNT() < counts) {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void IRAM_ATTR push_cfg_bit(bool bit) {
|
|
||||||
gpio_set_level(CFG_CLK, 0);
|
|
||||||
if (bit) {
|
|
||||||
gpio_set_level(CFG_DATA, 1);
|
|
||||||
} else {
|
|
||||||
gpio_set_level(CFG_DATA, 0);
|
|
||||||
}
|
|
||||||
gpio_set_level(CFG_CLK, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_base_init(uint32_t epd_row_width) {
|
|
||||||
|
|
||||||
config_reg_init(&config_reg);
|
|
||||||
|
|
||||||
/* Power Control Output/Off */
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_DATA], PIN_FUNC_GPIO);
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_CLK], PIN_FUNC_GPIO);
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[CFG_STR], PIN_FUNC_GPIO);
|
|
||||||
gpio_set_direction(CFG_DATA, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_set_direction(CFG_CLK, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_set_direction(CFG_STR, GPIO_MODE_OUTPUT);
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V4) || defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
// use latch pin as GPIO
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[V4_LATCH_ENABLE], PIN_FUNC_GPIO);
|
|
||||||
ESP_ERROR_CHECK(gpio_set_direction(V4_LATCH_ENABLE, GPIO_MODE_OUTPUT));
|
|
||||||
gpio_set_level(V4_LATCH_ENABLE, 0);
|
|
||||||
#endif
|
|
||||||
fast_gpio_set_lo(CFG_STR);
|
|
||||||
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
|
|
||||||
// Setup I2S
|
|
||||||
i2s_bus_config i2s_config;
|
|
||||||
// add an offset off dummy bytes to allow for enough timing headroom
|
|
||||||
i2s_config.epd_row_width = epd_row_width + 32;
|
|
||||||
i2s_config.clock = CKH;
|
|
||||||
i2s_config.start_pulse = STH;
|
|
||||||
i2s_config.data_0 = D0;
|
|
||||||
i2s_config.data_1 = D1;
|
|
||||||
i2s_config.data_2 = D2;
|
|
||||||
i2s_config.data_3 = D3;
|
|
||||||
i2s_config.data_4 = D4;
|
|
||||||
i2s_config.data_5 = D5;
|
|
||||||
i2s_config.data_6 = D6;
|
|
||||||
i2s_config.data_7 = D7;
|
|
||||||
|
|
||||||
i2s_bus_init(&i2s_config);
|
|
||||||
|
|
||||||
rmt_pulse_init(CKV);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_poweron() { cfg_poweron(&config_reg); }
|
|
||||||
|
|
||||||
void epd_poweroff() { cfg_poweroff(&config_reg); }
|
|
||||||
|
|
||||||
void epd_base_deinit(){
|
|
||||||
epd_poweroff();
|
|
||||||
i2s_deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_start_frame() {
|
|
||||||
while (i2s_is_busy() || rmt_busy()) {
|
|
||||||
};
|
|
||||||
config_reg.ep_mode = true;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
|
|
||||||
// This is very timing-sensitive!
|
|
||||||
config_reg.ep_stv = false;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
//busy_delay(240);
|
|
||||||
pulse_ckv_us(100, 100, false);
|
|
||||||
config_reg.ep_stv = true;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
//pulse_ckv_us(0, 10, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
|
|
||||||
config_reg.ep_output_enable = true;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void latch_row() {
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V2_V3) || defined(CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47)
|
|
||||||
config_reg.ep_latch_enable = true;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
|
|
||||||
config_reg.ep_latch_enable = false;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
#else
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V4) || defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
fast_gpio_set_hi(V4_LATCH_ENABLE);
|
|
||||||
fast_gpio_set_lo(V4_LATCH_ENABLE);
|
|
||||||
#else
|
|
||||||
#error "unknown revision"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR epd_skip() {
|
|
||||||
#if defined(CONFIG_EPD_DISPLAY_TYPE_ED097TC2) || \
|
|
||||||
defined(CONFIG_EPD_DISPLAY_TYPE_ED133UT2)
|
|
||||||
pulse_ckv_ticks(5, 5, false);
|
|
||||||
#else
|
|
||||||
// According to the spec, the OC4 maximum CKV frequency is 200kHz.
|
|
||||||
pulse_ckv_ticks(45, 5, false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR epd_output_row(uint32_t output_time_dus) {
|
|
||||||
|
|
||||||
while (i2s_is_busy() || rmt_busy()) {
|
|
||||||
};
|
|
||||||
|
|
||||||
fast_gpio_set_hi(STH);
|
|
||||||
|
|
||||||
latch_row();
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_DISPLAY_TYPE_ED097TC2) || \
|
|
||||||
defined(CONFIG_EPD_DISPLAY_TYPE_ED133UT2)
|
|
||||||
pulse_ckv_ticks(output_time_dus, 1, false);
|
|
||||||
#else
|
|
||||||
pulse_ckv_ticks(output_time_dus, 50, false);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
i2s_start_line_output();
|
|
||||||
i2s_switch_buffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_end_frame() {
|
|
||||||
config_reg.ep_stv = false;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
config_reg.ep_mode = false;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
pulse_ckv_us(0, 10, true);
|
|
||||||
config_reg.ep_output_enable = false;
|
|
||||||
push_cfg(&config_reg);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
pulse_ckv_us(1, 1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR epd_switch_buffer() { i2s_switch_buffer(); }
|
|
||||||
uint8_t IRAM_ATTR *epd_get_current_buffer() {
|
|
||||||
return (uint8_t *)i2s_get_current_buffer();
|
|
||||||
};
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
|
|
||||||
#define CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
#define D7 GPIO_NUM_23
|
|
||||||
#define D6 GPIO_NUM_22
|
|
||||||
#define D5 GPIO_NUM_21
|
|
||||||
#define D4 GPIO_NUM_19
|
|
||||||
#define D3 GPIO_NUM_18
|
|
||||||
#define D2 GPIO_NUM_5
|
|
||||||
#define D1 GPIO_NUM_4
|
|
||||||
#define D0 GPIO_NUM_25
|
|
||||||
|
|
||||||
|
|
||||||
/* Config Reggister Control */
|
|
||||||
#define CFG_DATA GPIO_NUM_33
|
|
||||||
#define CFG_CLK GPIO_NUM_32
|
|
||||||
#define CFG_STR GPIO_NUM_0
|
|
||||||
|
|
||||||
/* Control Lines */
|
|
||||||
#define CKV GPIO_NUM_26
|
|
||||||
#define STH GPIO_NUM_27
|
|
||||||
|
|
||||||
|
|
||||||
#define V4_LATCH_ENABLE GPIO_NUM_2
|
|
||||||
|
|
||||||
/* Edges */
|
|
||||||
#define CKH GPIO_NUM_15
|
|
||||||
|
|
||||||
#else
|
|
||||||
#define D7 GPIO_NUM_22
|
|
||||||
#define D6 GPIO_NUM_21
|
|
||||||
#define D5 GPIO_NUM_27
|
|
||||||
#define D4 GPIO_NUM_2
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47)
|
|
||||||
#define D3 GPIO_NUM_19
|
|
||||||
#else
|
|
||||||
#define D3 GPIO_NUM_0
|
|
||||||
#endif
|
|
||||||
#define D2 GPIO_NUM_4
|
|
||||||
#define D1 GPIO_NUM_32
|
|
||||||
#define D0 GPIO_NUM_33
|
|
||||||
|
|
||||||
#define CFG_DATA GPIO_NUM_23
|
|
||||||
#define CFG_CLK GPIO_NUM_18
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47)
|
|
||||||
#define CFG_STR GPIO_NUM_0
|
|
||||||
#else
|
|
||||||
#define CFG_STR GPIO_NUM_19
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Control Lines */
|
|
||||||
#define CKV GPIO_NUM_25
|
|
||||||
#define STH GPIO_NUM_26
|
|
||||||
|
|
||||||
#define V4_LATCH_ENABLE GPIO_NUM_15
|
|
||||||
|
|
||||||
/* Edges */
|
|
||||||
#define CKH GPIO_NUM_5
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void epd_base_init(uint32_t epd_row_width);
|
|
||||||
void epd_base_deinit();
|
|
||||||
void epd_poweron();
|
|
||||||
void epd_poweroff();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a draw cycle.
|
|
||||||
*/
|
|
||||||
void epd_start_frame();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End a draw cycle.
|
|
||||||
*/
|
|
||||||
void epd_end_frame();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits until all previously submitted data has been written.
|
|
||||||
* Then, the following operations are initiated:
|
|
||||||
*
|
|
||||||
* - Previously submitted data is latched to the output register.
|
|
||||||
* - The RMT peripheral is set up to pulse the vertical (gate) driver for
|
|
||||||
* `output_time_dus` / 10 microseconds.
|
|
||||||
* - The I2S peripheral starts transmission of the current buffer to
|
|
||||||
* the source driver.
|
|
||||||
* - The line buffers are switched.
|
|
||||||
*
|
|
||||||
* This sequence of operations allows for pipelining data preparation and
|
|
||||||
* transfer, reducing total refresh times.
|
|
||||||
*/
|
|
||||||
void IRAM_ATTR epd_output_row(uint32_t output_time_dus);
|
|
||||||
|
|
||||||
/** Skip a row without writing to it. */
|
|
||||||
void IRAM_ATTR epd_skip();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the currently writable line buffer.
|
|
||||||
*/
|
|
||||||
uint8_t IRAM_ATTR *epd_get_current_buffer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches front and back line buffer.
|
|
||||||
* If the switched-to line buffer is currently in use,
|
|
||||||
* this function blocks until transmission is done.
|
|
||||||
*/
|
|
||||||
void IRAM_ATTR epd_switch_buffer();
|
|
||||||
@ -1,344 +0,0 @@
|
|||||||
#include "epd_driver.h"
|
|
||||||
#include "epd_temperature.h"
|
|
||||||
|
|
||||||
#include "esp_assert.h"
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "esp_types.h"
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef _swap_int
|
|
||||||
#define _swap_int(a, b) \
|
|
||||||
{ \
|
|
||||||
int t = a; \
|
|
||||||
a = b; \
|
|
||||||
b = t; \
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
EpdRect epd_full_screen() {
|
|
||||||
EpdRect area = {.x = 0, .y = 0, .width = EPD_WIDTH, .height = EPD_HEIGHT};
|
|
||||||
return area;
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_clear() { epd_clear_area(epd_full_screen()); }
|
|
||||||
|
|
||||||
void epd_draw_hline(int x, int y, int length, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
int xx = x + i;
|
|
||||||
epd_draw_pixel(xx, y, color, framebuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_draw_vline(int x, int y, int length, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
int yy = y + i;
|
|
||||||
epd_draw_pixel(x, yy, color, framebuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_draw_pixel(int x, int y, uint8_t color, uint8_t *framebuffer) {
|
|
||||||
if (x < 0 || x >= EPD_WIDTH) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (y < 0 || y >= EPD_HEIGHT) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t *buf_ptr = &framebuffer[y * EPD_WIDTH / 2 + x / 2];
|
|
||||||
if (x % 2) {
|
|
||||||
*buf_ptr = (*buf_ptr & 0x0F) | (color & 0xF0);
|
|
||||||
} else {
|
|
||||||
*buf_ptr = (*buf_ptr & 0xF0) | (color >> 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_draw_circle(int x0, int y0, int r, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
int f = 1 - r;
|
|
||||||
int ddF_x = 1;
|
|
||||||
int ddF_y = -2 * r;
|
|
||||||
int x = 0;
|
|
||||||
int y = r;
|
|
||||||
|
|
||||||
epd_draw_pixel(x0, y0 + r, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0, y0 - r, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 + r, y0, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 - r, y0, color, framebuffer);
|
|
||||||
|
|
||||||
while (x < y) {
|
|
||||||
if (f >= 0) {
|
|
||||||
y--;
|
|
||||||
ddF_y += 2;
|
|
||||||
f += ddF_y;
|
|
||||||
}
|
|
||||||
x++;
|
|
||||||
ddF_x += 2;
|
|
||||||
f += ddF_x;
|
|
||||||
|
|
||||||
epd_draw_pixel(x0 + x, y0 + y, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 - x, y0 + y, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 + x, y0 - y, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 - x, y0 - y, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 + y, y0 + x, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 - y, y0 + x, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 + y, y0 - x, color, framebuffer);
|
|
||||||
epd_draw_pixel(x0 - y, y0 - x, color, framebuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_fill_circle(int x0, int y0, int r, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
epd_draw_vline(x0, y0 - r, 2 * r + 1, color, framebuffer);
|
|
||||||
epd_fill_circle_helper(x0, y0, r, 3, 0, color, framebuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_fill_circle_helper(int x0, int y0, int r, int corners, int delta,
|
|
||||||
uint8_t color, uint8_t *framebuffer) {
|
|
||||||
|
|
||||||
int f = 1 - r;
|
|
||||||
int ddF_x = 1;
|
|
||||||
int ddF_y = -2 * r;
|
|
||||||
int x = 0;
|
|
||||||
int y = r;
|
|
||||||
int px = x;
|
|
||||||
int py = y;
|
|
||||||
|
|
||||||
delta++; // Avoid some +1's in the loop
|
|
||||||
|
|
||||||
while (x < y) {
|
|
||||||
if (f >= 0) {
|
|
||||||
y--;
|
|
||||||
ddF_y += 2;
|
|
||||||
f += ddF_y;
|
|
||||||
}
|
|
||||||
x++;
|
|
||||||
ddF_x += 2;
|
|
||||||
f += ddF_x;
|
|
||||||
// These checks avoid double-drawing certain lines, important
|
|
||||||
// for the SSD1306 library which has an INVERT drawing mode.
|
|
||||||
if (x < (y + 1)) {
|
|
||||||
if (corners & 1)
|
|
||||||
epd_draw_vline(x0 + x, y0 - y, 2 * y + delta, color, framebuffer);
|
|
||||||
if (corners & 2)
|
|
||||||
epd_draw_vline(x0 - x, y0 - y, 2 * y + delta, color, framebuffer);
|
|
||||||
}
|
|
||||||
if (y != py) {
|
|
||||||
if (corners & 1)
|
|
||||||
epd_draw_vline(x0 + py, y0 - px, 2 * px + delta, color, framebuffer);
|
|
||||||
if (corners & 2)
|
|
||||||
epd_draw_vline(x0 - py, y0 - px, 2 * px + delta, color, framebuffer);
|
|
||||||
py = y;
|
|
||||||
}
|
|
||||||
px = x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_draw_rect(EpdRect rect, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
|
|
||||||
int x = rect.x; int y = rect.y; int w = rect.width; int h = rect.height;
|
|
||||||
epd_draw_hline(x, y, w, color, framebuffer);
|
|
||||||
epd_draw_hline(x, y + h - 1, w, color, framebuffer);
|
|
||||||
epd_draw_vline(x, y, h, color, framebuffer);
|
|
||||||
epd_draw_vline(x + w - 1, y, h, color, framebuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_fill_rect(EpdRect rect, uint8_t color, uint8_t *framebuffer) {
|
|
||||||
|
|
||||||
int x = rect.x; int y = rect.y; int w = rect.width; int h = rect.height;
|
|
||||||
for (int i = y; i < y + h; i++) {
|
|
||||||
epd_draw_hline(x, i, w, color, framebuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void epd_write_line(int x0, int y0, int x1, int y1, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
int steep = abs(y1 - y0) > abs(x1 - x0);
|
|
||||||
if (steep) {
|
|
||||||
_swap_int(x0, y0);
|
|
||||||
_swap_int(x1, y1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x0 > x1) {
|
|
||||||
_swap_int(x0, x1);
|
|
||||||
_swap_int(y0, y1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int dx, dy;
|
|
||||||
dx = x1 - x0;
|
|
||||||
dy = abs(y1 - y0);
|
|
||||||
|
|
||||||
int err = dx / 2;
|
|
||||||
int ystep;
|
|
||||||
|
|
||||||
if (y0 < y1) {
|
|
||||||
ystep = 1;
|
|
||||||
} else {
|
|
||||||
ystep = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; x0 <= x1; x0++) {
|
|
||||||
if (steep) {
|
|
||||||
epd_draw_pixel(y0, x0, color, framebuffer);
|
|
||||||
} else {
|
|
||||||
epd_draw_pixel(x0, y0, color, framebuffer);
|
|
||||||
}
|
|
||||||
err -= dy;
|
|
||||||
if (err < 0) {
|
|
||||||
y0 += ystep;
|
|
||||||
err += dx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_draw_line(int x0, int y0, int x1, int y1, uint8_t color,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
// Update in subclasses if desired!
|
|
||||||
if (x0 == x1) {
|
|
||||||
if (y0 > y1)
|
|
||||||
_swap_int(y0, y1);
|
|
||||||
epd_draw_vline(x0, y0, y1 - y0 + 1, color, framebuffer);
|
|
||||||
} else if (y0 == y1) {
|
|
||||||
if (x0 > x1)
|
|
||||||
_swap_int(x0, x1);
|
|
||||||
epd_draw_hline(x0, y0, x1 - x0 + 1, color, framebuffer);
|
|
||||||
} else {
|
|
||||||
epd_write_line(x0, y0, x1, y1, color, framebuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
|
|
||||||
uint8_t color, uint8_t *framebuffer) {
|
|
||||||
epd_draw_line(x0, y0, x1, y1, color, framebuffer);
|
|
||||||
epd_draw_line(x1, y1, x2, y2, color, framebuffer);
|
|
||||||
epd_draw_line(x2, y2, x0, y0, color, framebuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_fill_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
|
|
||||||
uint8_t color, uint8_t *framebuffer) {
|
|
||||||
|
|
||||||
int a, b, y, last;
|
|
||||||
|
|
||||||
// Sort coordinates by Y order (y2 >= y1 >= y0)
|
|
||||||
if (y0 > y1) {
|
|
||||||
_swap_int(y0, y1);
|
|
||||||
_swap_int(x0, x1);
|
|
||||||
}
|
|
||||||
if (y1 > y2) {
|
|
||||||
_swap_int(y2, y1);
|
|
||||||
_swap_int(x2, x1);
|
|
||||||
}
|
|
||||||
if (y0 > y1) {
|
|
||||||
_swap_int(y0, y1);
|
|
||||||
_swap_int(x0, x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
|
|
||||||
a = b = x0;
|
|
||||||
if (x1 < a)
|
|
||||||
a = x1;
|
|
||||||
else if (x1 > b)
|
|
||||||
b = x1;
|
|
||||||
if (x2 < a)
|
|
||||||
a = x2;
|
|
||||||
else if (x2 > b)
|
|
||||||
b = x2;
|
|
||||||
epd_draw_hline(a, y0, b - a + 1, color, framebuffer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0,
|
|
||||||
dx12 = x2 - x1, dy12 = y2 - y1;
|
|
||||||
int32_t sa = 0, sb = 0;
|
|
||||||
|
|
||||||
// For upper part of triangle, find scanline crossings for segments
|
|
||||||
// 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
|
|
||||||
// is included here (and second loop will be skipped, avoiding a /0
|
|
||||||
// error there), otherwise scanline y1 is skipped here and handled
|
|
||||||
// in the second loop...which also avoids a /0 error here if y0=y1
|
|
||||||
// (flat-topped triangle).
|
|
||||||
if (y1 == y2)
|
|
||||||
last = y1; // Include y1 scanline
|
|
||||||
else
|
|
||||||
last = y1 - 1; // Skip it
|
|
||||||
|
|
||||||
for (y = y0; y <= last; y++) {
|
|
||||||
a = x0 + sa / dy01;
|
|
||||||
b = x0 + sb / dy02;
|
|
||||||
sa += dx01;
|
|
||||||
sb += dx02;
|
|
||||||
/* longhand:
|
|
||||||
a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
|
|
||||||
b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
|
|
||||||
*/
|
|
||||||
if (a > b)
|
|
||||||
_swap_int(a, b);
|
|
||||||
epd_draw_hline(a, y, b - a + 1, color, framebuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For lower part of triangle, find scanline crossings for segments
|
|
||||||
// 0-2 and 1-2. This loop is skipped if y1=y2.
|
|
||||||
sa = (int32_t)dx12 * (y - y1);
|
|
||||||
sb = (int32_t)dx02 * (y - y0);
|
|
||||||
for (; y <= y2; y++) {
|
|
||||||
a = x1 + sa / dy12;
|
|
||||||
b = x0 + sb / dy02;
|
|
||||||
sa += dx12;
|
|
||||||
sb += dx02;
|
|
||||||
/* longhand:
|
|
||||||
a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
|
|
||||||
b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
|
|
||||||
*/
|
|
||||||
if (a > b)
|
|
||||||
_swap_int(a, b);
|
|
||||||
epd_draw_hline(a, y, b - a + 1, color, framebuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_copy_to_framebuffer(EpdRect image_area, const uint8_t *image_data,
|
|
||||||
uint8_t *framebuffer) {
|
|
||||||
|
|
||||||
assert(framebuffer != NULL);
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < image_area.width * image_area.height; i++) {
|
|
||||||
|
|
||||||
uint32_t value_index = i;
|
|
||||||
// for images of uneven width,
|
|
||||||
// consume an additional nibble per row.
|
|
||||||
if (image_area.width % 2) {
|
|
||||||
value_index += i / image_area.width;
|
|
||||||
}
|
|
||||||
uint8_t val = (value_index % 2) ? (image_data[value_index / 2] & 0xF0) >> 4
|
|
||||||
: image_data[value_index / 2] & 0x0F;
|
|
||||||
|
|
||||||
int xx = image_area.x + i % image_area.width;
|
|
||||||
if (xx < 0 || xx >= EPD_WIDTH) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int yy = image_area.y + i / image_area.width;
|
|
||||||
if (yy < 0 || yy >= EPD_HEIGHT) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
uint8_t *buf_ptr = &framebuffer[yy * EPD_WIDTH / 2 + xx / 2];
|
|
||||||
if (xx % 2) {
|
|
||||||
*buf_ptr = (*buf_ptr & 0x0F) | (val << 4);
|
|
||||||
} else {
|
|
||||||
*buf_ptr = (*buf_ptr & 0xF0) | val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError epd_draw_image(EpdRect area, const uint8_t *data, const EpdWaveform *waveform) {
|
|
||||||
int temperature = epd_ambient_temperature();
|
|
||||||
assert(waveform != NULL);
|
|
||||||
EpdRect no_crop = {
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = 0,
|
|
||||||
.height = 0,
|
|
||||||
};
|
|
||||||
return epd_draw_base(area, data, no_crop, EPD_MODE_DEFAULT, temperature, NULL, waveform);
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
#include "driver/adc.h"
|
|
||||||
#include "esp_adc_cal.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
/// Use GPIO 35
|
|
||||||
static const adc1_channel_t channel = ADC1_CHANNEL_7;
|
|
||||||
static esp_adc_cal_characteristics_t adc_chars;
|
|
||||||
|
|
||||||
#define NUMBER_OF_SAMPLES 100
|
|
||||||
|
|
||||||
void epd_temperature_init() {
|
|
||||||
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(
|
|
||||||
ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, &adc_chars);
|
|
||||||
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
|
|
||||||
ESP_LOGI("epd_temperature", "Characterized using Two Point Value\n");
|
|
||||||
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
|
|
||||||
ESP_LOGI("esp_temperature", "Characterized using eFuse Vref\n");
|
|
||||||
} else {
|
|
||||||
ESP_LOGI("esp_temperature", "Characterized using Default Vref\n");
|
|
||||||
}
|
|
||||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
|
||||||
adc1_config_channel_atten(channel, ADC_ATTEN_DB_6);
|
|
||||||
}
|
|
||||||
|
|
||||||
float epd_ambient_temperature() {
|
|
||||||
uint32_t value = 0;
|
|
||||||
for (int i = 0; i < NUMBER_OF_SAMPLES; i++) {
|
|
||||||
value += adc1_get_raw(channel);
|
|
||||||
}
|
|
||||||
value /= NUMBER_OF_SAMPLES;
|
|
||||||
// voltage in mV
|
|
||||||
float voltage = esp_adc_cal_raw_to_voltage(value, &adc_chars);
|
|
||||||
return (voltage - 500.0) / 10.0;
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the ADC for temperature measurement.
|
|
||||||
*/
|
|
||||||
void epd_temperature_init();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current ambient temperature in °C.
|
|
||||||
*/
|
|
||||||
float epd_ambient_temperature();
|
|
||||||
@ -1,375 +0,0 @@
|
|||||||
#include "epd_driver.h"
|
|
||||||
#include "esp_assert.h"
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#if ESP_IDF_VERSION < (4, 0, 0) || ARDUINO_ARCH_ESP32
|
|
||||||
#include "rom/miniz.h"
|
|
||||||
#else
|
|
||||||
#include "esp32/rom/miniz.h"
|
|
||||||
#endif
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t mask; /* char data will be bitwise AND with this */
|
|
||||||
uint8_t lead; /* start bytes of current char in utf-8 encoded character */
|
|
||||||
uint32_t beg; /* beginning of codepoint range */
|
|
||||||
uint32_t end; /* end of codepoint range */
|
|
||||||
int bits_stored; /* the number of bits from the codepoint that fits in char */
|
|
||||||
} utf_t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* UTF-8 decode inspired from rosetta code
|
|
||||||
* https://rosettacode.org/wiki/UTF-8_encode_and_decode#C
|
|
||||||
*/
|
|
||||||
static utf_t *utf[] = {
|
|
||||||
/* mask lead beg end bits */
|
|
||||||
[0] = &(utf_t){0b00111111, 0b10000000, 0, 0, 6},
|
|
||||||
[1] = &(utf_t){0b01111111, 0b00000000, 0000, 0177, 7},
|
|
||||||
[2] = &(utf_t){0b00011111, 0b11000000, 0200, 03777, 5},
|
|
||||||
[3] = &(utf_t){0b00001111, 0b11100000, 04000, 0177777, 4},
|
|
||||||
[4] = &(utf_t){0b00000111, 0b11110000, 0200000, 04177777, 3},
|
|
||||||
&(utf_t){0},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* static decompressor object for compressed fonts.
|
|
||||||
*/
|
|
||||||
static tinfl_decompressor decomp;
|
|
||||||
|
|
||||||
static inline int min(int x, int y) { return x < y ? x : y; }
|
|
||||||
static inline int max(int x, int y) { return x > y ? x : y; }
|
|
||||||
|
|
||||||
static int utf8_len(const uint8_t ch) {
|
|
||||||
int len = 0;
|
|
||||||
for (utf_t **u = utf; *u; ++u) {
|
|
||||||
if ((ch & ~(*u)->mask) == (*u)->lead) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++len;
|
|
||||||
}
|
|
||||||
if (len > 4) { /* Malformed leading byte */
|
|
||||||
assert("invalid unicode.");
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t next_cp(const uint8_t **string) {
|
|
||||||
if (**string == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int bytes = utf8_len(**string);
|
|
||||||
const uint8_t *chr = *string;
|
|
||||||
*string += bytes;
|
|
||||||
int shift = utf[0]->bits_stored * (bytes - 1);
|
|
||||||
uint32_t codep = (*chr++ & utf[bytes]->mask) << shift;
|
|
||||||
|
|
||||||
for (int i = 1; i < bytes; ++i, ++chr) {
|
|
||||||
shift -= utf[0]->bits_stored;
|
|
||||||
codep |= ((const uint8_t)*chr & utf[0]->mask) << shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
return codep;
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdFontProperties epd_font_properties_default() {
|
|
||||||
EpdFontProperties props = {
|
|
||||||
.fg_color = 0, .bg_color = 15, .fallback_glyph = 0, .flags = EPD_DRAW_ALIGN_LEFT};
|
|
||||||
return props;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdGlyph* epd_get_glyph(const EpdFont *font, uint32_t code_point) {
|
|
||||||
const EpdUnicodeInterval *intervals = font->intervals;
|
|
||||||
for (int i = 0; i < font->interval_count; i++) {
|
|
||||||
const EpdUnicodeInterval *interval = &intervals[i];
|
|
||||||
if (code_point >= interval->first && code_point <= interval->last) {
|
|
||||||
return &font->glyph[interval->offset + (code_point - interval->first)];
|
|
||||||
}
|
|
||||||
if (code_point < interval->first) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int uncompress(uint8_t *dest, uint32_t uncompressed_size, const uint8_t *source, uint32_t source_size) {
|
|
||||||
if (uncompressed_size == 0 || dest == NULL || source_size == 0 || source == NULL) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
tinfl_init(&decomp);
|
|
||||||
|
|
||||||
// we know everything will fit into the buffer.
|
|
||||||
tinfl_status decomp_status = tinfl_decompress(&decomp, source, &source_size, dest, dest, &uncompressed_size, TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
|
||||||
if (decomp_status != TINFL_STATUS_DONE) {
|
|
||||||
return decomp_status;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Draw a single character to a pre-allocated buffer.
|
|
||||||
*/
|
|
||||||
static enum EpdDrawError IRAM_ATTR draw_char(const EpdFont *font, uint8_t *buffer,
|
|
||||||
int *cursor_x, int cursor_y, uint16_t buf_width,
|
|
||||||
uint16_t buf_height, uint32_t cp,
|
|
||||||
const EpdFontProperties *props) {
|
|
||||||
|
|
||||||
assert(props != NULL);
|
|
||||||
|
|
||||||
const EpdGlyph *glyph = epd_get_glyph(font, cp);
|
|
||||||
if (!glyph) {
|
|
||||||
glyph = epd_get_glyph(font, props->fallback_glyph);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!glyph) {
|
|
||||||
return EPD_DRAW_GLYPH_FALLBACK_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t offset = glyph->data_offset;
|
|
||||||
uint8_t width = glyph->width, height = glyph->height;
|
|
||||||
int left = glyph->left;
|
|
||||||
|
|
||||||
int byte_width = (width / 2 + width % 2);
|
|
||||||
unsigned long bitmap_size = byte_width * height;
|
|
||||||
const uint8_t *bitmap = NULL;
|
|
||||||
if (font->compressed) {
|
|
||||||
uint8_t* tmp_bitmap = (uint8_t *)malloc(bitmap_size);
|
|
||||||
if (tmp_bitmap == NULL && bitmap_size) {
|
|
||||||
ESP_LOGE("font", "malloc failed.");
|
|
||||||
return EPD_DRAW_FAILED_ALLOC;
|
|
||||||
}
|
|
||||||
uncompress(tmp_bitmap, bitmap_size, &font->bitmap[offset],
|
|
||||||
glyph->compressed_size);
|
|
||||||
bitmap = tmp_bitmap;
|
|
||||||
} else {
|
|
||||||
bitmap = &font->bitmap[offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t color_lut[16];
|
|
||||||
for (int c = 0; c < 16; c++) {
|
|
||||||
int color_difference = (int)props->fg_color - (int)props->bg_color;
|
|
||||||
color_lut[c] = max(0, min(15, props->bg_color + c * color_difference / 15));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
int yy = cursor_y - glyph->top + y;
|
|
||||||
if (yy < 0 || yy >= buf_height) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int start_pos = *cursor_x + left;
|
|
||||||
bool byte_complete = start_pos % 2;
|
|
||||||
int x = max(0, -start_pos);
|
|
||||||
int max_x = min(start_pos + width, buf_width * 2);
|
|
||||||
for (int xx = start_pos; xx < max_x; xx++) {
|
|
||||||
uint32_t buf_pos = yy * buf_width + xx / 2;
|
|
||||||
uint8_t old = buffer[buf_pos];
|
|
||||||
uint8_t bm = bitmap[y * byte_width + x / 2];
|
|
||||||
if ((x & 1) == 0) {
|
|
||||||
bm = bm & 0xF;
|
|
||||||
} else {
|
|
||||||
bm = bm >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((xx & 1) == 0) {
|
|
||||||
buffer[buf_pos] = (old & 0xF0) | color_lut[bm];
|
|
||||||
} else {
|
|
||||||
buffer[buf_pos] = (old & 0x0F) | (color_lut[bm] << 4);
|
|
||||||
}
|
|
||||||
byte_complete = !byte_complete;
|
|
||||||
x++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (font->compressed) {
|
|
||||||
free((uint8_t*)bitmap);
|
|
||||||
}
|
|
||||||
*cursor_x += glyph->advance_x;
|
|
||||||
return EPD_DRAW_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Calculate the bounds of a character when drawn at (x, y), move the
|
|
||||||
* cursor (*x) forward, adjust the given bounds.
|
|
||||||
*/
|
|
||||||
static void get_char_bounds(const EpdFont *font, uint32_t cp, int *x, int *y,
|
|
||||||
int *minx, int *miny, int *maxx, int *maxy,
|
|
||||||
const EpdFontProperties *props) {
|
|
||||||
|
|
||||||
assert(props != NULL);
|
|
||||||
|
|
||||||
const EpdGlyph *glyph = epd_get_glyph(font, cp);
|
|
||||||
|
|
||||||
if (!glyph) {
|
|
||||||
glyph = epd_get_glyph(font, props->fallback_glyph);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!glyph) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x1 = *x + glyph->left, y1 = *y + glyph->top - glyph->height,
|
|
||||||
x2 = x1 + glyph->width, y2 = y1 + glyph->height;
|
|
||||||
|
|
||||||
// background needs to be taken into account
|
|
||||||
if (props->flags & EPD_DRAW_BACKGROUND) {
|
|
||||||
*minx = min(*x, min(*minx, x1));
|
|
||||||
*maxx = max(max(*x + glyph->advance_x, x2), *maxx);
|
|
||||||
*miny = min(*y + font->descender, min(*miny, y1));
|
|
||||||
*maxy = max(*y + font->ascender, max(*maxy, y2));
|
|
||||||
} else {
|
|
||||||
if (x1 < *minx)
|
|
||||||
*minx = x1;
|
|
||||||
if (y1 < *miny)
|
|
||||||
*miny = y1;
|
|
||||||
if (x2 > *maxx)
|
|
||||||
*maxx = x2;
|
|
||||||
if (y2 > *maxy)
|
|
||||||
*maxy = y2;
|
|
||||||
}
|
|
||||||
*x += glyph->advance_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_get_text_bounds(const EpdFont *font, const char *string,
|
|
||||||
const int *x, const int *y,
|
|
||||||
int *x1, int *y1, int *w, int *h,
|
|
||||||
const EpdFontProperties *properties) {
|
|
||||||
// FIXME: Does not respect alignment!
|
|
||||||
|
|
||||||
assert(properties != NULL);
|
|
||||||
EpdFontProperties props = *properties;
|
|
||||||
|
|
||||||
if (*string == '\0') {
|
|
||||||
*w = 0;
|
|
||||||
*h = 0;
|
|
||||||
*y1 = *y;
|
|
||||||
*x1 = *x;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int minx = 100000, miny = 100000, maxx = -1, maxy = -1;
|
|
||||||
int original_x = *x;
|
|
||||||
int temp_x = *x;
|
|
||||||
int temp_y = *y;
|
|
||||||
uint32_t c;
|
|
||||||
while ((c = next_cp((const uint8_t **)&string))) {
|
|
||||||
get_char_bounds(font, c, &temp_x, &temp_y, &minx, &miny, &maxx, &maxy, &props);
|
|
||||||
}
|
|
||||||
*x1 = min(original_x, minx);
|
|
||||||
*w = maxx - *x1;
|
|
||||||
*y1 = miny;
|
|
||||||
*h = maxy - miny;
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum EpdDrawError epd_write_line(
|
|
||||||
const EpdFont *font, const char *string, int *cursor_x,
|
|
||||||
int *cursor_y, uint8_t *framebuffer,
|
|
||||||
const EpdFontProperties *properties)
|
|
||||||
{
|
|
||||||
|
|
||||||
assert(framebuffer != NULL);
|
|
||||||
|
|
||||||
if (*string == '\0') {
|
|
||||||
return EPD_DRAW_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(properties != NULL);
|
|
||||||
EpdFontProperties props = *properties;
|
|
||||||
enum EpdFontFlags alignment_mask = EPD_DRAW_ALIGN_LEFT | EPD_DRAW_ALIGN_RIGHT | EPD_DRAW_ALIGN_CENTER;
|
|
||||||
enum EpdFontFlags alignment = props.flags & alignment_mask;
|
|
||||||
|
|
||||||
// alignments are mutually exclusive!
|
|
||||||
if ((alignment & (alignment - 1)) != 0) {
|
|
||||||
return EPD_DRAW_INVALID_FONT_FLAGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int x1 = 0, y1 = 0, w = 0, h = 0;
|
|
||||||
int tmp_cur_x = *cursor_x;
|
|
||||||
int tmp_cur_y = *cursor_y;
|
|
||||||
epd_get_text_bounds(font, string, &tmp_cur_x, &tmp_cur_y, &x1, &y1, &w, &h, &props);
|
|
||||||
|
|
||||||
// no printable characters
|
|
||||||
if (w < 0 || h < 0) {
|
|
||||||
return EPD_DRAW_NO_DRAWABLE_CHARACTERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
int baseline_height = *cursor_y - y1;
|
|
||||||
|
|
||||||
int buf_width = EPD_WIDTH / 2;
|
|
||||||
int buf_height = EPD_HEIGHT;
|
|
||||||
|
|
||||||
uint8_t* buffer = framebuffer;
|
|
||||||
int local_cursor_x = *cursor_x;
|
|
||||||
int local_cursor_y = *cursor_y;
|
|
||||||
uint32_t c;
|
|
||||||
|
|
||||||
int cursor_x_init = local_cursor_x;
|
|
||||||
int cursor_y_init = local_cursor_y;
|
|
||||||
|
|
||||||
switch (alignment) {
|
|
||||||
case EPD_DRAW_ALIGN_LEFT: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EPD_DRAW_ALIGN_CENTER: {
|
|
||||||
local_cursor_x -= w / 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EPD_DRAW_ALIGN_RIGHT: {
|
|
||||||
local_cursor_x -= w;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t bg = props.bg_color;
|
|
||||||
if (props.flags & EPD_DRAW_BACKGROUND) {
|
|
||||||
for (int l = local_cursor_y - font->ascender;
|
|
||||||
l < local_cursor_y - font->descender; l++) {
|
|
||||||
epd_draw_hline(local_cursor_x, l, w, bg << 4, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enum EpdDrawError err = EPD_DRAW_SUCCESS;
|
|
||||||
while ((c = next_cp((const uint8_t **)&string))) {
|
|
||||||
err |= draw_char(font, buffer, &local_cursor_x, local_cursor_y, buf_width,
|
|
||||||
buf_height, c, &props);
|
|
||||||
}
|
|
||||||
|
|
||||||
*cursor_x += local_cursor_x - cursor_x_init;
|
|
||||||
*cursor_y += local_cursor_y - cursor_y_init;
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError epd_write_default(const EpdFont *font, const char *string, int *cursor_x,
|
|
||||||
int *cursor_y, uint8_t *framebuffer) {
|
|
||||||
const EpdFontProperties props = epd_font_properties_default();
|
|
||||||
return epd_write_string(font, string, cursor_x, cursor_y, framebuffer, &props);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError epd_write_string(
|
|
||||||
const EpdFont *font, const char *string, int *cursor_x,
|
|
||||||
int *cursor_y, uint8_t *framebuffer,
|
|
||||||
const EpdFontProperties *properties
|
|
||||||
) {
|
|
||||||
char *token, *newstring, *tofree;
|
|
||||||
if (string == NULL) {
|
|
||||||
ESP_LOGE("font.c", "cannot draw a NULL string!");
|
|
||||||
return EPD_DRAW_STRING_INVALID;
|
|
||||||
}
|
|
||||||
tofree = newstring = strdup(string);
|
|
||||||
if (newstring == NULL) {
|
|
||||||
ESP_LOGE("font.c", "cannot allocate string copy!");
|
|
||||||
return EPD_DRAW_FAILED_ALLOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError err = EPD_DRAW_SUCCESS;
|
|
||||||
// taken from the strsep manpage
|
|
||||||
int line_start = *cursor_x;
|
|
||||||
while ((token = strsep(&newstring, "\n")) != NULL) {
|
|
||||||
*cursor_x = line_start;
|
|
||||||
err |= epd_write_line(font, token, cursor_x, cursor_y, framebuffer, properties);
|
|
||||||
*cursor_y += font->advance_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(tofree);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* High-level API implementation for epdiy.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "epd_highlevel.h"
|
|
||||||
#include <assert.h>
|
|
||||||
#include <esp_types.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <esp_heap_caps.h>
|
|
||||||
#include <esp_log.h>
|
|
||||||
|
|
||||||
static bool already_initialized = 0;
|
|
||||||
|
|
||||||
const static int fb_size = EPD_WIDTH / 2 * EPD_HEIGHT;
|
|
||||||
|
|
||||||
EpdiyHighlevelState epd_hl_init(const EpdWaveform* waveform) {
|
|
||||||
assert(!already_initialized);
|
|
||||||
assert(waveform != NULL);
|
|
||||||
|
|
||||||
EpdiyHighlevelState state;
|
|
||||||
state.front_fb = heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM);
|
|
||||||
assert(state.front_fb != NULL);
|
|
||||||
state.back_fb = heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM);
|
|
||||||
assert(state.back_fb != NULL);
|
|
||||||
state.difference_fb = heap_caps_malloc(2 * fb_size, MALLOC_CAP_SPIRAM);
|
|
||||||
assert(state.difference_fb != NULL);
|
|
||||||
state.dirty_lines = malloc(EPD_HEIGHT * sizeof(bool));
|
|
||||||
assert(state.dirty_lines != NULL);
|
|
||||||
state.waveform = waveform;
|
|
||||||
|
|
||||||
memset(state.front_fb, 0xFF, fb_size);
|
|
||||||
memset(state.back_fb, 0xFF, fb_size);
|
|
||||||
|
|
||||||
already_initialized = true;
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t* epd_hl_get_framebuffer(EpdiyHighlevelState* state) {
|
|
||||||
assert(state != NULL);
|
|
||||||
return state->front_fb;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError epd_hl_update_screen(EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature) {
|
|
||||||
return epd_hl_update_area(state, mode, temperature, epd_full_screen());
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError epd_hl_update_area(EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area) {
|
|
||||||
assert(state != NULL);
|
|
||||||
|
|
||||||
bool previously_white = false;
|
|
||||||
bool previously_black = false;
|
|
||||||
|
|
||||||
//FIXME: use crop information here, if available
|
|
||||||
EpdRect diff_area = epd_difference_image_cropped(
|
|
||||||
state->front_fb,
|
|
||||||
state->back_fb,
|
|
||||||
area,
|
|
||||||
state->difference_fb,
|
|
||||||
state->dirty_lines,
|
|
||||||
&previously_white,
|
|
||||||
&previously_black
|
|
||||||
);
|
|
||||||
|
|
||||||
if (diff_area.height == 0 || diff_area.width == 0) {
|
|
||||||
return EPD_DRAW_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError err;
|
|
||||||
if (previously_white) {
|
|
||||||
err = epd_draw_base(epd_full_screen(), state->front_fb, diff_area, MODE_PACKING_2PPB | PREVIOUSLY_WHITE | mode, temperature, state->dirty_lines, state->waveform);
|
|
||||||
} else if (previously_black) {
|
|
||||||
err = epd_draw_base(epd_full_screen(), state->front_fb, diff_area, MODE_PACKING_2PPB | PREVIOUSLY_BLACK | mode, temperature, state->dirty_lines, state->waveform);
|
|
||||||
} else {
|
|
||||||
err = epd_draw_base(epd_full_screen(), state->difference_fb, diff_area, MODE_PACKING_1PPB_DIFFERENCE | mode, temperature, state->dirty_lines, state->waveform);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int l=diff_area.y; l < diff_area.y + diff_area.height; l++) {
|
|
||||||
if (state->dirty_lines[l]) {
|
|
||||||
memcpy(
|
|
||||||
state->back_fb + EPD_WIDTH / 2 * l,
|
|
||||||
state->front_fb + EPD_WIDTH / 2 * l,
|
|
||||||
EPD_WIDTH / 2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void epd_hl_set_all_white(EpdiyHighlevelState* state) {
|
|
||||||
assert(state != NULL);
|
|
||||||
memset(state->front_fb, 0xFF, fb_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_fullclear(EpdiyHighlevelState* state, int temperature) {
|
|
||||||
assert(state != NULL);
|
|
||||||
epd_hl_set_all_white(state);
|
|
||||||
enum EpdDrawError err = epd_hl_update_screen(state, MODE_GC16, temperature);
|
|
||||||
assert(err == EPD_DRAW_SUCCESS);
|
|
||||||
epd_clear();
|
|
||||||
}
|
|
||||||
@ -1,262 +0,0 @@
|
|||||||
#include "i2s_data_bus.h"
|
|
||||||
#include "driver/periph_ctrl.h"
|
|
||||||
#if ESP_IDF_VERSION < (4, 0, 0) || ARDUINO_ARCH_ESP32
|
|
||||||
#include "rom/lldesc.h"
|
|
||||||
#else
|
|
||||||
#include "esp32/rom/lldesc.h"
|
|
||||||
#endif
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "soc/i2s_reg.h"
|
|
||||||
#include "soc/i2s_struct.h"
|
|
||||||
#include "soc/rtc.h"
|
|
||||||
|
|
||||||
/// DMA descriptors for front and back line buffer.
|
|
||||||
/// We use two buffers, so one can be filled while the other
|
|
||||||
/// is transmitted.
|
|
||||||
typedef struct {
|
|
||||||
volatile lldesc_t *dma_desc_a;
|
|
||||||
volatile lldesc_t *dma_desc_b;
|
|
||||||
|
|
||||||
/// Front and back line buffer.
|
|
||||||
uint8_t *buf_a;
|
|
||||||
uint8_t *buf_b;
|
|
||||||
} i2s_parallel_state_t;
|
|
||||||
|
|
||||||
/// Indicates which line buffer is currently back / front.
|
|
||||||
static int current_buffer = 0;
|
|
||||||
|
|
||||||
/// The I2S state instance.
|
|
||||||
static i2s_parallel_state_t i2s_state;
|
|
||||||
|
|
||||||
static intr_handle_t gI2S_intr_handle = NULL;
|
|
||||||
|
|
||||||
/// Indicates the device has finished its transmission and is ready again.
|
|
||||||
static volatile bool output_done = true;
|
|
||||||
/// The start pulse pin extracted from the configuration for use in the "done"
|
|
||||||
/// interrupt.
|
|
||||||
static gpio_num_t start_pulse_pin;
|
|
||||||
|
|
||||||
/// Initializes a DMA descriptor.
|
|
||||||
static void fill_dma_desc(volatile lldesc_t *dmadesc, uint8_t *buf,
|
|
||||||
i2s_bus_config *cfg) {
|
|
||||||
dmadesc->size = cfg->epd_row_width / 4;
|
|
||||||
dmadesc->length = cfg->epd_row_width / 4;
|
|
||||||
dmadesc->buf = buf;
|
|
||||||
dmadesc->eof = 1;
|
|
||||||
dmadesc->sosf = 1;
|
|
||||||
dmadesc->owner = 1;
|
|
||||||
dmadesc->qe.stqe_next = 0;
|
|
||||||
dmadesc->offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Address of the currently front DMA descriptor,
|
|
||||||
/// which uses only the lower 20bits (according to TRM)
|
|
||||||
uint32_t dma_desc_addr() {
|
|
||||||
return (uint32_t)(current_buffer ? i2s_state.dma_desc_a
|
|
||||||
: i2s_state.dma_desc_b) &
|
|
||||||
0x000FFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up a GPIO as output and route it to a signal.
|
|
||||||
static void gpio_setup_out(int gpio, int sig, bool invert) {
|
|
||||||
if (gpio == -1)
|
|
||||||
return;
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
|
|
||||||
gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
|
|
||||||
gpio_matrix_out(gpio, sig, invert, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets "Start Pulse" signal when the current row output is done.
|
|
||||||
static void IRAM_ATTR i2s_int_hdl(void *arg) {
|
|
||||||
i2s_dev_t *dev = &I2S1;
|
|
||||||
if (dev->int_st.out_done) {
|
|
||||||
//gpio_set_level(start_pulse_pin, 1);
|
|
||||||
//gpio_set_level(GPIO_NUM_26, 0);
|
|
||||||
output_done = true;
|
|
||||||
}
|
|
||||||
// Clear the interrupt. Otherwise, the whole device would hang.
|
|
||||||
dev->int_clr.val = dev->int_raw.val;
|
|
||||||
}
|
|
||||||
|
|
||||||
volatile uint8_t IRAM_ATTR *i2s_get_current_buffer() {
|
|
||||||
return current_buffer ? i2s_state.dma_desc_a->buf : i2s_state.dma_desc_b->buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IRAM_ATTR i2s_is_busy() {
|
|
||||||
// DMA and FIFO must be done
|
|
||||||
return !output_done || !I2S1.state.tx_idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR i2s_switch_buffer() {
|
|
||||||
// either device is done transmitting or the switch must be away from the
|
|
||||||
// buffer currently used by the DMA engine.
|
|
||||||
while (i2s_is_busy() && dma_desc_addr() != I2S1.out_link.addr) {
|
|
||||||
};
|
|
||||||
current_buffer = !current_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR i2s_start_line_output() {
|
|
||||||
output_done = false;
|
|
||||||
|
|
||||||
i2s_dev_t *dev = &I2S1;
|
|
||||||
dev->conf.tx_start = 0;
|
|
||||||
dev->conf.tx_reset = 1;
|
|
||||||
dev->conf.tx_fifo_reset = 1;
|
|
||||||
dev->conf.rx_fifo_reset = 1;
|
|
||||||
dev->conf.tx_reset = 0;
|
|
||||||
dev->conf.tx_fifo_reset = 0;
|
|
||||||
dev->conf.rx_fifo_reset = 0;
|
|
||||||
dev->out_link.addr = dma_desc_addr();
|
|
||||||
dev->out_link.start = 1;
|
|
||||||
|
|
||||||
// sth is pulled up through peripheral interrupt
|
|
||||||
// This is timing-critical!
|
|
||||||
gpio_set_level(start_pulse_pin, 0);
|
|
||||||
dev->conf.tx_start = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void i2s_bus_init(i2s_bus_config *cfg) {
|
|
||||||
// TODO: Why?
|
|
||||||
gpio_num_t I2S_GPIO_BUS[] = {cfg->data_6, cfg->data_7, cfg->data_4,
|
|
||||||
cfg->data_5, cfg->data_2, cfg->data_3,
|
|
||||||
cfg->data_0, cfg->data_1};
|
|
||||||
|
|
||||||
gpio_set_direction(cfg->start_pulse, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_set_level(cfg->start_pulse, 1);
|
|
||||||
// store pin in global variable for use in interrupt.
|
|
||||||
start_pulse_pin = cfg->start_pulse;
|
|
||||||
|
|
||||||
// Use I2S1 with no signal offset (for some reason the offset seems to be
|
|
||||||
// needed in 16-bit mode, but not in 8 bit mode.
|
|
||||||
int signal_base = I2S1O_DATA_OUT0_IDX;
|
|
||||||
|
|
||||||
// Setup and route GPIOS
|
|
||||||
for (int x = 0; x < 8; x++) {
|
|
||||||
gpio_setup_out(I2S_GPIO_BUS[x], signal_base + x, false);
|
|
||||||
}
|
|
||||||
// Invert word select signal
|
|
||||||
gpio_setup_out(cfg->clock, I2S1O_WS_OUT_IDX, true);
|
|
||||||
|
|
||||||
periph_module_enable(PERIPH_I2S1_MODULE);
|
|
||||||
|
|
||||||
i2s_dev_t *dev = &I2S1;
|
|
||||||
|
|
||||||
// Initialize device
|
|
||||||
dev->conf.tx_reset = 1;
|
|
||||||
dev->conf.tx_reset = 0;
|
|
||||||
|
|
||||||
// Reset DMA
|
|
||||||
dev->lc_conf.in_rst = 1;
|
|
||||||
dev->lc_conf.in_rst = 0;
|
|
||||||
dev->lc_conf.out_rst = 1;
|
|
||||||
dev->lc_conf.out_rst = 0;
|
|
||||||
|
|
||||||
// Setup I2S config. See section 12 of Technical Reference Manual
|
|
||||||
// Enable LCD mode
|
|
||||||
dev->conf2.val = 0;
|
|
||||||
dev->conf2.lcd_en = 1;
|
|
||||||
|
|
||||||
// Enable FRAME1-Mode (See technical reference manual)
|
|
||||||
dev->conf2.lcd_tx_wrx2_en = 1;
|
|
||||||
dev->conf2.lcd_tx_sdx2_en = 0;
|
|
||||||
|
|
||||||
// Set to 8 bit parallel output
|
|
||||||
dev->sample_rate_conf.val = 0;
|
|
||||||
dev->sample_rate_conf.tx_bits_mod = 8;
|
|
||||||
|
|
||||||
// Half speed of bit clock in LCD mode.
|
|
||||||
// (Smallest possible divider according to the spec).
|
|
||||||
dev->sample_rate_conf.tx_bck_div_num = 2;
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ)
|
|
||||||
// Initialize Audio Clock (APLL) for 120 Mhz.
|
|
||||||
rtc_clk_apll_enable(1, 0, 0, 8, 0);
|
|
||||||
#else
|
|
||||||
// Initialize Audio Clock (APLL) for 100 Mhz.
|
|
||||||
rtc_clk_apll_enable(1, 0, 0, 8, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set Audio Clock Dividers
|
|
||||||
dev->clkm_conf.val = 0;
|
|
||||||
dev->clkm_conf.clka_en = 1;
|
|
||||||
dev->clkm_conf.clkm_div_a = 1;
|
|
||||||
dev->clkm_conf.clkm_div_b = 0;
|
|
||||||
// 2 is the smallest possible divider according to the spec.
|
|
||||||
dev->clkm_conf.clkm_div_num = 2;
|
|
||||||
|
|
||||||
// Set up FIFO
|
|
||||||
dev->fifo_conf.val = 0;
|
|
||||||
dev->fifo_conf.tx_fifo_mod_force_en = 1;
|
|
||||||
dev->fifo_conf.tx_fifo_mod = 1;
|
|
||||||
dev->fifo_conf.tx_data_num = 32;
|
|
||||||
dev->fifo_conf.dscr_en = 1;
|
|
||||||
|
|
||||||
// Stop after transmission complete
|
|
||||||
dev->conf1.val = 0;
|
|
||||||
dev->conf1.tx_stop_en = 1;
|
|
||||||
dev->conf1.tx_pcm_bypass = 1;
|
|
||||||
|
|
||||||
// Configure TX channel
|
|
||||||
dev->conf_chan.val = 0;
|
|
||||||
dev->conf_chan.tx_chan_mod = 1;
|
|
||||||
dev->conf.tx_right_first = 1;
|
|
||||||
|
|
||||||
dev->timing.val = 0;
|
|
||||||
|
|
||||||
// Allocate DMA descriptors
|
|
||||||
i2s_state.buf_a = heap_caps_malloc(cfg->epd_row_width / 4, MALLOC_CAP_DMA);
|
|
||||||
i2s_state.buf_b = heap_caps_malloc(cfg->epd_row_width / 4, MALLOC_CAP_DMA);
|
|
||||||
i2s_state.dma_desc_a = heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA);
|
|
||||||
i2s_state.dma_desc_b = heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA);
|
|
||||||
|
|
||||||
// and fill them
|
|
||||||
fill_dma_desc(i2s_state.dma_desc_a, i2s_state.buf_a, cfg);
|
|
||||||
fill_dma_desc(i2s_state.dma_desc_b, i2s_state.buf_b, cfg);
|
|
||||||
|
|
||||||
// enable "done" interrupt
|
|
||||||
SET_PERI_REG_BITS(I2S_INT_ENA_REG(1), I2S_OUT_DONE_INT_ENA_V, 1,
|
|
||||||
I2S_OUT_DONE_INT_ENA_S);
|
|
||||||
// register interrupt
|
|
||||||
esp_intr_alloc(ETS_I2S1_INTR_SOURCE, 0, i2s_int_hdl, 0, &gI2S_intr_handle);
|
|
||||||
|
|
||||||
// Reset FIFO/DMA
|
|
||||||
dev->lc_conf.in_rst = 1;
|
|
||||||
dev->lc_conf.out_rst = 1;
|
|
||||||
dev->lc_conf.ahbm_rst = 1;
|
|
||||||
dev->lc_conf.ahbm_fifo_rst = 1;
|
|
||||||
dev->lc_conf.in_rst = 0;
|
|
||||||
dev->lc_conf.out_rst = 0;
|
|
||||||
dev->lc_conf.ahbm_rst = 0;
|
|
||||||
dev->lc_conf.ahbm_fifo_rst = 0;
|
|
||||||
dev->conf.tx_reset = 1;
|
|
||||||
dev->conf.tx_fifo_reset = 1;
|
|
||||||
dev->conf.rx_fifo_reset = 1;
|
|
||||||
dev->conf.tx_reset = 0;
|
|
||||||
dev->conf.tx_fifo_reset = 0;
|
|
||||||
dev->conf.rx_fifo_reset = 0;
|
|
||||||
|
|
||||||
// Start dma on front buffer
|
|
||||||
dev->lc_conf.val =
|
|
||||||
I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN;
|
|
||||||
dev->out_link.addr = ((uint32_t)(i2s_state.dma_desc_a));
|
|
||||||
dev->out_link.start = 1;
|
|
||||||
|
|
||||||
dev->int_clr.val = dev->int_raw.val;
|
|
||||||
|
|
||||||
dev->int_ena.val = 0;
|
|
||||||
dev->int_ena.out_done = 1;
|
|
||||||
|
|
||||||
dev->conf.tx_start = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void i2s_deinit() {
|
|
||||||
esp_intr_free(gI2S_intr_handle);
|
|
||||||
|
|
||||||
free(i2s_state.buf_a);
|
|
||||||
free(i2s_state.buf_b);
|
|
||||||
free((void *)i2s_state.dma_desc_a);
|
|
||||||
free((void *)i2s_state.dma_desc_b);
|
|
||||||
|
|
||||||
rtc_clk_apll_enable(0, 0, 0, 8, 0);
|
|
||||||
periph_module_disable(PERIPH_I2S1_MODULE);
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* Implements a 8bit parallel interface to transmit pixel
|
|
||||||
* data to the display, based on the I2S peripheral.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_attr.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* I2S bus configuration parameters.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
/// GPIO numbers of the parallel bus pins.
|
|
||||||
gpio_num_t data_0;
|
|
||||||
gpio_num_t data_1;
|
|
||||||
gpio_num_t data_2;
|
|
||||||
gpio_num_t data_3;
|
|
||||||
gpio_num_t data_4;
|
|
||||||
gpio_num_t data_5;
|
|
||||||
gpio_num_t data_6;
|
|
||||||
gpio_num_t data_7;
|
|
||||||
|
|
||||||
/// Data clock pin.
|
|
||||||
gpio_num_t clock;
|
|
||||||
|
|
||||||
/// "Start Pulse", enabling data input on the slave device (active low)
|
|
||||||
gpio_num_t start_pulse;
|
|
||||||
|
|
||||||
// Width of a display row in pixels.
|
|
||||||
uint32_t epd_row_width;
|
|
||||||
} i2s_bus_config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the I2S data bus for communication
|
|
||||||
* with a 8bit parallel display interface.
|
|
||||||
*/
|
|
||||||
void i2s_bus_init(i2s_bus_config *cfg);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the currently writable line buffer.
|
|
||||||
*/
|
|
||||||
volatile uint8_t IRAM_ATTR *i2s_get_current_buffer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches front and back line buffer.
|
|
||||||
* If the switched-to line buffer is currently in use,
|
|
||||||
* this function blocks until transmission is done.
|
|
||||||
*/
|
|
||||||
void IRAM_ATTR i2s_switch_buffer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start transmission of the current back buffer.
|
|
||||||
*/
|
|
||||||
void IRAM_ATTR i2s_start_line_output();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there is an ongoing transmission.
|
|
||||||
*/
|
|
||||||
bool IRAM_ATTR i2s_is_busy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Give up allocated resources.
|
|
||||||
*/
|
|
||||||
void i2s_deinit();
|
|
||||||
@ -1,471 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file epd_driver.h
|
|
||||||
* A driver library for drawing to an EPD.
|
|
||||||
*/
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CONFIG_EPD_DISPLAY_TYPE_ED047TC1
|
|
||||||
#define CONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "esp_attr.h"
|
|
||||||
#include "epd_internals.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/// An area on the display.
|
|
||||||
typedef struct {
|
|
||||||
/// Horizontal position.
|
|
||||||
int x;
|
|
||||||
/// Vertical position.
|
|
||||||
int y;
|
|
||||||
/// Area / image width, must be positive.
|
|
||||||
int width;
|
|
||||||
/// Area / image height, must be positive.
|
|
||||||
int height;
|
|
||||||
} EpdRect;
|
|
||||||
|
|
||||||
/// Possible failures when drawing.
|
|
||||||
enum EpdDrawError {
|
|
||||||
EPD_DRAW_SUCCESS = 0x0,
|
|
||||||
/// No valid framebuffer packing mode was specified.
|
|
||||||
EPD_DRAW_INVALID_PACKING_MODE = 0x1,
|
|
||||||
|
|
||||||
/// No lookup table implementation for this mode / packing.
|
|
||||||
EPD_DRAW_LOOKUP_NOT_IMPLEMENTED = 0x2,
|
|
||||||
|
|
||||||
/// The string to draw is invalid.
|
|
||||||
EPD_DRAW_STRING_INVALID = 0x4,
|
|
||||||
|
|
||||||
/// The string was not empty, but no characters where drawable.
|
|
||||||
EPD_DRAW_NO_DRAWABLE_CHARACTERS = 0x8,
|
|
||||||
|
|
||||||
/// Allocation failed
|
|
||||||
EPD_DRAW_FAILED_ALLOC = 0x10,
|
|
||||||
|
|
||||||
/// A glyph could not be drawn, and not fallback was present.
|
|
||||||
EPD_DRAW_GLYPH_FALLBACK_FAILED = 0x20,
|
|
||||||
|
|
||||||
/// The specified crop area is invalid.
|
|
||||||
EPD_DRAW_INVALID_CROP = 0x40,
|
|
||||||
|
|
||||||
/// No such mode is available with the current waveform.
|
|
||||||
EPD_DRAW_MODE_NOT_FOUND = 0x80,
|
|
||||||
|
|
||||||
/// The waveform info file contains no applicable temperature range.
|
|
||||||
EPD_DRAW_NO_PHASES_AVAILABLE = 0x100,
|
|
||||||
|
|
||||||
/// An invalid combination of font flags was used.
|
|
||||||
EPD_DRAW_INVALID_FONT_FLAGS = 0x200,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Global EPD driver options.
|
|
||||||
enum EpdInitOptions {
|
|
||||||
/// Use the default options.
|
|
||||||
EPD_OPTIONS_DEFAULT = 0,
|
|
||||||
/// Use a small look-up table of 1024 bytes.
|
|
||||||
/// The EPD driver will use less space, but performance may be worse.
|
|
||||||
EPD_LUT_1K = 1,
|
|
||||||
/// Use a 64K lookup table. (default)
|
|
||||||
/// Best performance, but permanently occupies a 64k block of internal memory.
|
|
||||||
EPD_LUT_64K = 2,
|
|
||||||
|
|
||||||
/// Use a small feed queue of 8 display lines.
|
|
||||||
/// This uses less memory, but may impact performance.
|
|
||||||
EPD_FEED_QUEUE_8 = 4,
|
|
||||||
/// Use a feed queue of 32 display lines. (default)
|
|
||||||
/// Best performance, but larger memory footprint.
|
|
||||||
EPD_FEED_QUEUE_32 = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The image drawing mode.
|
|
||||||
enum EpdDrawMode {
|
|
||||||
/// An init waveform.
|
|
||||||
/// This is currently unused, use `epd_clear()` instead.
|
|
||||||
MODE_INIT = 0x0,
|
|
||||||
/// Direct Update: Go from any color to black for white only.
|
|
||||||
MODE_DU = 0x1,
|
|
||||||
/// Go from any grayscale value to another with a flashing update.
|
|
||||||
MODE_GC16 = 0x2,
|
|
||||||
/// Faster version of `MODE_GC16`.
|
|
||||||
/// Not available with default epdiy waveforms.
|
|
||||||
MODE_GC16_FAST = 0x3,
|
|
||||||
/// Animation Mode: Fast, monochrom updates.
|
|
||||||
/// Not available with default epdiy waveforms.
|
|
||||||
MODE_A2 = 0x4,
|
|
||||||
/// Go from any grayscale value to another with a non-flashing update.
|
|
||||||
MODE_GL16 = 0x5,
|
|
||||||
/// Faster version of `MODE_GL16`.
|
|
||||||
/// Not available with default epdiy waveforms.
|
|
||||||
MODE_GL16_FAST = 0x6,
|
|
||||||
/// A 4-grayscale version of `MODE_DU`.
|
|
||||||
/// Not available with default epdiy waveforms.
|
|
||||||
MODE_DU4 = 0x7,
|
|
||||||
/// Arbitrary transitions for 4 grayscale values.
|
|
||||||
/// Not available with default epdiy waveforms.
|
|
||||||
MODE_GL4 = 0xA,
|
|
||||||
/// Not available with default epdiy waveforms.
|
|
||||||
MODE_GL16_INV = 0xB,
|
|
||||||
|
|
||||||
/// Go from a white screen to arbitrary grayscale, quickly.
|
|
||||||
/// Exclusively available with epdiy waveforms.
|
|
||||||
MODE_EPDIY_WHITE_TO_GL16 = 0x10,
|
|
||||||
/// Go from a black screen to arbitrary grayscale, quickly.
|
|
||||||
/// Exclusively available with epdiy waveforms.
|
|
||||||
MODE_EPDIY_BLACK_TO_GL16 = 0x11,
|
|
||||||
|
|
||||||
/// Monochrome mode. Only supported with 1bpp buffers.
|
|
||||||
MODE_EPDIY_MONOCHROME = 0x20,
|
|
||||||
|
|
||||||
MODE_UNKNOWN_WAVEFORM = 0x3F,
|
|
||||||
|
|
||||||
// Framebuffer packing modes
|
|
||||||
/// 1 bit-per-pixel framebuffer with 0 = black, 1 = white.
|
|
||||||
/// MSB is left is the leftmost pixel, LSB the rightmost pixel.
|
|
||||||
MODE_PACKING_8PPB = 0x40,
|
|
||||||
/// 4 bit-per pixel framebuffer with 0x0 = black, 0xF = white.
|
|
||||||
/// The upper nibble corresponds to the left pixel.
|
|
||||||
/// A byte cannot wrap over multiple rows, images of uneven width
|
|
||||||
/// must add a padding nibble per line.
|
|
||||||
MODE_PACKING_2PPB = 0x80,
|
|
||||||
/// A difference image with one pixel per byte.
|
|
||||||
/// The upper nibble marks the "from" color,
|
|
||||||
/// the lower nibble the "to" color.
|
|
||||||
MODE_PACKING_1PPB_DIFFERENCE = 0x100,
|
|
||||||
// reserver for 4PPB mode
|
|
||||||
|
|
||||||
/// Assert that the display has a uniform color, e.g. after initialization.
|
|
||||||
/// If `MODE_PACKING_2PPB` is specified, a optimized output calculation can be used.
|
|
||||||
/// Draw on a white background
|
|
||||||
PREVIOUSLY_WHITE = 0x200,
|
|
||||||
/// See `PREVIOUSLY_WHITE`.
|
|
||||||
/// Draw on a black background
|
|
||||||
PREVIOUSLY_BLACK = 0x400,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The default draw mode (non-flashy refresh, whith previously white screen).
|
|
||||||
#define EPD_MODE_DEFAULT (MODE_GL16 | PREVIOUSLY_WHITE)
|
|
||||||
|
|
||||||
/// Font drawing flags
|
|
||||||
enum EpdFontFlags {
|
|
||||||
/// Draw a background.
|
|
||||||
///
|
|
||||||
/// Take the background into account
|
|
||||||
/// when calculating the size.
|
|
||||||
EPD_DRAW_BACKGROUND = 0x1,
|
|
||||||
|
|
||||||
/// Left-Align lines
|
|
||||||
EPD_DRAW_ALIGN_LEFT = 0x2,
|
|
||||||
/// Right-align lines
|
|
||||||
EPD_DRAW_ALIGN_RIGHT = 0x4,
|
|
||||||
/// Center-align lines
|
|
||||||
EPD_DRAW_ALIGN_CENTER = 0x8,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Font properties.
|
|
||||||
typedef struct {
|
|
||||||
/// Foreground color
|
|
||||||
uint8_t fg_color : 4;
|
|
||||||
/// Background color
|
|
||||||
uint8_t bg_color : 4;
|
|
||||||
/// Use the glyph for this codepoint for missing glyphs.
|
|
||||||
uint32_t fallback_glyph;
|
|
||||||
/// Additional flags, reserved for future use
|
|
||||||
enum EpdFontFlags flags;
|
|
||||||
} EpdFontProperties;
|
|
||||||
|
|
||||||
/** Initialize the ePaper display */
|
|
||||||
void epd_init(enum EpdInitOptions options);
|
|
||||||
|
|
||||||
/** Deinit the ePaper display */
|
|
||||||
void epd_deinit();
|
|
||||||
|
|
||||||
/** Enable display power supply. */
|
|
||||||
void epd_poweron();
|
|
||||||
|
|
||||||
/** Disable display power supply. */
|
|
||||||
void epd_poweroff();
|
|
||||||
|
|
||||||
/** Clear the whole screen by flashing it. */
|
|
||||||
void epd_clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear an area by flashing it.
|
|
||||||
*
|
|
||||||
* @param area: The area to clear.
|
|
||||||
*/
|
|
||||||
void epd_clear_area(EpdRect area);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear an area by flashing it.
|
|
||||||
*
|
|
||||||
* @param area: The area to clear.
|
|
||||||
* @param cycles: The number of black-to-white clear cycles.
|
|
||||||
* @param cycle_time: Length of a cycle. Default: 50 (us).
|
|
||||||
*/
|
|
||||||
void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns Rectancle representing the whole screen area.
|
|
||||||
*/
|
|
||||||
EpdRect epd_full_screen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a picture to a given framebuffer.
|
|
||||||
*
|
|
||||||
* @param image_area: The area to copy to. `width` and `height` of the area
|
|
||||||
* must correspond to the image dimensions in pixels.
|
|
||||||
* @param image_data: The image data, as a buffer of 4 bit wide brightness
|
|
||||||
* values. Pixel data is packed (two pixels per byte). A byte cannot wrap over
|
|
||||||
* multiple rows, images of uneven width must add a padding nibble per line.
|
|
||||||
* @param framebuffer: The framebuffer object,
|
|
||||||
* which must be `EPD_WIDTH / 2 * EPD_HEIGHT` large.
|
|
||||||
*/
|
|
||||||
void epd_copy_to_framebuffer(EpdRect image_area, const uint8_t *image_data,
|
|
||||||
uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a pixel a given framebuffer.
|
|
||||||
*
|
|
||||||
* @param x: Horizontal position in pixels.
|
|
||||||
* @param y: Vertical position in pixels.
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_draw_pixel(int x, int y, uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a horizontal line to a given framebuffer.
|
|
||||||
*
|
|
||||||
* @param x: Horizontal start position in pixels.
|
|
||||||
* @param y: Vertical start position in pixels.
|
|
||||||
* @param length: Length of the line in pixels.
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
* which must be `EPD_WIDTH / 2 * EPD_HEIGHT` bytes large.
|
|
||||||
*/
|
|
||||||
void epd_draw_hline(int x, int y, int length, uint8_t color,
|
|
||||||
uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a horizontal line to a given framebuffer.
|
|
||||||
*
|
|
||||||
* @param x: Horizontal start position in pixels.
|
|
||||||
* @param y: Vertical start position in pixels.
|
|
||||||
* @param length: Length of the line in pixels.
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
* which must be `EPD_WIDTH / 2 * EPD_HEIGHT` bytes large.
|
|
||||||
*/
|
|
||||||
void epd_draw_vline(int x, int y, int length, uint8_t color,
|
|
||||||
uint8_t *framebuffer);
|
|
||||||
|
|
||||||
void epd_fill_circle_helper(int x0, int y0, int r, int corners, int delta,
|
|
||||||
uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a circle with given center and radius
|
|
||||||
*
|
|
||||||
* @param x: Center-point x coordinate
|
|
||||||
* @param y: Center-point y coordinate
|
|
||||||
* @param r: Radius of the circle in pixels
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_draw_circle(int x, int y, int r, uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a circle with fill with given center and radius
|
|
||||||
*
|
|
||||||
* @param x: Center-point x coordinate
|
|
||||||
* @param y: Center-point y coordinate
|
|
||||||
* @param r: Radius of the circle in pixels
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_fill_circle(int x, int y, int r, uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a rectanle with no fill color
|
|
||||||
*
|
|
||||||
* @param rect: The rectangle to draw.
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_draw_rect(EpdRect rect, uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a rectanle with fill color
|
|
||||||
*
|
|
||||||
* @param rect: The rectangle to fill.
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_fill_rect(EpdRect rect, uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a line
|
|
||||||
*
|
|
||||||
* @param x0 Start point x coordinate
|
|
||||||
* @param y0 Start point y coordinate
|
|
||||||
* @param x1 End point x coordinate
|
|
||||||
* @param y1 End point y coordinate
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_draw_line(int x0, int y0, int x1, int y1, uint8_t color,
|
|
||||||
uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a triangle with no fill color
|
|
||||||
*
|
|
||||||
* @param x0 Vertex #0 x coordinate
|
|
||||||
* @param y0 Vertex #0 y coordinate
|
|
||||||
* @param x1 Vertex #1 x coordinate
|
|
||||||
* @param y1 Vertex #1 y coordinate
|
|
||||||
* @param x2 Vertex #2 x coordinate
|
|
||||||
* @param y2 Vertex #2 y coordinate
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
|
|
||||||
uint8_t color, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a triangle with color-fill
|
|
||||||
*
|
|
||||||
* @param x0 Vertex #0 x coordinate
|
|
||||||
* @param y0 Vertex #0 y coordinate
|
|
||||||
* @param x1 Vertex #1 x coordinate
|
|
||||||
* @param y1 Vertex #1 y coordinate
|
|
||||||
* @param x2 Vertex #2 x coordinate
|
|
||||||
* @param y2 Vertex #2 y coordinate
|
|
||||||
* @param color: The gray value of the line (0-255);
|
|
||||||
* @param framebuffer: The framebuffer to draw to,
|
|
||||||
*/
|
|
||||||
void epd_fill_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
|
|
||||||
uint8_t color, uint8_t *framebuffer);
|
|
||||||
/**
|
|
||||||
* Get the current ambient temperature in °C, if supported by the board.
|
|
||||||
* Requires the display to be powered on.
|
|
||||||
*/
|
|
||||||
float epd_ambient_temperature();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default font properties.
|
|
||||||
*/
|
|
||||||
EpdFontProperties epd_font_properties_default();
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Get the text bounds for string, when drawn at (x, y).
|
|
||||||
* Set font properties to NULL to use the defaults.
|
|
||||||
*/
|
|
||||||
void epd_get_text_bounds(const EpdFont *font, const char *string,
|
|
||||||
const int *x, const int *y,
|
|
||||||
int *x1, int *y1, int *w, int *h,
|
|
||||||
const EpdFontProperties *props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write text to the EPD.
|
|
||||||
*/
|
|
||||||
enum EpdDrawError epd_write_string(const EpdFont *font, const char *string, int *cursor_x,
|
|
||||||
int *cursor_y, uint8_t *framebuffer,
|
|
||||||
const EpdFontProperties *properties);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a (multi-line) string to the EPD.
|
|
||||||
*/
|
|
||||||
enum EpdDrawError epd_write_default(const EpdFont *font, const char *string, int *cursor_x,
|
|
||||||
int *cursor_y, uint8_t *framebuffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the font glyph for a unicode code point.
|
|
||||||
*/
|
|
||||||
const EpdGlyph* epd_get_glyph(const EpdFont *font, uint32_t code_point);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Darken / lighten an area for a given time.
|
|
||||||
*
|
|
||||||
* @param area: The area to darken / lighten.
|
|
||||||
* @param time: The time in us to apply voltage to each pixel.
|
|
||||||
* @param color: 1: lighten, 0: darken.
|
|
||||||
*/
|
|
||||||
void epd_push_pixels(EpdRect area, short time, int color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base function for drawing an image on the screen.
|
|
||||||
* If It is very customizable, and the documentation below should be studied carefully.
|
|
||||||
* For simple applications, use the epdiy highlevel api in "epd_higlevel.h".
|
|
||||||
*
|
|
||||||
* @param area: The area of the screen to draw to.
|
|
||||||
* This can be imagined as shifting the origin of the frame buffer.
|
|
||||||
* @param data: A full framebuffer of display data.
|
|
||||||
* It's structure depends on the chosen `mode`.
|
|
||||||
* @param crop_to: Only draw a part of the frame buffer.
|
|
||||||
* Set to `epd_full_screen()` to draw the full buffer.
|
|
||||||
* @param mode: Specifies the Waveform used, the framebuffer format
|
|
||||||
* and additional information, like if the display is cleared.
|
|
||||||
* @param temperature: The temperature of the display in °C.
|
|
||||||
* Currently, this is unused by the default waveforms at can be
|
|
||||||
* set to room temperature, e.g. 20-25°C.
|
|
||||||
* @param drawn_lines: If not NULL, an array of at least the height of the
|
|
||||||
* image. Every line where the corresponding value in `lines` is `false` will be
|
|
||||||
* skipped.
|
|
||||||
* @param waveform: The waveform information to use for drawing.
|
|
||||||
* If you don't have special waveforms, use `EPD_BUILTIN_WAVEFORM`.
|
|
||||||
* @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise.
|
|
||||||
*/
|
|
||||||
enum EpdDrawError IRAM_ATTR epd_draw_base(EpdRect area,
|
|
||||||
const uint8_t *data,
|
|
||||||
EpdRect crop_to,
|
|
||||||
enum EpdDrawMode mode,
|
|
||||||
int temperature,
|
|
||||||
const bool *drawn_lines,
|
|
||||||
const EpdWaveform *waveform);
|
|
||||||
/**
|
|
||||||
* Calculate a `MODE_PACKING_1PPB_DIFFERENCE` difference image
|
|
||||||
* from two `MODE_PACKING_2PPB` (4 bit-per-pixel) buffers.
|
|
||||||
* If you're using the epdiy highlevel api, this is handled by the update functions.
|
|
||||||
*
|
|
||||||
* @param to: The goal image as 4-bpp (`MODE_PACKING_2PPB`) framebuffer.
|
|
||||||
* @param from: The previous image as 4-bpp (`MODE_PACKING_2PPB`) framebuffer.
|
|
||||||
* @param crop_to: Only calculate the difference for a crop of the input framebuffers.
|
|
||||||
* The `interlaced` will not be modified outside the crop area.
|
|
||||||
* @param interlaced: The resulting difference image in `MODE_PACKING_1PPB_DIFFERENCE` format.
|
|
||||||
* @param dirty_lines: An array of at least `EPD_HEIGHT`.
|
|
||||||
* The positions corresponding to lines where `to` and `from` differ
|
|
||||||
* are set to `true`, otherwise to `false`.
|
|
||||||
* @param previously_white: If not NULL, it is set to `true`
|
|
||||||
* if the considered crop of the `from`-image is completely white.
|
|
||||||
* @param previously_black: If not NULL, it is set to `true`
|
|
||||||
* if the considered crop of the `from`-image is completely black.
|
|
||||||
* @returns The smallest rectangle containing all changed pixels.
|
|
||||||
*/
|
|
||||||
EpdRect epd_difference_image_cropped(
|
|
||||||
const uint8_t* to,
|
|
||||||
const uint8_t* from,
|
|
||||||
EpdRect crop_to,
|
|
||||||
uint8_t* interlaced,
|
|
||||||
bool* dirty_lines,
|
|
||||||
bool* previously_white,
|
|
||||||
bool* previously_black
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplified version of `epd_difference_image_cropped()`, which considers the
|
|
||||||
* whole display frame buffer.
|
|
||||||
*
|
|
||||||
* See `epd_difference_image_cropped() for details.`
|
|
||||||
*/
|
|
||||||
EpdRect epd_difference_image(const uint8_t* to, const uint8_t* from, uint8_t* interlaced, bool* dirty_lines);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file "epd_highlevel.h"
|
|
||||||
* @brief High-level API for drawing to e-paper displays.
|
|
||||||
*
|
|
||||||
* The functions in this library provide a simple way to manage updates of e-paper display.
|
|
||||||
* To use it, follow the steps below:
|
|
||||||
*
|
|
||||||
* 1. First, we declare a global object that manages the display state.
|
|
||||||
*
|
|
||||||
* EpdiyHighlevelState hl;
|
|
||||||
*
|
|
||||||
* 2. Then, the driver and framebuffers must be initialized.
|
|
||||||
*
|
|
||||||
* epd_init(EPD_LUT_1K);
|
|
||||||
* hl = epd_hl_init(EPD_BUILTIN_WAVEFORM);
|
|
||||||
*
|
|
||||||
* 3. Now, we can draw to the allocated framebuffer,
|
|
||||||
* using the draw and text functions defined in `epd_driver.h`.
|
|
||||||
* This will not yet update the display, but only its representation in memory.
|
|
||||||
*
|
|
||||||
* // A reference to the framebuffer
|
|
||||||
* uint8_t* fb = epd_hl_get_framebuffer(&hl);
|
|
||||||
*
|
|
||||||
* // draw a black rectangle
|
|
||||||
* EpdRect some_rect = {
|
|
||||||
* .x = 100,
|
|
||||||
* .y = 100,
|
|
||||||
* .width = 100,
|
|
||||||
* .height = 100
|
|
||||||
* };
|
|
||||||
* epd_fill_rect(some_rect, 0x0, fb);
|
|
||||||
*
|
|
||||||
* // write a message
|
|
||||||
* int cursor_x = 100;
|
|
||||||
* int cursor_y = 300;
|
|
||||||
* epd_write_default(&FiraSans, "Hello, World!", &cursor_x, &cursor_y, fb);
|
|
||||||
*
|
|
||||||
* // finally, update the display!
|
|
||||||
* int temperature = 25;
|
|
||||||
* epd_poweron();
|
|
||||||
* EpdDrawError err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
|
|
||||||
* epd_poweroff();
|
|
||||||
*
|
|
||||||
* That's it! For many application, this will be enough.
|
|
||||||
* For special applications and requirements, have a
|
|
||||||
* closer look at the `epd_driver.h` header.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "epd_driver.h"
|
|
||||||
|
|
||||||
/// Holds the internal state of the high-level API.
|
|
||||||
typedef struct {
|
|
||||||
/// The "front" framebuffer object.
|
|
||||||
uint8_t* front_fb;
|
|
||||||
/// The "back" framebuffer object.
|
|
||||||
uint8_t* back_fb;
|
|
||||||
/// Buffer for holding the interlaced difference image.
|
|
||||||
uint8_t* difference_fb;
|
|
||||||
/// Tainted lines based on the last difference calculation.
|
|
||||||
bool* dirty_lines;
|
|
||||||
/// The waveform information to use.
|
|
||||||
const EpdWaveform* waveform;
|
|
||||||
} EpdiyHighlevelState;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a state object.
|
|
||||||
* This allocates two framebuffers and an update buffer for
|
|
||||||
* the display in the external PSRAM.
|
|
||||||
* In order to keep things simple, a chip reset is triggered if this fails.
|
|
||||||
*
|
|
||||||
* @param waveform: The waveform to use for updates.
|
|
||||||
* If you did not create your own, this will be `EPD_BUILTIN_WAVEFORM`.
|
|
||||||
* @returns An initialized state object.
|
|
||||||
*/
|
|
||||||
EpdiyHighlevelState epd_hl_init(const EpdWaveform* waveform);
|
|
||||||
|
|
||||||
/// Get a reference to the front framebuffer.
|
|
||||||
/// Use this to draw on the framebuffer before updating the screen with `epd_hl_update_screen()`.
|
|
||||||
uint8_t* epd_hl_get_framebuffer(EpdiyHighlevelState* state);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the EPD screen to match the content of the front frame buffer.
|
|
||||||
* Prior to this, power to the display must be enabled via `epd_poweron()`
|
|
||||||
* and should be disabled afterwards if no immediate additional updates follow.
|
|
||||||
*
|
|
||||||
* @param state: A reference to the `EpdiyHighlevelState` object used.
|
|
||||||
* @param mode: The update mode to use.
|
|
||||||
* Additional mode settings like the framebuffer format or
|
|
||||||
* previous display state are determined by the driver and must not be supplied here.
|
|
||||||
* In most cases, one of `MODE_GC16` and `MODE_GL16` should be used.
|
|
||||||
* @param temperature: Environmental temperature of the display in °C.
|
|
||||||
* @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise.
|
|
||||||
*/
|
|
||||||
enum EpdDrawError epd_hl_update_screen(EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an area of the screen to match the content of the front framebuffer.
|
|
||||||
* Supplying a small area to update can speed up the update process.
|
|
||||||
* Prior to this, power to the display must be enabled via `epd_poweron()`
|
|
||||||
* and should be disabled afterwards if no immediate additional updates follow.
|
|
||||||
*
|
|
||||||
* @param state: A reference to the `EpdiyHighlevelState` object used.
|
|
||||||
* @param mode: See `epd_hl_update_screen()`.
|
|
||||||
* @param temperature: Environmental temperature of the display in °C.
|
|
||||||
* @param area: Area of the screen to update.
|
|
||||||
* @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise.
|
|
||||||
*/
|
|
||||||
enum EpdDrawError epd_hl_update_area(EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the front framebuffer to a white state.
|
|
||||||
*
|
|
||||||
* @param state: A reference to the `EpdiyHighlevelState` object used.
|
|
||||||
*/
|
|
||||||
void epd_hl_set_all_white(EpdiyHighlevelState* state);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bring the display to a fully white state and get rid of any
|
|
||||||
* remaining artifacts.
|
|
||||||
*/
|
|
||||||
void epd_fullclear(EpdiyHighlevelState* state, int temperature);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file epd_internals.h
|
|
||||||
* @brief Internal definitions and auxiliary data types.
|
|
||||||
*
|
|
||||||
* Unless you want to extend the library itself (Which you are very welcome to do),
|
|
||||||
* you will most likely not need to know about this file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef EPD_INTERNALS_H
|
|
||||||
#define EPD_INTERNALS_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/// minimal draw time in ms for a frame layer,
|
|
||||||
/// which will allow all particles to set properly.
|
|
||||||
#ifndef MINIMUM_FRAME_TIME
|
|
||||||
#define MINIMUM_FRAME_TIME 12
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Frame draw time for monochrome mode in 1/10 us.
|
|
||||||
#define MONOCHROME_FRAME_TIME 120
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_DISPLAY_TYPE_ED097OC4) || \
|
|
||||||
defined(CONFIG_EPD_DISPLAY_TYPE_ED097TC2) || \
|
|
||||||
defined(CONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ)
|
|
||||||
/// Width of the display area in pixels.
|
|
||||||
#define EPD_WIDTH 1200
|
|
||||||
/// Height of the display area in pixels.
|
|
||||||
#define EPD_HEIGHT 825
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED133UT2)
|
|
||||||
#define EPD_WIDTH 1600
|
|
||||||
#define EPD_HEIGHT 1200
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED060SC4)
|
|
||||||
/// Width of the display area in pixels.
|
|
||||||
#define EPD_WIDTH 800
|
|
||||||
/// Height of the display area in pixels.
|
|
||||||
#define EPD_HEIGHT 600
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED060SCT)
|
|
||||||
/// Width of the display area in pixels.
|
|
||||||
#define EPD_WIDTH 800
|
|
||||||
/// Height of the display area in pixels.
|
|
||||||
#define EPD_HEIGHT 600
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED060XC3)
|
|
||||||
/// Width of the display area in pixels.
|
|
||||||
#define EPD_WIDTH 1024
|
|
||||||
/// Height of the display area in pixels.
|
|
||||||
#define EPD_HEIGHT 758
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED047TC1)
|
|
||||||
/// Width of the display area in pixels.
|
|
||||||
#define EPD_WIDTH 960
|
|
||||||
/// Height of the display area in pixels.
|
|
||||||
#define EPD_HEIGHT 540
|
|
||||||
#else
|
|
||||||
#error "no display type defined!"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int phases;
|
|
||||||
const uint8_t* luts;
|
|
||||||
/// If we have timing information for the individual
|
|
||||||
/// phases, this is an array of the on-times for each phase.
|
|
||||||
/// Otherwise, this is NULL.
|
|
||||||
const int* phase_times;
|
|
||||||
} EpdWaveformPhases;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t type;
|
|
||||||
uint8_t temp_ranges;
|
|
||||||
EpdWaveformPhases const **range_data;
|
|
||||||
} EpdWaveformMode;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int min;
|
|
||||||
int max;
|
|
||||||
} EpdWaveformTempInterval;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t num_modes;
|
|
||||||
uint8_t num_temp_ranges;
|
|
||||||
EpdWaveformMode const **mode_data;
|
|
||||||
EpdWaveformTempInterval const *temp_intervals;
|
|
||||||
} EpdWaveform;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extern const EpdWaveform ed047tc2;
|
|
||||||
extern const EpdWaveform epdiy_ED060SC4;
|
|
||||||
extern const EpdWaveform epdiy_ED097OC4;
|
|
||||||
extern const EpdWaveform epdiy_ED047TC1;
|
|
||||||
extern const EpdWaveform epdiy_ED097TC2;
|
|
||||||
extern const EpdWaveform epdiy_ED060XC3;
|
|
||||||
extern const EpdWaveform epdiy_ED060SCT;
|
|
||||||
extern const EpdWaveform epdiy_ED133UT2;
|
|
||||||
|
|
||||||
#if defined(CONFIG_EPD_DISPLAY_TYPE_ED047TC1)
|
|
||||||
//#define EPD_BUILTIN_WAVEFORM &epdiy_ED047TC1
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &ed047tc2
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED060SC4)
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &epdiy_ED060SC4
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED060XC3)
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &epdiy_ED060XC3
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED060SCT)
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &epdiy_ED060SCT
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED097OC4) || defined(CONFIG_EPD_DISPLAY_TYPE_ED097OC4_LQ)
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &epdiy_ED097OC4
|
|
||||||
#elif defined(CONFIG_EPD_DISPLAY_TYPE_ED097TC2)
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &epdiy_ED097TC2
|
|
||||||
#elif defined (CONFIG_EPD_DISPLAY_TYPE_ED133UT2)
|
|
||||||
#define EPD_BUILTIN_WAVEFORM &epdiy_ED133UT2
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Font data stored PER GLYPH
|
|
||||||
typedef struct {
|
|
||||||
uint8_t width; ///< Bitmap dimensions in pixels
|
|
||||||
uint8_t height; ///< Bitmap dimensions in pixels
|
|
||||||
uint8_t advance_x; ///< Distance to advance cursor (x axis)
|
|
||||||
int16_t left; ///< X dist from cursor pos to UL corner
|
|
||||||
int16_t top; ///< Y dist from cursor pos to UL corner
|
|
||||||
uint16_t compressed_size; ///< Size of the zlib-compressed font data.
|
|
||||||
uint32_t data_offset; ///< Pointer into EpdFont->bitmap
|
|
||||||
} EpdGlyph;
|
|
||||||
|
|
||||||
/// Glyph interval structure
|
|
||||||
typedef struct {
|
|
||||||
uint32_t first; ///< The first unicode code point of the interval
|
|
||||||
uint32_t last; ///< The last unicode code point of the interval
|
|
||||||
uint32_t offset; ///< Index of the first code point into the glyph array
|
|
||||||
} EpdUnicodeInterval;
|
|
||||||
|
|
||||||
/// Data stored for FONT AS A WHOLE
|
|
||||||
typedef struct {
|
|
||||||
const uint8_t *bitmap; ///< Glyph bitmaps, concatenated
|
|
||||||
const EpdGlyph *glyph; ///< Glyph array
|
|
||||||
const EpdUnicodeInterval *intervals; ///< Valid unicode intervals for this font
|
|
||||||
uint32_t interval_count; ///< Number of unicode intervals.
|
|
||||||
bool compressed; ///< Does this font use compressed glyph bitmaps?
|
|
||||||
uint8_t advance_y; ///< Newline distance (y axis)
|
|
||||||
int ascender; ///< Maximal height of a glyph above the base line
|
|
||||||
int descender; ///< Maximal height of a glyph below the base line
|
|
||||||
} EpdFont;
|
|
||||||
|
|
||||||
|
|
||||||
#endif // EPD_INTERNALS_H
|
|
||||||
@ -1,617 +0,0 @@
|
|||||||
#include "lut.h"
|
|
||||||
#include "display_ops.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Build Lookup tables and translate via LUTs.
|
|
||||||
* WARNING: These functions must only ever write to internal memory,
|
|
||||||
* Since we disable the PSRAM workaround here for performance reasons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Python script for generating the 1bpp lookup table:
|
|
||||||
* for i in range(256):
|
|
||||||
number = 0;
|
|
||||||
for b in range(8):
|
|
||||||
if not (i & (b << 1)):
|
|
||||||
number |= 1 << (2*b)
|
|
||||||
print ('0x%04x,'%number)
|
|
||||||
*/
|
|
||||||
const static uint32_t lut_1bpp_black[256] = {
|
|
||||||
0x5555, 0x5554, 0x5551, 0x5550, 0x5545, 0x5544, 0x5541, 0x5540, 0x5515,
|
|
||||||
0x5514, 0x5511, 0x5510, 0x5505, 0x5504, 0x5501, 0x5500, 0x5455, 0x5454,
|
|
||||||
0x5451, 0x5450, 0x5445, 0x5444, 0x5441, 0x5440, 0x5415, 0x5414, 0x5411,
|
|
||||||
0x5410, 0x5405, 0x5404, 0x5401, 0x5400, 0x5155, 0x5154, 0x5151, 0x5150,
|
|
||||||
0x5145, 0x5144, 0x5141, 0x5140, 0x5115, 0x5114, 0x5111, 0x5110, 0x5105,
|
|
||||||
0x5104, 0x5101, 0x5100, 0x5055, 0x5054, 0x5051, 0x5050, 0x5045, 0x5044,
|
|
||||||
0x5041, 0x5040, 0x5015, 0x5014, 0x5011, 0x5010, 0x5005, 0x5004, 0x5001,
|
|
||||||
0x5000, 0x4555, 0x4554, 0x4551, 0x4550, 0x4545, 0x4544, 0x4541, 0x4540,
|
|
||||||
0x4515, 0x4514, 0x4511, 0x4510, 0x4505, 0x4504, 0x4501, 0x4500, 0x4455,
|
|
||||||
0x4454, 0x4451, 0x4450, 0x4445, 0x4444, 0x4441, 0x4440, 0x4415, 0x4414,
|
|
||||||
0x4411, 0x4410, 0x4405, 0x4404, 0x4401, 0x4400, 0x4155, 0x4154, 0x4151,
|
|
||||||
0x4150, 0x4145, 0x4144, 0x4141, 0x4140, 0x4115, 0x4114, 0x4111, 0x4110,
|
|
||||||
0x4105, 0x4104, 0x4101, 0x4100, 0x4055, 0x4054, 0x4051, 0x4050, 0x4045,
|
|
||||||
0x4044, 0x4041, 0x4040, 0x4015, 0x4014, 0x4011, 0x4010, 0x4005, 0x4004,
|
|
||||||
0x4001, 0x4000, 0x1555, 0x1554, 0x1551, 0x1550, 0x1545, 0x1544, 0x1541,
|
|
||||||
0x1540, 0x1515, 0x1514, 0x1511, 0x1510, 0x1505, 0x1504, 0x1501, 0x1500,
|
|
||||||
0x1455, 0x1454, 0x1451, 0x1450, 0x1445, 0x1444, 0x1441, 0x1440, 0x1415,
|
|
||||||
0x1414, 0x1411, 0x1410, 0x1405, 0x1404, 0x1401, 0x1400, 0x1155, 0x1154,
|
|
||||||
0x1151, 0x1150, 0x1145, 0x1144, 0x1141, 0x1140, 0x1115, 0x1114, 0x1111,
|
|
||||||
0x1110, 0x1105, 0x1104, 0x1101, 0x1100, 0x1055, 0x1054, 0x1051, 0x1050,
|
|
||||||
0x1045, 0x1044, 0x1041, 0x1040, 0x1015, 0x1014, 0x1011, 0x1010, 0x1005,
|
|
||||||
0x1004, 0x1001, 0x1000, 0x0555, 0x0554, 0x0551, 0x0550, 0x0545, 0x0544,
|
|
||||||
0x0541, 0x0540, 0x0515, 0x0514, 0x0511, 0x0510, 0x0505, 0x0504, 0x0501,
|
|
||||||
0x0500, 0x0455, 0x0454, 0x0451, 0x0450, 0x0445, 0x0444, 0x0441, 0x0440,
|
|
||||||
0x0415, 0x0414, 0x0411, 0x0410, 0x0405, 0x0404, 0x0401, 0x0400, 0x0155,
|
|
||||||
0x0154, 0x0151, 0x0150, 0x0145, 0x0144, 0x0141, 0x0140, 0x0115, 0x0114,
|
|
||||||
0x0111, 0x0110, 0x0105, 0x0104, 0x0101, 0x0100, 0x0055, 0x0054, 0x0051,
|
|
||||||
0x0050, 0x0045, 0x0044, 0x0041, 0x0040, 0x0015, 0x0014, 0x0011, 0x0010,
|
|
||||||
0x0005, 0x0004, 0x0001, 0x0000};
|
|
||||||
|
|
||||||
// Timestamp when the last frame draw was started.
|
|
||||||
// This is used to enforce a minimum frame draw time, allowing
|
|
||||||
// all pixels to set.
|
|
||||||
static uint64_t last_frame_start = 0;
|
|
||||||
|
|
||||||
inline int min(int x, int y) { return x < y ? x : y; }
|
|
||||||
inline int max(int x, int y) { return x > y ? x : y; }
|
|
||||||
|
|
||||||
// status tracker for row skipping
|
|
||||||
uint32_t skipping;
|
|
||||||
|
|
||||||
// output a row to the display.
|
|
||||||
void IRAM_ATTR write_row(uint32_t output_time_dus) {
|
|
||||||
epd_output_row(output_time_dus);
|
|
||||||
skipping = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip a display row
|
|
||||||
void IRAM_ATTR skip_row(uint8_t pipeline_finish_time) {
|
|
||||||
// output previously loaded row, fill buffer with no-ops.
|
|
||||||
if (skipping < 2) {
|
|
||||||
memset(epd_get_current_buffer(), 0x00, EPD_LINE_BYTES);
|
|
||||||
epd_output_row(pipeline_finish_time);
|
|
||||||
} else {
|
|
||||||
epd_skip();
|
|
||||||
}
|
|
||||||
skipping++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR reorder_line_buffer(uint32_t *line_data) {
|
|
||||||
for (uint32_t i = 0; i < EPD_LINE_BYTES / 4; i++) {
|
|
||||||
uint32_t val = *line_data;
|
|
||||||
*(line_data++) = val >> 16 | ((val & 0x0000FFFF) << 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void IRAM_ATTR bit_shift_buffer_right(uint8_t *buf, uint32_t len,
|
|
||||||
int shift) {
|
|
||||||
uint8_t carry = 0xFF << (8 - shift);
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
|
||||||
uint8_t val = buf[i];
|
|
||||||
buf[i] = (val >> shift) | carry;
|
|
||||||
carry = val << (8 - shift);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void IRAM_ATTR nibble_shift_buffer_right(uint8_t *buf, uint32_t len) {
|
|
||||||
uint8_t carry = 0xF;
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
|
||||||
uint8_t val = buf[i];
|
|
||||||
buf[i] = (val << 4) | carry;
|
|
||||||
carry = (val & 0xF0) >> 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////// Looking up EPD Pixels
|
|
||||||
//////////////////////////////////
|
|
||||||
|
|
||||||
static void IRAM_ATTR calc_epd_input_1bpp(const uint32_t *line_data,
|
|
||||||
uint8_t *epd_input,
|
|
||||||
const uint8_t *lut) {
|
|
||||||
|
|
||||||
uint32_t *wide_epd_input = (uint32_t *)epd_input;
|
|
||||||
uint8_t *data_ptr = (uint8_t *)line_data;
|
|
||||||
uint32_t *lut_32 = (uint32_t *)lut;
|
|
||||||
// this is reversed for little-endian, but this is later compensated
|
|
||||||
// through the output peripheral.
|
|
||||||
for (uint32_t j = 0; j < EPD_WIDTH / 16; j++) {
|
|
||||||
uint8_t v1 = *(data_ptr++);
|
|
||||||
uint8_t v2 = *(data_ptr++);
|
|
||||||
wide_epd_input[j] = (lut_32[v1] << 16) | lut_32[v2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void IRAM_ATTR
|
|
||||||
calc_epd_input_4bpp_lut_64k(const uint32_t *line_data, uint8_t *epd_input,
|
|
||||||
const uint8_t *conversion_lut) {
|
|
||||||
|
|
||||||
uint32_t *wide_epd_input = (uint32_t *)epd_input;
|
|
||||||
const uint16_t *line_data_16 = (const uint16_t *)line_data;
|
|
||||||
|
|
||||||
// this is reversed for little-endian, but this is later compensated
|
|
||||||
// through the output peripheral.
|
|
||||||
for (uint32_t j = 0; j < EPD_WIDTH / 16; j++) {
|
|
||||||
|
|
||||||
uint16_t v1 = *(line_data_16++);
|
|
||||||
uint16_t v2 = *(line_data_16++);
|
|
||||||
uint16_t v3 = *(line_data_16++);
|
|
||||||
uint16_t v4 = *(line_data_16++);
|
|
||||||
uint32_t pixel = conversion_lut[v1] << 16 | conversion_lut[v2] << 24 |
|
|
||||||
conversion_lut[v3] | conversion_lut[v4] << 8;
|
|
||||||
wide_epd_input[j] = pixel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up 4 pixels of a differential image.
|
|
||||||
*/
|
|
||||||
static inline uint8_t
|
|
||||||
lookup_differential_pixels(uint32_t in, const uint8_t *conversion_lut) {
|
|
||||||
uint8_t out = conversion_lut[in & 0xFF];
|
|
||||||
in = in >> 8;
|
|
||||||
out |= (conversion_lut + 0x100)[in & 0xFF];
|
|
||||||
in = in >> 8;
|
|
||||||
out |= (conversion_lut + 0x200)[in & 0xFF];
|
|
||||||
in = in >> 8;
|
|
||||||
out |= (conversion_lut + 0x300)[in];
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate EPD input for a difference image with one pixel per byte.
|
|
||||||
*/
|
|
||||||
static void IRAM_ATTR calc_epd_input_1ppB(const uint32_t *ld,
|
|
||||||
uint8_t *epd_input,
|
|
||||||
const uint8_t *conversion_lut) {
|
|
||||||
|
|
||||||
// this is reversed for little-endian, but this is later compensated
|
|
||||||
// through the output peripheral.
|
|
||||||
for (uint32_t j = 0; j < EPD_WIDTH / 4; j += 4) {
|
|
||||||
epd_input[j + 2] = lookup_differential_pixels(*(ld++), conversion_lut);
|
|
||||||
epd_input[j + 3] = lookup_differential_pixels(*(ld++), conversion_lut);
|
|
||||||
epd_input[j + 0] = lookup_differential_pixels(*(ld++), conversion_lut);
|
|
||||||
epd_input[j + 1] = lookup_differential_pixels(*(ld++), conversion_lut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up 4 pixels in a 1K LUT with fixed "from" value.
|
|
||||||
*/
|
|
||||||
static inline uint8_t lookup_pixels_4bpp_1k(uint16_t in,
|
|
||||||
const uint8_t *conversion_lut,
|
|
||||||
uint8_t from) {
|
|
||||||
uint8_t v;
|
|
||||||
uint8_t out;
|
|
||||||
v = ((in << 4) | from);
|
|
||||||
out = conversion_lut[v & 0xFF];
|
|
||||||
v = ((in & 0xF0) | from);
|
|
||||||
out |= (conversion_lut + 0x100)[v & 0xFF];
|
|
||||||
in = in >> 8;
|
|
||||||
v = ((in << 4) | from);
|
|
||||||
out |= (conversion_lut + 0x200)[v & 0xFF];
|
|
||||||
v = ((in & 0xF0) | from);
|
|
||||||
out |= (conversion_lut + 0x300)[v];
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate EPD input for a 4bpp buffer, but with a difference image LUT.
|
|
||||||
* This is used for small-LUT mode.
|
|
||||||
*/
|
|
||||||
static void IRAM_ATTR calc_epd_input_4bpp_1k_lut(const uint32_t *ld,
|
|
||||||
uint8_t *epd_input,
|
|
||||||
const uint8_t *conversion_lut,
|
|
||||||
uint8_t from) {
|
|
||||||
|
|
||||||
uint16_t *ptr = (uint16_t *)ld;
|
|
||||||
// this is reversed for little-endian, but this is later compensated
|
|
||||||
// through the output peripheral.
|
|
||||||
for (uint32_t j = 0; j < EPD_WIDTH / 4; j += 4) {
|
|
||||||
epd_input[j + 2] = lookup_pixels_4bpp_1k(*(ptr++), conversion_lut, from);
|
|
||||||
epd_input[j + 3] = lookup_pixels_4bpp_1k(*(ptr++), conversion_lut, from);
|
|
||||||
epd_input[j + 0] = lookup_pixels_4bpp_1k(*(ptr++), conversion_lut, from);
|
|
||||||
epd_input[j + 1] = lookup_pixels_4bpp_1k(*(ptr++), conversion_lut, from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void IRAM_ATTR calc_epd_input_4bpp_1k_lut_white(
|
|
||||||
const uint32_t *ld, uint8_t *epd_input, const uint8_t *conversion_lut) {
|
|
||||||
calc_epd_input_4bpp_1k_lut(ld, epd_input, conversion_lut, 0xF);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void IRAM_ATTR calc_epd_input_4bpp_1k_lut_black(
|
|
||||||
const uint32_t *ld, uint8_t *epd_input, const uint8_t *conversion_lut) {
|
|
||||||
calc_epd_input_4bpp_1k_lut(ld, epd_input, conversion_lut, 0x0);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////// Calculate Lookup Tables
|
|
||||||
//////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpack the waveform data into a lookup table, with bit shifted copies.
|
|
||||||
*/
|
|
||||||
static void IRAM_ATTR waveform_lut(const EpdWaveform *waveform, uint8_t *lut,
|
|
||||||
uint8_t mode, int range, int frame) {
|
|
||||||
const uint8_t *p_lut =
|
|
||||||
waveform->mode_data[mode]->range_data[range]->luts + (16 * 4 * frame);
|
|
||||||
for (uint8_t to = 0; to < 16; to++) {
|
|
||||||
for (uint8_t from_packed = 0; from_packed < 4; from_packed++) {
|
|
||||||
uint8_t index = (to << 4) | (from_packed * 4);
|
|
||||||
uint8_t packed = *(p_lut++);
|
|
||||||
lut[index] = (packed >> 6) & 3;
|
|
||||||
lut[index + 1] = (packed >> 4) & 3;
|
|
||||||
lut[index + 2] = (packed >> 2) & 3;
|
|
||||||
lut[index + 3] = (packed >> 0) & 3;
|
|
||||||
// printf("%2X%2X%2X%2X (%d)", lut[index], lut[index + 1], lut[index + 2],
|
|
||||||
// lut[index + 3], index);
|
|
||||||
}
|
|
||||||
// printf("\n");
|
|
||||||
}
|
|
||||||
uint32_t index = 0x100;
|
|
||||||
for (uint8_t s = 2; s <= 6; s += 2) {
|
|
||||||
for (int i = 0; i < 0x100; i++) {
|
|
||||||
lut[index] = lut[index % 0x100] << s;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a 16-bit LUT from the waveform if the previous color is
|
|
||||||
* known, e.g. all white or all black.
|
|
||||||
* This LUT is use to look up 4 pixels at once, as with the epdiy LUT.
|
|
||||||
*/
|
|
||||||
static void IRAM_ATTR waveform_lut_static_from(const EpdWaveform *waveform,
|
|
||||||
uint8_t *lut, uint8_t from,
|
|
||||||
uint8_t mode, int range,
|
|
||||||
int frame) {
|
|
||||||
const uint8_t *p_lut =
|
|
||||||
waveform->mode_data[mode]->range_data[range]->luts + (16 * 4 * frame);
|
|
||||||
|
|
||||||
/// index into the packed "from" row
|
|
||||||
uint8_t fi = from >> 2;
|
|
||||||
/// bit shift amount for the packed "from" row
|
|
||||||
uint8_t fs = 6 - 2 * (from & 3);
|
|
||||||
|
|
||||||
// populate the first 4096 bytes
|
|
||||||
uint8_t v1 = 0;
|
|
||||||
uint32_t s1 = 0;
|
|
||||||
for (uint8_t t2 = 0; t2 < 16; t2++) {
|
|
||||||
uint8_t v2 = ((p_lut[(t2 << 2) + fi] >> fs) & 0x03) << 4;
|
|
||||||
uint32_t s2 = t2 << 8;
|
|
||||||
for (uint8_t t3 = 0; t3 < 16; t3++) {
|
|
||||||
uint8_t v3 = ((p_lut[(t3 << 2) + fi] >> fs) & 0x03) << 2;
|
|
||||||
uint32_t s3 = t3 << 4;
|
|
||||||
for (uint8_t t4 = 0; t4 < 16; t4++) {
|
|
||||||
uint8_t v4 = ((p_lut[(t4 << 2) + fi] >> fs) & 0x03) << 0;
|
|
||||||
uint32_t s4 = t4;
|
|
||||||
lut[s1 | s2 | s3 | s4] = v1 | v2 | v3 | v4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now just copy and the first 4096 bytes and add the upper two bits
|
|
||||||
for (uint8_t t1 = 1; t1 < 16; t1++) {
|
|
||||||
memcpy(&lut[t1 << 12], lut, 1 << 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
uint32_t v1 = ((p_lut[(i << 2) + fi] >> fs) & 0x03);
|
|
||||||
uint32_t mask = (v1 << 30) | (v1 << 22) | (v1 << 14) | (v1 << 6);
|
|
||||||
for (int j = 0; j < 16 * 16 * 16 / 4; j++) {
|
|
||||||
((uint32_t *)lut)[(i << 10) + j] |= mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR provide_out(OutputParams *params) {
|
|
||||||
while (true) {
|
|
||||||
// line must be able to hold 2-pixel-per-byte or 1-pixel-per-byte data
|
|
||||||
uint8_t line[EPD_WIDTH];
|
|
||||||
memset(line, 255, EPD_WIDTH);
|
|
||||||
|
|
||||||
xSemaphoreTake(params->start_smphr, portMAX_DELAY);
|
|
||||||
EpdRect area = params->area;
|
|
||||||
const uint8_t *ptr = params->data_ptr;
|
|
||||||
const bool crop = (params->crop_to.width > 0 && params->crop_to.height > 0);
|
|
||||||
|
|
||||||
// number of pixels per byte of input data
|
|
||||||
int ppB = 0;
|
|
||||||
int bytes_per_line = 0;
|
|
||||||
int width_divider = 0;
|
|
||||||
|
|
||||||
if (params->mode & MODE_PACKING_1PPB_DIFFERENCE) {
|
|
||||||
ppB = 1;
|
|
||||||
bytes_per_line = area.width;
|
|
||||||
width_divider = 1;
|
|
||||||
} else if (params->mode & MODE_PACKING_2PPB) {
|
|
||||||
ppB = 2;
|
|
||||||
bytes_per_line = area.width / 2 + area.width % 2;
|
|
||||||
width_divider = 2;
|
|
||||||
} else if (params->mode & MODE_PACKING_8PPB) {
|
|
||||||
ppB = 8;
|
|
||||||
bytes_per_line = (area.width / 8 + (area.width % 8 > 0));
|
|
||||||
width_divider = 8;
|
|
||||||
} else {
|
|
||||||
params->error |= EPD_DRAW_INVALID_PACKING_MODE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int crop_x = (crop ? params->crop_to.x : 0);
|
|
||||||
int crop_y = (crop ? params->crop_to.y : 0);
|
|
||||||
int crop_w = (crop ? params->crop_to.width : 0);
|
|
||||||
int crop_h = (crop ? params->crop_to.height : 0);
|
|
||||||
|
|
||||||
// Adjust for negative starting coordinates with optional crop
|
|
||||||
if (area.x - crop_x < 0) {
|
|
||||||
ptr += -(area.x - crop_x) / width_divider;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (area.y - crop_y < 0) {
|
|
||||||
ptr += -(area.y - crop_y) * bytes_per_line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate start and end row with crop
|
|
||||||
int min_y = area.y + crop_y;
|
|
||||||
int max_y = min(min_y + (crop ? crop_h : area.height), area.height);
|
|
||||||
for (int i = 0; i < EPD_HEIGHT; i++) {
|
|
||||||
if (i < min_y || i >= max_y) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (params->drawn_lines != NULL && !params->drawn_lines[i - area.y]) {
|
|
||||||
ptr += bytes_per_line;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t *lp = (uint32_t *)line;
|
|
||||||
bool shifted = false;
|
|
||||||
if (area.width == EPD_WIDTH && area.x == 0 && !crop && !params->error) {
|
|
||||||
lp = (uint32_t *)ptr;
|
|
||||||
ptr += bytes_per_line;
|
|
||||||
} else if (!params->error) {
|
|
||||||
uint8_t *buf_start = (uint8_t *)line;
|
|
||||||
uint32_t line_bytes = bytes_per_line;
|
|
||||||
|
|
||||||
int min_x = area.x + crop_x;
|
|
||||||
if (min_x >= 0) {
|
|
||||||
buf_start += min_x / width_divider;
|
|
||||||
} else {
|
|
||||||
// reduce line_bytes to actually used bytes
|
|
||||||
// ptr was already adjusted above
|
|
||||||
line_bytes += min_x / width_divider;
|
|
||||||
}
|
|
||||||
line_bytes = min(line_bytes, EPD_WIDTH / width_divider -
|
|
||||||
(uint32_t)(buf_start - line));
|
|
||||||
memcpy(buf_start, ptr, line_bytes);
|
|
||||||
ptr += bytes_per_line;
|
|
||||||
|
|
||||||
int cropped_width = (crop ? crop_w : area.width);
|
|
||||||
/// consider half-byte shifts in two-pixel-per-Byte mode.
|
|
||||||
if (ppB == 2) {
|
|
||||||
// mask last nibble for uneven width
|
|
||||||
if (cropped_width % 2 == 1 &&
|
|
||||||
min_x / 2 + cropped_width / 2 + 1 < EPD_WIDTH) {
|
|
||||||
*(buf_start + line_bytes - 1) |= 0xF0;
|
|
||||||
}
|
|
||||||
if (area.x % 2 == 1 && !(crop_x % 2 == 1) && min_x < EPD_WIDTH) {
|
|
||||||
shifted = true;
|
|
||||||
uint32_t remaining =
|
|
||||||
(uint32_t)line + EPD_WIDTH / 2 - (uint32_t)buf_start;
|
|
||||||
uint32_t to_shift = min(line_bytes + 1, remaining);
|
|
||||||
// shift one nibble to right
|
|
||||||
nibble_shift_buffer_right(buf_start, to_shift);
|
|
||||||
}
|
|
||||||
// consider bit shifts in bit buffers
|
|
||||||
} else if (ppB == 8) {
|
|
||||||
// mask last n bits if width is not divisible by 8
|
|
||||||
if (cropped_width % 8 != 0 && bytes_per_line + 1 < EPD_WIDTH) {
|
|
||||||
uint8_t mask = 0;
|
|
||||||
for (int s = 0; s < cropped_width % 8; s++) {
|
|
||||||
mask = (mask << 1) | 1;
|
|
||||||
}
|
|
||||||
*(buf_start + line_bytes - 1) |= ~mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (min_x % 8 != 0 && min_x < EPD_WIDTH) {
|
|
||||||
// shift to right
|
|
||||||
shifted = true;
|
|
||||||
uint32_t remaining =
|
|
||||||
(uint32_t)line + EPD_WIDTH / 8 - (uint32_t)buf_start;
|
|
||||||
uint32_t to_shift = min(line_bytes + 1, remaining);
|
|
||||||
bit_shift_buffer_right(buf_start, to_shift, min_x % 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lp = (uint32_t *)line;
|
|
||||||
}
|
|
||||||
xQueueSendToBack(*params->output_queue, lp, portMAX_DELAY);
|
|
||||||
if (shifted) {
|
|
||||||
memset(line, 255, EPD_WIDTH / width_divider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xSemaphoreGive(params->done_smphr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set all pixels not in [xmin,xmax) to nop in the current line buffer.
|
|
||||||
*/
|
|
||||||
static void IRAM_ATTR mask_line_buffer(int xmin, int xmax) {
|
|
||||||
// lower bound to where byte order is not an issue.
|
|
||||||
int memset_start = (xmin / 16) * 4;
|
|
||||||
int memset_end = min(((xmax + 15) / 16) * 4, EPD_LINE_BYTES);
|
|
||||||
uint8_t *lb = epd_get_current_buffer();
|
|
||||||
|
|
||||||
// memset the areas where order is not an issue
|
|
||||||
memset(lb, 0, memset_start);
|
|
||||||
memset(lb + memset_end, 0, EPD_LINE_BYTES - memset_end);
|
|
||||||
|
|
||||||
const int offset_table[4] = {2, 3, 0, 1};
|
|
||||||
|
|
||||||
// mask unused pixels at the start of the output interval
|
|
||||||
uint8_t line_start_mask = 0xFF << (2 * (xmin % 4));
|
|
||||||
uint8_t line_end_mask = 0xFF >> (8 - 2 * (xmax % 4));
|
|
||||||
|
|
||||||
// number of full bytes to mask
|
|
||||||
int lower_full_bytes = max(0, (xmin / 4 - memset_start));
|
|
||||||
int upper_full_bytes = max(0, (memset_end - ((xmax + 3) / 4)));
|
|
||||||
assert(lower_full_bytes <= 3);
|
|
||||||
assert(upper_full_bytes <= 3);
|
|
||||||
assert(memset_end >= 4);
|
|
||||||
|
|
||||||
// mask full bytes
|
|
||||||
for (int i = 0; i < lower_full_bytes; i++) {
|
|
||||||
lb[memset_start + offset_table[i]] = 0x0;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < upper_full_bytes; i++) {
|
|
||||||
lb[memset_end - 4 + offset_table[3 - i]] = 0x0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mask partial bytes
|
|
||||||
if ((memset_start + lower_full_bytes) * 4 < xmin) {
|
|
||||||
epd_get_current_buffer()[memset_start + offset_table[lower_full_bytes]] &=
|
|
||||||
line_start_mask;
|
|
||||||
}
|
|
||||||
if ((memset_end - upper_full_bytes) * 4 > xmax) {
|
|
||||||
epd_get_current_buffer()[memset_end - 4 +
|
|
||||||
offset_table[3 - upper_full_bytes]] &=
|
|
||||||
line_end_mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR busy_delay(uint32_t cycles);
|
|
||||||
|
|
||||||
static enum EpdDrawError calculate_lut(OutputParams *params) {
|
|
||||||
|
|
||||||
enum EpdDrawMode mode = params->mode;
|
|
||||||
enum EpdDrawMode selected_mode = mode & 0x3F;
|
|
||||||
|
|
||||||
// two pixel per byte packing with only target color
|
|
||||||
if (mode & MODE_PACKING_2PPB && mode & PREVIOUSLY_WHITE &&
|
|
||||||
params->conversion_lut_size == (1 << 16)) {
|
|
||||||
waveform_lut_static_from(params->waveform, params->conversion_lut, 0x0F,
|
|
||||||
params->waveform_index, params->waveform_range,
|
|
||||||
params->frame);
|
|
||||||
} else if (mode & MODE_PACKING_2PPB && mode & PREVIOUSLY_BLACK &&
|
|
||||||
params->conversion_lut_size == (1 << 16)) {
|
|
||||||
waveform_lut_static_from(params->waveform, params->conversion_lut, 0x00,
|
|
||||||
params->waveform_index, params->waveform_range,
|
|
||||||
params->frame);
|
|
||||||
|
|
||||||
// one pixel per byte with from and to colors
|
|
||||||
} else if (mode & MODE_PACKING_1PPB_DIFFERENCE ||
|
|
||||||
(mode & MODE_PACKING_2PPB &&
|
|
||||||
params->conversion_lut_size == (1 << 10))) {
|
|
||||||
waveform_lut(params->waveform, params->conversion_lut,
|
|
||||||
params->waveform_index, params->waveform_range, params->frame);
|
|
||||||
|
|
||||||
// 1bit per pixel monochrome with only target color
|
|
||||||
} else if (mode & MODE_PACKING_8PPB &&
|
|
||||||
selected_mode == MODE_EPDIY_MONOCHROME) {
|
|
||||||
// FIXME: Pack into waveform?
|
|
||||||
if (mode & PREVIOUSLY_WHITE) {
|
|
||||||
memcpy(params->conversion_lut, lut_1bpp_black, sizeof(lut_1bpp_black));
|
|
||||||
} else if (mode & PREVIOUSLY_BLACK) {
|
|
||||||
// FIXME: implement!
|
|
||||||
// memcpy(params->conversion_lut, lut_1bpp_white, sizeof(lut_1bpp_white));
|
|
||||||
return EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
|
||||||
} else {
|
|
||||||
return EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// unknown format.
|
|
||||||
} else {
|
|
||||||
return EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
return EPD_DRAW_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR feed_display(OutputParams *params) {
|
|
||||||
while (true) {
|
|
||||||
xSemaphoreTake(params->start_smphr, portMAX_DELAY);
|
|
||||||
|
|
||||||
skipping = 0;
|
|
||||||
EpdRect area = params->area;
|
|
||||||
enum EpdDrawMode mode = params->mode;
|
|
||||||
int frame_time = params->frame_time;
|
|
||||||
|
|
||||||
params->error |= calculate_lut(params);
|
|
||||||
|
|
||||||
void (*input_calc_func)(const uint32_t *, uint8_t *, const uint8_t *) =
|
|
||||||
NULL;
|
|
||||||
if (mode & MODE_PACKING_2PPB) {
|
|
||||||
if (params->conversion_lut_size == 1024) {
|
|
||||||
if (mode & PREVIOUSLY_WHITE) {
|
|
||||||
input_calc_func = &calc_epd_input_4bpp_1k_lut_white;
|
|
||||||
} else if (mode & PREVIOUSLY_BLACK) {
|
|
||||||
input_calc_func = &calc_epd_input_4bpp_1k_lut_black;
|
|
||||||
} else {
|
|
||||||
params->error |= EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
} else if (params->conversion_lut_size == (1 << 16)) {
|
|
||||||
input_calc_func = &calc_epd_input_4bpp_lut_64k;
|
|
||||||
} else {
|
|
||||||
params->error |= EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
} else if (mode & MODE_PACKING_1PPB_DIFFERENCE) {
|
|
||||||
input_calc_func = &calc_epd_input_1ppB;
|
|
||||||
} else if (mode & MODE_PACKING_8PPB) {
|
|
||||||
input_calc_func = &calc_epd_input_1bpp;
|
|
||||||
} else {
|
|
||||||
params->error |= EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust min and max row for crop.
|
|
||||||
const bool crop = (params->crop_to.width > 0 && params->crop_to.height > 0);
|
|
||||||
int crop_y = (crop ? params->crop_to.y : 0);
|
|
||||||
int min_y = area.y + crop_y;
|
|
||||||
int max_y = min(min_y + (crop ? params->crop_to.height : area.height), area.height);
|
|
||||||
|
|
||||||
// interval of the output line that is needed
|
|
||||||
// FIXME: only lookup needed parts
|
|
||||||
int line_start_x = area.x + (crop ? params->crop_to.x : 0);
|
|
||||||
int line_end_x = line_start_x + (crop ? params->crop_to.width : area.width);
|
|
||||||
line_start_x = min(max(line_start_x, 0), EPD_WIDTH);
|
|
||||||
line_end_x = min(max(line_end_x, 0), EPD_WIDTH);
|
|
||||||
|
|
||||||
uint64_t now = esp_timer_get_time();
|
|
||||||
uint64_t diff = (now - last_frame_start) / 1000;
|
|
||||||
if (diff < MINIMUM_FRAME_TIME) {
|
|
||||||
vTaskDelay(MINIMUM_FRAME_TIME - diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_frame_start = esp_timer_get_time();
|
|
||||||
|
|
||||||
epd_start_frame();
|
|
||||||
for (int i = 0; i < EPD_HEIGHT; i++) {
|
|
||||||
if (i < min_y || i >= max_y) {
|
|
||||||
skip_row(frame_time);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (params->drawn_lines != NULL && !params->drawn_lines[i - area.y]) {
|
|
||||||
skip_row(frame_time);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t output[EPD_WIDTH];
|
|
||||||
xQueueReceive(*params->output_queue, output, portMAX_DELAY);
|
|
||||||
if (!params->error) {
|
|
||||||
(*input_calc_func)((uint32_t *)output, epd_get_current_buffer(),
|
|
||||||
params->conversion_lut);
|
|
||||||
if (line_start_x > 0 || line_end_x < EPD_WIDTH) {
|
|
||||||
mask_line_buffer(line_start_x, line_end_x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write_row(frame_time);
|
|
||||||
}
|
|
||||||
if (!skipping) {
|
|
||||||
// Since we "pipeline" row output, we still have to latch out the last
|
|
||||||
// row.
|
|
||||||
write_row(frame_time);
|
|
||||||
}
|
|
||||||
epd_end_frame();
|
|
||||||
|
|
||||||
xSemaphoreGive(params->done_smphr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esp_attr.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include "freertos/queue.h"
|
|
||||||
#include "epd_driver.h"
|
|
||||||
|
|
||||||
// number of bytes needed for one line of EPD pixel data.
|
|
||||||
#define EPD_LINE_BYTES EPD_WIDTH / 4
|
|
||||||
|
|
||||||
///////////////////////////// Utils /////////////////////////////////////
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reorder the output buffer to account for I2S FIFO order.
|
|
||||||
*/
|
|
||||||
void IRAM_ATTR reorder_line_buffer(uint32_t *line_data);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const uint8_t *data_ptr;
|
|
||||||
EpdRect crop_to;
|
|
||||||
SemaphoreHandle_t done_smphr;
|
|
||||||
SemaphoreHandle_t start_smphr;
|
|
||||||
EpdRect area;
|
|
||||||
int frame;
|
|
||||||
/// index of the waveform mode when using vendor waveforms.
|
|
||||||
/// This is not necessarily the mode number if the waveform header
|
|
||||||
//only contains a selection of modes!
|
|
||||||
int waveform_index;
|
|
||||||
/// waveform range when using vendor waveforms
|
|
||||||
int waveform_range;
|
|
||||||
/// Draw time for the current frame in 1/10ths of us.
|
|
||||||
int frame_time;
|
|
||||||
const EpdWaveform* waveform;
|
|
||||||
enum EpdDrawMode mode;
|
|
||||||
enum EpdDrawError error;
|
|
||||||
const bool *drawn_lines;
|
|
||||||
// Queue of input data lines
|
|
||||||
QueueHandle_t* output_queue;
|
|
||||||
|
|
||||||
// Lookup table size.
|
|
||||||
size_t conversion_lut_size;
|
|
||||||
// Lookup table space.
|
|
||||||
uint8_t* conversion_lut;
|
|
||||||
} OutputParams;
|
|
||||||
|
|
||||||
|
|
||||||
void IRAM_ATTR feed_display(OutputParams *params);
|
|
||||||
void IRAM_ATTR provide_out(OutputParams *params);
|
|
||||||
|
|
||||||
|
|
||||||
void IRAM_ATTR write_row(uint32_t output_time_dus);
|
|
||||||
void IRAM_ATTR skip_row(uint8_t pipeline_finish_time);
|
|
||||||
@ -1,382 +0,0 @@
|
|||||||
#include "epd_temperature.h"
|
|
||||||
#include "display_ops.h"
|
|
||||||
#include "epd_driver.h"
|
|
||||||
#include "include/epd_driver.h"
|
|
||||||
#include "include/epd_internals.h"
|
|
||||||
#include "lut.h"
|
|
||||||
|
|
||||||
#include "driver/rtc_io.h"
|
|
||||||
#include "esp_types.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/queue.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "xtensa/core-macros.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
inline int min(int x, int y) { return x < y ? x : y; }
|
|
||||||
inline int max(int x, int y) { return x > y ? x : y; }
|
|
||||||
|
|
||||||
const int clear_cycle_time = 12;
|
|
||||||
|
|
||||||
const int DEFAULT_FRAME_TIME = 120;
|
|
||||||
|
|
||||||
#define RTOS_ERROR_CHECK(x) \
|
|
||||||
do { \
|
|
||||||
esp_err_t __err_rc = (x); \
|
|
||||||
if (__err_rc != pdPASS) { \
|
|
||||||
abort(); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define CLEAR_BYTE 0B10101010
|
|
||||||
#define DARK_BYTE 0B01010101
|
|
||||||
|
|
||||||
// Queue of input data lines
|
|
||||||
static QueueHandle_t output_queue;
|
|
||||||
|
|
||||||
static OutputParams fetch_params;
|
|
||||||
static OutputParams feed_params;
|
|
||||||
|
|
||||||
// Space to use for the EPD output lookup table, which
|
|
||||||
// is calculated for each cycle.
|
|
||||||
static uint8_t* conversion_lut;
|
|
||||||
|
|
||||||
void epd_push_pixels(EpdRect area, short time, int color) {
|
|
||||||
|
|
||||||
uint8_t row[EPD_LINE_BYTES] = {0};
|
|
||||||
|
|
||||||
const uint8_t color_choice[4] = {DARK_BYTE, CLEAR_BYTE, 0x00, 0xFF};
|
|
||||||
for (uint32_t i = 0; i < area.width; i++) {
|
|
||||||
uint32_t position = i + area.x % 4;
|
|
||||||
uint8_t mask = color_choice[color] & (0b00000011 << (2 * (position % 4)));
|
|
||||||
row[area.x / 4 + position / 4] |= mask;
|
|
||||||
}
|
|
||||||
reorder_line_buffer((uint32_t *)row);
|
|
||||||
|
|
||||||
epd_start_frame();
|
|
||||||
|
|
||||||
for (int i = 0; i < EPD_HEIGHT; i++) {
|
|
||||||
// before are of interest: skip
|
|
||||||
if (i < area.y) {
|
|
||||||
skip_row(time);
|
|
||||||
// start area of interest: set row data
|
|
||||||
} else if (i == area.y) {
|
|
||||||
epd_switch_buffer();
|
|
||||||
memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES);
|
|
||||||
epd_switch_buffer();
|
|
||||||
memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES);
|
|
||||||
|
|
||||||
write_row(time * 10);
|
|
||||||
// load nop row if done with area
|
|
||||||
} else if (i >= area.y + area.height) {
|
|
||||||
skip_row(time);
|
|
||||||
// output the same as before
|
|
||||||
} else {
|
|
||||||
write_row(time * 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Since we "pipeline" row output, we still have to latch out the last row.
|
|
||||||
write_row(time * 10);
|
|
||||||
|
|
||||||
epd_end_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Coordination ///////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the waveform temperature range index for a given temperature in °C.
|
|
||||||
* If no range in the waveform data fits the given temperature, return the
|
|
||||||
* closest one.
|
|
||||||
* Returns -1 if the waveform does not contain any temperature range.
|
|
||||||
*/
|
|
||||||
int waveform_temp_range_index(const EpdWaveform* waveform, int temperature) {
|
|
||||||
int idx = 0;
|
|
||||||
if (waveform->num_temp_ranges == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
while (idx < waveform->num_temp_ranges - 1
|
|
||||||
&& waveform->temp_intervals[idx].min < temperature) {
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////// API Procedures //////////////////////////////////
|
|
||||||
|
|
||||||
static int get_waveform_index(const EpdWaveform* waveform, enum EpdDrawMode mode) {
|
|
||||||
for (int i=0; i < waveform->num_modes; i++) {
|
|
||||||
if (waveform->mode_data[i]->type == (mode & 0x3F)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError IRAM_ATTR epd_draw_base(EpdRect area,
|
|
||||||
const uint8_t *data,
|
|
||||||
EpdRect crop_to,
|
|
||||||
enum EpdDrawMode mode,
|
|
||||||
int temperature,
|
|
||||||
const bool *drawn_lines,
|
|
||||||
const EpdWaveform *waveform) {
|
|
||||||
uint8_t line[EPD_WIDTH / 2];
|
|
||||||
memset(line, 255, EPD_WIDTH / 2);
|
|
||||||
|
|
||||||
int waveform_range = waveform_temp_range_index(waveform, temperature);
|
|
||||||
if (waveform_range < 0) {
|
|
||||||
return EPD_DRAW_NO_PHASES_AVAILABLE;
|
|
||||||
}
|
|
||||||
int waveform_index = 0;
|
|
||||||
uint8_t frame_count = 0;
|
|
||||||
const EpdWaveformPhases* waveform_phases = NULL;
|
|
||||||
|
|
||||||
// no waveform required for monochrome mode
|
|
||||||
if (!(mode & MODE_EPDIY_MONOCHROME)) {
|
|
||||||
waveform_index = get_waveform_index(waveform, mode);
|
|
||||||
if (waveform_index < 0) {
|
|
||||||
return EPD_DRAW_MODE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
waveform_phases = waveform->mode_data[waveform_index]
|
|
||||||
->range_data[waveform_range];
|
|
||||||
// FIXME: error if not present
|
|
||||||
frame_count = waveform_phases->phases;
|
|
||||||
} else {
|
|
||||||
frame_count = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crop_to.width < 0 || crop_to.height < 0) {
|
|
||||||
return EPD_DRAW_INVALID_CROP;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool crop = (crop_to.width > 0 && crop_to.height > 0);
|
|
||||||
if (crop && (crop_to.width > area.width
|
|
||||||
|| crop_to.height > area.height
|
|
||||||
|| crop_to.x > area.width
|
|
||||||
|| crop_to.y > area.height)) {
|
|
||||||
return EPD_DRAW_INVALID_CROP;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t k = 0; k < frame_count; k++) {
|
|
||||||
|
|
||||||
int frame_time = DEFAULT_FRAME_TIME;
|
|
||||||
if (waveform_phases != NULL && waveform_phases->phase_times != NULL) {
|
|
||||||
frame_time = waveform_phases->phase_times[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode & MODE_EPDIY_MONOCHROME) {
|
|
||||||
frame_time = MONOCHROME_FRAME_TIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch_params.area = area;
|
|
||||||
// IMPORTANT: This must only ever read from PSRAM,
|
|
||||||
// Since the PSRAM workaround is disabled for lut.c
|
|
||||||
fetch_params.data_ptr = data;
|
|
||||||
fetch_params.crop_to = crop_to;
|
|
||||||
fetch_params.frame = k;
|
|
||||||
fetch_params.waveform_range = waveform_range;
|
|
||||||
fetch_params.waveform_index = waveform_index;
|
|
||||||
fetch_params.frame_time = frame_time;
|
|
||||||
fetch_params.mode = mode;
|
|
||||||
fetch_params.waveform = waveform;
|
|
||||||
fetch_params.error = EPD_DRAW_SUCCESS;
|
|
||||||
fetch_params.drawn_lines = drawn_lines;
|
|
||||||
fetch_params.output_queue = &output_queue;
|
|
||||||
|
|
||||||
feed_params.area = area;
|
|
||||||
feed_params.data_ptr = data;
|
|
||||||
feed_params.crop_to = crop_to;
|
|
||||||
feed_params.frame = k;
|
|
||||||
feed_params.frame_time = frame_time;
|
|
||||||
feed_params.waveform_range = waveform_range;
|
|
||||||
feed_params.waveform_index = waveform_index;
|
|
||||||
feed_params.mode = mode;
|
|
||||||
feed_params.waveform = waveform;
|
|
||||||
feed_params.error = EPD_DRAW_SUCCESS;
|
|
||||||
feed_params.drawn_lines = drawn_lines;
|
|
||||||
feed_params.output_queue = &output_queue;
|
|
||||||
|
|
||||||
xSemaphoreGive(fetch_params.start_smphr);
|
|
||||||
xSemaphoreGive(feed_params.start_smphr);
|
|
||||||
xSemaphoreTake(fetch_params.done_smphr, portMAX_DELAY);
|
|
||||||
xSemaphoreTake(feed_params.done_smphr, portMAX_DELAY);
|
|
||||||
|
|
||||||
enum EpdDrawError all_errors = fetch_params.error | feed_params.error;
|
|
||||||
if (all_errors != EPD_DRAW_SUCCESS) {
|
|
||||||
return all_errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EPD_DRAW_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_clear_area(EpdRect area) {
|
|
||||||
epd_clear_area_cycles(area, 3, clear_cycle_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time) {
|
|
||||||
const short white_time = cycle_time;
|
|
||||||
const short dark_time = cycle_time;
|
|
||||||
|
|
||||||
for (int c = 0; c < cycles; c++) {
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
epd_push_pixels(area, dark_time, 0);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
epd_push_pixels(area, white_time, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void epd_init(enum EpdInitOptions options) {
|
|
||||||
epd_base_init(EPD_WIDTH);
|
|
||||||
epd_temperature_init();
|
|
||||||
|
|
||||||
size_t lut_size = 0;
|
|
||||||
if (options & EPD_LUT_1K) {
|
|
||||||
lut_size = 1 << 10;
|
|
||||||
} else if ((options & EPD_LUT_64K) || (options == EPD_OPTIONS_DEFAULT)) {
|
|
||||||
lut_size = 1 << 16;
|
|
||||||
} else {
|
|
||||||
ESP_LOGE("epd", "invalid init options: %d", options);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
conversion_lut = (uint8_t *)heap_caps_malloc(lut_size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
|
||||||
if (conversion_lut == NULL) {
|
|
||||||
ESP_LOGE("epd", "could not allocate LUT!");
|
|
||||||
}
|
|
||||||
assert(conversion_lut != NULL);
|
|
||||||
|
|
||||||
fetch_params.conversion_lut = conversion_lut;
|
|
||||||
fetch_params.conversion_lut_size = lut_size;
|
|
||||||
feed_params.conversion_lut = conversion_lut;
|
|
||||||
feed_params.conversion_lut_size = lut_size;
|
|
||||||
|
|
||||||
fetch_params.done_smphr = xSemaphoreCreateBinary();
|
|
||||||
fetch_params.start_smphr = xSemaphoreCreateBinary();
|
|
||||||
|
|
||||||
feed_params.done_smphr = xSemaphoreCreateBinary();
|
|
||||||
feed_params.start_smphr = xSemaphoreCreateBinary();
|
|
||||||
|
|
||||||
RTOS_ERROR_CHECK(xTaskCreatePinnedToCore((void (*)(void *))provide_out,
|
|
||||||
"epd_fetch", (1 << 12), &fetch_params, 5,
|
|
||||||
NULL, 0));
|
|
||||||
|
|
||||||
RTOS_ERROR_CHECK(xTaskCreatePinnedToCore((void (*)(void *))feed_display,
|
|
||||||
"epd_feed", 1 << 12, &feed_params,
|
|
||||||
5, NULL, 1));
|
|
||||||
|
|
||||||
//conversion_lut = (uint8_t *)heap_caps_malloc(1 << 16, MALLOC_CAP_8BIT);
|
|
||||||
//assert(conversion_lut != NULL);
|
|
||||||
int queue_len = 32;
|
|
||||||
if (options & EPD_FEED_QUEUE_32) {
|
|
||||||
queue_len = 32;
|
|
||||||
} else if (options & EPD_FEED_QUEUE_8) {
|
|
||||||
queue_len = 8;
|
|
||||||
}
|
|
||||||
output_queue = xQueueCreate(queue_len, EPD_WIDTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void epd_deinit() {
|
|
||||||
// FIXME: deinit processes
|
|
||||||
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
|
|
||||||
gpio_reset_pin(CKH);
|
|
||||||
rtc_gpio_isolate(CKH);
|
|
||||||
#endif
|
|
||||||
epd_base_deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
EpdRect epd_difference_image_base(
|
|
||||||
const uint8_t* to,
|
|
||||||
const uint8_t* from,
|
|
||||||
EpdRect crop_to,
|
|
||||||
int fb_width,
|
|
||||||
int fb_height,
|
|
||||||
uint8_t* interlaced,
|
|
||||||
bool* dirty_lines,
|
|
||||||
uint8_t* from_or,
|
|
||||||
uint8_t* from_and
|
|
||||||
) {
|
|
||||||
assert(from_or != NULL);
|
|
||||||
assert(from_and != NULL);
|
|
||||||
// OR over all pixels of the "from"-image
|
|
||||||
*from_or = 0x00;
|
|
||||||
// AND over all pixels of the "from"-image
|
|
||||||
*from_and = 0xFF;
|
|
||||||
|
|
||||||
uint8_t dirty_cols[EPD_WIDTH] = {0};
|
|
||||||
int x_end = min(fb_width, crop_to.x + crop_to.width);
|
|
||||||
int y_end = min(fb_height, crop_to.y + crop_to.height);
|
|
||||||
|
|
||||||
for (int y=crop_to.y; y < y_end; y++) {
|
|
||||||
uint8_t dirty = 0;
|
|
||||||
for (int x = crop_to.x; x < x_end; x++) {
|
|
||||||
uint8_t t = *(to + y*fb_width / 2 + x / 2);
|
|
||||||
t = (x % 2) ? (t >> 4) : (t & 0x0f);
|
|
||||||
uint8_t f = *(from + y*fb_width / 2+ x / 2);
|
|
||||||
*from_or |= f;
|
|
||||||
*from_and &= f;
|
|
||||||
f = (x % 2) ? (f >> 4) : (f & 0x0f);
|
|
||||||
dirty |= (t ^ f);
|
|
||||||
dirty_cols[x] |= (t ^ f);
|
|
||||||
interlaced[y * fb_width + x] = (t << 4) | f;
|
|
||||||
}
|
|
||||||
dirty_lines[y] = dirty > 0;
|
|
||||||
}
|
|
||||||
int min_x, min_y, max_x, max_y;
|
|
||||||
for (min_x = crop_to.x; min_x < x_end; min_x++) {
|
|
||||||
if (dirty_cols[min_x] != 0) break;
|
|
||||||
}
|
|
||||||
for (max_x = x_end - 1; max_x >= crop_to.x; max_x--) {
|
|
||||||
if (dirty_cols[max_x] != 0) break;
|
|
||||||
}
|
|
||||||
for (min_y = crop_to.y; min_y < y_end; min_y++) {
|
|
||||||
if (dirty_lines[min_y] != 0) break;
|
|
||||||
}
|
|
||||||
for (max_y = y_end - 1; max_y >= crop_to.y; max_y--) {
|
|
||||||
if (dirty_lines[max_y] != 0) break;
|
|
||||||
}
|
|
||||||
EpdRect crop_rect = {
|
|
||||||
.x = min_x,
|
|
||||||
.y = min_y,
|
|
||||||
.width = max(max_x - min_x + 1, 0),
|
|
||||||
.height = max(max_y - min_y + 1, 0),
|
|
||||||
};
|
|
||||||
return crop_rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdRect epd_difference_image(
|
|
||||||
const uint8_t* to,
|
|
||||||
const uint8_t* from,
|
|
||||||
uint8_t* interlaced,
|
|
||||||
bool* dirty_lines
|
|
||||||
) {
|
|
||||||
uint8_t from_or = 0;
|
|
||||||
uint8_t from_and = 0;
|
|
||||||
return epd_difference_image_base(to, from, epd_full_screen(), EPD_WIDTH, EPD_HEIGHT, interlaced, dirty_lines, &from_or, &from_and);
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdRect epd_difference_image_cropped(
|
|
||||||
const uint8_t* to,
|
|
||||||
const uint8_t* from,
|
|
||||||
EpdRect crop_to,
|
|
||||||
uint8_t* interlaced,
|
|
||||||
bool* dirty_lines,
|
|
||||||
bool* previously_white,
|
|
||||||
bool* previously_black
|
|
||||||
) {
|
|
||||||
uint8_t from_or, from_and;
|
|
||||||
|
|
||||||
EpdRect result = epd_difference_image_base(to, from, crop_to, EPD_WIDTH, EPD_HEIGHT, interlaced, dirty_lines, &from_or, &from_and);
|
|
||||||
|
|
||||||
if (previously_white != NULL) *previously_white = (from_and == 0xFF);
|
|
||||||
if (previously_black != NULL) *previously_black = (from_or == 0x00);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
#include "rmt_pulse.h"
|
|
||||||
#include "driver/rmt.h"
|
|
||||||
#include "esp_system.h"
|
|
||||||
|
|
||||||
static intr_handle_t gRMT_intr_handle = NULL;
|
|
||||||
|
|
||||||
// the RMT channel configuration object
|
|
||||||
static rmt_config_t row_rmt_config;
|
|
||||||
|
|
||||||
// keep track of wether the current pulse is ongoing
|
|
||||||
volatile bool rmt_tx_done = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remote peripheral interrupt. Used to signal when transmission is done.
|
|
||||||
*/
|
|
||||||
static void IRAM_ATTR rmt_interrupt_handler(void *arg) {
|
|
||||||
rmt_tx_done = true;
|
|
||||||
RMT.int_clr.val = RMT.int_st.val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rmt_pulse_init(gpio_num_t pin) {
|
|
||||||
|
|
||||||
row_rmt_config.rmt_mode = RMT_MODE_TX;
|
|
||||||
// currently hardcoded: use channel 0
|
|
||||||
row_rmt_config.channel = RMT_CHANNEL_1;
|
|
||||||
|
|
||||||
row_rmt_config.gpio_num = pin;
|
|
||||||
row_rmt_config.mem_block_num = 2;
|
|
||||||
|
|
||||||
// Divide 80MHz APB Clock by 8 -> .1us resolution delay
|
|
||||||
row_rmt_config.clk_div = 8;
|
|
||||||
|
|
||||||
row_rmt_config.tx_config.loop_en = false;
|
|
||||||
row_rmt_config.tx_config.carrier_en = false;
|
|
||||||
row_rmt_config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
|
||||||
row_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
|
||||||
row_rmt_config.tx_config.idle_output_en = true;
|
|
||||||
|
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 2, 0) && ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 0, 2)
|
|
||||||
#error "This driver is not compatible with IDF version 4.1.\nPlease use 4.0 or >= 4.2!"
|
|
||||||
#endif
|
|
||||||
esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_LEVEL3,
|
|
||||||
rmt_interrupt_handler, 0, &gRMT_intr_handle);
|
|
||||||
|
|
||||||
rmt_config(&row_rmt_config);
|
|
||||||
rmt_set_tx_intr_en(row_rmt_config.channel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR pulse_ckv_ticks(uint16_t high_time_ticks,
|
|
||||||
uint16_t low_time_ticks, bool wait) {
|
|
||||||
while (!rmt_tx_done) {
|
|
||||||
};
|
|
||||||
volatile rmt_item32_t *rmt_mem_ptr =
|
|
||||||
&(RMTMEM.chan[row_rmt_config.channel].data32[0]);
|
|
||||||
if (high_time_ticks > 0) {
|
|
||||||
rmt_mem_ptr->level0 = 1;
|
|
||||||
rmt_mem_ptr->duration0 = high_time_ticks;
|
|
||||||
rmt_mem_ptr->level1 = 0;
|
|
||||||
rmt_mem_ptr->duration1 = low_time_ticks;
|
|
||||||
} else {
|
|
||||||
rmt_mem_ptr->level0 = 1;
|
|
||||||
rmt_mem_ptr->duration0 = low_time_ticks;
|
|
||||||
rmt_mem_ptr->level1 = 0;
|
|
||||||
rmt_mem_ptr->duration1 = 0;
|
|
||||||
}
|
|
||||||
RMTMEM.chan[row_rmt_config.channel].data32[1].val = 0;
|
|
||||||
rmt_tx_done = false;
|
|
||||||
RMT.conf_ch[row_rmt_config.channel].conf1.mem_rd_rst = 1;
|
|
||||||
RMT.conf_ch[row_rmt_config.channel].conf1.mem_owner = RMT_MEM_OWNER_TX;
|
|
||||||
RMT.conf_ch[row_rmt_config.channel].conf1.tx_start = 1;
|
|
||||||
while (wait && !rmt_tx_done) {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR pulse_ckv_us(uint16_t high_time_us, uint16_t low_time_us,
|
|
||||||
bool wait) {
|
|
||||||
pulse_ckv_ticks(10 * high_time_us, 10 * low_time_us, wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IRAM_ATTR rmt_busy() { return !rmt_tx_done; }
|
|
||||||
@ -1,4 +1,157 @@
|
|||||||
|
/**
|
||||||
|
* @file "epd_highlevel.h"
|
||||||
|
* @brief High-level API for drawing to e-paper displays.
|
||||||
|
*
|
||||||
|
* The functions in this library provide a simple way to manage updates of e-paper display.
|
||||||
|
* To use it, follow the steps below:
|
||||||
|
*
|
||||||
|
* 1. First, we declare a global object that manages the display state.
|
||||||
|
*
|
||||||
|
* EpdiyHighlevelState hl;
|
||||||
|
*
|
||||||
|
* 2. Then, the driver and framebuffers must be initialized.
|
||||||
|
*
|
||||||
|
* epd_init(EPD_LUT_1K);
|
||||||
|
* hl = epd_hl_init(EPD_BUILTIN_WAVEFORM);
|
||||||
|
*
|
||||||
|
* 3. Now, we can draw to the allocated framebuffer,
|
||||||
|
* using the draw and text functions defined in `epdiy.h`.
|
||||||
|
* This will not yet update the display, but only its representation in memory.
|
||||||
|
*
|
||||||
|
* // A reference to the framebuffer
|
||||||
|
* uint8_t* fb = epd_hl_get_framebuffer(&hl);
|
||||||
|
*
|
||||||
|
* // draw a black rectangle
|
||||||
|
* EpdRect some_rect = {
|
||||||
|
* .x = 100,
|
||||||
|
* .y = 100,
|
||||||
|
* .width = 100,
|
||||||
|
* .height = 100
|
||||||
|
* };
|
||||||
|
* epd_fill_rect(some_rect, 0x0, fb);
|
||||||
|
*
|
||||||
|
* // write a message
|
||||||
|
* int cursor_x = 100;
|
||||||
|
* int cursor_y = 300;
|
||||||
|
* epd_write_default(&FiraSans, "Hello, World!", &cursor_x, &cursor_y, fb);
|
||||||
|
*
|
||||||
|
* // finally, update the display!
|
||||||
|
* int temperature = 25;
|
||||||
|
* epd_poweron();
|
||||||
|
* EpdDrawError err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
|
||||||
|
* epd_poweroff();
|
||||||
|
*
|
||||||
|
* That's it! For many application, this will be enough.
|
||||||
|
* For special applications and requirements, have a
|
||||||
|
* closer look at the `epdiy.h` header.
|
||||||
|
*
|
||||||
|
* Colors
|
||||||
|
* ======
|
||||||
|
*
|
||||||
|
* Since most displays only support 16 colors, we're only using the upper 4 bits (nibble) of a byte
|
||||||
|
* to detect the color.
|
||||||
|
*
|
||||||
|
* char pixel_color = color & 0xF0;
|
||||||
|
*
|
||||||
|
* So keep in mind, when passing a color to any function, to always set the upper 4 bits, otherwise
|
||||||
|
* the color would be black.
|
||||||
|
*
|
||||||
|
* Possible colors are `0xF0` (white) through `0x80` (median gray) til `0x00` (black).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
#endif
|
||||||
#pragma once
|
#pragma once
|
||||||
// This file is only used in the Arduino IDE
|
#include <stdint.h>
|
||||||
// and just includes the IDF component header.
|
#include "epdiy.h"
|
||||||
#include "epd_driver/include/epd_highlevel.h"
|
|
||||||
|
#define EPD_BUILTIN_WAVEFORM NULL
|
||||||
|
|
||||||
|
/// Holds the internal state of the high-level API.
|
||||||
|
typedef struct {
|
||||||
|
/// The "front" framebuffer object.
|
||||||
|
uint8_t* front_fb;
|
||||||
|
/// The "back" framebuffer object.
|
||||||
|
uint8_t* back_fb;
|
||||||
|
/// Buffer for holding the interlaced difference image.
|
||||||
|
uint8_t* difference_fb;
|
||||||
|
/// Tainted lines based on the last difference calculation.
|
||||||
|
bool* dirty_lines;
|
||||||
|
/// Tainted column nibbles based on the last difference calculation.
|
||||||
|
uint8_t* dirty_columns;
|
||||||
|
/// The waveform information to use.
|
||||||
|
const EpdWaveform* waveform;
|
||||||
|
} EpdiyHighlevelState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a state object.
|
||||||
|
* This allocates two framebuffers and an update buffer for
|
||||||
|
* the display in the external PSRAM.
|
||||||
|
* In order to keep things simple, a chip reset is triggered if this fails.
|
||||||
|
*
|
||||||
|
* @param waveform: The waveform to use for updates.
|
||||||
|
* If you did not create your own, this will be `EPD_BUILTIN_WAVEFORM`.
|
||||||
|
* @returns An initialized state object.
|
||||||
|
*/
|
||||||
|
EpdiyHighlevelState epd_hl_init(const EpdWaveform* waveform);
|
||||||
|
|
||||||
|
/// Get a reference to the front framebuffer.
|
||||||
|
/// Use this to draw on the framebuffer before updating the screen with `epd_hl_update_screen()`.
|
||||||
|
uint8_t* epd_hl_get_framebuffer(EpdiyHighlevelState* state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the EPD screen to match the content of the front frame buffer.
|
||||||
|
* Prior to this, power to the display must be enabled via `epd_poweron()`
|
||||||
|
* and should be disabled afterwards if no immediate additional updates follow.
|
||||||
|
*
|
||||||
|
* @param state: A reference to the `EpdiyHighlevelState` object used.
|
||||||
|
* @param mode: The update mode to use.
|
||||||
|
* Additional mode settings like the framebuffer format or
|
||||||
|
* previous display state are determined by the driver and must not be supplied here.
|
||||||
|
* In most cases, one of `MODE_GC16` and `MODE_GL16` should be used.
|
||||||
|
* @param temperature: Environmental temperature of the display in °C.
|
||||||
|
* @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise.
|
||||||
|
*/
|
||||||
|
enum EpdDrawError epd_hl_update_screen(
|
||||||
|
EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an area of the screen to match the content of the front framebuffer.
|
||||||
|
* Supplying a small area to update can speed up the update process.
|
||||||
|
* Prior to this, power to the display must be enabled via `epd_poweron()`
|
||||||
|
* and should be disabled afterwards if no immediate additional updates follow.
|
||||||
|
*
|
||||||
|
* @param state: A reference to the `EpdiyHighlevelState` object used.
|
||||||
|
* @param mode: See `epd_hl_update_screen()`.
|
||||||
|
* @param temperature: Environmental temperature of the display in °C.
|
||||||
|
* @param area: Area of the screen to update.
|
||||||
|
* @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise.
|
||||||
|
*/
|
||||||
|
enum EpdDrawError epd_hl_update_area(
|
||||||
|
EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the front framebuffer to a white state.
|
||||||
|
*
|
||||||
|
* @param state: A reference to the `EpdiyHighlevelState` object used.
|
||||||
|
*/
|
||||||
|
void epd_hl_set_all_white(EpdiyHighlevelState* state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bring the display to a fully white state and get rid of any
|
||||||
|
* remaining artifacts.
|
||||||
|
*/
|
||||||
|
void epd_fullclear(EpdiyHighlevelState* state, int temperature);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just changes the used Waveform
|
||||||
|
*/
|
||||||
|
void epd_hl_waveform(EpdiyHighlevelState* state, const EpdWaveform* waveform);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
89
lib/libesp32_eink/epdiy/src/epd_internals.h
Normal file
89
lib/libesp32_eink/epdiy/src/epd_internals.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* @file epd_internals.h
|
||||||
|
* @brief Internal definitions and auxiliary data types.
|
||||||
|
*
|
||||||
|
* Unless you want to extend the library itself (Which you are very welcome to do),
|
||||||
|
* you will most likely not need to know about this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EPD_INTERNALS_H
|
||||||
|
#define EPD_INTERNALS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/// minimal draw time in ms for a frame layer,
|
||||||
|
/// which will allow all particles to set properly.
|
||||||
|
#define MINIMUM_FRAME_TIME 12
|
||||||
|
|
||||||
|
/// Frame draw time for monochrome mode in 1/10 us.
|
||||||
|
#define MONOCHROME_FRAME_TIME 120
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int phases;
|
||||||
|
const uint8_t* luts;
|
||||||
|
/// If we have timing information for the individual
|
||||||
|
/// phases, this is an array of the on-times for each phase.
|
||||||
|
/// Otherwise, this is NULL.
|
||||||
|
const int* phase_times;
|
||||||
|
} EpdWaveformPhases;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t temp_ranges;
|
||||||
|
EpdWaveformPhases const** range_data;
|
||||||
|
} EpdWaveformMode;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
} EpdWaveformTempInterval;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t num_modes;
|
||||||
|
uint8_t num_temp_ranges;
|
||||||
|
EpdWaveformMode const** mode_data;
|
||||||
|
EpdWaveformTempInterval const* temp_intervals;
|
||||||
|
} EpdWaveform;
|
||||||
|
|
||||||
|
extern const EpdWaveform epdiy_ED060SC4;
|
||||||
|
extern const EpdWaveform epdiy_ED097OC4;
|
||||||
|
extern const EpdWaveform epdiy_ED047TC1;
|
||||||
|
extern const EpdWaveform epdiy_ED047TC2;
|
||||||
|
extern const EpdWaveform epdiy_ED097TC2;
|
||||||
|
extern const EpdWaveform epdiy_ED060XC3;
|
||||||
|
extern const EpdWaveform epdiy_ED060SCT;
|
||||||
|
extern const EpdWaveform epdiy_ED133UT2;
|
||||||
|
extern const EpdWaveform epdiy_NULL;
|
||||||
|
|
||||||
|
/// Font data stored PER GLYPH
|
||||||
|
typedef struct {
|
||||||
|
uint16_t width; ///< Bitmap dimensions in pixels
|
||||||
|
uint16_t height; ///< Bitmap dimensions in pixels
|
||||||
|
uint16_t advance_x; ///< Distance to advance cursor (x axis)
|
||||||
|
int16_t left; ///< X dist from cursor pos to UL corner
|
||||||
|
int16_t top; ///< Y dist from cursor pos to UL corner
|
||||||
|
uint32_t compressed_size; ///< Size of the zlib-compressed font data.
|
||||||
|
uint32_t data_offset; ///< Pointer into EpdFont->bitmap
|
||||||
|
} EpdGlyph;
|
||||||
|
|
||||||
|
/// Glyph interval structure
|
||||||
|
typedef struct {
|
||||||
|
uint32_t first; ///< The first unicode code point of the interval
|
||||||
|
uint32_t last; ///< The last unicode code point of the interval
|
||||||
|
uint32_t offset; ///< Index of the first code point into the glyph array
|
||||||
|
} EpdUnicodeInterval;
|
||||||
|
|
||||||
|
/// Data stored for FONT AS A WHOLE
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t* bitmap; ///< Glyph bitmaps, concatenated
|
||||||
|
const EpdGlyph* glyph; ///< Glyph array
|
||||||
|
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
||||||
|
uint32_t interval_count; ///< Number of unicode intervals.
|
||||||
|
bool compressed; ///< Does this font use compressed glyph bitmaps?
|
||||||
|
uint16_t advance_y; ///< Newline distance (y axis)
|
||||||
|
int ascender; ///< Maximal height of a glyph above the base line
|
||||||
|
int descender; ///< Maximal height of a glyph below the base line
|
||||||
|
} EpdFont;
|
||||||
|
|
||||||
|
#endif // EPD_INTERNALS_H
|
||||||
545
lib/libesp32_eink/epdiy/src/epdiy.c
Normal file
545
lib/libesp32_eink/epdiy/src/epdiy.c
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
#include "epdiy.h"
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epd_display.h"
|
||||||
|
#include "output_common/render_method.h"
|
||||||
|
#include "render.h"
|
||||||
|
|
||||||
|
#include <esp_assert.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_types.h>
|
||||||
|
|
||||||
|
// Simple x and y coordinate
|
||||||
|
typedef struct {
|
||||||
|
uint16_t x;
|
||||||
|
uint16_t y;
|
||||||
|
} Coord_xy;
|
||||||
|
|
||||||
|
static const EpdDisplay_t* display = NULL;
|
||||||
|
|
||||||
|
// Display rotation. Can be updated using epd_set_rotation(enum EpdRotation)
|
||||||
|
static enum EpdRotation display_rotation = EPD_ROT_LANDSCAPE;
|
||||||
|
|
||||||
|
#ifndef _swap_int
|
||||||
|
#define _swap_int(a, b) \
|
||||||
|
{ \
|
||||||
|
int t = a; \
|
||||||
|
a = b; \
|
||||||
|
b = t; \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EpdRect epd_full_screen() {
|
||||||
|
EpdRect area = { .x = 0, .y = 0, .width = epd_width(), .height = epd_height() };
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_clear() {
|
||||||
|
epd_clear_area(epd_full_screen());
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_hline(int x, int y, int length, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int xx = x + i;
|
||||||
|
epd_draw_pixel(xx, y, color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_vline(int x, int y, int length, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int yy = y + i;
|
||||||
|
epd_draw_pixel(x, yy, color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Coord_xy _rotate(uint16_t x, uint16_t y) {
|
||||||
|
switch (display_rotation) {
|
||||||
|
case EPD_ROT_LANDSCAPE:
|
||||||
|
break;
|
||||||
|
case EPD_ROT_PORTRAIT:
|
||||||
|
_swap_int(x, y);
|
||||||
|
x = epd_width() - x - 1;
|
||||||
|
break;
|
||||||
|
case EPD_ROT_INVERTED_LANDSCAPE:
|
||||||
|
x = epd_width() - x - 1;
|
||||||
|
y = epd_height() - y - 1;
|
||||||
|
break;
|
||||||
|
case EPD_ROT_INVERTED_PORTRAIT:
|
||||||
|
_swap_int(x, y);
|
||||||
|
y = epd_height() - y - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Coord_xy coord = { x, y };
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_pixel(int x, int y, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
// Check rotation and move pixel around if necessary
|
||||||
|
Coord_xy coord = _rotate(x, y);
|
||||||
|
x = coord.x;
|
||||||
|
y = coord.y;
|
||||||
|
|
||||||
|
if (x < 0 || x >= epd_width()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (y < 0 || y >= epd_height()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* buf_ptr = &framebuffer[y * epd_width() / 2 + x / 2];
|
||||||
|
if (x % 2) {
|
||||||
|
*buf_ptr = (*buf_ptr & 0x0F) | (color & 0xF0);
|
||||||
|
} else {
|
||||||
|
*buf_ptr = (*buf_ptr & 0xF0) | (color >> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_circle(int x0, int y0, int r, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
int f = 1 - r;
|
||||||
|
int ddF_x = 1;
|
||||||
|
int ddF_y = -2 * r;
|
||||||
|
int x = 0;
|
||||||
|
int y = r;
|
||||||
|
|
||||||
|
epd_draw_pixel(x0, y0 + r, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0, y0 - r, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 + r, y0, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 - r, y0, color, framebuffer);
|
||||||
|
|
||||||
|
while (x < y) {
|
||||||
|
if (f >= 0) {
|
||||||
|
y--;
|
||||||
|
ddF_y += 2;
|
||||||
|
f += ddF_y;
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
ddF_x += 2;
|
||||||
|
f += ddF_x;
|
||||||
|
|
||||||
|
epd_draw_pixel(x0 + x, y0 + y, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 - x, y0 + y, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 + x, y0 - y, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 - x, y0 - y, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 + y, y0 + x, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 - y, y0 + x, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 + y, y0 - x, color, framebuffer);
|
||||||
|
epd_draw_pixel(x0 - y, y0 - x, color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_fill_circle(int x0, int y0, int r, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
epd_draw_vline(x0, y0 - r, 2 * r + 1, color, framebuffer);
|
||||||
|
epd_fill_circle_helper(x0, y0, r, 3, 0, color, framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_fill_circle_helper(
|
||||||
|
int x0, int y0, int r, int corners, int delta, uint8_t color, uint8_t* framebuffer
|
||||||
|
) {
|
||||||
|
int f = 1 - r;
|
||||||
|
int ddF_x = 1;
|
||||||
|
int ddF_y = -2 * r;
|
||||||
|
int x = 0;
|
||||||
|
int y = r;
|
||||||
|
int px = x;
|
||||||
|
int py = y;
|
||||||
|
|
||||||
|
delta++; // Avoid some +1's in the loop
|
||||||
|
|
||||||
|
while (x < y) {
|
||||||
|
if (f >= 0) {
|
||||||
|
y--;
|
||||||
|
ddF_y += 2;
|
||||||
|
f += ddF_y;
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
ddF_x += 2;
|
||||||
|
f += ddF_x;
|
||||||
|
// These checks avoid double-drawing certain lines, important
|
||||||
|
// for the SSD1306 library which has an INVERT drawing mode.
|
||||||
|
if (x < (y + 1)) {
|
||||||
|
if (corners & 1)
|
||||||
|
epd_draw_vline(x0 + x, y0 - y, 2 * y + delta, color, framebuffer);
|
||||||
|
if (corners & 2)
|
||||||
|
epd_draw_vline(x0 - x, y0 - y, 2 * y + delta, color, framebuffer);
|
||||||
|
}
|
||||||
|
if (y != py) {
|
||||||
|
if (corners & 1)
|
||||||
|
epd_draw_vline(x0 + py, y0 - px, 2 * px + delta, color, framebuffer);
|
||||||
|
if (corners & 2)
|
||||||
|
epd_draw_vline(x0 - py, y0 - px, 2 * px + delta, color, framebuffer);
|
||||||
|
py = y;
|
||||||
|
}
|
||||||
|
px = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
int x = rect.x;
|
||||||
|
int y = rect.y;
|
||||||
|
int w = rect.width;
|
||||||
|
int h = rect.height;
|
||||||
|
epd_draw_hline(x, y, w, color, framebuffer);
|
||||||
|
epd_draw_hline(x, y + h - 1, w, color, framebuffer);
|
||||||
|
epd_draw_vline(x, y, h, color, framebuffer);
|
||||||
|
epd_draw_vline(x + w - 1, y, h, color, framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_fill_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
int x = rect.x;
|
||||||
|
int y = rect.y;
|
||||||
|
int w = rect.width;
|
||||||
|
int h = rect.height;
|
||||||
|
for (int i = y; i < y + h; i++) {
|
||||||
|
epd_draw_hline(x, i, w, color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void epd_write_line(int x0, int y0, int x1, int y1, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
int steep = abs(y1 - y0) > abs(x1 - x0);
|
||||||
|
if (steep) {
|
||||||
|
_swap_int(x0, y0);
|
||||||
|
_swap_int(x1, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x0 > x1) {
|
||||||
|
_swap_int(x0, x1);
|
||||||
|
_swap_int(y0, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dx, dy;
|
||||||
|
dx = x1 - x0;
|
||||||
|
dy = abs(y1 - y0);
|
||||||
|
|
||||||
|
int err = dx / 2;
|
||||||
|
int ystep;
|
||||||
|
|
||||||
|
if (y0 < y1) {
|
||||||
|
ystep = 1;
|
||||||
|
} else {
|
||||||
|
ystep = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; x0 <= x1; x0++) {
|
||||||
|
if (steep) {
|
||||||
|
epd_draw_pixel(y0, x0, color, framebuffer);
|
||||||
|
} else {
|
||||||
|
epd_draw_pixel(x0, y0, color, framebuffer);
|
||||||
|
}
|
||||||
|
err -= dy;
|
||||||
|
if (err < 0) {
|
||||||
|
y0 += ystep;
|
||||||
|
err += dx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_line(int x0, int y0, int x1, int y1, uint8_t color, uint8_t* framebuffer) {
|
||||||
|
// Update in subclasses if desired!
|
||||||
|
if (x0 == x1) {
|
||||||
|
if (y0 > y1)
|
||||||
|
_swap_int(y0, y1);
|
||||||
|
epd_draw_vline(x0, y0, y1 - y0 + 1, color, framebuffer);
|
||||||
|
} else if (y0 == y1) {
|
||||||
|
if (x0 > x1)
|
||||||
|
_swap_int(x0, x1);
|
||||||
|
epd_draw_hline(x0, y0, x1 - x0 + 1, color, framebuffer);
|
||||||
|
} else {
|
||||||
|
epd_write_line(x0, y0, x1, y1, color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_triangle(
|
||||||
|
int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer
|
||||||
|
) {
|
||||||
|
epd_draw_line(x0, y0, x1, y1, color, framebuffer);
|
||||||
|
epd_draw_line(x1, y1, x2, y2, color, framebuffer);
|
||||||
|
epd_draw_line(x2, y2, x0, y0, color, framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_fill_triangle(
|
||||||
|
int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer
|
||||||
|
) {
|
||||||
|
int a, b, y, last;
|
||||||
|
|
||||||
|
// Sort coordinates by Y order (y2 >= y1 >= y0)
|
||||||
|
if (y0 > y1) {
|
||||||
|
_swap_int(y0, y1);
|
||||||
|
_swap_int(x0, x1);
|
||||||
|
}
|
||||||
|
if (y1 > y2) {
|
||||||
|
_swap_int(y2, y1);
|
||||||
|
_swap_int(x2, x1);
|
||||||
|
}
|
||||||
|
if (y0 > y1) {
|
||||||
|
_swap_int(y0, y1);
|
||||||
|
_swap_int(x0, x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
|
||||||
|
a = b = x0;
|
||||||
|
if (x1 < a)
|
||||||
|
a = x1;
|
||||||
|
else if (x1 > b)
|
||||||
|
b = x1;
|
||||||
|
if (x2 < a)
|
||||||
|
a = x2;
|
||||||
|
else if (x2 > b)
|
||||||
|
b = x2;
|
||||||
|
epd_draw_hline(a, y0, b - a + 1, color, framebuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, dx12 = x2 - x1,
|
||||||
|
dy12 = y2 - y1;
|
||||||
|
int32_t sa = 0, sb = 0;
|
||||||
|
|
||||||
|
// For upper part of triangle, find scanline crossings for segments
|
||||||
|
// 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
|
||||||
|
// is included here (and second loop will be skipped, avoiding a /0
|
||||||
|
// error there), otherwise scanline y1 is skipped here and handled
|
||||||
|
// in the second loop...which also avoids a /0 error here if y0=y1
|
||||||
|
// (flat-topped triangle).
|
||||||
|
if (y1 == y2)
|
||||||
|
last = y1; // Include y1 scanline
|
||||||
|
else
|
||||||
|
last = y1 - 1; // Skip it
|
||||||
|
|
||||||
|
for (y = y0; y <= last; y++) {
|
||||||
|
a = x0 + sa / dy01;
|
||||||
|
b = x0 + sb / dy02;
|
||||||
|
sa += dx01;
|
||||||
|
sb += dx02;
|
||||||
|
/* longhand:
|
||||||
|
a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
|
||||||
|
b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
|
||||||
|
*/
|
||||||
|
if (a > b)
|
||||||
|
_swap_int(a, b);
|
||||||
|
epd_draw_hline(a, y, b - a + 1, color, framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For lower part of triangle, find scanline crossings for segments
|
||||||
|
// 0-2 and 1-2. This loop is skipped if y1=y2.
|
||||||
|
sa = (int32_t)dx12 * (y - y1);
|
||||||
|
sb = (int32_t)dx02 * (y - y0);
|
||||||
|
for (; y <= y2; y++) {
|
||||||
|
a = x1 + sa / dy12;
|
||||||
|
b = x0 + sb / dy02;
|
||||||
|
sa += dx12;
|
||||||
|
sb += dx02;
|
||||||
|
/* longhand:
|
||||||
|
a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
|
||||||
|
b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
|
||||||
|
*/
|
||||||
|
if (a > b)
|
||||||
|
_swap_int(a, b);
|
||||||
|
epd_draw_hline(a, y, b - a + 1, color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_copy_to_framebuffer(EpdRect image_area, const uint8_t* image_data, uint8_t* framebuffer) {
|
||||||
|
assert(framebuffer != NULL);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < image_area.width * image_area.height; i++) {
|
||||||
|
uint32_t value_index = i;
|
||||||
|
// for images of uneven width,
|
||||||
|
// consume an additional nibble per row.
|
||||||
|
if (image_area.width % 2) {
|
||||||
|
value_index += i / image_area.width;
|
||||||
|
}
|
||||||
|
uint8_t val = (value_index % 2) ? (image_data[value_index / 2] & 0xF0) >> 4
|
||||||
|
: image_data[value_index / 2] & 0x0F;
|
||||||
|
|
||||||
|
int xx = image_area.x + i % image_area.width;
|
||||||
|
if (xx < 0 || xx >= epd_width()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int yy = image_area.y + i / image_area.width;
|
||||||
|
if (yy < 0 || yy >= epd_height()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
uint8_t* buf_ptr = &framebuffer[yy * epd_width() / 2 + xx / 2];
|
||||||
|
if (xx % 2) {
|
||||||
|
*buf_ptr = (*buf_ptr & 0x0F) | (val << 4);
|
||||||
|
} else {
|
||||||
|
*buf_ptr = (*buf_ptr & 0xF0) | val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdDrawError epd_draw_image(EpdRect area, const uint8_t* data, const EpdWaveform* waveform) {
|
||||||
|
int temperature = epd_ambient_temperature();
|
||||||
|
assert(waveform != NULL);
|
||||||
|
EpdRect no_crop = {
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = 0,
|
||||||
|
.height = 0,
|
||||||
|
};
|
||||||
|
return epd_draw_base(area, data, no_crop, EPD_MODE_DEFAULT, temperature, NULL, NULL, waveform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_set_rotation(enum EpdRotation rotation) {
|
||||||
|
display_rotation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdRotation epd_get_rotation() {
|
||||||
|
return display_rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
int epd_rotated_display_width() {
|
||||||
|
int display_width = epd_width();
|
||||||
|
switch (display_rotation) {
|
||||||
|
case EPD_ROT_PORTRAIT:
|
||||||
|
display_width = epd_height();
|
||||||
|
break;
|
||||||
|
case EPD_ROT_INVERTED_PORTRAIT:
|
||||||
|
display_width = epd_height();
|
||||||
|
break;
|
||||||
|
case EPD_ROT_INVERTED_LANDSCAPE:
|
||||||
|
case EPD_ROT_LANDSCAPE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return display_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int epd_rotated_display_height() {
|
||||||
|
int display_height = epd_height();
|
||||||
|
switch (display_rotation) {
|
||||||
|
case EPD_ROT_PORTRAIT:
|
||||||
|
display_height = epd_width();
|
||||||
|
break;
|
||||||
|
case EPD_ROT_INVERTED_PORTRAIT:
|
||||||
|
display_height = epd_width();
|
||||||
|
break;
|
||||||
|
case EPD_ROT_INVERTED_LANDSCAPE:
|
||||||
|
case EPD_ROT_LANDSCAPE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return display_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t epd_get_pixel(int x, int y, int fb_width, int fb_height, const uint8_t* framebuffer) {
|
||||||
|
if (x < 0 || x >= fb_width) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (y < 0 || y >= fb_height) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int fb_width_bytes = fb_width / 2 + fb_width % 2;
|
||||||
|
uint8_t buf_val = framebuffer[y * fb_width_bytes + x / 2];
|
||||||
|
if (x % 2) {
|
||||||
|
buf_val = (buf_val & 0xF0) >> 4;
|
||||||
|
} else {
|
||||||
|
buf_val = (buf_val & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
// epd_draw_pixel needs a 0 -> 255 value
|
||||||
|
return buf_val << 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_rotated_transparent_image(
|
||||||
|
EpdRect image_area,
|
||||||
|
const uint8_t* image_buffer,
|
||||||
|
uint8_t* framebuffer,
|
||||||
|
uint8_t* transparent_color
|
||||||
|
) {
|
||||||
|
uint16_t x_offset = 0;
|
||||||
|
uint16_t y_offset = 0;
|
||||||
|
uint8_t pixel_color;
|
||||||
|
for (uint16_t y = 0; y < image_area.height; y++) {
|
||||||
|
for (uint16_t x = 0; x < image_area.width; x++) {
|
||||||
|
x_offset = image_area.x + x;
|
||||||
|
y_offset = image_area.y + y;
|
||||||
|
if (x_offset >= epd_rotated_display_width())
|
||||||
|
continue;
|
||||||
|
if (y_offset >= epd_rotated_display_height())
|
||||||
|
continue;
|
||||||
|
pixel_color = epd_get_pixel(x, y, image_area.width, image_area.height, image_buffer);
|
||||||
|
if (transparent_color == NULL || pixel_color != (*transparent_color))
|
||||||
|
epd_draw_pixel(x_offset, y_offset, pixel_color, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_rotated_transparent_image(
|
||||||
|
EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer, uint8_t transparent_color
|
||||||
|
) {
|
||||||
|
draw_rotated_transparent_image(image_area, image_buffer, framebuffer, &transparent_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_draw_rotated_image(EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer) {
|
||||||
|
if (epd_get_rotation() != EPD_ROT_LANDSCAPE) {
|
||||||
|
draw_rotated_transparent_image(image_area, image_buffer, framebuffer, NULL);
|
||||||
|
} else {
|
||||||
|
epd_copy_to_framebuffer(image_area, image_buffer, framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_poweron() {
|
||||||
|
epd_current_board()->poweron(epd_ctrl_state());
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_poweroff() {
|
||||||
|
epd_current_board()->poweroff(epd_ctrl_state());
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_init(
|
||||||
|
const EpdBoardDefinition* board, const EpdDisplay_t* disp, enum EpdInitOptions options
|
||||||
|
) {
|
||||||
|
display = disp;
|
||||||
|
epd_set_board(board);
|
||||||
|
epd_renderer_init(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_deinit() {
|
||||||
|
epd_renderer_deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
float epd_ambient_temperature() {
|
||||||
|
if (!epd_current_board()) {
|
||||||
|
ESP_LOGE("epdiy", "Could not read temperature: board not set!");
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!epd_current_board()->get_temperature) {
|
||||||
|
ESP_LOGW("epdiy", "No ambient temperature sensor - returning 21C");
|
||||||
|
return 21.0;
|
||||||
|
}
|
||||||
|
return epd_current_board()->get_temperature();
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_set_vcom(uint16_t vcom) {
|
||||||
|
if (!epd_current_board()) {
|
||||||
|
ESP_LOGE("epdiy", "Could not set VCOM: board not set!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!epd_current_board()->set_vcom) {
|
||||||
|
ESP_LOGE("epdiy", "Could not set VCOM: board does not support setting VCOM in software.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return epd_current_board()->set_vcom(vcom);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdDisplay_t* epd_get_display() {
|
||||||
|
assert(display != NULL);
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
int epd_width() {
|
||||||
|
return display->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int epd_height() {
|
||||||
|
return display->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_set_lcd_pixel_clock_MHz(int frequency) {
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
void epd_lcd_set_pixel_clock_MHz(int frequency);
|
||||||
|
epd_lcd_set_pixel_clock_MHz(frequency);
|
||||||
|
#else
|
||||||
|
ESP_LOGW("epdiy", "called set_lcd_pixel_clock_MHz, but LCD driver is not used!");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
605
lib/libesp32_eink/epdiy/src/epdiy.h
Normal file
605
lib/libesp32_eink/epdiy/src/epdiy.h
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
/**
|
||||||
|
* @file epdiy.h
|
||||||
|
* A driver library for drawing to an EPD.
|
||||||
|
*/
|
||||||
|
#include "epd_display.h"
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <esp_attr.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "epd_internals.h"
|
||||||
|
|
||||||
|
/// An area on the display.
|
||||||
|
typedef struct {
|
||||||
|
/// Horizontal position.
|
||||||
|
int x;
|
||||||
|
/// Vertical position.
|
||||||
|
int y;
|
||||||
|
/// Area / image width, must be positive.
|
||||||
|
int width;
|
||||||
|
/// Area / image height, must be positive.
|
||||||
|
int height;
|
||||||
|
} EpdRect;
|
||||||
|
|
||||||
|
/// Global EPD driver options.
|
||||||
|
enum EpdInitOptions {
|
||||||
|
/// Use the default options.
|
||||||
|
EPD_OPTIONS_DEFAULT = 0,
|
||||||
|
/// Use a small look-up table of 1024 bytes.
|
||||||
|
/// The EPD driver will use less space, but performance may be worse.
|
||||||
|
EPD_LUT_1K = 1,
|
||||||
|
/// Use a 64K lookup table. (default)
|
||||||
|
/// Best performance, but permanently occupies a 64k block of internal memory.
|
||||||
|
EPD_LUT_64K = 2,
|
||||||
|
|
||||||
|
/// Use a small feed queue of 8 display lines.
|
||||||
|
/// This uses less memory, but may impact performance.
|
||||||
|
EPD_FEED_QUEUE_8 = 4,
|
||||||
|
/// Use a feed queue of 32 display lines. (default)
|
||||||
|
/// Best performance, but larger memory footprint.
|
||||||
|
EPD_FEED_QUEUE_32 = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The image drawing mode.
|
||||||
|
enum EpdDrawMode {
|
||||||
|
/// An init waveform.
|
||||||
|
/// This is currently unused, use `epd_clear()` instead.
|
||||||
|
MODE_INIT = 0x0,
|
||||||
|
/// Direct Update: Go from any color to black for white only.
|
||||||
|
MODE_DU = 0x1,
|
||||||
|
/// Go from any grayscale value to another with a flashing update.
|
||||||
|
MODE_GC16 = 0x2,
|
||||||
|
/// Faster version of `MODE_GC16`.
|
||||||
|
/// Not available with default epdiy waveforms.
|
||||||
|
MODE_GC16_FAST = 0x3,
|
||||||
|
/// Animation Mode: Fast, monochrom updates.
|
||||||
|
/// Not available with default epdiy waveforms.
|
||||||
|
MODE_A2 = 0x4,
|
||||||
|
/// Go from any grayscale value to another with a non-flashing update.
|
||||||
|
MODE_GL16 = 0x5,
|
||||||
|
/// Faster version of `MODE_GL16`.
|
||||||
|
/// Not available with default epdiy waveforms.
|
||||||
|
MODE_GL16_FAST = 0x6,
|
||||||
|
/// A 4-grayscale version of `MODE_DU`.
|
||||||
|
/// Not available with default epdiy waveforms.
|
||||||
|
MODE_DU4 = 0x7,
|
||||||
|
/// Arbitrary transitions for 4 grayscale values.
|
||||||
|
/// Not available with default epdiy waveforms.
|
||||||
|
MODE_GL4 = 0xA,
|
||||||
|
/// Not available with default epdiy waveforms.
|
||||||
|
MODE_GL16_INV = 0xB,
|
||||||
|
|
||||||
|
/// Go from a white screen to arbitrary grayscale, quickly.
|
||||||
|
/// Exclusively available with epdiy waveforms.
|
||||||
|
MODE_EPDIY_WHITE_TO_GL16 = 0x10,
|
||||||
|
/// Go from a black screen to arbitrary grayscale, quickly.
|
||||||
|
/// Exclusively available with epdiy waveforms.
|
||||||
|
MODE_EPDIY_BLACK_TO_GL16 = 0x11,
|
||||||
|
|
||||||
|
/// Monochrome mode. Only supported with 1bpp buffers.
|
||||||
|
MODE_EPDIY_MONOCHROME = 0x20,
|
||||||
|
|
||||||
|
MODE_UNKNOWN_WAVEFORM = 0x3F,
|
||||||
|
|
||||||
|
// Framebuffer packing modes
|
||||||
|
/// 1 bit-per-pixel framebuffer with 0 = black, 1 = white.
|
||||||
|
/// MSB is left is the leftmost pixel, LSB the rightmost pixel.
|
||||||
|
MODE_PACKING_8PPB = 0x40,
|
||||||
|
/// 4 bit-per pixel framebuffer with 0x0 = black, 0xF = white.
|
||||||
|
/// The upper nibble corresponds to the left pixel.
|
||||||
|
/// A byte cannot wrap over multiple rows, images of uneven width
|
||||||
|
/// must add a padding nibble per line.
|
||||||
|
MODE_PACKING_2PPB = 0x80,
|
||||||
|
/// A difference image with one pixel per byte.
|
||||||
|
/// The upper nibble marks the "from" color,
|
||||||
|
/// the lower nibble the "to" color.
|
||||||
|
MODE_PACKING_1PPB_DIFFERENCE = 0x100,
|
||||||
|
// reserver for 4PPB mode
|
||||||
|
|
||||||
|
/// Assert that the display has a uniform color, e.g. after initialization.
|
||||||
|
/// If `MODE_PACKING_2PPB` is specified, a optimized output calculation can be used.
|
||||||
|
/// Draw on a white background
|
||||||
|
PREVIOUSLY_WHITE = 0x200,
|
||||||
|
/// See `PREVIOUSLY_WHITE`.
|
||||||
|
/// Draw on a black background
|
||||||
|
PREVIOUSLY_BLACK = 0x400,
|
||||||
|
|
||||||
|
/// Enforce NOT using S3 Vector extensions.
|
||||||
|
/// USed for testing.
|
||||||
|
MODE_FORCE_NO_PIE = 0x800,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Display software rotation.
|
||||||
|
* Sets the rotation for the purposes of the drawing and font functions
|
||||||
|
* Use epd_set_rotation(EPD_ROT_*) to set it using one of the options below
|
||||||
|
* Use epd_get_rotation() in case you need to read this value
|
||||||
|
*/
|
||||||
|
enum EpdRotation {
|
||||||
|
EPD_ROT_LANDSCAPE = 0,
|
||||||
|
EPD_ROT_PORTRAIT = 1,
|
||||||
|
EPD_ROT_INVERTED_LANDSCAPE = 2,
|
||||||
|
EPD_ROT_INVERTED_PORTRAIT = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Possible failures when drawing.
|
||||||
|
enum EpdDrawError {
|
||||||
|
EPD_DRAW_SUCCESS = 0x0,
|
||||||
|
/// No valid framebuffer packing mode was specified.
|
||||||
|
EPD_DRAW_INVALID_PACKING_MODE = 0x1,
|
||||||
|
|
||||||
|
/// No lookup table implementation for this mode / packing.
|
||||||
|
EPD_DRAW_LOOKUP_NOT_IMPLEMENTED = 0x2,
|
||||||
|
|
||||||
|
/// The string to draw is invalid.
|
||||||
|
EPD_DRAW_STRING_INVALID = 0x4,
|
||||||
|
|
||||||
|
/// The string was not empty, but no characters where drawable.
|
||||||
|
EPD_DRAW_NO_DRAWABLE_CHARACTERS = 0x8,
|
||||||
|
|
||||||
|
/// Allocation failed
|
||||||
|
EPD_DRAW_FAILED_ALLOC = 0x10,
|
||||||
|
|
||||||
|
/// A glyph could not be drawn, and not fallback was present.
|
||||||
|
EPD_DRAW_GLYPH_FALLBACK_FAILED = 0x20,
|
||||||
|
|
||||||
|
/// The specified crop area is invalid.
|
||||||
|
EPD_DRAW_INVALID_CROP = 0x40,
|
||||||
|
|
||||||
|
/// No such mode is available with the current waveform.
|
||||||
|
EPD_DRAW_MODE_NOT_FOUND = 0x80,
|
||||||
|
|
||||||
|
/// The waveform info file contains no applicable temperature range.
|
||||||
|
EPD_DRAW_NO_PHASES_AVAILABLE = 0x100,
|
||||||
|
|
||||||
|
/// An invalid combination of font flags was used.
|
||||||
|
EPD_DRAW_INVALID_FONT_FLAGS = 0x200,
|
||||||
|
|
||||||
|
/// The waveform lookup could not keep up with the display output.
|
||||||
|
///
|
||||||
|
/// Reduce the display clock speed.
|
||||||
|
EPD_DRAW_EMPTY_LINE_QUEUE = 0x400,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The default draw mode (non-flashy refresh, whith previously white screen).
|
||||||
|
#define EPD_MODE_DEFAULT (MODE_GL16 | PREVIOUSLY_WHITE)
|
||||||
|
|
||||||
|
/// Font drawing flags
|
||||||
|
enum EpdFontFlags {
|
||||||
|
/// Draw a background.
|
||||||
|
///
|
||||||
|
/// Take the background into account
|
||||||
|
/// when calculating the size.
|
||||||
|
EPD_DRAW_BACKGROUND = 0x1,
|
||||||
|
|
||||||
|
/// Left-Align lines
|
||||||
|
EPD_DRAW_ALIGN_LEFT = 0x2,
|
||||||
|
/// Right-align lines
|
||||||
|
EPD_DRAW_ALIGN_RIGHT = 0x4,
|
||||||
|
/// Center-align lines
|
||||||
|
EPD_DRAW_ALIGN_CENTER = 0x8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Font properties.
|
||||||
|
typedef struct {
|
||||||
|
/// Foreground color
|
||||||
|
uint8_t fg_color : 4;
|
||||||
|
/// Background color
|
||||||
|
uint8_t bg_color : 4;
|
||||||
|
/// Use the glyph for this codepoint for missing glyphs.
|
||||||
|
uint32_t fallback_glyph;
|
||||||
|
/// Additional flags, reserved for future use
|
||||||
|
enum EpdFontFlags flags;
|
||||||
|
} EpdFontProperties;
|
||||||
|
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epd_board_specific.h"
|
||||||
|
#include "epd_display.h"
|
||||||
|
#include "epd_highlevel.h"
|
||||||
|
|
||||||
|
/** Initialize the ePaper display */
|
||||||
|
void epd_init(
|
||||||
|
const EpdBoardDefinition* board, const EpdDisplay_t* display, enum EpdInitOptions options
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured display.
|
||||||
|
*/
|
||||||
|
const EpdDisplay_t* epd_get_display();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the EPD display's witdth.
|
||||||
|
*/
|
||||||
|
int epd_width();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the EPD display's height.
|
||||||
|
*/
|
||||||
|
int epd_height();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the display common voltage if supported.
|
||||||
|
*
|
||||||
|
* Voltage is set as absolute value in millivolts.
|
||||||
|
* Although VCOM is negative, this function takes a positive (absolute) value.
|
||||||
|
*/
|
||||||
|
void epd_set_vcom(uint16_t vcom);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current ambient temperature in °C,
|
||||||
|
* if the board has a sensor.
|
||||||
|
*/
|
||||||
|
float epd_ambient_temperature();
|
||||||
|
|
||||||
|
/** Get the display rotation value */
|
||||||
|
enum EpdRotation epd_get_rotation();
|
||||||
|
|
||||||
|
/** Set the display rotation: Affects the drawing and font functions */
|
||||||
|
void epd_set_rotation(enum EpdRotation rotation);
|
||||||
|
|
||||||
|
/** Get screen width after rotation */
|
||||||
|
int epd_rotated_display_width();
|
||||||
|
|
||||||
|
/** Get screen height after rotation */
|
||||||
|
int epd_rotated_display_height();
|
||||||
|
|
||||||
|
/** Deinit the ePaper display */
|
||||||
|
void epd_deinit();
|
||||||
|
|
||||||
|
/** Enable display power supply. */
|
||||||
|
void epd_poweron();
|
||||||
|
|
||||||
|
/** Disable display power supply. */
|
||||||
|
void epd_poweroff();
|
||||||
|
|
||||||
|
/** Clear the whole screen by flashing it. */
|
||||||
|
void epd_clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear an area by flashing it.
|
||||||
|
*
|
||||||
|
* @param area: The area to clear.
|
||||||
|
*/
|
||||||
|
void epd_clear_area(EpdRect area);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear an area by flashing it.
|
||||||
|
*
|
||||||
|
* @param area: The area to clear.
|
||||||
|
* @param cycles: The number of black-to-white clear cycles.
|
||||||
|
* @param cycle_time: Length of a cycle. Default: 50 (us).
|
||||||
|
*/
|
||||||
|
void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Rectancle representing the whole screen area.
|
||||||
|
*/
|
||||||
|
EpdRect epd_full_screen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a picture to a given framebuffer.
|
||||||
|
*
|
||||||
|
* @param image_area: The area to copy to. `width` and `height` of the area
|
||||||
|
* must correspond to the image dimensions in pixels.
|
||||||
|
* @param image_data: The image data, as a buffer of 4 bit wide brightness
|
||||||
|
* values. Pixel data is packed (two pixels per byte). A byte cannot wrap over
|
||||||
|
* multiple rows, images of uneven width must add a padding nibble per line.
|
||||||
|
* @param framebuffer: The framebuffer object,
|
||||||
|
* which must be `epd_width() / 2 * epd_height()` large.
|
||||||
|
*/
|
||||||
|
void epd_copy_to_framebuffer(EpdRect image_area, const uint8_t* image_data, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a pixel a given framebuffer.
|
||||||
|
*
|
||||||
|
* @param x: Horizontal position in pixels.
|
||||||
|
* @param y: Vertical position in pixels.
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_draw_pixel(int x, int y, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a horizontal line to a given framebuffer.
|
||||||
|
*
|
||||||
|
* @param x: Horizontal start position in pixels.
|
||||||
|
* @param y: Vertical start position in pixels.
|
||||||
|
* @param length: Length of the line in pixels.
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
* which must be `epd_width() / 2 * epd_height()` bytes large.
|
||||||
|
*/
|
||||||
|
void epd_draw_hline(int x, int y, int length, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a horizontal line to a given framebuffer.
|
||||||
|
*
|
||||||
|
* @param x: Horizontal start position in pixels.
|
||||||
|
* @param y: Vertical start position in pixels.
|
||||||
|
* @param length: Length of the line in pixels.
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
* which must be `epd_width() / 2 * epd_height()` bytes large.
|
||||||
|
*/
|
||||||
|
void epd_draw_vline(int x, int y, int length, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
void epd_fill_circle_helper(
|
||||||
|
int x0, int y0, int r, int corners, int delta, uint8_t color, uint8_t* framebuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a circle with given center and radius
|
||||||
|
*
|
||||||
|
* @param x: Center-point x coordinate
|
||||||
|
* @param y: Center-point y coordinate
|
||||||
|
* @param r: Radius of the circle in pixels
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_draw_circle(int x, int y, int r, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a circle with fill with given center and radius
|
||||||
|
*
|
||||||
|
* @param x: Center-point x coordinate
|
||||||
|
* @param y: Center-point y coordinate
|
||||||
|
* @param r: Radius of the circle in pixels
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_fill_circle(int x, int y, int r, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a rectanle with no fill color
|
||||||
|
*
|
||||||
|
* @param rect: The rectangle to draw.
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_draw_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a rectanle with fill color
|
||||||
|
*
|
||||||
|
* @param rect: The rectangle to fill.
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_fill_rect(EpdRect rect, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a line
|
||||||
|
*
|
||||||
|
* @param x0 Start point x coordinate
|
||||||
|
* @param y0 Start point y coordinate
|
||||||
|
* @param x1 End point x coordinate
|
||||||
|
* @param y1 End point y coordinate
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_draw_line(int x0, int y0, int x1, int y1, uint8_t color, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a triangle with no fill color
|
||||||
|
*
|
||||||
|
* @param x0 Vertex #0 x coordinate
|
||||||
|
* @param y0 Vertex #0 y coordinate
|
||||||
|
* @param x1 Vertex #1 x coordinate
|
||||||
|
* @param y1 Vertex #1 y coordinate
|
||||||
|
* @param x2 Vertex #2 x coordinate
|
||||||
|
* @param y2 Vertex #2 y coordinate
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_draw_triangle(
|
||||||
|
int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a triangle with color-fill
|
||||||
|
*
|
||||||
|
* @param x0 Vertex #0 x coordinate
|
||||||
|
* @param y0 Vertex #0 y coordinate
|
||||||
|
* @param x1 Vertex #1 x coordinate
|
||||||
|
* @param y1 Vertex #1 y coordinate
|
||||||
|
* @param x2 Vertex #2 x coordinate
|
||||||
|
* @param y2 Vertex #2 y coordinate
|
||||||
|
* @param color: The gray value of the line (see [Colors](#Colors));
|
||||||
|
* @param framebuffer: The framebuffer to draw to,
|
||||||
|
*/
|
||||||
|
void epd_fill_triangle(
|
||||||
|
int x0, int y0, int x1, int y1, int x2, int y2, uint8_t color, uint8_t* framebuffer
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Get the current ambient temperature in °C, if supported by the board.
|
||||||
|
* Requires the display to be powered on.
|
||||||
|
*/
|
||||||
|
float epd_ambient_temperature();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default font properties.
|
||||||
|
*/
|
||||||
|
EpdFontProperties epd_font_properties_default();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get the text bounds for string, when drawn at (x, y).
|
||||||
|
* Set font properties to NULL to use the defaults.
|
||||||
|
*/
|
||||||
|
void epd_get_text_bounds(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
const int* x,
|
||||||
|
const int* y,
|
||||||
|
int* x1,
|
||||||
|
int* y1,
|
||||||
|
int* w,
|
||||||
|
int* h,
|
||||||
|
const EpdFontProperties* props
|
||||||
|
);
|
||||||
|
/*!
|
||||||
|
* Returns a rect with the bounds of the text
|
||||||
|
* @param font : the font used to get the character sizes
|
||||||
|
* @param string: pointer to c string
|
||||||
|
* @param x : left most position of rectangle
|
||||||
|
* @param y : top most point of the rectangle
|
||||||
|
* @param margin : to be pllied to the width and height
|
||||||
|
* @returns EpdRect with x and y as per the original and height and width
|
||||||
|
* adjusted to fit the text with the margin added as well.
|
||||||
|
*/
|
||||||
|
EpdRect epd_get_string_rect(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int margin,
|
||||||
|
const EpdFontProperties* properties
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write text to the EPD.
|
||||||
|
*/
|
||||||
|
enum EpdDrawError epd_write_string(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
int* cursor_x,
|
||||||
|
int* cursor_y,
|
||||||
|
uint8_t* framebuffer,
|
||||||
|
const EpdFontProperties* properties
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a (multi-line) string to the EPD.
|
||||||
|
*/
|
||||||
|
enum EpdDrawError epd_write_default(
|
||||||
|
const EpdFont* font, const char* string, int* cursor_x, int* cursor_y, uint8_t* framebuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font glyph for a unicode code point.
|
||||||
|
*/
|
||||||
|
const EpdGlyph* epd_get_glyph(const EpdFont* font, uint32_t code_point);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Darken / lighten an area for a given time.
|
||||||
|
*
|
||||||
|
* @param area: The area to darken / lighten.
|
||||||
|
* @param time: The time in us to apply voltage to each pixel.
|
||||||
|
* @param color: 1: lighten, 0: darken.
|
||||||
|
*/
|
||||||
|
void epd_push_pixels(EpdRect area, short time, int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base function for drawing an image on the screen.
|
||||||
|
* If It is very customizable, and the documentation below should be studied carefully.
|
||||||
|
* For simple applications, use the epdiy highlevel api in "epd_higlevel.h".
|
||||||
|
*
|
||||||
|
* @param area: The area of the screen to draw to.
|
||||||
|
* This can be imagined as shifting the origin of the frame buffer.
|
||||||
|
* @param data: A full framebuffer of display data.
|
||||||
|
* It's structure depends on the chosen `mode`.
|
||||||
|
* @param crop_to: Only draw a part of the frame buffer.
|
||||||
|
* Set to `epd_full_screen()` to draw the full buffer.
|
||||||
|
* @param mode: Specifies the Waveform used, the framebuffer format
|
||||||
|
* and additional information, like if the display is cleared.
|
||||||
|
* @param temperature: The temperature of the display in °C.
|
||||||
|
* Currently, this is unused by the default waveforms at can be
|
||||||
|
* set to room temperature, e.g. 20-25°C.
|
||||||
|
* @param drawn_lines: If not NULL, an array of at least the height of the
|
||||||
|
* image. Every line where the corresponding value in `lines` is `false` will be
|
||||||
|
* skipped.
|
||||||
|
* @param drawn_columns: If not NULL, an array of at least the width of the
|
||||||
|
* image / 2, 16-byte aligned.
|
||||||
|
* The image will only be updated in pixel columns where the corresponding nibbles are
|
||||||
|
* non-zero.
|
||||||
|
* @param waveform: The waveform information to use for drawing.
|
||||||
|
* If you don't have special waveforms, use `EPD_BUILTIN_WAVEFORM`.
|
||||||
|
* @returns `EPD_DRAW_SUCCESS` on sucess, a combination of error flags otherwise.
|
||||||
|
*/
|
||||||
|
enum EpdDrawError epd_draw_base(
|
||||||
|
EpdRect area,
|
||||||
|
const uint8_t* data,
|
||||||
|
EpdRect crop_to,
|
||||||
|
enum EpdDrawMode mode,
|
||||||
|
int temperature,
|
||||||
|
const bool* drawn_lines,
|
||||||
|
const uint8_t* drawn_columns,
|
||||||
|
const EpdWaveform* waveform
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Calculate a `MODE_PACKING_1PPB_DIFFERENCE` difference image
|
||||||
|
* from two `MODE_PACKING_2PPB` (4 bit-per-pixel) buffers.
|
||||||
|
* If you're using the epdiy highlevel api, this is handled by the update functions.
|
||||||
|
*
|
||||||
|
* @param to: The goal image as 4-bpp (`MODE_PACKING_2PPB`) framebuffer.
|
||||||
|
* @param from: The previous image as 4-bpp (`MODE_PACKING_2PPB`) framebuffer.
|
||||||
|
* @param crop_to: Only calculate the difference for a crop of the input framebuffers.
|
||||||
|
* The `interlaced` will not be modified outside the crop area.
|
||||||
|
* @param interlaced: The resulting difference image in `MODE_PACKING_1PPB_DIFFERENCE` format.
|
||||||
|
* @param dirty_lines: An array of at least `epd_height()`.
|
||||||
|
* The positions corresponding to lines where `to` and `from` differ
|
||||||
|
* are set to `true`, otherwise to `false`.
|
||||||
|
* @param col_dirtyness: An array of at least `epd_width() / 2`.
|
||||||
|
* If a nibble is set to non-zero, the pixel column is marked as changed, aka "dirty."
|
||||||
|
* The buffer must be 16 byte aligned.
|
||||||
|
* @returns The smallest rectangle containing all changed pixels.
|
||||||
|
*/
|
||||||
|
EpdRect epd_difference_image_cropped(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
EpdRect crop_to,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
bool* dirty_lines,
|
||||||
|
uint8_t* col_dirtiness
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified version of `epd_difference_image_cropped()`, which considers the
|
||||||
|
* whole display frame buffer.
|
||||||
|
*
|
||||||
|
* See `epd_difference_image_cropped() for details.`
|
||||||
|
*/
|
||||||
|
EpdRect epd_difference_image(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
bool* dirty_lines,
|
||||||
|
uint8_t* col_dirtiness
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the pixel color of a 4 bit image array
|
||||||
|
* x,y coordinates of the image pixel
|
||||||
|
* fb_width, fb_height dimensions
|
||||||
|
* @returns uint8_t 0-255 representing the color on given coordinates (as in epd_draw_pixel)
|
||||||
|
*/
|
||||||
|
uint8_t epd_get_pixel(int x, int y, int fb_width, int fb_height, const uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw an image reading pixel per pixel and being rotation aware (via epd_draw_pixel)
|
||||||
|
*/
|
||||||
|
void epd_draw_rotated_image(EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw an image reading pixel per pixel and being rotation aware (via epd_draw_pixel)
|
||||||
|
* With an optional transparent color (color key transparency)
|
||||||
|
*/
|
||||||
|
void epd_draw_rotated_transparent_image(
|
||||||
|
EpdRect image_area, const uint8_t* image_buffer, uint8_t* framebuffer, uint8_t transparent_color
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the pixel clock when using the LCD driver for display output (Epdiy V7+).
|
||||||
|
* This may result in draws failing if it's set too high!
|
||||||
|
*
|
||||||
|
* This method can be used to tune your application for maximum refresh speed,
|
||||||
|
* if you can guarantee the driver can keep up.
|
||||||
|
*/
|
||||||
|
void epd_set_lcd_pixel_clock_MHz(int frequency);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
434
lib/libesp32_eink/epdiy/src/font.c
Normal file
434
lib/libesp32_eink/epdiy/src/font.c
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
#include <esp_assert.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <esp_idf_version.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include <miniz.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t mask; /* char data will be bitwise AND with this */
|
||||||
|
uint8_t lead; /* start bytes of current char in utf-8 encoded character */
|
||||||
|
uint32_t beg; /* beginning of codepoint range */
|
||||||
|
uint32_t end; /* end of codepoint range */
|
||||||
|
int bits_stored; /* the number of bits from the codepoint that fits in char */
|
||||||
|
} utf_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UTF-8 decode inspired from rosetta code
|
||||||
|
* https://rosettacode.org/wiki/UTF-8_encode_and_decode#C
|
||||||
|
*/
|
||||||
|
static utf_t* utf[] = {
|
||||||
|
/* mask lead beg end bits */
|
||||||
|
[0] = &(utf_t){ 0b00111111, 0b10000000, 0, 0, 6 },
|
||||||
|
[1] = &(utf_t){ 0b01111111, 0b00000000, 0000, 0177, 7 },
|
||||||
|
[2] = &(utf_t){ 0b00011111, 0b11000000, 0200, 03777, 5 },
|
||||||
|
[3] = &(utf_t){ 0b00001111, 0b11100000, 04000, 0177777, 4 },
|
||||||
|
[4] = &(utf_t){ 0b00000111, 0b11110000, 0200000, 04177777, 3 },
|
||||||
|
&(utf_t){ 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline int min(int x, int y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
static inline int max(int x, int y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int utf8_len(const uint8_t ch) {
|
||||||
|
int len = 0;
|
||||||
|
for (utf_t** u = utf; (*u)->mask; ++u) {
|
||||||
|
if ((ch & ~(*u)->mask) == (*u)->lead) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
if (len > 4) { /* Malformed leading byte */
|
||||||
|
assert("invalid unicode.");
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t next_cp(const uint8_t** string) {
|
||||||
|
if (**string == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int bytes = utf8_len(**string);
|
||||||
|
const uint8_t* chr = *string;
|
||||||
|
*string += bytes;
|
||||||
|
int shift = utf[0]->bits_stored * (bytes - 1);
|
||||||
|
uint32_t codep = (*chr++ & utf[bytes]->mask) << shift;
|
||||||
|
|
||||||
|
for (int i = 1; i < bytes; ++i, ++chr) {
|
||||||
|
shift -= utf[0]->bits_stored;
|
||||||
|
codep |= ((const uint8_t)*chr & utf[0]->mask) << shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
return codep;
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdFontProperties epd_font_properties_default() {
|
||||||
|
EpdFontProperties props
|
||||||
|
= { .fg_color = 0, .bg_color = 15, .fallback_glyph = 0, .flags = EPD_DRAW_ALIGN_LEFT };
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdGlyph* epd_get_glyph(const EpdFont* font, uint32_t code_point) {
|
||||||
|
const EpdUnicodeInterval* intervals = font->intervals;
|
||||||
|
for (int i = 0; i < font->interval_count; i++) {
|
||||||
|
const EpdUnicodeInterval* interval = &intervals[i];
|
||||||
|
if (code_point >= interval->first && code_point <= interval->last) {
|
||||||
|
return &font->glyph[interval->offset + (code_point - interval->first)];
|
||||||
|
}
|
||||||
|
if (code_point < interval->first) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int uncompress(
|
||||||
|
uint8_t* dest, size_t uncompressed_size, const uint8_t* source, size_t source_size
|
||||||
|
) {
|
||||||
|
tinfl_decompressor* decomp;
|
||||||
|
if (uncompressed_size == 0 || dest == NULL || source_size == 0 || source == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
decomp = malloc(sizeof(tinfl_decompressor));
|
||||||
|
if (!decomp) {
|
||||||
|
// Out of memory
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
tinfl_init(decomp);
|
||||||
|
|
||||||
|
// we know everything will fit into the buffer.
|
||||||
|
tinfl_status decomp_status = tinfl_decompress(
|
||||||
|
decomp,
|
||||||
|
source,
|
||||||
|
&source_size,
|
||||||
|
dest,
|
||||||
|
dest,
|
||||||
|
&uncompressed_size,
|
||||||
|
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF
|
||||||
|
);
|
||||||
|
free(decomp);
|
||||||
|
if (decomp_status != TINFL_STATUS_DONE) {
|
||||||
|
return decomp_status;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Draw a single character to a pre-allocated buffer.
|
||||||
|
*/
|
||||||
|
static enum EpdDrawError IRAM_ATTR draw_char(
|
||||||
|
const EpdFont* font,
|
||||||
|
uint8_t* buffer,
|
||||||
|
int* cursor_x,
|
||||||
|
int cursor_y,
|
||||||
|
uint32_t cp,
|
||||||
|
const EpdFontProperties* props
|
||||||
|
) {
|
||||||
|
assert(props != NULL);
|
||||||
|
|
||||||
|
const EpdGlyph* glyph = epd_get_glyph(font, cp);
|
||||||
|
if (!glyph) {
|
||||||
|
glyph = epd_get_glyph(font, props->fallback_glyph);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!glyph) {
|
||||||
|
return EPD_DRAW_GLYPH_FALLBACK_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t offset = glyph->data_offset;
|
||||||
|
uint16_t width = glyph->width, height = glyph->height;
|
||||||
|
int left = glyph->left;
|
||||||
|
|
||||||
|
int byte_width = (width / 2 + width % 2);
|
||||||
|
unsigned long bitmap_size = byte_width * height;
|
||||||
|
const uint8_t* bitmap = NULL;
|
||||||
|
if (bitmap_size > 0 && font->compressed) {
|
||||||
|
uint8_t* tmp_bitmap = (uint8_t*)malloc(bitmap_size);
|
||||||
|
if (tmp_bitmap == NULL && bitmap_size) {
|
||||||
|
ESP_LOGE("font", "malloc failed.");
|
||||||
|
return EPD_DRAW_FAILED_ALLOC;
|
||||||
|
}
|
||||||
|
uncompress(tmp_bitmap, bitmap_size, &font->bitmap[offset], glyph->compressed_size);
|
||||||
|
bitmap = tmp_bitmap;
|
||||||
|
} else {
|
||||||
|
bitmap = &font->bitmap[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t color_lut[16];
|
||||||
|
for (int c = 0; c < 16; c++) {
|
||||||
|
int color_difference = (int)props->fg_color - (int)props->bg_color;
|
||||||
|
color_lut[c] = max(0, min(15, props->bg_color + c * color_difference / 15));
|
||||||
|
}
|
||||||
|
bool background_needed = props->flags & EPD_DRAW_BACKGROUND;
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
int yy = cursor_y - glyph->top + y;
|
||||||
|
int start_pos = *cursor_x + left;
|
||||||
|
bool byte_complete = start_pos % 2;
|
||||||
|
int x = max(0, -start_pos);
|
||||||
|
int max_x = start_pos + width;
|
||||||
|
uint8_t color;
|
||||||
|
|
||||||
|
for (int xx = start_pos; xx < max_x; xx++) {
|
||||||
|
uint8_t bm = bitmap[y * byte_width + x / 2];
|
||||||
|
if ((x & 1) == 0) {
|
||||||
|
bm = bm & 0xF;
|
||||||
|
} else {
|
||||||
|
bm = bm >> 4;
|
||||||
|
}
|
||||||
|
if (background_needed || bm) {
|
||||||
|
color = color_lut[bm] << 4;
|
||||||
|
epd_draw_pixel(xx, yy, color, buffer);
|
||||||
|
}
|
||||||
|
byte_complete = !byte_complete;
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bitmap_size > 0 && font->compressed) {
|
||||||
|
free((uint8_t*)bitmap);
|
||||||
|
}
|
||||||
|
*cursor_x += glyph->advance_x;
|
||||||
|
return EPD_DRAW_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Calculate the bounds of a character when drawn at (x, y), move the
|
||||||
|
* cursor (*x) forward, adjust the given bounds.
|
||||||
|
*/
|
||||||
|
static void get_char_bounds(
|
||||||
|
const EpdFont* font,
|
||||||
|
uint32_t cp,
|
||||||
|
int* x,
|
||||||
|
int* y,
|
||||||
|
int* minx,
|
||||||
|
int* miny,
|
||||||
|
int* maxx,
|
||||||
|
int* maxy,
|
||||||
|
const EpdFontProperties* props
|
||||||
|
) {
|
||||||
|
assert(props != NULL);
|
||||||
|
|
||||||
|
const EpdGlyph* glyph = epd_get_glyph(font, cp);
|
||||||
|
|
||||||
|
if (!glyph) {
|
||||||
|
glyph = epd_get_glyph(font, props->fallback_glyph);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!glyph) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x1 = *x + glyph->left, y1 = *y + glyph->top - glyph->height, x2 = x1 + glyph->width,
|
||||||
|
y2 = y1 + glyph->height;
|
||||||
|
|
||||||
|
// background needs to be taken into account
|
||||||
|
if (props->flags & EPD_DRAW_BACKGROUND) {
|
||||||
|
*minx = min(*x, min(*minx, x1));
|
||||||
|
*maxx = max(max(*x + glyph->advance_x, x2), *maxx);
|
||||||
|
*miny = min(*y + font->descender, min(*miny, y1));
|
||||||
|
*maxy = max(*y + font->ascender, max(*maxy, y2));
|
||||||
|
} else {
|
||||||
|
if (x1 < *minx)
|
||||||
|
*minx = x1;
|
||||||
|
if (y1 < *miny)
|
||||||
|
*miny = y1;
|
||||||
|
if (x2 > *maxx)
|
||||||
|
*maxx = x2;
|
||||||
|
if (y2 > *maxy)
|
||||||
|
*maxy = y2;
|
||||||
|
}
|
||||||
|
*x += glyph->advance_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect epd_get_string_rect(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int margin,
|
||||||
|
const EpdFontProperties* properties
|
||||||
|
) {
|
||||||
|
assert(properties != NULL);
|
||||||
|
EpdFontProperties props = *properties;
|
||||||
|
props.flags |= EPD_DRAW_BACKGROUND;
|
||||||
|
EpdRect temp = { .x = x, .y = y, .width = 0, .height = 0 };
|
||||||
|
if (*string == '\0')
|
||||||
|
return temp;
|
||||||
|
int minx = 100000, miny = 100000, maxx = -1, maxy = -1;
|
||||||
|
int temp_x = x;
|
||||||
|
int temp_y = y + font->ascender;
|
||||||
|
|
||||||
|
// Go through each line and get it's co-ordinates
|
||||||
|
uint32_t c;
|
||||||
|
while ((c = next_cp((const uint8_t**)&string))) {
|
||||||
|
if (c == 0x000A) // newline
|
||||||
|
{
|
||||||
|
temp_x = x;
|
||||||
|
temp_y += font->advance_y;
|
||||||
|
} else
|
||||||
|
get_char_bounds(font, c, &temp_x, &temp_y, &minx, &miny, &maxx, &maxy, &props);
|
||||||
|
}
|
||||||
|
temp.width = maxx - x + (margin * 2);
|
||||||
|
temp.height = maxy - miny + (margin * 2);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_get_text_bounds(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
const int* x,
|
||||||
|
const int* y,
|
||||||
|
int* x1,
|
||||||
|
int* y1,
|
||||||
|
int* w,
|
||||||
|
int* h,
|
||||||
|
const EpdFontProperties* properties
|
||||||
|
) {
|
||||||
|
// FIXME: Does not respect alignment!
|
||||||
|
|
||||||
|
assert(properties != NULL);
|
||||||
|
EpdFontProperties props = *properties;
|
||||||
|
|
||||||
|
if (*string == '\0') {
|
||||||
|
*w = 0;
|
||||||
|
*h = 0;
|
||||||
|
*y1 = *y;
|
||||||
|
*x1 = *x;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int minx = 100000, miny = 100000, maxx = -1, maxy = -1;
|
||||||
|
int original_x = *x;
|
||||||
|
int temp_x = *x;
|
||||||
|
int temp_y = *y;
|
||||||
|
uint32_t c;
|
||||||
|
while ((c = next_cp((const uint8_t**)&string))) {
|
||||||
|
get_char_bounds(font, c, &temp_x, &temp_y, &minx, &miny, &maxx, &maxy, &props);
|
||||||
|
}
|
||||||
|
*x1 = min(original_x, minx);
|
||||||
|
*w = maxx - *x1;
|
||||||
|
*y1 = miny;
|
||||||
|
*h = maxy - miny;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum EpdDrawError epd_write_line(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
int* cursor_x,
|
||||||
|
int* cursor_y,
|
||||||
|
uint8_t* framebuffer,
|
||||||
|
const EpdFontProperties* properties
|
||||||
|
) {
|
||||||
|
assert(framebuffer != NULL);
|
||||||
|
|
||||||
|
if (*string == '\0') {
|
||||||
|
return EPD_DRAW_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(properties != NULL);
|
||||||
|
EpdFontProperties props = *properties;
|
||||||
|
enum EpdFontFlags alignment_mask
|
||||||
|
= EPD_DRAW_ALIGN_LEFT | EPD_DRAW_ALIGN_RIGHT | EPD_DRAW_ALIGN_CENTER;
|
||||||
|
enum EpdFontFlags alignment = props.flags & alignment_mask;
|
||||||
|
|
||||||
|
// alignments are mutually exclusive!
|
||||||
|
if ((alignment & (alignment - 1)) != 0) {
|
||||||
|
return EPD_DRAW_INVALID_FONT_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x1 = 0, y1 = 0, w = 0, h = 0;
|
||||||
|
int tmp_cur_x = *cursor_x;
|
||||||
|
int tmp_cur_y = *cursor_y;
|
||||||
|
epd_get_text_bounds(font, string, &tmp_cur_x, &tmp_cur_y, &x1, &y1, &w, &h, &props);
|
||||||
|
|
||||||
|
// no printable characters
|
||||||
|
if (w < 0 || h < 0) {
|
||||||
|
return EPD_DRAW_NO_DRAWABLE_CHARACTERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* buffer = framebuffer;
|
||||||
|
int local_cursor_x = *cursor_x;
|
||||||
|
int local_cursor_y = *cursor_y;
|
||||||
|
uint32_t c;
|
||||||
|
|
||||||
|
int cursor_x_init = local_cursor_x;
|
||||||
|
int cursor_y_init = local_cursor_y;
|
||||||
|
|
||||||
|
switch (alignment) {
|
||||||
|
case EPD_DRAW_ALIGN_LEFT: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EPD_DRAW_ALIGN_CENTER: {
|
||||||
|
local_cursor_x -= w / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EPD_DRAW_ALIGN_RIGHT: {
|
||||||
|
local_cursor_x -= w;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t bg = props.bg_color;
|
||||||
|
if (props.flags & EPD_DRAW_BACKGROUND) {
|
||||||
|
for (int l = local_cursor_y - font->ascender; l < local_cursor_y - font->descender; l++) {
|
||||||
|
epd_draw_hline(local_cursor_x, l, w, bg << 4, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum EpdDrawError err = EPD_DRAW_SUCCESS;
|
||||||
|
while ((c = next_cp((const uint8_t**)&string))) {
|
||||||
|
err |= draw_char(font, buffer, &local_cursor_x, local_cursor_y, c, &props);
|
||||||
|
}
|
||||||
|
|
||||||
|
*cursor_x += local_cursor_x - cursor_x_init;
|
||||||
|
*cursor_y += local_cursor_y - cursor_y_init;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdDrawError epd_write_default(
|
||||||
|
const EpdFont* font, const char* string, int* cursor_x, int* cursor_y, uint8_t* framebuffer
|
||||||
|
) {
|
||||||
|
const EpdFontProperties props = epd_font_properties_default();
|
||||||
|
return epd_write_string(font, string, cursor_x, cursor_y, framebuffer, &props);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdDrawError epd_write_string(
|
||||||
|
const EpdFont* font,
|
||||||
|
const char* string,
|
||||||
|
int* cursor_x,
|
||||||
|
int* cursor_y,
|
||||||
|
uint8_t* framebuffer,
|
||||||
|
const EpdFontProperties* properties
|
||||||
|
) {
|
||||||
|
char *token, *newstring, *tofree;
|
||||||
|
if (string == NULL) {
|
||||||
|
ESP_LOGE("font.c", "cannot draw a NULL string!");
|
||||||
|
return EPD_DRAW_STRING_INVALID;
|
||||||
|
}
|
||||||
|
tofree = newstring = strdup(string);
|
||||||
|
if (newstring == NULL) {
|
||||||
|
ESP_LOGE("font.c", "cannot allocate string copy!");
|
||||||
|
return EPD_DRAW_FAILED_ALLOC;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdDrawError err = EPD_DRAW_SUCCESS;
|
||||||
|
// taken from the strsep manpage
|
||||||
|
int line_start = *cursor_x;
|
||||||
|
while ((token = strsep(&newstring, "\n")) != NULL) {
|
||||||
|
*cursor_x = line_start;
|
||||||
|
err |= epd_write_line(font, token, cursor_x, cursor_y, framebuffer, properties);
|
||||||
|
*cursor_y += font->advance_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(tofree);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
0
lib/libesp32_eink/epdiy/src/epd_driver/hacks.cmake → lib/libesp32_eink/epdiy/src/hacks.cmake
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/hacks.cmake → lib/libesp32_eink/epdiy/src/hacks.cmake
Executable file → Normal file
216
lib/libesp32_eink/epdiy/src/highlevel.c
Normal file
216
lib/libesp32_eink/epdiy/src/highlevel.c
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* High-level API implementation for epdiy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
#include <esp_types.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "epd_highlevel.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#ifndef _swap_int
|
||||||
|
#define _swap_int(a, b) \
|
||||||
|
{ \
|
||||||
|
int t = a; \
|
||||||
|
a = b; \
|
||||||
|
b = t; \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool already_initialized = 0;
|
||||||
|
|
||||||
|
EpdiyHighlevelState epd_hl_init(const EpdWaveform* waveform) {
|
||||||
|
assert(!already_initialized);
|
||||||
|
if (waveform == NULL) {
|
||||||
|
waveform = epd_get_display()->default_waveform;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fb_size = epd_width() / 2 * epd_height();
|
||||||
|
|
||||||
|
#if !(defined(CONFIG_ESP32_SPIRAM_SUPPORT) || defined(CONFIG_ESP32S3_SPIRAM_SUPPORT))
|
||||||
|
ESP_LOGW(
|
||||||
|
"EPDiy", "Please enable PSRAM for the ESP32 (menuconfig→ Component config→ ESP32-specific)"
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
EpdiyHighlevelState state;
|
||||||
|
state.back_fb = heap_caps_aligned_alloc(16, fb_size, MALLOC_CAP_SPIRAM);
|
||||||
|
assert(state.back_fb != NULL);
|
||||||
|
state.front_fb = heap_caps_aligned_alloc(16, fb_size, MALLOC_CAP_SPIRAM);
|
||||||
|
assert(state.front_fb != NULL);
|
||||||
|
state.difference_fb = heap_caps_aligned_alloc(16, 2 * fb_size, MALLOC_CAP_SPIRAM);
|
||||||
|
assert(state.difference_fb != NULL);
|
||||||
|
state.dirty_lines = malloc(epd_height() * sizeof(bool));
|
||||||
|
assert(state.dirty_lines != NULL);
|
||||||
|
state.dirty_columns
|
||||||
|
= heap_caps_aligned_alloc(16, epd_width() / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||||
|
assert(state.dirty_columns != NULL);
|
||||||
|
state.waveform = waveform;
|
||||||
|
|
||||||
|
memset(state.front_fb, 0xFF, fb_size);
|
||||||
|
memset(state.back_fb, 0xFF, fb_size);
|
||||||
|
|
||||||
|
already_initialized = true;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* epd_hl_get_framebuffer(EpdiyHighlevelState* state) {
|
||||||
|
assert(state != NULL);
|
||||||
|
return state->front_fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdDrawError epd_hl_update_screen(
|
||||||
|
EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature
|
||||||
|
) {
|
||||||
|
return epd_hl_update_area(state, mode, temperature, epd_full_screen());
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect _inverse_rotated_area(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||||
|
// If partial update uses full screen do not rotate anything
|
||||||
|
if (!(x == 0 && y == 0 && epd_width() == w && epd_height() == h)) {
|
||||||
|
// invert the current display rotation
|
||||||
|
switch (epd_get_rotation()) {
|
||||||
|
// 0 landscape: Leave it as is
|
||||||
|
case EPD_ROT_LANDSCAPE:
|
||||||
|
break;
|
||||||
|
// 1 90 ° clockwise
|
||||||
|
case EPD_ROT_PORTRAIT:
|
||||||
|
_swap_int(x, y);
|
||||||
|
_swap_int(w, h);
|
||||||
|
x = epd_width() - x - w;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EPD_ROT_INVERTED_LANDSCAPE:
|
||||||
|
// 3 180°
|
||||||
|
x = epd_width() - x - w;
|
||||||
|
y = epd_height() - y - h;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EPD_ROT_INVERTED_PORTRAIT:
|
||||||
|
// 3 270 °
|
||||||
|
_swap_int(x, y);
|
||||||
|
_swap_int(w, h);
|
||||||
|
y = epd_height() - y - h;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect rotated = { x, y, w, h };
|
||||||
|
return rotated;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EpdDrawError epd_hl_update_area(
|
||||||
|
EpdiyHighlevelState* state, enum EpdDrawMode mode, int temperature, EpdRect area
|
||||||
|
) {
|
||||||
|
assert(state != NULL);
|
||||||
|
// Not right to rotate here since this copies part of buffer directly
|
||||||
|
|
||||||
|
// Check rotation FIX
|
||||||
|
EpdRect rotated_area = _inverse_rotated_area(area.x, area.y, area.width, area.height);
|
||||||
|
area.x = rotated_area.x;
|
||||||
|
area.y = rotated_area.y;
|
||||||
|
area.width = rotated_area.width;
|
||||||
|
area.height = rotated_area.height;
|
||||||
|
|
||||||
|
uint32_t ts = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
// FIXME: use crop information here, if available
|
||||||
|
EpdRect diff_area = epd_difference_image_cropped(
|
||||||
|
state->front_fb,
|
||||||
|
state->back_fb,
|
||||||
|
area,
|
||||||
|
state->difference_fb,
|
||||||
|
state->dirty_lines,
|
||||||
|
state->dirty_columns
|
||||||
|
);
|
||||||
|
|
||||||
|
if (diff_area.height == 0 || diff_area.width == 0) {
|
||||||
|
return EPD_DRAW_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t t1 = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
diff_area.x = 0;
|
||||||
|
diff_area.y = 0;
|
||||||
|
diff_area.width = epd_width();
|
||||||
|
diff_area.height = epd_height();
|
||||||
|
|
||||||
|
enum EpdDrawError err = EPD_DRAW_SUCCESS;
|
||||||
|
err = epd_draw_base(
|
||||||
|
epd_full_screen(),
|
||||||
|
state->difference_fb,
|
||||||
|
diff_area,
|
||||||
|
MODE_PACKING_1PPB_DIFFERENCE | mode,
|
||||||
|
temperature,
|
||||||
|
state->dirty_lines,
|
||||||
|
state->dirty_columns,
|
||||||
|
state->waveform
|
||||||
|
);
|
||||||
|
|
||||||
|
uint32_t t2 = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
diff_area.x = 0;
|
||||||
|
diff_area.y = 0;
|
||||||
|
diff_area.width = epd_width();
|
||||||
|
diff_area.height = epd_height();
|
||||||
|
|
||||||
|
int buf_width = epd_width();
|
||||||
|
|
||||||
|
for (int l = diff_area.y; l < diff_area.y + diff_area.height; l++) {
|
||||||
|
if (state->dirty_lines[l] > 0) {
|
||||||
|
uint8_t* lfb = state->front_fb + buf_width / 2 * l;
|
||||||
|
uint8_t* lbb = state->back_fb + buf_width / 2 * l;
|
||||||
|
|
||||||
|
int x = diff_area.x;
|
||||||
|
int x_last = diff_area.x + diff_area.width - 1;
|
||||||
|
|
||||||
|
if (x % 2) {
|
||||||
|
*(lbb + x / 2) = (*(lfb + x / 2) & 0xF0) | (*(lbb + x / 2) & 0x0F);
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(x_last % 2)) {
|
||||||
|
*(lbb + x_last / 2) = (*(lfb + x_last / 2) & 0x0F) | (*(lbb + x_last / 2) & 0xF0);
|
||||||
|
x_last -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(lbb + (x / 2), lfb + (x / 2), (x_last - x + 1) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t t3 = esp_timer_get_time() / 1000;
|
||||||
|
|
||||||
|
ESP_LOGI(
|
||||||
|
"epdiy",
|
||||||
|
"diff: %dms, draw: %dms, buffer update: %dms, total: %dms",
|
||||||
|
t1 - ts,
|
||||||
|
t2 - t1,
|
||||||
|
t3 - t2,
|
||||||
|
t3 - ts
|
||||||
|
);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_hl_set_all_white(EpdiyHighlevelState* state) {
|
||||||
|
assert(state != NULL);
|
||||||
|
int fb_size = epd_width() / 2 * epd_height();
|
||||||
|
memset(state->front_fb, 0xFF, fb_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_fullclear(EpdiyHighlevelState* state, int temperature) {
|
||||||
|
assert(state != NULL);
|
||||||
|
epd_hl_set_all_white(state);
|
||||||
|
enum EpdDrawError err = epd_hl_update_screen(state, MODE_GC16, temperature);
|
||||||
|
assert(err == EPD_DRAW_SUCCESS);
|
||||||
|
epd_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_hl_waveform(EpdiyHighlevelState* state, const EpdWaveform* waveform) {
|
||||||
|
if (waveform == NULL) {
|
||||||
|
waveform = epd_get_display()->default_waveform;
|
||||||
|
}
|
||||||
|
state->waveform = waveform;
|
||||||
|
}
|
||||||
86
lib/libesp32_eink/epdiy/src/output_common/line_queue.c
Normal file
86
lib/libesp32_eink/epdiy/src/output_common/line_queue.c
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <esp_attr.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "line_queue.h"
|
||||||
|
#include "render_method.h"
|
||||||
|
|
||||||
|
static inline int ceil_div(int x, int y) {
|
||||||
|
return x / y + (x % y != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the line queue and allocate memory.
|
||||||
|
LineQueue_t lq_init(int queue_len, int element_size) {
|
||||||
|
LineQueue_t queue;
|
||||||
|
queue.element_size = element_size;
|
||||||
|
queue.size = queue_len;
|
||||||
|
queue.current = 0;
|
||||||
|
queue.last = 0;
|
||||||
|
|
||||||
|
int elem_buf_size = ceil_div(element_size, 16) * 16;
|
||||||
|
|
||||||
|
queue.bufs = calloc(queue.size, elem_buf_size);
|
||||||
|
assert(queue.bufs != NULL);
|
||||||
|
|
||||||
|
for (int i = 0; i < queue.size; i++) {
|
||||||
|
queue.bufs[i] = heap_caps_aligned_alloc(16, elem_buf_size, MALLOC_CAP_INTERNAL);
|
||||||
|
assert(queue.bufs[i] != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deinitialize the line queue and free memory.
|
||||||
|
void lq_free(LineQueue_t* queue) {
|
||||||
|
for (int i = 0; i < queue->size; i++) {
|
||||||
|
heap_caps_free(queue->bufs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(queue->bufs);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* IRAM_ATTR lq_current(LineQueue_t* queue) {
|
||||||
|
int current = atomic_load_explicit(&queue->current, memory_order_acquire);
|
||||||
|
int last = atomic_load_explicit(&queue->last, memory_order_acquire);
|
||||||
|
|
||||||
|
if ((current + 1) % queue->size == last) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return queue->bufs[current];
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR lq_commit(LineQueue_t* queue) {
|
||||||
|
int current = atomic_load_explicit(&queue->current, memory_order_acquire);
|
||||||
|
|
||||||
|
if (current == queue->size - 1) {
|
||||||
|
queue->current = 0;
|
||||||
|
} else {
|
||||||
|
atomic_fetch_add(&queue->current, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int IRAM_ATTR lq_read(LineQueue_t* queue, uint8_t* dst) {
|
||||||
|
int current = atomic_load_explicit(&queue->current, memory_order_acquire);
|
||||||
|
int last = atomic_load_explicit(&queue->last, memory_order_acquire);
|
||||||
|
|
||||||
|
if (current == last) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(dst, queue->bufs[last], queue->element_size);
|
||||||
|
|
||||||
|
if (last == queue->size - 1) {
|
||||||
|
queue->last = 0;
|
||||||
|
} else {
|
||||||
|
atomic_fetch_add(&queue->last, 1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR lq_reset(LineQueue_t* queue) {
|
||||||
|
queue->current = 0;
|
||||||
|
queue->last = 0;
|
||||||
|
}
|
||||||
40
lib/libesp32_eink/epdiy/src/output_common/line_queue.h
Normal file
40
lib/libesp32_eink/epdiy/src/output_common/line_queue.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/// Circular line queue with atomic read / write operations
|
||||||
|
/// and accelerated masking on the output buffer.
|
||||||
|
typedef struct {
|
||||||
|
int size;
|
||||||
|
atomic_int current;
|
||||||
|
atomic_int last;
|
||||||
|
uint8_t** bufs;
|
||||||
|
// size of an element
|
||||||
|
size_t element_size;
|
||||||
|
} LineQueue_t;
|
||||||
|
|
||||||
|
/// Initialize the line queue and allocate memory.
|
||||||
|
LineQueue_t lq_init(int queue_len, int element_size);
|
||||||
|
|
||||||
|
/// Deinitialize the line queue and free memory.
|
||||||
|
void lq_free(LineQueue_t* queue);
|
||||||
|
|
||||||
|
/// Pointer to the next empty element in the line queue.
|
||||||
|
///
|
||||||
|
/// NULL if the queue is currently full.
|
||||||
|
uint8_t* lq_current(LineQueue_t* queue);
|
||||||
|
|
||||||
|
/// Advance the line queue.
|
||||||
|
void lq_commit(LineQueue_t* queue);
|
||||||
|
|
||||||
|
/// Read from the line queue.
|
||||||
|
///
|
||||||
|
/// Returns 0 for a successful read to `dst`, -1 for a failed read (empty queue).
|
||||||
|
int lq_read(LineQueue_t* queue, uint8_t* dst);
|
||||||
|
|
||||||
|
/// Reset the queue into an empty state.
|
||||||
|
/// This operation is *not* atomic!
|
||||||
|
void lq_reset(LineQueue_t* queue);
|
||||||
145
lib/libesp32_eink/epdiy/src/output_common/lut.S
Normal file
145
lib/libesp32_eink/epdiy/src/output_common/lut.S
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#include <xtensa/config/core-isa.h>
|
||||||
|
#include <xtensa/config/core-matmap.h>
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
.text
|
||||||
|
.align 4
|
||||||
|
.global calc_epd_input_1ppB_1k_S3_VE_aligned
|
||||||
|
.type calc_epd_input_1ppB_1k_S3_VE_aligned,@function
|
||||||
|
|
||||||
|
// // CRASH AND BURN for debugging
|
||||||
|
// EE.MOVI.32.A q3, a2, 0
|
||||||
|
// EE.MOVI.32.A q3, a3, 1
|
||||||
|
// EE.MOVI.32.A q3, a4, 2
|
||||||
|
// EE.MOVI.32.A q3, a5, 3
|
||||||
|
// l8ui a10, a10, 0
|
||||||
|
|
||||||
|
// void calc_epd_input_1ppB_1k_S3_VE_aligned(
|
||||||
|
// const uint32_t *ld,
|
||||||
|
// uint8_t *epd_input,
|
||||||
|
// const uint8_t *conversion_lut,
|
||||||
|
// uint32_t epd_width
|
||||||
|
//);
|
||||||
|
calc_epd_input_1ppB_1k_S3_VE_aligned:
|
||||||
|
// input - a2
|
||||||
|
// output - a3
|
||||||
|
// lut - a4
|
||||||
|
// len - a5
|
||||||
|
|
||||||
|
entry a1, 32
|
||||||
|
|
||||||
|
// divide by 16 and do one loop lesss,
|
||||||
|
// because the last loop is special
|
||||||
|
srli a5, a5, 4
|
||||||
|
addi.n a5, a5, -1
|
||||||
|
|
||||||
|
|
||||||
|
// bitmasks for bit shift by multiplication
|
||||||
|
movi.n a10, 0x40001000
|
||||||
|
EE.MOVI.32.Q q4,a10,0
|
||||||
|
movi.n a10, 0x04000100
|
||||||
|
EE.MOVI.32.Q q4,a10,1
|
||||||
|
movi.n a10, 0x00400010
|
||||||
|
EE.MOVI.32.Q q4,a10,2
|
||||||
|
movi a10, 0x00040001
|
||||||
|
EE.MOVI.32.Q q4,a10,3
|
||||||
|
|
||||||
|
EE.ZERO.Q q0
|
||||||
|
|
||||||
|
EE.VLD.128.IP q1, a2, 16
|
||||||
|
|
||||||
|
// Instructions sometimes are in an unexpected order
|
||||||
|
// for best pipeline utilization
|
||||||
|
loopnez a5, .loop_end_lut_lookup
|
||||||
|
|
||||||
|
// q1, q0 contain the input bytes, zero-extended to bits bytes
|
||||||
|
EE.VZIP.8 q1, q0
|
||||||
|
|
||||||
|
// load 32-bit LUT results
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 0, 6
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 1, 7
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 2, 4
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 3, 5
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 0, 2
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 1, 3
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 2, 0
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 3, 1
|
||||||
|
|
||||||
|
EE.ZERO.ACCX
|
||||||
|
|
||||||
|
// zip to have 16bit LUT results in q2, q3 zeroes
|
||||||
|
EE.VUNZIP.16 q2, q3
|
||||||
|
|
||||||
|
// combine results with using multiply-add as shift-or
|
||||||
|
EE.VMULAS.U16.ACCX q2,q4
|
||||||
|
|
||||||
|
// load 32-bit LUT results
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 0, 6
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 1, 7
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 2, 4
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 3, 5
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 0, 2
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 1, 3
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 2, 0
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 3, 1
|
||||||
|
|
||||||
|
// store multiplication result in a6
|
||||||
|
RUR.ACCX_0 a6
|
||||||
|
s16i a6, a3, 2
|
||||||
|
|
||||||
|
EE.ZERO.ACCX
|
||||||
|
|
||||||
|
// zip to have 16bit LUT results in q2, q0 zeroes
|
||||||
|
EE.VUNZIP.16 q2, q0
|
||||||
|
|
||||||
|
// Combine second set of results and load the next data
|
||||||
|
EE.VMULAS.U16.ACCX.LD.IP q1, a2, 16, q2, q4
|
||||||
|
|
||||||
|
// store result in a6
|
||||||
|
RUR.ACCX_0 a6
|
||||||
|
s16i a6, a3, 0
|
||||||
|
|
||||||
|
addi.n a3, a3, 4
|
||||||
|
.loop_end_lut_lookup:
|
||||||
|
|
||||||
|
// Same as above, but in the last iteration
|
||||||
|
// we do not load to not access out of bounds.
|
||||||
|
EE.VZIP.8 q1, q0
|
||||||
|
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 0, 6
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 1, 7
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 2, 4
|
||||||
|
EE.LDXQ.32 q2, q0, a4, 3, 5
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 0, 2
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 1, 3
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 2, 0
|
||||||
|
EE.LDXQ.32 q3, q0, a4, 3, 1
|
||||||
|
|
||||||
|
EE.ZERO.ACCX
|
||||||
|
EE.VUNZIP.16 q2, q3
|
||||||
|
EE.VMULAS.U16.ACCX q2,q4
|
||||||
|
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 0, 6
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 1, 7
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 2, 4
|
||||||
|
EE.LDXQ.32 q2, q1, a4, 3, 5
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 0, 2
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 1, 3
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 2, 0
|
||||||
|
EE.LDXQ.32 q0, q1, a4, 3, 1
|
||||||
|
|
||||||
|
RUR.ACCX_0 a6
|
||||||
|
s16i a6, a3, 2
|
||||||
|
EE.ZERO.ACCX
|
||||||
|
|
||||||
|
EE.VUNZIP.16 q2, q0
|
||||||
|
EE.VMULAS.U16.ACCX q2, q4
|
||||||
|
RUR.ACCX_0 a6
|
||||||
|
s16i a6, a3, 0
|
||||||
|
|
||||||
|
movi.n a2, 0 // return status ESP_OK
|
||||||
|
retw.n
|
||||||
|
|
||||||
|
#endif
|
||||||
495
lib/libesp32_eink/epdiy/src/output_common/lut.c
Normal file
495
lib/libesp32_eink/epdiy/src/output_common/lut.c
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
#include "lut.h"
|
||||||
|
|
||||||
|
#include "epdiy.h"
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "render_context.h"
|
||||||
|
#include "render_method.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_timer.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build Lookup tables and translate via LUTs.
|
||||||
|
* WARNING: These functions must only ever write to internal memory,
|
||||||
|
* Since we disable the PSRAM workaround here for performance reasons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Python script for generating the 8ppB, starting at white lookup table:
|
||||||
|
for i in range(256):
|
||||||
|
number = 0;
|
||||||
|
for b in range(8):
|
||||||
|
if not (i & (1 << b)):
|
||||||
|
number |= 1 << (2*b)
|
||||||
|
print ('0x%04x,'%number)
|
||||||
|
*/
|
||||||
|
const uint32_t lut_8ppB_start_at_white[256] = {
|
||||||
|
0x5555, 0x5554, 0x5551, 0x5550, 0x5545, 0x5544, 0x5541, 0x5540, 0x5515, 0x5514, 0x5511, 0x5510,
|
||||||
|
0x5505, 0x5504, 0x5501, 0x5500, 0x5455, 0x5454, 0x5451, 0x5450, 0x5445, 0x5444, 0x5441, 0x5440,
|
||||||
|
0x5415, 0x5414, 0x5411, 0x5410, 0x5405, 0x5404, 0x5401, 0x5400, 0x5155, 0x5154, 0x5151, 0x5150,
|
||||||
|
0x5145, 0x5144, 0x5141, 0x5140, 0x5115, 0x5114, 0x5111, 0x5110, 0x5105, 0x5104, 0x5101, 0x5100,
|
||||||
|
0x5055, 0x5054, 0x5051, 0x5050, 0x5045, 0x5044, 0x5041, 0x5040, 0x5015, 0x5014, 0x5011, 0x5010,
|
||||||
|
0x5005, 0x5004, 0x5001, 0x5000, 0x4555, 0x4554, 0x4551, 0x4550, 0x4545, 0x4544, 0x4541, 0x4540,
|
||||||
|
0x4515, 0x4514, 0x4511, 0x4510, 0x4505, 0x4504, 0x4501, 0x4500, 0x4455, 0x4454, 0x4451, 0x4450,
|
||||||
|
0x4445, 0x4444, 0x4441, 0x4440, 0x4415, 0x4414, 0x4411, 0x4410, 0x4405, 0x4404, 0x4401, 0x4400,
|
||||||
|
0x4155, 0x4154, 0x4151, 0x4150, 0x4145, 0x4144, 0x4141, 0x4140, 0x4115, 0x4114, 0x4111, 0x4110,
|
||||||
|
0x4105, 0x4104, 0x4101, 0x4100, 0x4055, 0x4054, 0x4051, 0x4050, 0x4045, 0x4044, 0x4041, 0x4040,
|
||||||
|
0x4015, 0x4014, 0x4011, 0x4010, 0x4005, 0x4004, 0x4001, 0x4000, 0x1555, 0x1554, 0x1551, 0x1550,
|
||||||
|
0x1545, 0x1544, 0x1541, 0x1540, 0x1515, 0x1514, 0x1511, 0x1510, 0x1505, 0x1504, 0x1501, 0x1500,
|
||||||
|
0x1455, 0x1454, 0x1451, 0x1450, 0x1445, 0x1444, 0x1441, 0x1440, 0x1415, 0x1414, 0x1411, 0x1410,
|
||||||
|
0x1405, 0x1404, 0x1401, 0x1400, 0x1155, 0x1154, 0x1151, 0x1150, 0x1145, 0x1144, 0x1141, 0x1140,
|
||||||
|
0x1115, 0x1114, 0x1111, 0x1110, 0x1105, 0x1104, 0x1101, 0x1100, 0x1055, 0x1054, 0x1051, 0x1050,
|
||||||
|
0x1045, 0x1044, 0x1041, 0x1040, 0x1015, 0x1014, 0x1011, 0x1010, 0x1005, 0x1004, 0x1001, 0x1000,
|
||||||
|
0x0555, 0x0554, 0x0551, 0x0550, 0x0545, 0x0544, 0x0541, 0x0540, 0x0515, 0x0514, 0x0511, 0x0510,
|
||||||
|
0x0505, 0x0504, 0x0501, 0x0500, 0x0455, 0x0454, 0x0451, 0x0450, 0x0445, 0x0444, 0x0441, 0x0440,
|
||||||
|
0x0415, 0x0414, 0x0411, 0x0410, 0x0405, 0x0404, 0x0401, 0x0400, 0x0155, 0x0154, 0x0151, 0x0150,
|
||||||
|
0x0145, 0x0144, 0x0141, 0x0140, 0x0115, 0x0114, 0x0111, 0x0110, 0x0105, 0x0104, 0x0101, 0x0100,
|
||||||
|
0x0055, 0x0054, 0x0051, 0x0050, 0x0045, 0x0044, 0x0041, 0x0040, 0x0015, 0x0014, 0x0011, 0x0010,
|
||||||
|
0x0005, 0x0004, 0x0001, 0x0000
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Python script for generating the 8ppB, starting at black lookup table:
|
||||||
|
for i in range(256):
|
||||||
|
number = 0;
|
||||||
|
for b in range(8):
|
||||||
|
if (i & (1 << b)):
|
||||||
|
number |= 2 << (2*b)
|
||||||
|
print ('0x%04x,'%number)
|
||||||
|
*/
|
||||||
|
const uint32_t lut_8ppB_start_at_black[256] = {
|
||||||
|
0x0000, 0x0002, 0x0008, 0x000a, 0x0020, 0x0022, 0x0028, 0x002a, 0x0080, 0x0082, 0x0088, 0x008a,
|
||||||
|
0x00a0, 0x00a2, 0x00a8, 0x00aa, 0x0200, 0x0202, 0x0208, 0x020a, 0x0220, 0x0222, 0x0228, 0x022a,
|
||||||
|
0x0280, 0x0282, 0x0288, 0x028a, 0x02a0, 0x02a2, 0x02a8, 0x02aa, 0x0800, 0x0802, 0x0808, 0x080a,
|
||||||
|
0x0820, 0x0822, 0x0828, 0x082a, 0x0880, 0x0882, 0x0888, 0x088a, 0x08a0, 0x08a2, 0x08a8, 0x08aa,
|
||||||
|
0x0a00, 0x0a02, 0x0a08, 0x0a0a, 0x0a20, 0x0a22, 0x0a28, 0x0a2a, 0x0a80, 0x0a82, 0x0a88, 0x0a8a,
|
||||||
|
0x0aa0, 0x0aa2, 0x0aa8, 0x0aaa, 0x2000, 0x2002, 0x2008, 0x200a, 0x2020, 0x2022, 0x2028, 0x202a,
|
||||||
|
0x2080, 0x2082, 0x2088, 0x208a, 0x20a0, 0x20a2, 0x20a8, 0x20aa, 0x2200, 0x2202, 0x2208, 0x220a,
|
||||||
|
0x2220, 0x2222, 0x2228, 0x222a, 0x2280, 0x2282, 0x2288, 0x228a, 0x22a0, 0x22a2, 0x22a8, 0x22aa,
|
||||||
|
0x2800, 0x2802, 0x2808, 0x280a, 0x2820, 0x2822, 0x2828, 0x282a, 0x2880, 0x2882, 0x2888, 0x288a,
|
||||||
|
0x28a0, 0x28a2, 0x28a8, 0x28aa, 0x2a00, 0x2a02, 0x2a08, 0x2a0a, 0x2a20, 0x2a22, 0x2a28, 0x2a2a,
|
||||||
|
0x2a80, 0x2a82, 0x2a88, 0x2a8a, 0x2aa0, 0x2aa2, 0x2aa8, 0x2aaa, 0x8000, 0x8002, 0x8008, 0x800a,
|
||||||
|
0x8020, 0x8022, 0x8028, 0x802a, 0x8080, 0x8082, 0x8088, 0x808a, 0x80a0, 0x80a2, 0x80a8, 0x80aa,
|
||||||
|
0x8200, 0x8202, 0x8208, 0x820a, 0x8220, 0x8222, 0x8228, 0x822a, 0x8280, 0x8282, 0x8288, 0x828a,
|
||||||
|
0x82a0, 0x82a2, 0x82a8, 0x82aa, 0x8800, 0x8802, 0x8808, 0x880a, 0x8820, 0x8822, 0x8828, 0x882a,
|
||||||
|
0x8880, 0x8882, 0x8888, 0x888a, 0x88a0, 0x88a2, 0x88a8, 0x88aa, 0x8a00, 0x8a02, 0x8a08, 0x8a0a,
|
||||||
|
0x8a20, 0x8a22, 0x8a28, 0x8a2a, 0x8a80, 0x8a82, 0x8a88, 0x8a8a, 0x8aa0, 0x8aa2, 0x8aa8, 0x8aaa,
|
||||||
|
0xa000, 0xa002, 0xa008, 0xa00a, 0xa020, 0xa022, 0xa028, 0xa02a, 0xa080, 0xa082, 0xa088, 0xa08a,
|
||||||
|
0xa0a0, 0xa0a2, 0xa0a8, 0xa0aa, 0xa200, 0xa202, 0xa208, 0xa20a, 0xa220, 0xa222, 0xa228, 0xa22a,
|
||||||
|
0xa280, 0xa282, 0xa288, 0xa28a, 0xa2a0, 0xa2a2, 0xa2a8, 0xa2aa, 0xa800, 0xa802, 0xa808, 0xa80a,
|
||||||
|
0xa820, 0xa822, 0xa828, 0xa82a, 0xa880, 0xa882, 0xa888, 0xa88a, 0xa8a0, 0xa8a2, 0xa8a8, 0xa8aa,
|
||||||
|
0xaa00, 0xaa02, 0xaa08, 0xaa0a, 0xaa20, 0xaa22, 0xaa28, 0xaa2a, 0xaa80, 0xaa82, 0xaa88, 0xaa8a,
|
||||||
|
0xaaa0, 0xaaa2, 0xaaa8, 0xaaaa,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline int min(int x, int y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
static inline int max(int x, int y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// status tracker for row skipping
|
||||||
|
uint32_t skipping;
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR
|
||||||
|
reorder_line_buffer(uint32_t* line_data, int buf_len) {
|
||||||
|
for (uint32_t i = 0; i < buf_len / 4; i++) {
|
||||||
|
uint32_t val = *line_data;
|
||||||
|
*(line_data++) = val >> 16 | ((val & 0x0000FFFF) << 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR
|
||||||
|
bit_shift_buffer_right(uint8_t* buf, uint32_t len, int shift) {
|
||||||
|
uint8_t carry = 0xFF << (8 - shift);
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
uint8_t val = buf[i];
|
||||||
|
buf[i] = (val >> shift) | carry;
|
||||||
|
carry = val << (8 - shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR
|
||||||
|
nibble_shift_buffer_right(uint8_t* buf, uint32_t len) {
|
||||||
|
uint8_t carry = 0xF;
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
uint8_t val = buf[i];
|
||||||
|
buf[i] = (val << 4) | carry;
|
||||||
|
carry = (val & 0xF0) >> 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_8ppB(
|
||||||
|
const uint32_t* line_data, uint8_t* epd_input, const uint8_t* lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
uint32_t* wide_epd_input = (uint32_t*)epd_input;
|
||||||
|
uint8_t* data_ptr = (uint8_t*)line_data;
|
||||||
|
uint32_t* lut_32 = (uint32_t*)lut;
|
||||||
|
// this is reversed for little-endian, but this is later compensated
|
||||||
|
// through the output peripheral.
|
||||||
|
for (int j = 0; j < epd_width / 16; j++) {
|
||||||
|
uint8_t v1 = *(data_ptr++);
|
||||||
|
uint8_t v2 = *(data_ptr++);
|
||||||
|
wide_epd_input[j] = (lut_32[v2] << 16) | lut_32[v1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account for missing line end if epd_width is not divisible by 16.
|
||||||
|
// We assume divisibility by 4.
|
||||||
|
for (int j = 0; j < (epd_width % 16) / 4; j++) {
|
||||||
|
uint8_t nibble = *data_ptr;
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
nibble = nibble >> 4;
|
||||||
|
data_ptr++;
|
||||||
|
} else {
|
||||||
|
nibble = nibble & 0xF;
|
||||||
|
}
|
||||||
|
epd_input[(epd_width / 16) * 4 + j] = lut_32[nibble];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up 4 pixels of a differential image in a LUT constructed for use with vector extensions.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static inline uint8_t lookup_pixels_in_VE_LUT(
|
||||||
|
const uint32_t in, const uint8_t* conversion_lut
|
||||||
|
) {
|
||||||
|
uint32_t* padded_lut = (uint32_t*)conversion_lut;
|
||||||
|
uint8_t out = padded_lut[(in >> 24) & 0xFF] << 6;
|
||||||
|
out |= padded_lut[(in >> 16) & 0xFF] << 4;
|
||||||
|
out |= padded_lut[(in >> 8) & 0xFF] << 2;
|
||||||
|
out |= padded_lut[(in >> 0) & 0xFF];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup accelerated by the S3 Vector Extensions.
|
||||||
|
* Expects aligned buffers and a length that is divisible by 16.
|
||||||
|
*/
|
||||||
|
void IRAM_ATTR calc_epd_input_1ppB_1k_S3_VE_aligned(
|
||||||
|
const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
);
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_I2S
|
||||||
|
void calc_epd_input_1ppB_1k_S3_VE_aligned(
|
||||||
|
const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
// dummy implementation, should never be called.
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup accelerated by the S3 Vector Extensions.
|
||||||
|
* Uses a 1K padded LUT (each entry takes up 32 bits)
|
||||||
|
*/
|
||||||
|
void IRAM_ATTR calc_epd_input_1ppB_1k_S3_VE(
|
||||||
|
const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
// alignment boundaries in pixels
|
||||||
|
int unaligned_len_front = (16 - (uint32_t)ld % 16) % 16;
|
||||||
|
int unaligned_len_back = ((uint32_t)ld + epd_width) % 16;
|
||||||
|
int aligned_len = epd_width - unaligned_len_front - unaligned_len_back;
|
||||||
|
|
||||||
|
if (unaligned_len_front) {
|
||||||
|
for (int i = 0; i < unaligned_len_front / 4; i++) {
|
||||||
|
(*epd_input++) = lookup_pixels_in_VE_LUT((*ld++), conversion_lut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calc_epd_input_1ppB_1k_S3_VE_aligned(ld, epd_input, conversion_lut, aligned_len);
|
||||||
|
|
||||||
|
ld += aligned_len / 4;
|
||||||
|
epd_input += aligned_len / 4;
|
||||||
|
|
||||||
|
if (unaligned_len_back) {
|
||||||
|
for (int i = 0; i < unaligned_len_back / 4; i++) {
|
||||||
|
(*epd_input++) = lookup_pixels_in_VE_LUT((*ld++), conversion_lut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate EPD input for a difference image with one pixel per byte.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_1ppB_64k(
|
||||||
|
const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
const uint16_t* lp = (uint16_t*)ld;
|
||||||
|
for (uint32_t j = 0; j < epd_width / 4; j++) {
|
||||||
|
epd_input[j] = (conversion_lut[lp[2 * j + 1]] << 4) | conversion_lut[lp[2 * j]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_lut_64k(
|
||||||
|
const uint32_t* line_data, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
const uint16_t* line_data_16 = (const uint16_t*)line_data;
|
||||||
|
|
||||||
|
for (uint32_t j = 0; j < epd_width / 4; j++) {
|
||||||
|
epd_input[j] = conversion_lut[*(line_data_16++)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up 4 pixels in a 1K LUT with fixed "from" value.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static uint8_t lookup_pixels_2ppB_1k(
|
||||||
|
uint16_t in, const uint8_t* conversion_lut, uint8_t from
|
||||||
|
) {
|
||||||
|
uint8_t v;
|
||||||
|
uint8_t out;
|
||||||
|
|
||||||
|
v = ((in << 4) | from);
|
||||||
|
out = conversion_lut[v & 0xFF];
|
||||||
|
v = ((in & 0xF0) | from);
|
||||||
|
out |= (conversion_lut + 0x100)[v & 0xFF];
|
||||||
|
in = in >> 8;
|
||||||
|
v = ((in << 4) | from);
|
||||||
|
out |= (conversion_lut + 0x200)[v & 0xFF];
|
||||||
|
v = ((in & 0xF0) | from);
|
||||||
|
out |= (conversion_lut + 0x300)[v];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate EPD input for a 2ppB buffer, but with a difference image LUT.
|
||||||
|
* This is used for small-LUT mode.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_1k_lut(
|
||||||
|
const uint32_t* ld,
|
||||||
|
uint8_t* epd_input,
|
||||||
|
const uint8_t* conversion_lut,
|
||||||
|
uint8_t from,
|
||||||
|
uint32_t epd_width
|
||||||
|
) {
|
||||||
|
const uint16_t* line_data_16 = (const uint16_t*)ld;
|
||||||
|
|
||||||
|
for (uint32_t j = 0; j < epd_width / 4; j++) {
|
||||||
|
epd_input[j] = lookup_pixels_2ppB_1k(*(line_data_16++), conversion_lut, from);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_1k_lut_white(
|
||||||
|
const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
calc_epd_input_2ppB_1k_lut(ld, epd_input, conversion_lut, 0xF, epd_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR calc_epd_input_2ppB_1k_lut_black(
|
||||||
|
const uint32_t* ld, uint8_t* epd_input, const uint8_t* conversion_lut, uint32_t epd_width
|
||||||
|
) {
|
||||||
|
calc_epd_input_2ppB_1k_lut(ld, epd_input, conversion_lut, 0x0, epd_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////// Calculate Lookup Tables
|
||||||
|
//////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack the waveform data into a lookup table, with bit shifted copies.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static void IRAM_ATTR
|
||||||
|
build_2ppB_lut_1k(uint8_t* lut, const EpdWaveformPhases* phases, int frame) {
|
||||||
|
const uint8_t* p_lut = phases->luts + (16 * 4 * frame);
|
||||||
|
for (uint8_t to = 0; to < 16; to++) {
|
||||||
|
for (uint8_t from_packed = 0; from_packed < 4; from_packed++) {
|
||||||
|
uint8_t index = (to << 4) | (from_packed * 4);
|
||||||
|
uint8_t packed = *(p_lut++);
|
||||||
|
lut[index] = (packed >> 6) & 3;
|
||||||
|
lut[index + 1] = (packed >> 4) & 3;
|
||||||
|
lut[index + 2] = (packed >> 2) & 3;
|
||||||
|
lut[index + 3] = (packed >> 0) & 3;
|
||||||
|
// printf("%2X%2X%2X%2X (%d)", lut[index], lut[index + 1], lut[index + 2],
|
||||||
|
// lut[index + 3], index);
|
||||||
|
}
|
||||||
|
// printf("\n");
|
||||||
|
}
|
||||||
|
uint32_t index = 0x100;
|
||||||
|
for (uint8_t s = 2; s <= 6; s += 2) {
|
||||||
|
for (int i = 0; i < 0x100; i++) {
|
||||||
|
lut[index] = lut[index % 0x100] << s;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack the waveform data into a lookup table,
|
||||||
|
* 64k to loop up two bytes at once
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static void IRAM_ATTR
|
||||||
|
build_1ppB_lut_64k(uint8_t* lut, const EpdWaveformPhases* phases, int frame) {
|
||||||
|
const uint8_t* p_lut = phases->luts + (16 * 4 * frame);
|
||||||
|
for (uint8_t to = 0; to < 16; to++) {
|
||||||
|
for (uint8_t from_packed = 0; from_packed < 4; from_packed++) {
|
||||||
|
uint8_t index = (to << 4) | (from_packed * 4);
|
||||||
|
uint8_t packed = *(p_lut++);
|
||||||
|
lut[index] = (packed >> 6) & 3;
|
||||||
|
lut[index + 1] = (packed >> 4) & 3;
|
||||||
|
lut[index + 2] = (packed >> 2) & 3;
|
||||||
|
lut[index + 3] = (packed >> 0) & 3;
|
||||||
|
// printf("%2X%2X%2X%2X (%d)", lut[index], lut[index + 1], lut[index + 2],
|
||||||
|
// lut[index + 3], index);
|
||||||
|
}
|
||||||
|
// printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int outer = 0xFF; outer >= 0; outer--) {
|
||||||
|
uint32_t outer_result = lut[outer] << 2;
|
||||||
|
outer_result |= (outer_result << 16);
|
||||||
|
outer_result |= (outer_result << 8);
|
||||||
|
uint32_t* lut_section = (uint32_t*)(&lut[outer << 8]);
|
||||||
|
memcpy(lut_section, lut, 0x100);
|
||||||
|
for (int i = 0; i < 0x100 / 4; i++) {
|
||||||
|
lut_section[i] = lut_section[i] | outer_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32bit aligned lookup table for lookup using the ESP32-S3 vector extensions.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static void IRAM_ATTR
|
||||||
|
build_1ppB_lut_S3_VE_1k(uint8_t* lut, const EpdWaveformPhases* phases, int frame) {
|
||||||
|
uint32_t* lut32 = (uint32_t*)lut;
|
||||||
|
const uint8_t* p_lut = phases->luts + (16 * 4 * frame);
|
||||||
|
for (uint8_t to = 0; to < 16; to++) {
|
||||||
|
for (uint8_t from_packed = 0; from_packed < 4; from_packed++) {
|
||||||
|
uint8_t index = (to << 4) | (from_packed * 4);
|
||||||
|
uint8_t packed = *(p_lut++);
|
||||||
|
lut32[index] = (packed >> 6) & 3;
|
||||||
|
lut32[index + 1] = (packed >> 4) & 3;
|
||||||
|
lut32[index + 2] = (packed >> 2) & 3;
|
||||||
|
lut32[index + 3] = (packed >> 0) & 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a 16-bit LUT from the waveform if the previous color is
|
||||||
|
* known, e.g. all white or all black.
|
||||||
|
* This LUT is use to look up 4 pixels at once, as with the epdiy LUT.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static void build_2ppB_lut_64k_static_from(
|
||||||
|
uint8_t* lut, const EpdWaveformPhases* phases, uint8_t from, int frame
|
||||||
|
) {
|
||||||
|
const uint8_t* p_lut = phases->luts + (16 * 4 * frame);
|
||||||
|
|
||||||
|
/// index into the packed "from" row
|
||||||
|
uint8_t fi = from >> 2;
|
||||||
|
/// bit shift amount for the packed "from" row
|
||||||
|
uint8_t fs = 6 - 2 * (from & 3);
|
||||||
|
|
||||||
|
// populate the first 4096 bytes
|
||||||
|
uint8_t v1 = 0;
|
||||||
|
uint32_t s1 = 0;
|
||||||
|
for (uint8_t t2 = 0; t2 < 16; t2++) {
|
||||||
|
uint8_t v2 = ((p_lut[(t2 << 2) + fi] >> fs) & 0x03) << 4;
|
||||||
|
uint32_t s2 = t2 << 8;
|
||||||
|
for (uint8_t t3 = 0; t3 < 16; t3++) {
|
||||||
|
uint8_t v3 = ((p_lut[(t3 << 2) + fi] >> fs) & 0x03) << 2;
|
||||||
|
uint32_t s3 = t3 << 4;
|
||||||
|
for (uint8_t t4 = 0; t4 < 16; t4++) {
|
||||||
|
uint8_t v4 = ((p_lut[(t4 << 2) + fi] >> fs) & 0x03) << 0;
|
||||||
|
uint32_t s4 = t4;
|
||||||
|
lut[s1 | s2 | s3 | s4] = v1 | v2 | v3 | v4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now just copy and the first 4096 bytes and add the upper two bits
|
||||||
|
for (uint8_t t1 = 1; t1 < 16; t1++) {
|
||||||
|
memcpy(&lut[t1 << 12], lut, 1 << 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
uint32_t v1 = ((p_lut[(i << 2) + fi] >> fs) & 0x03);
|
||||||
|
uint32_t mask = (v1 << 30) | (v1 << 22) | (v1 << 14) | (v1 << 6);
|
||||||
|
for (int j = 0; j < 16 * 16 * 16 / 4; j++) {
|
||||||
|
((uint32_t*)lut)[(i << 10) + j] |= mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_2ppB_lut_64k_from_0(uint8_t* lut, const EpdWaveformPhases* phases, int frame) {
|
||||||
|
build_2ppB_lut_64k_static_from(lut, phases, 0, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_2ppB_lut_64k_from_15(uint8_t* lut, const EpdWaveformPhases* phases, int frame) {
|
||||||
|
build_2ppB_lut_64k_static_from(lut, phases, 0xF, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_8ppB_lut_256b_from_white(
|
||||||
|
uint8_t* lut, const EpdWaveformPhases* phases, int frame
|
||||||
|
) {
|
||||||
|
memcpy(lut, lut_8ppB_start_at_white, sizeof(lut_8ppB_start_at_white));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_8ppB_lut_256b_from_black(
|
||||||
|
uint8_t* lut, const EpdWaveformPhases* phases, int frame
|
||||||
|
) {
|
||||||
|
memcpy(lut, lut_8ppB_start_at_black, sizeof(lut_8ppB_start_at_black));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR epd_apply_line_mask(uint8_t* buf, const uint8_t* mask, int len) {
|
||||||
|
for (int i = 0; i < len / 4; i++) {
|
||||||
|
((uint32_t*)buf)[i] &= ((uint32_t*)mask)[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LutFunctionPair find_lut_functions(enum EpdDrawMode mode, uint32_t lut_size) {
|
||||||
|
LutFunctionPair pair;
|
||||||
|
pair.build_func = NULL;
|
||||||
|
pair.lookup_func = NULL;
|
||||||
|
|
||||||
|
if (mode & MODE_PACKING_1PPB_DIFFERENCE) {
|
||||||
|
if (EPD_CURRENT_RENDER_METHOD == RENDER_METHOD_LCD && !(mode & MODE_FORCE_NO_PIE)
|
||||||
|
&& lut_size >= 1024) {
|
||||||
|
pair.build_func = &build_1ppB_lut_S3_VE_1k;
|
||||||
|
pair.lookup_func = &calc_epd_input_1ppB_1k_S3_VE;
|
||||||
|
return pair;
|
||||||
|
} else if (lut_size >= 1 << 16) {
|
||||||
|
pair.build_func = &build_1ppB_lut_64k;
|
||||||
|
pair.lookup_func = &calc_epd_input_1ppB_64k;
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
} else if (mode & MODE_PACKING_2PPB) {
|
||||||
|
if (lut_size >= 1 << 16) {
|
||||||
|
if (mode & PREVIOUSLY_WHITE) {
|
||||||
|
pair.build_func = &build_2ppB_lut_64k_from_15;
|
||||||
|
pair.lookup_func = &calc_epd_input_2ppB_lut_64k;
|
||||||
|
return pair;
|
||||||
|
} else if (mode & PREVIOUSLY_BLACK) {
|
||||||
|
pair.build_func = &build_2ppB_lut_64k_from_0;
|
||||||
|
pair.lookup_func = &calc_epd_input_2ppB_lut_64k;
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
} else if (lut_size >= 1024) {
|
||||||
|
if (mode & PREVIOUSLY_WHITE) {
|
||||||
|
pair.build_func = &build_2ppB_lut_1k;
|
||||||
|
pair.lookup_func = &calc_epd_input_2ppB_1k_lut_white;
|
||||||
|
return pair;
|
||||||
|
} else if (mode & PREVIOUSLY_BLACK) {
|
||||||
|
pair.build_func = &build_2ppB_lut_1k;
|
||||||
|
pair.lookup_func = &calc_epd_input_2ppB_1k_lut_black;
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mode & MODE_PACKING_8PPB) {
|
||||||
|
if (lut_size < sizeof(lut_8ppB_start_at_white)) {
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode & PREVIOUSLY_WHITE) {
|
||||||
|
pair.build_func = &build_8ppB_lut_256b_from_white;
|
||||||
|
pair.lookup_func = &calc_epd_input_8ppB;
|
||||||
|
return pair;
|
||||||
|
} else if (mode & PREVIOUSLY_BLACK) {
|
||||||
|
pair.build_func = &build_8ppB_lut_256b_from_black;
|
||||||
|
pair.lookup_func = &calc_epd_input_8ppB;
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
47
lib/libesp32_eink/epdiy/src/output_common/lut.h
Normal file
47
lib/libesp32_eink/epdiy/src/output_common/lut.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
// Make a block of 4 pixels lighter on the EPD.
|
||||||
|
#define CLEAR_BYTE 0B10101010
|
||||||
|
// Make a block of 4 pixels darker on the EPD.
|
||||||
|
#define DARK_BYTE 0B01010101
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type signature of a framebuffer to display output lookup function.
|
||||||
|
*/
|
||||||
|
typedef void (*lut_func_t)(
|
||||||
|
const uint32_t* line_buffer, uint8_t* epd_input, const uint8_t* lut, uint32_t epd_width
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type signature of a LUT preparation function.
|
||||||
|
*/
|
||||||
|
typedef void (*lut_build_func_t)(uint8_t* lut, const EpdWaveformPhases* phases, int frame);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
lut_build_func_t build_func;
|
||||||
|
lut_func_t lookup_func;
|
||||||
|
} LutFunctionPair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the appropriate LUT building and lookup function
|
||||||
|
* for the selected draw mode and allocated LUT size.
|
||||||
|
*/
|
||||||
|
LutFunctionPair find_lut_functions(enum EpdDrawMode mode, uint32_t lut_size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reorder the output buffer to account for I2S FIFO order.
|
||||||
|
*/
|
||||||
|
void reorder_line_buffer(uint32_t* line_data, int buf_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a mask to a line buffer.
|
||||||
|
* `len` must be divisible by 4.
|
||||||
|
*/
|
||||||
|
void epd_apply_line_mask(uint8_t* buf, const uint8_t* mask, int len);
|
||||||
|
|
||||||
|
// legacy functions
|
||||||
|
void bit_shift_buffer_right(uint8_t* buf, uint32_t len, int shift);
|
||||||
|
void nibble_shift_buffer_right(uint8_t* buf, uint32_t len);
|
||||||
106
lib/libesp32_eink/epdiy/src/output_common/render_context.c
Normal file
106
lib/libesp32_eink/epdiy/src/output_common/render_context.c
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#include "render_context.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include "../epdiy.h"
|
||||||
|
#include "lut.h"
|
||||||
|
#include "render_method.h"
|
||||||
|
|
||||||
|
/// For waveforms without timing and the I2S diving method,
|
||||||
|
/// the default hold time for each line is 12us
|
||||||
|
const static int DEFAULT_FRAME_TIME = 120;
|
||||||
|
|
||||||
|
static inline int min(int x, int y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_buffer_params(
|
||||||
|
RenderContext_t* ctx,
|
||||||
|
int* bytes_per_line,
|
||||||
|
const uint8_t** start_ptr,
|
||||||
|
int* min_y,
|
||||||
|
int* max_y,
|
||||||
|
int* pixels_per_byte
|
||||||
|
) {
|
||||||
|
EpdRect area = ctx->area;
|
||||||
|
|
||||||
|
enum EpdDrawMode mode = ctx->mode;
|
||||||
|
const EpdRect crop_to = ctx->crop_to;
|
||||||
|
const bool horizontally_cropped = !(crop_to.x == 0 && crop_to.width == area.width);
|
||||||
|
const bool vertically_cropped = !(crop_to.y == 0 && crop_to.height == area.height);
|
||||||
|
|
||||||
|
// number of pixels per byte of input data
|
||||||
|
int width_divider = 0;
|
||||||
|
|
||||||
|
if (mode & MODE_PACKING_1PPB_DIFFERENCE) {
|
||||||
|
*bytes_per_line = area.width;
|
||||||
|
width_divider = 1;
|
||||||
|
} else if (mode & MODE_PACKING_2PPB) {
|
||||||
|
*bytes_per_line = area.width / 2 + area.width % 2;
|
||||||
|
width_divider = 2;
|
||||||
|
} else if (mode & MODE_PACKING_8PPB) {
|
||||||
|
*bytes_per_line = (area.width / 8 + (area.width % 8 > 0));
|
||||||
|
width_divider = 8;
|
||||||
|
} else {
|
||||||
|
ctx->error |= EPD_DRAW_INVALID_PACKING_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crop_x = (horizontally_cropped ? crop_to.x : 0);
|
||||||
|
int crop_y = (vertically_cropped ? crop_to.y : 0);
|
||||||
|
int crop_h = (vertically_cropped ? crop_to.height : 0);
|
||||||
|
|
||||||
|
const uint8_t* ptr_start = ctx->data_ptr;
|
||||||
|
|
||||||
|
// Adjust for negative starting coordinates with optional crop
|
||||||
|
if (area.x - crop_x < 0) {
|
||||||
|
ptr_start += -(area.x - crop_x) / width_divider;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (area.y - crop_y < 0) {
|
||||||
|
ptr_start += -(area.y - crop_y) * *bytes_per_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate start and end row with crop
|
||||||
|
*min_y = area.y + crop_y;
|
||||||
|
*max_y = min(*min_y + (vertically_cropped ? crop_h : area.height), area.height);
|
||||||
|
*start_ptr = ptr_start;
|
||||||
|
*pixels_per_byte = width_divider;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR prepare_context_for_next_frame(RenderContext_t* ctx) {
|
||||||
|
int frame_time = DEFAULT_FRAME_TIME;
|
||||||
|
if (ctx->phase_times != NULL) {
|
||||||
|
frame_time = ctx->phase_times[ctx->current_frame];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->mode & MODE_EPDIY_MONOCHROME) {
|
||||||
|
frame_time = MONOCHROME_FRAME_TIME;
|
||||||
|
}
|
||||||
|
ctx->frame_time = frame_time;
|
||||||
|
|
||||||
|
const EpdWaveformPhases* phases
|
||||||
|
= ctx->waveform->mode_data[ctx->waveform_index]->range_data[ctx->waveform_range];
|
||||||
|
|
||||||
|
assert(ctx->lut_build_func != NULL);
|
||||||
|
ctx->lut_build_func(ctx->conversion_lut, phases, ctx->current_frame);
|
||||||
|
|
||||||
|
ctx->lines_prepared = 0;
|
||||||
|
ctx->lines_consumed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len) {
|
||||||
|
if (dirty_columns == NULL) {
|
||||||
|
memset(line_mask, 0xFF, mask_len);
|
||||||
|
} else {
|
||||||
|
int pixels = mask_len * 4;
|
||||||
|
for (int c = 0; c < pixels / 2; c += 2) {
|
||||||
|
uint8_t mask = 0;
|
||||||
|
mask |= (dirty_columns[c + 1] & 0xF0) != 0 ? 0xC0 : 0x00;
|
||||||
|
mask |= (dirty_columns[c + 1] & 0x0F) != 0 ? 0x30 : 0x00;
|
||||||
|
mask |= (dirty_columns[c] & 0xF0) != 0 ? 0x0C : 0x00;
|
||||||
|
mask |= (dirty_columns[c] & 0x0F) != 0 ? 0x03 : 0x00;
|
||||||
|
line_mask[c / 2] = mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
lib/libesp32_eink/epdiy/src/output_common/render_context.h
Normal file
110
lib/libesp32_eink/epdiy/src/output_common/render_context.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "../epdiy.h"
|
||||||
|
#include "line_queue.h"
|
||||||
|
#include "lut.h"
|
||||||
|
|
||||||
|
#define NUM_RENDER_THREADS 2
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
EpdRect area;
|
||||||
|
EpdRect crop_to;
|
||||||
|
const bool* drawn_lines;
|
||||||
|
const uint8_t* data_ptr;
|
||||||
|
|
||||||
|
/// The display width for quick access.
|
||||||
|
int display_width;
|
||||||
|
/// The display height for quick access.
|
||||||
|
int display_height;
|
||||||
|
|
||||||
|
/// index of the next line of data to process
|
||||||
|
atomic_int lines_prepared;
|
||||||
|
volatile int lines_consumed;
|
||||||
|
int lines_total;
|
||||||
|
|
||||||
|
/// frame currently in the current update cycle
|
||||||
|
int current_frame;
|
||||||
|
/// number of frames in the current update cycle
|
||||||
|
int cycle_frames;
|
||||||
|
|
||||||
|
TaskHandle_t feed_tasks[NUM_RENDER_THREADS];
|
||||||
|
SemaphoreHandle_t feed_done_smphr[NUM_RENDER_THREADS];
|
||||||
|
SemaphoreHandle_t frame_done;
|
||||||
|
/// Line buffers for feed tasks
|
||||||
|
uint8_t* feed_line_buffers[NUM_RENDER_THREADS];
|
||||||
|
|
||||||
|
/// index of the waveform mode when using vendor waveforms.
|
||||||
|
/// This is not necessarily the mode number if the waveform header
|
||||||
|
// only contains a selection of modes!
|
||||||
|
int waveform_index;
|
||||||
|
/// waveform range when using vendor waveforms
|
||||||
|
int waveform_range;
|
||||||
|
/// Draw time for the current frame in 1/10ths of us.
|
||||||
|
int frame_time;
|
||||||
|
|
||||||
|
const int* phase_times;
|
||||||
|
|
||||||
|
const EpdWaveform* waveform;
|
||||||
|
enum EpdDrawMode mode;
|
||||||
|
enum EpdDrawError error;
|
||||||
|
|
||||||
|
// Lookup table size.
|
||||||
|
size_t conversion_lut_size;
|
||||||
|
// Lookup table space.
|
||||||
|
uint8_t* conversion_lut;
|
||||||
|
|
||||||
|
/// LUT lookup function. Must not be NULL.
|
||||||
|
lut_func_t lut_lookup_func;
|
||||||
|
/// LUT building function. Must not be NULL
|
||||||
|
lut_build_func_t lut_build_func;
|
||||||
|
|
||||||
|
/// Queue of lines prepared for output to the display,
|
||||||
|
/// one for each thread.
|
||||||
|
LineQueue_t line_queues[NUM_RENDER_THREADS];
|
||||||
|
uint8_t* line_threads;
|
||||||
|
|
||||||
|
// Output line mask
|
||||||
|
uint8_t* line_mask;
|
||||||
|
|
||||||
|
/// track line skipping when working in old i2s mode
|
||||||
|
int skipping;
|
||||||
|
|
||||||
|
/// line buffer when using epd_push_pixels
|
||||||
|
uint8_t* static_line_buffer;
|
||||||
|
} RenderContext_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the render context, assign the bytes per line,
|
||||||
|
* framebuffer start pointer, min and max vertical positions and the pixels per byte.
|
||||||
|
*/
|
||||||
|
void get_buffer_params(
|
||||||
|
RenderContext_t* ctx,
|
||||||
|
int* bytes_per_line,
|
||||||
|
const uint8_t** start_ptr,
|
||||||
|
int* min_y,
|
||||||
|
int* max_y,
|
||||||
|
int* pixels_per_byte
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the render context for drawing the next frame.
|
||||||
|
*
|
||||||
|
* (Reset counters, etc)
|
||||||
|
*/
|
||||||
|
void prepare_context_for_next_frame(RenderContext_t* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate an output line mask from line dirtyness with two bits per pixel.
|
||||||
|
* If the dirtyness data is NULL, set the mask to neutral.
|
||||||
|
*
|
||||||
|
* don't inline for to ensure availability in tests.
|
||||||
|
*/
|
||||||
|
void __attribute__((noinline)) epd_populate_line_mask(
|
||||||
|
uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len
|
||||||
|
);
|
||||||
10
lib/libesp32_eink/epdiy/src/output_common/render_method.c
Normal file
10
lib/libesp32_eink/epdiy/src/output_common/render_method.c
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#include "render_method.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
const enum EpdRenderMethod EPD_CURRENT_RENDER_METHOD = RENDER_METHOD_I2S;
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
const enum EpdRenderMethod EPD_CURRENT_RENDER_METHOD = RENDER_METHOD_LCD;
|
||||||
|
#else
|
||||||
|
#error "unknown chip, cannot choose render method!"
|
||||||
|
#endif
|
||||||
29
lib/libesp32_eink/epdiy/src/output_common/render_method.h
Normal file
29
lib/libesp32_eink/epdiy/src/output_common/render_method.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendering Method / Hardware to use.
|
||||||
|
*/
|
||||||
|
enum EpdRenderMethod {
|
||||||
|
/// Use the I2S peripheral on ESP32 chips.
|
||||||
|
RENDER_METHOD_I2S = 1,
|
||||||
|
/// Use the CAM/LCD peripheral in ESP32-S3 chips.
|
||||||
|
RENDER_METHOD_LCD = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const enum EpdRenderMethod EPD_CURRENT_RENDER_METHOD;
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define RENDER_METHOD_I2S 1
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
#define RENDER_METHOD_LCD 1
|
||||||
|
#else
|
||||||
|
#error "unknown chip, cannot choose render method!"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#define IRAM_ATTR
|
||||||
|
// define this if we're using clangd to make it accept the GCC builtin
|
||||||
|
void __assert_func(const char* file, int line, const char* func, const char* failedexpr);
|
||||||
|
#endif
|
||||||
303
lib/libesp32_eink/epdiy/src/output_i2s/i2s_data_bus.c
Normal file
303
lib/libesp32_eink/epdiy/src/output_i2s/i2s_data_bus.c
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
#include "i2s_data_bus.h"
|
||||||
|
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
// the I2S driver is based on ESP32 registers and won't compile on the S3
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
|
||||||
|
#include "driver/rtc_io.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#if ESP_IDF_VERSION < (4, 0, 0) || ARDUINO_ARCH_ESP32
|
||||||
|
#include "rom/gpio.h"
|
||||||
|
#include "rom/lldesc.h"
|
||||||
|
#else
|
||||||
|
#include "esp32/rom/gpio.h"
|
||||||
|
#include "esp32/rom/lldesc.h"
|
||||||
|
#endif
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "soc/i2s_reg.h"
|
||||||
|
#include "soc/i2s_struct.h"
|
||||||
|
#include "soc/rtc.h"
|
||||||
|
|
||||||
|
#include "esp_system.h" // for ESP_IDF_VERSION_VAL
|
||||||
|
#include "esp_private/periph_ctrl.h"
|
||||||
|
|
||||||
|
/// DMA descriptors for front and back line buffer.
|
||||||
|
/// We use two buffers, so one can be filled while the other
|
||||||
|
/// is transmitted.
|
||||||
|
typedef struct {
|
||||||
|
volatile lldesc_t* dma_desc_a;
|
||||||
|
volatile lldesc_t* dma_desc_b;
|
||||||
|
|
||||||
|
/// Front and back line buffer.
|
||||||
|
uint8_t* buf_a;
|
||||||
|
uint8_t* buf_b;
|
||||||
|
} i2s_parallel_state_t;
|
||||||
|
|
||||||
|
/// Indicates which line buffer is currently back / front.
|
||||||
|
static int current_buffer = 0;
|
||||||
|
|
||||||
|
/// The I2S state instance.
|
||||||
|
static i2s_parallel_state_t i2s_state;
|
||||||
|
|
||||||
|
static intr_handle_t gI2S_intr_handle = NULL;
|
||||||
|
|
||||||
|
/// Indicates the device has finished its transmission and is ready again.
|
||||||
|
static volatile bool output_done = true;
|
||||||
|
/// The start pulse pin extracted from the configuration for use in the "done"
|
||||||
|
/// interrupt.
|
||||||
|
static gpio_num_t start_pulse_pin;
|
||||||
|
|
||||||
|
/// Initializes a DMA descriptor.
|
||||||
|
static void fill_dma_desc(
|
||||||
|
volatile lldesc_t* dmadesc, uint8_t* buf, uint32_t buf_size, uint32_t buf_length
|
||||||
|
) {
|
||||||
|
assert(buf_length % 4 == 0); // Must be word aligned
|
||||||
|
dmadesc->size = buf_size;
|
||||||
|
dmadesc->length = buf_length;
|
||||||
|
dmadesc->buf = buf;
|
||||||
|
dmadesc->eof = 1;
|
||||||
|
dmadesc->sosf = 1;
|
||||||
|
dmadesc->owner = 1;
|
||||||
|
dmadesc->qe.stqe_next = 0;
|
||||||
|
dmadesc->offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Address of the currently front DMA descriptor,
|
||||||
|
/// which uses only the lower 20bits (according to TRM)
|
||||||
|
uint32_t dma_desc_addr() {
|
||||||
|
return (uint32_t)(current_buffer ? i2s_state.dma_desc_a : i2s_state.dma_desc_b) & 0x000FFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to help align values
|
||||||
|
static size_t align_up(size_t x, size_t a) {
|
||||||
|
return (size_t)(x + ((size_t)a - 1)) & ~(size_t)(a - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up a GPIO as output and route it to a signal.
|
||||||
|
static void gpio_setup_out(int gpio, int sig, bool invert) {
|
||||||
|
if (gpio == -1)
|
||||||
|
return;
|
||||||
|
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(gpio, GPIO_MODE_DEF_OUTPUT);
|
||||||
|
gpio_matrix_out(gpio, sig, invert, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets "Start Pulse" signal when the current row output is done.
|
||||||
|
static void IRAM_ATTR i2s_int_hdl(void* arg) {
|
||||||
|
i2s_dev_t* dev = &I2S1;
|
||||||
|
if (dev->int_st.out_done) {
|
||||||
|
// gpio_set_level(start_pulse_pin, 1);
|
||||||
|
// gpio_set_level(GPIO_NUM_26, 0);
|
||||||
|
output_done = true;
|
||||||
|
}
|
||||||
|
// Clear the interrupt. Otherwise, the whole device would hang.
|
||||||
|
dev->int_clr.val = dev->int_raw.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* IRAM_ATTR i2s_get_current_buffer() {
|
||||||
|
return (uint8_t*)(current_buffer ? i2s_state.dma_desc_a->buf : i2s_state.dma_desc_b->buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IRAM_ATTR i2s_is_busy() {
|
||||||
|
// DMA and FIFO must be done
|
||||||
|
return !output_done || !I2S1.state.tx_idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR i2s_switch_buffer() {
|
||||||
|
// either device is done transmitting or the switch must be away from the
|
||||||
|
// buffer currently used by the DMA engine.
|
||||||
|
while (i2s_is_busy() && dma_desc_addr() != I2S1.out_link.addr) {
|
||||||
|
};
|
||||||
|
current_buffer = !current_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR i2s_start_line_output() {
|
||||||
|
output_done = false;
|
||||||
|
|
||||||
|
i2s_dev_t* dev = &I2S1;
|
||||||
|
dev->conf.tx_start = 0;
|
||||||
|
dev->conf.tx_reset = 1;
|
||||||
|
dev->conf.tx_fifo_reset = 1;
|
||||||
|
dev->conf.rx_fifo_reset = 1;
|
||||||
|
dev->conf.tx_reset = 0;
|
||||||
|
dev->conf.tx_fifo_reset = 0;
|
||||||
|
dev->conf.rx_fifo_reset = 0;
|
||||||
|
dev->out_link.addr = dma_desc_addr();
|
||||||
|
dev->out_link.start = 1;
|
||||||
|
|
||||||
|
// sth is pulled up through peripheral interrupt
|
||||||
|
// This is timing-critical!
|
||||||
|
gpio_set_level(start_pulse_pin, 0);
|
||||||
|
dev->conf.tx_start = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_gpio_attach(i2s_bus_config* cfg) {
|
||||||
|
gpio_num_t I2S_GPIO_BUS[] = { cfg->data_6, cfg->data_7, cfg->data_4, cfg->data_5,
|
||||||
|
cfg->data_2, cfg->data_3, cfg->data_0, cfg->data_1 };
|
||||||
|
|
||||||
|
gpio_set_direction(cfg->start_pulse, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_level(cfg->start_pulse, 1);
|
||||||
|
// Use I2S1 with no signal offset (for some reason the offset seems to be
|
||||||
|
// needed in 16-bit mode, but not in 8 bit mode.
|
||||||
|
int signal_base = I2S1O_DATA_OUT0_IDX;
|
||||||
|
|
||||||
|
// Setup and route GPIOS
|
||||||
|
for (int x = 0; x < 8; x++) {
|
||||||
|
gpio_setup_out(I2S_GPIO_BUS[x], signal_base + x, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free CKH after wakeup
|
||||||
|
gpio_hold_dis(cfg->clock);
|
||||||
|
// Invert word select signal
|
||||||
|
gpio_setup_out(cfg->clock, I2S1O_WS_OUT_IDX, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_gpio_detach(i2s_bus_config* cfg) {
|
||||||
|
gpio_set_direction(cfg->data_0, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_1, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_2, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_3, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_4, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_5, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_6, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->data_7, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->start_pulse, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_direction(cfg->clock, GPIO_MODE_INPUT);
|
||||||
|
|
||||||
|
gpio_reset_pin(cfg->clock);
|
||||||
|
if (cfg->clock != 5) {
|
||||||
|
rtc_gpio_isolate(cfg->clock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_bus_init(i2s_bus_config* cfg, uint32_t epd_row_width) {
|
||||||
|
i2s_gpio_attach(cfg);
|
||||||
|
|
||||||
|
// store pin in global variable for use in interrupt.
|
||||||
|
start_pulse_pin = cfg->start_pulse;
|
||||||
|
|
||||||
|
periph_module_enable(PERIPH_I2S1_MODULE);
|
||||||
|
|
||||||
|
i2s_dev_t* dev = &I2S1;
|
||||||
|
|
||||||
|
// Initialize device
|
||||||
|
dev->conf.tx_reset = 1;
|
||||||
|
dev->conf.tx_reset = 0;
|
||||||
|
|
||||||
|
// Reset DMA
|
||||||
|
dev->lc_conf.in_rst = 1;
|
||||||
|
dev->lc_conf.in_rst = 0;
|
||||||
|
dev->lc_conf.out_rst = 1;
|
||||||
|
dev->lc_conf.out_rst = 0;
|
||||||
|
|
||||||
|
// Setup I2S config. See section 12 of Technical Reference Manual
|
||||||
|
// Enable LCD mode
|
||||||
|
dev->conf2.val = 0;
|
||||||
|
dev->conf2.lcd_en = 1;
|
||||||
|
|
||||||
|
// Enable FRAME1-Mode (See technical reference manual)
|
||||||
|
dev->conf2.lcd_tx_wrx2_en = 1;
|
||||||
|
dev->conf2.lcd_tx_sdx2_en = 0;
|
||||||
|
|
||||||
|
// Set to 8 bit parallel output
|
||||||
|
dev->sample_rate_conf.val = 0;
|
||||||
|
dev->sample_rate_conf.tx_bits_mod = 8;
|
||||||
|
|
||||||
|
// Half speed of bit clock in LCD mode.
|
||||||
|
// (Smallest possible divider according to the spec).
|
||||||
|
dev->sample_rate_conf.tx_bck_div_num = 2;
|
||||||
|
|
||||||
|
// Initialize Audio Clock (APLL)
|
||||||
|
rtc_clk_apll_enable(true);
|
||||||
|
rtc_clk_apll_coeff_set(0, 0, 0, 8);
|
||||||
|
|
||||||
|
// Set Audio Clock Dividers
|
||||||
|
dev->clkm_conf.val = 0;
|
||||||
|
dev->clkm_conf.clka_en = 1;
|
||||||
|
dev->clkm_conf.clkm_div_a = 1;
|
||||||
|
dev->clkm_conf.clkm_div_b = 0;
|
||||||
|
// 2 is the smallest possible divider according to the spec.
|
||||||
|
dev->clkm_conf.clkm_div_num = 2;
|
||||||
|
|
||||||
|
// Set up FIFO
|
||||||
|
dev->fifo_conf.val = 0;
|
||||||
|
dev->fifo_conf.tx_fifo_mod_force_en = 1;
|
||||||
|
dev->fifo_conf.tx_fifo_mod = 1;
|
||||||
|
dev->fifo_conf.tx_data_num = 32;
|
||||||
|
dev->fifo_conf.dscr_en = 1;
|
||||||
|
|
||||||
|
// Stop after transmission complete
|
||||||
|
dev->conf1.val = 0;
|
||||||
|
dev->conf1.tx_stop_en = 0;
|
||||||
|
dev->conf1.tx_pcm_bypass = 1;
|
||||||
|
|
||||||
|
// Configure TX channel
|
||||||
|
dev->conf_chan.val = 0;
|
||||||
|
dev->conf_chan.tx_chan_mod = 1;
|
||||||
|
dev->conf.tx_right_first = 1;
|
||||||
|
|
||||||
|
dev->timing.val = 0;
|
||||||
|
|
||||||
|
// Allocate DMA descriptors
|
||||||
|
const size_t buf_size = align_up(epd_row_width / 4, 4); // Buf size must be word aligned
|
||||||
|
i2s_state.buf_a = heap_caps_malloc(buf_size, MALLOC_CAP_DMA);
|
||||||
|
i2s_state.buf_b = heap_caps_malloc(buf_size, MALLOC_CAP_DMA);
|
||||||
|
i2s_state.dma_desc_a = heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||||
|
i2s_state.dma_desc_b = heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA);
|
||||||
|
|
||||||
|
// and fill them
|
||||||
|
fill_dma_desc(i2s_state.dma_desc_a, i2s_state.buf_a, epd_row_width / 4, buf_size);
|
||||||
|
fill_dma_desc(i2s_state.dma_desc_b, i2s_state.buf_b, epd_row_width / 4, buf_size);
|
||||||
|
|
||||||
|
// enable "done" interrupt
|
||||||
|
SET_PERI_REG_BITS(I2S_INT_ENA_REG(1), I2S_OUT_DONE_INT_ENA_V, 1, I2S_OUT_DONE_INT_ENA_S);
|
||||||
|
// register interrupt
|
||||||
|
esp_intr_alloc(ETS_I2S1_INTR_SOURCE, 0, i2s_int_hdl, 0, &gI2S_intr_handle);
|
||||||
|
|
||||||
|
// Reset FIFO/DMA
|
||||||
|
dev->lc_conf.in_rst = 1;
|
||||||
|
dev->lc_conf.out_rst = 1;
|
||||||
|
dev->lc_conf.ahbm_rst = 1;
|
||||||
|
dev->lc_conf.ahbm_fifo_rst = 1;
|
||||||
|
dev->lc_conf.in_rst = 0;
|
||||||
|
dev->lc_conf.out_rst = 0;
|
||||||
|
dev->lc_conf.ahbm_rst = 0;
|
||||||
|
dev->lc_conf.ahbm_fifo_rst = 0;
|
||||||
|
dev->conf.tx_reset = 1;
|
||||||
|
dev->conf.tx_fifo_reset = 1;
|
||||||
|
dev->conf.rx_fifo_reset = 1;
|
||||||
|
dev->conf.tx_reset = 0;
|
||||||
|
dev->conf.tx_fifo_reset = 0;
|
||||||
|
dev->conf.rx_fifo_reset = 0;
|
||||||
|
|
||||||
|
// Start dma on front buffer
|
||||||
|
dev->lc_conf.val = I2S_OUT_DATA_BURST_EN | I2S_OUTDSCR_BURST_EN | I2S_OUT_DATA_BURST_EN;
|
||||||
|
dev->out_link.addr = ((uint32_t)(i2s_state.dma_desc_a));
|
||||||
|
dev->out_link.start = 1;
|
||||||
|
|
||||||
|
dev->int_clr.val = dev->int_raw.val;
|
||||||
|
|
||||||
|
dev->int_ena.val = 0;
|
||||||
|
dev->int_ena.out_done = 1;
|
||||||
|
|
||||||
|
dev->conf.tx_start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_bus_deinit() {
|
||||||
|
esp_intr_disable(gI2S_intr_handle);
|
||||||
|
esp_intr_free(gI2S_intr_handle);
|
||||||
|
|
||||||
|
free(i2s_state.buf_a);
|
||||||
|
free(i2s_state.buf_b);
|
||||||
|
free((void*)i2s_state.dma_desc_a);
|
||||||
|
free((void*)i2s_state.dma_desc_b);
|
||||||
|
|
||||||
|
rtc_clk_apll_coeff_set(0, 0, 0, 8);
|
||||||
|
rtc_clk_apll_enable(true);
|
||||||
|
|
||||||
|
periph_module_disable(PERIPH_I2S1_MODULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
77
lib/libesp32_eink/epdiy/src/output_i2s/i2s_data_bus.h
Normal file
77
lib/libesp32_eink/epdiy/src/output_i2s/i2s_data_bus.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Implements a 8bit parallel interface to transmit pixel
|
||||||
|
* data to the display, based on the I2S peripheral.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <esp_attr.h>
|
||||||
|
#include <soc/gpio_periph.h>
|
||||||
|
#include <soc/gpio_struct.h>
|
||||||
|
#include <soc/io_mux_reg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I2S bus configuration parameters.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
// GPIO numbers of the parallel bus pins.
|
||||||
|
gpio_num_t data_0;
|
||||||
|
gpio_num_t data_1;
|
||||||
|
gpio_num_t data_2;
|
||||||
|
gpio_num_t data_3;
|
||||||
|
gpio_num_t data_4;
|
||||||
|
gpio_num_t data_5;
|
||||||
|
gpio_num_t data_6;
|
||||||
|
gpio_num_t data_7;
|
||||||
|
|
||||||
|
// Data clock pin.
|
||||||
|
gpio_num_t clock;
|
||||||
|
|
||||||
|
// "Start Pulse", enabling data input on the slave device (active low)
|
||||||
|
gpio_num_t start_pulse;
|
||||||
|
} i2s_bus_config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the I2S data bus for communication
|
||||||
|
* with a 8bit parallel display interface.
|
||||||
|
*/
|
||||||
|
void i2s_bus_init(i2s_bus_config* cfg, uint32_t epd_row_width);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach I2S to gpio's
|
||||||
|
*/
|
||||||
|
void i2s_gpio_attach(i2s_bus_config* cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach I2S from gpio's
|
||||||
|
*/
|
||||||
|
void i2s_gpio_detach(i2s_bus_config* cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently writable line buffer.
|
||||||
|
*/
|
||||||
|
uint8_t* i2s_get_current_buffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches front and back line buffer.
|
||||||
|
* If the switched-to line buffer is currently in use,
|
||||||
|
* this function blocks until transmission is done.
|
||||||
|
*/
|
||||||
|
void i2s_switch_buffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start transmission of the current back buffer.
|
||||||
|
*/
|
||||||
|
void i2s_start_line_output();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is an ongoing transmission.
|
||||||
|
*/
|
||||||
|
bool i2s_is_busy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give up allocated resources.
|
||||||
|
*/
|
||||||
|
void i2s_bus_deinit();
|
||||||
402
lib/libesp32_eink/epdiy/src/output_i2s/render_i2s.c
Normal file
402
lib/libesp32_eink/epdiy/src/output_i2s/render_i2s.c
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
#include "render_i2s.h"
|
||||||
|
|
||||||
|
#include "../output_common/render_method.h"
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_I2S
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#include "epd_internals.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
// output a row to the display.
|
||||||
|
#include "../output_common/lut.h"
|
||||||
|
#include "../output_common/render_context.h"
|
||||||
|
#include "i2s_data_bus.h"
|
||||||
|
#include "rmt_pulse.h"
|
||||||
|
|
||||||
|
static const epd_ctrl_state_t NoChangeState = { 0 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until all previously submitted data has been written.
|
||||||
|
* Then, the following operations are initiated:
|
||||||
|
*
|
||||||
|
* - Previously submitted data is latched to the output register.
|
||||||
|
* - The RMT peripheral is set up to pulse the vertical (gate) driver for
|
||||||
|
* `output_time_dus` / 10 microseconds.
|
||||||
|
* - The I2S peripheral starts transmission of the current buffer to
|
||||||
|
* the source driver.
|
||||||
|
* - The line buffers are switched.
|
||||||
|
*
|
||||||
|
* This sequence of operations allows for pipelining data preparation and
|
||||||
|
* transfer, reducing total refresh times.
|
||||||
|
*/
|
||||||
|
static void IRAM_ATTR i2s_output_row(uint32_t output_time_dus) {
|
||||||
|
while (i2s_is_busy() || rmt_busy()) {
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdBoardDefinition* epd_board = epd_current_board();
|
||||||
|
epd_ctrl_state_t* ctrl_state = epd_ctrl_state();
|
||||||
|
epd_ctrl_state_t mask = NoChangeState;
|
||||||
|
|
||||||
|
ctrl_state->ep_sth = true;
|
||||||
|
ctrl_state->ep_latch_enable = true;
|
||||||
|
mask.ep_sth = true;
|
||||||
|
mask.ep_latch_enable = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
|
||||||
|
mask = NoChangeState;
|
||||||
|
ctrl_state->ep_latch_enable = false;
|
||||||
|
mask.ep_latch_enable = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
|
||||||
|
if (epd_get_display()->display_type == DISPLAY_TYPE_ED097TC2) {
|
||||||
|
pulse_ckv_ticks(output_time_dus, 1, false);
|
||||||
|
} else {
|
||||||
|
pulse_ckv_ticks(output_time_dus, 50, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
i2s_start_line_output();
|
||||||
|
i2s_switch_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Line i2s_output_row, but resets skip indicator. */
|
||||||
|
static void IRAM_ATTR i2s_write_row(RenderContext_t* ctx, uint32_t output_time_dus) {
|
||||||
|
i2s_output_row(output_time_dus);
|
||||||
|
ctx->skipping = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Skip a row without writing to it. */
|
||||||
|
static void IRAM_ATTR i2s_skip_row(RenderContext_t* ctx, uint8_t pipeline_finish_time) {
|
||||||
|
int line_bytes = ctx->display_width / 4;
|
||||||
|
// output previously loaded row, fill buffer with no-ops.
|
||||||
|
if (ctx->skipping < 2) {
|
||||||
|
memset((void*)i2s_get_current_buffer(), 0x00, line_bytes);
|
||||||
|
i2s_output_row(pipeline_finish_time);
|
||||||
|
} else {
|
||||||
|
if (epd_get_display()->display_type == DISPLAY_TYPE_ED097TC2) {
|
||||||
|
pulse_ckv_ticks(5, 5, false);
|
||||||
|
} else {
|
||||||
|
// According to the spec, the OC4 maximum CKV frequency is 200kHz.
|
||||||
|
pulse_ckv_ticks(45, 5, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->skipping++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a draw cycle.
|
||||||
|
*/
|
||||||
|
static void i2s_start_frame() {
|
||||||
|
while (i2s_is_busy() || rmt_busy()) {
|
||||||
|
};
|
||||||
|
|
||||||
|
const EpdBoardDefinition* epd_board = epd_current_board();
|
||||||
|
epd_ctrl_state_t* ctrl_state = epd_ctrl_state();
|
||||||
|
epd_ctrl_state_t mask = NoChangeState;
|
||||||
|
|
||||||
|
ctrl_state->ep_mode = true;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
|
||||||
|
// This is very timing-sensitive!
|
||||||
|
mask = NoChangeState;
|
||||||
|
ctrl_state->ep_stv = false;
|
||||||
|
mask.ep_stv = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
// busy_delay(240);
|
||||||
|
pulse_ckv_us(1000, 100, false);
|
||||||
|
mask = NoChangeState;
|
||||||
|
ctrl_state->ep_stv = true;
|
||||||
|
mask.ep_stv = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
// pulse_ckv_us(0, 10, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
|
||||||
|
mask = NoChangeState;
|
||||||
|
ctrl_state->ep_output_enable = true;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End a draw cycle.
|
||||||
|
*/
|
||||||
|
static void i2s_end_frame() {
|
||||||
|
const EpdBoardDefinition* epd_board = epd_current_board();
|
||||||
|
epd_ctrl_state_t* ctrl_state = epd_ctrl_state();
|
||||||
|
epd_ctrl_state_t mask = NoChangeState;
|
||||||
|
|
||||||
|
ctrl_state->ep_stv = false;
|
||||||
|
mask.ep_stv = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
mask = NoChangeState;
|
||||||
|
ctrl_state->ep_mode = false;
|
||||||
|
mask.ep_mode = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
pulse_ckv_us(0, 10, true);
|
||||||
|
mask = NoChangeState;
|
||||||
|
ctrl_state->ep_output_enable = false;
|
||||||
|
mask.ep_output_enable = true;
|
||||||
|
epd_board->set_ctrl(ctrl_state, &mask);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
pulse_ckv_us(1, 1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_do_update(RenderContext_t* ctx) {
|
||||||
|
for (uint8_t k = 0; k < ctx->cycle_frames; k++) {
|
||||||
|
prepare_context_for_next_frame(ctx);
|
||||||
|
|
||||||
|
// start both feeder tasks
|
||||||
|
xTaskNotifyGive(ctx->feed_tasks[!xPortGetCoreID()]);
|
||||||
|
xTaskNotifyGive(ctx->feed_tasks[xPortGetCoreID()]);
|
||||||
|
|
||||||
|
// transmission is started in renderer threads, now wait util it's done
|
||||||
|
xSemaphoreTake(ctx->frame_done, portMAX_DELAY);
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_RENDER_THREADS; i++) {
|
||||||
|
xSemaphoreTake(ctx->feed_done_smphr[i], portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->current_frame++;
|
||||||
|
|
||||||
|
// make the watchdog happy.
|
||||||
|
if (k % 10 == 0) {
|
||||||
|
vTaskDelay(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR epd_push_pixels_i2s(RenderContext_t* ctx, EpdRect area, short time, int color) {
|
||||||
|
int line_bytes = ctx->display_width / 4;
|
||||||
|
uint8_t row[line_bytes];
|
||||||
|
memset(row, 0, line_bytes);
|
||||||
|
|
||||||
|
const uint8_t color_choice[4] = { DARK_BYTE, CLEAR_BYTE, 0x00, 0xFF };
|
||||||
|
for (uint32_t i = 0; i < area.width; i++) {
|
||||||
|
uint32_t position = i + area.x % 4;
|
||||||
|
uint8_t mask = color_choice[color] & (0b00000011 << (2 * (position % 4)));
|
||||||
|
row[area.x / 4 + position / 4] |= mask;
|
||||||
|
}
|
||||||
|
reorder_line_buffer((uint32_t*)row, line_bytes);
|
||||||
|
|
||||||
|
i2s_start_frame();
|
||||||
|
|
||||||
|
for (int i = 0; i < ctx->display_height; i++) {
|
||||||
|
// before are of interest: skip
|
||||||
|
if (i < area.y) {
|
||||||
|
i2s_skip_row(ctx, time);
|
||||||
|
// start area of interest: set row data
|
||||||
|
} else if (i == area.y) {
|
||||||
|
i2s_switch_buffer();
|
||||||
|
memcpy((void*)i2s_get_current_buffer(), row, line_bytes);
|
||||||
|
i2s_switch_buffer();
|
||||||
|
memcpy((void*)i2s_get_current_buffer(), row, line_bytes);
|
||||||
|
|
||||||
|
i2s_write_row(ctx, time * 10);
|
||||||
|
// load nop row if done with area
|
||||||
|
} else if (i >= area.y + area.height) {
|
||||||
|
i2s_skip_row(ctx, time);
|
||||||
|
// output the same as before
|
||||||
|
} else {
|
||||||
|
i2s_write_row(ctx, time * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Since we "pipeline" row output, we still have to latch out the last row.
|
||||||
|
i2s_write_row(ctx, time * 10);
|
||||||
|
|
||||||
|
i2s_end_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR i2s_output_frame(RenderContext_t* ctx, int thread_id) {
|
||||||
|
uint8_t* line_buf = ctx->feed_line_buffers[thread_id];
|
||||||
|
|
||||||
|
ctx->skipping = 0;
|
||||||
|
EpdRect area = ctx->area;
|
||||||
|
int frame_time = ctx->frame_time;
|
||||||
|
|
||||||
|
i2s_start_frame();
|
||||||
|
for (int i = 0; i < ctx->display_height; i++) {
|
||||||
|
LineQueue_t* lq = &ctx->line_queues[0];
|
||||||
|
|
||||||
|
memset(line_buf, 0, ctx->display_width);
|
||||||
|
while (lq_read(lq, line_buf) < 0) {
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx->lines_consumed += 1;
|
||||||
|
|
||||||
|
if (ctx->drawn_lines != NULL && !ctx->drawn_lines[i - area.y]) {
|
||||||
|
i2s_skip_row(ctx, frame_time);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup pixel actions in the waveform LUT
|
||||||
|
ctx->lut_lookup_func(
|
||||||
|
(uint32_t*)line_buf,
|
||||||
|
(uint8_t*)i2s_get_current_buffer(),
|
||||||
|
ctx->conversion_lut,
|
||||||
|
ctx->display_width
|
||||||
|
);
|
||||||
|
|
||||||
|
// apply the line mask
|
||||||
|
epd_apply_line_mask(i2s_get_current_buffer(), ctx->line_mask, ctx->display_width / 4);
|
||||||
|
|
||||||
|
reorder_line_buffer((uint32_t*)i2s_get_current_buffer(), ctx->display_width / 4);
|
||||||
|
i2s_write_row(ctx, frame_time);
|
||||||
|
}
|
||||||
|
if (!ctx->skipping) {
|
||||||
|
// Since we "pipeline" row output, we still have to latch out the
|
||||||
|
// last row.
|
||||||
|
i2s_write_row(ctx, frame_time);
|
||||||
|
}
|
||||||
|
i2s_end_frame();
|
||||||
|
|
||||||
|
xSemaphoreGive(ctx->feed_done_smphr[thread_id]);
|
||||||
|
xSemaphoreGive(ctx->frame_done);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int min(int x, int y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
static inline int max(int x, int y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR i2s_fetch_frame_data(RenderContext_t* ctx, int thread_id) {
|
||||||
|
uint8_t* input_line = ctx->feed_line_buffers[thread_id];
|
||||||
|
|
||||||
|
// line must be able to hold 2-pixel-per-byte or 1-pixel-per-byte data
|
||||||
|
memset(input_line, 0x00, ctx->display_width);
|
||||||
|
|
||||||
|
LineQueue_t* lq = &ctx->line_queues[thread_id];
|
||||||
|
|
||||||
|
EpdRect area = ctx->area;
|
||||||
|
|
||||||
|
int min_y, max_y, bytes_per_line, pixels_per_byte;
|
||||||
|
const uint8_t* ptr_start;
|
||||||
|
get_buffer_params(ctx, &bytes_per_line, &ptr_start, &min_y, &max_y, &pixels_per_byte);
|
||||||
|
|
||||||
|
const EpdRect crop_to = ctx->crop_to;
|
||||||
|
const bool horizontally_cropped = !(crop_to.x == 0 && crop_to.width == area.width);
|
||||||
|
int crop_x = (horizontally_cropped ? crop_to.x : 0);
|
||||||
|
int crop_w = (horizontally_cropped ? crop_to.width : 0);
|
||||||
|
|
||||||
|
// interval of the output line that is needed
|
||||||
|
// FIXME: only lookup needed parts
|
||||||
|
int line_start_x = area.x + (horizontally_cropped ? crop_to.x : 0);
|
||||||
|
int line_end_x = line_start_x + (horizontally_cropped ? crop_to.width : area.width);
|
||||||
|
line_start_x = min(max(line_start_x, 0), ctx->display_width);
|
||||||
|
line_end_x = min(max(line_end_x, 0), ctx->display_width);
|
||||||
|
|
||||||
|
int l = 0;
|
||||||
|
while (l = atomic_fetch_add(&ctx->lines_prepared, 1), l < ctx->display_height) {
|
||||||
|
// if (thread_id) gpio_set_level(15, 0);
|
||||||
|
ctx->line_threads[l] = thread_id;
|
||||||
|
|
||||||
|
if (l < min_y || l >= max_y
|
||||||
|
|| (ctx->drawn_lines != NULL && !ctx->drawn_lines[l - area.y])) {
|
||||||
|
uint8_t* buf = NULL;
|
||||||
|
while (buf == NULL)
|
||||||
|
buf = lq_current(lq);
|
||||||
|
memset(buf, 0x00, lq->element_size);
|
||||||
|
lq_commit(lq);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* lp = (uint32_t*)input_line;
|
||||||
|
bool shifted = false;
|
||||||
|
const uint8_t* ptr = ptr_start + bytes_per_line * (l - min_y);
|
||||||
|
|
||||||
|
if (area.width == ctx->display_width && area.x == 0 && !ctx->error) {
|
||||||
|
lp = (uint32_t*)ptr;
|
||||||
|
} else if (!ctx->error) {
|
||||||
|
uint8_t* buf_start = (uint8_t*)input_line;
|
||||||
|
uint32_t line_bytes = bytes_per_line;
|
||||||
|
|
||||||
|
int min_x = area.x + crop_x;
|
||||||
|
if (min_x >= 0) {
|
||||||
|
buf_start += min_x / pixels_per_byte;
|
||||||
|
} else {
|
||||||
|
// reduce line_bytes to actually used bytes
|
||||||
|
// ptr was already adjusted above
|
||||||
|
line_bytes += min_x / pixels_per_byte;
|
||||||
|
}
|
||||||
|
line_bytes = min(
|
||||||
|
line_bytes,
|
||||||
|
ctx->display_width / pixels_per_byte - (uint32_t)(buf_start - input_line)
|
||||||
|
);
|
||||||
|
|
||||||
|
memcpy(buf_start, ptr, line_bytes);
|
||||||
|
|
||||||
|
int cropped_width = (horizontally_cropped ? crop_w : area.width);
|
||||||
|
/// consider half-byte shifts in two-pixel-per-Byte mode.
|
||||||
|
if (pixels_per_byte == 2) {
|
||||||
|
// mask last nibble for uneven width
|
||||||
|
if (cropped_width % 2 == 1
|
||||||
|
&& min_x / 2 + cropped_width / 2 + 1 < ctx->display_width) {
|
||||||
|
*(buf_start + line_bytes - 1) |= 0xF0;
|
||||||
|
}
|
||||||
|
if (area.x % 2 == 1 && !(crop_x % 2 == 1) && min_x < ctx->display_width) {
|
||||||
|
shifted = true;
|
||||||
|
uint32_t remaining
|
||||||
|
= (uint32_t)input_line + ctx->display_width / 2 - (uint32_t)buf_start;
|
||||||
|
uint32_t to_shift = min(line_bytes + 1, remaining);
|
||||||
|
// shift one nibble to right
|
||||||
|
nibble_shift_buffer_right(buf_start, to_shift);
|
||||||
|
}
|
||||||
|
// consider bit shifts in bit buffers
|
||||||
|
} else if (pixels_per_byte == 8) {
|
||||||
|
// mask last n bits if width is not divisible by 8
|
||||||
|
if (cropped_width % 8 != 0 && bytes_per_line + 1 < ctx->display_width) {
|
||||||
|
uint8_t mask = 0;
|
||||||
|
for (int s = 0; s < cropped_width % 8; s++) {
|
||||||
|
mask = (mask << 1) | 1;
|
||||||
|
}
|
||||||
|
*(buf_start + line_bytes - 1) |= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min_x % 8 != 0 && min_x < ctx->display_width) {
|
||||||
|
// shift to right
|
||||||
|
shifted = true;
|
||||||
|
uint32_t remaining
|
||||||
|
= (uint32_t)input_line + ctx->display_width / 8 - (uint32_t)buf_start;
|
||||||
|
uint32_t to_shift = min(line_bytes + 1, remaining);
|
||||||
|
bit_shift_buffer_right(buf_start, to_shift, min_x % 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lp = (uint32_t*)input_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* buf = NULL;
|
||||||
|
while (buf == NULL)
|
||||||
|
buf = lq_current(lq);
|
||||||
|
|
||||||
|
memcpy(buf, lp, lq->element_size);
|
||||||
|
|
||||||
|
lq_commit(lq);
|
||||||
|
|
||||||
|
if (shifted) {
|
||||||
|
memset(input_line, 255, ctx->display_width / pixels_per_byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_deinit() {
|
||||||
|
rmt_pulse_deinit();
|
||||||
|
i2s_bus_deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
59
lib/libesp32_eink/epdiy/src/output_i2s/render_i2s.h
Normal file
59
lib/libesp32_eink/epdiy/src/output_i2s/render_i2s.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <esp_assert.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <hal/gpio_ll.h>
|
||||||
|
#include <soc/gpio_struct.h>
|
||||||
|
|
||||||
|
#include "../output_common/render_context.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lighten / darken picels using the I2S driving method.
|
||||||
|
*/
|
||||||
|
void epd_push_pixels_i2s(RenderContext_t* ctx, EpdRect area, short time, int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a full update cycle with a configured context.
|
||||||
|
*/
|
||||||
|
void i2s_do_update(RenderContext_t* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker to fetch framebuffer data and write into a queue for processing.
|
||||||
|
*/
|
||||||
|
void i2s_fetch_frame_data(RenderContext_t* ctx, int thread_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker to output frame data to the display.
|
||||||
|
*/
|
||||||
|
void i2s_output_frame(RenderContext_t* ctx, int thread_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deinitialize the I2S peripheral for low power consumption.
|
||||||
|
*/
|
||||||
|
void i2s_deinit();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write bits directly using the registers in the ESP32.
|
||||||
|
* Won't work for some pins (>= 32).
|
||||||
|
*/
|
||||||
|
inline void fast_gpio_set_hi(gpio_num_t gpio_num) {
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
gpio_dev_t* device = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||||
|
device->out_w1ts = (1 << gpio_num);
|
||||||
|
#else
|
||||||
|
// not supportd on non ESP32 chips
|
||||||
|
assert(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fast_gpio_set_lo(gpio_num_t gpio_num) {
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
gpio_dev_t* device = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||||
|
device->out_w1tc = (1 << gpio_num);
|
||||||
|
#else
|
||||||
|
// not supportd on non ESP32 chips
|
||||||
|
assert(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
97
lib/libesp32_eink/epdiy/src/output_i2s/rmt_pulse.c
Normal file
97
lib/libesp32_eink/epdiy/src/output_i2s/rmt_pulse.c
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#include "../output_common/render_method.h"
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_I2S
|
||||||
|
|
||||||
|
#include "driver/rmt.h"
|
||||||
|
#include "rmt_pulse.h"
|
||||||
|
|
||||||
|
#include "soc/rmt_struct.h"
|
||||||
|
|
||||||
|
static intr_handle_t gRMT_intr_handle = NULL;
|
||||||
|
|
||||||
|
// the RMT channel configuration object
|
||||||
|
static rmt_config_t row_rmt_config;
|
||||||
|
|
||||||
|
// keep track of wether the current pulse is ongoing
|
||||||
|
volatile bool rmt_tx_done = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote peripheral interrupt. Used to signal when transmission is done.
|
||||||
|
*/
|
||||||
|
static void IRAM_ATTR rmt_interrupt_handler(void* arg) {
|
||||||
|
rmt_tx_done = true;
|
||||||
|
RMT.int_clr.val = RMT.int_st.val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The extern line is declared in esp-idf/components/driver/deprecated/rmt_legacy.c. It has access
|
||||||
|
// to RMTMEM through the rmt_private.h header which we can't access outside the sdk. Declare our own
|
||||||
|
// extern here to properly use the RMTMEM smybol defined in
|
||||||
|
// components/soc/[target]/ld/[target].peripherals.ld Also typedef the new rmt_mem_t struct to the
|
||||||
|
// old rmt_block_mem_t struct. Same data fields, different names
|
||||||
|
typedef rmt_mem_t rmt_block_mem_t;
|
||||||
|
extern rmt_block_mem_t RMTMEM;
|
||||||
|
|
||||||
|
void rmt_pulse_init(gpio_num_t pin) {
|
||||||
|
row_rmt_config.rmt_mode = RMT_MODE_TX;
|
||||||
|
// currently hardcoded: use channel 0
|
||||||
|
row_rmt_config.channel = RMT_CHANNEL_1;
|
||||||
|
|
||||||
|
row_rmt_config.gpio_num = pin;
|
||||||
|
row_rmt_config.mem_block_num = 2;
|
||||||
|
|
||||||
|
// Divide 80MHz APB Clock by 8 -> .1us resolution delay
|
||||||
|
row_rmt_config.clk_div = 8;
|
||||||
|
|
||||||
|
row_rmt_config.tx_config.loop_en = false;
|
||||||
|
row_rmt_config.tx_config.carrier_en = false;
|
||||||
|
row_rmt_config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||||
|
row_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
||||||
|
row_rmt_config.tx_config.idle_output_en = true;
|
||||||
|
|
||||||
|
esp_intr_alloc(
|
||||||
|
ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_LEVEL3, rmt_interrupt_handler, 0, &gRMT_intr_handle
|
||||||
|
);
|
||||||
|
|
||||||
|
rmt_config(&row_rmt_config);
|
||||||
|
rmt_set_tx_intr_en(row_rmt_config.channel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rmt_pulse_deinit() {
|
||||||
|
esp_intr_disable(gRMT_intr_handle);
|
||||||
|
esp_intr_free(gRMT_intr_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR pulse_ckv_ticks(uint16_t high_time_ticks, uint16_t low_time_ticks, bool wait) {
|
||||||
|
while (!rmt_tx_done) {
|
||||||
|
};
|
||||||
|
volatile rmt_item32_t* rmt_mem_ptr = &(RMTMEM.chan[row_rmt_config.channel].data32[0]);
|
||||||
|
if (high_time_ticks > 0) {
|
||||||
|
rmt_mem_ptr->level0 = 1;
|
||||||
|
rmt_mem_ptr->duration0 = high_time_ticks;
|
||||||
|
rmt_mem_ptr->level1 = 0;
|
||||||
|
rmt_mem_ptr->duration1 = low_time_ticks;
|
||||||
|
} else {
|
||||||
|
rmt_mem_ptr->level0 = 1;
|
||||||
|
rmt_mem_ptr->duration0 = low_time_ticks;
|
||||||
|
rmt_mem_ptr->level1 = 0;
|
||||||
|
rmt_mem_ptr->duration1 = 0;
|
||||||
|
}
|
||||||
|
RMTMEM.chan[row_rmt_config.channel].data32[1].val = 0;
|
||||||
|
rmt_tx_done = false;
|
||||||
|
RMT.conf_ch[row_rmt_config.channel].conf1.mem_rd_rst = 1;
|
||||||
|
RMT.conf_ch[row_rmt_config.channel].conf1.mem_owner = RMT_MEM_OWNER_TX;
|
||||||
|
RMT.conf_ch[row_rmt_config.channel].conf1.tx_start = 1;
|
||||||
|
while (wait && !rmt_tx_done) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR pulse_ckv_us(uint16_t high_time_us, uint16_t low_time_us, bool wait) {
|
||||||
|
pulse_ckv_ticks(10 * high_time_us, 10 * low_time_us, wait);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IRAM_ATTR rmt_busy() {
|
||||||
|
return !rmt_tx_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
15
lib/libesp32_eink/epdiy/src/epd_driver/rmt_pulse.h → lib/libesp32_eink/epdiy/src/output_i2s/rmt_pulse.h
Executable file → Normal file
15
lib/libesp32_eink/epdiy/src/epd_driver/rmt_pulse.h → lib/libesp32_eink/epdiy/src/output_i2s/rmt_pulse.h
Executable file → Normal file
@ -3,9 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "esp_attr.h"
|
#include "esp_attr.h"
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes RMT Channel 0 with a pin for RMT pulsing.
|
* Initializes RMT Channel 0 with a pin for RMT pulsing.
|
||||||
@ -13,6 +13,11 @@
|
|||||||
*/
|
*/
|
||||||
void rmt_pulse_init(gpio_num_t pin);
|
void rmt_pulse_init(gpio_num_t pin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the pin and RMT peripheral, frees associated resources.
|
||||||
|
*/
|
||||||
|
void rmt_pulse_deinit();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs a single pulse (high -> low) on the configured pin.
|
* Outputs a single pulse (high -> low) on the configured pin.
|
||||||
* This function will always wait for a previous call to finish.
|
* This function will always wait for a previous call to finish.
|
||||||
@ -21,12 +26,11 @@ void rmt_pulse_init(gpio_num_t pin);
|
|||||||
* @param: low_time_us Pulse low time in us.
|
* @param: low_time_us Pulse low time in us.
|
||||||
* @param: wait Block until the pulse is finished.
|
* @param: wait Block until the pulse is finished.
|
||||||
*/
|
*/
|
||||||
void IRAM_ATTR pulse_ckv_us(uint16_t high_time_us, uint16_t low_time_us,
|
void pulse_ckv_us(uint16_t high_time_us, uint16_t low_time_us, bool wait);
|
||||||
bool wait);
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the rmt is currently sending a pulse.
|
* Indicates if the rmt is currently sending a pulse.
|
||||||
*/
|
*/
|
||||||
bool IRAM_ATTR rmt_busy();
|
bool rmt_busy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs a single pulse (high -> low) on the configured pin.
|
* Outputs a single pulse (high -> low) on the configured pin.
|
||||||
@ -36,5 +40,4 @@ bool IRAM_ATTR rmt_busy();
|
|||||||
* @param: low_time_us Pulse low time in clock ticks.
|
* @param: low_time_us Pulse low time in clock ticks.
|
||||||
* @param: wait Block until the pulse is finished.
|
* @param: wait Block until the pulse is finished.
|
||||||
*/
|
*/
|
||||||
void IRAM_ATTR pulse_ckv_ticks(uint16_t high_time_us, uint16_t low_time_us,
|
void pulse_ckv_ticks(uint16_t high_time_us, uint16_t low_time_us, bool wait);
|
||||||
bool wait);
|
|
||||||
22
lib/libesp32_eink/epdiy/src/output_lcd/idf-4-backports.h
Normal file
22
lib/libesp32_eink/epdiy/src/output_lcd/idf-4-backports.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Backported functions to make the LCD-based driver compile with IDF < 5.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define RMT_BASECLK_DEFAULT RMT_BASECLK_APB
|
||||||
|
typedef int rmt_clock_source_t;
|
||||||
|
|
||||||
|
static inline void rmt_ll_enable_periph_clock(rmt_dev_t* dev, bool enable) {
|
||||||
|
dev->sys_conf.clk_en = enable; // register clock gating
|
||||||
|
dev->sys_conf.mem_clk_force_on = enable; // memory clock gating
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rmt_ll_enable_mem_access_nonfifo(rmt_dev_t* dev, bool enable) {
|
||||||
|
dev->sys_conf.apb_fifo_mask = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((always_inline)) static inline void rmt_ll_tx_fix_idle_level(
|
||||||
|
rmt_dev_t* dev, uint32_t channel, uint8_t level, bool enable
|
||||||
|
) {
|
||||||
|
dev->chnconf0[channel].idle_out_en_n = enable;
|
||||||
|
dev->chnconf0[channel].idle_out_lv_n = level;
|
||||||
|
}
|
||||||
706
lib/libesp32_eink/epdiy/src/output_lcd/lcd_driver.c
Normal file
706
lib/libesp32_eink/epdiy/src/output_lcd/lcd_driver.c
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
#include "lcd_driver.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include "../output_common/render_method.h"
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "hal/gpio_types.h"
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <esp_idf_version.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <soc/lcd_periph.h>
|
||||||
|
#include <soc/rmt_periph.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <driver/rmt_tx.h>
|
||||||
|
#include <driver/rmt_types.h>
|
||||||
|
#include <driver/rmt_types_legacy.h>
|
||||||
|
#include <esp_private/periph_ctrl.h>
|
||||||
|
#include <hal/rmt_types.h>
|
||||||
|
#include <soc/clk_tree_defs.h>
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <esp_lcd_panel_ops.h>
|
||||||
|
#include <esp_lcd_panel_rgb.h>
|
||||||
|
#include <esp_private/gdma.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <hal/dma_types.h>
|
||||||
|
#include <hal/gdma_ll.h>
|
||||||
|
#include <hal/gpio_hal.h>
|
||||||
|
#include <hal/lcd_hal.h>
|
||||||
|
#include <hal/lcd_ll.h>
|
||||||
|
#include <hal/rmt_ll.h>
|
||||||
|
#include <rom/cache.h>
|
||||||
|
#include <soc/lcd_periph.h>
|
||||||
|
#include <soc/rmt_struct.h>
|
||||||
|
|
||||||
|
#include "hal/gpio_hal.h"
|
||||||
|
|
||||||
|
gpio_hal_context_t hal = { .dev = GPIO_HAL_GET_HW(GPIO_PORT_0) };
|
||||||
|
|
||||||
|
#define TAG "epdiy"
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 2)
|
||||||
|
#define LCD_PERIPH_SIGNALS lcd_periph_signals
|
||||||
|
#else
|
||||||
|
#define LCD_PERIPH_SIGNALS lcd_periph_rgb_signals
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline int min(int x, int y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
static inline int max(int x, int y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define S3_LCD_PIN_NUM_BK_LIGHT -1
|
||||||
|
// #define S3_LCD_PIN_NUM_MODE 4
|
||||||
|
|
||||||
|
#define LINE_BATCH 1000
|
||||||
|
#define BOUNCE_BUF_LINES 4
|
||||||
|
|
||||||
|
#define RMT_CKV_CHAN RMT_CHANNEL_1
|
||||||
|
|
||||||
|
// The extern line is declared in esp-idf/components/driver/deprecated/rmt_legacy.c. It has access
|
||||||
|
// to RMTMEM through the rmt_private.h header which we can't access outside the sdk. Declare our own
|
||||||
|
// extern here to properly use the RMTMEM smybol defined in
|
||||||
|
// components/soc/[target]/ld/[target].peripherals.ld Also typedef the new rmt_mem_t struct to the
|
||||||
|
// old rmt_block_mem_t struct. Same data fields, different names
|
||||||
|
typedef rmt_mem_t rmt_block_mem_t;
|
||||||
|
extern rmt_block_mem_t RMTMEM;
|
||||||
|
|
||||||
|
// spinlock for protecting the critical section at frame start
|
||||||
|
static portMUX_TYPE frame_start_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
lcd_hal_context_t hal;
|
||||||
|
intr_handle_t vsync_intr;
|
||||||
|
intr_handle_t done_intr;
|
||||||
|
|
||||||
|
frame_done_func_t frame_done_cb;
|
||||||
|
line_cb_func_t line_source_cb;
|
||||||
|
void* line_cb_payload;
|
||||||
|
void* frame_cb_payload;
|
||||||
|
|
||||||
|
int line_length_us;
|
||||||
|
int line_cycles;
|
||||||
|
int lcd_res_h;
|
||||||
|
|
||||||
|
LcdEpdConfig_t config;
|
||||||
|
|
||||||
|
uint8_t* bounce_buffer[2];
|
||||||
|
// size of a single bounce buffer
|
||||||
|
size_t bb_size;
|
||||||
|
size_t batches;
|
||||||
|
|
||||||
|
// Number of DMA descriptors that used to carry the frame buffer
|
||||||
|
size_t num_dma_nodes;
|
||||||
|
// DMA channel handle
|
||||||
|
gdma_channel_handle_t dma_chan;
|
||||||
|
// DMA descriptors pool
|
||||||
|
dma_descriptor_t* dma_nodes;
|
||||||
|
|
||||||
|
/// The number of bytes in a horizontal display register line.
|
||||||
|
int line_bytes;
|
||||||
|
|
||||||
|
// With 8 bit bus width, we need a dummy cycle before the actual data,
|
||||||
|
// because the LCD peripheral behaves weirdly.
|
||||||
|
// Also see:
|
||||||
|
// https://blog.adafruit.com/2022/06/14/esp32uesday-hacking-the-esp32-s3-lcd-peripheral/
|
||||||
|
int dummy_bytes;
|
||||||
|
|
||||||
|
/// The number of lines of the display
|
||||||
|
int display_lines;
|
||||||
|
} s3_lcd_t;
|
||||||
|
|
||||||
|
static s3_lcd_t lcd = { 0 };
|
||||||
|
|
||||||
|
void IRAM_ATTR epd_lcd_line_source_cb(line_cb_func_t line_source, void* payload) {
|
||||||
|
lcd.line_source_cb = line_source;
|
||||||
|
lcd.line_cb_payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR epd_lcd_frame_done_cb(frame_done_func_t cb, void* payload) {
|
||||||
|
lcd.frame_done_cb = cb;
|
||||||
|
lcd.frame_cb_payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IRAM_ATTR bool fill_bounce_buffer(uint8_t* buffer) {
|
||||||
|
bool task_awoken = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < BOUNCE_BUF_LINES; i++) {
|
||||||
|
if (lcd.line_source_cb != NULL) {
|
||||||
|
// this is strange, with 16 bit need a dummy cycle. But still, the first byte in the
|
||||||
|
// FIFO is correct. So we only need a true dummy byte in the FIFO in the 8 bit
|
||||||
|
// configuration.
|
||||||
|
int buffer_offset = i * (lcd.line_bytes + lcd.dummy_bytes) + (lcd.dummy_bytes % 2);
|
||||||
|
task_awoken |= lcd.line_source_cb(lcd.line_cb_payload, &buffer[buffer_offset]);
|
||||||
|
} else {
|
||||||
|
memset(&buffer[i * lcd.line_bytes], 0x00, lcd.line_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return task_awoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void start_ckv_cycles(int cycles) {
|
||||||
|
rmt_ll_tx_enable_loop_count(&RMT, RMT_CKV_CHAN, true);
|
||||||
|
rmt_ll_tx_enable_loop_autostop(&RMT, RMT_CKV_CHAN, true);
|
||||||
|
rmt_ll_tx_set_loop_count(&RMT, RMT_CKV_CHAN, cycles);
|
||||||
|
rmt_ll_tx_reset_pointer(&RMT, RMT_CKV_CHAN);
|
||||||
|
rmt_ll_tx_start(&RMT, RMT_CKV_CHAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the RMT signal according to the timing set in the lcd object.
|
||||||
|
*/
|
||||||
|
static void ckv_rmt_build_signal() {
|
||||||
|
int low_time = (lcd.line_length_us * 10 - lcd.config.ckv_high_time);
|
||||||
|
volatile rmt_item32_t* rmt_mem_ptr = &(RMTMEM.chan[RMT_CKV_CHAN].data32[0]);
|
||||||
|
rmt_mem_ptr->duration0 = lcd.config.ckv_high_time;
|
||||||
|
rmt_mem_ptr->level0 = 1;
|
||||||
|
rmt_mem_ptr->duration1 = low_time;
|
||||||
|
rmt_mem_ptr->level1 = 0;
|
||||||
|
rmt_mem_ptr[1].val = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the RMT peripheral for use as the CKV clock.
|
||||||
|
*/
|
||||||
|
static void init_ckv_rmt() {
|
||||||
|
periph_module_reset(PERIPH_RMT_MODULE);
|
||||||
|
periph_module_enable(PERIPH_RMT_MODULE);
|
||||||
|
|
||||||
|
rmt_ll_enable_periph_clock(&RMT, true);
|
||||||
|
|
||||||
|
// Divide 80MHz APB Clock by 8 -> .1us resolution delay
|
||||||
|
// idf >= 5.0 calculates the clock divider differently
|
||||||
|
rmt_ll_set_group_clock_src(&RMT, RMT_CKV_CHAN, RMT_CLK_SRC_DEFAULT, 1, 0, 0);
|
||||||
|
rmt_ll_tx_set_channel_clock_div(&RMT, RMT_CKV_CHAN, 8);
|
||||||
|
rmt_ll_tx_set_mem_blocks(&RMT, RMT_CKV_CHAN, 2);
|
||||||
|
rmt_ll_enable_mem_access_nonfifo(&RMT, true);
|
||||||
|
rmt_ll_tx_fix_idle_level(&RMT, RMT_CKV_CHAN, RMT_IDLE_LEVEL_LOW, true);
|
||||||
|
rmt_ll_tx_enable_carrier_modulation(&RMT, RMT_CKV_CHAN, false);
|
||||||
|
|
||||||
|
rmt_ll_tx_enable_loop(&RMT, RMT_CKV_CHAN, true);
|
||||||
|
|
||||||
|
gpio_hal_func_sel(&hal, lcd.config.bus.ckv, PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(lcd.config.bus.ckv, GPIO_MODE_OUTPUT);
|
||||||
|
esp_rom_gpio_connect_out_signal(
|
||||||
|
lcd.config.bus.ckv, rmt_periph_signals.groups[0].channels[RMT_CKV_CHAN].tx_sig, false, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
ckv_rmt_build_signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the CKV RMT configuration.
|
||||||
|
*/
|
||||||
|
static void deinit_ckv_rmt() {
|
||||||
|
periph_module_reset(PERIPH_RMT_MODULE);
|
||||||
|
periph_module_disable(PERIPH_RMT_MODULE);
|
||||||
|
|
||||||
|
gpio_reset_pin(lcd.config.bus.ckv);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) IRAM_ATTR static void lcd_isr_vsync(void* args) {
|
||||||
|
bool need_yield = false;
|
||||||
|
|
||||||
|
uint32_t intr_status = lcd_ll_get_interrupt_status(lcd.hal.dev);
|
||||||
|
lcd_ll_clear_interrupt_status(lcd.hal.dev, intr_status);
|
||||||
|
|
||||||
|
if (intr_status & LCD_LL_EVENT_VSYNC_END) {
|
||||||
|
int batches_needed = lcd.display_lines / LINE_BATCH;
|
||||||
|
if (lcd.batches >= batches_needed) {
|
||||||
|
lcd_ll_stop(lcd.hal.dev);
|
||||||
|
if (lcd.frame_done_cb != NULL) {
|
||||||
|
(*lcd.frame_done_cb)(lcd.frame_cb_payload);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int ckv_cycles = 0;
|
||||||
|
// last batch
|
||||||
|
if (lcd.batches == batches_needed - 1) {
|
||||||
|
lcd_ll_enable_auto_next_frame(lcd.hal.dev, false);
|
||||||
|
lcd_ll_set_vertical_timing(lcd.hal.dev, 1, 0, lcd.display_lines % LINE_BATCH, 10);
|
||||||
|
ckv_cycles = lcd.display_lines % LINE_BATCH + 10;
|
||||||
|
} else {
|
||||||
|
lcd_ll_set_vertical_timing(lcd.hal.dev, 1, 0, LINE_BATCH, 1);
|
||||||
|
ckv_cycles = LINE_BATCH + 1;
|
||||||
|
}
|
||||||
|
// apparently, this is needed for the new timing to take effect.
|
||||||
|
lcd_ll_start(lcd.hal.dev);
|
||||||
|
|
||||||
|
// skip the LCD front porch line, which is not actual data
|
||||||
|
esp_rom_delay_us(lcd.line_length_us);
|
||||||
|
start_ckv_cycles(ckv_cycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
lcd.batches += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_yield) {
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ISR handling bounce buffer refill
|
||||||
|
static IRAM_ATTR bool lcd_rgb_panel_eof_handler(
|
||||||
|
gdma_channel_handle_t dma_chan, gdma_event_data_t* event_data, void* user_data
|
||||||
|
) {
|
||||||
|
dma_descriptor_t* desc = (dma_descriptor_t*)event_data->tx_eof_desc_addr;
|
||||||
|
// Figure out which bounce buffer to write to.
|
||||||
|
// Note: what we receive is the *last* descriptor of this bounce buffer.
|
||||||
|
int bb = (desc == &lcd.dma_nodes[0]) ? 0 : 1;
|
||||||
|
return fill_bounce_buffer(lcd.bounce_buffer[bb]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t init_dma_trans_link() {
|
||||||
|
lcd.dma_nodes[0].dw0.suc_eof = 1;
|
||||||
|
lcd.dma_nodes[0].dw0.size = lcd.bb_size;
|
||||||
|
lcd.dma_nodes[0].dw0.length = lcd.bb_size;
|
||||||
|
lcd.dma_nodes[0].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
|
||||||
|
lcd.dma_nodes[0].buffer = lcd.bounce_buffer[0];
|
||||||
|
|
||||||
|
lcd.dma_nodes[1].dw0.suc_eof = 1;
|
||||||
|
lcd.dma_nodes[1].dw0.size = lcd.bb_size;
|
||||||
|
lcd.dma_nodes[1].dw0.length = lcd.bb_size;
|
||||||
|
lcd.dma_nodes[1].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
|
||||||
|
lcd.dma_nodes[1].buffer = lcd.bounce_buffer[1];
|
||||||
|
|
||||||
|
// loop end back to start
|
||||||
|
lcd.dma_nodes[0].next = &lcd.dma_nodes[1];
|
||||||
|
lcd.dma_nodes[1].next = &lcd.dma_nodes[0];
|
||||||
|
|
||||||
|
// alloc DMA channel and connect to LCD peripheral
|
||||||
|
gdma_channel_alloc_config_t dma_chan_config = {
|
||||||
|
.direction = GDMA_CHANNEL_DIRECTION_TX,
|
||||||
|
};
|
||||||
|
ESP_RETURN_ON_ERROR(
|
||||||
|
gdma_new_channel(&dma_chan_config, &lcd.dma_chan), TAG, "alloc DMA channel failed"
|
||||||
|
);
|
||||||
|
gdma_trigger_t trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0);
|
||||||
|
ESP_RETURN_ON_ERROR(gdma_connect(lcd.dma_chan, trigger), TAG, "dma connect error");
|
||||||
|
gdma_transfer_ability_t ability = {
|
||||||
|
.psram_trans_align = 64,
|
||||||
|
.sram_trans_align = 4,
|
||||||
|
};
|
||||||
|
ESP_RETURN_ON_ERROR(gdma_set_transfer_ability(lcd.dma_chan, &ability), TAG, "dma setup error");
|
||||||
|
|
||||||
|
gdma_tx_event_callbacks_t cbs = {
|
||||||
|
.on_trans_eof = lcd_rgb_panel_eof_handler,
|
||||||
|
};
|
||||||
|
ESP_RETURN_ON_ERROR(
|
||||||
|
gdma_register_tx_event_callbacks(lcd.dma_chan, &cbs, NULL), TAG, "dma setup error"
|
||||||
|
);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deinit_dma_trans_link() {
|
||||||
|
gdma_reset(lcd.dma_chan);
|
||||||
|
gdma_disconnect(lcd.dma_chan);
|
||||||
|
gdma_del_channel(lcd.dma_chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure LCD peripheral and auxiliary GPIOs
|
||||||
|
*/
|
||||||
|
static esp_err_t init_bus_gpio() {
|
||||||
|
const int DATA_LINES[16] = {
|
||||||
|
lcd.config.bus.data[14], lcd.config.bus.data[15], lcd.config.bus.data[12],
|
||||||
|
lcd.config.bus.data[13], lcd.config.bus.data[10], lcd.config.bus.data[11],
|
||||||
|
lcd.config.bus.data[8], lcd.config.bus.data[9], lcd.config.bus.data[6],
|
||||||
|
lcd.config.bus.data[7], lcd.config.bus.data[4], lcd.config.bus.data[5],
|
||||||
|
lcd.config.bus.data[2], lcd.config.bus.data[3], lcd.config.bus.data[0],
|
||||||
|
lcd.config.bus.data[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
// connect peripheral signals via GPIO matrix
|
||||||
|
for (size_t i = (16 - lcd.config.bus_width); i < 16; i++) {
|
||||||
|
gpio_hal_func_sel(&hal, DATA_LINES[i], PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(DATA_LINES[i], GPIO_MODE_OUTPUT);
|
||||||
|
esp_rom_gpio_connect_out_signal(
|
||||||
|
DATA_LINES[i], LCD_PERIPH_SIGNALS.panels[0].data_sigs[i], false, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
gpio_hal_func_sel(&hal, lcd.config.bus.leh, PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(lcd.config.bus.leh, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_hal_func_sel(&hal, lcd.config.bus.clock, PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(lcd.config.bus.clock, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_hal_func_sel(&hal, lcd.config.bus.start_pulse, PIN_FUNC_GPIO);
|
||||||
|
gpio_set_direction(lcd.config.bus.start_pulse, GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
esp_rom_gpio_connect_out_signal(
|
||||||
|
lcd.config.bus.leh, LCD_PERIPH_SIGNALS.panels[0].hsync_sig, false, false
|
||||||
|
);
|
||||||
|
esp_rom_gpio_connect_out_signal(
|
||||||
|
lcd.config.bus.clock, LCD_PERIPH_SIGNALS.panels[0].pclk_sig, false, false
|
||||||
|
);
|
||||||
|
esp_rom_gpio_connect_out_signal(
|
||||||
|
lcd.config.bus.start_pulse, LCD_PERIPH_SIGNALS.panels[0].de_sig, false, false
|
||||||
|
);
|
||||||
|
|
||||||
|
gpio_config_t vsync_gpio_conf = {
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pin_bit_mask = 1ull << lcd.config.bus.stv,
|
||||||
|
};
|
||||||
|
gpio_config(&vsync_gpio_conf);
|
||||||
|
gpio_set_level(lcd.config.bus.stv, 1);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset bus GPIO pin functions.
|
||||||
|
*/
|
||||||
|
static void deinit_bus_gpio() {
|
||||||
|
for (size_t i = (16 - lcd.config.bus_width); i < 16; i++) {
|
||||||
|
gpio_reset_pin(lcd.config.bus.data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_reset_pin(lcd.config.bus.leh);
|
||||||
|
gpio_reset_pin(lcd.config.bus.clock);
|
||||||
|
gpio_reset_pin(lcd.config.bus.start_pulse);
|
||||||
|
gpio_reset_pin(lcd.config.bus.stv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the PSRAM cache is properly configured.
|
||||||
|
*/
|
||||||
|
static void check_cache_configuration() {
|
||||||
|
if (CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE < 64) {
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"cache line size is set to %d (< 64B)! This will degrade performance, please update "
|
||||||
|
"this option in menuconfig.",
|
||||||
|
CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE
|
||||||
|
);
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"If you are on arduino, you can't set this option yourself, you'll need to use a lower "
|
||||||
|
"speed."
|
||||||
|
);
|
||||||
|
ESP_LOGE(
|
||||||
|
"epdiy",
|
||||||
|
"Reducing the pixel clock from %d MHz to %d MHz for now!",
|
||||||
|
lcd.config.pixel_clock / 1000 / 1000,
|
||||||
|
lcd.config.pixel_clock / 1000 / 1000 / 2
|
||||||
|
);
|
||||||
|
lcd.config.pixel_clock = lcd.config.pixel_clock / 2;
|
||||||
|
|
||||||
|
// fixme: this would be nice, but doesn't work :(
|
||||||
|
// uint32_t d_autoload = Cache_Suspend_DCache();
|
||||||
|
/// Cache_Set_DCache_Mode(CACHE_SIZE_FULL, CACHE_4WAYS_ASSOC, CACHE_LINE_SIZE_32B);
|
||||||
|
// Cache_Invalidate_DCache_All();
|
||||||
|
// Cache_Resume_DCache(d_autoload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign LCD configuration parameters from a given configuration, without allocating memory or
|
||||||
|
* touching the LCD peripheral config.
|
||||||
|
*/
|
||||||
|
static void assign_lcd_parameters_from_config(
|
||||||
|
const LcdEpdConfig_t* config, int display_width, int display_height
|
||||||
|
) {
|
||||||
|
// copy over the configuraiton object
|
||||||
|
memcpy(&lcd.config, config, sizeof(LcdEpdConfig_t));
|
||||||
|
|
||||||
|
// Make sure the bounce buffers divide the display height evenly.
|
||||||
|
lcd.display_lines = (((display_height + 7) / 8) * 8);
|
||||||
|
|
||||||
|
lcd.line_bytes = display_width / 4;
|
||||||
|
lcd.lcd_res_h = lcd.line_bytes / (lcd.config.bus_width / 8);
|
||||||
|
|
||||||
|
// With 8 bit bus width, we need a dummy cycle before the actual data,
|
||||||
|
// because the LCD peripheral behaves weirdly.
|
||||||
|
// Also see:
|
||||||
|
// https://blog.adafruit.com/2022/06/14/esp32uesday-hacking-the-esp32-s3-lcd-peripheral/
|
||||||
|
lcd.dummy_bytes = lcd.config.bus_width / 8;
|
||||||
|
|
||||||
|
// each bounce buffer holds a number of lines with data + dummy bytes each
|
||||||
|
lcd.bb_size = BOUNCE_BUF_LINES * (lcd.line_bytes + lcd.dummy_bytes);
|
||||||
|
|
||||||
|
check_cache_configuration();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "using resolution %dx%d", lcd.lcd_res_h, lcd.display_lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate buffers for LCD driver operation.
|
||||||
|
*/
|
||||||
|
static esp_err_t allocate_lcd_buffers() {
|
||||||
|
uint32_t dma_flags = MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
|
||||||
|
|
||||||
|
// allocate bounce buffers
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
lcd.bounce_buffer[i] = heap_caps_aligned_calloc(4, 1, lcd.bb_size, dma_flags);
|
||||||
|
ESP_RETURN_ON_FALSE(lcd.bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "install interrupt failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// So far, I haven't seen any displays with > 4096 pixels per line,
|
||||||
|
// so we only need one DMA node for now.
|
||||||
|
assert(lcd.bb_size < DMA_DESCRIPTOR_BUFFER_MAX_SIZE);
|
||||||
|
lcd.dma_nodes = heap_caps_calloc(1, sizeof(dma_descriptor_t) * 2, dma_flags);
|
||||||
|
ESP_RETURN_ON_FALSE(lcd.dma_nodes, ESP_ERR_NO_MEM, TAG, "no mem for dma nodes");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_lcd_buffers() {
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
uint8_t* buf = lcd.bounce_buffer[i];
|
||||||
|
if (buf != NULL) {
|
||||||
|
heap_caps_free(buf);
|
||||||
|
lcd.bounce_buffer[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lcd.dma_nodes != NULL) {
|
||||||
|
heap_caps_free(lcd.dma_nodes);
|
||||||
|
lcd.dma_nodes = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the LCD peripheral itself and install interrupts.
|
||||||
|
*/
|
||||||
|
static esp_err_t init_lcd_peripheral() {
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
// enable APB to access LCD registers
|
||||||
|
periph_module_enable(PERIPH_LCD_CAM_MODULE);
|
||||||
|
periph_module_reset(PERIPH_LCD_CAM_MODULE);
|
||||||
|
|
||||||
|
lcd_hal_init(&lcd.hal, 0);
|
||||||
|
lcd_ll_enable_clock(lcd.hal.dev, true);
|
||||||
|
lcd_ll_select_clk_src(lcd.hal.dev, LCD_CLK_SRC_PLL240M);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "set source clock failed");
|
||||||
|
|
||||||
|
// install interrupt service, (LCD peripheral shares the interrupt source with Camera by
|
||||||
|
// different mask)
|
||||||
|
int flags = ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED
|
||||||
|
| ESP_INTR_FLAG_LOWMED;
|
||||||
|
|
||||||
|
int source = LCD_PERIPH_SIGNALS.panels[0].irq_id;
|
||||||
|
uint32_t status = (uint32_t)lcd_ll_get_interrupt_status_reg(lcd.hal.dev);
|
||||||
|
ret = esp_intr_alloc_intrstatus(
|
||||||
|
source, flags, status, LCD_LL_EVENT_VSYNC_END, lcd_isr_vsync, NULL, &lcd.vsync_intr
|
||||||
|
);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "install interrupt failed");
|
||||||
|
|
||||||
|
status = (uint32_t)lcd_ll_get_interrupt_status_reg(lcd.hal.dev);
|
||||||
|
ret = esp_intr_alloc_intrstatus(
|
||||||
|
source, flags, status, LCD_LL_EVENT_TRANS_DONE, lcd_isr_vsync, NULL, &lcd.done_intr
|
||||||
|
);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "install interrupt failed");
|
||||||
|
|
||||||
|
lcd_ll_fifo_reset(lcd.hal.dev);
|
||||||
|
lcd_ll_reset(lcd.hal.dev);
|
||||||
|
|
||||||
|
// pixel clock phase and polarity
|
||||||
|
lcd_ll_set_clock_idle_level(lcd.hal.dev, false);
|
||||||
|
lcd_ll_set_pixel_clock_edge(lcd.hal.dev, false);
|
||||||
|
|
||||||
|
// enable RGB mode and set data width
|
||||||
|
lcd_ll_enable_rgb_mode(lcd.hal.dev, true);
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
lcd_ll_set_dma_read_stride(lcd.hal.dev, lcd.config.bus_width);
|
||||||
|
lcd_ll_set_data_wire_width(lcd.hal.dev, lcd.config.bus_width);
|
||||||
|
#else
|
||||||
|
lcd_ll_set_data_width(lcd.hal.dev, lcd.config.bus_width);
|
||||||
|
#endif
|
||||||
|
lcd_ll_set_phase_cycles(lcd.hal.dev, 0, (lcd.dummy_bytes > 0), 1); // enable data phase only
|
||||||
|
lcd_ll_enable_output_hsync_in_porch_region(lcd.hal.dev, false); // enable data phase only
|
||||||
|
|
||||||
|
// number of data cycles is controlled by DMA buffer size
|
||||||
|
lcd_ll_enable_output_always_on(lcd.hal.dev, false);
|
||||||
|
lcd_ll_set_idle_level(lcd.hal.dev, false, true, true);
|
||||||
|
|
||||||
|
// configure blank region timing
|
||||||
|
// RGB panel always has a front and back blank (porch region)
|
||||||
|
lcd_ll_set_blank_cycles(lcd.hal.dev, 1, 1);
|
||||||
|
|
||||||
|
// output hsync even in porch region?
|
||||||
|
lcd_ll_enable_output_hsync_in_porch_region(lcd.hal.dev, false);
|
||||||
|
// send next frame automatically in stream mode
|
||||||
|
lcd_ll_enable_auto_next_frame(lcd.hal.dev, false);
|
||||||
|
|
||||||
|
lcd_ll_enable_interrupt(lcd.hal.dev, LCD_LL_EVENT_VSYNC_END, true);
|
||||||
|
lcd_ll_enable_interrupt(lcd.hal.dev, LCD_LL_EVENT_TRANS_DONE, true);
|
||||||
|
|
||||||
|
// enable intr
|
||||||
|
esp_intr_enable(lcd.vsync_intr);
|
||||||
|
esp_intr_enable(lcd.done_intr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deinit_lcd_peripheral() {
|
||||||
|
// disable and free interrupts
|
||||||
|
esp_intr_disable(lcd.vsync_intr);
|
||||||
|
esp_intr_disable(lcd.done_intr);
|
||||||
|
esp_intr_free(lcd.vsync_intr);
|
||||||
|
esp_intr_free(lcd.done_intr);
|
||||||
|
|
||||||
|
lcd_ll_fifo_reset(lcd.hal.dev);
|
||||||
|
lcd_ll_reset(lcd.hal.dev);
|
||||||
|
|
||||||
|
periph_module_reset(PERIPH_LCD_CAM_MODULE);
|
||||||
|
periph_module_disable(PERIPH_LCD_CAM_MODULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the LCD driver for epdiy.
|
||||||
|
*/
|
||||||
|
void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height) {
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
assign_lcd_parameters_from_config(config, display_width, display_height);
|
||||||
|
|
||||||
|
check_cache_configuration();
|
||||||
|
|
||||||
|
ret = allocate_lcd_buffers();
|
||||||
|
ESP_GOTO_ON_ERROR(ret, err, TAG, "lcd buffer allocation failed");
|
||||||
|
|
||||||
|
ret = init_lcd_peripheral();
|
||||||
|
ESP_GOTO_ON_ERROR(ret, err, TAG, "lcd peripheral init failed");
|
||||||
|
|
||||||
|
ret = init_dma_trans_link();
|
||||||
|
ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
|
||||||
|
|
||||||
|
ret = init_bus_gpio();
|
||||||
|
ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed");
|
||||||
|
|
||||||
|
init_ckv_rmt();
|
||||||
|
|
||||||
|
// setup driver state
|
||||||
|
epd_lcd_set_pixel_clock_MHz(lcd.config.pixel_clock / 1000 / 1000);
|
||||||
|
epd_lcd_line_source_cb(NULL, NULL);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LCD init done.");
|
||||||
|
return;
|
||||||
|
err:
|
||||||
|
ESP_LOGE(TAG, "LCD initialization failed!");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deinitializue the LCD driver, i.e., free resources and peripherals.
|
||||||
|
*/
|
||||||
|
void epd_lcd_deinit() {
|
||||||
|
epd_lcd_line_source_cb(NULL, NULL);
|
||||||
|
|
||||||
|
deinit_bus_gpio();
|
||||||
|
deinit_lcd_peripheral();
|
||||||
|
deinit_dma_trans_link();
|
||||||
|
free_lcd_buffers();
|
||||||
|
deinit_ckv_rmt();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LCD deinitialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_lcd_set_pixel_clock_MHz(int frequency) {
|
||||||
|
lcd.config.pixel_clock = frequency * 1000 * 1000;
|
||||||
|
|
||||||
|
// set pclk
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
hal_utils_clk_div_t clk_div = {};
|
||||||
|
/**
|
||||||
|
* There was a change in the parameters of this function in this commit:
|
||||||
|
* https://github.com/espressif/esp-idf/commit/d39388fe4f4c5bfb0b52df9177307b1688f41016#diff-2df607d77e3f6e350bab8eb31cfd914500ae42744564e1640cec47006cc17a9c
|
||||||
|
* There are different builds with the same IDF minor version, some with, some without the commit.
|
||||||
|
* So we try to select the correct one by checking if the flag value is defined.
|
||||||
|
*/
|
||||||
|
#ifdef LCD_HAL_PCLK_FLAG_ALLOW_EQUAL_SYSCLK
|
||||||
|
uint32_t freq
|
||||||
|
= lcd_hal_cal_pclk_freq(&lcd.hal, 240000000, lcd.config.pixel_clock, flags, &clk_div);
|
||||||
|
#else
|
||||||
|
uint32_t freq = lcd_hal_cal_pclk_freq(&lcd.hal, 240000000, lcd.config.pixel_clock, &clk_div);
|
||||||
|
#endif
|
||||||
|
lcd_ll_set_group_clock_coeff(
|
||||||
|
&LCD_CAM, (int)clk_div.integer, (int)clk_div.denominator, (int)clk_div.numerator
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
uint32_t freq = lcd_hal_cal_pclk_freq(&lcd.hal, 240000000, lcd.config.pixel_clock, flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "pclk freq: %d Hz", freq);
|
||||||
|
lcd.line_length_us = (lcd.lcd_res_h + lcd.config.le_high_time + lcd.config.line_front_porch - 1)
|
||||||
|
* 1000000 / lcd.config.pixel_clock
|
||||||
|
+ 1;
|
||||||
|
lcd.line_cycles = lcd.line_length_us * lcd.config.pixel_clock / 1000000;
|
||||||
|
ESP_LOGI(TAG, "line width: %dus, %d cylces", lcd.line_length_us, lcd.line_cycles);
|
||||||
|
|
||||||
|
ckv_rmt_build_signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR epd_lcd_start_frame() {
|
||||||
|
int initial_lines = min(LINE_BATCH, lcd.display_lines);
|
||||||
|
|
||||||
|
// hsync: pulse with, back porch, active width, front porch
|
||||||
|
int end_line
|
||||||
|
= lcd.line_cycles - lcd.lcd_res_h - lcd.config.le_high_time - lcd.config.line_front_porch;
|
||||||
|
lcd_ll_set_horizontal_timing(
|
||||||
|
lcd.hal.dev,
|
||||||
|
lcd.config.le_high_time - (lcd.dummy_bytes > 0),
|
||||||
|
lcd.config.line_front_porch,
|
||||||
|
// a dummy byte is neeed in 8 bit mode to work around LCD peculiarities
|
||||||
|
lcd.lcd_res_h + (lcd.dummy_bytes > 0),
|
||||||
|
end_line
|
||||||
|
);
|
||||||
|
lcd_ll_set_vertical_timing(lcd.hal.dev, 1, 0, initial_lines, 1);
|
||||||
|
|
||||||
|
// generate the hsync at the very beginning of line
|
||||||
|
lcd_ll_set_hsync_position(lcd.hal.dev, 1);
|
||||||
|
|
||||||
|
// reset FIFO of DMA and LCD, incase there remains old frame data
|
||||||
|
gdma_reset(lcd.dma_chan);
|
||||||
|
lcd_ll_stop(lcd.hal.dev);
|
||||||
|
lcd_ll_fifo_reset(lcd.hal.dev);
|
||||||
|
lcd_ll_enable_auto_next_frame(lcd.hal.dev, true);
|
||||||
|
|
||||||
|
lcd.batches = 0;
|
||||||
|
fill_bounce_buffer(lcd.bounce_buffer[0]);
|
||||||
|
fill_bounce_buffer(lcd.bounce_buffer[1]);
|
||||||
|
|
||||||
|
// the start of DMA should be prior to the start of LCD engine
|
||||||
|
gdma_start(lcd.dma_chan, (intptr_t)&lcd.dma_nodes[0]);
|
||||||
|
|
||||||
|
// enter a critical section to ensure the frame start timing is correct
|
||||||
|
taskENTER_CRITICAL(&frame_start_spinlock);
|
||||||
|
|
||||||
|
// delay 1us is sufficient for DMA to pass data to LCD FIFO
|
||||||
|
// in fact, this is only needed when LCD pixel clock is set too high
|
||||||
|
gpio_set_level(lcd.config.bus.stv, 0);
|
||||||
|
// esp_rom_delay_us(1);
|
||||||
|
// for picture clarity, it seems to be important to start CKV at a "good"
|
||||||
|
// time, seemingly start or towards end of line.
|
||||||
|
start_ckv_cycles(initial_lines + 5);
|
||||||
|
esp_rom_delay_us(lcd.line_length_us);
|
||||||
|
gpio_set_level(lcd.config.bus.stv, 1);
|
||||||
|
esp_rom_delay_us(lcd.line_length_us);
|
||||||
|
esp_rom_delay_us(lcd.config.ckv_high_time / 10);
|
||||||
|
|
||||||
|
// start LCD engine
|
||||||
|
lcd_ll_start(lcd.hal.dev);
|
||||||
|
|
||||||
|
taskEXIT_CRITICAL(&frame_start_spinlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/// Dummy implementation to link on the old ESP32
|
||||||
|
void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // S3 Target
|
||||||
50
lib/libesp32_eink/epdiy/src/output_lcd/lcd_driver.h
Normal file
50
lib/libesp32_eink/epdiy/src/output_lcd/lcd_driver.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LCD bus configuration parameters.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
// GPIO numbers of the parallel bus pins.
|
||||||
|
gpio_num_t data[16];
|
||||||
|
|
||||||
|
// horizontal clock pin.
|
||||||
|
gpio_num_t clock;
|
||||||
|
// vertical clock pin
|
||||||
|
gpio_num_t ckv;
|
||||||
|
|
||||||
|
// horizontal "Start Pulse", enabling data input on the line shift register
|
||||||
|
gpio_num_t start_pulse;
|
||||||
|
// latch enable
|
||||||
|
gpio_num_t leh;
|
||||||
|
// vertical start pulse, resetting the vertical line shift register.
|
||||||
|
gpio_num_t stv;
|
||||||
|
} lcd_bus_config_t;
|
||||||
|
|
||||||
|
/// Configuration structure for the LCD-based Epd driver.
|
||||||
|
typedef struct {
|
||||||
|
// high time for CKV in 1/10us.
|
||||||
|
size_t pixel_clock; // = 12000000
|
||||||
|
int ckv_high_time; // = 70
|
||||||
|
int line_front_porch; // = 4
|
||||||
|
int le_high_time; // = 4
|
||||||
|
int bus_width; // = 16
|
||||||
|
lcd_bus_config_t bus;
|
||||||
|
} LcdEpdConfig_t;
|
||||||
|
|
||||||
|
typedef bool (*line_cb_func_t)(void*, uint8_t*);
|
||||||
|
typedef void (*frame_done_func_t)(void*);
|
||||||
|
|
||||||
|
void epd_lcd_init(const LcdEpdConfig_t* config, int display_width, int display_height);
|
||||||
|
void epd_lcd_deinit();
|
||||||
|
void epd_lcd_frame_done_cb(frame_done_func_t, void* payload);
|
||||||
|
void epd_lcd_line_source_cb(line_cb_func_t, void* payload);
|
||||||
|
void epd_lcd_start_frame();
|
||||||
|
/**
|
||||||
|
* Set the LCD pixel clock frequency in MHz.
|
||||||
|
*/
|
||||||
|
void epd_lcd_set_pixel_clock_MHz(int frequency);
|
||||||
246
lib/libesp32_eink/epdiy/src/output_lcd/render_lcd.c
Normal file
246
lib/libesp32_eink/epdiy/src/output_lcd/render_lcd.c
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../output_common/render_method.h"
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <rom/cache.h>
|
||||||
|
|
||||||
|
#include "../epd_internals.h"
|
||||||
|
#include "../output_common/line_queue.h"
|
||||||
|
#include "../output_common/lut.h"
|
||||||
|
#include "../output_common/render_context.h"
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
#include "lcd_driver.h"
|
||||||
|
#include "render_lcd.h"
|
||||||
|
|
||||||
|
// declare vector optimized line mask application.
|
||||||
|
void epd_apply_line_mask_VE(uint8_t* line, const uint8_t* mask, int mask_len);
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) static bool IRAM_ATTR
|
||||||
|
retrieve_line_isr(RenderContext_t* ctx, uint8_t* buf) {
|
||||||
|
if (ctx->lines_consumed >= ctx->lines_total) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int thread = ctx->line_threads[ctx->lines_consumed];
|
||||||
|
assert(thread < NUM_RENDER_THREADS);
|
||||||
|
|
||||||
|
LineQueue_t* lq = &ctx->line_queues[thread];
|
||||||
|
|
||||||
|
BaseType_t awoken = pdFALSE;
|
||||||
|
|
||||||
|
if (lq_read(lq, buf) != 0) {
|
||||||
|
ctx->error |= EPD_DRAW_EMPTY_LINE_QUEUE;
|
||||||
|
memset(buf, 0x00, ctx->display_width / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->lines_consumed >= ctx->display_height) {
|
||||||
|
memset(buf, 0x00, ctx->display_width / 4);
|
||||||
|
}
|
||||||
|
ctx->lines_consumed += 1;
|
||||||
|
return awoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// start the next frame in the current update cycle
|
||||||
|
static void IRAM_ATTR handle_lcd_frame_done(RenderContext_t* ctx) {
|
||||||
|
epd_lcd_frame_done_cb(NULL, NULL);
|
||||||
|
epd_lcd_line_source_cb(NULL, NULL);
|
||||||
|
|
||||||
|
BaseType_t task_awoken = pdFALSE;
|
||||||
|
xSemaphoreGiveFromISR(ctx->frame_done, &task_awoken);
|
||||||
|
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
|
||||||
|
void lcd_do_update(RenderContext_t* ctx) {
|
||||||
|
epd_set_mode(1);
|
||||||
|
|
||||||
|
for (uint8_t k = 0; k < ctx->cycle_frames; k++) {
|
||||||
|
epd_lcd_frame_done_cb((frame_done_func_t)handle_lcd_frame_done, ctx);
|
||||||
|
prepare_context_for_next_frame(ctx);
|
||||||
|
|
||||||
|
// start both feeder tasks
|
||||||
|
xTaskNotifyGive(ctx->feed_tasks[!xPortGetCoreID()]);
|
||||||
|
xTaskNotifyGive(ctx->feed_tasks[xPortGetCoreID()]);
|
||||||
|
|
||||||
|
// transmission is started in renderer threads, now wait util it's done
|
||||||
|
xSemaphoreTake(ctx->frame_done, portMAX_DELAY);
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_RENDER_THREADS; i++) {
|
||||||
|
xSemaphoreTake(ctx->feed_done_smphr[i], portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->current_frame++;
|
||||||
|
|
||||||
|
// make the watchdog happy.
|
||||||
|
vTaskDelay(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
epd_lcd_line_source_cb(NULL, NULL);
|
||||||
|
epd_lcd_frame_done_cb(NULL, NULL);
|
||||||
|
|
||||||
|
epd_set_mode(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O3"))) static bool IRAM_ATTR
|
||||||
|
push_pixels_isr(RenderContext_t* ctx, uint8_t* buf) {
|
||||||
|
// Output no-op outside of drawn area
|
||||||
|
if (ctx->lines_consumed < ctx->area.y) {
|
||||||
|
memset(buf, 0, ctx->display_width / 4);
|
||||||
|
} else if (ctx->lines_consumed >= ctx->area.y + ctx->area.height) {
|
||||||
|
memset(buf, 0, ctx->display_width / 4);
|
||||||
|
} else {
|
||||||
|
memcpy(buf, ctx->static_line_buffer, ctx->display_width / 4);
|
||||||
|
}
|
||||||
|
ctx->lines_consumed += 1;
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate the line mask for use in epd_push_pixels.
|
||||||
|
*/
|
||||||
|
static void push_pixels_populate_line(RenderContext_t* ctx, int color) {
|
||||||
|
// Select fill pattern by draw color
|
||||||
|
int fill_byte = 0;
|
||||||
|
switch (color) {
|
||||||
|
case 0:
|
||||||
|
fill_byte = DARK_BYTE;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
fill_byte = CLEAR_BYTE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fill_byte = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a line mask based on the drawn area
|
||||||
|
uint8_t* dirtyness = malloc(ctx->display_width / 2);
|
||||||
|
assert(dirtyness != NULL);
|
||||||
|
|
||||||
|
memset(dirtyness, 0, ctx->display_width / 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < ctx->display_width; i++) {
|
||||||
|
if ((i >= ctx->area.x) && (i < ctx->area.x + ctx->area.width)) {
|
||||||
|
dirtyness[i / 2] |= i % 2 ? 0xF0 : 0x0F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
epd_populate_line_mask(ctx->line_mask, dirtyness, ctx->display_width / 4);
|
||||||
|
|
||||||
|
// mask the line pattern with the populated mask
|
||||||
|
memset(ctx->static_line_buffer, fill_byte, ctx->display_width / 4);
|
||||||
|
epd_apply_line_mask(ctx->static_line_buffer, ctx->line_mask, ctx->display_width / 4);
|
||||||
|
|
||||||
|
free(dirtyness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_push_pixels_lcd(RenderContext_t* ctx, short time, int color) {
|
||||||
|
ctx->current_frame = 0;
|
||||||
|
ctx->lines_total = ctx->display_height;
|
||||||
|
ctx->lines_consumed = 0;
|
||||||
|
ctx->static_line_buffer = malloc(ctx->display_width / 4);
|
||||||
|
assert(ctx->static_line_buffer != NULL);
|
||||||
|
|
||||||
|
push_pixels_populate_line(ctx, color);
|
||||||
|
epd_lcd_frame_done_cb((frame_done_func_t)handle_lcd_frame_done, ctx);
|
||||||
|
epd_lcd_line_source_cb((line_cb_func_t)&push_pixels_isr, ctx);
|
||||||
|
|
||||||
|
epd_set_mode(1);
|
||||||
|
epd_lcd_start_frame();
|
||||||
|
xSemaphoreTake(ctx->frame_done, portMAX_DELAY);
|
||||||
|
epd_set_mode(0);
|
||||||
|
|
||||||
|
free(ctx->static_line_buffer);
|
||||||
|
ctx->static_line_buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define int_min(a, b) (((a) < (b)) ? (a) : (b))
|
||||||
|
__attribute__((optimize("O3"))) void IRAM_ATTR
|
||||||
|
lcd_calculate_frame(RenderContext_t* ctx, int thread_id) {
|
||||||
|
assert(ctx->lut_lookup_func != NULL);
|
||||||
|
uint8_t* input_line = ctx->feed_line_buffers[thread_id];
|
||||||
|
|
||||||
|
LineQueue_t* lq = &ctx->line_queues[thread_id];
|
||||||
|
int l = 0;
|
||||||
|
|
||||||
|
// if there is an error, start the frame but don't feed data.
|
||||||
|
if (ctx->error) {
|
||||||
|
memset(ctx->line_threads, 0, ctx->lines_total);
|
||||||
|
epd_lcd_line_source_cb((line_cb_func_t)&retrieve_line_isr, ctx);
|
||||||
|
epd_lcd_start_frame();
|
||||||
|
ESP_LOGW("epd_lcd", "draw frame draw initiated, but an error flag is set: %X", ctx->error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// line must be able to hold 2-pixel-per-byte or 1-pixel-per-byte data
|
||||||
|
memset(input_line, 0x00, ctx->display_width);
|
||||||
|
|
||||||
|
EpdRect area = ctx->area;
|
||||||
|
int min_y, max_y, bytes_per_line, _ppB;
|
||||||
|
const uint8_t* ptr_start;
|
||||||
|
get_buffer_params(ctx, &bytes_per_line, &ptr_start, &min_y, &max_y, &_ppB);
|
||||||
|
|
||||||
|
assert(area.width == ctx->display_width && area.x == 0 && !ctx->error);
|
||||||
|
|
||||||
|
// index of the line that triggers the frame output when processed
|
||||||
|
int trigger_line = int_min(63, max_y - min_y);
|
||||||
|
|
||||||
|
while (l = atomic_fetch_add(&ctx->lines_prepared, 1), l < ctx->lines_total) {
|
||||||
|
ctx->line_threads[l] = thread_id;
|
||||||
|
|
||||||
|
// queue is sufficiently filled to fill both bounce buffers, frame
|
||||||
|
// can begin
|
||||||
|
if (l - min_y == trigger_line) {
|
||||||
|
epd_lcd_line_source_cb((line_cb_func_t)&retrieve_line_isr, ctx);
|
||||||
|
epd_lcd_start_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l < min_y || l >= max_y
|
||||||
|
|| (ctx->drawn_lines != NULL && !ctx->drawn_lines[l - area.y])) {
|
||||||
|
uint8_t* buf = NULL;
|
||||||
|
while (buf == NULL) {
|
||||||
|
// break in case of errors
|
||||||
|
if (ctx->error & EPD_DRAW_EMPTY_LINE_QUEUE) {
|
||||||
|
printf("on err 1: %d %d\n", ctx->lines_prepared, ctx->lines_consumed);
|
||||||
|
lq_reset(lq);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
buf = lq_current(lq);
|
||||||
|
}
|
||||||
|
memset(buf, 0x00, lq->element_size);
|
||||||
|
lq_commit(lq);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* lp = (uint32_t*)input_line;
|
||||||
|
const uint8_t* ptr = ptr_start + bytes_per_line * (l - min_y);
|
||||||
|
|
||||||
|
Cache_Start_DCache_Preload((uint32_t)ptr, ctx->display_width, 0);
|
||||||
|
|
||||||
|
lp = (uint32_t*)ptr;
|
||||||
|
|
||||||
|
uint8_t* buf = NULL;
|
||||||
|
while (buf == NULL) {
|
||||||
|
// break in case of errors
|
||||||
|
if (ctx->error & EPD_DRAW_EMPTY_LINE_QUEUE) {
|
||||||
|
lq_reset(lq);
|
||||||
|
printf("on err 2: %d %d\n", ctx->lines_prepared, ctx->lines_consumed);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
buf = lq_current(lq);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->lut_lookup_func(lp, buf, ctx->conversion_lut, ctx->display_width);
|
||||||
|
|
||||||
|
// apply the line mask
|
||||||
|
epd_apply_line_mask_VE(buf, ctx->line_mask, ctx->display_width / 4);
|
||||||
|
|
||||||
|
lq_commit(lq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
19
lib/libesp32_eink/epdiy/src/output_lcd/render_lcd.h
Normal file
19
lib/libesp32_eink/epdiy/src/output_lcd/render_lcd.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../output_common/render_context.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lighten / darken picels using the LCD driving method.
|
||||||
|
*/
|
||||||
|
void epd_push_pixels_lcd(RenderContext_t* ctx, short time, int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a full update cycle with a configured context.
|
||||||
|
*/
|
||||||
|
void lcd_do_update(RenderContext_t* ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker thread for output calculation.
|
||||||
|
* In LCD mode, both threads do the same thing.
|
||||||
|
*/
|
||||||
|
void lcd_calculate_frame(RenderContext_t* ctx, int thread_id);
|
||||||
526
lib/libesp32_eink/epdiy/src/render.c
Normal file
526
lib/libesp32_eink/epdiy/src/render.c
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
#include "render.h"
|
||||||
|
|
||||||
|
#include "epd_board.h"
|
||||||
|
#include "epd_internals.h"
|
||||||
|
#include "epdiy.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
#include <esp_types.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <stdalign.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "output_common/line_queue.h"
|
||||||
|
#include "output_common/lut.h"
|
||||||
|
#include "output_common/render_context.h"
|
||||||
|
#include "output_common/render_method.h"
|
||||||
|
#include "output_i2s/render_i2s.h"
|
||||||
|
#include "output_lcd/render_lcd.h"
|
||||||
|
|
||||||
|
static inline int min(int x, int y) {
|
||||||
|
return x < y ? x : y;
|
||||||
|
}
|
||||||
|
static inline int max(int x, int y) {
|
||||||
|
return x > y ? x : y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int clear_cycle_time = 12;
|
||||||
|
|
||||||
|
#define RTOS_ERROR_CHECK(x) \
|
||||||
|
do { \
|
||||||
|
esp_err_t __err_rc = (x); \
|
||||||
|
if (__err_rc != pdPASS) { \
|
||||||
|
abort(); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static RenderContext_t render_context;
|
||||||
|
|
||||||
|
void epd_push_pixels(EpdRect area, short time, int color) {
|
||||||
|
render_context.area = area;
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
epd_push_pixels_lcd(&render_context, time, color);
|
||||||
|
#else
|
||||||
|
epd_push_pixels_i2s(&render_context, area, time, color);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////// Coordination ///////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the waveform temperature range index for a given temperature in °C.
|
||||||
|
* If no range in the waveform data fits the given temperature, return the
|
||||||
|
* closest one.
|
||||||
|
* Returns -1 if the waveform does not contain any temperature range.
|
||||||
|
*/
|
||||||
|
int waveform_temp_range_index(const EpdWaveform* waveform, int temperature) {
|
||||||
|
int idx = 0;
|
||||||
|
if (waveform->num_temp_ranges == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while (idx < waveform->num_temp_ranges - 1 && waveform->temp_intervals[idx].min < temperature) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_waveform_index(const EpdWaveform* waveform, enum EpdDrawMode mode) {
|
||||||
|
for (int i = 0; i < waveform->num_modes; i++) {
|
||||||
|
if (waveform->mode_data[i]->type == (mode & 0x3F)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////// API Procedures //////////////////////////////////
|
||||||
|
|
||||||
|
/// Rounded up display height for even division into multi-line buffers.
|
||||||
|
static inline int rounded_display_height() {
|
||||||
|
return (((epd_height() + 7) / 8) * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: fix misleading naming:
|
||||||
|
// area -> buffer dimensions
|
||||||
|
// crop -> area taken out of buffer
|
||||||
|
enum EpdDrawError IRAM_ATTR epd_draw_base(
|
||||||
|
EpdRect area,
|
||||||
|
const uint8_t* data,
|
||||||
|
EpdRect crop_to,
|
||||||
|
enum EpdDrawMode mode,
|
||||||
|
int temperature,
|
||||||
|
const bool* drawn_lines,
|
||||||
|
const uint8_t* drawn_columns,
|
||||||
|
const EpdWaveform* waveform
|
||||||
|
) {
|
||||||
|
if (waveform == NULL) {
|
||||||
|
return EPD_DRAW_NO_PHASES_AVAILABLE;
|
||||||
|
}
|
||||||
|
int waveform_range = waveform_temp_range_index(waveform, temperature);
|
||||||
|
if (waveform_range < 0) {
|
||||||
|
return EPD_DRAW_NO_PHASES_AVAILABLE;
|
||||||
|
}
|
||||||
|
int waveform_index = 0;
|
||||||
|
uint8_t frame_count = 0;
|
||||||
|
const EpdWaveformPhases* waveform_phases = NULL;
|
||||||
|
|
||||||
|
// no waveform required for monochrome mode
|
||||||
|
if (!(mode & MODE_EPDIY_MONOCHROME)) {
|
||||||
|
waveform_index = get_waveform_index(waveform, mode);
|
||||||
|
if (waveform_index < 0) {
|
||||||
|
return EPD_DRAW_MODE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
waveform_phases = waveform->mode_data[waveform_index]->range_data[waveform_range];
|
||||||
|
// FIXME: error if not present
|
||||||
|
frame_count = waveform_phases->phases;
|
||||||
|
} else {
|
||||||
|
frame_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crop_to.width < 0 || crop_to.height < 0) {
|
||||||
|
return EPD_DRAW_INVALID_CROP;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool crop = (crop_to.width > 0 && crop_to.height > 0);
|
||||||
|
if (crop
|
||||||
|
&& (crop_to.width > area.width || crop_to.height > area.height || crop_to.x > area.width
|
||||||
|
|| crop_to.y > area.height)) {
|
||||||
|
return EPD_DRAW_INVALID_CROP;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
if (mode & MODE_PACKING_1PPB_DIFFERENCE && render_context.conversion_lut_size > 1 << 10) {
|
||||||
|
ESP_LOGI(
|
||||||
|
"epdiy",
|
||||||
|
"Using optimized vector implementation on the ESP32-S3, only 1k of %d LUT in use!",
|
||||||
|
render_context.conversion_lut_size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LutFunctionPair lut_functions = find_lut_functions(mode, render_context.conversion_lut_size);
|
||||||
|
if (lut_functions.build_func == NULL || lut_functions.lookup_func == NULL) {
|
||||||
|
ESP_LOGE("epdiy", "no output lookup method found for your mode and LUT size!");
|
||||||
|
return EPD_DRAW_LOOKUP_NOT_IMPLEMENTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_context.area = area;
|
||||||
|
render_context.crop_to = crop_to;
|
||||||
|
render_context.waveform_range = waveform_range;
|
||||||
|
render_context.waveform_index = waveform_index;
|
||||||
|
render_context.mode = mode;
|
||||||
|
render_context.waveform = waveform;
|
||||||
|
render_context.error = EPD_DRAW_SUCCESS;
|
||||||
|
render_context.drawn_lines = drawn_lines;
|
||||||
|
render_context.data_ptr = data;
|
||||||
|
render_context.lut_build_func = lut_functions.build_func;
|
||||||
|
render_context.lut_lookup_func = lut_functions.lookup_func;
|
||||||
|
|
||||||
|
render_context.lines_prepared = 0;
|
||||||
|
render_context.lines_consumed = 0;
|
||||||
|
render_context.lines_total = rounded_display_height();
|
||||||
|
render_context.current_frame = 0;
|
||||||
|
render_context.cycle_frames = frame_count;
|
||||||
|
render_context.phase_times = NULL;
|
||||||
|
if (waveform_phases != NULL && waveform_phases->phase_times != NULL) {
|
||||||
|
render_context.phase_times = waveform_phases->phase_times;
|
||||||
|
}
|
||||||
|
|
||||||
|
epd_populate_line_mask(
|
||||||
|
render_context.line_mask, drawn_columns, render_context.display_width / 4
|
||||||
|
);
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_I2S
|
||||||
|
i2s_do_update(&render_context);
|
||||||
|
#elif defined(RENDER_METHOD_LCD)
|
||||||
|
lcd_do_update(&render_context);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (render_context.error & EPD_DRAW_EMPTY_LINE_QUEUE) {
|
||||||
|
ESP_LOGE("epdiy", "line buffer underrun occurred!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render_context.error != EPD_DRAW_SUCCESS) {
|
||||||
|
return render_context.error;
|
||||||
|
}
|
||||||
|
return EPD_DRAW_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR render_thread(void* arg) {
|
||||||
|
int thread_id = (int)arg;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
lcd_calculate_frame(&render_context, thread_id);
|
||||||
|
#elif defined(RENDER_METHOD_I2S)
|
||||||
|
if (thread_id == 0) {
|
||||||
|
i2s_fetch_frame_data(&render_context, thread_id);
|
||||||
|
} else {
|
||||||
|
i2s_output_frame(&render_context, thread_id);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xSemaphoreGive(render_context.feed_done_smphr[thread_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_clear_area(EpdRect area) {
|
||||||
|
epd_clear_area_cycles(area, 3, clear_cycle_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time) {
|
||||||
|
const short white_time = cycle_time;
|
||||||
|
const short dark_time = cycle_time;
|
||||||
|
|
||||||
|
for (int c = 0; c < cycles; c++) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
epd_push_pixels(area, dark_time, 0);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
epd_push_pixels(area, white_time, 1);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
epd_push_pixels(area, white_time, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_renderer_init(enum EpdInitOptions options) {
|
||||||
|
// Either the board should be set in menuconfig or the epd_set_board() must
|
||||||
|
// be called before epd_init()
|
||||||
|
assert((epd_current_board() != NULL));
|
||||||
|
|
||||||
|
epd_current_board()->init(epd_width());
|
||||||
|
epd_control_reg_init();
|
||||||
|
|
||||||
|
render_context.display_width = epd_width();
|
||||||
|
render_context.display_height = epd_height();
|
||||||
|
|
||||||
|
size_t lut_size = 0;
|
||||||
|
if (options & EPD_LUT_1K) {
|
||||||
|
lut_size = 1 << 10;
|
||||||
|
} else if (options & EPD_LUT_64K) {
|
||||||
|
lut_size = 1 << 16;
|
||||||
|
} else if (options == EPD_OPTIONS_DEFAULT) {
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
lut_size = 1 << 10;
|
||||||
|
#else
|
||||||
|
lut_size = 1 << 16;
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
ESP_LOGE("epd", "invalid init options: %d", options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI("epd", "Space used for waveform LUT: %dK", lut_size / 1024);
|
||||||
|
render_context.conversion_lut
|
||||||
|
= (uint8_t*)heap_caps_malloc(lut_size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
if (render_context.conversion_lut == NULL) {
|
||||||
|
ESP_LOGE("epd", "could not allocate LUT!");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
render_context.conversion_lut_size = lut_size;
|
||||||
|
render_context.static_line_buffer = NULL;
|
||||||
|
|
||||||
|
render_context.frame_done = xSemaphoreCreateBinary();
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_RENDER_THREADS; i++) {
|
||||||
|
render_context.feed_done_smphr[i] = xSemaphoreCreateBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When using the LCD peripheral, we may need padding lines to
|
||||||
|
// satisfy the bounce buffer size requirements
|
||||||
|
render_context.line_threads = (uint8_t*)heap_caps_malloc(
|
||||||
|
rounded_display_height(), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL
|
||||||
|
);
|
||||||
|
|
||||||
|
int queue_len = 32;
|
||||||
|
if (options & EPD_FEED_QUEUE_32) {
|
||||||
|
queue_len = 32;
|
||||||
|
} else if (options & EPD_FEED_QUEUE_8) {
|
||||||
|
queue_len = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render_context.conversion_lut == NULL) {
|
||||||
|
ESP_LOGE("epd", "could not allocate line mask!");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
render_context.line_mask
|
||||||
|
= heap_caps_aligned_alloc(16, epd_width() / 4, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
||||||
|
assert(render_context.line_mask != NULL);
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
size_t queue_elem_size = render_context.display_width / 4;
|
||||||
|
#elif defined(RENDER_METHOD_I2S)
|
||||||
|
size_t queue_elem_size = render_context.display_width;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_RENDER_THREADS; i++) {
|
||||||
|
render_context.line_queues[i] = lq_init(queue_len, queue_elem_size);
|
||||||
|
render_context.feed_line_buffers[i] = (uint8_t*)heap_caps_malloc(
|
||||||
|
render_context.display_width, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL
|
||||||
|
);
|
||||||
|
assert(render_context.feed_line_buffers[i] != NULL);
|
||||||
|
RTOS_ERROR_CHECK(xTaskCreatePinnedToCore(
|
||||||
|
render_thread,
|
||||||
|
"epd_prep",
|
||||||
|
1 << 12,
|
||||||
|
(void*)i,
|
||||||
|
configMAX_PRIORITIES - 1,
|
||||||
|
&render_context.feed_tasks[i],
|
||||||
|
i
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void epd_renderer_deinit() {
|
||||||
|
const EpdBoardDefinition* epd_board = epd_current_board();
|
||||||
|
|
||||||
|
epd_board->poweroff(epd_ctrl_state());
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_RENDER_THREADS; i++) {
|
||||||
|
vTaskDelete(render_context.feed_tasks[i]);
|
||||||
|
lq_free(&render_context.line_queues[i]);
|
||||||
|
heap_caps_free(render_context.feed_line_buffers[i]);
|
||||||
|
vSemaphoreDelete(render_context.feed_done_smphr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_I2S
|
||||||
|
i2s_deinit();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
epd_control_reg_deinit();
|
||||||
|
|
||||||
|
if (epd_board->deinit) {
|
||||||
|
epd_board->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_caps_free(render_context.conversion_lut);
|
||||||
|
heap_caps_free(render_context.line_threads);
|
||||||
|
heap_caps_free(render_context.line_mask);
|
||||||
|
vSemaphoreDelete(render_context.frame_done);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RENDER_METHOD_LCD
|
||||||
|
uint32_t epd_interlace_4bpp_line_VE(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
uint8_t* col_dirtyness,
|
||||||
|
int fb_width
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interlaces `len` nibbles from the buffers `to` and `from` into `interlaced`.
|
||||||
|
* In the process, tracks which nibbles differ in `col_dirtyness`.
|
||||||
|
* Returns `1` if there are differences, `0` otherwise.
|
||||||
|
* Does not require special alignment of the buffers beyond 32 bit alignment.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) static inline int _interlace_line_unaligned(
|
||||||
|
const uint8_t* to, const uint8_t* from, uint8_t* interlaced, uint8_t* col_dirtyness, int len
|
||||||
|
) {
|
||||||
|
int dirty = 0;
|
||||||
|
for (int x = 0; x < len; x++) {
|
||||||
|
uint8_t t = *(to + x / 2);
|
||||||
|
uint8_t f = *(from + x / 2);
|
||||||
|
t = (x % 2) ? (t >> 4) : (t & 0x0f);
|
||||||
|
f = (x % 2) ? (f >> 4) : (f & 0x0f);
|
||||||
|
col_dirtyness[x / 2] |= (t ^ f) << (4 * (x % 2));
|
||||||
|
dirty |= (t ^ f);
|
||||||
|
interlaced[x] = (t << 4) | f;
|
||||||
|
}
|
||||||
|
return dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interlaces the lines at `to`, `from` into `interlaced`.
|
||||||
|
* returns `1` if there are differences, `0` otherwise.
|
||||||
|
*/
|
||||||
|
__attribute__((optimize("O3"))) bool _epd_interlace_line(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
uint8_t* col_dirtyness,
|
||||||
|
int fb_width
|
||||||
|
) {
|
||||||
|
#ifdef RENDER_METHOD_I2S
|
||||||
|
return _interlace_line_unaligned(to, from, interlaced, col_dirtyness, fb_width) > 0;
|
||||||
|
#elif defined(RENDER_METHOD_LCD)
|
||||||
|
// Use Vector Extensions with the ESP32-S3.
|
||||||
|
// Both input buffers should have the same alignment w.r.t. 16 bytes,
|
||||||
|
// as asserted in epd_difference_image_base.
|
||||||
|
uint32_t dirty = 0;
|
||||||
|
|
||||||
|
// alignment boundaries in pixels
|
||||||
|
int unaligned_len_front_px = ((16 - (uint32_t)to % 16) * 2) % 32;
|
||||||
|
int unaligned_len_back_px = (((uint32_t)to + fb_width / 2) % 16) * 2;
|
||||||
|
int unaligned_back_start_px = fb_width - unaligned_len_back_px;
|
||||||
|
int aligned_len_px = fb_width - unaligned_len_front_px - unaligned_len_back_px;
|
||||||
|
|
||||||
|
dirty |= _interlace_line_unaligned(to, from, interlaced, col_dirtyness, unaligned_len_front_px);
|
||||||
|
dirty |= epd_interlace_4bpp_line_VE(
|
||||||
|
to + unaligned_len_front_px / 2,
|
||||||
|
from + unaligned_len_front_px / 2,
|
||||||
|
interlaced + unaligned_len_front_px,
|
||||||
|
col_dirtyness + unaligned_len_front_px / 2,
|
||||||
|
aligned_len_px
|
||||||
|
);
|
||||||
|
dirty |= _interlace_line_unaligned(
|
||||||
|
to + unaligned_back_start_px / 2,
|
||||||
|
from + unaligned_back_start_px / 2,
|
||||||
|
interlaced + unaligned_back_start_px,
|
||||||
|
col_dirtyness + unaligned_back_start_px / 2,
|
||||||
|
unaligned_len_back_px
|
||||||
|
);
|
||||||
|
return dirty;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect epd_difference_image_base(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
EpdRect crop_to,
|
||||||
|
int fb_width,
|
||||||
|
int fb_height,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
bool* dirty_lines,
|
||||||
|
uint8_t* col_dirtyness
|
||||||
|
) {
|
||||||
|
assert(fb_width % 8 == 0);
|
||||||
|
assert(col_dirtyness != NULL);
|
||||||
|
|
||||||
|
// these buffers should be allocated 16 byte aligned
|
||||||
|
assert((uint32_t)to % 16 == 0);
|
||||||
|
assert((uint32_t)from % 16 == 0);
|
||||||
|
assert((uint32_t)col_dirtyness % 16 == 0);
|
||||||
|
assert((uint32_t)interlaced % 16 == 0);
|
||||||
|
|
||||||
|
memset(col_dirtyness, 0, fb_width / 2);
|
||||||
|
memset(dirty_lines, 0, sizeof(bool) * fb_height);
|
||||||
|
|
||||||
|
int x_end = min(fb_width, crop_to.x + crop_to.width);
|
||||||
|
int y_end = min(fb_height, crop_to.y + crop_to.height);
|
||||||
|
|
||||||
|
for (int y = crop_to.y; y < y_end; y++) {
|
||||||
|
uint32_t offset = y * fb_width / 2;
|
||||||
|
int dirty = _epd_interlace_line(
|
||||||
|
to + offset, from + offset, interlaced + offset * 2, col_dirtyness, fb_width
|
||||||
|
);
|
||||||
|
dirty_lines[y] = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min_x, min_y, max_x, max_y;
|
||||||
|
for (min_x = crop_to.x; min_x < x_end; min_x++) {
|
||||||
|
uint8_t mask = min_x % 2 ? 0xF0 : 0x0F;
|
||||||
|
if ((col_dirtyness[min_x / 2] & mask) != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (max_x = x_end - 1; max_x >= crop_to.x; max_x--) {
|
||||||
|
uint8_t mask = min_x % 2 ? 0xF0 : 0x0F;
|
||||||
|
if ((col_dirtyness[max_x / 2] & mask) != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (min_y = crop_to.y; min_y < y_end; min_y++) {
|
||||||
|
if (dirty_lines[min_y] != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (max_y = y_end - 1; max_y >= crop_to.y; max_y--) {
|
||||||
|
if (dirty_lines[max_y] != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect crop_rect = {
|
||||||
|
.x = min_x,
|
||||||
|
.y = min_y,
|
||||||
|
.width = max(max_x - min_x + 1, 0),
|
||||||
|
.height = max(max_y - min_y + 1, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
return crop_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect epd_difference_image(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
bool* dirty_lines,
|
||||||
|
uint8_t* col_dirtyness
|
||||||
|
) {
|
||||||
|
return epd_difference_image_base(
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
epd_full_screen(),
|
||||||
|
epd_width(),
|
||||||
|
epd_height(),
|
||||||
|
interlaced,
|
||||||
|
dirty_lines,
|
||||||
|
col_dirtyness
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EpdRect epd_difference_image_cropped(
|
||||||
|
const uint8_t* to,
|
||||||
|
const uint8_t* from,
|
||||||
|
EpdRect crop_to,
|
||||||
|
uint8_t* interlaced,
|
||||||
|
bool* dirty_lines,
|
||||||
|
uint8_t* col_dirtyness
|
||||||
|
) {
|
||||||
|
EpdRect result = epd_difference_image_base(
|
||||||
|
to, from, crop_to, epd_width(), epd_height(), interlaced, dirty_lines, col_dirtyness
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
12
lib/libesp32_eink/epdiy/src/render.h
Normal file
12
lib/libesp32_eink/epdiy/src/render.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "epdiy.h"
|
||||||
|
/**
|
||||||
|
* Initialize the EPD renderer and its render context.
|
||||||
|
*/
|
||||||
|
void epd_renderer_init(enum EpdInitOptions options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deinitialize the EPD renderer and free up its resources.
|
||||||
|
*/
|
||||||
|
void epd_renderer_deinit();
|
||||||
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED047TC1.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED047TC1.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED047TC1.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED047TC1.h
Executable file → Normal file
File diff suppressed because one or more lines are too long
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED060SC4.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED060SC4.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED060SC4.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED060SC4.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED060SCT.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED060SCT.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED060SCT.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED060SCT.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED060XC3.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED060XC3.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED060XC3.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED060XC3.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED097OC4.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED097OC4.h
Executable file → Normal file
0
lib/libesp32_eink/epdiy/src/epd_driver/waveforms/epdiy_ED097OC4.h → lib/libesp32_eink/epdiy/src/waveforms/epdiy_ED097OC4.h
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user