From 4b99c229a5e1ea6e0bdbe72b4896c0ef29fcdfcd Mon Sep 17 00:00:00 2001 From: "DESKTOP-PUHFO60\\dsx2016" Date: Tue, 27 Oct 2026 22:02:29 +0800 Subject: [PATCH] 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 --- src/main.cpp | 74 +++++++++++++++++++++++++++++++++++---------------- src/touch.cpp | 59 +++++++++++++++++++++++++++------------- 2 files changed, 91 insertions(+), 42 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f1b8d3e..e260bea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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) { diff --git a/src/touch.cpp b/src/touch.cpp index 3714d86..4ce1b7c 100644 --- a/src/touch.cpp +++ b/src/touch.cpp @@ -129,15 +129,23 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { #include -/* 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; }