一个用得住的拍手开关,读的是 KY-037 的模拟脚,不是它的数字脚:以 ADC 允许的最快速度采 A0,量出信号在 20 ms 的窗口里摆动了多大,再把间隔 120–800 ms 的两次摆动算作一次翻转。满网教程都在教的那套「拧电位器 + D0」,在台面上能用,到傍晚就飘了;这一套稳得住。
做一个 Arduino 拍手开关,需要哪些零件?
KY-037 声音传感器是一块红色小板,一头是两条弯脚撑起来的银色驻极体话筒,板上还有一颗蓝色微调电位器、一颗 LM393,以及四根直的公排针。这排针把接线方式定死了:它直接插进面包板,再用公对公杜邦线接到 Uno 的排母上。
材料清单 —— KY-037 拍手开关,Arduino Uno 配 5 V 继电器
选配 —— USB 数据线与面板指示灯
Uno 那个插座认的是全尺寸 USB-B(打印机那种)方头 —— 抽屉里没有这个形状的线就加一条。这个作品的第一阶段只让 Uno 板上自带的 13 脚 LED 闪,什么都不用多买;那颗 5 mm 绿色 LED 是给面板做指示灯用的,串在它前面的限流电阻不能省 —— 一般取 220 Ω,入门套件里的电阻排就能找到。
继电器模块要选 5 V 版本,因为它那颗 Songle SRD-05VDC-SL-C 的线圈可以直接吃 Uno 自己的 5 V 轨;12 V 模块是同一块红板换了 12 V 线圈,属于本来就有 12 V 的系统。公对母杜邦线可以把面包板整个省掉;电脑跟 CH340 驱动过不去的话,就选 FT232 版 Uno。
KY-037 是怎么把一次拍手变成信号的?
KY-037 的驻极体话筒,是从 + 脚经一颗电阻偏置起来的,而声音是压强的振荡,所以那个节点上是一个骑在稳定直流电平上的交流信号。一次拍手会让它在几毫秒里往静息电压的上下两边猛摆,随后房间的反射再拖着它振荡几十毫秒才平息。A0 接的就是这个节点。
KY-037 的 LM393 拿这个节点去和蓝色微调电位器设定的一个参考电压比较 —— 那颗电位器标着 104,10 后面四个 0,也就是 100 kΩ —— 再用比较结果驱动 D0。这个输出是开集的:里面只有一个能把引脚拉到地的三极管,没有任何能把它推上去的东西,所以板子自己配了上拉电阻和一颗指示 LED。D0 回答的是一个被硬件焊死的问题;A0 给的是整个信号。
| 引脚(按丝印顺序) | 接到 | 它承载什么 |
|---|---|---|
A0 |
Uno A0 |
话筒节点本身 —— 骑在直流偏置上的交流摆动 |
G |
− 电源轨(GND) |
地。注意它排在第二位,不在最后 |
+ |
+ 电源轨(5 V) |
话筒偏置和 LM393 的供电 |
D0 |
不接 | 比较器输出。这份代码不用它 |

