- New Discover app: active NODE_DISCOVER sweep -> signal-ranked nearby list, tap-to-add-contact, GPS wardriving log (SD CSV) + signal-coloured coverage overlay on the map. Board-agnostic. - T-Display P4 TFT-LCD (HI8561) variant support (WADA_P4_LCD build; AMOLED bin untouched). - Wire the Attaky Core board (#158, @attakygit) into the release matrix + web flasher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
416 lines
17 KiB
C++
416 lines
17 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
// LilyGo T-Display P4 (AMOLED) touch input — implements the shared touch-input
|
||
// API (HeltecV4CapTouch.h) for the board's HI8561 capacitive controller.
|
||
//
|
||
// The HI8561 shares I2C_1 (Wire, SDA=7 / SCL=8) with the XL9535 expander, the
|
||
// PCF8563 RTC and the BQ27220 gauge. Wire is already begun by the XL9535 driver
|
||
// (xl9535.begin(7,8) in main.cpp) and the controller's RST/INT lines are driven
|
||
// by the expander — powerOnSequence() releases TOUCH_RST (IO3) before we probe.
|
||
// So, like RakTapV2Touch, this driver uses the shared Wire and must NOT re-init
|
||
// or reset the bus.
|
||
//
|
||
// STATE MACHINE: the tap/swipe/long-press gesture logic below is the proven one
|
||
// shared with the CHSC6x (Heltec V4) and FT5x06 (RAK Tap) drivers — only the
|
||
// physical report read (hi8561ReadPoint) and the panel geometry differ.
|
||
//
|
||
// ⚠️ FIRST-BOOT STATUS (display-first): hi8561ReadPoint() currently reports NO
|
||
// touch — the panel boots and renders, but taps aren't delivered yet. Enabling
|
||
// real touch is a single localised change: fill in the HI8561 report parse (addr
|
||
// 0x48; see the TODO in hi8561ReadPoint) and verify the coordinate mapping on the
|
||
// device. A wrong report layout produces phantom presses that lock the UI, so it
|
||
// stays disabled until it can be validated against hardware. Everything else
|
||
// (probe, async poll task, gesture machine, rotation) is complete.
|
||
|
||
#if defined(HAS_HI8561_TOUCH) && defined(ESP32)
|
||
|
||
#include <helpers/input/HeltecV4CapTouch.h> // shared touch-API contract (this file lives in variants/, not next to it)
|
||
#include <Arduino.h>
|
||
#include <Wire.h>
|
||
#include <freertos/FreeRTOS.h>
|
||
#include <freertos/task.h>
|
||
#include <helpers/ui/MomentaryButton.h>
|
||
|
||
#ifndef PIN_TOUCH_SDA
|
||
#define PIN_TOUCH_SDA 7
|
||
#endif
|
||
#ifndef PIN_TOUCH_SCL
|
||
#define PIN_TOUCH_SCL 8
|
||
#endif
|
||
|
||
// RM69A10 AMOLED is a fixed 568×1232 portrait panel (no display rotation on this
|
||
// board — CAP_ROTATABLE=0). The controller reports in the same portrait space,
|
||
// so raw == screen and the default (identity) mapping applies.
|
||
// LVGL renders at 284x616 (half the 568x1232 panel; RM69A10Display upscales 2x on flush), so touch
|
||
// coordinates must be reported in that SAME 284x616 logical space — the GT9895's native 1060x2400
|
||
// grid is scaled straight to 284x616 here.
|
||
// Logical (LVGL) space = native panel / UI-scale(2). The LilyGo T-Display P4 has TWO panel SKUs,
|
||
// each with its OWN touch controller (a build-time choice — HAS_TDP4_LCD):
|
||
// AMOLED (RM69A10, 568x1232) -> 284x616 logical, touch = Goodix GT9895 (0x5D).
|
||
// TFT-LCD (HI8561, 540x1168) -> 270x584 logical, touch = HI8561 integrated TDDI touch (0x68).
|
||
#if defined(HAS_TDP4_LCD)
|
||
static const int SCR_W = 270;
|
||
static const int SCR_H = 584;
|
||
static const int RAW_W = 270;
|
||
static const int RAW_H = 584;
|
||
|
||
// The HI8561 is a TDDI (touch+display in one). Its integrated touch is at I2C 0x68 and reads via an
|
||
// indirect ERAM-pointer protocol (ported from LilyGo cpp_bus_driver hi8561_touch.cpp, GPL-3.0):
|
||
// once at init, read a touch-info start-address out of the ERAM section table (InitAddressInfo);
|
||
// then each poll writes {0xF3, addr(BE32), 0x03} and reads the point back (x/y big-endian, 0xFFFF =
|
||
// no finger). It reports in panel-native 540x1168; we scale /2 (SCR_W/HI8561_NATIVE_W) to logical.
|
||
// TODO(device): confirm the native grid + axis orientation against on-screen taps (the touch-debug
|
||
// overlay shows raw coords, via heltecV4CapTouchGetRaw).
|
||
static const uint8_t HI8561_ADDR = 0x68;
|
||
static const uint32_t HI8561_ERAM_BASE = 0x20011000;
|
||
static const uint16_t HI8561_ERAM_SIZE = 4096;
|
||
// kEsramSectionInfoStartAddress = base + 4 + 25*8 + 4 = 0x200110D0 (see cpp_bus_driver hi8561_touch.h).
|
||
static const uint32_t HI8561_ESRAM_SECTION_INFO = HI8561_ERAM_BASE + 4 + 25 * 8 + 4;
|
||
static const uint8_t HI8561_POINT_OFFSET = 3; // kTouchPointAddressOffset
|
||
static const int HI8561_NATIVE_W = 540;
|
||
static const int HI8561_NATIVE_H = 1168;
|
||
static uint32_t s_hi8561_info_addr = 0; // touch_info_start_address_ (learned at init)
|
||
#else
|
||
static const int SCR_W = 284;
|
||
static const int SCR_H = 616;
|
||
static const int RAW_W = 284;
|
||
static const int RAW_H = 616;
|
||
|
||
// NOTE: despite this file's name, the LilyGo T-Display P4 AMOLED (RM69A10) SKU actually ships a
|
||
// Goodix GT9895 touch controller at 0x5D — NOT the HI8561 (that's the LCD SKU, gated above). An I2C
|
||
// bus scan on the real board found 0x5D. This path talks to the GT9895. Ported from LilyGo
|
||
// cpp_bus_driver gt9895.cpp: the touch report is at register 0x00010308; each read = write that
|
||
// 32-bit address big-endian (4 bytes), then read the report.
|
||
static const uint8_t GT9895_ADDR = 0x5D;
|
||
static const uint8_t GT9895_TOUCH_CMD[4] = { 0x00, 0x01, 0x03, 0x08 }; // register 0x00010308, big-endian
|
||
// The GT9895 reports in its native 1060x2400 digitiser grid; scale to the 284x616 logical space.
|
||
static const int GT9895_NATIVE_W = 1060;
|
||
static const int GT9895_NATIVE_H = 2400;
|
||
#endif
|
||
|
||
static bool s_init_ok = false;
|
||
static bool s_given_up = false;
|
||
static int s_retries = 0;
|
||
static char s_scan_str[128] = "scan: (not run)";
|
||
|
||
static volatile uint16_t s_dbg_rawx = 0, s_dbg_rawy = 0;
|
||
|
||
static bool s_have_touch = false;
|
||
static uint16_t s_cur_x = 0, s_cur_y = 0;
|
||
static bool s_down = false;
|
||
static unsigned long s_down_at = 0;
|
||
static uint16_t s_start_x = 0, s_start_y = 0;
|
||
static uint16_t s_last_x = 0, s_last_y = 0;
|
||
static bool s_live = false;
|
||
static uint16_t s_live_x = 0, s_live_y = 0;
|
||
static bool s_tap_pending = false;
|
||
static uint16_t s_tap_x = 0, s_tap_y = 0;
|
||
static bool s_swiping_now = false;
|
||
static bool s_swipe_pending = false;
|
||
static int8_t s_swipe_x = 0, s_swipe_y = 0;
|
||
|
||
static uint8_t s_ui_rotation = 0;
|
||
static uint8_t s_point_rotation = 0;
|
||
|
||
// Larger panel -> larger swipe threshold than the 240×320 boards.
|
||
#ifndef P4_TOUCH_SWIPE_MIN
|
||
#define P4_TOUCH_SWIPE_MIN 60
|
||
#endif
|
||
#ifndef P4_TOUCH_TAP_MOVE_MAX
|
||
#define P4_TOUCH_TAP_MOVE_MAX 24
|
||
#endif
|
||
#ifndef P4_TOUCH_LONG_MS
|
||
#define P4_TOUCH_LONG_MS 1000
|
||
#endif
|
||
|
||
// ---- Touch-controller low-level access ----
|
||
|
||
// Write an N-byte command (repeated start, no stop), then read M bytes. One helper per SKU (only
|
||
// the compiled one is used); the transaction shape is identical, just the I2C address differs.
|
||
#if defined(HAS_TDP4_LCD)
|
||
static bool hi8561WriteRead(const uint8_t* cmd, uint8_t clen, uint8_t* out, uint8_t olen) {
|
||
Wire.beginTransmission(HI8561_ADDR);
|
||
Wire.write(cmd, clen);
|
||
if (Wire.endTransmission(false) != 0) return false; // repeated start, no stop
|
||
uint8_t got = Wire.requestFrom((int)HI8561_ADDR, (int)olen);
|
||
if (got != olen) return false;
|
||
for (uint8_t i = 0; i < olen; i++) out[i] = Wire.read();
|
||
return true;
|
||
}
|
||
#else
|
||
static bool gt9895WriteRead(const uint8_t* cmd, uint8_t clen, uint8_t* out, uint8_t olen) {
|
||
Wire.beginTransmission(GT9895_ADDR);
|
||
Wire.write(cmd, clen);
|
||
if (Wire.endTransmission(false) != 0) return false; // repeated start, no stop
|
||
uint8_t got = Wire.requestFrom((int)GT9895_ADDR, (int)olen);
|
||
if (got != olen) return false;
|
||
for (uint8_t i = 0; i < olen; i++) out[i] = Wire.read();
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
// ---- Init (called by UITask loop every tick until success or give-up) ----
|
||
|
||
bool heltecV4CapTouchBegin() {
|
||
if (s_init_ok) return true;
|
||
if (s_given_up) return false;
|
||
|
||
++s_retries;
|
||
if (s_retries > 3) {
|
||
s_given_up = true;
|
||
Serial.println("[TOUCH] HI8561 giving up after 3 attempts — booting without touch");
|
||
return false;
|
||
}
|
||
|
||
// Wire is up on 7/8 (xl9535.begin). Bump to 400 kHz + short timeout so a stuck
|
||
// controller can't stall the UI loop.
|
||
Wire.setClock(400000);
|
||
Wire.setTimeOut(20);
|
||
delay(10);
|
||
|
||
// Bring-up: scan the I2C bus once so we can see exactly what touch controller is present
|
||
// (HI8561=0x68, GT9895=0x5D/0x14, others) — the RM69A10 variant may pair with GT9895, not HI8561.
|
||
if (s_retries == 1) {
|
||
char sc[96]; int n = 0; n += snprintf(sc, sizeof sc, "[TOUCH] i2c7/8 scan:");
|
||
for (uint8_t a = 0x08; a < 0x78 && n < (int)sizeof sc - 6; a++) {
|
||
Wire.beginTransmission(a);
|
||
if (Wire.endTransmission() == 0) n += snprintf(sc + n, sizeof sc - n, " %02X", a);
|
||
}
|
||
Serial.println(sc);
|
||
}
|
||
|
||
#if defined(HAS_TDP4_LCD)
|
||
// HI8561 integrated TDDI touch at 0x68 — probe, then learn the ERAM touch-info pointer once.
|
||
Wire.beginTransmission(HI8561_ADDR);
|
||
if (Wire.endTransmission() != 0) {
|
||
snprintf(s_scan_str, sizeof s_scan_str, "i2c7/8: no ACK at 0x%02X", HI8561_ADDR);
|
||
Serial.printf("[TOUCH] HI8561 not found (retry %d/3)\n", s_retries);
|
||
return false;
|
||
}
|
||
// InitAddressInfo: read touch_info_start_address_ out of the ERAM section table (offset 8).
|
||
{
|
||
const uint32_t sa = HI8561_ESRAM_SECTION_INFO;
|
||
uint8_t cmd[6] = { 0xF3, (uint8_t)(sa >> 24), (uint8_t)(sa >> 16),
|
||
(uint8_t)(sa >> 8), (uint8_t)sa, 0x03 };
|
||
uint8_t buf[48] = {0};
|
||
if (!hi8561WriteRead(cmd, sizeof cmd, buf, sizeof buf)) {
|
||
snprintf(s_scan_str, sizeof s_scan_str, "0x%02X hi8561 info read fail", HI8561_ADDR);
|
||
Serial.printf("[TOUCH] %s (retry %d/3)\n", s_scan_str, s_retries);
|
||
return false;
|
||
}
|
||
uint32_t info = (uint32_t)buf[8] | ((uint32_t)buf[9] << 8) |
|
||
((uint32_t)buf[10] << 16) | ((uint32_t)buf[11] << 24);
|
||
if (info < HI8561_ERAM_BASE || info >= (HI8561_ERAM_BASE + HI8561_ERAM_SIZE)) {
|
||
snprintf(s_scan_str, sizeof s_scan_str, "0x%02X hi8561 info bad 0x%08lX",
|
||
HI8561_ADDR, (unsigned long)info);
|
||
Serial.printf("[TOUCH] %s (retry %d/3)\n", s_scan_str, s_retries);
|
||
return false;
|
||
}
|
||
s_hi8561_info_addr = info;
|
||
}
|
||
s_init_ok = true;
|
||
snprintf(s_scan_str, sizeof s_scan_str, "0x%02X hi8561 OK (info 0x%08lX)",
|
||
HI8561_ADDR, (unsigned long)s_hi8561_info_addr);
|
||
Serial.printf("[TOUCH] %s\n", s_scan_str);
|
||
return true;
|
||
#else
|
||
Wire.beginTransmission(GT9895_ADDR);
|
||
if (Wire.endTransmission() != 0) {
|
||
snprintf(s_scan_str, sizeof s_scan_str, "i2c7/8: no ACK at 0x%02X", GT9895_ADDR);
|
||
Serial.printf("[TOUCH] GT9895 not found (retry %d/3)\n", s_retries);
|
||
return false;
|
||
}
|
||
|
||
s_init_ok = true;
|
||
snprintf(s_scan_str, sizeof s_scan_str, "0x%02X gt9895 OK (retry %d)", GT9895_ADDR, s_retries - 1);
|
||
Serial.printf("[TOUCH] %s\n", s_scan_str);
|
||
return true;
|
||
#endif
|
||
}
|
||
|
||
// ---- Coordinate mapping (identity in portrait; rotation kept for parity) ----
|
||
|
||
static void mapRaw(uint16_t rx, uint16_t ry, uint16_t* ox, uint16_t* oy) {
|
||
int sx, sy;
|
||
switch (s_point_rotation) {
|
||
case 1: sx = (RAW_H - 1) - (int)ry; sy = (int)rx; break; // ROT_90
|
||
case 3: sx = (int)ry; sy = (RAW_W - 1) - (int)rx; break; // ROT_270
|
||
default: sx = (int)rx; sy = (int)ry; break; // portrait / 180
|
||
}
|
||
if (sx < 0) sx = 0; if (sx >= SCR_W) sx = SCR_W - 1;
|
||
if (sy < 0) sy = 0; if (sy >= SCR_H) sy = SCR_H - 1;
|
||
*ox = (uint16_t)sx;
|
||
*oy = (uint16_t)sy;
|
||
}
|
||
|
||
// ---- Physical report read ------------------------------------------------
|
||
// Returns 1 = finger down (rx/ry set to logical coords), 0 = no touch, -1 = I2C error.
|
||
// TODO(device): verify X/Y axis orientation + origin against on-screen taps (may need flip/swap in mapRaw).
|
||
#if defined(HAS_TDP4_LCD)
|
||
// HI8561 integrated touch: read finger 1's point at (info_addr + kTouchPointAddressOffset). The
|
||
// report is 5 bytes: [x_hi][x_lo][y_hi][y_lo][pressure] (x/y big-endian); 0xFFFF/0xFFFF = no touch.
|
||
static int hi8561ReadPoint(uint16_t* rx, uint16_t* ry) {
|
||
if (!s_hi8561_info_addr) return -1;
|
||
const uint32_t addr = s_hi8561_info_addr + HI8561_POINT_OFFSET; // finger 1 (stride handled at init)
|
||
uint8_t cmd[6] = { 0xF3, (uint8_t)(addr >> 24), (uint8_t)(addr >> 16),
|
||
(uint8_t)(addr >> 8), (uint8_t)addr, 0x03 };
|
||
uint8_t buf[5] = {0};
|
||
if (!hi8561WriteRead(cmd, sizeof cmd, buf, sizeof buf)) return -1;
|
||
uint16_t x = ((uint16_t)buf[0] << 8) | buf[1]; // big-endian
|
||
uint16_t y = ((uint16_t)buf[2] << 8) | buf[3];
|
||
if (x == 0xFFFF && y == 0xFFFF) return 0; // no finger down
|
||
if (x >= HI8561_NATIVE_W) x = HI8561_NATIVE_W - 1; // clamp a stray out-of-range report
|
||
if (y >= HI8561_NATIVE_H) y = HI8561_NATIVE_H - 1;
|
||
// Scale the native 540x1168 grid down to the 270x584 logical space.
|
||
*rx = (uint16_t)((uint32_t)x * SCR_W / HI8561_NATIVE_W);
|
||
*ry = (uint16_t)((uint32_t)y * SCR_H / HI8561_NATIVE_H);
|
||
return 1;
|
||
}
|
||
#else
|
||
// GT9895 single-touch read: 16 bytes = TOUCH_POINT_ADDRESS_OFFSET(8) + 1*SINGLE_TOUCH_POINT_DATA_SIZE(8).
|
||
// buf[2] = finger count; point data at offset 8: [id/status][?][x_lo][x_hi][y_lo][y_hi][pressure][?]
|
||
// (x/y little-endian).
|
||
static int hi8561ReadPoint(uint16_t* rx, uint16_t* ry) {
|
||
uint8_t buf[16] = {0};
|
||
if (!gt9895WriteRead(GT9895_TOUCH_CMD, 4, buf, sizeof buf)) return -1;
|
||
uint8_t fingers = buf[2];
|
||
if (fingers < 1 || fingers > 10) return 0; // no finger down
|
||
uint32_t x = (uint32_t)buf[10] | ((uint32_t)buf[11] << 8);
|
||
uint32_t y = (uint32_t)buf[12] | ((uint32_t)buf[13] << 8);
|
||
// Scale the native 1060x2400 grid down to the 284x616 logical space.
|
||
*rx = (uint16_t)(x * (uint32_t)SCR_W / GT9895_NATIVE_W);
|
||
*ry = (uint16_t)(y * (uint32_t)SCR_H / GT9895_NATIVE_H);
|
||
return 1;
|
||
}
|
||
#endif
|
||
|
||
static void hi8561Poll() {
|
||
uint16_t rx = 0, ry = 0;
|
||
if (hi8561ReadPoint(&rx, &ry) == 1) {
|
||
s_dbg_rawx = rx;
|
||
s_dbg_rawy = ry;
|
||
mapRaw(rx, ry, &s_cur_x, &s_cur_y);
|
||
s_have_touch = true;
|
||
} else {
|
||
s_have_touch = false;
|
||
}
|
||
}
|
||
|
||
void heltecV4CapTouchGetRaw(uint16_t* rx, uint16_t* ry) {
|
||
if (rx) *rx = s_dbg_rawx;
|
||
if (ry) *ry = s_dbg_rawy;
|
||
}
|
||
|
||
// ---- Gesture state machine (shared logic; mirrors RakTapV2Touch) ----
|
||
|
||
int heltecV4CapTouchCheck() {
|
||
if (!s_init_ok) return BUTTON_EVENT_NONE;
|
||
hi8561Poll();
|
||
|
||
if (s_have_touch) {
|
||
s_live = true;
|
||
s_live_x = s_cur_x;
|
||
s_live_y = s_cur_y;
|
||
if (!s_down) {
|
||
s_down = true;
|
||
s_down_at = millis();
|
||
s_start_x = s_cur_x;
|
||
s_start_y = s_cur_y;
|
||
s_swiping_now = false;
|
||
}
|
||
s_last_x = s_cur_x;
|
||
s_last_y = s_cur_y;
|
||
if (!s_swiping_now) {
|
||
int adx = abs((int)s_last_x - (int)s_start_x);
|
||
int ady = abs((int)s_last_y - (int)s_start_y);
|
||
if (adx >= P4_TOUCH_SWIPE_MIN && adx > ady) s_swiping_now = true;
|
||
}
|
||
return BUTTON_EVENT_NONE;
|
||
}
|
||
|
||
if (s_down) {
|
||
s_down = false;
|
||
s_live = false;
|
||
s_swiping_now = false;
|
||
unsigned long dur = millis() - s_down_at;
|
||
int adx = abs((int)s_last_x - (int)s_start_x);
|
||
int ady = abs((int)s_last_y - (int)s_start_y);
|
||
if (adx >= P4_TOUCH_SWIPE_MIN && adx > (ady + 8)) {
|
||
bool left = ((int)s_last_x - (int)s_start_x) < 0;
|
||
s_swipe_x = left ? -1 : 1;
|
||
s_swipe_y = 0;
|
||
s_swipe_pending = true;
|
||
return left ? BUTTON_EVENT_DOUBLE_CLICK : BUTTON_EVENT_TRIPLE_CLICK;
|
||
}
|
||
if (ady >= P4_TOUCH_SWIPE_MIN && ady > (adx + 8)) {
|
||
s_swipe_x = 0;
|
||
s_swipe_y = ((int)s_last_y - (int)s_start_y) < 0 ? -1 : 1;
|
||
s_swipe_pending = true;
|
||
return BUTTON_EVENT_NONE;
|
||
}
|
||
if (dur >= 12 && dur < (unsigned long)P4_TOUCH_LONG_MS &&
|
||
adx <= P4_TOUCH_TAP_MOVE_MAX && ady <= P4_TOUCH_TAP_MOVE_MAX) {
|
||
s_tap_x = s_last_x;
|
||
s_tap_y = s_last_y;
|
||
s_tap_pending = true;
|
||
return BUTTON_EVENT_CLICK;
|
||
}
|
||
if (dur >= (unsigned long)P4_TOUCH_LONG_MS) {
|
||
return BUTTON_EVENT_LONG_PRESS;
|
||
}
|
||
} else {
|
||
s_live = false;
|
||
}
|
||
return BUTTON_EVENT_NONE;
|
||
}
|
||
|
||
bool heltecV4CapTouchGetLive(uint16_t* x, uint16_t* y) {
|
||
if (!s_live) return false;
|
||
if (x) *x = s_live_x;
|
||
if (y) *y = s_live_y;
|
||
return true;
|
||
}
|
||
bool heltecV4CapTouchPopTap(uint16_t* x, uint16_t* y) {
|
||
if (!s_tap_pending) return false;
|
||
s_tap_pending = false;
|
||
if (x) *x = s_tap_x;
|
||
if (y) *y = s_tap_y;
|
||
return true;
|
||
}
|
||
bool heltecV4CapTouchPopSwipe(int8_t* xd, int8_t* yd) {
|
||
if (!s_swipe_pending) return false;
|
||
s_swipe_pending = false;
|
||
if (xd) *xd = s_swipe_x;
|
||
if (yd) *yd = s_swipe_y;
|
||
return true;
|
||
}
|
||
|
||
// ---- Background poll (core 0) ----
|
||
|
||
static TaskHandle_t s_poll_task = nullptr;
|
||
static volatile bool s_async = false;
|
||
static uint32_t s_period_ms = 8;
|
||
|
||
static void touchPollTask(void* arg) {
|
||
(void)arg;
|
||
for (;;) {
|
||
heltecV4CapTouchCheck();
|
||
vTaskDelay(pdMS_TO_TICKS(s_period_ms));
|
||
}
|
||
}
|
||
|
||
bool heltecV4CapTouchStartBackgroundPoll(uint32_t period_ms) {
|
||
if (s_async || !s_init_ok) return false;
|
||
s_period_ms = period_ms < 4 ? 4 : (period_ms > 100 ? 100 : period_ms);
|
||
BaseType_t ok = xTaskCreatePinnedToCore(touchPollTask, "hi8561_touch", 3072,
|
||
nullptr, 2, &s_poll_task, 0);
|
||
if (ok == pdPASS) { s_async = true; return true; }
|
||
return false;
|
||
}
|
||
bool heltecV4CapTouchIsAsyncPolling() { return s_async; }
|
||
bool heltecV4CapTouchIsSwiping() { return s_swiping_now; }
|
||
void heltecV4CapTouchSetRotation(uint8_t r) { s_ui_rotation = r & 3; }
|
||
void heltecV4CapTouchSetPointRotation(uint8_t r) { s_point_rotation = r & 3; }
|
||
const char* heltecV4CapTouchDebug() { return s_scan_str; }
|
||
|
||
#endif // HAS_HI8561_TOUCH && ESP32
|