├── .github ├── auto-comment.yml └── workflows │ └── unix_port.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── bindings.cmake ├── driver ├── SDL │ ├── modSDL.c │ ├── sdl.c │ ├── sdl.h │ ├── sdl_common.c │ └── sdl_common.h ├── esp32 │ ├── espidf.c │ ├── espidf.h │ ├── ili9341.py │ ├── ili9XXX.py │ ├── lv_spi.py │ ├── modILI9341.c │ ├── modrtch.c │ ├── modxpt2046.c │ ├── sh2lib.c │ ├── sh2lib.h │ └── xpt2046.py ├── generic │ ├── axp192.py │ ├── ft6x36.py │ ├── ili9xxx-test.py │ ├── ili9xxx.py │ ├── indev_example.py │ ├── st77xx-test.py │ ├── st77xx.py │ ├── xpt2046-test.py │ └── xpt2046.py ├── include │ └── common.h ├── js │ └── lv_timer.py ├── linux │ ├── evdev.py │ ├── lv_timer.py │ └── modfb.c ├── 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 ├── 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 ├── mkrules.cmake └── tests ├── run.sh └── run_test.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-latest 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 lib/lv_bindings 24 | - name: Update Unix port submodules 25 | run: make -C ports/unix VARIANT=dev DEBUG=1 submodules 26 | - name: Checkout lv_bindings 27 | working-directory: ./lib/lv_bindings 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 VARIANT=dev DEBUG=1 37 | - name: Run tests 38 | run: | 39 | export XDG_RUNTIME_DIR=/tmp 40 | lib/lv_bindings/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/SDL/modSDL.c: -------------------------------------------------------------------------------- 1 | #include "../include/common.h" 2 | #ifndef _POSIX_C_SOURCE 3 | #define _POSIX_C_SOURCE 200809L 4 | #endif 5 | #include 6 | #include 7 | #include 8 | #include "sdl_common.h" 9 | #include "sdl.h" 10 | #ifdef __EMSCRIPTEN__ 11 | #include 12 | #endif 13 | 14 | /* Defines the LittlevGL tick rate in milliseconds. */ 15 | /* Increasing this value might help with CPU usage at the cost of lower 16 | * responsiveness. */ 17 | #define LV_TICK_RATE 20 18 | 19 | /* Default screen dimensions */ 20 | #define LV_HOR_RES_MAX (480) 21 | #define LV_VER_RES_MAX (320) 22 | 23 | ////////////////////////////////////////////////////////////////////////////// 24 | 25 | STATIC pthread_t mp_thread; 26 | 27 | STATIC mp_obj_t mp_lv_task_handler(mp_obj_t arg) 28 | { 29 | lv_task_handler(); 30 | return mp_const_none; 31 | } 32 | 33 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_lv_task_handler_obj, mp_lv_task_handler); 34 | 35 | #ifndef __EMSCRIPTEN__ 36 | STATIC int tick_thread(void * data) 37 | { 38 | (void)data; 39 | 40 | Uint64 pfreq = SDL_GetPerformanceFrequency(); 41 | Uint64 tick_ms = (pfreq / 1000) * LV_TICK_RATE; 42 | Uint64 delta, acc = 0; 43 | 44 | while(monitor_active()) { 45 | delta = SDL_GetPerformanceCounter(); 46 | SDL_Delay(LV_TICK_RATE); /*Sleep for LV_TICK_RATE millisecond*/ 47 | delta = SDL_GetPerformanceCounter() - delta; 48 | acc += delta - tick_ms; 49 | lv_tick_inc(LV_TICK_RATE); /*Tell LittelvGL that LV_TICK_RATE milliseconds were elapsed*/ 50 | if (acc >= tick_ms) { 51 | lv_tick_inc(LV_TICK_RATE); 52 | acc -= tick_ms; 53 | } 54 | mp_sched_schedule((mp_obj_t)&mp_lv_task_handler_obj, mp_const_none); 55 | pthread_kill(mp_thread, SIGUSR1); // interrupt REPL blocking input. See handle_sigusr1 56 | } 57 | 58 | return 0; 59 | } 60 | #else 61 | STATIC void mp_lv_main_loop(void) 62 | { 63 | mp_sched_schedule((mp_obj_t)&mp_lv_task_handler_obj, mp_const_none); 64 | lv_tick_inc(LV_TICK_RATE); 65 | } 66 | #endif 67 | 68 | static void handle_sigusr1(int signo) 69 | { 70 | // Let the signal pass. blocking function would return E_INTR. 71 | // This would cause a call to "mp_handle_pending" even when 72 | // waiting for user input. 73 | // See https://github.com/micropython/micropython/pull/5723 74 | } 75 | 76 | STATIC mp_obj_t mp_init_SDL(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) 77 | { 78 | enum { ARG_w, ARG_h, ARG_zoom, ARG_fullscreen, ARG_auto_refresh }; 79 | static const mp_arg_t allowed_args[] = { 80 | { MP_QSTR_w, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = LV_HOR_RES_MAX} }, 81 | { MP_QSTR_h, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = LV_VER_RES_MAX} }, 82 | { MP_QSTR_zoom, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, 83 | { MP_QSTR_fullscreen, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false } }, 84 | { MP_QSTR_auto_refresh, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, 85 | }; 86 | 87 | // parse args 88 | mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; 89 | mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); 90 | sdl_init(args[ARG_w].u_int, 91 | args[ARG_h].u_int, 92 | args[ARG_zoom].u_obj != mp_const_none? mp_obj_get_float_to_f(args[ARG_zoom].u_obj) : 1, 93 | args[ARG_fullscreen].u_bool); 94 | #ifdef __EMSCRIPTEN__ 95 | emscripten_set_main_loop(mp_lv_main_loop, 1000 / LV_TICK_RATE, 0); 96 | #else 97 | if (args[ARG_auto_refresh].u_bool) { 98 | SDL_CreateThread(tick_thread, "tick", NULL); 99 | } 100 | #endif 101 | 102 | if (args[ARG_auto_refresh].u_bool) { 103 | mp_thread = pthread_self(); 104 | struct sigaction sa; 105 | sa.sa_handler = handle_sigusr1; 106 | sa.sa_flags = 0; 107 | sigemptyset(&sa.sa_mask); 108 | if (sigaction(SIGUSR1, &sa, NULL) == -1) { 109 | perror("sigaction"); 110 | exit(1); 111 | } 112 | } 113 | 114 | return mp_const_none; 115 | } 116 | 117 | STATIC mp_obj_t mp_deinit_SDL() 118 | { 119 | sdl_deinit(); 120 | return mp_const_none; 121 | } 122 | 123 | STATIC mp_obj_t mp_refresh_SDL() 124 | { 125 | // Placeholder for backward compatability 126 | return mp_const_none; 127 | } 128 | 129 | STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mp_init_SDL_obj, 0, mp_init_SDL); 130 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_deinit_SDL_obj, mp_deinit_SDL); 131 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_refresh_SDL_obj, mp_refresh_SDL); 132 | 133 | static void monitor_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 134 | { 135 | sdl_display_flush(disp_drv, area, color_p); 136 | } 137 | 138 | static void mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) 139 | { 140 | sdl_mouse_read(indev_drv, data); 141 | } 142 | 143 | static void keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) 144 | { 145 | sdl_keyboard_read(indev_drv, data); 146 | } 147 | 148 | DEFINE_PTR_OBJ(monitor_flush); 149 | DEFINE_PTR_OBJ(mouse_read); 150 | DEFINE_PTR_OBJ(keyboard_read); 151 | 152 | STATIC const mp_rom_map_elem_t SDL_globals_table[] = { 153 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_SDL) }, 154 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mp_init_SDL_obj) }, 155 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_deinit_SDL_obj) }, 156 | { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&mp_refresh_SDL_obj) }, 157 | { MP_ROM_QSTR(MP_QSTR_monitor_flush), MP_ROM_PTR(&PTR_OBJ(monitor_flush))}, 158 | { MP_ROM_QSTR(MP_QSTR_mouse_read), MP_ROM_PTR(&PTR_OBJ(mouse_read))}, 159 | { MP_ROM_QSTR(MP_QSTR_keyboard_read), MP_ROM_PTR(&PTR_OBJ(keyboard_read))}, 160 | }; 161 | 162 | 163 | STATIC MP_DEFINE_CONST_DICT ( 164 | mp_module_SDL_globals, 165 | SDL_globals_table 166 | ); 167 | 168 | const mp_obj_module_t mp_module_SDL = { 169 | .base = { &mp_type_module }, 170 | .globals = (mp_obj_dict_t*)&mp_module_SDL_globals 171 | }; 172 | 173 | MP_REGISTER_MODULE(MP_QSTR_SDL, mp_module_SDL); 174 | -------------------------------------------------------------------------------- /driver/SDL/sdl.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sdl.c 3 | * 4 | */ 5 | 6 | /********************* 7 | * INCLUDES 8 | *********************/ 9 | 10 | #include "sdl.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | /********************* 17 | * DEFINES 18 | *********************/ 19 | #ifndef KEYBOARD_BUFFER_SIZE 20 | #define KEYBOARD_BUFFER_SIZE SDL_TEXTINPUTEVENT_TEXT_SIZE 21 | #endif 22 | 23 | /********************** 24 | * TYPEDEFS 25 | **********************/ 26 | typedef struct { 27 | SDL_Window * window; 28 | SDL_Renderer * renderer; 29 | SDL_Texture * texture; 30 | volatile bool sdl_refr_qry; 31 | #if SDL_DOUBLE_BUFFERED 32 | uint32_t * tft_fb_act; 33 | #else 34 | uint32_t * tft_fb; 35 | #endif 36 | }monitor_t; 37 | 38 | /********************** 39 | * STATIC PROTOTYPES 40 | **********************/ 41 | static void window_create(monitor_t * m); 42 | static void window_update(monitor_t * m); 43 | static void monitor_sdl_clean_up(void); 44 | static void sdl_event_handler(lv_timer_t * t); 45 | static void monitor_sdl_refr(lv_timer_t * t); 46 | 47 | /*********************** 48 | * GLOBAL PROTOTYPES 49 | ***********************/ 50 | 51 | /********************** 52 | * STATIC VARIABLES 53 | **********************/ 54 | static int monitor_w; 55 | static int monitor_h; 56 | float sdl_zoom; 57 | bool sdl_fullscreen; 58 | 59 | monitor_t monitor; 60 | 61 | #if SDL_DUAL_DISPLAY 62 | monitor_t monitor2; 63 | #endif 64 | 65 | static volatile bool sdl_inited = false; 66 | 67 | /********************** 68 | * MACROS 69 | **********************/ 70 | 71 | /********************** 72 | * GLOBAL FUNCTIONS 73 | **********************/ 74 | 75 | bool monitor_active(void) 76 | { 77 | return sdl_inited && !sdl_quit_qry; 78 | } 79 | 80 | void sdl_init(int w, int h, float zoom, bool fullscreen) 81 | { 82 | monitor_w = w; 83 | monitor_h = h; 84 | sdl_zoom = zoom; 85 | sdl_fullscreen = fullscreen; 86 | 87 | /*Initialize the SDL*/ 88 | SDL_Init(SDL_INIT_VIDEO); 89 | #ifdef __EMSCRIPTEN__ 90 | SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); 91 | #endif 92 | 93 | SDL_SetEventFilter(quit_filter, NULL); 94 | 95 | window_create(&monitor); 96 | #if SDL_DUAL_DISPLAY 97 | window_create(&monitor2); 98 | int x, y; 99 | SDL_GetWindowPosition(monitor2.window, &x, &y); 100 | SDL_SetWindowPosition(monitor.window, (int)(x + (SDL_HOR_RES * SDL_ZOOM) / 2 + 10), y); 101 | SDL_SetWindowPosition(monitor2.window, (int)(x - (SDL_HOR_RES * SDL_ZOOM) / 2 - 10), y); 102 | #endif 103 | 104 | SDL_StartTextInput(); 105 | 106 | lv_timer_create(sdl_event_handler, 10, NULL); 107 | 108 | sdl_inited = true; 109 | } 110 | 111 | /** 112 | * Deinit the monitor and close SDL 113 | */ 114 | void sdl_deinit(void) 115 | { 116 | sdl_quit_qry = true; 117 | // Destroy calls are in monitor_sdl_clean_up 118 | } 119 | 120 | /** 121 | * Flush a buffer to the marked area 122 | * @param disp_drv pointer to driver where this function belongs 123 | * @param area an area where to copy `color_p` 124 | * @param color_p an array of pixels to copy to the `area` part of the screen 125 | */ 126 | void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 127 | { 128 | lv_coord_t hres = disp_drv->hor_res; 129 | lv_coord_t vres = disp_drv->ver_res; 130 | 131 | // printf("x1:%d,y1:%d,x2:%d,y2:%d\n", area->x1, area->y1, area->x2, area->y2); 132 | 133 | /*Return if the area is out the screen*/ 134 | if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { 135 | lv_disp_flush_ready(disp_drv); 136 | return; 137 | } 138 | 139 | #if SDL_DOUBLE_BUFFERED 140 | monitor.tft_fb_act = (uint32_t *)color_p; 141 | #else /*SDL_DOUBLE_BUFFERED*/ 142 | 143 | int32_t y; 144 | #if LV_COLOR_DEPTH != 24 && LV_COLOR_DEPTH != 32 /*32 is valid but support 24 for backward compatibility too*/ 145 | int32_t x; 146 | for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) { 147 | for(x = area->x1; x <= area->x2; x++) { 148 | monitor.tft_fb[y * disp_drv->hor_res + x] = lv_color_to32(*color_p); 149 | color_p++; 150 | } 151 | 152 | } 153 | #else 154 | uint32_t w = lv_area_get_width(area); 155 | for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) { 156 | memcpy(&monitor.tft_fb[y * SDL_HOR_RES + area->x1], color_p, w * sizeof(lv_color_t)); 157 | color_p += w; 158 | } 159 | #endif 160 | #endif /*SDL_DOUBLE_BUFFERED*/ 161 | 162 | monitor.sdl_refr_qry = true; 163 | 164 | /* TYPICALLY YOU DO NOT NEED THIS 165 | * If it was the last part to refresh update the texture of the window.*/ 166 | if(lv_disp_flush_is_last(disp_drv)) { 167 | monitor_sdl_refr(NULL); 168 | } 169 | 170 | /*IMPORTANT! It must be called to tell the system the flush is ready*/ 171 | lv_disp_flush_ready(disp_drv); 172 | 173 | } 174 | 175 | 176 | #if SDL_DUAL_DISPLAY 177 | 178 | /** 179 | * Flush a buffer to the marked area 180 | * @param disp_drv pointer to driver where this function belongs 181 | * @param area an area where to copy `color_p` 182 | * @param color_p an array of pixels to copy to the `area` part of the screen 183 | */ 184 | void sdl_display_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 185 | { 186 | lv_coord_t hres = disp_drv->hor_res; 187 | lv_coord_t vres = disp_drv->ver_res; 188 | 189 | /*Return if the area is out the screen*/ 190 | if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { 191 | lv_disp_flush_ready(disp_drv); 192 | return; 193 | } 194 | 195 | #if SDL_DOUBLE_BUFFERED 196 | monitor2.tft_fb_act = (uint32_t *)color_p; 197 | 198 | monitor2.sdl_refr_qry = true; 199 | 200 | /*IMPORTANT! It must be called to tell the system the flush is ready*/ 201 | lv_disp_flush_ready(disp_drv); 202 | #else 203 | 204 | int32_t y; 205 | #if LV_COLOR_DEPTH != 24 && LV_COLOR_DEPTH != 32 /*32 is valid but support 24 for backward compatibility too*/ 206 | int32_t x; 207 | for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) { 208 | for(x = area->x1; x <= area->x2; x++) { 209 | monitor2.tft_fb[y * disp_drv->hor_res + x] = lv_color_to32(*color_p); 210 | color_p++; 211 | } 212 | 213 | } 214 | #else 215 | uint32_t w = lv_area_get_width(area); 216 | for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) { 217 | memcpy(&monitor2.tft_fb[y * disp_drv->hor_res + area->x1], color_p, w * sizeof(lv_color_t)); 218 | color_p += w; 219 | } 220 | #endif 221 | 222 | monitor2.sdl_refr_qry = true; 223 | 224 | /* TYPICALLY YOU DO NOT NEED THIS 225 | * If it was the last part to refresh update the texture of the window.*/ 226 | if(lv_disp_flush_is_last(disp_drv)) { 227 | monitor_sdl_refr(NULL); 228 | } 229 | 230 | /*IMPORTANT! It must be called to tell the system the flush is ready*/ 231 | lv_disp_flush_ready(disp_drv); 232 | #endif 233 | } 234 | #endif 235 | 236 | /********************** 237 | * STATIC FUNCTIONS 238 | **********************/ 239 | 240 | 241 | /** 242 | * SDL main thread. All SDL related task have to be handled here! 243 | * It initializes SDL, handles drawing and the mouse. 244 | */ 245 | 246 | static void sdl_event_handler(lv_timer_t * t) 247 | { 248 | (void)t; 249 | 250 | /*Refresh handling*/ 251 | SDL_Event event; 252 | while(SDL_PollEvent(&event)) { 253 | mouse_handler(&event); 254 | mousewheel_handler(&event); 255 | keyboard_handler(&event); 256 | 257 | if((&event)->type == SDL_WINDOWEVENT) { 258 | switch((&event)->window.event) { 259 | #if SDL_VERSION_ATLEAST(2, 0, 5) 260 | case SDL_WINDOWEVENT_TAKE_FOCUS: 261 | #endif 262 | case SDL_WINDOWEVENT_EXPOSED: 263 | window_update(&monitor); 264 | #if SDL_DUAL_DISPLAY 265 | window_update(&monitor2); 266 | #endif 267 | break; 268 | default: 269 | break; 270 | } 271 | } 272 | } 273 | 274 | /*Run until quit event not arrives*/ 275 | if(sdl_quit_qry) { 276 | monitor_sdl_clean_up(); 277 | exit(0); 278 | } 279 | } 280 | 281 | /** 282 | * SDL main thread. All SDL related task have to be handled here! 283 | * It initializes SDL, handles drawing and the mouse. 284 | */ 285 | 286 | static void monitor_sdl_refr(lv_timer_t * t) 287 | { 288 | (void)t; 289 | 290 | /*Refresh handling*/ 291 | if(monitor.sdl_refr_qry != false) { 292 | monitor.sdl_refr_qry = false; 293 | window_update(&monitor); 294 | } 295 | 296 | #if SDL_DUAL_DISPLAY 297 | if(monitor2.sdl_refr_qry != false) { 298 | monitor2.sdl_refr_qry = false; 299 | window_update(&monitor2); 300 | } 301 | #endif 302 | } 303 | 304 | static void monitor_sdl_clean_up(void) 305 | { 306 | SDL_DestroyTexture(monitor.texture); 307 | SDL_DestroyRenderer(monitor.renderer); 308 | SDL_DestroyWindow(monitor.window); 309 | free(monitor.tft_fb); 310 | 311 | #if SDL_DUAL_DISPLAY 312 | SDL_DestroyTexture(monitor2.texture); 313 | SDL_DestroyRenderer(monitor2.renderer); 314 | SDL_DestroyWindow(monitor2.window); 315 | free(monitor2.tft_fb); 316 | 317 | #endif 318 | 319 | SDL_Quit(); 320 | } 321 | 322 | static void window_create(monitor_t * m) 323 | { 324 | 325 | int flag = 0; 326 | 327 | if (SDL_FULLSCREEN) { 328 | flag |= SDL_WINDOW_FULLSCREEN; 329 | } 330 | 331 | m->window = SDL_CreateWindow("TFT Simulator", 332 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 333 | (int) (SDL_HOR_RES * SDL_ZOOM), (int) (SDL_VER_RES * SDL_ZOOM), flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/ 334 | 335 | m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_SOFTWARE); 336 | m->texture = SDL_CreateTexture(m->renderer, 337 | SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, SDL_HOR_RES, SDL_VER_RES); 338 | SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_BLEND); 339 | 340 | /*Initialize the frame buffer to gray (77 is an empirical value) */ 341 | #if SDL_DOUBLE_BUFFERED 342 | SDL_UpdateTexture(m->texture, NULL, m->tft_fb_act, SDL_HOR_RES * sizeof(uint32_t)); 343 | #else 344 | m->tft_fb = (uint32_t *)malloc(sizeof(uint32_t) * SDL_HOR_RES * SDL_VER_RES); 345 | memset(m->tft_fb, 0x44, SDL_HOR_RES * SDL_VER_RES * sizeof(uint32_t)); 346 | #endif 347 | 348 | m->sdl_refr_qry = true; 349 | 350 | } 351 | 352 | static void window_update(monitor_t * m) 353 | { 354 | #if SDL_DOUBLE_BUFFERED == 0 355 | SDL_UpdateTexture(m->texture, NULL, m->tft_fb, SDL_HOR_RES * sizeof(uint32_t)); 356 | #else 357 | if(m->tft_fb_act == NULL) return; 358 | SDL_UpdateTexture(m->texture, NULL, m->tft_fb_act, SDL_HOR_RES * sizeof(uint32_t)); 359 | #endif 360 | SDL_RenderClear(m->renderer); 361 | lv_disp_t * d = _lv_refr_get_disp_refreshing(); 362 | if(d->driver->screen_transp) { 363 | SDL_SetRenderDrawColor(m->renderer, 0xff, 0, 0, 0xff); 364 | SDL_Rect r; 365 | r.x = 0; r.y = 0; r.w = SDL_HOR_RES; r.h = SDL_VER_RES; 366 | SDL_RenderDrawRect(m->renderer, &r); 367 | } 368 | 369 | /*Update the renderer with the texture containing the rendered image*/ 370 | SDL_RenderCopy(m->renderer, m->texture, NULL, NULL); 371 | SDL_RenderPresent(m->renderer); 372 | } 373 | 374 | -------------------------------------------------------------------------------- /driver/SDL/sdl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sdl.h 3 | * 4 | */ 5 | 6 | #ifndef SDL_H 7 | #define SDL_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | /********************* 14 | * INCLUDES 15 | *********************/ 16 | 17 | #include "sdl_common.h" 18 | 19 | #ifdef LV_LVGL_H_INCLUDE_SIMPLE 20 | #include "lvgl.h" 21 | #else 22 | #include "lvgl/lvgl.h" 23 | #endif 24 | 25 | /********************* 26 | * DEFINES 27 | *********************/ 28 | 29 | /********************** 30 | * TYPEDEFS 31 | **********************/ 32 | 33 | /********************** 34 | * GLOBAL PROTOTYPES 35 | **********************/ 36 | 37 | bool monitor_active(void); 38 | 39 | /** 40 | * Flush a buffer to the marked area 41 | * @param disp_drv pointer to driver where this function belongs 42 | * @param area an area where to copy `color_p` 43 | * @param color_p an array of pixels to copy to the `area` part of the screen 44 | */ 45 | void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); 46 | 47 | /** 48 | * Flush a buffer to the marked area 49 | * @param disp_drv pointer to driver where this function belongs 50 | * @param area an area where to copy `color_p` 51 | * @param color_p an array of pixels to copy to the `area` part of the screen 52 | */ 53 | void sdl_display_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); 54 | 55 | /** 56 | * Get the current position and state of the mouse 57 | * @param indev_drv pointer to the related input device driver 58 | * @param data store the mouse data here 59 | */ 60 | void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); 61 | 62 | /** 63 | * Get encoder (i.e. mouse wheel) ticks difference and pressed state 64 | * @param indev_drv pointer to the related input device driver 65 | * @param data store the read data here 66 | */ 67 | void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); 68 | 69 | /** 70 | * Get input from the keyboard. 71 | * @param indev_drv pointer to the related input device driver 72 | * @param data store the red data here 73 | */ 74 | void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); 75 | 76 | /********************** 77 | * MACROS 78 | **********************/ 79 | 80 | #endif /* USE_MONITOR || USE_SDL */ 81 | 82 | #ifdef __cplusplus 83 | } /* extern "C" */ 84 | #endif 85 | -------------------------------------------------------------------------------- /driver/SDL/sdl_common.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Mariotaku on 2021/10/14. 3 | // 4 | 5 | #include "sdl_common.h" 6 | 7 | /********************* 8 | * DEFINES 9 | *********************/ 10 | 11 | #ifndef KEYBOARD_BUFFER_SIZE 12 | #define KEYBOARD_BUFFER_SIZE SDL_TEXTINPUTEVENT_TEXT_SIZE 13 | #endif 14 | 15 | /********************** 16 | * STATIC PROTOTYPES 17 | **********************/ 18 | 19 | 20 | /********************** 21 | * STATIC VARIABLES 22 | **********************/ 23 | 24 | volatile bool sdl_quit_qry = false; 25 | 26 | static bool left_button_down = false; 27 | static int16_t last_x = 0; 28 | static int16_t last_y = 0; 29 | 30 | static int16_t wheel_diff = 0; 31 | static lv_indev_state_t wheel_state = LV_INDEV_STATE_RELEASED; 32 | 33 | static char buf[KEYBOARD_BUFFER_SIZE]; 34 | 35 | /********************** 36 | * GLOBAL FUNCTIONS 37 | **********************/ 38 | /** 39 | * Get the current position and state of the mouse 40 | * @param indev_drv pointer to the related input device driver 41 | * @param data store the mouse data here 42 | */ 43 | void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) 44 | { 45 | (void) indev_drv; /*Unused*/ 46 | 47 | /*Store the collected data*/ 48 | data->point.x = last_x; 49 | data->point.y = last_y; 50 | data->state = left_button_down ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; 51 | } 52 | 53 | 54 | /** 55 | * Get encoder (i.e. mouse wheel) ticks difference and pressed state 56 | * @param indev_drv pointer to the related input device driver 57 | * @param data store the read data here 58 | */ 59 | void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) 60 | { 61 | (void) indev_drv; /*Unused*/ 62 | 63 | data->state = wheel_state; 64 | data->enc_diff = wheel_diff; 65 | wheel_diff = 0; 66 | } 67 | 68 | /** 69 | * Get input from the keyboard. 70 | * @param indev_drv pointer to the related input device driver 71 | * @param data store the red data here 72 | */ 73 | void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) 74 | { 75 | (void) indev_drv; /*Unused*/ 76 | 77 | static bool dummy_read = false; 78 | const size_t len = strlen(buf); 79 | 80 | /*Send a release manually*/ 81 | if (dummy_read) { 82 | dummy_read = false; 83 | data->state = LV_INDEV_STATE_RELEASED; 84 | data->continue_reading = len > 0; 85 | } 86 | /*Send the pressed character*/ 87 | else if (len > 0) { 88 | dummy_read = true; 89 | data->state = LV_INDEV_STATE_PRESSED; 90 | data->key = buf[0]; 91 | memmove(buf, buf + 1, len); 92 | data->continue_reading = true; 93 | } 94 | } 95 | 96 | /********************** 97 | * STATIC FUNCTIONS 98 | **********************/ 99 | 100 | int quit_filter(void * userdata, SDL_Event * event) 101 | { 102 | (void)userdata; 103 | 104 | if(event->type == SDL_QUIT) { 105 | sdl_quit_qry = true; 106 | } 107 | 108 | return 1; 109 | } 110 | 111 | void mouse_handler(SDL_Event * event) 112 | { 113 | switch(event->type) { 114 | case SDL_MOUSEBUTTONUP: 115 | if(event->button.button == SDL_BUTTON_LEFT) 116 | left_button_down = false; 117 | break; 118 | case SDL_MOUSEBUTTONDOWN: 119 | if(event->button.button == SDL_BUTTON_LEFT) { 120 | left_button_down = true; 121 | last_x = (int16_t)(event->motion.x / SDL_ZOOM); 122 | last_y = (int16_t)(event->motion.y / SDL_ZOOM); 123 | } 124 | break; 125 | case SDL_MOUSEMOTION: 126 | last_x = (int16_t)(event->motion.x / SDL_ZOOM); 127 | last_y = (int16_t)(event->motion.y / SDL_ZOOM); 128 | break; 129 | 130 | case SDL_FINGERUP: 131 | left_button_down = false; 132 | last_x = (int16_t) (LV_HOR_RES * event->tfinger.x / SDL_ZOOM); 133 | last_y = (int16_t) (LV_VER_RES * event->tfinger.y / SDL_ZOOM); 134 | break; 135 | case SDL_FINGERDOWN: 136 | left_button_down = true; 137 | last_x = (int16_t) (LV_HOR_RES * event->tfinger.x / SDL_ZOOM); 138 | last_y = (int16_t) (LV_VER_RES * event->tfinger.y / SDL_ZOOM); 139 | break; 140 | case SDL_FINGERMOTION: 141 | last_x = (int16_t) (LV_HOR_RES * event->tfinger.x / SDL_ZOOM); 142 | last_y = (int16_t) (LV_VER_RES * event->tfinger.y / SDL_ZOOM); 143 | break; 144 | } 145 | 146 | } 147 | 148 | 149 | /** 150 | * It is called periodically from the SDL thread to check mouse wheel state 151 | * @param event describes the event 152 | */ 153 | void mousewheel_handler(SDL_Event * event) 154 | { 155 | switch(event->type) { 156 | case SDL_MOUSEWHEEL: 157 | // Scroll down (y = -1) means positive encoder turn, 158 | // so invert it 159 | #ifdef __EMSCRIPTEN__ 160 | /*Escripten scales it wrong*/ 161 | if(event->wheel.y < 0) wheel_diff++; 162 | if(event->wheel.y > 0) wheel_diff--; 163 | #else 164 | wheel_diff = -event->wheel.y; 165 | #endif 166 | break; 167 | case SDL_MOUSEBUTTONDOWN: 168 | if(event->button.button == SDL_BUTTON_MIDDLE) { 169 | wheel_state = LV_INDEV_STATE_PRESSED; 170 | } 171 | break; 172 | case SDL_MOUSEBUTTONUP: 173 | if(event->button.button == SDL_BUTTON_MIDDLE) { 174 | wheel_state = LV_INDEV_STATE_RELEASED; 175 | } 176 | break; 177 | default: 178 | break; 179 | } 180 | } 181 | 182 | 183 | /** 184 | * Called periodically from the SDL thread, store text input or control characters in the buffer. 185 | * @param event describes the event 186 | */ 187 | void keyboard_handler(SDL_Event * event) 188 | { 189 | /* We only care about SDL_KEYDOWN and SDL_TEXTINPUT events */ 190 | switch(event->type) { 191 | case SDL_KEYDOWN: /*Button press*/ 192 | { 193 | const uint32_t ctrl_key = keycode_to_ctrl_key(event->key.keysym.sym); 194 | if (ctrl_key == '\0') 195 | return; 196 | const size_t len = strlen(buf); 197 | if (len < KEYBOARD_BUFFER_SIZE - 1) { 198 | buf[len] = ctrl_key; 199 | buf[len + 1] = '\0'; 200 | } 201 | break; 202 | } 203 | case SDL_TEXTINPUT: /*Text input*/ 204 | { 205 | const size_t len = strlen(buf) + strlen(event->text.text); 206 | if (len < KEYBOARD_BUFFER_SIZE - 1) 207 | strcat(buf, event->text.text); 208 | } 209 | break; 210 | default: 211 | break; 212 | 213 | } 214 | } 215 | 216 | 217 | /** 218 | * Convert a SDL key code to it's LV_KEY_* counterpart or return '\0' if it's not a control character. 219 | * @param sdl_key the key code 220 | * @return LV_KEY_* control character or '\0' 221 | */ 222 | uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key) 223 | { 224 | /*Remap some key to LV_KEY_... to manage groups*/ 225 | 226 | SDL_Keymod mode = SDL_GetModState(); 227 | 228 | switch(sdl_key) { 229 | case SDLK_RIGHT: 230 | case SDLK_KP_PLUS: 231 | return LV_KEY_RIGHT; 232 | 233 | case SDLK_LEFT: 234 | case SDLK_KP_MINUS: 235 | return LV_KEY_LEFT; 236 | 237 | case SDLK_UP: 238 | return LV_KEY_UP; 239 | 240 | case SDLK_DOWN: 241 | return LV_KEY_DOWN; 242 | 243 | case SDLK_ESCAPE: 244 | return LV_KEY_ESC; 245 | 246 | case SDLK_BACKSPACE: 247 | return LV_KEY_BACKSPACE; 248 | 249 | case SDLK_DELETE: 250 | return LV_KEY_DEL; 251 | 252 | case SDLK_KP_ENTER: 253 | case '\r': 254 | return LV_KEY_ENTER; 255 | 256 | case SDLK_TAB: 257 | return (mode & KMOD_SHIFT)? LV_KEY_PREV: LV_KEY_NEXT; 258 | 259 | case SDLK_PAGEDOWN: 260 | return LV_KEY_NEXT; 261 | 262 | case SDLK_PAGEUP: 263 | return LV_KEY_PREV; 264 | 265 | default: 266 | return '\0'; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /driver/SDL/sdl_common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sdl_common.h 3 | * 4 | */ 5 | 6 | #ifndef SDL_COMMON_H 7 | #define SDL_COMMON_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | /********************* 14 | * INCLUDES 15 | *********************/ 16 | 17 | #ifdef LV_LVGL_H_INCLUDE_SIMPLE 18 | #include "lvgl.h" 19 | #else 20 | #include "lvgl/lvgl.h" 21 | #endif 22 | 23 | #ifndef SDL_INCLUDE_PATH 24 | #define SDL_INCLUDE_PATH 25 | #endif 26 | 27 | #include SDL_INCLUDE_PATH 28 | 29 | /********************* 30 | * DEFINES 31 | *********************/ 32 | 33 | #define SDL_HOR_RES monitor_w 34 | #define SDL_VER_RES monitor_h 35 | #define SDL_ZOOM sdl_zoom 36 | #define SDL_FULLSCREEN sdl_fullscreen 37 | 38 | 39 | /********************** 40 | * TYPEDEFS 41 | **********************/ 42 | 43 | /********************** 44 | * GLOBAL PROTOTYPES 45 | **********************/ 46 | 47 | extern float sdl_zoom; 48 | extern bool sdl_fullscreen; 49 | 50 | extern volatile bool sdl_quit_qry; 51 | 52 | /** 53 | * Initialize SDL to be used as display, mouse and mouse wheel drivers. 54 | */ 55 | void sdl_init(int w, int h, float zoom, bool fullscreen); 56 | 57 | /** 58 | * Deinit the monitor and close SDL 59 | */ 60 | void sdl_deinit(void); 61 | 62 | /** 63 | * Flush a buffer to the marked area 64 | * @param drv pointer to driver where this function belongs 65 | * @param area an area where to copy `color_p` 66 | * @param color_p an array of pixel to copy to the `area` part of the screen 67 | */ 68 | void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); 69 | 70 | /** 71 | * Get the current position and state of the mouse 72 | * @param indev_drv pointer to the related input device driver 73 | * @param data store the mouse data here 74 | */ 75 | void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); 76 | 77 | /** 78 | * Get encoder (i.e. mouse wheel) ticks difference and pressed state 79 | * @param indev_drv pointer to the related input device driver 80 | * @param data store the read data here 81 | */ 82 | void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); 83 | 84 | /** 85 | * Get input from the keyboard. 86 | * @param indev_drv pointer to the related input device driver 87 | * @param data store the red data here 88 | */ 89 | void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); 90 | 91 | int quit_filter(void * userdata, SDL_Event * event); 92 | 93 | void mouse_handler(SDL_Event * event); 94 | void mousewheel_handler(SDL_Event * event); 95 | uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key); 96 | void keyboard_handler(SDL_Event * event); 97 | 98 | /********************** 99 | * MACROS 100 | **********************/ 101 | 102 | #ifdef __cplusplus 103 | } /* extern "C" */ 104 | #endif 105 | 106 | #endif /* SDL_COMMON_H */ 107 | -------------------------------------------------------------------------------- /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_disp_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_disp_drv_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 | // We use disp_drv->user_data to pass data from MP to C 187 | // The following lines extract dc and spi 188 | 189 | int dc = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_dc))); 190 | int dt = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_dt))); 191 | mp_buffer_info_t buffer_info; 192 | mp_get_buffer_raise(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_spi)), &buffer_info, MP_BUFFER_READ); 193 | spi_device_handle_t *spi_ptr = buffer_info.buf; 194 | 195 | // Some devices may need a start_x and start_y offset for displays with LCD screens smaller 196 | // than the devices built in frame buffer. 197 | 198 | start_x = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_start_x))); 199 | start_y = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_start_y))); 200 | 201 | int x1 = area->x1 + start_x; 202 | int x2 = area->x2 + start_x; 203 | int y1 = area->y1 + start_y; 204 | int y2 = area->y2 + start_y; 205 | 206 | // Column addresses 207 | 208 | ili9xxx_send_cmd(0x2A, dc, *spi_ptr); 209 | dma_buf[0] = (x1 >> 8) & 0xFF; 210 | dma_buf[1] = x1 & 0xFF; 211 | dma_buf[2] = (x2 >> 8) & 0xFF; 212 | dma_buf[3] = x2 & 0xFF; 213 | 214 | ili9xxx_send_data(dma_buf, dc, *spi_ptr); 215 | 216 | // Page addresses 217 | 218 | ili9xxx_send_cmd(0x2B, dc, *spi_ptr); 219 | dma_buf[0] = (y1 >> 8) & 0xFF; 220 | dma_buf[1] = y1 & 0xFF; 221 | dma_buf[2] = (y2 >> 8) & 0xFF; 222 | dma_buf[3] = y2 & 0xFF; 223 | 224 | ili9xxx_send_data(dma_buf, dc, *spi_ptr); 225 | 226 | // Memory write by DMA, disp_flush_ready when finished 227 | 228 | ili9xxx_send_cmd(0x2C, dc, *spi_ptr); 229 | 230 | size_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); 231 | uint8_t color_size = 2; 232 | 233 | if ( dt == DISPLAY_TYPE_ILI9488 ) { 234 | color_size = 3; 235 | /*Convert ARGB to RGB is required (cut off A-byte)*/ 236 | size_t i; 237 | lv_color32_t* tmp32 = (lv_color32_t*) color_p; 238 | color24_t* tmp24 = (color24_t*) color_p; 239 | 240 | for(i=0; i < size; i++) { 241 | tmp24[i].red = tmp32[i].ch.red; 242 | tmp24[i].green = tmp32[i].ch.green; 243 | tmp24[i].blue = tmp32[i].ch.blue; 244 | } 245 | } 246 | 247 | ili9xxx_send_data_dma(disp_drv, color_p, size * color_size, dc, *spi_ptr); 248 | } 249 | -------------------------------------------------------------------------------- /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 inifinite 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 | // TOOD: 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/modxpt2046.c: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////// 2 | // Includes 3 | ////////////////////////////////////////////////////////////////////////////// 4 | 5 | #include "../include/common.h" 6 | #include "lvgl/src/lv_hal/lv_hal_indev.h" 7 | #include "lvgl/src/lv_core/lv_disp.h" 8 | #include "py/obj.h" 9 | #include "py/runtime.h" 10 | #include "esp_system.h" 11 | #include "driver/gpio.h" 12 | #include "driver/spi_master.h" 13 | 14 | ////////////////////////////////////////////////////////////////////////////// 15 | // Defines 16 | ////////////////////////////////////////////////////////////////////////////// 17 | 18 | #define XPT2046_AVG 4 19 | #define CMD_X_READ 0b10010000 20 | #define CMD_Y_READ 0b11010000 21 | 22 | ////////////////////////////////////////////////////////////////////////////// 23 | // Module definition 24 | ////////////////////////////////////////////////////////////////////////////// 25 | 26 | typedef struct _xpt2046_obj_t 27 | { 28 | mp_obj_base_t base; 29 | 30 | uint8_t mhz; 31 | uint8_t spihost; 32 | uint8_t cs; 33 | uint8_t irq; 34 | 35 | int16_t x_min; 36 | int16_t y_min; 37 | int16_t x_max; 38 | int16_t y_max; 39 | bool x_inv; 40 | bool y_inv; 41 | bool xy_swap; 42 | 43 | spi_device_handle_t spi; 44 | int16_t avg_buf_x[XPT2046_AVG]; 45 | int16_t avg_buf_y[XPT2046_AVG]; 46 | uint8_t avg_last; 47 | 48 | } xpt2046_obj_t; 49 | 50 | // Unfortunately, lvgl doesn't pass user_data to callbacks, so we use this global. 51 | // This means we can have only one active touch driver instance, pointed by this global. 52 | STATIC xpt2046_obj_t *g_xpt2046 = NULL; 53 | 54 | STATIC mp_obj_t mp_activate_xpt2046(mp_obj_t self_in) 55 | { 56 | xpt2046_obj_t *self = MP_OBJ_TO_PTR(self_in); 57 | g_xpt2046 = self; 58 | return mp_const_none; 59 | } 60 | 61 | STATIC mp_obj_t xpt2046_make_new(const mp_obj_type_t *type, 62 | size_t n_args, 63 | size_t n_kw, 64 | const mp_obj_t *all_args) 65 | { 66 | enum{ 67 | ARG_mhz, 68 | ARG_spihost, 69 | ARG_cs, 70 | ARG_irq, 71 | 72 | ARG_x_min, 73 | ARG_y_min, 74 | ARG_x_max, 75 | ARG_y_max, 76 | ARG_x_inv, 77 | ARG_y_inv, 78 | ARG_xy_swap, 79 | }; 80 | 81 | static const mp_arg_t allowed_args[] = { 82 | { MP_QSTR_mhz, MP_ARG_INT, {.u_int = 20}}, 83 | { MP_QSTR_spihost, MP_ARG_INT, {.u_int = HSPI_HOST}}, 84 | { MP_QSTR_cs, MP_ARG_INT, {.u_int = 33}}, 85 | { MP_QSTR_irq, MP_ARG_INT, {.u_int = 25}}, 86 | 87 | { MP_QSTR_x_min, MP_ARG_INT, {.u_int = 1000}}, 88 | { MP_QSTR_y_min, MP_ARG_INT, {.u_int = 1000}}, 89 | { MP_QSTR_x_max, MP_ARG_INT, {.u_int = 3200}}, 90 | { MP_QSTR_y_max, MP_ARG_INT, {.u_int = 2000}}, 91 | { MP_QSTR_x_inv, MP_ARG_BOOL, {.u_obj = mp_const_true}}, 92 | { MP_QSTR_y_inv, MP_ARG_BOOL, {.u_obj = mp_const_true}}, 93 | { MP_QSTR_xy_swap, MP_ARG_BOOL, {.u_obj = mp_const_false}}, 94 | }; 95 | 96 | mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; 97 | mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); 98 | xpt2046_obj_t *self = m_new_obj(xpt2046_obj_t); 99 | self->base.type = type; 100 | 101 | self->mhz = args[ARG_mhz].u_int; 102 | self->spihost = args[ARG_spihost].u_int; 103 | self->cs = args[ARG_cs].u_int; 104 | self->irq = args[ARG_irq].u_int; 105 | 106 | self->x_min = args[ARG_x_min].u_int; 107 | self->y_min = args[ARG_y_min].u_int; 108 | self->x_max = args[ARG_x_max].u_int; 109 | self->y_max = args[ARG_y_max].u_int; 110 | self->x_inv = args[ARG_x_inv].u_bool; 111 | self->y_inv = args[ARG_y_inv].u_bool; 112 | self->xy_swap = args[ARG_xy_swap].u_bool; 113 | return MP_OBJ_FROM_PTR(self); 114 | } 115 | 116 | STATIC mp_obj_t mp_xpt2046_init(mp_obj_t self_in); 117 | STATIC mp_obj_t mp_xpt2046_deinit(mp_obj_t self_in); 118 | STATIC bool xpt2046_read(lv_indev_drv_t * indev_drv, lv_indev_data_t *data); 119 | 120 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_init_xpt2046_obj, mp_xpt2046_init); 121 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_deinit_xpt2046_obj, mp_xpt2046_deinit); 122 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_activate_xpt2046_obj, mp_activate_xpt2046); 123 | DEFINE_PTR_OBJ(xpt2046_read); 124 | 125 | STATIC const mp_rom_map_elem_t xpt2046_locals_dict_table[] = { 126 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mp_init_xpt2046_obj) }, 127 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_deinit_xpt2046_obj) }, 128 | { MP_ROM_QSTR(MP_QSTR_activate), MP_ROM_PTR(&mp_activate_xpt2046_obj) }, 129 | { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&PTR_OBJ(xpt2046_read)) }, 130 | }; 131 | 132 | STATIC MP_DEFINE_CONST_DICT(xpt2046_locals_dict, xpt2046_locals_dict_table); 133 | 134 | STATIC const mp_obj_type_t xpt2046_type = { 135 | { &mp_type_type }, 136 | .name = MP_QSTR_xpt2046, 137 | //.print = xpt2046_print, 138 | .make_new = xpt2046_make_new, 139 | .locals_dict = (mp_obj_dict_t*)&xpt2046_locals_dict, 140 | }; 141 | 142 | STATIC const mp_rom_map_elem_t xpt2046_globals_table[] = { 143 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_xpt2046) }, 144 | { MP_ROM_QSTR(MP_QSTR_xpt2046), (mp_obj_t)&xpt2046_type}, 145 | }; 146 | 147 | 148 | STATIC MP_DEFINE_CONST_DICT ( 149 | mp_module_xpt2046_globals, 150 | xpt2046_globals_table 151 | ); 152 | 153 | const mp_obj_module_t mp_module_xpt2046 = { 154 | .base = { &mp_type_module }, 155 | .globals = (mp_obj_dict_t*)&mp_module_xpt2046_globals 156 | }; 157 | 158 | ////////////////////////////////////////////////////////////////////////////// 159 | // Module implementation 160 | ////////////////////////////////////////////////////////////////////////////// 161 | 162 | STATIC mp_obj_t mp_xpt2046_init(mp_obj_t self_in) 163 | { 164 | esp_err_t ret; 165 | 166 | xpt2046_obj_t *self = MP_OBJ_TO_PTR(self_in); 167 | mp_activate_xpt2046(self_in); 168 | 169 | spi_device_interface_config_t devcfg={ 170 | .clock_speed_hz=self->mhz*1000*1000, //Clock out at DISP_SPI_MHZ MHz 171 | .mode=0, //SPI mode 0 172 | .spics_io_num=-1, //CS pin is set manually 173 | .queue_size=1, 174 | .pre_cb=NULL, 175 | .post_cb=NULL, 176 | .flags=SPI_DEVICE_HALFDUPLEX, 177 | .duty_cycle_pos=128, 178 | }; 179 | 180 | 181 | gpio_set_direction(self->irq, GPIO_MODE_INPUT); 182 | gpio_set_direction(self->cs, GPIO_MODE_OUTPUT); 183 | gpio_set_level(self->cs, 1); 184 | 185 | //Attach the touch controller to the SPI bus 186 | ret=spi_bus_add_device(self->spihost, &devcfg, &self->spi); 187 | if (ret != ESP_OK) nlr_raise( 188 | mp_obj_new_exception_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Failed adding SPI device"))); 189 | 190 | return mp_const_none; 191 | } 192 | 193 | STATIC mp_obj_t mp_xpt2046_deinit(mp_obj_t self_in) 194 | { 195 | //xpt2046_obj_t *self = MP_OBJ_TO_PTR(self_in); 196 | 197 | return mp_const_none; 198 | } 199 | 200 | static void xpt2046_corr(xpt2046_obj_t *self, int16_t * x, int16_t * y); 201 | static void xpt2046_avg(xpt2046_obj_t *self, int16_t * x, int16_t * y); 202 | static uint8_t tp_spi_xchg(xpt2046_obj_t *self, uint8_t data_send); 203 | 204 | /** 205 | * Get the current position and state of the touchpad 206 | * @param indev_drv pointer to the caller input device driver 207 | * @param data store the read data here 208 | * @return false: because no ore data to be read 209 | */ 210 | static bool xpt2046_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) 211 | { 212 | xpt2046_obj_t *self = MP_OBJ_TO_PTR(g_xpt2046 ); 213 | if (!self || (!self->spi)) nlr_raise( 214 | mp_obj_new_exception_msg( 215 | &mp_type_RuntimeError, MP_ERROR_TEXT("xpt2046 instance needs to be created before callback is called!"))); 216 | static int16_t last_x = 0; 217 | static int16_t last_y = 0; 218 | bool valid = true; 219 | uint8_t buf; 220 | 221 | int16_t x = 0; 222 | int16_t y = 0; 223 | 224 | uint8_t irq = gpio_get_level(self->irq); 225 | 226 | if(irq == 0) { 227 | gpio_set_level(self->cs, 0); 228 | tp_spi_xchg(self, CMD_X_READ); /*Start x read*/ 229 | 230 | buf = tp_spi_xchg(self, 0); /*Read x MSB*/ 231 | x = buf << 8; 232 | buf = tp_spi_xchg(self, CMD_Y_READ); /*Until x LSB converted y command can be sent*/ 233 | x += buf; 234 | 235 | buf = tp_spi_xchg(self, 0); /*Read y MSB*/ 236 | y = buf << 8; 237 | 238 | buf = tp_spi_xchg(self, 0); /*Read y LSB*/ 239 | y += buf; 240 | gpio_set_level(self->cs, 1); 241 | 242 | /*Normalize Data*/ 243 | x = x >> 3; 244 | y = y >> 3; 245 | xpt2046_corr(self, &x, &y); 246 | xpt2046_avg(self, &x, &y); 247 | last_x = x; 248 | last_y = y; 249 | 250 | 251 | } else { 252 | x = last_x; 253 | y = last_y; 254 | self->avg_last = 0; 255 | valid = false; 256 | } 257 | 258 | data->point.x = x; 259 | data->point.y = y; 260 | data->state = valid == false ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; 261 | 262 | return valid; 263 | } 264 | 265 | /********************** 266 | * HELPER FUNCTIONS 267 | **********************/ 268 | 269 | static uint8_t tp_spi_xchg(xpt2046_obj_t *self, uint8_t data_send) 270 | { 271 | uint8_t data_rec = 0; 272 | spi_transaction_t t; 273 | memset(&t, 0, sizeof(t)); //Zero out the transaction 274 | t.length = 8; //Length is in bytes, transaction length is in bits. 275 | t.tx_buffer = &data_send; //Data 276 | t.rx_buffer = &data_rec; 277 | 278 | spi_device_queue_trans(self->spi, &t, portMAX_DELAY); 279 | 280 | spi_transaction_t * rt; 281 | spi_device_get_trans_result(self->spi, &rt, portMAX_DELAY); 282 | 283 | return data_rec; 284 | } 285 | 286 | static void xpt2046_corr(xpt2046_obj_t *self, int16_t * x, int16_t * y) 287 | { 288 | if (self->xy_swap){ 289 | int16_t swap_tmp; 290 | swap_tmp = *x; 291 | *x = *y; 292 | *y = swap_tmp; 293 | } 294 | 295 | if((*x) > self->x_min)(*x) -= self->x_min; 296 | else(*x) = 0; 297 | 298 | if((*y) > self->y_min)(*y) -= self->y_min; 299 | else(*y) = 0; 300 | 301 | (*x) = (uint32_t)((uint32_t)(*x) * LV_HOR_RES) / 302 | (self->x_max - self->x_min); 303 | 304 | (*y) = (uint32_t)((uint32_t)(*y) * LV_VER_RES) / 305 | (self->y_max - self->y_min); 306 | 307 | if (self->x_inv){ 308 | (*x) = LV_HOR_RES - (*x); 309 | } 310 | 311 | if (self->y_inv){ 312 | (*y) = LV_VER_RES - (*y); 313 | } 314 | } 315 | 316 | 317 | static void xpt2046_avg(xpt2046_obj_t *self, int16_t * x, int16_t * y) 318 | { 319 | /*Shift out the oldest data*/ 320 | uint8_t i; 321 | for(i = XPT2046_AVG - 1; i > 0 ; i--) { 322 | self->avg_buf_x[i] = self->avg_buf_x[i - 1]; 323 | self->avg_buf_y[i] = self->avg_buf_y[i - 1]; 324 | } 325 | 326 | /*Insert the new point*/ 327 | self->avg_buf_x[0] = *x; 328 | self->avg_buf_y[0] = *y; 329 | if(self->avg_last < XPT2046_AVG) self->avg_last++; 330 | 331 | /*Sum the x and y coordinates*/ 332 | int32_t x_sum = 0; 333 | int32_t y_sum = 0; 334 | for(i = 0; i < self->avg_last ; i++) { 335 | x_sum += self->avg_buf_x[i]; 336 | y_sum += self->avg_buf_y[i]; 337 | } 338 | 339 | /*Normalize the sums*/ 340 | (*x) = (int32_t)x_sum / self->avg_last; 341 | (*y) = (int32_t)y_sum / self->avg_last; 342 | } 343 | -------------------------------------------------------------------------------- /driver/esp32/xpt2046.py: -------------------------------------------------------------------------------- 1 | from machine import Pin 2 | import espidf as esp 3 | import lvgl as lv 4 | 5 | # TODO: Viper/native emmitters don't behave well when module is frozen. 6 | 7 | class xpt2046: 8 | 9 | # Command is 8 bit, but we add another bit as a space before xpt2046 stats sending the response, See Figure 12 on the datasheet 10 | 11 | CMD_X_READ = const(0b100100000) 12 | CMD_Y_READ = const(0b110100000) 13 | CMD_Z1_READ = const(0b101100000) 14 | CMD_Z2_READ = const(0b110000000) 15 | 16 | MAX_RAW_COORD = const((1<<12) - 1) 17 | 18 | def __init__(self, miso=-1, mosi=-1, clk=-1, cs=25, 19 | spihost=esp.HSPI_HOST, half_duplex=True, mhz=5, max_cmds=16, 20 | cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, 21 | transpose = True, samples = 3): 22 | 23 | # Initializations 24 | 25 | if not lv.is_initialized(): 26 | lv.init() 27 | 28 | disp = lv.disp_t.__cast__(None) 29 | self.screen_width = disp.get_hor_res() 30 | self.screen_height = disp.get_ver_res() 31 | self.miso = miso 32 | self.mosi = mosi 33 | self.clk = clk 34 | self.cs = cs 35 | self.spihost = spihost 36 | self.half_duplex = half_duplex 37 | self.mhz = mhz 38 | self.max_cmds = max_cmds 39 | self.cal_x0 = cal_x0 40 | self.cal_y0 = cal_y0 41 | self.cal_x1 = cal_x1 42 | self.cal_y1 = cal_y1 43 | self.transpose = transpose 44 | self.samples = samples 45 | 46 | self.touch_count = 0 47 | self.touch_cycles = 0 48 | 49 | self.spi_init() 50 | 51 | indev_drv = lv.indev_drv_t() 52 | indev_drv.init() 53 | indev_drv.type = lv.INDEV_TYPE.POINTER 54 | indev_drv.read_cb = self.read 55 | indev_drv.register() 56 | 57 | def calibrate(self, x0, y0, x1, y1): 58 | self.cal_x0 = x0 59 | self.cal_y0 = y0 60 | self.cal_x1 = x1 61 | self.cal_y1 = y1 62 | 63 | def spi_init(self): 64 | buscfg = esp.spi_bus_config_t({ 65 | "miso_io_num": self.miso, 66 | "mosi_io_num": self.mosi, 67 | "sclk_io_num": self.clk, 68 | "quadwp_io_num": -1, 69 | "quadhd_io_num": -1, 70 | "max_transfer_sz": 4, 71 | }) 72 | 73 | devcfg_flags = 0 # esp.SPI_DEVICE.NO_DUMMY 74 | if self.half_duplex: 75 | devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX 76 | 77 | devcfg = esp.spi_device_interface_config_t({ 78 | "command_bits": 9, # Actually 8, but need another cycle before xpt starts transmitting response, see Figure 12 on the datasheet. 79 | "clock_speed_hz": self.mhz*1000*1000, 80 | "mode": 0, # SPI mode 0 81 | "spics_io_num": self.cs, # CS pin 82 | "queue_size": self.max_cmds, 83 | "flags": devcfg_flags, 84 | "duty_cycle_pos": 128, 85 | }) 86 | 87 | esp.gpio_pad_select_gpio(self.cs) 88 | 89 | # Initialize the SPI bus, if needed 90 | 91 | if buscfg.miso_io_num >= 0 and \ 92 | buscfg.mosi_io_num >= 0 and \ 93 | buscfg.sclk_io_num >= 0: 94 | 95 | esp.gpio_pad_select_gpio(self.miso) 96 | esp.gpio_pad_select_gpio(self.mosi) 97 | esp.gpio_pad_select_gpio(self.clk) 98 | 99 | esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) 100 | esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) 101 | esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) 102 | esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) 103 | 104 | ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) 105 | if ret != 0: raise RuntimeError("Failed initializing SPI bus") 106 | 107 | # Attach the xpt2046 to the SPI bus 108 | 109 | ptr_to_spi = esp.C_Pointer() 110 | ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) 111 | if ret != 0: raise RuntimeError("Failed adding SPI device") 112 | self.spi = ptr_to_spi.ptr_val 113 | 114 | # Prepare transactions. Each response is 16bit long 115 | 116 | self.trans = [esp.spi_transaction_t({ 117 | 'rx_buffer': bytearray(2), 118 | 'length': 0 if self.half_duplex else 16, 119 | 'rxlength': 16 120 | }) for i in range(0, self.max_cmds)] 121 | 122 | trans_result_ptr = esp.C_Pointer() 123 | 124 | # 125 | # Deinitalize SPI device and bus 126 | # 127 | 128 | def deinit(self): 129 | 130 | print('Deinitializing XPT2046...') 131 | 132 | if self.spi: 133 | 134 | # Pop all pending transaction results 135 | ret = 0 136 | while ret == 0: 137 | ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , 1) 138 | 139 | # Remove device 140 | esp.spi_bus_remove_device(self.spi) 141 | 142 | # Free SPI bus 143 | esp.spi_bus_free(self.spihost) 144 | 145 | 146 | # @micropython.viper 147 | def xpt_cmds(self, cmds): 148 | cmd_count = int(len(cmds)) 149 | for i in range(0, cmd_count): 150 | self.trans[i].cmd = cmds[i] 151 | esp.spi_device_queue_trans(self.spi, self.trans[i], -1) 152 | result = [] 153 | for i in range(0, cmd_count): 154 | esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , -1) 155 | buf = self.trans[i].rx_buffer.__dereference__(2) 156 | value = (int(buf[0]) << 4) + (int(buf[1]) >> 4) # value is in the 12 higher bits, network order 157 | if value == int(self.MAX_RAW_COORD): 158 | value = 0 159 | result.append(value) 160 | return tuple(result) 161 | 162 | # @micropython.viper 163 | def get_med_coords(self, count : int): 164 | mid = count//2 165 | values = [] 166 | for i in range(0, count): 167 | values.append(self.xpt_cmds([self.CMD_X_READ, self.CMD_Y_READ])) 168 | # values = self.xpt_cmds([self.CMD_X_READ]*count + [self.CMD_Y_READ]*count) 169 | # x_values = sorted(values[:count]) 170 | # y_values = sorted(values[count:]) 171 | x_values = sorted([x for x,y in values]) 172 | y_values = sorted([y for x,y in values]) 173 | if int(x_values[0]) == 0 or int(y_values[0]) == 0 : return None 174 | return x_values[mid], y_values[mid] 175 | 176 | # @micropython.viper 177 | def get_coords(self): 178 | med_coords = self.get_med_coords(int(self.samples)) 179 | if not med_coords: return None 180 | if self.transpose: 181 | raw_y, raw_x = med_coords 182 | else: 183 | raw_x, raw_y = med_coords 184 | 185 | if int(raw_x) != 0 and int(raw_y) != 0: 186 | x = ((int(raw_x) - int(self.cal_x0)) * int(self.screen_width)) // (int(self.cal_x1) - int(self.cal_x0)) 187 | y = ((int(raw_y) - int(self.cal_y0)) * int(self.screen_height)) // (int(self.cal_y1) - int(self.cal_y0)) 188 | # print('(%d, %d) ==> (%d, %d)' % (raw_x, raw_y, x, y)) 189 | return x,y 190 | else: return None 191 | 192 | # @micropython.native 193 | def get_pressure(self, factor : int) -> int: 194 | z1, z2, x = self.xpt_cmds([self.CMD_Z1_READ, self.CMD_Z2_READ, self.CMD_X_READ]) 195 | if int(z1) == 0: return -1 196 | return ( (int(x)*factor) / 4096)*( int(z2)/int(z1) - 1) 197 | 198 | start_time_ptr = esp.C_Pointer() 199 | end_time_ptr = esp.C_Pointer() 200 | cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 201 | 202 | # @micropython.native 203 | def read(self, indev_drv, data) -> int: 204 | 205 | esp.get_ccount(self.start_time_ptr) 206 | coords = self.get_coords() 207 | esp.get_ccount(self.end_time_ptr) 208 | 209 | if self.end_time_ptr.int_val > self.start_time_ptr.int_val: 210 | self.touch_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val 211 | self.touch_count += 1 212 | 213 | if coords: 214 | data.point.x ,data.point.y = coords 215 | data.state = lv.INDEV_STATE.PRESSED 216 | return False 217 | data.state = lv.INDEV_STATE.RELEASED 218 | return False 219 | 220 | def stat(self): 221 | return self.touch_cycles / (self.touch_count * self.cycles_in_ms) 222 | 223 | 224 | -------------------------------------------------------------------------------- /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 | self.indev_drv = lv.indev_drv_t() 50 | self.indev_drv.init() 51 | self.indev_drv.type = lv.INDEV_TYPE.POINTER 52 | self.indev_drv.read_cb = self.callback 53 | self.indev_drv.register() 54 | 55 | def callback(self, driver, data): 56 | 57 | def get_point(offset): 58 | x = (sensorbytes[offset ] << 8 | sensorbytes[offset + 1]) & 0x0fff 59 | y = (sensorbytes[offset + 2] << 8 | sensorbytes[offset + 3]) & 0x0fff 60 | if (self.width != -1 and x >= self.width) or (self.height != -1 and y >= self.height): 61 | raise ValueError 62 | x = self.width - x - 1 if self.inv_x else x 63 | y = self.height - y - 1 if self.inv_y else y 64 | (x, y) = (y, x) if self.swap_xy else (x, y) 65 | return { 'x': x, 'y': y } 66 | 67 | data.point = self.points[0] 68 | data.state = self.state 69 | sensorbytes = self.i2c.readfrom_mem(self.addr, 2, 11) 70 | self.presses = sensorbytes[0] 71 | if self.presses > 2: 72 | return False 73 | try: 74 | if self.presses: 75 | self.points[0] = get_point(1) 76 | if self.presses == 2: 77 | self.points[1] = get_point(7) 78 | except ValueError: 79 | return False 80 | if sensorbytes[3] >> 4: 81 | self.points[0], self.points[1] = self.points[1], self.points[0] 82 | data.point = self.points[0] 83 | data.state = self.state = lv.INDEV_STATE.PRESSED if self.presses else lv.INDEV_STATE.RELEASED 84 | return False 85 | -------------------------------------------------------------------------------- /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 backgorund 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 | 106 | Requires ``LV_COLOR_DEPTH=16`` when building lv_micropython to function. 107 | """ 108 | super().__init__( 109 | res=(240, 320), 110 | suppRes=[ 111 | (240, 320), 112 | ], 113 | model=None, 114 | suppModel=None, 115 | bgr=False, 116 | **kw, 117 | ) 118 | 119 | def config_hw(self): 120 | self._run_seq( 121 | [ 122 | (_SLPOUT, None, 100), 123 | (_PWCTRB, b"\x00\xC1\x30"), # Pwr ctrl B 124 | (_POSC, b"\x64\x03\x12\x81"), # Pwr on seq. ctrl 125 | (_DTCA, b"\x85\x00\x78"), # Driver timing ctrl A 126 | (_PWCTRA, b"\x39\x2C\x00\x34\x02"), # Pwr ctrl A 127 | (_PUMPRC, b"\x20"), # Pump ratio control 128 | (_DTCB, b"\x00\x00"), # Driver timing ctrl B 129 | (_PWCTR1, b"\x23"), # Pwr ctrl 1 130 | (_PWCTR2, b"\x10"), # Pwr ctrl 2 131 | (_VMCTR1, b"\x3E\x28"), # VCOM ctrl 1 132 | (_VMCTR2, b"\x86"), # VCOM ctrl 2 133 | (_VSCRSADD, b"\x00"), # Vertical scrolling start address 134 | (_PIXFMT, b"\x55"), # COLMOD: Pixel format 135 | (_FRMCTR1, b"\x00\x18"), # Frame rate ctrl 136 | (_DFUNCTR, b"\x08\x82\x27"), 137 | (_ENABLE3G, b"\x00"), # Enable 3 gamma ctrl 138 | (_GAMMASET, b"\x01"), # Gamma curve selected 139 | ( 140 | _GMCTRP1, 141 | b"\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00", 142 | ), 143 | ( 144 | _GMCTRN1, 145 | b"\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F", 146 | ), 147 | (_SLPOUT, None, 100), 148 | (_DISPLAY_ON, None), 149 | ] 150 | ) 151 | 152 | def apply_rotation(self, rot): 153 | self.rot = rot 154 | if (self.rot % 2) == 0: 155 | self.width, self.height = self.res 156 | else: 157 | self.height, self.width = self.res 158 | self.write_register( 159 | _MADCTL, 160 | bytes([_MADCTL_BGR | _MADCTL_ROTS[self.rot % 4]]), 161 | ) 162 | 163 | 164 | class Ili9341(Ili9341_hw, st77xx.St77xx_lvgl): 165 | def __init__(self, doublebuffer=True, factor=4, **kw): 166 | """See :obj:`Ili9341_hw` for the meaning of the parameters.""" 167 | import lvgl as lv 168 | 169 | Ili9341_hw.__init__(self, **kw) 170 | st77xx.St77xx_lvgl.__init__(self, doublebuffer, factor) 171 | -------------------------------------------------------------------------------- /driver/generic/indev_example.py: -------------------------------------------------------------------------------- 1 | # This is the minimal pure-python lvgl pointer-type driver. It does not read 2 | # any hardware and instead just presses at location (50,100) 3 | # 4 | # In a real driver, always return the last position in data.point, even if 5 | # the data.state goes to 'released' (lv.INDEV_STATE.RELEASED), so maybe keep 6 | # that in self.point. 7 | # 8 | # The 'return False' just means there is no further data to read, which is 9 | # what you want for a pointer-type driver. 10 | # 11 | # To use your driver (after you've made it read some real hardware): 12 | # 13 | # from my_indev import my_indev 14 | # touch = my_indev() 15 | 16 | 17 | import lvgl as lv 18 | 19 | class my_indev: 20 | 21 | def __init__(self): 22 | self.indev_drv = lv.indev_drv_t() 23 | self.indev_drv.init() 24 | self.indev_drv.type = lv.INDEV_TYPE.POINTER 25 | self.indev_drv.read_cb = self.callback 26 | self.indev_drv.register() 27 | 28 | def callback(self, driverptr, dataptr): 29 | # This is where you need to get the actual position from 30 | # the hardware. This just simulates pressed at 50, 100. 31 | data.point = lv.point_t( {'x': 50, 'y': 100} ) 32 | data.state = lv.INDEV_STATE.PRESSED 33 | return False 34 | -------------------------------------------------------------------------------- /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 backgorund 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 underyling screen in pixels, in natural (rot=0) orientation (0..*width* is the range for reported horizontal coordinate) 48 | *height*: height of the underyling 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 | return False 105 | 106 | def __init__(self,spi,spiRate=24_000_000,spiPrereadCb=None,**kw): 107 | '''XPT2046 touchscreen driver for LVGL; cf. documentation of :obj:`Xpt2046_hw` for the meaning of parameters being passed. 108 | 109 | *spiPrereadCb*: call this before reading from SPI; used to block until DMA transfer is complete (when sharing SPI bus). 110 | *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. 111 | ''' 112 | super().__init__(spi=spi,**kw) 113 | self.spiRate=spiRate 114 | self.spiPrereadCb=spiPrereadCb 115 | 116 | import lvgl as lv 117 | if not lv.is_initialized(): lv.init() 118 | 119 | self.indev_drv=lv.indev_drv_t() 120 | self.indev_drv.init() 121 | self.indev_drv.type=lv.INDEV_TYPE.POINTER 122 | self.indev_drv.read_cb=self.indev_drv_read_cb 123 | self.indev_drv.register() 124 | -------------------------------------------------------------------------------- /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 const mp_obj_type_t ptr_obj_type = {\ 38 | { &mp_type_type },\ 39 | .name = ptr_type_qstr,\ 40 | .buffer_p = { .get_buffer = mp_ptr_get_buffer }\ 41 | } 42 | 43 | #define DEFINE_PTR_OBJ(ptr_global)\ 44 | DEFINE_PTR_OBJ_TYPE(ptr_global ## _type, MP_QSTR_ ## ptr_global);\ 45 | STATIC const mp_ptr_t PTR_OBJ(ptr_global) = {\ 46 | { &ptr_global ## _type },\ 47 | &ptr_global\ 48 | } 49 | 50 | #define NEW_PTR_OBJ(name, value)\ 51 | ({\ 52 | DEFINE_PTR_OBJ_TYPE(ptr_obj_type, MP_QSTR_ ## name);\ 53 | mp_ptr_t *self = m_new_obj(mp_ptr_t);\ 54 | self->base.type = &ptr_obj_type;\ 55 | self->ptr = value;\ 56 | MP_OBJ_FROM_PTR(self);\ 57 | }) 58 | 59 | #endif // __LVMP_DRV_COMMON_H 60 | -------------------------------------------------------------------------------- /driver/js/lv_timer.py: -------------------------------------------------------------------------------- 1 | # Stub Timer implementation for the JS port. 2 | # 3 | # This does absolutely nothing beyond provide a class, so it will 4 | # not be suitable for any application which needs a timer. 5 | # 6 | # MIT license; Copyright (c) 2021 embeddedt 7 | 8 | 9 | class Timer: 10 | 11 | PERIODIC = 0 12 | ONE_SHOT = 1 13 | 14 | def __init__(self, id): 15 | pass 16 | 17 | def init(self, mode=PERIODIC, period=-1, callback=None): 18 | pass 19 | 20 | def deinit(self): 21 | pass 22 | -------------------------------------------------------------------------------- /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_drv = lv.indev_drv_t() 47 | self.indev_drv.init() 48 | self.indev_drv.type = lv.INDEV_TYPE.POINTER 49 | self.indev_drv.read_cb = self.mouse_read 50 | self.indev = self.indev_drv.register() 51 | 52 | def mouse_read(self, indev_drv, data) -> int: 53 | 54 | # Check if there is input to be read from evdev 55 | if not self.poll.poll()[0][1] & select.POLLIN: 56 | return 0 57 | 58 | # Read and parse evdev mouse data 59 | mouse_data = ustruct.unpack('bbb',self.evdev.read(3)) 60 | 61 | # Data is relative, update coordinates 62 | data.point.x += mouse_data[1] 63 | data.point.y -= mouse_data[2] 64 | 65 | # Handle coordinate overflow cases 66 | data.point.x = min(data.point.x, self.hor_res - 1) 67 | data.point.y = min(data.point.y, self.ver_res - 1) 68 | data.point.x = max(data.point.x, 0) 69 | data.point.y = max(data.point.y, 0) 70 | 71 | # Update "pressed" status 72 | data.state = lv.INDEV_STATE.PRESSED if ((mouse_data[0] & 1) == 1) else lv.INDEV_STATE.RELEASED 73 | 74 | # Draw cursor, if needed 75 | if self.cursor: self.cursor(data) 76 | return 0 77 | 78 | def delete(self): 79 | self.evdev.close() 80 | if self.cursor and hasattr(self.cursor, 'delete'): 81 | self.cursor.delete() 82 | self.indev.enable(False) 83 | -------------------------------------------------------------------------------- /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": 8 | uctypes.INT32, 48 | "sigev_notify": 12 | 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/linux/modfb.c: -------------------------------------------------------------------------------- 1 | 2 | /********************* 3 | * INCLUDES 4 | *********************/ 5 | 6 | #include "../../lib/lv_bindings/lvgl/lvgl.h" 7 | #include "../include/common.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /********************* 20 | * DEFINES 21 | *********************/ 22 | 23 | #ifndef FBDEV_PATH 24 | #define FBDEV_PATH "/dev/fb0" 25 | #endif 26 | 27 | #define LV_TICK_RATE 20 28 | 29 | /********************** 30 | * FORWARD DECLARATIONS 31 | **********************/ 32 | 33 | bool fbdev_init(void); 34 | void fbdev_deinit(void); 35 | void fbdev_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); 36 | 37 | /********************** 38 | * STATIC VARIABLES 39 | **********************/ 40 | 41 | static struct fb_var_screeninfo vinfo; 42 | static struct fb_fix_screeninfo finfo; 43 | static char * fbp = 0; 44 | static long int screensize = 0; 45 | static int fbfd = -1; 46 | static pthread_t tid; 47 | static pthread_t mp_thread; 48 | static bool auto_refresh = true; 49 | 50 | /********************** 51 | * MODULE DEFINITION 52 | **********************/ 53 | 54 | STATIC inline bool fbdev_active() 55 | { 56 | return fbfd >= 0; 57 | } 58 | 59 | STATIC mp_obj_t mp_lv_task_handler(mp_obj_t arg) 60 | { 61 | lv_task_handler(); 62 | return mp_const_none; 63 | } 64 | 65 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_lv_task_handler_obj, mp_lv_task_handler); 66 | 67 | STATIC void* tick_thread(void * data) 68 | { 69 | (void)data; 70 | 71 | while(fbdev_active()) { 72 | usleep(LV_TICK_RATE * 1000); /*Sleep for 1 millisecond*/ 73 | lv_tick_inc(LV_TICK_RATE); /*Tell LittelvGL that LV_TICK_RATE milliseconds were elapsed*/ 74 | mp_sched_schedule((mp_obj_t)&mp_lv_task_handler_obj, mp_const_none); 75 | pthread_kill(mp_thread, SIGUSR1); // interrupt REPL blocking input. See handle_sigusr1 76 | } 77 | 78 | return NULL; 79 | } 80 | 81 | static void handle_sigusr1(int signo) 82 | { 83 | // Let the signal pass. blocking function would return E_INTR. 84 | // This would cause a call to "mp_handle_pending" even when 85 | // waiting for user input. 86 | // See https://github.com/micropython/micropython/pull/5723 87 | } 88 | 89 | STATIC mp_obj_t mp_init_fb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) 90 | { 91 | enum { ARG_auto_refresh }; 92 | static const mp_arg_t allowed_args[] = { 93 | { MP_QSTR_auto_refresh, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, 94 | }; 95 | 96 | // parse args 97 | mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; 98 | mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); 99 | 100 | bool init_succeeded = fbdev_init(); 101 | if (!init_succeeded) return mp_const_false; 102 | 103 | int err = 0; 104 | auto_refresh = args[ARG_auto_refresh].u_bool; 105 | if (auto_refresh) { 106 | err = pthread_create(&tid, NULL, &tick_thread, NULL); 107 | 108 | mp_thread = pthread_self(); 109 | struct sigaction sa; 110 | sa.sa_handler = handle_sigusr1; 111 | sa.sa_flags = 0; 112 | sigemptyset(&sa.sa_mask); 113 | if (sigaction(SIGUSR1, &sa, NULL) == -1) { 114 | perror("sigaction"); 115 | exit(1); 116 | } 117 | } 118 | 119 | return err == 0? mp_const_true: mp_const_false; 120 | } 121 | 122 | STATIC mp_obj_t mp_deinit_fb() 123 | { 124 | fbdev_deinit(); 125 | if (auto_refresh) { 126 | pthread_join(tid, NULL); 127 | } 128 | return mp_const_none; 129 | } 130 | 131 | STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mp_init_fb_obj, 0, mp_init_fb); 132 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_deinit_fb_obj, mp_deinit_fb); 133 | 134 | DEFINE_PTR_OBJ(fbdev_flush); 135 | 136 | STATIC const mp_rom_map_elem_t fb_globals_table[] = { 137 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_fb) }, 138 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mp_init_fb_obj) }, 139 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_deinit_fb_obj) }, 140 | { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&PTR_OBJ(fbdev_flush))}, 141 | }; 142 | 143 | 144 | STATIC MP_DEFINE_CONST_DICT ( 145 | mp_module_fb_globals, 146 | fb_globals_table 147 | ); 148 | 149 | const mp_obj_module_t mp_module_fb = { 150 | .base = { &mp_type_module }, 151 | .globals = (mp_obj_dict_t*)&mp_module_fb_globals 152 | }; 153 | 154 | MP_REGISTER_MODULE(MP_QSTR_fb, mp_module_fb); 155 | 156 | /********************** 157 | * IMPLEMENTATION 158 | **********************/ 159 | 160 | bool fbdev_init(void) 161 | { 162 | // Open the file for reading and writing 163 | fbfd = open(FBDEV_PATH, O_RDWR); 164 | if(fbfd == -1) { 165 | perror("Error: cannot open framebuffer device"); 166 | return false; 167 | } 168 | printf("The framebuffer device was opened successfully.\n"); 169 | 170 | // Get fixed screen information 171 | if(ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) { 172 | perror("Error reading fixed information"); 173 | return false; 174 | } 175 | 176 | // Get variable screen information 177 | if(ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) { 178 | perror("Error reading variable information"); 179 | return false; 180 | } 181 | 182 | printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); 183 | 184 | // Figure out the size of the screen in bytes 185 | screensize = finfo.line_length * vinfo.yres; 186 | 187 | // Map the device to memory 188 | fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); 189 | if((intptr_t)fbp == -1) { 190 | perror("Error: failed to map framebuffer device to memory"); 191 | return false; 192 | } 193 | printf("The framebuffer device was mapped to memory successfully.\n"); 194 | return true; 195 | 196 | } 197 | 198 | void fbdev_deinit(void) 199 | { 200 | close(fbfd); 201 | fbfd = -1; 202 | } 203 | 204 | /** 205 | * Flush a buffer to the marked area 206 | * @param area->x1 left coordinate 207 | * @param area->y1 top coordinate 208 | * @param area->x2 right coordinate 209 | * @param area->y2 bottom coordinate 210 | * @param color_p an array of colors 211 | */ 212 | void fbdev_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 213 | { 214 | if(fbp == NULL || 215 | area->x2 < 0 || 216 | area->y2 < 0 || 217 | area->x1 > (int32_t)vinfo.xres - 1 || 218 | area->y1 > (int32_t)vinfo.yres - 1) { 219 | lv_disp_flush_ready(disp_drv); 220 | return; 221 | } 222 | 223 | /*Truncate the area to the screen*/ 224 | int32_t act_x1 = area->x1 < 0 ? 0 : area->x1; 225 | int32_t act_y1 = area->y1 < 0 ? 0 : area->y1; 226 | int32_t act_x2 = area->x2 > (int32_t)vinfo.xres - 1 ? (int32_t)vinfo.xres - 1 : area->x2; 227 | int32_t act_y2 = area->y2 > (int32_t)vinfo.yres - 1 ? (int32_t)vinfo.yres - 1 : area->y2; 228 | 229 | long int location = 0; 230 | long int byte_location = 0; 231 | unsigned char bit_location = 0; 232 | 233 | /*32 or 24 bit per pixel*/ 234 | if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) { 235 | uint32_t * fbp32 = (uint32_t *)fbp; 236 | int32_t x; 237 | int32_t y; 238 | for(y = act_y1; y <= act_y2; y++) { 239 | for(x = act_x1; x <= act_x2; x++) { 240 | location = (x + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 4; 241 | fbp32[location] = color_p->full; 242 | color_p++; 243 | } 244 | 245 | color_p += area->x2 - act_x2; 246 | } 247 | } 248 | /*16 bit per pixel*/ 249 | else if(vinfo.bits_per_pixel == 16) { 250 | uint16_t * fbp16 = (uint16_t *)fbp; 251 | int32_t x; 252 | int32_t y; 253 | for(y = act_y1; y <= act_y2; y++) { 254 | for(x = act_x1; x <= act_x2; x++) { 255 | location = (x + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 2; 256 | fbp16[location] = color_p->full; 257 | color_p++; 258 | } 259 | 260 | color_p += area->x2 - act_x2; 261 | } 262 | } 263 | /*8 bit per pixel*/ 264 | else if(vinfo.bits_per_pixel == 8) { 265 | uint8_t * fbp8 = (uint8_t *)fbp; 266 | int32_t x; 267 | int32_t y; 268 | for(y = act_y1; y <= act_y2; y++) { 269 | for(x = act_x1; x <= act_x2; x++) { 270 | location = (x + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length; 271 | fbp8[location] = color_p->full; 272 | color_p++; 273 | } 274 | 275 | color_p += area->x2 - act_x2; 276 | } 277 | } 278 | /*1 bit per pixel*/ 279 | else if(vinfo.bits_per_pixel == 1) { 280 | uint8_t * fbp8 = (uint8_t *)fbp; 281 | int32_t x; 282 | int32_t y; 283 | for(y = act_y1; y <= act_y2; y++) { 284 | for(x = act_x1; x <= act_x2; x++) { 285 | location = (x + vinfo.xoffset) + (y + vinfo.yoffset) * vinfo.xres; 286 | byte_location = location / 8; /* find the byte we need to change */ 287 | bit_location = location % 8; /* inside the byte found, find the bit we need to change */ 288 | fbp8[byte_location] &= ~(((uint8_t)(1)) << bit_location); 289 | fbp8[byte_location] |= ((uint8_t)(color_p->full)) << bit_location; 290 | color_p++; 291 | } 292 | 293 | color_p += area->x2 - act_x2; 294 | } 295 | } else { 296 | /*Not supported bit per pixel*/ 297 | } 298 | 299 | //May be some direct update command is required 300 | //ret = ioctl(state->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)rect)); 301 | 302 | lv_disp_flush_ready(disp_drv); 303 | } 304 | 305 | -------------------------------------------------------------------------------- /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_drv_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_drv_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_drv_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 | lv_coord_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 | lv_coord_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 | lv_coord_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_drv_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_drv_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 alocate 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 | 21 | import display_driver_utils # Default implementation. Replace by your driver 22 | driver = display_driver_utils.driver() 23 | 24 | # FS driver init. 25 | 26 | fs_drv = lv.fs_drv_t() 27 | fs_driver.fs_register(fs_drv, 'S') 28 | 29 | ''' 30 |  load the font file from filesystem(For me is flash ) 31 |  How to convert font files refer here: https://github.com/lvgl/lv_font_conv 32 |  font-PHT-en-20.bin: 33 |    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 34 |  font-PHT-cn-20.bin: 35 |    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 36 |  font-PHT-jp-20.bin: 37 |    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 38 | ''' 39 | scr = lv.scr_act() 40 | scr.clean() 41 | 42 | myfont_cn = lv.font_load("S:%s/font/font-PHT-cn-20.bin" % script_path) 43 | 44 | label1 = lv.label(scr) 45 | label1.set_style_text_font(myfont_cn, 0) # set the font 46 | label1.set_text("上中下乎") 47 | label1.align(lv.ALIGN.CENTER, 0, -25) 48 | 49 | myfont_en = lv.font_load("S:%s/font/font-PHT-en-20.bin" % script_path) 50 | 51 | label2 = lv.label(scr) 52 | label2.set_style_text_font(myfont_en, 0) # set the font 53 | label2.set_text("Hello LVGL!") 54 | label2.align(lv.ALIGN.CENTER, 0, 25) 55 | 56 | 57 | myfont_jp = lv.font_load("S:%s/font/font-PHT-jp-20.bin" % script_path) 58 | 59 | label3 = lv.label(scr) 60 | label3.set_style_text_font(myfont_jp, 0) # set the font 61 | label3.set_text("こんにちはありがとう") 62 | label3.align(lv.ALIGN.CENTER, 0, 0) 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/blue_flower_32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/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("Contructor 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.RES.OK: 78 | return 79 | 80 | code = e.get_code() 81 | obj = e.get_target() 82 | 83 | # print("Event %s" % get_member_name(lv.EVENT, code)) 84 | 85 | if code == lv.EVENT.DRAW_MAIN: 86 | # Draw the widget 87 | draw_ctx = e.get_draw_ctx() 88 | self.draw(obj, draw_ctx) 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_rect_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 | 107 | obj.points = [ 108 | {'x':area.x1 + area.get_width()//2, 109 | 'y':area.y2 if obj.get_state() & lv.STATE.CHECKED else area.y1}, 110 | {'x':area.x2, 111 | 'y':area.y1 + area.get_height()//2}, 112 | {'x':area.x1, 113 | 'y':area.y1 + area.get_height()//2}, 114 | ] 115 | 116 | obj.valid = True 117 | 118 | def draw(self, obj, draw_ctx): 119 | # If object invalidated, recalculate its parameters 120 | if not obj.valid: 121 | self.calc(obj) 122 | 123 | # Draw the custom widget 124 | draw_ctx.polygon(obj.draw_desc, obj.points, len(obj.points)) 125 | 126 | ############################################################################## 127 | # A Python class to wrap the LVGL custom widget 128 | ############################################################################## 129 | 130 | class CustomWidget(): 131 | 132 | # An instance of a widget-class to be used for creating custom widgets 133 | d = lv.disp_get_default() 134 | dpi = d.get_dpi() 135 | cls = CustomWidgetClass(dpi, dpi) 136 | 137 | @staticmethod 138 | def get_class(): 139 | # Return the internal LVGL class 140 | return CustomWidget.cls.get_class() 141 | 142 | def __new__(cls, parent): 143 | # Return a new lv object instead of CustomWidget, 144 | # but first bind the LVGL object with CustomWidgetWrapper 145 | wrapper = cls.CustomWidgetWrapper(parent) 146 | return wrapper.lv_obj 147 | 148 | class CustomWidgetWrapper(): 149 | 150 | def __init__(self, parent): 151 | 152 | # Create the LVGL object from class 153 | self.lv_obj = CustomWidget.cls.create(parent) 154 | 155 | # Associates the LVGL object with CustomWidget wrapper 156 | self.lv_obj.set_user_data(self) 157 | 158 | # Initalize the object 159 | self.lv_obj.class_init_obj() 160 | 161 | 162 | def __getattr__(self, attr): 163 | # Provide access to LVGL object functions 164 | # print("__getattr__(%s, %s)" % (repr(self), repr(attr))) 165 | return getattr(self.lv_obj, attr) 166 | 167 | def __repr__(self): 168 | return "Custom Widget" 169 | 170 | ############################################################################## 171 | # A theme to apply styles to the custom widget 172 | ############################################################################## 173 | 174 | class CustomTheme(lv.theme_t): 175 | 176 | class Style(lv.style_t): 177 | def __init__(self): 178 | super().__init__() 179 | self.init() 180 | 181 | # Default color is gray 182 | self.set_bg_color(lv.palette_main(lv.PALETTE.GREY)) 183 | 184 | # Child elements are centered 185 | self.set_layout(lv.LAYOUT_FLEX.value) 186 | self.set_flex_main_place(lv.FLEX_ALIGN.CENTER) 187 | self.set_flex_cross_place(lv.FLEX_ALIGN.CENTER) 188 | self.set_flex_track_place(lv.FLEX_ALIGN.CENTER) 189 | 190 | class PressedStyle(lv.style_t): 191 | def __init__(self): 192 | super().__init__() 193 | self.init() 194 | 195 | # Pressed color is blue 196 | self.set_bg_color(lv.palette_main(lv.PALETTE.BLUE)) 197 | 198 | 199 | def __init__(self): 200 | super().__init__() 201 | self.custom_style = CustomTheme.Style() 202 | self.custom_pressed_style = CustomTheme.PressedStyle() 203 | 204 | # This theme is based on active theme 205 | base_theme = lv.theme_get_from_obj(lv.scr_act()) 206 | 207 | # This theme will be applied only after base theme is applied 208 | self.set_parent(base_theme) 209 | 210 | # Set the "apply" callback of this theme to a custom callback 211 | self.set_apply_cb(self.apply) 212 | 213 | # Activate this theme on the default display 214 | lv.disp_get_default().set_theme(self) 215 | 216 | def apply(self, theme, obj): 217 | # Apply this theme on CustomWidget class 218 | if obj.get_class() == CustomWidget.get_class(): 219 | obj.add_style(self.custom_style, lv.PART.MAIN) 220 | obj.add_style(self.custom_pressed_style, lv.PART.MAIN | lv.STATE.PRESSED) 221 | 222 | 223 | ############################################################################## 224 | # Main program - create screen and widgets 225 | ############################################################################## 226 | 227 | # Create the theme for the custom widget 228 | theme = CustomTheme() 229 | 230 | # Create a screen with flex layout 231 | scr = lv.scr_act() 232 | scr.set_flex_flow(lv.FLEX_FLOW.COLUMN) 233 | scr.set_flex_align(lv.FLEX_ALIGN.SPACE_EVENLY, lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.CENTER) 234 | 235 | # Add a button with a label 236 | btn = lv.btn(scr) 237 | l1 = lv.label(btn) 238 | l1.set_text("Hello!") 239 | 240 | # Add a custom widget with a label 241 | customWidget = CustomWidget(scr) 242 | l2 = lv.label(customWidget) 243 | l2.set_text("Click me!") 244 | 245 | # Add click events to both button and custom widget 246 | def event_cb(e): 247 | print("%s Clicked!" % repr(e.get_target())) 248 | 249 | for widget in [btn, customWidget]: 250 | widget.add_event_cb(event_cb, lv.EVENT.CLICKED, None) 251 | 252 | -------------------------------------------------------------------------------- /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 | lv.init() 14 | event_loop = lv_utils.event_loop() 15 | lcd.init(w=hres, h=vres) 16 | disp_buf1 = lv.disp_drwa_buf_t() 17 | buf1_1 = bytearray(hres * 10 * lv.color_t.__SIZE__) 18 | buf1_2 = bytearray(hres * 10 * lv.color_t.__SIZE__) 19 | disp_buf1.init(buf1_1, buf1_2, len(buf1_1) // lv.color_t.__SIZE__) 20 | disp_drv = lv.disp_drv_t() 21 | disp_drv.init() 22 | disp_drv.draw_buf = disp_buf1 23 | disp_drv.flush_cb = lcd.flush 24 | disp_drv.gpu_blend_cb = lcd.gpu_blend 25 | disp_drv.gpu_fill_cb = lcd.gpu_fill 26 | disp_drv.hor_res = hres 27 | disp_drv.ver_res = vres 28 | disp_drv.register() 29 | 30 | indev_drv = lv.indev_drv_t() 31 | indev_drv.init() 32 | indev_drv.type = lv.INDEV_TYPE.POINTER 33 | indev_drv.read_cb = lcd.ts_read 34 | indev_drv.register() 35 | 36 | except ImportError: 37 | import display_driver 38 | 39 | 40 | scr1 = lv.obj() 41 | scr2 = lv.obj() 42 | lv.scr_load(scr1) 43 | 44 | slider = lv.slider(scr2) 45 | slider.set_width(150) 46 | slider.align(lv.ALIGN.TOP_MID, 0, 15) 47 | 48 | btn1 = lv.btn(scr1) 49 | btn1.align(lv.ALIGN.TOP_RIGHT, -5, 5) 50 | label = lv.label(btn1) 51 | label.set_text(">") 52 | 53 | btn2 = lv.btn(scr2) 54 | btn2.align(lv.ALIGN.TOP_LEFT, 5, 5) 55 | label2 = lv.label(btn2) 56 | label2.set_text("<") 57 | 58 | led1 = lv.led(scr2) 59 | led1.align(lv.ALIGN.CENTER, 0, 0) 60 | led1.set_brightness(slider.get_value() * 2) 61 | # led1.set_drag(True) 62 | led1.set_size(20,20) 63 | 64 | 65 | def slider_event_cb(event): 66 | led1.set_brightness(slider.get_value() * 2) 67 | 68 | def btn1_event_cb(event): 69 | lv.scr_load(scr2) 70 | 71 | 72 | def btn2_event_cb(event): 73 | lv.scr_load(scr1) 74 | 75 | slider.add_event_cb(slider_event_cb, lv.EVENT.VALUE_CHANGED, None) 76 | btn1.add_event_cb(btn1_event_cb, lv.EVENT.CLICKED, None) 77 | btn2.add_event_cb(btn2_event_cb, lv.EVENT.CLICKED, None) 78 | 79 | # Create a keyboard 80 | kb = lv.keyboard(scr1) 81 | # kb.set_cursor_manage(True) 82 | 83 | # Create a text area. The keyboard will write here 84 | ta = lv.textarea(scr1) 85 | ta.set_width(450) 86 | ta.set_height(70) 87 | ta.align(lv.ALIGN.CENTER, 0, 0) 88 | ta.set_text("") 89 | 90 | # Assign the text area to the keyboard 91 | kb.set_textarea(ta) 92 | 93 | # Create a Spinner object 94 | spin = lv.spinner(scr2, 1000, 100) 95 | spin.set_size(100, 100) 96 | spin.align(lv.ALIGN.CENTER, 0, 0) 97 | # spin.set_type(lv.spinner.TYPE.FILLSPIN_ARC) 98 | -------------------------------------------------------------------------------- /examples/fb_test.py: -------------------------------------------------------------------------------- 1 | # A simple test for Linux Frame Buffer 2 | # Imports fb (frame buffer) module and uses it as lvgl display driver 3 | # Registers mouse evdev and shows line cursor 4 | # then shows a button on screen. 5 | 6 | import lvgl as lv 7 | import fb 8 | import evdev 9 | 10 | lv.init() 11 | fb.init() 12 | 13 | # Register FB display driver 14 | 15 | disp_buf1 = lv.disp_draw_buf_t() 16 | buf1_1 = bytes(480*10) 17 | disp_buf1.init(buf1_1, None, len(buf1_1)//4) 18 | disp_drv = lv.disp_drv_t() 19 | disp_drv.init() 20 | disp_drv.draw_buf = disp_buf1 21 | disp_drv.flush_cb = fb.flush 22 | disp_drv.hor_res = 480 23 | disp_drv.ver_res = 320 24 | disp_drv.register() 25 | 26 | # Create a screen and a button 27 | 28 | scr = lv.obj() 29 | btn = lv.btn(scr) 30 | btn.align(lv.ALIGN.CENTER, 0, 0) 31 | label = lv.label(btn) 32 | label.set_text("Hello World!") 33 | 34 | # Regiser mouse device and crosshair cursor 35 | 36 | mouse = evdev.mouse_indev(scr) 37 | 38 | # Load the screen 39 | 40 | lv.scr_load(scr) 41 | -------------------------------------------------------------------------------- /examples/font/font-PHT-cn-20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/font/font-PHT-cn-20.bin -------------------------------------------------------------------------------- /examples/font/font-PHT-en-20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/font/font-PHT-en-20.bin -------------------------------------------------------------------------------- /examples/font/font-PHT-jp-20.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/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.btn(scr) 32 | lbl=lv.label(btn) 33 | lbl.set_text("Press me!") 34 | btn.center() 35 | btn.add_event_cb(lambda event: print('Button clicked!'),lv.EVENT.CLICKED,None) 36 | lv.scr_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/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_0.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_v.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_vx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_vx.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_vxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_vxy.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_vy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_vy.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_x.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_xy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/examples/madctl/images/madctl_xy.png -------------------------------------------------------------------------------- /examples/madctl/images/madctl_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/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.btn(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/imliubo/lv_binding_micropython/c544c51c97b38907709a0cd7ea7ca8ab96410cbb/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_IMG_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 | scr = lv.scr_act() 19 | lv.img.cache_set_size(2) 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_img_dsc = lv.img_dsc_t({ 31 | 'data_size': len(png_data), 32 | 'data': png_data 33 | }) 34 | 35 | # Create an image using the decoder 36 | 37 | img1 = lv.img(scr) 38 | img1.set_src(png_img_dsc) 39 | img1.set_pos(100,50) 40 | 41 | # Create an image from a symbol 42 | 43 | img2 = lv.img(scr) 44 | img2.set_src(lv.SYMBOL.OK + " Accept") 45 | img2.set_pos(100,200) 46 | 47 | # Drag handler 48 | 49 | def drag_event_handler(e): 50 | self = e.get_target() 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 img in [img1, img2]: 61 | img.add_flag(img.FLAG.CLICKABLE) 62 | img.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 initialize ili9341/xpt2046 41 | try: 42 | from ili9XXX import ili9341 43 | from xpt2046 import xpt2046 44 | 45 | # Initialize and register drivers 46 | 47 | disp = ili9341(dc=32, cs=33, power=-1, backlight=-1, asynchronous=True, initialize=True) 48 | touch = xpt2046() 49 | 50 | except ImportError: 51 | pass 52 | 53 | # Try to initialize SDL 54 | try: 55 | import SDL 56 | 57 | # Initialize driver 58 | 59 | SDL.init(w=HOR_RES, h=VER_RES, auto_refresh=False) 60 | event_loop = lv_utils.event_loop(asynchronous=True) 61 | 62 | # Register SDL display driver. 63 | 64 | disp_buf1 = lv.disp_draw_buf_t() 65 | buf1_1 = bytearray(HOR_RES * 10) 66 | disp_buf1.init(buf1_1, None, len(buf1_1)//4) 67 | disp_drv = lv.disp_drv_t() 68 | disp_drv.init() 69 | disp_drv.draw_buf = disp_buf1 70 | disp_drv.flush_cb = SDL.monitor_flush 71 | disp_drv.hor_res = HOR_RES 72 | disp_drv.ver_res = VER_RES 73 | disp_drv.register() 74 | 75 | # Regsiter SDL mouse driver 76 | 77 | indev_drv = lv.indev_drv_t() 78 | indev_drv.init() 79 | indev_drv.type = lv.INDEV_TYPE.POINTER 80 | indev_drv.read_cb = SDL.mouse_read 81 | indev_drv.register() 82 | 83 | except ImportError: 84 | pass 85 | 86 | 87 | ################################################################################################## 88 | # Stylized Message Box class 89 | ################################################################################################## 90 | 91 | class MsgBox(lv.win): 92 | 93 | def drag_event_handler(self, e): 94 | self.move_foreground() 95 | indev = lv.indev_get_act() 96 | indev.get_vect(self.vect) 97 | x = self.get_x() + self.vect.x 98 | y = self.get_y() + self.vect.y 99 | self.set_pos(x, y) 100 | 101 | def __init__(self, parent): 102 | super().__init__(parent, 20) 103 | self.vect = lv.point_t() 104 | 105 | self.set_size(100,80) 106 | self.add_title("Pop") 107 | msg_box_close_btn = self.add_btn(lv.SYMBOL.CLOSE, 20) 108 | msg_box_close_btn.add_event_cb(lambda e: self.close_msg_box(), lv.EVENT.RELEASED, None) 109 | 110 | header = self.get_header() 111 | header.set_style_bg_color(lv.color_hex3(0xFEE), lv.PART.MAIN) 112 | 113 | content = self.get_content() 114 | content.set_style_bg_color(lv.color_hex3(0xFFF), lv.PART.MAIN) 115 | 116 | self.set_style_border_width(4, lv.PART.MAIN) 117 | self.set_style_border_color(lv.color_hex3(0xF88), lv.PART.MAIN) 118 | self.set_style_shadow_color(lv.color_hex3(0x000), lv.PART.MAIN) 119 | self.set_style_shadow_opa(50, lv.PART.MAIN) 120 | self.set_style_shadow_width(20, lv.PART.MAIN) 121 | self.set_style_shadow_ofs_x(10, lv.PART.MAIN) 122 | self.set_style_shadow_ofs_y(10, lv.PART.MAIN) 123 | self.set_style_shadow_spread(0, lv.PART.MAIN) 124 | self.set_style_radius(10, lv.PART.MAIN) 125 | 126 | self.label = lv.label(content) 127 | 128 | for element in [content, header]: 129 | element.add_event_cb(self.drag_event_handler, lv.EVENT.PRESSING, None) 130 | 131 | self.opened = True 132 | 133 | def is_open(self): 134 | return self.opened 135 | 136 | def close_msg_box(self): 137 | if self.is_open(): 138 | self.anim = lv.anim_t() 139 | self.anim.init() 140 | self.anim.set_var(self) 141 | self.anim.set_time(500) 142 | self.anim.set_values(lv.OPA.COVER,lv.OPA.TRANSP) 143 | self.anim.set_custom_exec_cb(lambda obj, val: 144 | self.set_style_opa(val, lv.PART.MAIN)) 145 | self.anim.set_path_cb(lv.anim_t.path_ease_in) 146 | self.anim.set_ready_cb(lambda a: self.del_async()) 147 | lv.anim_t.start(self.anim) 148 | self.opened = False 149 | 150 | def set_text(self, txt): 151 | 152 | # If the msg box is already closed, cancel the calling task 153 | if not self.is_open(): 154 | raise CancelledError() 155 | 156 | self.label.set_text(txt) 157 | 158 | ################################################################################################## 159 | # Async task 160 | ################################################################################################## 161 | 162 | async def btn_event_task(obj=None, event=-1): 163 | 164 | # Create and position the a new msg box 165 | 166 | msg_box = MsgBox(scr) 167 | msg_box.set_pos(getrandbits(7), 50 + getrandbits(7)) 168 | 169 | # Countdown 170 | 171 | for i in range(10, 0, -1): 172 | msg_box.set_text(str(i)) 173 | await sleep(1) 174 | 175 | # Close the msg box 176 | 177 | msg_box.close_msg_box() 178 | 179 | ################################################################################################## 180 | # Create objects and screen 181 | ################################################################################################## 182 | 183 | scr = lv.scr_act() 184 | btn = lv.btn(scr) 185 | btn.align(lv.ALIGN.TOP_MID, 0, 10) 186 | btn.add_event_cb(lambda e: create_task(btn_event_task()), lv.EVENT.CLICKED, None) 187 | label = lv.label(btn) 188 | label.set_text('Click Me Again!') 189 | 190 | ################################################################################################## 191 | # Start event loop 192 | ################################################################################################## 193 | 194 | Loop.run_forever() 195 | 196 | -------------------------------------------------------------------------------- /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 | 39 | import SDL 40 | SDL.init(w=self.width, h=self.height, auto_refresh=(not lv_utils_available)) 41 | if lv_utils_available: 42 | self.event_loop = lv_utils.event_loop(refresh_cb = SDL.refresh, asynchronous=self.asynchronous, exception_sink=self.exception_sink) 43 | 44 | # Register SDL display driver. 45 | 46 | disp_buf1 = lv.disp_draw_buf_t() 47 | buf1_1 = bytearray(self.width*10) 48 | disp_buf1.init(buf1_1, None, len(buf1_1)//4) 49 | disp_drv = lv.disp_drv_t() 50 | disp_drv.init() 51 | disp_drv.draw_buf = disp_buf1 52 | disp_drv.flush_cb = SDL.monitor_flush 53 | disp_drv.hor_res = self.width 54 | disp_drv.ver_res = self.height 55 | disp_drv.register() 56 | 57 | # Register SDL mouse driver 58 | 59 | indev_drv = lv.indev_drv_t() 60 | indev_drv.init() 61 | indev_drv.type = lv.INDEV_TYPE.POINTER 62 | indev_drv.read_cb = SDL.mouse_read 63 | indev_drv.register() 64 | 65 | # Register keyboard driver 66 | 67 | keyboard_drv = lv.indev_drv_t() 68 | keyboard_drv.init() 69 | keyboard_drv.type = lv.INDEV_TYPE.KEYPAD 70 | keyboard_drv.read_cb = SDL.keyboard_read 71 | self.keyboard = keyboard_drv.register() 72 | self.keyboard.set_group(self.group) 73 | 74 | self.type = "SDL" 75 | print("Running the SDL lvgl version") 76 | 77 | def init_gui_ili9341(self): 78 | 79 | # Initialize ILI9341 display 80 | 81 | from ili9XXX import ili9341,LANDSCAPE 82 | from xpt2046 import xpt2046 83 | import espidf as esp 84 | 85 | if lv_utils_available: 86 | self.event_loop = lv_utils.event_loop(asynchronous=self.asynchronous, exception_sink=self.exception_sink) 87 | 88 | if self.orientation == ORIENT_PORTRAIT: 89 | print ("Running the ili9341 lvgl version in portait mode") 90 | 91 | # Initialize ILI9341 display in prtrait mode 92 | # the following are the settings for the Lolin tft 2.4 display 93 | # self.disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, spihost=esp.VSPI_HOST) 94 | # self.touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=3751, cal_x1 = 210, cal_y0=3906, cal_y1 = 283, transpose=True) 95 | 96 | self.disp = ili9341() 97 | self.touch = xpt2046() 98 | 99 | elif self.orientation == ORIENT_LANDSCAPE: 100 | print ("Running the ili9341 lvgl version in landscape mode") 101 | # Initialize ILI9341 display 102 | # the following are the settings for the Lolin tft 2.4 display 103 | # self.disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, backlight_on=0, 104 | # spihost=esp.VSPI_HOST, width=320, height=240, rot=LANDSCAPE) 105 | # self.touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=3799, cal_x1 = 353, cal_y0=220, cal_y1 = 3719, transpose=False) 106 | 107 | # Register xpt2046 touch driver 108 | self.disp = ili9341(width=320, height=240, rot=LANDSCAPE) 109 | self.touch = xpt2046(cal_x0=3799, cal_x1 = 353, cal_y0=220, cal_y1 = 3719,transpose = False) 110 | 111 | else: 112 | raise RuntimeError("Invalid orientation") 113 | 114 | self.type="ili9341" 115 | 116 | ''' 117 | # Register raw resistive touch driver (remove xpt2046 initialization first) 118 | import rtch 119 | touch = rtch.touch(xp = 32, yp = 33, xm = 25, ym = 26, touch_rail = 27, touch_sense = 33) 120 | touch.init() 121 | indev_drv = lv.indev_drv_t() 122 | lv.indev_drv_init(indev_drv) 123 | indev_drv.type = lv.INDEV_TYPE.POINTER 124 | indev_drv.read_cb = touch.read 125 | lv.indev_drv_register(indev_drv) 126 | ''' 127 | 128 | def init_gui_twatch(self): 129 | 130 | import ttgo 131 | from axp_constants import AXP202_VBUS_VOL_ADC1,AXP202_VBUS_CUR_ADC1,AXP202_BATT_CUR_ADC1,AXP202_BATT_VOL_ADC1 132 | 133 | watch = ttgo.Watch() 134 | tft = watch.tft 135 | power = watch.pmu 136 | power.adc1Enable(AXP202_VBUS_VOL_ADC1 137 | | AXP202_VBUS_CUR_ADC1 138 | | AXP202_BATT_CUR_ADC1 139 | | AXP202_BATT_VOL_ADC1, True) 140 | watch.lvgl_begin() 141 | watch.tft.backlight_fade(100) 142 | 143 | self.type="t-watch" 144 | print("Running lvgl on the LilyGo t-watch 2020") 145 | 146 | def init_gui(self): 147 | 148 | # Identify platform and initialize it 149 | 150 | try: 151 | self.init_gui_twatch() 152 | return 153 | except: 154 | pass 155 | 156 | try: 157 | self.init_gui_ili9341() 158 | return 159 | except ImportError: 160 | pass 161 | 162 | try: 163 | self.init_gui_SDL() 164 | return 165 | except ImportError: 166 | pass 167 | 168 | raise RuntimeError("Could not find a suitable display driver!") 169 | 170 | -------------------------------------------------------------------------------- /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 | # # Regsiter SDL mouse driver 12 | # event_loop = lv_utils.event_loop() 13 | # 14 | # 15 | # uasyncio example with SDL: 16 | # 17 | # SDL.init(auto_refresh=False) 18 | # # Register SDL display driver. 19 | # # Regsiter SDL mouse driver 20 | # event_loop = lv_utils.event_loop(asynchronous=True) 21 | # uasyncio.Loop.run_forever() 22 | # 23 | # uasyncio example with ili9341: 24 | # 25 | # event_loop = lv_utils.event_loop(asynchronous=True) # Optional! 26 | # self.disp = ili9341(asynchronous=True) 27 | # uasyncio.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 usys 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 | raise RuntimeError("Missing machine.Timer implementation!") 46 | 47 | # Try to determine default timer id 48 | 49 | default_timer_id = 0 50 | if usys.platform == 'pyboard': 51 | # stm32 only supports SW timer -1 52 | default_timer_id = -1 53 | 54 | if usys.platform == 'rp2': 55 | # rp2 only supports SW timer -1 56 | default_timer_id = -1 57 | 58 | # Try importing uasyncio, if available 59 | 60 | try: 61 | import uasyncio 62 | uasyncio_available = True 63 | except: 64 | uasyncio_available = False 65 | 66 | ############################################################################## 67 | 68 | class event_loop(): 69 | 70 | _current_instance = None 71 | 72 | def __init__(self, freq=25, timer_id=default_timer_id, max_scheduled=2, refresh_cb=None, asynchronous=False, exception_sink=None): 73 | if self.is_running(): 74 | raise RuntimeError("Event loop is already running!") 75 | 76 | if not lv.is_initialized(): 77 | lv.init() 78 | 79 | event_loop._current_instance = self 80 | 81 | self.delay = 1000 // freq 82 | self.refresh_cb = refresh_cb 83 | self.exception_sink = exception_sink if exception_sink else self.default_exception_sink 84 | 85 | self.asynchronous = asynchronous 86 | if self.asynchronous: 87 | if not uasyncio_available: 88 | raise RuntimeError("Cannot run asynchronous event loop. uasyncio is not available!") 89 | self.refresh_event = uasyncio.Event() 90 | self.refresh_task = uasyncio.create_task(self.async_refresh()) 91 | self.timer_task = uasyncio.create_task(self.async_timer()) 92 | else: 93 | self.timer = Timer(timer_id) 94 | self.task_handler_ref = self.task_handler # Allocation occurs here 95 | self.timer.init(mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb) 96 | self.max_scheduled = max_scheduled 97 | self.scheduled = 0 98 | 99 | def deinit(self): 100 | if self.asynchronous: 101 | self.refresh_task.cancel() 102 | self.timer_task.cancel() 103 | else: 104 | self.timer.deinit() 105 | event_loop._current_instance = None 106 | 107 | def disable(self): 108 | self.scheduled += self.max_scheduled 109 | 110 | def enable(self): 111 | self.scheduled -= self.max_scheduled 112 | 113 | @staticmethod 114 | def is_running(): 115 | return event_loop._current_instance is not None 116 | 117 | @staticmethod 118 | def current_instance(): 119 | return event_loop._current_instance 120 | 121 | def task_handler(self, _): 122 | try: 123 | lv.task_handler() 124 | if self.refresh_cb: self.refresh_cb() 125 | self.scheduled -= 1 126 | except Exception as e: 127 | if self.exception_sink: 128 | self.exception_sink(e) 129 | 130 | def timer_cb(self, t): 131 | # Can be called in Interrupt context 132 | # Use task_handler_ref since passing self.task_handler would cause allocation. 133 | lv.tick_inc(self.delay) 134 | if self.scheduled < self.max_scheduled: 135 | try: 136 | micropython.schedule(self.task_handler_ref, 0) 137 | self.scheduled += 1 138 | except: 139 | pass 140 | 141 | async def async_refresh(self): 142 | while True: 143 | await self.refresh_event.wait() 144 | self.refresh_event.clear() 145 | try: 146 | lv.task_handler() 147 | except Exception as e: 148 | if self.exception_sink: 149 | self.exception_sink(e) 150 | if self.refresh_cb: self.refresh_cb() 151 | 152 | async def async_timer(self): 153 | while True: 154 | await uasyncio.sleep_ms(self.delay) 155 | lv.tick_inc(self.delay) 156 | self.refresh_event.set() 157 | 158 | 159 | def default_exception_sink(self, e): 160 | usys.print_exception(e) 161 | event_loop.current_instance().deinit() 162 | -------------------------------------------------------------------------------- /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 coordiantes 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.btn(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_cb(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 | -------------------------------------------------------------------------------- /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 | add_custom_command( 70 | OUTPUT 71 | ${LV_OUTPUT} 72 | COMMAND 73 | ${Python3_EXECUTABLE} ${LV_BINDINGS_DIR}/gen/gen_mpy.py ${LV_GEN_OPTIONS} -MD ${LV_MPY_METADATA} -E ${LV_PP_FILTERED} ${LV_INPUT} > ${LV_OUTPUT} || (rm -f ${LV_OUTPUT} && /bin/false) 74 | DEPENDS 75 | ${LV_BINDINGS_DIR}/gen/gen_mpy.py 76 | ${LV_PP_FILTERED} 77 | COMMAND_EXPAND_LISTS 78 | ) 79 | 80 | endfunction() 81 | 82 | # Definitions for specific bindings 83 | 84 | set(LVGL_DIR ${LV_BINDINGS_DIR}/lvgl) 85 | 86 | set(LV_MP ${CMAKE_BINARY_DIR}/lv_mp.c) 87 | if(ESP_PLATFORM) 88 | set(LV_ESPIDF ${CMAKE_BINARY_DIR}/lv_espidf.c) 89 | endif() 90 | 91 | # Function for creating all specific bindings 92 | 93 | function(all_lv_bindings) 94 | 95 | # LVGL bindings 96 | 97 | file(GLOB_RECURSE LVGL_HEADERS ${LVGL_DIR}/src/*.h ${LV_BINDINGS_DIR}/lv_conf.h) 98 | lv_bindings( 99 | OUTPUT 100 | ${LV_MP} 101 | INPUT 102 | ${LVGL_DIR}/lvgl.h 103 | DEPENDS 104 | ${LVGL_HEADERS} 105 | GEN_OPTIONS 106 | -M lvgl -MP lv 107 | ) 108 | 109 | # ESPIDF bindings 110 | 111 | if(ESP_PLATFORM) 112 | file(GLOB_RECURSE LV_ESPIDF_HEADERS ${IDF_PATH}/components/*.h ${LV_BINDINGS_DIR}/driver/esp32/*.h) 113 | lv_bindings( 114 | OUTPUT 115 | ${LV_ESPIDF} 116 | INPUT 117 | ${LV_BINDINGS_DIR}/driver/esp32/espidf.h 118 | DEPENDS 119 | ${LV_ESPIDF_HEADERS} 120 | GEN_OPTIONS 121 | -M espidf 122 | FILTER 123 | i2s_ll.h 124 | i2s_hal.h 125 | esp_intr_alloc.h 126 | soc/spi_periph.h 127 | rom/ets_sys.h 128 | soc/sens_struct.h 129 | soc/rtc.h 130 | driver/periph_ctrl.h 131 | ) 132 | endif(ESP_PLATFORM) 133 | 134 | endfunction() 135 | 136 | # Add includes to CMake component 137 | 138 | set(LV_INCLUDE 139 | ${LV_BINDINGS_DIR} 140 | ) 141 | 142 | # Add sources to CMake component 143 | 144 | set(LV_SRC 145 | ${LV_MP} 146 | ) 147 | 148 | if(ESP_PLATFORM) 149 | LIST(APPEND LV_SRC 150 | ${LV_BINDINGS_DIR}/driver/esp32/espidf.c 151 | ${LV_BINDINGS_DIR}/driver/esp32/modrtch.c 152 | ${LV_BINDINGS_DIR}/driver/esp32/sh2lib.c 153 | ${LV_ESPIDF} 154 | ) 155 | endif(ESP_PLATFORM) 156 | -------------------------------------------------------------------------------- /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/micropython-dev $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.scr_act() 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 | lv.event_send(obj, 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.scr_act().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 | --------------------------------------------------------------------------------