有源蜂鸣器自己带震荡电路,所以一段稳定的直流就能让它一直响同一个音;无源蜂鸣器只是一个裸的发声元件,你送什么波形进去,它才发什么声音。各给一段稳定电压,差别马上就听得出来 —— 有源的会一直唱,无源的只「嗒」一声就没了。
有源和无源蜂鸣器,里面到底差在哪里?
有源蜂鸣器模块的黑色圆罐里,除了线圈和振膜,还灌封着一颗自激震荡电路。通上直流,那个电路就自己产生交流驱动,频率落在发声元件的共振点上,大约 2.3 kHz,12 mm 的罐子在这个频率最响。你只会得到一个音,引脚能决定的只有它响不响。
无源蜂鸣器模块里没有震荡电路 —— 只有线圈和振膜。你送一个边沿,振膜就动一次,所以音高不多不少就是你那道方波的频率。给它稳定的直流,振膜只会被拉到一个位置然后停在那里:一声「嗒」,然后安静。
整个取舍就在这里。有源只花你一句 digitalWrite(),换来一个音;无源要花掉一个硬件定时器,换来一整个音阶。
材料清单 —— 先分清两种蜂鸣器,再做那个警报器
要更大声的选项
| Item | Price | Qty | |
|---|---|---|---|
SFM-20B Active Buzzer DC 3-24V Piezoelectric Long Continous BeepSFM208S | RM3.15 |
一颗 3–24 V 的有源蜂鸣器,走飞线出线,模块不够大声的时候用它。它在标称的 12 V 下最多只吃 12 mA,所以在 5 V 下 Uno 的引脚可以直接驱动 —— 但它两条镀锡多股线插不进任何排针座,得缠在杜邦线的针尖上,或者压进接线端子里。
裸装无源蜂鸣器是同一颗发声元件,下面只有两根按面包板间距排的硬针 —— 便宜到几毛钱,但这两根针是要插面包板的,插不进杜邦线的胶壳。KY-006 无源模块则是一块直针的入门板,标称 1.5–2.5 kHz。
它们长得一模一样,要怎么分辨?
我们的有源蜂鸣器模块和无源蜂鸣器模块是刻意做成双胞胎的:一样的蓝色 PCB,一样的 MH-FMD 丝印,一样是 GND、I/O、VCC 三个脚。裸装蜂鸣器更麻烦 —— 从上面看就是同一颗中间开孔的黑色圆罐。有两个办法可以定案。
用代码测最准。把模块的 VCC 接 5 V、GND 接 GND、I/O 接 D8,然后让那个引脚一动不动,用耳朵听:一直响就是有源,只有很轻的一声「嗒」就是无源。四行就够。
const uint8_t IO_PIN = 8;
void setup() {
digitalWrite(IO_PIN, HIGH); // 先设成静音 - 原因看接线那一节
pinMode(IO_PIN, OUTPUT);
delay(1000);
digitalWrite(IO_PIN, LOW); // 从这里开始就是稳定的直流
}
void loop() {}
换成裸装蜂鸣器,这段代码要反过来,因为引脚和发声元件之间没有任何东西挡着:(+) 脚接引脚,另一脚接 GND,把引脚拉 HIGH 才会响。路径上串一颗 100 Ω 电阻,而且测一下就好 —— 裸装蜂鸣器的电流是直接从引脚拿的,不是从三极管拿的,而无源元件本身只是一圈几十欧姆的线圈,远低于一个 Arduino 引脚该被要求单独驱动的负载。
看底面,连电都不用通。把裸装蜂鸣器翻过来:有源的底面被黑色灌封胶封得平平的,因为下面有一圈驱动电路要保护;无源的则一眼就看见驱动线圈和两个焊点。高度是比较弱的线索 —— 有源的罐子会略高一点 —— 而且只有两颗放在一起比才管用。货一到手,就在袋子上写清楚。
值得并排听一次:

