Article16 Mar 2026

AI 立大功、闯大祸?记一次 ESP32 开发调试

上次说我的语音机器人已经几乎实现了第一阶段,可以按键和豆包的大模型对话。然后我就开始接屏幕。等这些零件都接好了,我发现一个问题,喇叭的音质不知道什么时候变差了。说变差其实不准确,因为我不确定是从什么时候开始坏的,也许一开始就有问题。具体表现就是破音,音量很大,似乎怎么调都没有变化。


硬件排查

于是我开始跟 AI 聊。AI 说什么,我就做什么。因为我既不懂 C++,也不懂任何的硬件、声音波形之类的。

刚开始 AI 说是功放的线的问题,太长了,容易干扰。我自己做了短线。功放需要稳定的供电,我在 5 伏和接地之间加了 100 微法和 0.1 微法的电容。AI 说可能是电源的问题。我试了笔记本电源、以前亚马逊火棒的 5 伏 1 安的电源、充电宝,全都试了,没有任何变化。

AI 说是喇叭不好,功放可能是坏的。我马上在拼多多下单了,买了好几个喇叭和功放芯片。因为零件没到,就暂时把这个事给撂下了。

又到了周末,新的喇叭到了,换上去,还不如原来的喇叭呢。

之前测试喇叭的时候,只是播放了 AI 帮我生成的一段固定赫兹的声音,只听到了响,并不能分辨好坏。我想,为什么不播放一个音乐,实际试一下效果呢?我从网上找了一首 MP3,丢给 AI,让它从副歌部分截取几秒钟,烧到板子里播放,用了单独的一个测试项目。还是不行,噪音很大。在 AI 的建议下,我把 GAIN 引脚接入了 3.3 伏,音质马上就变好了。但是换回机器人固件,还是不行。我又一次更换了功放和开发板的连接线,换成了新买的更高品质的。


换 Claude Code,一条一条试

Codex 搞不定,我就换 Claude Code。我把症状告诉它,它一条一条给我建议,我一条一条去试:

  • 麦克风 I2S 串扰:播放前把麦克风驱动卸掉。没变化。
  • WiFi 射频干扰:接收完数据把 WiFi 彻底关掉,等 3 秒再播放。没变化。
  • DMA 缓冲区欠载:缓冲区从大改小。没变化。
  • 屏幕刷新阻塞音频线程:屏幕已经拆了,函数也去掉了。没变化。
  • 电源不稳定:换了四种电源,笔记本、火棒充电头、充电宝、台式机 USB。没变化。
  • WiFi 发射功率过高:功率调低。没变化。
  • 网络抖动导致音频不连续:改成先把音频整段缓冲进内存再播放。没变化。

每次排查完,都觉得"这次应该是这个",每次结果都一样。

这些看起来都有道理,因为它们都对应着一个假设:射频干扰、GPIO 串扰、网络抖动、服务端音频异常。在这些都排除以后,AI 又开始怀疑喇叭的问题了。


转折

我让 AI 把豆包返回的问候语音频存下来,在电脑上播放,超级纯净,我从来没有听到这么纯净的声音。毕竟是电脑合成的,怎么可能会有噪音呢?

我又想起来,我之前用于硬件验证的代码,功能很简单,就是播放固定的 440 赫兹的声音。我让 AI 帮我把 TTS 音频嵌进了这个测试项目里播放。超级清晰,从来没听到这个喇叭发出这么清楚的人声,甚至超出了我对它的预期。

同样的喇叭、同样的接线、同样的音频数据,在测试项目上清楚,在机器人程序里破音。问题 100% 在代码里,和硬件无关,和接线无关。

在机器人播放 AI 的问候语音之前,先播放这个测试音频——结果两个问候一样的破音。范围锁定了。


找到了

刷入固件,启动,播放了两次问候语——测试音频和原来的问候语都在,两次,都超级清晰。

我当时直接给 AI 发了一条消息:

解决了!!!就是这个问题,现在声音很小,但是超级纯净了

然后问它:git blame 下看看什么时候写的。这算 bug 吗,还是 C++ 没用好?

Claude Code 对比了两个项目的增益处理函数。测试项目里是这样写的:

Codecpp
constexpr uint32_t SPK_VOLUME_PERCENT = 12;
int16_t s = (int16_t)(((int32_t)s * SPK_VOLUME_PERCENT) / 100);

机器人代码里重构过一次,改成了:

Codecpp
constexpr uint32_t SPK_GAIN_PERCENT = 12;
int32_t scaled = ((int32_t)rawSample * SPK_GAIN_PERCENT) / 100;

看起来一样,但不一样。int32_t × uint32_t,在 C++ 里,有符号数会被隐式提升成无符号数。rawSample 是负数,转成 uint32_t 就变成了一个巨大的正数,再乘以 12,直接溢出。音频波形有正有负,负数的部分全被翻转并放大了,波形下半段变成了一堆溢出的噪声。

constexpr uint32_t 改成 constexpr int32_t,一个字符,问题消失。


反思

这个 bug 是谁写的?所有代码都是 AI 写的,也许是某一次代码重构的时候引入的。原来的代码用 #define SPK_VOLUME_PERCENT 22,整数字面量,隐式有符号,没有这个问题。改成 constexpr uint32_t 的时候,看起来更现代、更规范,实际上踩了一个 C++ 的经典陷阱。

这个代码做起来很快,从 0 开始,花了一点时间设计项目,又花了很少的时间就组装起来了。但是一旦遇到真正的问题,我就持续地陷在里面出不来。

在我熟悉的领域,我可以很快发现问题,做出正确假设。但是在这个项目里,我对 C++ 的类型提升规则、I2S 驱动的参数含义、功放 GPIO 特性完全不了解。我不知道什么是正常,什么是异常。AI 生成的代码我根本看不出问题,只能靠症状反推,这就是为什么排查了这么久。

教训是:调试不熟悉领域的问题,最重要的是先隔离变量。 把完整的信号链拆成独立的段,一段一段验证。存音频在电脑上播放,确认服务器没问题;同一份音频放进测试项目,确认硬件没问题;同一份音频放进机器人代码还是破音,问题锁定在代码。这个过程如果早一点做,可能之前就解决了。

有意思的是,如果这段代码用 Rust 写,这 bug 根本不会存在。Rust 要求所有类型转换都显式写出来,i32 × u32 直接编译报错。当然 Rust 也有自己的问题,生态没有这么成熟。

AI 在高速发展,我经历了从补全,到辅助,到完全交给AI写代码的过程,我对它的未来十分看好。AI 可以帮你做很多事情,但它无法替代你的品味,无法替代你的判断力。

About this article

Author
Lerry
Published
2026-03-16