电流采样链路设计实战:同一台逆变器早上显示 5.00A、中午 5.42A——拆到最后,8% 的漂移里有 5.5% 是走线上那段铜

2026年10月12日 0 By admin

那台 3.5kW 离网逆变器是两年前做的,一直卖得还行。这次是客户返修回来的一台,附了一张手写的纸条:”早上开机电池电流 5.00A,中午再看不低于 5.4A,负载没动过。”

我先按”客户不会用”的思路查了一遍,然后按”传感器坏了”的思路换了一块采样板,最后按”固件算错了”的思路把代码翻了个底朝天——三圈下来,5.00 到 5.42 这个 8.4% 一分没少。

真正让我停下来的,是我做的一个对照实验:把分流电阻两端短路,只看读数零点。

冷机 25℃、热机 65℃(机内温度,用贴在板上的热电偶量的),零点差了大概 3 个 ADC 码。3 个码,按当时的换算关系是 97mA。客户看到的是 420mA。

也就是说:零点只解释了四分之一,剩下四分之三全在增益上。 而增益误差这个东西,只要你在常温下校准过一次,它就会安安稳稳地藏在”已校准”三个字后面,直到温度变了才露头。

这篇文章就是那次拆解的全过程。结论我先放在这里:

把 8.4% 拆开,其中 9.65% 来自”增益随温度漂”,-1.2% 来自”零点反过来抵消”——而 9.65% 里,4.1% 是分流电阻本体、5.55% 是 PCB 上那两段”镀铜的走线”。

最后那个数字是这篇的核心。它意味着:我精心挑的那颗 0.5mΩ、±1% 的分流电阻,它的精度还不如我随手画的那两根 2mm 宽的 sense 走线。

一、现象:早上 5.00A,中午 5.42A

先把现场信息摆全,后面所有计算都以这些数为准。

项 值 说明
设备 3.5kW 离网逆变器 电池侧 48V 系统
被测电流 电池侧母线电流,满量程 ±50A 实际工作点只有 5A
分流电阻 0.5mΩ,2512 两端子贴片,3W,±1% 器件级 TCR ±400 ppm/℃
放大器 专用电流检测放大器,增益 50 Vos ≤ 25µV,温漂 ≤ 250 nV/℃
ADC 12 bit,Vref 3.3V 硬件定时器触发,与 PWM 同步
冷机条件 机内 25℃,读数 5.00A 出厂时在这个温度校准过
热机条件 机内 65℃,读数 5.42A 负载完全没动

我用一只 0.5 级钳形表和一台电子负载做了交叉验证,实际电流在两次测量之间变化不超过 0.3%。所以 8.4% 是读数的漂移,不是电流的变化。

1.1 三次误判,三次白干

误判一:ADC 或参考电压的锅。 这个最容易被怀疑,因为”ADC 精度”是个万能背锅位。我用同一个 Vref 去测一颗已知的 1.000V 精密源,冷热两次都是 1.0015V 和 1.0019V,差 0.04%——参考不是主犯。不过这一刀没白挥,它给后面排除了一个变量。

误判二:固件里的温度补偿写反了。 我把补偿代码整个注释掉再试,漂移还是 8% 量级,只是形状略变。排除。

误判三:分流电阻坏了。 换了一块采样板,同样的现象,同样的幅度。排除。

三次排除之后,剩下的就不多了。我列了个表,把”从电流变成数字”这条链路上所有会随温度变的东西一一点出来,然后从最贵的那个开始算。

1.2 一个 20 分钟的对照实验,直接切中要害

在往下算之前,先做一个能定性的实验,值回票价:

把放大器的两个差分输入短路(分流电阻两端短接),让 V_sense 严格等于 0,然后把整机跑热。

这一步的意义在于:它把”增益类误差”整个从结果里摘出去了。输入是 0,无论增益漂多少,输出都应该还是 0。所以此时你看到的任何非零读数,全部是零点类误差——放大器 Vos、Vos 温漂、ADC 偏置、热电势、参考的零点偏移。

我的结果:冷机 0 码,热机 +3 码。折算 97mA。

97mA 对比客户看到的 420mA,说明零点类误差只占四分之一左右。剩下四分之三必须在增益链上。

这个”短路输入、跑热、看零点”的动作,我后来写进了部门的硬件调试规范。它比任何仪器都便宜,但它能把一个含糊的”读数不准”一刀切成两块,而且切得干净。

二、先把采样链路拆成七段

排障最怕的就是把整条链路当一个黑盒。我在白板上画了一张图,把”电流”到”安培读数”之间切成了七段:

  ┌──────────────────────────────────────────────────────────────────┐
  │ 1 传感器      分流电阻:把电流 I 变成 mV 级电压 V = I x R_shunt   │
  ├──────────────────────────────────────────────────────────────────┤
  │ 2 前端        放大器 / 隔离器:放大并搬运到 ADC 能吃的电平        │
  ├──────────────────────────────────────────────────────────────────┤
  │ 3 滤波        抗混叠 + 削尖峰:决定带宽与相位                    │
  ├──────────────────────────────────────────────────────────────────┤
  │ 4 参考        ADC 的"尺子":量程、温漂、噪声                      │
  ├──────────────────────────────────────────────────────────────────┤
  │ 5 ADC         量化 + 采样时刻:码值、触发源、同步关系             │
  ├──────────────────────────────────────────────────────────────────┤
  │ 6 标定/补偿   把码变成安培:零点、增益、温补、限幅                │
  ├──────────────────────────────────────────────────────────────────┤
  │ 7 系统边界    走线、焊点、接地、散热、机械——不画进原理图的那部分   │
  └──────────────────────────────────────────────────────────────────┘

对照这张图,我把自己的三个疑点重新排了一遍:

段 这一类误差的典型量级 温漂特征 我这次的位置
1 传感器 公差 ±1%、TCR 几十到几百 ppm/℃ 跟分流器自热走,秒级建立 主犯(4.1%)
2 前端 Vos 几十 µV、增益误差 0.2% 跟放大器结温走,秒级 从犯(零点项)
3 滤波 相位滞后、纹波残留 基本不漂 无关
4 参考 初始化 0.1%、温漂 10~100 ppm/℃ 跟芯片温度走,秒级 从犯(1.0%)
5 ADC 量化 0.01%、INL 0.5 LSB 很小 无关
6 标定 标定条件与实际工况不符 取决于标定策略 放大器(没做温补)
7 系统边界 没有上限 跟板温走,分钟级 主犯(5.55%)

注意第 7 行那个”没有上限”。这一行是我这次学到最多的地方——同一块板子上,分流器和铜走线的温度变化是两个不同的时间常数:分流器是秒级的自热,铜走线是分钟级的板温。所以误差会呈现出”先快速漂一段、再缓慢漂一段”的复合形状,而不是一条单调曲线。这也解释了为什么我第一次看数据时觉得”漂移没什么规律”。

三、分流电阻:真正的约束是两个,不是三个

分流电阻选型,教科书上一般给你”三约束”:压降够大、功耗够小、阻值稳定。但我在实际选型时发现,第三项是要花大价钱买的,前两项是免费的。所以真正的决策顺序应该是:

先用”功耗”划出阻值的上界,再用”零点精度”划出下界,最后看这个窗口存不存在;存在的话,再花钱买第三项(TCR)。

3.1 上界:功耗与自热

分流电阻是个”设计出来就是为了发热”的元件。所以它的第一约束不是阻值,是功耗:

P = I_fs^2 x R

满量程 50A 配 0.5mΩ:

P = 50 x 50 x 0.0005 = 1.25 W

