Restore TF card UI and speaker at 20% volume.

Keep calibrated SoftSPI touch mapping unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-17 18:43:11 +08:00
parent 7b5f984731
commit a1ab088d70
12 changed files with 581 additions and 62 deletions

View File

@@ -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°).

View File

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

Binary file not shown.

20
include/audio_test.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <Arduino.h>
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();

View File

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

21
include/sd_info.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <Arduino.h>
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);

View File

@@ -1,6 +1,9 @@
#pragma once
#include "sd_info.h"
#include <stdint.h>
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);

View File

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

159
src/audio_test.cpp Normal file
View File

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

View File

@@ -1,7 +1,9 @@
#include <Arduino.h>
#include <lvgl.h>
#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);
}

136
src/sd_info.cpp Normal file
View File

@@ -0,0 +1,136 @@
#include "sd_info.h"
#include "pins.h"
#include <FS.h>
#include <SD.h>
#include <SPI.h>
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<double>(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<unsigned long long>(bytes));
}
return String(buf);
}

View File

@@ -1,85 +1,186 @@
#include "ui.h"
#include "audio_test.h"
#include "pins.h"
#include <Arduino.h>
#include <lvgl.h>
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<int>((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();
}