两块蜂鸣器模块要怎么接到 Uno 上?
两块蜂鸣器模块带的是三根弯的公针,SW-18010P 震动模块是四根直的公针,而 Uno 上面全是母座 —— 所以一包公对母杜邦线就能把整个作品接完。不用面包板,也不用焊。插上去之前先读丝印:两块蜂鸣器模块的排针顺序都是 GND、I/O、VCC,电源和地各在一端,所以三芯连接器插反了,5 V 就会落在模块的地脚上。
| 模块引脚 | Uno 引脚 | 为什么 |
|---|---|---|
| 蜂鸣器 VCC(两块都是) | 5V | 经模块上的三极管给发声元件供电 |
| 蜂鸣器 GND(两块都是) | GND | 蜂鸣器电流和信号的回流路径 |
| 有源蜂鸣器 I/O | D8 | 任何一个普通数字脚都行;它只会是高或低 |
| 无源蜂鸣器 I/O | D9 | 任何数字脚都行 —— tone() 在每个脚上都能用 |
| 传感器 VCC | 5V | 给 LM393 比较器和两颗指示 LED 供电 |
| 传感器 GND | GND | 是第三根,不是最后一根 —— 丝印顺序是 AO DO GND VCC |
| 传感器 DO | D2 | D2 就是外部中断 INT0 —— Uno 的外部中断只有 D2 和 D3 |
| 传感器 AO | 不接 | 开关节点的原始电压;我们要的是数字输出 |
有一件事任何材料清单都不会写:Uno 上有三个 GND 座,5 V 却只有一个,而一根杜邦线的一头就把它占满了。文章最后那个警报器要同时带蜂鸣器和传感器,所以需要第二路 5 V —— 从板边那组 2×3 的 ICSP 排针上取:那是一排公针,它的 5 V 脚在另一排,就贴着有标记的 1 号脚,母头杜邦线直接按上去就行。用一块迷你面包板分电源也一样好。
为什么代码一跑,蜂鸣器就叫个不停?
两块 MakerHub 蜂鸣器模块的丝印上都印着低电平触发四个字,而这四个字把电路描述得分毫不差。字旁边那颗 SOT-23 封装的元件是一颗 S8550 PNP 三极管,发射极接在 VCC 上;紧挨着它的电阻标着 102,在三位数代码里就是 10 × 10²,也就是 1 kΩ。这颗电阻把基极接到 I/O 脚上。
PNP 三极管要在基极被拉到低于发射极的时候才导通。把 I/O 拉到 0 V,基极就整整比 VCC 低了一个电源电压;大约 (5 V − 0.7 V) ÷ 1 kΩ ≈ 4 mA 从基极流出来,把三极管推进饱和,蜂鸣器就接上了 5 V。把 I/O 按在 5 V,发射结上根本没有压差,也就没有基极电流,蜂鸣器是哑的。所以LOW 是响,HIGH 是静音。那 4 mA 是由 Arduino 引脚吸收的,稳稳落在 Uno 每个 I/O 脚 20 mA 的规格之内。
同一笔算术也能回答 3.3 V 的板子拿这些模块能做什么。模块照旧吃 5 V,却用 ESP32 去驱动 I/O,那么 3.3 V 的 HIGH 仍然比发射极低了 1.7 V,于是大约 (5 V − 3.3 V − 0.7 V) ÷ 1 kΩ ≈ 1 mA 还在继续往外流:三极管永远关不掉,蜂鸣器也就永远停不下来。改成把模块的 VCC 接到 3.3 V 那一路,电平就又对上了 —— HIGH 的时候发射结上完全没有压差,蜂鸣器是真的安静。它在低一点的电压下会响得柔和一些,而这就是这个修法的全部代价。
接下来是那个坑。在 AVR 上,pinMode(pin, OUTPUT) 会打开引脚的输出驱动,而这时输出锁存器还保持着复位后的 0 —— 所以引脚一变成输出就掉到低电平,而在这两块模块上,低电平正是「响」的状态。在那之前它是悬空的输入,蜂鸣器安安静静,这就是为什么代码一走到 setup() 噪音就来了。修法只有一行,而且顺序不能反:
digitalWrite(BUZZER_PIN, HIGH); // 还是输入的时候就先把锁存器设好
pinMode(BUZZER_PIN, OUTPUT); // 引脚直接以 HIGH 起来,不会经过 LOW
同一个坑在 noTone() 上还有第二种形态:Arduino AVR core 的这个函数结尾就是一句 digitalWrite(_pin, 0),而带时长参数的 tone() 收尾方式一模一样 —— 在 Uno 上,时长一到,是那个音自己的定时器中断去调用 noTone()。在高电平触发的蜂鸣器上,那是静音;在我们这块上,那是一记停不下来的长鸣。每一句 noTone() 后面都补上 digitalWrite(pin, HIGH)。