2512 封装 3W 额定,跑在 1.25W,看着很安全。但额定功率只是个起点,后面还有一道更狠的:

dT = P x R_th        (R_th = 结到环境的热阻)

2512 贴片在标准焊盘下,R_th 大概在 40~60 ℃/W。取 50:

dT = 1.25 W x 50 ℃/W = 62.5 ℃

机内环境 65℃,所以分流器的电阻体温度是 127.5℃。

这个数一出来我就知道自己错在哪了。我一直盯着”1.25W 只占 3W 额定的 42%,很安全”,但额定功率管的是”会不会烧”,热阻管的是”会不会漂”,两回事。一个 2512 电阻跑到 127.5℃,它周围的焊点和铜箔全都在这个温度上,误差已经不是”能不能用”的问题了。

顺手把常见的散热条件列一下——这张表的数字会告诉你,为什么”多铺点铜”有时候比”换个贵电阻”更值:

封装 / 散热条件 R_th 量级 (℃/W) 1.25W 下的温升
0603 / 0805,标准焊盘 200 ~ 300 250 ~ 375℃(早就烧了)
1206,标准焊盘 100 ~ 150 125 ~ 188℃
2512,标准焊盘 40 ~ 60 50 ~ 75℃
2512 + 加大铜箔 + 多过孔 25 ~ 40 31 ~ 50℃
5931 / 8518 四端子,带铜块 8 ~ 15 10 ~ 19℃

这张表只用于量级判断。R_th 对铜箔面积极其敏感,实际偏差能到 2~3 倍,精算必须查器件手册再用热像仪实测确认。

顺便:“加大铜箔”和”多打过孔”这两招很便宜,但它们只能解决温升,解决不了 TCR。这两件事在第四、五节会分开算。

3.2 下界:一个必须背下来的公式

现在看下界。这条是从”零点精度”倒推出来的。

放大器的输入失调 Vos,是串联在分流电阻信号通路上的一个”假电压”。它折算成电流误差的方式是:

I_err = Vos / R_shunt

注意这里没有增益。 增益把信号和失调一起放大,所以比值不变。我第一次算的时候盯着这个式子看了半天,因为它实在太反直觉了——你花钱把增益从 20 提到 50,零点误差一毫安都不会改善。

代入我的参数(Vos ≤ 25µV,R = 0.5mΩ):

I_err = 25 µV / 0.5 mΩ = 50 mA

50mA。而我刚才用短路实验测出来的是 97mA(含温漂),量级完全对得上。

现在反过来用这个公式做选型。假设我的目标是零点绝对误差小于 30mA,那么:

放大器档次 Vos (max) 满足 30mA 所需的 R 50A 时该 R 上的功耗
通用运放搭差分 1000 µV ≥ 33 mΩ 82.5 W(不可行)
精密零漂运放 10 µV ≥ 0.33 mΩ 0.83 W ✓
专用 CSA 入门档 150 µV ≥ 5 mΩ 12.5 W(不可行)
专用 CSA 高精度档 25 µV ≥ 0.83 mΩ 2.1 W ✓
隔离放大器(普通档) 1500 µV ≥ 50 mΩ 125 W(不可行)
隔离放大器(高精度档) 200 µV ≥ 6.7 mΩ 16.7 W(不可行)

这张表就是”为什么高精度电流采样必须用专用 CSA”的定量证明。

通用运放搭差分,Vos 1mV 是常态;要把 1mV 的失调压到 30mA 以内,你得用 33mΩ 的分流电阻,而 50A 过 33mΩ 就是 82.5W——一个电阻扛 82.5W,已经不是”设计”而是”造电器”了。

而专用 CSA 之所以能成立,全部价值就在把那 1000µV 的 Vos 压到 25µV,压了 40 倍。这 40 倍就是它的价格。

一个容易忽略的推论:同一颗放大器,在同一个系统里,如果功率级电流变小了,你必须重新算这个下界。 我见过一个案子,硬件从 50A 平台挪到 10A 平台,分流电阻跟着从 0.5mΩ 提到 2mΩ 是对的,但没人重算 Vos,结果零点误差指标实际上宽松了 4 倍却没人知道,白留了一堆余量。

3.3 所以选型的完整流程是四步

把上面两节合起来,我最后固化成四步,写进了部门的选型模板:

第 1 步:由功耗上界定 R 的上限
         P_max 一般取额定功率的 50%,再除以 I_fs^2

第 2 步:由零点目标定 R 的下限
         R_min = Vos_max / I_err_target

第 3 步:看窗口是否存在
         R_min <= R <= R_max  —— 不存在就换放大器档次,别硬凑

第 4 步:在窗口内挑阻值档,再看该档的 TCR 和热阻
         (第四、五节的内容)

我的参数走一遍:

  • R 上限:3W 额定,取 50% 即 1.5W,1.5 / 50² = 0.6 mΩ
  • R 下限:25µV / 30mA = 0.83 mΩ(用高精度 CSA 档)
  • 0.6 < 0.83 → 窗口不存在。

这就是问题的真正起点。我当初选 0.5mΩ 是”为了压住功耗”,结果它同时把零点精度也压死了。正确做法是在窗口内取值——把 CSA 提到更高精度档(Vos 降到 10µV 级),R 下限降到 0.33mΩ,窗口就是 0.33~0.6mΩ,取 0.5mΩ 也才勉强成立。

而我当时的实际选择是把 R 定在 0.5mΩ 这个窗口外的点上,然后靠”常温标定”把这个矛盾盖住了。

四、TCR:数据手册上那两个数,根本不是一回事

这一节是整篇文章里我觉得最值钱的发现,因为它推翻了我对”低 TCR 电阻”的朴素理解。

4.1 合金本体 < 20 ppm/℃,器件级 ±400 ppm/℃

去看一颗典型 2512 功率金属条电流采样电阻的数据手册(比如 Vishay 的 Power Metal Strip 系列),你会看到两个 TCR 数字,写在不同位置:

  • 在”特性”那一栏:Solid metal ... alloy resistive element with low TCR (< 20 ppm/°C)
  • 在”技术规格”那一栏:一张按阻值分档的表

那张分档表长这样(这是本文所有计算的依据):

阻值档 器件级 TCR 规格
0.5 ~ 0.99 mΩ ± 400 ppm/℃
1.0 ~ 2.9 mΩ ± 275 ppm/℃
3.0 ~ 4.9 mΩ ± 150 ppm/℃
5.0 ~ 6.9 mΩ ± 110 ppm/℃
7.0 mΩ ~ 100 mΩ ± 75 ppm/℃

同一个器件、同一份手册,合金本体是 <20 ppm/℃,而 0.5mΩ 档的规格是 ±400 ppm/℃。中间那 380 ppm 是谁?

答案是:两端的铜端子、内部焊接点、以及封装引出的那一段导体。

一颗 0.5mΩ 的 2512,其”0.5mΩ”指的是合金条本身的阻值;但你在外面能测到的、能用的,是”合金条 + 两端铜端子 + 两个焊点”的总和。铜的电阻温度系数是 3930 ppm/℃——是合金的 200 倍。所以当阻值小到 0.5mΩ 这个量级时,端子那点铜在总电阻里的占比大到足以主导整个器件的温漂。

数据手册的分档表就是把这个物理事实诚实地写成规格:阻值越小,铜的相对占比越大,TCR 越差。 从 0.5mΩ 的 ±400 一路降到 100mΩ 的 ±75,本质上是一条”铜占比”的曲线。

这个认知直接改了我对”低阻值 = 更精密”的直觉。在 mΩ 级别,阻值和精度是反向的。

