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 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,12 @@ import httpx
|
|||||||
from bleak import BleakClient, BleakScanner
|
from bleak import BleakClient, BleakScanner
|
||||||
from bleak.exc import BleakError
|
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"
|
SERVICE_UUID = "4c41555a-4465-7669-6365-000000000001"
|
||||||
RX_CHAR_UUID = "4c41555a-4465-7669-6365-000000000002"
|
RX_CHAR_UUID = "4c41555a-4465-7669-6365-000000000002"
|
||||||
REQ_CHAR_UUID = "4c41555a-4465-7669-6365-000000000004"
|
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)
|
device = await BleakScanner.find_device_by_name(DEVICE_NAME, timeout=SCAN_TIMEOUT)
|
||||||
if device:
|
if device:
|
||||||
log(f"Found: {device.address}")
|
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:
|
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.
|
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
|
# D-01: retry wrapper — defeats WinRT post-wake failure modes
|
||||||
# (Could not get GATT services: Unreachable, stale is_connected).
|
# (Could not get GATT services: Unreachable, stale is_connected).
|
||||||
# Rebuild a fresh BleakClient each attempt (locked D-05 recipe).
|
# Rebuild a fresh BleakClient each attempt (locked D-05 recipe).
|
||||||
|
|||||||
@@ -111,6 +111,63 @@ lib_deps =
|
|||||||
h2zero/NimBLE-Arduino@^2.1.1
|
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 =
|
||||||
|
+<*>
|
||||||
|
-<boards/>
|
||||||
|
+<boards/waveshare_amoled_206/>
|
||||||
|
|
||||||
|
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]
|
[env:waveshare_amoled_216_c6]
|
||||||
; C6 sibling of the AMOLED-2.16. Same panel/touch/PMU/IMU, no PSRAM,
|
; 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
|
; single-core RISC-V, BLE 5.3 only (no classic BT). If the build fails
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#include "board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#include "../../hal/display_hal.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Arduino_GFX_Library.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#include "../../hal/imu_hal.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <SensorQMI8658.hpp>
|
||||||
|
|
||||||
|
// 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; }
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "../../hal/input_hal.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#include "../../hal/power_hal.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <XPowersLib.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#include "../../hal/touch_hal.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user