diff --git a/.gitmodules b/.gitmodules index 0d18c27c6..695474766 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "Libraries/cJSON/cJSON"] path = Libraries/cJSON/cJSON url = https://github.com/DaveGamble/cJSON.git +[submodule "Libraries/esp_epaper"] + path = Libraries/esp_epaper + url = https://github.com/NellowTCS/esp_epaper.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 02965b4be..195f180a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ if (DEFINED ENV{ESP_IDF_VERSION}) "TactilityC" "TactilityFreeRtos" "Libraries/elf_loader" + "Libraries/esp_epaper" "Libraries/lv_screenshot" "Libraries/minitar" "Libraries/minmea" diff --git a/Devices/cl32/CMakeLists.txt b/Devices/cl32/CMakeLists.txt new file mode 100644 index 000000000..09a2fdee3 --- /dev/null +++ b/Devices/cl32/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/cl32/LICENSE-Apache-2.0.md b/Devices/cl32/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Devices/cl32/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/cl32/cl32.dts b/Devices/cl32/cl32.dts new file mode 100644 index 000000000..9027c8e99 --- /dev/null +++ b/Devices/cl32/cl32.dts @@ -0,0 +1,121 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/ { + compatible = "root"; + model = "CL-32"; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + wifi0 { + compatible = "espressif,esp32-wifi"; + status = "disabled"; + }; + + // Top Stemma/Qwiic port 1 shares this bus with the keyboard and RTC (SDA: pin 1 <-> SCL: pin 2). + // External I2C devices on that port are reachable via i2c0. + i2c0 { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <100000>; + pin-sda = <&gpio0 1 GPIO_FLAG_PULL_UP>; + pin-scl = <&gpio0 2 GPIO_FLAG_PULL_UP>; + + keyboard { + compatible = "ti,tca8418"; + reg = <0x34>; + rows = <8>; + columns = <10>; + keymap-lc = [ + 37 49 50 51 52 53 54 55 56 0 // % 1 2 3 4 5 6 7 8 + 57 48 8 91 93 43 34 39 27 0 // 9 0 BKSP [ ] + " ' EXIT + 9 113 119 101 114 116 121 117 105 0 // TAB q w e r t y u i + 111 112 10 40 41 45 59 58 3 0 // o p ENTER ( ) - ; : STOP + 0 97 115 100 102 103 104 106 107 0 // a s d f g h j k + 108 17 35 123 125 42 44 46 2 0 // l UP # { } * , . MENU + 122 120 99 118 98 32 32 110 109 0 // z x c v b n m + 20 18 19 60 62 47 92 61 10 0 // LEFT DOWN RIGHT < > / \ = RUN + ]; + keymap-uc = [ + 37 49 50 51 52 53 54 55 56 0 // % 1 2 3 4 5 6 7 8 + 57 48 8 91 93 43 34 39 27 0 // 9 0 BKSP [ ] + " ' EXIT + 9 81 87 69 82 84 89 85 73 0 // TAB Q W E R T Y U I + 79 80 10 40 41 45 59 58 3 0 // O P ENTER ( ) - ; : STOP + 0 65 83 68 70 71 72 74 75 0 // A S D F G H J K + 76 17 35 123 125 42 44 46 2 0 // L UP # { } * , . MENU + 90 88 67 86 66 32 32 78 77 0 // Z X C V B N M + 20 18 19 60 62 47 92 61 10 0 // LEFT DOWN RIGHT < > / \ = RUN + ]; + keymap-sy = [ + 37 49 50 51 52 53 54 55 56 0 // % 1 2 3 4 5 6 7 8 + 57 48 8 91 93 43 34 39 27 0 // 9 0 BKSP [ ] + " ' EXIT + 9 113 119 101 114 116 121 117 105 0 // TAB q w e r t y u i + 111 112 10 40 41 45 59 58 3 0 // o p ENTER ( ) - ; : STOP + 0 97 115 100 102 103 104 106 107 0 // a s d f g h j k + 108 17 35 123 125 42 44 46 2 0 // l UP # { } * , . MENU + 122 120 99 118 98 32 32 110 109 0 // z x c v b n m + 20 18 19 60 62 47 92 61 10 0 // LEFT DOWN RIGHT < > / \ = RUN + ]; + shift-row = <4>; + shift-col = <0>; + sym-row = <5>; + sym-col = <8>; + }; + + rtc: bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; + }; + + port_b: grove1 { + compatible = "espressif,esp32-grove"; + defaultMode = ; + pinSdaTx = <&gpio0 47 GPIO_FLAG_NONE>; + pinSclRx = <&gpio0 48 GPIO_FLAG_NONE>; + uartPort = ; + i2cPort = ; + i2cClockFrequency = <100000>; + }; + + spi1 { + compatible = "espressif,esp32-spi"; + host = ; + cs-gpios = <&gpio0 7 GPIO_FLAG_NONE>; + pin-mosi = <&gpio0 10 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 11 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 9 GPIO_FLAG_NONE>; + max-transfer-size = <4096>; + + sdcard@0 { + compatible = "espressif,esp32-sdspi"; + frequency-khz = <20000>; + }; + + epd@1 { + compatible = "tuanpmt,esp-epaper"; + pin-dc = <&gpio0 13 GPIO_FLAG_NONE>; + pin-reset = <&gpio0 12 GPIO_FLAG_NONE>; + pin-busy = <&gpio0 14 GPIO_FLAG_NONE>; + pin-cs = <&gpio0 6 GPIO_FLAG_NONE>; + clock-speed-hz = <4000000>; + panel-type = "gdey029t71h"; + update-mode = <2>; + rotation = <1>; + }; + }; +}; diff --git a/Devices/cl32/device.properties b/Devices/cl32/device.properties new file mode 100644 index 000000000..f59540f82 --- /dev/null +++ b/Devices/cl32/device.properties @@ -0,0 +1,26 @@ +general.vendor=CL-32 +general.name=CL-32 + +apps.launcherAppId=Launcher + +hardware.target=ESP32S3 +hardware.flashSize=8MB +hardware.spiRam=true +hardware.spiRamMode=QUAD +hardware.spiRamSpeed=80M +hardware.esptoolFlashFreq=80M +hardware.bluetooth=true + +storage.userDataLocation=SD + +display.size=2.9" +display.shape=rectangle +display.dpi=139 + +lvgl.colorDepth=8 +lvgl.theme=Mono + +# Fix error "PSRAM space not enough for the Flash instructions" on boot: +sdkconfig.CONFIG_SPIRAM_FETCH_INSTRUCTIONS=n +sdkconfig.CONFIG_SPIRAM_RODATA=n +sdkconfig.CONFIG_SPIRAM_XIP_FROM_PSRAM=n diff --git a/Devices/cl32/devicetree.yaml b/Devices/cl32/devicetree.yaml new file mode 100644 index 000000000..e2bc7d3bb --- /dev/null +++ b/Devices/cl32/devicetree.yaml @@ -0,0 +1,6 @@ +dependencies: + - Platforms/platform-esp32 + - Drivers/tca8418-module + - Drivers/esp-epaper-module + - Drivers/bm8563-module +dts: cl32.dts diff --git a/Devices/cl32/source/module.cpp b/Devices/cl32/source/module.cpp new file mode 100644 index 000000000..5b88b4dd5 --- /dev/null +++ b/Devices/cl32/source/module.cpp @@ -0,0 +1,9 @@ +#include + +extern "C" { + +struct Module cl32_module = { + .name = "cl32" +}; + +} diff --git a/Drivers/esp-epaper-module/CMakeLists.txt b/Drivers/esp-epaper-module/CMakeLists.txt new file mode 100644 index 000000000..7bf6aa3a5 --- /dev/null +++ b/Drivers/esp-epaper-module/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +tactility_add_module(esp-epaper-module + SRCS source/esp_epaper.cpp source/module.cpp + INCLUDE_DIRS include/ + REQUIRES TactilityKernel platform-esp32 driver esp_epaper +) diff --git a/Drivers/esp-epaper-module/LICENSE-Apache-2.0.md b/Drivers/esp-epaper-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/esp-epaper-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/esp-epaper-module/bindings/tuanpmt,esp-epaper.yaml b/Drivers/esp-epaper-module/bindings/tuanpmt,esp-epaper.yaml new file mode 100644 index 000000000..8b44b5e4a --- /dev/null +++ b/Drivers/esp-epaper-module/bindings/tuanpmt,esp-epaper.yaml @@ -0,0 +1,65 @@ +description: > + E-paper display panels driven by the tuanpmt/esp_epaper ESP-IDF component + (SSD1680/1681 BW, GDEY0154D67 and ACeP/BWRY color controllers). The node must + be a child of an SPI controller: the display shares the parent's SPI host, + clock and MOSI pins, and manages its DC/RESET/BUSY/CS pins itself via the + component. Only 1bpp black/white panels are supported by the kernel display + bridge (DISPLAY_COLOR_FORMAT_MONOCHROME); color panels fail to start. + +compatible: "tuanpmt,esp-epaper" + +bus: spi + +properties: + pin-dc: + type: phandles + required: true + description: Data/Command GPIO pin + pin-reset: + type: phandles + required: true + description: Reset GPIO pin (the esp_epaper component drives it unconditionally) + pin-busy: + type: phandles + required: true + description: Busy GPIO pin (active high) + pin-cs: + type: phandles + required: true + description: Chip-select GPIO pin (driven manually by the component) + clock-speed-hz: + type: int + default: 4000000 + description: SPI clock frequency in Hz (must be > 0) + panel-type: + type: text + required: true + description: > + esp_epaper panel registry name. One of "gdey0154d67", "gdep073e01", + "gdey037f51", "gdey029t71h", "ssd16xx-154", "ssd16xx-213", "ssd16xx-266", + "ssd16xx-270", "ssd16xx-290", "ssd16xx-370", "ssd16xx-420". + width: + type: int + default: 0 + description: Horizontal resolution override in pixels (0 = the panel's default, maximum 2048) + height: + type: int + default: 0 + description: Vertical resolution override in pixels (0 = the panel's default, maximum 2048) + update-mode: + type: int + default: 0 + description: > + epd_update_mode_t used for frame updates: 0 = full, 1 = fast, 2 = partial. + Full is the safest default; fast and partial refresh quickly but ghost. + Values above 2 are rejected at driver start. + rotation: + type: int + default: 0 + description: > + Fixed display rotation applied at start. 0 = 0 degrees, 1 = 90 degrees, + 2 = 180 degrees, 3 = 270 degrees counter-clockwise (matching + LV_DISPLAY_ROTATION_*). The kernel bridge reports the rotated resolution, + so LVGL renders in rotated space and the driver rotates the 1bpp frame + back to the panel's native layout before updating. Not changeable at + runtime. diff --git a/Drivers/esp-epaper-module/devicetree.yaml b/Drivers/esp-epaper-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/esp-epaper-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/esp-epaper-module/include/bindings/esp_epaper.h b/Drivers/esp-epaper-module/include/bindings/esp_epaper.h new file mode 100644 index 000000000..0c6057f61 --- /dev/null +++ b/Drivers/esp-epaper-module/include/bindings/esp_epaper.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +DEFINE_DEVICETREE(esp_epaper, struct EspEpaperConfig) diff --git a/Drivers/esp-epaper-module/include/drivers/esp_epaper.h b/Drivers/esp-epaper-module/include/drivers/esp_epaper.h new file mode 100644 index 000000000..cba3a7706 --- /dev/null +++ b/Drivers/esp-epaper-module/include/drivers/esp_epaper.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/** + * @brief Panel configuration for an esp_epaper-driven e-paper display. + * + * The struct field order matches the binding's property order and must stay in + * sync with it: the devicetree compiler emits struct initializers in binding + * order, not declaration order. + */ +struct EspEpaperConfig { + struct GpioPinSpec pin_dc; + struct GpioPinSpec pin_reset; + struct GpioPinSpec pin_busy; + struct GpioPinSpec pin_cs; + /** SPI clock frequency in Hz */ + int clock_speed_hz; + /** Panel registry name, e.g. "ssd16xx-290" (see the binding's panel-type) */ + const char* panel_type; + /** Horizontal resolution override; 0 = the panel's default */ + uint16_t width; + /** Vertical resolution override; 0 = the panel's default */ + uint16_t height; + /** epd_update_mode_t used for frame updates */ + epd_update_mode_t update_mode; + /** + * Fixed display rotation, 0 = 0 degrees, 1 = 90, 2 = 180, 3 = 270 degrees + * counter-clockwise (LV_DISPLAY_ROTATION_*). Not changeable at runtime. + */ + uint8_t rotation; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp-epaper-module/include/esp_epaper_module.h b/Drivers/esp-epaper-module/include/esp_epaper_module.h new file mode 100644 index 000000000..fb3d5f3de --- /dev/null +++ b/Drivers/esp-epaper-module/include/esp_epaper_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module esp_epaper_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp-epaper-module/source/esp_epaper.cpp b/Drivers/esp-epaper-module/source/esp_epaper.cpp new file mode 100644 index 000000000..8bff9eea6 --- /dev/null +++ b/Drivers/esp-epaper-module/source/esp_epaper.cpp @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include "esp_epaper_rotate.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +constexpr auto* TAG = "esp_epaper"; +#define GET_CONFIG(device) (static_cast((device)->config)) + +/** Width/height overrides above this are rejected as nonsense. */ +constexpr uint16_t MAX_PANEL_DIMENSION = 2048; + +struct EspEpaperInternal { + /** Opaque esp_epaper device, owns the panel's pins and SPI device. */ + epd_handle_t epd; + epd_panel_info_t info; + /** Scratch buffer in native panel layout, for rotated frames (rotation != 0). */ + uint8_t* rotate_buffer; + /** Serializes panel/SPI access between draw_bitmap and power state changes. */ + SemaphoreHandle_t panel_mutex; + /** disp_on_off state; the panel is in deep sleep while false. */ + bool display_on; +}; + +static uint16_t esp_epaper_get_display_width(const EspEpaperInternal* internal, uint8_t rotation) { + return esp_epaper_rotation_swaps_axes(rotation) ? internal->info.height : internal->info.width; +} + +static uint16_t esp_epaper_get_display_height(const EspEpaperInternal* internal, uint8_t rotation) { + return esp_epaper_rotation_swaps_axes(rotation) ? internal->info.width : internal->info.height; +} + +static bool resolve_panel_type(const char* name, epd_panel_type_t* out_type) { + struct PanelMapping { + const char* name; + epd_panel_type_t type; + }; + static constexpr PanelMapping kPanelMappings[] = { + { "gdey0154d67", EPD_PANEL_GDEY0154D67 }, + { "gdep073e01", EPD_PANEL_GDEP073E01 }, + { "gdey037f51", EPD_PANEL_GDEY037F51 }, + { "gdey029t71h", EPD_PANEL_GDEY029T71H }, + { "ssd16xx-154", EPD_PANEL_SSD16XX_154 }, + { "ssd16xx-213", EPD_PANEL_SSD16XX_213 }, + { "ssd16xx-266", EPD_PANEL_SSD16XX_266 }, + { "ssd16xx-270", EPD_PANEL_SSD16XX_270 }, + { "ssd16xx-290", EPD_PANEL_SSD16XX_290 }, + { "ssd16xx-370", EPD_PANEL_SSD16XX_370 }, + { "ssd16xx-420", EPD_PANEL_SSD16XX_420 }, + }; + for (const auto& mapping : kPanelMappings) { + if (std::strcmp(name, mapping.name) == 0) { + *out_type = mapping.type; + return true; + } + } + return false; +} + +static error_t esp_epaper_reset(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); + // epd_wake() toggles the reset pin and re-runs the full init sequence. + const esp_err_t ret = epd_wake(internal->epd); + // epd_wake() re-inits the panel, so it is awake (and drawable) again. + if (ret == ESP_OK) { + internal->display_on = true; + } + xSemaphoreGive(internal->panel_mutex); + return ret == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +static error_t esp_epaper_init(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); + const esp_err_t ret = epd_wake(internal->epd); + if (ret == ESP_OK) { + internal->display_on = true; + } + xSemaphoreGive(internal->panel_mutex); + return ret == ESP_OK ? ERROR_NONE : ERROR_RESOURCE; +} + +// LVGL only ever calls this with the full frame: DISPLAY_COLOR_FORMAT_MONOCHROME forces +// LV_DISPLAY_RENDER_MODE_FULL in the generic kernel LVGL bridge (lvgl_display.c), and FULL mode +// only presents (calls draw_bitmap) once per render cycle, with the complete 0,0..hres,vres rect. +// hres/vres are the rotated (display) resolution reported by get_resolution_*; color_data is +// row-major, MSB-first 1bpp (LVGL's LV_COLOR_FORMAT_I1 with the palette header already stripped by +// the caller); bit 1 = white, bit 0 = black. epd_update() expects exactly that layout and polarity. +static error_t esp_epaper_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 auto* config = GET_CONFIG(device); + + const uint16_t display_width = esp_epaper_get_display_width(internal, config->rotation); + const uint16_t display_height = esp_epaper_get_display_height(internal, config->rotation); + if (x_start != 0 || y_start != 0 || x_end != display_width || y_end != display_height) { + LOG_W(TAG, "draw_bitmap: only full-frame draws are supported (got %ld,%ld..%ld,%ld)", (long)x_start, (long)y_start, (long)x_end, (long)y_end); + return ERROR_NOT_SUPPORTED; + } + + xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); + + if (!internal->display_on) { + // Display is off (deep sleep). Drop the frame; the mismatch is fine because the next + // power-on triggers a full refresh of the current render anyway. + xSemaphoreGive(internal->panel_mutex); + return ERROR_NONE; + } + + const uint8_t* source; + if (config->rotation == 0) { + source = static_cast(color_data); + } else { + memset(internal->rotate_buffer, 0, internal->info.buffer_size); + esp_epaper_rotate_frame(static_cast(color_data), internal->rotate_buffer, + internal->info.width, internal->info.height, config->rotation); + source = internal->rotate_buffer; + } + + const esp_err_t ret = epd_update(internal->epd, source, config->update_mode); + xSemaphoreGive(internal->panel_mutex); + if (ret != ESP_OK) { + LOG_E(TAG, "epd_update failed: %s", esp_err_to_name(ret)); + return ERROR_RESOURCE; + } + return ERROR_NONE; +} + +static error_t esp_epaper_disp_on_off(Device* device, bool on_off) { + auto* internal = static_cast(device_get_driver_data(device)); + + xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); + if (on_off == internal->display_on) { + xSemaphoreGive(internal->panel_mutex); + return ERROR_NONE; + } + + bool ok = true; + if (on_off) { + if (epd_wake(internal->epd) != ESP_OK) { + LOG_E(TAG, "epd_wake failed"); + ok = false; + } + } else { + if (epd_sleep(internal->epd) != ESP_OK) { + LOG_E(TAG, "epd_sleep failed"); + ok = false; + } + } + + if (ok) { + internal->display_on = on_off; + } + xSemaphoreGive(internal->panel_mutex); + return ok ? ERROR_NONE : ERROR_RESOURCE; +} + +static DisplayColorFormat esp_epaper_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_MONOCHROME; +} + +static uint16_t esp_epaper_get_resolution_x(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_epaper_get_display_width(internal, GET_CONFIG(device)->rotation); +} + +static uint16_t esp_epaper_get_resolution_y(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + return esp_epaper_get_display_height(internal, GET_CONFIG(device)->rotation); +} + +static void esp_epaper_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t esp_epaper_get_frame_buffer_count(Device*) { + return 0; +} + +static const DisplayApi esp_epaper_display_api = { + .capabilities = DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_SLOW_REFRESH, + .reset = esp_epaper_reset, + .init = esp_epaper_init, + .draw_bitmap = esp_epaper_draw_bitmap, + .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 = esp_epaper_disp_on_off, + .disp_sleep = nullptr, + .get_color_format = esp_epaper_get_color_format, + .get_resolution_x = esp_epaper_get_resolution_x, + .get_resolution_y = esp_epaper_get_resolution_y, + .get_frame_buffer = esp_epaper_get_frame_buffer, + .get_frame_buffer_count = esp_epaper_get_frame_buffer_count, + .get_backlight = nullptr, + .has_capability = nullptr, +}; + +static void free_internal(EspEpaperInternal* internal) { + if (internal->epd != nullptr) { + epd_deinit(internal->epd); + } + if (internal->rotate_buffer != nullptr) { + free(internal->rotate_buffer); + } + if (internal->panel_mutex != nullptr) { + vSemaphoreDelete(internal->panel_mutex); + } + free(internal); +} + +static error_t start(Device* device) { + auto* parent = device_get_parent(device); + check(device_get_type(parent) == &SPI_CONTROLLER_TYPE); + + const auto* spi_config = static_cast(parent->config); + const auto* config = GET_CONFIG(device); + + epd_panel_type_t panel_type; + if (!resolve_panel_type(config->panel_type, &panel_type)) { + LOG_E(TAG, "Unknown panel type: %s", config->panel_type); + return ERROR_NOT_SUPPORTED; + } + + if (config->clock_speed_hz == 0) { + LOG_E(TAG, "Invalid clock_speed_hz (0)"); + return ERROR_NOT_SUPPORTED; + } + + if (config->update_mode > EPD_UPDATE_PARTIAL) { + LOG_E(TAG, "Invalid update_mode %d (expected %d-%d)", (int)config->update_mode, (int)EPD_UPDATE_FULL, (int)EPD_UPDATE_PARTIAL); + return ERROR_NOT_SUPPORTED; + } + + if (config->width > MAX_PANEL_DIMENSION || config->height > MAX_PANEL_DIMENSION) { + LOG_E(TAG, "Invalid panel size %ux%u (maximum %ux%u)", (unsigned)config->width, (unsigned)config->height, (unsigned)MAX_PANEL_DIMENSION, (unsigned)MAX_PANEL_DIMENSION); + return ERROR_NOT_SUPPORTED; + } + + epd_config_t epd_config = EPD_CONFIG_DEFAULT(); + epd_config.pins.busy = config->pin_busy.pin; + epd_config.pins.rst = config->pin_reset.pin; + epd_config.pins.dc = config->pin_dc.pin; + epd_config.pins.cs = config->pin_cs.pin; + epd_config.pins.sck = spi_config->pin_sclk.pin; + epd_config.pins.mosi = spi_config->pin_mosi.pin; + epd_config.spi.host = spi_config->host; + epd_config.spi.speed_hz = config->clock_speed_hz; + epd_config.panel.type = panel_type; + epd_config.panel.width = config->width; + epd_config.panel.height = config->height; + + epd_handle_t epd = nullptr; + if (epd_init(&epd_config, &epd) != ESP_OK) { + LOG_E(TAG, "epd_init failed"); + return ERROR_RESOURCE; + } + + auto* internal = static_cast(calloc(1, sizeof(EspEpaperInternal))); + if (internal == nullptr) { + epd_deinit(epd); + return ERROR_OUT_OF_MEMORY; + } + + internal->epd = epd; + internal->panel_mutex = xSemaphoreCreateMutex(); + if (internal->panel_mutex == nullptr) { + free_internal(internal); + return ERROR_OUT_OF_MEMORY; + } + + if (epd_get_info(epd, &internal->info) != ESP_OK) { + LOG_E(TAG, "epd_get_info failed"); + free_internal(internal); + return ERROR_RESOURCE; + } + + if (internal->info.color_mode != EPD_COLOR_BW) { + LOG_E(TAG, "Panel color mode %d is not supported by the kernel display bridge (monochrome only)", internal->info.color_mode); + free_internal(internal); + return ERROR_NOT_SUPPORTED; + } + + if (config->rotation > 3) { + LOG_E(TAG, "Invalid rotation %u (expected 0-3)", config->rotation); + free_internal(internal); + return ERROR_NOT_SUPPORTED; + } + + if (config->rotation != 0) { + internal->rotate_buffer = static_cast(malloc(internal->info.buffer_size)); + if (internal->rotate_buffer == nullptr) { + LOG_E(TAG, "Failed to allocate %lu-byte rotation buffer", internal->info.buffer_size); + free_internal(internal); + return ERROR_OUT_OF_MEMORY; + } + } + + internal->display_on = true; + device_set_driver_data(device, internal); + + LOG_I(TAG, "Started %ux%u panel (buffer %lu bytes, rotation %u)", internal->info.width, internal->info.height, internal->info.buffer_size, config->rotation); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + xSemaphoreTake(internal->panel_mutex, portMAX_DELAY); + if (internal->display_on) { + // Leave the panel in deep sleep. + epd_sleep(internal->epd); + internal->display_on = false; + } + xSemaphoreGive(internal->panel_mutex); + + free_internal(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +Driver esp_epaper_driver = { + .name = "esp_epaper", + .compatible = (const char*[]) { "tuanpmt,esp-epaper", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &esp_epaper_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &esp_epaper_module, + .internal = nullptr +}; diff --git a/Drivers/esp-epaper-module/source/esp_epaper_rotate.h b/Drivers/esp-epaper-module/source/esp_epaper_rotate.h new file mode 100644 index 000000000..1cd6ae48f --- /dev/null +++ b/Drivers/esp-epaper-module/source/esp_epaper_rotate.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Rotates a full 1bpp frame (row-major, MSB-first) from the display's rotated + * space into the panel's native space. The display is the panel's native + * width x height turned counter-clockwise by `rotation` (0 = 0, 1 = 90, 2 = 180, + * 3 = 270 degrees), so its own dimensions are the swapped native ones for + * 90/270. Each mapping is the inverse of LVGL's lv_display_rotate_area() + * (Libraries/lvgl/src/display/lv_display.c), i.e. the exact transform LVGL + * applies when displaying on the panel. + * + * src points to the display frame (stride = ceil(display_width / 8) bytes, + * MSB-first bits), dst to the native frame (stride = ceil(width / 8) bytes). + * dst is overwritten; it may not alias src. + */ +static inline bool esp_epaper_rotation_swaps_axes(uint8_t rotation) { + return rotation == 1 || rotation == 3; +} + +static inline void esp_epaper_rotate_frame(const uint8_t* src, uint8_t* dst, uint16_t width, uint16_t height, uint8_t rotation) { + const uint16_t display_width = esp_epaper_rotation_swaps_axes(rotation) ? height : width; + const uint32_t src_stride = (display_width + 7) / 8; + const uint32_t dst_stride = (width + 7) / 8; + + memset(dst, 0, dst_stride * height); + + for (uint16_t y = 0; y < height; y++) { + for (uint16_t x = 0; x < width; x++) { + // Display pixel (u, v) that lands on native pixel (x, y). + uint16_t u; + uint16_t v; + switch (rotation) { + case 0: + u = x; + v = y; + break; + case 1: + u = height - 1 - y; + v = x; + break; + case 2: + u = width - 1 - x; + v = height - 1 - y; + break; + case 3: + default: + u = y; + v = width - 1 - x; + break; + } + if (src[(uint32_t)v * src_stride + u / 8] & (0x80 >> (u % 8))) { + dst[(uint32_t)y * dst_stride + x / 8] |= (0x80 >> (x % 8)); + } + } + } +} + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/esp-epaper-module/source/module.cpp b/Drivers/esp-epaper-module/source/module.cpp new file mode 100644 index 000000000..9a24dde3f --- /dev/null +++ b/Drivers/esp-epaper-module/source/module.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +extern Driver esp_epaper_driver; + +extern "C" { + +static Driver* const esp_epaper_drivers[] = { + &esp_epaper_driver, + nullptr +}; + +Module esp_epaper_module = { + .name = "esp_epaper", + .drivers = esp_epaper_drivers +}; + +} // extern "C" diff --git a/Libraries/esp_epaper b/Libraries/esp_epaper new file mode 160000 index 000000000..0bf4f144b --- /dev/null +++ b/Libraries/esp_epaper @@ -0,0 +1 @@ +Subproject commit 0bf4f144b543048f10bdadd49f0578858bdc2a39 diff --git a/Tactility/Source/lvgl/UsbHidInput.cpp b/Tactility/Source/lvgl/UsbHidInput.cpp index 731046a19..87f3f8449 100644 --- a/Tactility/Source/lvgl/UsbHidInput.cpp +++ b/Tactility/Source/lvgl/UsbHidInput.cpp @@ -155,16 +155,24 @@ static void usbHidInputTask(void* arg) { lvgl_lock(); - ctx->mouse_cursor = lv_image_create(lv_layer_sys()); - lv_obj_remove_flag(ctx->mouse_cursor, LV_OBJ_FLAG_CLICKABLE); - lv_image_set_src(ctx->mouse_cursor, TT_ASSETS_UI_CURSOR); - lv_obj_add_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN); + // Without a registered display, lv_layer_sys() is NULL: creating the cursor image on it trips + // an LVGL assert whose default handler is an infinite loop (while(1);), hanging this task while + // it holds the LVGL lock. Only create the cursor when a system layer actually exists. + lv_obj_t* sys_layer = lv_layer_sys(); + if (sys_layer != nullptr) { + ctx->mouse_cursor = lv_image_create(sys_layer); + lv_obj_remove_flag(ctx->mouse_cursor, LV_OBJ_FLAG_CLICKABLE); + lv_image_set_src(ctx->mouse_cursor, TT_ASSETS_UI_CURSOR); + lv_obj_add_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN); + } ctx->mouse_indev = lv_indev_create(); lv_indev_set_type(ctx->mouse_indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(ctx->mouse_indev, mouse_read_cb); lv_indev_set_user_data(ctx->mouse_indev, ctx); - lv_indev_set_cursor(ctx->mouse_indev, ctx->mouse_cursor); + if (ctx->mouse_cursor != nullptr) { + lv_indev_set_cursor(ctx->mouse_indev, ctx->mouse_cursor); + } ctx->kb_indev = lv_indev_create(); lv_indev_set_type(ctx->kb_indev, LV_INDEV_TYPE_KEYPAD); diff --git a/device.py b/device.py index f97a51999..448908258 100644 --- a/device.py +++ b/device.py @@ -254,6 +254,11 @@ def write_lvgl_variables(output_file, device_properties: dict): output_file.write("CONFIG_LV_THEME_DEFAULT_LIGHT=y\n") elif theme == "Mono": output_file.write("CONFIG_LV_USE_THEME_MONO=y\n") + # LVGL selects the theme with #if LV_USE_THEME_DEFAULT / #elif LV_USE_THEME_SIMPLE / + # #elif LV_USE_THEME_MONO, and the Kconfig defaults enable DEFAULT+SIMPLE for any + # non-1bpp color depth so MONO only takes effect once those are disabled. + output_file.write("CONFIG_LV_USE_THEME_DEFAULT=n\n") + output_file.write("CONFIG_LV_USE_THEME_SIMPLE=n\n") else: exit_with_error(f"Unknown theme: {theme}") font_height_text = get_property_or_default(device_properties, "lvgl.fontSize", "14") diff --git a/partitions-16mb-no-sd-dev.csv b/partitions-16mb-no-sd-dev.csv index 497ffa1ef..5d69f3878 100644 --- a/partitions-16mb-no-sd-dev.csv +++ b/partitions-16mb-no-sd-dev.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 100k, +system, data, fat, , 128k, data, data, fat, , 1000k, diff --git a/partitions-16mb-no-sd.csv b/partitions-16mb-no-sd.csv index daeee992d..65eeb5a8a 100644 --- a/partitions-16mb-no-sd.csv +++ b/partitions-16mb-no-sd.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 100k, +system, data, fat, , 128k, data, data, fat, , 11800k, diff --git a/partitions-16mb-with-sd.csv b/partitions-16mb-with-sd.csv index 262186df8..6e011704c 100644 --- a/partitions-16mb-with-sd.csv +++ b/partitions-16mb-with-sd.csv @@ -3,4 +3,4 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 100k, +system, data, fat, , 128k, diff --git a/partitions-4mb-with-sd.csv b/partitions-4mb-with-sd.csv index 59d1243b4..bf83015c4 100644 --- a/partitions-4mb-with-sd.csv +++ b/partitions-4mb-with-sd.csv @@ -3,4 +3,4 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 3800k, -system, data, fat, , 100k, +system, data, fat, , 128k, diff --git a/partitions-8mb-no-sd-dev.csv b/partitions-8mb-no-sd-dev.csv index 497ffa1ef..5d69f3878 100644 --- a/partitions-8mb-no-sd-dev.csv +++ b/partitions-8mb-no-sd-dev.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 100k, +system, data, fat, , 128k, data, data, fat, , 1000k, diff --git a/partitions-8mb-no-sd.csv b/partitions-8mb-no-sd.csv index 79544b13d..bdc40599e 100644 --- a/partitions-8mb-no-sd.csv +++ b/partitions-8mb-no-sd.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 100k, +system, data, fat, , 128k, data, data, fat, , 3800k, diff --git a/partitions-8mb-with-sd.csv b/partitions-8mb-with-sd.csv index 262186df8..6e011704c 100644 --- a/partitions-8mb-with-sd.csv +++ b/partitions-8mb-with-sd.csv @@ -3,4 +3,4 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 4M, -system, data, fat, , 100k, +system, data, fat, , 128k,