顺带一个坑,我踩过:分销商页面往往只标一个 TCR 数字。 我查那颗 0.5mΩ 的时候,某大平台的参数栏写着”±75 ppm/℃”——那是手册里较大阻值档的数字,或者是合金本体值。如果你按这个数字做设计,就等着现场翻车。买 mΩ 级采样电阻,TCR 必须去翻数据手册的分档表,一个数字都不能省。

4.2 算一下:这一项贡献了 4.1%

TCR 的误差算法是把”温漂率”乘上”实际温度偏离标定点的量”:

误差 = TCR (ppm/℃) x dT (K) x 1e-6

代入我现在掌握的数字:

分流器电阻体温度   T_shunt = 环境 + 自热温升 = 65 + 62.5 = 127.5 ℃
相对 25℃ 标定点     dT = 127.5 - 25 = 102.5 K
器件级 TCR          400 ppm/℃

误差 = 400 x 102.5 x 1e-6 = 0.041 = 4.1%

4.1%。 单这一项,就吃掉了我”±1% 精度”承诺的四倍。

而那 102.5K 里,只有 40K 是环境温度,62.5K 是我自己算出来的自热。这也解释了一个看起来很奇怪的现象:客户说”早上准、中午不准”,但中午的机内温度未必比早上高 40℃。真正让分流器温度变的,是电流本身——中午负载上来,自热上来了。

4.3 并联能不能改善?能,但不是你以为的那个原因

“那我用 4 颗 2mΩ 并成 0.5mΩ 行不行?”——我当时的第一反应就是这个。算一下:

并联数 单体阻值 单体 TCR 档 单体功耗 单体温升
1 0.5 mΩ ±400 ppm/℃ 1.25 W 62.5℃
2 1 mΩ ±275 ppm/℃ 0.31 W 15.6℃
4 2 mΩ ±275 ppm/℃ 0.078 W 3.9℃
8 4 mΩ ±150 ppm/℃ 0.0195 W 1.0℃

(按 50A 满量程、每颗 R_th = 50℃/W 估算。)

注意这里有个陷阱:并联并没有改善材料本身的 TCR。 4 颗 2mΩ 并联,等效还是 ±275 ppm/℃——因为四颗是同样的材料、同一个温度,它们的漂移完全同步,并起来漂移率不变。

并联真正的收益是:每个单体的自热功耗按 1/N² 下降,温升从 62.5℃ 掉到 3.9℃。所以它改善的是”温升 × TCR“这个乘积里的前一项。

而更”坑”的是那个附加效应:同样 0.5mΩ 的等效阻值,并联会让单体阻值进入更小的一档,单体 TCR 反而更差(1mΩ 档 ±275 vs 0.5mΩ 档 ±400,这里是变好了;但如果你从 1mΩ 并成 0.5mΩ 等效,单体是 2mΩ,TCR 从 ±400 到 ±275 也是变好)。

所以正确的判断口径是:不看 TCR,看”温升 × TCR”的乘积。

阻值档 温升 40K 带来的增益误差
0.5 ~ 0.99 mΩ 1.6%
1.0 ~ 2.9 mΩ 1.1%
3.0 ~ 4.9 mΩ 0.6%
5.0 ~ 6.9 mΩ 0.44%
7.0 mΩ ~ 100 mΩ 0.3%

这张表才是选型时该看的表。

4.4 正确的解法:换拓扑,不是换数量

并联到 8 颗去解决问题,成本和面积都不划算。真正的解法是把”两端子贴片”换成”四端子”封装。

同样 0.5mΩ,一个 5931/8518 这类带铜块的四端子分流器,TCR 通常在 ±50 ppm/℃ 甚至更好,热阻也在 8~15℃/W。差别不在”材料更好”,而在它的端子是专门设计成大截面积低热阻的结构,不再是”顺便当端子用的那段铜”。

改完的账:

温升 = 1.25 W x 12 ℃/W = 15 ℃
电阻体温度 = 65 + 15 = 80 ℃
误差 = 50 ppm/℃ x (80 - 25) = 0.275%

4.1% → 0.275%,降了 15 倍。 这 15 倍,就是四端子封装比两端子贴片贵的那点钱买来的东西。

五、铜:那 5.5% 的真凶

现在回到最反直觉的那部分。前面算出本体贡献 4.1%,但短路实验告诉我有四分之三的误差在增益链上——所以增益链上不止本体这一项。

第二项是走线。

5.1 先学会算铜箔的电阻

铜箔有个很好用的量:方块电阻。一块正方形铜箔,无论多大,从对边量过去都是同一个阻值。1oz 铜(35µm 厚)在 20℃ 下是:

R_sheet(1oz) = rho_Cu / t = 1.72e-8 / 35e-6 = 0.491 mΩ/□

一段铜箔的电阻就是”方块数 × 方块电阻”:

R = R_sheet x (L / W)
铜厚 R_sheet @20℃ R_sheet @60℃
0.5 oz 0.983 mΩ/□ 1.137 mΩ/□
1 oz 0.491 mΩ/□ 0.569 mΩ/□
2 oz 0.246 mΩ/□ 0.284 mΩ/□
3 oz 0.164 mΩ/□ 0.190 mΩ/□

而铜的温度系数是 3930 ppm/℃。这个数字请和第四节那个 ±400 ppm/℃ 放一起看——铜的温漂是那片 2512 的十倍。

计算一段 1oz 铜在 40K 温升下的漂移:

3930 ppm/℃ x 40 K = 15.7%

15.7%。 现在你明白为什么”用铜箔或铜条自己做分流器”是个灾难性的想法了:一段 10mm x 1mm 的 1oz 铜箔在 20℃ 下是 4.91mΩ,到 60℃ 变成 5.68mΩ——同一段铜,温漂 15.7%,是正经分流电阻(±400ppm)的 39 倍。

5.2 “两线接法”的三种死法

那么问题来了:既然铜这么差,为什么我还让走线进了测量回路?因为我用的那颗是两端子封装,走线只能从焊盘上引出去。

我后来复盘,把”两线接法”的失败归纳成三种,每一种的严重程度差得很远:

死法 A:细走线。 从焊盘边缘用 0.3mm 宽的普通走线走 2mm 到放大器。

R_wire = 0.491 x (2 / 0.3) = 3.27 mΩ
相对分流电阻 0.5mΩ 的占比 = 654%

串入的走线电阻是分流电阻的 6.5 倍。 这种情况下你的”0.5mΩ 采样电阻”实际工作在 3.77mΩ 上——测出来的电流只有真实值的七分之一。这不是”精度问题”,这是根本没在测。而且它还有个更坏的后果:温漂被放大到 3930ppm × 654% = 25700 ppm/℃,也就是每 40K 漂 103%。

如果你在常温下校准过,这个错误会完美地隐藏起来——因为它是个恒定的比例误差。

死法 B:宽走线。 用 2mm 宽的走线,只走 0.61mm。

R_wire = 0.491 x (0.61 / 2) = 0.150 mΩ
占比 = 0.150 / 0.5 = 30%
温漂贡献 = 30% x 3930 ppm/℃ x 40 K = 5.55%

这就是我那 5.55% 的来源。 一段 0.61mm 长的铜,因为占了分流电阻 30% 的阻值,在板温变化 40K 时贡献了 5.55% 的漂移。

0.61mm。 这个数字请记住。它意味着:在 0.5mΩ 这个量级,任何一段你能看见的铜,都已经太大了。 我用来”打样验证”的那版板子,sense 走线其实已经画得很短了,还加宽到了 2mm——照样 5.55%。

