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
This commit is contained in:
Thomas Farstrike
2026-02-07 12:54:14 +01:00
parent 4e16096207
commit e8de45312f
7 changed files with 361 additions and 40 deletions
@@ -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
+178
View File
@@ -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
@@ -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()
@@ -0,0 +1,4 @@
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
import lvgl as lv # NOQA
+3 -1
View File
@@ -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
@@ -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,
+1 -1
View File
@@ -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