Files

27 KiB
Raw Permalink Blame History

HiFi 播放器(ESP32-S3 + LVGL + ES8388 + SD 卡)

基于正点原子 DNESP32S3 开发板标准例程移植改造的 HiFi 音乐播放器。 使用 SPI LCD 作为显示界面、SD 卡 存储音频、4 个板载按键 交互, 界面底部实时标注每个按键的功能。

当前包含 3 个应用:主界面(应用启动器)、音乐播放器、文件管理器。

音频通路为 原生位深透传:源文件是 16/24/32bit,就按对应位宽重新配置 I2S 与 ES8388, 不做位深压缩,配合 FLAC 无损解码构成完整的无损链路。


版本记录

版本 主要变更
V1.0 移植 BSP、LVGL 8.4、ES8388 + I2S,WAV 播放
V1.2 应用启动器框架、全局 K0 返回键、按应用分文件夹
V1.3 iPhone 风格图标网格主界面
V1.4 增加 FLAC 解码(esphome/micro-flac),WAV/FLAC 统一音源层
V1.5 原生位深透传 + I2S 位宽/采样率动态重配;新增文件管理器应用;修复 .flac 文件无法被扫描到的问题

1. 功能特性

功能 说明
应用启动器 开机进入主界面,图标网格选择应用(MUSIC / FILES 可用,SETTINGS / ABOUT 占位)
音乐播放器 WAV(PCM 16/24/32bit)与 FLAC(原生 FLAC / Ogg FLAC),支持 8k~96k 采样率
文件管理器 浏览 SD 卡全部文件,可进出任意层级子文件夹,显示文件大小与类型图标
原生位深输出 16/24/32bit 按源文件位深直接输出,不做位深压缩;I2S 位宽与采样率随曲目动态重配
图形界面 LVGL 8.4,320×240 横屏,深色 HiFi 风格
存储 SD 卡(SPI 模式),FATFS,音乐目录 /MUSIC
播放控制 播放/暂停 / 下一曲 / 音量切换(无上一曲,K0 用于返回)
状态显示 曲目序号、歌名、格式、播放进度、音量、运行状态
按键提示 底部 4 个按键卡片,含义随界面变化,按下时高亮
后台挂起 从播放器返回主界面自动暂停,再进入时从原位置继续
音量记忆 音量保存在 NVS,掉电不丢失
自动恢复 SD 卡拔出/插入、目录为空时自动重新挂载与扫描

界面布局

[主界面 HOME · iPhone 风格图标网格]
┌──────────────────────────┐
│ ☰  APP LAUNCHER      1/4 │
├──────────────────────────┤
│  ╭──╮  ╭──╮  ╭──╮  ╭──╮  │
│  │♪ │  │📁│  │⚙ │  │☰ │  │   K3 打开选中的应用
│  ╰──╯  ╰──╯  ╰──╯  ╰──╯  │
│  MUSIC FILES SETTING ABOUT│
│  WAV/FLAC files in SD …   │
│           ●              │
├──────────────────────────┤
│ [◀K1]  [▶K2]  [✔K3]  [→K0]│
│ PREV    NEXT   OPEN  SWITCH│
└──────────────────────────┘

[播放器 PLAYER]
┌──────────────────────────┐
│ ♪  HiFi PLAYER     01/12 │
├──────────────────────────┤
│ ┌────┐ 歌名               │
│ │ ♪  │ FLAC 44100Hz      │
│ └────┘ 24bit STEREO       │
│ ( PLAYING )               │
│ 00:42 ────●──────  03:35  │
│ 🔊 ──────●─────  60%      │
├──────────────────────────┤
│ [←K0] [⏯K1] [⏭K2] [🔊K3] │
│ BACK   PLAY  NEXT   VOL   │
└──────────────────────────┘

