diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1d879..018b7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## 2026-10-17 — Touch-only firmware +## 2026-10-17 — Restore TF + speaker (touch map frozen) -- Removed TF/SD and speaker/audio code entirely. -- UI: TL/TR/BL/BR/CENTER targets + red cursor for alignment. +- Re-add SD info UI and speaker ON/OFF + Play Music. +- Default volume 20% via LEDC duty (GPIO26). +- Touch mapping left unchanged (diagonal BL/TR fix). + +## 2026-10-17 — Touch-only then calibrated + +- Stripped to touch test; fixed BL/TR with diagonal mirror (not 180°). diff --git a/firmware/cyd/VERSION.txt b/firmware/cyd/VERSION.txt index fa31bdc..f819a68 100644 --- a/firmware/cyd/VERSION.txt +++ b/firmware/cyd/VERSION.txt @@ -1,5 +1,5 @@ name: CYD TF card + touch speaker (cyd) -git: 4f41621 +git: 7b5f984 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 2D2D76255F85D2EB2ABBF0E7B98F32AC39E0692E1BAC99FF79DFF3F0B211CB47 + firmware.bin D2A9F0E360CCA1ED115E042A3127D6D9C990E84B12CD972D713A46AF8A4D4A46 partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1 diff --git a/firmware/cyd/firmware.bin b/firmware/cyd/firmware.bin index 7b4d47b..1d4efe0 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 new file mode 100644 index 0000000..1a78116 --- /dev/null +++ b/include/audio_test.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +void audio_init(); + +void audio_set_enabled(bool on); +bool audio_is_enabled(); + +// 音量 0–100,默认 20 +void audio_set_volume(uint8_t percent); +uint8_t audio_get_volume(); + +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 d538a23..44adc4c 100644 --- a/include/pins.h +++ b/include/pins.h @@ -1,16 +1,23 @@ #pragma once -// CYD ESP32-2432S028R — 仅屏幕 + 触摸 +// CYD ESP32-2432S028R #define TFT_BL_PIN 21 +// TF / microSD (VSPI;触摸用 SoftSPI,不抢总线) +#define SD_MISO 19 +#define SD_MOSI 23 +#define SD_SCK 18 +#define SD_CS 5 + +#define SPEAKER_PIN 26 + #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 -// 物理竖屏;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 new file mode 100644 index 0000000..6b6e755 --- /dev/null +++ b/include/sd_info.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +struct SdCardInfo { + bool mounted; + bool present; + const char *type; + const char *filesystem; + 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 9bb4f00..204e7cf 100644 --- a/include/ui.h +++ b/include/ui.h @@ -1,6 +1,9 @@ #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 b6f0d8e..2d1324a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,4 +1,4 @@ -; CYD ESP32-2432S028 — 屏幕 + 触控测试 +; CYD ESP32-2432S028 — TF + 触控喇叭 + LVGL [env:cyd] platform = espressif32 board = esp32dev @@ -43,8 +43,10 @@ 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 new file mode 100644 index 0000000..fad2c7c --- /dev/null +++ b/src/audio_test.cpp @@ -0,0 +1,159 @@ +#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 uint8_t g_volume = 20; // 默认 20% +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; + +static constexpr int k_ledc_ch = 0; +static constexpr int k_ledc_res = 8; + +struct Note { + uint16_t freq; + uint16_t ms; +}; + +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() { + ledcWrite(k_ledc_ch, 0); + pinMode(SPEAKER_PIN, OUTPUT); + digitalWrite(SPEAKER_PIN, LOW); +} + +static void speaker_tone(uint16_t freq) { + if (freq == 0 || g_volume == 0) { + speaker_off_pin(); + return; + } + ledcSetup(k_ledc_ch, freq, k_ledc_res); + ledcAttachPin(SPEAKER_PIN, k_ledc_ch); + // 方波占空比随音量;20% → duty≈25/255 + uint32_t duty = (127u * g_volume) / 100u; + if (duty < 1) { + duty = 1; + } + ledcWrite(k_ledc_ch, duty); +} + +void audio_init() { + pinMode(SPEAKER_PIN, OUTPUT); + digitalWrite(SPEAKER_PIN, LOW); + g_enabled = false; + g_playing = false; + g_volume = 20; + g_status = "Speaker OFF"; + Serial.printf("Speaker GPIO%d vol=%u%%\n", SPEAKER_PIN, (unsigned)g_volume); +} + +void audio_set_volume(uint8_t percent) { + if (percent > 100) { + percent = 100; + } + g_volume = percent; + Serial.printf("Volume %u%%\n", (unsigned)g_volume); +} + +uint8_t audio_get_volume() { + return g_volume; +} + +void audio_set_enabled(bool on) { + g_enabled = on; + if (!on) { + audio_stop(); + g_status = "Speaker OFF"; + } else { + g_status = "Speaker ON"; + speaker_tone(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; + } + + 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]; + speaker_tone(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 27b0402..f45d0ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,9 @@ #include #include +#include "audio_test.h" #include "pins.h" +#include "sd_info.h" #include "touch.h" #include "ui.h" @@ -9,19 +11,38 @@ 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_dbg_ms = 0; +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); +} void setup() { Serial.begin(115200); delay(200); Serial.println(); - Serial.printf("LVGL %d.%d.%d | touch test only\n", lv_version_major(), + Serial.printf("LVGL %d.%d.%d | CYD TF + Speaker\n", lv_version_major(), lv_version_minor(), lv_version_patch()); pinMode(TFT_BL_PIN, OUTPUT); digitalWrite(TFT_BL_PIN, HIGH); + // 先触摸再 SD:触控 SoftSPI 与 SD VSPI 分脚,映射勿改 touch.cpp touch_init(); + audio_init(); + lv_init(); lv_display_t *disp = @@ -34,15 +55,30 @@ 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(); const uint32_t now = millis(); - if (now - last_dbg_ms >= 50) { - last_dbg_ms = now; + 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(); 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 new file mode 100644 index 0000000..4a69ec2 --- /dev/null +++ b/src/sd_info.cpp @@ -0,0 +1,136 @@ +#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); + 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/ui.cpp b/src/ui.cpp index bb3aa88..42a3135 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -1,85 +1,186 @@ #include "ui.h" +#include "audio_test.h" #include "pins.h" #include #include -static lv_obj_t *lbl_info; -static lv_obj_t *lbl_hit; +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 *touch_cursor; -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 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 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_speaker_clicked(lv_event_t *e) { + LV_UNUSED(e); + audio_set_enabled(!audio_is_enabled()); + ui_refresh_audio_buttons(); +} - 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; +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) { + char buf[28]; + snprintf(buf, sizeof(buf), "%s %u%%", + audio_is_enabled() ? "Speaker ON" : "Speaker OFF", + (unsigned)audio_get_volume()); + lv_label_set_text(lbl_speaker, buf); + } + 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()); + } } void ui_create() { lv_obj_t *scr = lv_screen_active(); - lv_obj_set_style_bg_color(scr, lv_color_hex(0x102027), 0); + lv_obj_set_style_bg_color(scr, lv_color_hex(0x0D1B2A), 0); lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); - lv_obj_clear_flag(scr, 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 *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); - 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); + 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_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); + 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); + + 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, 18, 18); + lv_obj_set_size(touch_cursor, 14, 14); 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); - lv_obj_set_style_border_width(touch_cursor, 2, 0); + lv_obj_set_style_border_width(touch_cursor, 1, 0); lv_obj_set_style_border_color(touch_cursor, lv_color_hex(0xFFFFFF), 0); 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_info) { + if (!lbl_touch_dbg) { return; } if (!down) { - lv_label_set_text(lbl_info, "Touch: up"); + lv_label_set_text(lbl_touch_dbg, "Touch: up"); if (touch_cursor) { lv_obj_add_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN); } @@ -87,10 +188,39 @@ void ui_set_touch_debug(int16_t lx, int16_t ly, bool down) { } char buf[40]; snprintf(buf, sizeof(buf), "Touch %d,%d", (int)lx, (int)ly); - lv_label_set_text(lbl_info, buf); + lv_label_set_text(lbl_touch_dbg, buf); if (touch_cursor) { lv_obj_clear_flag(touch_cursor, LV_OBJ_FLAG_HIDDEN); - lv_obj_set_pos(touch_cursor, lx - 9, ly - 9); + lv_obj_set_pos(touch_cursor, lx - 7, ly - 7); 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(); +}