Skip to content

Commit 2f4adfc

Browse files
authored
Add support for unPhone 9 (#74)
https://unphone.net/ What worked: - hx8357d Display and XPT2046 touch screen works - Turn display backlight on/off via TCA9555 chip - Buttons TODOs: - Use LEDs - LoRa - IR `.../lib/drivers/display/hx8357d/` is a not modified copy from https://github.com/lvgl-micropython/lvgl_micropython/tree/main/api_drivers/common_api_drivers/display/hx8357d `.../lib/drivers/indev/xpt2046.py` based on https://github.com/lvgl-micropython/lvgl_micropython/blob/main/api_drivers/common_api_drivers/indev/xpt2046.py but is modified: Because of the shared SPI bus for SPI for hx8357d display and xpt2046 touch controller. For this i add the management of `CS` pins for reading the touch controller. Let's discuss how to add this to upstream in lvgl-micropython/lvgl_micropython#536
1 parent 5ded4b4 commit 2f4adfc

7 files changed

Lines changed: 814 additions & 5 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import sys
2+
from . import hx8357d
3+
from . import _hx8357d_init
4+
5+
# Register _hx8357d_init in sys.modules so __import__('_hx8357d_init') can find it
6+
# This is needed because display_driver_framework.py uses __import__('_hx8357d_init')
7+
# expecting a top-level module, but _hx8357d_init is in the hx8357d package subdirectory
8+
sys.modules['_hx8357d_init'] = _hx8357d_init
9+
10+
# Explicitly define __all__ and re-export public symbols from hx8357d module
11+
__all__ = [
12+
'HX8357D',
13+
'STATE_HIGH',
14+
'STATE_LOW',
15+
'STATE_PWM',
16+
'BYTE_ORDER_RGB',
17+
'BYTE_ORDER_BGR',
18+
]
19+
20+
# Re-export the public symbols
21+
HX8357D = hx8357d.HX8357D
22+
STATE_HIGH = hx8357d.STATE_HIGH
23+
STATE_LOW = hx8357d.STATE_LOW
24+
STATE_PWM = hx8357d.STATE_PWM
25+
BYTE_ORDER_RGB = hx8357d.BYTE_ORDER_RGB
26+
BYTE_ORDER_BGR = hx8357d.BYTE_ORDER_BGR
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
2+
3+
import time
4+
from micropython import const # NOQA
5+
6+
import lvgl as lv # NOQA
7+
import lcd_bus # NOQA
8+
9+
10+
_SWRESET = const(0x01)
11+
_SLPOUT = const(0x11)
12+
_DISPON = const(0x29)
13+
_COLMOD = const(0x3A)
14+
_MADCTL = const(0x36)
15+
_TEON = const(0x35)
16+
_TEARLINE = const(0x44)
17+
_SETOSC = const(0xB0)
18+
_SETPWR1 = const(0xB1)
19+
_SETRGB = const(0xB3)
20+
_SETCOM = const(0xB6)
21+
_SETCYC = const(0xB4)
22+
_SETC = const(0xB9)
23+
_SETSTBA = const(0xC0)
24+
_SETPANEL = const(0xCC)
25+
_SETGAMMA = const(0xE0)
26+
27+
28+
def init(self):
29+
param_buf = bytearray(34)
30+
param_mv = memoryview(param_buf)
31+
32+
time.sleep_ms(300) # NOQA
33+
param_buf[:3] = bytearray([0xFF, 0x83, 0x57])
34+
self.set_params(_SETC, param_mv[:3])
35+
36+
param_buf[0] = 0x80
37+
self.set_params(_SETRGB, param_mv[:1])
38+
39+
param_buf[:4] = bytearray([0x00, 0x06, 0x06, 0x25])
40+
self.set_params(_SETCOM, param_mv[:4])
41+
42+
param_buf[0] = 0x68
43+
self.set_params(_SETOSC, param_mv[:1])
44+
45+
param_buf[0] = 0x05
46+
self.set_params(_SETPANEL, param_mv[:1])
47+
48+
param_buf[:6] = bytearray([0x00, 0x15, 0x1C, 0x1C, 0x83, 0xAA])
49+
self.set_params(_SETPWR1, param_mv[:6])
50+
51+
param_buf[:6] = bytearray([0x50, 0x50, 0x01, 0x3C, 0x1E, 0x08])
52+
self.set_params(_SETSTBA, param_mv[:6])
53+
54+
param_buf[:7] = bytearray([0x02, 0x40, 0x00, 0x2A, 0x2A, 0x0D, 0x78])
55+
self.set_params(_SETCYC, param_mv[:7])
56+
57+
param_buf[:34] = bytearray([
58+
0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A,
59+
0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35,
60+
0x41, 0x4b, 0x4b, 0x42, 0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x00, 0x01])
61+
self.set_params(_SETGAMMA, param_mv[:34])
62+
63+
param_buf[0] = (
64+
self._madctl(
65+
self._color_byte_order,
66+
self._ORIENTATION_TABLE # NOQA
67+
)
68+
)
69+
self.set_params(_MADCTL, param_mv[:1])
70+
71+
color_size = lv.color_format_get_size(self._color_space)
72+
if color_size == 2: # NOQA
73+
pixel_format = 0x55
74+
else:
75+
raise RuntimeError(
76+
f'{self.__class__.__name__} IC only supports '
77+
'lv.COLOR_FORMAT.RGB565'
78+
)
79+
80+
param_buf[0] = pixel_format
81+
self.set_params(_COLMOD, param_mv[:1])
82+
83+
param_buf[0] = 0x00
84+
self.set_params(_TEON, param_mv[:1])
85+
86+
param_buf[:2] = bytearray([0x00, 0x02])
87+
self.set_params(_TEARLINE, param_mv[:2])
88+
89+
time.sleep_ms(150) # NOQA
90+
self.set_params(_SLPOUT)
91+
92+
time.sleep_ms(50) # NOQA
93+
self.set_params(_DISPON)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
2+
3+
import display_driver_framework
4+
5+
6+
STATE_HIGH = display_driver_framework.STATE_HIGH
7+
STATE_LOW = display_driver_framework.STATE_LOW
8+
STATE_PWM = display_driver_framework.STATE_PWM
9+
10+
BYTE_ORDER_RGB = display_driver_framework.BYTE_ORDER_RGB
11+
BYTE_ORDER_BGR = display_driver_framework.BYTE_ORDER_BGR
12+
13+
14+
class HX8357D(display_driver_framework.DisplayDriver):
15+
pass
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
2+
3+
import lvgl as lv # NOQA
4+
from micropython import const # NOQA
5+
import micropython # NOQA
6+
import machine # NOQA
7+
import pointer_framework
8+
import time
9+
10+
11+
_CMD_X_READ = const(0xD0) # 12 bit resolution
12+
_CMD_Y_READ = const(0x90) # 12 bit resolution
13+
_CMD_Z1_READ = const(0xB0)
14+
_CMD_Z2_READ = const(0xC0)
15+
_MIN_RAW_COORD = const(10)
16+
_MAX_RAW_COORD = const(4090)
17+
18+
19+
class XPT2046(pointer_framework.PointerDriver):
20+
touch_threshold = 400
21+
confidence = 5
22+
margin = 50
23+
24+
def __init__(
25+
self,
26+
device: machine.SPI.Bus,
27+
display_width: int,
28+
display_height: int,
29+
lcd_cs: int,
30+
touch_cs: int,
31+
touch_cal=None,
32+
startup_rotation=lv.DISPLAY_ROTATION._0,
33+
debug=False,
34+
):
35+
self._device = device # machine.SPI.Bus() instance, shared with display
36+
self._debug = debug
37+
38+
self.lcd_cs = machine.Pin(lcd_cs, machine.Pin.OUT, value=0)
39+
self.touch_cs = machine.Pin(touch_cs, machine.Pin.OUT, value=1)
40+
41+
self._width = display_width
42+
self._height = display_height
43+
44+
self._tx_buf = bytearray(3)
45+
self._tx_mv = memoryview(self._tx_buf)
46+
47+
self._rx_buf = bytearray(3)
48+
self._rx_mv = memoryview(self._rx_buf)
49+
50+
self.__confidence = max(min(self.confidence, 25), 3)
51+
self.__points = [[0, 0] for _ in range(self.__confidence)]
52+
53+
margin = max(min(self.margin, 100), 1)
54+
self.__margin = margin * margin
55+
56+
super().__init__(
57+
touch_cal=touch_cal, startup_rotation=startup_rotation, debug=debug
58+
)
59+
60+
def _read_reg(self, reg, num_bytes):
61+
self._tx_buf[0] = reg
62+
self._device.write_readinto(self._tx_mv[:num_bytes], self._rx_mv[:num_bytes])
63+
return ((self._rx_buf[1] << 8) | self._rx_buf[2]) >> 3
64+
65+
def _get_coords(self):
66+
try:
67+
self.lcd_cs.value(1) # deselect LCD to avoid conflicts
68+
self.touch_cs.value(0) # select touch chip
69+
70+
z1 = self._read_reg(_CMD_Z1_READ, 3)
71+
z2 = self._read_reg(_CMD_Z2_READ, 3)
72+
z = z1 + ((_MAX_RAW_COORD + 6) - z2)
73+
if z < self.touch_threshold:
74+
return None # Not touched
75+
76+
points = self.__points
77+
count = 0
78+
end_time = time.ticks_us() + 5000
79+
while time.ticks_us() < end_time:
80+
if count == self.__confidence:
81+
break
82+
83+
raw_x = self._read_reg(_CMD_X_READ, 3)
84+
if raw_x < _MIN_RAW_COORD:
85+
continue
86+
87+
raw_y = self._read_reg(_CMD_Y_READ, 3)
88+
if raw_y > _MAX_RAW_COORD:
89+
continue
90+
91+
# put in buff
92+
points[count][0] = raw_x
93+
points[count][1] = raw_y
94+
count += 1
95+
96+
finally:
97+
self.touch_cs.value(1) # deselect touch chip
98+
self.lcd_cs.value(0) # select LCD
99+
100+
if not count:
101+
return None # Not touched
102+
103+
meanx = sum([points[i][0] for i in range(count)]) // count
104+
meany = sum([points[i][1] for i in range(count)]) // count
105+
dev = (
106+
sum(
107+
[
108+
(points[i][0] - meanx) ** 2 + (points[i][1] - meany) ** 2
109+
for i in range(count)
110+
]
111+
)
112+
/ count
113+
)
114+
if dev >= self.__margin:
115+
return None # Not touched
116+
117+
x = pointer_framework.remap(
118+
meanx, _MIN_RAW_COORD, _MAX_RAW_COORD, 0, self._orig_width
119+
)
120+
y = pointer_framework.remap(
121+
meany, _MIN_RAW_COORD, _MAX_RAW_COORD, 0, self._orig_height
122+
)
123+
if self._debug:
124+
print(
125+
f"{self.__class__.__name__}_TP_DATA({count=} {meanx=} {meany=} {z1=} {z2=} {z=})"
126+
) # NOQA
127+
return self.PRESSED, x, y

0 commit comments

Comments
 (0)