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 走线上感应的噪声通过两个途径影响 AFE:
- I2C 数据线(SDA/SCL)耦合:直接导致读写数据错误
- AFE 寄存器被静电/EMI 直接打翻:即使不通信,寄存器值也会跳变
第二个途径尤其隐蔽------程序没有主动写寄存器,但寄存器的值自己变了。这说明不是 I2C 通信的问题,而是 AFE 芯片内部的寄存器单元在强 EMI 下发生了 bit-flip。
解决方案:三层容错架构
第一层:I2C 读写重试
c
// Sh367306_1_Drv.c --- 每次读写失败重试 5 次
static uint8_t AfeIf_Read(uint8_t RdAddr, uint8_t Length, uint8_t *RdBuf) {
uint8_t times = 0;
while (times++ < TRY_TIMES) { // TRY_TIMES = 5
result = Twi1Drv_Read(SlaveAdd << 1, RdAddr, Length, RdBuf);
if (result == 1) return result; // 成功就返回
// 失败继续重试
}
return result;
}
这一层解决的是通信链路上的偶发错误------I2C 波形被干扰导致 ACK 丢失或数据位翻转。
第二层:写入后回读验证
c
// Sh367306_1_Drv.c --- 写寄存器后立即回读,确认值确实写入了
static uint8_t AfeIf_RegWrite(uint8_t RdAddr, uint8_t Data) {
uint8_t RegWriteFlg = 1;
uint8_t Buffer[2] = {0};
while (RegWriteFlg) {
AfeIf_Write(RdAddr, Data); // 写入
AfeIf_Read(RdAddr, 2, Buffer); // 立即回读
if (Buffer[0] == Data) break; // 一致 → OK
if (++RegWriteFlg > 20) return 0; // 20 次都不一致 → 报错
}
return 1;
}
这一层解决的是写入不生效的问题------AFE 内部正在做 ADC 转换时收到 I2C 写命令,可能导致写入被忽略。回读验证确保每个寄存器写入都是"实打实"的。
第三层:运行时定时回读校验
c
// Sh367306_1_Drv.c:1041-1049 --- 每 4s 检测 SCONF1 是否被电磁干扰打翻
if (s_usCTLDPinChek % 100 == 1) { // 100 × 40ms = 4s
AfeIf_Read(SCONF1, 2, &DataBuff);
if (DataBuff[0] != g_Afe1Sconfig1.Data) {
AfeIf_RegWrite(SCONF1, g_Afe1Sconfig1.Data); // 被改了,回写修复
}
}
这一层是最关键的一层。它不依赖任何通信触发------即使程序完全没有操作 AFE,AFE 的寄存器也可能被 EMI 打翻。每 4 秒一次的巡检就是"安保巡逻",发现异常立刻修复。
面试要点
"这个问题让我意识到,在电力电子环境中,不能假设芯片的寄存器是稳定的。普通 I2C 驱动的思维是'我写了,所以值就是对的'------这在实验室是对的,在 30A PWM 的 PCB 上不是。三层容错分别解决三个不同层面的问题:通信链路错误、写入不生效、寄存器被动翻转。"
问题二:电流零点温漂 ------ 为什么静止状态下的电流读数是 -1.5A?
现象
BMS 在常温(25°C)下校准完毕后电流精度很好(±50mA)。但样品放在高低温箱里测试时:
- -20°C 时 :0A 静止状态,电流读数约 -1.2A
- 60°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 内部基准电压温漂
关键问题是:零点偏移不是固定的,而是随芯片温度变化的。上电时做的零点自学习只在那个温度点有效。
解决方案:零点自学习 + 温漂补偿表
第一步:上电零点自学习
c
// Sh367306_1_Drv.c:880-933 --- 上电时自动学习当前温度下的零点
while (CadcSample_Check == 1) {
if (FLAG2.CADC == 1) {
CadcValue = ReadReg(REGCUR);
// 判断充放电方向,取偏移量
if (CadcValue >> 12 == 1) // 放电方向
Offset = -((0x10000 - CadcValue) & 0x1FFF);
else
Offset = CadcValue & 0x1FFF;
learnCadcOffsetOver++;
// 取两次不同值的均值(不是所有采样的均值!)
if (learnCadcOffsetOver == 2)
learnCadcOffsetBuffer[0] = Offset;
if (learnCadcOffsetOver > 2 && Offset != learnCadcOffsetBuffer[0]) {
learnCadcOffsetBuffer[1] = Offset;
Offset = (learnCadcOffsetBuffer[0] + learnCadcOffsetBuffer[1]) / 2;
CadcSample_Check = 0; // 学习完成
}
// 限制 ±10 LSB(约 ±240mA)
if (Offset >= 10) Offset = 10;
if (Offset <= -10) Offset = -10;
}
}
第二步:芯片温度补偿表
c
// Sh367306_1_Drv.c:679-728 --- 根据芯片内部温度查表补偿温漂
int8_t GetTheTempOffset(int16_t ChipTemp) {
// 低温 → 负偏移(AFE 读到的电流偏充电方向)
if (ChipTemp > -44 && ChipTemp <= -40) return -6;
else if (ChipTemp <= -30) return -5;
else if (ChipTemp <= -20) return -4;
else if (ChipTemp <= -10) return -3;
else if (ChipTemp <= 0) return -2;
else if (ChipTemp <= 10) return -1;
// 常温 → 不补偿
else if (ChipTemp > 10 && ChipTemp < 35) return 0;
// 高温 → 正偏移(AFE 读到的电流偏放电方向)
else if (ChipTemp >= 75) return +4;
else if (ChipTemp >= 65) return +3;
else if (ChipTemp >= 55) return +2;
else if (ChipTemp >= 35) return +1;
return 0;
}
第三步:最终电流计算
c
// AfeIf_Calculate.c --- 零点 = 自学习值 + 温度补偿
CorrectedValue = RawValue - ZeroOffset + GetTheTempOffset(ChipTemp);
Current_mA = CorrectedValue × (50mV / 4096) / 0.5mΩ;
温漂补偿表是怎么来的?
取 10 块板子,放在高低温箱里,电池断开(确保真·零电流),从 -40°C 到 85°C 每个温度点停留 30 分钟,记录 AFE 的电流读数。10 块板子的数据取中位数,做成表。每个 LSB ≈ 24.4mA,所以 -6 LSB 约 -146mA,+4 LSB 约 +98mA。
面试要点
"这个问题让我认识到,电流采样精度的瓶颈不在 ADC 分辨率,而在模拟链路的温漂。0.5mΩ 采样电阻在 30A 时只有 15mV 的信号,而 AFE 的输入失调电压温漂可以达到几百 μV------信号和误差在同一个数量级。解决方案必须从系统层面考虑:上电自学习解决初始偏差,温度补偿表解决全温漂移,两次采样取均值解决瞬态噪声。"
问题三:均衡时电压读数异常 ------ 为什么满充时电芯电压"跳"了 50mV?
现象
在充电末端(单体 3.55V 附近)开启均衡后,上位机显示的电压趋势图上出现规律的锯齿波:电压先跌 30-50mV,然后恢复正常,再过几秒又跌。这导致 FDR 模块产生误报------把正常的电芯判为"单体电压高保护"或"单体无效故障"。
更诡异的是:电压异常的总是那些相邻的均衡通道。比如 Cell3 开启均衡时,Cell4 的电压读数也偏低。
根因
SH367306 内部只有一个 ADC,10 路电芯电压通过模拟开关切换后顺序采样。均衡开启时,均衡电流(约 50mA)流过电芯的采样线和内部走线,在采样线上产生额外的压降:
均衡电流路径:Cell_N+ → 均衡 MOSFET → 均衡电阻 → Cell_N-
采样电流路径:Cell_N+ → 采样线 → AFE → 采样线 → Cell_N-
两条路径共享 Cell_N+ 和 Cell_N- 的引线!
均衡电流 × 引线阻抗 = 额外的压降 → 电压读数偏低
相邻通道的影响是因为均衡电流的回路经过了相邻电芯的采样线------这在 20 串菊花链连接中是不可避免的。
解决方案:分时调度 + 交错均衡
策略 1:电压采样和均衡不同时进行
c
// Sh367306_1_Drv.c:1070-1099 --- 11 个周期的调度表
void Sh367306_1_Runable(uint8_t Cnt) {
switch (Cnt) {
case 2:
GetVadcData(); // ← 读电压
BalanceOpen(); // ← 然后立刻开均衡(奇通道)
break;
case 6:
BalanceOpen(); // ← 只开均衡(偶通道),不读电压
break;
case 10:
BalanceClose(); // ← 关均衡,准备读下一轮电压
break;
case 11:
g_Afe1Flag2.B.VADC = 0; // 清除标志,触发新一轮电压采样
break;
}
}
// 每个周期 40ms,11 个周期 = 440ms 一个完整循环
// 在 GetVadcData() 之前,均衡已经被关闭了(上一轮的 case 10)
// 所以读到的是没有均衡干扰的"干净"电压
关键设计:先关均衡,等 2 个周期(80ms)让采样线稳定,再读电压。
策略 2:均衡交错开启
c
// Sh367306_1_Drv.c:378-418 --- 相邻电芯不同时均衡
if (i % 2 == 0) {
SCONF4 = Config & 0x55; // 只开 bit 0,2,4,6,8 = Cell6,8,10 和 Cell1,3,5
SCONF5 = Config & 0x55;
} else {
SCONF4 = Config & 0xAA; // 只开 bit 1,3,5,7,9 = Cell7,9 和 Cell2,4
SCONF5 = Config & 0xAA;
}
交错不只是为了防止相邻电芯串扰------更重要的是防止局部过热。5 个均衡 MOSFET 同时导通时,芯片内部的热量集中在一侧,会导致那几路的 ADC 读数同时偏移。
完整的 440ms 调度循环:
Cnt=0 空闲(均衡全关,等待 VADC 采样完成)
Cnt=1 空闲
Cnt=2 GetVadcData() → 立即 BalanceOpen(奇通道)
Cnt=3 均衡开着(奇通道),不读电压
Cnt=4 均衡开着(奇通道),不读电压
Cnt=5 均衡开着(奇通道),不读电压
Cnt=6 BalanceOpen(偶通道),不读电压
Cnt=7 均衡开着(偶通道),不读电压
Cnt=8 均衡开着(偶通道),不读电压
Cnt=9 均衡开着(偶通道),不读电压
Cnt=10 BalanceClose() → 全关均衡
Cnt=11 清除 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-216):
c
if (FactoryMode == 1) {
// 利用其余 19 节电芯的平均值来校准 Cell1
// 前提:电流 > 6A(确保有足够的信号用于计算)
uint16_t AvgOfOther19 = (PackVoltage - CellVoltage[0]) / 19;
if (abs(Current) > 6000) {
if (abs(CellVoltage[0] - AvgOfOther19) < 150mV) {
CellVoltage[0] = AvgOfOther19; // 在线自动校准
}
}
}
这个方法的巧妙之处:假设 20 节电芯在工厂下线检测时是均衡的(生产线上会先做一次满充均衡),那么每节电芯的电压应该相等。19 节电芯的平均值 ≈ Cell1 的真实电压。
运营模式 (AfeIf_Calculate.c:219-226):
c
else {
// 工厂模式校准完毕后,运营模式用固定系数补偿
// 不同电池包的 B- 线阻抗不同,系数也不同
#ifdef BMP6020S20
CellVoltage[0] -= Current_mA × 50 / 100000; // 0.50mV/A
#endif
#ifdef BMP6030S20
CellVoltage[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)
对 6020:k ≈ 0.50mV/A → 等效 B- 线阻抗 = 0.5mΩ
对 6030:k ≈ 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.2A | PGA 输入失调电压的温度系数 | 上电自学习 + 芯片温度查表补偿 |
| 均衡时电压跳变 | 充电末端电压锯齿波 ±50mV | 均衡电流经采样线产生压降 | 分时调度 + 相邻通道交错均衡 |
| Cell1 系统性偏低 | Cell1 读数偏低 23mV | B- 线电流回路阻抗压降 | 工厂模式在线校准 + 运营模式固定补偿 |
四个问题的共同点:都不是芯片本身的 bug,而是物理世界的约束在数字域的投影。
- EMI 是物理的
- 温漂是物理的
- 均衡电流的采样线压降是物理的
- B- 线的铜阻抗是物理的
量产代码和开发板 demo 的区别就在于------是否处理好了这些物理约束。
本文基于 STM32F072 + SH367306 × 2 两轮车换电 BMS 量产代码(APP-V02.01.20)分析,所有代码引用均可追溯到源文件对应行号。