[文件管理器 FILES]
┌──────────────────────────┐
│ 📁 FILES           12 SD │
├──────────────────────────┤
│ 0:/MUSIC/Album           │
├──────────────────────────┤
│ 📁 ..            <DIR>   │
│ 📁 Album         <DIR>   │  ← 选中行带强调色描边
│ ♪  song01.flac   24.1M   │
│ 📄 notes.txt      128B   │
│ 📄 readme.md        2K   │
├──────────────────────────┤
│ [←K0] [▲K1] [▼K2] [✔K3]  │
│ PARENT  UP   DOWN  OPEN  │
└──────────────────────────┘

界面文本使用 LVGL 内置 Montserrat 字体(含图标),因此为 ASCII。 如需中文界面,见第 10 节“扩展建议”。


2. 硬件资源与引脚

外设 引脚 / 总线 说明
SPI LCD(ST7789 320×240) SPI2:SCLK=12, MOSI=11, CS=21, DC=40 与 SD 卡共用 SPI2
SD 卡 SPI2:CS=2 FATFS 挂载点 /0:
ES8388 I2S0:MCLK=3, BCLK=46, WS=9, DOUT=10, DIN=14 位宽随曲目动态配置(16/24/32bit)
XL9555 I2C0:SDA=41, SCL=42 按键、LCD 电源/复位/背光、喇叭使能
按键 XL9555 的 KEY0~KEY3 低电平有效
LED0 GPIO1 播放时点亮

LCD 电源(SLCD_PWR)、复位(SLCD_RST)、背光(LCD_BL)、喇叭使能(SPK_EN) 均由 XL9555 扩展 IO 控制,初始化顺序必须是: my_spi_init → myiic_init → xl9555_init → spilcd_init。


3. 按键说明(界面底部同步显示)

全局约定:K0 固定为「返回 / 切换」键,任何 App 都一致;K1/K2/K3 由各个 App 自行定义。

按键 全局含义
K0 短按 在任意 App 内 → 返回主界面;在主界面 → 切换到下一个应用
K0 长按(0.7s) 任意界面 → 直接切换到下一个应用

例外:实现了“就地返回”的 App 可以先消费掉 K0 短按。目前文件管理器就是这样—— 在子目录里按 K0 是返回上一级目录,只有在 0: 根目录时才回主界面。 该行为由 ui.c 中调用 ui_files_back() 钩子实现,其它 App 不受影响。

主界面(应用启动器)

按键 功能 界面提示
KEY0 切换到下一个应用 → K0 SWITCH
KEY1 选择上一个图标 ◀ K1 PREV
KEY2 选择下一个图标 ▶ K2 NEXT
KEY3 打开选中的应用 ✔ K3 OPEN

播放器界面

按键 功能 界面提示
KEY0 返回主界面(长按 = 切换应用) ← K0 BACK
KEY1 播放 / 暂停 K1 PLAY(播放中图标变为暂停)
KEY2 下一曲 K2 NEXT
KEY3 音量切档(20/40/60/80/100%) K3 VOL

已按要求取消「上一首」功能,K0 释放出来作为独立的返回键。

文件管理器界面

按键 功能 界面提示
KEY0 返回上一级目录;已在根目录时回主界面(长按 = 切换应用) ← K0 UP / ← K0 HOME
KEY1 选择上一项(到顶后跳到底部) ▲ K1 UP
KEY2 选择下一项 ▼ K2 DOWN
KEY3 进入文件夹 / 查看文件详情 ✔ K3 OPEN

退出子文件夹有两条路:按 K0,或选中列表最上方的 .. 行按 K3。 按 K3 打开普通文件时,屏幕下方会弹出 2 秒浮层,显示文件名、大小和类型 (AUDIO / IMAGE / FILE)。

按键由 app.c 的按键服务统一扫描(10ms 周期、30ms 去抖、短按/长按区分), ui.c 是事件的唯一消费者并负责分发,因此不会重复扫描按键,也不会因为去抖延时而卡住音频。


4. 目录结构

