2、加速度计校准:六面静态校准法、椭球拟合校准原理、温度补偿策略
好,咱们接着聊加速度计的校准。说实话,加速度计是飞控里最基础的传感器之一,但也是最容易被忽视的。很多人觉得加速度计嘛,不就是测个重力方向?其实没那么简单。我见过不少飞控炸机,最后查出来是加速度计没校准好,姿态解算从一开始就是歪的。
今天咱们重点讲三种校准方法:六面静态校准、椭球拟合校准,还有温度补偿。这三种方法,说白了就是解决不同层次的问题。六面法是基础,椭球拟合是进阶,温度补偿是实战中不得不面对的坑。
2.1 六面静态校准法
六面静态校准,这是最经典、最直观的方法。原理很简单:把加速度计分别朝向六个方向,记录每个方向上的输出值。
哪六个方向?就是+X、-X、+Y、-Y、+Z、-Z。每个方向都让重力完全作用在那个轴上。比如Z轴朝上时,理论上输出应该是+1g;Z轴朝下时,输出是-1g。
嗯,这里有个关键点:静态。什么意思?就是校准过程中传感器不能动,不能有振动。我刚开始做飞控时,有次偷懒,用手拿着传感器翻来翻去,结果校准出来的参数一塌糊涂。后来才明白,哪怕手轻微的抖动,都会引入噪声。
六面校准要解决的核心问题是:零偏误差和比例因子误差。
- 零偏误差:当加速度为0时,传感器输出不为0。比如静止在桌面上,理论上三个轴输出应该是(0, 0, 1g),但实际可能是(0.02, -0.01, 0.98g)。
- 比例因子误差:每个轴的灵敏度不完全一致。比如X轴1g对应1000 LSB,Y轴1g对应1020 LSB,这就导致同样的重力加速度,不同轴输出不一样。
校准模型通常是这样:
a_calibrated = (a_raw - bias) * scale_factor
实际操作时,我习惯把传感器固定在水平台上,用水平仪确认每个面都放平了。然后采集每个方向的数据,取平均值。六个方向的数据采集完后,解一个线性方程组就能算出bias和scale_factor。
这里给个简单的代码示例,展示如何计算:
// 六面校准数据示例(单位:g)
// 每个方向采集100个点取平均
float accel_data[6][3] = {
{0.98, 0.01, 0.02}, // +X
{-1.02, -0.01, 0.01}, // -X
{0.01, 0.99, -0.01}, // +Y
{-0.01, -1.01, 0.02}, // -Y
{0.01, -0.01, 0.98}, // +Z
{-0.02, 0.01, -1.02} // -Z
};
// 计算零偏
float bias[3];
for (int i = 0; i < 3; i++) {
bias[i] = (accel_data[2*i][i] + accel_data[2*i+1][i]) / 2.0f;
}
// 计算比例因子
float scale[3];
for (int i = 0; i < 3; i++) {
scale[i] = 2.0f / (accel_data[2*i][i] - accel_data[2*i+1][i]);
}
2.2 椭球拟合校准原理
六面校准虽然好用,但它有个前提:传感器安装得足够正,而且每个轴完全独立。现实中呢?传感器芯片焊在PCB上,PCB装在机架上,机架本身可能还有角度。这就引入了轴间耦合误差。
说白了,就是X轴受到Y轴和Z轴的影响。比如你只给X轴施加加速度,Y轴和Z轴也会有点输出。这就是所谓的交叉轴灵敏度。
椭球拟合校准,就是解决这个问题的。它的原理基于一个物理事实:在静止状态下,加速度计的三轴输出模长应该等于1g(假设已经去除了重力方向)。
你想想看,如果传感器是理想的,所有静止状态下的数据点应该落在一个球面上,球心在原点,半径是1g。但因为有零偏、比例因子误差和轴间耦合,实际数据点落在一个椭球上。
校准的目标就是:把这个椭球拉回成一个标准的球。
椭球拟合的数学模型是:
(x - bx)² / sx² + (y - by)² / sy² + (z - bz)² / sz² +
2 * cxy * (x - bx) * (y - by) + 2 * cxz * (x - bx) * (z - bz) + 2 * cyz * (y - by) * (z - bz) = 1
其中bx, by, bz是零偏,sx, sy, sz是比例因子,cxy, cxz, cyz是交叉耦合系数。一共9个参数需要求解。
怎么求解?最小二乘法。采集大量不同姿态下的数据点,然后拟合出椭球参数。我一般会采集至少50个不同的姿态,覆盖所有方向。
这里给个Python伪代码,展示椭球拟合的核心思路:
import numpy as np
def ellipsoid_fit(data):
"""
data: Nx3 矩阵,每行是一个姿态下的加速度计输出
返回:bias (3x1), scale (3x3)
"""
# 构建设计矩阵
x, y, z = data[:, 0], data[:, 1], data[:, 2]
D = np.column_stack([
x*x, y*y, z*z,
2*x*y, 2*x*z, 2*y*z,
2*x, 2*y, 2*z,
np.ones_like(x)
])
# 最小二乘求解
v = np.linalg.lstsq(D, np.ones_like(x), rcond=None)[0]
# 从v中提取椭球参数
# ... (具体推导略,网上有现成代码)
return bias, scale_matrix
2.3 温度补偿策略
好,六面校准和椭球拟合都讲完了。但如果你以为这样就万事大吉,那就太天真了。实际飞行中,温度变化才是最大的敌人。
我记得有一次做无人机测试,早上校准好的参数,到了中午太阳一晒,加速度计输出直接漂了0.1g。姿态解算瞬间就乱了,飞机开始剧烈晃动。那次之后,我彻底明白了温度补偿的重要性。
加速度计的温度特性主要有两种:
- 零偏温漂:温度变化时,零偏会跟着变。典型值在0.1~1 mg/°C之间。
- 比例因子温漂:温度变化时,灵敏度也会变。这个影响相对小一些,但高精度应用不能忽略。
温度补偿的策略,我总结为三个层次:
- 查表法:在实验室里测出不同温度下的零偏和比例因子,做成一个查找表。飞行时根据当前温度查表补偿。这是最直接的方法,但需要提前做大量标定实验。
- 多项式拟合:用多项式拟合温度曲线。比如零偏随温度的变化可以用二次多项式表示:
bias(T) = a0 + a1*T + a2*T²。这种方法比查表法更平滑,但需要确定多项式阶数。 - 在线估计:在飞行过程中,利用其他传感器(比如GPS、视觉)的信息,实时估计温度漂移。这是最先进的方法,但实现复杂,一般用于高端飞控。
我个人最常用的是查表法+线性插值。具体做法是:
// 温度补偿查表(示例数据)
typedef struct {
float temp; // 温度,单位°C
float bias_x; // X轴零偏,单位g
float bias_y;
float bias_z;
} TempCompEntry;
TempCompEntry comp_table[] = {
{-20, 0.02, -0.01, 0.03},
{0, 0.01, 0.00, 0.02},
{25, 0.00, 0.01, 0.01},
{50, -0.01, 0.02, 0.00},
{70, -0.02, 0.03, -0.01}
};
// 线性插值函数
float interpolate(float temp, TempCompEntry *table, int len, int axis) {
// 找到温度区间
for (int i = 0; i < len - 1; i++) {
if (temp >= table[i].temp && temp <= table[i+1].temp) {
float ratio = (temp - table[i].temp) / (table[i+1].temp - table[i].temp);
float b0, b1;
if (axis == 0) { b0 = table[i].bias_x; b1 = table[i+1].bias_x; }
else if (axis == 1) { b0 = table[i].bias_y; b1 = table[i+1].bias_y; }
else { b0 = table[i].bias_z; b1 = table[i+1].bias_z; }
return b0 + ratio * (b1 - b0);
}
}
// 超出范围则取边界值
if (temp < table[0].temp) return table[0].bias_x;
return table[len-1].bias_x;
}
最后说一句,温度补偿不是一劳永逸的。传感器会老化,温度特性也会变化。我建议每隔半年重新做一次温度标定,或者至少做一次六面校准来验证参数是否还准确。
好了,加速度计校准这部分就讲到这里。下一章咱们聊陀螺仪的校准,那个又是另一番天地了。