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

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


1. 功能特性

功能 说明
应用启动器 开机进入主界面,可选择并进入「音乐播放器」(预留 SETTINGS / ABOUT)
图形界面 LVGL 8.4,320×240 横屏,深色 HiFi 风格
音频播放 WAV(PCM 16/24/32bit)与 FLAC(原生 FLAC / Ogg FLAC),I2S + ES8388,支持 8k~96k 采样率
存储 SD 卡(SPI 模式),FATFS,音乐目录 /MUSIC
播放控制 播放/暂停 / 下一曲 / 音量切换(无上一曲,K0 用于返回)
状态显示 曲目序号、歌名、格式、播放进度、音量、运行状态
按键提示 底部 4 个按键卡片,含义随界面变化,按下时高亮
后台挂起 从播放器返回主界面自动暂停,再进入时从原位置继续
音量记忆 音量保存在 NVS,掉电不丢失
自动恢复 SD 卡拔出/插入、目录为空时自动重新挂载与扫描

界面布局(两个界面):

[主界面 HOME · iPhone 风格图标网格]        [播放器 PLAYER]
┌──────────────────────────┐               ┌──────────────────────────┐
│ ☰ APP LAUNCHER      1/4  │               │ ♪ HiFi PLAYER      01/12 │
├──────────────────────────┤               ├──────────────────────────┤
│  ╭──╮  ╭──╮  ╭──╮  ╭──╮  │               │ ┌────┐ 歌名               │
│  │♪ │  │📁│  │⚙ │  │☰ │  │   K3 打开     │ │ ♪  │ WAV 44100Hz 16bit│
│  ╰──╯  ╰──╯  ╰──╯  ╰──╯  │  ───────────▶ │ └────┘ ( PLAYING )        │
│  MUSIC FILES SETTINGS …  │  ◀─────────── │ 00:42 ────●────── 03:35  │
│   WAV files in SD /MUSIC │   K0 返回     │ 🔊 ──────●─────  60%      │
│           ●              │               │ [←K0][⏯K1][⏭K2][🔊K3]   │
│ [◀K1][▶K2][✔K3][→K0]     │               │ BACK  PLAY  NEXT   VOL   │
└──────────────────────────┘               └──────────────────────────┘

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


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 16bit 输出
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) 任意界面 → 直接切换到下一个应用

主界面(应用启动器)

按键 功能 界面提示
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 释放出来作为独立的返回键。

按键由 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
        └── 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 → 返回主界面 / 切换应用(全局)
           └─ K1~K3 → 交给当前 App(ui_home / ui_player)
                          │
播放:  ui_player → player_request() → player_task 消费并执行
                          (只有 APP_MODE_PLAYER 时才输出 I2S)

5. 编译与烧录

5.1 环境

  • ESP-IDF v6.1(本工程已在此版本上编译验证通过,生成固件约 893 KB)
  • 也兼容 v5.3.x / v5.4(BSP 已按 IDF 6.x 的驱动组件拆分方式声明依赖)
  • 目标芯片:esp32s3
  • 首次编译需要联网下载 lvgl/lvgl 8.4.0 与 esphome/micro-flac 0.3.0

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)。
  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. 说明:

    • 两种格式都统一转换为 16bit 立体声后送给 ES8388: 24/32bit 取高位,单声道复制到左右,多声道(FLAC)只取前两声道。
    • 界面上的 WAV/FLAC xxxHz xxbit 显示的是源文件参数。
    • 文件类型按文件头自动识别(RIFF / fLaC / OggS),扩展名只用于扫描列表。

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┤                                                  ├─▶ 16bit 立体声 ──▶ I2S
                 └─ FLAC ── micro_flac 解码 ── 取高位 / 声道归一化 ─┘

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

关键实现点

  1. 单任务音频流 一个任务完成“读文件 + 解码 + 写 I2S + 处理按键请求”,每块音频约 46ms, 避免多任务同时访问 FATFS/I2S 的竞争。

  2. 非阻塞按键去抖 key_scan() 每次读取 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 / sample_rate 后再打开。

  5. FLAC 解码

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


8. 常见问题

现象 排查方向
屏幕全白 / 全黑 检查 XL9555 是否初始化成功、背光 LCD_BL 是否置 1;确认 sdkconfig.defaults 的 PSRAM 配置与板子一致
屏幕花屏 确认 SPI 时钟 60MHz 未超频,排线接触良好;检查 LCD_RGB_DATA_ENDIAN_BIG 与字节交换逻辑
显示 NO SD CARD SD 卡需 FAT32;my_spi_init() 必须在 spilcd_init() 之前;检查 CS=GPIO2
显示 PUT WAV IN /MUSIC SD 卡根目录需有 MUSIC 文件夹且包含 .wav / .flac 文件
显示 UNSUPPORTED WAV / UNSUPPORTED FILE WAV 需为 PCM(16/24/32bit);FLAC 需为标准 fLaC 或 Ogg FLAC;APE/MP3 需要额外解码库
FLAC 无法播放但 WAV 正常 检查串口日志 flac_dec 前缀的报错;确认组件已下载(managed_components/esphome__micro-flac)
FLAC 播放有断续 提高 CONFIG_SPIRAM_SPEED_80M、确认 PSRAM 已启用;或降低 SD 卡 SPI 时钟

9. 扩展建议

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

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

  1. 建立文件夹:例如 main/APP/clock/,放入 ui_clock.c / ui_clock.h, 参考 player/ui_player.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 */
    
  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_tick_cb() 里各加一个 case APP_MODE_CLOCK: 分支;同时把 main/CMakeLists.txt 的 SRC_DIRS / INCLUDE_DIRS 加上 "APP/clock"。

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

9.2 其他扩展

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

  • 中文界面:在 menuconfig → Component config → LVGL → Font usage 中启用 LV_FONT_SIMSUN_16_CJK(内置 1000 常用汉字),或使用 lv_font_conv 生成子集字体,然后把 ui_home.c / ui_player.c 中的英文文案替换为中文。

  • 图标换成图片:当前图标是「圆角渐变 + 内置字形」,如需真实图标, 可用 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);
        ...
    }
    
  • 专辑封面:LVGL 支持从文件系统读取 PNG/JPG(需开启 LV_USE_PNG / LV_USE_SJPG

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


10. 参考例程

  • 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)、播放器状态机与按键交互为本工程重新实现。

S
Description
audio player
Readme
157 KiB
0 Stars 1 Watchers 0 Forks
Languages
C 91.4%
C++ 7.7%
CMake 0.7%
Dockerfile 0.2%