hifi_player/
├── CMakeLists.txt                  # 工程入口
├── partitions-16MiB.csv            # 16MB Flash 分区表(app 3MB)
├── sdkconfig.defaults              # 默认配置:PSRAM/FATFS/LVGL 等
├── components/
│   └── BSP/                        # 从标准例程移植的板级驱动
│       ├── CMakeLists.txt
│       ├── LED/                    # 指示灯
│       ├── MYIIC/                  # I2C0(新 I2C 主机驱动)
│       ├── XL9555/                 # IO 扩展:按键 / LCD 控制 / 功放使能
│       ├── MYSPI/                  # SPI2 总线 + SD 卡设备
│       ├── SPILCD/                 # ST7789 SPI LCD(含 flush 同步)
│       ├── SPI_SD/                 # SD 卡 FATFS 挂载
│       ├── ES8388/                 # 音频 DAC 配置
│       └── MYI2S/                  # I2S 标准模式收发 + 位宽动态重配
└── main/
    ├── CMakeLists.txt
    ├── idf_component.yml           # 依赖:LVGL 8.4 + micro-flac
    ├── main.c                      # 初始化流程
    └── APP/
        ├── app.c/.h                # 应用管理器 + 按键服务(模式 / 事件队列 / 切换)
        ├── ui.c/.h                 # 界面路由:换屏 + 按键分发(唯一按键消费者)
        ├── ui_common.c/.h          # 界面公共组件:配色 / 卡片 / 按键提示条
        ├── lvgl_port.c/.h          # LVGL 移植层(显示 + 1ms 时基)
        ├── home/                   # 主界面(应用启动器)
        │   ├── ui_home.c
        │   └── ui_home.h
        ├── files/                  # 文件管理器 App
        │   ├── ui_files.c          #   浏览 SD 卡目录(滑动窗口列表)
        │   └── ui_files.h
        └── player/                 # 播放器 App
            ├── ui_player.c/.h      #   播放器界面
            ├── player.c/.h         #   播放核心(扫描 / I2S / 请求处理)
            ├── audio_source.c/.h   #   统一音源:WAV 直读 + FLAC 解码转发
            ├── flac_dec.cpp        #   FLAC 解码的 C 封装(C++ 实现)
            └── flac_dec.h          #   纯 C 接口

分层关系

按键:  XL9555 → app.c 的 key_task → 事件队列
                                        │
界面:  ui.c(LVGL 任务,50ms)  ────────┘  唯一消费者
           ├─ K0 → 返回主界面 / 切换应用(全局)
           │        └─ 例外:FILES 在子目录时先返回上一级
           └─ K1~K3 → 交给当前 App(ui_home / ui_files / ui_player)
                          │
播放:  ui_player → player_request() → player_task 消费并执行
                          (只有 APP_MODE_PLAYER 时才输出 I2S)

5. 编译与烧录

5.1 环境

  • ESP-IDF v6.1(本工程已在此版本上编译验证通过,固件 0xdb3f0 = 898,032 字节 ≈ 877 KB,分区剩余 71%)
  • 也兼容 v5.3.x / v5.4(BSP 已按 IDF 6.x 的驱动组件拆分方式声明依赖)
  • 目标芯片:esp32s3
  • 首次编译需要联网下载 lvgl/lvgl 8.4.0 与 esphome/micro-flac 0.3.0

IDF 6.x 注意:driver 这个“伞形”组件不再转发 esp_driver_* 子组件, 因此在 components/BSP/CMakeLists.txt 里必须逐个显式列出: esp_driver_gpio esp_driver_i2c esp_driver_i2s esp_driver_spi esp_driver_sdspi esp_driver_sdmmc sdmmc esp_lcd fatfs。 同时 driver/i2s.h、driver/i2c.h、I2S_BITS_PER_SAMPLE_* 已被移除, 代码里改用 esp_driver_i2s 头文件与纯数字位宽。

5.2 命令

# 1. 设置目标(只需一次)
idf.py set-target esp32s3

# 2. 编译
idf.py build

# 3. 烧录并查看日志(把 COMx 换成实际串口)
idf.py -p COMx flash monitor

或直接在 VS Code 的 ESP-IDF 插件里点击 Build / Flash / Monitor。

注意:sdkconfig.defaults 已配置 16MB Flash、八线 PSRAM(80MHz)、自定义分区表。 首次编译前请确认板载 Flash 为 16MB(DNESP32S3 默认如此)。


