├── .github ├── auto-comment.yml └── workflows │ └── unix_port.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── bindings.cmake ├── driver ├── esp32 │ ├── espidf.c │ ├── espidf.h │ ├── ili9341.py │ ├── ili9XXX.py │ ├── lv_spi.py │ ├── modrtch.c │ ├── modxpt2046.c │ ├── sh2lib.c │ ├── sh2lib.h │ └── xpt2046.py ├── generic │ ├── axp192.py │ ├── ft6x36.py │ ├── ili9xxx-test.py │ ├── ili9xxx.py │ ├── st77xx-test.py │ ├── st77xx.py │ ├── xpt2046-test.py │ └── xpt2046.py ├── include │ └── common.h ├── js │ └── lv_timer.py ├── linux │ ├── evdev.py │ └── lv_timer.py ├── rp2 │ ├── rp2_dma-test.py │ └── rp2_dma.py ├── stm32 │ ├── STM32F7DISC │ │ ├── ft5336.c │ │ ├── ft5336.h │ │ ├── modrk043fn48h.c │ │ ├── rk043fn48h.h │ │ ├── stm32746g_discovery.h │ │ ├── stm32746g_discovery_ts.c │ │ ├── stm32746g_discovery_ts.h │ │ └── ts.h │ └── string1.c └── zephyr │ ├── README.md │ ├── lvgl.c │ ├── lvgl_driver.h │ └── modlvzephyr.c ├── examples ├── Dynamic_loading_font_example.py ├── advanced_demo.py ├── blue_flower_32.bin ├── custom_widget_example.py ├── example1.py ├── example3.py ├── fb_test.py ├── font │ ├── font-PHT-cn-20.bin │ ├── font-PHT-en-20.bin │ └── font-PHT-jp-20.bin ├── generic-st77xx-with-xpt2046.py ├── madctl │ ├── README.md │ ├── images │ │ ├── madctl_0.png │ │ ├── madctl_v.png │ │ ├── madctl_vx.png │ │ ├── madctl_vxy.png │ │ ├── madctl_vy.png │ │ ├── madctl_x.png │ │ ├── madctl_xy.png │ │ └── madctl_y.png │ └── madctl_helper.py ├── png_decoder_test.png ├── png_example.py └── uasyncio_example1.py ├── gen ├── .gitignore ├── gen_mpy.py ├── lv_mpy_example.c └── lv_mpy_example.json ├── include └── lv_mp_mem_custom_include.h ├── lib ├── display_driver.py ├── display_driver_utils.py ├── fs_driver.py ├── lv_colors.py ├── lv_utils.py ├── tpcal.py └── utils.py ├── lv_conf.h ├── micropython.cmake ├── micropython.mk ├── mkrules.cmake ├── mkrules_usermod.cmake ├── ports ├── esp32 │ └── manifest.py ├── stm32 │ └── manifest.py └── unix │ └── manifest.py └── tests ├── README.md ├── api ├── basic.py ├── basic.py.exp ├── basic_indev.py ├── basic_indev.py.exp ├── basic_slider.py └── basic_slider.py.exp ├── display ├── basic.py ├── basic.py.exp ├── basic_indev.py ├── basic_indev.py.exp ├── basic_slider.py ├── basic_slider.py.exp └── display_config.py ├── hwdisplay.py ├── imageconvert.py ├── indev ├── basic.py ├── basic.py.exp ├── basic_slider.py ├── basic_slider.py.exp └── display_config.py ├── run.sh ├── run_test.py ├── testdisplay.py ├── testrunner.py └── tools └── calibrate.py /.github/auto-comment.yml: -------------------------------------------------------------------------------- 1 | # Comment to a new issue. 2 | pullRequestOpened: | 3 | Thank you for raising your pull request. 4 | 5 | To ensure that all licensing criteria is met all repositories of the LVGL project apply a process called DCO (Developer's Certificate of Origin). 6 | 7 | The text of DCO can be read here: https://developercertificate.org/ 8 | For a more detailed description see the [Documentation](https://docs.lvgl.io/latest/en/html/contributing/index.html#developer-certification-of-origin-dco) site. 9 | 10 | By contributing to any repositories of the LVGL project you state that your contribution corresponds with the DCO. 11 | 12 | No further action is required if your contribution fulfills the DCO. If you are not sure about it feel free to ask us in a comment. 13 | -------------------------------------------------------------------------------- /.github/workflows/unix_port.yml: -------------------------------------------------------------------------------- 1 | name: Build lv_micropython unix port 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-24.04 11 | 12 | steps: 13 | - name: Install Dependencies 14 | run: | 15 | sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" 16 | sudo apt-get update -y -qq 17 | sudo apt-get install libsdl2-dev parallel libfreetype-dev librlottie-dev libavformat-dev libavcodec-dev libswscale-dev libavutil-dev 18 | - name: Clone lv_micropython 19 | run: | 20 | git clone https://github.com/lvgl/lv_micropython.git . 21 | git checkout master 22 | - name: Initialize lv_bindings submodule 23 | run: git submodule update --init --recursive user_modules/lv_binding_micropython 24 | - name: Update Unix port submodules 25 | run: make -C ports/unix DEBUG=1 submodules 26 | - name: Checkout lv_bindings 27 | working-directory: ./user_modules/lv_binding_micropython 28 | run: | 29 | git fetch --force ${{ github.event.repository.html_url }} "+refs/heads/*:refs/remotes/origin/*" 30 | git fetch --force ${{ github.event.repository.html_url }} "+refs/pull/*:refs/remotes/origin/pr/*" 31 | git checkout ${{ github.sha }} || git checkout ${{ github.event.pull_request.head.sha }} 32 | git submodule update --init --recursive 33 | - name: Build mpy-cross 34 | run: make -j $(nproc) -C mpy-cross 35 | - name: Build the unix port 36 | run: make -j $(nproc) -C ports/unix DEBUG=1 VARIANT=lvgl 37 | - name: Run tests 38 | run: | 39 | export XDG_RUNTIME_DIR=/tmp 40 | user_modules/lv_binding_micropython/tests/run.sh 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lextab.py 2 | yacctab.py 3 | *.swp 4 | *.swo 5 | *.swn 6 | *.bak 7 | .history 8 | .vscode 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lvgl"] 2 | path = lvgl 3 | url = https://github.com/lvgl/lvgl.git 4 | [submodule "micropython/pycparser"] 5 | path = pycparser 6 | url = https://github.com/eliben/pycparser.git 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 LVGL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bindings.cmake: -------------------------------------------------------------------------------- 1 | # This file is to be given as "make USER_C_MODULES=..." when building Micropython port 2 | 3 | include(${CMAKE_CURRENT_LIST_DIR}/mkrules.cmake) 4 | 5 | # lvgl bindings depend on lvgl itself, pull it in 6 | include(${LVGL_DIR}/CMakeLists.txt) 7 | 8 | # lvgl bindings target (the mpy module) 9 | add_library(usermod_lv_bindings INTERFACE) 10 | target_sources(usermod_lv_bindings INTERFACE ${LV_SRC}) 11 | target_include_directories(usermod_lv_bindings INTERFACE ${LV_INCLUDE}) 12 | 13 | target_link_libraries(usermod_lv_bindings INTERFACE lvgl_interface) 14 | 15 | # make usermod (target declared by Micropython for all user compiled modules) link to bindings 16 | # this way the bindings (and transitively lvgl_interface) get proper compilation flags 17 | target_link_libraries(usermod INTERFACE usermod_lv_bindings) 18 | 19 | # create targets for generated files 20 | all_lv_bindings() 21 | -------------------------------------------------------------------------------- /driver/esp32/espidf.c: -------------------------------------------------------------------------------- 1 | #include "../include/common.h" 2 | #include "py/obj.h" 3 | #include "py/runtime.h" 4 | #include "py/gc.h" 5 | #include "py/stackctrl.h" 6 | #include "mphalport.h" 7 | #include "espidf.h" 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "esp_system.h" 11 | #include "soc/cpu.h" 12 | 13 | 14 | // ESP IDF has some functions that are declared but not implemented. 15 | // To avoid linking errors, provide empty implementation 16 | 17 | inline void gpio_pin_wakeup_disable(void){} 18 | inline void gpio_pin_wakeup_enable(uint32_t i, GPIO_INT_TYPE intr_state){} 19 | inline void gpio_intr_ack_high(uint32_t ack_mask){} 20 | inline void gpio_intr_ack(uint32_t ack_mask){} 21 | inline uint32_t gpio_intr_pending_high(void){return 0;} 22 | inline uint32_t gpio_intr_pending(void){return 0;} 23 | inline void gpio_intr_handler_register(gpio_intr_handler_fn_t fn, void *arg){} 24 | inline void gpio_init(void){} 25 | 26 | void task_delay_ms(int ms) 27 | { 28 | vTaskDelay(ms / portTICK_RATE_MS); 29 | } 30 | 31 | // ISR callbacks handling 32 | // Can't use mp_sched_schedule because lvgl won't yield to give micropython a chance to run 33 | // Must run Micropython in ISR itself. 34 | 35 | DEFINE_PTR_OBJ_TYPE(spi_transaction_ptr_type, MP_QSTR_spi_transaction_ptr_t); 36 | 37 | typedef struct{ 38 | mp_ptr_t spi_transaction_ptr; 39 | mp_obj_t pre_cb; 40 | mp_obj_t post_cb; 41 | } mp_spi_device_callbacks_t; 42 | 43 | void *spi_transaction_set_cb(mp_obj_t pre_cb, mp_obj_t post_cb) 44 | { 45 | mp_spi_device_callbacks_t *callbacks = m_new_obj(mp_spi_device_callbacks_t); 46 | callbacks->spi_transaction_ptr.base.type = &spi_transaction_ptr_type; 47 | callbacks->pre_cb = pre_cb != mp_const_none? pre_cb: NULL; 48 | callbacks->post_cb = post_cb != mp_const_none? post_cb: NULL; 49 | return callbacks; 50 | } 51 | 52 | STATIC void isr_print_strn(void *env, const char *str, size_t len) { 53 | size_t i; 54 | (void)env; 55 | for (i=0; iuser; 105 | if (self) { 106 | self->spi_transaction_ptr.ptr = trans; 107 | cb_isr(self->pre_cb, MP_OBJ_FROM_PTR(self)); 108 | } 109 | } 110 | 111 | // Called in ISR context! 112 | void ex_spi_post_cb_isr(spi_transaction_t *trans) 113 | { 114 | mp_spi_device_callbacks_t *self = (mp_spi_device_callbacks_t*)trans->user; 115 | if (self) { 116 | self->spi_transaction_ptr.ptr = trans; 117 | cb_isr(self->post_cb, MP_OBJ_FROM_PTR(self)); 118 | } 119 | } 120 | 121 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 122 | // ILI9xxx flush and ISR implementation in C 123 | 124 | #include "lvgl/lvgl.h" 125 | 126 | DMA_ATTR static uint8_t dma_buf[4] = {0}; 127 | DMA_ATTR static spi_transaction_t spi_trans = {0}; 128 | 129 | static void spi_send_value(const uint8_t *value, uint8_t size, spi_device_handle_t spi) 130 | { 131 | spi_trans.length = size*8; 132 | spi_trans.tx_buffer = value; 133 | spi_trans.user = NULL; 134 | spi_device_polling_transmit(spi, &spi_trans); 135 | } 136 | 137 | static inline void ili9xxx_send_cmd(uint8_t cmd, int dc, spi_device_handle_t spi) 138 | { 139 | dma_buf[0] = cmd; 140 | gpio_set_level(dc, 0); 141 | spi_send_value(dma_buf, 1, spi); 142 | } 143 | 144 | static inline void ili9xxx_send_data(const uint8_t *value, int dc, spi_device_handle_t spi) 145 | { 146 | gpio_set_level(dc, 1); 147 | spi_send_value(value, 4, spi); 148 | } 149 | 150 | static void ili9xxx_send_data_dma(void *disp_drv, void *data, size_t size, int dc, spi_device_handle_t spi) 151 | { 152 | gpio_set_level(dc, 1); 153 | spi_trans.length = size*8; 154 | spi_trans.tx_buffer = data; 155 | spi_trans.user = disp_drv; 156 | spi_device_queue_trans(spi, &spi_trans, -1); 157 | } 158 | 159 | void ili9xxx_post_cb_isr(spi_transaction_t *trans) 160 | { 161 | if (trans->user) 162 | lv_display_flush_ready(trans->user); 163 | } 164 | 165 | 166 | typedef struct { 167 | uint8_t blue; 168 | uint8_t green; 169 | uint8_t red; 170 | } color24_t; 171 | 172 | #define DISPLAY_TYPE_ILI9341 1 173 | #define DISPLAY_TYPE_ILI9488 2 174 | #define DISPLAY_TYPE_GC9A01 3 175 | #define DISPLAY_TYPE_ST7789 4 176 | #define DISPLAY_TYPE_ST7735 5 177 | 178 | void ili9xxx_flush(void *_disp_drv, const void *_area, void *_color_p) 179 | { 180 | lv_display_t *disp_drv = _disp_drv; 181 | const lv_area_t *area = _area; 182 | lv_color_t *color_p = _color_p; 183 | int start_x = 0; 184 | int start_y = 0; 185 | 186 | void *driver_data = lv_display_get_driver_data(disp_drv); 187 | 188 | // We use disp_drv->driver_data to pass data from MP to C 189 | // The following lines extract dc and spi 190 | 191 | int dc = mp_obj_get_int(mp_obj_dict_get(driver_data, MP_OBJ_NEW_QSTR(MP_QSTR_dc))); 192 | int dt = mp_obj_get_int(mp_obj_dict_get(driver_data, MP_OBJ_NEW_QSTR(MP_QSTR_dt))); 193 | mp_buffer_info_t buffer_info; 194 | mp_get_buffer_raise(mp_obj_dict_get(driver_data, MP_OBJ_NEW_QSTR(MP_QSTR_spi)), &buffer_info, MP_BUFFER_READ); 195 | spi_device_handle_t *spi_ptr = buffer_info.buf; 196 | 197 | // Some devices may need a start_x and start_y offset for displays with LCD screens smaller 198 | // than the devices built in frame buffer. 199 | 200 | start_x = mp_obj_get_int(mp_obj_dict_get(driver_data, MP_OBJ_NEW_QSTR(MP_QSTR_start_x))); 201 | start_y = mp_obj_get_int(mp_obj_dict_get(driver_data, MP_OBJ_NEW_QSTR(MP_QSTR_start_y))); 202 | 203 | int x1 = area->x1 + start_x; 204 | int x2 = area->x2 + start_x; 205 | int y1 = area->y1 + start_y; 206 | int y2 = area->y2 + start_y; 207 | 208 | // Column addresses 209 | 210 | ili9xxx_send_cmd(0x2A, dc, *spi_ptr); 211 | dma_buf[0] = (x1 >> 8) & 0xFF; 212 | dma_buf[1] = x1 & 0xFF; 213 | dma_buf[2] = (x2 >> 8) & 0xFF; 214 | dma_buf[3] = x2 & 0xFF; 215 | 216 | ili9xxx_send_data(dma_buf, dc, *spi_ptr); 217 | 218 | // Page addresses 219 | 220 | ili9xxx_send_cmd(0x2B, dc, *spi_ptr); 221 | dma_buf[0] = (y1 >> 8) & 0xFF; 222 | dma_buf[1] = y1 & 0xFF; 223 | dma_buf[2] = (y2 >> 8) & 0xFF; 224 | dma_buf[3] = y2 & 0xFF; 225 | 226 | ili9xxx_send_data(dma_buf, dc, *spi_ptr); 227 | 228 | // Memory write by DMA, disp_flush_ready when finished 229 | 230 | ili9xxx_send_cmd(0x2C, dc, *spi_ptr); 231 | 232 | size_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); 233 | uint8_t color_size = 2; 234 | 235 | bool swap_rgb565_bytes = mp_obj_get_int(mp_obj_dict_get(driver_data, MP_OBJ_NEW_QSTR(MP_QSTR_swap_rgb565_bytes))); 236 | if ( swap_rgb565_bytes == true ) { 237 | lv_draw_sw_rgb565_swap(color_p, size); 238 | } 239 | 240 | if ( dt == DISPLAY_TYPE_ILI9488 ) { 241 | color_size = 3; 242 | /*Convert ARGB to RGB is required (cut off A-byte)*/ 243 | size_t i; 244 | lv_color32_t* tmp32 = (lv_color32_t*) color_p; 245 | color24_t* tmp24 = (color24_t*) color_p; 246 | 247 | for(i=0; i < size; i++) { 248 | tmp24[i].red = tmp32[i].red; 249 | tmp24[i].green = tmp32[i].green; 250 | tmp24[i].blue = tmp32[i].blue; 251 | } 252 | } 253 | 254 | ili9xxx_send_data_dma(disp_drv, color_p, size * color_size, dc, *spi_ptr); 255 | } 256 | -------------------------------------------------------------------------------- /driver/esp32/espidf.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This file defines the Micorpython API to ESP-IDF 3 | * It is used as input to gen_mpy.py to create a micropython module 4 | **/ 5 | #if __has_include("esp_idf_version.h") 6 | # include "esp_idf_version.h" 7 | #endif 8 | 9 | // Disable some macros and includes that make pycparser choke 10 | 11 | #ifdef PYCPARSER 12 | #define __attribute__(x) 13 | #define _Static_assert(x,y) 14 | #define __extension__ 15 | #define _SOC_IO_MUX_REG_H_ 16 | #define _SYS_REENT_H_ 17 | #define PORTMACRO_H 18 | #define PORTABLE_H 19 | #define INC_FREERTOS_H 20 | #define QUEUE_H 21 | #define SEMAPHORE_H 22 | #define XTENSA_HAL_H 23 | #define _SOC_I2S_STRUCT_H_ 24 | #define XTRUNTIME_H 25 | #define _SOC_SPI_STRUCT_H_ 26 | #define _SOC_RTC_CNTL_STRUCT_H_ 27 | #define __XTENSA_API_H__ 28 | #define _SOC_GPIO_STRUCT_H_ 29 | #define _SOC_RTC_IO_STRUCT_H_ 30 | #define _SOC_PCNT_STRUCT_H_ 31 | #define _SYS_FCNTL_H_ 32 | #define __SYS_ARCH_H__ 33 | #define LIST_H 34 | #define INC_TASK_H 35 | #define LWIP_HDR_NETIF_H 36 | #define ESP_EVENT_H_ 37 | #define __SNTP_H__ 38 | #define XTENSA_CONFIG_CORE_H 39 | #define _SOC_SPI_MEM_STRUCT_H_ 40 | 41 | typedef int BaseType_t; 42 | typedef unsigned int UBaseType_t; 43 | typedef void* system_event_t; 44 | typedef void *intr_handle_t; 45 | 46 | // Exclude SOC just because it contains large structs that don't interest the user 47 | #define _SOC_SPI_PERIPH_H_ 48 | typedef void *spi_dev_t; 49 | 50 | // TODO: Check why lldesc_t causes infinite recursion on gen_mpy.py 51 | #define _ROM_LLDESC_H_ 52 | typedef void *lldesc_t; 53 | 54 | // FreeRTOS definitions we want available on Micropython 55 | #include 56 | typedef uint32_t TickType_t; 57 | typedef void * TaskHandle_t; 58 | static inline uint32_t xPortGetCoreID(); 59 | UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask ); 60 | 61 | // Micropython specific types 62 | typedef void *mp_obj_t; 63 | 64 | static inline void SPH0645_WORKAROUND(int i2s_num); 65 | static inline void get_ccount(int *ccount); 66 | 67 | // Memory management helper functions 68 | void * memcpy ( void * destination, const void * source, size_t num ); 69 | void * memset ( void * ptr, int value, size_t num ); 70 | 71 | 72 | #else // PYCPARSER 73 | 74 | 75 | ///////////////////////////////////////////////////////////////////////////////////////////// 76 | // A workaround for SPH0645 I2S, see: 77 | // - https://hackaday.io/project/162059-street-sense/log/160705-new-i2s-microphone/discussion-124677 78 | // - https://www.esp32.com/viewtopic.php?t=4997#p45366 79 | // Since reg access is based on macros, this cannot currently be directly implemented in Micropython 80 | 81 | #include "soc/i2s_reg.h" // for SPH0645_WORKAROUND 82 | 83 | static inline void SPH0645_WORKAROUND(int i2s_num) 84 | { 85 | REG_SET_BIT( I2S_TIMING_REG(i2s_num), BIT(9)); 86 | REG_SET_BIT( I2S_CONF_REG(i2s_num), I2S_RX_MSB_SHIFT); 87 | } 88 | 89 | ///////////////////////////////////////////////////////////////////////////////////////////// 90 | // Helper function to measure CPU cycles 91 | // 92 | static inline void get_ccount(int *ccount) 93 | { 94 | asm volatile("rsr.ccount %0" : "=a"(*ccount)); 95 | } 96 | 97 | 98 | #endif //PYCPARSER 99 | 100 | // The following includes are the source of the esp-idf micropython module. 101 | // All included files are API we want to include in the module 102 | 103 | #if defined(ESP_IDF_VERSION_MAJOR) && ESP_IDF_VERSION_MAJOR >= 4 104 | # if CONFIG_IDF_TARGET_ESP32 105 | # include "esp32/clk.h" 106 | # elif CONFIG_IDF_TARGET_ESP32S2 107 | # include "esp32s2/clk.h" 108 | # elif CONFIG_IDF_TARGET_ESP32S3 109 | # include "esp32s3/clk.h" 110 | # elif CONFIG_IDF_TARGET_ESP32C3 111 | # include "esp32c3/clk.h" 112 | # elif CONFIG_IDF_TARGET_ESP32H2 113 | # include "esp32h2/clk.h" 114 | # else // CONFIG_IDF_TARGET_* not defined 115 | # include "esp32/clk.h" 116 | # endif 117 | #else 118 | # include "esp_clk.h" 119 | #endif 120 | 121 | #include "driver/gpio.h" 122 | #include "driver/spi_master.h" 123 | #include "esp_heap_caps.h" 124 | #include "esp_log.h" 125 | #include "driver/adc.h" 126 | #include "driver/i2s.h" 127 | #include "driver/pcnt.h" 128 | #include "mdns.h" 129 | #include "esp_http_client.h" 130 | #include "sh2lib.h" 131 | 132 | ///////////////////////////////////////////////////////////////////////////////////////////// 133 | // Helper function to register HTTP event handler 134 | // Needed to fulfill gen_mpy.py callback conventions 135 | // 136 | static inline void esp_http_client_register_event_handler(esp_http_client_config_t *config, http_event_handle_cb http_event_handler, void *user_data) 137 | { 138 | config->event_handler = http_event_handler; 139 | config->user_data = user_data; 140 | } 141 | 142 | // We don't want the whole FreeRTOS, only selected functions 143 | 144 | void task_delay_ms(int ms); 145 | 146 | // The binding only publishes structs that are used in some function. We need spi_transaction_ext_t 147 | // TODO: Find some way to mark structs for binding export instead of new function. 148 | static inline void set_spi_transaction_ext( 149 | spi_transaction_ext_t *ext_trans, 150 | spi_transaction_t *trans, 151 | uint8_t command_bits, 152 | uint8_t address_bits){ 153 | ext_trans->base = *trans; 154 | ext_trans->command_bits = command_bits; 155 | ext_trans->address_bits = address_bits; 156 | } 157 | 158 | // Wrapper for safe ISR callbacks from micropython 159 | // Need to call both spi_transaction_set_cb and set spi_pre/post_cb_isr! 160 | 161 | // Use this to set pre/post callbacks for spi transaction. 162 | // pre_cb/post_cb should be either a callable object or "None". 163 | // Result should be set to spi_transaction_t user field. 164 | // Allocates RAM. 165 | 166 | void *spi_transaction_set_cb(mp_obj_t pre_cb, mp_obj_t post_cb); 167 | 168 | // These functions can be set into pre_cb/post_cb of spi_device_interface_config_t 169 | 170 | void ex_spi_pre_cb_isr(spi_transaction_t *trans); 171 | void ex_spi_post_cb_isr(spi_transaction_t *trans); 172 | 173 | // Useful constants 174 | 175 | #define EXPORT_CONST_INT(int_value) enum {ENUM_##int_value = int_value} 176 | 177 | #if defined(ESP_IDF_VERSION_MAJOR) && ESP_IDF_VERSION_MAJOR >= 4 178 | // SPI HOST enum was changed to macros on v4 179 | enum { 180 | ENUM_SPI_HOST = SPI_HOST, 181 | ENUM_HSPI_HOST = HSPI_HOST, 182 | ENUM_VSPI_HOST = VSPI_HOST, 183 | }; 184 | #endif 185 | 186 | enum { 187 | ENUM_portMAX_DELAY = portMAX_DELAY 188 | }; 189 | 190 | enum { 191 | ENUM_I2S_PIN_NO_CHANGE = I2S_PIN_NO_CHANGE 192 | }; 193 | 194 | enum { 195 | ENUM_SPI_DEVICE_TXBIT_LSBFIRST = SPI_DEVICE_TXBIT_LSBFIRST, 196 | ENUM_SPI_DEVICE_RXBIT_LSBFIRST = SPI_DEVICE_RXBIT_LSBFIRST, 197 | ENUM_SPI_DEVICE_BIT_LSBFIRST = SPI_DEVICE_BIT_LSBFIRST, 198 | ENUM_SPI_DEVICE_3WIRE = SPI_DEVICE_3WIRE, 199 | ENUM_SPI_DEVICE_POSITIVE_CS = SPI_DEVICE_POSITIVE_CS, 200 | ENUM_SPI_DEVICE_HALFDUPLEX = SPI_DEVICE_HALFDUPLEX, 201 | ENUM_SPI_DEVICE_NO_DUMMY = SPI_DEVICE_NO_DUMMY, 202 | ENUM_SPI_DEVICE_CLK_AS_CS = SPI_DEVICE_CLK_AS_CS, 203 | }; 204 | 205 | enum { 206 | ENUM_SPI_TRANS_MODE_DIO = SPI_TRANS_MODE_DIO, 207 | ENUM_SPI_TRANS_MODE_QIO = SPI_TRANS_MODE_QIO, 208 | ENUM_SPI_TRANS_MODE_DIOQIO_ADDR = SPI_TRANS_MODE_DIOQIO_ADDR, 209 | ENUM_SPI_TRANS_USE_RXDATA = SPI_TRANS_USE_RXDATA, 210 | ENUM_SPI_TRANS_USE_TXDATA = SPI_TRANS_USE_TXDATA, 211 | ENUM_SPI_TRANS_VARIABLE_CMD = SPI_TRANS_VARIABLE_CMD, 212 | ENUM_SPI_TRANS_VARIABLE_ADDR = SPI_TRANS_VARIABLE_ADDR, 213 | }; 214 | 215 | enum { 216 | ENUM_MALLOC_CAP_EXEC = MALLOC_CAP_EXEC, 217 | ENUM_MALLOC_CAP_32BIT = MALLOC_CAP_32BIT, 218 | ENUM_MALLOC_CAP_8BIT = MALLOC_CAP_8BIT, 219 | ENUM_MALLOC_CAP_DMA = MALLOC_CAP_DMA, 220 | ENUM_MALLOC_CAP_SPIRAM = MALLOC_CAP_SPIRAM, 221 | ENUM_MALLOC_CAP_INTERNAL = MALLOC_CAP_INTERNAL, 222 | ENUM_MALLOC_CAP_DEFAULT = MALLOC_CAP_DEFAULT, 223 | // Missing on espidf v4.02: 224 | // ENUM_MALLOC_CAP_IRAM_8BIT = MALLOC_CAP_IRAM_8BIT, 225 | ENUM_MALLOC_CAP_INVALID = MALLOC_CAP_INVALID, 226 | }; 227 | 228 | enum { 229 | ENUM_ESP_TASK_PRIO_MAX = ESP_TASK_PRIO_MAX, 230 | ENUM_ESP_TASK_PRIO_MIN = ESP_TASK_PRIO_MIN, 231 | }; 232 | 233 | ///////////////////////////////////////////////////////////////////////////////////////////// 234 | // ili9xxx flush and ISR in C 235 | // 236 | // disp_drv->user_data should be a dict that contains dc and spi, setup by micropython. 237 | // like this: "self.disp_drv.user_data = {'dc': self.dc, 'spi': self.spi, 'dt': display_type}" 238 | 239 | 240 | void ili9xxx_post_cb_isr(spi_transaction_t *trans); 241 | 242 | void ili9xxx_flush(void *disp_drv, const void *area, void *color_p); 243 | 244 | 245 | -------------------------------------------------------------------------------- /driver/esp32/ili9341.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Wrapper function for backward compatibility with new ILI9XXX library. 4 | # 5 | ############################################################################## 6 | 7 | print(""" 8 | *************************************** 9 | * This library is obsoled now! 10 | * Please, use ili9XXX library instead: 11 | * 12 | * from ili9XXX import ili9341 13 | * 14 | *************************************** 15 | """) 16 | 17 | from ili9XXX import ili9341 18 | 19 | -------------------------------------------------------------------------------- /driver/esp32/lv_spi.py: -------------------------------------------------------------------------------- 1 | """ 2 | MicroPython espidf SPI driver 3 | Used, for example, to share spi bus between SPI screen and sdcard.py driver 4 | 5 | Provides simple init, deinit, write, read, readinto and write_readinto methods 6 | 7 | Example usage with sdcard.py driver on ESP32 : 8 | 9 | import machine, os, lv_spi, sdcard 10 | spi = lv_spi.SPI(mosi=23, miso=38, clk=18) 11 | sd = sdcard.SDCard(spi, machine.Pin(4)) 12 | os.mount(sd, "/sd") 13 | print(os.listdir('/sd')) 14 | 15 | Example usage with ili93XXX.py + sdcard.py drivers (SPI bus sharing) : 16 | 17 | import machine, os, lv_spi, sdcard 18 | from ili9XXX import ili9341 19 | lcd = ili9341(mosi=23, miso=38, clk=18, dc=15, cs=5, invert=True, rot=0x10, width=320, height=240, 20 | half_duplex=False) # half_duplex do not work with SDCard 21 | sd = sdcard.SDCard(lv_spi.SPI(), machine.Pin(4)) 22 | os.mount(sd, "/sd") 23 | print(os.listdir('/sd')) 24 | 25 | """ 26 | 27 | import espidf as esp 28 | 29 | class SPI(object): 30 | 31 | SPI_HOST = esp.SPI_HOST 32 | HSPI_HOST = esp.HSPI_HOST 33 | VSPI_HOST = esp.VSPI_HOST 34 | 35 | def __init__(self, spihost=HSPI_HOST, baudrate=200000, miso=-1, mosi=-1, clk=-1, cs=-1, 36 | phase=0, polarity=0, half_duplex=False, max_transfer_sz=4092): 37 | 38 | self.spihost = spihost 39 | self.baudrate = baudrate 40 | self.miso = miso 41 | self.mosi = mosi 42 | self.clk = clk 43 | self.cs = cs 44 | self.phase = phase 45 | self.polarity = polarity 46 | self.half_duplex = half_duplex 47 | self.max_transfer_sz = max_transfer_sz 48 | 49 | self.spi = None 50 | 51 | buscfg = esp.spi_bus_config_t( 52 | { 53 | "miso_io_num": self.miso, 54 | "mosi_io_num": self.mosi, 55 | "sclk_io_num": self.clk, 56 | "quadwp_io_num": -1, 57 | "quadhd_io_num": -1, 58 | "max_transfer_sz": max_transfer_sz, 59 | } 60 | ) 61 | 62 | # Initialize the SPI bus, if needed. 63 | if buscfg.miso_io_num >= 0 and buscfg.mosi_io_num >= 0 and buscfg.sclk_io_num >= 0: 64 | 65 | esp.gpio_pad_select_gpio(self.miso) 66 | esp.gpio_pad_select_gpio(self.mosi) 67 | esp.gpio_pad_select_gpio(self.clk) 68 | 69 | esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) 70 | esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) 71 | esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) 72 | esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) 73 | 74 | ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) 75 | if ret != 0: 76 | raise RuntimeError("Failed initializing SPI bus") 77 | 78 | 79 | def init(self, baudrate=None, phase=None, polarity=None, cs=-1): 80 | 81 | if baudrate: 82 | self.baudrate = baudrate 83 | 84 | if phase: 85 | self.phase = phase 86 | 87 | if polarity: 88 | self.polarity = polarity 89 | 90 | if cs > -1: 91 | self.cs=cs 92 | 93 | if self.spi: 94 | # Remove device (to change device configuration) 95 | esp.spi_bus_remove_device(self.spi) 96 | self.spi = None 97 | 98 | 99 | devcfg_flags = esp.SPI_DEVICE.NO_DUMMY 100 | if self.half_duplex : devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX 101 | 102 | devcfgSD = esp.spi_device_interface_config_t({ 103 | "clock_speed_hz": self.baudrate, 104 | "mode": self.phase | self.polarity << 1, 105 | "spics_io_num": self.cs, 106 | "queue_size": 2, 107 | "flags": devcfg_flags, 108 | "duty_cycle_pos": 128, 109 | }) 110 | 111 | if cs > -1 : 112 | esp.gpio_pad_select_gpio(self.cs) 113 | 114 | # Attach device to the SPI bus 115 | ptr_to_spi = esp.C_Pointer() 116 | ret = esp.spi_bus_add_device(self.spihost, devcfgSD, ptr_to_spi) 117 | if ret != 0: 118 | raise RuntimeError("Failed adding SPI device") 119 | 120 | self.spi = ptr_to_spi.ptr_val 121 | self.trans_result_ptr = esp.C_Pointer() 122 | 123 | 124 | def write(self, data): 125 | if not isinstance(data,bytearray) : data = bytearray(data) 126 | trans = esp.spi_transaction_t({ 127 | "length": len(data) * 8, 128 | "tx_buffer": data, 129 | "rx_buffer": None, 130 | "user": None, 131 | }) 132 | esp.spi_device_queue_trans(self.spi, trans, -1) 133 | esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , -1) 134 | 135 | 136 | def read(self, length:int, write=None): 137 | if write: write = bytearray([write]) 138 | buf=bytearray(length) 139 | trans = esp.spi_transaction_t({ 140 | "length": length * 8, 141 | "tx_buffer": write, 142 | "rx_buffer": buf, 143 | "user": None, 144 | }) 145 | esp.spi_device_queue_trans(self.spi, trans, -1) 146 | esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , -1) 147 | return bytes(buf) 148 | 149 | 150 | def readinto(self, buf:bytearray, write=None): 151 | if write: write = bytearray([write]) 152 | trans = esp.spi_transaction_t({ 153 | "length": len(buf) * 8, 154 | "tx_buffer": write, 155 | "rx_buffer": buf, 156 | "user": None, 157 | }) 158 | esp.spi_device_queue_trans(self.spi, trans, -1) 159 | esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , -1) 160 | 161 | 162 | def write_readinto(self, data, buf:bytearray): 163 | if not isinstance(data,bytearray) : data = bytearray(data) 164 | trans = esp.spi_transaction_t({ 165 | "length": len(data) * 8, 166 | "tx_buffer": data, 167 | "rx_buffer": buf, 168 | "user": None, 169 | }) 170 | esp.spi_device_queue_trans(self.spi, trans, -1) 171 | esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , -1) 172 | 173 | 174 | def deinit(self): 175 | 176 | if self.spi: 177 | # Remove device 178 | esp.spi_bus_remove_device(self.spi) 179 | self.spi = None 180 | 181 | if self.miso >= 0 and self.mosi >= 0 and self.clk >= 0: 182 | # Free SPI bus 183 | esp.spi_bus_free(self.spihost) 184 | self.spihost = None 185 | 186 | -------------------------------------------------------------------------------- /driver/esp32/xpt2046.py: -------------------------------------------------------------------------------- 1 | from micropython import const 2 | 3 | import espidf as esp 4 | import lvgl as lv 5 | 6 | # TODO: Viper/native emmitters don't behave well when module is frozen. 7 | 8 | _STATE_PRESSED = lv.INDEV_STATE.PRESSED 9 | _STATE_RELEASED = lv.INDEV_STATE.RELEASED 10 | 11 | class xpt2046: 12 | 13 | # Command is 8 bit, but we add another bit as a space before xpt2046 stats sending the response, See Figure 12 on the datasheet 14 | CMD_X_READ = const(0b100100000) 15 | CMD_Y_READ = const(0b110100000) 16 | CMD_Z1_READ = const(0b101100000) 17 | CMD_Z2_READ = const(0b110000000) 18 | 19 | MAX_RAW_COORD = const((1<<12) - 1) 20 | 21 | def __init__(self, miso=-1, mosi=-1, clk=-1, cs=25, 22 | spihost=esp.HSPI_HOST, half_duplex=True, mhz=5, max_cmds=16, 23 | cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, 24 | transpose = True, samples = 3): 25 | 26 | # Initializations 27 | if not lv.is_initialized(): 28 | lv.init() 29 | disp = lv.display_t() 30 | else: 31 | disp = lv.display_get_default() 32 | 33 | self.screen_width = disp.get_horizontal_resolution() 34 | self.screen_height = disp.get_vertical_resolution() 35 | self.miso = miso 36 | self.mosi = mosi 37 | self.clk = clk 38 | self.cs = cs 39 | self.spihost = spihost 40 | self.half_duplex = half_duplex 41 | self.mhz = mhz 42 | self.max_cmds = max_cmds 43 | self.cal_x0 = cal_x0 44 | self.cal_y0 = cal_y0 45 | self.cal_x1 = cal_x1 46 | self.cal_y1 = cal_y1 47 | self.transpose = transpose 48 | self.samples = samples 49 | 50 | self.trans_result_ptr = esp.C_Pointer() 51 | self.start_time_ptr = esp.C_Pointer() 52 | self.end_time_ptr = esp.C_Pointer() 53 | self.cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 54 | 55 | self.touch_count = 0 56 | self.touch_cycles = 0 57 | 58 | self.spi_init() 59 | 60 | self.indev_drv = lv.indev_create() 61 | self.indev_drv.set_type(lv.INDEV_TYPE.POINTER) 62 | self.indev_drv.set_display(disp) 63 | self.indev_drv.set_read_cb(self.read) 64 | 65 | def calibrate(self, x0, y0, x1, y1): 66 | self.cal_x0 = x0 67 | self.cal_y0 = y0 68 | self.cal_x1 = x1 69 | self.cal_y1 = y1 70 | 71 | def spi_init(self): 72 | buscfg = esp.spi_bus_config_t({ 73 | "miso_io_num": self.miso, 74 | "mosi_io_num": self.mosi, 75 | "sclk_io_num": self.clk, 76 | "quadwp_io_num": -1, 77 | "quadhd_io_num": -1, 78 | "max_transfer_sz": 4, 79 | }) 80 | 81 | devcfg_flags = 0 # esp.SPI_DEVICE.NO_DUMMY 82 | if self.half_duplex: 83 | devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX 84 | 85 | devcfg = esp.spi_device_interface_config_t({ 86 | "command_bits": 9, # Actually 8, but need another cycle before xpt starts transmitting response, see Figure 12 on the datasheet. 87 | "clock_speed_hz": self.mhz*1000*1000, 88 | "mode": 0, # SPI mode 0 89 | "spics_io_num": self.cs, # CS pin 90 | "queue_size": self.max_cmds, 91 | "flags": devcfg_flags, 92 | "duty_cycle_pos": 128, 93 | }) 94 | 95 | esp.gpio_pad_select_gpio(self.cs) 96 | 97 | # Initialize the SPI bus, if needed 98 | 99 | if buscfg.miso_io_num >= 0 and \ 100 | buscfg.mosi_io_num >= 0 and \ 101 | buscfg.sclk_io_num >= 0: 102 | 103 | esp.gpio_pad_select_gpio(self.miso) 104 | esp.gpio_pad_select_gpio(self.mosi) 105 | esp.gpio_pad_select_gpio(self.clk) 106 | 107 | esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) 108 | esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) 109 | esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) 110 | esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) 111 | 112 | ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) 113 | if ret != 0: raise RuntimeError("Failed initializing SPI bus") 114 | 115 | # Attach the xpt2046 to the SPI bus 116 | ptr_to_spi = esp.C_Pointer() 117 | ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) 118 | if ret != 0: raise RuntimeError("Failed adding SPI device") 119 | self.spi = ptr_to_spi.ptr_val 120 | 121 | # Prepare transactions. Each response is 16bit long 122 | 123 | self.trans = [esp.spi_transaction_t({ 124 | 'rx_buffer': bytearray(2), 125 | 'length': 0 if self.half_duplex else 16, 126 | 'rxlength': 16 127 | }) for i in range(0, self.max_cmds)] 128 | 129 | 130 | # 131 | # Deinitalize SPI device and bus 132 | # 133 | 134 | def deinit(self): 135 | 136 | print('Deinitializing XPT2046...') 137 | 138 | if self.spi: 139 | 140 | # Pop all pending transaction results 141 | ret = 0 142 | while ret == 0: 143 | ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , 1) 144 | 145 | # Remove device 146 | esp.spi_bus_remove_device(self.spi) 147 | 148 | # Free SPI bus 149 | esp.spi_bus_free(self.spihost) 150 | 151 | 152 | # @micropython.viper 153 | def xpt_cmds(self, cmds): 154 | cmd_count = int(len(cmds)) 155 | for i in range(0, cmd_count): 156 | self.trans[i].cmd = cmds[i] 157 | esp.spi_device_queue_trans(self.spi, self.trans[i], -1) 158 | result = [] 159 | for i in range(0, cmd_count): 160 | esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , -1) 161 | buf = self.trans[i].rx_buffer.__dereference__(2) 162 | value = (int(buf[0]) << 4) + (int(buf[1]) >> 4) # value is in the 12 higher bits, network order 163 | if value == int(self.MAX_RAW_COORD): 164 | value = 0 165 | result.append(value) 166 | return tuple(result) 167 | 168 | # @micropython.viper 169 | def get_med_coords(self, count : int): 170 | mid = count//2 171 | values = [] 172 | for i in range(0, count): 173 | values.append(self.xpt_cmds([self.CMD_X_READ, self.CMD_Y_READ])) 174 | # values = self.xpt_cmds([self.CMD_X_READ]*count + [self.CMD_Y_READ]*count) 175 | # x_values = sorted(values[:count]) 176 | # y_values = sorted(values[count:]) 177 | x_values = sorted([x for x,y in values]) 178 | y_values = sorted([y for x,y in values]) 179 | if int(x_values[0]) == 0 or int(y_values[0]) == 0 : return None 180 | return x_values[mid], y_values[mid] 181 | 182 | # @micropython.viper 183 | def get_coords(self): 184 | med_coords = self.get_med_coords(int(self.samples)) 185 | if not med_coords: return None 186 | if self.transpose: 187 | raw_y, raw_x = med_coords 188 | else: 189 | raw_x, raw_y = med_coords 190 | 191 | if int(raw_x) != 0 and int(raw_y) != 0: 192 | x = ((int(raw_x) - int(self.cal_x0)) * int(self.screen_width)) // (int(self.cal_x1) - int(self.cal_x0)) 193 | y = ((int(raw_y) - int(self.cal_y0)) * int(self.screen_height)) // (int(self.cal_y1) - int(self.cal_y0)) 194 | # print('(%d, %d) ==> (%d, %d)' % (raw_x, raw_y, x, y)) 195 | return x,y 196 | else: return None 197 | 198 | # @micropython.native 199 | def get_pressure(self, factor : int) -> int: 200 | z1, z2, x = self.xpt_cmds([self.CMD_Z1_READ, self.CMD_Z2_READ, self.CMD_X_READ]) 201 | if int(z1) == 0: return -1 202 | return ( (int(x)*factor) / 4096)*( int(z2)/int(z1) - 1) 203 | 204 | 205 | # @micropython.native 206 | def read(self, indev_drv, data) -> int: 207 | 208 | esp.get_ccount(self.start_time_ptr) 209 | coords = self.get_coords() 210 | esp.get_ccount(self.end_time_ptr) 211 | 212 | if self.end_time_ptr.int_val > self.start_time_ptr.int_val: 213 | self.touch_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val 214 | self.touch_count += 1 215 | 216 | if coords: 217 | data.point.x ,data.point.y = coords 218 | data.state = _STATE_PRESSED 219 | return False 220 | data.state = _STATE_RELEASED 221 | return False 222 | 223 | def stat(self): 224 | return self.touch_cycles / (self.touch_count * self.cycles_in_ms) 225 | 226 | 227 | -------------------------------------------------------------------------------- /driver/generic/ft6x36.py: -------------------------------------------------------------------------------- 1 | # Pure Python LVGL indev driver for the FocalTech FT6X36 capacitive touch IC 2 | # 3 | # from ft6x36 import ft6x36 4 | # 5 | # touch = ft6x36(sda=, scl=) 6 | # 7 | # If you set the size of the touchpad, you have the option to invert each 8 | # axis, and you'll get some extra robustness against occasional read errors 9 | # as values outside the touchpad are quietly rejected. If you select to swap 10 | # the axes, width and height as well as the inversions refer to the situation 11 | # before the swap. 12 | # 13 | # The nice thing about this driver is that it allows access to the second 14 | # finger, as the FT6X36 is multi-touch. (Two fingers max, with caveats on 15 | # some boards.) 16 | # 17 | # The number of presses is in touch.presses, touch.points[0] and points[1] 18 | # hold the positions. LVGL is not (yet) multi-touch, so all it sees is the 19 | # position in points[0]. 20 | 21 | 22 | import lvgl as lv 23 | from machine import I2C, Pin 24 | 25 | class ft6x36: 26 | 27 | def __init__(self, i2c_dev=0, sda=21, scl=22, freq=400000, addr=0x38, width=-1, height=-1, 28 | inv_x=False, inv_y=False, swap_xy=False): 29 | 30 | if not lv.is_initialized(): 31 | lv.init() 32 | 33 | self.width, self.height = width, height 34 | self.inv_x, self.inv_y, self.swap_xy = inv_x, inv_y, swap_xy 35 | self.i2c = I2C(i2c_dev, sda=Pin(sda), scl=Pin(scl), freq=freq) 36 | self.addr = addr 37 | try: 38 | print("FT6X36 touch IC ready (fw id 0x{0:X} rel {1:d}, lib {2:X})".format( \ 39 | int.from_bytes(self.i2c.readfrom_mem(self.addr, 0xA6, 1), "big"), \ 40 | int.from_bytes(self.i2c.readfrom_mem(self.addr, 0xAF, 1), "big"), \ 41 | int.from_bytes(self.i2c.readfrom_mem(self.addr, 0xA1, 2), "big") \ 42 | )) 43 | except: 44 | print("FT6X36 touch IC not responding") 45 | return 46 | self.point = lv.point_t( {'x': 0, 'y': 0} ) 47 | self.points = [lv.point_t( {'x': 0, 'y': 0} ), lv.point_t( {'x': 0, 'y': 0} )] 48 | self.state = lv.INDEV_STATE.RELEASED 49 | 50 | self.indev_drv = lv.indev_create() 51 | self.indev_drv.set_type(lv.INDEV_TYPE.POINTER) 52 | self.indev_drv.set_read_cb(self.callback) 53 | 54 | def callback(self, driver, data): 55 | 56 | def get_point(offset): 57 | x = (sensorbytes[offset ] << 8 | sensorbytes[offset + 1]) & 0x0fff 58 | y = (sensorbytes[offset + 2] << 8 | sensorbytes[offset + 3]) & 0x0fff 59 | if (self.width != -1 and x >= self.width) or (self.height != -1 and y >= self.height): 60 | raise ValueError 61 | x = self.width - x - 1 if self.inv_x else x 62 | y = self.height - y - 1 if self.inv_y else y 63 | (x, y) = (y, x) if self.swap_xy else (x, y) 64 | return { 'x': x, 'y': y } 65 | 66 | data.point = self.points[0] 67 | data.state = self.state 68 | sensorbytes = self.i2c.readfrom_mem(self.addr, 2, 11) 69 | self.presses = sensorbytes[0] 70 | if self.presses > 2: 71 | return 72 | try: 73 | if self.presses: 74 | self.points[0] = get_point(1) 75 | if self.presses == 2: 76 | self.points[1] = get_point(7) 77 | except ValueError: 78 | return 79 | if sensorbytes[3] >> 4: 80 | self.points[0], self.points[1] = self.points[1], self.points[0] 81 | data.point = self.points[0] 82 | data.state = self.state = lv.INDEV_STATE.PRESSED if self.presses else lv.INDEV_STATE.RELEASED 83 | -------------------------------------------------------------------------------- /driver/generic/ili9xxx-test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from machine import SPI, Pin 4 | 5 | from ili9xxx import Ili9341_hw 6 | 7 | 8 | def build_rect_buf(w, h, inner=[0x00, 0x00]): 9 | top = b"\xFF\xFF" * w 10 | body = (b"\xFF\xFF\xFF" + bytes(inner) * (w - 3) + b"\xFF\xFF\xFF") * (h - 3) 11 | bot = b"\xFF\xFF" * w 12 | return top + body + bot 13 | 14 | 15 | spi = SPI( 16 | 0, 17 | baudrate=24_000_000, 18 | sck=Pin(18), 19 | mosi=Pin(19), 20 | miso=Pin(16), 21 | ) 22 | lcd = Ili9341_hw(spi=spi, cs=17, dc=15, rst=14) 23 | 24 | lcd.set_backlight(30) 25 | for rot in (0, 1, 2, 3): 26 | lcd.apply_rotation(rot) 27 | 28 | lcd.clear(0x0000) 29 | # 1/4 screen pixels square with white border red background 30 | w, h = lcd.width // 4, lcd.height // 8 31 | bmp = build_rect_buf(w, h, [0x03, 0x03]) 32 | t0 = time.ticks_us() 33 | lcd.blit(w, h, w, h, bmp) 34 | t1 = time.ticks_us() 35 | bmp = build_rect_buf(lcd.width, lcd.height // 20, [0x09, 0x09]) 36 | lcd.blit(0, 0, lcd.width, lcd.height // 20, bmp) 37 | 38 | print("Maximum FPS @24MHz:", 24e6 / (320 * 240 * 16)) # FPS = F/(W*H*BPP) 39 | print( 40 | "Achieved FPS:", 1 / (16 * (t1 - t0) * 1e-6) 41 | ) # Note: Test only draws 1/16 of the sreen area 42 | 43 | print("Draw TSC calibration pattern") 44 | w, h, wu, hu = lcd.width // 10, lcd.height // 10, lcd.width // 5, lcd.height // 5 45 | bmp = build_rect_buf(w, h, [0xA0, 0xF0]) 46 | lcd.blit(wu, hu, w, h, bmp) 47 | lcd.blit(4 * wu, hu, w, h, bmp) 48 | lcd.blit(4 * wu, 4 * hu, w, h, bmp) 49 | lcd.blit(wu, 4 * hu, w, h, bmp) 50 | time.sleep(0.5) 51 | -------------------------------------------------------------------------------- /driver/generic/ili9xxx.py: -------------------------------------------------------------------------------- 1 | """Generic ILI9xxx drivers. 2 | 3 | This code is licensed under MIT license. 4 | 5 | Adapted from: 6 | https://github.com/rdagger/micropython-ili9341 7 | 8 | The following code snippet will instantiate the driver and 9 | automatically register it to lvgl. Adjust the SPI bus and 10 | pin configurations to match your hardware setup:: 11 | 12 | import ili9xxx 13 | from machine import SPI, Pin 14 | spi = SPI(0, baudrate=24_000_000, sck=Pin(18), mosi=Pin(19), miso=Pin(16)) 15 | drv = ili9xxx.Ili9341(spi=spi, dc=15, cs=17, rst=14) 16 | """ 17 | from micropython import const 18 | 19 | import st77xx 20 | 21 | # Command constants from ILI9341 datasheet 22 | _NOP = const(0x00) # No-op 23 | _SWRESET = const(0x01) # Software reset 24 | _RDDID = const(0x04) # Read display ID info 25 | _RDDST = const(0x09) # Read display status 26 | _SLPIN = const(0x10) # Enter sleep mode 27 | _SLPOUT = const(0x11) # Exit sleep mode 28 | _PTLON = const(0x12) # Partial mode on 29 | _NORON = const(0x13) # Normal display mode on 30 | _RDMODE = const(0x0A) # Read display power mode 31 | _RDMADCTL = const(0x0B) # Read display MADCTL 32 | _RDPIXFMT = const(0x0C) # Read display pixel format 33 | _RDIMGFMT = const(0x0D) # Read display image format 34 | _RDSELFDIAG = const(0x0F) # Read display self-diagnostic 35 | _INVOFF = const(0x20) # Display inversion off 36 | _INVON = const(0x21) # Display inversion on 37 | _GAMMASET = const(0x26) # Gamma set 38 | _DISPLAY_OFF = const(0x28) # Display off 39 | _DISPLAY_ON = const(0x29) # Display on 40 | _SET_COLUMN = const(0x2A) # Column address set 41 | _SET_PAGE = const(0x2B) # Page address set 42 | _WRITE_RAM = const(0x2C) # Memory write 43 | _READ_RAM = const(0x2E) # Memory read 44 | _PTLAR = const(0x30) # Partial area 45 | _VSCRDEF = const(0x33) # Vertical scrolling definition 46 | _MADCTL = const(0x36) # Memory access control 47 | _VSCRSADD = const(0x37) # Vertical scrolling start address 48 | _PIXFMT = const(0x3A) # COLMOD: Pixel format set 49 | _WRITE_DISPLAY_BRIGHTNESS = const(0x51) # Brightness hardware dependent! 50 | _READ_DISPLAY_BRIGHTNESS = const(0x52) 51 | _WRITE_CTRL_DISPLAY = const(0x53) 52 | _READ_CTRL_DISPLAY = const(0x54) 53 | _WRITE_CABC = const(0x55) # Write Content Adaptive Brightness Control 54 | _READ_CABC = const(0x56) # Read Content Adaptive Brightness Control 55 | _WRITE_CABC_MINIMUM = const(0x5E) # Write CABC Minimum Brightness 56 | _READ_CABC_MINIMUM = const(0x5F) # Read CABC Minimum Brightness 57 | _FRMCTR1 = const(0xB1) # Frame rate control (In normal mode/full colors) 58 | _FRMCTR2 = const(0xB2) # Frame rate control (In idle mode/8 colors) 59 | _FRMCTR3 = const(0xB3) # Frame rate control (In partial mode/full colors) 60 | _INVCTR = const(0xB4) # Display inversion control 61 | _DFUNCTR = const(0xB6) # Display function control 62 | _PWCTR1 = const(0xC0) # Power control 1 63 | _PWCTR2 = const(0xC1) # Power control 2 64 | _PWCTRA = const(0xCB) # Power control A 65 | _PWCTRB = const(0xCF) # Power control B 66 | _VMCTR1 = const(0xC5) # VCOM control 1 67 | _VMCTR2 = const(0xC7) # VCOM control 2 68 | _RDID1 = const(0xDA) # Read ID 1 69 | _RDID2 = const(0xDB) # Read ID 2 70 | _RDID3 = const(0xDC) # Read ID 3 71 | _RDID4 = const(0xDD) # Read ID 4 72 | _GMCTRP1 = const(0xE0) # Positive gamma correction 73 | _GMCTRN1 = const(0xE1) # Negative gamma correction 74 | _DTCA = const(0xE8) # Driver timing control A 75 | _DTCB = const(0xEA) # Driver timing control B 76 | _POSC = const(0xED) # Power on sequence control 77 | _ENABLE3G = const(0xF2) # Enable 3 gamma control 78 | _PUMPRC = const(0xF7) # Pump ratio control 79 | 80 | _MADCTL_MY = const(0x80) # page address order (0: top to bottom; 1: bottom to top) 81 | _MADCTL_MX = const(0x40) # column address order (0: left to right; 1: right to left) 82 | _MADCTL_MV = const(0x20) # page/column order (0: normal mode 1; reverse mode) 83 | _MADCTL_ML = const( 84 | 0x10 85 | ) # line address order (0: refresh to to bottom; 1: refresh bottom to top) 86 | _MADCTL_BGR = const(0x08) # colors are BGR (not RGB) 87 | _MADCTL_RTL = const(0x04) # refresh right to left 88 | 89 | _MADCTL_ROTS = ( 90 | const(_MADCTL_MX), # 0 = portrait 91 | const(_MADCTL_MV), # 1 = landscape 92 | const(_MADCTL_MY), # 2 = inverted portrait 93 | const(_MADCTL_MX | _MADCTL_MY | _MADCTL_MV), # 3 = inverted landscape 94 | ) 95 | 96 | ILI9XXX_PORTRAIT = st77xx.ST77XX_PORTRAIT 97 | ILI9XXX_LANDSCAPE = st77xx.ST77XX_LANDSCAPE 98 | ILI9XXX_INV_PORTRAIT = st77xx.ST77XX_INV_PORTRAIT 99 | ILI9XXX_INV_LANDSCAPE = st77xx.ST77XX_INV_LANDSCAPE 100 | 101 | 102 | class Ili9341_hw(st77xx.St77xx_hw): 103 | def __init__(self, **kw): 104 | """ILI9341 TFT Display Driver.""" 105 | super().__init__( 106 | res=(240, 320), 107 | suppRes=[ 108 | (240, 320), 109 | ], 110 | model=None, 111 | suppModel=None, 112 | bgr=False, 113 | **kw, 114 | ) 115 | 116 | def config_hw(self): 117 | self._run_seq( 118 | [ 119 | (_SLPOUT, None, 100), 120 | (_PWCTRB, b"\x00\xC1\x30"), # Pwr ctrl B 121 | (_POSC, b"\x64\x03\x12\x81"), # Pwr on seq. ctrl 122 | (_DTCA, b"\x85\x00\x78"), # Driver timing ctrl A 123 | (_PWCTRA, b"\x39\x2C\x00\x34\x02"), # Pwr ctrl A 124 | (_PUMPRC, b"\x20"), # Pump ratio control 125 | (_DTCB, b"\x00\x00"), # Driver timing ctrl B 126 | (_PWCTR1, b"\x23"), # Pwr ctrl 1 127 | (_PWCTR2, b"\x10"), # Pwr ctrl 2 128 | (_VMCTR1, b"\x3E\x28"), # VCOM ctrl 1 129 | (_VMCTR2, b"\x86"), # VCOM ctrl 2 130 | (_VSCRSADD, b"\x00"), # Vertical scrolling start address 131 | (_PIXFMT, b"\x55"), # COLMOD: Pixel format 132 | (_FRMCTR1, b"\x00\x18"), # Frame rate ctrl 133 | (_DFUNCTR, b"\x08\x82\x27"), 134 | (_ENABLE3G, b"\x00"), # Enable 3 gamma ctrl 135 | (_GAMMASET, b"\x01"), # Gamma curve selected 136 | ( 137 | _GMCTRP1, 138 | b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00", 139 | ), 140 | ( 141 | _GMCTRN1, 142 | b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F", 143 | ), 144 | (_SLPOUT, None, 100), 145 | (_DISPLAY_ON, None), 146 | ] 147 | ) 148 | 149 | def apply_rotation(self, rot): 150 | self.rot = rot 151 | if (self.rot % 2) == 0: 152 | self.width, self.height = self.res 153 | else: 154 | self.height, self.width = self.res 155 | self.write_register( 156 | _MADCTL, 157 | bytes([_MADCTL_BGR | _MADCTL_ROTS[self.rot % 4]]), 158 | ) 159 | 160 | 161 | class Ili9341(Ili9341_hw, st77xx.St77xx_lvgl): 162 | def __init__(self, doublebuffer=True, factor=4, **kw): 163 | """See :obj:`Ili9341_hw` for the meaning of the parameters.""" 164 | 165 | Ili9341_hw.__init__(self, **kw) 166 | st77xx.St77xx_lvgl.__init__(self, doublebuffer, factor) 167 | -------------------------------------------------------------------------------- /driver/generic/st77xx-test.py: -------------------------------------------------------------------------------- 1 | import machine 2 | import sys 3 | sys.path.append('.') 4 | from st77xx import * 5 | 6 | def build_rect_buf(w, h, inner=[0x00,0x00]): 7 | top = b"\xFF\xFF"*w 8 | body=(b"\xFF\xFF\xFF" + bytes(inner)*(w-3) + b"\xFF\xFF\xFF")*(h-3) 9 | bot = b"\xFF\xFF"*w 10 | return top + body + bot 11 | 12 | def test_lcd(lcd): 13 | # lcd.hard_reset() 14 | lcd.set_backlight(30) 15 | for rot in (0,1,2,3): 16 | lcd.apply_rotation(rot) 17 | 18 | lcd.clear(0x0000) 19 | # 1/4 screen pixels square with white border red background 20 | w,h=lcd.width//4,lcd.height//8 21 | bmp=build_rect_buf(w,h,[0x03,0x03]) 22 | t0=time.ticks_us() 23 | lcd.blit(w,h,w,h,bmp) 24 | t1=time.ticks_us() 25 | bmp=build_rect_buf(lcd.width,lcd.height//20,[0x09,0x09]) 26 | lcd.blit(0,0,lcd.width,lcd.height//20,bmp) 27 | 28 | print("Maximum FPS @24MHz:",24e6/(320*240*16)) # FPS = F/(W*H*BPP) 29 | print("Achieved FPS:",1/(16*(t1-t0)*1e-6)) # Note: Test only draws 1/16 of the sreen area 30 | 31 | print( "Draw TSC calibration pattern") 32 | w,h,wu,hu=lcd.width//10,lcd.height//10,lcd.width//5,lcd.height//5 33 | bmp=build_rect_buf(w,h,[0xa0,0xf0]) 34 | lcd.blit(wu,hu,w,h,bmp) 35 | lcd.blit(4*wu,hu,w,h,bmp) 36 | lcd.blit(4*wu,4*hu,w,h,bmp) 37 | lcd.blit(wu,4*hu,w,h,bmp) 38 | time.sleep(.5) 39 | 40 | for p in (20,100,80,50,10,60): 41 | lcd.set_backlight(p) 42 | time.sleep(.1) 43 | 44 | spi=machine.SPI( 45 | 1, 46 | baudrate=24_000_000, 47 | polarity=0, 48 | phase=0, 49 | sck=machine.Pin(10,machine.Pin.OUT), 50 | mosi=machine.Pin(11,machine.Pin.OUT), 51 | miso=machine.Pin(12,machine.Pin.IN) 52 | ) 53 | # dma=rp2_dma.DMA(0) 54 | rp2_dma=None 55 | 56 | if 1: 57 | # Waveshare Pi Pico 2.8 LCD https://www.waveshare.com/Pico-ResTouch-LCD-2.8.htm 58 | waveshare_28_lcd=St7789_hw(rot=0,res=(240,320),spi=spi,rp2_dma=rp2_dma,cs=9,dc=8,bl=13,rst=15) 59 | test_lcd(lcd=waveshare_28_lcd) 60 | if 1: 61 | # Waveshare Pi Pico 1.8 LCD https://www.waveshare.com/wiki/Pico-LCD-1.8 62 | # (not sure if this is redtab, but the driver works; someone with access to more hardware can adjust perhaps) 63 | waveshare_18_lcd=St7735_hw(rot=0,res=(128,160),spi=spi,rp2_dma=rp2_dma,cs=9,dc=8,bl=13,rst=12,model='redtab') 64 | test_lcd(lcd=waveshare_18_lcd) 65 | if 1: 66 | # no-name variant which arduino library calls blacktab (IIRC) 67 | noname_177_lcd=St7735_hw(rot=0,res=(128,160),spi=spi,rp2_dma=None,cs=9,dc=8,bl=13,rst=12,model='blacktab') 68 | test_lcd(lcd=noname_177_lcd) 69 | 70 | -------------------------------------------------------------------------------- /driver/generic/xpt2046-test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('.') 3 | from xpt2046 import * 4 | 5 | 6 | TP_CLK_PIN,TP_MOSI_PIN,TP_MISO_PIN=10,11,12 7 | TP_CS_PIN=16 8 | spi=machine.SPI( 9 | 1, 10 | baudrate=2_000_000, # the chip does not handle more than 2MHz (!) 11 | polarity=0, 12 | phase=0, 13 | sck=machine.Pin(TP_CLK_PIN, machine.Pin.OUT), 14 | mosi=machine.Pin(TP_MOSI_PIN, machine.Pin.OUT), 15 | miso=machine.Pin(TP_MISO_PIN, machine.Pin.OUT), 16 | ) 17 | 18 | tsc=Xpt2046_hw(spi=spi,cs=TP_CS_PIN,rot=1) 19 | for i in range(100000): 20 | if p:=tsc.pos(): print(p) 21 | 22 | -------------------------------------------------------------------------------- /driver/generic/xpt2046.py: -------------------------------------------------------------------------------- 1 | # © 2022 Václav Šmilauer 2 | # MIT-licensed 3 | import machine 4 | import struct 5 | 6 | # for inspiration see e.g. 7 | # https://github.com/MatthewLowden/RPi-XPT2046-Touchscreen-Python/blob/master/XPT2046.py 8 | # and the XPT2046 datasheet 9 | 10 | XPT2046_PORTRAIT = const(0) 11 | XPT2046_LANDSCAPE = const(1) 12 | XPT2046_INV_PORTRAIT = const(2) 13 | XPT2046_INV_LANDSCAPE = const(3) 14 | 15 | 16 | class Xpt2046_hw(object): 17 | CHAN_X = const(0b0101_0000) 18 | CHAN_Y = const(0b0001_0000) 19 | CHAN_Z1 = const(0b0011_0000) 20 | CHAN_Z2 = const(0b0100_0000) 21 | CHAN_T0 = const(0b0000_0000) 22 | CHAN_T1 = const(0b0111_0000) 23 | CHAN_BAT= const(0b0010_0000) 24 | CHAN_AUX= const(0b0110_0000) 25 | 26 | CONV_8_BIT =const(0b0000_1000) 27 | CONV_12_BIT=const(0b0000_0000) 28 | START_BIT =const(0b1000_0000) 29 | 30 | def _chanRead(self,chan): 31 | self.cs.value(0) 32 | struct.pack_into('BBB',self.buf,0,Xpt2046.START_BIT|self.conv|chan,0,0) 33 | self.spi.write_readinto(self.buf,self.buf) 34 | if self.conv==Xpt2046.CONV_8_BIT: ret=self.buf[1] 35 | else: ret=(self.buf[1]<<4)|(self.buf[2]>>4) 36 | self.cs.value(1) 37 | return ret 38 | 39 | def __init__(self,*, 40 | spi: machine.SPI,cs,bits=12,ranges=((100,1900),(200,1950)),width=240,height=320,rot=XPT2046_PORTRAIT): 41 | ''' 42 | Construct the Xpt2046 touchscreen controller. 43 | *spi*: spi bus instance; its baud rate must *not* exceed 2_000_000 (2MHz) for correct functionality 44 | *cs*: chip select (GPIO number or machine.Pin instance) 45 | *bits*: ADC precision, can be 12 or 8; note that 8 will require you to provide different *ranges* 46 | *ranges*: `(x_min,x_max),(y_min,y_max)` for raw coordinate readings; calibrated values might be provided. 47 | *width*: width of the underlying screen in pixels, in natural (rot=0) orientation (0..*width* is the range for reported horizontal coordinate) 48 | *height*: height of the underlying screen in pixels 49 | *rot*: screen rotation (0: portrait, 1: landscape, 2: inverted portrait, 3: inverted landscape); the constants XPT2046_PORTRAIT, XPT2046_LANDSCAPE, XPT2046_INV_PORTRAIT, XPT2046_INV_LANDSCAPE may be used. 50 | ''' 51 | self.buf = bytearray(3) 52 | self.spi = spi 53 | self.cs = (machine.Pin(cs,machine.Pin.OUT) if isinstance(cs,int) else cs) 54 | self.cs.value(1) 55 | if bits not in (8,12): raise ValueError('Xpt2046.bits: must be 8 or 12 (not %s)'%str(bits)) 56 | self.conv=(Xpt2046.CONV_8_BIT if bits==8 else Xpt2046.CONV_12_BIT) 57 | self.xy_range,self.dim,self.rot=ranges,(width,height),(rot%4) 58 | self.xy_scale=[self.dim[ax]*1./(self.xy_range[ax][1]-self.xy_range[ax][0]) for ax in (0,1)] 59 | self.xy_origin=[self.xy_range[ax][0] for ax in (0,1)] 60 | def _raw2px(self,rxy): 61 | 'Convert raw coordinates to pixel coordinates' 62 | x,y=[int(self.xy_scale[ax]*(rxy[ax]-self.xy_origin[ax])) for ax in (0,1)] 63 | if self.rot==0: return self.dim[0]-x,y 64 | elif self.rot==1: return self.dim[1]-y,x 65 | elif self.rot==2: return x,self.dim[1]-y 66 | else: return y,self.dim[0]-x 67 | def _raw_pos(self): 68 | 'Read raw position; return value if within valid ranges (`__init__(ranges=...)`) or `None` if outside.' 69 | ret=[0,0] 70 | for ax,chan in [(0,Xpt2046.CHAN_X),(1,Xpt2046.CHAN_Y)]: 71 | r=self._chanRead(chan) 72 | if not self.xy_range[ax][0]<=r<=self.xy_range[ax][1]: return None 73 | ret[ax]=r 74 | return ret 75 | 76 | def pos(self,N=10,attempts=20): 77 | '''' 78 | Get N position readings (limited by 20 attempts) and return mean position of valid readings. 79 | If attempts are exhausted, return None. 80 | ''' 81 | N,attempts=10,20 82 | xx,yy,done=0,0,0 83 | for _ in range(attempts): 84 | if (r:=self._raw_pos()) is None: continue 85 | xx+=r[0]; yy+=r[1]; done+=1 86 | if done==N: break 87 | else: return None 88 | mx,my=xx*1./N,yy*1./N 89 | return self._raw2px((mx,my)) 90 | 91 | 92 | class Xpt2046(Xpt2046_hw): 93 | def indev_drv_read_cb(self, indev_drv, data): 94 | # wait for DMA transfer (if any) before switchint SPI to 1 MHz 95 | if self.spiPrereadCb: self.spiPrereadCb() 96 | # print('.',end='') 97 | if self.spiRate: self.spi.init(baudrate=1_000_000) 98 | pos=self.pos() 99 | if pos is None: data.state=0 100 | else: (data.point.x,data.point.y),data.state=pos,1 101 | # print('#',end='') 102 | # switch SPI back to spiRate 103 | if self.spiRate: self.spi.init(baudrate=self.spiRate) 104 | 105 | def __init__(self,spi,spiRate=24_000_000,spiPrereadCb=None,**kw): 106 | '''XPT2046 touchscreen driver for LVGL; cf. documentation of :obj:`Xpt2046_hw` for the meaning of parameters being passed. 107 | 108 | *spiPrereadCb*: call this before reading from SPI; used to block until DMA transfer is complete (when sharing SPI bus). 109 | *spiRate*: the SPI bus must set to low frequency (1MHz) when reading from the XPT2046; when *spiRate* is given, the bus will be switched back to this frequency when XPT2046 is done reading. The default 24MHz targets St77xx display chips which operate at that frequency and come often with XPT2046-based touchscreen. 110 | ''' 111 | super().__init__(spi=spi,**kw) 112 | self.spiRate=spiRate 113 | self.spiPrereadCb=spiPrereadCb 114 | 115 | import lvgl as lv 116 | if not lv.is_initialized(): lv.init() 117 | 118 | self.indev_drv = lv.indev_create() 119 | self.indev_drv.set_type(lv.INDEV_TYPE.POINTER) 120 | self.indev_drv.set_read_cb(self.indev_drv_read_cb) 121 | -------------------------------------------------------------------------------- /driver/include/common.h: -------------------------------------------------------------------------------- 1 | #ifndef __LVMP_DRV_COMMON_H 2 | #define __LVMP_DRV_COMMON_H 3 | 4 | #include "py/obj.h" 5 | #include "py/runtime.h" 6 | #include "py/binary.h" 7 | 8 | ////////////////////////////////////////////////////////////////////////////// 9 | // A read-only buffer that contains a C pointer 10 | // Used to communicate function pointers to lvgl Micropython bindings 11 | // 12 | 13 | typedef struct mp_ptr_t 14 | { 15 | mp_obj_base_t base; 16 | void *ptr; 17 | } mp_ptr_t; 18 | 19 | STATIC mp_int_t mp_ptr_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) 20 | { 21 | mp_ptr_t *self = MP_OBJ_TO_PTR(self_in); 22 | 23 | if (flags & MP_BUFFER_WRITE) { 24 | // read-only ptr 25 | return 1; 26 | } 27 | 28 | bufinfo->buf = &self->ptr; 29 | bufinfo->len = sizeof(self->ptr); 30 | bufinfo->typecode = BYTEARRAY_TYPECODE; 31 | return 0; 32 | } 33 | 34 | #define PTR_OBJ(ptr_global) ptr_global ## _obj 35 | 36 | #define DEFINE_PTR_OBJ_TYPE(ptr_obj_type, ptr_type_qstr)\ 37 | STATIC MP_DEFINE_CONST_OBJ_TYPE(\ 38 | ptr_obj_type,\ 39 | ptr_type_qstr,\ 40 | MP_TYPE_FLAG_NONE,\ 41 | buffer, mp_ptr_get_buffer\ 42 | ); 43 | 44 | #define DEFINE_PTR_OBJ(ptr_global)\ 45 | DEFINE_PTR_OBJ_TYPE(ptr_global ## _type, MP_QSTR_ ## ptr_global);\ 46 | STATIC const mp_ptr_t PTR_OBJ(ptr_global) = {\ 47 | { &ptr_global ## _type },\ 48 | &ptr_global\ 49 | } 50 | 51 | #define NEW_PTR_OBJ(name, value)\ 52 | ({\ 53 | DEFINE_PTR_OBJ_TYPE(ptr_obj_type, MP_QSTR_ ## name);\ 54 | mp_ptr_t *self = m_new_obj(mp_ptr_t);\ 55 | self->base.type = &ptr_obj_type;\ 56 | self->ptr = value;\ 57 | MP_OBJ_FROM_PTR(self);\ 58 | }) 59 | 60 | #endif // __LVMP_DRV_COMMON_H 61 | -------------------------------------------------------------------------------- /driver/js/lv_timer.py: -------------------------------------------------------------------------------- 1 | # Timer implementation for the JS port. 2 | # 3 | # Idea by X-Ryl669: https://github.com/lvgl/lvgl/issues/4011#issuecomment-1483094051 4 | 5 | import jswindow 6 | class Timer: 7 | PERIODIC = 0 8 | ONE_SHOT = 1 9 | 10 | def __init__(self, id): 11 | pass 12 | 13 | def init(self, mode=PERIODIC, period=-1, callback=None): 14 | self.mode = mode 15 | if mode == Timer.PERIODIC: 16 | self.id = jswindow.setInterval(callback, period) 17 | else: 18 | self.id = jswindow.setTimeout(callback, period) 19 | 20 | def deinit(self): 21 | if self.mode == Timer.PERIODIC: 22 | jswindow.clearInterval(self.id) 23 | else: 24 | jswindow.clearTimeout(self.id) 25 | -------------------------------------------------------------------------------- /driver/linux/evdev.py: -------------------------------------------------------------------------------- 1 | # LVGL indev driver for evdev mouse device 2 | # (for the unix micropython port) 3 | 4 | import ustruct 5 | import select 6 | import lvgl as lv 7 | 8 | # Default crosshair cursor 9 | class crosshair_cursor: 10 | def __init__(self, scr=None): 11 | self.scr = scr if scr else lv.scr_act() 12 | self.hor_res = self.scr.get_width() 13 | self.ver_res = self.scr.get_height() 14 | self.cursor_style = lv.style_t() 15 | self.cursor_style.set_line_width(1) 16 | self.cursor_style.set_line_dash_gap(5) 17 | self.cursor_style.set_line_dash_width(1) 18 | self.cursor_hor = lv.line(self.scr) 19 | self.cursor_hor.add_style(self.cursor_style, lv.PART.MAIN) 20 | self.cursor_ver = lv.line(self.scr) 21 | self.cursor_ver.add_style(self.cursor_style, lv.PART.MAIN) 22 | 23 | def __call__(self, data): 24 | # print("%d : %d:%d" % (data.state, data.point.x, data.point.y)) 25 | self.cursor_hor.set_points([{'x':0,'y':data.point.y},{'x':self.hor_res,'y':data.point.y}],2) 26 | self.cursor_ver.set_points([{'y':0,'x':data.point.x},{'y':self.ver_res,'x':data.point.x}],2) 27 | 28 | def delete(self): 29 | self.cursor_hor.delete() 30 | self.cursor_ver.delete() 31 | 32 | # evdev driver for mouse 33 | class mouse_indev: 34 | def __init__(self, scr=None, cursor=None, device='/dev/input/mice'): 35 | 36 | # Open evdev and initialize members 37 | self.evdev = open(device, 'rb') 38 | self.poll = select.poll() 39 | self.poll.register(self.evdev.fileno()) 40 | self.scr = scr if scr else lv.scr_act() 41 | self.cursor = cursor if cursor else crosshair_cursor(self.scr) 42 | self.hor_res = self.scr.get_width() 43 | self.ver_res = self.scr.get_height() 44 | 45 | # Register LVGL indev driver 46 | self.indev = lv.indev_create() 47 | self.indev.set_type(lv.INDEV_TYPE.POINTER) 48 | self.indev.set_read_cb(self.mouse_read) 49 | 50 | def mouse_read(self, indev, data) -> int: 51 | 52 | # Check if there is input to be read from evdev 53 | if not self.poll.poll()[0][1] & select.POLLIN: 54 | return 0 55 | 56 | # Read and parse evdev mouse data 57 | mouse_data = ustruct.unpack('bbb',self.evdev.read(3)) 58 | 59 | # Data is relative, update coordinates 60 | data.point.x += mouse_data[1] 61 | data.point.y -= mouse_data[2] 62 | 63 | # Handle coordinate overflow cases 64 | data.point.x = min(data.point.x, self.hor_res - 1) 65 | data.point.y = min(data.point.y, self.ver_res - 1) 66 | data.point.x = max(data.point.x, 0) 67 | data.point.y = max(data.point.y, 0) 68 | 69 | # Update "pressed" status 70 | data.state = lv.INDEV_STATE.PRESSED if ((mouse_data[0] & 1) == 1) else lv.INDEV_STATE.RELEASED 71 | 72 | # Draw cursor, if needed 73 | if self.cursor: self.cursor(data) 74 | return 0 75 | 76 | def delete(self): 77 | self.evdev.close() 78 | if self.cursor and hasattr(self.cursor, 'delete'): 79 | self.cursor.delete() 80 | self.indev.enable(False) 81 | -------------------------------------------------------------------------------- /driver/linux/lv_timer.py: -------------------------------------------------------------------------------- 1 | 2 | # Timer that matches machine.Timer (https://docs.micropython.org/en/latest/library/machine.Timer.html) 3 | # for the unix port. 4 | # 5 | # MIT license; Copyright (c) 2021 Amir Gonnen 6 | # 7 | # Based on timer.py from micropython-lib (https://github.com/micropython/micropython-lib/blob/master/unix-ffi/machine/machine/timer.py) 8 | 9 | 10 | import ffi 11 | import uctypes 12 | import array 13 | import os 14 | import sys 15 | 16 | # FFI libraries 17 | 18 | libc = ffi.open("libc.so.6") 19 | try: 20 | librt = ffi.open("librt.so") 21 | except OSError as e: 22 | librt = libc 23 | 24 | 25 | # C constants 26 | 27 | CLOCK_REALTIME = 0 28 | CLOCK_MONOTONIC = 1 29 | SIGEV_SIGNAL = 0 30 | 31 | # C structs 32 | 33 | sigaction_t = { 34 | "sa_handler" : (0 | uctypes.UINT64), 35 | "sa_mask" : (8 | uctypes.ARRAY, 16 | uctypes.UINT64), 36 | "sa_flags" : (136 | uctypes.INT32), 37 | "sa_restorer": (144 |uctypes.PTR, uctypes.UINT8), 38 | } 39 | 40 | sigval_t = { 41 | "sival_int": 0 | uctypes.INT32, 42 | "sival_ptr": (0 | uctypes.PTR, uctypes.UINT8), 43 | } 44 | 45 | sigevent_t = { 46 | "sigev_value": (0, sigval_t), 47 | "sigev_signo": uctypes.sizeof(sigval_t) | uctypes.INT32, 48 | "sigev_notify": (uctypes.sizeof(sigval_t) + 4) | uctypes.INT32, 49 | } 50 | 51 | timespec_t = { 52 | "tv_sec": 0 | uctypes.INT32, 53 | "tv_nsec": 8 | uctypes.INT64, 54 | } 55 | 56 | itimerspec_t = { 57 | "it_interval": (0, timespec_t), 58 | "it_value": (16, timespec_t), 59 | } 60 | 61 | # C functions 62 | 63 | __libc_current_sigrtmin = libc.func("i", "__libc_current_sigrtmin", "") 64 | SIGRTMIN = __libc_current_sigrtmin() 65 | 66 | timer_create_ = librt.func("i", "timer_create", "ipp") 67 | timer_delete_ = librt.func("i", "timer_delete", "i") 68 | timer_settime_ = librt.func("i", "timer_settime", "PiPp") 69 | 70 | sigaction_ = libc.func("i", "sigaction", "iPp") 71 | 72 | # Create a new C struct 73 | 74 | def new(sdesc): 75 | buf = bytearray(uctypes.sizeof(sdesc)) 76 | s = uctypes.struct(uctypes.addressof(buf), sdesc, uctypes.NATIVE) 77 | return s 78 | 79 | # Posix Signal handling 80 | 81 | 82 | def sigaction(signum, handler, flags=0): 83 | sa = new(sigaction_t) 84 | sa_old = new(sigaction_t) 85 | cb = ffi.callback("v", handler, "i", lock=True) 86 | sa.sa_handler = cb.cfun() 87 | sa.sa_flags = flags 88 | r = sigaction_(signum, sa, sa_old) 89 | if r != 0: 90 | raise RuntimeError("sigaction_ error: %d (errno = %d)" % (r, os.errno())) 91 | return cb # sa_old.sa_handler 92 | 93 | # Posix Timer handling 94 | 95 | def timer_create(sig_id): 96 | sev = new(sigevent_t) 97 | # print(sev) 98 | sev.sigev_notify = SIGEV_SIGNAL 99 | sev.sigev_signo = SIGRTMIN + sig_id 100 | timerid = array.array("P", [0]) 101 | r = timer_create_(CLOCK_MONOTONIC, sev, timerid) 102 | if r != 0: 103 | raise RuntimeError("timer_create_ error: %d (errno = %d)" % (r, os.errno())) 104 | # print("timerid", hex(timerid[0])) 105 | return timerid[0] 106 | 107 | def timer_delete(tid): 108 | r = timer_delete_(tid) 109 | if r != 0: 110 | raise RuntimeError("timer_delete_ error: %d (errno = %d)" % (r, os.errno())) 111 | 112 | def timer_settime(tid, period_ms, periodic): 113 | period_ns = (period_ms * 1000000) % 1000000000 114 | period_sec = (period_ms * 1000000) // 1000000000 115 | 116 | new_val = new(itimerspec_t) 117 | new_val.it_value.tv_sec = period_sec 118 | new_val.it_value.tv_nsec = period_ns 119 | if periodic: 120 | new_val.it_interval.tv_sec = period_sec 121 | new_val.it_interval.tv_nsec = period_ns 122 | # print("new_val:", bytes(new_val)) 123 | old_val = new(itimerspec_t) 124 | # print(new_val, old_val) 125 | r = timer_settime_(tid, 0, new_val, old_val) 126 | if r != 0: 127 | raise RuntimeError("timer_settime_ error: %d (errno = %d)" % (r, os.errno())) 128 | # print("old_val:", bytes(old_val)) 129 | 130 | # Timer class 131 | 132 | class Timer: 133 | 134 | PERIODIC = 0 135 | ONE_SHOT = 1 136 | 137 | def __init__(self, id): 138 | self.id = id 139 | self._valid = False 140 | 141 | def init(self, mode=PERIODIC, period=-1, callback=None): 142 | self.tid = timer_create(self.id) 143 | self.mode = mode 144 | self.period = period 145 | self.cb = callback 146 | timer_settime(self.tid, self.period, self.mode == Timer.PERIODIC) 147 | self.handler_ref = self.handler 148 | # print("Sig %d: %s" % (SIGRTMIN + self.id, self.org_sig)) 149 | self.action = sigaction(SIGRTMIN + self.id, self.handler_ref) 150 | self._valid = True 151 | 152 | def deinit(self): 153 | if self._valid: 154 | timer_settime(self.tid, 0, self.mode == Timer.PERIODIC) 155 | # timer_delete(self.tid) 156 | self._valid = False 157 | 158 | def handler(self, signum=-1): 159 | # print('Signal handler called with signal', signum) 160 | try: 161 | self.cb(self) 162 | except: 163 | self.deinit() 164 | raise 165 | 166 | -------------------------------------------------------------------------------- /driver/rp2/rp2_dma-test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('.') 3 | from rp2_dma import * 4 | 5 | dma = DMA(0) 6 | src_buf = b"Hello World!"*1000 7 | dst_buf = bytearray( 12*1000 ) 8 | 9 | dma.config( 10 | src_addr = uctypes.addressof( src_buf ), 11 | dst_addr = uctypes.addressof( dst_buf ), 12 | count = len( src_buf ), 13 | src_inc = True, 14 | dst_inc = True, 15 | trig_dreq = DMA.DREQ_PERMANENT 16 | ) 17 | 18 | t0 = time.ticks_us() 19 | dma.enable() 20 | while( dma.is_busy() ): 21 | pass 22 | dma.disable() 23 | t1 = time.ticks_us() 24 | 25 | print( "dst", dst_buf[0:12], "..." ) 26 | 27 | print( "Transfer speed [B/s]:", len( src_buf )/((t1-t0)*1e-6) ) 28 | print( "@CPU freq:", machine.freq() ) 29 | -------------------------------------------------------------------------------- /driver/rp2/rp2_dma.py: -------------------------------------------------------------------------------- 1 | import time 2 | import uctypes 3 | import struct 4 | import machine 5 | import sys 6 | 7 | if sys.platform!='rp2': raise ImportError('This module can only be meaningfully used on the rp2 platform.') 8 | 9 | class DMA: 10 | DMA_BASE = const(0x50000000) 11 | 12 | DMA_EN = const(0x01 << 0) 13 | HIGH_PRIO = const(0x01 << 1) 14 | INCR_READ = const(0x01 << 4) 15 | INCR_WRITE= const(0x01 << 5) 16 | DREQ_PIO0_RX0 = const(0x04 << 15) 17 | DREQ_SPI1_TX = const(0x12 << 15) 18 | DREQ_PERMANENT= const(0x3F << 15) 19 | IRQ_QUIET = const(0x01 << 21) 20 | BUSY = const(0x01 << 24) 21 | 22 | def __init__( self, channelNumber ): 23 | offset = channelNumber * 0x40 24 | self.CHx_READ_ADDR = DMA.DMA_BASE + 0x00 + offset 25 | self.CHx_WRITE_ADDR = DMA.DMA_BASE + 0x04 + offset 26 | self.CHx_TRANS_COUNT = DMA.DMA_BASE + 0x08 + offset 27 | self.CHx_CTRL_TRIG = DMA.DMA_BASE + 0x0C + offset 28 | 29 | def config( self, src_addr, dst_addr, count, src_inc, dst_inc, trig_dreq ): 30 | machine.mem32[ self.CHx_CTRL_TRIG ] = 0 31 | machine.mem32[ self.CHx_READ_ADDR ] = src_addr 32 | machine.mem32[ self.CHx_WRITE_ADDR ] = dst_addr 33 | machine.mem32[ self.CHx_TRANS_COUNT ] = count 34 | trig_val = 0 35 | if( src_inc ): 36 | trig_val |= DMA.INCR_READ 37 | if( dst_inc ): 38 | trig_val |= DMA.INCR_WRITE 39 | trig_val |= trig_dreq 40 | machine.mem32[ self.CHx_CTRL_TRIG ] = trig_val 41 | 42 | def enable( self ): 43 | machine.mem32[ self.CHx_CTRL_TRIG ] |= DMA.DMA_EN 44 | 45 | def disable( self ): 46 | machine.mem32[ self.CHx_CTRL_TRIG ] = 0 47 | 48 | def is_busy( self ): 49 | if( machine.mem32[ self.CHx_CTRL_TRIG ] & DMA.BUSY ): 50 | return True 51 | else: 52 | return False 53 | -------------------------------------------------------------------------------- /driver/stm32/STM32F7DISC/rk043fn48h.h: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file rk043fn48h.h 4 | * @author MCD Application Team 5 | * @brief This file contains all the constants parameters for the RK043FN48H-CT672B 6 | * LCD component. 7 | ****************************************************************************** 8 | * @attention 9 | * 10 | *

