用 ESP32S3 + PCM5102A 打造一个U盘音乐播放器


ESP32程序:
由官方usb程序修改而来
examples\peripherals\usb\host\msc\main\msc_example_main.c
使用了 arduino 库
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
board_upload.psram_size = 8MB
board_upload.flash_size = 16MB
board_build.arduino.partitions = default_16MB.csv
board_build.arduino.memory_type = qio_opi
build_flags = -DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
monitor_speed = 115200
lib_deps =
crankyoldgit/IRremoteESP8266@^2.8.1
firechip/usb-host-msc@1.1.4
adafruit/Adafruit SleepyDog Library@^1.7.2
esphome/ESP32-audioI2S@^2.3.0
这里使用 ESP32-audioI2S 发现不支持 c语言FILE *文件,所以在 Audip.cpp 开始
加了一些 :
uint32_t audiofilesize = 0;
String audiofilename;
FILE *audiofile;
bool audiofileseek1(int m_resumeFilePos)
{
return fseek(audiofile, m_resumeFilePos, SEEK_SET) == 0;
}
uint8_t audiofileread()
{
return fgetc(audiofile);
}
size_t audiofilereadBytes(char *buf, size_t len)
{
return fread(buf, 1, len, audiofile);
}
size_t audiofileread(void *buf, size_t len)
{
return fread(buf, 1, len, audiofile);
}
long audiofileposition()
{
if (audiofile == nullptr)
return -1;
return ftell(audiofile);
}
size_t get_file_size(FILE *f8)
{
if (f8 == nullptr)
return 0;
// 保存当前指针位置
long current_pos = ftell(f8);
if (current_pos == -1)
return 0;
// 移动到文件末尾获取大小
fseek(f8, 0, SEEK_END);
size_t size = ftell(f8);
// 恢复指针位置
fseek(f8, current_pos, SEEK_SET);
return size;
}
bool Audio::connecttoFS(const char *path, int32_t fileStartPos)
{
if (!path)
{ // guard
printProcessLog(AUDIOLOG_PATH_IS_NULL);
return false;
}
xSemaphoreTake(mutex_playAudioData, portMAX_DELAY); // #3
m_fileStartPos = fileStartPos;
setDefaults(); // free buffers an set defaults
char *audioPath = (char *)__malloc_heap_psram(strlen(path) + 2);
if (!audioPath)
{
printProcessLog(AUDIOLOG_OUT_OF_MEMORY);
xSemaphoreGive(mutex_playAudioData);
return false;
}
if (path[0] == '/')
{
strcpy(audioPath, path);
}
else
{
audioPath[0] = '/';
strcpy(audioPath + 1, path);
}
audiofile = fopen(audioPath, "rb"); // #86
AUDIO_INFO("Reading file: \"%s\"", audioPath);
if (!audiofile)
{
printProcessLog(AUDIOLOG_FILE_READ_ERR, audioPath);
free(audioPath);
xSemaphoreGive(mutex_playAudioData);
return false;
}
setDatamode(AUDIO_LOCALFILE);
m_fileSize = audiofilesize = get_file_size(audiofile); // TEST loop
audiofilename = audioPath;
char *afn = NULL; // audioFileName
afn = strdup(audioPath);
uint8_t dotPos = lastIndexOf(afn, ".");
for (uint8_t i = dotPos + 1; i < strlen(afn); i++)
{
afn[i] = toLowerCase(afn[i]);
}
if (endsWith(afn, ".mp3"))
m_codec = CODEC_MP3; // m_codec is by default CODEC_NONE
if (endsWith(afn, ".m4a"))
m_codec = CODEC_M4A;
if (endsWith(afn, ".aac"))
m_codec = CODEC_AAC;
if (endsWith(afn, ".wav"))
m_codec = CODEC_WAV;
if (endsWith(afn, ".flac"))
m_codec = CODEC_FLAC;
if (endsWith(afn, ".opus"))
m_codec = CODEC_OPUS;
if (endsWith(afn, ".ogg"))
m_codec = CODEC_OGG;
if (endsWith(afn, ".oga"))
m_codec = CODEC_OGG;
if (m_codec == CODEC_NONE)
AUDIO_INFO("The %s format is not supported", afn + dotPos);
if (afn)
{
free(afn);
afn = NULL;
}
free(audioPath);
bool ret = initializeDecoder();
if (ret)
m_f_running = true;
else
fclose(audiofile);
xSemaphoreGive(mutex_playAudioData);
return ret;
}
凡是 出现FS 的方法 都替换 比如audiofile.size() 替换 为audiofilesize
audiofile.name() 替换为 audiofilename
audiofile.close() 替换为 fclose(audioeile)
等等等..............................................
大神们,还有什么好办法吗
main.cpp
#include <Arduino.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include "Audio.h"
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRutils.h>
#include "esp_random.h"
#include "bootloader_random.h"
#include "usb/usb_host.h"
#include "msc_host.h"
#include "msc_host_vfs.h"
#include <Adafruit_SleepyDog.h>
#include "esp_sleep.h"
// ===================== 配置定义 =====================
// 音频核心配置
#define AUDIO_TASK_STACK 16384 // 音频任务堆栈
#define AUDIO_TASK_PRIO 10 // 音频任务优先级
#define AUDIO_TASK_CORE 0 // 音频任务运行核心
#define AUDIO_BUFFER_SIZE 64 // 音频缓存大小
#define PSRAM_BUFFER_SIZE 524288 // PSRAM缓存大小
#define AUDIO_TASK_DELAY 5 // 音频任务延迟
// 硬件引脚定义
#define IR_RX 2 // 红外接收引脚
#define I2S_BCLK 8 // I2S时钟引脚
#define I2S_DOUT 17 // I2S数据输出引脚
#define I2S_LRC 18 // I2S左右声道引脚
// USB相关定义
#define MNT_PATH "/usb" // USB挂载路径
const String LIST_CACHE_FILE = "/usb/audio_list.cache"; // 音频列表缓存文件
// 状态枚举(统一管理,避免重复定义)
enum UsbEvent
{
USB_EVENT_DISCONNECTED = 0,
USB_EVENT_CONNECTED = 1,
USB_EVENT_QUIT = 2
};
enum MscEvent
{
MSC_EVENT_CONNECTED = 0,
MSC_EVENT_DISCONNECTED = 1
};
// ===================== 全局变量 =====================
IRrecv IRR(IR_RX);
decode_results results;
Audio audio;
// 音频相关
bool audioisMusicFinished = false;
String currentTrack = "";
int trackIndex = 0;
bool isPlaying = false;
std::vector<String> trackList;
int yl = 2; // 音量
// USB相关
bool usbMounted = false;
uint8_t usbaddress = 0;
uint8_t usb_event_id = 5; // USB事件标识:5=无事件,1=连接,0=断开,2=退出
msc_host_device_handle_t msc_device = NULL;
msc_host_vfs_handle_t vfs_handle = NULL;
bool dev_present = false;
unsigned long shutdown_ms = millis() + 1000 * 60 * 10;
// ===================== USB事件回调 =====================
static void msc_event_cb(const msc_host_event_t *event, void *arg)
{
if (event->event == MSC_EVENT_CONNECTED)
{
Serial.println("MSC device connected");
usbaddress = event->device.address;
usb_event_id = USB_EVENT_CONNECTED; // 标记USB连接事件
}
else if (event->event == MSC_EVENT_DISCONNECTED)
{
Serial.println("MSC device disconnected");
usb_event_id = USB_EVENT_DISCONNECTED; // 标记USB断开事件
}
}
// ===================== USB任务 =====================
static void usb_task(void *args)
{
printf("usb_task running on core: %d\n", xPortGetCoreID());
// 初始化USB主机
const usb_host_config_t host_config = {.intr_flags = ESP_INTR_FLAG_LEVEL1};
ESP_ERROR_CHECK(usb_host_install(&host_config));
// 初始化MSC主机驱动
const msc_host_driver_config_t msc_config = {
.create_backround_task = true,
.task_priority = 1,
.stack_size = 4096,
.core_id = 1,
.callback = msc_event_cb,
};
ESP_ERROR_CHECK(msc_host_install(&msc_config));
bool has_clients = true;
while (true)
{
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// 释放无客户端的设备
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
{
has_clients = false;
if (usb_host_device_free_all() == ESP_OK)
break;
}
// 所有设备释放完成且无客户端时退出
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE && !has_clients)
break;
vTaskDelay(100);
}
vTaskDelay(10); // 给客户端卸载时间
Serial.println("Deinitializing USB");
ESP_ERROR_CHECK(usb_host_uninstall());
vTaskDelete(NULL);
}
// ===================== USB初始化 =====================
void usbinit()
{
printf("usbinit running on core: %d\n", xPortGetCoreID());
// 创建USB任务
BaseType_t task_created = xTaskCreatePinnedToCore(
usb_task, "usb_task", 4096, NULL, 2, NULL, 1);
assert(task_created);
// 等待USB设备连接(初始化阶段)
int js = 0;
Serial.println("Waiting for USB flash drive to be connected");
while (1)
{
// 处理USB连接事件
if (usb_event_id == USB_EVENT_CONNECTED)
{
usb_event_id = 5; // 重置事件标识
if (!dev_present)
{
dev_present = true;
// 挂载USB设备
ESP_ERROR_CHECK(msc_host_install_device(usbaddress, &msc_device));
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 8192,
};
ESP_ERROR_CHECK(msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle));
usbMounted = true;
Serial.println("USB flash drive mounted successfully");
break;
}
}
// 超时退出(避免无限等待)
if (js++ > 10)
break;
vTaskDelay(500);
}
}
// ===================== 工具函数 =====================
// 生成指定范围随机数
uint32_t get_random_in_range(uint32_t min, uint32_t max)
{
return min + (esp_random() % (max - min + 1));
}
// 过滤音频文件
bool isAudioFile(const String &fileName)
{
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex == -1)
return false;
String ext = fileName.substring(dotIndex + 1);
ext.toLowerCase();
return (ext == "mp3" || ext == "wav" || ext == "flac");
}
// 遍历目录
bool traverseDirectory(const String &dirPath, bool (*callback)(const String &, bool))
{
DIR *dir = opendir(dirPath.c_str());
if (!dir)
{
Serial.printf("打开目录失败:%s\n", dirPath.c_str());
return false;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
String fullPath = dirPath + "/" + entry->d_name;
if (fullPath.startsWith("//"))
fullPath = fullPath.substring(1);
bool isDir = (entry->d_type == DT_DIR);
if (!callback(fullPath, isDir))
{
closedir(dir);
return false;
}
}
closedir(dir);
return true;
}
// 保存音频列表到缓存
bool saveAudioListToCache()
{
if (!usbMounted)
return false;
FILE *cacheFile = fopen(LIST_CACHE_FILE.c_str(), "w");
if (!cacheFile)
{
Serial.printf("创建缓存文件失败:%s\n", LIST_CACHE_FILE.c_str());
return false;
}
fprintf(cacheFile, "%d\n", trackList.size());
for (size_t i = 0; i < trackList.size(); i++)
{
String fileName = trackList[i];
if (!fileName.startsWith("/"))
fileName = "/" + fileName;
fprintf(cacheFile, "%s\n", fileName.c_str());
}
fclose(cacheFile);
Serial.printf("音频列表已缓存到USB,共%d个文件\n", trackList.size());
return true;
}
// 从缓存加载音频列表
bool loadAudioListFromCache()
{
if (!usbMounted)
return false;
FILE *cacheFile = fopen(LIST_CACHE_FILE.c_str(), "r");
if (!cacheFile)
{
Serial.println("缓存文件不存在,需要扫描USB");
return false;
}
trackList.clear();
int fileCount = 0;
if (fscanf(cacheFile, "%d\n", &fileCount) != 1)
{
Serial.println("读取缓存文件数量失败");
fclose(cacheFile);
return false;
}
char buf[256];
for (int i = 0; i < fileCount; i++)
{
if (fgets(buf, sizeof(buf), cacheFile) == NULL)
break;
String fileName = String(buf);
fileName.trim();
if (fileName.length() > 0)
{
if (!fileName.startsWith("/"))
fileName = "/" + fileName;
trackList.push_back(fileName);
}
}
fclose(cacheFile);
Serial.printf("从USB缓存加载音频列表,共%d个文件\n", trackList.size());
return true;
}
// 扫描音频文件(优先加载缓存)
void scanAudioFiles()
{
if (!usbMounted)
{
trackList.clear();
return;
}
// 先尝试加载缓存
if (loadAudioListFromCache())
return;
// 缓存加载失败,扫描USB
trackList.clear();
traverseDirectory("/usb", [](const String &fullPath, bool isDir) -> bool
{
if (!isDir && isAudioFile(fullPath))
{
String fileName = fullPath;
if (!fileName.startsWith("/")) fileName = "/" + fileName;
trackList.push_back(fileName);
Serial.printf("发现音频文件:%s\n", fileName.c_str());
}
return true; });
saveAudioListToCache();
Serial.printf("扫描USB完成,共%d个音频文件\n", trackList.size());
}
// 检查文件是否存在
bool fileExists(const String &path)
{
struct stat st;
return (stat(path.c_str(), &st) == 0);
}
// ===================== 音频播放控制 =====================
bool playAudioFile(const String &filePath)
{
if (!usbMounted || trackList.empty())
return false;
String realFilePath = filePath;
if (!realFilePath.startsWith("/"))
realFilePath = "/" + realFilePath;
if (!fileExists(realFilePath))
{
Serial.printf("文件不存在:%s\n", realFilePath.c_str());
return false;
}
// 停止当前播放
audio.stopSong();
delay(200);
// 连接并播放文件
bool result = audio.connecttoFS(realFilePath.c_str());
isPlaying = result;
currentTrack = realFilePath;
return result;
}
// 随机播放
void sjbf()
{
if (!trackList.empty())
{
int sjs = get_random_in_range(0, trackList.size() - 1);
playAudioFile(trackList[sjs]);
}
else
{
isPlaying = false;
currentTrack = "";
}
}
// 音量增加
void yljia()
{
if (yl < 21)
yl++;
audio.setVolume(yl);
Serial.printf("音量:%d\n", yl);
}
// 音量减少
void yljian()
{
if (yl > 0)
yl--;
audio.setVolume(yl);
Serial.printf("音量:%d\n", yl);
}
// ===================== 红外遥控处理 =====================
void dogogo()
{
if (IRR.decode(&results))
{
serialPrintUint64(results.value, HEX);
Serial.println();
switch (results.value)
{
case 0xFFB04F: // 重启
esp_restart();
break;
case 0xFF02FD: // 播放指定歌曲
audio.connecttoFS("/usb/赵静-故乡情.mp3");
break;
case 0xFF18E7: // 音量+
yljia();
break;
case 0xFF4AB5: // 音量-
yljian();
break;
case 0xFF5AA5: // 下一曲/随机播放
sjbf();
break;
// 其他未使用的按键,保留但不处理
case 0xFFE21D:
case 0xFFA25D:
case 0xFF6897:
case 0xFF38C7:
audio.pauseResume();
break;
case 0xFF10EF:
case 0xFF9867:
break;
}
IRR.resume();
}
}
// ===================== 音频任务 =====================
void audioTask(void *args)
{
printf("audioTask running on core: %d\n", xPortGetCoreID());
// 音频初始化
audio.setBufsize(AUDIO_BUFFER_SIZE, PSRAM_BUFFER_SIZE);
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(yl);
// audio.setCallback(audio_info, audio_eof_mp3); // 注册回调(修复播放完成检测)
while (1)
{
audio.loop();
// 播放完成自动切换
if (isPlaying && audioisMusicFinished)
{
audioisMusicFinished = false;
Serial.println("当前曲目播放完成,自动切换下一曲");
sjbf();
}
Watchdog.reset();
portYIELD();
}
}
// ===================== 热插拔处理函数 =====================
void handleUsbHotplug()
{
if (!audio.isRunning())
{
if (millis() > shutdown_ms)
esp_deep_sleep_start();
}
else
shutdown_ms = millis() + 1000 * 60 * 10;
// 处理USB连接事件
if (usb_event_id == USB_EVENT_CONNECTED)
{
usb_event_id = 5; // 重置事件标识
if (!dev_present)
{
dev_present = true;
// 挂载新连接的USB设备
ESP_ERROR_CHECK(msc_host_install_device(usbaddress, &msc_device));
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 8192,
};
ESP_ERROR_CHECK(msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle));
usbMounted = true;
Serial.println("USB device mounted (hotplug)");
// 重新扫描音频文件
scanAudioFiles();
// 如果当前没有播放,自动开始播放
if (!isPlaying && !trackList.empty())
{
sjbf();
}
}
}
// 处理USB断开事件
else if (usb_event_id == USB_EVENT_DISCONNECTED)
{
usb_event_id = 5; // 重置事件标识
if (dev_present)
{
dev_present = false;
usbMounted = false;
// 停止当前播放
if (isPlaying)
{
audio.stopSong();
isPlaying = false;
currentTrack = "";
}
// 卸载USB设备
if (vfs_handle)
{
ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle));
vfs_handle = NULL;
}
if (msc_device)
{
ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device));
msc_device = NULL;
}
// 清空曲目列表
trackList.clear();
Serial.println("USB device unmounted (hotplug)");
}
}
// 处理退出事件
else if (usb_event_id == USB_EVENT_QUIT)
{
usb_event_id = 5;
ESP_ERROR_CHECK(msc_host_uninstall());
}
}
// ===================== 回调函数 =====================
// optional
void audio_info(const char *info)
{
Serial.print("info ");
Serial.println(info);
}
void audio_id3data(const char *info)
{ // id3 metadata
Serial.print("id3data ");
Serial.println(info);
}
void audio_eof_mp3(const char *info)
{ // end of file
Serial.print("eof_mp3 ");
Serial.println(info);
audioisMusicFinished = true;
}
void audio_showstation(const char *info)
{
Serial.print("station ");
Serial.println(info);
}
void audio_showstreamtitle(const char *info)
{
Serial.print("streamtitle ");
Serial.println(info);
}
void audio_bitrate(const char *info)
{
Serial.print("bitrate ");
Serial.println(info);
}
void audio_commercial(const char *info)
{ // duration in sec
Serial.print("commercial ");
Serial.println(info);
}
void audio_icyurl(const char *info)
{ // homepage
Serial.print("icyurl ");
Serial.println(info);
}
void audio_lasthost(const char *info)
{ // stream URL played
Serial.print("lasthost ");
Serial.println(info);
}
// ===================== 初始化 =====================
void setup()
{
delay(500);
Serial.begin(115200);
while (!Serial)
delay(10);
// 初始化红外接收
IRR.enableIRIn();
printf("setup running on core: %d\n", xPortGetCoreID());
delay(100);
// 初始化USB
usbinit();
Serial.println("\n===== usb启动 =====");
delay(500);
// 创建音频任务
Serial.println("\n===== ESP32 音频播放器启动 =====");
BaseType_t audio_task = xTaskCreatePinnedToCore(
audioTask, "AudioTask", AUDIO_TASK_STACK,
NULL, AUDIO_TASK_PRIO, NULL, AUDIO_TASK_CORE);
assert(audio_task);
Serial.printf("音频任务已启动(核心0)\n");
// 扫描音频文件
scanAudioFiles();
Serial.println("\n===== 播放列表加载完毕 =====");
// 开始播放
sjbf();
// 启用看门狗
int countdownMS = Watchdog.enable(10000);
Serial.print("Enabled the watchdog with max countdown of ");
Serial.print(countdownMS, DEC);
Serial.println(" milliseconds!");
Watchdog.reset();
// esp_sleep_enable_ext0_wakeup(GPIO_NUM_2, 0);
}
// ===================== 主循环 =====================
void loop()
{
Watchdog.reset();
// 处理红外遥控
dogogo();
// 处理USB热插拔(核心修复)
handleUsbHotplug();
delay(100);
}
更多推荐


所有评论(0)