6. SD 卡准备

  1. SD 卡格式化为 FAT32(建议 ≤ 32GB)。不支持 exFAT。
  2. 在根目录新建文件夹 MUSIC,把音频文件放进去(不递归子目录):
SD 卡根目录/
├── MUSIC/                ← 播放器只扫描这一层
│   ├── canon.wav
│   ├── song.flac
│   └── ...
└── 其他文件/               ← 可以用「文件管理器」应用浏览
  1. 音频要求:

    容器 编码 位深 声道 采样率
    WAV(RIFF/WAVE) PCM(WAVE_FORMAT_PCM 或 EXTENSIBLE+PCM) 16 / 24 / 32bit 单声道 / 立体声 8k ~ 96kHz
    FLAC(原生 fLaC / Ogg FLAC) FLAC 4 ~ 32bit 1 ~ 8 声道 8k ~ 96kHz
  2. 说明:

    • 文件名建议只用 ASCII(英文 / 数字 / 下划线)。中文文件名能正常播放, 但界面上会显示为 ?(见下方第 5 条)。
    • 单曲数量上限 200 首(MAX_TRACKS),扩展名支持 .wav / .flac(大小写不敏感)。
    • 文件类型按文件头自动识别(RIFF / fLaC / OggS),扩展名只用于筛选列表。
    • 位深不再统一转 16bit:源文件是什么位深就按什么位宽输出,映射关系见下表。
    • 中文文件名是 GBK 编码,LVGL 内置字体无法渲染,make_display_name() 会把 连续的非 ASCII 字节折叠成一个 ?,但底层路径使用原始字节,不影响播放。

6.1 位深与输出映射(V1.5)

源位深 每采样字节 每帧字节(立体声) ES8388 R23 字长 I2S data_bit_width / ws_width mclk_multiple
≤ 16bit(8 / 16) 2 4 16bit(码 3) I2S_DATA_BIT_WIDTH_16BIT 256
17 ~ 24bit(20 / 24) 3 6 24bit(码 0) I2S_DATA_BIT_WIDTH_24BIT 384
25 ~ 32bit(32) 4 8 32bit(码 4) I2S_DATA_BIT_WIDTH_32BIT 256

为什么 24bit 要用 384 倍 MCLK:ESP-IDF 的 i2s_std.c 有一条硬性校验—— slot 位宽为 24bit 时 mclk_multiple 必须是 3 的倍数,否则 i2s_channel_reconfig_std_clock() 直接返回 ESP_ERR_INVALID_ARG。 myi2s.c 的 i2s_set_samplerate_bits_sample() 会自动切换:24bit → 384,其余 → 256。

例:20bit 的 FLAC 会输出成 24bit 槽位(out_bytes = 3), 界面上显示为 FLAC 44100Hz 20->24bit,提示“源 20bit,实际按 24bit 输出”。


7. 软件架构

┌──────────────────────┐        ┌───────────────────────┐
│   player_task        │        │   lvgl_task           │
│   (优先级 6)         │        │   (优先级 4, 8KB 栈)  │
│                      │        │                       │
│  扫描 /MUSIC         │        │  lv_timer_handler()   │
│  audio_source_read() │        │  ui_tick_cb(50ms)     │
│   → I2S 写           │        │        │              │
│  消费按键请求队列    │        │        ▼              │
│        │             │        │  player_get_state()   │
│        ▼             │        │  (互斥锁保护的状态快照)│
│   s_state (互斥锁)   │───────▶│                       │
└──────────────────────┘        └───────────┬───────────┘
                                            │ flush_cb
                                            ▼
                                  spilcd_flush() → SPI DMA → ST7789

音频解码链路

              ┌─ WAV  ── f_read 原始 PCM ─────── 单声道复制 ──┐
audio_source_read                                              │
              └─ FLAC ── micro_flac 解码 ── 声道归一化 ────────┘
                                                               │
                              ┌────────────────────────────────┘
                              ▼
             立体声 + 原生位深(16/24/32bit,左对齐)
                              │
                     audio_set_format(rate, out_bits)
                     ├─ es8388_i2s_cfg(0, len_code)
                     └─ i2s_set_samplerate_bits_sample(rate, bits)
                              │
                              ▼
                    i2s_tx_write(frames * 2 * out_bytes)

