diff --git a/CHANGELOG.md b/CHANGELOG.md index f8ae06d..8b1d879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,70 +1,6 @@ # Changelog -## 2026-10-17 — Sunton official touch + log offset +## 2026-10-17 — Touch-only firmware -- Use esp32-2432S028R.json touch flags: X=0xD1 Y=0x91, linear/4096, MIRROR_X. -- Keep landscape 240x320+ROTATION_270. -- Serial/user: red cursor lower-left of finger → UI corr (+45,-45), inverse-feed LVGL. - -## 2026-10-17 — Restore landscape + RNT touch map - -- Revert create(320x240) 花屏; RNT map superseded by Sunton official above. - -## 2026-10-17 — Touch = draw coords (CYD LVGL9 official) [reverted] - -- create(320x240) without LVGL rot caused 花屏 on this panel; superseded. - -## 2026-10-17 — SoftSPI touch + LVGL inverse (fix mux) - - -- VSPI remux broke SD mount and froze touch at raw(0,4095). -- Touch back to SoftSPI on 25/32/39/33; SD keeps exclusive VSPI 18/19/23/5. -- Same temperature UI map, then invert LVGL ROTATION_270 for button hits. - -## 2026-10-17 — Touch = temperature driver + LVGL inverse - -- Attempted XPT2046 HW SPI + VSPI pin mux; superseded (broke SD/touch). - -## 2026-10-17 — Touch calib from COM3 logs - -- Logged live `Touch phys/logic` on COM3; inverted physical X so bottom matches UI. -- On-screen `Touch L:x,y` debug; larger 150×60 buttons. -- Verified Speaker ON/OFF via serial during capture. - -## 2026-10-17 — Fix touch Y (buttons dead zone) - -- Root cause: feeding already-landscape 320x240 points while LVGL also applies - ROTATION_270 (double transform) → only upper ~1/3 hit-tested correctly. -- Fix: report physical 240x320 coords; let LVGL rotate (matches `lv_indev.c`). - -## 2026-10-17 — Restore LVGL touch - -- SoftSPI + LVGL `lv_indev` (with `lv_indev_set_display`). -- Larger clickable speaker/play buttons. - -## 2026-10-17 — Fix touch offset - -- SoftSPI touch now uses the same rotation=2 transform + map as `temperature`. -- (Pressure-only path was too strict; superseded by restore above.) - -## 2026-10-17 — Prebuilt firmware folder - -- Add `firmware/cyd/` with bin + `flash.ps1` / `flash.bat` (same pattern as temperature). -- Add `firmware/export_from_build.ps1` to refresh archived binaries after builds. -- Track `firmware/**/*.bin` in git; document flash-without-compile in README. - -## 2026-10-17 — Touch speaker control + music - -- SoftSPI XPT2046 touch (GPIO 32/39/25/33/36), coexists with SD. -- LVGL buttons: **Speaker ON/OFF**, **Play Music / Stop**. -- Non-blocking built-in melody (Twinkle Twinkle); no auto beeps. -- Docs updated. - -## 2026-10-17 — Speaker audio test - -- Add GPIO26 speaker path (onboard SC8002B amp / P4 connector). -- (Superseded by touch control above.) - -## 2026-10-17 — Initial - -- CYD TF/microSD mount (VSPI) + LVGL storage info UI. +- Removed TF/SD and speaker/audio code entirely. +- UI: TL/TR/BL/BR/CENTER targets + red cursor for alignment. diff --git a/README.md b/README.md index f1c55a9..a3a9e75 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,15 @@ -# CYD TF 卡信息 + 触屏扬声器(LVGL) +# CYD 触控测试(仅屏幕) -在 **ESP32-2432S028(CYD)** 上: +ESP32-2432S028R:LVGL 横屏 + SoftSPI 触摸。无 TF、无喇叭。 -- 挂载 TF/microSD,LVGL 显示卡信息 -- **触屏按钮**开关扬声器、播放内置音乐 - -## 硬件 - -| 功能 | GPIO | -|------|------| -| TFT MOSI / MISO / SCLK | 13 / 12 / 14 | -| TFT CS / DC / BL | 15 / 2 / 21 | -| **SD MOSI / MISO / SCK / CS** | **23 / 19 / 18 / 5** | -| **Speaker(SC8002B → P4)** | **26** | -| **Touch MOSI / MISO / CLK / CS / IRQ** | **32 / 39 / 25 / 33 / 36** | - -触摸使用 SoftSPI bitbang,不占用 VSPI,可与 SD 同时工作。喇叭接 **SPEAK / P4**。 - -## 触屏操作 - -| 按钮 | 作用 | -|------|------| -| **Speaker ON / OFF** | 打开/关闭扬声器(关=静音并停止播放) | -| **Play Music / Stop** | 播放内置旋律《小星星》;播放中变为 Stop | - -建议顺序:先点 **Speaker ON**(会有短提示音)→ 再点 **Play Music**。 - -## 屏幕信息 - -- Status / Type / Format / Capacity(TF) -- Audio:当前音频状态 -- 用量进度条 - -## 预编译固件(随时刷,不必编译) - -与 `temperature` 工程相同:归档在 [`firmware/cyd/`](firmware/cyd/)(含 bootloader / partitions / app): - -```powershell -cd firmware\cyd -.\flash.ps1 -# 或 .\flash.ps1 -Port COM3 -``` - -说明见 [`firmware/README.md`](firmware/README.md)。源码更新后重新打包: - -```powershell -.\firmware\export_from_build.ps1 -# 已编译过、只复制: -.\firmware\export_from_build.ps1 -SkipBuild -``` - -## 从源码编译烧录 +## 构建 / 烧录 ```bash -pio run -e cyd -t upload -pio device monitor +pio run -e cyd -t upload --upload-port COM3 ``` -## 注意事项 +或用 `firmware/cyd/flash.bat`。 -- TF 需 **FAT/FAT32**。 -- 触屏校准与 `temperature` 工程一致(`setRotation(2)` + map);仍偏可改 `src/touch.cpp` 的 `map(...)` 边界。 -- 变更请更新 `CHANGELOG.md`、必要时重新 `export_from_build.ps1`,并提交本地 git。 +## 界面 -## 版本 - -见 [CHANGELOG.md](CHANGELOG.md)。 +四角 + 中心按钮;红点跟随触点。点中按钮变绿并串口打印 `HIT …`。 diff --git a/firmware/cyd/VERSION.txt b/firmware/cyd/VERSION.txt index 4b72310..44dd075 100644 --- a/firmware/cyd/VERSION.txt +++ b/firmware/cyd/VERSION.txt @@ -1,5 +1,5 @@ name: CYD TF card + touch speaker (cyd) -git: 8a29196 +git: 8705827 date: 2026-10-17 board: ESP32-2432S028R (ILI9341) features: TF/SD info, SoftSPI touch, GPIO26 speaker @@ -12,5 +12,5 @@ offsets: sha256: bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8 boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0 - firmware.bin 71D6F25E57435D8D3EAE086122BD459C0ECEFA6CBEB78065C653F580EC787545 + firmware.bin DB8EEBFEF7FF0995BBF7AF3D525CDE2560060CC008C2656094CF28C6DD7FEEAB partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1 diff --git a/firmware/cyd/firmware.bin b/firmware/cyd/firmware.bin index a0e38be..b0bc9a1 100644 Binary files a/firmware/cyd/firmware.bin and b/firmware/cyd/firmware.bin differ diff --git a/include/audio_test.h b/include/audio_test.h deleted file mode 100644 index cff609d..0000000 --- a/include/audio_test.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -void audio_init(); - -// 扬声器总开关(关则静音并停止播放) -void audio_set_enabled(bool on); -bool audio_is_enabled(); - -// 开始/停止内置音乐(非阻塞,在 audio_tick 推进) -void audio_play_music(); -void audio_stop(); -bool audio_is_playing(); - -// 主循环调用 -void audio_tick(); - -const char *audio_last_status(); diff --git a/include/pins.h b/include/pins.h index 2d84f1e..d538a23 100644 --- a/include/pins.h +++ b/include/pins.h @@ -1,27 +1,16 @@ #pragma once -// CYD ESP32-2432S028R 引脚 +// CYD ESP32-2432S028R — 仅屏幕 + 触摸 -// TFT (HSPI / TFT_eSPI 已在 platformio.ini 配置) #define TFT_BL_PIN 21 -// TF / microSD (独立 VSPI) -#define SD_MISO 19 -#define SD_MOSI 23 -#define SD_SCK 18 -#define SD_CS 5 - -// 板载 SC8002B 功放 → P4 喇叭座(DAC2) -#define SPEAKER_PIN 26 - -// 触摸 XPT2046(SoftSPI 专用脚,VSPI 留给 SD;勿用 TOUCH_CS,会与 TFT_eSPI 冲突) #define CYD_TOUCH_IRQ 36 #define CYD_TOUCH_MOSI 32 #define CYD_TOUCH_MISO 39 #define CYD_TOUCH_CLK 25 #define CYD_TOUCH_CS 33 -// 与 Random Nerd Tutorials / temperature 一致:物理竖屏 + LVGL 270 得横屏 +// 物理竖屏;LVGL ROTATION_270 → 横屏 UI #define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 320 #define UI_HOR_RES 320 diff --git a/include/sd_info.h b/include/sd_info.h deleted file mode 100644 index af6e7d0..0000000 --- a/include/sd_info.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -struct SdCardInfo { - bool mounted; - bool present; - const char *type; // MMC / SDSC / SDHC / UNKNOWN / NONE - const char *filesystem; // FAT12 / FAT16 / FAT32 / exFAT / FAT / — - uint64_t card_bytes; // 卡物理容量 - uint64_t total_bytes; // 分区总容量 - uint64_t used_bytes; - uint64_t free_bytes; - uint32_t root_files; // 根目录条目数 - String error; // 失败原因 -}; - -bool sd_init(); -void sd_deinit(); -SdCardInfo sd_read_info(); -String format_bytes(uint64_t bytes); diff --git a/include/ui.h b/include/ui.h index db66073..9bb4f00 100644 --- a/include/ui.h +++ b/include/ui.h @@ -1,8 +1,6 @@ #pragma once -#include "sd_info.h" +#include void ui_create(); -void ui_update(const SdCardInfo &info); -void ui_refresh_audio_buttons(); void ui_set_touch_debug(int16_t lx, int16_t ly, bool down); diff --git a/platformio.ini b/platformio.ini index 5d72fc5..b6f0d8e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,4 +1,4 @@ -; CYD ESP32-2432S028 — TF 卡信息 + LVGL 显示 +; CYD ESP32-2432S028 — 屏幕 + 触控测试 [env:cyd] platform = espressif32 board = esp32dev @@ -35,7 +35,6 @@ build_flags = -DSMOOTH_FONT=1 -DSPI_FREQUENCY=40000000 -DSPI_READ_FREQUENCY=20000000 - ; lv_conf.h 放在 .pio/libdeps/cyd/lv_conf.h(见 extra_script) -DLV_CONF_SKIP -DLV_COLOR_DEPTH=16 -DLV_MEM_SIZE=49152 @@ -44,10 +43,8 @@ build_flags = -DLV_FONT_MONTSERRAT_18=1 -DLV_FONT_MONTSERRAT_20=1 -DLV_USE_TFT_ESPI=1 - -DLV_USE_FLEX=1 -DLV_USE_BUTTON=1 -DLV_USE_LABEL=1 - -DLV_USE_BAR=1 -I${PROJECT_DIR}/include extra_scripts = pre:copy_lv_conf.py diff --git a/src/audio_test.cpp b/src/audio_test.cpp deleted file mode 100644 index 9569b63..0000000 --- a/src/audio_test.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "audio_test.h" -#include "pins.h" - -static const char *g_status = "Speaker OFF"; -static bool g_enabled = false; -static bool g_playing = false; -static size_t g_note_i = 0; -static uint32_t g_until_ms = 0; - -enum Phase : uint8_t { kStartNote, kWaitNote, kWaitGap }; -static Phase g_phase = kStartNote; - -struct Note { - uint16_t freq; - uint16_t ms; -}; - -// Twinkle Twinkle Little Star -static const Note k_music[] = { - {262, 280}, {262, 280}, {392, 280}, {392, 280}, {440, 280}, {440, 280}, {392, 520}, - {349, 280}, {349, 280}, {330, 280}, {330, 280}, {294, 280}, {294, 280}, {262, 520}, - {392, 280}, {392, 280}, {349, 280}, {349, 280}, {330, 280}, {330, 280}, {294, 520}, - {392, 280}, {392, 280}, {349, 280}, {349, 280}, {330, 280}, {330, 280}, {294, 520}, - {262, 280}, {262, 280}, {392, 280}, {392, 280}, {440, 280}, {440, 280}, {392, 520}, - {349, 280}, {349, 280}, {330, 280}, {330, 280}, {294, 280}, {294, 280}, {262, 700}, -}; - -static constexpr size_t k_music_len = sizeof(k_music) / sizeof(k_music[0]); - -static void speaker_off_pin() { - noTone(SPEAKER_PIN); - digitalWrite(SPEAKER_PIN, LOW); -} - -void audio_init() { - pinMode(SPEAKER_PIN, OUTPUT); - speaker_off_pin(); - g_enabled = false; - g_playing = false; - g_status = "Speaker OFF"; - Serial.printf("Speaker init GPIO%d (touch-controlled)\n", SPEAKER_PIN); -} - -void audio_set_enabled(bool on) { - g_enabled = on; - if (!on) { - audio_stop(); - g_status = "Speaker OFF"; - } else { - g_status = "Speaker ON"; - tone(SPEAKER_PIN, 1000); - delay(80); - speaker_off_pin(); - } - Serial.printf("Speaker %s\n", on ? "ON" : "OFF"); -} - -bool audio_is_enabled() { - return g_enabled; -} - -void audio_stop() { - g_playing = false; - g_note_i = 0; - g_phase = kStartNote; - speaker_off_pin(); - if (g_enabled) { - g_status = "Speaker ON"; - } -} - -bool audio_is_playing() { - return g_playing; -} - -void audio_play_music() { - if (!g_enabled) { - g_status = "Turn ON first"; - Serial.println("Audio: blocked (speaker off)"); - return; - } - g_playing = true; - g_note_i = 0; - g_phase = kStartNote; - g_until_ms = 0; - g_status = "Playing..."; - Serial.println("Audio: play music"); -} - -void audio_tick() { - if (!g_playing || !g_enabled) { - return; - } - - const uint32_t now = millis(); - if (now < g_until_ms) { - return; - } - - if (g_phase == kWaitNote) { - speaker_off_pin(); - g_phase = kWaitGap; - g_until_ms = now + 35; - g_note_i++; - return; - } - - if (g_phase == kWaitGap) { - g_phase = kStartNote; - } - - // kStartNote - if (g_note_i >= k_music_len) { - audio_stop(); - g_status = "Music done"; - Serial.println("Audio: music done"); - return; - } - - const Note &n = k_music[g_note_i]; - if (n.freq == 0) { - speaker_off_pin(); - } else { - tone(SPEAKER_PIN, n.freq); - } - g_until_ms = now + n.ms; - g_phase = kWaitNote; -} - -const char *audio_last_status() { - return g_status; -} diff --git a/src/main.cpp b/src/main.cpp index e9e61d6..27b0402 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,7 @@ #include -#include #include -#include "audio_test.h" #include "pins.h" -#include "sd_info.h" #include "touch.h" #include "ui.h" @@ -12,41 +9,21 @@ static constexpr uint32_t DRAW_BUF_SIZE = (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8)); static uint32_t draw_buf[DRAW_BUF_SIZE / 4]; -static uint32_t last_refresh_ms = 0; -static uint32_t last_btn_ui_ms = 0; -static constexpr uint32_t REFRESH_MS = 3000; - -static void refresh_sd_ui() { - SdCardInfo info = sd_read_info(); - - Serial.println("---- TF Card ----"); - if (!info.mounted) { - Serial.printf("Error: %s\n", info.error.c_str()); - } else { - Serial.printf("Type: %s FS: %s Cap: %s\n", info.type, info.filesystem, - format_bytes(info.card_bytes).c_str()); - } - - ui_update(info); -} +static uint32_t last_dbg_ms = 0; void setup() { Serial.begin(115200); delay(200); Serial.println(); - Serial.printf("LVGL %d.%d.%d | CYD TF + Touch Speaker\n", lv_version_major(), + Serial.printf("LVGL %d.%d.%d | touch test only\n", lv_version_major(), lv_version_minor(), lv_version_patch()); pinMode(TFT_BL_PIN, OUTPUT); digitalWrite(TFT_BL_PIN, HIGH); - audio_init(); touch_init(); - lv_init(); - // Sunton 官方:240x320 + ROTATION_270 → 横屏(勿 create 320x240,会花屏) - // 触摸见 touch.cpp(官方 mirror_x + 日志补偿) lv_display_t *disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf)); lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270); @@ -57,31 +34,15 @@ void setup() { lv_indev_set_display(indev, disp); ui_create(); - - if (!sd_init()) { - Serial.println("SD mount failed at boot"); - } - - refresh_sd_ui(); - last_refresh_ms = millis(); - last_btn_ui_ms = millis(); } void loop() { lv_timer_handler(); lv_tick_inc(5); - audio_tick(); - uint32_t now = millis(); - if (now - last_refresh_ms >= REFRESH_MS) { - last_refresh_ms = now; - refresh_sd_ui(); - } - - // 播放过程中刷新按钮文字;同步触点调试 - if (now - last_btn_ui_ms >= 200) { - last_btn_ui_ms = now; - ui_refresh_audio_buttons(); + const uint32_t now = millis(); + if (now - last_dbg_ms >= 50) { + last_dbg_ms = now; ui_set_touch_debug(g_dbg_logic_x, g_dbg_logic_y, g_dbg_touch_down); } diff --git a/src/sd_info.cpp b/src/sd_info.cpp deleted file mode 100644 index b5e1394..0000000 --- a/src/sd_info.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "sd_info.h" -#include "pins.h" - -#include -#include -#include - -static SPIClass sd_spi(VSPI); -static bool sd_ready = false; - -static const char *card_type_name(uint8_t t) { - switch (t) { - case CARD_MMC: return "MMC"; - case CARD_SD: return "SDSC"; - case CARD_SDHC: return "SDHC"; - case CARD_NONE: return "NONE"; - default: return "UNKNOWN"; - } -} - -static const char *detect_filesystem() { - File root = SD.open("/"); - if (!root) { - return "—"; - } - root.close(); - - uint64_t total = SD.totalBytes(); - if (total == 0) { - return "FAT"; - } - if (total > 32ULL * 1024ULL * 1024ULL) { - return "FAT32"; - } - if (total > 2ULL * 1024ULL * 1024ULL) { - return "FAT16"; - } - return "FAT12/16"; -} - -static uint32_t count_root_entries() { - File root = SD.open("/"); - if (!root || !root.isDirectory()) { - return 0; - } - uint32_t n = 0; - File f = root.openNextFile(); - while (f) { - n++; - f = root.openNextFile(); - } - root.close(); - return n; -} - -bool sd_init() { - if (sd_ready) { - return true; - } - - pinMode(SD_CS, OUTPUT); - digitalWrite(SD_CS, HIGH); - // 触摸 SoftSPI CS 拉高,避免干扰(脚位不同,但防误选) - pinMode(CYD_TOUCH_CS, OUTPUT); - digitalWrite(CYD_TOUCH_CS, HIGH); - - sd_spi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); - - if (!SD.begin(SD_CS, sd_spi, 25000000)) { - sd_ready = false; - return false; - } - - sd_ready = true; - return true; -} - -void sd_deinit() { - if (sd_ready) { - SD.end(); - sd_ready = false; - } -} - -SdCardInfo sd_read_info() { - SdCardInfo info{}; - info.mounted = false; - info.present = false; - info.type = "NONE"; - info.filesystem = "—"; - info.card_bytes = 0; - info.total_bytes = 0; - info.used_bytes = 0; - info.free_bytes = 0; - info.root_files = 0; - - if (!sd_ready && !sd_init()) { - info.error = "Mount failed"; - return info; - } - - uint8_t type = SD.cardType(); - info.type = card_type_name(type); - - if (type == CARD_NONE) { - info.error = "No card"; - info.present = false; - return info; - } - - info.present = true; - info.mounted = true; - info.card_bytes = SD.cardSize(); - info.total_bytes = SD.totalBytes(); - info.used_bytes = SD.usedBytes(); - info.free_bytes = - (info.total_bytes > info.used_bytes) ? (info.total_bytes - info.used_bytes) : 0; - info.filesystem = detect_filesystem(); - info.root_files = count_root_entries(); - info.error = ""; - return info; -} - -String format_bytes(uint64_t bytes) { - const double b = static_cast(bytes); - char buf[48]; - if (bytes >= (1ULL << 30)) { - snprintf(buf, sizeof(buf), "%.2f GB", b / (1024.0 * 1024.0 * 1024.0)); - } else if (bytes >= (1ULL << 20)) { - snprintf(buf, sizeof(buf), "%.2f MB", b / (1024.0 * 1024.0)); - } else if (bytes >= (1ULL << 10)) { - snprintf(buf, sizeof(buf), "%.2f KB", b / 1024.0); - } else { - snprintf(buf, sizeof(buf), "%llu B", static_cast(bytes)); - } - return String(buf); -} diff --git a/src/touch.cpp b/src/touch.cpp index 0140ab4..6cb0295 100644 --- a/src/touch.cpp +++ b/src/touch.cpp @@ -4,11 +4,8 @@ #include /** - * SoftSPI XPT2046 — Sunton esp32-2432S028R 官方通道 + 线性映射 - * X CMD=0xD1, Y CMD=0x91, scale /4096 → 240x320 - * SoftSPI 与官方 HW SPI 极性不同:不做 MIRROR_X(日志证明 mirror 会把 UI Y 压到顶部) - * 显示:240x320 + LV_DISPLAY_ROTATION_270;触点直接喂物理坐标,由 LVGL 旋转 - * 板级:https://github.com/rzeldent/platformio-espressif32-sunton + * SoftSPI XPT2046 → 物理 240x320 → 交给 LVGL ROTATION_270 + * RNT 标定区间 + rot2;红点用变换后 UI 坐标 */ int16_t g_dbg_phys_x = -1; @@ -59,15 +56,14 @@ void touch_init() { pinMode(CYD_TOUCH_IRQ, INPUT); digitalWrite(CYD_TOUCH_CS, HIGH); digitalWrite(CYD_TOUCH_CLK, LOW); - Serial.println("Touch: Sunton 0xD1/0x91 linear, SoftSPI no-mirror"); + Serial.println("Touch: SoftSPI rot2 map 240x320"); } void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { - (void)xpt_read(0xD1); - uint16_t adc_x = xpt_read(0xD1); - uint16_t adc_y = xpt_read(0x91); - adc_x = (uint16_t)((adc_x + xpt_read(0xD1)) / 2); - adc_y = (uint16_t)((adc_y + xpt_read(0x91)) / 2); + uint16_t adc_x = xpt_read(0x90); + uint16_t adc_y = xpt_read(0xD0); + adc_x = (uint16_t)((adc_x + xpt_read(0x90)) / 2); + adc_y = (uint16_t)((adc_y + xpt_read(0xD0)) / 2); const bool irq = (digitalRead(CYD_TOUCH_IRQ) == LOW); const bool in_range = @@ -79,40 +75,41 @@ void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { return; } - int16_t px = (int16_t)(((uint32_t)adc_x * SCREEN_WIDTH) / 4096); - int16_t py = (int16_t)(((uint32_t)adc_y * SCREEN_HEIGHT) / 4096); + // setRotation(2) + const int16_t p_x = (int16_t)adc_x; + const int16_t p_y = (int16_t)(4095 - adc_y); - if (px < 0) { - px = 0; + int16_t x = map(p_x, 200, 3700, 1, SCREEN_WIDTH); + int16_t y = map(p_y, 240, 3800, 1, SCREEN_HEIGHT); + + if (x < 0) { + x = 0; } - if (py < 0) { - py = 0; + if (y < 0) { + y = 0; } - if (px >= SCREEN_WIDTH) { - px = SCREEN_WIDTH - 1; + if (x >= SCREEN_WIDTH) { + x = SCREEN_WIDTH - 1; } - if (py >= SCREEN_HEIGHT) { - py = SCREEN_HEIGHT - 1; + if (y >= SCREEN_HEIGHT) { + y = SCREEN_HEIGHT - 1; } - // LVGL 270 后预估 UI(红点);触点仍喂物理坐标 - const int16_t ui_x = py; - const int16_t ui_y = (int16_t)((SCREEN_WIDTH - 1) - px); - - g_dbg_phys_x = px; - g_dbg_phys_y = py; - g_dbg_logic_x = ui_x; - g_dbg_logic_y = ui_y; + // LVGL 270 → UI + g_dbg_phys_x = x; + g_dbg_phys_y = y; + g_dbg_logic_x = y; + g_dbg_logic_y = (int16_t)((SCREEN_WIDTH - 1) - x); g_dbg_touch_down = true; static uint32_t last_log = 0; - if (millis() - last_log > 100) { + if (millis() - last_log > 80) { last_log = millis(); - Serial.printf("Touch adc(%u,%u) phys(%d,%d) UI~(%d,%d)\n", adc_x, adc_y, px, - py, ui_x, ui_y); + Serial.printf("Touch phys(%d,%d) UI(%d,%d)\n", x, y, g_dbg_logic_x, + g_dbg_logic_y); } - data->point.x = px; - data->point.y = py; + data->point.x = x; + data->point.y = y; data->state = LV_INDEV_STATE_PRESSED; } diff --git a/src/ui.cpp b/src/ui.cpp index b2bad01..bb3aa88 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -1,164 +1,69 @@ #include "ui.h" -#include "audio_test.h" +#include "pins.h" +#include #include -static lv_obj_t *lbl_status; -static lv_obj_t *lbl_type; -static lv_obj_t *lbl_fs; -static lv_obj_t *lbl_capacity; -static lv_obj_t *lbl_audio; -static lv_obj_t *bar_usage; -static lv_obj_t *btn_speaker; -static lv_obj_t *btn_play; -static lv_obj_t *lbl_speaker; -static lv_obj_t *lbl_play; -static lv_obj_t *lbl_touch_dbg; +static lv_obj_t *lbl_info; +static lv_obj_t *lbl_hit; static lv_obj_t *touch_cursor; -static lv_obj_t *make_row(lv_obj_t *parent, const char *title, lv_obj_t **value_out) { - lv_obj_t *row = lv_obj_create(parent); - lv_obj_set_width(row, LV_PCT(100)); - lv_obj_set_height(row, LV_SIZE_CONTENT); - lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(row, 0, 0); - lv_obj_set_style_pad_all(row, 1, 0); - lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE); - - lv_obj_t *t = lv_label_create(row); - lv_label_set_text(t, title); - lv_obj_set_style_text_color(t, lv_color_hex(0x90A4AE), 0); - lv_obj_set_style_text_font(t, &lv_font_montserrat_14, 0); - - lv_obj_t *v = lv_label_create(row); - lv_label_set_text(v, "—"); - lv_obj_set_style_text_color(v, lv_color_hex(0xECEFF1), 0); - lv_obj_set_style_text_font(v, &lv_font_montserrat_14, 0); - *value_out = v; - return row; +static void on_target_clicked(lv_event_t *e) { + lv_obj_t *btn = (lv_obj_t *)lv_event_get_target(e); + const char *name = (const char *)lv_event_get_user_data(e); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x2E7D32), 0); + if (lbl_hit && name) { + char buf[48]; + snprintf(buf, sizeof(buf), "HIT: %s", name); + lv_label_set_text(lbl_hit, buf); + } + Serial.printf("HIT %s\n", name ? name : "?"); } -static void on_speaker_clicked(lv_event_t *e) { - LV_UNUSED(e); - audio_set_enabled(!audio_is_enabled()); - ui_refresh_audio_buttons(); -} +static lv_obj_t *make_target(lv_obj_t *parent, const char *name, int x, int y, + int w, int h) { + lv_obj_t *btn = lv_button_create(parent); + lv_obj_set_pos(btn, x, y); + lv_obj_set_size(btn, w, h); + lv_obj_set_style_radius(btn, 6, 0); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x1565C0), 0); + lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_event_cb(btn, on_target_clicked, LV_EVENT_CLICKED, (void *)name); -static void on_play_clicked(lv_event_t *e) { - LV_UNUSED(e); - if (audio_is_playing()) { - audio_stop(); - } else { - audio_play_music(); - } - ui_refresh_audio_buttons(); -} - -void ui_refresh_audio_buttons() { - if (lbl_speaker) { - lv_label_set_text(lbl_speaker, audio_is_enabled() ? "Speaker ON" : "Speaker OFF"); - } - if (btn_speaker) { - lv_obj_set_style_bg_color( - btn_speaker, - audio_is_enabled() ? lv_color_hex(0x2E7D32) : lv_color_hex(0x455A64), 0); - } - - if (lbl_play) { - lv_label_set_text(lbl_play, audio_is_playing() ? "Stop" : "Play Music"); - } - if (btn_play) { - lv_obj_set_style_bg_color( - btn_play, - audio_is_playing() ? lv_color_hex(0xC62828) : lv_color_hex(0x1565C0), 0); - } - - if (lbl_audio) { - lv_label_set_text(lbl_audio, audio_last_status()); - } + lv_obj_t *lbl = lv_label_create(btn); + lv_label_set_text(lbl, name); + lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0); + lv_obj_center(lbl); + return btn; } void ui_create() { lv_obj_t *scr = lv_screen_active(); - lv_obj_set_style_bg_color(scr, lv_color_hex(0x0D1B2A), 0); + lv_obj_set_style_bg_color(scr, lv_color_hex(0x102027), 0); lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); + lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_t *col = lv_obj_create(scr); - lv_obj_set_size(col, LV_PCT(100), LV_PCT(100)); - lv_obj_set_style_bg_opa(col, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(col, 0, 0); - lv_obj_set_style_pad_left(col, 10, 0); - lv_obj_set_style_pad_right(col, 10, 0); - lv_obj_set_style_pad_top(col, 6, 0); - lv_obj_set_style_pad_bottom(col, 6, 0); - lv_obj_set_flex_flow(col, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(col, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - lv_obj_set_style_pad_row(col, 2, 0); - lv_obj_clear_flag(col, LV_OBJ_FLAG_SCROLLABLE); + // 四角 + 中心,专门验触控与绘制是否重合 + make_target(scr, "TL", 8, 8, 90, 56); + make_target(scr, "TR", UI_HOR_RES - 98, 8, 90, 56); + make_target(scr, "BL", 8, UI_VER_RES - 64, 90, 56); + make_target(scr, "BR", UI_HOR_RES - 98, UI_VER_RES - 64, 90, 56); + make_target(scr, "CENTER", UI_HOR_RES / 2 - 60, UI_VER_RES / 2 - 28, 120, 56); - lv_obj_t *title = lv_label_create(col); - lv_label_set_text(title, "TF + Speaker"); - lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0); - lv_obj_set_style_text_color(title, lv_color_hex(0x4FC3F7), 0); + lbl_info = lv_label_create(scr); + lv_label_set_text(lbl_info, "Touch: --"); + lv_obj_set_style_text_color(lbl_info, lv_color_hex(0xFFB74D), 0); + lv_obj_set_style_text_font(lbl_info, &lv_font_montserrat_16, 0); + lv_obj_align(lbl_info, LV_ALIGN_TOP_MID, 0, 72); - make_row(col, "Status", &lbl_status); - make_row(col, "Type", &lbl_type); - make_row(col, "Format", &lbl_fs); - make_row(col, "Capacity", &lbl_capacity); - make_row(col, "Audio", &lbl_audio); + lbl_hit = lv_label_create(scr); + lv_label_set_text(lbl_hit, "Tap a button"); + lv_obj_set_style_text_color(lbl_hit, lv_color_hex(0xECEFF1), 0); + lv_obj_set_style_text_font(lbl_hit, &lv_font_montserrat_16, 0); + lv_obj_align(lbl_hit, LV_ALIGN_BOTTOM_MID, 0, -72); - bar_usage = lv_bar_create(col); - lv_obj_set_width(bar_usage, LV_PCT(100)); - lv_obj_set_height(bar_usage, 10); - lv_bar_set_range(bar_usage, 0, 100); - lv_bar_set_value(bar_usage, 0, LV_ANIM_OFF); - lv_obj_set_style_bg_color(bar_usage, lv_color_hex(0x263238), 0); - lv_obj_set_style_bg_color(bar_usage, lv_color_hex(0x29B6F6), LV_PART_INDICATOR); - - // 底部两个大按钮(加大命中区) - lv_obj_t *row_btns = lv_obj_create(col); - lv_obj_set_width(row_btns, LV_PCT(100)); - lv_obj_set_height(row_btns, 64); - lv_obj_set_style_bg_opa(row_btns, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(row_btns, 0, 0); - lv_obj_set_style_pad_all(row_btns, 0, 0); - lv_obj_set_flex_flow(row_btns, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(row_btns, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - lv_obj_clear_flag(row_btns, LV_OBJ_FLAG_SCROLLABLE); - - btn_speaker = lv_button_create(row_btns); - lv_obj_set_size(btn_speaker, 150, 60); - lv_obj_set_style_radius(btn_speaker, 8, 0); - lv_obj_add_flag(btn_speaker, LV_OBJ_FLAG_CLICKABLE); - lv_obj_add_event_cb(btn_speaker, on_speaker_clicked, LV_EVENT_CLICKED, nullptr); - lbl_speaker = lv_label_create(btn_speaker); - lv_label_set_text(lbl_speaker, "Speaker OFF"); - lv_obj_set_style_text_font(lbl_speaker, &lv_font_montserrat_14, 0); - lv_obj_center(lbl_speaker); - - btn_play = lv_button_create(row_btns); - lv_obj_set_size(btn_play, 150, 60); - lv_obj_set_style_radius(btn_play, 8, 0); - lv_obj_add_flag(btn_play, LV_OBJ_FLAG_CLICKABLE); - lv_obj_add_event_cb(btn_play, on_play_clicked, LV_EVENT_CLICKED, nullptr); - lbl_play = lv_label_create(btn_play); - lv_label_set_text(lbl_play, "Play Music"); - lv_obj_set_style_text_font(lbl_play, &lv_font_montserrat_14, 0); - lv_obj_center(lbl_play); - - lbl_touch_dbg = lv_label_create(col); - lv_label_set_text(lbl_touch_dbg, "Touch: --"); - lv_obj_set_style_text_font(lbl_touch_dbg, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(lbl_touch_dbg, lv_color_hex(0xFFB74D), 0); - - // 触点光标:落在哪画在哪,验证与按钮重合 touch_cursor = lv_obj_create(scr); - lv_obj_set_size(touch_cursor, 16, 16); + lv_obj_set_size(touch_cursor, 18, 18); lv_obj_set_style_radius(touch_cursor, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_bg_color(touch_cursor, lv_color_hex(0xFF5252), 0); lv_obj_set_style_bg_opa(touch_cursor, LV_OPA_COVER, 0); @@ -167,56 +72,25 @@ void ui_create() { lv_obj_add_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_CLICKABLE); lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_SCROLLABLE); - - ui_refresh_audio_buttons(); } void ui_set_touch_debug(int16_t lx, int16_t ly, bool down) { - if (!lbl_touch_dbg) { + if (!lbl_info) { return; } if (!down) { - lv_label_set_text(lbl_touch_dbg, "Touch: up"); + lv_label_set_text(lbl_info, "Touch: up"); if (touch_cursor) { lv_obj_add_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN); } return; } char buf[40]; - snprintf(buf, sizeof(buf), "Touch L:%d,%d", (int)lx, (int)ly); - lv_label_set_text(lbl_touch_dbg, buf); + snprintf(buf, sizeof(buf), "Touch %d,%d", (int)lx, (int)ly); + lv_label_set_text(lbl_info, buf); if (touch_cursor) { lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN); - lv_obj_set_pos(touch_cursor, lx - 8, ly - 8); + lv_obj_set_pos(touch_cursor, lx - 9, ly - 9); lv_obj_move_foreground(touch_cursor); } } - -void ui_update(const SdCardInfo &info) { - if (!info.mounted || !info.present) { - lv_label_set_text(lbl_status, info.error.length() ? info.error.c_str() : "Fail"); - lv_obj_set_style_text_color(lbl_status, lv_color_hex(0xEF5350), 0); - lv_label_set_text(lbl_type, info.type); - lv_label_set_text(lbl_fs, "—"); - lv_label_set_text(lbl_capacity, "—"); - lv_bar_set_value(bar_usage, 0, LV_ANIM_OFF); - } else { - lv_label_set_text(lbl_status, "Mounted OK"); - lv_obj_set_style_text_color(lbl_status, lv_color_hex(0x66BB6A), 0); - lv_label_set_text(lbl_type, info.type); - lv_label_set_text(lbl_fs, info.filesystem); - String cap = format_bytes(info.card_bytes); - lv_label_set_text(lbl_capacity, cap.c_str()); - - int pct = 0; - if (info.total_bytes > 0) { - pct = static_cast((info.used_bytes * 100ULL) / info.total_bytes); - if (pct > 100) { - pct = 100; - } - } - lv_bar_set_value(bar_usage, pct, LV_ANIM_ON); - } - - ui_refresh_audio_buttons(); -}