From c610e3af4815843a080c52447a1997557780bc27 Mon Sep 17 00:00:00 2001 From: wenil Date: Sat, 20 Jun 2026 12:46:46 +0300 Subject: [PATCH] Add Waveshare ESP32-S3-Touch-AMOLED-2.06 board port + Windows daemon fixes - New board firmware/src/boards/waveshare_amoled_206/ (CO5300 410x502, FT3168 touch, AXP2101 PMU, QMI8658 IMU). PlatformIO env waveshare_amoled_206. CO5300_COL_OFFSET=22, fixed orientation, PWR button on GPIO10, FocalTech inline touch reader. - Windows daemon: fix advertised device name (Claude Controller -> Clawdmeter) to match firmware ble.cpp, and add CLAWDMETER_ADDRESS env fallback so it connects by address when the bonded HID device is not advertising (Windows keeps it HID-connected). Co-Authored-By: Claude Opus 4.8 --- daemon/claude_usage_daemon_windows.py | 17 ++- firmware/platformio.ini | 57 +++++++++ .../src/boards/waveshare_amoled_206/board.h | 55 ++++++++ .../waveshare_amoled_206/board_init.cpp | 24 ++++ .../src/boards/waveshare_amoled_206/caps.cpp | 14 +++ .../boards/waveshare_amoled_206/display.cpp | 55 ++++++++ .../src/boards/waveshare_amoled_206/imu.cpp | 25 ++++ .../src/boards/waveshare_amoled_206/input.cpp | 20 +++ .../src/boards/waveshare_amoled_206/power.cpp | 119 ++++++++++++++++++ .../src/boards/waveshare_amoled_206/touch.cpp | 74 +++++++++++ 10 files changed, 457 insertions(+), 3 deletions(-) create mode 100644 firmware/src/boards/waveshare_amoled_206/board.h create mode 100644 firmware/src/boards/waveshare_amoled_206/board_init.cpp create mode 100644 firmware/src/boards/waveshare_amoled_206/caps.cpp create mode 100644 firmware/src/boards/waveshare_amoled_206/display.cpp create mode 100644 firmware/src/boards/waveshare_amoled_206/imu.cpp create mode 100644 firmware/src/boards/waveshare_amoled_206/input.cpp create mode 100644 firmware/src/boards/waveshare_amoled_206/power.cpp create mode 100644 firmware/src/boards/waveshare_amoled_206/touch.cpp diff --git a/daemon/claude_usage_daemon_windows.py b/daemon/claude_usage_daemon_windows.py index 85e6f32..01135ea 100644 --- a/daemon/claude_usage_daemon_windows.py +++ b/daemon/claude_usage_daemon_windows.py @@ -23,7 +23,12 @@ import httpx from bleak import BleakClient, BleakScanner from bleak.exc import BleakError -DEVICE_NAME = "Claude Controller" +DEVICE_NAME = "Clawdmeter" # must match firmware ble.cpp DEVICE_NAME +# Optional direct-connect address. Once the device is bonded as a BLE HID +# keyboard, Windows keeps it connected, so it stops advertising and an active +# scan (find_device_by_name) never sees it. Setting CLAWDMETER_ADDRESS lets the +# daemon connect straight to the bonded device by address via WinRT. +DEVICE_ADDRESS = os.environ.get("CLAWDMETER_ADDRESS") SERVICE_UUID = "4c41555a-4465-7669-6365-000000000001" RX_CHAR_UUID = "4c41555a-4465-7669-6365-000000000002" REQ_CHAR_UUID = "4c41555a-4465-7669-6365-000000000004" @@ -162,7 +167,12 @@ async def scan_for_device(): device = await BleakScanner.find_device_by_name(DEVICE_NAME, timeout=SCAN_TIMEOUT) if device: log(f"Found: {device.address}") - return device # BLEDevice or None — NOT an address string + return device # BLEDevice + if DEVICE_ADDRESS: + # Bonded device that's connected to Windows won't advertise — connect by address. + log(f"Not advertising; connecting by bonded address {DEVICE_ADDRESS}") + return DEVICE_ADDRESS # address string — BleakClient accepts it on WinRT + return device # None class Session: @@ -324,7 +334,8 @@ async def connect_and_run(device, stop_event: asyncio.Event, tray_state=None) -> Returns True if at least one successful write occurred. """ - log(f"Connecting to {device.address}...") + addr = device if isinstance(device, str) else device.address + log(f"Connecting to {addr}...") # D-01: retry wrapper — defeats WinRT post-wake failure modes # (Could not get GATT services: Unreachable, stale is_connected). # Rebuild a fresh BleakClient each attempt (locked D-05 recipe). diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 1c25ca8..aa3b8ba 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -111,6 +111,63 @@ lib_deps = h2zero/NimBLE-Arduino@^2.1.1 +[env:waveshare_amoled_206] +; Waveshare ESP32-S3-Touch-AMOLED-2.06 watch kit. 410x502 CO5300 + FT3168 +; touch + AXP2101 PMU + QMI8658 IMU. 32MB flash / 8MB OPI PSRAM. No IO +; expander (reset lines are direct GPIOs), fixed orientation. +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip +board = esp32-s3-devkitc-1 +framework = arduino +board_build.arduino.memory_type = qio_opi +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +board_build.partitions = default_16MB.csv +upload_speed = 921600 +monitor_speed = 115200 + +build_src_filter = + +<*> + - + + + +build_flags = + -DBOARD_AMOLED_206 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DBOARD_HAS_PSRAM + -DXPOWERS_CHIP_AXP2101 + ; NimBLE config — peripheral, 2 simultaneous connections so the OS can + ; hold the HID link while the daemon holds the data-service connection. + -DCONFIG_BT_NIMBLE_ROLE_BROADCASTER=1 + -DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL=0 + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER=0 + -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=2 + ; LVGL config via build flags + -DLV_CONF_SKIP + -DLV_COLOR_DEPTH=16 + -DLV_USE_LOG=0 + -DLV_USE_BAR=1 + -DLV_USE_LABEL=1 + -DLV_USE_ARC=1 + -DLV_USE_BTN=1 + -DLV_USE_LINE=1 + -DLV_USE_OBJ=1 + -DLV_USE_IMG=1 + -DLV_USE_IMAGE=1 + -DLV_USE_ANIMIMG=0 + -DLV_TICK_CUSTOM=1 + -DLV_USE_SNAPSHOT=1 + +lib_deps = + ; 1.6.4+ ships Arduino_CO5300 in mainline + moononournation/GFX Library for Arduino@^1.6.4 + lewisxhe/SensorLib@^0.2.6 + lewisxhe/XPowersLib@^0.2.7 + lvgl/lvgl@^9.2.0 + bblanchon/ArduinoJson@^7.0.0 + h2zero/NimBLE-Arduino@^2.1.1 + + [env:waveshare_amoled_216_c6] ; C6 sibling of the AMOLED-2.16. Same panel/touch/PMU/IMU, no PSRAM, ; single-core RISC-V, BLE 5.3 only (no classic BT). If the build fails diff --git a/firmware/src/boards/waveshare_amoled_206/board.h b/firmware/src/boards/waveshare_amoled_206/board.h new file mode 100644 index 0000000..000d5ba --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/board.h @@ -0,0 +1,55 @@ +#pragma once + +// Waveshare ESP32-S3-Touch-AMOLED-2.06 — rectangular AMOLED smartwatch kit. +// 410x502 CO5300 + FT3168 touch + AXP2101 PMU + QMI8658 IMU + PCF85063 RTC. +// No IO expander: display/touch reset lines are direct ESP32 GPIOs. +// IMU is present (initialized for I2C bus health) but rotation is disabled — +// the panel is non-square and the kit mounts in a fixed orientation. + +#define BOARD_NAME "Waveshare AMOLED 2.06" + +// ---- Display geometry (portrait, non-square) ---- +#define LCD_WIDTH 410 +#define LCD_HEIGHT 502 + +// ---- QSPI display pins (CO5300) ---- +// Same data lines as the 2.16 board, but SCLK and RESET differ. +#define LCD_CS 12 +#define LCD_SCLK 11 // 2.06: GPIO11 (2.16 board used GPIO38) +#define LCD_SDIO0 4 +#define LCD_SDIO1 5 +#define LCD_SDIO2 6 +#define LCD_SDIO3 7 +#define LCD_RESET 8 // direct GPIO (2.16 used GPIO2) +#define LCD_TE 13 // tearing-effect line (unused by GFX driver) + +// CO5300 GRAM is wider than the 410px active area, so the visible image needs +// a horizontal start offset. 22 verified for this panel (matches the value used +// by other 2.06 firmwares; eliminates the uninitialized green band on the right). +#define CO5300_COL_OFFSET 22 + +// ---- I2C bus (touch + PMU + IMU + RTC all share one bus) ---- +#define IIC_SDA 15 +#define IIC_SCL 14 + +// ---- Touch (FT3168 via minimal inline FocalTech-style I2C reader) ---- +#define TP_INT 38 +#define TP_RST 9 // direct GPIO (active LOW) +#define FT3168_ADDR 0x38 + +// ---- PMU ---- +#define AXP2101_ADDR 0x34 + +// ---- Buttons ---- +#define BTN_BACK_GPIO 0 // BOOT — primary, Space (PTT) +#define BTN_PWR_GPIO 10 // PWR side button — cycle screens / animations +// PWR polarity: assumed active LOW with internal pull-up (button to GND, like +// BOOT). If the screen-cycle button never registers, flip BTN_PWR_ACTIVE_LOW. +#define BTN_PWR_ACTIVE_LOW 1 + +// ---- Capability flags ---- +#define BOARD_HAS_SECONDARY_BUTTON 0 // only BOOT via input_hal; PWR via power_hal +#define BOARD_HAS_ROTATION 0 // non-square panel, fixed orientation +#define BOARD_HAS_IMU 1 // present + initialized, rotation off +#define BOARD_HAS_BATTERY 1 +#define BOARD_HAS_IO_EXPANDER 0 diff --git a/firmware/src/boards/waveshare_amoled_206/board_init.cpp b/firmware/src/boards/waveshare_amoled_206/board_init.cpp new file mode 100644 index 0000000..759bf76 --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/board_init.cpp @@ -0,0 +1,24 @@ +#include "board.h" +#include +#include + +// AMOLED-2.06 has no IO expander — display and touch reset lines are direct +// ESP32 GPIOs. We bring up the shared I2C bus and release both reset lines +// before any display/touch HAL call runs. +// +// Display power: on this kit the AMOLED rail is supplied by the AXP2101 PMU +// (the "DSI_PWR_EN" net on the schematic sits on the PMIC, not an ESP32 pin), +// matching the AMOLED-2.16 sibling which lights up with no explicit rail +// poke after pmu.begin(). If the panel stays dark on first flash, enable the +// AXP rails in power.cpp (see the commented block there). +extern "C" void board_init(void) { + Wire.begin(IIC_SDA, IIC_SCL); + + // Release the touch controller from reset (active LOW). The display's own + // reset is driven by the Arduino_GFX CO5300 driver via LCD_RESET. + pinMode(TP_RST, OUTPUT); + digitalWrite(TP_RST, LOW); + delay(5); + digitalWrite(TP_RST, HIGH); + delay(50); +} diff --git a/firmware/src/boards/waveshare_amoled_206/caps.cpp b/firmware/src/boards/waveshare_amoled_206/caps.cpp new file mode 100644 index 0000000..4cff9b4 --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/caps.cpp @@ -0,0 +1,14 @@ +#include "../../hal/board_caps.h" +#include "board.h" + +static const BoardCaps caps = { + .name = BOARD_NAME, + .width = LCD_WIDTH, + .height = LCD_HEIGHT, + .button_count = 1, + .has_rotation = false, + .has_battery = true, + .has_imu = true, +}; + +const BoardCaps& board_caps(void) { return caps; } diff --git a/firmware/src/boards/waveshare_amoled_206/display.cpp b/firmware/src/boards/waveshare_amoled_206/display.cpp new file mode 100644 index 0000000..446ca10 --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/display.cpp @@ -0,0 +1,55 @@ +#include "../../hal/display_hal.h" +#include "board.h" +#include +#include + +// AMOLED-2.06 is fixed at 0° (non-square 410x502 panel — the 2.16 board's CPU +// strip-rotation assumes a square panel and can't be reused here). No rot_buf. +// +// Display driver is the CO5300 (same controller family as the 2.16 board). +// Reset is a direct GPIO (LCD_RESET), so the GFX driver owns it. The CO5300's +// active area starts partway into the controller GRAM, so it gets a column +// offset to center the image (CO5300_COL_OFFSET — verify on hardware). + +static Arduino_DataBus* bus = nullptr; +static Arduino_CO5300* gfx = nullptr; + +void display_hal_init(void) { + bus = new Arduino_ESP32QSPI( + LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3); + // CO5300: (bus, rst, rotation, w, h, col_off1, row_off1, col_off2, row_off2) + gfx = new Arduino_CO5300( + bus, LCD_RESET, 0 /* no rotation */, + LCD_WIDTH, LCD_HEIGHT, CO5300_COL_OFFSET, 0, 0, 0); +} + +void display_hal_begin(void) { + gfx->begin(); + gfx->fillScreen(0x0000); + gfx->setBrightness(200); +} + +void display_hal_set_brightness(uint8_t level) { + if (gfx) gfx->setBrightness(level); +} + +void display_hal_fill_screen(uint16_t color) { + if (gfx) gfx->fillScreen(color); +} + +void display_hal_draw_bitmap(int32_t x, int32_t y, int32_t w, int32_t h, + const uint16_t* pixels) { + if (gfx) gfx->draw16bitRGBBitmap(x, y, (uint16_t*)pixels, w, h); +} + +void display_hal_tick(void) { + // No rotation handling needed on this board. +} + +// CO5300 requires even-aligned flush regions. +void display_hal_round_area(int32_t* x1, int32_t* y1, int32_t* x2, int32_t* y2) { + *x1 = *x1 & ~1; + *y1 = *y1 & ~1; + *x2 = *x2 | 1; + *y2 = *y2 | 1; +} diff --git a/firmware/src/boards/waveshare_amoled_206/imu.cpp b/firmware/src/boards/waveshare_amoled_206/imu.cpp new file mode 100644 index 0000000..da76ac9 --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/imu.cpp @@ -0,0 +1,25 @@ +#include "../../hal/imu_hal.h" +#include "board.h" +#include +#include +#include + +// AMOLED-2.06 ships with QMI8658 populated, but the panel is non-square and +// mounts in a fixed orientation, so rotation is disabled. We initialize the +// device anyway to keep the shared I2C bus healthy, but always report 0. + +static SensorQMI8658 imu; + +void imu_hal_init(void) { + if (!imu.begin(Wire, QMI8658_L_SLAVE_ADDRESS, IIC_SDA, IIC_SCL)) { + Serial.println("QMI8658 init failed"); + return; + } + Serial.println("QMI8658 init OK (rotation disabled on this board)"); +} + +void imu_hal_tick(void) { + // No-op — rotation is disabled. +} + +uint8_t imu_hal_rotation_quadrant(void) { return 0; } diff --git a/firmware/src/boards/waveshare_amoled_206/input.cpp b/firmware/src/boards/waveshare_amoled_206/input.cpp new file mode 100644 index 0000000..0649bb2 --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/input.cpp @@ -0,0 +1,20 @@ +#include "../../hal/input_hal.h" +#include "board.h" +#include + +// AMOLED-2.06 exposes the BOOT button as the primary HID input. The PWR side +// button is handled in power_hal (BTN_PWR_GPIO). No secondary button. + +void input_hal_init(void) { + pinMode(BTN_BACK_GPIO, INPUT_PULLUP); +} + +bool input_hal_is_held(InputButton btn) { + switch (btn) { + case INPUT_BTN_PRIMARY: + return digitalRead(BTN_BACK_GPIO) == LOW; + case INPUT_BTN_SECONDARY: + return false; // not present on this board + } + return false; +} diff --git a/firmware/src/boards/waveshare_amoled_206/power.cpp b/firmware/src/boards/waveshare_amoled_206/power.cpp new file mode 100644 index 0000000..cba8b4a --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/power.cpp @@ -0,0 +1,119 @@ +#include "../../hal/power_hal.h" +#include "board.h" +#include +#include +#include + +// AXP2101 provides battery monitoring. The PWR side button on this kit is a +// direct ESP32 GPIO (BTN_PWR_GPIO), not the AXP PKEY, so we poll it and +// synthesize the same three edges the AXP IRQ path produces on the 2.16 board: +// short — fired on release if the hold was shorter than PWR_LONG_MS +// long — fired once when a hold crosses PWR_LONG_MS +// release — fired on every release edge +// This keeps the hold-to-pair gesture logic in main.cpp board-agnostic. + +#define BATTERY_POLL_MS 2000 +#define CHARGING_POLL_MS 500 +#define PWR_POLL_MS 50 +#define PWR_LONG_MS 1500 // hold threshold, mirrors the AXP LONG IRQ + +#if BTN_PWR_ACTIVE_LOW + #define PWR_IS_DOWN(level) ((level) == LOW) +#else + #define PWR_IS_DOWN(level) ((level) == HIGH) +#endif + +static XPowersPMU pmu; + +static int cached_pct = -1; +static bool cached_charging = false; +static bool cached_vbus = false; +static bool pwr_pressed_flag = false; +static bool pwr_long_flag = false; +static bool pwr_released_flag = false; +static bool last_pwr_down = false; // edge detector for BTN_PWR_GPIO +static uint32_t pwr_press_started_ms = 0; +static bool pwr_long_fired = false; +static uint32_t last_battery_ms = 0; +static uint32_t last_charging_ms = 0; +static uint32_t last_pwr_ms = 0; + +void power_hal_init(void) { + if (!pmu.begin(Wire, AXP2101_ADDR, IIC_SDA, IIC_SCL)) { + Serial.println("AXP2101 init failed"); + return; + } + Serial.println("AXP2101 init OK"); + + pmu.enableBattDetection(); + pmu.enableBattVoltageMeasure(); + + // ---- OPTIONAL: explicit AMOLED rail enable ---------------------------- + // The 2.16 sibling lights up without this, so it's left off by default. + // If the panel stays DARK on first flash, the display rail isn't powered. + // Uncomment and set the channels/voltages to match the Waveshare 2.06 + // schematic (typical AXP2101 AMOLED kits use the ALDO/BLDO rails): + // + // pmu.setALDO1Voltage(1800); pmu.enableALDO1(); // panel logic 1.8V + // pmu.setBLDO1Voltage(3300); pmu.enableBLDO1(); // panel/touch 3.3V + // pmu.setBLDO2Voltage(3300); pmu.enableBLDO2(); + // delay(50); + // ----------------------------------------------------------------------- + + // PWR button is a direct GPIO on this board. + pinMode(BTN_PWR_GPIO, BTN_PWR_ACTIVE_LOW ? INPUT_PULLUP : INPUT_PULLDOWN); + + cached_charging = pmu.isCharging(); + cached_vbus = pmu.isVbusIn(); + cached_pct = pmu.getBatteryPercent(); +} + +void power_hal_tick(void) { + uint32_t now = millis(); + + if (now - last_charging_ms >= CHARGING_POLL_MS) { + last_charging_ms = now; + cached_charging = pmu.isCharging(); + cached_vbus = pmu.isVbusIn(); + } + if (now - last_battery_ms >= BATTERY_POLL_MS) { + last_battery_ms = now; + cached_pct = pmu.getBatteryPercent(); + } + if (now - last_pwr_ms >= PWR_POLL_MS) { + last_pwr_ms = now; + bool pwr_down = PWR_IS_DOWN(digitalRead(BTN_PWR_GPIO)); + if (pwr_down && !last_pwr_down) { // press begins + pwr_press_started_ms = now; + pwr_long_fired = false; + } else if (pwr_down && last_pwr_down) { // held + if (!pwr_long_fired && (now - pwr_press_started_ms >= PWR_LONG_MS)) { + pwr_long_flag = true; + pwr_long_fired = true; + } + } else if (!pwr_down && last_pwr_down) { // release + pwr_released_flag = true; + if (!pwr_long_fired) pwr_pressed_flag = true; // short press + } + last_pwr_down = pwr_down; + } +} + +int power_hal_battery_pct(void) { return cached_pct; } +bool power_hal_is_charging(void) { return cached_charging; } +bool power_hal_is_vbus_in(void) { return cached_vbus; } + +bool power_hal_pwr_pressed(void) { + if (pwr_pressed_flag) { pwr_pressed_flag = false; return true; } + return false; +} + +bool power_hal_pwr_long_pressed(void) { + if (pwr_long_flag) { pwr_long_flag = false; return true; } + return false; +} + +bool power_hal_pwr_released(void) { + if (pwr_released_flag) { pwr_released_flag = false; return true; } + return false; +} diff --git a/firmware/src/boards/waveshare_amoled_206/touch.cpp b/firmware/src/boards/waveshare_amoled_206/touch.cpp new file mode 100644 index 0000000..99dd8fe --- /dev/null +++ b/firmware/src/boards/waveshare_amoled_206/touch.cpp @@ -0,0 +1,74 @@ +#include "../../hal/touch_hal.h" +#include "board.h" +#include +#include + +// Minimal capacitive-touch reader for the FT3168 (FocalTech). Same data layout +// as the FT3168 on the AMOLED-1.8 board, so we reuse the inline reader instead +// of vendoring the GPLv3 Arduino_DriveBus library. +// reg 0x02: low nibble = active finger count +// reg 0x03 / 0x04: X1 high (low nibble) + X1 low +// reg 0x05 / 0x06: Y1 high (low nibble) + Y1 low +// +// Axis orientation: started with no swap/mirror (matches the portrait FT3168 +// on the 1.8 board). If touch coordinates come out flipped/swapped on this +// panel, mirror/swap them in touch_read_into_shared_state() below. + +static volatile bool touch_data_ready = false; +static volatile bool touch_pressed = false; +static volatile uint16_t touch_x = 0; +static volatile uint16_t touch_y = 0; + +static void IRAM_ATTR touch_isr(void) { + touch_data_ready = true; +} + +static void touch_read_into_shared_state(void) { + Wire.beginTransmission(FT3168_ADDR); + Wire.write(0x02); + if (Wire.endTransmission(false) != 0) { touch_pressed = false; return; } + if (Wire.requestFrom((uint8_t)FT3168_ADDR, (uint8_t)5) != 5) { touch_pressed = false; return; } + uint8_t fingers = Wire.read() & 0x0F; + uint8_t xH = Wire.read(); + uint8_t xL = Wire.read(); + uint8_t yH = Wire.read(); + uint8_t yL = Wire.read(); + if (fingers == 0 || fingers > 5) { + touch_pressed = false; + return; + } + touch_x = ((uint16_t)(xH & 0x0F) << 8) | xL; + touch_y = ((uint16_t)(yH & 0x0F) << 8) | yL; + touch_pressed = true; +} + +void touch_hal_init(void) { + // FT3168 power-mode register 0xA5 = 0x00: active scanning. + Wire.beginTransmission(FT3168_ADDR); + Wire.write(0xA5); + Wire.write(0x00); + Wire.endTransmission(); + + // Verify the controller answers (FT3168 chip-id is reg 0xA0). + Wire.beginTransmission(FT3168_ADDR); + Wire.write(0xA0); + if (Wire.endTransmission(false) == 0 && Wire.requestFrom((uint8_t)FT3168_ADDR, (uint8_t)1) == 1) { + Serial.printf("Touch FT3168 ID=0x%02X (addr 0x%02X)\n", Wire.read(), FT3168_ADDR); + } else { + Serial.printf("Touch FT3168 ID read failed (addr 0x%02X)\n", FT3168_ADDR); + } + + pinMode(TP_INT, INPUT_PULLUP); + attachInterrupt(TP_INT, touch_isr, FALLING); + Serial.println("Touch attached on INT pin"); +} + +void touch_hal_read(uint16_t* x, uint16_t* y, bool* pressed) { + if (touch_data_ready) { + touch_data_ready = false; + touch_read_into_shared_state(); + } + *x = touch_x; + *y = touch_y; + *pressed = touch_pressed; +}