audio_source.c 对外只暴露「给我一块立体声 + 原生位深 PCM,并告诉我 out_bytes」, 因此 player.c 的播放循环与容器格式完全无关;新增格式(如 MP3)只需在 audio_source_open() 里加一个分支。

关键实现点

  1. 单任务音频流 一个任务完成“读文件 + 解码 + 写 I2S + 处理按键请求”,每块音频约 23ms (AUDIO_CHUNK_FRAMES = 1024 @44.1kHz),避免多任务同时访问 FATFS/I2S 的竞争。 发送缓冲按最坏情况分配 8 字节/帧(32bit 立体声):AUDIO_BUF_SIZE = 1024 × 8 = 8KB。

  2. 非阻塞按键去抖 key_task() 每 10ms 读一次 XL9555 输入寄存器(一次 I2C 读 2 字节), 连续 3 次采样一致才确认状态变化,不使用 vTaskDelay 阻塞去抖。

  3. LVGL 与 SPI LCD

    • 绘图缓冲区 320×40 像素,放在内部 DMA RAM(SPI DMA 不能直接读 PSRAM)。
    • spilcd_flush() 通过 esp_lcd 注册的 on_color_trans_done 回调 + 二值信号量 等待传输完成,保证 LVGL 缓冲区不会被提前复用。
    • LVGL 的 RGB565 是小端,ST7789 要求高字节在前,因此在 disp_flush_cb() 中做原地字节交换。
  4. I2S 通道管理 i2s_trx_start()/i2s_trx_stop() 带状态判断,可重复调用而不报错; 切换曲目时会先关闭通道,重新配置 data_bit_width / ws_width / mclk_multiple / sample_rate 后再由播放循环按需打开。

  5. FLAC 解码

    • 使用 esphome/micro-flac(ESP32-S3 带 Xtensa 汇编优化,16bit 约 30 倍实时)。
    • 该库是 C++,flac_dec.cpp 提供一层纯 C 封装(flac_dec_open/read/close), 播放器侧代码仍是 C。
    • 用的是 uint8_t* 输出重载(原生字节打包),而不是 int32_t* 的 32bit 归一化重载, 这样才能保留源位深。
    • 码流缓冲 8KB,采用「未消费数据前移 + 追加读取」,避免解码器返回 NEED_MORE_DATA 时丢数据;同时有“无进展”保护,坏文件不会死循环。
    • 解码输出缓冲按 STREAMINFO 的 max_block_size 动态分配,优先放 PSRAM。
  6. 状态同步 播放器只写 s_state(互斥锁保护),UI 每 50ms 读取一次快照, LVGL 的所有控件操作都发生在 LVGL 任务内,天然线程安全。


8. 文件管理器实现要点

main/APP/files/ui_files.c 是一个可直接复用的「列表型 App」模板:

  1. 滑动窗口列表 界面创建时只建 5 个固定行控件(FILES_ROWS),刷新时把 [s_top, s_top + 5) 区间映射到这些行上。目录里有几百个文件也不会撑爆 LVGL 的 48KB 内存池;也完全避免“清空重建”带来的闪烁。

  2. 排序规则 qsort + 自定义比较:.. 永远第一 → 目录 → 文件,同类按名称 strcasecmp(不区分大小写)。

  3. 过滤 隐藏(AM_HID)、系统(AM_SYS)、卷标(0x08)属性一律不显示。 单目录最多显示 FILES_MAX_ENTRIES = 96 条,超出部分截断。

  4. 图标着色 目录 → 黄色 LV_SYMBOL_DIRECTORY;音频(.wav/.flac)→ 青色 LV_SYMBOL_AUDIO; 图片(.bmp/.png/.jpg/.jpeg/.gif)→ 紫色 LV_SYMBOL_IMAGE;其它 → 灰色 LV_SYMBOL_FILE。

  5. 大小格式化 files_format_size() 用纯整数运算输出 128B / 2K / 24.1M, 不依赖浮点 printf(省 flash、避免 %f 在 newlib nano 下失效)。

  6. 别名安全的路径切换 files_open_dir() 先把入参复制到 target[] 再写入 s_path, 因此可以安全地用 s_path 自身作为实参调用(files_go_up() 就是这么做的)。 打开失败会自动回退到原目录并恢复原来的选中项。

  7. SD 卡挂载策略 先 f_opendir("0:") 探测;只有探测失败且从未尝试过时才调用 sd_spi_init()。 正常开机时播放器任务已经挂好卡,这里只读探测,绝不和播放器抢挂载 (否则正在播放的文件句柄会失效)。

