diff --git a/Devices/lilygo-t5-epd47-s3/CMakeLists.txt b/Devices/lilygo-t5-epd47-s3/CMakeLists.txt new file mode 100644 index 000000000..09a2fdee3 --- /dev/null +++ b/Devices/lilygo-t5-epd47-s3/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "source" + REQUIRES TactilityKernel +) diff --git a/Devices/lilygo-t5-epd47-s3/LICENSE-Apache-2.0.md b/Devices/lilygo-t5-epd47-s3/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Devices/lilygo-t5-epd47-s3/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Devices/lilygo-t5-epd47-s3/device.properties b/Devices/lilygo-t5-epd47-s3/device.properties new file mode 100644 index 000000000..f518283d2 --- /dev/null +++ b/Devices/lilygo-t5-epd47-s3/device.properties @@ -0,0 +1,26 @@ +general.vendor=LilyGO +general.name=T5 4.7 Inch E-Paper S3 +general.incubating=true + +apps.launcherAppId=tactility.launcher + +hardware.target=ESP32S3 +hardware.flashSize=16MB +hardware.spiRam=true +hardware.spiRamMode=OCT +hardware.spiRamSpeed=80M +hardware.esptoolFlashFreq=80M +hardware.bluetooth=true + +storage.userDataLocation=Internal + +display.size=4.7" +display.shape=rectangle +display.dpi=235 + +lvgl.colorDepth=8 +lvgl.fontSize=24 +lvgl.theme=Mono +lvgl.statusbarColorsInverted=true + +sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0 diff --git a/Devices/lilygo-t5-epd47-s3/lilygo,t5-epd47-s3.dts b/Devices/lilygo-t5-epd47-s3/lilygo,t5-epd47-s3.dts new file mode 100644 index 000000000..c38777838 --- /dev/null +++ b/Devices/lilygo-t5-epd47-s3/lilygo,t5-epd47-s3.dts @@ -0,0 +1,92 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/ { + compatible = "root"; + model = "LILYGO T5 4.7 Inch E-Paper S3"; + + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + }; + + ble0 { + compatible = "espressif,esp32-ble"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + // Listed before the I2C bus on purpose, the touch controller was only tested with the display started first + display { + compatible = "lilygo,t5s3-display"; + }; + + i2c_internal { + compatible = "espressif,esp32-i2c-master"; + port = ; + clock-frequency = <400000>; + // The board relies on the internal pull-ups, as the Arduino Wire library enables them + pin-sda = <&gpio0 18 GPIO_FLAG_PULL_UP>; + pin-scl = <&gpio0 17 GPIO_FLAG_PULL_UP>; + + // PCF8563, register-compatible with the BM8563 + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; + + // The controller reports 540x960 (portrait) and the panel runs in landscape + touch { + compatible = "goodix,gt911"; + reg = <0x5D>; + x-max = <540>; + y-max = <960>; + swap-xy; + mirror-x; + pin-interrupt = <&gpio0 47 GPIO_FLAG_NONE>; + }; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + cs-gpios = <&gpio0 42 GPIO_FLAG_NONE>; + pin-mosi = <&gpio0 15 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 16 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>; + max-transfer-size = <4096>; + + sdcard@0 { + compatible = "espressif,esp32-sdspi"; + frequency-khz = <20000>; + }; + }; + + // Battery voltage behind a 1:2 divider on GPIO14 (ADC2 channel 3), not yet calibrated + adc0 { + compatible = "espressif,esp32-adc-oneshot"; + unit-id = ; + channels = ; + }; + + battery-sense { + compatible = "battery-sense"; + io-channel = <&adc0 0>; + reference-voltage-mv = <3300>; + multiplier = <2000>; + }; +}; diff --git a/Devices/lilygo-t5-epd47-s3/module.yaml b/Devices/lilygo-t5-epd47-s3/module.yaml new file mode 100644 index 000000000..e36a197a0 --- /dev/null +++ b/Devices/lilygo-t5-epd47-s3/module.yaml @@ -0,0 +1,6 @@ +dependencies: +- Platforms/platform-esp32 +- Drivers/bm8563-module +- Drivers/gt911-module +- Drivers/lilygo-t5s3-epd-module +dts: lilygo,t5-epd47-s3.dts diff --git a/Devices/lilygo-t5-epd47-s3/source/module.cpp b/Devices/lilygo-t5-epd47-s3/source/module.cpp new file mode 100644 index 000000000..f7e3dc7f4 --- /dev/null +++ b/Devices/lilygo-t5-epd47-s3/source/module.cpp @@ -0,0 +1,14 @@ +#include + +extern "C" { + +Module lilygo_t5_epd47_s3_module = { + .name = "lilygo-t5-epd47-s3", + .start = nullptr, + .stop = nullptr, + .drivers = nullptr, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Drivers/lilygo-t5s3-epd-module/CMakeLists.txt b/Drivers/lilygo-t5s3-epd-module/CMakeLists.txt new file mode 100644 index 000000000..14415ac4b --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(lilygo-t5s3-epd-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel esp_lcd esp_driver_gpio esp_driver_rmt esp_timer esp_rom hal soc heap freertos +) + +# The pixel loops of the display are too slow with the debug optimization level of the project +set_source_files_properties( + source/t5s3_epd.cpp + source/t5s3_display.cpp + PROPERTIES COMPILE_OPTIONS -O2 +) diff --git a/Drivers/lilygo-t5s3-epd-module/LICENSE-Apache-2.0.md b/Drivers/lilygo-t5s3-epd-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/lilygo-t5s3-epd-module/LICENSE-LGPL-3.0.md b/Drivers/lilygo-t5s3-epd-module/LICENSE-LGPL-3.0.md new file mode 100644 index 000000000..0a041280b --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/LICENSE-LGPL-3.0.md @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. diff --git a/Drivers/lilygo-t5s3-epd-module/README.md b/Drivers/lilygo-t5s3-epd-module/README.md new file mode 100644 index 000000000..ad0f1239c --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/README.md @@ -0,0 +1,28 @@ +# lilygo-t5s3-epd-module + +A kernel driver for the 4.7" 960x540 `ED047TC1` e-paper panel of the LILYGO T5 4.7 Inch E-Paper S3 (V2.3/V2.4). + +The driver only works on this board. The pins of the panel, the 74HCT4094 shift register that carries the panel supplies and the sequence of the panel are fixed in the code, so the devicetree node only has the pixel clock as property. + +- `t5s3_display.cpp`: the Tactility display driver. It decides between fast and quality updates. +- `t5s3_epd.cpp`: the engine. It controls the shift register, the CKV gate clock (RMT peripheral), the row transfer (LCD peripheral in i80 mode), the update loop and the clear sequence. +- `t5s3_epd_rows.h`: the conversion of framebuffer rows into bus data. It has no hardware access. +- `t5s3_epd_waveform.h`: the 30 phase drive table of the quality updates. + +There are three update modes. Fast draws black and white only. Quality draws 16 levels and leaves white pixels that stay white alone, which is used for partial updates. Full also flashes those white pixels, which is used for the clear and the refresh. + +## License + +The engine and its tables are derived from [epdiy](https://github.com/vroland/epdiy) and are licensed under the +[LGPL v3.0 or later](LICENSE-LGPL-3.0.md) (the LGPL supplements the [GPL v3.0](../../Documentation/LICENSE-GPL-3.0.md)): + +- `source/t5s3_epd.cpp` +- `source/t5s3_epd.h` +- `source/t5s3_epd_rows.h` +- `source/t5s3_epd_waveform.h` + +These files follow the sequences and timings of epdiy and contain its ED047TC1 waveform data. Each of them has an `SPDX-License-Identifier: LGPL-3.0-or-later` header. + +The rest of the module (`source/t5s3_display.cpp`, `source/module.cpp`, the bindings and the headers in `include/`) is licensed under [Apache License v2.0](LICENSE-Apache-2.0.md). + +A project that contains this module has to follow the LGPL for the four files above. diff --git a/Drivers/lilygo-t5s3-epd-module/bindings/lilygo,t5s3-display.yaml b/Drivers/lilygo-t5s3-epd-module/bindings/lilygo,t5s3-display.yaml new file mode 100644 index 000000000..47f6ac120 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/bindings/lilygo,t5s3-display.yaml @@ -0,0 +1,11 @@ +description: > + LILYGO T5 4.7 Inch E-Paper S3 (V2.3/V2.4) display with an ED047TC1 panel. The pins of the panel + are fixed in the driver, so this node only has the pixel clock as property. + +compatible: "lilygo,t5s3-display" + +properties: + pixel-clock-hz: + type: int + default: 10000000 + description: Clock of the bus that sends the pixel data of a row. Faster speeds up updates if the panel accepts it. diff --git a/Drivers/lilygo-t5s3-epd-module/include/bindings/t5s3_display.h b/Drivers/lilygo-t5s3-epd-module/include/bindings/t5s3_display.h new file mode 100644 index 000000000..cd708f83e --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/include/bindings/t5s3_display.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +DEFINE_DEVICETREE(t5s3_display, struct T5s3DisplayConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/lilygo-t5s3-epd-module/include/drivers/t5s3_display.h b/Drivers/lilygo-t5s3-epd-module/include/drivers/t5s3_display.h new file mode 100644 index 000000000..97eaaefc6 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/include/drivers/t5s3_display.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct T5s3DisplayConfig { + uint32_t pixel_clock_hz; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/lilygo-t5s3-epd-module/include/lilygo_t5s3_epd_module.h b/Drivers/lilygo-t5s3-epd-module/include/lilygo_t5s3_epd_module.h new file mode 100644 index 000000000..c50e7ec87 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/include/lilygo_t5s3_epd_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module lilygo_t5s3_epd_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/lilygo-t5s3-epd-module/module.yaml b/Drivers/lilygo-t5s3-epd-module/module.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/module.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/lilygo-t5s3-epd-module/source/module.cpp b/Drivers/lilygo-t5s3-epd-module/source/module.cpp new file mode 100644 index 000000000..ac1344199 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/source/module.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern "C" { + +extern Driver t5s3_display_driver; + +static Driver* const lilygo_t5s3_epd_drivers[] = { + &t5s3_display_driver, + nullptr +}; + +Module lilygo_t5s3_epd_module = { + .name = "lilygo-t5s3-epd", + .start = nullptr, + .stop = nullptr, + .drivers = lilygo_t5s3_epd_drivers, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Drivers/lilygo-t5s3-epd-module/source/t5s3_display.cpp b/Drivers/lilygo-t5s3-epd-module/source/t5s3_display.cpp new file mode 100644 index 000000000..0d9b27572 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/source/t5s3_display.cpp @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "t5s3_epd.h" + +#include +#include + +#define TAG "T5s3Display" +#define GET_CONFIG(device) (static_cast((device)->config)) + +// An update covering at least this fraction of the panel is a full-screen change and gets a quality refresh +static constexpr float FULL_AREA_QUALITY_THRESHOLD = 0.6f; + +// Fast updates allowed before a quality refresh limits ghosting. A full-screen redraw takes about 10 updates, so this must be higher +static constexpr uint32_t QUALITY_REFRESH_PARTIAL_COUNT = 20; + +// Updates within this time after a quality update also use quality mode, so the tiles of one redraw stay consistent. +// It must stay well below the duration of a quality update. +static constexpr uint32_t QUALITY_HOLD_MS = 50; + +// Maximum time that consecutive quality updates keep extending the hold window +static constexpr uint32_t QUALITY_HOLD_SESSION_MAX_MS = 200; + +// 4x4 ordered (Bayer) dither thresholds, spread evenly across a 0-15 nibble range. +static constexpr uint8_t BAYER_4X4[4][4] = { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 }, +}; + +// Dithers an 8-bit luminance (0x00 black to 0xFF white) to a 4-bit level (0x0 black to 0xF white) +static inline uint8_t dither_to_nibble(uint8_t luminance, int32_t x, int32_t y) { + // Scaling by 17 spreads the thresholds over one full quantization step + const uint32_t threshold = BAYER_4X4[y & 3][x & 3] * 17U; + const uint32_t level = (static_cast(luminance) * 15U + threshold) / 255U; + return static_cast(level > 15U ? 15U : level); +} + +// Binary variant for fast updates, which only draw black and white +static inline uint8_t dither_to_bw_nibble(uint8_t luminance, int32_t x, int32_t y) { + // Scaling by 16 keeps the highest threshold at 240, so pure white (0xFF) is never classified as black + const uint32_t threshold = BAYER_4X4[y & 3][x & 3] * 16U; + return luminance > threshold ? 0xF : 0x0; +} + +extern "C" { + +struct T5s3DisplayInternal { + uint8_t* framebuffer; + bool powered; + // Fast updates since the last quality refresh + uint32_t partial_count_since_quality; + // Until this tick every update uses quality mode + TickType_t quality_hold_until_tick; + // Tick at which the current hold window started + TickType_t quality_hold_session_start_tick; +}; + +static void power_on(T5s3DisplayInternal* internal) { + if (!internal->powered) { + t5s3_epd_power_on(); + internal->powered = true; + } +} + +// region DisplayApi + +static error_t t5s3_display_reset(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + // The panel has no reset line, so a power cycle is the closest equivalent + t5s3_epd_power_off(); + internal->powered = false; + power_on(internal); + return ERROR_NONE; +} + +/** Initialization is done in start() */ +static error_t t5s3_display_init(Device*) { + return ERROR_NONE; +} + +/** Fills the panel white with one full update, without the flashing cycles of the initial clear */ +static error_t t5s3_display_clear(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + power_on(internal); + t5s3_epd_fill_white(); + const bool success = t5s3_epd_update(T5s3EpdMode::Full, 0, T5S3_EPD_HEIGHT); + if (success) { + internal->partial_count_since_quality = 0; + internal->quality_hold_until_tick = 0; + } + return success ? ERROR_NONE : ERROR_RESOURCE; +} + +/** + * Redraws the current content with a quality update to remove ghosting. + * The update skips pixels that did not change, so every pixel is invalidated first. + */ +static error_t t5s3_display_refresh(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + power_on(internal); + t5s3_epd_invalidate(); + const bool success = t5s3_epd_update(T5s3EpdMode::Full, 0, T5S3_EPD_HEIGHT); + if (success) { + internal->partial_count_since_quality = 0; + internal->quality_hold_until_tick = 0; + } + return success ? ERROR_NONE : ERROR_RESOURCE; +} + +// Decides between a quality refresh and a fast update, *out_within_hold tells if the hold window applied +static bool should_use_quality_mode(T5s3DisplayInternal* internal, int32_t width, int32_t height, bool* out_within_hold) { + const TickType_t now = get_ticks(); + + const uint32_t area = static_cast(width) * static_cast(height); + const bool is_full_screen_change = area >= static_cast( + static_cast(T5S3_EPD_WIDTH * T5S3_EPD_HEIGHT) * FULL_AREA_QUALITY_THRESHOLD + ); + const bool partial_count_exceeded = internal->partial_count_since_quality >= QUALITY_REFRESH_PARTIAL_COUNT; + // Signed difference stays correct across the tick counter wrap, 0 means no hold + const TickType_t hold_until = internal->quality_hold_until_tick; + const bool within_hold = hold_until != 0 && static_cast(hold_until - now) > 0; + *out_within_hold = within_hold; + + return is_full_screen_change || partial_count_exceeded || within_hold; +} + +// Updates the counters after an update. A failed quality update leaves them unchanged and a failed fast update still counts. +// A quality update starts or extends the hold window, up to QUALITY_HOLD_SESSION_MAX_MS. +static void commit_quality_mode_decision(T5s3DisplayInternal* internal, bool used_quality, bool draw_succeeded, bool was_within_hold) { + if (used_quality) { + if (draw_succeeded) { + internal->partial_count_since_quality = 0; + const TickType_t now = get_ticks(); + if (!was_within_hold) { + internal->quality_hold_session_start_tick = now; + } + const TickType_t session_elapsed = now - internal->quality_hold_session_start_tick; + if (session_elapsed < millis_to_ticks(QUALITY_HOLD_SESSION_MAX_MS)) { + internal->quality_hold_until_tick = now + millis_to_ticks(QUALITY_HOLD_MS); + } else { + internal->quality_hold_until_tick = 0; + } + } + } else { + internal->partial_count_since_quality++; + } +} + +// Reports GRAYSCALE8 so that draw_bitmap() is called per changed tile +static error_t t5s3_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { + auto* internal = static_cast(device_get_driver_data(device)); + + const int32_t width = x_end - x_start; + const int32_t height = y_end - y_start; + bool within_hold = false; + const bool use_quality = should_use_quality_mode(internal, width, height, &within_hold); + + // color_data is 8-bit luminance (0x00 black to 0xFF white) and the panel wants 4-bit levels (0x0 black to 0xF white) + // Dithering uses 16 levels for quality updates and 2 levels for fast updates + const auto* src = static_cast(color_data); + const size_t src_stride = static_cast(width); + + for (int32_t row = 0; row < height; row++) { + const uint8_t* src_row = src + static_cast(row) * src_stride; + const int32_t display_y = y_start + row; + + for (int32_t col = 0; col < width; col++) { + const int32_t display_x = x_start + col; + const uint8_t nibble = use_quality + ? dither_to_nibble(src_row[col], display_x, display_y) + : dither_to_bw_nibble(src_row[col], display_x, display_y); + t5s3_epd_set_pixel(internal->framebuffer, display_x, display_y, nibble); + } + } + + power_on(internal); + const bool success = t5s3_epd_update(use_quality ? T5s3EpdMode::Quality : T5s3EpdMode::Fast, y_start, y_end); + + commit_quality_mode_decision(internal, use_quality, success, within_hold); + return success ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t t5s3_display_disp_on_off(Device* device, bool on_off) { + auto* internal = static_cast(device_get_driver_data(device)); + if (on_off) { + power_on(internal); + } else if (internal->powered) { + t5s3_epd_power_off(); + internal->powered = false; + } + return ERROR_NONE; +} + +static DisplayColorFormat t5s3_display_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_GRAYSCALE8; +} + +static uint16_t t5s3_display_get_resolution_x(Device*) { + return T5S3_EPD_WIDTH; +} + +static uint16_t t5s3_display_get_resolution_y(Device*) { + return T5S3_EPD_HEIGHT; +} + +// endregion + +static const DisplayApi t5s3_display_api = { + // The driver converts into its own framebuffer, so the LVGL draw buffers may live in external RAM + .capabilities = DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_SLOW_REFRESH | DISPLAY_CAPABILITY_PREFER_EXTERNAL_RAM, + .reset = t5s3_display_reset, + .init = t5s3_display_init, + .draw_bitmap = t5s3_display_draw_bitmap, + .clear = t5s3_display_clear, + .refresh = t5s3_display_refresh, + .mirror = nullptr, + .swap_xy = nullptr, + .get_swap_xy = nullptr, + .get_mirror_x = nullptr, + .get_mirror_y = nullptr, + .set_gap = nullptr, + .get_gap_x = nullptr, + .get_gap_y = nullptr, + .invert_color = nullptr, + .disp_on_off = t5s3_display_disp_on_off, + .disp_sleep = nullptr, + .get_color_format = t5s3_display_get_color_format, + .get_resolution_x = t5s3_display_get_resolution_x, + .get_resolution_y = t5s3_display_get_resolution_y, + // The framebuffer is 4bpp and not in the reported GRAYSCALE8 format + .get_frame_buffer = nullptr, + .get_frame_buffer_count = nullptr, + .get_backlight = nullptr, + .has_capability = nullptr, +}; + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(T5s3DisplayInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + internal->powered = false; + + if (!t5s3_epd_init(config->pixel_clock_hz)) { + free(internal); + return ERROR_RESOURCE; + } + internal->framebuffer = t5s3_epd_framebuffer(); + + internal->partial_count_since_quality = 0; + internal->quality_hold_until_tick = 0; + internal->quality_hold_session_start_tick = 0; + + device_set_driver_data(device, internal); + + // The boot splash leaves ghosting, so clear the panel with the full flash cycle before LVGL draws + power_on(internal); + t5s3_epd_clear(); + + LOG_I(TAG, "Initialized (%dx%d)", T5S3_EPD_WIDTH, T5S3_EPD_HEIGHT); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + if (internal->powered) { + t5s3_epd_power_off(); + internal->powered = false; + } + + t5s3_epd_deinit(); + + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +// endregion + +Driver t5s3_display_driver = { + .name = "t5s3-display", + .compatible = (const char*[]) { "lilygo,t5s3-display", nullptr }, + .start_device = start, + .stop_device = stop, + .probe = nullptr, + .api = &t5s3_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &lilygo_t5s3_epd_module, + .internal = nullptr +}; + +} diff --git a/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.cpp b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.cpp new file mode 100644 index 000000000..39f9f83fb --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.cpp @@ -0,0 +1,596 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Minimal driver for the ED047TC1 panel of the LILYGO T5 4.7 Inch E-Paper S3. +// The sequences and timings follow the epdiy project (https://github.com/vroland/epdiy). +// +// The panel is controlled by a 74HCT4094 shift register (latch, output enable, mode, start of frame and the +// panel supplies), the CKV gate clock (RMT peripheral) and an 8 bit bus (LCD peripheral in i80 mode) that +// carries the 2 bit actions of 4 pixels per byte. Rows are sent one at a time. The data of a row is applied by the +// CKV pulse after the one that was running during its transfer. + +#include "t5s3_epd.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TAG "T5s3Epd" + +static constexpr gpio_num_t PIN_REGISTER_DATA = GPIO_NUM_13; +static constexpr gpio_num_t PIN_REGISTER_CLOCK = GPIO_NUM_12; +static constexpr gpio_num_t PIN_REGISTER_STROBE = GPIO_NUM_0; +static constexpr gpio_num_t PIN_CKV = GPIO_NUM_38; +// Start pulse of a row, driven as the data/command line of the bus +static constexpr gpio_num_t PIN_STH = GPIO_NUM_40; +// Pixel clock, driven as the write line of the bus +static constexpr gpio_num_t PIN_CKH = GPIO_NUM_41; +static constexpr gpio_num_t BUS_PINS[8] = {GPIO_NUM_6, GPIO_NUM_7, GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_2, GPIO_NUM_3, GPIO_NUM_8, GPIO_NUM_1}; + +// A row has headroom after the pixel data for the timing of the source driver +static constexpr int BUS_TRANSFER_BYTES = ((T5S3_EPD_WIDTH + 32) / 4 + 3) & ~3; + +// Time base of the CKV pulses, 10 MHz +static constexpr uint32_t PULSE_RESOLUTION_HZ = 10000000; +// Low time after the pulse of a row in ticks +static constexpr uint32_t ROW_LOW_TICKS = 50; +// More phases drive the pixels further into black or white, which leaves less ghosting but makes fast updates slower +static constexpr uint32_t FAST_PHASES = 10; +// Hold time of a phase of the fast update in ticks +static constexpr uint32_t FAST_HOLD_TICKS = 1000; +// Hold time of the frames that flash the panel to white +static constexpr uint32_t CLEAR_HOLD_TICKS = 120; +static constexpr int CLEAR_CYCLES = 3; +static constexpr int CLEAR_DARK_FRAMES = 10; +static constexpr int CLEAR_LIGHT_FRAMES = 10; +static constexpr int CLEAR_REST_FRAMES = 2; +static constexpr uint8_t ALL_DARK = 0x55; +static constexpr uint8_t ALL_LIGHT = 0xAA; + +// Limit for waiting on a flag that an interrupt handler sets, so that a lost interrupt fails the update instead of spinning forever +static constexpr uint32_t WAIT_SPIN_LIMIT = 20000000; + +struct Engine { + uint8_t* front = nullptr; + uint8_t* back = nullptr; + uint8_t* row_buffer[2] = {nullptr, nullptr}; + int current = 0; + esp_lcd_i80_bus_handle_t bus = nullptr; + esp_lcd_panel_io_handle_t io = nullptr; + rmt_channel_handle_t pulse_channel = nullptr; + rmt_encoder_handle_t pulse_encoder = nullptr; + volatile bool transfer_done = true; + volatile bool pulse_done = true; + bool failed = false; + // Shift register + bool latch = false; + bool output_enable = false; + bool mode = false; + bool start_of_frame = true; + bool power_disable = true; + bool positive_power = false; + bool negative_power = false; + // Rows written since the last row that carried data + int rows_skipped = 0; + bool row_dirty[T5S3_EPD_HEIGHT]; + alignas(4) uint8_t changes[T5S3_EPD_FB_ROW_BYTES]; + alignas(4) uint8_t line_mask[T5S3_EPD_BUS_ROW_BYTES]; +}; + +static Engine engine; +static rmt_symbol_word_t pulse_symbol; + +static inline void IRAM_ATTR pin_set(gpio_num_t pin, uint32_t level) { + gpio_ll_set_level(GPIO_LL_GET_HW(GPIO_PORT_0), pin, level); +} + +static bool IRAM_ATTR on_pulse_done(rmt_channel_handle_t, const rmt_tx_done_event_data_t*, void*) { + engine.pulse_done = true; + return false; +} + +static bool IRAM_ATTR on_transfer_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void*) { + engine.transfer_done = true; + return false; +} + +// After a failure nothing is waited for anymore, so the rest of the update ends quickly +static void IRAM_ATTR wait_for(volatile bool& flag) { + uint32_t spins = 0; + while (!flag) { + if (engine.failed || ++spins > WAIT_SPIN_LIMIT) { + engine.failed = true; + flag = true; + } + } +} + +// region Shift register + +static void IRAM_ATTR push_register_bit(bool bit) { + pin_set(PIN_REGISTER_CLOCK, 0); + pin_set(PIN_REGISTER_DATA, bit ? 1 : 0); + pin_set(PIN_REGISTER_CLOCK, 1); +} + +static void IRAM_ATTR push_register() { + pin_set(PIN_REGISTER_STROBE, 0); + push_register_bit(engine.output_enable); + push_register_bit(engine.mode); + // Always set + push_register_bit(true); + push_register_bit(engine.start_of_frame); + push_register_bit(engine.negative_power); + push_register_bit(engine.positive_power); + push_register_bit(engine.power_disable); + push_register_bit(engine.latch); + pin_set(PIN_REGISTER_STROBE, 1); +} + +// endregion + +// region CKV pulses and row transfer + +// Pulses the gate clock for high_ticks and keeps it low for low_ticks, repeated for the given number of pulses. +// Without a high time the line is high for low_ticks. +static void IRAM_ATTR pulse_ckv(uint32_t high_ticks, uint32_t low_ticks, bool wait, int count = 1) { + wait_for(engine.pulse_done); + if (high_ticks > 0) { + pulse_symbol.duration0 = high_ticks; + pulse_symbol.level0 = 1; + pulse_symbol.duration1 = low_ticks; + pulse_symbol.level1 = 0; + } else { + pulse_symbol.duration0 = low_ticks; + pulse_symbol.level0 = 1; + pulse_symbol.duration1 = 0; + pulse_symbol.level1 = 0; + } + engine.pulse_done = false; + rmt_transmit_config_t config = {}; + config.loop_count = count; + if (rmt_transmit(engine.pulse_channel, engine.pulse_encoder, &pulse_symbol, sizeof(pulse_symbol), &config) != ESP_OK) { + engine.pulse_done = true; + engine.failed = true; + return; + } + if (wait) { + wait_for(engine.pulse_done); + } +} + +static void IRAM_ATTR pulse_ckv_us(uint32_t high_us, uint32_t low_us, bool wait) { + pulse_ckv(high_us * 10, low_us * 10, wait); +} + +static void IRAM_ATTR start_transfer() { + engine.transfer_done = false; + if (esp_lcd_panel_io_tx_color(engine.io, 0, engine.row_buffer[engine.current], BUS_TRANSFER_BYTES) != ESP_OK) { + engine.transfer_done = true; + engine.failed = true; + } +} + +// Applies the data that was sent last and starts the transfer of the current row buffer while the CKV pulse runs +static void IRAM_ATTR output_row(uint32_t hold_ticks) { + wait_for(engine.transfer_done); + wait_for(engine.pulse_done); + engine.latch = true; + push_register(); + engine.latch = false; + push_register(); + pulse_ckv(hold_ticks, ROW_LOW_TICKS, false); + start_transfer(); + engine.current ^= 1; +} + +static void IRAM_ATTR write_row(uint32_t hold_ticks) { + output_row(hold_ticks); + engine.rows_skipped = 0; +} + +// Skips a row after rows with data. The first two skipped rows push blank data through the pipeline. +static void IRAM_ATTR skip_row(uint32_t hold_ticks) { + memset(engine.row_buffer[engine.current], 0, T5S3_EPD_BUS_ROW_BYTES); + output_row(hold_ticks); + engine.rows_skipped++; +} + +// Clocks the gate driver through rows that carry no data with one train of pulses +static void IRAM_ATTR skip_rows(int count) { + pulse_ckv(45, 5, false, count); + engine.rows_skipped += count; +} + +static void IRAM_ATTR start_frame() { + wait_for(engine.transfer_done); + wait_for(engine.pulse_done); + engine.rows_skipped = 0; + + engine.mode = true; + push_register(); + pulse_ckv_us(1, 1, true); + + // The start of frame line must go low and high again while the long pulse runs + engine.start_of_frame = false; + push_register(); + pulse_ckv_us(1000, 100, false); + engine.start_of_frame = true; + push_register(); + for (int i = 0; i < 4; i++) { + pulse_ckv_us(1, 1, true); + } + + engine.output_enable = true; + push_register(); +} + +static void IRAM_ATTR end_frame() { + engine.start_of_frame = false; + push_register(); + for (int i = 0; i < 5; i++) { + pulse_ckv_us(1, 1, true); + } + engine.mode = false; + push_register(); + pulse_ckv_us(0, 10, true); + engine.output_enable = false; + push_register(); + for (int i = 0; i < 3; i++) { + pulse_ckv_us(1, 1, true); + } +} + +// endregion + +// region Frames + +static void IRAM_ATTR draw_frame(const uint8_t* table, uint32_t hold_ticks) { + start_frame(); + int y = 0; + bool ends_with_train = false; + while (y < T5S3_EPD_HEIGHT && !engine.failed) { + ends_with_train = false; + if (engine.row_dirty[y]) { + t5s3_epd_prepare_row( + engine.row_buffer[engine.current], + engine.front + y * T5S3_EPD_FB_ROW_BYTES, + engine.back + y * T5S3_EPD_FB_ROW_BYTES, + table, + engine.line_mask + ); + write_row(hold_ticks); + y++; + } else if (engine.rows_skipped < 2) { + skip_row(hold_ticks); + y++; + } else { + int run = 1; + while (y + run < T5S3_EPD_HEIGHT && !engine.row_dirty[y + run]) { + run++; + } + skip_rows(run); + ends_with_train = true; + y += run; + } + } + // The last row is applied by one more pulse + if (engine.rows_skipped == 0 && !engine.failed) { + write_row(hold_ticks); + } else if (ends_with_train) { + // The end of the frame must not overlap the train of pulses of the skipped rows + wait_for(engine.pulse_done); + } + end_frame(); +} + +static void IRAM_ATTR draw_uniform_frame(uint8_t pattern, uint32_t hold_ticks) { + memset(engine.row_buffer[0], pattern, T5S3_EPD_BUS_ROW_BYTES); + memset(engine.row_buffer[1], pattern, T5S3_EPD_BUS_ROW_BYTES); + start_frame(); + for (int y = 0; y < T5S3_EPD_HEIGHT && !engine.failed; y++) { + write_row(hold_ticks); + } + if (!engine.failed) { + write_row(hold_ticks); + } + end_frame(); +} + +// endregion + +// region Setup + +static bool setup_register_pins() { + gpio_config_t config = {}; + config.pin_bit_mask = (1ULL << PIN_REGISTER_DATA) | (1ULL << PIN_REGISTER_CLOCK) | (1ULL << PIN_REGISTER_STROBE); + config.mode = GPIO_MODE_OUTPUT; + if (gpio_config(&config) != ESP_OK) { + return false; + } + pin_set(PIN_REGISTER_STROBE, 0); + return true; +} + +static bool setup_bus(uint32_t pixel_clock_hz) { + esp_lcd_i80_bus_config_t bus_config = {}; + bus_config.dc_gpio_num = PIN_STH; + bus_config.wr_gpio_num = PIN_CKH; + bus_config.clk_src = LCD_CLK_SRC_DEFAULT; + for (int i = 0; i < 8; i++) { + bus_config.data_gpio_nums[i] = BUS_PINS[i]; + } + bus_config.bus_width = 8; + bus_config.max_transfer_bytes = BUS_TRANSFER_BYTES; + if (esp_lcd_new_i80_bus(&bus_config, &engine.bus) != ESP_OK) { + return false; + } + + // The data/command line is low when idle, high during a dummy command phase and low during the data of a row + esp_lcd_panel_io_i80_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_NC; + io_config.pclk_hz = pixel_clock_hz; + io_config.trans_queue_depth = 4; + io_config.on_color_trans_done = on_transfer_done; + io_config.lcd_cmd_bits = 10; + io_config.lcd_param_bits = 0; + io_config.dc_levels.dc_idle_level = 0; + io_config.dc_levels.dc_cmd_level = 1; + io_config.dc_levels.dc_dummy_level = 0; + io_config.dc_levels.dc_data_level = 0; + return esp_lcd_new_panel_io_i80(engine.bus, &io_config, &engine.io) == ESP_OK; +} + +static bool setup_pulse() { + rmt_tx_channel_config_t channel_config = {}; + channel_config.gpio_num = PIN_CKV; + channel_config.clk_src = RMT_CLK_SRC_DEFAULT; + channel_config.resolution_hz = PULSE_RESOLUTION_HZ; + channel_config.mem_block_symbols = 48; + channel_config.trans_queue_depth = 1; + if (rmt_new_tx_channel(&channel_config, &engine.pulse_channel) != ESP_OK) { + return false; + } + rmt_copy_encoder_config_t encoder_config = {}; + if (rmt_new_copy_encoder(&encoder_config, &engine.pulse_encoder) != ESP_OK) { + return false; + } + rmt_tx_event_callbacks_t callbacks = {}; + callbacks.on_trans_done = on_pulse_done; + if (rmt_tx_register_event_callbacks(engine.pulse_channel, &callbacks, nullptr) != ESP_OK) { + return false; + } + return rmt_enable(engine.pulse_channel) == ESP_OK; +} + +bool t5s3_epd_init(uint32_t pixel_clock_hz) { + if (engine.front != nullptr) { + return true; + } + + engine.front = static_cast(heap_caps_aligned_alloc(16, T5S3_EPD_FB_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); + engine.back = static_cast(heap_caps_aligned_alloc(16, T5S3_EPD_FB_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); + for (auto& buffer: engine.row_buffer) { + buffer = static_cast(heap_caps_aligned_calloc(16, 1, BUS_TRANSFER_BYTES, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + } + if (engine.front == nullptr || engine.back == nullptr || engine.row_buffer[0] == nullptr || engine.row_buffer[1] == nullptr) { + LOG_E(TAG, "Out of memory"); + t5s3_epd_deinit(); + return false; + } + memset(engine.front, 0xFF, T5S3_EPD_FB_BYTES); + memset(engine.back, 0xFF, T5S3_EPD_FB_BYTES); + + if (!setup_register_pins() || !setup_bus(pixel_clock_hz) || !setup_pulse()) { + LOG_E(TAG, "Failed to set up the pins and peripherals"); + t5s3_epd_deinit(); + return false; + } + + engine.latch = false; + engine.output_enable = false; + engine.mode = false; + engine.start_of_frame = true; + engine.power_disable = true; + engine.positive_power = false; + engine.negative_power = false; + engine.current = 0; + engine.transfer_done = true; + engine.pulse_done = true; + push_register(); + return true; +} + +void t5s3_epd_deinit() { + if (engine.pulse_channel != nullptr) { + // Both calls fail harmlessly when the channel was not enabled + rmt_disable(engine.pulse_channel); + rmt_del_channel(engine.pulse_channel); + engine.pulse_channel = nullptr; + } + if (engine.pulse_encoder != nullptr) { + rmt_del_encoder(engine.pulse_encoder); + engine.pulse_encoder = nullptr; + } + if (engine.io != nullptr) { + esp_lcd_panel_io_del(engine.io); + engine.io = nullptr; + } + if (engine.bus != nullptr) { + esp_lcd_del_i80_bus(engine.bus); + engine.bus = nullptr; + } + heap_caps_free(engine.front); + heap_caps_free(engine.back); + heap_caps_free(engine.row_buffer[0]); + heap_caps_free(engine.row_buffer[1]); + engine.front = nullptr; + engine.back = nullptr; + engine.row_buffer[0] = nullptr; + engine.row_buffer[1] = nullptr; +} + +// endregion + +// region Power + +void t5s3_epd_power_on() { + engine.power_disable = false; + push_register(); + esp_rom_delay_us(100); + engine.negative_power = true; + push_register(); + esp_rom_delay_us(500); + engine.positive_power = true; + push_register(); + esp_rom_delay_us(100); + engine.start_of_frame = true; + push_register(); +} + +void t5s3_epd_power_off() { + engine.positive_power = false; + push_register(); + esp_rom_delay_us(10); + engine.negative_power = false; + push_register(); + esp_rom_delay_us(100); + engine.start_of_frame = false; + engine.output_enable = false; + engine.mode = false; + engine.power_disable = true; + push_register(); +} + +// endregion + +// region Updates + +uint8_t* t5s3_epd_framebuffer() { + return engine.front; +} + +void t5s3_epd_fill_white() { + memset(engine.front, 0xFF, T5S3_EPD_FB_BYTES); +} + +void t5s3_epd_invalidate() { + auto* words = reinterpret_cast(engine.back); + for (int i = 0; i < T5S3_EPD_FB_BYTES / 4; i++) { + words[i] = ~words[i]; + } +} + +bool t5s3_epd_update(T5s3EpdMode mode, int32_t y_start, int32_t y_end) { + if (y_start < 0) { + y_start = 0; + } + if (y_end > T5S3_EPD_HEIGHT) { + y_end = T5S3_EPD_HEIGHT; + } + if (y_start >= y_end) { + return true; + } + const int64_t started = esp_timer_get_time(); + + // Find the rows and columns that changed + memset(engine.row_dirty, 0, sizeof(engine.row_dirty)); + memset(engine.changes, 0, sizeof(engine.changes)); + auto* changes = reinterpret_cast(engine.changes); + bool any_changes = false; + for (int y = y_start; y < y_end; y++) { + const auto* to_row = reinterpret_cast(engine.front + y * T5S3_EPD_FB_ROW_BYTES); + const auto* from_row = reinterpret_cast(engine.back + y * T5S3_EPD_FB_ROW_BYTES); + uint32_t row_changes = 0; + for (int i = 0; i < T5S3_EPD_FB_ROW_BYTES / 4; i++) { + const uint32_t difference = to_row[i] ^ from_row[i]; + changes[i] |= difference; + row_changes |= difference; + } + engine.row_dirty[y] = row_changes != 0; + any_changes = any_changes || row_changes != 0; + } + if (!any_changes) { + return true; + } + t5s3_epd_build_line_mask(engine.line_mask, engine.changes); + + engine.failed = false; + uint8_t table[256]; + if (mode == T5s3EpdMode::Fast) { + t5s3_epd_build_fast_table(table); + for (uint32_t phase = 0; phase < FAST_PHASES && !engine.failed; phase++) { + draw_frame(table, FAST_HOLD_TICKS); + taskYIELD(); + } + } else { + // Long updates sleep between frames so that the idle task can feed the watchdog + for (int phase = 0; phase < T5S3_EPD_QUALITY_PHASES && !engine.failed; phase++) { + t5s3_epd_build_quality_table(table, phase, mode == T5s3EpdMode::Full); + draw_frame(table, T5S3_EPD_QUALITY_HOLD[phase]); + vTaskDelay(1); + } + } + + // A failed update leaves the back buffer alone, so the next update draws the same rows again + if (engine.failed) { + LOG_E(TAG, "Update failed, a peripheral did not respond"); + return false; + } + + for (int y = y_start; y < y_end; y++) { + if (engine.row_dirty[y]) { + memcpy(engine.back + y * T5S3_EPD_FB_ROW_BYTES, engine.front + y * T5S3_EPD_FB_ROW_BYTES, T5S3_EPD_FB_ROW_BYTES); + } + } + + const int elapsed_ms = static_cast((esp_timer_get_time() - started) / 1000); + // Quality and slow updates are logged at info level, frequent fast updates would flood the log + if (mode != T5s3EpdMode::Fast || elapsed_ms > 150) { + LOG_I(TAG, "Updated rows %d to %d in %d ms", static_cast(y_start), static_cast(y_end), elapsed_ms); + } else { + LOG_D(TAG, "Updated rows %d to %d in %d ms", static_cast(y_start), static_cast(y_end), elapsed_ms); + } + return true; +} + +static void draw_clear_frames(uint8_t pattern, int count) { + for (int i = 0; i < count && !engine.failed; i++) { + draw_uniform_frame(pattern, CLEAR_HOLD_TICKS); + vTaskDelay(1); + } +} + +bool t5s3_epd_clear() { + const int64_t started = esp_timer_get_time(); + t5s3_epd_fill_white(); + if (!t5s3_epd_update(T5s3EpdMode::Full, 0, T5S3_EPD_HEIGHT)) { + return false; + } + engine.failed = false; + for (int cycle = 0; cycle < CLEAR_CYCLES; cycle++) { + draw_clear_frames(ALL_DARK, CLEAR_DARK_FRAMES); + draw_clear_frames(ALL_LIGHT, CLEAR_LIGHT_FRAMES); + draw_clear_frames(0, CLEAR_REST_FRAMES); + } + if (engine.failed) { + LOG_E(TAG, "Clear failed, a peripheral did not respond"); + return false; + } + LOG_I(TAG, "Cleared in %d ms", static_cast((esp_timer_get_time() - started) / 1000)); + return true; +} + +// endregion diff --git a/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.h b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.h new file mode 100644 index 000000000..f6dd6b898 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Minimal driver for the ED047TC1 panel of the LILYGO T5 4.7 Inch E-Paper S3. +// The calls must be serialized by the user, the driver has no locking. + +#pragma once + +#include "t5s3_epd_rows.h" + +#include + +enum class T5s3EpdMode { + // Black and white only + Fast, + // 16 levels in 30 phases, white pixels that stay white are not touched + Quality, + // 16 levels in 30 phases, white pixels that stay white are flashed too, which clears ghosting + Full +}; + +// Allocates the frame buffers and sets up the pins and peripherals. The panel stays unpowered. +bool t5s3_epd_init(uint32_t pixel_clock_hz); + +void t5s3_epd_deinit(); + +// Frame buffer to draw in, 4 bits per pixel. The panel shows it after t5s3_epd_update(). +uint8_t* t5s3_epd_framebuffer(); + +void t5s3_epd_power_on(); + +void t5s3_epd_power_off(); + +// Drives all pixels that changed in the rows [y_start, y_end) since the last update. Needs the panel to be powered. +bool t5s3_epd_update(T5s3EpdMode mode, int32_t y_start, int32_t y_end); + +void t5s3_epd_fill_white(); + +// Makes the next update drive every pixel +void t5s3_epd_invalidate(); + +// Flashes the whole panel to white. Needs the panel to be powered. +bool t5s3_epd_clear(); + +inline void t5s3_epd_set_pixel(uint8_t* framebuffer, int32_t x, int32_t y, uint8_t level) { + if (x < 0 || x >= T5S3_EPD_WIDTH || y < 0 || y >= T5S3_EPD_HEIGHT) { + return; + } + uint8_t* byte = &framebuffer[y * T5S3_EPD_FB_ROW_BYTES + x / 2]; + if (x % 2) { + *byte = (*byte & 0x0F) | static_cast(level << 4); + } else { + *byte = (*byte & 0xF0) | (level & 0x0F); + } +} diff --git a/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_rows.h b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_rows.h new file mode 100644 index 000000000..34f36befc --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_rows.h @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Conversion of framebuffer rows into the data the source driver of the ED047TC1 panel expects. +// This code has no hardware access. + +#pragma once + +#include "t5s3_epd_waveform.h" + +#include + +#ifdef ESP_PLATFORM +#include +#define T5S3_EPD_IRAM IRAM_ATTR +#else +#define T5S3_EPD_IRAM +#endif + +constexpr int T5S3_EPD_WIDTH = 960; +constexpr int T5S3_EPD_HEIGHT = 540; + +// Framebuffer rows hold 4 bits per pixel with the even pixel of a pair in the low nibble, 0x0 is black and 0xF is white +constexpr int T5S3_EPD_FB_ROW_BYTES = T5S3_EPD_WIDTH / 2; +constexpr int T5S3_EPD_FB_BYTES = T5S3_EPD_FB_ROW_BYTES * T5S3_EPD_HEIGHT; + +// Rows on the bus hold 2 bits per pixel with the first of four pixels in the lowest bits +constexpr int T5S3_EPD_BUS_ROW_BYTES = T5S3_EPD_WIDTH / 4; + +constexpr uint8_t T5S3_EPD_ACTION_HOLD = 0; +constexpr uint8_t T5S3_EPD_ACTION_DARK = 1; +constexpr uint8_t T5S3_EPD_ACTION_LIGHT = 2; + +// Tables map (target level << 4 | source level) to the action for one phase of an update. + +// Fast update: pixels that end up black are driven dark and pixels that end up white are driven light +inline void t5s3_epd_build_fast_table(uint8_t* table) { + for (int to = 0; to < 16; to++) { + for (int from = 0; from < 16; from++) { + uint8_t action = T5S3_EPD_ACTION_HOLD; + if (to == 0 && from != 0) { + action = T5S3_EPD_ACTION_DARK; + } else if (to == 15 && from != 15) { + action = T5S3_EPD_ACTION_LIGHT; + } + table[(to << 4) | from] = action; + } + } +} + +// The waveform drives white pixels that stay white with a dark and a light pulse, which flashes them. +// Holding them instead leaves the rest of an area alone, so an update of a part of the screen does not leave visible columns. +inline void t5s3_epd_build_quality_table(uint8_t* table, int phase, bool flash_white) { + const uint8_t* packed = T5S3_EPD_QUALITY_LUT[phase]; + for (int to = 0; to < 16; to++) { + for (int group = 0; group < 4; group++) { + const uint8_t actions = *packed++; + const int index = (to << 4) | (group * 4); + table[index] = (actions >> 6) & 3; + table[index + 1] = (actions >> 4) & 3; + table[index + 2] = (actions >> 2) & 3; + table[index + 3] = actions & 3; + } + } + if (!flash_white) { + table[(15 << 4) | 15] = T5S3_EPD_ACTION_HOLD; + } +} + +// Keeps the pixels of changed columns and holds all others. A framebuffer byte of the changes holds the XOR of two pixels. +inline void t5s3_epd_build_line_mask(uint8_t* mask, const uint8_t* changes) { + for (int i = 0; i < T5S3_EPD_BUS_ROW_BYTES; i++) { + const uint8_t first = changes[2 * i]; + const uint8_t second = changes[2 * i + 1]; + uint8_t value = 0; + value |= (first & 0x0F) != 0 ? 0x03 : 0x00; + value |= (first & 0xF0) != 0 ? 0x0C : 0x00; + value |= (second & 0x0F) != 0 ? 0x30 : 0x00; + value |= (second & 0xF0) != 0 ? 0xC0 : 0x00; + mask[i] = value; + } +} + +// Converts one row of target levels and one row of source levels into bus data. +// All pointers must be 4-byte aligned. Four framebuffer bytes (8 pixels) become two bus bytes per step. +inline void T5S3_EPD_IRAM t5s3_epd_prepare_row( + uint8_t* out, + const uint8_t* to_row, + const uint8_t* from_row, + const uint8_t* table, + const uint8_t* mask +) { + const auto* to_words = reinterpret_cast(to_row); + const auto* from_words = reinterpret_cast(from_row); + const auto* mask_words = reinterpret_cast(mask); + auto* out_words = reinterpret_cast(out); + for (int i = 0; i < T5S3_EPD_FB_ROW_BYTES / 4; i++) { + const uint32_t to = to_words[i]; + const uint32_t from = from_words[i]; + // Table index of a pixel is (target level << 4) | source level, the even pixel of a byte is in its low nibble + const uint32_t even = ((to & 0x0F0F0F0F) << 4) | (from & 0x0F0F0F0F); + const uint32_t odd = (to & 0xF0F0F0F0) | ((from >> 4) & 0x0F0F0F0F); + const uint32_t first = table[even & 0xFF] | (table[odd & 0xFF] << 2) | (table[(even >> 8) & 0xFF] << 4) | (table[(odd >> 8) & 0xFF] << 6); + const uint32_t second = table[(even >> 16) & 0xFF] | (table[(odd >> 16) & 0xFF] << 2) | (table[even >> 24] << 4) | (table[odd >> 24] << 6); + out_words[i] = static_cast((first | (second << 8)) & mask_words[i]); + } +} diff --git a/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_waveform.h b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_waveform.h new file mode 100644 index 000000000..464540911 --- /dev/null +++ b/Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_waveform.h @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Drive data of the ED047TC1 panel for the 16 level update, taken from the epdiy project +// (https://github.com/vroland/epdiy, built-in waveform epdiy_ED047TC1, mode GC16). +// This file is under the LGPL v3.0 or later like epdiy itself. + +#pragma once + +#include + +constexpr int T5S3_EPD_QUALITY_PHASES = 30; + +// Hold time of each phase in units of 0.1 microsecond +constexpr uint16_t T5S3_EPD_QUALITY_HOLD[T5S3_EPD_QUALITY_PHASES] = { + 30, 30, 20, 20, 30, 30, 30, 40, 40, 50, 50, 50, 100, 200, 300, 10, 10, 8, 8, 8, 8, 8, 10, 10, 15, 15, 20, 20, 100, 300 +}; + +// Per phase: 16 target levels with 4 bytes each. A byte holds the 2 bit actions of 4 source levels, +// the lowest source level in the top bits. Action 0 holds the pixel, 1 drives it dark and 2 light. +constexpr uint8_t T5S3_EPD_QUALITY_LUT[T5S3_EPD_QUALITY_PHASES][64] = { + { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + }, + { + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, + }, + { + 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, + }, + { + 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, + }, + { + 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, + 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, + 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, + 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, + }, + { + 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, + 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, + 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, + 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, + }, + { + 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, + 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, + 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, + 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, + }, + { + 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, + 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, + 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, + 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, + }, + { + 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, + 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, + 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, + 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, + }, + { + 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, + 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, + 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, + 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, + }, + { + 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, + 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, + 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, + 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, + }, + { + 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, + 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, + 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, + 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, + }, + { + 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, + 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, + 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, + 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, + }, + { + 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, + 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, + 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, + 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, + }, + { + 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, + 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, + 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, + 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, + }, + { + 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }, + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, + }, +}; diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 49df9c427..d1cd4d399 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -8,6 +8,14 @@ Website: https://github.com/valdanylchuk/breezybox License: [MIT License](https://github.com/valdanylchuk/breezybox/blob/main/LICENSE) +### epdiy + +The e-paper driver of the LILYGO T5 4.7 Inch E-Paper S3 (`Drivers/lilygo-t5s3-epd-module`) is derived from epdiy and contains its ED047TC1 waveform data. + +Website: https://github.com/vroland/epdiy + +License: [LGPL v3.0 or later](https://github.com/vroland/epdiy/blob/main/LICENSE) + ### ESP-IDF This project uses ESP-IDF to compile the ESP32 firmware.