1. 项目缘起:为什么选择Az-Nano V3和DFPlayer Mini做声音板?
如果你玩过一些互动装置、Cosplay道具,或者想给家里的智能设备加点有趣的音效,一个能随时播放特定声音的“声音板”(Soundboard)绝对是个好玩的点子。市面上的成品要么功能单一,要么价格不菲。自己动手做一个,成本可控、功能自定义,乐趣和成就感都翻倍。这次我选择的方案是Az-Nano V3搭配DFPlayer Mini,这几乎是目前DIY声音板领域里,在成本、易用性和性能上最均衡的“黄金搭档”了。
先说核心组件。Az-Nano V3是一款基于ESP32-S3的开发板,它比我们熟知的ESP32性能更强,自带Wi-Fi和蓝牙,双核处理器,内存也更大。这意味着它不仅能处理简单的按钮触发,未来如果你想通过手机App、网页甚至语音来控制声音播放,都有充足的硬件基础,扩展性极佳。而DFPlayer Mini则是一个专为MP3/WAV播放设计的模块,价格低廉,接口简单,通过串口指令就能轻松控制,音质对于大多数应用场景也完全够用。最关键的是,它可以直接驱动小喇叭,省去了额外的功放电路,对新手非常友好。
这个组合解决了几个关键痛点:一是摆脱了电脑或手机的束缚,做成一个独立的、可电池供电的便携设备;二是避免了使用SD卡模块+音频解码库的复杂方案,DFPlayer Mini把解码和播放的脏活累活都干了,我们只需要告诉它“播放第几首”就行;三是为物联网功能留足了空间,一个带网络功能的声效盒子,想象空间就大了去了。接下来,我就带你从零开始,一步步实现这个功能完善、稳定可靠的声音板。
2. 硬件清单与电路连接:一张图看懂所有接线
动手之前,先把需要的家伙事儿备齐。除了核心的两大件,一些外围元件决定了项目的完成度和用户体验。
硬件清单:
- 主控板:Az-Nano V3 (ESP32-S3) x1
- 音频模块:DFPlayer Mini MP3模块 x1
- 存储:Micro SD卡 (建议Class 10, 容量不超过32GB) x1
- 音频输出:小型扬声器 (8Ω 1W-3W) 或 3.5mm耳机插座模块 x1
- 输入设备:轻触按键开关 (6x6mm或类似) x N个 (根据你需要的音效数量决定,本次以4个为例)
- 上拉电阻:10kΩ 电阻 x N个 (与按键数量一致)
- 电源:
- 方案A (调试/固定使用): USB-C 数据线 (为Az-Nano供电,DFPlayer可从Az-Nano取电)
- 方案B (便携使用): 3.7V锂电池 (如18650) + 充放电保护板,或 5V移动电源
- 其他:杜邦线 (公对公、公对母)、面包板 (用于原型搭建)、电烙铁及焊锡 (最终成品焊接用)
电路连接详解:
连接的核心是让Az-Nano V3能和DFPlayer Mini“对话”(串口通信),并检测按键动作。下图是接线示意图的精简描述:
Az-Nano V3 (GPIO引脚) <--> DFPlayer Mini GPIO16 (RX) <--> TX GPIO17 (TX) <--> RX 5V 或 3.3V <--> VCC (注意:DFPlayer Mini需5V供电以获得最佳音量) GND <--> GND Az-Nano V3 (GPIO引脚) <--> 按键电路 (以按键1为例) GPIO14 (或其他) <--> 按键一脚 GND <--> 按键另一脚 (通过10kΩ上拉电阻连接到3.3V)具体接线步骤与原理:
供电部分:这是第一个容易出问题的地方。Az-Nano V3的USB口输入是5V,板载稳压器会将其转为3.3V供芯片使用。DFPlayer Mini模块虽然标称工作电压是3.2-5V,但实测在5V供电下,其驱动能力和音量表现远好于3.3V。因此,最佳实践是:将外部5V电源(如USB口的5V,或移动电源的5V)正极同时连接到Az-Nano V3的
5V引脚和DFPlayer Mini的VCC,负极共地。这样两者都获得稳定的5V输入。如果你只用Az-Nano的3.3V引脚给DFPlayer供电,可能会发现声音很小甚至不工作。串口通信:我们需要使用Az-Nano V3的一个硬件串口(UART)与DFPlayer Mini通信。ESP32-S3有多个UART,我们通常使用
UART1(RX: GPIO16, TX: GPIO17)或UART2。注意,连接时要交叉:Az-Nano的RX(GPIO16) 接DFPlayer的TX;Az-Nano的TX(GPIO17) 接DFPlayer的RX。DFPlayer的RX引脚串联一个1KΩ电阻可以起到保护作用,但不是必须。按键输入:每个按键需要占用一个GPIO。我们将其配置为
INPUT_PULLUP模式(即使用Az-Nano内部的上拉电阻),那么按键的另一端直接接地即可。当按键按下时,GPIO读到低电平(LOW);松开时,内部上拉电阻将其拉至高电平(HIGH)。这种接法最简洁。如果你使用外部10kΩ上拉电阻到3.3V,则GPIO模式应设为INPUT。我推荐直接用内部上拉,省事又省空间。音频输出:DFPlayer Mini有两条音频输出线:
SPK_1和SPK_2,可以直接连接一个4Ω或8Ω的喇叭(注意正负极)。如果你希望音质更好或驱动更大功率喇叭,可以使用DAC_L和DAC_R输出到功放模块。对于小体积项目,直接接个小喇叭最简单。
注意:在焊接或插接时,务必确保电源正负极没有接反,尤其是给DFPlayer Mini供电时,接反极易烧毁模块。建议先在不接喇叭的情况下,通电测试串口通信是否正常。
3. 软件环境搭建与核心库解析
硬件连好了,接下来就是让大脑(程序)动起来。我们主要依赖一个非常优秀的Arduino库:DFRobotDFPlayerMini。
环境配置步骤:
安装Arduino IDE与ESP32支持:如果你还没做,首先去Arduino官网下载IDE。然后,在“文件”->“首选项”的“附加开发板管理器网址”中,添加ESP32的板支持网址:
https://espressif.github.io/arduino-esp32/package_esp32_index.json。接着,在“工具”->“开发板”->“开发板管理器”中,搜索并安装“esp32”(由Espressif Systems提供)。选择正确的开发板:安装完成后,在“工具”->“开发板”中选择“ESP32S3 Dev Module”。然后,在“工具”菜单中,进行以下关键设置:
USB CDC On Boot:Enabled(这允许通过串口监控输出)CPU Frequency: 240MHz (默认即可)Flash Size: 根据你的Az-Nano V3版本选择 (通常是16MB)Partition Scheme:Huge APP (3MB No OTA/1MB SPIFFS)(为程序留出足够空间)Upload Speed:921600Port: 选择你的Az-Nano V3连接的COM口
安装DFPlayer库:在Arduino IDE中,点击“项目”->“加载库”->“管理库”,搜索“
DFRobotDFPlayerMini”,找到并安装它。这个库封装了与DFPlayer Mini通信的所有底层指令,让我们可以用简单的函数调用来控制播放。
核心库函数与工作原理:
DFRobotDFPlayerMini库的核心是通过SoftwareSerial或HardwareSerial向DFPlayer Mini发送特定的串口指令包。指令包包含起始字节、版本、长度、指令、反馈、参数、校验和等。库帮我们处理了所有这些复杂的组包和校验工作。
我们需要掌握的几个关键对象和方法:
DFRobotDFPlayerMini myDFPlayer;: 创建一个播放器对象。myDFPlayer.begin(serial, isACK, doReset);: 初始化,其中serial是指定的串口对象(如Serial1),isACK决定是否等待模块应答(建议设为true以确保可靠性),doReset决定是否初始化时复位模块。myDFPlayer.volume(15);: 设置音量(0-30)。myDFPlayer.play(1);: 播放SD卡中编号为1的音频文件。myDFPlayer.pause();/myDFPlayer.start();: 暂停/继续播放。myDFPlayer.next();/myDFPlayer.previous();: 下一曲/上一曲(在顺序播放模式下有用)。myDFPlayer.enableLoopAll();: 开启全部循环(慎用,对于声音板通常不需要)。
库内部会处理与模块的握手、超时和错误处理。在代码中,我们通常在一个循环里调用myDFPlayer.loop();来让库处理后台的通信和状态维护。同时,我们可以通过myDFPlayer.available()来检查是否有从模块返回的状态信息(如播放完成),并通过myDFPlayer.readType()来读取类型,进行相应处理。
理解了这个基础,我们就能写出不仅能让它“响”,还能知道它“响没响完”、有没有出错的健壮代码了。
4. 音频文件准备与SD卡格式化:90%的问题出在这里
很多新手第一次做,硬件代码都没问题,但就是没声音,十有八九问题出在SD卡和音频文件上。DFPlayer Mini对存储介质和文件格式有比较“挑剔”的要求。
SD卡准备工作流程:
选择与格式化:首选正规品牌的Micro SD卡,容量建议4GB-32GB,速度Class 4或Class 10均可。第一步也是最重要的一步:将其格式化为FAT32文件系统。在Windows上,你可以用系统自带的格式化工具(分配单元大小选“默认”),如果卡大于32GB,Windows默认可能只提供exFAT选项,这时你需要使用第三方工具(如“guiformat”)强制格式化为FAT32。在Mac上,使用“磁盘工具”,选择“MS-DOS (FAT)”格式。格式化时,卷标可以留空或简单命名,不要用中文或特殊字符。
创建文件夹:在SD卡根目录下,必须创建一个名为
mp3的文件夹(全部小写)。DFPlayer Mini会固定在这个文件夹里寻找音频文件。这是它的硬性规定。音频文件命名与转换:
- 命名规则:将你的音效文件(如笑声、枪声、警报声)重命名为4位数字,从
0001开始,例如0001.mp3,0002.mp3,0003.mp3... 文件必须连续编号,即使中间有空的号码,模块也可能会去寻找,导致错误。 - 文件格式:虽然模块也支持WAV,但强烈建议使用MP3格式,并且是固定的44.1kHz采样率、128kbps比特率的MP3文件。这是兼容性最好的格式。你可以使用免费软件如
Audacity或在线转换工具,将任何音频文件统一转换为这个标准格式。 - 文件放置:将所有重命名好的
0001.mp3、0002.mp3等文件,直接放入mp3文件夹内。不要再在mp3文件夹内创建子文件夹。
- 命名规则:将你的音效文件(如笑声、枪声、警报声)重命名为4位数字,从
常见问题排查:
- 没声音:首先检查SD卡是否FAT32格式,是否有
mp3文件夹,文件名是否正确,音频格式是否符合要求。可以用myDFPlayer.readState()或myDFPlayer.readFileCounts()函数在串口监视器里打印状态,看看模块是否成功识别了SD卡和文件数量。 - 播放错误或杂音:通常是音频文件编码问题。确保转换时选择的是“恒定比特率(CBR)”而不是“可变比特率(VBR)”,并且是128kbps。有些模块对VBR支持不好。
- 只能播放一部分文件:检查文件名是否连续编号,中间没有断号。另外,SD卡接触不良也是常见原因,可以按压一下卡座或重新插拔。
实操心得:准备一张专门用于这个项目的SD卡,按照上述流程格式化并存放文件后,就尽量不要在其他设备上读写它,避免文件系统被意外更改或产生碎片。每次更新音效,都最好在电脑上完成整个“格式化-复制”流程,而不是直接删除或添加文件,这样最稳妥。
5. 核心代码实现:从基础触发到高级功能
掌握了硬件连接、库函数和文件准备,我们就可以开始编写代码了。代码将分为几个层次:基础的单次触发、防抖与状态管理、以及扩展的连续播放和网络控制思路。
5.1 基础按键触发代码框架
首先,我们实现最核心的功能:按下不同的按键,播放不同的音效。
#include "DFRobotDFPlayerMini.h" #include "HardwareSerial.h" // 硬件串口1引脚定义 (RX: GPIO16, TX: GPIO17) #define DFPLAYER_RX 16 #define DFPLAYER_TX 17 // 按键引脚定义 #define BUTTON_1 14 #define BUTTON_2 27 #define BUTTON_3 26 #define BUTTON_4 25 // 创建串口1对象和播放器对象 HardwareSerial dfplayerSerial(1); DFRobotDFPlayerMini myDFPlayer; // 按键状态变量 int buttonState1, buttonState2, buttonState3, buttonState4; int lastButtonState1 = HIGH, lastButtonState2 = HIGH, lastButtonState3 = HIGH, lastButtonState4 = HIGH; // 假设内部上拉,初始为高 void setup() { // 初始化调试串口 Serial.begin(115200); // 初始化按键引脚,启用内部上拉电阻 pinMode(BUTTON_1, INPUT_PULLUP); pinMode(BUTTON_2, INPUT_PULLUP); pinMode(BUTTON_3, INPUT_PULLUP); pinMode(BUTTON_4, INPUT_PULLUP); // 初始化与DFPlayer Mini通信的串口 dfplayerSerial.begin(9600, SERIAL_8N1, DFPLAYER_RX, DFPLAYER_TX); Serial.println("正在初始化DFPlayer..."); // 初始化DFPlayer模块 if (!myDFPlayer.begin(dfplayerSerial, true, false)) { // 使用ACK,不自动复位 Serial.println("DFPlayer初始化失败!请检查:"); Serial.println("1. 接线是否正确 (TX/RX是否交叉?)"); Serial.println("2. SD卡是否插入且格式正确?"); Serial.println("3. 供电是否充足(建议5V)?"); while (true); // 卡住 } Serial.println("DFPlayer初始化成功!"); // 设置初始音量 (0~30) myDFPlayer.volume(20); // 可选:设置EQ模式,0-5分别对应Normal/Pop/Rock/Jazz/Classic/Base myDFPlayer.EQ(DFPLAYER_EQ_NORMAL); } void loop() { // 必须调用,用于处理库的内部状态和消息 myDFPlayer.loop(); // 读取当前按键状态 buttonState1 = digitalRead(BUTTON_1); buttonState2 = digitalRead(BUTTON_2); buttonState3 = digitalRead(BUTTON_3); buttonState4 = digitalRead(BUTTON_4); // 检测按键1的下降沿(按下动作) if (buttonState1 == LOW && lastButtonState1 == HIGH) { Serial.println("按键1按下,播放音效0001.mp3"); myDFPlayer.play(1); // 播放SD卡mp3文件夹下的0001.mp3 delay(50); // 简单防抖延时 } lastButtonState1 = buttonState1; // 同理处理其他按键... if (buttonState2 == LOW && lastButtonState2 == HIGH) { Serial.println("按键2按下,播放音效0002.mp3"); myDFPlayer.play(2); delay(50); } lastButtonState2 = buttonState2; if (buttonState3 == LOW && lastButtonState3 == HIGH) { Serial.println("按键3按下,播放音效0003.mp3"); myDFPlayer.play(3); delay(50); } lastButtonState3 = buttonState3; if (buttonState4 == LOW && lastButtonState4 == HIGH) { Serial.println("按键4按下,播放音效0004.mp3"); myDFPlayer.play(4); delay(50); } lastButtonState4 = buttonState4; }这段代码实现了最基本的功能。但它有一个明显的问题:delay(50)会阻塞整个循环,如果在播放一个长音效时按下其他按键,可能无法立即响应。同时,机械按键存在抖动,简单的延时防抖并不总是可靠。
5.2 改进:状态机与非阻塞防抖
我们需要一个更健壮的方法来处理按键和播放状态。思路是使用状态机和毫秒级时间戳来消除抖动,并跟踪DFPlayer的播放状态,以实现“播放时忽略新触发”或“打断当前播放”等逻辑。
// ... 前面的include和引脚定义不变 ... // 按键结构体,用于管理状态 struct Button { int pin; int lastStableState; int lastDebounceState; unsigned long lastDebounceTime; const unsigned long debounceDelay = 50; // 防抖延时毫秒数 }; Button buttons[] = { {BUTTON_1, HIGH, HIGH, 0}, {BUTTON_2, HIGH, HIGH, 0}, {BUTTON_3, HIGH, HIGH, 0}, {BUTTON_4, HIGH, HIGH, 0} }; const int buttonCount = sizeof(buttons) / sizeof(buttons[0]); bool isPlaying = false; // 全局播放状态标志 void setup() { // ... 初始化串口、DFPlayer部分与之前相同 ... for (int i = 0; i < buttonCount; i++) { pinMode(buttons[i].pin, INPUT_PULLUP); } } void loop() { myDFPlayer.loop(); // 必须 // 检查DFPlayer状态(示例:通过库的回调或查询,这里简化处理) // 实际中,可以通过 myDFPlayer.available() 和 myDFPlayer.readType() 来获取播放完成等状态 // 假设我们有一个函数 updatePlayStatus() 来更新 isPlaying 变量 updatePlayStatus(); // 处理所有按键 for (int i = 0; i < buttonCount; i++) { handleButton(&buttons[i], i + 1); // i+1 对应音效编号 } } void updatePlayStatus() { // 这是一个简化示例。更准确的做法是在 myDFPlayer.available() 时检查消息类型。 // 例如,如果收到 DFPlayerPlayFinished 消息,则将 isPlaying 设为 false。 // 这里为了演示,我们用一个简单的方法:播放命令发出后,设定一个超时时间。 // 实际项目建议实现完整的状态回调。 static unsigned long playStartTime = 0; static const unsigned long maxPlayTime = 10000; // 假设音效最长10秒 if (isPlaying && (millis() - playStartTime > maxPlayTime)) { isPlaying = false; } } void handleButton(Button* btn, int trackNum) { int currentReading = digitalRead(btn->pin); // 防抖逻辑:如果读数发生变化,重置防抖计时器 if (currentReading != btn->lastDebounceState) { btn->lastDebounceTime = millis(); btn->lastDebounceState = currentReading; } // 如果经过防抖延时后,状态稳定且是按下动作(从高到低) if ((millis() - btn->lastDebounceTime) > btn->debounceDelay) { if (currentReading == LOW && btn->lastStableState == HIGH) { // 按键被稳定按下 onButtonPressed(trackNum); } // 更新稳定状态 btn->lastStableState = currentReading; } } void onButtonPressed(int trackNum) { Serial.print("触发音效: "); Serial.println(trackNum); // 策略1:如果正在播放,忽略新按键(避免声音重叠) // if (isPlaying) { // Serial.println("当前正在播放,忽略此次触发。"); // return; // } // 策略2:如果正在播放,停止当前播放并开始新的(实现打断功能) if (isPlaying) { myDFPlayer.stop(); delay(200); // 给停止命令一点时间生效 } myDFPlayer.play(trackNum); isPlaying = true; // 记录播放开始时间,用于简单的超时判断 // 在实际应用中,应通过DFPlayer的回调来准确知道何时播放结束 }这个版本引入了非阻塞的防抖逻辑,并且通过isPlaying标志位管理播放状态,可以实现更复杂的交互逻辑。你需要根据实际需求选择“忽略”还是“打断”策略。
5.3 功能扩展:音量调节、播放模式与状态反馈
一个完整的声音板通常不止有播放功能。我们可以通过增加电位器或旋转编码器来调节音量,通过额外按键切换循环模式,并通过Az-Nano V3自带的RGB LED或一个小型OLED屏来提供状态反馈。
- 音量调节:将一个模拟引脚(如GPIO4)连接到一个10kΩ电位器的中间脚,电位器两端接3.3V和GND。在
loop()中读取模拟值(0-4095),映射到0-30的音量范围,并仅在变化超过一定阈值时发送myDFPlayer.volume(level)指令,避免频繁发送。 - 播放模式:增加一个模式切换按键。用一个变量(如
playMode)记录当前模式(0:单次,1:循环本曲,2:随机等)。在onButtonPressed函数中,根据模式调用不同的播放函数,如myDFPlayer.play(trackNum)或myDFPlayer.loop(trackNum)。 - 状态反馈:使用Az-Nano V3板载的RGB LED(如果有),可以在播放时点亮特定颜色,空闲时呼吸灯效果。或者连接一个I2C的0.96寸OLED屏,显示当前播放的音效编号、音量、模式等信息,用户体验会提升一个档次。
这些扩展功能的代码结构清晰,主要是对外设的读取和状态变量的管理,核心的播放控制逻辑不变。
6. 电源管理与便携化设计
要让声音板真正脱离电脑和电源适配器,成为一个便携设备,电源管理是关键。Az-Nano V3和DFPlayer Mini的功耗都不算高,但设计不当会导致续航短或不稳定。
供电方案对比:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| USB移动电源 | 简单可靠,电量足,可边充边用 | 体积较大,需要额外USB线 | 桌面使用、展示台 |
| 18650锂电池+保护板 | 能量密度高,可充电,形状规整 | 需要充电管理,有安全风险(需选用带保护板电池) | 手持设备、穿戴道具 |
| 聚合物锂电池 | 体积小,形状灵活,可定制 | 容量相对较小,需要充电管理 | 小型化、嵌入式项目 |
| 3xAA/AAA电池盒 | 易于获取,更换方便 | 电压可能不足(4.5V),且DFPlayer在4.5V下音量可能偏小 | 低功耗、临时原型 |
推荐方案与电路设计:
对于大多数便携应用,我推荐使用单节3.7V锂电池(如14500或18650)配合一个带充放电保护的TP4056模块,再连接一个DC-DC升压模块(将3.7V升压至稳定的5V)。
- 充电/保护模块:TP4056模块负责给锂电池安全充电(通过Micro USB口),并提供了过充、过放、短路保护。电池正负极接模块的
B+和B-。 - 升压模块:选择一款小型的、输出5V/1A以上的DC-DC升压模块(如MT3608)。将TP4056模块的
OUT+和OUT-(即电池保护后的输出)接到升压模块的输入。 - 系统供电:升压模块的5V输出,同时供给Az-Nano V3的
5V引脚和DFPlayer Mini的VCC。所有地线(GND)共地。
这样,整个系统就可以通过一个Micro USB口充电,使用时由锂电池供电,并由升压模块提供稳定的5V,确保DFPlayer Mini工作在最佳状态。你还可以在电源总线上加一个开关,方便控制整个系统的通断。
功耗优化技巧:
- ESP32-S3睡眠模式:如果声音板不常使用,可以让ESP32进入深度睡眠(Deep Sleep),仅由按键中断唤醒。这需要将按键连接到支持唤醒的GPIO(如GPIO0-5等),并编写相应的睡眠与唤醒代码。在深度睡眠下,电流可以降至10μA级别。
- DFPlayer关机:DFPlayer Mini本身也有一定的待机功耗。可以通过发送
myDFPlayer.sleep()指令让其进入睡眠模式(功耗约20μA),在需要播放前再通过myDFPlayer.start()或任意播放指令唤醒它。注意,睡眠后第一次通信可能需要一点初始化时间。 - 关闭无用外设:在代码中,关闭未使用的Wi-Fi、蓝牙(如果不需要),可以显著降低ESP32的功耗。
7. 外壳设计与制作:从原型到产品
一个项目是否完整,外壳占了很大比重。好的外壳不仅能保护电路,更能提升用户体验和产品质感。
设计考量:
- 布局:根据你的按键数量、是否包含屏幕/电位器等,在纸上先画一个草图。确定主板、电池、喇叭、SD卡插槽的位置。喇叭开孔对音质和音量影响巨大,开孔面积要足够,并且最好在内部为喇叭做一个小的共鸣腔。
- 按键手感:选择合适大小的按键帽。如果面板较厚,可以使用加长型的按键柱,或者设计一个内部支架将按键开关垫高,确保按键帽能灵活按压。
- 散热与维护:考虑电池的散热,避免密封过死。设计可拆卸的后盖或电池仓,方便更换SD卡和电池。
- 固定方式:电路板和电池仓可以使用螺丝柱、热熔胶或3D打印的卡扣固定。避免使用双面胶,高温下容易脱落。
制作方法:
- 3D打印:这是最灵活的方式。使用Fusion 360、SolidWorks或免费的Tinkercad进行建模。材料选择PLA即可,强度足够。打印时注意面板的孔位精度,可以先打印一个带小孔的小样测试按键配合。
- 亚克力激光切割:适合制作层次分明的“层叠式”外壳。设计好各层板子的图纸,用激光切割机切出,再用螺丝或胶水组装。这种方式外观很极客风。
- 现成盒子改造:去电子市场或网上找一个尺寸合适的塑料防水盒、铝制机箱甚至是一个精致的糖果铁盒。在盒子上用手钻或电钻开孔。这种方式成本低,速度快,但个性化程度稍差。
以3D打印为例的步骤:
- 测量所有核心元件(开发板、电池、喇叭)的最大外形尺寸。
- 在建模软件中,根据布局画出外壳的底板、侧壁和面板。
- 在面板上开出按键孔、喇叭孔、USB充电孔等。开孔直径要比元件实际尺寸略小(约0.2-0.3mm),这样按键装上去不会晃动。
- 在底板上设计螺丝柱,其内径与你的螺丝匹配(通常M2或M2.5)。
- 导出STL文件,用切片软件(如Cura)生成G代码进行打印。
- 打印完成后,进行组装测试,可能需要用小锉刀或电磨对孔位进行微调。
最后,贴上标签或喷漆,一个专属于你的、功能完备的便携式声音板就诞生了。你可以用它来制作游戏音效触发器、智能门铃、儿童故事机,或者任何需要即时声音反馈的创意项目。这个组合的潜力,远不止于播放几个简单的音效。