You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
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 https://github.com/lvgl-micropython/lvgl_micropython/issues/536
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import sys
|
||||
from . import hx8357d
|
||||
from . import _hx8357d_init
|
||||
|
||||
# Register _hx8357d_init in sys.modules so __import__('_hx8357d_init') can find it
|
||||
# This is needed because display_driver_framework.py uses __import__('_hx8357d_init')
|
||||
# expecting a top-level module, but _hx8357d_init is in the hx8357d package subdirectory
|
||||
sys.modules['_hx8357d_init'] = _hx8357d_init
|
||||
|
||||
# Explicitly define __all__ and re-export public symbols from hx8357d module
|
||||
__all__ = [
|
||||
'HX8357D',
|
||||
'STATE_HIGH',
|
||||
'STATE_LOW',
|
||||
'STATE_PWM',
|
||||
'BYTE_ORDER_RGB',
|
||||
'BYTE_ORDER_BGR',
|
||||
]
|
||||
|
||||
# Re-export the public symbols
|
||||
HX8357D = hx8357d.HX8357D
|
||||
STATE_HIGH = hx8357d.STATE_HIGH
|
||||
STATE_LOW = hx8357d.STATE_LOW
|
||||
STATE_PWM = hx8357d.STATE_PWM
|
||||
BYTE_ORDER_RGB = hx8357d.BYTE_ORDER_RGB
|
||||
BYTE_ORDER_BGR = hx8357d.BYTE_ORDER_BGR
|
||||
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
import time
|
||||
from micropython import const # NOQA
|
||||
|
||||
import lvgl as lv # NOQA
|
||||
import lcd_bus # NOQA
|
||||
|
||||
|
||||
_SWRESET = const(0x01)
|
||||
_SLPOUT = const(0x11)
|
||||
_DISPON = const(0x29)
|
||||
_COLMOD = const(0x3A)
|
||||
_MADCTL = const(0x36)
|
||||
_TEON = const(0x35)
|
||||
_TEARLINE = const(0x44)
|
||||
_SETOSC = const(0xB0)
|
||||
_SETPWR1 = const(0xB1)
|
||||
_SETRGB = const(0xB3)
|
||||
_SETCOM = const(0xB6)
|
||||
_SETCYC = const(0xB4)
|
||||
_SETC = const(0xB9)
|
||||
_SETSTBA = const(0xC0)
|
||||
_SETPANEL = const(0xCC)
|
||||
_SETGAMMA = const(0xE0)
|
||||
|
||||
|
||||
def init(self):
|
||||
param_buf = bytearray(34)
|
||||
param_mv = memoryview(param_buf)
|
||||
|
||||
time.sleep_ms(300) # NOQA
|
||||
param_buf[:3] = bytearray([0xFF, 0x83, 0x57])
|
||||
self.set_params(_SETC, param_mv[:3])
|
||||
|
||||
param_buf[0] = 0x80
|
||||
self.set_params(_SETRGB, param_mv[:1])
|
||||
|
||||
param_buf[:4] = bytearray([0x00, 0x06, 0x06, 0x25])
|
||||
self.set_params(_SETCOM, param_mv[:4])
|
||||
|
||||
param_buf[0] = 0x68
|
||||
self.set_params(_SETOSC, param_mv[:1])
|
||||
|
||||
param_buf[0] = 0x05
|
||||
self.set_params(_SETPANEL, param_mv[:1])
|
||||
|
||||
param_buf[:6] = bytearray([0x00, 0x15, 0x1C, 0x1C, 0x83, 0xAA])
|
||||
self.set_params(_SETPWR1, param_mv[:6])
|
||||
|
||||
param_buf[:6] = bytearray([0x50, 0x50, 0x01, 0x3C, 0x1E, 0x08])
|
||||
self.set_params(_SETSTBA, param_mv[:6])
|
||||
|
||||
param_buf[:7] = bytearray([0x02, 0x40, 0x00, 0x2A, 0x2A, 0x0D, 0x78])
|
||||
self.set_params(_SETCYC, param_mv[:7])
|
||||
|
||||
param_buf[:34] = bytearray([
|
||||
0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A,
|
||||
0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35,
|
||||
0x41, 0x4b, 0x4b, 0x42, 0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x00, 0x01])
|
||||
self.set_params(_SETGAMMA, param_mv[:34])
|
||||
|
||||
param_buf[0] = (
|
||||
self._madctl(
|
||||
self._color_byte_order,
|
||||
self._ORIENTATION_TABLE # NOQA
|
||||
)
|
||||
)
|
||||
self.set_params(_MADCTL, param_mv[:1])
|
||||
|
||||
color_size = lv.color_format_get_size(self._color_space)
|
||||
if color_size == 2: # NOQA
|
||||
pixel_format = 0x55
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f'{self.__class__.__name__} IC only supports '
|
||||
'lv.COLOR_FORMAT.RGB565'
|
||||
)
|
||||
|
||||
param_buf[0] = pixel_format
|
||||
self.set_params(_COLMOD, param_mv[:1])
|
||||
|
||||
param_buf[0] = 0x00
|
||||
self.set_params(_TEON, param_mv[:1])
|
||||
|
||||
param_buf[:2] = bytearray([0x00, 0x02])
|
||||
self.set_params(_TEARLINE, param_mv[:2])
|
||||
|
||||
time.sleep_ms(150) # NOQA
|
||||
self.set_params(_SLPOUT)
|
||||
|
||||
time.sleep_ms(50) # NOQA
|
||||
self.set_params(_DISPON)
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
import display_driver_framework
|
||||
|
||||
|
||||
STATE_HIGH = display_driver_framework.STATE_HIGH
|
||||
STATE_LOW = display_driver_framework.STATE_LOW
|
||||
STATE_PWM = display_driver_framework.STATE_PWM
|
||||
|
||||
BYTE_ORDER_RGB = display_driver_framework.BYTE_ORDER_RGB
|
||||
BYTE_ORDER_BGR = display_driver_framework.BYTE_ORDER_BGR
|
||||
|
||||
|
||||
class HX8357D(display_driver_framework.DisplayDriver):
|
||||
pass
|
||||
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
import lvgl as lv # NOQA
|
||||
from micropython import const # NOQA
|
||||
import micropython # NOQA
|
||||
import machine # NOQA
|
||||
import pointer_framework
|
||||
import time
|
||||
|
||||
|
||||
_CMD_X_READ = const(0xD0) # 12 bit resolution
|
||||
_CMD_Y_READ = const(0x90) # 12 bit resolution
|
||||
_CMD_Z1_READ = const(0xB0)
|
||||
_CMD_Z2_READ = const(0xC0)
|
||||
_MIN_RAW_COORD = const(10)
|
||||
_MAX_RAW_COORD = const(4090)
|
||||
|
||||
|
||||
class XPT2046(pointer_framework.PointerDriver):
|
||||
touch_threshold = 400
|
||||
confidence = 5
|
||||
margin = 50
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: machine.SPI.Bus,
|
||||
display_width: int,
|
||||
display_height: int,
|
||||
lcd_cs: int,
|
||||
touch_cs: int,
|
||||
touch_cal=None,
|
||||
startup_rotation=lv.DISPLAY_ROTATION._0,
|
||||
debug=False,
|
||||
):
|
||||
self._device = device # machine.SPI.Bus() instance, shared with display
|
||||
self._debug = debug
|
||||
|
||||
self.lcd_cs = machine.Pin(lcd_cs, machine.Pin.OUT, value=0)
|
||||
self.touch_cs = machine.Pin(touch_cs, machine.Pin.OUT, value=1)
|
||||
|
||||
self._width = display_width
|
||||
self._height = display_height
|
||||
|
||||
self._tx_buf = bytearray(3)
|
||||
self._tx_mv = memoryview(self._tx_buf)
|
||||
|
||||
self._rx_buf = bytearray(3)
|
||||
self._rx_mv = memoryview(self._rx_buf)
|
||||
|
||||
self.__confidence = max(min(self.confidence, 25), 3)
|
||||
self.__points = [[0, 0] for _ in range(self.__confidence)]
|
||||
|
||||
margin = max(min(self.margin, 100), 1)
|
||||
self.__margin = margin * margin
|
||||
|
||||
super().__init__(
|
||||
touch_cal=touch_cal, startup_rotation=startup_rotation, debug=debug
|
||||
)
|
||||
|
||||
def _read_reg(self, reg, num_bytes):
|
||||
self._tx_buf[0] = reg
|
||||
self._device.write_readinto(self._tx_mv[:num_bytes], self._rx_mv[:num_bytes])
|
||||
return ((self._rx_buf[1] << 8) | self._rx_buf[2]) >> 3
|
||||
|
||||
def _get_coords(self):
|
||||
try:
|
||||
self.lcd_cs.value(1) # deselect LCD to avoid conflicts
|
||||
self.touch_cs.value(0) # select touch chip
|
||||
|
||||
z1 = self._read_reg(_CMD_Z1_READ, 3)
|
||||
z2 = self._read_reg(_CMD_Z2_READ, 3)
|
||||
z = z1 + ((_MAX_RAW_COORD + 6) - z2)
|
||||
if z < self.touch_threshold:
|
||||
return None # Not touched
|
||||
|
||||
points = self.__points
|
||||
count = 0
|
||||
end_time = time.ticks_us() + 5000
|
||||
while time.ticks_us() < end_time:
|
||||
if count == self.__confidence:
|
||||
break
|
||||
|
||||
raw_x = self._read_reg(_CMD_X_READ, 3)
|
||||
if raw_x < _MIN_RAW_COORD:
|
||||
continue
|
||||
|
||||
raw_y = self._read_reg(_CMD_Y_READ, 3)
|
||||
if raw_y > _MAX_RAW_COORD:
|
||||
continue
|
||||
|
||||
# put in buff
|
||||
points[count][0] = raw_x
|
||||
points[count][1] = raw_y
|
||||
count += 1
|
||||
|
||||
finally:
|
||||
self.touch_cs.value(1) # deselect touch chip
|
||||
self.lcd_cs.value(0) # select LCD
|
||||
|
||||
if not count:
|
||||
return None # Not touched
|
||||
|
||||
meanx = sum([points[i][0] for i in range(count)]) // count
|
||||
meany = sum([points[i][1] for i in range(count)]) // count
|
||||
dev = (
|
||||
sum(
|
||||
[
|
||||
(points[i][0] - meanx) ** 2 + (points[i][1] - meany) ** 2
|
||||
for i in range(count)
|
||||
]
|
||||
)
|
||||
/ count
|
||||
)
|
||||
if dev >= self.__margin:
|
||||
return None # Not touched
|
||||
|
||||
x = pointer_framework.remap(
|
||||
meanx, _MIN_RAW_COORD, _MAX_RAW_COORD, 0, self._orig_width
|
||||
)
|
||||
y = pointer_framework.remap(
|
||||
meany, _MIN_RAW_COORD, _MAX_RAW_COORD, 0, self._orig_height
|
||||
)
|
||||
if self._debug:
|
||||
print(
|
||||
f"{self.__class__.__name__}_TP_DATA({count=} {meanx=} {meany=} {z1=} {z2=} {z=})"
|
||||
) # NOQA
|
||||
return self.PRESSED, x, y
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ def init_rootscreen():
|
||||
|
||||
# Initialize DisplayMetrics with actual display values
|
||||
DisplayMetrics.set_resolution(width, height)
|
||||
DisplayMetrics.set_dpi(dpi)
|
||||
DisplayMetrics.set_dpi(dpi)
|
||||
print(f"init_rootscreen set resolution to {width}x{height} at {dpi} DPI")
|
||||
|
||||
# Show logo
|
||||
@@ -92,6 +92,10 @@ def detect_board():
|
||||
import machine
|
||||
unique_id_prefixes = machine.unique_id()[0:3]
|
||||
|
||||
print("unPhone ?")
|
||||
if unique_id_prefixes == b'00\xf9': # '30:30:F9'
|
||||
return "unphone"
|
||||
|
||||
print("(emulated) lilygo_t_display_s3 ?")
|
||||
if unique_id_prefixes == b'\x10\x01\x00' or unique_id_prefixes == b'\xc0\x4e\x30':
|
||||
return "lilygo_t_display_s3" # display gets confused by the i2c stuff below
|
||||
|
||||
+12
-4
@@ -14,6 +14,7 @@ if [ -z "$target" ]; then
|
||||
echo "Example: $0 macOS"
|
||||
echo "Example: $0 esp32"
|
||||
echo "Example: $0 esp32s3"
|
||||
echo "Example: $0 unphone"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -97,12 +98,18 @@ popd
|
||||
echo "Refreshing freezefs..."
|
||||
"$codebasedir"/scripts/freezefs_mount_builtin.sh
|
||||
|
||||
if [ "$target" == "esp32" -o "$target" == "esp32s3" ]; then
|
||||
if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; then
|
||||
partition_size="4194304"
|
||||
flash_size="16"
|
||||
extra_configs=""
|
||||
if [ "$target" == "esp32" ]; then
|
||||
if [ "$target" == "esp32" ]; then
|
||||
BOARD=ESP32_GENERIC
|
||||
BOARD_VARIANT=SPIRAM
|
||||
else # esp32s3
|
||||
else # esp32s3 or unphone
|
||||
if [ "$target" == "unphone" ]; then
|
||||
partition_size="3900000"
|
||||
flash_size="8"
|
||||
fi
|
||||
BOARD=ESP32_GENERIC_S3
|
||||
BOARD_VARIANT=SPIRAM_OCT
|
||||
# These options disable hardware AES, SHA and MPI because they give warnings in QEMU: [AES] Error reading from GDMA buffer
|
||||
@@ -131,7 +138,8 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" ]; then
|
||||
# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
# CONFIG_ADC_MIC_TASK_CORE=1 because with the default (-1) it hangs the CPU
|
||||
# CONFIG_SPIRAM_XIP_FROM_PSRAM: load entire firmware into RAM to reduce SD vs PSRAM contention (recommended at https://github.com/MicroPythonOS/MicroPythonOS/issues/17)
|
||||
python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \
|
||||
# python3 make.py --ota --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \
|
||||
python3 make.py --optimize-size --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \
|
||||
USER_C_MODULE="$codebasedir"/micropython-camera-API/src/micropython.cmake \
|
||||
USER_C_MODULE="$codebasedir"/secp256k1-embedded-ecdh/micropython.cmake \
|
||||
USER_C_MODULE="$codebasedir"/c_mpos/micropython.cmake \
|
||||
|
||||
Reference in New Issue
Block a user