KY-037 和继电器模块怎么接到 Uno 上?
Uno 只有一个 5 V 插孔,而这里有两个模块要用,所以先从那个插孔把面包板的电源轨喂起来,两路供电都从轨上取。插 KY-037 的时候照着它的丝印来:地排在这一排的第二位,所以模块插错一格,就是把 5 V 灌进模拟输入。继电器板的两条边上各有一个三位的螺丝端子 —— 触点这一侧是 NO/COM/NC,控制那一侧是 IN/DC−/DC+ —— 所以每颗螺丝底下压一根杜邦线的针脚。
| 从 | 接到 | 为什么 |
|---|---|---|
Uno 5V、GND |
+ 与 − 电源轨 |
两根公对公杜邦线。一个插孔就变成两个模块可以共用的一整条轨 |
KY-037 +、G |
+ 与 − 电源轨 |
话筒偏置和比较器的供电 |
KY-037 A0 |
Uno A0 |
代码要量的就是这一路信号 |
继电器 DC+、DC− |
+ 与 − 电源轨 |
给板子和线圈供电 —— 吸合时约 71 mA |
继电器 IN |
Uno D8 |
只驱动光耦里那颗 LED,几个毫安而已 |
| 灯 | 继电器 COM 和 NO |
继电器不动作、或者板子没电的时候,灯都是灭的 |
| 选配 LED | Uno D13 → 220 Ω → LED 长脚,短脚回 − 电源轨 |
面板指示灯,和代码已经在驱动的板载 LED 并联。那颗电阻不是可选项 |
为什么「拧电位器 + D0」的拍手开关永远调不定?
KY-037 的那颗微调电位器只设定一个数字 —— 话筒节点要越过多高的电压,D0 才会翻转 —— 而这个唯一的固定阈值会带来三个问题。我们那篇 LDR 光敏电阻自动灯对光讲的是同一套道理。
阈值是固定的,房间不是。一台风扇、打在铁皮屋顶上的雨、空调压缩机启动,都会把环境声抬高几十毫伏,所以晚上十一点调好的那个点,到了中午不是漏掉拍手,就是被路过的车流触发。电位器对它所在的这个房间一无所知。
一次拍手是很多个边沿。波形上冲时越过阈值一次,回落时再越一次,之后每一个还够响的反射又各越一次,所以一次拍手出来的是一串毛糙的脉冲。按边沿翻转的代码就会翻好几次 —— 这正是最朴素的那版拍手开关只会闪、不会切换的原因。
贴着设定点它会抖。声音在阈值附近徘徊时会反复穿过它 —— 而这颗微调还是多圈的,螺丝要拧很多圈才走完整个量程,拧到两端还会打滑「咔咔」响。「拧到能用为止」,结果往往是螺丝已经顶死在一头了:
D0 不是没用,但它带不动这个继电器 —— 至少不是按想当然的那个方向。LM393 的灌电流很强,拉电流却只能靠自己那颗上拉电阻挤出来,不到一毫安,而光耦的 LED 要的是几毫安 —— 所以跳线帽必须挪到 L,让继电器在 D0 为 LOW 时吸合。而在 KY-037 上,LOW 恰恰是静息状态,拍手才是 HIGH —— 板子自己那颗 D0 指示 LED 会跟着翻,所以在接任何线之前,你就能亲眼看着这支脚回应一次拍手。于是安静的时候灯一直亮着,有噪声反而灭掉;把灯改接到 NC 而不是 NO,噪声又变回把灯点亮,代价是丢掉失效安全 —— 因为接在 NC 上的灯,在板子断电时也是亮的。不管哪一种,它都只是瞬时的。要「翻转」就要有记忆,要有记忆就要有代码。
拍手开关的代码是怎么在 A0 上认出一次拍手的?
量摆幅,不量电平。代码对 KY-037 的 A0 脚全速采样 20 ms,只留下这段里的最大值和最小值;两者之差就是峰峰摆幅。20 ms 长到足以让一次拍手最响的那几毫秒总能落进某一个窗口,又短到让后面那套双击计时还有够细的颗粒度。两个采样相减把直流偏置消掉了,所以根本不必知道静息电平在哪,信号先往上摆还是先往下摆也一样。
Uno 的 ADC 跑在 125 kHz 的时钟上 —— 16 MHz 除以 Arduino 内核默认的 128 分频 —— 每次转换要 13 个周期,所以 analogRead() 每秒大约给出 9,600 个采样,一个窗口里约 190 个。这远远不够重建音频,也不需要:高于采样率一半的成分不会凭空消失,而是折叠回低频,照样落成一个个大幅度的采样。我们量的是信号动得多猛,不是它在说什么。
阈值于是就是房间自己的底噪,加上一段余量。代码把安静窗口的摆幅做平均,每个窗口只往新值挪 3% —— 一步 20 ms,所以跟上房间的变化大约要三十个窗口、三分之二秒 —— 并且跳过任何含有拍手的窗口,这样一次拍手就永远抬不高它刚刚越过的那道杠。80 个计数的余量,就是 80 × 4.9 mV ≈ 0.39 V 的额外摆幅 —— 比一个安静房间自己游走的幅度高出一大截,作为一臂之遥拍手的起手数字很合适。A0 是话筒自己的节点,后面没有放大器,所以你一往后退,这个摆幅掉得很快:串口会把你的拍手实际量到多少打印出来,余量就跟着那个数字走。

