2026寒假练-基于xiao esp32s3实现语音点歌音乐盒
要求实现:
1. 预置不少于 3 段旋律序列,通过扬声器或蜂鸣器播放;
2. 如若使用蜂鸣器,需要使用RGB 灯带按节拍或音符强弱做律动灯效;若使用扬声器,无需使用RGB灯。
3. 麦克风关键词识别实现播放、暂停、下一首、上一首、音量大、音量小等至少 3 条指令,注意指令必须包含切换播放曲目。语音识别可使用EDGE IMPULSE、ESP-SR或上位机等部署相关模型;
4. OLED 显示曲目编号、播放进度百分比、当前音量;
项目介绍:
1.使用xiao esp32s3承载代码的运行。
2.使用蜂鸣器完成音乐的播放。
3.使用RGB灯带根据音符强度产生律动灯效。
4.使用旋转电位器完成音量的控制。
5.在pc端使用麦克风及语音识别平台完成语音识别。
6.通过数据线完成串口通信
所用硬件:
esp32s3-sense,RGB灯带,蜂鸣器,oled模块,旋转电位计。
代码架构:
pc端负责语音识别,esp32负责音乐播放,两者通过usb串口通讯。
设计思路:
经分析,该项目核心解决问题是旋律的播放和语音识别与控制,考虑到在pc端实现语音识别更为方便,因此我才用分离式的功能设计,将语音识别部分内容部署在pc端,使esp32端专注于音乐播放,通过串口获取pc端的命令,经查阅资料,esp32部分代码决定在vscode上的platformio平台上以auduino的框架下进行编写,而pc端则采用更为方便的python语言进行编写。
在音乐播放方面,考虑到本方案对延迟的要求较低,因此短时间的阻塞式播放对功能实现影响不大,因此采用软件pwm波的方式控制音调以及音量大小,实现方案简易,减轻了代码复杂度,在各个功能的实现以简单且不影响实际使用为最优解。
本项目采用旋转电位器来控制音量的大小,在设计过程中发现读取的数值会在一定范围内波动,因此在读取时设计读取一段时间的读数进行平均后计算得到当前的音量。为一定程度上优化系统性能,音量控制采用中断的方式控制,当音量变化小于一定值时,不发生中断,一方面防止由于读数本身的波动时音量存在波动,另一方面也极大的节省了系统资源,优化程序性能。
在语音识别方面,第一次尝试时发现在edge impluse平台上训练出的模型效果不佳,因此改用使用目前较为成熟的大平台的语音识别模型(如百度ai开放平台),在pc端通过api接口将数据发送到这些语音识别平台上,让这些平台处理后获得识别的文字,处理过后通过串口发送命令,完成语音识别。
调试软件及使用的编程语言说明、软件流程图:
使用的编程语言说明:
本项目是处于vscode上的platformio平台上以auduino的框架下进行编写,语言为c语言
软件流程简述:
软件流程为初始化——循环————检查串口是否有命令,若有则执行命令——打印信息以及显示灯带————播放当前音符并下次将指针指向下一个音符,若该歌曲播放完毕,则指针指向下一首的歌曲的第一个音符————循环 。
同时若有音量改变则通过中断改变全局音量大小。
项目软件流程图如下:

硬件框图:
关键代码说明:
主程序:
void loop() {
// 处理串口接收的语音命令
while (Serial.available() > 0) {
char c = Serial.read();
// 检查命令结束符(换行或回车)
if (c == '\n' || c == '\r') {
if (serialBuffer.length() > 0) {
handleSerialCommand(serialBuffer);
serialBuffer = ""; // 清空缓冲区
}
} else {
// 累积字符到缓冲区
serialBuffer += c;
}
}
if (songs_state){
handlePotentiometer();
show_volume_level(song_list[songs_curid].melody[song_list[songs_curid].current_tick]);
show_inform(song_list[songs_curid], volume_level+volume_level_alter);
play_song(song_list[songs_curid], volume_level+volume_level_alter, &songs_curid, &(song_list[songs_curid].current_tick));
} else {
delay(100);
show_inform(song_list[songs_curid], volume_level+volume_level_alter);
}
}
这个是主程序,从串口读取命令,若处于播放状态,则会根据目前状态先后执行灯带显示,oled显示以及音乐播放,相关的详细代码集成到了函数中,提高逻辑清晰度,串口的设计使电脑端输入的指令能够有效的被esp32收到。
音乐播放程序:
void play_song(songs now_song,int volume_level_param,int *songs_curid,int *current_tick){
if (*current_tick < now_song.total_time / now_song.rhythm) {
toneWithVolume(now_song.melody[*current_tick], now_song.rhythm, volume_level_param);
show_inform(now_song, volume_level_param);
*current_tick += 1;
} else {
*current_tick = 0;
*songs_curid +=1;
if (*songs_curid >= sizeof(song_list)/sizeof(song_list[0])){
*songs_curid = 0;
}
}
}
void toneWithVolume(int frequency, long duration, int volume) {
if (volume == 0) {
digitalWrite(buzzerPin, LOW);
delay(duration);
return;
}
if (frequency < 20 || frequency > 5000 || duration <= 0) {
return;
}
int duty = 0;
if (volume <= 5
){
duty = 0;
}
else if (volume < 32) { duty = 0;
} else if (volume < 64) { duty = 1;
} else if (volume < 96) { duty = 4;
} else if (volume < 128) { duty = 10;
} else if (volume < 192) { duty = 50;
} else { duty = 80;
}
该为音乐播放程序,采用音符分别播放的方法,可以自由设定每个音符的播放时长,
以适应不同节奏的音乐风格,使用软件pwm控制音量大小,每个音符播放后下次自动播放下个音符,
若歌曲播放完则进入下一首歌,歌曲结构为音符的音阶的数组,每次播放完索引加1,
实现歌曲的播放,持续时间为预设的每段旋律的音符持续时间。
功能展示:
oled显示信息展示:

功能:
可以播放预先给定的音乐,并可以通过旋转电位器进行音量的调节,同时还支持语音识别功能,能够识别:“播放”“暂停”“上一首”“下一首”等命令,实现语音点歌的效果。
遇到的难题以及解决方案:
我在设计的过程中遇到了两个较大的难题,
其一是音乐播放的方式,刚开始尝试过硬件pwm波形以及状态机的方式,但最后发现效果不佳,最后采用了软件pwm的方法,同时音量大小也十分棘手,因为人耳对音乐大小的感觉并不是线性的,因此单纯用数学公式并不好拟合,我最后采用了每个音量大小大区间的调节参数,以实现较为符合人类感知的音量大小。这花费了我大量的时间。
其二是串口通信,在其他功能已经完备之后,我发现我的串口通讯异常,为此,我搜寻了很多资料,以及询问ai,都找不出异常的原因,我十分苦恼,因为这意味这无法与上位机进行有效通讯。后来我静下心来去认真阅读官网资料,最后发现是由于一个系统设置板子选型错误,这是由于我在项目初期在b站上找资料时看到的错误配置,将这个配置纠正后,所有问题迎刃而解了。
项目心得:
这是我第一次参加硬禾科技的活动,最开始我选择这个项目有是因为觉得这个项目十分有趣且较为轻松,但最后上手了之后才发现有趣背后也蕴含了许多困难,我的新鲜劲早就被困难消磨得一干二净,在这过程中我学习到了非常多新知识,这是我第一次接触了这种较为复杂功能指导向的项目并成功落地,尽管过程中充满了许许多多的困难,但是我最后把这个项目完成后,我认为我自己还是从中受益匪浅,为一次具有价值的冒险。也着实锻炼的我的耐心,在实际项目里,往往很难一眼看得到头,更需要操作者的冷静与智慧。嵌入式开发里没有神话,所有问题都有其根源,都可以找到并解决他,从百思不得其解到发现问题有种感觉荒唐的恍然大悟,这也许就是嵌入式开发的魅力吧。
