diff --git a/README.md b/README.md index 2d228fd..85b5200 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ ESP32 Cheap Yellow Display + INMP441(I2S)→ microSD **WAV 录音**,支持 - 实时电平条(近似 dB,与 inmp441test 同一换算) - **PLAY / STOP**:用板载喇叭(GPIO26)回放最近一条录音 - 需插入 **FAT32 microSD** -- 备用:板载 **BOOT** 键切换 REC;串口 `115200` 发 `r` / `p` 切换录音 / 播放 +- 备用:板载 **BOOT** 键切换 REC;串口整行 `r` / `rec` / `p` / `play`(不会吃 USB 杂字节) +- **绝无自动开录**:无触控按钮 / BOOT / 串口整行命令;上电 4s 内锁死 ## 2.8" 触控说明(重要) @@ -49,9 +50,9 @@ ESP32 Cheap Yellow Display + INMP441(I2S)→ microSD **WAV 录音**,支持 |----|------| | 总线 | SoftSPI,读触控前拉高 `SD_CS` | | 坐标 | raw ADC → 横屏 UI 320×240;**X/Y 均反向** 以对齐 `LVGL ROTATION_270` 画面 | -| 点击 | 只命中屏幕上画的 REC / PLAY 区域(带边距);松手触发 | -| 防抖 | 压力滞回 + **1.5 s** 动作冷却,避免误触反复开停录音 | -| 备用 | BOOT / 串口 `r`·`p`(触控异常时仍可用) | +| 点击 | 只命中画出的 REC / PLAY;按住 ≥120ms 且指头不滑出按钮才算 | +| 防抖 | z≥70 且连续 5 采样;**2 s** 冷却;上电 **4 s** 动作锁 | +| 备用 | BOOT(稳定按下)/ 串口整行命令 | 参考:[RNT CYD + SoftSPI + SD](https://randomnerdtutorials.com/esp32-cyd-display-touchscreen-microsd-card/)、[hexeguitar/CYD28_Touchscreen](https://github.com/hexeguitar/CYD28_Touchscreen)。 @@ -102,3 +103,4 @@ README.md |------|------| | 2026-10-27 | 初版:INMP441 → microSD WAV 录音 + DAC 回放;双板 `cyd_28r` / `cyd_35r` | | 2026-10-27 | 2.8 触控:SoftSPI + XY 反向校准、按钮命中区、1.5s 冷却;消除误触反复录音 | +| 2026-10-27 | 显式操作门闩:上电锁 4s、触控长按确认、BOOT 防抖、串口整行;禁止无操作自启录音 | diff --git a/firmware/cyd_28r/VERSION.txt b/firmware/cyd_28r/VERSION.txt index 1b1bb37..06c82fb 100644 --- a/firmware/cyd_28r/VERSION.txt +++ b/firmware/cyd_28r/VERSION.txt @@ -1,5 +1,5 @@ name: CYD inmp441recondtest (cyd_28r) -git: 348cf9d +git: b8527e9 date: 2026-10-27 board: ESP32-2432S028R (ILI9341) inmp441: WS=GPIO22 SCK=GPIO27 SD=GPIO35 @@ -14,5 +14,5 @@ offsets: sha256: bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8 boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0 - firmware.bin 7C0659A130E7ED20DD1DA0006616E31AFA7F597F65E1156C8FF9CD12BBC79994 + firmware.bin A637924B5D9A00E3E0473C50DCA582E019616C563991A0AD842AD176C2318EC1 partitions.bin 0A8B5720E7B77FF11F1462458C3A509DEE79224E5279898F26D6A2E3AE0517B7 diff --git a/firmware/cyd_28r/firmware.bin b/firmware/cyd_28r/firmware.bin index 5bb962a..47b4686 100644 Binary files a/firmware/cyd_28r/firmware.bin and b/firmware/cyd_28r/firmware.bin differ diff --git a/src/main.cpp b/src/main.cpp index dc72533..000c856 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -98,16 +98,35 @@ 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 uint32_t actions_unlock_ms = 0; /* no REC/PLAY until this time */ 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; } +/* Hard gate: nothing starts/stops without an explicit, accepted input path. */ +static bool user_action_ok(const char *via) { + const uint32_t now = millis(); + if (now < actions_unlock_ms) { + Serial.printf("Action blocked (boot lock %lums) via %s\n", + (unsigned long)(actions_unlock_ms - now), via); + return false; + } + if (now - last_action_ms < 2000) { + Serial.printf("Action blocked (cooldown) via %s\n", via); + return false; + } + last_action_ms = now; + return true; +} + static void touchscreen_read(lv_indev_t *indev, lv_indev_data_t *data) { static bool was_pressed = false; static uint32_t press_ms = 0; static int16_t press_ux = 0; static int16_t press_uy = 0; + static bool press_in_rec = false; + static bool press_in_play = false; touch_read_cb(indev, data); @@ -121,34 +140,40 @@ static void touchscreen_read(lv_indev_t *indev, lv_indev_data_t *data) { 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); + press_in_rec = pt_in(&hit_rec, ux, uy); + press_in_play = pt_in(&hit_play, ux, uy); + Serial.printf("UI press (%d,%d) rec=%d play=%d\n", (int)ux, (int)uy, + (int)press_in_rec, (int)press_in_play); + } else { + /* Finger must stay on the same button — sliding/noise cancels. */ + if (press_in_rec && !pt_in(&hit_rec, ux, uy)) press_in_rec = false; + if (press_in_play && !pt_in(&hit_play, ux, uy)) press_in_play = false; } } else if (was_pressed) { was_pressed = false; const uint32_t held = millis() - press_ms; - const uint32_t now = millis(); - /* SoftSPI taps can be <15ms; 1.5s lockout kills auto start/stop. */ - if (held > 900) return; - if (now - last_action_ms < 1500) { - Serial.println("Touch ignored (cooldown)"); + /* Real finger: latch(~25ms) + hold ≥120ms. EMI spikes are << this. */ + if (held < 120 || held > 1500) { + Serial.printf("Touch reject hold=%lums\n", (unsigned long)held); + press_in_rec = press_in_play = false; return; } - - /* Only the two drawn buttons — no top-strip actions (was causing chaos). */ - 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(); + if (press_in_rec && !press_in_play) { + if (user_action_ok("touch-REC")) { + Serial.println("Hit → REC"); + do_rec_toggle(); + } + } else if (press_in_play && !press_in_rec) { + if (user_action_ok("touch-PLAY")) { + Serial.println("Hit → PLAY"); + do_play_toggle(); + } } else { - Serial.printf("Touch miss (%d,%d)\n", (int)press_ux, (int)press_uy); + Serial.printf("Touch miss/ambiguous (%d,%d)\n", (int)press_ux, (int)press_uy); } + press_in_rec = press_in_play = false; } } @@ -472,7 +497,7 @@ void setup() { Serial.println(mic_ok ? "I2S mic OK" : "I2S mic FAIL — check wiring"); Serial.println(card_ok ? "SD OK" : "SD FAIL — insert FAT32 microSD"); - Serial.println("Hint: BOOT key or serial 'r' toggles REC; 'p' toggles PLAY"); + Serial.println("Hint: BOOT / serial line 'r'|'rec' / 'p'|'play' (explicit only)"); #if defined(CYD_BOARD_3248S035) Serial.println("3.5C: SCK=GPIO21 (shared GT911 INT; touch is poll-only)"); #endif @@ -496,6 +521,11 @@ void setup() { else if (!card_ok) show_hint("insert microSD", 3000); refresh_ui(); + + /* No REC/PLAY from any path for first 4s (boot noise / USB / touch settle). */ + actions_unlock_ms = millis() + 4000; + last_action_ms = millis(); + Serial.println("Actions armed after 4s — touch/BOOT/serial only"); } void loop() { @@ -504,29 +534,56 @@ void loop() { recorder_tick(); player_tick(); - /* BOOT (GPIO0) short press = REC — works even if touch is flaky. */ - static bool boot_was = false; + /* BOOT (GPIO0): require stable LOW, ignore glitches. */ + static uint8_t boot_low_n = 0; + static bool boot_latched = false; static uint32_t boot_ms = 0; - bool boot_now = (digitalRead(0) == LOW); - if (boot_now && !boot_was) { - boot_was = true; - boot_ms = millis(); - } else if (!boot_now && boot_was) { - boot_was = false; - if (millis() - boot_ms < 800) { - Serial.println("BOOT → REC"); - do_rec_toggle(); + if (digitalRead(0) == LOW) { + if (boot_low_n < 255) boot_low_n++; + if (!boot_latched && boot_low_n >= 8) { /* ~40ms stable */ + boot_latched = true; + boot_ms = millis(); } + } else { + if (boot_latched) { + const uint32_t held = millis() - boot_ms; + boot_latched = false; + if (held >= 80 && held < 2000 && user_action_ok("BOOT")) { + Serial.println("BOOT → REC"); + do_rec_toggle(); + } + } + boot_low_n = 0; } + /* Serial: whole line only — lone 'r' in USB garbage must NOT start REC. + * Accept: "r" / "rec" / "p" / "play" (+ CR/LF). */ + static char sline[24]; + static uint8_t slen = 0; while (Serial.available() > 0) { char c = (char)Serial.read(); - if (c == 'r' || c == 'R') { - Serial.println("Serial → REC"); - do_rec_toggle(); - } else if (c == 'p' || c == 'P') { - Serial.println("Serial → PLAY"); - do_play_toggle(); + if (c == '\r' || c == '\n') { + if (slen > 0) { + sline[slen] = 0; + if (!strcmp(sline, "r") || !strcmp(sline, "R") || + !strcmp(sline, "rec") || !strcmp(sline, "REC")) { + if (user_action_ok("serial-REC")) { + Serial.println("Serial → REC"); + do_rec_toggle(); + } + } else if (!strcmp(sline, "p") || !strcmp(sline, "P") || + !strcmp(sline, "play") || !strcmp(sline, "PLAY")) { + if (user_action_ok("serial-PLAY")) { + Serial.println("Serial → PLAY"); + do_play_toggle(); + } + } + slen = 0; + } + } else if (slen + 1 < sizeof(sline) && c >= 32 && c < 127) { + sline[slen++] = c; + } else { + slen = 0; /* binary / overflow → drop */ } } diff --git a/src/touch.cpp b/src/touch.cpp index 2d595cd..4765b97 100644 --- a/src/touch.cpp +++ b/src/touch.cpp @@ -129,10 +129,11 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { #include -/* Hysteresis: idle z≈5–15; light taps often z≈45–80. */ -static constexpr int16_t Z_DOWN = 48; -static constexpr int16_t Z_UP = 25; +/* Idle z≈5–15. Demand a real finger — EMI/SD spikes are short & weak. */ +static constexpr int16_t Z_DOWN = 70; +static constexpr int16_t Z_UP = 35; static constexpr int16_t RAW_MIN = 150; +static constexpr uint8_t DOWN_NEED = 5; /* ≥5 polls (~25ms) before latch */ static CYD28_TouchR ts(SCREEN_WIDTH, SCREEN_HEIGHT); static bool s_down = false; @@ -143,8 +144,8 @@ void touch_init() { pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); ts.begin(); - ts.setThreshold(45); /* XY update only when clearly above idle */ - Serial.println("Touch: CYD28 SoftSPI + hysteresis (SD-safe)"); + ts.setThreshold(55); + Serial.println("Touch: SoftSPI strict (5-sample latch, z>=70)"); } void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { @@ -160,19 +161,17 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { s_down_streak = 0; } } else if (p.z >= Z_DOWN && raw_ok) { - /* Single solid sample OK — SoftSPI taps are often 1 poll wide. - * Cooldown in main.cpp blocks EMI auto-toggle. */ - s_down = true; - s_down_streak = 1; + if (s_down_streak < 255) s_down_streak++; + if (s_down_streak >= DOWN_NEED) s_down = true; } else { s_down_streak = 0; } static uint32_t last_log = 0; - if (millis() - last_log > (s_down ? 200u : 800u)) { + if (millis() - last_log > (s_down ? 250u : 1000u)) { 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); + Serial.printf("Touch raw(%d,%d) z=%d down=%d streak=%u\n", (int)p.x, + (int)p.y, (int)p.z, (int)s_down, (unsigned)s_down_streak); } if (!s_down || !raw_ok) { @@ -182,8 +181,7 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { return; } - /* Both axes inverted on this CYD + ROTATION_270 panel: - * physical bottom → high UI Y, physical left → low UI X. */ + /* Both axes inverted on this CYD + ROTATION_270 panel. */ int16_t ux = (int16_t)map(p.x, 3700, 200, 0, UI_HOR_RES - 1); int16_t uy = (int16_t)map(p.y, 3800, 240, 0, UI_VER_RES - 1); s_ux = clampi(ux, 0, UI_HOR_RES - 1);