死法 C:焊盘外缘取点。 这是我原本的做法,也是三种里最隐蔽的。分流电阻焊在焊盘上,我从焊盘的外侧边缘引 sense 走线。这样看起来”接到了电阻上”,实际上测量回路包含了焊盘内侧到外侧的那一段铜,以及焊锡。折算下来几毫欧到十几毫欧,具体取决于焊盘设计。

这类错误的典型症状是:误差与负载无关,只跟板温和环境温度相关。 因为它串入的是个固定的铜电阻,只有温度在变。

我这次的板子就是 A 和 C 的混合——短而宽的走线(避免了 A 的量级灾难),但从焊盘外缘取点(中了 C)。所以是”温和但致命”的 5.55%,而不是 103% 的爆炸值。这也是它为什么能通过研发阶段的测试:8.4% 的漂移,在产品规格的 ±2% 面前当然是大问题,但它不会让板子不工作,所以它藏了下来。

5.3 开尔文四线接法:本质是把走线赶出测量回路

解法大家都知道叫”开尔文接法”或”四端子接法”,但我想强调的是它的原理,而不是它的名字。

关键不在于”多接两根线”,而在于”让 sense 线上不流过主电流”。

放大器的输入阻抗很高(µA 级偏置电流),所以 sense 走线上根本不走主电流。既然如此,sense 走线自己的电阻就不产生压降,也就不会进入测量结果。测量点在哪里,测量结果就在哪里。

所以正确的做法是:

  • 主电流从外圈的宽铜箔走(走线电阻 3mΩ 也无所谓,它只是多耗点电);
  • sense 走线从元素内部的有效测量点引出,走单独的细线到放大器;
  • 两者只在那个点上交汇一次,之后老死不相往来。

用这个视角回看上面的三种死法:A 和 B 是”sense 线上流过了主电流”,C 是”测量点选错了地方”。 两者是同一件事的两面。

改成开尔文接法之后,那 5.55% 直接变成 0:

接法 串入电阻 40K 温漂贡献 增益总误差
两线 + 细走线(0.3mm x 2mm) 3.27 mΩ +121% 灾难
两线 + 宽走线(2mm x 0.61mm) 0.150 mΩ +5.55% 9.65%
开尔文四线 0 0 4.1%

从 9.65% 降到 4.1%。 改动量是:把 sense 走线的引点和走向改一下,一个 30 分钟的活。

5.4 PCB 上的五条落线规则

我把开尔文接法在 PCB 上固化成五条可检查的规则,写进了设计规范。这五条不需要理解原理就能执行,也因此更不容易被偷懒跳过:

  1. sense 走线必须从焊盘内侧(元件有效区一侧)引出,绝不能从焊盘外缘或电流通道上引出。 如果器件的数据手册给了推荐 land pattern 和 sense 点,照抄。
  2. sense 走线不承载主电流,所以可以做得很细(0.2~0.3mm)——细一点反而好,可以贴着走、可以走差分对。别为了”保险”把它加宽,加宽会增加它与大电流路径的耦合面积。
  3. sense 差分对必须紧邻同行(等长、等间距、同层),全程避开主电流通道。 两条线之间不要有任何一条会流大电流的铜箔穿过。
  4. 两端 sense 出来的铜面积、过孔数量、走线几何必须对称。 这一条不是为了让压降对称,而是为了让两端的温度对称——原因在下一节的塞贝克效应。
  5. 分流电阻的两端下方必须有足够大的铜块和过孔,把自热导走;但两端的铜块必须等面积。 我们常常会不自觉地给”有空间的那一侧”堆更多铜,而不对称的铜,正是温差的来源。

六、热电势:那个你没设计进来的热电偶

上一节第 4 条规则提到”温度对称”。这一节解释为什么。

凡是两种不同金属的连接点,都是一个热电偶。分流电阻的两个焊点,就是两个”铜-合金”热电偶。如果这两个焊点温度不同,它们产生的电动势就不相等,差值会直接加在测量信号上。

不同材料对的塞贝克系数差别巨大:

材料对 热电势量级 说明
锰铜(Manganin)对铜 1 ~ 3 µV/℃ 精密分流器的默认选择,热电势小
康铜(Constantan)对铜 约 40 µV/℃ 就是 T 型热电偶的负极材料,热电势大
镍铬(Nichrome)对铜 中等 高温强度好,但热电势不友好

康铜对铜的 40µV/℃,意味着两个焊点只要差 5℃,就产生 200µV 的误差电压。

200µV 折算到我这个系统(0.5mΩ)是多少电流?

I_err = 200 µV / 0.5 mΩ = 400 mA

400mA。 而我客户看到的异常是 420mA。

当时我看到这个数字,第一反应是”找到了”。但继续验证后确认不是它——因为它的症状不对。塞贝克效应的读数会随着气流、风扇启停、以及焊点两侧的散热条件变化而跳变,不会呈现”早上 5.00 / 中午 5.42″这种单调规律。而且我用的那颗贴片电阻,数据手册明确标注热电势 < 3µV/℃(这是 2512 金属条封装的一个隐藏优势)。

但这一节不能省,原因是它解释了两件事:

第一,如果你选了康铜类合金,或者你自己用铜条 + 合金片”拼”了一个分流器,热电势会成为你的头号误差源,量级轻松超过 400mA,而且它没有规律——你没法用温补解决它,因为你不是在补”温度”,你是在补”未知方向的温差”。

第二,”对称布局”这条规则真正要防的就是它。 给两端的铜以相同的面积、相同的过孔数、相同的走线几何——目的不是电气对称,而是让两个焊点处于相同的温度。这跟”哪边有空间就在哪边多铺点铜”的直觉是相反的,而不对称的铜恰好就是温差的来源。

现场诊断的一个小技巧:用热风枪对着分流电阻的一侧吹,看读数是否变化。 如果读数随热气飘动,而分流器本体温度还没上来,那基本就是热电势或 Vos 在作怪。

七、放大器:不是”增益够不够”,是”零点稳不稳”

到这一节,链路的前两段已经有了定量的账。先把放大器的关键参数捋清楚,因为它们各自管的东西不一样:

参数 典型值(高精度 CSA) 它管什么 折算成电流误差的方式
输入失调 Vos ±25 µV (max) 零点绝对精度 Vos / R_shunt
Vos 温漂 ≤ 250 nV/℃ 零点的稳定性 (dVos/dT) x dT / R_shunt
增益误差 ±0.2% (max) 增益绝对精度(可标定掉) 直接计入
增益漂移 2.5 ppm/℃ (max) 增益稳定性(标定不掉) ppm x dT
CMRR (DC) 132 dB 共模直流干扰 共模电压 / 10^(CMRR/20)
CMRR (AC, 50kHz) 93 dB PWM 开关瞬变 开关噪声残留
PSRR ±10 µV/V 电源纹波串入信号 电源波动 x 10µV/V

这张表里,有两行是我以前从没认真看过的,而它们恰好是这次问题的关键。

第一行是 Vos 温漂。 250nV/℃ 看着小到可以忽略。但代入:

温漂贡献 = 250 nV/℃ x 102.5 K = 25.6 µV
折算电流 = 25.6 µV / 0.5 mΩ = 51 mA

51mA。 跟 Vos 本身的 50mA 一个量级。也就是说,放大器这一项在冷热之间的变化量,跟它的初始失调一样大——你在常温下把零点校准到 0,到 127℃ 时它会自己漂回去。

第七行是 AC CMRR。 直流 CMRR 132dB 听着很厉害,但在 50kHz(PWM 开关频率的谐波)处降到了 93dB。这个降幅在电机驱动、逆变器、开关电源这类共模电压在几百 ns 内跳几百伏的场合是决定性的——它直接决定了你要不要在采样点上”躲”开关噪声(第九节)。

