两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案

发布时间:2026/7/6 1:25:40
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案 SH367306 AFE 调试实录四个关键问题与解决方案面试场景你调试 SH367306/SH367309 期间遇到了哪些最难搞的问题怎么解决的本文基于量产 60V 20 串 BMS 代码还原四个真实工程问题每个都附代码级解决路径。问题一I2C 寄存器被 EMI 打翻 —— 为什么不能用普通的 I2C 驱动现象BMS 在做大电流放电测试30A PWM 斩波时AFE 的配置寄存器会随机跳变。最常见的表现是均衡开关莫名全关、短路保护阈值被改、充放电 MOS 意外关断。用示波器抓 I2C 波形能看到偶尔的毛刺但不是每次故障都能复现。根因BMS 板上的 MOSFET 在 30A 电流下以 303Hz 的 PWM 频率开关di/dt 可达 30A/100μs 300A/ms。这个瞬态在 PCB 走线上感应的噪声通过两个途径影响 AFEI2C 数据线SDA/SCL耦合直接导致读写数据错误AFE 寄存器被静电/EMI 直接打翻即使不通信寄存器值也会跳变第二个途径尤其隐蔽——程序没有主动写寄存器但寄存器的值自己变了。这说明不是 I2C 通信的问题而是AFE 芯片内部的寄存器单元在强 EMI 下发生了 bit-flip。解决方案三层容错架构第一层I2C 读写重试// Sh367306_1_Drv.c — 每次读写失败重试 5 次staticuint8_tAfeIf_Read(uint8_tRdAddr,uint8_tLength,uint8_t*RdBuf){uint8_ttimes0;while(timesTRY_TIMES){// TRY_TIMES 5resultTwi1Drv_Read(SlaveAdd1,RdAddr,Length,RdBuf);if(result1)returnresult;// 成功就返回// 失败继续重试}returnresult;}这一层解决的是通信链路上的偶发错误——I2C 波形被干扰导致 ACK 丢失或数据位翻转。第二层写入后回读验证// Sh367306_1_Drv.c — 写寄存器后立即回读确认值确实写入了staticuint8_tAfeIf_RegWrite(uint8_tRdAddr,uint8_tData){uint8_tRegWriteFlg1;uint8_tBuffer[2]{0};while(RegWriteFlg){AfeIf_Write(RdAddr,Data);// 写入AfeIf_Read(RdAddr,2,Buffer);// 立即回读if(Buffer[0]Data)break;// 一致 → OKif(RegWriteFlg20)return0;// 20 次都不一致 → 报错}return1;}这一层解决的是写入不生效的问题——AFE 内部正在做 ADC 转换时收到 I2C 写命令可能导致写入被忽略。回读验证确保每个寄存器写入都是实打实的。第三层运行时定时回读校验// Sh367306_1_Drv.c:1041-1049 — 每 4s 检测 SCONF1 是否被电磁干扰打翻if(s_usCTLDPinChek%1001){// 100 × 40ms 4sAfeIf_Read(SCONF1,2,DataBuff);if(DataBuff[0]!g_Afe1Sconfig1.Data){AfeIf_RegWrite(SCONF1,g_Afe1Sconfig1.Data);// 被改了回写修复}}这一层是最关键的一层。它不依赖任何通信触发——即使程序完全没有操作 AFEAFE 的寄存器也可能被 EMI 打翻。每 4 秒一次的巡检就是安保巡逻发现异常立刻修复。面试要点“这个问题让我意识到在电力电子环境中不能假设芯片的寄存器是稳定的。普通 I2C 驱动的思维是’我写了所以值就是对的’——这在实验室是对的在 30A PWM 的 PCB 上不是。三层容错分别解决三个不同层面的问题通信链路错误、写入不生效、寄存器被动翻转。”问题二电流零点温漂 —— 为什么静止状态下的电流读数是 -1.5A现象BMS 在常温25°C下校准完毕后电流精度很好±50mA。但样品放在高低温箱里测试时-20°C 时0A 静止状态电流读数约-1.2A60°C 时0A 静止状态电流读数约0.8A这意味着低温下电池实际在休息BMS 却判定有 1.2A 放电电流触发模式切换和 SOC 累积误差。更糟的是这个误差在不同板子上还不一样——有的偏 0.5A有的偏 2A。根因SH367306 的 CADC电流 ADC链路中有三个温漂源0.5mΩ 采样电阻的 TCR锰铜电阻的 TCR 约 ±50ppm/°C在全温范围内电阻值变化约 0.4%AFE 内部 PGA 的输入失调电压温漂这是主因。PGA 的输入失调电压约几个 μV但温漂系数约 50nV/°C。经过 PGA 放大后-20°C→60°C 的 80°C 温差对应几百 μV 的偏移折算到 0.5mΩ 采样电阻就是几百 mA 的等效电流AFE 内部基准电压温漂关键问题是零点偏移不是固定的而是随芯片温度变化的。上电时做的零点自学习只在那个温度点有效。解决方案零点自学习 温漂补偿表第一步上电零点自学习// Sh367306_1_Drv.c:880-933 — 上电时自动学习当前温度下的零点while(CadcSample_Check1){if(FLAG2.CADC1){CadcValueReadReg(REGCUR);// 判断充放电方向取偏移量if(CadcValue121)// 放电方向Offset-((0x10000-CadcValue)0x1FFF);elseOffsetCadcValue0x1FFF;learnCadcOffsetOver;// 取两次不同值的均值不是所有采样的均值if(learnCadcOffsetOver2)learnCadcOffsetBuffer[0]Offset;if(learnCadcOffsetOver2Offset!learnCadcOffsetBuffer[0]){learnCadcOffsetBuffer[1]Offset;Offset(learnCadcOffsetBuffer[0]learnCadcOffsetBuffer[1])/2;CadcSample_Check0;// 学习完成}// 限制 ±10 LSB约 ±240mAif(Offset10)Offset10;if(Offset-10)Offset-10;}}第二步芯片温度补偿表// Sh367306_1_Drv.c:679-728 — 根据芯片内部温度查表补偿温漂int8_tGetTheTempOffset(int16_tChipTemp){// 低温 → 负偏移AFE 读到的电流偏充电方向if(ChipTemp-44ChipTemp-40)return-6;elseif(ChipTemp-30)return-5;elseif(ChipTemp-20)return-4;elseif(ChipTemp-10)return-3;elseif(ChipTemp0)return-2;elseif(ChipTemp10)return-1;// 常温 → 不补偿elseif(ChipTemp10ChipTemp35)return0;// 高温 → 正偏移AFE 读到的电流偏放电方向elseif(ChipTemp75)return4;elseif(ChipTemp65)return3;elseif(ChipTemp55)return2;elseif(ChipTemp35)return1;return0;}第三步最终电流计算// AfeIf_Calculate.c — 零点 自学习值 温度补偿CorrectedValueRawValue-ZeroOffsetGetTheTempOffset(ChipTemp);Current_mACorrectedValue ×(50mV/4096)/0.5mΩ;温漂补偿表是怎么来的取 10 块板子放在高低温箱里电池断开确保真·零电流从 -40°C 到 85°C 每个温度点停留 30 分钟记录 AFE 的电流读数。10 块板子的数据取中位数做成表。每个 LSB ≈ 24.4mA所以 -6 LSB 约 -146mA4 LSB 约 98mA。面试要点“这个问题让我认识到电流采样精度的瓶颈不在 ADC 分辨率而在模拟链路的温漂。0.5mΩ 采样电阻在 30A 时只有 15mV 的信号而 AFE 的输入失调电压温漂可以达到几百 μV——信号和误差在同一个数量级。解决方案必须从系统层面考虑上电自学习解决初始偏差温度补偿表解决全温漂移两次采样取均值解决瞬态噪声。”问题三均衡时电压读数异常 —— 为什么满充时电芯电压跳了 50mV现象在充电末端单体 3.55V 附近开启均衡后上位机显示的电压趋势图上出现规律的锯齿波电压先跌 30-50mV然后恢复正常再过几秒又跌。这导致 FDR 模块产生误报——把正常的电芯判为单体电压高保护或单体无效故障。更诡异的是电压异常的总是那些相邻的均衡通道。比如 Cell3 开启均衡时Cell4 的电压读数也偏低。根因SH367306内部只有一个 ADC10 路电芯电压通过模拟开关切换后顺序采样。均衡开启时均衡电流约 50mA流过电芯的采样线和内部走线在采样线上产生额外的压降均衡电流路径Cell_N → 均衡 MOSFET → 均衡电阻 → Cell_N- 采样电流路径Cell_N → 采样线 → AFE → 采样线 → Cell_N- 两条路径共享 Cell_N 和 Cell_N- 的引线 均衡电流 × 引线阻抗 额外的压降 → 电压读数偏低相邻通道的影响是因为均衡电流的回路经过了相邻电芯的采样线——这在 20 串菊花链连接中是不可避免的。解决方案分时调度 交错均衡策略 1电压采样和均衡不同时进行// Sh367306_1_Drv.c:1070-1099 — 11 个周期的调度表voidSh367306_1_Runable(uint8_tCnt){switch(Cnt){case2:GetVadcData();// ← 读电压BalanceOpen();// ← 然后立刻开均衡奇通道break;case6:BalanceOpen();// ← 只开均衡偶通道不读电压break;case10:BalanceClose();// ← 关均衡准备读下一轮电压break;case11:g_Afe1Flag2.B.VADC0;// 清除标志触发新一轮电压采样break;}}// 每个周期 40ms11 个周期 440ms 一个完整循环// 在 GetVadcData() 之前均衡已经被关闭了上一轮的 case 10// 所以读到的是没有均衡干扰的干净电压关键设计先关均衡等 2 个周期80ms让采样线稳定再读电压。策略 2均衡交错开启// Sh367306_1_Drv.c:378-418 — 相邻电芯不同时均衡if(i%20){SCONF4Config0x55;// 只开 bit 0,2,4,6,8 Cell6,8,10 和 Cell1,3,5SCONF5Config0x55;}else{SCONF4Config0xAA;// 只开 bit 1,3,5,7,9 Cell7,9 和 Cell2,4SCONF5Config0xAA;}交错不只是为了防止相邻电芯串扰——更重要的是防止局部过热。5 个均衡 MOSFET 同时导通时芯片内部的热量集中在一侧会导致那几路的 ADC 读数同时偏移。完整的 440ms 调度循环Cnt0 空闲均衡全关等待 VADC 采样完成 Cnt1 空闲 Cnt2 GetVadcData() → 立即 BalanceOpen(奇通道) Cnt3 均衡开着奇通道不读电压 Cnt4 均衡开着奇通道不读电压 Cnt5 均衡开着奇通道不读电压 Cnt6 BalanceOpen(偶通道)不读电压 Cnt7 均衡开着偶通道不读电压 Cnt8 均衡开着偶通道不读电压 Cnt9 均衡开着偶通道不读电压 Cnt10 BalanceClose() → 全关均衡 Cnt11 清除 VADC 标志 → AFE 开始新一轮电压采样面试要点“这个问题让我理解了芯片内部资源冲突的概念。SH367306 用单颗 ADC 做 10 路电压 2 路温度 1 路电流 1 路芯片温度共 14 路——它不是一个并行的数据采集系统。均衡 MOSFET 开启时流过采样线的电流会干扰 ADC 的测量结果。解决方案不是换芯片而是从时序调度上保证均衡和采样永远不同时发生。这种’时间分片’的思路在资源受限的嵌入式系统中非常普遍。”问题四Cell1 电压系统性偏低 —— B- 线上的隐形电阻现象同一电池包用高精度万用表Fluke 289四线法直接测量 Cell1 正负极读数是 3.521V。但 BMS 显示的 Cell1 只有3.498V偏差 23mV。而 Cell2~Cell20 的读数与万用表一致偏差 5mV。只有 Cell1 偏低。换了板子也一样。换了电池包也复现。根因Cell1 是电池组的最负端电芯。它的负极直接连到 BMS 板的B-端子中间经过了一段较长的导线约 15-20cm从电池包内部到 BMS 板。电池 Cell1 负极 ──── 长导线(≈20cm, ≈3mΩ) ──── BMS B- 端子 ──── 采样电阻(0.5mΩ) ──── GND │ AFE Cell1 采样点问题出在B- 线是电流的必经之路。30A 放电时B- 线上 3mΩ 的阻抗产生3mΩ × 30A 90mV的压降。这个压降被算进了 Cell1——即 AFE 采到的 Cell1 电压 真实 Cell1 电压 - B-线压降。Cell2 不受影响因为 Cell2 的负极是 Cell1 的正极——这条线不过大电流。为什么只有 Cell1这是开尔文连接的局限。Cell2~Cell20 的采样线只流过 μA 级的 ADC 输入电流几乎无压降。但 Cell1 的 B- 线是所有 20 串电芯的电流回路——30A 的电流在这条线的阻抗上产生的压降Cell1 的采样线无法区分这是电芯的真实电压差还是这是导线的压降。解决方案工厂模式在线校准 运营模式固定补偿工厂模式AfeIf_Calculate.c:199-216if(FactoryMode1){// 利用其余 19 节电芯的平均值来校准 Cell1// 前提电流 6A确保有足够的信号用于计算uint16_tAvgOfOther19(PackVoltage-CellVoltage[0])/19;if(abs(Current)6000){if(abs(CellVoltage[0]-AvgOfOther19)150mV){CellVoltage[0]AvgOfOther19;// 在线自动校准}}}这个方法的巧妙之处假设 20 节电芯在工厂下线检测时是均衡的生产线上会先做一次满充均衡那么每节电芯的电压应该相等。19 节电芯的平均值 ≈ Cell1 的真实电压。运营模式AfeIf_Calculate.c:219-226else{// 工厂模式校准完毕后运营模式用固定系数补偿// 不同电池包的 B- 线阻抗不同系数也不同#ifdefBMP6020S20CellVoltage[0]-Current_mA ×50/100000;// 0.50mV/A#endif#ifdefBMP6030S20CellVoltage[0]-Current_mA ×33/100000;// 0.33mV/A#endif}6020 电池包的 B- 线更细/更长20Ah 容量电流较小线径可以细一些但阻抗更大所以补偿系数 0.50mV/A。6030 电池包线更粗只有 0.33mV/A。B- 线阻抗系数是怎么确定的在工厂模式下让电池以不同的电流放电0A、5A、10A、15A、20A记录每个电流下的 Cell1 读数和其余 19 节的平均值。然后拟合Cell1_误差(mV) k × Current(A) 对 6020k ≈ 0.50mV/A → 等效 B- 线阻抗 0.5mΩ 对 6030k ≈ 0.33mV/A → 等效 B- 线阻抗 0.33mΩ面试要点“这个问题让我认识到采样精度不只取决于 ADC 的位数更取决于物理连接的质量。在 μV 级别的测量中一根 20cm 的导线就是一个’电阻’。12-bit ADC 有 1.47mV 的分辨率但 B- 线的 90mV 压降30A 时相当于 60 个 LSB——它淹没了 ADC 的精度的 60 倍。解决方案的核心是找到那个’差’的系统性来源用数学建模把它补偿掉。工厂模式的在线校准就是这个思路——利用已知条件19 节电芯的均值求解未知量Cell1 的真实值。”总结四个问题的共同线索问题表象根因解决思路I2C 寄存器翻转均衡/保护参数随机跳变EMI 通过辐射打翻寄存器三层容错重试 → 回读验证 → 定时巡检修复电流零点温漂-20°C 时静止电流显示 -1.2APGA 输入失调电压的温度系数上电自学习 芯片温度查表补偿均衡时电压跳变充电末端电压锯齿波 ±50mV均衡电流经采样线产生压降分时调度 相邻通道交错均衡Cell1 系统性偏低Cell1 读数偏低 23mVB- 线电流回路阻抗压降工厂模式在线校准 运营模式固定补偿四个问题的共同点都不是芯片本身的 bug而是物理世界的约束在数字域的投影。EMI 是物理的温漂是物理的均衡电流的采样线压降是物理的B- 线的铜阻抗是物理的量产代码和开发板 demo 的区别就在于——是否处理好了这些物理约束。本文基于 STM32F072 SH367306 × 2 两轮车换电 BMS 量产代码APP-V02.01.20分析所有代码引用均可追溯到源文件对应行号。