两次拍手怎么变成刚好一次翻转?
第一次被接受的摆动会启动一个时钟,之后的 120 ms 代码是故意装聋的:一次拍手的直达声只有几毫秒,但它的反射会把电平撑住久得多,而且没有人能在十分之一秒里拍两下 —— 所以这段时间里的一切,都是同一次拍手。从 120 ms 到 800 ms,第二次摆动就把灯翻转。过了 800 ms 状态机放弃,所以一记关门声自己不会改变任何东西,一声大笑也不会。
这两个数字都是常数,不是物理定律:第二次拍手老是来得偏晚,就把 GAP_MAX 放宽;在硬墙贴砖的房间里串口把一次拍手数成两次,就把 REFRACTORY 加长。
/*
拍手开关 - KY-037 声音传感器 -> Arduino Uno -> 5 V 继电器模块。
不需要任何库。
KY-037 + -> 面包板 + 电源轨(5 V) G -> 面包板 - 电源轨(GND)
KY-037 A0 -> Uno A0 D0 -> 这份代码不用
继电器 DC+ -> + 电源轨 DC- -> - 电源轨 IN -> Uno D8
继电器的跳线帽保持出厂时的 H(高电平触发)。
这份代码量的是话筒信号在一小段窗口里摆动了多大,而不是它停在什么电平上,
所以直流偏置和那颗微调电位器都无关紧要。
计时窗口内的两次摆动,就把灯翻转一次。
*/
const uint8_t PIN_MIC = A0;
const uint8_t PIN_RELAY = 8;
const uint8_t PIN_LED = 13; // Uno 板上自带的 LED - 第一阶段不用继电器
const uint16_t WINDOW_MS = 20; // 一个测量窗口的长度
const int CLAP_MARGIN = 80; // 拍手要比房间高出多少个 ADC 计数
const uint16_t REFRACTORY = 120; // 拍手之后装聋的毫秒数(吞掉它自己的回声)
const uint16_t GAP_MAX = 800; // 毫秒:第二次拍手最晚可以在何时到
// 跳线帽在 H(出厂状态)= HIGH 吸合。挪到 L 就把这里设成 false。
const bool RELAY_ACTIVE_HIGH = true;
float ambient = 0; // 滚动的底噪,单位是每个窗口的摆幅计数
bool lampOn = false;
uint8_t state = 0; // 0 = 等第一次拍手,1 = 等第二次拍手
uint32_t lastClapAt = 0;
uint32_t firstClapAt = 0;
// 用 analogRead() 能达到的最快速度采样 WINDOW_MS,返回这段里的
// 峰峰差。最大值减最小值,直流偏置就被消掉了。
int measureWindow() {
uint16_t lo = 1023, hi = 0;
uint32_t start = millis();
while (millis() - start < WINDOW_MS) {
uint16_t s = analogRead(PIN_MIC);
if (s < lo) lo = s;
if (s > hi) hi = s;
}
return (int)(hi - lo);
}
void setLamp(bool on) {
lampOn = on;
digitalWrite(PIN_LED, on ? HIGH : LOW);
digitalWrite(PIN_RELAY, (on == RELAY_ACTIVE_HIGH) ? HIGH : LOW);
}
void setup() {
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_LED, OUTPUT);
setLamp(false); // 别的事情之前,先把继电器驱到关闭
Serial.begin(115200);
// 认一认这个房间:50 个窗口大约一秒。这段时间里请保持安静。
long sum = 0;
for (uint8_t i = 0; i < 50; i++) sum += measureWindow();
ambient = sum / 50.0;
Serial.print(F("noise floor "));
Serial.print(ambient, 1);
Serial.print(F(" counts, clap needs "));
Serial.println(ambient + CLAP_MARGIN, 1);
}
void loop() {
int amp = measureWindow();
uint32_t now = millis();
bool loud = amp > (int)(ambient + CLAP_MARGIN);
// 只有安静的窗口才挪动底噪:每 20 ms 的窗口挪 3%,大约三分之二秒
// 跟上房间的变化,而一次 20 ms 的拍手永远撼不动它。
if (!loud) ambient += 0.03f * (amp - ambient);
bool clap = loud && (now - lastClapAt >= REFRACTORY);
if (clap) {
lastClapAt = now;
Serial.print(F("clap, swing "));
Serial.println(amp);
}
if (state == 0) {
if (clap) { firstClapAt = now; state = 1; }
} else {
if (clap) {
setLamp(!lampOn);
Serial.println(lampOn ? F("lamp ON") : F("lamp OFF"));
state = 0;
} else if (now - firstClapAt > GAP_MAX) {
state = 0; // 单独一次拍手什么都不改变
}
}
}
在 Uno 上这份代码编译出来占 4,418 字节 Flash —— 32 KB 的 13% —— 加 214 字节 RAM。先别接继电器就跑起来:同一份代码会驱动 Uno 板上自带的 13 脚 LED,所以在任何东西「咔嗒」响之前,你可以先把拍手认稳。串口监视器设 115200,会打印出量到的底噪和每一次拍手的摆幅,所以漏掉的那一次拍手,会告诉你它差了多少。
继电器这一级是怎么把灯切换掉的?
继电器模块上那颗 SRD-05VDC-SL-C 的线圈是 70 Ω,所以吸合一次要吃 5 V ÷ 70 Ω ≈ 71 mA。加上 Uno 自己的 45 mA 左右,再加上传感器那几个毫安,整套大约落在 120 mA —— 在 Uno 那颗 USB 自恢复保险丝允许的 500 mA 以内,这也正是这里不需要第二组电源的原因。Uno 的引脚从来不承担那 71 mA:IN 只驱动光耦里的 LED,几个毫安而已,线圈是由 DC+ 喂的。这里光耦的两侧共用 DC−,所以它起的是缓冲而不是隔离作用:真正管用的那层隔离在继电器内部 —— 它的触点和线圈本来就是机械上分开的两块金属。
黑色跳线帽出厂时扣在 H 上,也就是高电平触发 —— 板子在那个三针小排针的一端印着 L、另一端印着 H,通电之前扫一眼就能确认 —— 而这个默认值是对的:Arduino 的引脚从上电到 setup() 跑起来之间都是高阻输入,所以 IN 悬空或为 LOW 时,整个启动过程灯都是灭的。它同时也是「我的继电器反着动」最常见的原因,因为从低电平触发的板子上抄来的代码,是写 LOW 来开灯的。把跳线帽挪到 L,就必须把 RELAY_ACTIVE_HIGH 设成 false。