© COPYRIGHT(c) 2015 STMicroelectronics

11 | * 12 | * Redistribution and use in source and binary forms, with or without modification, 13 | * are permitted provided that the following conditions are met: 14 | * 1. Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * 2. Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * 3. Neither the name of STMicroelectronics nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software 21 | * without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * 34 | ****************************************************************************** 35 | */ 36 | 37 | /* Define to prevent recursive inclusion -------------------------------------*/ 38 | #ifndef __RK043FN48H_H 39 | #define __RK043FN48H_H 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | /* Includes ------------------------------------------------------------------*/ 46 | 47 | /** @addtogroup BSP 48 | * @{ 49 | */ 50 | 51 | /** @addtogroup Components 52 | * @{ 53 | */ 54 | 55 | /** @addtogroup rk043fn48h 56 | * @{ 57 | */ 58 | 59 | /** @defgroup RK043FN48H_Exported_Types 60 | * @{ 61 | */ 62 | 63 | /** 64 | * @} 65 | */ 66 | 67 | /** @defgroup RK043FN48H_Exported_Constants 68 | * @{ 69 | */ 70 | 71 | /** 72 | * @brief RK043FN48H Size 73 | */ 74 | #define RK043FN48H_WIDTH ((uint16_t)480) /* LCD PIXEL WIDTH */ 75 | #define RK043FN48H_HEIGHT ((uint16_t)272) /* LCD PIXEL HEIGHT */ 76 | 77 | /** 78 | * @brief RK043FN48H Timing 79 | */ 80 | #define RK043FN48H_HSYNC ((uint16_t)41) /* Horizontal synchronization */ 81 | #define RK043FN48H_HBP ((uint16_t)13) /* Horizontal back porch */ 82 | #define RK043FN48H_HFP ((uint16_t)32) /* Horizontal front porch */ 83 | #define RK043FN48H_VSYNC ((uint16_t)10) /* Vertical synchronization */ 84 | #define RK043FN48H_VBP ((uint16_t)2) /* Vertical back porch */ 85 | #define RK043FN48H_VFP ((uint16_t)2) /* Vertical front porch */ 86 | 87 | /** 88 | * @brief RK043FN48H frequency divider 89 | */ 90 | #define RK043FN48H_FREQUENCY_DIVIDER 5 /* LCD Frequency divider */ 91 | /** 92 | * @} 93 | */ 94 | 95 | /** @defgroup RK043FN48H_Exported_Functions 96 | * @{ 97 | */ 98 | 99 | /** 100 | * @} 101 | */ 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | 106 | #endif /* __RK043FN48H_H */ 107 | /** 108 | * @} 109 | */ 110 | 111 | /** 112 | * @} 113 | */ 114 | 115 | /** 116 | * @} 117 | */ 118 | 119 | /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ 120 | -------------------------------------------------------------------------------- /driver/stm32/STM32F7DISC/stm32746g_discovery_ts.h: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file stm32746g_discovery_ts.h 4 | * @author MCD Application Team 5 | * @brief This file contains the common defines and functions prototypes for 6 | * the stm32746g_discovery_ts.c driver. 7 | ****************************************************************************** 8 | * @attention 9 | * 10 | *

