模块熟悉

第 13 课:麦克风模块——声音有多响

它不是录音机,是个"响度计"——而且安静的时候读数也不是 0

我是吴老师。这节课我要先泼一盆冷水。

看到"麦克风"三个字,你脑子里是不是已经冒出一堆想法了——能录音吧?能识别我说的话吧?能不能做个语音助手,我说开灯它就开灯?

都不能。

这个模块不能录音,不能存音频,不能识别你说了什么词。 一个都做不到。

那它能干什么?

它只能告诉你一件事:现在有多响。

就这一件事。它是把"响度计",不是麦克风。

先把期待降下来,这节课你才不会失望。 后面你会发现,就这一件事,其实够用得很——拍手开灯、噪音报警、声控开关,全是它。

一、看视频时注意的三件事

视频教程声音模块:接线、读声音大小、用阈值做反应下载 ↗

第一,注意它也是四根线,还是只用一根。

模块上有 AODO 两个输出,跟光敏、热敏一模一样

针脚 含义 我们用它吗
AO 模拟量,声音越响数值越大 用这个
DO 数字量,超过某个响度才翻转 ❌ 本课不用
GND
VCC 电源

这三个模块(光敏、热敏、麦克风)丝印完全相同,长得也像,最容易拿混的就是它们。 区分方法还是看顶上的元件:光敏是带花纹的扁圆片,热敏是玻璃珠,麦克风是一个圆形的金属小咪头

第二,注意我安静的时候那个数。

它不是 0。

视频里我一开始什么都没干,串口上就已经在刷一个数了。可能是 100 多,可能是 300 多,每个人还不一样。

这一点是这节课最容易踩的坑,后面我专门讲。

第三,注意我说话的时候数字是怎么动的。

不是慢慢涨上去的——是猛地跳一下,然后掉回来,又跳一下,又掉回来。

声音就是这样,它变化得非常快

二、它读到的是什么

先把原理说清楚,就一段话。

声音是快速起伏的波形,读到的值反映的是当前响度,峰值保持能抓住一瞬间的响声

声音在物理上是一条"上下起伏的波"。

你说话、拍手的时候,空气被压缩、又松开、又压缩……这个疏密变化传到咪头里,咪头就输出一个跟着一起起伏的电压

而这个模块做的事,就是把这条波"现在有多高"变成一个数字给你。

注意——它给你的是"这一刻的高度",不是"这句话有多吵"。

你喊一秒钟,这条波里其实起伏了几百上千次。你读到的只是其中某一个瞬间。

这就带来一个很实际的问题,先记住,后面动手会碰到。

三、第一个坑:安静的时候不是 0

现在说那个坑。

你可能会本能地这么写代码:

C++
int sound = analogRead(0);

if (sound > 100) {        // 有声音!
  digitalWrite(3, HIGH);
}

看着挺合理,对吧?没声音就是 0,有声音就大于 0。

但它是错的。

因为安静的时候,读数不是 0。

那这个底数是从哪来的?两个地方:

  • 咪头本身有静态输出——就算一点声音都没有,它输出的电压也不是正好在正中间
  • 模块上有个放大电路——它把咪头的信号放大了一截,连那个"静态偏移"也一起放大了
  • 再加上 ADC 自己的误差和电源的纹波

这些叠在一起,就是你安静时看到的那个数。它是模块的"本底",不是"声音"。

所以判断"吵不吵",绝对不能拿 0 当基准。

要拿"安静时的读数"当基准。

比如你测出来安静时是 250,那你不能说"大于 0 就是有声音",得说"比 250 明显大才算有声音"——比如 450、600,看你的需求。

这个"先测一遍再定数"的动作,你在第 11 课已经学过了,它叫标定。

光敏标定的是"亮和暗的分界",热敏标定的是"热和凉的分界",这里标定的是"安静和吵的分界"。换了个量,方法一个字没变。

四、第二个坑:声音跑得太快

现在说第二个坑,它比第一个更隐蔽。

回到刚才那句话:你读到的只是某个瞬间的高度。

那问题就来了——

你拍一下手,这个响声只持续了几十毫秒。你的程序如果刚好在它响完之后才读,会读到什么?

读到安静时的底数。然后判断"没有声音"。

啪的一声,就这么漏掉了。

