From 6c537e99a2cd935a04cc724f551ff8773ac4653c Mon Sep 17 00:00:00 2001 From: "Bairan.Zhang" Date: Mon, 13 Jul 2026 16:56:03 +0800 Subject: [PATCH] feat: add ESP32-S3 ST7789 LCD and GT911 wiring test --- .gitignore | 9 + 2.jpg | Bin 0 -> 14999 bytes CMakeLists.txt | 6 + README.md | 100 +++++ dependencies.lock | 112 ++++++ main/2_rgb565.bin | 82 ++++ main/CMakeLists.txt | 3 + main/Kconfig.projbuild | 169 ++++++++ main/assets/2_rgb565.bin | 75 ++++ main/esp_lcd_st7735.c | 319 +++++++++++++++ main/esp_lcd_st7735.h | 34 ++ main/idf_component.yml | 7 + main/lvgl_demo_ui.c | 56 +++ main/spi_lcd_touch_example_main.c | 618 ++++++++++++++++++++++++++++++ sdkconfig.defaults | 17 + 15 files changed, 1607 insertions(+) create mode 100644 .gitignore create mode 100644 2.jpg create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 dependencies.lock create mode 100644 main/2_rgb565.bin create mode 100644 main/CMakeLists.txt create mode 100644 main/Kconfig.projbuild create mode 100644 main/assets/2_rgb565.bin create mode 100644 main/esp_lcd_st7735.c create mode 100644 main/esp_lcd_st7735.h create mode 100644 main/idf_component.yml create mode 100644 main/lvgl_demo_ui.c create mode 100644 main/spi_lcd_touch_example_main.c create mode 100644 sdkconfig.defaults diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5fd5082 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +build/ +managed_components/ +sdkconfig +sdkconfig.old +tmp/ +work/ +.codex/ +.agents/ +.vscode/ diff --git a/2.jpg b/2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..57a4e4d92ef406c98c52015c182e00687795208a GIT binary patch literal 14999 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 Y|LJ&J%+i7F`U&)1X9I#Z)j|3H0laBTH~;_u literal 0 HcmV?d00001 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0ee65a9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(touch-lcd) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b984c0e --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | + +# SPI LCD and Touch Panel Example + +[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) provides several panel drivers out-of box, e.g. ST7789, SSD1306, NT35510. However, there're a lot of other panels on the market, it's beyond `esp_lcd` component's responsibility to include them all. + +`esp_lcd` allows user to add their own panel drivers in the project scope (i.e. panel driver can live outside of esp-idf), so that the upper layer code like LVGL porting code can be reused without any modifications, as long as user-implemented panel driver follows the interface defined in the `esp_lcd` component. + +This example shows how to use GC9A01 or ILI9341 display driver from Component manager in esp-idf project. These components are using API provided by `esp_lcd` component. This example will draw a fancy dash board with the LVGL library. + +This example uses the [esp_timer](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html) to generate the ticks needed by LVGL and uses a dedicated task to run the `lv_timer_handler()`. Since the LVGL APIs are not thread-safe, this example uses a mutex which be invoked before the call of `lv_timer_handler()` and released after it. The same mutex needs to be used in other tasks and threads around every LVGL (lv_...) related function call and code. For more porting guides, please refer to [LVGL porting doc](https://docs.lvgl.io/master/porting/index.html). + +## Touch controller STMPE610 + +In this example you can enable touch controller STMPE610 connected via SPI. The SPI connection is shared with LCD screen. + +## How to use the example + +### Hardware Required + +* An ESP development board +* An GC9A01 or ILI9341 LCD panel, with SPI interface (with/without STMPE610 SPI touch) +* An USB cable for power supply and programming + +### Hardware Connection + +The connection between ESP Board and the LCD is as follows: + +``` + ESP Board GC9A01/ILI9341 Panel + TOUCH +┌──────────────────────┐ ┌────────────────────┐ +│ GND ├─────────────►│ GND │ +│ │ │ │ +│ 3V3 ├─────────────►│ VCC │ +│ │ │ │ +│ PCLK ├─────────────►│ SCL │ +│ │ │ │ +│ MOSI ├─────────────►│ MOSI │ +│ │ │ │ +│ MISO |◄─────────────┤ MISO │ +│ │ │ │ +│ RST ├─────────────►│ RES │ +│ │ │ │ +│ DC ├─────────────►│ DC │ +│ │ │ │ +│ LCD CS ├─────────────►│ LCD CS │ +│ │ │ │ +│ TOUCH CS ├─────────────►│ TOUCH CS │ +│ │ │ │ +│ BK_LIGHT ├─────────────►│ BLK │ +└──────────────────────┘ └────────────────────┘ +``` + +The GPIO number used by this example can be changed in [lvgl_example_main.c](main/spi_lcd_touch_example_main.c). +Especially, please pay attention to the level used to turn on the LCD backlight, some LCD module needs a low level to turn it on, while others take a high level. You can change the backlight level macro `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL` in [lvgl_example_main.c](main/spi_lcd_touch_example_main.c). + +### Build and Flash + +Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A fancy animation will show up on the LCD as expected. + +The first time you run `idf.py` for the example will cost extra time as the build system needs to address the component dependencies and downloads the missing components from the ESP Component Registry into `managed_components` folder. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +### Example Output + +```bash +... +I (409) cpu_start: Starting scheduler on APP CPU. +I (419) example: Turn off LCD backlight +I (419) gpio: GPIO[2]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (429) example: Initialize SPI bus +I (439) example: Install panel IO +I (439) gpio: GPIO[5]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (449) example: Install GC9A01 panel driver +I (459) gpio: GPIO[3]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (589) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (589) example: Initialize touch controller STMPE610 +I (589) STMPE610: TouchPad ID: 0x0811 +I (589) STMPE610: TouchPad Ver: 0x03 +I (599) example: Turn on LCD backlight +I (599) example: Initialize LVGL library +I (609) example: Register display driver to LVGL +I (619) example: Install LVGL tick timer +I (619) example: Starting LVGL task +I (619) example: Display LVGL animation +I (619) example: Display LVGL Meter Widget +... +``` + + +## Troubleshooting + +* Why the LCD doesn't light up? + * Check the backlight's turn-on level, and update it in `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL` + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..d39c403 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,112 @@ +dependencies: + atanisoft/esp_lcd_touch_xpt2046: + component_hash: 94c1d199922bc7468b5e14912ab0eb053a4a5a25680ce8a4ca2f4f18febbb01a + dependencies: + - name: idf + require: private + version: '>=4.4' + - name: espressif/esp_lcd_touch + registry_url: https://components.espressif.com + require: private + version: '>=1.0.4' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.0.5 + espressif/cmake_utilities: + component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f + dependencies: + - name: idf + require: private + version: '>=4.1' + source: + registry_url: https://components.espressif.com + type: service + version: 0.5.3 + espressif/esp_lcd_gc9a01: + component_hash: 2ccbdfa5a1762c7a8be66770b69a256d809651d6069098852d2c829648707b5b + dependencies: + - name: idf + require: private + version: '>=4.4' + - name: espressif/cmake_utilities + registry_url: https://components.espressif.com + require: private + version: 0.* + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.2.0 + espressif/esp_lcd_ili9341: + component_hash: 31f1b793aa2110dd2ae071c21ccbff0a4eb20d9a4ee40b6294c0dc0ad9552c4e + dependencies: + - name: idf + require: private + version: '>=4.4' + - name: espressif/cmake_utilities + registry_url: https://components.espressif.com + require: private + version: 0.* + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.2.0 + espressif/esp_lcd_touch: + component_hash: 3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862 + dependencies: + - name: idf + require: private + version: '>=4.4.2' + source: + registry_url: https://components.espressif.com + type: service + version: 1.2.1 + espressif/esp_lcd_touch_gt911: + component_hash: 664c544533b91497f29b41e045bb4374a0d4d002f9d8c67c7c1bf43fb4179e3f + dependencies: + - name: espressif/esp_lcd_touch + registry_url: https://components.espressif.com + require: public + version: ^1.1.0 + - name: idf + require: private + version: '>=4.4.2' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.1 + espressif/esp_lcd_touch_stmpe610: + component_hash: d8adce47eabe3f8caa23c5acf03cfefe78012628abd974673ccb9a577c3d738b + dependencies: + - name: espressif/esp_lcd_touch + registry_url: https://components.espressif.com + require: public + version: ^1.2.0 + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.0.8 + idf: + source: + type: idf + version: 5.5.4 + lvgl/lvgl: + component_hash: 2409fdc84e9766d2a18afd84b051aef3f838348136cdb1c10ac2e8bcdb012bf6 + dependencies: [] + source: + registry_url: https://components.espressif.com/ + type: service + version: 9.2.0 +direct_dependencies: +- atanisoft/esp_lcd_touch_xpt2046 +- espressif/esp_lcd_gc9a01 +- espressif/esp_lcd_ili9341 +- espressif/esp_lcd_touch_gt911 +- espressif/esp_lcd_touch_stmpe610 +- lvgl/lvgl +manifest_hash: 8cf9f661b2064254ff1e1958c26de271dfe5b3094fef2a6cf74d3f3f64c506f0 +target: esp32s3 +version: 2.0.0 diff --git a/main/2_rgb565.bin b/main/2_rgb565.bin new file mode 100644 index 0000000..9e77c81 --- /dev/null +++ b/main/2_rgb565.bin @@ -0,0 +1,82 @@ +[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M[U[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M[M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M[M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;MZU;MZU;MZU;MZU;MZU;MZU;MZU;MZU;M[U;M[U;M[U;M[U;M[U;M[U;MZU;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U;M[U[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[M;M[U;M[M;M\E\E{E\E[M[E[M[E[M[EZU[U:U;U;];U[U;MZU;MZMZMZMZMZM[M[M +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_lcd_panel_commands.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_log.h" + +#include "esp_lcd_st7735.h" + +#define ST7735_CMD_FRMCTR1 0xB1 +#define ST7735_CMD_FRMCTR2 0xB2 +#define ST7735_CMD_FRMCTR3 0xB3 +#define ST7735_CMD_INVCTR 0xB4 +#define ST7735_CMD_PWCTR1 0xC0 +#define ST7735_CMD_PWCTR2 0xC1 +#define ST7735_CMD_PWCTR3 0xC2 +#define ST7735_CMD_PWCTR4 0xC3 +#define ST7735_CMD_PWCTR5 0xC4 +#define ST7735_CMD_VMCTR1 0xC5 +#define ST7735_CMD_GMCTRP1 0xE0 +#define ST7735_CMD_GMCTRN1 0xE1 + +static const char *TAG = "st7735"; + +static esp_err_t panel_st7735_del(esp_lcd_panel_t *panel); +static esp_err_t panel_st7735_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_st7735_init(esp_lcd_panel_t *panel); +static esp_err_t panel_st7735_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, + const void *color_data); +static esp_err_t panel_st7735_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_st7735_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_st7735_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_st7735_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_st7735_disp_on_off(esp_lcd_panel_t *panel, bool on_off); +static esp_err_t panel_st7735_sleep(esp_lcd_panel_t *panel, bool sleep); + +typedef struct { + uint8_t cmd; + const void *data; + size_t data_bytes; + unsigned int delay_ms; +} st7735_lcd_init_cmd_t; + +typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; + uint8_t colmod_val; +} st7735_panel_t; + +static const st7735_lcd_init_cmd_t vendor_specific_init_default[] = { + {ST7735_CMD_FRMCTR1, (uint8_t []){0x01, 0x2C, 0x2D}, 3, 0}, + {ST7735_CMD_FRMCTR2, (uint8_t []){0x01, 0x2C, 0x2D}, 3, 0}, + {ST7735_CMD_FRMCTR3, (uint8_t []){0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D}, 6, 0}, + {ST7735_CMD_INVCTR, (uint8_t []){0x07}, 1, 0}, + {ST7735_CMD_PWCTR1, (uint8_t []){0xA2, 0x02, 0x84}, 3, 0}, + {ST7735_CMD_PWCTR2, (uint8_t []){0xC5}, 1, 0}, + {ST7735_CMD_PWCTR3, (uint8_t []){0x0A, 0x00}, 2, 0}, + {ST7735_CMD_PWCTR4, (uint8_t []){0x8A, 0x2A}, 2, 0}, + {ST7735_CMD_PWCTR5, (uint8_t []){0x8A, 0xEE}, 2, 0}, + {ST7735_CMD_VMCTR1, (uint8_t []){0x0E}, 1, 0}, + {ST7735_CMD_GMCTRP1, (uint8_t []){0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, + 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10}, 16, 0}, + {ST7735_CMD_GMCTRN1, (uint8_t []){0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, + 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10}, 16, 0}, +}; + +esp_err_t esp_lcd_new_panel_st7735(const esp_lcd_panel_io_handle_t io, + const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel) +{ + esp_err_t ret = ESP_OK; + st7735_panel_t *st7735 = NULL; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + st7735 = calloc(1, sizeof(st7735_panel_t)); + ESP_GOTO_ON_FALSE(st7735, ESP_ERR_NO_MEM, err, TAG, "no mem for st7735 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + switch (panel_dev_config->rgb_ele_order) { + case LCD_RGB_ELEMENT_ORDER_RGB: + st7735->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + st7735->madctl_val = LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported RGB element order"); + break; + } + + switch (panel_dev_config->bits_per_pixel) { + case 16: + st7735->colmod_val = 0x05; + st7735->fb_bits_per_pixel = 16; + break; + case 18: + st7735->colmod_val = 0x06; + st7735->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + st7735->io = io; + st7735->reset_gpio_num = panel_dev_config->reset_gpio_num; + st7735->reset_level = panel_dev_config->flags.reset_active_high; + st7735->base.del = panel_st7735_del; + st7735->base.reset = panel_st7735_reset; + st7735->base.init = panel_st7735_init; + st7735->base.draw_bitmap = panel_st7735_draw_bitmap; + st7735->base.invert_color = panel_st7735_invert_color; + st7735->base.set_gap = panel_st7735_set_gap; + st7735->base.mirror = panel_st7735_mirror; + st7735->base.swap_xy = panel_st7735_swap_xy; + st7735->base.disp_on_off = panel_st7735_disp_on_off; + st7735->base.disp_sleep = panel_st7735_sleep; + + *ret_panel = &(st7735->base); + ESP_LOGD(TAG, "new st7735 panel @%p", st7735); + return ESP_OK; + +err: + if (st7735) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(st7735); + } + return ret; +} + +static esp_err_t panel_st7735_del(esp_lcd_panel_t *panel) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + + if (st7735->reset_gpio_num >= 0) { + gpio_reset_pin(st7735->reset_gpio_num); + } + ESP_LOGD(TAG, "del st7735 panel @%p", st7735); + free(st7735); + return ESP_OK; +} + +static esp_err_t panel_st7735_reset(esp_lcd_panel_t *panel) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + esp_lcd_panel_io_handle_t io = st7735->io; + + if (st7735->reset_gpio_num >= 0) { + gpio_set_level(st7735->reset_gpio_num, st7735->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(st7735->reset_gpio_num, !st7735->reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + return ESP_OK; +} + +static esp_err_t panel_st7735_init(esp_lcd_panel_t *panel) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + esp_lcd_panel_io_handle_t io = st7735->io; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) { + st7735->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t []) { + st7735->colmod_val, + }, 1), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + + for (int i = 0; i < sizeof(vendor_specific_init_default) / sizeof(vendor_specific_init_default[0]); i++) { + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, vendor_specific_init_default[i].cmd, + vendor_specific_init_default[i].data, + vendor_specific_init_default[i].data_bytes), + TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(vendor_specific_init_default[i].delay_ms)); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_NORON, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + return ESP_OK; +} + +static esp_err_t panel_st7735_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, + const void *color_data) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = st7735->io; + + x_start += st7735->x_gap; + x_end += st7735->x_gap; + y_start += st7735->y_gap; + y_end += st7735->y_gap; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t []) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t []) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + + size_t len = (x_end - x_start) * (y_end - y_start) * st7735->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed"); + return ESP_OK; +} + +static esp_err_t panel_st7735_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + int command = invert_color_data ? LCD_CMD_INVON : LCD_CMD_INVOFF; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(st7735->io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7735_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + + if (mirror_x) { + st7735->madctl_val |= LCD_CMD_MX_BIT; + } else { + st7735->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) { + st7735->madctl_val |= LCD_CMD_MY_BIT; + } else { + st7735->madctl_val &= ~LCD_CMD_MY_BIT; + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(st7735->io, LCD_CMD_MADCTL, (uint8_t []) { + st7735->madctl_val, + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7735_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + + if (swap_axes) { + st7735->madctl_val |= LCD_CMD_MV_BIT; + } else { + st7735->madctl_val &= ~LCD_CMD_MV_BIT; + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(st7735->io, LCD_CMD_MADCTL, (uint8_t []) { + st7735->madctl_val, + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7735_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + + st7735->x_gap = x_gap; + st7735->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_st7735_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + int command = on_off ? LCD_CMD_DISPON : LCD_CMD_DISPOFF; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(st7735->io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7735_sleep(esp_lcd_panel_t *panel, bool sleep) +{ + st7735_panel_t *st7735 = __containerof(panel, st7735_panel_t, base); + int command = sleep ? LCD_CMD_SLPIN : LCD_CMD_SLPOUT; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(st7735->io, command, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + return ESP_OK; +} diff --git a/main/esp_lcd_st7735.h b/main/esp_lcd_st7735.h new file mode 100644 index 0000000..f9c7b78 --- /dev/null +++ b/main/esp_lcd_st7735.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2026 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create LCD panel for ST7735. + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config general panel device configuration + * @param[out] ret_panel returned LCD panel handle + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if parameters are invalid + * - ESP_ERR_NO_MEM if out of memory + * - ESP_ERR_NOT_SUPPORTED if the requested pixel format is unsupported + */ +esp_err_t esp_lcd_new_panel_st7735(const esp_lcd_panel_io_handle_t io, + const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel); + +#ifdef __cplusplus +} +#endif diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..126429c --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + lvgl/lvgl: 9.2.0 + esp_lcd_ili9341: ^1.0 + esp_lcd_gc9a01: ^1.0 + esp_lcd_touch_stmpe610: ^1.0 + atanisoft/esp_lcd_touch_xpt2046: 1.0.5 + espressif/esp_lcd_touch_gt911: ==1.1.1 diff --git a/main/lvgl_demo_ui.c b/main/lvgl_demo_ui.c new file mode 100644 index 0000000..a47d76f --- /dev/null +++ b/main/lvgl_demo_ui.c @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +// This demo UI is adapted from LVGL official example: https://docs.lvgl.io/master/examples.html#loader-with-arc + +#include "lvgl.h" + +static lv_obj_t * btn; +static lv_display_rotation_t rotation = LV_DISP_ROTATION_0; + +static void btn_cb(lv_event_t * e) +{ + lv_display_t *disp = lv_event_get_user_data(e); + rotation++; + if (rotation > LV_DISP_ROTATION_270) { + rotation = LV_DISP_ROTATION_0; + } + lv_disp_set_rotation(disp, rotation); +} +static void set_angle(void * obj, int32_t v) +{ + lv_arc_set_value(obj, v); +} + +void example_lvgl_demo_ui(lv_display_t *disp) +{ + lv_obj_t *scr = lv_display_get_screen_active(disp); + + btn = lv_button_create(scr); + lv_obj_t * lbl = lv_label_create(btn); + lv_label_set_text_static(lbl, LV_SYMBOL_REFRESH" ROTATE"); + lv_obj_align(btn, LV_ALIGN_BOTTOM_LEFT, 30, -30); + /*Button event*/ + lv_obj_add_event_cb(btn, btn_cb, LV_EVENT_CLICKED, disp); + + /*Create an Arc*/ + lv_obj_t * arc = lv_arc_create(scr); + lv_arc_set_rotation(arc, 270); + lv_arc_set_bg_angles(arc, 0, 360); + lv_obj_remove_style(arc, NULL, LV_PART_KNOB); /*Be sure the knob is not displayed*/ + lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE); /*To not allow adjusting by click*/ + lv_obj_center(arc); + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, arc); + lv_anim_set_exec_cb(&a, set_angle); + lv_anim_set_duration(&a, 1000); + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); /*Just for the demo*/ + lv_anim_set_repeat_delay(&a, 500); + lv_anim_set_values(&a, 0, 100); + lv_anim_start(&a); +} diff --git a/main/spi_lcd_touch_example_main.c b/main/spi_lcd_touch_example_main.c new file mode 100644 index 0000000..cfea175 --- /dev/null +++ b/main/spi_lcd_touch_example_main.c @@ -0,0 +1,618 @@ +/* + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_master.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_lcd_io_i2c.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_touch.h" +#include "esp_lcd_touch_gt911.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "lvgl.h" +#include "demos/lv_demos.h" + +static const char *TAG = "lvgl_touch"; + +#define LCD_SPI_HOST SPI2_HOST +#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000) +#define EXAMPLE_LCD_CMD_BITS 8 +#define EXAMPLE_LCD_PARAM_BITS 8 +#define EXAMPLE_LVGL_TICK_PERIOD_MS 2 +#define EXAMPLE_LVGL_BUFFER_LINES 80 +#define EXAMPLE_TOUCH_I2C_FREQ_HZ (100 * 1000) +#define EXAMPLE_TOUCH_LOG_PERIOD_MS 120 +#define EXAMPLE_TOUCH_PROBE_TIMEOUT_MS 30 + +// Current adapter wiring: +// LCD: CLK->IO6, MOSI/SDA->IO7, RES->IO15, DC->IO16, CS->IO5, BLK->IO17. +// MISO is not required for LCD write-only testing. +// GT911 touch: T_SCL->IO4, T_SDA->IO18, T_INT->IO8, T_RES->IO9. +#define EXAMPLE_PIN_NUM_SCLK 6 +#define EXAMPLE_PIN_NUM_MOSI 7 +#define EXAMPLE_PIN_NUM_MISO GPIO_NUM_NC +#define EXAMPLE_PIN_NUM_LCD_CS 5 +#define EXAMPLE_PIN_NUM_LCD_DC 16 +#define EXAMPLE_PIN_NUM_LCD_RST 15 +#define EXAMPLE_PIN_NUM_BK_LIGHT 17 +#define EXAMPLE_PIN_NUM_TOUCH_SCL 4 +#define EXAMPLE_PIN_NUM_TOUCH_SDA 18 +#define EXAMPLE_PIN_NUM_TOUCH_IRQ 8 +#define EXAMPLE_PIN_NUM_TOUCH_RST 9 +#define EXAMPLE_LCD_H_RES CONFIG_EXAMPLE_LCD_ST7735_H_RES +#define EXAMPLE_LCD_V_RES CONFIG_EXAMPLE_LCD_ST7735_V_RES +#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1 +#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL + +static esp_lcd_panel_handle_t s_panel; +static esp_lcd_touch_handle_t s_touch; +static i2c_master_bus_handle_t s_i2c_bus; +static uint8_t *s_image_buffer; + +extern const uint8_t image_rgb565_start[] asm("_binary_2_rgb565_bin_start"); +extern const uint8_t image_rgb565_end[] asm("_binary_2_rgb565_bin_end"); +static lv_obj_t *s_coord_label; +static lv_obj_t *s_status_label; +#if !LV_USE_DEMO_WIDGETS +static lv_obj_t *s_button_label; +static lv_obj_t *s_slider_label; +static int s_button_count; +#endif + +static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, + void *user_ctx) +{ + (void)panel_io; + (void)edata; + lv_display_flush_ready((lv_display_t *)user_ctx); + return false; +} + +static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) +{ + esp_err_t ret = esp_lcd_panel_draw_bitmap(s_panel, area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "LCD flush failed: %s", esp_err_to_name(ret)); + lv_display_flush_ready(disp); + } +} + +static void lvgl_touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) +{ + (void)indev; + esp_lcd_touch_point_data_t point[CONFIG_ESP_LCD_TOUCH_MAX_POINTS]; + uint8_t point_num = 0; + static bool was_pressed = false; + static int64_t last_log_ms = 0; + static uint8_t last_point_num = 0; + + if (!s_touch) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + esp_err_t ret = esp_lcd_touch_read_data(s_touch); + if (ret == ESP_OK) { + ret = esp_lcd_touch_get_data(s_touch, point, &point_num, CONFIG_ESP_LCD_TOUCH_MAX_POINTS); + } + + if (ret == ESP_OK && point_num > 0) { + data->state = LV_INDEV_STATE_PRESSED; + data->point.x = point[0].x; + data->point.y = point[0].y; + + int64_t now_ms = esp_timer_get_time() / 1000; + if (!was_pressed || point_num != last_point_num || now_ms - last_log_ms >= EXAMPLE_TOUCH_LOG_PERIOD_MS) { + ESP_LOGI(TAG, "gt911 points=%u p0=(%u,%u,%u)", point_num, point[0].x, point[0].y, + point[0].strength); + for (uint8_t i = 1; i < point_num; i++) { + ESP_LOGI(TAG, " p%u=(%u,%u,%u)", i, point[i].x, point[i].y, point[i].strength); + } + if (s_coord_label) { + lv_label_set_text_fmt(s_coord_label, "x:%u y:%u z:%u", point[0].x, point[0].y, point[0].strength); + } + if (s_status_label) { + lv_label_set_text(s_status_label, "PRESSED"); + } + last_log_ms = now_ms; + } + was_pressed = true; + last_point_num = point_num; + } else { + data->state = LV_INDEV_STATE_RELEASED; + if (ret != ESP_OK) { + ESP_LOGW(TAG, "touch read failed: %s", esp_err_to_name(ret)); + } else if (was_pressed) { + ESP_LOGI(TAG, "released"); + if (s_status_label) { + lv_label_set_text(s_status_label, "RELEASED"); + } + } + was_pressed = false; + last_point_num = 0; + } +} + +static void lvgl_tick_cb(void *arg) +{ + (void)arg; + lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS); +} + +static void lvgl_task(void *arg) +{ + (void)arg; + while (1) { + uint32_t delay_ms = lv_timer_handler(); + if (delay_ms < 2) { + delay_ms = 2; + } else if (delay_ms > 8) { + delay_ms = 8; + } + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + } +} + +#if !LV_USE_DEMO_WIDGETS +static void button_event_cb(lv_event_t *e) +{ + if (lv_event_get_code(e) == LV_EVENT_CLICKED) { + s_button_count++; + ESP_LOGI(TAG, "LVGL button clicked: %d", s_button_count); + lv_label_set_text_fmt(s_button_label, "Button: %d", s_button_count); + } +} + +static void slider_event_cb(lv_event_t *e) +{ + lv_obj_t *slider = lv_event_get_target(e); + int value = (int)lv_slider_get_value(slider); + lv_label_set_text_fmt(s_slider_label, "Slider: %d", value); + ESP_LOGI(TAG, "LVGL slider value: %d", value); +} + +static void create_touch_ui(void) +{ + lv_obj_t *screen = lv_screen_active(); + lv_obj_set_style_bg_color(screen, lv_color_hex(0x101820), 0); + + lv_obj_t *title = lv_label_create(screen); + lv_label_set_text(title, "ST7789 + GT911 Touch"); + lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12); + + s_status_label = lv_label_create(screen); + lv_label_set_text(s_status_label, "RELEASED"); + lv_obj_set_style_text_color(s_status_label, lv_color_hex(0x64D2FF), 0); + lv_obj_align(s_status_label, LV_ALIGN_TOP_MID, 0, 42); + + s_coord_label = lv_label_create(screen); + lv_label_set_text(s_coord_label, "x:-- y:-- z:--"); + lv_obj_set_style_text_color(s_coord_label, lv_color_hex(0xE9F1F7), 0); + lv_obj_align(s_coord_label, LV_ALIGN_TOP_MID, 0, 70); + + lv_obj_t *button = lv_button_create(screen); + lv_obj_set_size(button, 160, 52); + lv_obj_align(button, LV_ALIGN_CENTER, 0, -20); + lv_obj_add_event_cb(button, button_event_cb, LV_EVENT_CLICKED, NULL); + s_button_label = lv_label_create(button); + lv_label_set_text(s_button_label, "Button: 0"); + lv_obj_center(s_button_label); + + lv_obj_t *slider = lv_slider_create(screen); + lv_obj_set_width(slider, 190); + lv_slider_set_range(slider, 0, 100); + lv_slider_set_value(slider, 50, LV_ANIM_OFF); + lv_obj_align(slider, LV_ALIGN_CENTER, 0, 70); + lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + + s_slider_label = lv_label_create(screen); + lv_label_set_text(s_slider_label, "Slider: 50"); + lv_obj_set_style_text_color(s_slider_label, lv_color_hex(0xE9F1F7), 0); + lv_obj_align_to(s_slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 12); +} +#endif + +static esp_err_t init_spi_bus(void) +{ + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = { + .sclk_io_num = EXAMPLE_PIN_NUM_SCLK, + .mosi_io_num = EXAMPLE_PIN_NUM_MOSI, + .miso_io_num = EXAMPLE_PIN_NUM_MISO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUFFER_LINES * sizeof(lv_color_t), + }; + return spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO); +} + +static void verify_lcd_reset_released(void) +{ + // The ST7789 driver configures RST as output-only. Enable the input path so + // gpio_get_level() reports the physical voltage instead of a disabled input. + esp_err_t ret = gpio_set_direction(EXAMPLE_PIN_NUM_LCD_RST, GPIO_MODE_INPUT_OUTPUT); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "LCD RES IO%d input enable failed: %s", EXAMPLE_PIN_NUM_LCD_RST, + esp_err_to_name(ret)); + return; + } + + ret = gpio_set_level(EXAMPLE_PIN_NUM_LCD_RST, 1); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "LCD RES IO%d release failed: %s", EXAMPLE_PIN_NUM_LCD_RST, + esp_err_to_name(ret)); + return; + } + + vTaskDelay(pdMS_TO_TICKS(2)); + int level = gpio_get_level(EXAMPLE_PIN_NUM_LCD_RST); + ESP_LOGI(TAG, "LCD RES IO%d readback after reset: %d", EXAMPLE_PIN_NUM_LCD_RST, level); + if (level != 1) { + ESP_LOGE(TAG, "LCD RES is held low externally; check the adapter, FPC, and RES wiring"); + } +} + +static esp_err_t init_st7789_panel(esp_lcd_panel_io_handle_t *ret_io) +{ + ESP_LOGI(TAG, "Initialize ST7789 panel"); + + gpio_config_t bk_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT, + }; + ESP_RETURN_ON_ERROR(gpio_config(&bk_gpio_config), TAG, "configure backlight GPIO failed"); + gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL); + + esp_lcd_panel_io_spi_config_t io_config = { + .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC, + .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS, + .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, + .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, + .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, + .spi_mode = 0, + .trans_queue_depth = 10, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, ret_io), + TAG, "install panel IO failed"); + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, + .data_endian = LCD_RGB_DATA_ENDIAN_BIG, + .bits_per_pixel = 16, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(*ret_io, &panel_config, &s_panel), TAG, + "install ST7789 panel failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG, "reset panel failed"); + verify_lcd_reset_released(); + ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "init panel failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_set_gap(s_panel, CONFIG_EXAMPLE_LCD_ST7735_X_GAP, + CONFIG_EXAMPLE_LCD_ST7735_Y_GAP), + TAG, "set panel gap failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_disp_on_off(s_panel, true), TAG, "turn panel on failed"); + + gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL); + return ESP_OK; +} + +static esp_err_t init_display_buffer(void) +{ + size_t expected_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t); + size_t image_size = image_rgb565_end - image_rgb565_start; + + ESP_RETURN_ON_FALSE(image_size == expected_size, ESP_ERR_INVALID_SIZE, TAG, + "embedded image size is %u bytes, expected %u", + (unsigned int)image_size, (unsigned int)expected_size); + + s_image_buffer = heap_caps_malloc(expected_size, MALLOC_CAP_DMA); + ESP_RETURN_ON_FALSE(s_image_buffer, ESP_ERR_NO_MEM, TAG, "no DMA memory for image frame"); + return ESP_OK; +} + +static void prepare_standard_color_bars(void) +{ + // The DCT module's ST7789 interface requires each RGB565 pixel MSB first. + static const uint8_t colors[][sizeof(uint16_t)] = { + {0xF8, 0x00}, // red: 0xF800 + {0x07, 0xE0}, // green: 0x07E0 + {0x00, 0x1F}, // blue: 0x001F + {0x00, 0x00}, // black: 0x0000 + }; + + for (int y = 0; y < EXAMPLE_LCD_V_RES; y++) { + for (int x = 0; x < EXAMPLE_LCD_H_RES; x++) { + size_t offset = (y * EXAMPLE_LCD_H_RES + x) * sizeof(uint16_t); + memcpy(&s_image_buffer[offset], colors[x * 4 / EXAMPLE_LCD_H_RES], sizeof(uint16_t)); + } + } +} + +static void prepare_standard_image(void) +{ + size_t expected_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t); + + // 2_rgb565.bin is stored little-endian for the ESP32, but the panel + // interface must receive the high byte of every RGB565 pixel first. + for (size_t offset = 0; offset < expected_size; offset += sizeof(uint16_t)) { + s_image_buffer[offset] = image_rgb565_start[offset + 1]; + s_image_buffer[offset + 1] = image_rgb565_start[offset]; + } +} + +static esp_err_t draw_display_buffer(const char *description) +{ + ESP_LOGI(TAG, "Draw %s, RGB565 MSB first (first pixel: %02x %02x)", description, + s_image_buffer[0], s_image_buffer[1]); + ESP_RETURN_ON_ERROR(esp_lcd_panel_draw_bitmap(s_panel, 0, 0, EXAMPLE_LCD_H_RES, + EXAMPLE_LCD_V_RES, s_image_buffer), + TAG, "queue display frame failed"); + + vTaskDelay(pdMS_TO_TICKS(200)); + return ESP_OK; +} + +static esp_err_t display_standard_diagnostic(void) +{ + prepare_standard_color_bars(); + ESP_RETURN_ON_ERROR(draw_display_buffer("four color bars"), TAG, "draw color bars failed"); + vTaskDelay(pdMS_TO_TICKS(2000)); + + prepare_standard_image(); + return draw_display_buffer("embedded 2.jpg" ); +} + +static esp_err_t init_i2c_bus(void) +{ + ESP_LOGI(TAG, "Initialize GT911 I2C bus"); + i2c_master_bus_config_t i2c_bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = I2C_NUM_0, + .scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL, + .sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA, + .glitch_ignore_cnt = 7, + .flags = { + .enable_internal_pullup = true, + }, + }; + ESP_RETURN_ON_ERROR(i2c_new_master_bus(&i2c_bus_config, &s_i2c_bus), TAG, + "create GT911 I2C bus failed"); + ESP_LOGI(TAG, "GT911 I2C bus initialized"); + return ESP_OK; +} + +static esp_err_t release_touch_reset_before_i2c(void) +{ + gpio_config_t rst_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_TOUCH_RST, + }; + + ESP_LOGI(TAG, "Release GT911 reset on IO%d before I2C bus init", EXAMPLE_PIN_NUM_TOUCH_RST); + esp_err_t ret = gpio_config(&rst_gpio_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "GPIO%d configuration failed: %s (0x%x)", EXAMPLE_PIN_NUM_TOUCH_RST, + esp_err_to_name(ret), ret); + return ret; + } + + ret = gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_RST, 1); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "GPIO%d release failed: %s (0x%x)", EXAMPLE_PIN_NUM_TOUCH_RST, + esp_err_to_name(ret), ret); + return ret; + } + + vTaskDelay(pdMS_TO_TICKS(120)); + return ESP_OK; +} + +static bool scan_i2c_bus_handle(i2c_master_bus_handle_t bus, const char *label, int scl, int sda) +{ + bool found = false; + + ESP_LOGI(TAG, "Scan I2C devices on %s: SCL=IO%d SDA=IO%d", label, scl, sda); + for (uint8_t addr = 0x08; addr < 0x78; addr++) { + esp_err_t ret = i2c_master_probe(bus, addr, 10); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "I2C device found on %s at 0x%02x", label, addr); + found = true; + } + } + + if (!found) { + ESP_LOGW(TAG, "No I2C device found on %s: SCL=IO%d SDA=IO%d", label, scl, sda); + } + + return found; +} + +static bool scan_i2c_bus(void) +{ + bool found = scan_i2c_bus_handle(s_i2c_bus, "configured GT911 bus", EXAMPLE_PIN_NUM_TOUCH_SCL, + EXAMPLE_PIN_NUM_TOUCH_SDA); + if (!found) { + ESP_LOGW(TAG, "Check T_SCL/T_SDA wiring, touch power, common GND, and I2C pull-ups"); + } + return found; +} + +static esp_err_t select_gt911_address(uint8_t dev_addr) +{ + uint32_t int_level = (dev_addr == ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP) ? 1 : 0; + gpio_config_t rst_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_TOUCH_RST, + }; + gpio_config_t int_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = 1, + .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_TOUCH_IRQ, + }; + + ESP_LOGI(TAG, "Select GT911 address 0x%02x with RST=IO%d INT=IO%d", + dev_addr, EXAMPLE_PIN_NUM_TOUCH_RST, EXAMPLE_PIN_NUM_TOUCH_IRQ); + ESP_RETURN_ON_ERROR(gpio_config(&rst_gpio_config), TAG, "configure GT911 reset GPIO failed"); + ESP_RETURN_ON_ERROR(gpio_config(&int_gpio_config), TAG, "configure GT911 INT GPIO failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_RST, 0), TAG, "drive GT911 reset low failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_IRQ, 0), TAG, "drive GT911 INT low failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_IRQ, int_level), TAG, "set GT911 address level failed"); + vTaskDelay(pdMS_TO_TICKS(2)); + ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_PIN_NUM_TOUCH_RST, 1), TAG, "release GT911 reset failed"); + vTaskDelay(pdMS_TO_TICKS(80)); + return ESP_OK; +} + +static esp_err_t try_init_gt911_touch(uint8_t dev_addr) +{ + ESP_LOGI(TAG, "Initialize GT911 touch at I2C address 0x%02x", dev_addr); + ESP_RETURN_ON_ERROR(select_gt911_address(dev_addr), TAG, "select GT911 address failed"); + + esp_err_t ret = i2c_master_probe(s_i2c_bus, dev_addr, EXAMPLE_TOUCH_PROBE_TIMEOUT_MS); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "GT911 address 0x%02x did not ACK: %s", dev_addr, esp_err_to_name(ret)); + return ESP_ERR_NOT_FOUND; + } + + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.dev_addr = dev_addr; + tp_io_config.scl_speed_hz = EXAMPLE_TOUCH_I2C_FREQ_HZ; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c(s_i2c_bus, &tp_io_config, &tp_io_handle), + TAG, "install GT911 panel IO failed"); + + static esp_lcd_touch_io_gt911_config_t gt911_config; + gt911_config.dev_addr = dev_addr; + + esp_lcd_touch_config_t tp_cfg = { + .x_max = EXAMPLE_LCD_H_RES, + .y_max = EXAMPLE_LCD_V_RES, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_IRQ, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + .driver_data = >911_config, + }; + + ret = esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &s_touch); + if (ret != ESP_OK) { + s_touch = NULL; + esp_lcd_panel_io_del(tp_io_handle); + } + return ret; +} + +static esp_err_t init_gt911_touch(void) +{ + esp_err_t ret = try_init_gt911_touch(ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS); + if (ret == ESP_OK) { + return ESP_OK; + } + + ESP_LOGW(TAG, "GT911 address 0x%02x failed: %s, try 0x%02x", + ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, esp_err_to_name(ret), + ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP); + return try_init_gt911_touch(ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP); +} + +static void init_touch_optional(void) +{ + esp_err_t ret = release_touch_reset_before_i2c(); + if (ret == ESP_OK) { + ret = init_i2c_bus(); + } + if (ret == ESP_OK) { + ret = init_gt911_touch(); + } + + if (ret == ESP_OK) { + ESP_LOGI(TAG, "GT911 touch initialized"); + return; + } + + ESP_LOGE(TAG, "GT911 is unavailable: %s. Continue with LCD only", esp_err_to_name(ret)); + s_touch = NULL; + if (s_i2c_bus != NULL) { + scan_i2c_bus(); + } +} + +static esp_err_t init_lvgl(esp_lcd_panel_io_handle_t panel_io) +{ + ESP_LOGI(TAG, "Initialize LVGL"); + lv_init(); + + size_t buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUFFER_LINES * sizeof(lv_color_t); + void *draw_buf1 = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA); + void *draw_buf2 = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA); + ESP_RETURN_ON_FALSE(draw_buf1 && draw_buf2, ESP_ERR_NO_MEM, TAG, "no memory for LVGL draw buffers"); + + lv_display_t *display = lv_display_create(EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES); + lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565); + lv_display_set_flush_cb(display, lvgl_flush_cb); + lv_display_set_buffers(display, draw_buf1, draw_buf2, buffer_size, LV_DISPLAY_RENDER_MODE_PARTIAL); + + esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = notify_lvgl_flush_ready, + }; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, display), TAG, + "register panel IO callback failed"); + + if (s_touch) { + lv_indev_t *indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(indev, lvgl_touch_read_cb); + } else { + ESP_LOGW(TAG, "LVGL touch input is disabled because GT911 was not initialized"); + } + + const esp_timer_create_args_t tick_timer_args = { + .callback = lvgl_tick_cb, + .name = "lvgl_tick", + }; + esp_timer_handle_t tick_timer = NULL; + ESP_RETURN_ON_ERROR(esp_timer_create(&tick_timer_args, &tick_timer), TAG, "create LVGL tick failed"); + ESP_RETURN_ON_ERROR(esp_timer_start_periodic(tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000), TAG, + "start LVGL tick failed"); + +#if LV_USE_DEMO_WIDGETS + lv_demo_widgets(); +#else + create_touch_ui(); +#endif + xTaskCreate(lvgl_task, "lvgl", 8192, NULL, 5, NULL); + return ESP_OK; +} + +void app_main(void) +{ + esp_lcd_panel_io_handle_t panel_io = NULL; + + ESP_ERROR_CHECK(init_spi_bus()); + ESP_ERROR_CHECK(init_st7789_panel(&panel_io)); + ESP_ERROR_CHECK(init_display_buffer()); + ESP_ERROR_CHECK(display_standard_diagnostic()); +} diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..78a142e --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,17 @@ +CONFIG_LV_CONF_SKIP=y +CONFIG_LV_USE_OBSERVER=y +CONFIG_LV_USE_SYSMON=y +CONFIG_LV_USE_DEMO_WIDGETS=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_18=y +# CONFIG_LV_BUILD_EXAMPLES is not set +CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 +CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=0 +CONFIG_FREERTOS_HZ=1000 +CONFIG_IDF_TARGET="esp32s3" +CONFIG_EXAMPLE_LCD_CONTROLLER_ST7789=y +CONFIG_EXAMPLE_LCD_ST7735_H_RES=240 +CONFIG_EXAMPLE_LCD_ST7735_V_RES=320 +CONFIG_EXAMPLE_LCD_ST7735_X_GAP=0 +CONFIG_EXAMPLE_LCD_ST7735_Y_GAP=0 +CONFIG_EXAMPLE_LCD_ST7735_BGR_COLOR=y