FatFs 配置为 FF_FS_REENTRANT = 1,因此播放器任务与 LVGL 任务同时访问 SD 卡是安全的。


9. 常见问题

现象 排查方向
屏幕全白 / 全黑 检查 XL9555 是否初始化成功、背光 LCD_BL 是否置 1;确认 PSRAM 配置与板子一致
屏幕花屏 确认 SPI 时钟 60MHz 未超频,排线接触良好;检查字节交换逻辑
显示 NO SD CARD SD 卡需 FAT32(不是 exFAT);my_spi_init() 必须在 spilcd_init() 之前;检查 CS=GPIO2
显示 PUT WAV/FLAC IN /MUSIC SD 卡根目录需有 MUSIC 文件夹且直接包含 .wav / .flac(不递归子目录)
FLAC 文件不显示 / 不能播放 V1.5 已修复 name_is_audio() 提前 return false 导致 .flac 全被过滤的 bug。若仍异常,确认扩展名确实是 .flac、文件是标准 fLaC 或 Ogg FLAC,并查看串口 flac_dec 前缀日志
显示 UNSUPPORTED FILE WAV 必须是 PCM(16/24/32bit);APE / MP3 / M4A 需要额外解码库
界面显示 24->32bit 之类 这是正常提示:源位深与实际 I2S 输出位宽不同(如 20bit 源按 24bit 输出)
24bit 曲目无声 / 报错 检查 mclk_multiple 是否为 3 的倍数;ESP-IDF 要求 24bit slot 必须满足该条件,myi2s.c 已自动设 384
FLAC 播放有断续 提高 CONFIG_SPIRAM_SPEED_80M、确认 PSRAM 已启用;或降低 SD 卡 SPI 时钟
文件管理器看不到某些文件 隐藏 / 系统属性的文件被有意过滤;单目录超过 96 条会被截断
文件管理器中文名显示 ? 预期行为:GBK 文件名无法用内置字体渲染,功能不受影响
编译报 'LIST_H' redefined 自定义宏名与 FreeRTOS list.h 的 include guard 冲突(-Werror),改名即可
编译报 'AM_VOL' undeclared 本版 FatFs 未定义该宏,用字面量 0x08 代替
编译报 -Werror=format-truncation FILINFO::fname 是 char[256],拷入更小缓冲时改用 strlcpy 或显式长度校验

10. 扩展建议

10.1 新增一个应用(推荐流程)

