├── .gitignore ├── main ├── CMakeLists.txt ├── LGFX_MakerFabs_Parallel_S2.hpp ├── LGFX_MakerFabs_Parallel_S3.hpp └── main.cpp ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── LICENSE.md └── sdkconfig.defaults /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build_*/ 3 | dependencies.lock 4 | managed_components/ 5 | sdkconfig 6 | sdkconfig.old 7 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | REQUIRES LovyanGFX lvgl 5 | ) 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/LovyanGFX"] 2 | path = components/LovyanGFX 3 | url = https://github.com/radiosound-com/LovyanGFX.git 4 | branch = out-of-bounds 5 | [submodule "components/lvgl"] 6 | path = components/lvgl 7 | url = https://github.com/lvgl/lvgl.git 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | 9 | project(MakerFabs-Parallel-TFT) 10 | 11 | idf_build_set_property(COMPILE_OPTIONS "-Wno-array-bounds" APPEND) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LVGL + LGFX for MakerFabs 3.5" Parallel TFT ESP32-S2 and S3 2 | 3 | A simple project that puts all the LVGL demos together in a simple esp-idf project with the LovyanGFX driver 4 | 5 | ## Credits 6 | 7 | * sukesh-ak's WT32-SC01 project: https://github.com/sukesh-ak/LVGL8-WT32-SC01-IDF 8 | * LovyanGFX's panel/touch/light definitions for the MakerFabs S2 screen: https://github.com/lovyan03/LovyanGFX/blob/master/src/lgfx_user/LGFX_ESP32S2_MakerabsParallelTFTwithTouch.hpp 9 | * MakerFabs' definitions for the S3 screen: https://github.com/Makerfabs/Makerfabs-ESP32-S3-Parallel-TFT-with-Touch/blob/main/firmware/SD16_3.5/SD16_3.5.ino 10 | 11 | ## Important 12 | 13 | **You must clone this repo recursively** 14 | 15 | ```sh 16 | git clone https://github.com/radiosound-com/makerfabs-parallel-tft-lvgl-lgfx --recursive 17 | ``` 18 | 19 | ## Setup 20 | 21 | Tested with ESP-IDF v5.2.1. Other versions may work as well. 22 | 23 | Make sure you have esp-idf exported, e.g.: 24 | 25 | ```sh 26 | source ~/esp/esp-idf/export.sh 27 | ``` 28 | 29 | Depending on which board you have: 30 | 31 | ```sh 32 | idf.py set-target esp32s2 33 | ``` 34 | 35 | or 36 | 37 | ```sh 38 | idf.py set-target esp32s3 39 | ``` 40 | 41 | Then 42 | 43 | ```sh 44 | idf.py build flash monitor 45 | ``` 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Radio Sound, Inc. 4 | 5 | Author: Dennis Field 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # This file was generated using idf.py save-defconfig. It can be edited manually. 2 | # Espressif IoT Development Framework (ESP-IDF) 5.2.1 Project Minimal Configuration 3 | # 4 | CONFIG_IDF_TARGET="esp32s3" 5 | CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y 6 | CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y 7 | CONFIG_COMPILER_OPTIMIZATION_PERF=y 8 | CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y 9 | CONFIG_LV_USE_CLIB_MALLOC=y 10 | CONFIG_LV_USE_CLIB_STRING=y 11 | CONFIG_LV_USE_CLIB_SPRINTF=y 12 | CONFIG_LV_DEF_REFR_PERIOD=16 13 | CONFIG_LV_OS_FREERTOS=y 14 | CONFIG_LV_USE_NATIVE_HELIUM_ASM=y 15 | CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y 16 | CONFIG_LV_FONT_MONTSERRAT_8=y 17 | CONFIG_LV_FONT_MONTSERRAT_10=y 18 | CONFIG_LV_FONT_MONTSERRAT_12=y 19 | CONFIG_LV_FONT_MONTSERRAT_16=y 20 | CONFIG_LV_FONT_MONTSERRAT_18=y 21 | CONFIG_LV_FONT_MONTSERRAT_20=y 22 | CONFIG_LV_FONT_MONTSERRAT_22=y 23 | CONFIG_LV_FONT_MONTSERRAT_24=y 24 | CONFIG_LV_FONT_MONTSERRAT_26=y 25 | CONFIG_LV_FONT_MONTSERRAT_28=y 26 | CONFIG_LV_FONT_MONTSERRAT_30=y 27 | CONFIG_LV_FONT_MONTSERRAT_32=y 28 | CONFIG_LV_FONT_MONTSERRAT_34=y 29 | CONFIG_LV_FONT_MONTSERRAT_36=y 30 | CONFIG_LV_FONT_MONTSERRAT_38=y 31 | CONFIG_LV_FONT_MONTSERRAT_40=y 32 | CONFIG_LV_FONT_MONTSERRAT_42=y 33 | CONFIG_LV_FONT_MONTSERRAT_44=y 34 | CONFIG_LV_FONT_MONTSERRAT_46=y 35 | CONFIG_LV_FONT_MONTSERRAT_48=y 36 | CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_" 37 | CONFIG_LV_THEME_DEFAULT_DARK=y 38 | CONFIG_LV_USE_SNAPSHOT=y 39 | CONFIG_LV_USE_IMGFONT=y 40 | CONFIG_LV_USE_OBSERVER=n 41 | CONFIG_LV_USE_DEMO_WIDGETS=y 42 | CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER=y 43 | CONFIG_LV_USE_DEMO_BENCHMARK=y 44 | CONFIG_LV_USE_DEMO_RENDER=y 45 | CONFIG_LV_USE_DEMO_SCROLL=y 46 | CONFIG_LV_USE_DEMO_STRESS=y 47 | CONFIG_LV_USE_DEMO_TRANSFORM=y 48 | CONFIG_LV_USE_DEMO_MUSIC=y 49 | CONFIG_LV_DEMO_MUSIC_LANDSCAPE=y 50 | CONFIG_LV_USE_DEMO_FLEX_LAYOUT=y 51 | CONFIG_LV_USE_DEMO_MULTILANG=y 52 | -------------------------------------------------------------------------------- /main/LGFX_MakerFabs_Parallel_S2.hpp: -------------------------------------------------------------------------------- 1 | // from https://github.com/lovyan03/LovyanGFX/blob/master/src/lgfx_user/LGFX_ESP32S2_MakerabsParallelTFTwithTouch.hpp 2 | #pragma once 3 | 4 | #define LGFX_USE_V1 5 | 6 | #include 7 | #include 8 | 9 | // LGFX for Makerfabs ESP32-S2-Parallel-TFT-with-Touch 10 | // https://github.com/Makerfabs/Makerfabs-ESP32-S2-Parallel-TFT-with-Touch/ 11 | 12 | class LGFX : public lgfx::LGFX_Device 13 | { 14 | static constexpr int I2C_PORT_NUM = I2C_NUM_0; 15 | static constexpr int I2C_PIN_SDA = 38; 16 | static constexpr int I2C_PIN_SCL = 39; 17 | static constexpr int I2C_PIN_INT = 40; 18 | 19 | lgfx::Bus_Parallel16 _bus_instance; 20 | lgfx::Panel_ILI9488 _panel_instance; 21 | lgfx::Light_PWM _light_instance; 22 | lgfx::ITouch* _touch_instance_ptr = nullptr; 23 | 24 | /// Detects and configures the touch panel during initialization; 25 | bool init_impl(bool use_reset, bool use_clear) override 26 | { 27 | if (_touch_instance_ptr == nullptr) 28 | { 29 | lgfx::ITouch::config_t cfg; 30 | lgfx::i2c::init(I2C_PORT_NUM, I2C_PIN_SDA, I2C_PIN_SCL); 31 | if (lgfx::i2c::beginTransaction(I2C_PORT_NUM, 0x38, 400000, false).has_value() 32 | && lgfx::i2c::endTransaction(I2C_PORT_NUM).has_value()) 33 | { 34 | _touch_instance_ptr = new lgfx::Touch_FT5x06(); 35 | cfg = _touch_instance_ptr->config(); 36 | cfg.i2c_addr = 0x38; 37 | cfg.x_max = 320; 38 | cfg.y_max = 480; 39 | } 40 | else 41 | if (lgfx::i2c::beginTransaction(I2C_PORT_NUM, 0x48, 400000, false).has_value() 42 | && lgfx::i2c::endTransaction(I2C_PORT_NUM).has_value()) 43 | { 44 | _touch_instance_ptr = new lgfx::Touch_NS2009(); 45 | cfg = _touch_instance_ptr->config(); 46 | cfg.i2c_addr = 0x48; 47 | cfg.x_min = 368; 48 | cfg.y_min = 212; 49 | cfg.x_max = 3800; 50 | cfg.y_max = 3800; 51 | } 52 | if (_touch_instance_ptr != nullptr) 53 | { 54 | cfg.i2c_port = I2C_PORT_NUM; 55 | cfg.pin_sda = I2C_PIN_SDA; 56 | cfg.pin_scl = I2C_PIN_SCL; 57 | cfg.pin_int = I2C_PIN_INT; 58 | cfg.freq = 400000; 59 | cfg.bus_shared = false; 60 | _touch_instance_ptr->config(cfg); 61 | _panel_instance.touch(_touch_instance_ptr); 62 | } 63 | } 64 | return lgfx::LGFX_Device::init_impl(use_reset, use_clear); 65 | } 66 | 67 | public: 68 | 69 | LGFX(void) 70 | { 71 | { 72 | auto cfg = _bus_instance.config(); 73 | 74 | cfg.freq_write = 40000000; 75 | cfg.pin_wr = 35; 76 | cfg.pin_rd = 34; 77 | cfg.pin_rs = 36; 78 | 79 | cfg.pin_d0 = 33; 80 | cfg.pin_d1 = 21; 81 | cfg.pin_d2 = 14; 82 | cfg.pin_d3 = 13; 83 | cfg.pin_d4 = 12; 84 | cfg.pin_d5 = 11; 85 | cfg.pin_d6 = 10; 86 | cfg.pin_d7 = 9; 87 | cfg.pin_d8 = 3; 88 | cfg.pin_d9 = 8; 89 | cfg.pin_d10 = 16; 90 | cfg.pin_d11 = 15; 91 | cfg.pin_d12 = 7; 92 | cfg.pin_d13 = 6; 93 | cfg.pin_d14 = 5; 94 | cfg.pin_d15 = 4; 95 | _bus_instance.config(cfg); 96 | _panel_instance.bus(&_bus_instance); 97 | } 98 | 99 | { 100 | auto cfg = _panel_instance.config(); 101 | cfg.pin_cs = 37; 102 | cfg.pin_rst = -1; 103 | cfg.pin_busy = -1; 104 | cfg.offset_rotation = 0; 105 | cfg.readable = true; 106 | cfg.invert = false; 107 | cfg.rgb_order = false; 108 | cfg.dlen_16bit = true; 109 | cfg.bus_shared = false; 110 | 111 | _panel_instance.config(cfg); 112 | } 113 | 114 | { 115 | auto cfg = _light_instance.config(); 116 | 117 | cfg.pin_bl = 45; 118 | cfg.invert = false; 119 | cfg.freq = 44100; 120 | cfg.pwm_channel = 7; 121 | 122 | _light_instance.config(cfg); 123 | _panel_instance.light(&_light_instance); 124 | } 125 | setPanel(&_panel_instance); 126 | } 127 | }; 128 | 129 | -------------------------------------------------------------------------------- /main/LGFX_MakerFabs_Parallel_S3.hpp: -------------------------------------------------------------------------------- 1 | // based on https://github.com/lovyan03/LovyanGFX/blob/experimental/src/lgfx_user/LGFX_ESP32S2_MakerabsParallelTFTwithTouch.hpp 2 | // LGFX for Makerfabs ESP32-S2-Parallel-TFT-with-Touch 3 | // https://github.com/Makerfabs/Makerfabs-ESP32-S2-Parallel-TFT-with-Touch/ 4 | 5 | // modified for the S3 version's pins as with https://github.com/Makerfabs/Makerfabs-ESP32-S3-Parallel-TFT-with-Touch/blob/main/firmware/SD16_3.5/SD16_3.5.ino 6 | #pragma once 7 | 8 | #define LGFX_USE_V1 9 | 10 | #include 11 | #include 12 | 13 | 14 | class LGFX : public lgfx::LGFX_Device 15 | { 16 | static constexpr int I2C_PORT_NUM = I2C_NUM_0; 17 | static constexpr int I2C_PIN_SDA = 38; 18 | static constexpr int I2C_PIN_SCL = 39; 19 | static constexpr int I2C_PIN_INT = 40; 20 | 21 | lgfx::Bus_Parallel16 _bus_instance; 22 | lgfx::Panel_ILI9488 _panel_instance; 23 | lgfx::Light_PWM _light_instance; 24 | lgfx::ITouch* _touch_instance_ptr = nullptr; 25 | 26 | /// Detects and configures the touch panel during initialization; 27 | bool init_impl(bool use_reset, bool use_clear) override 28 | { 29 | if (_touch_instance_ptr == nullptr) 30 | { 31 | lgfx::ITouch::config_t cfg; 32 | lgfx::i2c::init(I2C_PORT_NUM, I2C_PIN_SDA, I2C_PIN_SCL); 33 | if (lgfx::i2c::beginTransaction(I2C_PORT_NUM, 0x38, 400000, false).has_value() 34 | && lgfx::i2c::endTransaction(I2C_PORT_NUM).has_value()) 35 | { 36 | _touch_instance_ptr = new lgfx::Touch_FT5x06(); 37 | cfg = _touch_instance_ptr->config(); 38 | cfg.i2c_addr = 0x38; 39 | cfg.x_max = 320; 40 | cfg.y_max = 480; 41 | } 42 | else 43 | if (lgfx::i2c::beginTransaction(I2C_PORT_NUM, 0x48, 400000, false).has_value() 44 | && lgfx::i2c::endTransaction(I2C_PORT_NUM).has_value()) 45 | { 46 | _touch_instance_ptr = new lgfx::Touch_NS2009(); 47 | cfg = _touch_instance_ptr->config(); 48 | cfg.i2c_addr = 0x48; 49 | cfg.x_min = 368; 50 | cfg.y_min = 212; 51 | cfg.x_max = 3800; 52 | cfg.y_max = 3800; 53 | } 54 | if (_touch_instance_ptr != nullptr) 55 | { 56 | cfg.i2c_port = I2C_PORT_NUM; 57 | cfg.pin_sda = I2C_PIN_SDA; 58 | cfg.pin_scl = I2C_PIN_SCL; 59 | cfg.pin_int = I2C_PIN_INT; 60 | cfg.freq = 400000; 61 | cfg.bus_shared = false; 62 | _touch_instance_ptr->config(cfg); 63 | _panel_instance.touch(_touch_instance_ptr); 64 | } 65 | } 66 | return lgfx::LGFX_Device::init_impl(use_reset, use_clear); 67 | } 68 | 69 | public: 70 | 71 | LGFX(void) 72 | { 73 | { 74 | auto cfg = _bus_instance.config(); 75 | 76 | cfg.freq_write = 40000000; 77 | cfg.pin_wr = 35; 78 | cfg.pin_rd = 48; 79 | cfg.pin_rs = 36; 80 | 81 | cfg.pin_d0 = 47; 82 | cfg.pin_d1 = 21; 83 | cfg.pin_d2 = 14; 84 | cfg.pin_d3 = 13; 85 | cfg.pin_d4 = 12; 86 | cfg.pin_d5 = 11; 87 | cfg.pin_d6 = 10; 88 | cfg.pin_d7 = 9; 89 | cfg.pin_d8 = 3; 90 | cfg.pin_d9 = 8; 91 | cfg.pin_d10 = 16; 92 | cfg.pin_d11 = 15; 93 | cfg.pin_d12 = 7; 94 | cfg.pin_d13 = 6; 95 | cfg.pin_d14 = 5; 96 | cfg.pin_d15 = 4; 97 | _bus_instance.config(cfg); 98 | _panel_instance.bus(&_bus_instance); 99 | } 100 | 101 | { 102 | auto cfg = _panel_instance.config(); 103 | cfg.pin_cs = 37; 104 | cfg.pin_rst = -1; 105 | cfg.pin_busy = -1; 106 | cfg.offset_rotation = 0; 107 | cfg.readable = true; 108 | cfg.invert = false; 109 | cfg.rgb_order = false; 110 | cfg.dlen_16bit = true; 111 | cfg.bus_shared = false; 112 | 113 | _panel_instance.config(cfg); 114 | } 115 | 116 | { 117 | auto cfg = _light_instance.config(); 118 | 119 | cfg.pin_bl = 45; 120 | cfg.invert = false; 121 | cfg.freq = 44100; 122 | cfg.pwm_channel = 7; 123 | 124 | _light_instance.config(cfg); 125 | _panel_instance.light(&_light_instance); 126 | } 127 | setPanel(&_panel_instance); 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | // based on https://github.com/sukesh-ak/LVGL8-WT32-SC01-IDF 2 | // adapted for MakerFabs parallel TFT dev boards (S2 and S3) 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "sdkconfig.h" 7 | 8 | #include "esp_log.h" 9 | 10 | #define TAG "Demo" 11 | 12 | #define LV_TICK_PERIOD_MS 1 13 | 14 | #define LGFX_USE_V1 15 | 16 | #define LV_DOUBLE_BUFFER 17 | #define LANDSCAPE // if changing this, make sure to uncheck landscape in menuconfig -> components -> lvgl -> demos -> music 18 | 19 | #if CONFIG_IDF_TARGET_ESP32S3 20 | #include "LGFX_MakerFabs_Parallel_S3.hpp" 21 | // if you get "will not fit, dram segment overflow" reduce this 22 | #ifdef LANDSCAPE 23 | #define LV_BUFFER_SIZE 80 /* if not double buffering, then buf will be 2x this */ 24 | #else 25 | #define LV_BUFFER_SIZE 120 /* if not double buffering, then buf will be 2x this */ 26 | #endif 27 | #elif CONFIG_IDF_TARGET_ESP32S2 28 | #include "LGFX_MakerFabs_Parallel_S2.hpp" 29 | // if you get "will not fit, dram segment overflow" reduce this 30 | #ifdef LANDSCAPE 31 | #define LV_BUFFER_SIZE 40 /* if not double buffering, then buf will be 2x this */ 32 | #else 33 | #define LV_BUFFER_SIZE 60 /* if not double buffering, then buf will be 2x this */ 34 | #endif 35 | #else 36 | #error I don't know which board you're talking to! idf.py set-target esp32s2 or esp32s3 37 | #endif 38 | 39 | // Uncomment to test benchmark speed without display refresh. You won't see any output on screen, look in the log window to see results 40 | //#define DISABLE_FLUSH_DURING_BENCHMARK 41 | 42 | #if defined(DISABLE_FLUSH_DURING_BENCHMARK) && !CONFIG_LV_USE_LOG 43 | #error You'll need to enable LVGL logging (and probably set log to printf) in the menuconfig to get results. 44 | #endif 45 | 46 | static LGFX lcd; 47 | 48 | #include 49 | #include 50 | 51 | /*** Setup screen resolution for LVGL ***/ 52 | #ifdef LANDSCAPE 53 | static const uint16_t screenWidth = 480; 54 | static const uint16_t screenHeight = 320; 55 | #else 56 | static const uint16_t screenWidth = 320; 57 | static const uint16_t screenHeight = 480; 58 | #endif 59 | 60 | #ifdef LV_DOUBLE_BUFFER 61 | static uint16_t buf[screenWidth * LV_BUFFER_SIZE]; 62 | static uint16_t buf2[screenWidth * LV_BUFFER_SIZE]; 63 | #else 64 | static uint16_t buf[screenWidth * LV_BUFFER_SIZE * 2]; 65 | #endif 66 | 67 | typedef void (*function_pointer_t)(void); 68 | 69 | typedef struct demo_button 70 | { 71 | const char * const name; 72 | function_pointer_t function; 73 | } demo_button_t; 74 | 75 | /* List of buttons to create, and the associated demo function that needs to be called when clicked */ 76 | static demo_button_t demos[] = { 77 | {"Music", lv_demo_music}, 78 | {"Widgets", lv_demo_widgets}, 79 | {"Encoder", lv_demo_keypad_encoder}, 80 | {"Benchmark", lv_demo_benchmark}, 81 | {"Stress", lv_demo_stress} 82 | }; 83 | 84 | 85 | static lv_obj_t *demo_selection_panel; 86 | 87 | static bool disable_flush = false; 88 | 89 | /*** Function declaration ***/ 90 | static void display_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *color_p); 91 | static void touchpad_read(lv_indev_t *indev_driver, lv_indev_data_t *data); 92 | static void lv_tick_task(void *arg); 93 | 94 | /* Button event handler */ 95 | static void button_event_handler(lv_event_t *e) 96 | { 97 | lv_event_code_t code = lv_event_get_code(e); 98 | lv_obj_t *btn = (lv_obj_t*)lv_event_get_target(e); 99 | if (code == LV_EVENT_CLICKED) 100 | { 101 | function_pointer_t demo_function = (function_pointer_t)lv_event_get_user_data(e); 102 | if (demo_function) 103 | { 104 | lv_obj_t *label = lv_obj_get_child(btn, 0); 105 | ESP_LOGI(TAG, "Starting %s", lv_label_get_text(label)); 106 | 107 | #ifdef DISABLE_FLUSH_DURING_BENCHMARK 108 | if (demo_function == lv_demo_benchmark) 109 | { 110 | ESP_LOGI(TAG, "Starting benchmark with flush disabled. Wait a couple minutes for benchmark results. They'll be here soon."); 111 | disable_flush = true; 112 | } 113 | #endif 114 | demo_function(); 115 | lv_obj_del(demo_selection_panel); 116 | } 117 | } 118 | } 119 | 120 | /* tell LVGL how to tell how much time has passed */ 121 | static uint32_t my_tick_get_cb(void) 122 | { 123 | return esp_timer_get_time() / 1000; 124 | } 125 | 126 | static void init_lvgl_lgfx() 127 | { 128 | lcd.init(); 129 | lv_init(); 130 | lv_tick_set_cb(my_tick_get_cb); 131 | 132 | #ifdef LANDSCAPE 133 | // Rotate to landscape 134 | lcd.setRotation(1); 135 | #endif 136 | 137 | //lcd.setBrightness(10); 138 | 139 | /*** LVGL : Setup & Initialize the display device driver ***/ 140 | static lv_display_t *disp_drv = lv_display_create(screenWidth, screenHeight); 141 | lv_display_set_flush_cb(disp_drv, display_flush); 142 | #ifdef LV_DOUBLE_BUFFER 143 | lv_display_set_buffers(disp_drv, buf, buf2, screenWidth * LV_BUFFER_SIZE * sizeof(uint16_t), LV_DISPLAY_RENDER_MODE_PARTIAL); 144 | #else 145 | lv_display_set_buffers(disp_drv, buf, NULL, screenWidth * LV_BUFFER_SIZE * sizeof(uint16_t), LV_DISPLAY_RENDER_MODE_PARTIAL); 146 | #endif 147 | 148 | /*** LVGL : Setup & Initialize the input device driver ***/ 149 | static lv_indev_t *indev_drv = lv_indev_create(); 150 | lv_indev_set_type(indev_drv, LV_INDEV_TYPE_POINTER); 151 | lv_indev_set_read_cb(indev_drv, touchpad_read); 152 | } 153 | 154 | extern "C" void app_main(void) 155 | { 156 | init_lvgl_lgfx(); 157 | 158 | ESP_LOGI(TAG, "Ready to start a demo. Tap a button on screen. Reset the board with the reset button or Ctrl+T Ctrl+R to pick a new one."); 159 | 160 | // Create buttons to pick which demo 161 | demo_selection_panel = lv_obj_create(lv_scr_act()); 162 | lv_obj_set_size(demo_selection_panel, lv_pct(100), 120); 163 | lv_obj_set_scroll_snap_x(demo_selection_panel, LV_SCROLL_SNAP_CENTER); 164 | lv_obj_set_flex_flow(demo_selection_panel, LV_FLEX_FLOW_ROW); 165 | lv_obj_align(demo_selection_panel, LV_ALIGN_CENTER, 0, 20); 166 | 167 | for(auto& demo : demos) 168 | { 169 | lv_obj_t * btn = lv_btn_create(demo_selection_panel); 170 | lv_obj_set_size(btn, 120, lv_pct(100)); 171 | 172 | lv_obj_add_event_cb(btn, button_event_handler, LV_EVENT_CLICKED, (void*)demo.function); 173 | 174 | lv_obj_t * label = lv_label_create(btn); 175 | lv_label_set_text_static(label, demo.name); 176 | 177 | lv_obj_center(label); 178 | } 179 | lv_obj_update_snap(demo_selection_panel, LV_ANIM_ON); 180 | 181 | /* UI thread */ 182 | while (true) 183 | { 184 | /* let the GUI do its work */ 185 | vTaskDelay(pdMS_TO_TICKS(lv_timer_handler())); 186 | } 187 | } 188 | 189 | /*** Display callback to flush the buffer to screen ***/ 190 | static void display_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *color_p) 191 | { 192 | #ifdef DISABLE_FLUSH_DURING_BENCHMARK 193 | if (disable_flush) 194 | { 195 | lv_display_flush_ready(disp); 196 | return; 197 | } 198 | #endif 199 | uint32_t w = (area->x2 - area->x1 + 1); 200 | uint32_t h = (area->y2 - area->y1 + 1); 201 | 202 | lcd.startWrite(); 203 | lcd.setAddrWindow(area->x1, area->y1, w, h); 204 | lcd.writePixels((uint16_t *)color_p, w * h, true); 205 | lcd.endWrite(); 206 | 207 | lv_display_flush_ready(disp); 208 | } 209 | 210 | /*** Touchpad callback to read the touchpad ***/ 211 | static void touchpad_read(lv_indev_t *indev_driver, lv_indev_data_t *data) 212 | { 213 | uint16_t touchX, touchY; 214 | bool touched = lcd.getTouch(&touchX, &touchY); 215 | 216 | if (!touched) 217 | { 218 | data->state = LV_INDEV_STATE_REL; 219 | } 220 | else 221 | { 222 | data->state = LV_INDEV_STATE_PR; 223 | 224 | /*Set the coordinates*/ 225 | data->point.x = touchX; 226 | data->point.y = touchY; 227 | } 228 | } --------------------------------------------------------------------------------