怎么用 tone() 放一段旋律?
tone() 这个函数把你的引脚交给一个硬件定时器,让它以你要的频率的两倍去翻转引脚,然后立刻返回 —— 音继续响着,你的代码继续往下跑。中间那颗反相的三极管并不会坏事:50% 占空比的方波倒过来,还是同一个频率的 50% 方波,所以模块发出来的音高,就是你要的那个。
限制也全都来自那一个定时器。在 Uno 上 tone() 会占用 Timer2,所以只要有音在响,3 号和 11 号脚的 PWM 就会停,而且同一时间只能响一个音。离发声元件共振点太远的音也会明显小声 —— 那是振膜的事,不是你代码的问题。
// 无源(PASSIVE)蜂鸣器模块上的八音阶。Uno,I/O 接 D9。
const uint8_t IO_PIN = 9;
const uint8_t BUZZER_OFF = HIGH; // 低电平触发:HIGH 才是静音
const int NOTES[] = { 262, 294, 330, 349, 392, 440, 494, 523 }; // C4..C5
const uint8_t NOTE_COUNT = sizeof(NOTES) / sizeof(NOTES[0]);
const int NOTE_MS = 250;
const int GAP_MS = 60;
// noTone() 收尾时会把引脚写成 LOW,而在低电平触发的模块上
// LOW 就是「响」。所以之后一定要把引脚停在 HIGH。
void silence() {
noTone(IO_PIN);
digitalWrite(IO_PIN, BUZZER_OFF);
}
void setup() {
digitalWrite(IO_PIN, BUZZER_OFF); // 引脚变成输出之前,先让它是静音的
pinMode(IO_PIN, OUTPUT);
}
void loop() {
for (uint8_t i = 0; i < NOTE_COUNT; i++) {
tone(IO_PIN, NOTES[i]); // 用这个频率发方波,然后就撒手不管
delay(NOTE_MS); // 我们在这里等的时候,音还一直响着
silence();
delay(GAP_MS);
}
delay(2000);
}
这份代码编译出来是 2,390 字节,占 Uno 闪存的 7%。把 silence() 拿掉,整个音阶就会连成一声停不下来的怪叫。
怎么做一个「该响的时候才响」的警报器?
SW-18010P 那颗黑色小罐子里,是一圈缠在中心针外面的弹簧,常态断开。一记震动把弹簧甩到中心针上,接触几毫秒又弹回来 —— 所以它是一颗事件传感器,而且敲一下,弹簧的抖动会产生一连串接触。
LM393 把这堆杂讯整理成干净的数字边沿。一颗电阻把开关节点拉到接近 VCC,弹簧一闭合就把它拽到地,比较器再报告这个节点落在参考电压的哪一边 —— 那颗蓝色的 3362 电位器就是那个参考。按设计,DO 平时是高电平,被触发时往下拉一下。不过,把电位器转到行程的任何一端,参考电压就会落在节点根本到不了的范围之外,于是 DO 一直卡着不动,D2 收不到任何边沿,警报器也就永远不会响。设定一次就好:给板子上电,让它静止不动,慢慢转电位器直到 DO 那颗 LED 刚刚熄灭,然后敲一下桌子,看它闪。如果你宁可看数字而不是看灯,就在一个空循环里印 digitalRead(2):静止时应该是 1,敲一下变 0。这个读数同时也证实了,下面那个中断该挂在 FALLING 上。
那份代码做了四件事,每一件都对应警报器惹人烦的一种方式。上电后头十秒它什么都不理,这样把东西放下来不算一次事件。40 ms 之内的重复边沿它不算数,因为那是一敲之后弹簧在抖,不是敲了四下。它要在 1.5 秒内收到四次分开的敲击,这样一辆罗里经过还不够格。而它用 millis() 而不是 delay() 把警笛锁住五秒 —— 用 delay() 的警笛在整段时间里是聋的,一旦你想加一个消音按钮,这一点马上就有关系。
// 震动警报器。SW-18010P DO -> D2,有源(ACTIVE)蜂鸣器 I/O -> D8。Uno。
const uint8_t SHAKE_PIN = 2; // D2 就是 INT0 - 用这个脚的唯一理由
const uint8_t BUZZER_PIN = 8;
const uint8_t BUZZER_ON = LOW; // 低电平触发的模块
const uint8_t BUZZER_OFF = HIGH;
const unsigned long ARM_DELAY_MS = 10000UL; // 够你把它放好再走开的时间
const unsigned long BOUNCE_MS = 40UL; // 敲一下,弹簧会抖一阵
const unsigned long WINDOW_MS = 1500UL; // 敲击必须落在这段时间内
const uint8_t PULSES_NEEDED = 4; // 调小 = 警报器更神经质
const unsigned long SIREN_MS = 5000UL;
volatile unsigned long lastEdgeMs = 0;
volatile unsigned int pulses = 0;
bool armed = false, sounding = false, windowOpen = false;
unsigned long windowStart = 0, sirenStart = 0;
// DO 一掉下来就跑,不管 loop() 当时在做什么。这里只负责数 -
// 判断都留在 loop() 里,那里读起来清楚得多。
void onShake() {
unsigned long now = millis();
if (now - lastEdgeMs < BOUNCE_MS) return; // 还是同一敲在抖
lastEdgeMs = now;
pulses++;
}
// pulses 是 16 位的,AVR 要分两条指令才读得完。读的时候把中断
// 挡住,否则一个边沿可能刚好落在两个半边之间。
unsigned int readPulses() {
noInterrupts();
unsigned int n = pulses;
interrupts();
return n;
}
void clearPulses() {
noInterrupts();
pulses = 0;
interrupts();
}
void setup() {
digitalWrite(BUZZER_PIN, BUZZER_OFF); // 要在 pinMode 之前 - 绝不经过 LOW
pinMode(BUZZER_PIN, OUTPUT);
// LM393 的输出是开集电极的:它只能把线拉低,推不高,
// 所以要给它一个上拉,让它有个高电平可以往下掉。
pinMode(SHAKE_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(SHAKE_PIN), onShake, FALLING);
}
void loop() {
unsigned long now = millis();
// 1. 布防延时 - 你自己在摆弄它的时候先不理。
if (!armed) {
if (now >= ARM_DELAY_MS) { clearPulses(); armed = true; }
return;
}
// 2. 警笛已经在响:让它响完,然后安静地重新布防。
if (sounding) {
if (now - sirenStart >= SIREN_MS) {
digitalWrite(BUZZER_PIN, BUZZER_OFF);
sounding = false;
windowOpen = false;
clearPulses(); // 警笛响的时候被敲,不会再触发一次
}
return;
}
unsigned int n = readPulses();
if (n == 0) return;
// 3. 第一下敲击打开一个时间窗。
if (!windowOpen) { windowOpen = true; windowStart = now; }
// 4. 窗内敲够了次数 - 这是真的。
if (n >= PULSES_NEEDED) {
digitalWrite(BUZZER_PIN, BUZZER_ON);
sounding = true;
sirenStart = now;
return;
}
// 5. 窗过了还不够次数 - 是一辆罗里经过。忘掉它。
if (now - windowStart >= WINDOW_MS) {
windowOpen = false;
clearPulses();
}
}
传感器要装在你要保护的那件东西上,而不是摆在它旁边 —— 弹簧回应的是真正传到它身上的那记震动。然后用最上面那几个常量去调:如果轻轻一敲就报警,说明弹簧过了 40 ms 还在抖,把 BOUNCE_MS 调大;如果用力推都凑不满四次,就把 PULSES_NEEDED 调小,或者把 WINDOW_MS 放宽。
它编译出来是 1,370 字节、30 字节 RAM,还留得下一个键盘、一块显示屏或者一个消音按钮的空间。整套都留在 Uno 的 5 V 上就好 —— 这里没有任何东西该靠近市电。如果你换上更大声的 SFM-20B,请拿远一点试,别贴着耳朵;它是设计来让整间屋子都听得见的。它也会把这份代码反过来,因为它是一颗裸的两线蜂鸣器,前面没有三极管挡着:红线接 D8,黑线接 GND,然后把常量对调,让 BUZZER_ON 变成 HIGH、BUZZER_OFF 变成 LOW。照原样不改,警笛就会反着跑,只有在警报触发的时候才安静。
把弹簧换成 PIR,就是我们那篇人体感应作品,它是有人走过房间才触发,而不是家具被搬动才触发;换成气体或火焰传感器,就成了 MQ-2 烟雾警报器或者火焰警报器。再配一个红外遥控,就是很俐落的消音按钮,因为 millis() 的计时方式让循环一直空着,可以顺便去等它。
我们从真实客户那里看到的常见错误
买了有源蜂鸣器去放曲子。最常见的一种:tone() 跑起来了,代码里的频率也在变,每个音听起来却一模一样。没有东西坏 —— 音高是内部震荡电路定的,你那道方波能决定的只有开和关。
传感器排针从错的那一头数起。习惯会告诉你地脚在一排的最尾端。这里 VCC 才是最后一根,GND 排第三 —— 顺序是 AO DO GND VCC —— 所以排针读反了,5 V 会灌进模拟输出,而板子根本没电。
把悬空的引脚当成「关」。引脚留着当输入,在这两块模块上确实是安静的,问题就这样被藏起来,直到代码里别的地方把它拉低。在 setup() 里把电平明确写出来,蜂鸣器的状态就再也不是碰运气。
电位器卡住了,却怪蜂鸣器。敲了完全没反应的警报器,多半是震动模块的参考电压被转到开关节点的范围之外了,而不是蜂鸣器坏掉 —— DO 完全不动,代码自然一个边沿都看不到。
常见问题
有源蜂鸣器可以用 tone() 吗?
可以调用,但音高不会变。有源蜂鸣器的震荡电路是固定的,所以你传什么频率进去,作用都只是把它打开。它适合做提示音和警示音;要放曲子,就用无源蜂鸣器。
Arduino 一上电,蜂鸣器就叫个不停 —— 是哪里坏了?
几乎可以肯定没坏。我们两块模块都是低电平触发,所以引脚停在 0 V 就等于「响」。光是 pinMode(pin, OUTPUT) 会让引脚处在低电平,所以要先把它写成 HIGH,再设成输出。
学校专题的警报器,该选哪一种蜂鸣器?
选有源模块,除非评分表指明要有旋律。一行代码,以体积来说够大声,也没有定时器要解释。盒子里再留一块无源模块,给那种要放曲子的专题用。
这些模块还需要另外加电阻或三极管吗?
不用 —— 三极管已经在板子上了,这正是买模块的意义。Arduino 引脚要负责的只有大约 4 mA 的基极电流。
怎么让警报器更大声?
换成 SFM-20B,那是一颗为长时间警报而生的更大发声元件。它可以跑 3 到 24 V,在标称的 12 V 下最多吃 12 mA,所以在 5 V 下 Uno 引脚可以直接驱动 —— 而且供电电压越高它越大声。它前面没有三极管,所以是 HIGH 才响:换上它的时候,把 BUZZER_ON 和 BUZZER_OFF 对调。

「发生了什么事」用有源,凡是要带旋律的用无源 —— 而在这两种上面,HIGH 都是你用来要求安静的那一边。
最后更新:2026 年 8 月。有问题?WhatsApp 联系我们。



Arduino Uno Compatible SMD UNO R3 with Type B Cable - ATMEGA328P with CH340G-Microcontroller Project
Buzzer Active Passive Buzzer 5V Electronic Sound Alarm Module Tone Piezo - Active Buzzer Module
Buzzer Active Passive Buzzer 5V Electronic Sound Alarm Module Tone Piezo - Passive Buzzer Module
Tilt Sensor Vibration Sensor Tilt Switch Module Alarm Trigger SW1801P For Arduino Application - VIBRATION/ TILT SENSOR
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-F 20CM
SFM-20B Active Buzzer DC 3-24V Piezoelectric Long Continous Beep