© COPYRIGHT(c) 2016 STMicroelectronics

11 | * 12 | * Redistribution and use in source and binary forms, with or without modification, 13 | * are permitted provided that the following conditions are met: 14 | * 1. Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * 2. Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * 3. Neither the name of STMicroelectronics nor the names of its contributors 20 | * may be used to endorse or promote products derived from this software 21 | * without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | * 34 | ****************************************************************************** 35 | */ 36 | 37 | /* Define to prevent recursive inclusion -------------------------------------*/ 38 | #ifndef __STM32746G_DISCOVERY_TS_H 39 | #define __STM32746G_DISCOVERY_TS_H 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | /* Includes ------------------------------------------------------------------*/ 46 | #include "stm32746g_discovery.h" 47 | /* Include touch screen FT5336 component Driver */ 48 | #include "ft5336.h" 49 | 50 | /** @addtogroup BSP 51 | * @{ 52 | */ 53 | 54 | /** @addtogroup STM32746G_DISCOVERY 55 | * @{ 56 | */ 57 | 58 | /** @addtogroup STM32746G_DISCOVERY_TS 59 | * @{ 60 | */ 61 | 62 | /** @defgroup STM32746G_DISCOVERY_TS_Exported_Constants STM32746G_DISCOVERY_TS Exported Constants 63 | * @{ 64 | */ 65 | 66 | /** @brief With FT5336 : maximum 5 touches detected simultaneously 67 | */ 68 | #define TS_MAX_NB_TOUCH ((uint32_t) FT5336_MAX_DETECTABLE_TOUCH) 69 | 70 | #define TS_NO_IRQ_PENDING ((uint8_t) 0) 71 | #define TS_IRQ_PENDING ((uint8_t) 1) 72 | 73 | #define TS_SWAP_NONE ((uint8_t) 0x01) 74 | #define TS_SWAP_X ((uint8_t) 0x02) 75 | #define TS_SWAP_Y ((uint8_t) 0x04) 76 | #define TS_SWAP_XY ((uint8_t) 0x08) 77 | 78 | /** 79 | * @} 80 | */ 81 | 82 | /** @defgroup STM32746G_DISCOVERY_TS_Exported_Types STM32746G_DISCOVERY_TS Exported Types 83 | * @{ 84 | */ 85 | /** 86 | * @brief TS_StateTypeDef 87 | * Define TS State structure 88 | */ 89 | typedef struct 90 | { 91 | uint8_t touchDetected; /*!< Total number of active touches detected at last scan */ 92 | uint16_t touchX[TS_MAX_NB_TOUCH]; /*!< Touch X[0], X[1] coordinates on 12 bits */ 93 | uint16_t touchY[TS_MAX_NB_TOUCH]; /*!< Touch Y[0], Y[1] coordinates on 12 bits */ 94 | 95 | #if (TS_MULTI_TOUCH_SUPPORTED == 1) 96 | uint8_t touchWeight[TS_MAX_NB_TOUCH]; /*!< Touch_Weight[0], Touch_Weight[1] : weight property of touches */ 97 | uint8_t touchEventId[TS_MAX_NB_TOUCH]; /*!< Touch_EventId[0], Touch_EventId[1] : take value of type @ref TS_TouchEventTypeDef */ 98 | uint8_t touchArea[TS_MAX_NB_TOUCH]; /*!< Touch_Area[0], Touch_Area[1] : touch area of each touch */ 99 | uint32_t gestureId; /*!< type of gesture detected : take value of type @ref TS_GestureIdTypeDef */ 100 | #endif /* TS_MULTI_TOUCH_SUPPORTED == 1 */ 101 | 102 | } TS_StateTypeDef; 103 | 104 | /** 105 | * @} 106 | */ 107 | 108 | /** @defgroup STM32746G_DISCOVERY_TS_Exported_Constants STM32746G_DISCOVERY_TS Exported Constants 109 | * @{ 110 | */ 111 | 112 | typedef enum 113 | { 114 | TS_OK = 0x00, /*!< Touch Ok */ 115 | TS_ERROR = 0x01, /*!< Touch Error */ 116 | TS_TIMEOUT = 0x02, /*!< Touch Timeout */ 117 | TS_DEVICE_NOT_FOUND = 0x03 /*!< Touchscreen device not found */ 118 | }TS_StatusTypeDef; 119 | 120 | /** 121 | * @brief TS_GestureIdTypeDef 122 | * Define Possible managed gesture identification values returned by touch screen 123 | * driver. 124 | */ 125 | typedef enum 126 | { 127 | GEST_ID_NO_GESTURE = 0x00, /*!< Gesture not defined / recognized */ 128 | GEST_ID_MOVE_UP = 0x01, /*!< Gesture Move Up */ 129 | GEST_ID_MOVE_RIGHT = 0x02, /*!< Gesture Move Right */ 130 | GEST_ID_MOVE_DOWN = 0x03, /*!< Gesture Move Down */ 131 | GEST_ID_MOVE_LEFT = 0x04, /*!< Gesture Move Left */ 132 | GEST_ID_ZOOM_IN = 0x05, /*!< Gesture Zoom In */ 133 | GEST_ID_ZOOM_OUT = 0x06, /*!< Gesture Zoom Out */ 134 | GEST_ID_NB_MAX = 0x07 /*!< max number of gesture id */ 135 | 136 | } TS_GestureIdTypeDef; 137 | 138 | /** 139 | * @brief TS_TouchEventTypeDef 140 | * Define Possible touch events kind as returned values 141 | * by touch screen IC Driver. 142 | */ 143 | typedef enum 144 | { 145 | TOUCH_EVENT_NO_EVT = 0x00, /*!< Touch Event : undetermined */ 146 | TOUCH_EVENT_PRESS_DOWN = 0x01, /*!< Touch Event Press Down */ 147 | TOUCH_EVENT_LIFT_UP = 0x02, /*!< Touch Event Lift Up */ 148 | TOUCH_EVENT_CONTACT = 0x03, /*!< Touch Event Contact */ 149 | TOUCH_EVENT_NB_MAX = 0x04 /*!< max number of touch events kind */ 150 | 151 | } TS_TouchEventTypeDef; 152 | /** 153 | * @} 154 | */ 155 | 156 | /** @defgroup STM32746G_DISCOVERY_TS_Imported_Variables STM32746G_DISCOVERY_TS Imported Variables 157 | * @{ 158 | */ 159 | /** 160 | * @brief Table for touchscreen event information display on LCD : 161 | * table indexed on enum @ref TS_TouchEventTypeDef information 162 | */ 163 | extern char * ts_event_string_tab[TOUCH_EVENT_NB_MAX]; 164 | 165 | /** 166 | * @brief Table for touchscreen gesture Id information display on LCD : table indexed 167 | * on enum @ref TS_GestureIdTypeDef information 168 | */ 169 | extern char * ts_gesture_id_string_tab[GEST_ID_NB_MAX]; 170 | /** 171 | * @} 172 | */ 173 | 174 | /** @addtogroup STM32746G_DISCOVERY_TS_Exported_Functions 175 | * @{ 176 | */ 177 | uint8_t BSP_TS_Init(uint16_t ts_SizeX, uint16_t ts_SizeY); 178 | uint8_t BSP_TS_DeInit(void); 179 | uint8_t BSP_TS_GetState(TS_StateTypeDef *TS_State); 180 | 181 | #if (TS_MULTI_TOUCH_SUPPORTED == 1) 182 | uint8_t BSP_TS_Get_GestureId(TS_StateTypeDef *TS_State); 183 | #endif /* TS_MULTI_TOUCH_SUPPORTED == 1 */ 184 | 185 | uint8_t BSP_TS_ITConfig(void); 186 | uint8_t BSP_TS_ITGetStatus(void); 187 | void BSP_TS_ITClear(void); 188 | uint8_t BSP_TS_ResetTouchData(TS_StateTypeDef *TS_State); 189 | 190 | /** 191 | * @} 192 | */ 193 | 194 | /** 195 | * @} 196 | */ 197 | 198 | /** 199 | * @} 200 | */ 201 | 202 | /** 203 | * @} 204 | */ 205 | 206 | 207 | #ifdef __cplusplus 208 | } 209 | #endif 210 | 211 | #endif /* __STM32746G_DISCOVERY_TS_H */ 212 | 213 | /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ 214 | -------------------------------------------------------------------------------- /driver/stm32/STM32F7DISC/ts.h: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file ts.h 4 | * @author MCD Application Team 5 | * @version V4.0.1 6 | * @date 21-July-2015 7 | * @brief This file contains all the functions prototypes for the Touch Screen driver. 8 | ****************************************************************************** 9 | * @attention 10 | * 11 | *

