3、校准数学模型:线性校准模型(y = kx + b)、分段线性模型、多项式拟合模型

好,咱们接着聊。上一章讲了校准的本质——找到测量值和真实值之间的映射关系。那这个映射关系长什么样?用什么数学式子去描述它?这就是今天要聊的校准数学模型。

说白了,校准数学模型就是一把「尺子」。你拿万用表测出一个数,通过这把尺子一换算,就得到了更接近真实值的结果。我做了这么多年嵌入式计量,接触过的校准模型无非三大类:线性模型、分段线性模型、多项式拟合模型。今天一个一个掰开揉碎了讲。

3.1 线性校准模型(y = kx + b)

这是最基础、最常用的模型。公式很简单:y = kx + b。其中 x 是测量值,y 是校准后的值,k 是增益系数,b 是偏移量。

为什么它最常用?因为大部分传感器和ADC在正常工作范围内,响应曲线都接近直线。你想想看,一个分压电阻网络、一个运放跟随器,它们的传递函数本质上就是线性的。所以用线性模型去校准,既简单又有效。

我在项目中遇到过一件事。有一次调试一个电流采样电路,发现测出来的值总是比实际值偏大 0.5mA。排查了半天,发现是PCB走线上的压降导致的系统偏移。这种情况,用线性模型里的 b 参数就能轻松搞定。把 b 设为 -0.5,问题就解决了。

核心要点:线性模型适用于系统误差主要是增益误差和偏移误差的场景。校准过程就是求解 k 和 b 两个参数。

怎么求 k 和 b?最常用的方法是用两个标准点进行两点校准。比如你给万用表输入一个 1.000V 的标准电压,读数是 0.985V;再输入一个 5.000V 的标准电压,读数是 4.925V。那么:

// 两点校准计算 k 和 b
// 标准点1: (x1=0.985, y1=1.000)
// 标准点2: (x2=4.925, y2=5.000)

k = (y2 - y1) / (x2 - x1) = (5.000 - 1.000) / (4.925 - 0.985) = 4.000 / 3.940 ≈ 1.0152
b = y1 - k * x1 = 1.000 - 1.0152 * 0.985 ≈ 1.000 - 1.000 ≈ 0.000

// 校准后的值 y = 1.0152 * x + 0.000

你看,这个例子里的 k 略大于 1,说明ADC的增益偏小,需要放大一点。b 接近 0,说明偏移误差很小。实际项目中,我一般会用三个标准点做最小二乘法拟合,这样更稳健。

我的经验:两点校准虽然简单,但如果标准点选得太近,k 的误差会很大。我建议两个标准点尽量覆盖量程的 20% 和 80% 位置。这样拟合出来的直线更可靠。

3.2 分段线性模型

线性模型虽好,但有些传感器天生非线性。比如热电偶、NTC热敏电阻,它们的输出曲线是弯曲的。这时候用一条直线去拟合,误差会很大。怎么办?分段线性模型就派上用场了。

分段线性模型,说白了就是把整个量程切成若干段,每一段单独用一条直线去拟合。段数越多,拟合精度越高,但需要的存储空间和计算量也越大。

我记得有一次做高精度温度测量,用的是PT100铂电阻。PT100在 -50°C 到 500°C 范围内,电阻-温度关系虽然不是严格线性,但分段线性后精度完全够用。我把量程分成了 5 段,每段 110°C 左右,每段存一组 k 和 b。最终精度做到了 ±0.1°C。

温度范围 (°C) k 系数 b 系数
-50 ~ 50 0.3851 100.00
50 ~ 150 0.3872 99.89
150 ~ 250 0.3893 99.58
250 ~ 350 0.3914 99.05
350 ~ 500 0.3940 98.20

你看,每一段的 k 和 b 都不一样。校准的时候,先判断当前测量值落在哪个区间,然后调用对应的参数进行计算。代码实现也很简单:

// 分段线性校准函数
float piecewise_linear_calibrate(float raw_value) {
    // 定义分段点
    const float breakpoints[] = {-50.0, 50.0, 150.0, 250.0, 350.0, 500.0};
    const float k_coeff[] = {0.3851, 0.3872, 0.3893, 0.3914, 0.3940};
    const float b_coeff[] = {100.00, 99.89, 99.58, 99.05, 98.20};
    
    // 查找所在区间
    int segment = 0;
    for (int i = 0; i < 4; i++) {
        if (raw_value >= breakpoints[i] && raw_value < breakpoints[i+1]) {
            segment = i;
            break;
        }
    }
    
    // 应用线性校准
    return k_coeff[segment] * raw_value + b_coeff[segment];
}
注意:分段线性模型在分段点处可能会出现不连续。如果分段点两侧的校准值跳变太大,测量结果会「抖」一下。我曾经遇到过这个问题,后来在分段点附近做了平滑过渡处理,才解决了。

3.3 多项式拟合模型

如果分段线性还满足不了精度要求,那就得上多项式拟合了。多项式模型用一条连续曲线去逼近传感器的真实响应,理论上可以做到任意精度。

最常用的是二次多项式:y = a*x² + b*x + c。为什么是二次?因为大多数传感器的非线性可以用二次曲线很好地描述。比如热电偶的塞贝克效应,本质上就是二次关系。

我个人的习惯是:先用线性模型试试,如果残差太大,再试二次多项式。如果二次还不够,才考虑三次或更高次。因为高次多项式容易过拟合,而且计算量大,在嵌入式平台上跑起来吃力。

举个例子,校准一个 NTC 热敏电阻温度传感器。NTC 的电阻-温度关系是:R = R0 * exp(B*(1/T - 1/T0))。这个关系用线性模型根本搞不定,但用二次多项式拟合,精度就非常好了。

// 二次多项式校准
// 假设拟合出的系数: a = -0.0005, b = 0.425, c = -10.2
float quadratic_calibrate(float raw_adc) {
    float a = -0.0005;
    float b = 0.425;
    float c = -10.2;
    
    return a * raw_adc * raw_adc + b * raw_adc + c;
}

多项式系数怎么求?一般是在 PC 上用最小二乘法拟合,然后把系数烧录到嵌入式设备的 Flash 里。我常用的工具是 Python 的 numpy.polyfit,或者 MATLAB 的 polyfit。拟合的时候要注意:

  • 数据点要足够多:至少比多项式阶数多 3-5 个点
  • 覆盖整个量程:不要只在中间区域取点,两端也要有
  • 检查残差:拟合完后看看每个点的误差,如果某个点误差特别大,可能是测量有问题
三种模型对比:
  • 线性模型:最简单,适合线性度好的传感器,存储 2 个参数
  • 分段线性:折中方案,适合中度非线性,存储 2N 个参数(N 为段数)
  • 多项式拟合:精度最高,适合强非线性,存储 N+1 个参数(N 为阶数)

最后说一句,选哪种模型,不是越复杂越好。我见过有人用五次多项式去校准一个线性度很好的压力传感器,结果计算量大了好几倍,精度提升却微乎其微。说白了,够用就好。先评估传感器的非线性程度,再决定用哪种模型。这才是工程师该有的思路。