Skip to content

Commit 2e541ad

Browse files
committed
feat(scripts): support lvgl running on the unix
1 parent ddb03d5 commit 2e541ad

File tree

11 files changed

+933
-6
lines changed

11 files changed

+933
-6
lines changed

ports/esp32/boards/ESP32_S3_BOX_3/mpconfigboard.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@
99

1010
#define MICROPY_HW_I2C0_SCL (9)
1111
#define MICROPY_HW_I2C0_SDA (8)
12+
13+
#define MICROPY_LV_USE_LOG (1)

scripts/display_driver_utils.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import usys as sys
2+
sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419
3+
4+
import lvgl as lv
5+
6+
try:
7+
import lv_utils
8+
lv_utils_available = True
9+
except:
10+
lv_utils_available = False
11+
12+
ORIENT_LANDSCAPE = False
13+
ORIENT_PORTRAIT = True
14+
15+
class driver:
16+
17+
def __init__(self,width=420,height=320,orientation=ORIENT_PORTRAIT, asynchronous=False, exception_sink=None, defaultGroup=True):
18+
19+
if not lv.is_initialized():
20+
lv.init()
21+
22+
self.group = lv.group_create()
23+
if defaultGroup:
24+
self.group.set_default()
25+
26+
self.width = width
27+
self.height = height
28+
self.orientation = orientation
29+
self.asynchronous = asynchronous
30+
self.exception_sink = exception_sink
31+
self.disp = None
32+
self.touch = None
33+
self.type = None
34+
if not (lv_utils_available and lv_utils.event_loop.is_running()):
35+
self.init_gui()
36+
37+
def init_gui_SDL(self):
38+
if lv_utils_available:
39+
self.event_loop = lv_utils.event_loop(asynchronous=self.asynchronous, exception_sink=self.exception_sink)
40+
41+
lv.sdl_window_create(self.width, self.height)
42+
self.mouse = lv.sdl_mouse_create()
43+
self.keyboard = lv.sdl_keyboard_create()
44+
self.keyboard.set_group(self.group)
45+
self.type = "SDL"
46+
print("Running the SDL lvgl version")
47+
48+
def init_gui_ili9341(self):
49+
50+
# Initialize ILI9341 display
51+
52+
from ili9XXX import ili9341,LANDSCAPE
53+
from xpt2046 import xpt2046
54+
import espidf as esp
55+
56+
if lv_utils_available:
57+
self.event_loop = lv_utils.event_loop(asynchronous=self.asynchronous, exception_sink=self.exception_sink)
58+
59+
if self.orientation == ORIENT_PORTRAIT:
60+
print ("Running the ili9341 lvgl version in portrait mode")
61+
62+
# Initialize ILI9341 display in prtrait mode
63+
# the following are the settings for the Lolin tft 2.4 display
64+
# self.disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, spihost=esp.VSPI_HOST)
65+
# self.touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=3751, cal_x1 = 210, cal_y0=3906, cal_y1 = 283, transpose=True)
66+
67+
self.disp = ili9341()
68+
self.touch = xpt2046()
69+
70+
elif self.orientation == ORIENT_LANDSCAPE:
71+
print ("Running the ili9341 lvgl version in landscape mode")
72+
# Initialize ILI9341 display
73+
# the following are the settings for the Lolin tft 2.4 display
74+
# self.disp = ili9341(miso=19,mosi=23,clk=18, cs=26, dc=5, rst=-1, power=-1, backlight=-1, backlight_on=0,
75+
# spihost=esp.VSPI_HOST, width=320, height=240, rot=LANDSCAPE)
76+
# self.touch = xpt2046(spihost=esp.VSPI_HOST,cal_x0=3799, cal_x1 = 353, cal_y0=220, cal_y1 = 3719, transpose=False)
77+
78+
# Register xpt2046 touch driver
79+
self.disp = ili9341(width=320, height=240, rot=LANDSCAPE)
80+
self.touch = xpt2046(cal_x0=3799, cal_x1 = 353, cal_y0=220, cal_y1 = 3719,transpose = False)
81+
82+
else:
83+
raise RuntimeError("Invalid orientation")
84+
85+
self.type="ili9341"
86+
87+
'''
88+
# Register raw resistive touch driver (remove xpt2046 initialization first)
89+
import rtch
90+
touch = rtch.touch(xp = 32, yp = 33, xm = 25, ym = 26, touch_rail = 27, touch_sense = 33)
91+
touch.init()
92+
self.indev_drv = lv.indev_create()
93+
self.indev_drv.set_type(lv.INDEV_TYPE.POINTER)
94+
self.indev_drv.set_read_cb(touch.read)
95+
'''
96+
97+
def init_gui_twatch(self):
98+
99+
import ttgo
100+
from axp_constants import AXP202_VBUS_VOL_ADC1,AXP202_VBUS_CUR_ADC1,AXP202_BATT_CUR_ADC1,AXP202_BATT_VOL_ADC1
101+
102+
watch = ttgo.Watch()
103+
tft = watch.tft
104+
power = watch.pmu
105+
power.adc1Enable(AXP202_VBUS_VOL_ADC1
106+
| AXP202_VBUS_CUR_ADC1
107+
| AXP202_BATT_CUR_ADC1
108+
| AXP202_BATT_VOL_ADC1, True)
109+
watch.lvgl_begin()
110+
watch.tft.backlight_fade(100)
111+
112+
self.type="t-watch"
113+
print("Running lvgl on the LilyGo t-watch 2020")
114+
115+
def init_gui(self):
116+
117+
# Identify platform and initialize it
118+
119+
try:
120+
self.init_gui_twatch()
121+
return
122+
except:
123+
pass
124+
125+
try:
126+
self.init_gui_ili9341()
127+
return
128+
except ImportError:
129+
pass
130+
131+
try:
132+
self.init_gui_SDL()
133+
return
134+
except ImportError:
135+
pass
136+
137+
raise RuntimeError("Could not find a suitable display driver!")
138+

