模块熟悉
第 13 课:麦克风模块——声音有多响
它不是录音机,是个"响度计"——而且安静的时候读数也不是 0
我是吴老师。这节课我要先泼一盆冷水。
看到"麦克风"三个字,你脑子里是不是已经冒出一堆想法了——能录音吧?能识别我说的话吧?能不能做个语音助手,我说开灯它就开灯?
都不能。
这个模块不能录音,不能存音频,不能识别你说了什么词。 一个都做不到。
那它能干什么?
它只能告诉你一件事:现在有多响。
就这一件事。它是把"响度计",不是麦克风。
先把期待降下来,这节课你才不会失望。 后面你会发现,就这一件事,其实够用得很——拍手开灯、噪音报警、声控开关,全是它。
一、看视频时注意的三件事
第一,注意它也是四根线,还是只用一根。
模块上有 AO 和 DO 两个输出,跟光敏、热敏一模一样:
| 针脚 | 含义 | 我们用它吗 |
|---|---|---|
AO |
模拟量,声音越响数值越大 | ✅ 用这个 |
DO |
数字量,超过某个响度才翻转 | ❌ 本课不用 |
| GND | 地 | ✅ |
| VCC | 电源 | ✅ |
这三个模块(光敏、热敏、麦克风)丝印完全相同,长得也像,最容易拿混的就是它们。 区分方法还是看顶上的元件:光敏是带花纹的扁圆片,热敏是玻璃珠,麦克风是一个圆形的金属小咪头。
第二,注意我安静的时候那个数。
它不是 0。
视频里我一开始什么都没干,串口上就已经在刷一个数了。可能是 100 多,可能是 300 多,每个人还不一样。
这一点是这节课最容易踩的坑,后面我专门讲。
第三,注意我说话的时候数字是怎么动的。
不是慢慢涨上去的——是猛地跳一下,然后掉回来,又跳一下,又掉回来。
声音就是这样,它变化得非常快。
二、它读到的是什么
先把原理说清楚,就一段话。
声音在物理上是一条"上下起伏的波"。
你说话、拍手的时候,空气被压缩、又松开、又压缩……这个疏密变化传到咪头里,咪头就输出一个跟着一起起伏的电压。
而这个模块做的事,就是把这条波"现在有多高"变成一个数字给你。
注意——它给你的是"这一刻的高度",不是"这句话有多吵"。
你喊一秒钟,这条波里其实起伏了几百上千次。你读到的只是其中某一个瞬间。
这就带来一个很实际的问题,先记住,后面动手会碰到。
三、第一个坑:安静的时候不是 0
现在说那个坑。
你可能会本能地这么写代码:
int sound = analogRead(0);
if (sound > 100) { // 有声音!
digitalWrite(3, HIGH);
}看着挺合理,对吧?没声音就是 0,有声音就大于 0。
但它是错的。
因为安静的时候,读数不是 0。
那这个底数是从哪来的?两个地方:
- 咪头本身有静态输出——就算一点声音都没有,它输出的电压也不是正好在正中间
- 模块上有个放大电路——它把咪头的信号放大了一截,连那个"静态偏移"也一起放大了
- 再加上 ADC 自己的误差和电源的纹波
这些叠在一起,就是你安静时看到的那个数。它是模块的"本底",不是"声音"。
所以判断"吵不吵",绝对不能拿 0 当基准。
要拿"安静时的读数"当基准。
比如你测出来安静时是 250,那你不能说"大于 0 就是有声音",得说"比 250 明显大才算有声音"——比如 450、600,看你的需求。
这个"先测一遍再定数"的动作,你在第 11 课已经学过了,它叫标定。
光敏标定的是"亮和暗的分界",热敏标定的是"热和凉的分界",这里标定的是"安静和吵的分界"。换了个量,方法一个字没变。
四、第二个坑:声音跑得太快
现在说第二个坑,它比第一个更隐蔽。
回到刚才那句话:你读到的只是某个瞬间的高度。
那问题就来了——
你拍一下手,这个响声只持续了几十毫秒。你的程序如果刚好在它响完之后才读,会读到什么?
读到安静时的底数。然后判断"没有声音"。
啪的一声,就这么漏掉了。
这不是你代码写错了,这是"抽样"这件事本身的性质——你不可能一秒钟检查无穷多次,总有缝隙。
那怎么办?两个思路,各有取舍。
| 策略 | 怎么做 | 好处 | 代价 |
|---|---|---|---|
| 峰值保持 | 连续读很多次,只记住最大的那个 | 不容易漏掉瞬间的响声 | 反应"迟钝"一点,一时吵就一直算吵 |
| 取平均 | 连续读很多次,求平均值 | 数字稳,不抖 | 会把瞬间的响声抹平,容易漏 |
看出来了没有——这俩是矛盾的。想要"不漏"就用峰值保持,想要"不抖"就用取平均,你不可能两个都要。 得看你要做什么:
- 拍手开关灯 → 你关心的就是那一下"啪",用峰值保持
- 监测房间吵不吵 → 你要的是平均的噪音水平,一阵咳嗽不该让它报警,用取平均
视频里我用的是峰值保持。代码大概长这样:
int peak = 0;
// 连续读 50 次,只留最大的那个
for (int i = 0; i < 50; i++) {
int v = analogRead(0);
if (v > peak) peak = v;
delay(1);
}
Serial.println(peak); // 这 50 次里最响的那一下
delay(100);注意那个 delay(1)——别用第 11 课那个 delay(200)。一次循环 200 毫秒,一秒才检查 5 次,拍十下手你能漏掉八下。
声音这东西,你检查得越勤,抓得越准。
五、第三个坑:阈值还是得自己定
又是阈值。 你已经很熟了。
阈值是你自己画的一条线。 线的一边叫"安静",另一边叫"响了"。
int peak = 0;
for (int i = 0; i < 50; i++) {
int v = analogRead(0);
if (v > peak) peak = v;
delay(1);
}
// 静音值 + 幅度 = 阈值
if (peak > 600) { // 这个 600 是举例,你得自己测
digitalWrite(3, HIGH); // 响了就亮灯
} else {
digitalWrite(3, LOW);
}那个 600 是怎么来的? 不是抄的,是算出来的:
- 先安静跑一遍,记下安静时的读数(假设是 250)
- 再拍手、说话,看数字能跳到多高(假设跳到 900 以上)
- 在这两个数中间取一个——比如 600,比安静值高出一大截,又留了余量
写代码的时候心里要清楚:阈值 = 安静值 + 一个幅度。 这样换到别的房间,只要重新测一下安静值,阈值就能跟着调整。而如果你硬记一个"600",换个环境可能就废了。
还有个小提醒:如果你把阈值定得离安静值太近(比如安静 250,阈值定 280),它会自己疯狂闪——因为那个底数本来就在飘,一会儿 245 一会儿 260,刚好来回跨过阈值。
这就是第 11 课提过的那个"临界点抖动"问题。解决办法也一样:把阈值定远一点,留足余量。
六、动手
第一步:接线。
| 麦克风 | 接到 |
|---|---|
| VCC | 3.3V |
| GND | GND |
AO |
一个模拟引脚(GPIO0 ~ GPIO5) |
注意是 AO 不是 DO。 接成 DO 的话,你只能读到 0 和 1,那你就丢掉了"有多响",只剩下"比某个门槛响"——信息量差远了。
第二步:先安静地跑一遍。
别说话,别敲桌子,让教室安静下来。 跑个一分钟,看那个数字大概在什么范围飘。
把这个数记在纸上。这就是你的"安静值"。
如果这时候读数是 0 或者 4095 一直不动,先查线——别急着怀疑模块坏了。 十有八九是
AO接错了位置。
第三步:开始制造声音。
先拍一下手。
看串口——数字是不是猛地跳一下,又掉回去?
然后说话。 大声一点,说一整句。
再然后,试试吹气、敲桌子、挪椅子。 看看哪种声音能让它跳得最高。
你会发现一件事:不同的声音,跳起来的高度差别很大。拍手那种又短又尖的,跳得最高;你说话这种持续的,反而没那么夸张。
第四步:把你的阈值算出来,然后验证。
拿纸上的安静值加上一个幅度,写进代码(别抄我的 600),然后拍手看灯亮不亮。如果它一直亮着或者一直不亮,那就是阈值定得不对——回去改数,别改代码。
第五步(可选):对比两种策略。
把峰值保持改成取平均(把 if (v > peak) peak = v; 换成累加再除以次数),再看看拍手还能不能触发。你会亲眼看到"取平均会漏掉瞬间响声"是真实存在的,不是我吓唬你。
自测
答案与解析
它是个"响度计",不是录音机。 录音需要把波形按时间完整存下来,识别语音需要跑复杂的模型——都不是这个几块钱的模块能做到的。它只做一件事:把声音的瞬时强度变成一个 0~4095 的数。先接受这一点,你就不会走弯路。
答案与解析
咪头本身有静态输出,模块上的放大电路又把这个偏移一起放大了,再加上 ADC 的误差——这几样叠起来就是你看到的那个数。所以正确做法是:先测出安静值,在它基础上加一个幅度当阈值。拿 0 当基准的话,程序会一直判定为"有声音"。
答案与解析
平均是把一段时间内的值"摊平"——一个几十毫秒的尖峰扔进 50 次采样里,平均下来几乎看不出来。 峰值保持只记最大的那个,尖峰再短也留得住。代价是它反应迟钝:一时吵过之后,要等所有采样都低于阈值才会复位。想要不漏用峰值,想要稳用平均,两者不可兼得。
答题要点
答题要点应该包含:① 先测安静值——在你要用的实际环境里跑一遍,记下安静时读数的大致范围;② 再测响声的高度——拍手、说话,看数字能跳到多高;③ 阈值 = 安静值 + 一个幅度,取在两者中间,并且离安静值远一点,留足余量,否则底数的正常飘动会让它疯狂闪烁;④ 为什么不能抄——因为安静值跟环境(教室、家里、马路边)和模块个体都有关系,别人的数字在他的环境里成立,在你这里可能一直触发或者永远不触发。本质上这跟第 11 课的标定是同一件事。
拓展为什么手机能听懂你说话,而这个模块不能?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"语音识别 原理 声学模型"、"麦克风阵列 降噪"。想一想:手机里那颗芯片一秒钟要算多少次?识别"开灯"这两个字,需要知道声音的哪些信息,而不只是"有多响"?
拓展用取平均的时候,怎么才能既稳又不漏掉瞬间的响声?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"滑动平均 窗口大小"、"指数加权平均 传感器滤波"。想一想:如果我改成"最近 10 次的平均",跟"开机到现在所有次的平均",有什么区别?窗口大小该怎么选?
本节你要动手做的事
- [ ] 接好麦克风:
VCC→3.3V,GND→GND,AO→ 一个模拟引脚(注意是AO不是DO) - [ ] 安静地跑一分钟,记下安静时的读数范围——这就是你的基准
- [ ] 拍手、说话、敲桌子,看数字怎么跳、能跳到多高
- [ ] 把
delay改小,体会一下"检查得越勤,抓得越准" - [ ] 做个声控灯:用峰值保持 + 自己标定的阈值,拍一下手就亮
- [ ] (可选)把峰值保持换成取平均,看看拍手还能不能触发
这节课的知识点,其实比光敏和热敏都少——还是 analogRead,还是阈值。
但它是这三节里最容易做错的一个。 为什么?因为它有两个坑:安静的时候不是 0(坑在"基准")、声音变化太快会漏(坑在"时机")。
这两个坑我提前给你指出来了,你就能绕过去。 而很多人的第一版声控灯,就是卡在这两个地方——灯要么一直亮,要么怎么拍都不亮,代码看着一点问题都没有。
现在你手上已经有三个模拟传感器了:光敏、热敏、麦克风。 你回头看看,它们三个的代码骨架是不是几乎一样? 读一个模拟值 → 跟一条自己定的线比一比 → 让灯亮或灭。
换的只是"什么在改变那个数",骨架从来没变过。 这个感觉,比记住任何一个传感器的用法都值钱。
下一节我们学红外传感器。不过先提前说一句,别看到"红外"就以为它能感应人——它感应的是几厘米内有没有东西挡在它面前,完全不是一回事。它到底是干什么的,我们下节课说。
去拍个手试试吧,看看你的板子能不能"听见"。