Add touch buttons for speaker toggle and music playback.
Enable SoftSPI touch, replace auto beeps with Speaker ON/OFF and Play Music controls, and document usage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
11
CHANGELOG.md
11
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
|
||||
|
||||
|
||||
46
README.md
46
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)。
|
||||
|
||||
@@ -3,8 +3,17 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
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();
|
||||
|
||||
@@ -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
|
||||
|
||||
6
include/touch.h
Normal file
6
include/touch.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
void touch_init();
|
||||
void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data);
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
40
src/main.cpp
40
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<unsigned long>(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);
|
||||
|
||||
83
src/touch.cpp
Normal file
83
src/touch.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "touch.h"
|
||||
#include "pins.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
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;
|
||||
}
|
||||
186
src/ui.cpp
186
src/ui.cpp
@@ -1,4 +1,5 @@
|
||||
#include "ui.h"
|
||||
#include "audio_test.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
@@ -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<unsigned long>(info.root_files));
|
||||
lv_label_set_text(lbl_files, files_buf);
|
||||
|
||||
int pct = 0;
|
||||
if (info.total_bytes > 0) {
|
||||
pct = static_cast<int>((info.used_bytes * 100ULL) / info.total_bytes);
|
||||
if (pct > 100) {
|
||||
pct = 100;
|
||||
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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user