这次我们来看一个很典型的 Arduino 入门娱乐项目:用一块 UNO 板和一个蜂鸣器,把一首喜欢的曲子演奏出来。项目标题是“用 UNO 板和蜂鸣器演奏《Aoharu》评论区点歌呀 qwq”,本质上就是一个“单片机 + 无源蜂鸣器”的乐曲播放器。拆开看,它能把不少硬件知识点串起来:有源蜂鸣器和无源蜂鸣器怎么区分、三脚压电陶瓷蜂鸣器怎么接、为什么有人会用 ULN2003APG 去驱动蜂鸣器、tone() 函数到底怎么用、乐谱怎么转成单片机认识的数组。
先说结论:这个项目的门槛很低。它不涉及复杂算法,不涉及 GPU、推理、模型权重,也不需要高成本开发板。一套最常见的 Arduino UNO(或者兼容板、Nano 板),加一个几块钱的无源蜂鸣器,再接几根杜邦线,就能在 Arduino IDE 里把代码烧进去,上电听到旋律。适合刚接触单片机、想把“会闪灯”升级成“会唱歌”的读者,也适合学校实验、社团展示、创客空间的小项目。
本文会按一条完整流程来写:先帮你把蜂鸣器选型弄清楚,再给出两种接线方案(引脚直接驱动和 ULN2003APG 驱动),然后给出 Arduino IDE 环境准备、tone() 演奏代码框架、音符频率对照表和乐曲编码方法,最后用《小星星》做一次全流程跑通演示,说明如何把同样的方法迁移到《Aoharu》这类曲目上,并汇总常见问题和排查思路。如果你之前只见过“按下按键响一声”的蜂鸣器实验,这篇文章可以让你把它升级成一个真正能演奏旋律的小播放器。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 单片机音乐播放 / Arduino 入门实验 |
| 主控平台 | Arduino UNO / Nano / 兼容板,ATmega328P 系列 |
| 发声设备 | 无源蜂鸣器(推荐)、有源蜂鸣器(只能单音)、三脚压电陶瓷蜂鸣器模块 |
| 核心原理 | tone() 输出指定频率方波驱动蜂鸣器,不同频率组合成旋律 |
| 驱动方式 | 引脚直接驱动;大功率/高响度场景可用 ULN2003APG 达林顿管驱动 |
| 编程环境 | Arduino IDE,无需外接下载器,USB 线即可烧录 |
| 启动方式 | 上电自动播放;可扩展按键、串口、蓝牙切换曲目 |
| 硬件成本 | 很低,UNO 兼容板 + 蜂鸣器即可 |
| 是否支持 API | 不支持,属于本地硬件项目;如需远程点歌可通过串口/蓝牙透传 |
| 适合场景 | 硬件入门、创客展示、校园实验、旋律编码练习 |
从这张表能看出来,它和“跑 AI 模型”是两个方向的项目。它不讲显存,不讲推理框架,重点在硬件连接、时序控制、音符数据编码和音频驱动。正因为如此,它非常适合作为单片机学习的第一类“有反馈”项目:烧录之后立刻能听到结果,成功或失败都很好判断,不需要昂贵的仪器,也不需要等待模型加载。对于想理解“频率、周期、定时器、引脚输出”这些基础概念的读者,这个项目的性价比很高。
2. 适用场景与使用边界
适合的人有三类。第一是 Arduino 初学者,想从“控制 LED 闪灯”往前推一步,理解引脚输出、定时器、方波频率这些概念;第二是喜欢音乐又懂一点硬件的玩家,想把自己喜欢的曲子在蜂鸣器上复刻出来,也就是标题里说的“点歌”玩法;第三是做校园社团展示、开放日小作品的开发者,蜂鸣器演奏比单纯闪灯更有辨识度,成本又低,还能现场“点歌”互动。
不适合的场景要先说清楚。第一,不要指望蜂鸣器有 Hifi 音质。单片机的 tone() 本质是方波驱动,音量刺耳、没有泛音、没有多声道,适合听“旋律轮廓”,不适合听“音色”。第二,不要在小房间里长时间大音量播放,蜂鸣器高频部分会比较吵,长时间测试建议降低音量或外接小功放。第三,《Aoharu》这类商业歌曲的旋律版权归原权利人所有,个人学习、实验室验证没有问题,但如果要公开传播演奏视频、做成产品售卖,需要确认授权范围。更稳妥的做法是,先用公有领域曲目(比如《小星星》《欢乐颂》)跑通硬件,再自行扒谱用于个人学习。
使用边界上还要注意:所有接线都要在断电状态下完成,避免短路;外接电源驱动蜂鸣器时,先确认电压等级和正负极,避免烧毁元件。蜂鸣器播放时不要长时间贴近耳朵,高频方波对听力并不友好。
3. 硬件准备与蜂鸣器选型
3.1 元件清单
| 元件 | 数量 | 说明 |
|---|---|---|
| Arduino UNO 或兼容板 | 1 | 也可用 Nano,引脚定义和代码基本一致 |
| 无源蜂鸣器 | 1 | 推荐 5V 电磁式无源蜂鸣器,成本很低 |
| 杜邦线 | 若干 | 公对公、公对母各准备几根 |
| 面包板 | 1 | 方便接线,非必须 |
| 100Ω 电阻 | 1 | 可选项,用于限制引脚驱动电流 |
| ULN2003APG | 1 | 当需要更大音量、更高电压驱动时使用 |
| 外接直流电源 | 1 | 使用 ULN2003APG 时推荐,电压按蜂鸣器规格选择 |
3.2 有源蜂鸣器和无源蜂鸣器的区别
这是很多新手第一个翻车点。有源蜂鸣器(active buzzer)内部自带振荡电路,只要给它通电,它就会以固定频率发声,Arduino 只能控制“响”和“不响”,不能控制音调;无源蜂鸣器(passive buzzer)内部没有振荡电路,需要外部给它一个频率信号才会发声,频率不同音调就不同,所以才能播放旋律。
| 对比项 | 有源蜂鸣器 | 无源蜂鸣器 |
|---|---|---|
| 内部振荡电路 | 有 | 无 |
| 通电效果 | 通电即响 | 通电不响 |
| 声音控制 | 只能响/不响 | 可以改变频率,播放旋律 |
| Arduino 控制方式 | digitalWrite 高低电平 | tone() 输出指定频率 |
| 能否演奏《Aoharu》 | 不能 | 能 |
| 判断方法 | 接 5V 会响 | 接 5V 不响,需要频率信号才响 |
判断手里蜂鸣器类型很简单:把两个引脚接到 5V 正负极,会响的是有源,不响的是无源;或者看底部,有源蜂鸣器一般有“+”和密封贴纸,无源蜂鸣器能看到线圈骨架和 PCB 走线。本项目要播放旋律,必须选无源蜂鸣器。
3.3 三脚压电陶瓷蜂鸣器怎么用
热词里提到的“三脚压电陶瓷蜂鸣器”主要分两类。一类是模块形式,板上三个引脚是 VCC、GND、I/O,这类通常内置了驱动三极管或振荡电路,本质是有源蜂鸣器模块,给 I/O 高电平就响,适合做报警提示,但不适合播放旋律。另一类是压电陶瓷片本身带反馈电极的三脚元件,需要外接振荡电路才能起振,直接接 UNO 引脚通常不会响,也不适合做 melody 播放。
怎么判断手上的三脚模块能不能做本项目的播放器?先看丝印,如果标注 VCC/GND/IO,用 5V 和 GND 上电,IO 接 3.3V,能响说明是有源模块,只能做单音提示;不响再查数据手册确认是否无源类型。从项目需求看,要播放《Aoharu》这种旋律变化明显的曲子,首选两只脚的无源电磁式蜂鸣器,而不是三脚有源模块。
3.4 什么时候需要 ULN2003APG
ULN2003APG 是达林顿晶体管阵列,单颗芯片内部有 7 路达林顿管,常用于驱动继电器、步进电机、电磁阀这类需要较大电流的负载。蜂鸣器本身电流很小,UNO 引脚直接驱动无源蜂鸣器完全够用。为什么要用 ULN2003APG?两种常见场景:一是蜂鸣器额定电压高于 5V,比如 9V、12V 蜂鸣器,UNO 引脚无法直接提供,需要用外部电源加 ULN2003APG 作为开关;二是想驱动无源蜂鸣器之外的扬声器,比如 8Ω 小喇叭,直接接引脚容易拉低电压,音量上不去,用 ULN2003APG 更稳。
4. 硬件接线
4.1 方案一:UNO 引脚直接驱动无源蜂鸣器
这是跑通项目最快的接法。无源蜂鸣器一般有正负极,长引脚是正极,短引脚是负极,也有的底座上直接标注“+”和“-”。建议在信号线上串一个 100Ω 电阻做电流限制,防止引脚被意外短路拉到地。
Arduino UNO D8 --> 100Ω电阻 --> 无源蜂鸣器正极(+) Arduino UNO GND --> 无源蜂鸣器负极(-)如果用的是蜂鸣器模块(两脚或三脚模块),通常模块上已有驱动电路,直接把信号引脚接到 D8,VCC 接 5V,GND 接 GND,不需要外接电阻。这种模块的好处是接线干净,坏处是很多模块内部会把信号引脚拉到一个默认状态,判断有源无源时要先看模块说明。
4.2 方案二:ULN2003APG 驱动无源蜂鸣器
当蜂鸣器额定电压 9V/12V,或者需要更大音量时,用 ULN2003APG 做开关。ULN2003APG 的输入输出是反向的,输入高电平时输出对地导通,所以蜂鸣器要接在外部电源正极和 ULN2003APG 输出脚之间,公共地必须和 UNO 地连在一起。
Arduino UNO D8 --> ULN2003APG IN1(1脚) ULN2003APG OUT1(16脚) --> 无源蜂鸣器负极(-) 外部电源正极 VCC --> 无源蜂鸣器正极(+) 外部电源负极 GND --> Arduino UNO GND(必须共地) Arduino UNO GND --> ULN2003APG E(8脚) ULN2003APG COM(9脚) --> 外部电源正极(感性负载续流,接上更安全)这个接法要注意三点:共地必须接,否则信号参考电位不一致,蜂鸣器不会正常工作;外部电源电压不要超过蜂鸣器和 ULN2003APG 的额定值;ULN2003APG 的 COM 脚是续流二极管公共端,驱动电磁式蜂鸣器这类感性负载时接外部电源正极更安全。
5. Arduino IDE 环境准备
代码烧录部分不复杂。安装 Arduino IDE(官方版本即可,版本差距不影响本例),在“工具 -> 开发板”里选择 Arduino Uno,在“工具 -> 端口”里选择 UNO 所在的串口号,然后点上传。如果一切正常,IDE 底部会提示上传完成,UNO 板上自带的 TX/RX 灯会快速闪烁。
如果你用的是几块钱的兼容板,大概率是 CH340 串口芯片。这种情况下 Windows 第一次插上 USB 会识别不到串口,需要安装 CH340 驱动。装好之后,“端口”菜单里会出现一个新的 COM 口。如果没有,检查 USB 线是不是纯充电线,很多数据线只能充电不能传输数据,这是最常见的“连不上板子”原因。
环境准备阶段不需要额外装库,tone() 和 noTone() 都是 Arduino 内置函数,直接就能用。这一点对新手特别友好,不需要像很多 Python 项目那样先解决依赖地狱。
6. 编写演奏程序
6.1 音符频率表
tone() 需要一个频率值,单位是 Hz。C 大调常用音阶的频率如下,标准音 A4 是 440Hz:
| 音名 | 频率(Hz) | 音名 | 频率(Hz) |
|---|---|---|---|
| C4 | 262 | C5 | 523 |
| D4 | 294 | D5 | 587 |
| E4 | 330 | E5 | 659 |
| F4 | 349 | F5 | 698 |
| G4 | 392 | G5 | 784 |
| A4 | 440 | A5 | 880 |
| B4 | 494 | B5 | 988 |
半音频率如 C#4=277、D#4=311 等,按十二平均律计算即可。实际使用中,C4 到 C5 这个范围已经能覆盖大多数简单曲目的主旋律,不需要把所有频率都背下来。
6.2 乐曲数据怎么编码
旋律本质上就是“音高 + 时值”的序列。Arduino 代码里常用两个数组来保存:一个数组存每个音符的频率,另一个数组存每个音符的时值。时值用几分音符表示比较方便,比如 4 表示四分音符,8 表示八分音符,2 表示二分音符。播放时用下面的公式转换成毫秒:
int duration = 1000 / noteDurations[i]; // 常见的教程近似写法这个公式不是严格的音乐节拍,而是 Arduino 教程里的简化近似。要让节奏更接近原曲,需要先确定基准 BPM,再按音符类型换算。示例代码先用最常见的写法跑通,后面再讲调优。
6.3 使用 tone() 和 noTone()
tone() 函数原型是tone(pin, frequency, duration)。它用一个定时器在指定引脚上输出方波,第三个参数 duration 是持续时间。播放结束后建议调用noTone(pin)停止输出,避免引脚悬在某个电平。要注意两件事:第一,tone() 在 UNO 上会占用定时器 2,所以引脚 3 和 11 的 PWM 输出会受影响,如果你的项目同时用这两个引脚做 PWM,需要避开;第二,tone() 一次只能在一个引脚上输出,不能同时播放多声道。
6.4 完整示例:先用《小星星》跑通
先不要直接上《Aoharu》,先用一段公有领域旋律验证硬件。下面的代码可以直接复制到 Arduino IDE,烧录后上电就会循环播放《小星星》第一段。这段程序的逻辑很简单:setup() 里遍历两个数组,依次把音符频率和时值传给 tone(),用 delay() 让它播完,再 noTone() 清掉输出,最后停顿两秒进入 loop() 空循环,实现循环播放。
#define BUZZER_PIN 8 // 音符频率(Hz) const int melody[] = { 262, 262, 392, 392, 440, 440, 392, // C C G G A A G 349, 349, 330, 330, 294, 294, 262 // F F E E D D C }; // 时值:4=四分音符,2=二分音符 const int noteDurations[] = { 4, 4, 4, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 2 }; void setup() { int notes = sizeof(melody) / sizeof(melody[0]); for (int i = 0; i < notes; i++) { int duration = 1000 / noteDurations[i]; tone(BUZZER_PIN, melody[i], duration); delay(duration * 1.3); // 播放当前音符,并留出一点间隔 noTone(BUZZER_PIN); } delay(2000); // 播完一段停顿,再循环 } void loop() { }这段程序烧录后,如果蜂鸣器发出清晰的“一闪一闪亮晶晶”旋律,说明硬件接线、IDE 环境、时钟和 tone() 调用全部正确。此时停下来,确认你用的是无源蜂鸣器;如果听到的是一声长音或者只有开关一样的蜂鸣声,八成是用了有源蜂鸣器,或者接线接到了模块的固定电平引脚上。
6.5 把方法迁移到《Aoharu》
跑通《小星星》之后,替换成《Aoharu》的流程就是:找谱、确定调号、转音符频率、填数组、试听调节拍。
找谱这一步,可以用自己扒谱,也可以找现成简谱。拿到简谱后,先确认调号。比如简谱里写“1=C”,那么数字 1 对应 C4,也就是 262Hz;如果写“1=G”,那么 1 就是 G4,对应 392Hz。然后把简谱的每个数字转成对应频率,休止符记成 0,并在时值数组里对应位置填入时值。为了说明格式,下面给一个自编的 8 音符片段,它不是《Aoharu》原曲,只是演示跨音域怎么写:
// 演示片段:C4 E4 G4 C5 G4 E4 C4(自编旋律,非《Aoharu》) const int demoMelody[] = { 262, 330, 392, 523, 392, 330, 262, 0 }; const int demoDurations[] = { 4, 4, 4, 2, 4, 4, 4, 2 };把 demoMelody 和 demoDurations 替换进第 6.4 节的代码框架,再按《Aoharu》的实际速度调整 delay 的倍率,就完成了一次换歌。这个过程需要反复试听。要理解的是,音准和节拍是两个独立问题:音准由频率数组决定,节拍由 delay 和 duration 决定,调音准不用动时值,调节奏不用动频率,分开排查效率更高。
如果你希望直接拿到《A