What changed, and why it matters
This commit adds support for a new hardware device called the Wonder K PRO to the Krux Bitcoin signing firmware. It is a routine feature addition: it introduces a new touchscreen driver (GT911), updates build scripts, simulators, display/camera settings, and font handling to recognize the new device. There is no indication in the commit or supplied references that this change fixes or introduces a security vulnerability.
No security action required. Treat as a normal hardware-support merge. If desired, review the new GT911 driver for robustness (I2C error handling, interrupt debounce, and register-clear behavior) during regular QA, but the commit itself does not present a known security issue.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit is a hardware-enablement patch for the ‘maixpy_wonder_k’ board. Key changes include: adding the device to build/flash aliases and simulator configurations; introducing a GT911 capacitive-touch controller driver with I2C register reads/writes, interrupt handling, and debounce logic; refactoring the simulator’s FT6X36 mock to share a common touchscreen base class; and updating display, camera, input, and settings logic to branch on the new board type. No security-relevant bug fixes, privilege changes, cryptographic modifications, or input-validation changes are present in the diff.
Changed components
firmware build scripts (krux, pyproject.toml)simulator (kruxsim/devices.py, mocks/ft6x36.py, mocks/gt911.py, mocks/touchscreen_common.py, simulator.py)src/krux/camera.pysrc/krux/display.pysrc/krux/input.pysrc/krux/kboard.pysrc/krux/krux_settings.pysrc/krux/pages/settings_page.pysrc/krux/touch.pysrc/krux/touchscreens/gt911.pyfirmware/font/bdftokff.pyfirmware/MaixPy (submodule pointer)Inspect captured patch +386 / −69
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1fe945..6d29fb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@
### New Device Support: TZT
The TZT CanMV is similar to the WonderMV but includes five buttons and a premium milled aluminum housing.
+### New Device Support: WonderK PRO
+From the wonderful land of Korea, a new creation arrives: the WonderK PRO. Created by an entrepreneur who loves the Krux project, the WonderK follows in the footsteps of the WonderMV, but boasts a larger 2.8" display!
+
### Code optimization
Reduced firmware size by 25% and lowered RAM usage through code cleanup and optimizations.
diff --git a/firmware/font/bdftokff.py b/firmware/font/bdftokff.py
index 55bbf31..6f0887f 100644
--- a/firmware/font/bdftokff.py
+++ b/firmware/font/bdftokff.py
@@ -49,7 +49,14 @@ BIG_FONT_REF = "amigo"
REF_DEVICES = [SMALL_FONT_REF, MID_FONT_REF, BIG_FONT_REF]
SMALL_FONT_DEVICES_TO_COPY = ["cube"]
-MID_FONT_DEVICES_TO_COPY = ["bit", "yahboom", "wonder_mv", "yahboom_devkit"]
+MID_FONT_DEVICES_TO_COPY = [
+ "bit",
+ "yahboom",
+ "wonder_mv",
+ "tzt",
+ "wonder_k",
+ "yahboom_devkit",
+]
BIG_FONT_DEVICES_TO_COPY = []
ALL_DEVICES = REF_DEVICES + SMALL_FONT_DEVICES_TO_COPY + MID_FONT_DEVICES_TO_COPY
diff --git a/krux b/krux
index 08b4491..2b4ac87 100755
--- a/krux
+++ b/krux
@@ -68,6 +68,7 @@ if [ "$1" == "build" ]; then
[ "$device" == "maixpy_cube" ]||
[ "$device" == "maixpy_wonder_mv" ]||
[ "$device" == "maixpy_tzt" ]||
+ [ "$device" == "maixpy_wonder_k" ]||
[ "$allow_unsupported" ]; then
declare $(grep -m 1 version pyproject.toml | sed 's/ *= */=/g' | sed 's/"//g' | sed 's/\r//g')
sed -i -- 's/VERSION = ".*"/VERSION = "'"$version"'"/g' src/krux/metadata.py
@@ -112,7 +113,8 @@ elif [ "$1" == "flash" -o "$1" == "boot" -o "$1" == "erase" ]; then
device_vendor_product_id="0403:6010"
elif [ "$device" == "maixpy_dock" ]||
[ "$device" == "maixpy_yahboom" ]||
- [ "$device" == "maixpy_wonder_mv" ]; then
+ [ "$device" == "maixpy_wonder_mv" ]||
+ [ "$device" == "maixpy_wonder_k" ]; then
# https://devicehunt.com/view/type/usb/vendor/1a86/device/7523
device_vendor_product_id="1a86:7523"
elif [ "$device" == "maixpy_tzt" ]; then
@@ -131,7 +133,8 @@ elif [ "$1" == "flash" -o "$1" == "boot" -o "$1" == "erase" ]; then
if [ "$device" == "maixpy_dock" ]||
[ "$device" == "maixpy_wonder_mv" ]||
- [ "$device" == "maixpy_tzt" ]; then
+ [ "$device" == "maixpy_tzt" ]||
+ [ "$device" == "maixpy_wonder_k" ]; then
BOARD="dan"
else
BOARD="goE"
diff --git a/pyproject.toml b/pyproject.toml
index 48916f1..bca3042 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -121,12 +121,15 @@ simulator-dock = "python simulator/simulator.py --device maixpy_dock"
simulator-yahboom = "python simulator/simulator.py --device maixpy_yahboom"
simulator-cube = "python simulator/simulator.py --device maixpy_cube"
simulator-wonder-mv = "python simulator/simulator.py --device maixpy_wonder_mv"
+simulator-wonder-k = "python simulator/simulator.py --device maixpy_wonder_k"
# aliases
simulator.ref = "simulator-amigo"
simulator-m5.ref = "simulator-m5stickv"
simulator-mv.ref = "simulator-wonder-mv"
simulator-wonder.ref = "simulator-wonder-mv"
simulator-wondermv.ref = "simulator-wonder-mv"
+simulator-wonderk.ref = "simulator-wonder-k"
+simulator-k.ref = "simulator-wonder-k"
# git tasks
git-update = "git submodule update --init --recursive"
diff --git a/simulator/kruxsim/devices.py b/simulator/kruxsim/devices.py
index 53a6c47..d6c49cc 100644
--- a/simulator/kruxsim/devices.py
+++ b/simulator/kruxsim/devices.py
@@ -29,6 +29,7 @@ DOCK = "maixpy_dock"
YAHBOOM = "maixpy_yahboom"
CUBE = "maixpy_cube"
WONDER_MV = "maixpy_wonder_mv"
+WONDER_K = "maixpy_wonder_k"
WINDOW_SIZES = {
M5STICKV: (320, 640),
@@ -38,6 +39,7 @@ WINDOW_SIZES = {
YAHBOOM: (450, 600),
CUBE: (484, 612),
WONDER_MV: (410, 590),
+ WONDER_K: (500, 650),
}
@@ -50,7 +52,8 @@ images = {}
def load_image(device):
device = with_prefix(device)
- if device == PC:
+ # TODO: remove WONDER_K after img is ready
+ if device == PC or device == WONDER_K:
return None
if device not in images:
images[device] = pg.image.load(
@@ -74,7 +77,7 @@ def load_font(device):
os.path.join("..", "firmware", "font", "FusionPixel-14.bdf"),
),
]
- elif device in (DOCK, YAHBOOM, WONDER_MV):
+ elif device in (DOCK, YAHBOOM, WONDER_MV, WONDER_K):
fonts[device] = [
pg.freetype.Font(
os.path.join("..", "firmware", "font", "ter-u16n.bdf")
@@ -144,4 +147,12 @@ def screenshot_rect(device):
screen.get_rect().center[0] - 0,
screen.get_rect().center[1] + 10,
)
+ # TODO: fix after WONDER_K img is ready
+ # elif device == WONDER_K:
+ # rect.width -= 0
+ # rect.height -= 0
+ # rect.center = (
+ # screen.get_rect().center[0] - 0,
+ # screen.get_rect().center[1] + 0,
+ # )
return rect
diff --git a/simulator/kruxsim/mocks/ft6x36.py b/simulator/kruxsim/mocks/ft6x36.py
index 3fe6b19..9d8a643 100644
--- a/simulator/kruxsim/mocks/ft6x36.py
+++ b/simulator/kruxsim/mocks/ft6x36.py
@@ -21,55 +21,13 @@
# THE SOFTWARE.
import sys
from unittest import mock
-import pygame as pg
-from . import lcd
+from kruxsim.mocks.touchscreen_common import TCOMMON, register_sequence_executor
-sequence_executor = None
-
-
-def register_sequence_executor(s):
- global sequence_executor
- sequence_executor = s
-
-
-class FT6X36:
- def __init__(self):
- self.event_flag = False
-
- def to_screen_pos(self, pos):
- if lcd.screen:
- rect = lcd.screen.get_rect()
- rect.center = pg.display.get_surface().get_rect().center
- if rect.collidepoint(pos):
- out = pos[0] - rect.left, pos[1] - rect.top
- return out
- return None
+class FT6X36(TCOMMON):
def activate_irq(self, irq_pin):
pass
- def current_point(self):
- return (
- self.to_screen_pos(pg.mouse.get_pos())
- if pg.mouse.get_pressed()[0]
- else None
- )
-
- def trigger_event(self):
- self.event_flag = True
- self.irq_point = self.current_point()
-
- def event(self):
- if sequence_executor and sequence_executor.touch_pos is not None:
- sequence_executor.touch_pos = None
- return True
- flag = self.event_flag
- self.event_flag = False # Always clean event flag
- return flag
-
- def threshold(self, value):
- pass
-
touch_control = FT6X36()
@@ -77,4 +35,5 @@ touch_control = FT6X36()
if "krux.touchscreens.ft6x36" not in sys.modules:
sys.modules["krux.touchscreens.ft6x36"] = mock.MagicMock(
touch_control=touch_control,
+ register_sequence_executor=register_sequence_executor,
)
diff --git a/simulator/kruxsim/mocks/gt911.py b/simulator/kruxsim/mocks/gt911.py
new file mode 100644
index 0000000..4bc842e
--- /dev/null
+++ b/simulator/kruxsim/mocks/gt911.py
@@ -0,0 +1,39 @@
+# The MIT License (MIT)
+
+# Copyright (c) 2021-2023 Krux contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+import sys
+from unittest import mock
+from kruxsim.mocks.touchscreen_common import TCOMMON, register_sequence_executor
+
+
+class GT911(TCOMMON):
+ def activate(self, irq_pin, res_pin):
+ pass
+
+
+touch_control = GT911()
+
+
+if "krux.touchscreens.gt911" not in sys.modules:
+ sys.modules["krux.touchscreens.gt911"] = mock.MagicMock(
+ touch_control=touch_control,
+ register_sequence_executor=register_sequence_executor,
+ )
diff --git a/simulator/kruxsim/mocks/touchscreen_common.py b/simulator/kruxsim/mocks/touchscreen_common.py
new file mode 100644
index 0000000..f49b179
--- /dev/null
+++ b/simulator/kruxsim/mocks/touchscreen_common.py
@@ -0,0 +1,66 @@
+# The MIT License (MIT)
+
+# Copyright (c) 2021-2023 Krux contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+import pygame as pg
+from . import lcd
+
+sequence_executor = None
+
+
+def register_sequence_executor(s):
+ global sequence_executor
+ sequence_executor = s
+
+
+class TCOMMON:
+ def __init__(self):
+ self.event_flag = False
+
+ def to_screen_pos(self, pos):
+ if lcd.screen:
+ rect = lcd.screen.get_rect()
+ rect.center = pg.display.get_surface().get_rect().center
+ if rect.collidepoint(pos):
+ out = pos[0] - rect.left, pos[1] - rect.top
+ return out
+ return None
+
+ def current_point(self):
+ return (
+ self.to_screen_pos(pg.mouse.get_pos())
+ if pg.mouse.get_pressed()[0]
+ else None
+ )
+
+ def trigger_event(self):
+ self.event_flag = True
+ self.irq_point = self.current_point()
+
+ def event(self):
+ if sequence_executor and sequence_executor.touch_pos is not None:
+ sequence_executor.touch_pos = None
+ return True
+ flag = self.event_flag
+ self.event_flag = False # Always clean event flag
+ return flag
+
+ def threshold(self, value):
+ pass
diff --git a/simulator/simulator.py b/simulator/simulator.py
index d23f1ce..398abfa 100644
--- a/simulator/simulator.py
+++ b/simulator/simulator.py
@@ -111,6 +111,7 @@ from kruxsim.mocks import qrcode
from kruxsim.mocks import sensor
from kruxsim.mocks import shannon
from kruxsim.mocks import ft6x36
+from kruxsim.mocks import gt911
from kruxsim.mocks import buttons
from kruxsim.mocks import rotary
from kruxsim.sequence import SequenceExecutor
@@ -196,6 +197,12 @@ elif (args.device == devices.WONDER_MV):
mask_img = pg.image.load(
os.path.join("assets", "maixpy_wonder_mv_mask.png")
).convert_alpha()
+# TODO: WONDER_K IMG
+# elif (args.device == devices.WONDER_K):
+# device_screenshot_size = WONDER_K_SIZE
+# mask_img = pg.image.load(
+# os.path.join("assets", "maixpy_wonder_k_mask.png")
+# ).convert_alpha()
# Handle screenshots filename suffix when scaled
from krux.krux_settings import Settings
@@ -284,7 +291,10 @@ try:
if event.key == pg.K_s or event.key == pg.K_p:
screenshot("%s-%s.png" % (args.device, time.strftime('%d%m%y_%H_%M_%S')))
if event.type == pg.MOUSEBUTTONDOWN:
- ft6x36.touch_control.trigger_event()
+ if args.device == devices.WONDER_K:
+ gt911.touch_control.trigger_event()
+ else:
+ ft6x36.touch_control.trigger_event()
if event.type == pg.ACTIVEEVENT and event.gain:
pg.display.flip()
diff --git a/src/krux/camera.py b/src/krux/camera.py
index 1f9848f..8f61f47 100644
--- a/src/krux/camera.py
+++ b/src/krux/camera.py
@@ -30,7 +30,7 @@ OV2640_ID = 0x2642 # Lenses, vertical flip - Bit
OV5642_ID = 0x5642 # Lenses, horizontal flip - Bit
OV7740_ID = 0x7742 # No lenses, no Flip - M5sitckV, Amigo
GC0328_ID = 0x9D # Dock
-GC2145_ID = 0x45 # Yahboom
+GC2145_ID = 0x45 # Yahboom, WonderK
QR_SCAN_MODE = 0
ANTI_GLARE_MODE = 1
@@ -83,14 +83,22 @@ class Camera:
except Exception as e:
print("Camera not found:", e)
+ def _rotate_yaboom_or_wondermv(self):
+ return (
+ kboard.is_yahboom or kboard.is_wonder_mv
+ ) and Settings().is_flipped_orientation()
+
+ def _rotate_wonderk(self):
+ return kboard.is_wonder_k and not Settings().is_flipped_orientation()
+
def initialize_sensor(self, mode=QR_SCAN_MODE):
"""Initializes the camera"""
sensor.reset(freq=18200000)
self.cam_id = sensor.get_id()
- if kboard.is_cube or (
- kboard.can_flip_orientation
- and hasattr(Settings().hardware, "display")
- and getattr(Settings().hardware.display, "flipped_orientation", False)
+ if (
+ kboard.is_cube
+ or self._rotate_yaboom_or_wondermv()
+ or self._rotate_wonderk()
):
# Rotate camera 180 degrees on Cube
sensor.set_hmirror(1)
diff --git a/src/krux/display.py b/src/krux/display.py
index 1f5fd92..148cbdf 100644
--- a/src/krux/display.py
+++ b/src/krux/display.py
@@ -138,9 +138,14 @@ class Display:
],
)
self.set_pmu_backlight(Settings().hardware.display.brightness)
- elif kboard.is_yahboom or kboard.is_wonder_mv or kboard.is_tzt:
+ elif (
+ kboard.is_yahboom
+ or kboard.is_wonder_mv
+ or kboard.is_tzt
+ or kboard.is_wonder_k
+ ):
lcd.init(
- invert=True,
+ invert=not kboard.is_wonder_k,
rst=board.config["lcd"]["rst"],
dcx=board.config["lcd"]["dcx"],
ss=board.config["lcd"]["ss"],
diff --git a/src/krux/input.py b/src/krux/input.py
index 083779c..560aa49 100644
--- a/src/krux/input.py
+++ b/src/krux/input.py
@@ -107,6 +107,7 @@ class Input:
board.config["lcd"]["width"],
board.config["lcd"]["height"],
board.config["krux"]["pins"]["TOUCH_IRQ"],
+ board.config["krux"]["pins"].get("TOUCH_RESET", None),
)
self.buttons_active = False
self.button_integrity_check()
diff --git a/src/krux/kboard.py b/src/krux/kboard.py
index 93c051d..828abea 100644
--- a/src/krux/kboard.py
+++ b/src/krux/kboard.py
@@ -33,15 +33,16 @@ class KBoard:
self.is_yahboom = board.config["type"] == "yahboom"
self.is_wonder_mv = board.config["type"] == "wonder_mv"
self.is_tzt = board.config["type"] == "tzt"
+ self.is_wonder_k = board.config["type"] == "wonder_k"
self.is_m5stickv = board.config["type"] == "m5stickv"
- self.has_touchscreen = any(
- [self.is_yahboom, self.is_wonder_mv, self.is_tzt, self.is_amigo]
- )
+ self.has_touchscreen = board.config["krux"]["display"].get("touch", False)
self.has_minimal_display = self.is_m5stickv or self.is_cube
self.can_control_brightness = any(
- [self.is_cube, self.is_m5stickv, self.is_wonder_mv]
+ [self.is_cube, self.is_m5stickv, self.is_wonder_mv, self.is_wonder_k]
+ )
+ self.can_flip_orientation = (
+ self.is_yahboom or self.is_wonder_mv or self.is_wonder_k
)
- self.can_flip_orientation = self.is_yahboom or self.is_wonder_mv
self.has_light = "LED_W" in board.config["krux"]["pins"]
self.has_backlight = "BACKLIGHT" in board.config["krux"]["pins"]
self.has_encoder = "ENCODER" in board.config["krux"]["pins"]
diff --git a/src/krux/krux_settings.py b/src/krux/krux_settings.py
index 4dd6604..1d0ca7d 100644
--- a/src/krux/krux_settings.py
+++ b/src/krux/krux_settings.py
@@ -313,7 +313,8 @@ class TouchSettings(SettingsNamespace):
"""Touch sensitivity settings"""
namespace = "settings.touchscreen"
- threshold = NumberSetting(int, "threshold", 22, [10, 200])
+ default_th = 40 if kboard.is_wonder_k else 22
+ threshold = NumberSetting(int, "threshold", default_th, [10, 200])
def label(self, attr):
"""Returns a label for UI when given a setting name or namespace"""
@@ -498,6 +499,12 @@ class Settings(SettingsNamespace):
self.persist = PersistSettings()
self.appearance = ThemeSettings()
+ def is_flipped_orientation(self):
+ """Returns flipped orientation setting"""
+ return hasattr(Settings().hardware, "display") and getattr(
+ Settings().hardware.display, "flipped_orientation", False
+ )
+
def label(self, attr):
"""Returns a label for UI when given a setting name or namespace"""
main_menu = {
diff --git a/src/krux/pages/settings_page.py b/src/krux/pages/settings_page.py
index bce1976..20cd559 100644
--- a/src/krux/pages/settings_page.py
+++ b/src/krux/pages/settings_page.py
@@ -406,7 +406,7 @@ class SettingsPage(Page):
self.ctx.display.to_landscape()
self.ctx.display.to_portrait()
elif setting.attr == "brightness":
- if kboard.is_cube or kboard.is_wonder_mv:
+ if kboard.is_cube or kboard.is_wonder_mv or kboard.is_wonder_k:
self.ctx.display.gpio_backlight_ctrl(new_category)
elif kboard.is_m5stickv:
self.ctx.display.set_pmu_backlight(new_category)
diff --git a/src/krux/touch.py b/src/krux/touch.py
index 5dfa181..f222a65 100644
--- a/src/krux/touch.py
+++ b/src/krux/touch.py
@@ -23,7 +23,6 @@
import time
-from .touchscreens.ft6x36 import touch_control
from .krux_settings import Settings
IDLE = 0
@@ -42,7 +41,7 @@ TOUCH_S_PERIOD = 20 # Touch sample period - Min = 10
class Touch:
"""Touch is a singleton API to interact with touchscreen driver"""
- def __init__(self, width, height, irq_pin=None):
+ def __init__(self, width, height, irq_pin=None, res_pin=None):
"""Touch API init - width and height are in Landscape mode
For Krux width = max_y, height = max_x
"""
@@ -55,8 +54,17 @@ class Touch:
self.gesture = None
self.state = IDLE
self.width, self.height = width, height
- self.touch_driver = touch_control
- self.touch_driver.activate_irq(irq_pin)
+ if res_pin is not None:
+ from .touchscreens.gt911 import touch_control
+
+ self.touch_driver = touch_control
+ self.touch_driver.activate(irq_pin, res_pin)
+ else:
+ from .touchscreens.ft6x36 import touch_control
+
+ self.touch_driver = touch_control
+ self.touch_driver.activate_irq(irq_pin)
+
self.touch_driver.threshold(Settings().hardware.touch.threshold)
def clear_regions(self):
diff --git a/src/krux/touchscreens/gt911.py b/src/krux/touchscreens/gt911.py
new file mode 100644
index 0000000..0b99934
--- /dev/null
+++ b/src/krux/touchscreens/gt911.py
@@ -0,0 +1,186 @@
+# The MIT License (MIT)
+
+# Copyright (c) 2021-2024 Krux contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# GT911 specs:
+# I2C addresses: 0x5D (primary), 0x14 (secondary)
+
+import time
+from Maix import GPIO
+from fpioa_manager import fm
+from . import Touchscreen
+from ..i2c import i2c_bus
+
+# GT911 Register addresses
+GT911_PRODUCT_ID = 0x8140
+GT911_CONFIG_DATA = 0x8047
+GT911_COORD_ADDR = 0x814E
+GT911_CHECK_SUM = 0x80FF
+GT911_COMMAND = 0x8040
+GT911_CONFIG_TRIGGER = 0x8057
+
+GT911_ADDR = 0x5D
+TOUCH_THRESHOLD = 40
+ACTIVITY_THRESHOLD = 100 # Minimum time between touch events (ms)
+
+
+def __handler__(pin_num=None):
+ # pylint: disable=unused-argument
+ """GPIO interrupt handler for touch events"""
+ touch_control.trigger_event()
+
+
+class GT911(Touchscreen):
+ """GT911 capacitive touchscreen controller driver
+
+ Provides I2C communication interface for GT911 touch IC
+ with interrupt-driven touch event detection.
+ """
+
+ def __init__(self):
+ self.irq_pin = None
+ self.reset_pin = None
+ self.event_flag = False
+ self.irq_point = None
+ self.addr = GT911_ADDR
+ self.last_touch_time = 0
+
+ def _init_pins(self, irq_pin, reset_pin):
+ """Initialize interrupt and reset pins with proper timing sequence"""
+ fm.register(irq_pin, fm.fpioa.GPIOHS1)
+ fm.register(reset_pin, fm.fpioa.GPIOHS2)
+
+ # Configure pins (INT as pull-down sets I2C address to 0x5D)
+ self.irq_pin = GPIO(GPIO.GPIOHS1, GPIO.IN, GPIO.PULL_DOWN)
+ self.reset_pin = GPIO(GPIO.GPIOHS2, GPIO.OUT)
+
+ # Power-on reset sequence
+ self.reset_pin.value(0)
+ time.sleep_ms(10)
+ self.reset_pin.value(1)
+ time.sleep_ms(50)
+
+ def _init_gt911(self):
+ """Configure GT911 registers for touch detection"""
+ try:
+ # Verify device communication
+ product_id = self.read_reg(GT911_PRODUCT_ID, 4)
+ if product_id is not None:
+ print("GT911 found. Product ID:", product_id)
+
+ # Configure interrupt trigger mode and touch sensitivity
+ self.write_reg(GT911_CONFIG_TRIGGER, bytearray([0x02]))
+ self.write_reg(GT911_CONFIG_DATA, bytearray([TOUCH_THRESHOLD]))
+ self.write_reg(GT911_COMMAND, bytearray([0x00]))
+
+ except Exception as e:
+ print("GT911 initialization error:", e)
+
+ def activate(self, irq_pin, res_pin):
+ """Enable touchscreen with interrupt handling"""
+ self._init_pins(irq_pin, res_pin)
+ self._init_gt911()
+ self.irq_pin.irq(__handler__, GPIO.IRQ_RISING)
+
+ def write_reg(self, reg_addr, buf):
+ """Write data to GT911 register"""
+ if i2c_bus is not None:
+ try:
+ # Send 16-bit register address + data
+ payload = bytearray([reg_addr >> 8, reg_addr & 0xFF])
+ payload.extend(bytearray(buf))
+ i2c_bus.writeto(self.addr, payload)
+ except Exception as e:
+ print("GT911 write error:", e)
+
+ def read_reg(self, reg_addr, buf_len):
+ """Read data from GT911 register"""
+ if i2c_bus is not None:
+ try:
+ addr_bytes = bytearray([reg_addr >> 8, reg_addr & 0xFF])
+ i2c_bus.writeto(self.addr, addr_bytes)
+ return i2c_bus.readfrom(self.addr, buf_len)
+ except Exception as e:
+ print("GT911 read error:", e)
+ return None
+
+ def _read_touch_coordinates(self):
+ """Extract X,Y coordinates from touch data and clear interrupt flag"""
+ try:
+ touch_data = self.read_reg(GT911_COORD_ADDR + 1, 8)
+ if touch_data is not None and len(touch_data) >= 8:
+ x = touch_data[1] | (touch_data[2] << 8)
+ y = touch_data[3] | (touch_data[4] << 8)
+ return (x, y)
+ except Exception as e:
+ print("GT911 coordinate read error:", e)
+ return None
+
+ def current_point_validate(self):
+ """Check touch status and return coordinates if valid touch detected"""
+ try:
+ status = self.read_reg(GT911_COORD_ADDR, 1)
+ if status is not None and (status[0] & 0x80) and (status[0] & 0x0F) >= 1:
+ return self._read_touch_coordinates()
+ except Exception as e:
+ print("GT911 current_point error:", e)
+ return None
+
+ def current_point(self):
+ """Return touch coordinates if within activity threshold"""
+ if time.ticks_ms() - self.last_touch_time <= ACTIVITY_THRESHOLD:
+ return self._read_touch_coordinates()
+ return None
+
+ def trigger_event(self):
+ """Process touch interrupt and update event state"""
+ current_time = time.ticks_ms()
+
+ # Debounce: ignore rapid successive touches
+ if current_time - self.last_touch_time <= ACTIVITY_THRESHOLD:
+ self.last_touch_time = current_time
+ self.write_reg(GT911_COORD_ADDR, bytearray([0x00])) # Clear irq flag
+ return
+
+ # Capture new touch event
+ irq_point = self.current_point_validate()
+ if irq_point is not None:
+ self.irq_point = irq_point
+ self.event_flag = True
+ self.last_touch_time = current_time
+ self.write_reg(GT911_COORD_ADDR, bytearray([0x00])) # Clear irq flag
+
+ def event(self):
+ """Return and clear touch event flag"""
+ flag = self.event_flag
+ self.event_flag = False
+ return flag
+
+ def threshold(self, value):
+ """Update touch sensitivity threshold"""
+ try:
+ config_data = bytearray([value])
+ self.write_reg(GT911_CONFIG_DATA, config_data)
+ except Exception as e:
+ print("GT911 threshold error", e)
+
+
+touch_control = GT911()
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.