Bug 故事:两个 DAC 通道并联,我改了 5 个地方才修好(改了5个地方才修好)

📅 2026/7/7 5:33:31
Bug 故事:两个 DAC 通道并联,我改了 5 个地方才修好(改了5个地方才修好)
tags: 嵌入式,调试,DAC,SPI,多通道,Bug故事 category: 嵌入式 type: original series: debug-troubleshootBug 故事两个 DAC 通道并联我改了 5 个地方才修好改了5个地方才修好一句话: 为了输出更大的电流把 DAC 的两个通道并联使用。写完通道 A 之后忘记同步写通道 B导致只有一半电流输出。这个 bug 藏在 5 个代码位置里——初始化、设定值、递增、递减、协议解析——花了三个小时排完。系列文章《嵌入式调试排错》 — 编译错误、看门狗、DAC踩坑、协议对齐——真实Bug故事排查方法论 一个宏搞定 全网独家解决方案 ✅ 改了5个地方才修好 15条指令找出7处错误背景为什么要并联电路里需要驱动一个负载最大 600mA。但单片 DAC 每个通道只能输出 300mA。硬件同事的办法把两个 DAC 通道的输出引脚焊在一起让它们并联供电。CH3 出 300mACH4 也出 300mA合起来 600mA。原理没问题但固件里埋了一个坑。坑写完通道 A忘了通道 B这个 DAC 是通过 SPI 总线控制的。设定电流值时写一个寄存器就完事// 单通道模式300mA 量程 void DAC_SetCurrent(uint8_t u8Channel, uint16_t u16Code) { SPI_CS_Low(); SPI_Write(u8Channel, u16Code); // 通道 A SPI_CS_High(); }当 CH3 和 CH4 并联使用时你脑子里知道它们是同一个东西。所以写代码的时候自然就写成// 并联模式 — 初始版本有 bug void SOA_SetCurrent(uint16_t u16Code) { SPI_CS_Low(); SPI_Write(CH3, u16Code); // 写完通道 A // 忘了写通道 B SPI_CS_High(); }结果只有通道 A 在输出电流通道 B 还在输出上次的值。负载只拿到一半电流。这个问题本身不难发现——如果代码只有一个地方写电流值的话。为什么修了 5 个地方一个真实的嵌入式项目里设定电流值不是只有一个入口的。我翻了一遍代码发现电流值可以从这几个地方被改变位置场景1初始化— 上电默认值2直接设定— 串口发命令设电流 XXX3递增— 串口发命令电流 104递减— 串口发命令电流 -105协议批量写入— 上位机一口气写多个参数这 5 个位置每个都只写了通道 A没写通道 B。修法是一样的——每个位置加一行写通道 B// 修好之后 SPI_Write(CH3, u16Code); // 通道 A SPI_Write(CH4, u16Code); // 通道 B — 一个字节都不能少修完第 1 个位置用串口直接设定电流对了。修完第 2 个递增也对。修到第 5 个——上位机批量写入——才发现还有一个遗漏。5 个位置全修完三个小时过去了。教训怎么避免这种 bug1. 封装不要撒胡椒如果一开始就把并联写入封装成一个函数void DAC_WriteDualChannel(uint16_t u16Code) { SPI_CS_Low(); SPI_Write(CH3, u16Code); SPI_Write(CH4, u16Code); SPI_CS_High(); }然后所有地方只调这个函数——bug 根本不存在。撒胡椒式地到处写 SPI_Write出了问题只能在每一处补。2. 不要靠脑子记硬件细节CH3 和 CH4 是并联的——这个信息只存在脑子里不在代码里。过三个月换个同事维护他看到SPI_Write(CH3, code)会自然认为这就够了。用宏定义把这个关系显式化#define DAC_CH_SOA_A CH3 #define DAC_CH_SOA_B CH4 #define DAC_SOA_IS_DUAL 1 // 标记这个通道是并联的3. 并联通道应该强制同步最彻底的修法在 DAC 驱动的初始化时标记哪些通道是并联的。写任意一个时自动同步另一个。这样上层代码完全不需要知道并联这件事// 驱动层 — 自动同步并联通道 void DAC_WriteChannel(uint8_t u8Ch, uint16_t u16Code) { SPI_CS_Low(); SPI_Write(u8Ch, u16Code); // 查同步表这通道有没有并联的兄弟 uint8_t u8Partner g_au8SyncPartner[u8Ch]; if (u8Partner ! 0xFF) { SPI_Write(u8Partner, u16Code); // 自动同步 } SPI_CS_High(); }硬件细节封装在驱动层上层代码完全不用关心。总结教训一句话封装并联写入应该是一个函数不是到处撒的 SPI_Write显式化硬件细节写在宏里不要只存脑子里同步机制并联关系应该在驱动层自动处理上层无知这个 bug 不是技术多难而是一个硬件细节泄露到了 5 个代码位置。封装做好的话它根本不会发生。实测对比封装前: 5处代码各自写SPI_Write | 封装后: 1个函数统一处理bug永久消灭有用的话点个收藏下次调试直接用。有问题欢迎评论区交流看到了都会回。下一篇《一个宏搞定》——编译错误、看门狗、DAC踩坑、协议对齐——真实Bug故事排查方法论