---
title: "AI 立大功、闯大祸？记一次 ESP32 开发调试"
url: "https://lerry.me/post/2026/03/ai-helped-esp32-audio-bug"
date: 2026-03-17T07:50:00.000Z
updated: 2026-09-13T04:22:12.854Z
tags:
  - "调试"
  - "AI"
  - "C++"
  - "ESP32"
  - "Rust"
language: zh-CN
---

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

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

***

## 硬件排查

于是我开始跟 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 对比了两个项目的增益处理函数。测试项目里是这样写的：

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

机器人代码里重构过一次，改成了：

```cpp
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 可以帮你做很多事情，但它无法替代你的品味，无法替代你的判断力。
