Stop touch auto-toggle with hysteresis and button hitboxes.

Require solid press samples, cooldown between actions, and only fire
REC/PLAY inside real button bounds instead of half-screen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 22:02:29 +08:00
parent 242798fef2
commit 4b99c229a5
2 changed files with 91 additions and 42 deletions

View File

@@ -93,6 +93,16 @@ static const char *basename_of(const char *path) {
return p ? p + 1 : path;
}
/* Absolute UI hit boxes filled after create_ui (screen coords). */
static lv_area_t hit_rec;
static lv_area_t hit_play;
static lv_area_t hit_bl;
static uint32_t last_action_ms = 0;
static bool pt_in(const lv_area_t *a, int16_t x, int16_t y) {
return x >= a->x1 && x <= a->x2 && y >= a->y1 && y <= a->y2;
}
static void touchscreen_read(lv_indev_t *indev, lv_indev_data_t *data) {
static bool was_pressed = false;
static uint32_t press_ms = 0;
@@ -101,44 +111,48 @@ static void touchscreen_read(lv_indev_t *indev, lv_indev_data_t *data) {
touch_read_cb(indev, data);
/* Touch already reports landscape UI coords (320×240). LVGL still applies
* ROTATION_270 transform on indev — undo it so widgets + hit-test match. */
/* Touch reports landscape UI coords. Convert to native for LVGL ROTATION_270. */
if (data->state == LV_INDEV_STATE_PRESSED) {
const int16_t ux = data->point.x;
const int16_t uy = data->point.y;
/* Inverse of LVGL ROTATION_270: UI(ux,uy) → native(nx,ny) for indev feed */
data->point.x = uy;
data->point.y = (int16_t)(UI_HOR_RES - 1 - ux);
if (!was_pressed) {
was_pressed = true;
press_ms = millis();
/* Freeze first solid sample — never track finger/noise to release. */
press_ux = ux;
press_uy = uy;
Serial.printf("UI press (%d,%d)\n", (int)ux, (int)uy);
} else {
press_ux = ux;
press_uy = uy;
}
} else if (was_pressed) {
was_pressed = false;
uint32_t held = millis() - press_ms;
if (held < 700) {
/* Hit-test only (no LV_EVENT_CLICKED) — avoids double toggle.
* Top strip = backlight; everything else is half-screen REC | PLAY
* (touch Y cal is coarse on SoftSPI CYD — don't require exact button box). */
if (press_uy < 36) {
set_backlight((bright_level + 1) & 0x03);
char buf[24];
lv_snprintf(buf, sizeof(buf), "brightness %u/3", (unsigned)bright_level);
show_hint(buf, 900);
} else if (press_ux < (UI_HOR_RES / 2)) {
Serial.println("Hit → REC");
do_rec_toggle();
} else {
Serial.println("Hit → PLAY");
do_play_toggle();
}
const uint32_t held = millis() - press_ms;
const uint32_t now = millis();
/* Min hold filters spikes; max ignores long rests; cooldown stops auto-toggle. */
if (held < 40 || held > 800) return;
if (now - last_action_ms < 900) {
Serial.println("Touch ignored (cooldown)");
return;
}
if (pt_in(&hit_bl, press_ux, press_uy)) {
last_action_ms = now;
set_backlight((bright_level + 1) & 0x03);
char buf[24];
lv_snprintf(buf, sizeof(buf), "brightness %u/3", (unsigned)bright_level);
show_hint(buf, 900);
} else if (pt_in(&hit_rec, press_ux, press_uy)) {
last_action_ms = now;
Serial.println("Hit → REC");
do_rec_toggle();
} else if (pt_in(&hit_play, press_ux, press_uy)) {
last_action_ms = now;
Serial.println("Hit → PLAY");
do_play_toggle();
} else {
Serial.printf("Touch miss (%d,%d)\n", (int)press_ux, (int)press_uy);
}
}
}
@@ -339,6 +353,20 @@ static void create_ui(void) {
lv_obj_set_style_text_font(hint2, &lv_font_montserrat_12, 0);
lv_obj_set_style_text_color(hint2, COL_MUTED, 0);
lv_obj_align(hint2, LV_ALIGN_BOTTOM_MID, 0, 0);
/* Force layout then capture absolute hit boxes (+pad). */
lv_obj_update_layout(scr);
lv_obj_get_coords(btn_rec, &hit_rec);
lv_obj_get_coords(btn_play, &hit_play);
hit_rec.x1 -= 8; hit_rec.y1 -= 8; hit_rec.x2 += 8; hit_rec.y2 += 12;
hit_play.x1 -= 8; hit_play.y1 -= 8; hit_play.x2 += 8; hit_play.y2 += 12;
hit_bl.x1 = 0;
hit_bl.y1 = 0;
hit_bl.x2 = UI_HOR_RES - 1;
hit_bl.y2 = 28;
Serial.printf("Hit REC(%d..%d,%d..%d) PLAY(%d..%d,%d..%d)\n",
(int)hit_rec.x1, (int)hit_rec.x2, (int)hit_rec.y1, (int)hit_rec.y2,
(int)hit_play.x1, (int)hit_play.x2, (int)hit_play.y1, (int)hit_play.y2);
}
static void refresh_ui(void) {

View File

@@ -129,15 +129,23 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
#include <CYD28_TouchscreenR.h>
/* Native panel size — LVGL create(240,320) + ROTATION_270. */
/* Library thresh for XY update; our down uses hysteresis above that.
* Real presses on this panel are often z≈45–100; idle ≈5–15. */
static constexpr int16_t Z_DOWN = 55; /* press latch */
static constexpr int16_t Z_UP = 28; /* release */
static constexpr int16_t RAW_MIN = 150;
static CYD28_TouchR ts(SCREEN_WIDTH, SCREEN_HEIGHT);
static bool s_down = false;
static uint8_t s_down_streak = 0;
static int16_t s_ux = 0, s_uy = 0;
void touch_init() {
pinMode(SD_CS, OUTPUT);
digitalWrite(SD_CS, HIGH);
ts.begin();
ts.setThreshold(40); /* idle z~10; press usually >>100 */
Serial.println("Touch: CYD28 SoftSPI bitbang (SD-safe, poll) RNT map");
ts.setThreshold(45); /* XY update only when clearly above idle */
Serial.println("Touch: CYD28 SoftSPI + hysteresis (SD-safe)");
}
void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
@@ -145,32 +153,45 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
ts.isrWake = true;
CYD28_TS_Point p = ts.getPointRaw();
/* z threshold only — x/y stay 0 until press updates them in library */
const bool down = (p.z >= 40);
const bool raw_ok = (p.x >= RAW_MIN && p.y >= RAW_MIN);
static uint32_t last_log = 0;
if (millis() - last_log > (down ? 120u : 500u)) {
last_log = millis();
Serial.printf("Touch raw(%d,%d) z=%d down=%d\n", (int)p.x, (int)p.y,
(int)p.z, (int)down);
if (s_down) {
if (p.z < Z_UP) {
s_down = false;
s_down_streak = 0;
}
} else if (p.z >= Z_DOWN && raw_ok) {
if (s_down_streak < 255) s_down_streak++;
/* Need 3 solid samples — kills SD/EMI spikes that auto-toggle REC */
if (s_down_streak >= 3) {
s_down = true;
}
} else {
s_down_streak = 0;
}
if (!down) {
static uint32_t last_log = 0;
if (millis() - last_log > (s_down ? 200u : 800u)) {
last_log = millis();
Serial.printf("Touch raw(%d,%d) z=%d down=%d\n", (int)p.x, (int)p.y,
(int)p.z, (int)s_down);
}
if (!s_down || !raw_ok) {
data->state = LV_INDEV_STATE_RELEASED;
data->point.x = s_ux;
data->point.y = s_uy;
return;
}
/* Map raw ADC → landscape UI 320×240 directly (USB-left / ROTATION_270).
* Do NOT feed portrait+let-LVGL-rotate: LVGL 9.2 90/270 indev math is
* swapped on many CYD builds (see lvgl#10052). Invert Y raw→X so left
* physical taps land on left UI half. */
/* Landscape UI 320×240 (USB on left). Y-raw→X inverted for this panel. */
int16_t ux = (int16_t)map(p.y, 3800, 240, 0, UI_HOR_RES - 1);
int16_t uy = (int16_t)map(p.x, 200, 3700, 0, UI_VER_RES - 1);
ux = clampi(ux, 0, UI_HOR_RES - 1);
uy = clampi(uy, 0, UI_VER_RES - 1);
s_ux = clampi(ux, 0, UI_HOR_RES - 1);
s_uy = clampi(uy, 0, UI_VER_RES - 1);
data->point.x = ux;
data->point.y = uy;
data->point.x = s_ux;
data->point.y = s_uy;
data->state = LV_INDEV_STATE_PRESSED;
}