Gate REC/PLAY to explicit user input only.

Add boot lockout, long-press touch confirm, BOOT debounce, and
line-based serial commands so noise cannot auto-start recording.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 22:24:19 +08:00
parent b8527e9fb8
commit 8f0d1d739b
5 changed files with 113 additions and 56 deletions

View File

@@ -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 防抖、串口整行;禁止无操作自启录音 |

View File

@@ -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

Binary file not shown.

View File

@@ -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 */
}
}

View File

@@ -129,10 +129,11 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) {
#include <CYD28_TouchscreenR.h>
/* 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);