diff --git a/CHANGELOG.md b/CHANGELOG.md index 22ad6cb..8061899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,16 @@ # Changelog +## 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). -- Boot: 3 beeps + short melody; then beep every 12s. -- LVGL row `Speaker` shows last audio status. -- Docs: README speaker section. +- (Superseded by touch control above.) ## 2026-10-17 — Initial diff --git a/README.md b/README.md index e713981..0f0a037 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ -# CYD TF 卡信息显示 + 喇叭测试(LVGL) +# CYD TF 卡信息 + 触屏扬声器(LVGL) -在 **ESP32-2432S028(CYD)** 上挂载 TF/microSD,用 LVGL 显示卡信息;并通过板载功放测试外接喇叭。 +在 **ESP32-2432S028(CYD)** 上: + +- 挂载 TF/microSD,LVGL 显示卡信息 +- **触屏按钮**开关扬声器、播放内置音乐 ## 硬件 @@ -10,30 +13,24 @@ | 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** | -TF 卡使用独立 VSPI;显示使用 TFT_eSPI(HSPI)。喇叭接 CYD 的 **SPEAK / P4** 两针座(极性对调只会相位反,一般可出声)。 +触摸使用 SoftSPI bitbang,不占用 VSPI,可与 SD 同时工作。喇叭接 **SPEAK / P4**。 -本工程未启用触摸,避免与 SD 争用总线。 +## 触屏操作 -## 屏幕显示内容 +| 按钮 | 作用 | +|------|------| +| **Speaker ON / OFF** | 打开/关闭扬声器(关=静音并停止播放) | +| **Play Music / Stop** | 播放内置旋律《小星星》;播放中变为 Stop | -- Status:是否挂载成功 -- Type:MMC / SDSC / SDHC -- Format:FAT16 / FAT32 等(按分区容量推断) -- Capacity / Partition / Used / Free:容量与空间 -- Root files:根目录条目数 -- **Speaker**:最近一次音频测试状态(GPIO26) -- 进度条:已用百分比 +建议顺序:先点 **Speaker ON**(会有短提示音)→ 再点 **Play Music**。 -每 3 秒刷新 SD;串口 115200 同步打印。 +## 屏幕信息 -## 喇叭测试 - -- **开机**:三声蜂鸣 + 一小段旋律 -- **之后**:约每 12 秒再播一段三声蜂鸣 -- 实现:`tone()` / `noTone()` 打在 GPIO26(经板载放大器) - -听不到声时检查:P4 是否插紧、喇叭阻值(常见 8Ω)、音量是否过小。 +- Status / Type / Format / Capacity(TF) +- Audio:当前音频状态 +- 用量进度条 ## 编译烧录 @@ -44,10 +41,11 @@ pio device monitor ## 注意事项 -- TF 卡需为 **FAT/FAT32**;exFAT 通常无法挂载。 -- 若花屏,在 `platformio.ini` 把 `ILI9341_2_DRIVER` 换成 `ILI9341_DRIVER`。 -- 变更请同步更新 `CHANGELOG.md` 并提交本地 git。 +- TF 需 **FAT/FAT32**。 +- 若触屏坐标偏移,改 `src/touch.cpp` 里 `map(...)` 校准范围。 +- 若点按无反应,检查排线/旋转;可尝试交换 raw_x/raw_y 映射。 +- 变更请更新 `CHANGELOG.md` 并提交本地 git。 -## 文档与版本 +## 版本 见 [CHANGELOG.md](CHANGELOG.md)。 diff --git a/include/audio_test.h b/include/audio_test.h index da8f52a..cff609d 100644 --- a/include/audio_test.h +++ b/include/audio_test.h @@ -3,8 +3,17 @@ #include void audio_init(); -// 短测:三声蜂鸣(开机 / 周期测试) -void audio_play_beep_sequence(); -// 简短旋律(约 1.5s) -void audio_play_melody(); + +// 扬声器总开关(关则静音并停止播放) +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 8e703b1..b7ca59f 100644 --- a/include/pins.h +++ b/include/pins.h @@ -14,5 +14,17 @@ // 板载 SC8002B 功放 → P4 喇叭座(DAC2) #define SPEAKER_PIN 26 +// 触摸 XPT2046(SoftSPI,避开 VSPI) +#define TOUCH_IRQ 36 +#define TOUCH_MOSI 32 +#define TOUCH_MISO 39 +#define TOUCH_CLK 25 +#define TOUCH_CS 33 + +// 物理竖屏分辨率(TFT 驱动) #define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 320 + +// 横屏逻辑分辨率(LV_DISPLAY_ROTATION_270) +#define UI_HOR_RES 320 +#define UI_VER_RES 240 diff --git a/include/touch.h b/include/touch.h new file mode 100644 index 0000000..788ec0f --- /dev/null +++ b/include/touch.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void touch_init(); +void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data); diff --git a/include/ui.h b/include/ui.h index 47b07c0..2dcdfdf 100644 --- a/include/ui.h +++ b/include/ui.h @@ -4,4 +4,4 @@ void ui_create(); void ui_update(const SdCardInfo &info); -void ui_set_audio_status(const char *text); +void ui_refresh_audio_buttons(); diff --git a/platformio.ini b/platformio.ini index dc935dd..5d72fc5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,6 +45,9 @@ build_flags = -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 index d2f9a7d..9569b63 100644 --- a/src/audio_test.cpp +++ b/src/audio_test.cpp @@ -1,47 +1,130 @@ #include "audio_test.h" #include "pins.h" -static const char *g_status = "Idle"; +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); - digitalWrite(SPEAKER_PIN, LOW); - noTone(SPEAKER_PIN); - g_status = "Ready GPIO26"; - Serial.printf("Speaker init on GPIO%d (SC8002B amp)\n", SPEAKER_PIN); + speaker_off_pin(); + g_enabled = false; + g_playing = false; + g_status = "Speaker OFF"; + Serial.printf("Speaker init GPIO%d (touch-controlled)\n", SPEAKER_PIN); } -static void beep(uint16_t freq_hz, uint16_t ms) { - tone(SPEAKER_PIN, freq_hz); - delay(ms); - noTone(SPEAKER_PIN); - digitalWrite(SPEAKER_PIN, LOW); -} - -void audio_play_beep_sequence() { - g_status = "Beep x3"; - Serial.println("Audio: beep sequence"); - beep(880, 120); - delay(80); - beep(1175, 120); - delay(80); - beep(1397, 180); - delay(50); - g_status = "Beep done"; -} - -void audio_play_melody() { - // 简化版:C D E G A - static const uint16_t notes[] = {523, 587, 659, 784, 880, 784, 659}; - static const uint16_t durs[] = {160, 160, 160, 200, 240, 160, 280}; - - g_status = "Melody"; - Serial.println("Audio: melody test"); - for (size_t i = 0; i < sizeof(notes) / sizeof(notes[0]); ++i) { - beep(notes[i], durs[i]); - delay(40); +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(); } - g_status = "Melody done"; + 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() { diff --git a/src/main.cpp b/src/main.cpp index 3b6e7aa..b0df454 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "audio_test.h" #include "pins.h" #include "sd_info.h" +#include "touch.h" #include "ui.h" static constexpr uint32_t DRAW_BUF_SIZE = @@ -12,9 +13,8 @@ static constexpr uint32_t DRAW_BUF_SIZE = static uint32_t draw_buf[DRAW_BUF_SIZE / 4]; static uint32_t last_refresh_ms = 0; -static uint32_t last_audio_ms = 0; +static uint32_t last_btn_ui_ms = 0; static constexpr uint32_t REFRESH_MS = 3000; -static constexpr uint32_t AUDIO_INTERVAL_MS = 12000; static void refresh_sd_ui() { SdCardInfo info = sd_read_info(); @@ -23,31 +23,25 @@ static void refresh_sd_ui() { if (!info.mounted) { Serial.printf("Error: %s\n", info.error.c_str()); } else { - Serial.printf("Type: %s\n", info.type); - Serial.printf("FS: %s\n", info.filesystem); - Serial.printf("Card: %s\n", format_bytes(info.card_bytes).c_str()); - Serial.printf("Part: %s\n", format_bytes(info.total_bytes).c_str()); - Serial.printf("Used: %s\n", format_bytes(info.used_bytes).c_str()); - Serial.printf("Free: %s\n", format_bytes(info.free_bytes).c_str()); - Serial.printf("Root entries: %lu\n", - static_cast(info.root_files)); + Serial.printf("Type: %s FS: %s Cap: %s\n", info.type, info.filesystem, + format_bytes(info.card_bytes).c_str()); } ui_update(info); - ui_set_audio_status(audio_last_status()); } void setup() { Serial.begin(115200); delay(200); Serial.println(); - Serial.printf("LVGL %d.%d.%d | CYD TF + Speaker\n", lv_version_major(), + Serial.printf("LVGL %d.%d.%d | CYD TF + Touch Speaker\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(); @@ -55,8 +49,11 @@ void setup() { lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf)); lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270); + lv_indev_t *indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(indev, touch_read_cb); + ui_create(); - ui_set_audio_status(audio_last_status()); if (!sd_init()) { Serial.println("SD mount failed at boot"); @@ -64,18 +61,13 @@ void setup() { refresh_sd_ui(); last_refresh_ms = millis(); - - // 开机立刻测喇叭 - audio_play_beep_sequence(); - delay(200); - audio_play_melody(); - ui_set_audio_status(audio_last_status()); - last_audio_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) { @@ -83,10 +75,10 @@ void loop() { refresh_sd_ui(); } - if (now - last_audio_ms >= AUDIO_INTERVAL_MS) { - last_audio_ms = now; - audio_play_beep_sequence(); - ui_set_audio_status(audio_last_status()); + // 播放过程中刷新按钮文字(Playing / Stop) + if (now - last_btn_ui_ms >= 300) { + last_btn_ui_ms = now; + ui_refresh_audio_buttons(); } delay(5); diff --git a/src/touch.cpp b/src/touch.cpp new file mode 100644 index 0000000..a5bc2f7 --- /dev/null +++ b/src/touch.cpp @@ -0,0 +1,83 @@ +#include "touch.h" +#include "pins.h" + +#include + +static void soft_spi_write(uint8_t data) { + for (int i = 7; i >= 0; --i) { + digitalWrite(TOUCH_MOSI, (data >> i) & 1); + digitalWrite(TOUCH_CLK, HIGH); + delayMicroseconds(1); + digitalWrite(TOUCH_CLK, LOW); + delayMicroseconds(1); + } +} + +static uint16_t soft_spi_read12() { + uint16_t v = 0; + for (int i = 0; i < 12; ++i) { + digitalWrite(TOUCH_CLK, HIGH); + delayMicroseconds(1); + v = (v << 1) | (digitalRead(TOUCH_MISO) ? 1 : 0); + digitalWrite(TOUCH_CLK, LOW); + delayMicroseconds(1); + } + // 丢掉多余时钟位 + digitalWrite(TOUCH_CLK, HIGH); + delayMicroseconds(1); + digitalWrite(TOUCH_CLK, LOW); + return v; +} + +static uint16_t xpt_read(uint8_t cmd) { + digitalWrite(TOUCH_CS, LOW); + soft_spi_write(cmd); + delayMicroseconds(5); + uint16_t v = soft_spi_read12(); + digitalWrite(TOUCH_CS, HIGH); + return v; +} + +void touch_init() { + pinMode(TOUCH_CS, OUTPUT); + pinMode(TOUCH_CLK, OUTPUT); + pinMode(TOUCH_MOSI, OUTPUT); + pinMode(TOUCH_MISO, INPUT); + pinMode(TOUCH_IRQ, INPUT); + digitalWrite(TOUCH_CS, HIGH); + digitalWrite(TOUCH_CLK, LOW); + Serial.println("Touch: XPT2046 SoftSPI ready"); +} + +void touch_read_cb(lv_indev_t * /*indev*/, lv_indev_data_t *data) { + const bool irq = (digitalRead(TOUCH_IRQ) == LOW); + + uint16_t raw_x = xpt_read(0x90); + uint16_t raw_y = xpt_read(0xD0); + + if (!irq || raw_x < 80 || raw_y < 80 || raw_x > 4000 || raw_y > 4000) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + // LV_DISPLAY_ROTATION_270 → 逻辑 320x240 + int16_t x = map((int)raw_y, 200, 3800, 0, UI_HOR_RES - 1); + int16_t y = map((int)raw_x, 240, 3700, 0, UI_VER_RES - 1); + + if (x < 0) { + x = 0; + } + if (y < 0) { + y = 0; + } + if (x >= UI_HOR_RES) { + x = UI_HOR_RES - 1; + } + if (y >= UI_VER_RES) { + y = UI_VER_RES - 1; + } + + 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 523b2bf..8bac0b2 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -1,4 +1,5 @@ #include "ui.h" +#include "audio_test.h" #include @@ -6,13 +7,12 @@ 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_total; -static lv_obj_t *lbl_used; -static lv_obj_t *lbl_free; -static lv_obj_t *lbl_files; -static lv_obj_t *bar_usage; -static lv_obj_t *lbl_hint; 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 *make_row(lv_obj_t *parent, const char *title, lv_obj_t **value_out) { lv_obj_t *row = lv_obj_create(parent); @@ -20,7 +20,7 @@ static lv_obj_t *make_row(lv_obj_t *parent, const char *title, lv_obj_t **value_ 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, 2, 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); @@ -34,11 +34,51 @@ static lv_obj_t *make_row(lv_obj_t *parent, const char *title, lv_obj_t **value_ 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_16, 0); + lv_obj_set_style_text_font(v, &lv_font_montserrat_14, 0); *value_out = v; return row; } +static void on_speaker_clicked(lv_event_t *e) { + LV_UNUSED(e); + audio_set_enabled(!audio_is_enabled()); + ui_refresh_audio_buttons(); +} + +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()); + } +} + void ui_create() { lv_obj_t *scr = lv_screen_active(); lv_obj_set_style_bg_color(scr, lv_color_hex(0x0D1B2A), 0); @@ -48,59 +88,66 @@ void ui_create() { 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, 12, 0); - lv_obj_set_style_pad_right(col, 12, 0); - lv_obj_set_style_pad_top(col, 10, 0); - lv_obj_set_style_pad_bottom(col, 8, 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, 4, 0); + lv_obj_set_style_pad_row(col, 2, 0); lv_obj_clear_flag(col, LV_OBJ_FLAG_SCROLLABLE); lv_obj_t *title = lv_label_create(col); - lv_label_set_text(title, "TF Card"); - lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0); + 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); - lv_obj_t *subtitle = lv_label_create(col); - lv_label_set_text(subtitle, "CYD ESP32 Storage Info"); - lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(subtitle, lv_color_hex(0x607D8B), 0); - - lv_obj_t *sep = lv_obj_create(col); - lv_obj_set_size(sep, LV_PCT(100), 1); - lv_obj_set_style_bg_color(sep, lv_color_hex(0x37474F), 0); - lv_obj_set_style_bg_opa(sep, LV_OPA_COVER, 0); - lv_obj_set_style_border_width(sep, 0, 0); - lv_obj_set_style_pad_all(sep, 0, 0); - 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, "Partition", &lbl_total); - make_row(col, "Used", &lbl_used); - make_row(col, "Free", &lbl_free); - make_row(col, "Root files", &lbl_files); - make_row(col, "Speaker", &lbl_audio); - lv_label_set_text(lbl_audio, "GPIO26"); + 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, 14); + 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_opa(bar_usage, LV_OPA_COVER, 0); lv_obj_set_style_bg_color(bar_usage, lv_color_hex(0x29B6F6), LV_PART_INDICATOR); - lv_obj_set_style_radius(bar_usage, 4, 0); - lv_obj_set_style_radius(bar_usage, 4, LV_PART_INDICATOR); - lbl_hint = lv_label_create(col); - lv_label_set_text(lbl_hint, "Refreshing every 3s"); - lv_obj_set_style_text_font(lbl_hint, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(lbl_hint, lv_color_hex(0x546E7A), 0); + // 底部两个触控按钮 + lv_obj_t *row_btns = lv_obj_create(col); + lv_obj_set_width(row_btns, LV_PCT(100)); + lv_obj_set_height(row_btns, 52); + 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, 145, 46); + lv_obj_set_style_radius(btn_speaker, 8, 0); + 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, 145, 46); + lv_obj_set_style_radius(btn_play, 8, 0); + 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); + + ui_refresh_audio_buttons(); } void ui_update(const SdCardInfo &info) { @@ -110,51 +157,24 @@ void ui_update(const SdCardInfo &info) { lv_label_set_text(lbl_type, info.type); lv_label_set_text(lbl_fs, "—"); lv_label_set_text(lbl_capacity, "—"); - lv_label_set_text(lbl_total, "—"); - lv_label_set_text(lbl_used, "—"); - lv_label_set_text(lbl_free, "—"); - lv_label_set_text(lbl_files, "—"); lv_bar_set_value(bar_usage, 0, LV_ANIM_OFF); - lv_label_set_text(lbl_hint, "Insert TF and wait..."); - return; - } + } 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()); - 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); - String tot = format_bytes(info.total_bytes); - String used = format_bytes(info.used_bytes); - String freeb = format_bytes(info.free_bytes); - - lv_label_set_text(lbl_capacity, cap.c_str()); - lv_label_set_text(lbl_total, tot.c_str()); - lv_label_set_text(lbl_used, used.c_str()); - lv_label_set_text(lbl_free, freeb.c_str()); - - char files_buf[16]; - snprintf(files_buf, sizeof(files_buf), "%lu", static_cast(info.root_files)); - lv_label_set_text(lbl_files, files_buf); - - int pct = 0; - if (info.total_bytes > 0) { - pct = static_cast((info.used_bytes * 100ULL) / info.total_bytes); - if (pct > 100) { - pct = 100; + 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); } - lv_bar_set_value(bar_usage, pct, LV_ANIM_ON); - char hint[48]; - snprintf(hint, sizeof(hint), "Used %d%% | SD+Audio", pct); - lv_label_set_text(lbl_hint, hint); -} - -void ui_set_audio_status(const char *text) { - if (lbl_audio == nullptr || text == nullptr) { - return; - } - lv_label_set_text(lbl_audio, text); + ui_refresh_audio_buttons(); }