7.1 器件档次对照

把主要的几档放在一起(Vos 折算到 R = 0.5mΩ):

类别 Vos (max) 折算电流误差 备注
通用运放搭差分 1000 µV 2000 mA 还要额外算电阻网络的匹配误差
精密零漂运放 10 µV 20 mA Vos 极好,但共模范围窄,要外接网络
专用 CSA 入门档 150 µV 300 mA 够用档,增益误差 1% 量级
专用 CSA 高精度档 25 µV 50 mA 增强 PWM 抑制,共模 −4 ~ 80V
隔离放大器(普通档) 1500 µV 3000 mA CMTI 只有 10~15 kV/µs
隔离放大器(高精度档) 200 µV 400 mA CMTI 75~140 kV/µs

看这张表的时候要注意一件事:“折算电流误差”这一列与增益倍率无关,但与你的 R 有关。 换一颗放大器之前,先想想能不能换 R——虽然第三节说过 R 的取值范围是被功耗和零点两头夹住的,但在夹出来的窗口里挪一挪,往往比换器件便宜。

7.2 增益倍率怎么定

增益的定法很简单:让满量程输出落在 ADC 量程的合理区间(15%~90%),既不饱和也不浪费分辨率。

我的参数(R = 0.5mΩ,12bit,Vref = 3.3V):

增益 满量程 50A 输出 电流分辨率 判定
20 0.50 V 80.6 mA/LSB OK(但分辨率偏粗)
50 1.25 V 32.2 mA/LSB OK,选中
100 2.50 V 16.1 mA/LSB OK(双向方案下余量不足)
200 5.00 V 8.1 mA/LSB 饱和

这里有个双向采样的坑。 电池电流是双向的(充电/放电),所以要把零点抬到 Vref/2 = 1.65V:

可用摆幅 = ±(3.3/2 - 0.2) = ±1.45 V     (留 0.2V 上下轨余量)
最大可测电流 = 1.45 / (0.5mΩ x 50) = 58 A

增益 100 的话满量程输出 2.5V,超过 1.45V 的可用摆幅,单向量程没问题,双向就饱和了。所以增益 50 是这颗 12bit ADC 下的正确选择。

另外注意:32.2 mA/LSB 的分辨率比 Vos 折算的 50mA 还小,说明量化不是瓶颈——这是好的,因为量化误差可以用过采样压下去,Vos 不行。

八、隔离:dv/dt 才是选型的硬门槛

这块板子最后我改成隔离方案了,原因不是”隔离更安全”这种正确的废话,而是一个具体的数:dv/dt。

8.1 三种拓扑的取舍

拓扑 分流电阻位置 共模条件 优点 代价
低边采样 桥臂下管与地之间 共模 ≈ 地 最简、无需隔离 只能在下管导通时测,有盲区;下管的 di/dt 噪声直接进信号
高边采样 母线/电池正极侧 共模 = 母线电压 全周期可测 需要高共模 CSA 或浮地电源;dv/dt 抑制是核心指标
隔离采样 任意位置 安规隔离 安规与共模双解 延迟大、成本高、需要隔离电源

我这次要测的是电池侧母线电流,位置在高边、且共模跟着电池电压走(48V 系统充电时能到 58V 左右)。所以低边方案直接出局——没有合适的位置。

在高边非隔离和高边隔离之间,判据就是这个:

8.2 定量门槛:800V / 50ns = 16 kV/µs

先把 dv/dt 算出来。设母线电压 V_bus(我这个系统是 48V 级,但为了说明问题,按常见的 800V 母线算),开关时间 t_sw:

dv/dt = V_bus / t_sw = 800 V / 50 ns = 16 kV/µs

16 kV/µs。 这就是这个系统对隔离器提出的硬指标。工程上一般要求器件 CMTI(共模瞬态抗扰度)至少是实际 dv/dt 的 2~3 倍,也就是 32~48 kV/µs。

对照一下:

器件 CMTI 判定
隔离放大器(普通档) 10 kV/µs (min) 不够
通用数字隔离器 25 ~ 50 kV/µs 刚够,风险
隔离放大器(高精度档) 75 kV/µs (min) 余量充足
隔离放大器(高精度档,典型值) 140 kV/µs 余量充足

这就是选型依据。 不是”哪个贵用哪个”,而是”800V 母线的 dv/dt 是 16kV/µs,普通档隔离器的 10kV/µs 不够,必须上到 75kV/µs 档”。

我把这一条固化成了设计模板里的一行:

CMTI 要求 (kV/µs) >= 3 x V_bus / t_sw

这条式子之所以重要,是因为它把”隔离”从一个定性的词变成了一个可以验算的数。很多设计是”因为看到别人用隔离器所以我也用”,然后选了一颗便宜的、CMTI 只有 10kV/µs 的——装上去能工作,EMC 测试也能过,但在极端工况下偶发出错,而且永远查不出来。

8.3 延迟也要进环路预算

隔离器换来的不只是隔离,还有延迟。这些延迟会进入电流环,变成相位滞后:

环节 延迟 2kHz 环带宽下的相位滞后
隔离放大器(普通档) 3.4 µs 2.4°
隔离放大器(高精度档) 3.0 µs 2.2°
非隔离高精度 CSA 2.0 µs 1.4°
ADC + 抗混叠滤波 ~2.0 µs 1.4°

单看每一级都不大,但它们是串联相加的:隔离器 3µs + ADC/滤波 2µs = 5µs,在 2kHz 电流环带宽下累计 3.6°。如果电流环带宽做到 5kHz,这个滞后会直接放大到 9° 以上——在电流环的相位裕度里,这不是小数。

所以选隔离器的时候,”带宽/延迟”这一列必须和”CMTI”一起看。我见过一个设计,为了 CMTI 选了带延迟补偿的高档隔离器,结果替代料的延迟差了 1µs,环路直接从稳定变成了轻微振荡。

九、同步采样:不要在孤立的时刻读数

这一节讲的是”什么时候采样”,它属于第二节那张图里的第 5 段。

9.1 谷点采样:一个物理上正确的时刻

逆变器的电流是 PWM 调制的,电感电流是三角波纹波。采样时刻不同,读到的瞬时值不同。 如果你在开关瞬间采样,读到的是尖峰;如果在波峰或波谷采样,读到的是脉动电流的平均值。

所以规则是:在三角载波的谷点(或峰点)触发采样。

在谷点:

  • 电感电流此刻恰好等于周期平均值(前提是对称 PWM);
  • 开关管刚完成一个开关动作,噪声处于静默窗口;
  • 两侧各留 0.5µs 的留白(死区 + 振铃衰减),剩下大约 24µs 的可用窗口。

实现上有个细节:必须用定时器的硬件触发(TRGO 之类的机制)去启动 ADC,不要在中断里软件启动。 中断有抖动,而谷点窗口只有几百 ns 的容错。

9.2 每周期只采一次,会撞上混叠

这是我在这次改造里踩的第二个坑,也是我觉得最有教育意义的一个。

如果每周期只采一次,采样率就等于 f_pwm(20kHz),奈奎斯特频率是 10kHz。而开关纹波的基频也是 20kHz——它正好落在”会折叠回基带”的位置上。

想要滤掉它,抗混叠滤波器必须在 20kHz 处就有足够衰减。一阶 RC 的衰减是:

抗混叠滤波器 fc 在 20kHz 处的衰减 在 2kHz 电流环带宽处的相位滞后
1 kHz −26 dB 63.4°
2 kHz −20 dB 45°
5 kHz −12.3 dB 21.8°
10 kHz −7 dB 11.3°