把灯接在 COM 和 NO 之间,这样继电器不动作、或者整块板子没电的时候,灯都是暗的。这两颗螺丝是一个机械开关,不是一个输出:灯用的还是它自己那组电源 —— 把那组电源的正极线剪断,一头压进 COM,另一头压进 NO,Uno 那条轨上的电就永远流不到灯上。外壳上印着触点能承受多少:250 V AC 10 A、30 V DC 10 A,阻性负载。这里的一切都停在低压直流 —— 一条自带电源的 12 V 灯带,或者一盏小的直流灯。240 V 的灯自带另一套规矩:外壳、爬电距离、线缆载流量,还有你手能不能碰到的端子 —— 先读我们的继电器模块安全指南。拍手是词汇量只有一个词的语音控制,所以语音识别模块是从这里往上走最自然的一步;如果你要的是跟着「动作」走的灯,而不是跟着声音走的,那 PIR 人体感应才是更对的传感器。
真实客户身上常见的错误
指望它在大声音乐里也能用。只有安静的窗口才会挪动底噪 —— 这恰恰就是拍手抬不高自己那道杠的原因 —— 所以一段持续的大声音,就是一道底噪永远爬不上去的杠,代码于是把它读成一次接一次接一次的拍手。把 CLAP_MARGIN 调大、把传感器挪离音箱,或者接受一件事:拍手开关听的是一个安静房间之上的瞬态,而不是在一个吵闹房间里面听。
对着 D0 的每一个边沿做翻转。一次拍手产生六个边沿就翻六次,灯最后停在哪全看运气。修法不是去数边沿,而是在第一个边沿之后,把那 120 ms 里的一切都忽略掉。
想靠拧电位器来修一个模拟方案。那颗微调设定的是 LM393 的参考电压,而这个参考只到得了 D0 —— 它碰不到这份代码读的 A0 信号。
常见问题
为什么我的拍手开关会自己开开关关?
有东西在计时窗口里把阈值清过了两次。把 CLAP_MARGIN 调大,并且让传感器离继电器的「咔嗒」声远一点。
能用 KY-037 的 D0 脚代替 A0 吗?
做一个瞬时的声控继电器可以,前提是记住 D0 静息为 LOW、有声音才变 HIGH。做两次拍手的翻转就不行 —— 一个固定阈值,一次拍手好几个边沿。
为什么我的继电器动作方向反了?
跳线帽出厂扣在 H 上,所以 HIGH 才让线圈吸合,而低电平触发的代码写的是 LOW。要么挪跳线帽,要么把 RELAY_ACTIVE_HIGH 设成 false。
Arduino 的 5 V 脚能带得动继电器模块吗?
5 V 那款可以:线圈 71 mA,对 USB 的 500 mA 预算。12 V 的线圈在 5 V 上是吸不合的。
我能站多远拍手?
只要摆幅还能比底噪高出 CLAP_MARGIN 就行。声压随距离衰减,底噪不会 —— 要么把余量调低,要么把传感器挪近。
最后更新:2026 年 8 月。有问题?WhatsApp 联系我们。



Analog Digital Sound Detector Mic Microphone 4 Pin KY 037 KY-037 For Arduino IOT Application
Arduino Uno Compatible SMD UNO R3 with Type B Cable - ATMEGA328P with CH340G-Microcontroller Project
Relay Module 3.3V 5V 12V 1/2/4/8 Ways Optocoupler Trigger Relay Module 1 2 4 8 Channel Relay Module - 5V RELAY MODULE(1WAY)
MB102 Breadboard 170 400 830 Holes Breadboard Donut Board Arduino Prototype Multi Color - BREADBOARD (400 HOLES)
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 20CM
Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-B (1.0M)
Round Head LED 3mm / 5mm / 8mm - Red/Yellow/Blue/Green/White - 5mm LED (GREEN)
Electronic Component Set Beginner Electrical Learning Kit For Arduino Beginner Learning - ELECTRICAL STARTER KIT