scripts/evdev.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# LVGL indev driver for evdev mouse device
2+
# (for the unix micropython port)
3+
4+
import ustruct
5+
import select
6+
import lvgl as lv
7+
8+
# Default crosshair cursor
9+
class crosshair_cursor:
10+
def __init__(self, scr=None):
11+
self.scr = scr if scr else lv.scr_act()
12+
self.hor_res = self.scr.get_width()
13+
self.ver_res = self.scr.get_height()
14+
self.cursor_style = lv.style_t()
15+
self.cursor_style.set_line_width(1)
16+
self.cursor_style.set_line_dash_gap(5)
17+
self.cursor_style.set_line_dash_width(1)
18+
self.cursor_hor = lv.line(self.scr)
19+
self.cursor_hor.add_style(self.cursor_style, lv.PART.MAIN)
20+
self.cursor_ver = lv.line(self.scr)
21+
self.cursor_ver.add_style(self.cursor_style, lv.PART.MAIN)
22+
23+
def __call__(self, data):
24+
# print("%d : %d:%d" % (data.state, data.point.x, data.point.y))
25+
self.cursor_hor.set_points([{'x':0,'y':data.point.y},{'x':self.hor_res,'y':data.point.y}],2)
26+
self.cursor_ver.set_points([{'y':0,'x':data.point.x},{'y':self.ver_res,'x':data.point.x}],2)
27+
28+
def delete(self):
29+
self.cursor_hor.delete()
30+
self.cursor_ver.delete()
31+
32+
# evdev driver for mouse
33+
class mouse_indev:
34+
def __init__(self, scr=None, cursor=None, device='/dev/input/mice'):
35+
36+
# Open evdev and initialize members
37+
self.evdev = open(device, 'rb')
38+
self.poll = select.poll()
39+
self.poll.register(self.evdev.fileno())
40+
self.scr = scr if scr else lv.scr_act()
41+
self.cursor = cursor if cursor else crosshair_cursor(self.scr)
42+
self.hor_res = self.scr.get_width()
43+
self.ver_res = self.scr.get_height()
44+
45+
# Register LVGL indev driver
46+
self.indev = lv.indev_create()
47+
self.indev.set_type(lv.INDEV_TYPE.POINTER)
48+
self.indev.set_read_cb(self.mouse_read)
49+
50+
def mouse_read(self, indev, data) -> int:
51+
52+
# Check if there is input to be read from evdev
53+
if not self.poll.poll()[0][1] & select.POLLIN:
54+
return 0
55+
56+
# Read and parse evdev mouse data
57+
mouse_data = ustruct.unpack('bbb',self.evdev.read(3))
58+
59+
# Data is relative, update coordinates
60+
data.point.x += mouse_data[1]
61+
data.point.y -= mouse_data[2]
62+
63+
# Handle coordinate overflow cases
64+
data.point.x = min(data.point.x, self.hor_res - 1)
65+
data.point.y = min(data.point.y, self.ver_res - 1)
66+
data.point.x = max(data.point.x, 0)
67+
data.point.y = max(data.point.y, 0)
68+
69+
# Update "pressed" status
70+
data.state = lv.INDEV_STATE.PRESSED if ((mouse_data[0] & 1) == 1) else lv.INDEV_STATE.RELEASED
71+
72+
# Draw cursor, if needed
73+
if self.cursor: self.cursor(data)
74+
return 0
75+
76+
def delete(self):
77+
self.evdev.close()
78+
if self.cursor and hasattr(self.cursor, 'delete'):
79+
self.cursor.delete()
80+
self.indev.enable(False)

