电流采样链路设计实战:同一台逆变器早上显示 5.00A、中午 5.42A——拆到最后,8% 的漂移里有 5.5% 是走线上那段铜
那台 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 上固化成五条可检查的规则,写进了设计规范。这五条不需要理解原理就能执行,也因此更不容易被偷懒跳过:
- sense 走线必须从焊盘内侧(元件有效区一侧)引出,绝不能从焊盘外缘或电流通道上引出。 如果器件的数据手册给了推荐 land pattern 和 sense 点,照抄。
- sense 走线不承载主电流,所以可以做得很细(0.2~0.3mm)——细一点反而好,可以贴着走、可以走差分对。别为了”保险”把它加宽,加宽会增加它与大电流路径的耦合面积。
- sense 差分对必须紧邻同行(等长、等间距、同层),全程避开主电流通道。 两条线之间不要有任何一条会流大电流的铜箔穿过。
- 两端 sense 出来的铜面积、过孔数量、走线几何必须对称。 这一条不是为了让压降对称,而是为了让两端的温度对称——原因在下一节的塞贝克效应。
- 分流电阻的两端下方必须有足够大的铜块和过孔,把自热导走;但两端的铜块必须等面积。 我们常常会不自觉地给”有空间的那一侧”堆更多铜,而不对称的铜,正是温差的来源。
六、热电势:那个你没设计进来的热电偶
上一节第 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。
三条纪律:
- NTC 必须贴在分流电阻旁边(同一块铜岛),不能贴 MCU。 你要补的是分流电阻的温度,不是芯片的温度。这两者在大电流下能差 50℃ 以上。
- 零点系数和增益系数两套,分别标定。 零点项加性、增益项乘性,混在一起会得到一个”在标定点准、在别处不准”的诡异曲线。
- 只在标定温度区间内插值,区间外钳位,禁止外推。 外推出来的补偿系数看起来很美,实际上是错的,而且会让问题在极端温度下集中爆发。
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);
}
}
三条纪律:
- 单次修正必须限幅。 不限幅的话,一次误判(比如窗口里其实还有电流)就会把零点带跑偏,而且再也回不来。
- 必须有上限。 零点码跑到 ±64 之外,说明不是漂移而是故障(分流器开路、放大器坏、走线断),此时应该上报而不是继续补偿。
- 绝不要在电流环里更新零点。 那等于给一个闭环控制器加了一个随机的时变偏置,环路稳定性要出事。校准放在低优先级任务里,用原子方式更新。
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 点到底画在哪儿”这件事,别人是怎么定规矩的。
相关阅读
- ADC 采样精度实战:同一颗 12 位 ADC,凭什么别人做到 11.5 bit 有效精度,我只有 9 bit? —— 本篇第十一节的”零点/增益两点标定 + 温度补偿”和那篇是同一套方法;那篇讲 ADC 自身,本篇讲 ADC 前面那一段
- 电池管理与 SOC 估算实战:磷酸铁锂显示还剩 40%,三分钟后直接关机 —— 那篇的库仑计要积分电流,而积分的精度完全取决于本篇这条采样链路的零点稳定性;两篇是上下游
- LDO 与 DCDC 电源设计实战:一次 ADC 采样跳动,逼我把整块板子的电源重做了一遍 —— 第七节那个 PSRR ±10µV/V 的账,在那篇里有完整的电源侧算法
- PCB 布局与 EMC 实战:一次辐射超标 8dB 的整改记 —— 第五节那五条 sense 落线规则,属于那篇”布局即滤波器”思路在模拟小信号上的应用
- EMC 传导发射(CE)排查实战:辐射整改完刚松口气,传导又超标 10dB —— 第九节的 PWM 同步采样与那篇的共模噪声路径是同一套物理
- ITM/SWO 打印实战:printf 把 100µs 的控制中断拖到超时 —— 第十一节”禁
%f、零 malloc”这两条铁律的完整来历,以及定点排查的实操方法 - 上拉/下拉电阻取值实战:同一颗 I2C,为什么别人 4.7kΩ 我得换 2.2kΩ —— 那篇讲”同一个公式在不同物理条件下给出不同答案”,与本篇”同一个 Vos 在不同 R 上代价差 20 倍”是同一类思路
- 信号完整性与串阻源端匹配实战:一条 25MHz 时钟线上的振铃,逼我把 33Ω 的位置和取值都算了个明白 —— 那篇讲”串阻的位置决定它是源端匹配还是终端匹配”,与本篇”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 数据融合(嵌入式软件)。哪个先写,看这周的活先到哪个。