这不是你代码写错了,这是"抽样"这件事本身的性质——你不可能一秒钟检查无穷多次,总有缝隙。

那怎么办?两个思路,各有取舍。

策略 怎么做 好处 代价
峰值保持 连续读很多次,只记住最大的那个 不容易漏掉瞬间的响声 反应"迟钝"一点,一时吵就一直算吵
取平均 连续读很多次,求平均值 数字稳,不抖 会把瞬间的响声抹平,容易漏

看出来了没有——这俩是矛盾的。想要"不漏"就用峰值保持,想要"不抖"就用取平均,你不可能两个都要。 得看你要做什么:

  • 拍手开关灯 → 你关心的就是那一下"啪",用峰值保持
  • 监测房间吵不吵 → 你要的是平均的噪音水平,一阵咳嗽不该让它报警,用取平均

视频里我用的是峰值保持。代码大概长这样:

C++
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 次,拍十下手你能漏掉八下。

声音这东西,你检查得越勤,抓得越准。

五、第三个坑:阈值还是得自己定

又是阈值。 你已经很熟了。

阈值就是自己画的一条线,读数高于它算亮,低于它算暗

阈值是你自己画的一条线。 线的一边叫"安静",另一边叫"响了"。

C++
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 是怎么来的? 不是抄的,是算出来的

  1. 先安静跑一遍,记下安静时的读数(假设是 250)
  2. 再拍手、说话,看数字能跳到多高(假设跳到 900 以上)
  3. 在这两个数中间取一个——比如 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; 换成累加再除以次数),再看看拍手还能不能触发。你会亲眼看到"取平均会漏掉瞬间响声"是真实存在的,不是我吓唬你。

自测

单选题这个麦克风模块,能做到下面哪件事?
单选题教室很安静,串口却一直显示 250 左右。这说明?
单选题为什么判断"拍手"要用峰值保持,而不是取平均?
问答题麦克风的阈值该怎么定?为什么不能直接写一个抄来的数字?
拓展为什么手机能听懂你说话,而这个模块不能?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"语音识别 原理 声学模型"、"麦克风阵列 降噪"。想一想:手机里那颗芯片一秒钟要算多少次?识别"开灯"这两个字,需要知道声音的哪些信息,而不只是"有多响"?

拓展用取平均的时候,怎么才能既稳又不漏掉瞬间的响声?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"滑动平均 窗口大小"、"指数加权平均 传感器滤波"。想一想:如果我改成"最近 10 次的平均",跟"开机到现在所有次的平均",有什么区别?窗口大小该怎么选?

本节你要动手做的事

  • [ ] 接好麦克风VCC3.3VGNDGNDAO→ 一个模拟引脚(注意是 AO 不是 DO
  • [ ] 安静地跑一分钟,记下安静时的读数范围——这就是你的基准
  • [ ] 拍手、说话、敲桌子,看数字怎么跳、能跳到多高
  • [ ] delay 改小,体会一下"检查得越勤,抓得越准"
  • [ ] 做个声控灯:用峰值保持 + 自己标定的阈值,拍一下手就亮
  • [ ] (可选)把峰值保持换成取平均,看看拍手还能不能触发

这节课的知识点,其实比光敏和热敏都少——还是 analogRead,还是阈值。

但它是这三节里最容易做错的一个。 为什么?因为它有两个坑:安静的时候不是 0(坑在"基准")、声音变化太快会漏(坑在"时机")。

这两个坑我提前给你指出来了,你就能绕过去。 而很多人的第一版声控灯,就是卡在这两个地方——灯要么一直亮,要么怎么拍都不亮,代码看着一点问题都没有

现在你手上已经有三个模拟传感器了:光敏、热敏、麦克风。 你回头看看,它们三个的代码骨架是不是几乎一样? 读一个模拟值 → 跟一条自己定的线比一比 → 让灯亮或灭。

换的只是"什么在改变那个数",骨架从来没变过。 这个感觉,比记住任何一个传感器的用法都值钱。

下一节我们学红外传感器。不过先提前说一句,别看到"红外"就以为它能感应人——它感应的是几厘米内有没有东西挡在它面前,完全不是一回事。它到底是干什么的,我们下节课说。

去拍个手试试吧,看看你的板子能不能"听见"。