scripts/fs_driver.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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("<L", len(tmp_data))
42+
except OSError as e:
43+
raise RuntimeError("fs_read_callback(%s) exception %s" % (fs_file.__cast__()['path'], e))
44+
45+
return lv.FS_RES.OK
46+
47+
48+
def fs_seek_cb(drv, fs_file, pos, whence):
49+
try:
50+
fs_file.__cast__()['file'].seek(pos, whence)
51+
except OSError as e:
52+
raise RuntimeError("fs_seek_callback(%s) exception %s" % (fs_file.__cast__()['path'], e))
53+
54+
return lv.FS_RES.OK
55+
56+
57+
def fs_tell_cb(drv, fs_file, pos):
58+
try:
59+
tpos = fs_file.__cast__()['file'].tell()
60+
pos.__dereference__(4)[0:4] = struct.pack("<L", tpos)
61+
except OSError as e:
62+
raise RuntimeError("fs_tell_callback(%s) exception %s" % (fs_file.__cast__()['path'], e))
63+
64+
return lv.FS_RES.OK
65+
66+
67+
def fs_write_cb(drv, fs_file, buf, btw, bw):
68+
try:
69+
wr = fs_file.__cast__()['file'].write(buf.__dereference__(btw)[0:btw])
70+
bw.__dereference__(4)[0:4] = struct.pack("<L", wr)
71+
except OSError as e:
72+
raise RuntimeError("fs_write_callback(%s) exception %s" % (fs_file.__cast__()['path'], e))
73+
74+
return lv.FS_RES.OK
75+
76+
77+
def fs_register(fs_drv, letter, cache_size=500):
78+
79+
fs_drv.init()
80+
fs_drv.letter = ord(letter)
81+
fs_drv.open_cb = fs_open_cb
82+
fs_drv.read_cb = fs_read_cb
83+
fs_drv.write_cb = fs_write_cb
84+
fs_drv.seek_cb = fs_seek_cb
85+
fs_drv.tell_cb = fs_tell_cb
86+
fs_drv.close_cb = fs_close_cb
87+
88+
if cache_size >= 0:
89+
fs_drv.cache_size = cache_size
90+
91+
fs_drv.register()
92+

scripts/lv_colors.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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)

0 commit comments

Comments
 (0)