这就是矛盾所在:要衰减够(fc 要低),相位滞后就大(fc 要低);要相位滞后小(fc 要高),衰减就不够。两个要求直接打架。

而我一开始选的就是 fc = 10kHz 那行:衰减只有 −7dB,同时相位滞后 11.3° 已经超标。两边都不满足。

9.3 正解是过采样,不是更好的滤波器

解决这个矛盾的办法,不是去找一个”又陡又不延迟”的滤波器(这种滤波器不存在,因为物理定律),而是把采样率提上去:

每周期采样次数 采样率 奈奎斯特 效果
1 20 kHz 10 kHz 纹波正好折叠回基带
2 40 kHz 20 kHz 纹波仍在边缘
4 80 kHz 40 kHz 纹波落到折叠带外
8 160 kHz 80 kHz 余量充足

每周期采 4~16 次,用定时器硬件触发(在载波周期内均匀打点),奈奎斯特就被推到了 40~80kHz,开关纹波不再折叠。滤波器只需要削掉尖峰,不需要做得很陡,于是 fc 可以留在十几 kHz,相位滞后压回几度。

取用时,只保留”谷点那一次”的值(或者对同一谷点附近几次做滑动平均)。这样:

  • 等效带宽 < 10kHz
  • 等效延迟 < 5µs
  • 两个约束同时满足。

这个思路可以推广:任何”采样率和干扰源频率在同一量级”的场合,第一反应应该是提高采样率,而不是加更复杂的滤波。滤波是有代价的(相位),采样率是免费的(只要 ADC 跟得上)。

十、误差预算:把 8% 拆成一张表

到这里,所有项都算完了。把它们合成一张表——这是整个排障过程中最有价值的一步,因为它把”感觉哪里有问题”变成了”哪一项占多少”。

R = 0.5mΩ,I_fs = 50A,分流器温度 127.5℃(环境 65℃ + 自热 62.5℃),相对 25℃ 标定点:

误差项 占满量程 类别
分流器初始公差 1.0% 常量
分流器长期漂移 0.5% 常量
放大器增益误差 0.2% 常量
ADC 增益/参考初始 0.1% 常量
ADC 量化(1/2 LSB) 0.012% 常量
分流器本体 TCR 4.10% 温度
放大器增益漂移 0.026% 温度
参考温漂 1.025% 温度
放大器 Vos(折算 50mA) 0.10% 零点
放大器 Vos 温漂 0.102% 零点
热电势(3µV/℃ × 5℃) 0.06% 零点
(开尔文前的走线) 5.55% 温度

按类别合成(均方根 RSS):

类别 RSS 贡献 能不能靠常温标定消掉
温度相关 4.23%(含走线则 6.9%) 不能
常量项 1.14% 能(常温两点标定即可)
零点类 0.155% 部分

注意这个分布的含义:三类里,唯一能靠”出厂标定”解决的常量项,占比最小。 而占大头的温度相关项,出厂标定一点用都没有——因为它在标定的时候数值是 0,要等温度变才出现。

这解释了为什么”出厂检验全过、到客户手上就漂”这类问题这么多:我们的验收标准测的都是常量项。

现在把最后一笔账合上——为什么客户看到的是 8.4%,而我算出来是 9.65%:

增益项(分流器本体 + 走线)  = +9.65%    →  5.00 A x 1.0965 = 5.483 A
零点项(Vos 使读数偏小)      ≈ -60 mA    →  5.483 - 0.060 = 5.423 A
最终读数                       = 5.42 A
实测                          = 5.42 A   ✓

5.42A,和客户看到的完全一致。

我在白板上写下这个数的时候,说实话有点后怕——不是因为这个误差有多大,而是因为它是由两个方向相反的独立误差凑出来的。如果哪天走线改短了、或者 Vos 换了个符号,误差可能变成 11%,也可能变成 8%。你没法通过观察最终读数来推断中间有什么问题。

10.1 改造前后的对照

项 改造前 改造后
分流电阻 2512 两端子,TCR ±400 ppm/℃ 四端子带铜块,TCR ±50 ppm/℃
热阻 50 ℃/W 12 ℃/W
走线接法 两线,串入 0.150 mΩ 开尔文四线,串入 0
参考电压 MCU 内部,±100 ppm/℃ 外部精密基准 + 软件监测
温补 无 NTC 贴分流器,增益/零点分开补
零点 上电标定一次 停机窗口自校准
增益误差(10~65℃) 9.65% 0.4%
零点误差 50 mA 8 mA
5A 读数漂移 8.4% 0.6%

最后那行,从 8.4% 到 0.6%,一共 14 倍。

而成本增加主要在”四端子分流器”上——一颗 2512 贴片和一颗带铜块的四端子,差价大概几块钱。5.55% 那部分,改动成本是 0(只是走线改了个引点)。

十一、固件侧:定点、温补、零电流自校准

硬件改完,固件这边有三件事要补。前提还是站里的两条铁律:不用 malloc,不用 %f。 电流采样链路是控制回路的一部分,它的代码必须能在任何时刻确定性地完成——%f 会走 newlib 的 _dtoa_r(内部 malloc),而一旦编译器把 nanoprintf 的浮点支持关掉(Release 配置的默认行为),snprintf("%f") 会编译零告警、运行时输出空字符串,症状是 JSON 变成 "i":, 被服务端整帧丢弃。

11.1 定点换算:ADC 码 → 毫安

先把链路参数固化成整数:

/* 电流采样链路参数(单位为"毫"和"微",全程整数) */
#define CS_SHUNT_UOHM      500      /* 分流电阻 0.5 mΩ = 500 µΩ */
#define CS_GAIN_NUM        50       /* 放大器增益 50        */
#define CS_ADC_FS          4096     /* 12 bit               */
#define CS_VREF_MV         3300     /* 参考 3.3 V,单位 mV  */
#define CS_VREF_MID        1650     /* 双向零点 Vref/2,单位 mV */

/* 1 LSB 对应的电流,单位 = 微安(µA)
 * 推导:V_LSB = 3300 mV / 4096 = 0.80566 mV = 805.66 µV
 *       R_total = R_shunt x Gain = 0.5 mΩ x 50 = 0.025 Ω
 *       I_LSB = 805.66 µV / 0.025 Ω = 32226 µA = 32.226 mA
 * 合并成一行:µA/LSB = Vref_mV * 1000 * 1000 / (ADC_FS * R_uOhm * Gain / 1000)
 *                  = 3.3e9 / 102400 = 32226
 */
#define CS_UA_PER_LSB      32226

/* ADC 码 -> 毫安;adc_signed 为有符号原始码,已扣除零点码 */
static inline int32_t cs_code_to_ma(int32_t adc_signed)
{
    /* 先乘后除,中间结果用 int64 防溢出;+/-500 是四舍五入 */
    int64_t ua = (int64_t)adc_signed * CS_UA_PER_LSB;

    return (int32_t)((ua + ((ua >= 0) ? 500 : -500)) / 1000);
}

自检一下:adc_signed = 155(即 5A 工作点)→ 155 x 32226 = 4995030 µA → 4995 mA,也就是 5.00A。adc_signed = 1551(满量程 50A)→ 49982526 µA → 49983 mA,即 49.98A。对得上。

几个必须注意的点:

  • 先乘后除。先除会把分辨率丢掉,而且误差会在后续累积。
  • 中间结果用 int64。12bit 码 × 16113 只有 6.6e7,int32 够;但如果后面加了滤波器系数,很容易溢。
  • 不要用 float 中转。有些同学喜欢 (float)adc * 0.016113f,这在一颗没有 FPU 的 M0 上就是一次软件浮点乘——慢,而且引入了新的舍入路径。整数乘除在 Cortex-M3 以上就是一个指令。