工程已经把「界面」「按键」和「应用」解耦,新增应用只需要 4 步,并且独立文件夹:

  1. 建立文件夹:例如 main/APP/clock/,放入 ui_clock.c / ui_clock.h, 参考 player/ui_player.c 或 files/ui_files.c 实现 4 个函数:

    void ui_clock_create(void);              /* 创建屏幕(lv_obj_create(NULL)) */
    void ui_clock_show(void);                /* 刷新一次并 lv_scr_load() */
    void ui_clock_tick(void);                /* 周期刷新显示 */
    void ui_clock_handle_key(uint8_t key);   /* 只处理 K1/K2/K3 */
    

    如需 K0 短按在 App 内“就地返回”(像文件管理器那样逐级退出),再额外实现:

    bool ui_clock_back(void);   /* true = 已就地处理,ui.c 不再回主界面 */
    

    并在 main/APP/ui.c 的 ui_dispatch() 里加一个 else if 分支。

  2. 加模式:在 main/APP/app.h 的 app_mode_t 中,于 APP_MODE_NUM 之前加一项, 例如 APP_MODE_CLOCK。

  3. 加表项:在 main/APP/app.c 的 s_apps[] 中加一条:

    {
        .icon       = LV_SYMBOL_FILE,      /* 图标字形 */
        .title      = "CLOCK",             /* 图标下方名称(建议 ≤ 8 字符) */
        .subtitle   = "Digital clock",     /* 选中时下方显示的说明 */
        .mode       = APP_MODE_CLOCK,      /* 进入后切到的模式 */
        .enabled    = true,                /* 允许进入(false 时显示为禁用) */
        .color_from = 0x4FC3F7,            /* 图标渐变起始色 */
        .color_to   = 0x2E86DE,            /* 图标渐变结束色 */
    },
    
  4. 挂路由:在 main/APP/ui.c 的 ui_load_mode()、ui_dispatch()、 ui_tick_cb() 里各加一个 case APP_MODE_CLOCK: 分支; 同时把 main/CMakeLists.txt 的 SRCS 加上 "APP/clock/ui_clock.c"、 INCLUDE_DIRS 加上 "APP/clock"(本工程用的是显式文件列表,不是 SRC_DIRS)。

返回主界面、切换应用是全局自动生效的(ui.c 里统一处理 K0),新 App 不需要关心。

10.2 其他扩展

主界面按 iPhone 风格 一排 4 个图标,超过 4 个应用会自动分页, 屏幕下方出现页码圆点;K1/K2 可在页间连续移动。

  • 中文界面:在 menuconfig → Component config → LVGL → Font usage 中启用 LV_FONT_SIMSUN_16_CJK(内置 1000 常用汉字),或使用 lv_font_conv 生成子集字体,然后把 各界面里的英文文案替换为中文。文件名同理——启用中文字体后, make_display_name() / files_display_name() 里的 ? 折叠逻辑就可以去掉。

  • 图标换成图片:当前图标是「圆角渐变 + 内置字形」,如需真实图标, 可用 LV_IMG_DECLARE() + lv_img_create() 替代 s_glyph[] 处的标签 (图片需要用 LVGL 图片转换工具转成 C 数组,或开启 LV_USE_PNG + 文件系统驱动)。

  • 再增加一种格式(如 MP3 / APE):引入对应解码库后,只需在 audio_source.c 的 audio_source_open() 里加一个分支、在 audio_source_read() 里加一个转发即可,player.c 与界面代码都不需要改:

    if (is_mp3) {
        src->codec = AUDIO_CODEC_MP3;
        src->mp3 = mp3_dec_open(path);
        ...
    }
    

    注意 MP3 等有损格式位深固定,out_bytes 填 2(16bit)即可, audio_set_format() 会自动把 ES8388 / I2S 配成 16bit。

  • 专辑封面:LVGL 支持从文件系统读取 PNG/JPG(需开启 LV_USE_PNG / LV_USE_SJPG

    • 文件系统驱动),可在播放器左侧封面卡片显示 /MUSIC 中的同名图片。
  • 歌词显示:解析同名 .lrc 文件,在进度条下方增加滚动歌词控件。

  • 文件管理器直接播放:在 files_enter() 里判断选中项是音频文件时, 调用 player_load_path()(需在 player.h 新增该接口)+ app_set_mode(APP_MODE_PLAYER), 即可实现「浏览目录 → 点开即播」。


11. 参考例程

  • 10_spilcd —— SPI LCD 初始化、引脚定义
  • 30_music —— ES8388 / I2S / WAV 播放 / XL9555 按键
  • 25_sd —— SD 卡(SPI)挂载
  • 4,扩展例程-IDF版/4,LVGL例程/01_lvgl_transplant —— LVGL 8.4 移植结构

第三方组件:

驱动代码从上述例程移植;LVGL 移植层、应用管理器、界面路由、图标式主界面、 统一音源层(WAV/FLAC + 原生位深)、文件管理器、播放器状态机与按键交互为本工程重新实现。