启明云端WT9932S3项目-简易音乐播放器
本文将详细介绍如何使用ESP32 S3开发板结合DFPlayer Mini MP3模块制作一个简易音乐播放器,支持通过编码器选择歌曲、按键播放,OLED屏幕显示歌曲信息。教程包含元器件清单、硬件接线、软件配置、程序解析及调试步骤,代码可直接复用。

一、元器件清单
制作该音乐播放器需要以下元器件,清单已标注关键参数和用途:
-
启明云端WT9932S3:作为主控单元,负责逻辑控制、OLED显示和编码器交互
-

-
DFPlayer Mini MP3模块:音频播放核心,支持SD卡读取MP3文件
-

-
OLED屏幕(128x64 I2C接口):用于显示歌曲总数、当前选择歌曲等信息
-

-
旋转编码器(带按键):实现歌曲选择(顺时针/逆时针旋转)和播放控制(按下按键)
-
Micro SD卡(≤32GB):存储MP3格式音乐文件,需提前格式化为FAT32格式
-
扬声器:连接DFPlayer Mini的音频输出接口
-
杜邦线(公对母/公对公):用于硬件之间的接线
-
5V电源模块:为ESP32 S3和DFPlayer Mini供电(可选,也可通过开发板USB供电)
二、硬件接线图与说明
以下是各模块与ESP32 S3的接线对应关系,严格按照程序中的引脚定义接线,避免因引脚错误导致功能异常:
| 模块 | 引脚 | ESP32 S3 引脚 | 功能说明 |
|---|---|---|---|
| OLED屏幕(I2C) | SDA | 11 | I2C数据线 |
| SCL | 12 | I2C时钟线 | |
| 旋转编码器 | CLK | 38 | 编码器时钟引脚(上拉输入) |
| DT | 21 | 编码器数据引脚(上拉输入) | |
| SW | 47 | 编码器按键引脚(上拉输入) | |
| DFPlayer Mini | VCC | 5V | 模块供电(需5V电压) |
| GND | GND | 接地(与开发板共地) | |
| TX | 17(MP3_RX_PIN) | 模块串口发送→开发板串口接收(已对调) | |
| RX | 18(MP3_TX_PIN) | 模块串口接收→开发板串口发送(已对调) |
注意:DFPlayer Mini的TX/RX引脚与ESP32 S3的串口引脚已在程序中对调,接线时需严格对应表格,否则无法通信。编码器引脚需启用开发板内部上拉电阻(程序中已配置)。
三、软件准备
3.1 安装Arduino IDE与ESP32开发环境
-
下载并安装最新版Arduino IDE;
-
打开Arduino IDE,依次点击「文件」→「首选项」→「附加开发板管理器网址」,输入ESP32开发板地址:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json; -
点击「工具」→「开发板」→「开发板管理器」,搜索「ESP32」并安装最新版「esp32 by Espressif Systems」;
-
安装完成后,在「工具」→「开发板」中选择「ESP32 S3 Dev Module」。
3.2 安装所需库文件
程序依赖以下3个库,通过Arduino IDE的库管理器安装:
-
U8g2库:OLED屏幕驱动库,支持中文显示。搜索「U8g2」安装(作者:Oliver Kraus);
-
DFRobotDFPlayerMini库:DFPlayer Mini模块控制库。搜索「DFRobotDFPlayerMini」安装(作者:DFRobot);
-
Wire库:I2C通信库,一般随Arduino IDE默认安装,无需额外下载。
四、程序解析与修改
以下是对核心代码的解析,关键部分已标注注释,可根据实际硬件调整引脚定义(若接线与本文一致,无需修改)。
4.1 硬件配置宏定义
4.2 编码器与按键处理逻辑
程序采用纯轮询+硬锁定方式处理编码器,避免抖动和重复触发:
-
旋转检测:通过CLK引脚状态变化判断旋转方向,顺时针增加歌曲号,逆时针减少,超出范围时循环;
-
按键检测:按下编码器按键时,播放当前选中的歌曲号,带200ms防抖处理;
-
锁定机制:旋转时临时锁定编码器,确保单次旋转仅触发一次状态变化。
4.3 OLED显示功能
OLED屏幕支持两种显示模式:
-
无歌曲时:显示「未检测到SD卡歌曲」提示,提醒检查SD卡;
-
有歌曲时:显示标题「MP3播放器」、当前选择歌曲号、总歌曲数和操作提示「按编码器键播放」。
4.4 DFPlayer初始化与歌曲读取
`initDFPlayer()`函数实现MP3模块初始化,关键步骤包括:
-
初始化串口通信(波特率9600);
-
配置音量(默认30,范围0-30)和输出设备(SD卡);
-
自动读取SD卡中MP3文件夹的歌曲数量,失败时重试一次;
-
初始化失败时,OLED显示错误提示并进入死循环。
五、SD卡准备与调试运行
5.1 SD卡文件准备
-
将Micro SD卡格式化为FAT32格式(右键SD卡→格式化→选择FAT32);
-
在SD卡根目录创建名为「MP3」的文件夹(必须大写);
-
将MP3格式的音乐文件放入「MP3」文件夹,文件命名建议按顺序(如001.mp3、002.mp3...),方便对应歌曲号。
5.2 程序下载与调试
-
将ESP32 S3开发板通过USB线连接电脑;
-
在Arduino IDE中选择正确的「端口」(工具→端口);
-
复制完整代码到Arduino IDE,点击「上传」按钮(右箭头图标);
-
上传完成后,打开「串口监视器」(波特率设为115200),查看初始化信息: 成功时显示「自动检测到歌曲数:X」和「系统初始化完成!」;
-
失败时显示「DFPlayer初始化失败!」或「无法读取SD卡歌曲数!」,需检查接线和SD卡。
六、功能测试与常见问题
6.1 功能测试
-
旋转编码器:观察OLED屏幕,当前歌曲号应随旋转方向变化;
-
按下编码器按键:播放器应播放当前选中的歌曲;
-
歌曲切换:播放时旋转编码器,下次按键会播放新选中的歌曲。
6.2 常见问题解决
-
OLED不显示:检查I2C引脚接线(SCL=12,SDA=11),确认屏幕供电正常,尝试重新安装U8g2库;
-
编码器无响应:检查CLK/DT/SW引脚接线,确保程序中启用了INPUT_PULLUP模式;
-
DFPlayer不工作:确认TX/RX引脚对调接线,检查SD卡格式和MP3文件夹路径,尝试更换SD卡或模块;
-
无声音输出:检查扬声器与DFPlayer的音频接口连接,增大程序中的音量值(myPlayer.volume(30))。
七、完整代码
以下是可直接复用的完整代码,已包含所有功能实现:
#include <U8g2lib.h>
#include <Wire.h>
#include "DFRobotDFPlayerMini.h"
#include <HardwareSerial.h>
// ==================== 硬件配置 ====================
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, 12, 11, U8X8_PIN_NONE);
// 编码器配置(纯轮询,无中断/无库)
#define ENCODER_CLK 38
#define ENCODER_DT 21
#define ENCODER_SW 47
// 编码器防抖变量
byte lastCLK = HIGH;
byte lastDT = HIGH;
unsigned long encoderDebounce = 0;
const int encoderDebounceTime = 20; // 防抖时间
bool encoderLocked = false; // 旋转锁定(防止重复触发)
// MP3模块配置(RX/TX已对调)
#define MP3_RX_PIN 17
#define MP3_TX_PIN 18
HardwareSerial mp3Serial(1);
DFRobotDFPlayerMini myPlayer;
// ==================== 全局变量 ====================
int totalSongs = 0; // 自动读取总歌曲数
int currentSong = 1; // 当前选中的歌曲号(硬锁定)
// 按键消抖
bool btnPressed = false;
unsigned long btnPressTime = 0;
const unsigned long btnDebounce = 200;
// ==================== 编码器纯轮询检测(核心:硬锁定) ====================
void checkEncoder() {
// 无歌曲时不处理
if (totalSongs == 0) return;
// 防抖+锁定:仅当无操作且超时后才检测
if (encoderLocked || millis() - encoderDebounce < encoderDebounceTime) return;
// 读取当前引脚状态(上拉输入,默认HIGH)
byte currCLK = digitalRead(ENCODER_CLK);
byte currDT = digitalRead(ENCODER_DT);
// 仅当CLK引脚状态变化时处理(核心:只认CLK的有效跳变)
if (currCLK != lastCLK && currCLK == LOW) {
// 锁定编码器:处理完成前不再响应
encoderLocked = true;
encoderDebounce = millis();
// 判断旋转方向(仅一次有效切换)
if (currDT != currCLK) {
// 顺时针:下一首(循环)
currentSong = (currentSong >= totalSongs) ? 1 : currentSong + 1;
} else {
// 逆时针:上一首(循环)
currentSong = (currentSong <= 1) ? totalSongs : currentSong - 1;
}
// 强制刷新显示(确保数值正确)
updateOLEDDisplay();
Serial.print("当前选中:");
Serial.println(currentSong);
// 解锁延迟:确保单次旋转仅触发一次
delay(100);
encoderLocked = false;
}
// 更新最后状态
lastCLK = currCLK;
lastDT = currDT;
encoderDebounce = millis();
}
// ==================== 编码器按键检测(纯轮询) ====================
void checkEncoderButton() {
// 无歌曲时不处理
if (totalSongs == 0) return;
if (digitalRead(ENCODER_SW) == LOW && !btnPressed && millis() - btnPressTime > btnDebounce) {
btnPressed = true;
btnPressTime = millis();
// 强制播放当前锁定的歌曲号,绝不跳变
myPlayer.play(currentSong);
Serial.print("播放歌曲:");
Serial.println(currentSong);
updateOLEDDisplay();
} else if (digitalRead(ENCODER_SW) == HIGH && btnPressed) {
btnPressed = false;
btnPressTime = millis();
}
}
// ==================== OLED显示(支持自动读取歌曲数) ====================
void updateOLEDDisplay() {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_unifont_t_chinese1); // 中文支持字体
u8g2.setFontPosTop();
u8g2.enableUTF8Print();
// 标题
u8g2.setCursor(0, 0);
u8g2.print("MP3播放器");
// 分割线
u8g2.drawHLine(0, 15, 128);
// 无歌曲时提示
if (totalSongs == 0) {
u8g2.setCursor(0, 20);
u8g2.print("未检测到SD卡歌曲");
u8g2.setCursor(0, 35);
u8g2.print("请检查SD卡!");
} else {
// 有歌曲时显示正常信息
u8g2.setCursor(0, 20);
u8g2.print("当前选择:");
u8g2.print(currentSong);
u8g2.print(" 号歌曲");
u8g2.setCursor(0, 35);
u8g2.print("总歌曲数:");
u8g2.print(totalSongs);
u8g2.setCursor(0, 50);
u8g2.print("按编码器键播放");
}
} while (u8g2.nextPage());
}
// ==================== 初始化DFPlayer(自动读取歌曲数) ====================
bool initDFPlayer() {
mp3Serial.begin(9600, SERIAL_8N1, MP3_RX_PIN, MP3_TX_PIN);
delay(2000); // 超长初始化延时,确保SD卡识别
// 初始化播放器
if (!myPlayer.begin(mp3Serial)) {
Serial.println("DFPlayer初始化失败!");
return false;
}
// 基础配置
myPlayer.setTimeOut(1000); // 延长超时时间
myPlayer.volume(30);
myPlayer.outputDevice(DFPLAYER_DEVICE_SD);
delay(1000); // 等待SD卡挂载
// 自动读取SD卡中MP3文件夹的歌曲数量(核心)
totalSongs = myPlayer.readFileCounts(DFPLAYER_DEVICE_SD);
// 容错处理:读取失败时重试一次
if (totalSongs <= 0) {
Serial.println("首次读取歌曲数失败,重试...");
delay(1000);
totalSongs = myPlayer.readFileCounts(DFPLAYER_DEVICE_SD);
}
// 最终容错:仍失败则提示
if (totalSongs <= 0) {
Serial.println("无法读取SD卡歌曲数!");
totalSongs = 0;
} else {
Serial.print("自动检测到歌曲数:");
Serial.println(totalSongs);
// 初始化当前歌曲号(确保在有效范围)
currentSong = (totalSongs >= 1) ? 1 : 0;
}
Serial.println("MP3模块初始化完成");
return true;
}
// ==================== 初始化函数 ====================
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
// OLED初始化(强制清屏)
u8g2.setI2CAddress(0x3C*2);
u8g2.begin();
u8g2.enableUTF8Print();
u8g2.clearBuffer();
u8g2.sendBuffer();
// 编码器引脚初始化(强制上拉,硬件防抖)
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
pinMode(ENCODER_SW, INPUT_PULLUP);
// 初始读取编码器状态
lastCLK = digitalRead(ENCODER_CLK);
lastDT = digitalRead(ENCODER_DT);
encoderLocked = false;
// MP3初始化(自动读取歌曲数)
if (!initDFPlayer()) {
// 初始化失败时显示错误
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_unifont_t_chinese1);
u8g2.setCursor(0, 20);
u8g2.print("MP3模块初始化失败!");
u8g2.setCursor(0, 35);
u8g2.print("请检查接线!");
} while (u8g2.nextPage());
while (1) delay(1000);
}
// 强制初始化显示
updateOLEDDisplay();
Serial.println("系统初始化完成!");
}
// ==================== 主循环(极简,仅轮询) ====================
void loop() {
// 仅轮询编码器,无任何自动修改歌曲号的逻辑
checkEncoder();
// 仅轮询按键,播放当前锁定的歌曲号
checkEncoderButton();
// 极低延时,保证响应但不占用过多资源
delay(5);
}
更多推荐


所有评论(0)