11.2 两点温补:增益和零点必须分开

温补最容易犯的错是”用一条直线把两者糊在一起”。增益的温度特性和零点的温度特性是两个独立的物理过程(前者是分流电阻和放大器增益,后者是 Vos),必须分开补。

标定方法是两点法:在 T1 和 T2 两个温度点各测一次”给定电流下的读数”,然后反解出增益系数和零点系数。举例(数字是示意):

标定点 温度 实际通入 读数
T1 25℃ 5.000 A 5.000 A
T2 85℃ 5.000 A 5.420 A
相对增益漂移 = (5.420 - 5.000) / 5.000 / (85 - 25) = 1400 ppm/℃

定点实现全程整数。注意范式要选对:正确的还原式是 I_true = I_read / (1 + TC x dT),不是 I_read x (1 - TC x dT)。后者是前者的线性近似,在 8.4% 这个量级上会带来约 35mA 的残差——看着不大,但它是个系统性的、随温度增大的偏置,正好会毁掉你辛苦做出来的零点精度。

/* 温补系数:相对增益温漂,单位 ppm/℃ */
#define CS_GAIN_TC_PPM  1400
#define CS_T0_X10       250          /* 标定温度 25.0℃(0.1℃ 为单位) */

/* 温度补偿:i_ma 为未补偿读数(mA),t_x10 为 NTC 温度(0.1℃)
 * I_true = I_read * 1e6 / (1e6 + TC_ppm * dT)
 */
static inline int32_t cs_temp_compensate(int32_t i_ma, int32_t t_x10)
{
    int64_t den;

    /* 温差,先保持 0.1℃ 单位,再 /10 化成 ℃ */
    den = 1000000 + ((int64_t)CS_GAIN_TC_PPM * (t_x10 - CS_T0_X10)) / 10;

    /* den 恒为正(本设计温差不超过 100℃),无需判断符号 */
    return (int32_t)(((int64_t)i_ma * 1000000) / den);
}

验算:i_ma = 5420、t_x10 = 850(85℃)→ dT = 60℃、den = 1000000 + 84000 = 1084000 → 5420 x 1000000 / 1084000 = 5000 mA。恰好还原成 5.000A。

三条纪律:

  1. NTC 必须贴在分流电阻旁边(同一块铜岛),不能贴 MCU。 你要补的是分流电阻的温度,不是芯片的温度。这两者在大电流下能差 50℃ 以上。
  2. 零点系数和增益系数两套,分别标定。 零点项加性、增益项乘性,混在一起会得到一个”在标定点准、在别处不准”的诡异曲线。
  3. 只在标定温度区间内插值,区间外钳位,禁止外推。 外推出来的补偿系数看起来很美,实际上是错的,而且会让问题在极端温度下集中爆发。

11.3 零电流窗口自校准

零点漂移有个便宜又有效的对策:在确定无电流的时刻,重新采一次零点。

逆变器有天然的零电流窗口——关机指令下发、继电器断开之后、到下一次开机之前。这个窗口里电流严格为 0,此时读到的任何非零值就是零点误差。

/* 零电流自校准:带速率限制,防止"边跑边漂" */
#define CS_ZERO_STEP_MAX   3    /* 单次最多修正 3 个码           */
#define CS_ZERO_LIMIT      64   /* 零点码超出 ±64 判传感器故障   */

static int32_t s_zero_code = 0;

void cs_zero_calibrate(int32_t observed_code)
{
    int32_t delta;

    /* 只允许在"确认无电流"的窗口内调用 */
    delta = observed_code - s_zero_code;

    if (delta > CS_ZERO_STEP_MAX) {
        delta = CS_ZERO_STEP_MAX;
    } else if (delta < -CS_ZERO_STEP_MAX) {
        delta = -CS_ZERO_STEP_MAX;
    }

    s_zero_code += delta;

    if (s_zero_code > CS_ZERO_LIMIT || s_zero_code < -CS_ZERO_LIMIT) {
        /* 零点偏得离谱 -> 不是漂,是故障,上报而不是继续修 */
        cs_fault_set(CS_FAULT_ZERO_OUT_OF_RANGE);
    }
}

三条纪律:

  1. 单次修正必须限幅。 不限幅的话,一次误判(比如窗口里其实还有电流)就会把零点带跑偏,而且再也回不来。
  2. 必须有上限。 零点码跑到 ±64 之外,说明不是漂移而是故障(分流器开路、放大器坏、走线断),此时应该上报而不是继续补偿。
  3. 绝不要在电流环里更新零点。 那等于给一个闭环控制器加了一个随机的时变偏置,环路稳定性要出事。校准放在低优先级任务里,用原子方式更新。

11.4 上报:整数 + 定点格式化

/* 上报 JSON:全程整数,零 malloc,禁 %f */
typedef struct {
    int32_t i_ma;          /* 电流,mA(有符号,正=放电)  */
    int32_t v_bus_mv;      /* 母线电压,mV                 */
    int32_t t_shunt_x10;   /* 分流器温度,0.1℃             */
    uint16_t zero_code;    /* 当前零点码                   */
    uint16_t fault_flags;  /* 故障位                        */
    uint32_t seq;          /* 序号,检测丢帧                */
} cs_sample_t;

/* 帧例:{"i":5120,"v":52180,"t":682,"z":12,"f":0,"n":1301} */
int cs_format_json(char *buf, uint16_t cap, const cs_sample_t *s)
{
    int n;

    n = snprintf(buf, cap,
                 "{\"i\":%d,\"v\":%d,\"t\":%d,\"z\":%u,\"f\":%u,\"n\":%lu}",
                 (int)s->i_ma, (int)s->v_bus_mv, (int)s->t_shunt_x10,
                 (unsigned)s->zero_code, (unsigned)s->fault_flags,
                 (unsigned long)s->seq);

    /* snprintf 返回"想写的长度",截断时必须自己发现 */
    if (n < 0 || (uint16_t)n >= cap) {
        cs_fault_set(CS_FAULT_JSON_TRUNC);
        return -1;
    }
    return n;
}

注意最后那个 snprintf 返回值的检查。snprintf 返回的是”如果没有长度限制,会写多少字节”,不是”实际写了多少”。不检查这个返回值,缓冲区被截断时你会发出一帧不完整但看起来合法的 JSON——服务端解析失败,整帧丢弃,而设备端日志里没有任何异常。这个坑我在别的地方踩过一次,现在成了肌肉记忆。

定点格式化有个配套的小工具,如果你们的代码里到处是 double 转字符串,可以看看站里那篇关于定点格式化的记录——核心就是自己写四舍五入到小数位的整数打印,不碰 %f。

十二、现场五招:不用仪器也能定位

这次排障过程中我总结出五个动作,全部只需要万用表、热风枪、和一台能显示 mA 的电源。它们的共同特点是:把”混合误差”拆成”单项误差”,而且拆得干净。

招 1:零点法(最快,最先做)

短接放大器两个差分输入(或让负载断开、确认无电流),把整机跑热,看读数的变化量。

读到的变化量 = 全部零点类误差。 它把增益类误差整个排除掉了。

招 2:对称法(分离 Vos 与热电势)

用一台可编程电源给分流电阻通正反两个方向的同一电流(或者把分流电阻反过来接),两次读数记为 I+ 和 I-。

  • 理想情况:I+ = −I−,两者之和为 0。
  • 两者之和不等于 0 → 存在零点偏置(Vos 或 ADC 偏置)。
  • 两者绝对值不相等(|I+| ≠ |I−|)→ 存在非线性误差,通常是热电势或共模问题。

