├── .gitignore ├── firmware ├── tdisplay │ └── firmware.bin └── twatch-2020 │ └── firmware.bin ├── examples ├── tdisplay │ ├── lv_example_spinner_1.py │ ├── p_label.py │ ├── l_label.py │ ├── lv_example_chart_1.py │ ├── lv_example_meter_1.py │ ├── lv_example_meter_2.py │ └── lv_example_chart_2.py └── twatch-2020 │ ├── lv_example_spinner_1.py │ ├── p_label.py │ ├── l_label.py │ ├── lv_example_chart_1.py │ ├── lv_example_meter_1.py │ ├── lv_example_meter_2.py │ └── lv_example_chart_2.py ├── LICENSE.txt ├── README.md └── esp32 ├── espidf.h ├── espidf.c └── ili9XXX.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .history 3 | .vscode 4 | -------------------------------------------------------------------------------- /firmware/tdisplay/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heixiaoma/lv_st7789/master/firmware/tdisplay/firmware.bin -------------------------------------------------------------------------------- /firmware/twatch-2020/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heixiaoma/lv_st7789/master/firmware/twatch-2020/firmware.bin -------------------------------------------------------------------------------- /examples/tdisplay/lv_example_spinner_1.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_spinner_1.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | disp = st7789(width=135, height=240, rot=st7789.LANDSCAPE) 9 | 10 | # Create a spinner 11 | spinner = lv.spinner(lv.scr_act(), 1000, 60) 12 | spinner.set_size(100, 100) 13 | spinner.center() 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/twatch-2020/lv_example_spinner_1.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_spinner_1.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | import axp202c 9 | 10 | # init power manager, set backlight 11 | axp = axp202c.PMU() 12 | axp.enablePower(axp202c.AXP202_LDO2) 13 | axp.setLDO2Voltage(2800) 14 | 15 | # init display 16 | disp = st7789( 17 | mosi=19, 18 | clk=18, 19 | cs=5, 20 | dc=27, 21 | rst=-1, 22 | backlight=12, 23 | power=-1, 24 | width=240, 25 | height=240, 26 | rot=st7789.INVERSE_PORTRAIT, 27 | factor=4) 28 | 29 | # Create a spinner 30 | spinner = lv.spinner(lv.scr_act(), 1000, 60) 31 | spinner.set_size(200, 200) 32 | spinner.center() 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/tdisplay/p_label.py: -------------------------------------------------------------------------------- 1 | """ 2 | p_label.py example using st7789 driver on a T-Display in Portrait mode 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | disp = st7789(width=135, height=240, rot=st7789.PORTRAIT) 9 | 10 | # Show line wrap, re-color, line align and text scrolling. 11 | label1 = lv.label(lv.scr_act()) 12 | label1.set_long_mode(lv.label.LONG.WRAP); # Break the long lines*/ 13 | label1.set_recolor(True) # Enable re-coloring by commands in the text 14 | label1.set_text("#0000ff Re-color# #ff00ff words# #ff0000 of a# label, align the lines to the center" 15 | " and wrap long text automatically.") 16 | label1.set_width(135) # Set smaller width to make the lines wrap 17 | label1.set_style_text_align(lv.ALIGN.CENTER, 0) 18 | label1.align(lv.ALIGN.CENTER, 0, -40) 19 | 20 | label2 = lv.label(lv.scr_act()) 21 | label2.set_long_mode(lv.label.LONG.SCROLL_CIRCULAR) # Circular scroll 22 | label2.set_width(135) 23 | label2.set_text("This is a circularly scrolling text.......") 24 | label2.align(lv.ALIGN.CENTER, 0, 40) 25 | -------------------------------------------------------------------------------- /examples/tdisplay/l_label.py: -------------------------------------------------------------------------------- 1 | """ 2 | l_label.py example using st7789 driver on a T-Display in Landscape mode 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | disp = st7789(width=135, height=240, rot=st7789.LANDSCAPE) 9 | 10 | # Show line wrap, re-color, line align and text scrolling. 11 | 12 | label1 = lv.label(lv.scr_act()) 13 | label1.set_long_mode(lv.label.LONG.WRAP); # Break the long lines*/ 14 | label1.set_recolor(True) # Enable re-coloring by commands in the text 15 | label1.set_text("#0000ff Re-color# #ff00ff words# #ff0000 of a# label, align the lines to the center" 16 | " and wrap long text automatically.") 17 | label1.set_width(240) # Set smaller width to make the lines wrap 18 | label1.set_style_text_align(lv.ALIGN.CENTER, 0) 19 | label1.align(lv.ALIGN.CENTER, 0, -40) 20 | 21 | label2 = lv.label(lv.scr_act()) 22 | label2.set_long_mode(lv.label.LONG.SCROLL_CIRCULAR) # Circular scroll 23 | label2.set_width(240) 24 | label2.set_text("This is a circularly scrolling text.......") 25 | label2.align(lv.ALIGN.CENTER, 0, 40) 26 | -------------------------------------------------------------------------------- /examples/tdisplay/lv_example_chart_1.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_chart_1.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | disp = st7789(width=135, height=240, rot=st7789.LANDSCAPE) 9 | 10 | # Create a chart 11 | chart = lv.chart(lv.scr_act()) 12 | chart.set_size(200, 150) 13 | chart.center() 14 | chart.set_type(lv.chart.TYPE.LINE) # Show lines and points too 15 | 16 | # Add two data series 17 | ser1 = chart.add_series(lv.palette_main(lv.PALETTE.RED), lv.chart.AXIS.PRIMARY_Y); 18 | ser2 = chart.add_series(lv.palette_main(lv.PALETTE.GREEN), lv.chart.AXIS.SECONDARY_Y) 19 | print(ser2) 20 | # Set next points on ser1 21 | chart.set_next_value(ser1,10) 22 | chart.set_next_value(ser1,10) 23 | chart.set_next_value(ser1,10) 24 | chart.set_next_value(ser1,10) 25 | chart.set_next_value(ser1,10) 26 | chart.set_next_value(ser1,10) 27 | chart.set_next_value(ser1,10) 28 | chart.set_next_value(ser1,30) 29 | chart.set_next_value(ser1,70) 30 | chart.set_next_value(ser1,90) 31 | 32 | # Directly set points on 'ser2' 33 | ser2.y_points = [90, 70, 65, 65, 65, 65, 65, 65, 65, 65] 34 | chart.refresh() # Required after direct set 35 | 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT licence 2 | 3 | Copyright (c) 2021 Russ Hughes 4 | This file incorporates work covered by the following copyright and permission notice and is licensed under the same terms: 5 | 6 | MIT licence 7 | 8 | Copyright (c) 2021 LVGL Kft 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /examples/twatch-2020/p_label.py: -------------------------------------------------------------------------------- 1 | """ 2 | p_label.py example using st7789 driver on a T-Display in Portrait mode 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | import axp202c 9 | 10 | # init power manager, set backlight 11 | axp = axp202c.PMU() 12 | axp.enablePower(axp202c.AXP202_LDO2) 13 | axp.setLDO2Voltage(2800) 14 | 15 | # init display 16 | disp = st7789( 17 | mosi=19, 18 | clk=18, 19 | cs=5, 20 | dc=27, 21 | rst=-1, 22 | backlight=12, 23 | power=-1, 24 | width=240, 25 | height=240, 26 | rot=st7789.INVERSE_PORTRAIT, 27 | factor=4) 28 | 29 | # Show line wrap, re-color, line align and text scrolling. 30 | label1 = lv.label(lv.scr_act()) 31 | label1.set_long_mode(lv.label.LONG.WRAP); # Break the long lines*/ 32 | label1.set_recolor(True) # Enable re-coloring by commands in the text 33 | label1.set_text("#0000ff Re-color# #ff00ff words# #ff0000 of a# label, align the lines to the center" 34 | " and wrap long text automatically.") 35 | label1.set_width(135) # Set smaller width to make the lines wrap 36 | label1.set_style_text_align(lv.ALIGN.CENTER, 0) 37 | label1.align(lv.ALIGN.CENTER, 0, -40) 38 | 39 | label2 = lv.label(lv.scr_act()) 40 | label2.set_long_mode(lv.label.LONG.SCROLL_CIRCULAR) # Circular scroll 41 | label2.set_width(135) 42 | label2.set_text("This is a circularly scrolling text.......") 43 | label2.align(lv.ALIGN.CENTER, 0, 40) 44 | -------------------------------------------------------------------------------- /examples/twatch-2020/l_label.py: -------------------------------------------------------------------------------- 1 | """ 2 | l_label.py example using st7789 driver on a T-Display in Landscape mode 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | import axp202c 9 | 10 | # init power manager, set backlight 11 | axp = axp202c.PMU() 12 | axp.enablePower(axp202c.AXP202_LDO2) 13 | axp.setLDO2Voltage(2800) 14 | 15 | # init display 16 | disp = st7789( 17 | mosi=19, 18 | clk=18, 19 | cs=5, 20 | dc=27, 21 | rst=-1, 22 | backlight=12, 23 | power=-1, 24 | width=240, 25 | height=240, 26 | rot=st7789.INVERSE_PORTRAIT, 27 | factor=16) 28 | 29 | # Show line wrap, re-color, line align and text scrolling. 30 | 31 | label1 = lv.label(lv.scr_act()) 32 | label1.set_long_mode(lv.label.LONG.WRAP); # Break the long lines*/ 33 | label1.set_recolor(True) # Enable re-coloring by commands in the text 34 | label1.set_text("#0000ff Re-color# #ff00ff words# #ff0000 of a# label, align the lines to the center" 35 | " and wrap long text automatically.") 36 | label1.set_width(240) # Set smaller width to make the lines wrap 37 | label1.set_style_text_align(lv.ALIGN.CENTER, 0) 38 | label1.align(lv.ALIGN.CENTER, 0, -40) 39 | 40 | label2 = lv.label(lv.scr_act()) 41 | label2.set_long_mode(lv.label.LONG.SCROLL_CIRCULAR) # Circular scroll 42 | label2.set_width(240) 43 | label2.set_text("This is a circularly scrolling text.......") 44 | label2.align(lv.ALIGN.CENTER, 0, 40) 45 | -------------------------------------------------------------------------------- /examples/twatch-2020/lv_example_chart_1.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_chart_1.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import lvgl as lv 6 | from ili9XXX import st7789 7 | 8 | import axp202c 9 | 10 | # init power manager, set backlight 11 | axp = axp202c.PMU() 12 | axp.enablePower(axp202c.AXP202_LDO2) 13 | axp.setLDO2Voltage(2800) 14 | 15 | # init display 16 | disp = st7789( 17 | mosi=19, 18 | clk=18, 19 | cs=5, 20 | dc=27, 21 | rst=-1, 22 | backlight=12, 23 | power=-1, 24 | width=240, 25 | height=240, 26 | rot=st7789.INVERSE_PORTRAIT, 27 | factor=4) 28 | 29 | # Create a chart 30 | chart = lv.chart(lv.scr_act()) 31 | chart.set_size(200, 150) 32 | chart.center() 33 | chart.set_type(lv.chart.TYPE.LINE) # Show lines and points too 34 | 35 | # Add two data series 36 | ser1 = chart.add_series(lv.palette_main(lv.PALETTE.RED), lv.chart.AXIS.PRIMARY_Y); 37 | ser2 = chart.add_series(lv.palette_main(lv.PALETTE.GREEN), lv.chart.AXIS.SECONDARY_Y) 38 | print(ser2) 39 | # Set next points on ser1 40 | chart.set_next_value(ser1,10) 41 | chart.set_next_value(ser1,10) 42 | chart.set_next_value(ser1,10) 43 | chart.set_next_value(ser1,10) 44 | chart.set_next_value(ser1,10) 45 | chart.set_next_value(ser1,10) 46 | chart.set_next_value(ser1,10) 47 | chart.set_next_value(ser1,30) 48 | chart.set_next_value(ser1,70) 49 | chart.set_next_value(ser1,90) 50 | 51 | # Directly set points on 'ser2' 52 | ser2.y_points = [90, 70, 65, 65, 65, 65, 65, 65, 65, 65] 53 | chart.refresh() # Required after direct set 54 | 55 | -------------------------------------------------------------------------------- /examples/tdisplay/lv_example_meter_1.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_meter_1.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import utime as time 6 | import lvgl as lv 7 | from ili9XXX import st7789 8 | 9 | disp = st7789(width=135, height=240, rot=st7789.PORTRAIT) 10 | 11 | def set_value(indic, v): 12 | meter.set_indicator_value(indic, v) 13 | 14 | # 15 | # A simple meter 16 | # 17 | meter = lv.meter(lv.scr_act()) 18 | meter.center() 19 | meter.set_size(135, 135) 20 | 21 | # Add a scale first 22 | scale = meter.add_scale() 23 | meter.set_scale_ticks(scale, 51, 2, 10, lv.palette_main(lv.PALETTE.GREY)) 24 | meter.set_scale_major_ticks(scale, 10, 4, 15, lv.color_black(), 10) 25 | 26 | indic = lv.meter_indicator_t() 27 | 28 | # Add a blue arc to the start 29 | indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.BLUE), 0) 30 | meter.set_indicator_start_value(indic, 0) 31 | meter.set_indicator_end_value(indic, 20) 32 | 33 | # Make the tick lines blue at the start of the scale 34 | indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.BLUE), lv.palette_main(lv.PALETTE.BLUE), False, 0) 35 | meter.set_indicator_start_value(indic, 0) 36 | meter.set_indicator_end_value(indic, 20) 37 | 38 | # Add a red arc to the end 39 | indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.RED), 0) 40 | meter.set_indicator_start_value(indic, 80) 41 | meter.set_indicator_end_value(indic, 100) 42 | 43 | # Make the tick lines red at the end of the scale 44 | indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.RED), lv.palette_main(lv.PALETTE.RED), False, 0) 45 | meter.set_indicator_start_value(indic, 80) 46 | meter.set_indicator_end_value(indic, 100) 47 | 48 | # Add a needle line indicator 49 | indic = meter.add_needle_line(scale, 4, lv.palette_main(lv.PALETTE.GREY), -10) 50 | 51 | # Create an animation to set the value 52 | a = lv.anim_t() 53 | a.init() 54 | a.set_var(indic) 55 | a.set_values(0, 100) 56 | a.set_time(2000) 57 | a.set_repeat_delay(100) 58 | a.set_playback_time(500) 59 | a.set_playback_delay(100) 60 | a.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 61 | a.set_custom_exec_cb(lambda a,val: set_value(indic,val)) 62 | lv.anim_t.start(a) 63 | 64 | -------------------------------------------------------------------------------- /examples/tdisplay/lv_example_meter_2.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_meter_2.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import utime as time 6 | import lvgl as lv 7 | from ili9XXX import st7789 8 | 9 | disp = st7789(width=135, height=240, rot=st7789.LANDSCAPE) 10 | 11 | def set_value(indic,v): 12 | meter.set_indicator_end_value(indic, v) 13 | 14 | # 15 | # A meter with multiple arcs 16 | # 17 | 18 | meter = lv.meter(lv.scr_act()) 19 | meter.center() 20 | meter.set_size(200, 200) 21 | 22 | # Remove the circle from the middle 23 | meter.remove_style(None, lv.PART.INDICATOR) 24 | 25 | # Add a scale first 26 | scale = meter.add_scale() 27 | meter.set_scale_ticks(scale, 11, 2, 10, lv.palette_main(lv.PALETTE.GREY)) 28 | meter.set_scale_major_ticks(scale, 1, 2, 30, lv.color_hex3(0xeee), 10) 29 | meter.set_scale_range(scale, 0, 100, 270, 90) 30 | 31 | # Add a three arc indicator 32 | indic1 = meter.add_arc(scale, 10, lv.palette_main(lv.PALETTE.RED), 0) 33 | indic2 = meter.add_arc(scale, 10, lv.palette_main(lv.PALETTE.GREEN), -10) 34 | indic3 = meter.add_arc(scale, 10, lv.palette_main(lv.PALETTE.BLUE), -20) 35 | 36 | # Create an animation to set the value 37 | a1 = lv.anim_t() 38 | a1.init() 39 | a1.set_values(0, 100) 40 | a1.set_time(2000) 41 | a1.set_repeat_delay(100) 42 | a1.set_playback_delay(100) 43 | a1.set_playback_time(500) 44 | a1.set_var(indic1) 45 | a1.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 46 | a1.set_custom_exec_cb(lambda a,val: set_value(indic1,val)) 47 | lv.anim_t.start(a1) 48 | 49 | a2 = lv.anim_t() 50 | a2.init() 51 | a2.set_values(0, 100) 52 | a2.set_time(1000) 53 | a2.set_repeat_delay(100) 54 | a2.set_playback_delay(100) 55 | a2.set_playback_time(1000) 56 | a2.set_var(indic2) 57 | a2.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 58 | a2.set_custom_exec_cb(lambda a,val: set_value(indic2,val)) 59 | lv.anim_t.start(a2) 60 | 61 | a3 = lv.anim_t() 62 | a3.init() 63 | a3.set_values(0, 100) 64 | a3.set_time(1000) 65 | a3.set_repeat_delay(100) 66 | a3.set_playback_delay(100) 67 | a3.set_playback_time(2000) 68 | a3.set_var(indic3) 69 | a3.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 70 | a3.set_custom_exec_cb(lambda a,val: set_value(indic3,val)) 71 | lv.anim_t.start(a3) 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /examples/twatch-2020/lv_example_meter_1.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_meter_1.py using st7789 driver on a T-Display 3 | """ 4 | 5 | import utime as time 6 | import lvgl as lv 7 | from ili9XXX import st7789 8 | 9 | import axp202c 10 | 11 | # init power manager, set backlight 12 | axp = axp202c.PMU() 13 | axp.enablePower(axp202c.AXP202_LDO2) 14 | axp.setLDO2Voltage(2800) 15 | 16 | # init display 17 | disp = st7789( 18 | mosi=19, 19 | clk=18, 20 | cs=5, 21 | dc=27, 22 | rst=-1, 23 | backlight=12, 24 | power=-1, 25 | width=240, 26 | height=240, 27 | rot=st7789.INVERSE_PORTRAIT, 28 | factor=4) 29 | 30 | def set_value(indic, v): 31 | meter.set_indicator_value(indic, v) 32 | 33 | # 34 | # A simple meter 35 | # 36 | meter = lv.meter(lv.scr_act()) 37 | meter.center() 38 | meter.set_size(240, 240) 39 | 40 | # Add a scale first 41 | scale = meter.add_scale() 42 | meter.set_scale_ticks(scale, 51, 2, 10, lv.palette_main(lv.PALETTE.GREY)) 43 | meter.set_scale_major_ticks(scale, 10, 4, 15, lv.color_black(), 10) 44 | 45 | indic = lv.meter_indicator_t() 46 | 47 | # Add a blue arc to the start 48 | indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.BLUE), 0) 49 | meter.set_indicator_start_value(indic, 0) 50 | meter.set_indicator_end_value(indic, 20) 51 | 52 | # Make the tick lines blue at the start of the scale 53 | indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.BLUE), lv.palette_main(lv.PALETTE.BLUE), False, 0) 54 | meter.set_indicator_start_value(indic, 0) 55 | meter.set_indicator_end_value(indic, 20) 56 | 57 | # Add a red arc to the end 58 | indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.RED), 0) 59 | meter.set_indicator_start_value(indic, 80) 60 | meter.set_indicator_end_value(indic, 100) 61 | 62 | # Make the tick lines red at the end of the scale 63 | indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.RED), lv.palette_main(lv.PALETTE.RED), False, 0) 64 | meter.set_indicator_start_value(indic, 80) 65 | meter.set_indicator_end_value(indic, 100) 66 | 67 | # Add a needle line indicator 68 | indic = meter.add_needle_line(scale, 4, lv.palette_main(lv.PALETTE.GREY), -10) 69 | 70 | # Create an animation to set the value 71 | a = lv.anim_t() 72 | a.init() 73 | a.set_var(indic) 74 | a.set_values(0, 100) 75 | a.set_time(2000) 76 | a.set_repeat_delay(100) 77 | a.set_playback_time(500) 78 | a.set_playback_delay(100) 79 | a.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 80 | a.set_custom_exec_cb(lambda a,val: set_value(indic,val)) 81 | lv.anim_t.start(a) 82 | 83 | -------------------------------------------------------------------------------- /examples/twatch-2020/lv_example_meter_2.py: -------------------------------------------------------------------------------- 1 | """ 2 | lv_example_meter_2.py using st7789 driver on a T-Watch 3 | """ 4 | 5 | import utime as time 6 | from machine import Pin, SPI, SoftI2C 7 | import lvgl as lv 8 | from ili9XXX import st7789 9 | 10 | import axp202c 11 | 12 | # init power manager, set backlight 13 | axp = axp202c.PMU() 14 | axp.enablePower(axp202c.AXP202_LDO2) 15 | axp.setLDO2Voltage(2800) 16 | 17 | # init display 18 | disp = st7789( 19 | mosi=19, 20 | clk=18, 21 | cs=5, 22 | dc=27, 23 | rst=-1, 24 | backlight=12, 25 | power=-1, 26 | width=240, 27 | height=240, 28 | rot=st7789.INVERSE_PORTRAIT, 29 | factor=4) 30 | 31 | def set_value(indic,v): 32 | meter.set_indicator_end_value(indic, v) 33 | 34 | # 35 | # A meter with multiple arcs 36 | # 37 | 38 | meter = lv.meter(lv.scr_act()) 39 | meter.center() 40 | meter.set_size(240, 240) 41 | 42 | # Remove the circle from the middle 43 | meter.remove_style(None, lv.PART.INDICATOR) 44 | 45 | # Add a scale first 46 | scale = meter.add_scale() 47 | meter.set_scale_ticks(scale, 11, 2, 10, lv.palette_main(lv.PALETTE.GREY)) 48 | meter.set_scale_major_ticks(scale, 1, 2, 30, lv.color_hex3(0xeee), 10) 49 | meter.set_scale_range(scale, 0, 100, 270, 90) 50 | 51 | # Add a three arc indicator 52 | indic1 = meter.add_arc(scale, 10, lv.palette_main(lv.PALETTE.RED), 0) 53 | indic2 = meter.add_arc(scale, 10, lv.palette_main(lv.PALETTE.GREEN), -10) 54 | indic3 = meter.add_arc(scale, 10, lv.palette_main(lv.PALETTE.BLUE), -20) 55 | 56 | # Create an animation to set the value 57 | a1 = lv.anim_t() 58 | a1.init() 59 | a1.set_values(0, 100) 60 | a1.set_time(2000) 61 | a1.set_repeat_delay(100) 62 | a1.set_playback_delay(100) 63 | a1.set_playback_time(500) 64 | a1.set_var(indic1) 65 | a1.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 66 | a1.set_custom_exec_cb(lambda a,val: set_value(indic1,val)) 67 | lv.anim_t.start(a1) 68 | 69 | a2 = lv.anim_t() 70 | a2.init() 71 | a2.set_values(0, 100) 72 | a2.set_time(1000) 73 | a2.set_repeat_delay(100) 74 | a2.set_playback_delay(100) 75 | a2.set_playback_time(1000) 76 | a2.set_var(indic2) 77 | a2.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 78 | a2.set_custom_exec_cb(lambda a,val: set_value(indic2,val)) 79 | lv.anim_t.start(a2) 80 | 81 | a3 = lv.anim_t() 82 | a3.init() 83 | a3.set_values(0, 100) 84 | a3.set_time(1000) 85 | a3.set_repeat_delay(100) 86 | a3.set_playback_delay(100) 87 | a3.set_playback_time(2000) 88 | a3.set_var(indic3) 89 | a3.set_repeat_count(lv.ANIM_REPEAT.INFINITE) 90 | a3.set_custom_exec_cb(lambda a,val: set_value(indic3,val)) 91 | lv.anim_t.start(a3) 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lv_st7789 Hybrid MicroPython LVGL driver for ST7789 Displays 2 | 3 | This driver adds support for 320x240, 240x240 and 135x240 ST7789 displays with 4 | rotations for the ESP32 to the LVGL MicroPython bindings. 5 | 6 | ## Install and Build 7 | 8 | - Clone the https://github.com/lvgl/lv_micropython repo. 9 | - Copy the espidf.c and ili9XXX.py files from the es32 directory in this repo into the lv_micropython/lib/lv_bindings/driver/esp32/ directory of your cloned lv_micropython repo. 10 | - Follow the build instructions from the README.md of the https://github.com/lvgl/lv_micropython repo. 11 | 12 | ## Pre-compiled firmware.bin 13 | 14 | See the firmware directory for a pre-compiled firmware files you can flash to an ESP32 device using the esptool.py utility. 15 | 16 | Directory | File | Device 17 | ------------ | ------------ | ------------------------------------------ 18 | tdisplay | firmware.bin | LILYGO® TTGO T-Display or GENERIC ESP32 19 | twatch-2020 | firmware.bin | LILYGO® T-Watch 2020 or 16MB GENERIC ESP32 20 | 21 | ## Examples 22 | 23 | See the examples directory for several example LVGL MicroPython programs configured for the LILYGO® TTGO T-Display and LILYGO® T-Watch 2020 devices. 24 | 25 | ## Parameters and defaults 26 | 27 | ``` 28 | st7789( 29 | miso=-1, mosi=19, clk=18, cs=5, dc=16, rst=23, power=-1, power_on=0 30 | backlight=4, backlight_on=1, spihost=esp.HSPI_HOST, mhz=40, factor=4, 31 | hybrid=True, width=135, height=240, colormode=COLOR_MODE_BGR, rot=PORTRAIT, 32 | initialize=True) 33 | ``` 34 | 35 | Arg | Description 36 | --- | ----------- 37 | miso | Pin for SPI Data from display, -1 if not used as many st7789 displays do not have this pin 38 | mosi | Pin for SPI Data to display (REQUIRED) 39 | clk | Pin for SPI Clock (REQUIRED) 40 | cs | Pin for display CS 41 | dc | Pin for display DC (REQUIRED) 42 | rst | Pin for display RESET 43 | power | Pin for display Power ON, -1 if not used 44 | power_on | Pin value for Power ON 45 | backlight | Pin for display backlight control 46 | backlight_on | Pin value for backlight on 47 | spihost | ESP SPI Port 48 | mhz | SPI baud rate in mhz 49 | factor | Decrease frame buffer by factor 50 | hybrid | Boolean, True to use C refresh routine, False for pure Python driver 51 | width | Display width 52 | height | Display height 53 | colormode | Display colormode 54 | rot | Display orientation, PORTRAIT, LANDSCAPE, INVERSE_PORTRAIT, INVERSE_LANDSCAPE 55 | invert | Display invert colors setting 56 | double_buffer | Boolean, True to use double buffering, False to use single buffer (saves memory) 57 | half_duplex | Boolean, True to use half duplex SPI communications 58 | asynchronous | Boolean, True to use asynchronous routines 59 | initialize | Boolean, True to initialize display 60 | 61 | -------------------------------------------------------------------------------- /examples/tdisplay/lv_example_chart_2.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | from ili9XXX import st7789 3 | 4 | disp = st7789(width=135, height=240, rot=st7789.LANDSCAPE) 5 | 6 | def draw_event_cb(e): 7 | 8 | obj = e.get_target() 9 | 10 | # Add the faded area before the lines are drawn 11 | dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) 12 | if dsc.part != lv.PART.ITEMS: 13 | return 14 | if not dsc.p1 or not dsc.p2: 15 | return 16 | 17 | # Add a line mask that keeps the area below the line 18 | line_mask_param = lv.draw_mask_line_param_t() 19 | line_mask_param.points_init(dsc.p1.x, dsc.p1.y, dsc.p2.x, dsc.p2.y, lv.DRAW_MASK_LINE_SIDE.BOTTOM) 20 | # line_mask_id = line_mask_param.draw_mask_add(None) 21 | line_mask_id = lv.draw_mask_add(line_mask_param, None) 22 | # Add a fade effect: transparent bottom covering top 23 | h = obj.get_height() 24 | fade_mask_param = lv.draw_mask_fade_param_t() 25 | coords = lv.area_t() 26 | obj.get_coords(coords) 27 | fade_mask_param.init(coords, lv.OPA.COVER, coords.y1 + h // 8, lv.OPA.TRANSP,coords.y2) 28 | fade_mask_id = lv.draw_mask_add(fade_mask_param,None) 29 | 30 | # Draw a rectangle that will be affected by the mask 31 | draw_rect_dsc = lv.draw_rect_dsc_t() 32 | draw_rect_dsc.init() 33 | draw_rect_dsc.bg_opa = lv.OPA._20 34 | draw_rect_dsc.bg_color = dsc.line_dsc.color 35 | 36 | a = lv.area_t() 37 | a.x1 = dsc.p1.x 38 | a.x2 = dsc.p2.x - 1 39 | a.y1 = min(dsc.p1.y, dsc.p2.y) 40 | coords = lv.area_t() 41 | obj.get_coords(coords) 42 | a.y2 = coords.y2 43 | lv.draw_rect(a, dsc.clip_area, draw_rect_dsc) 44 | 45 | # Remove the masks 46 | lv.draw_mask_remove_id(line_mask_id) 47 | lv.draw_mask_remove_id(fade_mask_id) 48 | 49 | 50 | def add_data(timer): 51 | # LV_UNUSED(timer); 52 | cnt = 0; 53 | char1.set_next_value(ser1, lv.rand(20, 90)) 54 | 55 | if cnt % 4 == 0: 56 | chart1.set_next_value(ser2, lv_rand(40, 60)) 57 | 58 | cnt +=1 59 | 60 | # 61 | # Add a faded area effect to the line chart 62 | # 63 | 64 | # Create a chart1 65 | chart1 = lv.chart(lv.scr_act()) 66 | chart1.set_size(240, 135) 67 | chart1.center() 68 | chart1.set_type(lv.chart.TYPE.LINE) # Show lines and points too 69 | 70 | chart1.add_event_cb(draw_event_cb, lv.EVENT.DRAW_PART_BEGIN, None) 71 | chart1.set_update_mode(lv.chart.UPDATE_MODE.CIRCULAR) 72 | 73 | # Add two data series 74 | ser1 = chart1.add_series(lv.palette_main(lv.PALETTE.RED), lv.chart.AXIS.PRIMARY_Y) 75 | ser2 = chart1.add_series(lv.palette_main(lv.PALETTE.BLUE), lv.chart.AXIS.SECONDARY_Y) 76 | 77 | for i in range(10): 78 | chart1.set_next_value(ser1, lv.rand(20, 90)) 79 | chart1.set_next_value(ser2, lv.rand(30, 70)) 80 | 81 | # timer = lv.timer_t(add_data, 200, None) 82 | 83 | -------------------------------------------------------------------------------- /examples/twatch-2020/lv_example_chart_2.py: -------------------------------------------------------------------------------- 1 | import lvgl as lv 2 | from ili9XXX import st7789 3 | 4 | import axp202c 5 | 6 | # init power manager, set backlight 7 | axp = axp202c.PMU() 8 | axp.enablePower(axp202c.AXP202_LDO2) 9 | axp.setLDO2Voltage(2800) 10 | 11 | # init display 12 | disp = st7789( 13 | mosi=19, 14 | clk=18, 15 | cs=5, 16 | dc=27, 17 | rst=-1, 18 | backlight=12, 19 | power=-1, 20 | width=240, 21 | height=240, 22 | rot=st7789.INVERSE_PORTRAIT, 23 | factor=4) 24 | 25 | def draw_event_cb(e): 26 | 27 | obj = e.get_target() 28 | 29 | # Add the faded area before the lines are drawn 30 | dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) 31 | if dsc.part != lv.PART.ITEMS: 32 | return 33 | if not dsc.p1 or not dsc.p2: 34 | return 35 | 36 | # Add a line mask that keeps the area below the line 37 | line_mask_param = lv.draw_mask_line_param_t() 38 | line_mask_param.points_init(dsc.p1.x, dsc.p1.y, dsc.p2.x, dsc.p2.y, lv.DRAW_MASK_LINE_SIDE.BOTTOM) 39 | # line_mask_id = line_mask_param.draw_mask_add(None) 40 | line_mask_id = lv.draw_mask_add(line_mask_param, None) 41 | # Add a fade effect: transparent bottom covering top 42 | h = obj.get_height() 43 | fade_mask_param = lv.draw_mask_fade_param_t() 44 | coords = lv.area_t() 45 | obj.get_coords(coords) 46 | fade_mask_param.init(coords, lv.OPA.COVER, coords.y1 + h // 8, lv.OPA.TRANSP,coords.y2) 47 | fade_mask_id = lv.draw_mask_add(fade_mask_param,None) 48 | 49 | # Draw a rectangle that will be affected by the mask 50 | draw_rect_dsc = lv.draw_rect_dsc_t() 51 | draw_rect_dsc.init() 52 | draw_rect_dsc.bg_opa = lv.OPA._20 53 | draw_rect_dsc.bg_color = dsc.line_dsc.color 54 | 55 | a = lv.area_t() 56 | a.x1 = dsc.p1.x 57 | a.x2 = dsc.p2.x - 1 58 | a.y1 = min(dsc.p1.y, dsc.p2.y) 59 | coords = lv.area_t() 60 | obj.get_coords(coords) 61 | a.y2 = coords.y2 62 | lv.draw_rect(a, dsc.clip_area, draw_rect_dsc) 63 | 64 | # Remove the masks 65 | lv.draw_mask_remove_id(line_mask_id) 66 | lv.draw_mask_remove_id(fade_mask_id) 67 | 68 | 69 | def add_data(timer): 70 | # LV_UNUSED(timer); 71 | cnt = 0; 72 | char1.set_next_value(ser1, lv.rand(20, 90)) 73 | 74 | if cnt % 4 == 0: 75 | chart1.set_next_value(ser2, lv_rand(40, 60)) 76 | 77 | cnt +=1 78 | 79 | # 80 | # Add a faded area effect to the line chart 81 | # 82 | 83 | # Create a chart1 84 | chart1 = lv.chart(lv.scr_act()) 85 | chart1.set_size(240, 135) 86 | chart1.center() 87 | chart1.set_type(lv.chart.TYPE.LINE) # Show lines and points too 88 | 89 | chart1.add_event_cb(draw_event_cb, lv.EVENT.DRAW_PART_BEGIN, None) 90 | chart1.set_update_mode(lv.chart.UPDATE_MODE.CIRCULAR) 91 | 92 | # Add two data series 93 | ser1 = chart1.add_series(lv.palette_main(lv.PALETTE.RED), lv.chart.AXIS.PRIMARY_Y) 94 | ser2 = chart1.add_series(lv.palette_main(lv.PALETTE.BLUE), lv.chart.AXIS.SECONDARY_Y) 95 | 96 | for i in range(10): 97 | chart1.set_next_value(ser1, lv.rand(20, 90)) 98 | chart1.set_next_value(ser2, lv.rand(30, 70)) 99 | 100 | # timer = lv.timer_t(add_data, 200, None) 101 | 102 | -------------------------------------------------------------------------------- /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_DMA = MALLOC_CAP_DMA, 217 | ENUM_MALLOC_CAP_INTERNAL = MALLOC_CAP_INTERNAL, 218 | ENUM_MALLOC_CAP_SPIRAM = MALLOC_CAP_SPIRAM, 219 | }; 220 | 221 | enum { 222 | ENUM_ESP_TASK_PRIO_MAX = ESP_TASK_PRIO_MAX, 223 | ENUM_ESP_TASK_PRIO_MIN = ESP_TASK_PRIO_MIN, 224 | }; 225 | 226 | ///////////////////////////////////////////////////////////////////////////////////////////// 227 | // ili9xxx flush and ISR in C 228 | // 229 | // disp_drv->user_data should be a dict that contains dc and spi, setup by micropython. 230 | // like this: "self.disp_drv.user_data = {'dc': self.dc, 'spi': self.spi, 'dt': display_type}" 231 | 232 | 233 | void ili9xxx_post_cb_isr(spi_transaction_t *trans); 234 | 235 | void ili9xxx_flush(void *disp_drv, const void *area, void *color_p); 236 | 237 | 238 | -------------------------------------------------------------------------------- /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 | 177 | void ili9xxx_flush(void *_disp_drv, const void *_area, void *_color_p) 178 | { 179 | lv_disp_drv_t *disp_drv = _disp_drv; 180 | const lv_area_t *area = _area; 181 | lv_color_t *color_p = _color_p; 182 | int start_x = 0; 183 | int start_y = 0; 184 | 185 | // We use disp_drv->user_data to pass data from MP to C 186 | // The following lines extract dc and spi 187 | 188 | int dc = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_dc))); 189 | int dt = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_dt))); 190 | mp_buffer_info_t buffer_info; 191 | mp_get_buffer_raise(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_spi)), &buffer_info, MP_BUFFER_READ); 192 | spi_device_handle_t *spi_ptr = buffer_info.buf; 193 | 194 | // st7789 may need a start_x and start_y offset for display smaller than 320x240 like the TTGO T-Display. 195 | if (dt == DISPLAY_TYPE_ST7789) { 196 | start_x = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_start_x))); 197 | start_y = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_start_y))); 198 | } 199 | 200 | int x1 = area->x1 + start_x; 201 | int x2 = area->x2 + start_x; 202 | int y1 = area->y1 + start_y; 203 | int y2 = area->y2 + start_y; 204 | 205 | // Column addresses 206 | 207 | ili9xxx_send_cmd(0x2A, dc, *spi_ptr); 208 | dma_buf[0] = (x1 >> 8) & 0xFF; 209 | dma_buf[1] = x1 & 0xFF; 210 | dma_buf[2] = (x2 >> 8) & 0xFF; 211 | dma_buf[3] = x2 & 0xFF; 212 | 213 | ili9xxx_send_data(dma_buf, dc, *spi_ptr); 214 | 215 | // Page addresses 216 | 217 | ili9xxx_send_cmd(0x2B, dc, *spi_ptr); 218 | dma_buf[0] = (y1 >> 8) & 0xFF; 219 | dma_buf[1] = y1 & 0xFF; 220 | dma_buf[2] = (y2 >> 8) & 0xFF; 221 | dma_buf[3] = y2 & 0xFF; 222 | 223 | ili9xxx_send_data(dma_buf, dc, *spi_ptr); 224 | 225 | // Memory write by DMA, disp_flush_ready when finished 226 | 227 | ili9xxx_send_cmd(0x2C, dc, *spi_ptr); 228 | 229 | size_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); 230 | uint8_t color_size = 2; 231 | 232 | if ( dt == DISPLAY_TYPE_ILI9488 ) { 233 | color_size = 3; 234 | /*Convert ARGB to RGB is required (cut off A-byte)*/ 235 | size_t i; 236 | lv_color32_t* tmp32 = (lv_color32_t*) color_p; 237 | color24_t* tmp24 = (color24_t*) color_p; 238 | 239 | for(i=0; i < size; i++) { 240 | tmp24[i].red = tmp32[i].ch.red; 241 | tmp24[i].green = tmp32[i].ch.green; 242 | tmp24[i].blue = tmp32[i].ch.blue; 243 | } 244 | } 245 | 246 | ili9xxx_send_data_dma(disp_drv, color_p, size * color_size, dc, *spi_ptr); 247 | } 248 | -------------------------------------------------------------------------------- /esp32/ili9XXX.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Pure/Hybrid micropython lvgl display driver for ili9341 and ili9488 on ESP32 3 | # 4 | # For ili9341 display: 5 | # 6 | # Build micropython with 7 | # LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" 8 | # (make parameter) to configure LVGL use the same color format as ili9341 9 | # and prevent the need to loop over all pixels to translate them. 10 | # 11 | # For ili9488 display: 12 | # 13 | # Build micropython with 14 | # LV_CFLAGS="-DLV_COLOR_DEPTH=32" 15 | # (make parameter) to configure LVGL use the ARGB color format, which can be 16 | # easily converted to RGB format used by ili9488 display. 17 | # 18 | # Default SPI freq is set to 40MHz. This means cca 9fps of full screen 19 | # redraw. to increase FPS, you can use 80MHz SPI - easily add parameter 20 | # mhz=80 in initialization of driver. 21 | # 22 | # For gc9a01 display: 23 | # 24 | # Build micropython with 25 | # LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" 26 | # (make parameter) to configure LVGL use the same color format as ili9341 27 | # and prevent the need to loop over all pixels to translate them. 28 | # 29 | # Default SPI freq is set to 60MHz as that is the maximum the tested dislay 30 | # would suport despite the datasheet suggesting that higher freqs would be 31 | # supported 32 | # 33 | # Critical function for high FPS are flush and ISR. 34 | # when "hybrid=True", use C implementation for these functions instead of 35 | # pure python implementation. This improves each frame in about 15ms! 36 | # 37 | # When hybrid=False driver is pure micropython. 38 | # Pure Micropython could be viable when ESP32 supports Viper code emitter. 39 | # 40 | # ili9488 driver DO NOT support pure micropython now (because of required 41 | # color convert). Pure micropython is only supported the for ili9341 and 42 | # gc9a01 displays! 43 | ############################################################################## 44 | 45 | import espidf as esp 46 | import lvgl as lv 47 | import lv_utils 48 | import micropython 49 | import gc 50 | 51 | micropython.alloc_emergency_exception_buf(256) 52 | # gc.threshold(0x10000) # leave enough room for SPI master TX DMA buffers 53 | 54 | # Constants 55 | 56 | COLOR_MODE_RGB = const(0x00) 57 | COLOR_MODE_BGR = const(0x08) 58 | 59 | MADCTL_MH = const(0x04) 60 | MADCTL_ML = const(0x10) 61 | MADCTL_MV = const(0x20) 62 | MADCTL_MX = const(0x40) 63 | MADCTL_MY = const(0x80) 64 | 65 | PORTRAIT = MADCTL_MX 66 | LANDSCAPE = MADCTL_MV 67 | 68 | DISPLAY_TYPE_ILI9341 = const(1) 69 | DISPLAY_TYPE_ILI9488 = const(2) 70 | DISPLAY_TYPE_GC9A01 = const(3) 71 | DISPLAY_TYPE_ST7789 = const(4) 72 | 73 | class ili9XXX: 74 | 75 | TRANS_BUFFER_LEN = const(16) 76 | 77 | display_name = 'ili9XXX' 78 | 79 | init_cmds = [ ] 80 | 81 | # Default values of "power" and "backlight" are reversed logic! 0 means ON. 82 | # You can change this by setting backlight_on and power_on arguments. 83 | 84 | def __init__(self, 85 | miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, 86 | spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=240, height=320, start_x=0, start_y=0, 87 | invert=False, double_buffer=True, half_duplex=True, display_type=0, asynchronous=False, initialize=True 88 | ): 89 | 90 | # Initializations 91 | 92 | if not lv.is_initialized(): 93 | lv.init() 94 | 95 | self.asynchronous = asynchronous 96 | self.initialize = initialize 97 | 98 | self.width = width 99 | self.height = height 100 | self.start_x = start_x 101 | self.start_y = start_y 102 | 103 | self.miso = miso 104 | self.mosi = mosi 105 | self.clk = clk 106 | self.cs = cs 107 | self.dc = dc 108 | self.rst = rst 109 | self.power = power 110 | self.backlight = backlight 111 | self.backlight_on = backlight_on 112 | self.power_on = power_on 113 | self.spihost = spihost 114 | self.mhz = mhz 115 | self.factor = factor 116 | self.hybrid = hybrid 117 | self.half_duplex = half_duplex 118 | self.display_type = display_type 119 | 120 | self.buf_size = (self.width * self.height * lv.color_t.__SIZE__) // factor 121 | 122 | if invert: 123 | self.init_cmds.append({'cmd': 0x21}) 124 | 125 | # Register display driver 126 | 127 | self.buf1 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) 128 | self.buf2 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) if double_buffer else None 129 | 130 | if self.buf1 and self.buf2: 131 | print("Double buffer") 132 | elif self.buf1: 133 | print("Single buffer") 134 | else: 135 | raise RuntimeError("Not enough DMA-able memory to allocate display buffer") 136 | 137 | self.disp_buf = lv.disp_draw_buf_t() 138 | self.disp_drv = lv.disp_drv_t() 139 | 140 | self.disp_buf.init(self.buf1, self.buf2, self.buf_size // lv.color_t.__SIZE__) 141 | self.disp_drv.init() 142 | self.disp_spi_init() 143 | 144 | self.disp_drv.user_data = { 145 | 'dc': self.dc, 146 | 'spi': self.spi, 147 | 'dt': self.display_type, 148 | 'start_x': self.start_x, 149 | 'start_y': self.start_y} 150 | 151 | self.disp_drv.draw_buf = self.disp_buf 152 | self.disp_drv.flush_cb = esp.ili9xxx_flush if hybrid and hasattr(esp, 'ili9xxx_flush') else self.flush 153 | self.disp_drv.monitor_cb = self.monitor 154 | self.disp_drv.hor_res = self.width 155 | self.disp_drv.ver_res = self.height 156 | 157 | if self.initialize: 158 | self.init() 159 | 160 | if not lv_utils.event_loop.is_running(): 161 | self.event_loop = lv_utils.event_loop(asynchronous=self.asynchronous) 162 | 163 | 164 | ###################################################### 165 | 166 | def disp_spi_init(self): 167 | 168 | # TODO: Register finalizer callback to deinit SPI. 169 | # That would get called on soft reset. 170 | 171 | buscfg = esp.spi_bus_config_t({ 172 | "miso_io_num": self.miso, 173 | "mosi_io_num": self.mosi, 174 | "sclk_io_num": self.clk, 175 | "quadwp_io_num": -1, 176 | "quadhd_io_num": -1, 177 | "max_transfer_sz": self.buf_size, 178 | }) 179 | 180 | devcfg_flags = esp.SPI_DEVICE.NO_DUMMY 181 | if self.half_duplex: 182 | devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX 183 | 184 | devcfg = esp.spi_device_interface_config_t({ 185 | "clock_speed_hz": self.mhz*1000*1000, # Clock out at DISP_SPI_MHZ MHz 186 | "mode": 0, # SPI mode 0 187 | "spics_io_num": self.cs, # CS pin 188 | "queue_size": 2, 189 | "flags": devcfg_flags, 190 | "duty_cycle_pos": 128, 191 | }) 192 | 193 | if self.hybrid and hasattr(esp, 'ili9xxx_post_cb_isr'): 194 | devcfg.pre_cb = None 195 | devcfg.post_cb = esp.ili9xxx_post_cb_isr 196 | else: 197 | devcfg.pre_cb = esp.ex_spi_pre_cb_isr 198 | devcfg.post_cb = esp.ex_spi_post_cb_isr 199 | 200 | esp.gpio_pad_select_gpio(self.cs) 201 | 202 | # Initialize the SPI bus, if needed. 203 | 204 | if buscfg.mosi_io_num >= 0 and \ 205 | buscfg.sclk_io_num >= 0: 206 | 207 | if buscfg.miso_io_num >= 0: 208 | esp.gpio_pad_select_gpio(self.miso) 209 | esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) 210 | esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) 211 | 212 | esp.gpio_pad_select_gpio(self.mosi) 213 | esp.gpio_pad_select_gpio(self.clk) 214 | esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) 215 | esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) 216 | 217 | ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) 218 | if ret != 0: raise RuntimeError("Failed initializing SPI bus") 219 | 220 | self.trans_buffer = esp.heap_caps_malloc(TRANS_BUFFER_LEN, esp.MALLOC_CAP.DMA) 221 | self.cmd_trans_data = self.trans_buffer.__dereference__(1) 222 | self.word_trans_data = self.trans_buffer.__dereference__(4) 223 | 224 | # Attach the LCD to the SPI bus 225 | 226 | ptr_to_spi = esp.C_Pointer() 227 | ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) 228 | if ret != 0: raise RuntimeError("Failed adding SPI device") 229 | self.spi = ptr_to_spi.ptr_val 230 | 231 | self.bytes_transmitted = 0 232 | completed_spi_transaction = esp.spi_transaction_t() 233 | cast_spi_transaction_instance = esp.spi_transaction_t.__cast_instance__ 234 | 235 | def post_isr(arg): 236 | reported_transmitted = self.bytes_transmitted 237 | if reported_transmitted > 0: 238 | print('- Completed DMA of %d bytes (mem_free=0x%X)' % (reported_transmitted , gc.mem_free())) 239 | self.bytes_transmitted -= reported_transmitted 240 | 241 | # Called in ISR context! 242 | def flush_isr(spi_transaction_ptr): 243 | self.disp_drv.flush_ready() 244 | # esp.spi_device_release_bus(self.spi) 245 | esp.get_ccount(self.end_time_ptr) 246 | 247 | # cast_spi_transaction_instance(completed_spi_transaction, spi_transaction_ptr) 248 | # self.bytes_transmitted += completed_spi_transaction.length 249 | # try: 250 | # micropython.schedule(post_isr, None) 251 | # except RuntimeError: 252 | # pass 253 | 254 | self.spi_callbacks = esp.spi_transaction_set_cb(None, flush_isr) 255 | 256 | # 257 | # Deinitialize SPI device and bus, and free memory 258 | # This function is called from finilizer during gc sweep - therefore must not allocate memory! 259 | # 260 | 261 | trans_result_ptr = esp.C_Pointer() 262 | 263 | def deinit(self): 264 | 265 | print('Deinitializing {}..'.format(self.display_name)) 266 | 267 | # Prevent callbacks to lvgl, which refer to the buffers we are about to delete 268 | 269 | if lv_utils.event_loop.is_running(): 270 | self.event_loop.deinit() 271 | 272 | self.disp_drv.remove() 273 | 274 | if self.spi: 275 | 276 | # Pop all pending transaction results 277 | 278 | ret = 0 279 | while ret == 0: 280 | ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , 1) 281 | 282 | # Remove device 283 | 284 | esp.spi_bus_remove_device(self.spi) 285 | self.spi = None 286 | 287 | # Free SPI bus 288 | 289 | esp.spi_bus_free(self.spihost) 290 | self.spihost = None 291 | 292 | # Free RAM 293 | 294 | if self.buf1: 295 | esp.heap_caps_free(self.buf1) 296 | self.buf1 = None 297 | 298 | if self.buf2: 299 | esp.heap_caps_free(self.buf2) 300 | self.buf2 = None 301 | 302 | if self.trans_buffer: 303 | esp.heap_caps_free(self.trans_buffer) 304 | self.trans_buffer = None 305 | 306 | 307 | ###################################################### 308 | 309 | trans = esp.spi_transaction_t() # .__cast__( 310 | # esp.heap_caps_malloc( 311 | # esp.spi_transaction_t.__SIZE__, esp.MALLOC_CAP.DMA)) 312 | 313 | def spi_send(self, data): 314 | self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. 315 | self.trans.tx_buffer = data # data should be allocated as DMA-able memory 316 | self.trans.user = None 317 | esp.spi_device_polling_transmit(self.spi, self.trans) 318 | 319 | def spi_send_dma(self, data): 320 | self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. 321 | self.trans.tx_buffer = data # data should be allocated as DMA-able memory 322 | self.trans.user = self.spi_callbacks 323 | esp.spi_device_queue_trans(self.spi, self.trans, -1) 324 | 325 | ###################################################### 326 | ###################################################### 327 | 328 | def send_cmd(self, cmd): 329 | esp.gpio_set_level(self.dc, 0) # Command mode 330 | self.cmd_trans_data[0] = cmd 331 | self.spi_send(self.cmd_trans_data) 332 | 333 | def send_data(self, data): 334 | esp.gpio_set_level(self.dc, 1) # Data mode 335 | if len(data) > TRANS_BUFFER_LEN: raise RuntimeError('Data too long, please use DMA!') 336 | trans_data = self.trans_buffer.__dereference__(len(data)) 337 | trans_data[:] = data[:] 338 | self.spi_send(trans_data) 339 | 340 | def send_trans_word(self): 341 | esp.gpio_set_level(self.dc, 1) # Data mode 342 | self.spi_send(self.word_trans_data) 343 | 344 | def send_data_dma(self, data): # data should be allocated as DMA-able memory 345 | esp.gpio_set_level(self.dc, 1) # Data mode 346 | self.spi_send_dma(data) 347 | 348 | ###################################################### 349 | 350 | async def _init(self, sleep_func): 351 | 352 | # Initialize non-SPI GPIOs 353 | 354 | esp.gpio_pad_select_gpio(self.dc) 355 | if self.rst != -1: esp.gpio_pad_select_gpio(self.rst) 356 | if self.backlight != -1: esp.gpio_pad_select_gpio(self.backlight) 357 | if self.power != -1: esp.gpio_pad_select_gpio(self.power) 358 | 359 | esp.gpio_set_direction(self.dc, esp.GPIO_MODE.OUTPUT) 360 | if self.rst != -1: esp.gpio_set_direction(self.rst, esp.GPIO_MODE.OUTPUT) 361 | if self.backlight != -1: esp.gpio_set_direction(self.backlight, esp.GPIO_MODE.OUTPUT) 362 | if self.power != -1: esp.gpio_set_direction(self.power, esp.GPIO_MODE.OUTPUT) 363 | 364 | # Power the display 365 | 366 | if self.power != -1: 367 | esp.gpio_set_level(self.power, self.power_on) 368 | await sleep_func(100) 369 | 370 | # Reset the display 371 | 372 | if self.rst != -1: 373 | esp.gpio_set_level(self.rst, 0) 374 | await sleep_func(100) 375 | esp.gpio_set_level(self.rst, 1) 376 | await sleep_func(100) 377 | 378 | # Send all the commands 379 | 380 | for cmd in self.init_cmds: 381 | self.send_cmd(cmd['cmd']) 382 | if 'data' in cmd: 383 | self.send_data(cmd['data']) 384 | if 'delay' in cmd: 385 | await sleep_func(cmd['delay']) 386 | 387 | print("{} initialization completed".format(self.display_name)) 388 | 389 | # Enable backlight 390 | 391 | if self.backlight != -1: 392 | print("Enable backlight") 393 | esp.gpio_set_level(self.backlight, self.backlight_on) 394 | 395 | # Register the driver 396 | self.disp_drv.register() 397 | 398 | 399 | def init(self): 400 | import utime 401 | generator = self._init(lambda ms:(yield ms)) 402 | try: 403 | while True: 404 | ms = next(generator) 405 | utime.sleep_ms(ms) 406 | except StopIteration: 407 | pass 408 | 409 | async def init_async(self): 410 | import uasyncio 411 | await self._init(uasyncio.sleep_ms) 412 | 413 | def power_down(self): 414 | 415 | if self.power != -1: 416 | esp.gpio_set_level(self.power, 1 - self.power_on) 417 | 418 | if self.backlight != -1: 419 | esp.gpio_set_level(self.backlight, 1 - self.backlight_on) 420 | 421 | 422 | ###################################################### 423 | 424 | start_time_ptr = esp.C_Pointer() 425 | end_time_ptr = esp.C_Pointer() 426 | flush_acc_setup_cycles = 0 427 | flush_acc_dma_cycles = 0 428 | 429 | def flush(self, disp_drv, area, color_p): 430 | 431 | if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: 432 | self.flush_acc_dma_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val 433 | 434 | esp.get_ccount(self.start_time_ptr) 435 | 436 | # esp.spi_device_acquire_bus(self.spi, esp.ESP.MAX_DELAY) 437 | 438 | # Column addresses 439 | self.send_cmd(0x2A) 440 | 441 | x1 = area.x1 + self.start_x 442 | x2 = area.x2 + self.start_x 443 | 444 | self.word_trans_data[0] = (x1 >> 8) & 0xFF 445 | self.word_trans_data[1] = x1 & 0xFF 446 | self.word_trans_data[2] = (x2 >> 8) & 0xFF 447 | self.word_trans_data[3] = x2 & 0xFF 448 | self.send_trans_word() 449 | 450 | # Page addresses 451 | 452 | self.send_cmd(0x2B) 453 | 454 | y1 = area.y1 + self.start_y 455 | y2 = area.y2 + self.start_y 456 | 457 | self.word_trans_data[0] = (y1 >> 8) & 0xFF 458 | self.word_trans_data[1] = y1 & 0xFF 459 | self.word_trans_data[2] = (y2 >> 8) & 0xFF 460 | self.word_trans_data[3] = y2 & 0xFF 461 | self.send_trans_word() 462 | 463 | # Memory write by DMA, disp_flush_ready when finished 464 | 465 | self.send_cmd(0x2C) 466 | 467 | size = (x2 - x1 + 1) * (y2 - y1 + 1) 468 | data_view = color_p.__dereference__(size * lv.color_t.__SIZE__) 469 | 470 | esp.get_ccount(self.end_time_ptr) 471 | if self.end_time_ptr.int_val > self.start_time_ptr.int_val: 472 | self.flush_acc_setup_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val 473 | esp.get_ccount(self.start_time_ptr) 474 | 475 | self.send_data_dma(data_view) 476 | 477 | ###################################################### 478 | 479 | monitor_acc_time = 0 480 | monitor_acc_px = 0 481 | monitor_count = 0 482 | 483 | cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 484 | 485 | def monitor(self, disp_drv, time, px): 486 | self.monitor_acc_time += time 487 | self.monitor_acc_px += px 488 | self.monitor_count += 1 489 | 490 | def stat(self): 491 | if self.monitor_count == 0: 492 | return None 493 | 494 | time = self.monitor_acc_time // self.monitor_count 495 | setup = self.flush_acc_setup_cycles // (self.monitor_count * self.cycles_in_ms) 496 | dma = self.flush_acc_dma_cycles // (self.monitor_count * self.cycles_in_ms) 497 | px = self.monitor_acc_px // self.monitor_count 498 | 499 | self.monitor_acc_time = 0 500 | self.monitor_acc_px = 0 501 | self.monitor_count = 0 502 | self.flush_acc_setup_cycles = 0 503 | self.flush_acc_dma_cycles = 0 504 | 505 | return time, setup, dma, px 506 | 507 | 508 | class ili9341(ili9XXX): 509 | 510 | def __init__(self, 511 | miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, 512 | spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=240, height=320, colormode=COLOR_MODE_BGR, 513 | rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True 514 | ): 515 | 516 | # Make sure Micropython was built such that color won't require processing before DMA 517 | 518 | if lv.color_t.__SIZE__ != 2: 519 | raise RuntimeError('ili9341 micropython driver requires defining LV_COLOR_DEPTH=16') 520 | if colormode == COLOR_MODE_BGR and not hasattr(lv.color_t().ch, 'green_l'): 521 | raise RuntimeError('ili9341 BGR color mode requires defining LV_COLOR_16_SWAP=1') 522 | 523 | self.display_name = 'ILI9341' 524 | self.display_type = DISPLAY_TYPE_ILI9341 525 | 526 | self.init_cmds = [ 527 | {'cmd': 0xCF, 'data': bytes([0x00, 0x83, 0X30])}, 528 | {'cmd': 0xED, 'data': bytes([0x64, 0x03, 0X12, 0X81])}, 529 | {'cmd': 0xE8, 'data': bytes([0x85, 0x01, 0x79])}, 530 | {'cmd': 0xCB, 'data': bytes([0x39, 0x2C, 0x00, 0x34, 0x02])}, 531 | {'cmd': 0xF7, 'data': bytes([0x20])}, 532 | {'cmd': 0xEA, 'data': bytes([0x00, 0x00])}, 533 | {'cmd': 0xC0, 'data': bytes([0x26])}, # Power control 534 | {'cmd': 0xC1, 'data': bytes([0x11])}, # Power control 535 | {'cmd': 0xC5, 'data': bytes([0x35, 0x3E])}, # VCOM control 536 | {'cmd': 0xC7, 'data': bytes([0xBE])}, # VCOM control 537 | {'cmd': 0x36, 'data': bytes([rot | colormode])}, # Memory Access Control 538 | {'cmd': 0x3A, 'data': bytes([0x55])}, # Pixel Format Set 539 | {'cmd': 0xB1, 'data': bytes([0x00, 0x1B])}, 540 | {'cmd': 0xF2, 'data': bytes([0x08])}, 541 | {'cmd': 0x26, 'data': bytes([0x01])}, 542 | {'cmd': 0xE0, 'data': bytes([0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00])}, 543 | {'cmd': 0XE1, 'data': bytes([0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F])}, 544 | {'cmd': 0x2A, 'data': bytes([0x00, 0x00, 0x00, 0xEF])}, 545 | {'cmd': 0x2B, 'data': bytes([0x00, 0x00, 0x01, 0x3f])}, 546 | {'cmd': 0x2C, 'data': bytes([0])}, 547 | {'cmd': 0xB7, 'data': bytes([0x07])}, 548 | {'cmd': 0xB6, 'data': bytes([0x0A, 0x82, 0x27, 0x00])}, 549 | {'cmd': 0x11, 'data': bytes([0]), 'delay':100}, 550 | {'cmd': 0x29, 'data': bytes([0]), 'delay':100} 551 | ] 552 | 553 | super().__init__(miso=miso, mosi=mosi, clk=clk, cs=cs, dc=dc, rst=rst, power=power, backlight=backlight, 554 | backlight_on=backlight_on, power_on=power_on, spihost=spihost, mhz=mhz, factor=factor, hybrid=hybrid, 555 | width=width, height=height, invert=invert, double_buffer=double_buffer, half_duplex=half_duplex, 556 | display_type=self.display_type, asynchronous=asynchronous, initialize=initialize) 557 | 558 | class ili9488(ili9XXX): 559 | 560 | def __init__(self, 561 | miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, 562 | spihost=esp.HSPI_HOST, mhz=40, factor=8, hybrid=True, width=320, height=480, colormode=COLOR_MODE_RGB, 563 | rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True 564 | ): 565 | 566 | if lv.color_t.__SIZE__ != 4: 567 | raise RuntimeError('ili9488 micropython driver requires defining LV_COLOR_DEPTH=32') 568 | if not hybrid: 569 | raise RuntimeError('ili9488 micropython driver do not support non-hybrid driver') 570 | 571 | self.display_name = 'ILI9488' 572 | self.display_type = DISPLAY_TYPE_ILI9488 573 | 574 | self.init_cmds = [ 575 | {'cmd': 0x01, 'data': bytes([0]), 'delay': 200}, 576 | {'cmd': 0x11, 'data': bytes([0]), 'delay': 120}, 577 | {'cmd': 0xE0, 'data': bytes([0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F])}, 578 | {'cmd': 0xE1, 'data': bytes([0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F])}, 579 | {'cmd': 0xC0, 'data': bytes([0x17, 0x15])}, ### 0x13, 0x13 580 | {'cmd': 0xC1, 'data': bytes([0x41])}, ### 581 | {'cmd': 0xC2, 'data': bytes([0x44])}, ### 582 | {'cmd': 0xC5, 'data': bytes([0x00, 0x12, 0x80])}, 583 | #{'cmd': 0xC5, 'data': bytes([0x00, 0x0, 0x0, 0x0])}, 584 | {'cmd': 0x36, 'data': bytes([rot | colormode])}, # Memory Access Control 585 | {'cmd': 0x3A, 'data': bytes([0x66])}, 586 | {'cmd': 0xB0, 'data': bytes([0x00])}, 587 | {'cmd': 0xB1, 'data': bytes([0xA0])}, 588 | {'cmd': 0xB4, 'data': bytes([0x02])}, 589 | {'cmd': 0xB6, 'data': bytes([0x02, 0x02])}, 590 | {'cmd': 0xE9, 'data': bytes([0x00])}, 591 | {'cmd': 0x53, 'data': bytes([0x28])}, 592 | {'cmd': 0x51, 'data': bytes([0x7F])}, 593 | {'cmd': 0xF7, 'data': bytes([0xA9, 0x51, 0x2C, 0x02])}, 594 | {'cmd': 0x29, 'data': bytes([0]), 'delay': 120} 595 | ] 596 | 597 | super().__init__(miso=miso, mosi=mosi, clk=clk, cs=cs, dc=dc, rst=rst, power=power, backlight=backlight, 598 | backlight_on=backlight_on, power_on=power_on, spihost=spihost, mhz=mhz, factor=factor, hybrid=hybrid, 599 | width=width, height=height, invert=invert, double_buffer=double_buffer, half_duplex=half_duplex, 600 | display_type=self.display_type, asynchronous=asynchronous, initialize=initialize) 601 | 602 | class gc9a01(ili9XXX): 603 | # On the tested display the write direction and colormode appear to be 604 | # reversed from how they are presented in the datasheet 605 | 606 | def __init__(self, 607 | miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, 608 | spihost=esp.HSPI_HOST, mhz=60, factor=4, hybrid=True, width=240, height=240, colormode=COLOR_MODE_RGB, 609 | rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True 610 | ): 611 | 612 | if lv.color_t.SIZE != 2: 613 | raise RuntimeError('gc9a01 micropython driver requires defining LV_COLOR_DEPTH=16') 614 | 615 | # This is included as the color mode appears to be reversed from the 616 | # datasheet and the ili9XXX driver values 617 | if colormode == COLOR_MODE_RGB: 618 | self.colormode = COLOR_MODE_BGR 619 | elif colormode == COLOR_MODE_BGR: 620 | self.colormode = COLOR_MODE_RGB 621 | 622 | self.display_name = 'GC9A01' 623 | self.display_type = DISPLAY_TYPE_GC9A01 624 | 625 | self.init_cmds = [ 626 | {'cmd': 0xEF, 'data': bytes([0])}, 627 | {'cmd': 0xEB, 'data': bytes([0x14])}, 628 | {'cmd': 0xFE, 'data': bytes([0])}, 629 | {'cmd': 0xEF, 'data': bytes([0])}, 630 | {'cmd': 0xEB, 'data': bytes([0x14])}, 631 | {'cmd': 0x84, 'data': bytes([0x40])}, 632 | {'cmd': 0x85, 'data': bytes([0xFF])}, 633 | {'cmd': 0x86, 'data': bytes([0xFF])}, 634 | {'cmd': 0x87, 'data': bytes([0xFF])}, 635 | {'cmd': 0x88, 'data': bytes([0x0A])}, 636 | {'cmd': 0x89, 'data': bytes([0x21])}, 637 | {'cmd': 0x8A, 'data': bytes([0x00])}, 638 | {'cmd': 0x8B, 'data': bytes([0x80])}, 639 | {'cmd': 0x8C, 'data': bytes([0x01])}, 640 | {'cmd': 0x8D, 'data': bytes([0x01])}, 641 | {'cmd': 0x8E, 'data': bytes([0xFF])}, 642 | {'cmd': 0x8F, 'data': bytes([0xFF])}, 643 | {'cmd': 0xB6, 'data': bytes([0x00, 0x00])}, 644 | {'cmd': 0x36, 'data': bytes([rot | self.colormode])}, # MADCTL 645 | {'cmd': 0x3A, 'data': bytes([0x05])}, 646 | {'cmd': 0x90, 'data': bytes([0x08, 0x08, 0x08, 0x08])}, 647 | {'cmd': 0xBD, 'data': bytes([0x06])}, 648 | {'cmd': 0xBC, 'data': bytes([0x00])}, 649 | {'cmd': 0xFF, 'data': bytes([0x60, 0x01, 0x04])}, 650 | {'cmd': 0xC3, 'data': bytes([0x13])}, 651 | {'cmd': 0xC4, 'data': bytes([0x13])}, 652 | {'cmd': 0xC9, 'data': bytes([0x22])}, 653 | {'cmd': 0xBE, 'data': bytes([0x11])}, 654 | {'cmd': 0xE1, 'data': bytes([0x10, 0x0E])}, 655 | {'cmd': 0xDF, 'data': bytes([0x21, 0x0c, 0x02])}, 656 | {'cmd': 0xF0, 'data': bytes([0x45, 0x09, 0x08, 0x08, 0x26, 0x2A])}, 657 | {'cmd': 0xF1, 'data': bytes([0x43, 0x70, 0x72, 0x36, 0x37, 0x6F])}, 658 | {'cmd': 0xF2, 'data': bytes([0x45, 0x09, 0x08, 0x08, 0x26, 0x2A])}, 659 | {'cmd': 0xF3, 'data': bytes([0x43, 0x70, 0x72, 0x36, 0x37, 0x6F])}, 660 | {'cmd': 0xED, 'data': bytes([0x1B, 0x0B])}, 661 | {'cmd': 0xAE, 'data': bytes([0x77])}, 662 | {'cmd': 0xCD, 'data': bytes([0x63])}, 663 | {'cmd': 0x70, 'data': bytes([0x07, 0x07, 0x04, 0x0E, 0x0F, 0x09, 0x07, 0x08, 0x03])}, 664 | {'cmd': 0xE8, 'data': bytes([0x34])}, 665 | {'cmd': 0x62, 'data': bytes([0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70])}, 666 | {'cmd': 0x63, 'data': bytes([0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70])}, 667 | {'cmd': 0x64, 'data': bytes([0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07])}, 668 | {'cmd': 0x66, 'data': bytes([0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00])}, 669 | {'cmd': 0x67, 'data': bytes([0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98])}, 670 | {'cmd': 0x74, 'data': bytes([0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00])}, 671 | {'cmd': 0x98, 'data': bytes([0x3e, 0x07])}, 672 | {'cmd': 0x35, 'data': bytes([0])}, 673 | {'cmd': 0x21, 'data': bytes([0])}, 674 | {'cmd': 0x11, 'data': bytes([0]), 'delay': 20}, 675 | {'cmd': 0x29, 'data': bytes([0]), 'delay': 120} 676 | ] 677 | 678 | super().__init__(miso=miso, mosi=mosi, clk=clk, cs=cs, dc=dc, rst=rst, power=power, backlight=backlight, 679 | backlight_on=backlight_on, power_on=power_on, spihost=spihost, mhz=mhz, factor=factor, hybrid=hybrid, 680 | width=width, height=height, invert=invert, double_buffer=double_buffer, half_duplex=half_duplex, 681 | display_type=self.display_type, asynchronous=asynchronous, initialize=initialize) 682 | 683 | class st7789(ili9XXX): 684 | PORTRAIT = const(0) 685 | LANDSCAPE = const(1) 686 | INVERSE_PORTRAIT = const(2) 687 | INVERSE_LANDSCAPE = const(3) 688 | 689 | def __init__(self, 690 | miso=-1, mosi=19, clk=18, cs=5, dc=16, rst=23, power=-1, backlight=4, backlight_on=1, power_on=0, 691 | spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=135, height=240, start_x=0, start_y=0, 692 | colormode=COLOR_MODE_BGR, rot=PORTRAIT, invert=True, double_buffer=True, half_duplex=True, 693 | asynchronous=False, initialize=True): 694 | 695 | # Make sure Micropython was built such that color won't require processing before DMA 696 | 697 | if lv.color_t.__SIZE__ != 2: 698 | raise RuntimeError('st7789 micropython driver requires defining LV_COLOR_DEPTH=16') 699 | if colormode == COLOR_MODE_BGR and not hasattr(lv.color_t().ch, 'green_l'): 700 | raise RuntimeError('st7789 BGR color mode requires defining LV_COLOR_16_SWAP=1') 701 | 702 | self.display_name = 'ST7789' 703 | self.display_type = DISPLAY_TYPE_ST7789 704 | 705 | # MADCTL[rot % 4] 706 | madctl = [0x00, 0x60, 0xc0, 0xa0][rot % 4] 707 | 708 | # (width, height, start_x, start_y)[rot % 4] 709 | if [width, height] == [320, 240] or [width, height] == [240, 320]: 710 | table = [ 711 | (320, 240, 0, 0), (240, 320, 0, 0), 712 | (320, 240, 0, 0), (240, 320, 0, 0) 713 | ] 714 | elif [width, height] == [240, 240]: 715 | table = [ 716 | (240, 240, 0, 0), (240, 240, 0, 0), 717 | (240, 240, 0, 80), (240, 240, 80, 0) 718 | ] 719 | elif [width, height] == [135, 240] or [width, height] == [240, 135]: 720 | table = [ 721 | (135, 240, 52, 40), (240, 135, 40, 53), 722 | (135, 240, 53, 40), (240, 135, 40, 52) 723 | ] 724 | else: 725 | raise ValueError( 726 | "Unsupported display. 320x240, 240x240 and 135x240 are supported." 727 | ) 728 | 729 | width, height, start_x, start_y = table[rot % 4] 730 | 731 | self.init_cmds = [ 732 | {'cmd': 0x11, 'data': bytes([0x0]), 'delay': 120}, 733 | {'cmd': 0x13, 'data': bytes([0x0])}, 734 | {'cmd': 0x36, 'data': bytes([madctl | colormode])}, # MADCTL 735 | {'cmd': 0xb6, 'data': bytes([0xa, 0x82])}, 736 | {'cmd': 0x3a, 'data': bytes([0x55]),'delay': 10}, 737 | {'cmd': 0xb2, 'data': bytes([0xc, 0xc, 0x0, 0x33, 0x33])}, 738 | {'cmd': 0xb7, 'data': bytes([0x35])}, 739 | {'cmd': 0xbb, 'data': bytes([0x28])}, 740 | {'cmd': 0xc0, 'data': bytes([0xc])}, 741 | {'cmd': 0xc2, 'data': bytes([0x1, 0xff])}, 742 | {'cmd': 0xc3, 'data': bytes([0x10])}, 743 | {'cmd': 0xc4, 'data': bytes([0x20])}, 744 | {'cmd': 0xc6, 'data': bytes([0xf])}, 745 | {'cmd': 0xd0, 'data': bytes([0xa4, 0xa1])}, 746 | {'cmd': 0xe0, 'data': bytes([0xd0, 0x0, 0x2, 0x7, 0xa, 0x28, 0x32, 0x44, 0x42, 0x6, 0xe, 0x12, 0x14, 0x17])}, 747 | {'cmd': 0xe1, 'data': bytes([0xd0, 0x0, 0x2, 0x7, 0xa, 0x28, 0x31, 0x54, 0x47, 0xe, 0x1c, 0x17, 0x1b, 0x1e])}, 748 | {'cmd': 0x21, 'data': bytes([0x0])}, 749 | {'cmd': 0x2a, 'data': bytes([0x0, 0x0, 0x0, 0xe5])}, 750 | {'cmd': 0x2b, 'data': bytes([0x0, 0x0, 0x1, 0x3f]), 'delay': 120}, 751 | {'cmd': 0x29, 'data': bytes([0x0]), 'delay': 120} 752 | ] 753 | 754 | super().__init__(miso=miso, mosi=mosi, clk=clk, cs=cs, dc=dc, rst=rst, power=power, backlight=backlight, 755 | backlight_on=backlight_on, power_on=power_on, spihost=spihost, mhz=mhz, factor=factor, hybrid=hybrid, 756 | width=width, height=height, start_x=start_x, start_y=start_y, invert=invert, double_buffer=double_buffer, 757 | half_duplex=half_duplex, display_type=self.display_type, asynchronous=asynchronous, initialize=initialize) 758 | --------------------------------------------------------------------------------