工程日志
项目里一共记录了 13 个真实事故,这里挑出 6 个成体系地写。 每个都按同一个结构:现象 → 证据 → 排除过程 → 根因 → 修复 → 方法论。
为什么写这一页:功能清单谁都能列,怎么找到根因才是工程能力的证据。 而且我把走过弯路的部分也留着——过程真实比结论漂亮更有用。
CASE 1 · 无日志复位:硬件问题伪装成软件崩溃
语音板硬件排障最难的一个
现象
语音板在播报时无规律复位,且没有任何日志——只有开机痕迹,崩溃点不可复现、不可定位。 软件层的第一反应是"某个任务死循环或者栈溢出"。
排除过程(我走的弯路)
- 怀疑任务死循环 → 周期性打印所有任务状态,全部正常,排除
- 怀疑 PSRAM 模式错误 → 改成 QUAD 直接启动失败,反证板子是 OCT,排除
- 怀疑欠压检测阈值设置不当 → 调低阈值,没用
- 被一个 Xtensa 32 位 ABI 下的变参对齐 bug 误导,打印出十亿级的假样本数,浪费了一阵
E BOD: Brownout detector was triggered。
根因
功放(MAX98357A)从 3.3 V 轨取电,播放瞬间的瞬态大电流把 3.3 V 轨拉垮, 电压跌破 BOD 阈值(实测跌到 2.1 V 以下)→ 触发硬件欠压复位。
这就解释了"为什么没有日志":BOD 复位是硬件级的,比软件写日志快得多, 软件根本来不及留下任何痕迹。不是软件 bug,是供电设计问题。
修复
- 硬性要求:必须用充电器 / 独立 5 V 电源供电(电脑 USB 口 500 mA 限制撑不住功放瞬态)
- 改善:功放 VIN 改接 5 V 轨,或就地并联 ≥470 µF 电容储能
- 软件侧妥协:音量降到 30
方法论
- 无日志复位,优先怀疑硬件层(供电、时钟、复位电路),而不是软件死循环
- 区分"软件 bug"与"硬件不稳"最快的办法:换一个更强的电源做对照实验
- 当时的设备条件是"没有示波器",我靠日志打点 + 控制变量走通了; 但现在的做法会先测电源轨——那才是正道,这条弯路我留着
CASE 2 · 音频链路的四个坑:每个根因都不一样
语音板I2S 驱动四连坑
这块语音板是裸 I2S 方案(数字麦克风 + 数字功放,中间没有 codec 芯片)。 好处是省掉 codec 和它的 I2C 控制线;代价是采样率、槽宽、声道全得自己在软件里配对—— 四个坑就是这么来的。
| # | 现象 | 根因 | 修复 |
|---|---|---|---|
| 1 | 麦克风完全没声音 | I2S 通道没有显式使能,读写直接返回 ESP_ERR_INVALID_STATE,外设根本没启动,既没时钟也没数据 |
显式调用 i2s_channel_enable() |
| 2 | 采到的数据是错的 | 槽位宽用错:配 32-bit 槽位时 BCLK 翻倍到 1.024 MHz,与麦克风时序假设不匹配 → 输出 8192 步长的乱码 | 换 16-bit 槽位(BCLK 512 kHz)即正常 |
| 3 | 唤醒词触发不了 | 输入幅度只有满幅的 ~1.5%,电平太低 | 读取后 ×16 增益 + 限幅 |
| 4 | 播放沙哑、语速像 2 倍速 | 发送端是立体声槽,单声道数据直接写进去被按双声道解释 → 有效采样率减半、播放速度翻倍 | mono → stereo 复制(L = R) |
两个值钱的判据
- 固定步长乱码 = 位错位(8192 = 2¹³,是位边界整体错开的特征); 随机噪声 = 时钟/连接问题。这一条判据让我没在错误方向上耗时间。
- "2 倍速"是采样率不匹配的经典听感——症状会伪装,"沙哑"其实是"太快"。
方法论
音频链路上游的默认配置最坑:克隆来的代码带着一堆隐式假设(通道已使能、槽宽 16、增益够大、mono 会自动展开), 在你的器件组合上可能一条都不成立。质疑默认配置,比读懂自己的代码更重要。
CASE 3 · 雷达丢一个字节,整帧就乱:用状态机做自愈
跌倒板协议解析设计取舍
问题
毫米波雷达以固定格式持续发帧:SOF(0x01) + ID(2B) + LEN(2B) + TYPE(2B) + HCK(1B) + DATA(N) + DCK(1B),
两个校验字段都是 XOR 取反。
bring-up 阶段发现雷达 UART 会偶发丢字节。如果按"收到完整一帧再解析"的写法, 丢一个字节会让后续所有帧整体错位,而且错位后就再也对不齐了。
方案:逐字节状态机
我实现的是逐字节状态机而不是整帧解析——因为 UART 字节流没有任何成帧保证。 每收一个字节走一个状态:找 SOF → 读 ID/LEN/TYPE → 校验 HCK → 缓存 DATA → 校验 DCK → 帧合法才上抛。
关键收益:丢包自愈是自然发生的——任一校验失败就退回"找 SOF"状态, 不需要缓冲区重同步,也不需要上层干预。上层最终拿到 x/y/z/speed 四个浮点值,直接进判定逻辑。
可观测性
解析失败不是静默吞掉:失败帧计入丢帧计数,并且能报告 "前导噪声长度 + SOF 首次出现的偏移"——排障时这比"解析失败"四个字有用得多。
方法论
流式数据要给"重新同步"留一条低成本路径。 状态机让"恢复"成为默认行为,而不是异常处理分支。
CASE 4 · 引脚选错:GPIO 和 PSRAM 总线打架
语音板板级数据手册陷阱
现象
第一版引脚方案把麦克风放在 GPIO33/35。刷进去直接崩溃:
Guru Meditation Error: Core 0 panic'ed (Interrupt wdt timeout)。
根因
GPIO33–37 是这块板 PSRAM 的总线引脚(OCT 模式)。 I2S 占用它们 = 和外设总线抢引脚,I2S 初始化直接挂起。另一段 GPIO26–32 是 flash 总线,同样不可用。
第二个坑:引脚"可用"不等于"干净"
换到 GPIO15/16 后程序能跑,但数据仍然不对。我没有示波器, 就用 PCNT 硬件计数器飞线测时钟频率——读到 1.126 MHz / 120 kHz 这种"不该存在"的频率, 判断这两个脚被板载信号干扰。
换成 GPIO12/13 后数据立刻正常。所以最终接线表里这两个脚是明确避开的。
方法论
- 数据手册只告诉你"这个脚能不能配成外设",不告诉你"它有没有被别的东西占着"。 移植到新板子时,引脚可用性要按"物理占用"重新核对,而不是按芯片手册的复用表。
- 没有示波器也能测频:PCNT 计数器 + 飞线。工具不够时,实验设计来凑。
CASE 5 · 审计缺口:「校验逻辑存在」不等于「校验有效」
EvoAgent数据依赖可证伪验证
现象
系统里需求进入时有两条把关:工具名冲突检测、防重复投递去重。 文档上写得好好的,看起来互为补充。
根因:同源依赖
我实测发现两条检查都从来没有生效过——因为它们的"已装插件"维度 读的是同一个从未被写入的文件(保存函数有实现,但全代码库零调用点)。 于是两条检查的那一维度同时恒为空集:
- 与已装插件重名的需求拦不住
- 同一能力被重复投递时会重复构建,而不是被拦下
"同源"是排查盲区——两条看起来独立的检查,可能共享同一个从未被生成的事实来源。
修复
- 补上落盘调用与"安装成功"审计事件
- 并加了一条我认为最重要的约束:登记以"插件文件真实存在"为前提—— 需求标了完成但文件已被删除的,拒绝登记并告警。注册表反映已装现实,不是纸面声明。
- 幂等:同名内容未变则不重复写盘、不重复记事件
怎么证明修好了:造一个「本应被拦下」的场景
不是"看返回值对了",而是投一条重复能力需求,看它是否真的被拦下:
| 检查 | 结果 |
|---|---|
| 重复需求是否被拦 | 5 秒内被拒,错误信息点名是「已装插件工具」冲突 |
| 反证:配置文件有没有被改动 | 没有(mtime 未变)→ 说明构建进程根本没被启动,零浪费构建 |
| 真实进化一次(新能力) | 安装审计事件自动落盘,插件注册表自动更新 |
第二行才是关键:它证明的不是"返回值正确",而是"系统没有做多余的动作"。 这就是"可证伪"和"看了一眼日志"的区别。
方法论
验证机制时不信它存在,信它拦得住。 另外:把没修的部分也如实写进公开文档(安全扫描器的一处误报、一条被遮蔽的分支), 暴露边界比假装完美更能赢得信任。
CASE 6 · 安全扫描器的误报:真正的风险是「告警疲劳」
EvoAgent安全判断力
现象
系统对自动生成的新插件做装后危险模式扫描,报出:
检测到危险模式 ['exec('],请人工复核。
根因:裸子串匹配
复查后确认是误报:命中点是插件里正则的 RegExp.prototype.exec() 调用
(解析日志文本的常规写法),与命令执行毫无关系。
根因是危险模式表里的 "exec(" 走的是裸子串匹配,因此会命中任何 .exec(。
为什么我没改它
但另一方面,安全扫描器"宁可误报"本身是合理设计,且消息写的就是"请人工复核"。 所以我只记录、不擅自降低灵敏度,把修法(改成"前面不是点号的 exec(",需把扫描从子串改为正则)写进文档交给人决定。
方法论
误报本身不危险,它导致的"告警疲劳"才危险——当一条告警经常是假的, 真出问题时它也会被忽略。所以误报值得进文档、值得被修,但修法要由承担风险的人定。
四条 90 秒 STAR(练过、能顺口讲出来)
① 雷达丢包自愈
UART 字节流没有成帧保证 → 用逐字节状态机代替整帧解析 → 校验失败退回找 SOF, 重新同步成为默认行为而不是异常分支;丢帧可观测。
② 无日志复位 → 欠压
软件层全部排除后做换电源对照实验 → 定位到功放瞬态电流拉垮 3.3 V 轨触发 BOD。 硬件问题伪装成软件崩溃,先查电。
③ I2S 四连坑
四个症状、四个不同根因:通道未使能 / 槽宽错 / 增益低 / mono 写进立体声槽。 判据:"固定步长乱码 = 位错位"。
④ 审计缺口闭环
顺着数据依赖找出两条检查同源失效 → 修复 → 用"本应被拦下"的场景做可证伪验证 (含"没发生多余动作"的反证)。
三条我坚持的工程判断
- 该用规则的地方不用模型 —— 跌倒判定的初筛是雷达规则状态机,不是神经网络。 用最简单的机制解决能解决的问题,把模型留给真正需要它的地方。
- 无日志复位先怀疑硬件 —— 软件排查走完就换对照实验,别在错误的方向上深挖。
- 没做完的不写成做完了 —— 未测量的指标主动列出,未验证的版本明确标注, 未修的问题写进公开文档。