这个方法的好处是:不需要已知的精密电流源,只需要一个可换向的源,误差就被分离出来了。

招 3:交叉法(直接暴露走线电阻)

用四线制微欧计(带开尔文夹的那种)直接量分流电阻两端的实际阻值。

关键操作是两个探夹放在哪里:

  • 夹在元件焊盘上 → 量到的是”分流电阻 + 焊点 + 焊盘”;
  • 夹在你实际引 sense 走线的那个点上 → 量到的是”分流电阻 + 焊点 + 焊盘 + sense 走线上的那一段铜”。

两个读数的差值,就是你串进测量回路的走线电阻。

我的那次测量:元件上是 0.502mΩ,sense 引点是 0.652mΩ,差 0.150mΩ——正好是第五节算出来的那个 0.150mΩ。

招 4:温度法(分离本体温漂)

用热风枪只对着分流电阻本体吹 20℃(别吹放大器,别吹 ADC),看读数变化。

  • 读数变化 ≈ 本体 TCR × 20K 折算的电流 → 确认是本体温漂;
  • 读数不变,但用热气吹整个板子会变 → 是走线/板温的问题;
  • 读数忽大忽小、跟着气流飘 → 热电势。

招 5:铜线法(现场验证 sense 点选对不对)

在分流电阻的 sense 点上直接焊两根细线(0.1mm 的漆包线就行),引到万用表的 mV 档,然后跟设备自己的读数对比。

如果设备读数偏大、而万用表读数正常,说明你的 sense 点在元件之外——把那两根细线的引点往里挪,直到两者一致。找到了正确位置,再回去改 PCB。

症状 → 根因定位表

现象 根因 对策
冷机准、热机偏,且偏移量随负载时间单调上升 分流器自热 + 本体 TCR 降热阻、降功耗、换低 TCR 档
偏移量与负载无关,只跟环境温度相关 铜走线/焊点(非开尔文) 改四端子取点,走线移出回路
正反方向读数不对称 热电势(塞贝克)或 Vos 两端铜面积/过孔数对称;换低 EMF 合金
正反两次读数之和不等于 0 放大器 Vos 或 ADC 零点偏置 零电流窗口自校准 + 温漂补偿
读数随风扇/气流变化 塞贝克热电势 对称布局 + 加护罩,不要单侧堆铜
小电流档误差巨大,大电流相对正常 Vos 折算(I_err = Vos/R) 增大 R 或换低 Vos 器件
PWM 占空比跳变瞬间读数有尖刺 共模瞬变 + 放大器恢复时间 用带 PWM 抑制的 CSA;采样点挪到静默窗口
读数整体偏移一个固定比例 增益标定错误 / 参考电压不准 外部精密参考重标;查 Vref 温漂
上电几秒内读数漂到稳定值 器件自热建立过程 等热平衡再采样,或做上电延时
换一批板子误差规律不同 走线/焊盘尺寸版本差异 把 sense 走线几何写进规范,做 DFM 检查
大电流下读数偏低但功耗计算正常 走线串入被算进测量 四端子 + 独立 sense 网络
温补后仍然漂 温补测温点与发热点不重合 NTC 贴分流器铜岛;热时间常数对齐

十三、十二项落地清单

把上面所有内容压成一张可以直接贴进设计评审的清单:

  • ☐ 用”功耗上界”和”零点下界”两头夹出 R 的取值窗口,确认窗口非空
  • ☐ 分流电阻按 I²R 算功耗,取额定的 50% 以内;按 R_th 算温升,别只看额定功率
  • ☐ 去数据手册的分档表里查该阻值档的器件级 TCR,不要用分销商页面上的单个数字
  • ☐ 把这颗电阻的”温升 × TCR”算出来,超过 0.5% 就考虑换封装或拓扑
  • ☐ 优先选四端子(带铜块)封装,别用两端子贴片硬扛 50A
  • ☐ sense 走线从焊盘内侧引出,全程不承载主电流,走等长差分对
  • ☐ 两端 sense 的铜面积、过孔数、走线几何严格对称(防热电势)
  • ☐ 放大器的 Vos / Vos 温漂单独算一遍折算电流(Vos/R),并和 Vos 本体一起看
  • ☐ 隔离方案先用 CMTI ≥ 3 × V_bus / t_sw 算出门槛,再选器件
  • ☐ 采样点定在三角载波谷点,用定时器硬件触发;每周期过采样 4~16 次
  • ☐ 温补系数分”零点”和”增益”两套,NTC 贴分流电阻本体,区间外钳位
  • ☐ 固件全程整数定点(先乘后除 + int64 中间量),零 malloc,禁 %f,检查 snprintf 返回值

最后

那台机器最后是这么修好的:换了一颗四端子分流器(4.1% → 0.275%),把 sense 走线的引点从焊盘外缘挪到焊盘内侧(5.55% → 0),参考换成外部基准并加了 NTC 温补(1.0% → 0.1%),固件加了停机窗口的零点自校准(50mA → 8mA)。

5A 读数漂移从 8.4% 降到 0.6%。

我最大的收获不是这些具体数字,而是一个思维方式的转变:

以前我看电流采样,看到的是”一颗电阻 + 一颗放大器 + 一个 ADC”。现在我看到的是一条七段的链路,每一段都有自己的误差机制,而且它们的温度时间常数不一样。

那个”早上 5.00、中午 5.42″,本质上就是三段不同时间常数的误差叠出来的:分流器的自热是秒级、铜走线是分钟级、放大器是秒级。你看到一个复合的漂移曲线,就一定说明有好几个独立的原因在同时作用——这句话后来帮我省了很多时间。

还有个更具体的教训:出厂标定测的都是”常量项”,而现场漂移几乎全是”温度项”。 如果验收标准里没有”冷热两点 + 满载”这一条,那么不管你的样机在实验室里表现多好,这类问题都会在你的第一个量产批次之后冒出来。

如果你也在做电流采样,或者正被某个”温度一上来读数就不对”的问题缠着,欢迎留言——我特别好奇”sense 点到底画在哪儿”这件事,别人是怎么定规矩的。


相关阅读


本文配套脚本:_tools/current_sense_calc.py —— 电流采样链路现场估算器。mode: shunt(分流电阻体检:压降/功耗/自热温升/TCR 误差/铜走线误差/总增益误差,两线 vs 四线对照)、wire(铜箔走线电阻:几 oz 铜 × 线宽 × 长度 × 温度,含温度修正表)、vos(Vos 折算最小可分辨电流、反解最小 R、六档放大器对照)、budget(完整误差预算,按常量/温度/零点三类做 RSS 合成)、adc(LSB → 电流分辨率、满量程占位、双向方案的可用摆幅、四档增益对照)、iso(dv/dt 计算 + CMTI 门槛 + 器件对照 + 延迟的环路相位账)、tcr(TCR 分档表与并联降自热对比,含”温升 × TCR”乘积表)、sync(PWM 同步采样:采样率、奈奎斯特、抗混叠衰减与相位滞后、过采样收益)、ntc(两点温补系数与 Q16 定点表述)、table(速查表)、triage(症状 → 根因定位表)。纯标准库,全离线,不联网。自带 --self-test(15 项,全过)。

下次打算写:CAN/LIN 之外的 24V 系统与 SAE J2602 差异(通信协议),或者 RT-Thread 的 sensor_hub 数据融合(嵌入式软件)。哪个先写,看这周的活先到哪个。