© COPYRIGHT(c) 2015 STMicroelectronics

12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 1. Redistributions of source code must retain the above copyright notice, 16 | * this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright notice, 18 | * this list of conditions and the following disclaimer in the documentation 19 | * and/or other materials provided with the distribution. 20 | * 3. Neither the name of STMicroelectronics nor the names of its contributors 21 | * may be used to endorse or promote products derived from this software 22 | * without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | * 35 | ****************************************************************************** 36 | */ 37 | 38 | /* Define to prevent recursive inclusion -------------------------------------*/ 39 | #ifndef __TS_H 40 | #define __TS_H 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | /* Includes ------------------------------------------------------------------*/ 47 | #include 48 | 49 | /** @addtogroup BSP 50 | * @{ 51 | */ 52 | 53 | /** @addtogroup Components 54 | * @{ 55 | */ 56 | 57 | /** @addtogroup TS 58 | * @{ 59 | */ 60 | 61 | /** @defgroup TS_Exported_Types 62 | * @{ 63 | */ 64 | 65 | /** @defgroup TS_Driver_structure Touch Sensor Driver structure 66 | * @{ 67 | */ 68 | typedef struct 69 | { 70 | void (*Init)(uint16_t); 71 | uint16_t (*ReadID)(uint16_t); 72 | void (*Reset)(uint16_t); 73 | void (*Start)(uint16_t); 74 | uint8_t (*DetectTouch)(uint16_t); 75 | void (*GetXY)(uint16_t, uint16_t*, uint16_t*); 76 | void (*EnableIT)(uint16_t); 77 | void (*ClearIT)(uint16_t); 78 | uint8_t (*GetITStatus)(uint16_t); 79 | void (*DisableIT)(uint16_t); 80 | }TS_DrvTypeDef; 81 | /** 82 | * @} 83 | */ 84 | 85 | /** 86 | * @} 87 | */ 88 | 89 | /** 90 | * @} 91 | */ 92 | 93 | /** 94 | * @} 95 | */ 96 | 97 | /** 98 | * @} 99 | */ 100 | 101 | #ifdef __cplusplus 102 | } 103 | #endif 104 | 105 | #endif /* __TS_H */ 106 | 107 | /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ 108 | -------------------------------------------------------------------------------- /driver/stm32/string1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | char *strncpy(char * dest, const char * src, size_t n){ 5 | if (n != 0) { 6 | char *d = dest; 7 | const char *s = src; 8 | do { 9 | if ((*d++ = *s++) == '\0') { 10 | while (--n != 0) 11 | *d++ = '\0'; 12 | break; 13 | } 14 | } while (--n != 0); 15 | } 16 | return (dest); 17 | } 18 | -------------------------------------------------------------------------------- /driver/zephyr/README.md: -------------------------------------------------------------------------------- 1 | ## Zephyr driver for LV bindings project 2 | 3 | This driver allows Micropython with LVGL as an external C module to run as a Zephyr OS application; thus, all boards, display, and input drivers can be used from Zephyr's codebase. 4 | 5 | ### lv_micropython 6 | This driver is integrated for Micropython's Zephyr port build in the [lv_micropython project](https://github.com/lvgl/lv_micropython). Using the lv_micopython Zephyr port with LVGL requires: 7 | * specifying the display and input drivers in the your-board.prj and in the lvgl_driver.h 8 | * editing LV_TICK_CUSTOM_INCLUDE in lv_conf.h as stated below. 9 | 10 | ### Driver architecture 11 | This driver consist of three files: 12 | 13 | #### lvgl.c 14 | This file is taken from the Zephyr codebase and is adjusted for this use case. Display driver initialization is being done here. 15 | #### modlvzephyr.c 16 | This file provides a binding to the lv_init function, which needs to be called before using LVGL from Micropython. 17 | #### lvgl_driver.h 18 | In this file, the display driver and the input driver need to be defined. In addition, the display resolution has to be defined here. 19 | 20 | ### Building the driver 21 | LV bindings project needs to be included as stated in the main README. 22 | To integrate the Zephyr driver, the following lines have to be inserted in Micropython's Zephyr port: 23 | * Include path of Micropython's build systems have to be added to the Makefile: 24 | ``` 25 | INC += -I$(ZEPHYR_BASE) 26 | INC += -I$(TOP)/lib/lv_bindings 27 | INC += -I$(TOP)/lib/lv_bindings/driver/zephyr 28 | INC += -I$(ZEPHYR_BASE)/include 29 | INC += -I$(ZEPHYR_BASE)/lib/gui/lvgl/ 30 | INC += -I$(TOP)/lib/lv_bindings/lvgl 31 | ``` 32 | 33 | * Driver C files from Zephyr codebase need to be add for the build to the Makefile. 34 | ``` 35 | SRC_C += $(ZEPHYR_BASE)/lib/gui/lvgl/lvgl_display_16bit.c \ 36 | $(ZEPHYR_BASE)/lib/gui/lvgl/lvgl_display_24bit.c \ 37 | $(ZEPHYR_BASE)/lib/gui/lvgl/lvgl_display_32bit.c \ 38 | $(ZEPHYR_BASE)/lib/gui/lvgl/lvgl_display.c \ 39 | $(ZEPHYR_BASE)/lib/gui/lvgl/lvgl_display_mono.c \ 40 | build/lvgl/lv_mpy.c 41 | ``` 42 | * ```lvgl_driver.h``` has to be adjusted to specific board and display. 43 | * Display driver and input device driver need to be enabled in some project config, e.g., in your board .prj file. The following example is enabling the ELCDIF display driver and FT5336 input driver: 44 | ``` 45 | CONFIG_DISPLAY_MCUX_ELCDIF=y 46 | CONFIG_DISPLAY=y 47 | CONFIG_MCUX_ELCDIF_PANEL_RK043FN02H=y 48 | CONFIG_KSCAN=y 49 | CONFIG_KSCAN_FT5336=y 50 | ``` 51 | * Link Zephyr kernel with MPY app, adding following line to Zepjyr's port CMakelists.txt: 52 | ``` 53 | target_link_libraries(libmicropython INTERFACE kernel) 54 | ``` 55 | * edit lv_conf.h: 56 | ``` 57 | #define LV_TICK_CUSTOM_INCLUDE "kernel.h" 58 | #define LV_TICK_CUSTOM_SYS_TIME_EXPR (k_uptime_get_32()) 59 | 60 | typedef void *lv_disp_drv_user_data_t; 61 | typedef void *lv_indev_drv_user_data_t; 62 | 63 | ``` 64 | 65 | #### Usage 66 | LVGL and driver initialization is following: 67 | ``` 68 | import lvgl as lv 69 | import ZDD 70 | ZDD.init() 71 | ``` 72 | -------------------------------------------------------------------------------- /driver/zephyr/lvgl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019 Jan Van Winkel 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * changes: 7 | * Zephyr's logging -> printf 8 | * CONFIG_LVGL_HOR_RES_MAX -> LV_HOR_RES_MAX 9 | * CONFIG_LVGL_VER_RES_MAX -> LV_VER_RES_MAX 10 | * added debug log 11 | * removed SYS_INIT for lvgl_init 12 | * lvgl_init no longer static 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "lvgl_display.h" 20 | #include "lvgl_driver.h" 21 | #include 22 | #ifdef CONFIG_LVGL_USE_FILESYSTEM 23 | #include "lvgl_fs.h" 24 | #endif 25 | #define CONFIG_LVGL_POINTER_KSCAN 1 26 | #ifdef CONFIG_LVGL_POINTER_KSCAN 27 | #include 28 | #endif 29 | #include LV_MEM_CUSTOM_INCLUDE 30 | 31 | #ifdef CONFIG_LVGL_BUFFER_ALLOC_STATIC 32 | 33 | lv_disp_buf_t disp_buf; 34 | 35 | #define BUFFER_SIZE (CONFIG_LVGL_BITS_PER_PIXEL * ((CONFIG_LVGL_VDB_SIZE * \ 36 | LV_HOR_RES_MAX * LV_VER_RES_MAX) / 100) / 8) 37 | 38 | #define NBR_PIXELS_IN_BUFFER (BUFFER_SIZE * 8 / CONFIG_LVGL_BITS_PER_PIXEL) 39 | 40 | /* NOTE: depending on chosen color depth buffer may be accessed using uint8_t *, 41 | * uint16_t * or uint32_t *, therefore buffer needs to be aligned accordingly to 42 | * prevent unaligned memory accesses. 43 | */ 44 | static uint8_t buf0[BUFFER_SIZE] __aligned(4); 45 | #ifdef CONFIG_LVGL_DOUBLE_VDB 46 | static uint8_t buf1[BUFFER_SIZE] __aligned(4); 47 | #endif 48 | 49 | #endif /* CONFIG_LVGL_BUFFER_ALLOC_STATIC */ 50 | 51 | #ifdef CONFIG_LVGL_BUFFER_ALLOC_STATIC 52 | 53 | static int lvgl_allocate_rendering_buffers(lv_disp_t *disp_drv) 54 | { 55 | struct display_capabilities cap; 56 | const struct device *display_dev = (const struct device *)disp_drv->user_data; 57 | int err = 0; 58 | 59 | display_get_capabilities(display_dev, &cap); 60 | 61 | if (cap.x_resolution <= LV_HOR_RES_MAX) { 62 | disp_drv->hor_res = cap.x_resolution; 63 | } else { 64 | printf("Horizontal resolution is larger than maximum"); 65 | err = -ENOTSUP; 66 | } 67 | 68 | if (cap.y_resolution <= LV_VER_RES_MAX) { 69 | disp_drv->ver_res = cap.y_resolution; 70 | } else { 71 | printf("Vertical resolution is larger than maximum"); 72 | err = -ENOTSUP; 73 | } 74 | 75 | disp_drv->buffer = &disp_buf; 76 | #ifdef CONFIG_LVGL_DOUBLE_VDB 77 | lv_disp_buf_init(disp_drv->buffer, &buf0, &buf1, NBR_PIXELS_IN_BUFFER); 78 | #else 79 | lv_disp_buf_init(disp_drv->buffer, &buf0, NULL, NBR_PIXELS_IN_BUFFER); 80 | #endif /* CONFIG_LVGL_DOUBLE_VDB */ 81 | 82 | return err; 83 | } 84 | 85 | #else 86 | 87 | static int lvgl_allocate_rendering_buffers(lv_disp_t *disp_drv) 88 | { 89 | void *buf0 = NULL; 90 | void *buf1 = NULL; 91 | uint16_t buf_nbr_pixels; 92 | uint32_t buf_size; 93 | struct display_capabilities cap; 94 | const struct device *display_dev = (const struct device *)disp_drv->user_data; 95 | 96 | display_get_capabilities(display_dev, &cap); 97 | 98 | disp_drv->hor_res = cap.x_resolution; 99 | disp_drv->ver_res = cap.y_resolution; 100 | 101 | buf_nbr_pixels = (CONFIG_LVGL_VDB_SIZE * disp_drv->hor_res * 102 | disp_drv->ver_res) / 100; 103 | /* one horizontal line is the minimum buffer requirement for lvgl */ 104 | if (buf_nbr_pixels < disp_drv->hor_res) { 105 | buf_nbr_pixels = disp_drv->hor_res; 106 | } 107 | 108 | switch (cap.current_pixel_format) { 109 | case PIXEL_FORMAT_ARGB_8888: 110 | buf_size = 4 * buf_nbr_pixels; 111 | break; 112 | case PIXEL_FORMAT_RGB_888: 113 | buf_size = 3 * buf_nbr_pixels; 114 | break; 115 | case PIXEL_FORMAT_RGB_565: 116 | buf_size = 2 * buf_nbr_pixels; 117 | break; 118 | case PIXEL_FORMAT_MONO01: 119 | case PIXEL_FORMAT_MONO10: 120 | buf_size = buf_nbr_pixels / 8; 121 | buf_size += (buf_nbr_pixels % 8) == 0 ? 0 : 1; 122 | break; 123 | default: 124 | return -ENOTSUP; 125 | } 126 | 127 | buf0 = LV_MEM_CUSTOM_ALLOC(buf_size); 128 | if (buf0 == NULL) { 129 | printf("Failed to allocate memory for rendering buffer"); 130 | return -ENOMEM; 131 | } 132 | 133 | #ifdef CONFIG_LVGL_DOUBLE_VDB 134 | buf1 = LV_MEM_CUSTOM_ALLOC(buf_size); 135 | if (buf1 == NULL) { 136 | LV_MEM_CUSTOM_FREE(buf0); 137 | printf("Failed to allocate memory for rendering buffer"); 138 | return -ENOMEM; 139 | } 140 | #endif 141 | 142 | disp_drv->buffer = LV_MEM_CUSTOM_ALLOC(sizeof(lv_disp_buf_t)); 143 | if (disp_drv->buffer == NULL) { 144 | LV_MEM_CUSTOM_FREE(buf0); 145 | LV_MEM_CUSTOM_FREE(buf1); 146 | printf("Failed to allocate memory to store rendering buffers"); 147 | return -ENOMEM; 148 | } 149 | 150 | lv_disp_buf_init(disp_drv->buffer, buf0, buf1, buf_nbr_pixels); 151 | return 0; 152 | } 153 | #endif /* CONFIG_LVGL_BUFFER_ALLOC_STATIC */ 154 | 155 | #ifdef CONFIG_LVGL_POINTER_KSCAN 156 | K_MSGQ_DEFINE(kscan_msgq, sizeof(lv_indev_data_t), 157 | CONFIG_LVGL_POINTER_KSCAN_MSGQ_COUNT, 4); 158 | 159 | static void lvgl_pointer_kscan_callback(const struct device *dev, 160 | uint32_t row, 161 | uint32_t col, bool pressed) 162 | { 163 | lv_indev_data_t data = { 164 | .point.x = col, 165 | .point.y = row, 166 | .state = pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL, 167 | }; 168 | int err = k_msgq_put(&kscan_msgq, &data, K_NO_WAIT); 169 | if ( err != 0) { 170 | printf("Could not put input data into queue, err: %d\n", err); 171 | } 172 | } 173 | 174 | static bool lvgl_pointer_kscan_read(lv_indev_t *drv, lv_indev_data_t *data) 175 | { 176 | lv_disp_t *disp; 177 | const struct device *disp_dev; 178 | struct display_capabilities cap; 179 | lv_indev_data_t curr; 180 | 181 | static lv_indev_data_t prev = { 182 | .point.x = 0, 183 | .point.y = 0, 184 | .state = LV_INDEV_STATE_REL, 185 | }; 186 | 187 | if (k_msgq_get(&kscan_msgq, &curr, K_NO_WAIT) != 0) { 188 | goto set_and_release; 189 | } 190 | 191 | prev = curr; 192 | 193 | disp = lv_disp_get_default(); 194 | disp_dev = disp->driver.user_data; 195 | 196 | display_get_capabilities(disp_dev, &cap); 197 | 198 | /* adjust kscan coordinates */ 199 | if (IS_ENABLED(CONFIG_LVGL_POINTER_KSCAN_SWAP_XY)) { 200 | int32_t x; 201 | 202 | x = prev.point.x; 203 | prev.point.x = prev.point.y; 204 | prev.point.y = x; 205 | } 206 | 207 | if (IS_ENABLED(CONFIG_LVGL_POINTER_KSCAN_INVERT_X)) { 208 | if (cap.current_orientation == DISPLAY_ORIENTATION_NORMAL || 209 | cap.current_orientation == DISPLAY_ORIENTATION_ROTATED_180) { 210 | prev.point.x = cap.x_resolution - prev.point.x; 211 | } else { 212 | prev.point.x = cap.y_resolution - prev.point.x; 213 | } 214 | } 215 | 216 | if (IS_ENABLED(CONFIG_LVGL_POINTER_KSCAN_INVERT_Y)) { 217 | if (cap.current_orientation == DISPLAY_ORIENTATION_NORMAL || 218 | cap.current_orientation == DISPLAY_ORIENTATION_ROTATED_180) { 219 | prev.point.y = cap.y_resolution - prev.point.y; 220 | } else { 221 | prev.point.y = cap.x_resolution - prev.point.y; 222 | } 223 | } 224 | 225 | /* rotate touch point to match display rotation */ 226 | if (cap.current_orientation == DISPLAY_ORIENTATION_ROTATED_90) { 227 | int32_t x; 228 | 229 | x = prev.point.x; 230 | prev.point.x = prev.point.y; 231 | prev.point.y = cap.y_resolution - x; 232 | } else if (cap.current_orientation == DISPLAY_ORIENTATION_ROTATED_180) { 233 | prev.point.x = cap.x_resolution - prev.point.x; 234 | prev.point.y = cap.y_resolution - prev.point.y; 235 | } else if (cap.current_orientation == DISPLAY_ORIENTATION_ROTATED_270) { 236 | int32_t x; 237 | 238 | x = prev.point.x; 239 | prev.point.x = cap.x_resolution - prev.point.y; 240 | prev.point.y = x; 241 | } 242 | 243 | set_and_release: 244 | *data = prev; 245 | 246 | return k_msgq_num_used_get(&kscan_msgq) > 0; 247 | } 248 | 249 | static int lvgl_pointer_kscan_init(void) 250 | { 251 | const struct device *kscan_dev = 252 | device_get_binding(CONFIG_LVGL_POINTER_KSCAN_DEV_NAME); 253 | 254 | lv_indev_t indev_drv; 255 | 256 | if (kscan_dev == NULL) { 257 | printf("Keyboard scan device not found."); 258 | return -ENODEV; 259 | } 260 | 261 | if (kscan_config(kscan_dev, lvgl_pointer_kscan_callback) < 0) { 262 | printf("Could not configure keyboard scan device."); 263 | return -ENODEV; 264 | } 265 | 266 | lv_indev_drv_init(&indev_drv); 267 | indev_drv.type = LV_INDEV_TYPE_POINTER; 268 | indev_drv.read_cb = lvgl_pointer_kscan_read; 269 | 270 | if (lv_indev_drv_register(&indev_drv) == NULL) { 271 | printf("Failed to register input device."); 272 | return -EPERM; 273 | } 274 | 275 | int err = kscan_enable_callback(kscan_dev); 276 | if (err != 0) { 277 | printf("Callback enabling failed\n"); 278 | return err; 279 | } 280 | 281 | return 0; 282 | } 283 | #endif /* CONFIG_LVGL_POINTER_KSCAN */ 284 | 285 | int lvgl_init(const struct device *dev) 286 | { 287 | ARG_UNUSED(dev); 288 | 289 | const struct device *display_dev = 290 | device_get_binding(CONFIG_LVGL_DISPLAY_DEV_NAME); 291 | int err = 0; 292 | lv_disp_t disp_drv; 293 | 294 | if (display_dev == NULL) { 295 | printf("Display device not found."); 296 | return -ENODEV; 297 | } 298 | 299 | #if CONFIG_LVGL_LOG_LEVEL != 0 300 | lv_log_register_print_cb(lvgl_log); 301 | #endif 302 | 303 | lv_init(); 304 | 305 | #ifdef CONFIG_LVGL_USE_FILESYSTEM 306 | lvgl_fs_init(); 307 | #endif 308 | 309 | lv_disp_drv_init(&disp_drv); 310 | disp_drv.user_data = (void *) display_dev; 311 | 312 | err = lvgl_allocate_rendering_buffers(&disp_drv); 313 | if (err != 0) { 314 | printf("Error allocate buffers\n"); 315 | return err; 316 | } 317 | 318 | if (set_lvgl_rendering_cb(&disp_drv) != 0) { 319 | printf("Display not supported.\n"); 320 | return -ENOTSUP; 321 | } 322 | 323 | if (lv_disp_drv_register(&disp_drv) == NULL) { 324 | printf("Failed to register display device.\n"); 325 | return -EPERM; 326 | } 327 | 328 | #ifdef CONFIG_LVGL_POINTER_KSCAN 329 | lvgl_pointer_kscan_init(); 330 | #endif /* CONFIG_LVGL_POINTER_KSCAN */ 331 | 332 | return 0; 333 | } 334 | -------------------------------------------------------------------------------- /driver/zephyr/lvgl_driver.h: -------------------------------------------------------------------------------- 1 | #define CONFIG_LVGL_BITS_PER_PIXEL 32 2 | #define CONFIG_LVGL_VDB_SIZE 16 3 | #define CONFIG_LVGL_DISPLAY_DEV_NAME "ELCDIF_1" // choose from Zephyr display drivers 4 | #define CONFIG_LVGL_BUFFER_ALLOC_STATIC 1 // LVGL buffer settings, see other options in Zephyr's Kconfig 5 | #define CONFIG_LVGL_POINTER_KSCAN 1 // enabling/disabling kernel scan for input queue 6 | #define CONFIG_LVGL_POINTER_KSCAN_MSGQ_COUNT 10 7 | #define CONFIG_LVGL_POINTER_KSCAN_DEV_NAME "FT5336" // choose from Zephyr input drivers 8 | 9 | 10 | int lvgl_init(const struct device *dev); 11 | -------------------------------------------------------------------------------- /driver/zephyr/modlvzephyr.c: -------------------------------------------------------------------------------- 1 | #include "py/runtime.h" 2 | #include "lvgl_driver.h" 3 | 4 | STATIC mp_obj_t init() { 5 | lvgl_init(NULL); 6 | return mp_const_none; 7 | } 8 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(init_obj, init); 9 | 10 | STATIC const mp_rom_map_elem_t ZDD_module_globals_table[] = { 11 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ZDD) }, 12 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&init_obj) }, 13 | }; 14 | STATIC MP_DEFINE_CONST_DICT(ZDD_module_globals, ZDD_module_globals_table); 15 | 16 | const mp_obj_module_t ZDD_user_cmodule = { 17 | .base = { &mp_type_module }, 18 | .globals = (mp_obj_dict_t*)&ZDD_module_globals, 19 | }; 20 | 21 | MP_REGISTER_MODULE(MP_QSTR_ZDD, ZDD_user_cmodule, 1); 22 | -------------------------------------------------------------------------------- /examples/Dynamic_loading_font_example.py: -------------------------------------------------------------------------------- 1 | # 2 | # Command line for running this example on the unix port from examples directory: 3 | # MICROPYPATH=./:../lib ../../../ports/unix/micropython -i Dynamic_loading_font_example.py 4 | # 5 | 6 | import usys as sys 7 | sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 8 | 9 | try: 10 | script_path = __file__[:__file__.rfind('/')] if __file__.find('/') >= 0 else '.' 11 | except NameError: 12 | script_path = '' 13 | 14 | import lvgl as lv 15 | import fs_driver 16 | 17 | lv.init() 18 | 19 | # display driver init... 20 | import display_driver_utils # Default implementation. Replace by your driver 21 | driver = display_driver_utils.driver() 22 | 23 | # FS driver init. 24 | 25 | fs_drv = lv.fs_drv_t() 26 | fs_driver.fs_register(fs_drv, 'S') 27 | 28 | ''' 29 |  load the font file from filesystem(For me is flash ) 30 |  How to convert font files refer here: https://github.com/lvgl/lv_font_conv 31 |  font-PHT-en-20.bin: 32 |    lv_font_conv --size 20 --format bin --bpp 1 --font Alibaba-PuHuiTi-Medium.subset.ttf --range 0x20-0x7f --no-compress -o font-PHT-en-20.bin 33 |  font-PHT-cn-20.bin: 34 |    lv_font_conv --size 20 --format bin --bpp 1 --font Alibaba-PuHuiTi-Medium.subset.ttf --range 0x4e00-0x4e56 --no-compress -o font-PHT-cn-20.bin 35 |  font-PHT-jp-20.bin: 36 |    lv_font_conv --size 20 --format bin --bpp 1 --font Alibaba-PuHuiTi-Medium.subset.ttf --range 0x3042-0x3093 --no-compress -o font-PHT-jp-20.bin 37 | ''' 38 | scr = lv.screen_active() 39 | scr.clean() 40 | 41 | myfont_cn = lv.binfont_create("S:%s/font/font-PHT-cn-20.bin" % script_path) 42 | 43 | label1 = lv.label(scr) 44 | label1.set_style_text_font(myfont_cn, 0) # set the font 45 | label1.set_text("上中下乎") 46 | label1.align(lv.ALIGN.CENTER, 0, -25) 47 | 48 | myfont_en = lv.binfont_create("S:%s/font/font-PHT-en-20.bin" % script_path) 49 | 50 | label2 = lv.label(scr) 51 | label2.set_style_text_font(myfont_en, 0) # set the font 52 | label2.set_text("Hello LVGL!") 53 | label2.align(lv.ALIGN.CENTER, 0, 25) 54 | 55 | 56 | myfont_jp= lv.binfont_create("S:%s/font/font-PHT-jp-20.bin" % script_path) 57 | 58 | label3 = lv.label(scr) 59 | label3.set_style_text_font(myfont_jp, 0) # set the font 60 | label3.set_text("こんにちはありがとう") 61 | label3.align(lv.ALIGN.CENTER, 0, 0) 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/blue_flower_32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/blue_flower_32.bin -------------------------------------------------------------------------------- /examples/custom_widget_example.py: -------------------------------------------------------------------------------- 1 | 2 | # This example shows how to create a custom widget and a custom theme 3 | 4 | ############################################################################## 5 | # Initializations 6 | ############################################################################## 7 | 8 | import usys as sys 9 | sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 10 | 11 | import lvgl as lv 12 | import display_driver 13 | 14 | lv.init() 15 | 16 | ############################################################################## 17 | # Helper debug function to print member name 18 | ############################################################################## 19 | 20 | member_name_cache = {} 21 | 22 | def get_member_name(obj, value): 23 | try: 24 | return member_name_cache[id(obj)][id(value)] 25 | except KeyError: 26 | pass 27 | 28 | for member in dir(obj): 29 | if getattr(obj, member) == value: 30 | try: 31 | member_name_cache[id(obj)][id(value)] = member 32 | except KeyError: 33 | member_name_cache[id(obj)] = {id(value): member} 34 | return member 35 | 36 | ############################################################################## 37 | # A class that describes a custom widget class 38 | # An instance of this class can be used to create custom widgets 39 | ############################################################################## 40 | 41 | class CustomWidgetClass(): 42 | 43 | def __init__(self, width, height): 44 | # Define LVGL widget class 45 | # In this example the class receive parameters (width, height) 46 | self.lv_cls = lv.obj_class_t() 47 | self.lv_cls.constructor_cb = self.constructor 48 | self.lv_cls.destructor_cb = self.destructor 49 | self.lv_cls.event_cb = self.event_cb 50 | self.lv_cls.width_def = int(width) 51 | self.lv_cls.height_def = int(height) 52 | self.lv_cls.group_def = lv.obj.CLASS_GROUP_DEF.TRUE 53 | self.lv_cls.base_class = lv.obj_class 54 | 55 | def create(self, parent): 56 | # Create LVGL object from class 57 | return self.lv_cls.create_obj(parent) 58 | 59 | def get_class(self): 60 | # Return the internal LVGL class 61 | return self.lv_cls 62 | 63 | def constructor(self, lv_cls, obj): 64 | # Initialize the custom widget instance 65 | obj.valid = False 66 | obj.add_flag(obj.FLAG.CLICKABLE) 67 | obj.add_flag(obj.FLAG.CHECKABLE) 68 | obj.add_flag(obj.FLAG.SCROLL_ON_FOCUS) 69 | # print("Constructor called!") 70 | 71 | def destructor(self, lv_cls, obj): 72 | pass 73 | 74 | def event_cb(self, lv_cls, e): 75 | # Call the ancestor's event handler 76 | res = lv_cls.event_base(e) 77 | if res != lv.RESULT.OK: 78 | return 79 | 80 | code = e.get_code() 81 | obj = e.get_target_obj() 82 | 83 | # print("Event %s" % get_member_name(lv.EVENT, code)) 84 | 85 | if code == lv.EVENT.DRAW_MAIN: 86 | # Draw the widget 87 | layer = e.get_layer() 88 | self.draw(obj, layer) 89 | elif code in [ 90 | lv.EVENT.STYLE_CHANGED, 91 | lv.EVENT.VALUE_CHANGED, 92 | lv.EVENT.PRESSING, 93 | lv.EVENT.RELEASED]: 94 | # Check if need to recalculate widget parameters 95 | obj.valid = False 96 | 97 | def calc(self, obj): 98 | # Calculate object parameters 99 | area = lv.area_t() 100 | obj.get_content_coords(area) 101 | 102 | obj.draw_desc = lv.draw_triangle_dsc_t() 103 | obj.draw_desc.init() 104 | obj.draw_desc.bg_opa = lv.OPA.COVER 105 | obj.draw_desc.bg_color = obj.get_style_bg_color(lv.PART.MAIN) 106 | obj.points = [ 107 | {'x':area.x1 + area.get_width()//2, 108 | 'y':area.y2 if obj.get_state() & lv.STATE.CHECKED else area.y1}, 109 | {'x':area.x2, 110 | 'y':area.y1 + area.get_height()//2}, 111 | {'x':area.x1, 112 | 'y':area.y1 + area.get_height()//2}, 113 | ] 114 | obj.draw_desc.p[0] = obj.points[0] 115 | obj.draw_desc.p[1] = obj.points[1] 116 | obj.draw_desc.p[2] = obj.points[2] 117 | 118 | obj.valid = True 119 | 120 | def draw(self, obj, layer): 121 | # If object invalidated, recalculate its parameters 122 | if not obj.valid: 123 | self.calc(obj) 124 | 125 | # Draw the custom widget 126 | lv.draw_triangle(layer, obj.draw_desc) 127 | 128 | ############################################################################## 129 | # A Python class to wrap the LVGL custom widget 130 | ############################################################################## 131 | 132 | class CustomWidget(): 133 | 134 | # An instance of a widget-class to be used for creating custom widgets 135 | # d = lv.display_get_default() 136 | dpi = 130 # d.get_dpi() 137 | cls = CustomWidgetClass(dpi, dpi) 138 | 139 | @staticmethod 140 | def get_class(): 141 | # Return the internal LVGL class 142 | return CustomWidget.cls.get_class() 143 | 144 | def __new__(cls, parent): 145 | # Return a new lv object instead of CustomWidget, 146 | # but first bind the LVGL object with CustomWidgetWrapper 147 | wrapper = cls.CustomWidgetWrapper(parent) 148 | return wrapper.lv_obj 149 | 150 | class CustomWidgetWrapper(): 151 | 152 | def __init__(self, parent): 153 | 154 | # Create the LVGL object from class 155 | self.lv_obj = CustomWidget.cls.create(parent) 156 | 157 | # Associates the LVGL object with CustomWidget wrapper 158 | self.lv_obj.set_user_data(self) 159 | 160 | # Initialize the object 161 | self.lv_obj.class_init_obj() 162 | 163 | 164 | def __getattr__(self, attr): 165 | # Provide access to LVGL object functions 166 | # print("__getattr__(%s, %s)" % (repr(self), repr(attr))) 167 | return getattr(self.lv_obj, attr) 168 | 169 | def __repr__(self): 170 | return "Custom Widget" 171 | 172 | ############################################################################## 173 | # A theme to apply styles to the custom widget 174 | ############################################################################## 175 | 176 | class CustomTheme(lv.theme_t): 177 | 178 | class Style(lv.style_t): 179 | def __init__(self): 180 | super().__init__() 181 | self.init() 182 | 183 | # Default color is gray 184 | self.set_bg_color(lv.palette_main(lv.PALETTE.GREY)) 185 | 186 | # Child elements are centered 187 | self.set_layout(lv.LAYOUT.FLEX) 188 | self.set_flex_main_place(lv.FLEX_ALIGN.CENTER) 189 | self.set_flex_cross_place(lv.FLEX_ALIGN.CENTER) 190 | self.set_flex_track_place(lv.FLEX_ALIGN.CENTER) 191 | 192 | class PressedStyle(lv.style_t): 193 | def __init__(self): 194 | super().__init__() 195 | self.init() 196 | 197 | # Pressed color is blue 198 | self.set_bg_color(lv.palette_main(lv.PALETTE.BLUE)) 199 | 200 | 201 | def __init__(self): 202 | super().__init__() 203 | self.custom_style = CustomTheme.Style() 204 | self.custom_pressed_style = CustomTheme.PressedStyle() 205 | 206 | # This theme is based on active theme 207 | base_theme = lv.theme_get_from_obj(lv.screen_active()) 208 | 209 | # This theme will be applied only after base theme is applied 210 | self.set_parent(base_theme) 211 | 212 | # Set the "apply" callback of this theme to a custom callback 213 | self.set_apply_cb(self.apply) 214 | 215 | # Activate this theme on the default display 216 | lv.display_get_default().set_theme(self) 217 | 218 | def apply(self, theme, obj): 219 | # Apply this theme on CustomWidget class 220 | if obj.get_class() == CustomWidget.get_class(): 221 | obj.add_style(self.custom_style, lv.PART.MAIN) 222 | obj.add_style(self.custom_pressed_style, lv.PART.MAIN | lv.STATE.PRESSED) 223 | 224 | 225 | ############################################################################## 226 | # Main program - create screen and widgets 227 | ############################################################################## 228 | 229 | # Create the theme for the custom widget 230 | theme = CustomTheme() 231 | 232 | # Create a screen with flex layout 233 | scr = lv.screen_active() 234 | scr.set_flex_flow(lv.FLEX_FLOW.COLUMN) 235 | scr.set_flex_align(lv.FLEX_ALIGN.SPACE_EVENLY, lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.CENTER) 236 | 237 | # Add a button with a label 238 | button = lv.button(scr) 239 | l1 = lv.label(button) 240 | l1.set_text("Hello!") 241 | 242 | # Add a custom widget with a label 243 | customWidget = CustomWidget(scr) 244 | l2 = lv.label(customWidget) 245 | l2.set_text("Click me!") 246 | 247 | # Add click events to both button and custom widget 248 | def event_cb(e): 249 | print("%s Clicked!" % repr(e.get_target_obj())) 250 | 251 | for widget in [button, customWidget]: 252 | widget.add_event_cb(event_cb, lv.EVENT.CLICKED, None) 253 | 254 | -------------------------------------------------------------------------------- /examples/example3.py: -------------------------------------------------------------------------------- 1 | import usys as sys 2 | sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 3 | 4 | import lv_utils 5 | import lvgl as lv 6 | 7 | try: 8 | import pyb 9 | import rk043fn48h as lcd 10 | 11 | hres = 480 12 | vres = 272 13 | color_format = lv.COLOR_FORMAT.ARGB8888 14 | 15 | lv.init() 16 | event_loop = lv_utils.event_loop() 17 | lcd.init(w=hres, h=vres) 18 | 19 | buf1 = lcd.framebuffer(1) 20 | buf2 = lcd.framebuffer(2) 21 | 22 | disp_drv = lv.display_create(hres, vres) 23 | disp_drv.set_flush_cb(lcd.flush) 24 | disp_drv.set_color_format(color_format) 25 | disp_drv.set_buffers(buf1, buf2, len(buf1), lv.DISPLAY_RENDER_MODE.PARTIAL) 26 | 27 | # disp_drv.gpu_blend_cb = lcd.gpu_blend 28 | # disp_drv.gpu_fill_cb = lcd.gpu_fill 29 | 30 | indev_drv = lv.indev_create() 31 | indev_drv.set_type(lv.INDEV_TYPE.POINTER) 32 | indev_drv.set_read_cb(lcd.ts_read) 33 | 34 | except ImportError: 35 | import display_driver 36 | 37 | scr1 = lv.obj() 38 | scr2 = lv.obj() 39 | lv.screen_load(scr1) 40 | 41 | slider = lv.slider(scr2) 42 | slider.set_width(150) 43 | slider.align(lv.ALIGN.TOP_MID, 0, 15) 44 | 45 | button1 = lv.button(scr1) 46 | button1.align(lv.ALIGN.TOP_RIGHT, -5, 5) 47 | label = lv.label(button1) 48 | label.set_text(">") 49 | 50 | button2 = lv.button(scr2) 51 | button2.align(lv.ALIGN.TOP_LEFT, 5, 5) 52 | label2 = lv.label(button2) 53 | label2.set_text("<") 54 | 55 | led1 = lv.led(scr2) 56 | led1.align(lv.ALIGN.CENTER, 0, 0) 57 | led1.set_brightness(slider.get_value() * 2) 58 | # led1.set_drag(True) 59 | led1.set_size(20,20) 60 | 61 | 62 | def slider_event_cb(event): 63 | led1.set_brightness(slider.get_value() * 2) 64 | 65 | def button1_event_cb(event): 66 | lv.screen_load(scr2) 67 | 68 | 69 | def button2_event_cb(event): 70 | lv.screen_load(scr1) 71 | 72 | slider.add_event_cb(slider_event_cb, lv.EVENT.VALUE_CHANGED, None) 73 | button1.add_event_cb(button1_event_cb, lv.EVENT.CLICKED, None) 74 | button2.add_event_cb(button2_event_cb, lv.EVENT.CLICKED, None) 75 | 76 | # Create a keyboard 77 | kb = lv.keyboard(scr1) 78 | # kb.set_cursor_manage(True) 79 | 80 | # Create a text area. The keyboard will write here 81 | ta = lv.textarea(scr1) 82 | ta.set_width(450) 83 | ta.set_height(70) 84 | ta.align(lv.ALIGN.CENTER, 0, 0) 85 | ta.set_text("") 86 | 87 | # Assign the text area to the keyboard 88 | kb.set_textarea(ta) 89 | 90 | # Create a Spinner object 91 | spin = lv.spinner(scr2) 92 | spin.set_anim_params(1000, 100) 93 | spin.set_size(100, 100) 94 | spin.align(lv.ALIGN.CENTER, 0, 0) 95 | # spin.set_type(lv.spinner.TYPE.FILLSPIN_ARC) 96 | -------------------------------------------------------------------------------- /examples/fb_test.py: -------------------------------------------------------------------------------- 1 | # A simple test for Linux Frame Buffer 2 | # Registers FB display and mouse evdev and shows line cursor 3 | # then shows a button on screen. 4 | 5 | import lvgl as lv 6 | from lv_utils import event_loop 7 | import evdev 8 | 9 | lv.init() 10 | 11 | # Register FB display driver 12 | 13 | event_loop = event_loop() 14 | disp = lv.linux_fbdev_create() 15 | lv.linux_fbdev_set_file(disp, "/dev/fb0") 16 | 17 | # Register mouse device and crosshair cursor 18 | 19 | mouse = evdev.mouse_indev(lv.screen_active()) 20 | 21 | # Create a screen and a button 22 | 23 | btn = lv.button(lv.screen_active()) 24 | btn.align(lv.ALIGN.CENTER, 0, 0) 25 | label = lv.label(btn) 26 | label.set_text("Hello World!") 27 | -------------------------------------------------------------------------------- /examples/font/font-PHT-cn-20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/font/font-PHT-cn-20.bin -------------------------------------------------------------------------------- /examples/font/font-PHT-en-20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/font/font-PHT-en-20.bin -------------------------------------------------------------------------------- /examples/font/font-PHT-jp-20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/font/font-PHT-jp-20.bin -------------------------------------------------------------------------------- /examples/generic-st77xx-with-xpt2046.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('.') 3 | from st77xx import * 4 | from xpt2046 import * 5 | 6 | spi=machine.SPI( 7 | 1, 8 | baudrate=24_000_000, 9 | polarity=0, 10 | phase=0, 11 | sck=machine.Pin(10,machine.Pin.OUT), 12 | mosi=machine.Pin(11,machine.Pin.OUT), 13 | miso=machine.Pin(12,machine.Pin.IN) 14 | ) 15 | 16 | if 0: 17 | # with DMA, the repaints seem to be too slow? To be investigated 18 | # we seem to be fine performance-wise without DMA with 320x240 anyway 19 | import rp2_dma 20 | rp2_dma=rp2_dma.DMA(0) 21 | else: rp2_dma=None 22 | 23 | import lvgl as lv 24 | lv.init() 25 | 26 | lcd=St7789(rot=3,res=(240,320),spi=spi,cs=9,dc=8,bl=13,rst=15,rp2_dma=rp2_dma,factor=8) 27 | lcd.set_backlight(30) 28 | touch=Xpt2046(spi=spi,cs=16,rot=1,spiPrereadCb=lcd.rp2_wait_dma) 29 | 30 | scr=lv.obj() 31 | btn=lv.button(scr) 32 | lbl=lv.label(btn) 33 | lbl.set_text("Press me!") 34 | btn.center() 35 | btn.add_event(lambda event: print('Button clicked!'),lv.EVENT.CLICKED,None) 36 | lv.screen_load(scr) 37 | -------------------------------------------------------------------------------- /examples/madctl/README.md: -------------------------------------------------------------------------------- 1 | # Determining the `colormode`, `invert`, and `rot` parameters 2 | 3 | The [madctl_helper.py](madctl_helper.py) program can help determine the `colormode`, `invert`, and 4 | `rot` parameters for a display. Set the `mosi`, `clk`, `dc`, `cs`, `backlight`, `width`, and 5 | `height` parameters for your display. Start with `rot=0`, `colormode=COLOR_MODE_RGB`, and `rot=0`. 6 | Run the program with the display in the orientation you would like to configure. 7 | 8 | ## Setting the `colormode` and `invert` parameter 9 | 10 | The program will draw a large red full-screen button with an 'F' character centered in the button. 11 | You can determine the `colormode` and `invert` parameters by observing the button's color. 12 | 13 | - If the button is RED, the `colormode` and `invert` parameters are correct. 14 | - If the button is BLUE, `colormode` should be `COLOR_MODE_BGR`. 15 | - If the button is YELLOW, `invert` should be `True`. 16 | - If the button is CYAN, `colormode` should be `COLOR_MODE_BGR` and `invert` should be `True`. 17 | 18 | ## Setting the `rot` parameter 19 | 20 | The `rot` parameter is used to set the MADCTL register of the display. The MADCTL register controls 21 | the order that pixels are written to the framebuffer. This sets the Orientation or Rotation of the 22 | display. Match the orientation and direction of the 'F' character as drawn on the display by the 23 | [madctl_helper.py](madctl_helper.py) program with the examples in the table below to determine the 24 | `rot` parameter value. 25 | 26 | Image | `rot` parameter 27 | ----- | --------------- 28 | ![MADCTL_0](images/madctl_0.png) | rot=0 29 | ![MADCTL_MY](images/madctl_y.png) | rot=MADCTL_MY 30 | ![MADCTL_MX](images/madctl_x.png) | rot=MADCTL_MX 31 | ![MADCTL_MX | MADCTL_MY](images/madctl_xy.png) | rot=MADCTL_MX | MADCTL_MY 32 | ![MADCTL_MV](images/madctl_v.png) | rot=MADCTL_MV 33 | ![MADCTL_MV | MADCTL_MY](images/madctl_vy.png) | rot=MADCTL_MV | MADCTL_MY 34 | ![MADCTL_MV | MADCTL_MX](images/madctl_vx.png) | rot=MADCTL_MV | MADCTL_MX 35 | ![MADCTL_MV | MADCTL_MX | MADCTL_MY](images/madctl_vxy.png) | rot=MADCTL_MV | MADCTL_MX | MADCTL_MX 36 | 37 | ## MADCTL Constants 38 | 39 | The following table shows the MADCTL bit flags and their effects. 40 | 41 | Constant | Value | Description 42 | -------------- | ----- | ---------------------- 43 | MADCTL_MY | 0x80 | Page Address Order 44 | MADCTL_MX | 0x40 | Column Address Order 45 | MADCTL_MV | 0x20 | Page/Column Order 46 | MADCTL_ML | 0x10 | Line Address Order 47 | MADCTL_MH | 0x04 | Display Data Latch Order 48 | COLOR_MODE_RGB | 0x00 | RGB color order 49 | COLOR_MODE_BGR | 0x08 | BGR color order 50 | -------------------------------------------------------------------------------- /examples/madctl/images/madctl_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_0.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_v.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_vx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_vx.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_vxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_vxy.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_vy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_vy.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_x.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_xy.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/madctl/images/madctl_y.png -------------------------------------------------------------------------------- /examples/madctl/madctl_helper.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | from ili9XXX import * 3 | 4 | # ili9341 example 5 | disp = ili9341( 6 | mosi=18, clk=19, cs=13, dc=12, rst=4, backlight=15, backlight_on=1, 7 | width=128, height=160, colormode=COLOR_MODE_RGB, invert=False, rot=0) 8 | 9 | # st7789 example 10 | # disp = st7789( 11 | # mosi=19, clk=18, cs=5, dc=16, rst=23, backlight=4, backlight_on=1, 12 | # width=240, height=320, colormode=COLOR_MODE_RGB, invert=False, rot=0) 13 | 14 | style = lv.style_t() 15 | style.init() 16 | style.set_bg_color(lv.palette_main(lv.PALETTE.RED)) 17 | 18 | btn = lv.button(lv.scr_act()) 19 | btn.set_size(disp.width, disp.height) 20 | btn.align(lv.ALIGN.CENTER,0,0) 21 | btn.add_style(style, 0) 22 | 23 | label = lv.label(btn) 24 | label.set_text("F") 25 | label.center() -------------------------------------------------------------------------------- /examples/png_decoder_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgl/lv_binding_micropython/44f70b1e1adb087e00ea5d39fe45a0b0f3551646/examples/png_decoder_test.png -------------------------------------------------------------------------------- /examples/png_example.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Example of the PNG image decoder Usage. 4 | # For dragging to work reasonable, make sure LV_CACHE_DEF_SIZE is not 0! 5 | # 6 | ############################################################################## 7 | 8 | import usys as sys 9 | sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 10 | 11 | import lvgl as lv 12 | import display_driver_utils 13 | 14 | # Init 15 | 16 | lv.init() 17 | driver = display_driver_utils.driver() 18 | 19 | scr = lv.screen_active() 20 | try: 21 | script_path = __file__[:__file__.rfind('/')] if __file__.find('/') >= 0 else '.' 22 | except NameError: 23 | script_path = '' 24 | 25 | # Load the image 26 | 27 | with open('%s/png_decoder_test.png' % script_path, 'rb') as f: 28 | png_data = f.read() 29 | 30 | png_image_dsc = lv.image_dsc_t({ 31 | 'data_size': len(png_data), 32 | 'data': png_data 33 | }) 34 | 35 | # Create an image using the decoder 36 | 37 | image1 = lv.image(scr) 38 | image1.set_src(png_image_dsc) 39 | image1.set_pos(100,50) 40 | 41 | # Create an image from a symbol 42 | 43 | image2 = lv.image(scr) 44 | image2.set_src(lv.SYMBOL.OK + " Accept") 45 | image2.set_pos(100,200) 46 | 47 | # Drag handler 48 | 49 | def drag_event_handler(e): 50 | self = e.get_target_obj() 51 | indev = lv.indev_get_act() 52 | vect = lv.point_t() 53 | indev.get_vect(vect) 54 | x = self.get_x() + vect.x 55 | y = self.get_y() + vect.y 56 | self.set_pos(x, y) 57 | 58 | # Register drag handler for images 59 | 60 | for image in [image1, image2]: 61 | image.add_flag(image.FLAG.CLICKABLE) 62 | image.add_event_cb(drag_event_handler, lv.EVENT.PRESSING, None) 63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/uasyncio_example1.py: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # 3 | # Example of LVGL with uasyncio event loop on unix and esp32 ports 4 | # 5 | # Running on Linux: (need micropython-dev for uasyncio): 6 | # ports/unix/micropython-dev -i lib/lv_bindings/examples/uasyncio_example1.py 7 | # 8 | # Important differences from non-uasyncio examples: 9 | # 10 | # - Import lv_utils and call lv_utils.event_loop (optional for ili9XXX) 11 | # - Pass asynchronous=True 12 | # - Call uasyncio.Loop.run_forever() 13 | # 14 | ################################################################################################## 15 | 16 | # Workaround for including frozen modules when running micropython with a script argument 17 | # https://github.com/micropython/micropython/issues/6419 18 | import usys as sys 19 | sys.path.append('') 20 | 21 | # Imports 22 | 23 | from urandom import getrandbits, seed 24 | from utime import ticks_us 25 | from uasyncio import sleep, create_task, Loop, CancelledError 26 | import lv_utils 27 | import lvgl as lv 28 | 29 | seed(ticks_us()) 30 | lv.init() 31 | 32 | ################################################################################################## 33 | # Display initialization 34 | ################################################################################################## 35 | 36 | # Default resolution match ili9341 37 | HOR_RES = 240 38 | VER_RES = 320 39 | 40 | # Try to initialize SDL 41 | try: 42 | # Register SDL display driver. 43 | 44 | disp_drv = lv.sdl_window_create(HOR_RES, VER_RES) 45 | mouse = lv.sdl_mouse_create() 46 | 47 | event_loop = lv_utils.event_loop(asynchronous=True) 48 | 49 | except AttributeError: 50 | pass 51 | 52 | # Try initialize ili9341/xpt2046 53 | try: 54 | from ili9XXX import ili9341 55 | from xpt2046 import xpt2046 56 | 57 | # Initialize and register drivers 58 | 59 | disp = ili9341(dc=32, cs=33, power=-1, backlight=-1, asynchronous=True, initialize=True) 60 | touch = xpt2046() 61 | 62 | except ImportError: 63 | pass 64 | 65 | ################################################################################################## 66 | # Stylized Message Box class 67 | ################################################################################################## 68 | 69 | class MsgBox(lv.win): 70 | 71 | def drag_event_handler(self, e): 72 | self.move_foreground() 73 | indev = lv.indev_get_act() 74 | indev.get_vect(self.vect) 75 | x = self.get_x() + self.vect.x 76 | y = self.get_y() + self.vect.y 77 | self.set_pos(x, y) 78 | 79 | def __init__(self, parent): 80 | super().__init__(parent) 81 | self.vect = lv.point_t() 82 | 83 | self.set_size(100,80) 84 | self.add_title("Pop") 85 | msg_box_close_btn = self.add_button(lv.SYMBOL.CLOSE, 20) 86 | msg_box_close_btn.add_event(lambda e: self.close_msg_box(), lv.EVENT.RELEASED, None) 87 | 88 | header = self.get_header() 89 | header.set_style_bg_color(lv.color_hex3(0xFEE), lv.PART.MAIN) 90 | 91 | content = self.get_content() 92 | content.set_style_bg_color(lv.color_hex3(0xFFF), lv.PART.MAIN) 93 | 94 | self.set_style_border_width(4, lv.PART.MAIN) 95 | self.set_style_border_color(lv.color_hex3(0xF88), lv.PART.MAIN) 96 | self.set_style_shadow_color(lv.color_hex3(0x000), lv.PART.MAIN) 97 | self.set_style_shadow_opa(50, lv.PART.MAIN) 98 | self.set_style_shadow_width(20, lv.PART.MAIN) 99 | self.set_style_shadow_offset_x(10, lv.PART.MAIN) 100 | self.set_style_shadow_offset_y(10, lv.PART.MAIN) 101 | self.set_style_shadow_spread(0, lv.PART.MAIN) 102 | self.set_style_radius(10, lv.PART.MAIN) 103 | 104 | self.label = lv.label(content) 105 | 106 | for element in [content, header]: 107 | element.add_event(self.drag_event_handler, lv.EVENT.PRESSING, None) 108 | 109 | self.opened = True 110 | 111 | def is_open(self): 112 | return self.opened 113 | 114 | def close_msg_box(self): 115 | if self.is_open(): 116 | self.anim = lv.anim_t() 117 | self.anim.init() 118 | self.anim.set_var(self) 119 | self.anim.set_time(500) 120 | self.anim.set_values(lv.OPA.COVER,lv.OPA.TRANSP) 121 | self.anim.set_custom_exec_cb(lambda obj, val: 122 | self.set_style_opa(val, lv.PART.MAIN)) 123 | self.anim.set_path_cb(lv.anim_t.path_ease_in) 124 | self.anim.set_ready_cb(lambda a: self.del_async()) 125 | lv.anim_t.start(self.anim) 126 | self.opened = False 127 | 128 | def set_text(self, txt): 129 | 130 | # If the msg box is already closed, cancel the calling task 131 | if not self.is_open(): 132 | raise CancelledError() 133 | 134 | self.label.set_text(txt) 135 | 136 | ################################################################################################## 137 | # Async task 138 | ################################################################################################## 139 | 140 | async def btn_event_task(obj=None, event=-1): 141 | 142 | # Create and position the a new msg box 143 | 144 | msg_box = MsgBox(scr) 145 | msg_box.set_pos(getrandbits(7), 50 + getrandbits(7)) 146 | 147 | # Countdown 148 | 149 | for i in range(10, 0, -1): 150 | msg_box.set_text(str(i)) 151 | await sleep(1) 152 | 153 | # Close the msg box 154 | 155 | msg_box.close_msg_box() 156 | 157 | ################################################################################################## 158 | # Create objects and screen 159 | ################################################################################################## 160 | 161 | scr = lv.screen_active() 162 | btn = lv.button(scr) 163 | btn.align(lv.ALIGN.TOP_MID, 0, 10) 164 | btn.add_event(lambda e: create_task(btn_event_task()), lv.EVENT.CLICKED, None) 165 | label = lv.label(btn) 166 | label.set_text('Click Me Again!') 167 | 168 | ################################################################################################## 169 | # Start event loop 170 | ################################################################################################## 171 | 172 | Loop.run_forever() 173 | 174 | -------------------------------------------------------------------------------- /gen/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | lextab.py 3 | yacctab.py 4 | 5 | -------------------------------------------------------------------------------- /include/lv_mp_mem_custom_include.h: -------------------------------------------------------------------------------- 1 | #ifndef __LV_MP_MEM_CUSTOM_INCLUDE_H 2 | #define __LV_MP_MEM_CUSTOM_INCLUDE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #endif //__LV_MP_MEM_CUSTOM_INCLUDE_H 9 | -------------------------------------------------------------------------------- /lib/display_driver.py: -------------------------------------------------------------------------------- 1 | from display_driver_utils import driver 2 | drv = driver() 3 | -------------------------------------------------------------------------------- /lib/display_driver_utils.py: -------------------------------------------------------------------------------- 1 | import usys as sys 2 | sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 3 | 4 | import lvgl as lv 5 | 6 | try: 7 | import lv_utils 8 | lv_utils_available = True 9 | except: 10 | lv_utils_available = False 11 | 12 | ORIENT_LANDSCAPE = False 13 | ORIENT_PORTRAIT = True 14 | 15 | class driver: 16 | 17 | def __init__(self,width=420,height=320,orientation=ORIENT_PORTRAIT, asynchronous=False, exception_sink=None, defaultGroup=True): 18 | 19 | if not lv.is_initialized(): 20 | lv.init() 21 | 22 | self.group = lv.group_create() 23 | if defaultGroup: 24 | self.group.set_default() 25 | 26 | self.width = width 27 | self.height = height 28 | self.orientation = orientation 29 | self.asynchronous = asynchronous 30 | self.exception_sink = exception_sink 31 | self.disp = None 32 | self.touch = None 33 | self.type = None 34 | if not (lv_utils_available and lv_utils.event_loop.is_running()): 35 | self.init_gui() 36 | 37 | def init_gui_SDL(self): 38 | if lv_utils_available: 39 | self.event_loop = lv_utils.event_loop(asynchronous=self.asynchronous, exception_sink=self.exception_sink) 40 | 41 | lv.sdl_window_create(self.width, self.height) 42 | self.mouse = lv.sdl_mouse_create() 43 | self.keyboard = lv.sdl_keyboard_create() 44 | self.keyboard.set_group(self.group) 45 | self.type = "SDL" 46 | print("Running the SDL lvgl version") 47 | 48 | def init_gui_ili9341(self): 49 | 50 | # Initialize ILI9341 display 51 | 52 | from ili9XXX import ili9341,LANDSCAPE 53 | from xpt2046 import xpt2046 54 | #import espidf as esp 55 | 56 | if lv_utils_available: 57 | self.event_loop = lv_utils.event_loop(asynchronous=self.asynchronous, exception_sink=self.exception_sink) 58 | 59 | if self.orientation == ORIENT_PORTRAIT: 60 | print ("Running the ili9341 lvgl version in portrait mode") 61 | 62 | # Initialize ILI9341 display in prtrait mode 63 | # the following are the settings for the Lolin tft 2.4 display 64 | # self.disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, spihost=esp.VSPI_HOST) 65 | # self.touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=3751, cal_x1 = 210, cal_y0=3906, cal_y1 = 283, transpose=True) 66 | 67 | self.disp = ili9341() 68 | self.touch = xpt2046() 69 | 70 | elif self.orientation == ORIENT_LANDSCAPE: 71 | print ("Running the ili9341 lvgl version in landscape mode") 72 | # Initialize ILI9341 display 73 | # the following are the settings for the Lolin tft 2.4 display 74 | # self.disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, backlight_on=0, 75 | # spihost=esp.VSPI_HOST, width=320, height=240, rot=LANDSCAPE) 76 | # self.touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=3799, cal_x1 = 353, cal_y0=220, cal_y1 = 3719, transpose=False) 77 | 78 | # Register xpt2046 touch driver 79 | self.disp = ili9341(width=320, height=240, rot=LANDSCAPE) 80 | self.touch = xpt2046(cal_x0=3799, cal_x1 = 353, cal_y0=220, cal_y1 = 3719,transpose = False) 81 | 82 | else: 83 | raise RuntimeError("Invalid orientation") 84 | 85 | self.type="ili9341" 86 | 87 | ''' 88 | # Register raw resistive touch driver (remove xpt2046 initialization first) 89 | import rtch 90 | touch = rtch.touch(xp = 32, yp = 33, xm = 25, ym = 26, touch_rail = 27, touch_sense = 33) 91 | touch.init() 92 | self.indev_drv = lv.indev_create() 93 | self.indev_drv.set_type(lv.INDEV_TYPE.POINTER) 94 | self.indev_drv.set_read_cb(touch.read) 95 | ''' 96 | 97 | def init_gui_twatch(self): 98 | 99 | import ttgo 100 | from axp_constants import AXP202_VBUS_VOL_ADC1,AXP202_VBUS_CUR_ADC1,AXP202_BATT_CUR_ADC1,AXP202_BATT_VOL_ADC1 101 | 102 | watch = ttgo.Watch() 103 | tft = watch.tft 104 | power = watch.pmu 105 | power.adc1Enable(AXP202_VBUS_VOL_ADC1 106 | | AXP202_VBUS_CUR_ADC1 107 | | AXP202_BATT_CUR_ADC1 108 | | AXP202_BATT_VOL_ADC1, True) 109 | watch.lvgl_begin() 110 | watch.tft.backlight_fade(100) 111 | 112 | self.type="t-watch" 113 | print("Running lvgl on the LilyGo t-watch 2020") 114 | 115 | def init_gui(self): 116 | 117 | # Identify platform and initialize it 118 | 119 | try: 120 | self.init_gui_twatch() 121 | return 122 | except: 123 | pass 124 | 125 | try: 126 | self.init_gui_ili9341() 127 | return 128 | except ImportError: 129 | pass 130 | 131 | try: 132 | self.init_gui_SDL() 133 | return 134 | except ImportError: 135 | pass 136 | 137 | raise RuntimeError("Could not find a suitable display driver!") 138 | 139 | -------------------------------------------------------------------------------- /lib/fs_driver.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Original author: mhepp(https://forum.lvgl.io/u/mhepp/summary) 3 | ''' 4 | 5 | import lvgl as lv 6 | import ustruct as struct 7 | 8 | def fs_open_cb(drv, path, mode): 9 | 10 | if mode == lv.FS_MODE.WR: 11 | p_mode = 'wb' 12 | elif mode == lv.FS_MODE.RD: 13 | p_mode = 'rb' 14 | elif mode == lv.FS_MODE.WR | lv.FS_MODE.RD: 15 | p_mode = 'rb+' 16 | else: 17 | raise RuntimeError("fs_open_callback() - open mode error, %s is invalid mode" % mode) 18 | 19 | try: 20 | f = open(path, p_mode) 21 | 22 | except OSError as e: 23 | raise RuntimeError("fs_open_callback(%s) exception: %s" % (path, e)) 24 | 25 | return {'file' : f, 'path': path} 26 | 27 | 28 | def fs_close_cb(drv, fs_file): 29 | try: 30 | fs_file.__cast__()['file'].close() 31 | except OSError as e: 32 | raise RuntimeError("fs_close_callback(%s) exception: %s" % (fs_file.__cast__()['path'], e)) 33 | 34 | return lv.FS_RES.OK 35 | 36 | 37 | def fs_read_cb(drv, fs_file, buf, btr, br): 38 | try: 39 | tmp_data = fs_file.__cast__()['file'].read(btr) 40 | buf.__dereference__(btr)[0:len(tmp_data)] = tmp_data 41 | br.__dereference__(4)[0:4] = struct.pack("= 0: 89 | fs_drv.cache_size = cache_size 90 | 91 | fs_drv.register() 92 | 93 | -------------------------------------------------------------------------------- /lib/lv_colors.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | def LV_COLOR_MAKE(r,g,b): 3 | # return lv.color_hex(r<<16| g<<8 |b) 4 | return lv.color_make(r,g,b) 5 | 6 | class lv_colors: 7 | WHITE=LV_COLOR_MAKE(0xFF, 0xFF, 0xFF) 8 | SILVER=LV_COLOR_MAKE(0xC0, 0xC0, 0xC0) 9 | GRAY=LV_COLOR_MAKE(0x80, 0x80, 0x80) 10 | BLACK=LV_COLOR_MAKE(0x00, 0x00, 0x00) 11 | RED=LV_COLOR_MAKE(0xFF, 0x00, 0x00) 12 | MAROON=LV_COLOR_MAKE(0x80, 0x00, 0x00) 13 | YELLOW=LV_COLOR_MAKE(0xFF, 0xFF, 0x00) 14 | OLIVE=LV_COLOR_MAKE(0x80, 0x80, 0x00) 15 | LIME=LV_COLOR_MAKE(0x00, 0xFF, 0x00) 16 | GREEN=LV_COLOR_MAKE(0x00, 0x80, 0x00) 17 | CYAN=LV_COLOR_MAKE(0x00, 0xFF, 0xFF) 18 | AQUA=CYAN 19 | TEAL=LV_COLOR_MAKE(0x00, 0x80, 0x80) 20 | BLUE=LV_COLOR_MAKE(0x00, 0x00, 0xFF) 21 | NAVY=LV_COLOR_MAKE(0x00, 0x00, 0x80) 22 | MAGENTA=LV_COLOR_MAKE(0xFF, 0x00, 0xFF) 23 | PURPLE=LV_COLOR_MAKE(0x80, 0x00, 0x80) 24 | ORANGE=LV_COLOR_MAKE(0xFF, 0xA5, 0x00) 25 | -------------------------------------------------------------------------------- /lib/lv_utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Event Loop module: advancing tick count and scheduling lvgl task handler. 3 | # Import after lvgl module. 4 | # This should be imported and used by display driver. 5 | # Display driver should first check if an event loop is already running. 6 | # 7 | # Usage example with SDL: 8 | # 9 | # SDL.init(auto_refresh=False) 10 | # # Register SDL display driver. 11 | # # Register SDL mouse driver 12 | # event_loop = lv_utils.event_loop() 13 | # 14 | # 15 | # asyncio example with SDL: 16 | # 17 | # SDL.init(auto_refresh=False) 18 | # # Register SDL display driver. 19 | # # Register SDL mouse driver 20 | # event_loop = lv_utils.event_loop(asynchronous=True) 21 | # asyncio.Loop.run_forever() 22 | # 23 | # asyncio example with ili9341: 24 | # 25 | # event_loop = lv_utils.event_loop(asynchronous=True) # Optional! 26 | # self.disp = ili9341(asynchronous=True) 27 | # asyncio.Loop.run_forever() 28 | # 29 | # MIT license; Copyright (c) 2021 Amir Gonnen 30 | # 31 | ############################################################################## 32 | 33 | import lvgl as lv 34 | import micropython 35 | import sys 36 | 37 | # Try standard machine.Timer, or custom timer from lv_timer, if available 38 | 39 | try: 40 | from machine import Timer 41 | except: 42 | try: 43 | from lv_timer import Timer 44 | except: 45 | if sys.platform != "darwin": 46 | raise RuntimeError("Missing machine.Timer implementation!") 47 | Timer = False 48 | 49 | # Try to determine default timer id 50 | 51 | default_timer_id = 0 52 | if sys.platform == "pyboard": 53 | # stm32 only supports SW timer -1 54 | default_timer_id = -1 55 | 56 | if sys.platform == "rp2": 57 | # rp2 only supports SW timer -1 58 | default_timer_id = -1 59 | 60 | # Try importing asyncio, if available 61 | 62 | try: 63 | import asyncio 64 | 65 | asyncio_available = True 66 | except: 67 | asyncio_available = False 68 | 69 | ############################################################################## 70 | 71 | 72 | class event_loop: 73 | _current_instance = None 74 | 75 | def __init__( 76 | self, 77 | freq=25, 78 | timer_id=default_timer_id, 79 | max_scheduled=2, 80 | refresh_cb=None, 81 | asynchronous=False, 82 | exception_sink=None, 83 | ): 84 | if self.is_running(): 85 | raise RuntimeError("Event loop is already running!") 86 | 87 | if not lv.is_initialized(): 88 | lv.init() 89 | 90 | event_loop._current_instance = self 91 | 92 | self.delay = 1000 // freq 93 | self.refresh_cb = refresh_cb 94 | self.exception_sink = ( 95 | exception_sink if exception_sink else self.default_exception_sink 96 | ) 97 | 98 | self.asynchronous = asynchronous 99 | if self.asynchronous: 100 | if not asyncio_available: 101 | raise RuntimeError( 102 | "Cannot run asynchronous event loop. asyncio is not available!" 103 | ) 104 | self.refresh_event = asyncio.Event() 105 | self.refresh_task = asyncio.create_task(self.async_refresh()) 106 | self.timer_task = asyncio.create_task(self.async_timer()) 107 | else: 108 | if Timer: 109 | self.timer = Timer(timer_id) 110 | self.timer.init( 111 | mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb 112 | ) 113 | self.task_handler_ref = self.task_handler # Allocation occurs here 114 | self.max_scheduled = max_scheduled 115 | self.scheduled = 0 116 | 117 | def init_async(self): 118 | self.refresh_event = asyncio.Event() 119 | self.refresh_task = asyncio.create_task(self.async_refresh()) 120 | self.timer_task = asyncio.create_task(self.async_timer()) 121 | 122 | def deinit(self): 123 | if self.asynchronous: 124 | self.refresh_task.cancel() 125 | self.timer_task.cancel() 126 | else: 127 | if Timer: 128 | self.timer.deinit() 129 | event_loop._current_instance = None 130 | 131 | def disable(self): 132 | self.scheduled += self.max_scheduled 133 | 134 | def enable(self): 135 | self.scheduled -= self.max_scheduled 136 | 137 | @staticmethod 138 | def is_running(): 139 | return event_loop._current_instance is not None 140 | 141 | @staticmethod 142 | def current_instance(): 143 | return event_loop._current_instance 144 | 145 | def task_handler(self, _): 146 | try: 147 | if lv._nesting.value == 0: 148 | lv.task_handler() 149 | if self.refresh_cb: 150 | self.refresh_cb() 151 | self.scheduled -= 1 152 | except Exception as e: 153 | if self.exception_sink: 154 | self.exception_sink(e) 155 | 156 | def tick(self): 157 | self.timer_cb(None) 158 | 159 | def run(self): 160 | if sys.platform == "darwin": 161 | while True: 162 | self.tick() 163 | 164 | def timer_cb(self, t): 165 | # Can be called in Interrupt context 166 | # Use task_handler_ref since passing self.task_handler would cause allocation. 167 | lv.tick_inc(self.delay) 168 | if self.scheduled < self.max_scheduled: 169 | try: 170 | micropython.schedule(self.task_handler_ref, 0) 171 | self.scheduled += 1 172 | except: 173 | pass 174 | 175 | async def async_refresh(self): 176 | while True: 177 | await self.refresh_event.wait() 178 | if lv._nesting.value == 0: 179 | self.refresh_event.clear() 180 | try: 181 | lv.task_handler() 182 | except Exception as e: 183 | if self.exception_sink: 184 | self.exception_sink(e) 185 | if self.refresh_cb: 186 | self.refresh_cb() 187 | 188 | async def async_timer(self): 189 | while True: 190 | await asyncio.sleep_ms(self.delay) 191 | lv.tick_inc(self.delay) 192 | self.refresh_event.set() 193 | 194 | def default_exception_sink(self, e): 195 | sys.print_exception(e) 196 | # event_loop.current_instance().deinit() 197 | -------------------------------------------------------------------------------- /lib/tpcal.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 3 | 4 | import lvgl as lv 5 | lv.init() 6 | # lv.log_register_print_cb(lambda msg: print(msg)) 7 | 8 | # Initialize ILI9341 display 9 | 10 | #import espidf as esp 11 | from ili9XXX import ili9341,COLOR_MODE_BGR,LANDSCAPE,PORTRAIT 12 | 13 | disp = ili9341(dc=32, cs=33, power=-1, backlight=-1) 14 | 15 | # The call below is for a Lolin TFT-2.4 board 16 | # disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, backlight_on=0, power_on=0, 17 | # spihost=esp.VSPI_HOST, mhz=40, factor=4, hybrid=True, width=240, height=320, 18 | # colormode=COLOR_MODE_BGR, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True) 19 | 20 | # In order to calibrate in landscape mode please use: 21 | # disp = ili9341(rot=LANDSCAPE,width=320,height=240)) 22 | 23 | HRES = lv.scr_act().get_disp().driver.hor_res 24 | VRES = lv.scr_act().get_disp().driver.ver_res 25 | 26 | # Register xpt touch driver 27 | from xpt2046 import xpt2046 28 | touch = xpt2046(cal_x0=0, cal_x1 = HRES, cal_y0=0, cal_y1 = VRES) 29 | 30 | # The call below is for a Lolin TFT-2.4 board 31 | # touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=0, cal_x1 = HRES, cal_y0=0, cal_y1 = VRES, transpose=True) 32 | 33 | # In order to calibrate in landscape mode please use: 34 | # touch = xpt2046(cal_x0=0, cal_x1 = HRES, cal_y0=0, cal_y1 = VRES,transpose=False) 35 | 36 | # Point class, with both display and touch coordinates 37 | 38 | class Tpcal_point(): 39 | def __init__(self, x, y, name): 40 | self.display_coordinates = lv.point_t({'x': x, 'y': y}) 41 | self.touch_coordinate = None 42 | self.name = name 43 | 44 | def __repr__(self): 45 | return "%s: (%d, %d)" % (self.name, 46 | self.touch_coordinate.x, 47 | self.touch_coordinate.y) 48 | 49 | # Calibration helper class 50 | 51 | class Tpcal(): 52 | 53 | # Create a screen with a button and a label 54 | 55 | CIRCLE_SIZE = const(20) 56 | CIRCLE_OFFSET = const(20) 57 | TP_MAX_VALUE = const(10000) 58 | 59 | LV_COORD_MAX = const((1 << (8 * 2 - 1)) - 1000) 60 | LV_RADIUS_CIRCLE = const(LV_COORD_MAX) # TODO use lv.RADIUS_CIRCLE constant when it's available! 61 | 62 | def __init__(self, points, calibrate, touch_count = 5): 63 | 64 | self.points = points 65 | self.calibrate = calibrate 66 | self.touch_count = touch_count 67 | 68 | self.med = [lv.point_t() for i in range(0,self.touch_count)] # Storage point to calculate median 69 | 70 | self.cur_point = 0 71 | self.cur_touch = 0 72 | 73 | self.scr = lv.obj(None) 74 | self.scr.set_size(TP_MAX_VALUE, TP_MAX_VALUE) 75 | lv.scr_load(self.scr) 76 | 77 | # Create a big transparent button screen to receive clicks 78 | style_transp = lv.style_t() 79 | style_transp.init() 80 | style_transp.set_bg_opa(lv.OPA.TRANSP) 81 | self.big_btn = lv.button(lv.scr_act()) 82 | self.big_btn.set_size(TP_MAX_VALUE, TP_MAX_VALUE) 83 | self.big_btn.add_style(style_transp, lv.PART.MAIN) 84 | self.big_btn.add_style(style_transp, lv.PART.MAIN) 85 | #self.big_btn.set_layout(lv.LAYOUT.OFF) 86 | self.big_btn.add_event(lambda event, self=self: self.calibrate_clicked(),lv.EVENT.CLICKED, None) 87 | 88 | # Create the screen, circle and label 89 | 90 | self.label_main = lv.label(lv.scr_act()) 91 | 92 | style_circ = lv.style_t() 93 | style_circ.init() 94 | style_circ.set_radius(LV_RADIUS_CIRCLE) 95 | 96 | self.circ_area = lv.obj(lv.scr_act()) 97 | self.circ_area.set_size(CIRCLE_SIZE, CIRCLE_SIZE) 98 | self.circ_area.add_style(style_circ, lv.STATE.DEFAULT) 99 | self.circ_area.clear_flag(lv.obj.FLAG.CLICKABLE) # self.circ_area.set_click(False) 100 | 101 | self.show_circle() 102 | 103 | def show_text(self, txt): 104 | self.label_main.set_text(txt) 105 | # self.label_main.set_align(lv.label.ALIGN.CENTER) 106 | self.label_main.set_pos((HRES - self.label_main.get_width() ) // 2, 107 | (VRES - self.label_main.get_height()) // 2) 108 | def show_circle(self): 109 | point = self.points[self.cur_point] 110 | self.show_text("Click the circle in\n" + \ 111 | point.name + "\n" + \ 112 | "%d left" % (self.touch_count - self.cur_touch)) 113 | if point.display_coordinates.x < 0: point.display_coordinates.x += HRES 114 | if point.display_coordinates.y < 0: point.display_coordinates.y += VRES 115 | # print("Circle coordinates: x: %d, y: %d"%(point.display_coordinates.x - self.CIRCLE_SIZE // 2, 116 | # point.display_coordinates.y - self.CIRCLE_SIZE // 2)) 117 | self.circ_area.set_pos(point.display_coordinates.x - CIRCLE_SIZE // 2, 118 | point.display_coordinates.y - CIRCLE_SIZE // 2) 119 | 120 | def calibrate_clicked(self): 121 | point = self.points[self.cur_point] 122 | indev = lv.indev_get_act() 123 | indev.get_point(self.med[self.cur_touch]) 124 | # print("calibrate_clicked: x: %d, y: %d"%(self.med[self.cur_touch].x,self.med[self.cur_touch].y)) 125 | self.cur_touch += 1 126 | 127 | if self.cur_touch == self.touch_count: 128 | med_x = sorted([med.x for med in self.med]) 129 | med_y = sorted([med.y for med in self.med]) 130 | x = med_x[len(med_x) // 2] 131 | y = med_y[len(med_y) // 2] 132 | point.touch_coordinate = lv.point_t({ 133 | 'x': x, 134 | 'y': y}) 135 | 136 | self.cur_point += 1 137 | self.cur_touch = 0 138 | 139 | if self.cur_point == len(self.points): 140 | self.calibrate(self.points) 141 | self.cur_point = 0 142 | self.show_text("Click/drag on screen\n" + \ 143 | "to check calibration") 144 | self.big_btn.set_event_cb(lambda event, self=self: self.check(), lv.EVENT.PRESSING, None) 145 | else: 146 | self.show_circle() 147 | 148 | def check(self): 149 | point = lv.point_t() 150 | indev = lv.indev_get_act() 151 | indev.get_point(point) 152 | # print("click position: x: %d, y: %d"%(point.x,point.y)) 153 | self.circ_area.set_pos(point.x - CIRCLE_SIZE // 2, 154 | point.y - CIRCLE_SIZE // 2) 155 | 156 | # Calculate calibration, and calibrate 157 | 158 | def calibrate(points): 159 | visual_width = points[1].display_coordinates.x - points[0].display_coordinates.x 160 | visual_height = points[1].display_coordinates.y - points[0].display_coordinates.y 161 | touch_width = points[1].touch_coordinate.x - points[0].touch_coordinate.x 162 | touch_height = points[1].touch_coordinate.y - points[0].touch_coordinate.y 163 | 164 | pixel_width = touch_width / visual_width 165 | pixel_height = touch_height / visual_height 166 | 167 | x0 = points[0].touch_coordinate.x - points[0].display_coordinates.x * pixel_width 168 | y0 = points[0].touch_coordinate.y - points[0].display_coordinates.y * pixel_height 169 | 170 | x1 = points[1].touch_coordinate.x + (HRES - points[1].display_coordinates.x) * pixel_width 171 | y1 = points[1].touch_coordinate.y + (VRES - points[1].display_coordinates.y) * pixel_height 172 | 173 | print("Calibration result: x0=%d, y0=%d, x1=%d, y1=%d" % (round(x0), round(y0), round(x1), round(y1))) 174 | touch.calibrate(round(x0), round(y0), round(x1), round(y1)) 175 | 176 | # Run calibration 177 | 178 | tpcal = Tpcal([ 179 | Tpcal_point(20, 20, "upper left-hand corner"), 180 | Tpcal_point(-40, -40, "lower right-hand corner"), 181 | ], calibrate) 182 | 183 | # while True: 184 | # pass 185 | -------------------------------------------------------------------------------- /lib/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import lvgl as lv 3 | import ustruct 4 | import uctypes 5 | 6 | # Calculate pointer size on current machine, and corresponding fmt 7 | 8 | ptr_size = uctypes.sizeof({'p': (uctypes.PTR, uctypes.VOID)}) 9 | fmt_options = {2:'H', 4:'L', 8:'Q'} 10 | buf_fmt = fmt_options[ptr_size] if ptr_size in fmt_options else None 11 | 12 | 13 | def aligned_buf(buf, alignment): 14 | 15 | """Return an aligned buffer 16 | 17 | Given a buffer, return a memory view within that buffer, which starts 18 | at an aligned address in RAM. 19 | The returned memory view is possibly smaller. 20 | 21 | !! You must keep a reference to the original buffer to prevent the 22 | garbage collector from collecting the aligned view! 23 | 24 | Arguments: 25 | 26 | buf -- An object that implements buffer protocol 27 | alignment -- Integer value 28 | 29 | """ 30 | 31 | p = lv.C_Pointer() 32 | p.ptr_val = buf 33 | if not buf_fmt: return None 34 | addr = ustruct.unpack(buf_fmt, p.ptr_val)[0] 35 | mod = addr % alignment 36 | offset = alignment - mod if mod != 0 else 0 37 | if len(buf) <= offset: return None 38 | addr += offset 39 | p = lv.C_Pointer.__cast__(ustruct.pack(buf_fmt, addr)) 40 | return p.ptr_val.__dereference__(len(buf) - offset) 41 | 42 | -------------------------------------------------------------------------------- /micropython.cmake: -------------------------------------------------------------------------------- 1 | 2 | # This file is to be given as "make USER_C_MODULES=..." when building Micropython port 3 | 4 | # Include LVGL component, ignore KCONFIG 5 | separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS}) 6 | 7 | if(ESP_PLATFORM) 8 | idf_build_set_property(LV_MICROPYTHON 1) 9 | idf_build_component(${CMAKE_CURRENT_LIST_DIR}/lvgl) 10 | idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND) 11 | idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND) 12 | idf_build_set_property(COMPILE_OPTIONS "-Wno-unused-function" APPEND) 13 | idf_build_set_property(SRCS "${LV_SRC}" APPEND) 14 | idf_build_set_property(INCLUDE_DIRS "${LV_INCLUDE}" APPEND) 15 | 16 | # Fix for idf 5.2.x 17 | idf_build_get_property(component_targets __COMPONENT_TARGETS) 18 | string(REPLACE "___idf_lvgl" "" component_targets "${component_targets}") 19 | idf_build_set_property(__COMPONENT_TARGETS "${component_targets}") 20 | endif(ESP_PLATFORM) 21 | 22 | # DEBUG LV_CONF_PATH 23 | message(STATUS "LV_CONF_PATH=${LV_CONF_PATH}") 24 | 25 | include(${CMAKE_CURRENT_LIST_DIR}/mkrules_usermod.cmake) 26 | 27 | # Add lv_bindings rules 28 | 29 | all_lv_bindings() 30 | 31 | 32 | # # # make usermod (target declared by Micropython for all user compiled modules) link to bindings 33 | # # # this way the bindings (and transitively lvgl_interface) get proper compilation flags 34 | # target_link_libraries(usermod INTERFACE usermod_lvgl) 35 | 36 | file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/src/*.c) 37 | 38 | add_library(lvgl_interface INTERFACE) 39 | 40 | target_sources(lvgl_interface INTERFACE ${SOURCES}) 41 | target_compile_options(lvgl_interface INTERFACE ${LV_CFLAGS}) 42 | 43 | # # lvgl bindings target (the mpy module) 44 | 45 | add_library(usermod_lvgl INTERFACE) 46 | target_sources(usermod_lvgl INTERFACE ${LV_SRC}) 47 | target_include_directories(usermod_lvgl INTERFACE ${LV_INCLUDE}) 48 | if (DEFINED LV_CONF_DIR) 49 | target_include_directories(usermod_lvgl INTERFACE ${LV_CONF_DIR}) 50 | endif() 51 | 52 | file(WRITE ${LV_MP} "") 53 | 54 | target_link_libraries(usermod_lvgl INTERFACE lvgl_interface) 55 | 56 | # # # make usermod (target declared by Micropython for all user compiled modules) link to bindings 57 | # # # this way the bindings (and transitively lvgl_interface) get proper compilation flags 58 | if (DEFINED LV_CONF_DIR) 59 | target_include_directories(usermod INTERFACE ${LV_CONF_DIR}) 60 | endif() 61 | target_compile_options(usermod INTERFACE -DLV_CONF_PATH="${LV_CONF_PATH}") 62 | target_link_libraries(usermod INTERFACE usermod_lvgl) 63 | 64 | -------------------------------------------------------------------------------- /micropython.mk: -------------------------------------------------------------------------------- 1 | 2 | ################################################################################ 3 | # LVGL unix optional libraries 4 | # Update CFLAGS_USERMOD and LDFLAGS_USERMOD for LVGL extenral library, 5 | # but do that only on the unix port, for unix specific dependencies 6 | ifeq ($(notdir $(CURDIR)),unix) 7 | ifneq ($(UNAME_S),Darwin) 8 | CFLAGS_USERMOD += -DMICROPY_FB=1 9 | endif 10 | 11 | SDL_CFLAGS_USERMOD := $(shell pkg-config --silence-errors --cflags sdl2) 12 | SDL_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs sdl2) 13 | ifneq ($(SDL_LDFLAGS_USERMOD),) 14 | CFLAGS_USERMOD += $(SDL_CFLAGS_USERMOD) -DMICROPY_SDL=1 15 | LDFLAGS_USERMOD += $(SDL_LDFLAGS_USERMOD) 16 | endif 17 | 18 | # Avoid including unwanted local headers other than sdl2 19 | ifeq ($(UNAME_S),Darwin) 20 | CFLAGS_USERMOD:=$(filter-out -I/usr/local/include,$(CFLAGS_USERMOD)) 21 | endif 22 | 23 | RLOTTIE_CFLAGS_USERMOD := $(shell pkg-config --silence-errors --cflags rlottie) 24 | RLOTTIE_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs rlottie) 25 | ifneq ($(RLOTTIE_LDFLAGS_USERMOD),) 26 | CFLAGS_USERMOD += $(RLOTTIE_CFLAGS_USERMOD) -DMICROPY_RLOTTIE=1 27 | LDFLAGS_USERMOD += $(RLOTTIE_LDFLAGS_USERMOD) 28 | endif 29 | 30 | FREETYPE_CFLAGS_USERMOD := $(shell pkg-config --silence-errors --cflags freetype2) 31 | FREETYPE_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs freetype2) 32 | ifneq ($(FREETYPE_LDFLAGS_USERMOD),) 33 | CFLAGS_USERMOD += $(FREETYPE_CFLAGS_USERMOD) -DMICROPY_FREETYPE=1 34 | LDFLAGS_USERMOD += $(FREETYPE_LDFLAGS_USERMOD) 35 | endif 36 | 37 | # Enable FFMPEG 38 | # FFMPEG_LIBS := libavformat libavcodec libswscale libavutil 39 | # FFMPEG_CFLAGS_USERMOD := $(shell pkg-config --silence-errors --cflags $(FFMPEG_LIBS)) 40 | # FFMPEG_LDFLAGS_USERMOD := $(shell pkg-config --silence-errors --libs $(FFMPEG_LIBS)) 41 | # ifneq ($(FFMPEG_LDFLAGS_USERMOD),) 42 | # CFLAGS_USERMOD += $(FFMPEG_CFLAGS_USERMOD) -DMICROPY_FFMPEG=1 43 | # LDFLAGS_USERMOD += $(FFMPEG_LDFLAGS_USERMOD) 44 | # endif 45 | 46 | endif 47 | 48 | ################################################################################ 49 | 50 | # LVGL build rules 51 | 52 | 53 | LVGL_BINDING_DIR := $(USERMOD_DIR) 54 | ifeq ($(LV_CONF_PATH),) 55 | LV_CONF_PATH = $(LVGL_BINDING_DIR)/lv_conf.h 56 | endif 57 | 58 | # LV_CONF_PATH DEBUG 59 | $(info LV_CONF_PATH is $(LV_CONF_PATH)) 60 | 61 | 62 | LVGL_DIR = $(LVGL_BINDING_DIR)/lvgl 63 | LVGL_GENERIC_DRV_DIR = $(LVGL_BINDING_DIR)/driver/generic 64 | INC += -I$(LVGL_BINDING_DIR) 65 | ALL_LVGL_SRC = $(shell find $(LVGL_DIR) -type f -name '*.h') $(LV_CONF_PATH) 66 | LVGL_PP = $(BUILD)/lvgl/lvgl.pp.c 67 | LVGL_MPY = $(BUILD)/lvgl/lv_mpy.c 68 | LVGL_MPY_METADATA = $(BUILD)/lvgl/lv_mpy.json 69 | CFLAGS_USERMOD += $(LV_CFLAGS) 70 | 71 | # MAKE SURE LV_CONF_PATH is a STRING 72 | CFLAGS_USERMOD += -DLV_CONF_PATH='"$(LV_CONF_PATH)"' 73 | # CFLAGS_USERMOD += -DLV_CONF_PATH=$(LV_CONF_PATH) 74 | 75 | 76 | # CFLAGS DEBUG 77 | $(info CFLAGS_USERMOD is $(CFLAGS_USERMOD)) 78 | 79 | $(LVGL_MPY): $(ALL_LVGL_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py 80 | $(ECHO) "LVGL-GEN $@" 81 | $(Q)mkdir -p $(dir $@) 82 | $(Q)$(CPP) $(CFLAGS_USERMOD) -DPYCPARSER -x c -I $(LVGL_BINDING_DIR)/pycparser/utils/fake_libc_include $(INC) $(LVGL_DIR)/lvgl.h > $(LVGL_PP) 83 | $(Q)$(PYTHON) $(LVGL_BINDING_DIR)/gen/gen_mpy.py -M lvgl -MP lv -MD $(LVGL_MPY_METADATA) -E $(LVGL_PP) $(LVGL_DIR)/lvgl.h > $@ 84 | 85 | .PHONY: LVGL_MPY 86 | LVGL_MPY: $(LVGL_MPY) 87 | 88 | CFLAGS_USERMOD += -Wno-unused-function 89 | CFLAGS_EXTRA += -Wno-unused-function 90 | SRC_USERMOD_LIB_C += $(subst $(TOP)/,,$(shell find $(LVGL_DIR)/src $(LVGL_DIR)/examples $(LVGL_GENERIC_DRV_DIR) -type f -name "*.c")) 91 | SRC_USERMOD_C += $(LVGL_MPY) 92 | -------------------------------------------------------------------------------- /mkrules.cmake: -------------------------------------------------------------------------------- 1 | 2 | find_package(Python3 REQUIRED COMPONENTS Interpreter) 3 | find_program(AWK awk mawk gawk) 4 | 5 | set(LV_BINDINGS_DIR ${CMAKE_CURRENT_LIST_DIR}) 6 | 7 | # Common function for creating LV bindings 8 | 9 | function(lv_bindings) 10 | set(_options) 11 | set(_one_value_args OUTPUT) 12 | set(_multi_value_args INPUT DEPENDS COMPILE_OPTIONS PP_OPTIONS GEN_OPTIONS FILTER) 13 | cmake_parse_arguments( 14 | PARSE_ARGV 0 LV 15 | "${_options}" 16 | "${_one_value_args}" 17 | "${_multi_value_args}" 18 | ) 19 | 20 | set(LV_PP ${LV_OUTPUT}.pp) 21 | set(LV_MPY_METADATA ${LV_OUTPUT}.json) 22 | 23 | add_custom_command( 24 | OUTPUT 25 | ${LV_PP} 26 | COMMAND 27 | ${CMAKE_C_COMPILER} -E -DPYCPARSER ${LV_COMPILE_OPTIONS} ${LV_PP_OPTIONS} "${LV_CFLAGS}" -I ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include ${MICROPY_CPP_FLAGS} ${LV_INPUT} > ${LV_PP} 28 | DEPENDS 29 | ${LV_INPUT} 30 | ${LV_DEPENDS} 31 | ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include 32 | IMPLICIT_DEPENDS 33 | C ${LV_INPUT} 34 | VERBATIM 35 | COMMAND_EXPAND_LISTS 36 | ) 37 | 38 | if(ESP_PLATFORM) 39 | target_compile_options(${COMPONENT_LIB} PRIVATE ${LV_COMPILE_OPTIONS}) 40 | else() 41 | target_compile_options(usermod_lv_bindings INTERFACE ${LV_COMPILE_OPTIONS}) 42 | endif() 43 | 44 | if (DEFINED LV_FILTER) 45 | 46 | set(LV_PP_FILTERED ${LV_PP}.filtered) 47 | set(LV_AWK_CONDITION) 48 | foreach(_f ${LV_FILTER}) 49 | string(APPEND LV_AWK_CONDITION "\$3!~\"${_f}\" && ") 50 | endforeach() 51 | string(APPEND LV_AWK_COMMAND "\$1==\"#\"{p=(${LV_AWK_CONDITION} 1)} p{print}") 52 | 53 | # message("AWK COMMAND: ${LV_AWK_COMMAND}") 54 | 55 | add_custom_command( 56 | OUTPUT 57 | ${LV_PP_FILTERED} 58 | COMMAND 59 | ${AWK} ${LV_AWK_COMMAND} ${LV_PP} > ${LV_PP_FILTERED} 60 | DEPENDS 61 | ${LV_PP} 62 | VERBATIM 63 | COMMAND_EXPAND_LISTS 64 | ) 65 | else() 66 | set(LV_PP_FILTERED ${LV_PP}) 67 | endif() 68 | 69 | set(LV_JSON ${CMAKE_BINARY_DIR}/lvgl_all.json) 70 | 71 | if (EXISTS ${LVGL_DIR}/scripts/gen_json/gen_json.py) 72 | set(LVGL_ALL_H ${CMAKE_BINARY_DIR}/lvgl_all.h) 73 | add_custom_command( 74 | OUTPUT 75 | ${LVGL_ALL_H} 76 | COMMAND 77 | echo "\"#include\"" "\"\\\"${LVGL_DIR}/lvgl.h\\\"\"" > ${LVGL_ALL_H} 78 | COMMAND 79 | echo "\"#include\"" "\"\\\"${LVGL_DIR}/src/lvgl_private.h\\\"\"" >> ${LVGL_ALL_H} 80 | COMMAND_EXPAND_LISTS 81 | ) 82 | add_custom_command( 83 | OUTPUT 84 | ${LV_JSON} 85 | COMMAND 86 | ${Python3_EXECUTABLE} ${LVGL_DIR}/scripts/gen_json/gen_json.py --target-header ${LVGL_ALL_H} > ${LV_JSON} 87 | DEPENDS 88 | ${LVGL_DIR}/scripts/gen_json/gen_json.py 89 | ${LVGL_ALL_H} 90 | COMMAND_EXPAND_LISTS 91 | ) 92 | else() 93 | add_custom_command( 94 | OUTPUT 95 | ${LV_JSON} 96 | COMMAND 97 | echo "{}" > ${LV_JSON} 98 | COMMAND_EXPAND_LISTS 99 | ) 100 | endif() 101 | 102 | add_custom_command( 103 | OUTPUT 104 | ${LV_OUTPUT} 105 | COMMAND 106 | ${Python3_EXECUTABLE} ${LV_BINDINGS_DIR}/gen/gen_mpy.py ${LV_GEN_OPTIONS} -MD ${LV_MPY_METADATA} -E ${LV_PP_FILTERED} -J ${LV_JSON} ${LV_INPUT} > ${LV_OUTPUT} || (rm -f ${LV_OUTPUT} && /bin/false) 107 | DEPENDS 108 | ${LV_BINDINGS_DIR}/gen/gen_mpy.py 109 | ${LV_PP_FILTERED} 110 | ${LV_JSON} 111 | COMMAND_EXPAND_LISTS 112 | ) 113 | 114 | endfunction() 115 | 116 | # Definitions for specific bindings 117 | 118 | set(LVGL_DIR ${LV_BINDINGS_DIR}/lvgl) 119 | 120 | set(LV_MP ${CMAKE_BINARY_DIR}/lv_mp.c) 121 | if(ESP_PLATFORM) 122 | set(LV_ESPIDF ${CMAKE_BINARY_DIR}/lv_espidf.c) 123 | endif() 124 | 125 | # Function for creating all specific bindings 126 | 127 | function(all_lv_bindings) 128 | 129 | # LVGL bindings 130 | 131 | file(GLOB_RECURSE LVGL_HEADERS ${LVGL_DIR}/src/*.h ${LV_BINDINGS_DIR}/lv_conf.h) 132 | lv_bindings( 133 | OUTPUT 134 | ${LV_MP} 135 | INPUT 136 | ${LVGL_DIR}/lvgl.h 137 | DEPENDS 138 | ${LVGL_HEADERS} 139 | GEN_OPTIONS 140 | -M lvgl -MP lv 141 | ) 142 | 143 | # ESPIDF bindings 144 | 145 | if(ESP_PLATFORM) 146 | file(GLOB_RECURSE LV_ESPIDF_HEADERS ${IDF_PATH}/components/*.h ${LV_BINDINGS_DIR}/driver/esp32/*.h) 147 | lv_bindings( 148 | OUTPUT 149 | ${LV_ESPIDF} 150 | INPUT 151 | ${LV_BINDINGS_DIR}/driver/esp32/espidf.h 152 | DEPENDS 153 | ${LV_ESPIDF_HEADERS} 154 | GEN_OPTIONS 155 | -M espidf 156 | FILTER 157 | i2s_ll.h 158 | i2s_hal.h 159 | esp_intr_alloc.h 160 | soc/spi_periph.h 161 | rom/ets_sys.h 162 | soc/sens_struct.h 163 | soc/rtc.h 164 | driver/periph_ctrl.h 165 | ) 166 | endif(ESP_PLATFORM) 167 | 168 | endfunction() 169 | 170 | # Add includes to CMake component 171 | 172 | set(LV_INCLUDE 173 | ${LV_BINDINGS_DIR} 174 | ) 175 | 176 | # Add sources to CMake component 177 | 178 | set(LV_SRC 179 | ${LV_MP} 180 | ) 181 | 182 | if(ESP_PLATFORM) 183 | LIST(APPEND LV_SRC 184 | ${LV_BINDINGS_DIR}/driver/esp32/espidf.c 185 | ${LV_BINDINGS_DIR}/driver/esp32/modrtch.c 186 | ${LV_BINDINGS_DIR}/driver/esp32/sh2lib.c 187 | ${LV_ESPIDF} 188 | ) 189 | endif(ESP_PLATFORM) 190 | -------------------------------------------------------------------------------- /mkrules_usermod.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | find_package(Python3 REQUIRED COMPONENTS Interpreter) 4 | find_program(AWK awk mawk gawk) 5 | 6 | set(LV_BINDINGS_DIR ${CMAKE_CURRENT_LIST_DIR}) 7 | 8 | # first check 9 | if(NOT DEFINED LV_CONF_PATH) 10 | set(LV_CONF_PATH ${LV_BINDINGS_DIR}/lv_conf.h) 11 | 12 | message(STATUS "LV_CONF_PATH=${LV_CONF_PATH}") 13 | endif() 14 | 15 | # Common function for creating LV bindings 16 | 17 | function(lv_bindings) 18 | set(_options) 19 | set(_one_value_args OUTPUT) 20 | set(_multi_value_args INPUT DEPENDS COMPILE_OPTIONS PP_OPTIONS GEN_OPTIONS FILTER) 21 | cmake_parse_arguments( 22 | PARSE_ARGV 0 LV 23 | "${_options}" 24 | "${_one_value_args}" 25 | "${_multi_value_args}" 26 | ) 27 | 28 | set(LV_PP ${LV_OUTPUT}.pp) 29 | set(LV_MPY_METADATA ${LV_OUTPUT}.json) 30 | 31 | # message(STATUS "LV_CONF_PATH=${LV_CONF_PATH}") 32 | # message(STATUS "LV_COMPILE_OPTIONS=${LV_COMPILE_OPTIONS}") 33 | 34 | # message(STATUS "LV_PP_OPTIONS=${LV_PP_OPTIONS}") 35 | # message(STATUS "LV_CFLAGS=${LV_CFLAGS}") 36 | 37 | add_custom_command( 38 | OUTPUT 39 | ${LV_PP} 40 | COMMAND 41 | ${CMAKE_C_COMPILER} -E -DPYCPARSER -DLV_CONF_PATH="${LV_CONF_PATH}" ${LV_COMPILE_OPTIONS} ${LV_PP_OPTIONS} "${LV_CFLAGS}" -I ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include ${MICROPY_CPP_FLAGS} ${LV_INPUT} > ${LV_PP} 42 | DEPENDS 43 | ${LV_INPUT} 44 | ${LV_DEPENDS} 45 | ${LV_BINDINGS_DIR}/pycparser/utils/fake_libc_include 46 | IMPLICIT_DEPENDS 47 | C ${LV_INPUT} 48 | COMMENT 49 | "LV_BINDINGS CPP" 50 | VERBATIM 51 | COMMAND_EXPAND_LISTS 52 | ) 53 | 54 | # if(ESP_PLATFORM) 55 | # target_compile_options(${COMPONENT_LIB} PRIVATE ${LV_COMPILE_OPTIONS}) 56 | # else() 57 | # target_compile_options(usermod_lv_bindings INTERFACE ${LV_COMPILE_OPTIONS}) 58 | # endif() 59 | 60 | if (DEFINED LV_FILTER) 61 | 62 | set(LV_PP_FILTERED ${LV_PP}.filtered) 63 | set(LV_AWK_CONDITION) 64 | foreach(_f ${LV_FILTER}) 65 | string(APPEND LV_AWK_CONDITION "\$3!~\"${_f}\" && ") 66 | endforeach() 67 | string(APPEND LV_AWK_COMMAND "\$1==\"#\"{p=(${LV_AWK_CONDITION} 1)} p{print}") 68 | 69 | # message("AWK COMMAND: ${LV_AWK_COMMAND}") 70 | 71 | add_custom_command( 72 | OUTPUT 73 | ${LV_PP_FILTERED} 74 | COMMAND 75 | ${AWK} ${LV_AWK_COMMAND} ${LV_PP} > ${LV_PP_FILTERED} 76 | DEPENDS 77 | ${LV_PP} 78 | 79 | COMMENT 80 | "LV_BINDINGS: FILTER" 81 | VERBATIM 82 | COMMAND_EXPAND_LISTS 83 | ) 84 | else() 85 | set(LV_PP_FILTERED ${LV_PP}) 86 | endif() 87 | 88 | add_custom_command( 89 | OUTPUT 90 | ${LV_OUTPUT} 91 | COMMAND 92 | ${Python3_EXECUTABLE} ${LV_BINDINGS_DIR}/gen/gen_mpy.py ${LV_GEN_OPTIONS} -DLV_CONF_PATH="${LV_CONF_PATH}" -MD ${LV_MPY_METADATA} -E ${LV_PP_FILTERED} ${LV_INPUT} > ${LV_OUTPUT} || (rm -f ${LV_OUTPUT} && /bin/false) 93 | DEPENDS 94 | ${LV_BINDINGS_DIR}/gen/gen_mpy.py 95 | ${LV_PP_FILTERED} 96 | 97 | COMMENT 98 | "LV_BINDINGS GEN MPY" 99 | COMMAND_EXPAND_LISTS 100 | ) 101 | 102 | endfunction() 103 | 104 | # Definitions for specific bindings 105 | 106 | set(LVGL_DIR ${LV_BINDINGS_DIR}/lvgl) 107 | 108 | set(LV_MP ${CMAKE_BINARY_DIR}/lv_mp.c) 109 | # if(ESP_PLATFORM) 110 | # set(LV_ESPIDF ${CMAKE_BINARY_DIR}/lv_espidf.c) 111 | # endif() 112 | 113 | # Function for creating all specific bindings 114 | 115 | function(all_lv_bindings) 116 | 117 | # LVGL bindings 118 | 119 | file(GLOB_RECURSE LVGL_HEADERS ${LVGL_DIR}/src/*.h ${LV_CONF_PATH}) 120 | lv_bindings( 121 | OUTPUT 122 | ${LV_MP} 123 | INPUT 124 | ${LVGL_DIR}/lvgl.h 125 | DEPENDS 126 | ${LVGL_HEADERS} 127 | GEN_OPTIONS 128 | -M lvgl -MP lv 129 | ) 130 | 131 | 132 | # ESPIDF bindings 133 | 134 | # if(ESP_PLATFORM) 135 | # file(GLOB_RECURSE LV_ESPIDF_HEADERS ${IDF_PATH}/components/*.h ${LV_BINDINGS_DIR}/driver/esp32/*.h) 136 | # lv_bindings( 137 | # OUTPUT 138 | # ${LV_ESPIDF} 139 | # INPUT 140 | # ${LV_BINDINGS_DIR}/driver/esp32/espidf.h 141 | # DEPENDS 142 | # ${LV_ESPIDF_HEADERS} 143 | # GEN_OPTIONS 144 | # -M espidf 145 | # FILTER 146 | # i2s_ll.h 147 | # i2s_hal.h 148 | # esp_intr_alloc.h 149 | # soc/spi_periph.h 150 | # rom/ets_sys.h 151 | # soc/sens_struct.h 152 | # soc/rtc.h 153 | # driver/periph_ctrl.h 154 | # ) 155 | # endif(ESP_PLATFORM) 156 | 157 | endfunction() 158 | 159 | # Add includes to CMake component 160 | 161 | set(LV_INCLUDE 162 | ${LV_BINDINGS_DIR} 163 | ) 164 | 165 | # Add sources to CMake component 166 | 167 | set(LV_SRC 168 | ${LV_MP} 169 | ) 170 | 171 | # if(ESP_PLATFORM) 172 | # LIST(APPEND LV_SRC 173 | # ${LV_BINDINGS_DIR}/driver/esp32/espidf.c 174 | # ${LV_BINDINGS_DIR}/driver/esp32/modrtch.c 175 | # ${LV_BINDINGS_DIR}/driver/esp32/sh2lib.c 176 | # ${LV_ESPIDF} 177 | # ) 178 | # endif(ESP_PLATFORM) 179 | 180 | -------------------------------------------------------------------------------- /ports/esp32/manifest.py: -------------------------------------------------------------------------------- 1 | module("lv_utils.py", base_path="../../lib") 2 | module("fs_driver.py", base_path="../../lib") -------------------------------------------------------------------------------- /ports/stm32/manifest.py: -------------------------------------------------------------------------------- 1 | module("lv_utils.py", base_path="../../lib") 2 | -------------------------------------------------------------------------------- /ports/unix/manifest.py: -------------------------------------------------------------------------------- 1 | module("evdev.py", base_path="../../driver/linux") 2 | module("lv_timer.py", base_path="../../driver/linux") 3 | module("display_driver_utils.py", base_path="../../lib") 4 | module("display_driver.py", base_path="../../lib") 5 | module("fs_driver.py", base_path="../../lib") 6 | module("lv_utils.py", base_path="../../lib") 7 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Tests: 3 | 4 | - `api/`: These tests are to test MicroPython-LVGL bindings. They can be 5 | automated/included in CI. 6 | To run from `micropython/tests`: 7 | ``` 8 | ./run-tests.py ../../user_modules/lv_binding_micropython/tests/api/basic*.py -r . 9 | ``` 10 | 11 | - `display/`: These are to test the `api` + display driver. Intended for HIL 12 | (Hardware in the loop) testing. Display only, no touch interface, touch is 13 | automated and simulated in software. 14 | To run from `micropython/tests`: 15 | ``` 16 | ./run-tests.py ../../user_modules/lv_binding_micropython/tests/display/basic*.py -r . 17 | ``` 18 | e.g. in unix port a display will appear to provide visual feedback. 19 | 20 | 21 | - `indev/`: These are to test the `display` + indev (touch) driver. Intended for 22 | interactive HIL testing, e.g. they expect user input to complete the test. 23 | 24 | To run from `micropython/tests`: 25 | ``` 26 | ./run-tests.py ../../user_modules/lv_binding_micropython/tests/indev/basic*.py -r . 27 | ``` 28 | e.g. in unix port a display will appear to allow user input. 29 | 30 | All tests are intended/expected to be run both in desktop (unix port) and in devices with the same result. 31 | 32 | For devices `testrunner.py`, `testdisplay.py` and `display_config.py` need to be 33 | uploaded. Also for display/indev testing a `hwdisplay.py` with a display driver 34 | called `display` is expected. This `display` driver is expected to have at least a 35 | ```py 36 | 37 | def blit(self, x1, y1, w, h, buff): 38 | ``` 39 | method or handle the lv display setup by itself (e.g setting buffers, `flush_cb`, etc) 40 | 41 | For interactive indev tests, it is required to have a 42 | ```py 43 | 44 | def read_cb(self, indev, data): 45 | ``` 46 | method too, or handle indev creation by itself. 47 | -------------------------------------------------------------------------------- /tests/api/basic.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | async def demo(scr, display=None): 15 | def get_button(scr, text, align, color): 16 | _btn = lv.button(scr) 17 | _btn.set_size(lv.pct(25), lv.pct(10)) 18 | _lab = lv.label(_btn) 19 | _lab.set_text(text) 20 | _lab.center() 21 | _btn.set_style_align(align, 0) 22 | _btn.set_style_bg_color(lv.color_make(*color), 0) 23 | return _btn, text 24 | 25 | buttons = [ 26 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 27 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 28 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 29 | ] 30 | 31 | def button_cb(event, name): 32 | print(f"{name} PRESSED") 33 | 34 | _all_btns = [get_button(scr, *btn) for btn in buttons] 35 | 36 | for btn, name in _all_btns: 37 | btn.add_event_cb( 38 | lambda event, button_name=name: button_cb(event, button_name), 39 | lv.EVENT.CLICKED, 40 | None, 41 | ) 42 | 43 | await asyncio.sleep_ms(500) # await so the frame can be rendered 44 | print("EVENT TEST:") 45 | for _btn, name in _all_btns: 46 | _btn.send_event(lv.EVENT.CLICKED, None) 47 | await asyncio.sleep_ms(200) 48 | 49 | return _all_btns 50 | 51 | 52 | __file__ = globals().get("__file__", "test") 53 | 54 | try: 55 | import display_config 56 | 57 | display_config.MODE = "sim" 58 | display_config.POINTER = "sim" 59 | except Exception: 60 | display_config = testrunner.display_config 61 | 62 | testrunner.run(demo, __file__, disp_config=display_config) 63 | testrunner.devicereset() 64 | -------------------------------------------------------------------------------- /tests/api/basic.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: SIM 2 | INDEV_MODE: SIM 3 | 4 | FRAME: 0 (0, 0, 240, 32, 23040) 5 | d5c5d09cff879bb12cb926dc44bf10161cded58d2057806e7cbde536540b1421 6 | 7 | FRAME: 1 (0, 32, 240, 32, 23040) 8 | f281e1fce42dc013342ad8a4573d74874238d995e6dff46dc29a1d68b780f920 9 | 10 | FRAME: 2 (0, 64, 240, 32, 23040) 11 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 12 | 13 | FRAME: 3 (0, 96, 240, 32, 23040) 14 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 15 | 16 | FRAME: 4 (0, 128, 240, 32, 23040) 17 | 424125778438a53da017c2dca09964ec2cec5ad4e2689038dd0e830125112fd8 18 | 19 | FRAME: 5 (0, 160, 240, 32, 23040) 20 | 9abb7f9219bb7ccc8784119c784b1bf41c451f9957989fd2a9fc12a15606b1d0 21 | 22 | FRAME: 6 (0, 192, 240, 32, 23040) 23 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 24 | 25 | FRAME: 7 (0, 224, 240, 32, 23040) 26 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 27 | 28 | FRAME: 8 (0, 256, 240, 32, 23040) 29 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 30 | 31 | FRAME: 9 (0, 288, 240, 32, 23040) 32 | f546d8ae7340f5fb71e30358ef0d6f33a4f2d72946d9b312444b07fa9d659396 33 | EVENT TEST: 34 | RED PRESSED 35 | GREEN PRESSED 36 | BLUE PRESSED 37 | -------------------------------------------------------------------------------- /tests/api/basic_indev.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | async def demo(scr, display=None): 15 | def get_button(scr, text, align, color): 16 | _btn = lv.button(scr) 17 | _btn.set_size(lv.pct(25), lv.pct(10)) 18 | _lab = lv.label(_btn) 19 | _lab.set_text(text) 20 | _lab.center() 21 | _btn.set_style_align(align, 0) 22 | _btn.set_style_bg_color(lv.color_make(*color), 0) 23 | return _btn, text 24 | 25 | buttons = [ 26 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 27 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 28 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 29 | ] 30 | 31 | def button_cb(event, name): 32 | print(f"{name} PRESSED") 33 | 34 | _all_btns = [get_button(scr, *btn) for btn in buttons] 35 | 36 | for btn, name in _all_btns: 37 | btn.add_event_cb( 38 | lambda event, button_name=name: button_cb(event, button_name), 39 | lv.EVENT.CLICKED, 40 | None, 41 | ) 42 | 43 | await asyncio.sleep_ms(500) # await so the frame can be rendered 44 | print("EVENT TEST:") 45 | for _btn, name in _all_btns: 46 | _btn.send_event(lv.EVENT.CLICKED, None) 47 | await asyncio.sleep_ms(200) 48 | 49 | # simulate touch events 50 | if display: 51 | print("INDEV TEST:") 52 | await display.touch(100, 100) 53 | 54 | await asyncio.sleep_ms(500) 55 | 56 | print("INDEV + BUTTONS TEST:") 57 | # display.debug_indev(press=False, release=False) 58 | display.debug_display(False) 59 | for _btn, name in _all_btns: 60 | pos = _btn.get_x(), _btn.get_y() 61 | await display.touch(*pos) 62 | await asyncio.sleep_ms(1000) 63 | 64 | await asyncio.sleep_ms(500) 65 | 66 | return _all_btns 67 | 68 | 69 | __file__ = globals().get("__file__", "test") 70 | 71 | try: 72 | import display_config 73 | 74 | display_config.MODE = "sim" 75 | display_config.POINTER = "sim" 76 | except Exception: 77 | display_config = testrunner.display_config 78 | 79 | testrunner.run(demo, __file__, disp_config=display_config) 80 | testrunner.devicereset() 81 | -------------------------------------------------------------------------------- /tests/api/basic_indev.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: SIM 2 | INDEV_MODE: SIM 3 | 4 | FRAME: 0 (0, 0, 240, 32, 23040) 5 | d5c5d09cff879bb12cb926dc44bf10161cded58d2057806e7cbde536540b1421 6 | 7 | FRAME: 1 (0, 32, 240, 32, 23040) 8 | f281e1fce42dc013342ad8a4573d74874238d995e6dff46dc29a1d68b780f920 9 | 10 | FRAME: 2 (0, 64, 240, 32, 23040) 11 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 12 | 13 | FRAME: 3 (0, 96, 240, 32, 23040) 14 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 15 | 16 | FRAME: 4 (0, 128, 240, 32, 23040) 17 | 424125778438a53da017c2dca09964ec2cec5ad4e2689038dd0e830125112fd8 18 | 19 | FRAME: 5 (0, 160, 240, 32, 23040) 20 | 9abb7f9219bb7ccc8784119c784b1bf41c451f9957989fd2a9fc12a15606b1d0 21 | 22 | FRAME: 6 (0, 192, 240, 32, 23040) 23 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 24 | 25 | FRAME: 7 (0, 224, 240, 32, 23040) 26 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 27 | 28 | FRAME: 8 (0, 256, 240, 32, 23040) 29 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 30 | 31 | FRAME: 9 (0, 288, 240, 32, 23040) 32 | f546d8ae7340f5fb71e30358ef0d6f33a4f2d72946d9b312444b07fa9d659396 33 | EVENT TEST: 34 | RED PRESSED 35 | GREEN PRESSED 36 | BLUE PRESSED 37 | INDEV TEST: 38 | [PRESSED]: (100,100) 39 | [RELEASED]: (100,100) 40 | INDEV + BUTTONS TEST: 41 | [PRESSED]: (90,0) 42 | RED PRESSED 43 | [RELEASED]: (90,0) 44 | [PRESSED]: (90,288) 45 | GREEN PRESSED 46 | [RELEASED]: (90,288) 47 | [PRESSED]: (90,144) 48 | BLUE PRESSED 49 | [RELEASED]: (90,144) 50 | -------------------------------------------------------------------------------- /tests/api/basic_slider.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner # noqa 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | async def demo(scr, display=None): 15 | def get_button(scr, text, align, color): 16 | scr.set_style_pad_all(10, 0) 17 | _btn = lv.slider(scr) 18 | _btn.set_width(lv.pct(75)) 19 | _btn.set_height(lv.pct(10)) 20 | _lab = lv.label(_btn) 21 | _lab.set_text(text) 22 | _lab.set_style_text_color(lv.color_white(), 0) 23 | _lab.center() 24 | _btn.set_style_align(align, 0) 25 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.INDICATOR) 26 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.MAIN) 27 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.KNOB) 28 | return _btn, text 29 | 30 | buttons = [ 31 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 32 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 33 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 34 | ] 35 | 36 | def button_cb(event, name, slider): 37 | if slider.get_value() == 100: 38 | print(f"{name} VALUE: {slider.get_value()}") 39 | 40 | _all_btns = [get_button(scr, *btn) for btn in buttons] 41 | 42 | for btn, name in _all_btns: 43 | btn.add_event_cb( 44 | lambda event, button_name=name, slider=btn: button_cb( 45 | event, button_name, slider 46 | ), 47 | lv.EVENT.VALUE_CHANGED, 48 | None, 49 | ) 50 | 51 | await asyncio.sleep_ms(500) # await so the frame can be rendered 52 | # simulate touch events 53 | if display: 54 | print("INDEV + SLIDER TEST:") 55 | display.debug_indev(press=False) 56 | display.debug_display(False) 57 | for _btn, name in _all_btns: 58 | pos = _btn.get_x(), _btn.get_y() 59 | pos2 = _btn.get_x2(), _btn.get_y2() 60 | x1, y1 = pos 61 | x2, y2 = pos2 62 | y_mid = y2 - ((y2 - y1) // 2) 63 | await display.swipe(x1 + 5, y_mid, x2 + (y2 - y1), y_mid, ms=500) 64 | await asyncio.sleep_ms(100) 65 | 66 | return _all_btns 67 | 68 | 69 | __file__ = globals().get("__file__", "test") 70 | 71 | try: 72 | import display_config 73 | 74 | display_config.MODE = "sim" 75 | display_config.POINTER = "sim" 76 | except Exception: 77 | display_config = testrunner.display_config 78 | 79 | testrunner.run(demo, __file__, disp_config=display_config) 80 | testrunner.devicereset() 81 | -------------------------------------------------------------------------------- /tests/api/basic_slider.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: SIM 2 | INDEV_MODE: SIM 3 | 4 | FRAME: 0 (0, 0, 240, 32, 23040) 5 | 6e5737038637abc5ea724930a5113dd9193a3e708b13c3be75d2e5164ccfc57a 6 | 7 | FRAME: 1 (0, 32, 240, 32, 23040) 8 | 5c139099d39acc6aa2081459fb397f698035937288fd088b60325f11d8d839a9 9 | 10 | FRAME: 2 (0, 64, 240, 32, 23040) 11 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 12 | 13 | FRAME: 3 (0, 96, 240, 32, 23040) 14 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 15 | 16 | FRAME: 4 (0, 128, 240, 32, 23040) 17 | 85b39d4c5e001bd4aa82e2c0efd390d2f1c20517426ebc07a63a64c8315dcdc4 18 | 19 | FRAME: 5 (0, 160, 240, 32, 23040) 20 | 798e52f4dd160d6e592d0d4d075ef4779eeffed0a4f2339aa9b524e2fe008eae 21 | 22 | FRAME: 6 (0, 192, 240, 32, 23040) 23 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 24 | 25 | FRAME: 7 (0, 224, 240, 32, 23040) 26 | 46e2096b907947368d310929303a04005b39c4a278e3a7de2225c355b4522694 27 | 28 | FRAME: 8 (0, 256, 240, 32, 23040) 29 | a6b9cdacc2013dbb3ce95198217d24ce42333d0178da7fddd15ff353ab012891 30 | 31 | FRAME: 9 (0, 288, 240, 32, 23040) 32 | 08943f10a0eeb2c8a3b8ec437fd2d0725b5a764d29375282553eaafd51ff704a 33 | INDEV + SLIDER TEST: 34 | RED VALUE: 100 35 | [RELEASED]: (218,15) 36 | GREEN VALUE: 100 37 | [RELEASED]: (218,285) 38 | BLUE VALUE: 100 39 | [RELEASED]: (218,150) 40 | -------------------------------------------------------------------------------- /tests/display/basic.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | async def demo(scr, display=None): 15 | def get_button(scr, text, align, color): 16 | _btn = lv.button(scr) 17 | _btn.set_size(lv.pct(25), lv.pct(10)) 18 | _lab = lv.label(_btn) 19 | _lab.set_text(text) 20 | _lab.center() 21 | _btn.set_style_align(align, 0) 22 | _btn.set_style_bg_color(lv.color_make(*color), 0) 23 | return _btn, text 24 | 25 | buttons = [ 26 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 27 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 28 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 29 | ] 30 | 31 | def button_cb(event, name): 32 | print(f"{name} PRESSED") 33 | 34 | _all_btns = [get_button(scr, *btn) for btn in buttons] 35 | 36 | for btn, name in _all_btns: 37 | btn.add_event_cb( 38 | lambda event, button_name=name: button_cb(event, button_name), 39 | lv.EVENT.CLICKED, 40 | None, 41 | ) 42 | 43 | await asyncio.sleep_ms(500) # await so the frame can be rendered 44 | print("EVENT TEST:") 45 | for _btn, name in _all_btns: 46 | _btn.send_event(lv.EVENT.CLICKED, None) 47 | await asyncio.sleep_ms(200) 48 | 49 | return _all_btns 50 | 51 | 52 | __file__ = globals().get("__file__", "test") 53 | 54 | try: 55 | import display_config 56 | 57 | display_config.MODE = "interactive" 58 | display_config.POINTER = "sim" 59 | except Exception: 60 | display_config = testrunner.display_config 61 | 62 | testrunner.run(demo, __file__, disp_config=display_config) 63 | testrunner.devicereset() 64 | -------------------------------------------------------------------------------- /tests/display/basic.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: INTERACTIVE 2 | INDEV_MODE: SIM 3 | EVENT TEST: 4 | RED PRESSED 5 | GREEN PRESSED 6 | BLUE PRESSED 7 | -------------------------------------------------------------------------------- /tests/display/basic_indev.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | async def demo(scr, display=None): 15 | def get_button(scr, text, align, color): 16 | _btn = lv.button(scr) 17 | _btn.set_size(lv.pct(25), lv.pct(10)) 18 | _lab = lv.label(_btn) 19 | _lab.set_text(text) 20 | _lab.center() 21 | _btn.set_style_align(align, 0) 22 | _btn.set_style_bg_color(lv.color_make(*color), 0) 23 | return _btn, text 24 | 25 | buttons = [ 26 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 27 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 28 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 29 | ] 30 | 31 | def button_cb(event, name): 32 | print(f"{name} PRESSED") 33 | 34 | _all_btns = [get_button(scr, *btn) for btn in buttons] 35 | 36 | for btn, name in _all_btns: 37 | btn.add_event_cb( 38 | lambda event, button_name=name: button_cb(event, button_name), 39 | lv.EVENT.CLICKED, 40 | None, 41 | ) 42 | 43 | await asyncio.sleep_ms(500) # await so the frame can be rendered 44 | print("EVENT TEST:") 45 | for _btn, name in _all_btns: 46 | _btn.send_event(lv.EVENT.CLICKED, None) 47 | await asyncio.sleep_ms(200) 48 | 49 | # simulate touch events 50 | if display: 51 | print("INDEV TEST:") 52 | await display.touch(100, 100) 53 | 54 | await asyncio.sleep_ms(500) 55 | 56 | print("INDEV + BUTTONS TEST:") 57 | # display.debug_indev(press=True, release=True) 58 | display.debug_display(False) 59 | for _btn, name in _all_btns: 60 | pos = _btn.get_x(), _btn.get_y() 61 | await display.touch(*pos) 62 | await asyncio.sleep_ms(1000) 63 | 64 | await asyncio.sleep_ms(500) 65 | 66 | return _all_btns 67 | 68 | 69 | __file__ = globals().get("__file__", "test") 70 | 71 | try: 72 | import display_config 73 | 74 | display_config.MODE = "interactive" 75 | display_config.POINTER = "sim" 76 | except Exception: 77 | display_config = testrunner.display_config 78 | 79 | testrunner.run(demo, __file__, disp_config=display_config) 80 | testrunner.devicereset() 81 | -------------------------------------------------------------------------------- /tests/display/basic_indev.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: INTERACTIVE 2 | INDEV_MODE: SIM 3 | EVENT TEST: 4 | RED PRESSED 5 | GREEN PRESSED 6 | BLUE PRESSED 7 | INDEV TEST: 8 | [PRESSED]: (100,100) 9 | [RELEASED]: (100,100) 10 | INDEV + BUTTONS TEST: 11 | [PRESSED]: (90,0) 12 | RED PRESSED 13 | [RELEASED]: (90,0) 14 | [PRESSED]: (90,288) 15 | GREEN PRESSED 16 | [RELEASED]: (90,288) 17 | [PRESSED]: (90,144) 18 | BLUE PRESSED 19 | [RELEASED]: (90,144) 20 | -------------------------------------------------------------------------------- /tests/display/basic_slider.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner # noqa 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | async def demo(scr, display=None): 15 | def get_button(scr, text, align, color): 16 | scr.set_style_pad_all(10, 0) 17 | _btn = lv.slider(scr) 18 | _btn.set_width(lv.pct(75)) 19 | _btn.set_height(lv.pct(10)) 20 | _lab = lv.label(_btn) 21 | _lab.set_text(text) 22 | _lab.set_style_text_color(lv.color_white(), 0) 23 | _lab.center() 24 | _btn.set_style_align(align, 0) 25 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.INDICATOR) 26 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.MAIN) 27 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.KNOB) 28 | return _btn, text 29 | 30 | buttons = [ 31 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 32 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 33 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 34 | ] 35 | 36 | def button_cb(event, name, slider): 37 | if slider.get_value() == 100: 38 | print(f"{name} VALUE: {slider.get_value()}") 39 | 40 | _all_btns = [get_button(scr, *btn) for btn in buttons] 41 | 42 | for btn, name in _all_btns: 43 | btn.add_event_cb( 44 | lambda event, button_name=name, slider=btn: button_cb( 45 | event, button_name, slider 46 | ), 47 | lv.EVENT.VALUE_CHANGED, 48 | None, 49 | ) 50 | 51 | await asyncio.sleep_ms(500) # await so the frame can be rendered 52 | # simulate touch events 53 | if display: 54 | print("INDEV + SLIDER TEST:") 55 | display.debug_indev(press=False) 56 | display.debug_display(False) 57 | for _btn, name in _all_btns: 58 | pos = _btn.get_x(), _btn.get_y() 59 | pos2 = _btn.get_x2(), _btn.get_y2() 60 | x1, y1 = pos 61 | x2, y2 = pos2 62 | y_mid = y2 - ((y2 - y1) // 2) 63 | await display.swipe(x1 + 5, y_mid, x2 + (y2 - y1), y_mid, ms=500) 64 | await asyncio.sleep_ms(100) 65 | 66 | return _all_btns 67 | 68 | 69 | __file__ = globals().get("__file__", "test") 70 | 71 | try: 72 | import display_config 73 | 74 | display_config.MODE = "interactive" 75 | display_config.POINTER = "sim" 76 | except Exception: 77 | display_config = testrunner.display_config 78 | 79 | testrunner.run(demo, __file__, disp_config=display_config) 80 | testrunner.devicereset() 81 | -------------------------------------------------------------------------------- /tests/display/basic_slider.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: INTERACTIVE 2 | INDEV_MODE: SIM 3 | INDEV + SLIDER TEST: 4 | RED VALUE: 100 5 | [RELEASED]: (218,15) 6 | GREEN VALUE: 100 7 | [RELEASED]: (218,285) 8 | BLUE VALUE: 100 9 | [RELEASED]: (218,150) 10 | -------------------------------------------------------------------------------- /tests/display/display_config.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | 3 | MODE = "interactive" 4 | POINTER = "sim" 5 | WIDTH = 240 6 | HEIGHT = 320 7 | COLOR_FORMAT = lv.COLOR_FORMAT.RGB888 8 | -------------------------------------------------------------------------------- /tests/hwdisplay.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import pyb 3 | 4 | 5 | class HwDisplayDriver: 6 | def __init__(self, width=240, height=320, color_format=lv.COLOR_FORMAT.RGB565): 7 | self.width = width 8 | self.height = height 9 | self.color_depth = lv.color_format_get_bpp(color_format) 10 | self.color_size = lv.color_format_get_size(color_format) 11 | self._debug = False 12 | self._press_event = False 13 | self._x = int(width / 2) 14 | self.__y = self.gen_y() 15 | 16 | @property 17 | def debug(self): 18 | return self._debug 19 | 20 | @debug.setter 21 | def debug(self, x): 22 | self._debug = x 23 | 24 | def gen_y(self): 25 | for i in range(0, self.height * 2, int(self.height / 6)): 26 | yield i 27 | 28 | def get_y(self): 29 | self._y = next(self.__y) 30 | 31 | def blit(self, x1, y1, w, h, buff): 32 | pyb.LED((y1 % 4) + 1).toggle() 33 | ... 34 | 35 | def read_cb(self, indev, data): 36 | if pyb.Switch().value() and not self._press_event: 37 | self._press_event = True 38 | self.get_y() 39 | if self._debug: 40 | print(f"[PRESSED]: ({self._x},{self._y})") 41 | data.point = lv.point_t({"x": self._x, "y": self._y}) 42 | data.state = lv.INDEV_STATE.PRESSED 43 | elif not pyb.Switch().value() and self._press_event: 44 | self._press_event = False 45 | if self._debug: 46 | print(f"[RELEASED]: ({self._x},{self._y})") 47 | data.state = lv.INDEV_STATE.RELEASED 48 | 49 | 50 | # display = HwDisplayDriver() 51 | -------------------------------------------------------------------------------- /tests/imageconvert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from PIL import Image 4 | import sys 5 | import argparse 6 | 7 | parser = argparse.ArgumentParser(description="RGB888 to PNG converter") 8 | parser.add_argument("file", help="file/s name", nargs="*") 9 | 10 | args = parser.parse_args() 11 | 12 | for file in args.file: 13 | with open(file, "rb") as ff: 14 | w, h, cs = ff.readline().split(b":") 15 | width, height = int(w.decode()), int(h.decode()) 16 | frame = ff.read() 17 | assert len(frame) == width * height * int(cs) 18 | 19 | image = Image.new("RGB", (width, height)) 20 | image.frombytes(frame) 21 | image.save(file.replace(".bin", ".png"), "PNG") 22 | image.close() 23 | -------------------------------------------------------------------------------- /tests/indev/basic.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | class TestButton(lv.button): 15 | def __init__(self, parent): 16 | super().__init__(parent) 17 | self.event_press = asyncio.Event() 18 | 19 | 20 | async def demo(scr, display=None): 21 | def get_button(scr, text, align, color): 22 | _btn = TestButton(scr) 23 | _btn.set_size(lv.pct(25), lv.pct(10)) 24 | _lab = lv.label(_btn) 25 | _lab.set_text(text) 26 | _lab.center() 27 | _btn.set_style_align(align, 0) 28 | _btn.set_style_bg_color(lv.color_make(*color), 0) 29 | return _btn, text 30 | 31 | buttons = [ 32 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 33 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 34 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 35 | ] 36 | 37 | def button_cb(event, name, button): 38 | print(f"{name} PRESSED") 39 | button.event_press.set() 40 | 41 | _all_btns = [get_button(scr, *btn) for btn in buttons] 42 | 43 | for btn, name in _all_btns: 44 | btn.add_event_cb( 45 | lambda event, button_name=name, button=btn: button_cb( 46 | event, button_name, button 47 | ), 48 | lv.EVENT.CLICKED, 49 | None, 50 | ) 51 | 52 | await asyncio.sleep_ms(500) # await so the frame can be rendered 53 | print("PRESS EVENT TEST:") 54 | for _btn, name in _all_btns: 55 | await _btn.event_press.wait() 56 | return _all_btns 57 | 58 | 59 | __file__ = globals().get("__file__", "test") 60 | 61 | try: 62 | import display_config 63 | 64 | display_config.MODE = "interactive" 65 | display_config.POINTER = "interactive" 66 | except Exception: 67 | display_config = testrunner.display_config 68 | 69 | testrunner.run(demo, __file__, disp_config=display_config) 70 | testrunner.devicereset() 71 | -------------------------------------------------------------------------------- /tests/indev/basic.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: INTERACTIVE 2 | INDEV_MODE: INTERACTIVE 3 | PRESS EVENT TEST: 4 | RED PRESSED 5 | BLUE PRESSED 6 | GREEN PRESSED 7 | -------------------------------------------------------------------------------- /tests/indev/basic_slider.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner # noqa 9 | 10 | # This is a basic test to test buttons, labels, 11 | # RGB colors, layout aligment and events. 12 | 13 | 14 | class TestSlider(lv.slider): 15 | def __init__(self, parent): 16 | super().__init__(parent) 17 | self.event_completed = asyncio.Event() 18 | 19 | 20 | async def demo(scr, display=None): 21 | def get_button(scr, text, align, color): 22 | scr.set_style_pad_all(10, 0) 23 | _btn = TestSlider(scr) 24 | _btn.set_width(lv.pct(75)) 25 | _btn.set_height(lv.pct(10)) 26 | _lab = lv.label(_btn) 27 | _lab.set_text(text) 28 | _lab.set_style_text_color(lv.color_white(), 0) 29 | _lab.center() 30 | _btn.set_style_align(align, 0) 31 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.INDICATOR) 32 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.MAIN) 33 | _btn.set_style_bg_color(lv.color_make(*color), lv.PART.KNOB) 34 | return _btn, text 35 | 36 | buttons = [ 37 | ("RED", lv.ALIGN.TOP_MID, (255, 0, 0)), 38 | ("GREEN", lv.ALIGN.BOTTOM_MID, (0, 255, 0)), 39 | ("BLUE", lv.ALIGN.CENTER, (0, 0, 255)), 40 | ] 41 | 42 | def button_cb(event, name, slider): 43 | if slider.get_value() == 100: 44 | if not slider.event_completed.is_set(): 45 | print(f"{name} VALUE: {slider.get_value()}") 46 | slider.event_completed.set() 47 | 48 | _all_btns = [get_button(scr, *btn) for btn in buttons] 49 | 50 | for btn, name in _all_btns: 51 | btn.add_event_cb( 52 | lambda event, button_name=name, slider=btn: button_cb( 53 | event, button_name, slider 54 | ), 55 | lv.EVENT.VALUE_CHANGED, 56 | None, 57 | ) 58 | 59 | print("INDEV + SLIDER TEST:") 60 | display.debug_indev(press=False) 61 | display.debug_display(False) 62 | for _btn, name in _all_btns: 63 | await _btn.event_completed.wait() 64 | return _all_btns 65 | 66 | 67 | __file__ = globals().get("__file__", "test") 68 | 69 | try: 70 | import display_config 71 | 72 | display_config.MODE = "interactive" 73 | display_config.POINTER = "interactive" 74 | except Exception: 75 | display_config = testrunner.display_config 76 | 77 | testrunner.run(demo, __file__, disp_config=display_config) 78 | testrunner.devicereset() 79 | -------------------------------------------------------------------------------- /tests/indev/basic_slider.py.exp: -------------------------------------------------------------------------------- 1 | DISPLAY_MODE: INTERACTIVE 2 | INDEV_MODE: INTERACTIVE 3 | INDEV + SLIDER TEST: 4 | RED VALUE: 100 5 | BLUE VALUE: 100 6 | GREEN VALUE: 100 7 | -------------------------------------------------------------------------------- /tests/indev/display_config.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | 3 | MODE = "interactive" 4 | POINTER = "interactive" 5 | WIDTH = 240 6 | HEIGHT = 320 7 | COLOR_FORMAT = lv.COLOR_FORMAT.RGB888 8 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | SCRIPT_PATH="`dirname \"$0\"`" 6 | 7 | NUMCPUS=`nproc` 8 | 9 | TEST_PATH=" \ 10 | $SCRIPT_PATH/../examples/*.py \ 11 | $SCRIPT_PATH/../lvgl/examples/ \ 12 | $SCRIPT_PATH/../lvgl/demos/ \ 13 | " 14 | 15 | EXCLUDE_PATH=" \ 16 | $SCRIPT_PATH/../examples/fb_test.py \ 17 | $SCRIPT_PATH/../examples/uasyncio*.py \ 18 | $SCRIPT_PATH/../examples/generic-st77xx-with-xpt2046.py \ 19 | $SCRIPT_PATH/../lvgl/demos/music/assets/spectrum.py \ 20 | " 21 | 22 | EXCLUDE_FINDEXP=$(echo $EXCLUDE_PATH | sed "s/^\|[[:space:]]/ -and -not -path /g") 23 | 24 | find $TEST_PATH -name "*.py" $EXCLUDE_FINDEXP |\ 25 | parallel --halt-on-error now,fail=1 --max-args=1 --max-procs $NUMCPUS -I {} timeout 5m catchsegv $SCRIPT_PATH/../../../ports/unix/build-lvgl/micropython $SCRIPT_PATH/run_test.py {} 26 | 27 | -------------------------------------------------------------------------------- /tests/run_test.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Run LVGL examples 3 | # An example script should be provided as an argument. 4 | # 5 | # This script would: 6 | # - Initialize LVGL and display 7 | # - Read the example file 8 | # - Chdir to the example file dir 9 | # - Run the example 10 | # - gc collect 11 | # - Try to perform some actions such as sending click/value-changed events 12 | # - Deinit the display 13 | # 14 | # It tries not to pollute the namespace of script under test. 15 | # The only import visible for it is 'lv'. 16 | # 17 | # In case of an error, display the error and exit with code 255 18 | # 255 was selected to stop xargs 19 | # 20 | ############################################################################## 21 | 22 | DELAY_MS=25 23 | 24 | import usys 25 | usys.path.append('') # See: https://github.com/micropython/micropython/issues/6419 26 | 27 | import gc 28 | import os 29 | import time 30 | import lvgl as lv 31 | import lv_utils 32 | import display_driver_utils 33 | 34 | events = [lv.EVENT.SCROLL, lv.EVENT.CLICKED, lv.EVENT.VALUE_CHANGED, lv.EVENT.READY, lv.EVENT.FOCUSED] 35 | 36 | class ExceptionHandler: 37 | def __init__(self): 38 | self.recorded_exception = None 39 | 40 | def handle_exceptions(self, e): 41 | lv_utils.event_loop.current_instance().deinit() 42 | if not self.recorded_exception: 43 | self.recorded_exception = e 44 | 45 | def reraise(self): 46 | if self.recorded_exception: 47 | raise self.recorded_exception 48 | 49 | lv.init() 50 | exception_handler = ExceptionHandler() 51 | driver = display_driver_utils.driver(exception_sink = exception_handler.handle_exceptions) 52 | scr = lv.screen_active() 53 | objects = [] 54 | 55 | def collect_objects(obj, user_data): 56 | if hasattr(obj, 'lv_obj'): 57 | obj = obj.lv_obj 58 | objects.append(obj) 59 | return lv.obj.TREE_WALK.NEXT 60 | 61 | def send_events(): 62 | for obj in objects: 63 | if lv.obj.__cast__(obj): # skip deleted objects 64 | obj_info = '' 65 | if hasattr(obj, 'get_text'): 66 | obj_info += ' text:"%s"' % obj.get_text() 67 | if hasattr(obj, 'get_value'): 68 | obj_info += ' value:"%s"' % obj.get_value() 69 | print('%s %s' % (obj, obj_info)) 70 | for event in events: 71 | if not lv.obj.__cast__(obj): # skip deleted objects 72 | continue 73 | # print('\t%s' % get_member_name(lv.EVENT, event)) 74 | obj.send_event(event, None) 75 | time.sleep_ms(DELAY_MS) 76 | gc.collect() 77 | 78 | def run(): 79 | try: 80 | script = usys.argv[1] 81 | script_path = script[:script.rfind('/')] if script.find('/') >= 0 else '.' 82 | script_name = script[script.rfind('/')+1:] if script.find('/') >= 0 else script 83 | 84 | print('Running %s ...' % script) 85 | 86 | with open(script, 'r') as file: 87 | file_string = file.read() 88 | os.chdir(script_path) 89 | usys.argv[0] = script_name 90 | del usys.argv[1] 91 | exec(file_string, {'__file__': script_name, 'lv': lv}) 92 | time.sleep_ms(DELAY_MS * 2) 93 | gc.collect() 94 | lv.screen_active().tree_walk(collect_objects, None) 95 | send_events() 96 | time.sleep_ms(DELAY_MS) 97 | exception_handler.reraise() 98 | if lv_utils.event_loop.is_running(): 99 | lv_utils.event_loop.current_instance().deinit() 100 | time.sleep_ms(DELAY_MS) 101 | 102 | except Exception as e: 103 | usys.print_exception(e) 104 | usys.exit(255) # 255 to exit xargs 105 | 106 | run() 107 | -------------------------------------------------------------------------------- /tests/testrunner.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append("..") 5 | sys.path.append(os.getcwd()) 6 | 7 | from testdisplay import get_display, display_config # noqa 8 | 9 | _int = sys.argv.pop() if sys.platform in ["darwin", "linux"] else "" 10 | _mode = "sim" 11 | if _int in ("-id", "-d"): 12 | _mode = "interactive" 13 | 14 | 15 | async def run_test(func, display=None): 16 | import lvgl as lv # noqa 17 | 18 | lv.init() 19 | 20 | scr = lv.obj() 21 | scr.set_style_bg_color(lv.color_black(), 0) 22 | lv.screen_load(scr) 23 | 24 | resp = await func(scr, display) 25 | return scr, resp 26 | 27 | 28 | def run(func, filename, disp_config=display_config, **kwargs): 29 | import asyncio 30 | 31 | # import micropython # noqa 32 | 33 | # micropython.mem_info() 34 | 35 | async def _run(func, disp_config=disp_config, **kwargs): 36 | if hasattr(disp_config, "COLOR_FORMAT"): 37 | display_config.COLOR_FORMAT = disp_config.COLOR_FORMAT 38 | display = get_display( 39 | disp_config.WIDTH, 40 | disp_config.HEIGHT, 41 | mode=disp_config.MODE if disp_config.MODE is not None else _mode, 42 | pointer=disp_config.POINTER, 43 | color_format=display_config.COLOR_FORMAT, 44 | ) 45 | 46 | if display.mode == "sim": 47 | display.set_test_name(f"{filename.replace('.py', '')}.{func.__name__}") 48 | await run_test(func, display) 49 | await asyncio.sleep_ms(100) 50 | elif display.mode == "interactive": 51 | await run_test(func, display) 52 | if _int == "-id": 53 | while True: 54 | try: 55 | await asyncio.sleep_ms(1000) 56 | except KeyboardInterrupt: 57 | sys.exit() 58 | except Exception as e: 59 | sys.print_exception(e) 60 | 61 | asyncio.run(_run(func, disp_config, **kwargs)) 62 | 63 | 64 | def devicereset(): 65 | import lvgl as lv 66 | 67 | if lv.is_initialized(): 68 | lv.deinit() 69 | -------------------------------------------------------------------------------- /tests/tools/calibrate.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | import sys 3 | import asyncio 4 | import os 5 | 6 | sys.path.append("..") 7 | sys.path.append(os.getcwd()) 8 | import testrunner 9 | 10 | # Calibrate indev with canvas 11 | 12 | 13 | class TestCanvas(lv.obj): 14 | def __init__(self, parent, display): 15 | super().__init__(parent) 16 | 17 | self.set_style_bg_color(lv.color_black(), 0) 18 | self.set_size(lv.pct(100), lv.pct(100)) 19 | self.set_style_pad_all(0, 0) 20 | self.set_style_pad_top(0, 0) 21 | self.set_style_radius(0, 0) 22 | self.set_style_margin_bottom(0, 0) 23 | self.set_style_margin_top(0, 0) 24 | self.set_style_margin_left(0, 0) 25 | self.set_style_margin_right(0, 0) 26 | self.set_style_border_width(0, 0) 27 | self.set_style_outline_width(0, 0) 28 | self.center() 29 | self.WIDTH = display.width 30 | self.HEIGHT = display.width 31 | 32 | self.X_OFFSET = round(self.WIDTH / 2) 33 | self.Y_OFFSET = self.X_OFFSET 34 | self.debug = True 35 | self.event_press = asyncio.Event() 36 | 37 | self._cbuf = lv.draw_buf_create( 38 | self.WIDTH, self.HEIGHT, lv.COLOR_FORMAT.RGB888, 0 39 | ) 40 | self.canvas = lv.canvas(self) 41 | self.canvas.set_draw_buf(self._cbuf) 42 | self.canvas.fill_bg(lv.color_white(), lv.OPA.TRANSP) 43 | self.canvas.center() 44 | 45 | # Coordinates label 46 | self._xy_lab = lv.label(self) 47 | self._xy_lab.align(lv.ALIGN.TOP_LEFT, 5, 10) # FIX: 48 | self._xy_lab.set_text(f"X: {0} Y: {0}") 49 | self._xy_lab.set_style_text_color(lv.color_make(0, 255, 0), 0) 50 | 51 | # Draw callback 52 | def press_cb(event): 53 | indev = lv.indev_active() 54 | if indev: 55 | _point = lv.point_t() 56 | indev.get_point(_point) 57 | y = -self.canvas.get_y() + _point.y - round(self.HEIGHT / 2) 58 | x = -self.canvas.get_x() + _point.x - round(self.WIDTH / 2) 59 | if self.debug: 60 | print(f"x:{x}, y:{y}") 61 | # draw a red point 62 | self.canvas.set_px( 63 | x + self.X_OFFSET, 64 | y + self.Y_OFFSET, 65 | lv.color_make(255, 0, 0), 66 | lv.OPA.COVER, 67 | ) 68 | 69 | self._xy_lab.set_text(f"X: {x} Y: {y}") 70 | self.event_press.set() 71 | 72 | self.canvas.add_event_cb(lambda event: press_cb(event), lv.EVENT.PRESSING, None) 73 | self.canvas.add_flag(lv.obj.FLAG.CLICKABLE) 74 | 75 | # Clear button 76 | self._cl_btn = lv.button(self) 77 | self._cl_btn.align(lv.ALIGN.BOTTOM_LEFT, 4, -4) 78 | 79 | self._cl_btn.set_style_bg_color(lv.color_make(0, 255, 0), 0) 80 | self._cl_btn.set_size(lv.pct(25), lv.pct(10)) 81 | self._lab = lv.label(self._cl_btn) 82 | self._lab.set_text("CLEAR") 83 | self._lab.center() 84 | 85 | def clear_cb(event): 86 | self.canvas.fill_bg(lv.color_white(), lv.OPA.TRANSP) 87 | 88 | self._cl_btn.add_event_cb(lambda event: clear_cb(event), lv.EVENT.PRESSED, None) 89 | 90 | 91 | async def calib(scr, display=None): 92 | tc = TestCanvas(scr, display.display_drv) 93 | print("INDEV CALIBRATION TOOL") 94 | while True: 95 | await tc.event_press.wait() 96 | tc.event_press.clear() 97 | await asyncio.sleep_ms(100) 98 | 99 | 100 | __file__ = globals().get("__file__", "test") 101 | 102 | try: 103 | import display_config 104 | 105 | display_config.MODE = "interactive" 106 | display_config.POINTER = "interactive" 107 | except Exception: 108 | display_config = testrunner.display_config 109 | 110 | testrunner.run(calib, __file__, disp_config=display_config) 111 | testrunner.devicereset() 112 | --------------------------------------------------------------------------------