From e8de45312fa556b6a256743b3a7c155ff1d312b2 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Sat, 7 Feb 2026 12:54:14 +0100 Subject: [PATCH] matouch_esp32_s3_2_8_spi work - Natural orientation: camera on top, USB ports on bottom - OV3660 camera works (but colors are off and it breaks the touch input) - Improve board detection --- .../lib/mpos/board/matouch_esp32_s3_2_8.py | 42 ++--- internal_filesystem/lib/mpos/indev/gt911.py | 178 ++++++++++++++++++ .../lib/mpos/indev/gt911_extension.py | 157 +++++++++++++++ .../lib/mpos/indev/gt911_settings_gui.py | 4 + internal_filesystem/lib/mpos/main.py | 4 +- .../lib/mpos/ui/camera_activity.py | 14 +- scripts/build_mpos.sh | 2 +- 7 files changed, 361 insertions(+), 40 deletions(-) create mode 100644 internal_filesystem/lib/mpos/indev/gt911.py create mode 100644 internal_filesystem/lib/mpos/indev/gt911_extension.py create mode 100644 internal_filesystem/lib/mpos/indev/gt911_settings_gui.py diff --git a/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py b/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py index 90b54715..679356ce 100644 --- a/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py +++ b/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py @@ -1,7 +1,7 @@ print("matouch_esp32_s3_2_8.py initialization") # Hardware initialization for Makerfabs MaTouch ESP32-S3 SPI 2.8" with Camera -# Manufacturer's website: https://www.makerfabs.com/matouch-esp32-s3.html +# Manufacturer's website: https://www.makerfabs.com/matouch-esp32-s3-spi-ips-2-8-with-camera-ov3660.html # Hardware Specifications: # - MCU: ESP32-S3 with 16MB Flash, 8MB Octal PSRAM # - Display: 2.8" IPS LCD, 320x240 resolution, ST7789 driver, SPI interface @@ -77,45 +77,25 @@ mpos.ui.main_display.set_power(True) mpos.ui.main_display.set_backlight(100) # Touch handling -# Often times, a "ghost" device seems to show up on the I2C bus at 0x14. -# Initializing it, although it fails, seems to bring up the "proper" GT911 at address 0x5D (gt911.I2C_ADDR). try: import i2c - import gt911 - i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39, freq=400000, use_locks=False) - touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=0x14, reg_bits=gt911.BITS) - indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=True) -except Exception as e: - print(f"Touch init phase 1 got exception: {e}") -try: - import pointer_framework + i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39) + import mpos.indev.gt911 as gt911 touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=gt911.I2C_ADDR, reg_bits=gt911.BITS) - indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, startup_rotation=pointer_framework.lv.DISPLAY_ROTATION._180, debug=True) + indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=True) # remove debug because it's slower except Exception as e: - print(f"Touch init phase 2 got exception: {e}") + print(f"Touch init got exception: {e}") # Initialize LVGL lv.init() -mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._90) # must be done after initializing display and creating the touch drivers, to ensure proper handling -# === BATTERY VOLTAGE MONITORING === -# Note: MaTouch ESP32-S3 battery monitoring configuration may vary -# This is a placeholder - adjust ADC pin and conversion formula based on actual hardware -from mpos import BatteryManager +# TODO: initialize SDIO (instead of SPI) SD card with: +# CMD = 2 +# SCLK = 42 +# D0 = 41 +#import mpos.sdcard +#mpos.sdcard.init(spi_bus, cs_pin=14) -def adc_to_voltage(adc_value): - """ - Convert raw ADC value to battery voltage. - Currently uses simple linear scaling: voltage = adc * 0.00262 - - This should be calibrated with actual battery voltages and ADC readings. - To calibrate: measure actual battery voltages and corresponding ADC readings, - then fit a linear or polynomial function. - """ - return adc_value * 0.00262 - -# Note: Adjust ADC pin number based on actual hardware schematic -# BatteryManager.init_adc(5, adc_to_voltage) # === AUDIO HARDWARE === # Note: MaTouch ESP32-S3 has no buzzer or I2S audio hardware diff --git a/internal_filesystem/lib/mpos/indev/gt911.py b/internal_filesystem/lib/mpos/indev/gt911.py new file mode 100644 index 00000000..2b6ab191 --- /dev/null +++ b/internal_filesystem/lib/mpos/indev/gt911.py @@ -0,0 +1,178 @@ +# Copyright (c) 2024 - 2025 Kevin G. Schlosser + +# this driver uses a special i2c bus implimentation I have written. +# This implimentation takes into consideration the ESP32 and it having +# threading available. It also has some convience methods built into it +# that figure out what is wanting to be done automatically. +# read more about it's use in the stub files. + +from micropython import const # NOQA +import pointer_framework +import machine # NOQA +import time + + +_CMD_REG = const(0x8040) +_CMD_CHECK_REG = const(0x8046) +_CMD_READ_DATA = const(0x01) + +_ESD_CHECK_REG = const(0x8041) + +_STATUS_REG = const(0x814E) +_POINT_1_REG = const(0x8150) + +_PRODUCT_ID_REG = const(0x8140) +_FIRMWARE_VERSION_REG = const(0x8144) +_VENDOR_ID_REG = const(0x814A) + +_X_CORD_RES_REG = const(0x8146) +_Y_CORD_RES_REG = const(0x8148) + +I2C_ADDR = 0x5D +BITS = 16 + +_ADDR2 = const(0x14) + + +class GT911(pointer_framework.PointerDriver): + + def _read_reg(self, reg, num_bytes=None, buf=None): + self._tx_buf[0] = reg >> 8 + self._tx_buf[1] = reg & 0xFF + try: + if num_bytes is not None: + self._device.write_readinto(self._tx_mv[:2], self._rx_mv[:num_bytes]) + else: + self._device.write_readinto(self._tx_mv[:2], buf) + except Exception as e: + print(f"GT911 _read_reg got exception: {e}") + + def _write_reg(self, reg, value=None, buf=None): + try: + if value is not None: + self._tx_buf[0] = value + self._device.write_mem(reg, self._tx_mv[:1]) + elif buf is not None: + self._device.write_mem(reg, buf) + except Exception as e: + print(f"GT911 _write_reg got exception: {e}") + + def __init__( + self, + device, + reset_pin=None, + interrupt_pin=None, + touch_cal=None, + startup_rotation=pointer_framework.lv.DISPLAY_ROTATION._0, # NOQA + debug=False + ): + self._tx_buf = bytearray(3) + self._tx_mv = memoryview(self._tx_buf) + self._rx_buf = bytearray(6) + self._rx_mv = memoryview(self._rx_buf) + + self._device = device + + self.__x = 0 + self.__y = 0 + self.__last_state = self.RELEASED + + if isinstance(reset_pin, int): + reset_pin = machine.Pin(reset_pin, machine.Pin.OUT) + + if isinstance(interrupt_pin, int): + interrupt_pin = machine.Pin(interrupt_pin, machine.Pin.OUT) + + self._reset_pin = reset_pin + self._interrupt_pin = interrupt_pin + + self.hw_reset() + super().__init__( + touch_cal=touch_cal, startup_rotation=startup_rotation, debug=debug + ) + + def hw_reset(self): + if self._interrupt_pin and self._reset_pin: + self._interrupt_pin.init(self._interrupt_pin.OUT) + self._interrupt_pin(0) + self._reset_pin(0) + time.sleep_ms(10) # NOQA + self._interrupt_pin(0) + time.sleep_ms(1) # NOQA + self._reset_pin(1) + time.sleep_ms(5) # NOQA + self._interrupt_pin(0) + time.sleep_ms(50) # NOQA + self._interrupt_pin.init(self._interrupt_pin.IN) + time.sleep_ms(50) # NOQA + + self._write_reg(_ESD_CHECK_REG, 0x00) + self._write_reg(_CMD_CHECK_REG, _CMD_READ_DATA) + self._write_reg(_CMD_REG, _CMD_READ_DATA) + + self._read_reg(_PRODUCT_ID_REG, 4) + + product_id = '' + for item in self._rx_buf[:4]: + try: + product_id += chr(item) + except: # NOQA + break + + print('Touch Product id:', product_id) + + self._read_reg(_FIRMWARE_VERSION_REG, 2) + print( + 'Touch Firmware version:', + hex(self._rx_buf[0] + (self._rx_buf[1] << 8)) + ) + + self._read_reg(_VENDOR_ID_REG, 1) + print(f'Touch Vendor id: 0x{hex(self._rx_buf[0])[2:].upper()}') + x, y = self.hw_size + print(f'Touch resolution: width={x}, height={y}') + + @property + def hw_size(self): + self._read_reg(_X_CORD_RES_REG, 2) + x = self._rx_buf[0] + (self._rx_buf[1] << 8) + + self._read_reg(_Y_CORD_RES_REG, 2) + y = self._rx_buf[0] + (self._rx_buf[1] << 8) + + return x, y + + @property + def firmware_config(self): + try: + import gt911_extension + except ImportError: + raise ImportError( + 'you need to upload the gt911_extension.py file to the MCU' + ) + return gt911_extension.GT911Extension(self, self._device) + + def _get_coords(self): + self._read_reg(_STATUS_REG, 1) + touch_cnt = self._rx_buf[0] & 0x0F + status = self._rx_buf[0] & 0x80 + + if status: + if touch_cnt == 1: + self._read_reg(_POINT_1_REG, 6) + + x = self._rx_buf[0] + (self._rx_buf[1] << 8) + y = self._rx_buf[2] + (self._rx_buf[3] << 8) + + self._write_reg(_STATUS_REG, 0x00) + + self.__x = x + self.__y = y + self.__last_state = self.PRESSED + + elif touch_cnt == 0: + self.__last_state = self.RELEASED + + self._write_reg(_STATUS_REG, 0x00) + + return self.__last_state, self.__x, self.__y diff --git a/internal_filesystem/lib/mpos/indev/gt911_extension.py b/internal_filesystem/lib/mpos/indev/gt911_extension.py new file mode 100644 index 00000000..3d504c4d --- /dev/null +++ b/internal_filesystem/lib/mpos/indev/gt911_extension.py @@ -0,0 +1,157 @@ +# Copyright (c) 2024 - 2025 Kevin G. Schlosser + +from micropython import const # NOQA + + +_CONFIG_START_REG = const(0x8047) +_CONFIG_VERSION_POS = const(0x00) +_X_OUTPUT_MAX_LOW_POS = const(0x01) +_X_OUTPUT_MAX_HIGH_POS = const(0x02) +_Y_OUTPUT_MAX_LOW_POS = const(0x03) +_Y_OUTPUT_MAX_HIGH_POS = const(0x04) +# Touch Number 0x05 +# Module_Switch1 0x06 +# Module_Switch2 0x07 +# Shake_Count 0x08 +# Filter 0x09 +# Large_Touch 0x0A +_NOISE_REDUCTION_POS = const(0x0B) +_TOUCH_PRESS_LEVEL_POS = const(0x0C) +_TOUCH_LEAVE_LEVEL_POS = const(0x0D) +# Low_Power_Control 0x0E +# Refresh_Rate 0x0F +# x_threshold 0x10 +# y_threshold 0x11 +# X_Speed_Limit 0x12 +# y_Speed_Limit 0x13 +_VER_SPACE_POS = const(0x14) # const(0x805B) # low 4 bits are bottom and hight is top +_HOR_SPACE_POS = const(0x15) # const(0x805C) # low 4 bits is right and high is left + +_CONFIG_CHKSUM_REG = const(0x80FF) +_CONFIG_FRESH_REG = const(0x8100) +# 0-15 * 32 + + +class GT911Extension(object): + + def _read_reg(self, reg, num_bytes=None, buf=None): + self._tx_buf[0] = reg >> 8 + self._tx_buf[1] = reg & 0xFF + if num_bytes is not None: + self._i2c.write_readinto(self._tx_mv[:2], self._rx_mv[:num_bytes]) + else: + self._i2c.write_readinto(self._tx_mv[:2], buf) + + def _write_reg(self, reg, value=None, buf=None): + if value is not None: + self._tx_buf[0] = value + self._i2c.write_mem(reg, self._tx_mv[:1]) + elif buf is not None: + self._i2c.write_mem(reg, buf) + + def __init__(self, indev, i2c): + self._indev = indev + self._i2c = i2c + + self._tx_buf = bytearray(3) + self._tx_mv = memoryview(self._tx_buf) + self._rx_buf = bytearray(6) + self._rx_mv = memoryview(self._rx_buf) + + self._config_data = bytearray(_CONFIG_FRESH_REG - _CONFIG_START_REG + 1) + self._config_mv = memoryview(self._config_data) + + self._read_reg(_CONFIG_START_REG, buf=self._config_mv[:-2]) + + @property + def width(self): + return ( + (self._config_data[_X_OUTPUT_MAX_HIGH_POS] << 8) | + self._config_data[_X_OUTPUT_MAX_LOW_POS] + ) + + @width.setter + def width(self, value): + self._config_data[_X_OUTPUT_MAX_LOW_POS] = value & 0xFF + self._config_data[_X_OUTPUT_MAX_HIGH_POS] = (value >> 8) & 0xFF + + @property + def height(self): + return ( + (self._config_data[_Y_OUTPUT_MAX_HIGH_POS] << 8) | + self._config_data[_Y_OUTPUT_MAX_LOW_POS] + ) + + @height.setter + def height(self, value): + self._config_data[_Y_OUTPUT_MAX_LOW_POS] = value & 0xFF + self._config_data[_Y_OUTPUT_MAX_HIGH_POS] = (value >> 8) & 0xFF + + @property + def noise_reduction(self): + return self._config_data[_NOISE_REDUCTION_POS] & 0x0F + + @noise_reduction.setter + def noise_reduction(self, value): + upper_val = self._config_data[_NOISE_REDUCTION_POS] >> 4 + self._config_data[_NOISE_REDUCTION_POS + 2] = (upper_val << 4) | (value & 0x0F) + + @property + def touch_press_level(self): + return self._config_data[_TOUCH_PRESS_LEVEL_POS] + + @touch_press_level.setter + def touch_press_level(self, value): + self._config_data[_TOUCH_PRESS_LEVEL_POS] = value & 0xFF + + @property + def touch_leave_level(self): + return self._config_data[_TOUCH_LEAVE_LEVEL_POS] + + @touch_leave_level.setter + def touch_leave_level(self, value): + self._config_data[_TOUCH_LEAVE_LEVEL_POS] = value & 0xFF + + @property + def pad_left(self): + return self._config_data[_HOR_SPACE_POS] >> 4 + + @pad_left.setter + def pad_left(self, value): + self._config_data[_HOR_SPACE_POS] = (value << 4) | self.pad_right + + @property + def pad_right(self): + return self._config_data[_HOR_SPACE_POS] & 0xF + + @pad_right.setter + def pad_right(self, value): + self._config_data[_HOR_SPACE_POS] = (self.pad_left << 4) | (value & 0xF) + + @property + def pad_top(self): + return self._config_data[_VER_SPACE_POS] >> 4 + + @pad_top.setter + def pad_top(self, value): + self._config_data[_VER_SPACE_POS] = (value << 4) | self.pad_bottom + + @property + def pad_bottom(self): + return self._config_data[_VER_SPACE_POS] & 0xF + + @pad_bottom.setter + def pad_bottom(self, value): + self._config_data[_VER_SPACE_POS] = (self.pad_top << 4) | (value & 0xF) + + def save(self): + # calculate the checksum + self._config_data[-2] = ((~sum(self._config_data[:-2])) + 1) & 0xFF + + # set the flag to save the data the data + self._config_data[-1] = 0x01 # _CONFIG_FRESH_REG + + # write all config data to the touch IC + self._write_reg(_CONFIG_START_REG, buf=self._config_mv) + + self._indev.hw_reset() diff --git a/internal_filesystem/lib/mpos/indev/gt911_settings_gui.py b/internal_filesystem/lib/mpos/indev/gt911_settings_gui.py new file mode 100644 index 00000000..57f0eff7 --- /dev/null +++ b/internal_filesystem/lib/mpos/indev/gt911_settings_gui.py @@ -0,0 +1,4 @@ +# Copyright (c) 2024 - 2025 Kevin G. Schlosser + +import lvgl as lv # NOQA + diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index b54bda0c..d75d155f 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -32,9 +32,11 @@ def detect_board(): return "linux" elif sys.platform == "esp32": from machine import Pin, I2C + return "matouch_esp32_s3_2_8" # i2c scan confuses the camera so hard-code for now i2c0 = I2C(0, sda=Pin(39), scl=Pin(38), freq=400000) - if {0x14} <= set(i2c0.scan()): # GT911 touch initial "ghost" device + devices = set(i2c0.scan()) # causes a "ghost" device to appear on 0x20 and breaks the camera + if {0x14} <= devices or {0x5D} <= devices: # "ghost" device or real GT911 return "matouch_esp32_s3_2_8" i2c0 = I2C(0, sda=Pin(48), scl=Pin(47)) # on matouch_esp32_s3_2_8, this "finds" devices at all addresses 8-119 so only do this after matouch_esp32_s3_2_8 diff --git a/internal_filesystem/lib/mpos/ui/camera_activity.py b/internal_filesystem/lib/mpos/ui/camera_activity.py index 90c642a6..a457a283 100644 --- a/internal_filesystem/lib/mpos/ui/camera_activity.py +++ b/internal_filesystem/lib/mpos/ui/camera_activity.py @@ -414,13 +414,13 @@ class CameraActivity(Activity): for attempt in range(max_attempts): try: cam = Camera( - data_pins=[12,13,15,11,14,10,7,2], - vsync_pin=6, - href_pin=4, - sda_pin=21, - scl_pin=16, - pclk_pin=9, - xclk_pin=8, + data_pins=[7,5,4,6,16,8,3,46], + vsync_pin=11, + href_pin=10, + sda_pin=39, + scl_pin=38, + pclk_pin=17, + xclk_pin=9, xclk_freq=20000000, powerdown_pin=-1, reset_pin=-1, diff --git a/scripts/build_mpos.sh b/scripts/build_mpos.sh index 776006ec..6323443c 100755 --- a/scripts/build_mpos.sh +++ b/scripts/build_mpos.sh @@ -111,7 +111,7 @@ if [ "$target" == "esp32" ]; then # CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y pushd "$codebasedir"/lvgl_micropython/ rm -rf lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/ - python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=st7789 INDEV=gt911 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 CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y "$frozenmanifest" + python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=st7789 INDEV=cst816s 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 CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y "$frozenmanifest" popd echo "Grepping..." pwd