简介:一套针对深空探测、卫星组网及在轨服务等场景的基于扩展卡尔曼滤波航天器姿态轨道耦合控制系统Matlab实现方案,面向航天器动力学与控制方向的高年级本科生、研究生及工程人员,可用于课程设计、专题仿真与毕业设计。系统采用模块化参数化编程,涵盖姿态动力学、轨道运动、EKF状态估计、PD/LQR控制及绘图分析等模块,关键算法附详细注释,支持2014a、2019b、2024b等多个版本直接运行。包体共89个文件,含30个m脚本、19个mat数据、15个bmp仿真图与2个slx模型,另有zbak备份、xml配置及说明文档,压缩包整体16.53MB,目录结构便于按功能查阅。配套案例数据集无需预处理即可复现仿真,能帮助读者深入理解EKF在耦合系统中的实现流程与参数调节方法,提升建模与控制算法实践能力。当前已有43人浏览学习。
1. 基于EKF算法的航天器姿态轨道耦合控制系统设计与Matlab实现:为什么说这不是把两个滤波器拼在一起
做过姿控仿真的人都知道一个反直觉结论:姿态和轨道分开做两个EKF,在数学上通常也能跑,但真要把结果拿到半物理试验台上去用,几乎必然翻车。原因在于航天器在轨飞行时,姿态运动和轨道运动之间存在实实在在的耦合通道——J2摄动在改变轨道根数的同时改变了重力梯度力矩的方向,重力梯度力矩又直接作用在姿态动力学上;反过来,帆板转动、飞轮角动量变化也会影响整星惯量与轨道机动推力的方向。把这些通道写进同一个状态向量,用同一个EKF去联合估计姿态参数和轨道参数,就是“基于EKF算法的航天器姿态轨道耦合控制系统设计”要做的事:建立13维紧耦合模型,设计滤波算法,并在Matlab里以可复现的代码跑通验证。这篇文章面向的是做GNC方案论证、姿控算法仿真或者毕业设计的学生和工程师,内容和步骤可以直接对应到你的仿真工程。
2. 航天器姿态轨道耦合系统建模:把EKF需要的那部分动力学写明白
2.1 为什么不能把姿态和轨道分开建模
很多初学姿态轨道耦合控制的人,第一反应是把现有的姿态确定EKF和轨道确定EKF“拼”到一个仿真里。这种做法在轨道机动不频繁、姿态稳定且精度要求不高的场景下勉强能用,但一旦进入以下三种工况就会原形毕露:
- 大椭圆轨道或低轨太阳同步轨道:J2摄动对轨道面的长期影响显著,轨道位置误差会造成重力梯度力矩计算偏差,而重力梯度力矩是低轨航天器最大的环境干扰力矩之一。
- 姿态大角度机动或快速机动期间:喷气推力在体坐标系中的指向偏差会同时产生姿态干扰力矩和轨道方向上的推力损失,这时姿态和轨道的状态误差是互相放大的。
- 高精度遥感或指向控制任务:星敏与磁强计、GPS/BD的测量频率和坐标系基准不同,分开滤波会使两个滤波器对同一物理量的估计不一致,最终体现在控制指令的跳变上。
所以这里采用一个经典的紧耦合做法:把轨道位置、轨道速度、姿态四元数、角速度放进同一个状态向量,用一套动力学方程描述它们之间的相互作用。滤波器估计的是一次全状态,而不是各自维护一套估计值后再融合。从状态估计的角度看,这样做信息利用率更高,协方差矩阵能自然描述姿态误差与轨道误差的关联性。
2.2 姿态运动学与动力学模型(四元数约定先定死)
姿态部分的状态定义如下:四元数使用Hamilton约定,标量在前,记为
其中 q_v 为矢量部分。对应的惯性系到本体系的姿态矩阵为 C(q) = (q_w^2 - ||q_v||^2) I_3 + 2 q_v q_v^T - 2 q_w [q_v]×。角速度 ω 表示本体系相对惯性系的角速度在本体系中的分量列阵。
姿态运动学方程为:
动力学方程为:
这里的 T_gg 是重力梯度力矩,T_dist 是其他环境干扰力矩,T_act 是执行机构力矩。重力梯度力矩是姿态轨道耦合的关键项,低轨场景下不能省略:
其中 u_r 是星体在当地轨道系中指向地心的单位方向矢量在本体系下的投影,I 是惯量张量。可以看到,r 的估计误差会直接通过 u_r 进入力矩计算,这就是轨道误差耦合到姿态通道的第一条路径。
这里有一个工程上非常容易踩坑的点:四元数必须满足归一化约束,在仿真里每一步积分后都要重新归一化,在EKF更新时不能直接把 delta_q 像普通向量一样加到 q 上。这个坑会在第3章和第5章展开。
2.3 轨道动力学模型(含J2摄动的笛卡尔坐标形式)
轨道部分的状态用惯性系下的位置 r 和速度 v 表示,而不是用轨道根数。原因是EKF的预测模型需要连续求导,笛卡尔坐标的动力学方程形式最直接,也不存在奇异问题(比如圆轨道下近点角定义的麻烦)。
二体加速度为:
J2摄动加速度在惯性系下的 x、y、z 分量写成:
其中 R_e 为地球赤道半径,J2 为地球摄动常数。需要强调,这里采用的是惯性系分量形式,而不是轨道系下的RAC形式,因为在EKF状态转移矩阵里所有分量都要对 r 求偏导,笛卡尔坐标更便于做解析雅可比。
在这个系统里,J2是轨道部分的主导摄动力,而且J2导致轨道面进动,进动带来当地轨道系姿态基准的缓慢旋转,进而改变重力梯度力矩在当地轨道系中的方向。这就是第二条耦合路径:J2 → 轨道几何变化 → 重力梯度力矩变化 → 姿态动力学变化。
当然实际工程还有大气阻力、太阳光压等,但在EKF的建模阶段建议先把它们作为过程噪声考虑,而不是直接加进状态方程。原因在第5章讲滤波稳定性时会说明。
2.4 状态向量与状态方程汇总
把上述动力学合并成13维状态向量:
写成紧凑形式:
为了给后续的EKF代码提供直接可用的接口,我建议把这个右端函数写成统一的函数形式
其中 x 为13维列向量,params 为结构体(包含mu、J2、Re、I、disturbance开关等)。这样EKF主循环、仿真积分、校验test全部复用同一个函数,避免在代码里维护两套动力学。
为什么强调“一个函数多处复用”?因为EKF最隐蔽的错误来源就是预测模型和仿真模型不一致。如果你的标称模型(滤波器里用的)和真实模型(仿真环境里用的)不一致但自己没意识到,蒙特卡洛跑出来的结果会非常“好看”也可能非常差,而你根本不知道错在哪里。统一函数后,做无噪声收敛性测试时直接把这个函数作为环境模型,才能单独检验滤波算法本身是否正确。
这里还需要约定坐标基准:轨道位置和速度定义在地心惯性系(ECI)下,四元数和角速度定义在本体系对惯性系。所有量纲一律使用国际单位制。把单位约定写进代码注释和参数结构体里,这是后续所有调试的基础。
3. 把EKF装进耦合系统:误差四元数与雅可比是核心
3.1 EKF的滤波框架与耦合系统下的适用性
EKF处理非线性系统的思路,是在当前估计点处做线性化,把问题转化为线性卡尔曼滤波的框架。标准流程分为预测和更新两步:
预测步:
更新步:
这里的 K 为卡尔曼增益,P 为协方差矩阵,Q 为过程噪声协方差,R 为量测噪声协方差。对于本文讨论的13维耦合系统,F 是系统矩阵,H 是观测矩阵。
在姿态轨道耦合场景下,EKF的适用性是有边界的:如果初始姿态误差超过30°以上,或者角速度误差超过0.1°/s以上,线性化误差会变得明显,此时EKF可能不收敛。工程上一般先用确定性姿态确定算法(比如QUEST或TRIAD)给出一个初步姿态,再用EKF做精估计;或者把初始噪声协方差特意调大,给滤波器更多“搜索空间”。另外如果你手头有Matlab的System Identification Toolbox或者Robust Control Toolbox,可以对比UKF,但EKF在计算量和工程实用性上仍然是最常用的起点。
3.2 四元数在EKF里的“加法”问题
这是整个EKF设计里最容易出错的地方,也是和普通卡尔曼滤波最大的不同。
标准卡尔曼滤波的更新公式是 x = x_hat + K * z,本质上是在状态空间里做加法。但四元数不是欧氏空间里的向量,它必须满足单位模长约束。如果直接用 x 的第7到第10维做加法,得到的四元数模长不再为1,破坏了姿态的唯一性表示。
工程上通用的做法是误差四元数方法。把真实四元数和估计四元数的关系定义为:
其中 delta_q 是误差四元数,在滤波更新中,我们只对误差四元数的小角度部分做可加处理。具体做法是:状态向量拆分
误差四元数的矢量部分是 delta_q_v,对于小角度可以近似为:
于是EKF在更新步给出的修正量,对姿态部分只取误差四元数的矢量部分进行更新,然后再用四元数乘法修正估计值:
这个乘性更新是必须的。如果当成普通向量加法处理,即使没有量测噪声,滤波也会在几个周期内发散——这是初学仿真最常见的问题。
3.3 状态转移矩阵F与观测矩阵H的构造
在EKF中,最重要的两个矩阵就是F和H。F是状态方程对状态向量求偏导的雅可比矩阵,H是观测方程对状态向量求偏导的雅可比矩阵。对13维状态,F是13×13矩阵,H取决于观测量的维度。
F矩阵的分块结构:
其中各分块的来源是:
- F_rv:轨道位置对速度的偏导(单位矩阵)以及J2摄动位置项对位置向量求偏导
- F_rw 和 F_vw:位置和速度对角速度的偏导,理论上为0(因为轨道运动与姿态角速度无关),但注意位置r是重力梯度力矩的输入,它通过挠性等效应可能作用于角速度——这里不展开
- F_qq:四元数运动学对四元数求偏导,得到的矩阵和当前角速度有关
- F_qw:四元数运动学对角速度求偏导
- F_wq:动力学方程中重力梯度力矩对四元数求偏导
- F_ww:动力学方程中重力梯度力矩对角速度的偏导与陀螺力矩项
问题在于,手推这些偏导时很容易漏项。我常用的做法是先用有限差分法写一个数值雅可比函数做校验,再用手推解析式。具体代码在第4章中给出。
观测方程方面,工程上典型的配置是:GPS/BD提供位置(3维),星敏感器提供星光/太阳矢量(2个方向,每个2维),陀螺提供角速度增量(可选项)。这里为了让仿真脚本简洁、直观,我选用两个单位矢量作为姿态观测,位置作为轨道观测。观测量与状态的关系为:
- 位置观测:z_pos = r + v_r,观测矩阵H对应位置行是单位矩阵
- 姿态观测:星体到某个已知惯性方向(比如太阳方向)的单位矢量在本体系下的投影,计算方法是 h(q) = C(q)^T * r_inertial_dir,然后量测模型对该矢量做归一化,观测的“2维”体现在球坐标或小角度残差上。为了简化代码,后续实现中使用“虚拟星敏”输出两个方向的单位矢量测量值。
在EKF的更新里,如果直接用单位矢量做观测量,残差不是简单的矢量相减,而是需要把量测矢量与估计矢量之间的夹角作为标量残差处理。最大程度上简化但不失本质的方法是:使用两个矢量构造一个观测向量,然后把传感器误差建模在每个矢量分量的高斯噪声上;这在姿态确定的小角度情况下是工程上可接受的做法。
4. Matlab实现:从状态方程到EKF滤波循环的可运行代码
4.1 仿真参数设置与总体框架
首先建立仿真参数脚本。这一节给出一个最小可跑通的仿真框架,所有参数以国际单位制为准。
参数表如下:
| 参数 | 值 | 说明 |
|---|---|---|
| mu | 3.986004418e14 m³/s² | 地球引力常数 |
| Re | 6378137 m | 地球赤道半径 |
| J2 | 1.08262668e-3 | 地球扁率摄动系数 |
| I | diag([10, 15, 12]) kg·m² | 惯量矩阵(示例小卫星) |
| 轨道初始高度 | 500 km | 近圆轨道 |
| 初始角速度 | [0; 0; 0.0012] rad/s | 约每圈3.6° |
| 初始姿态 | 单位四元数 [1;0;0;0] | 坐标对齐 |
| 仿真时长 | 2000 s | 约3.3个轨道周期 |
主程序结构如下:
% 主仿真脚本: coupled_ekf_demo.m clear; clc; close all; % 参数初始化 params = load_params(); % 用户可调参数 params.dt_sim = 0.1; % 动力学积分步长,单位s params.dt_filt = 0.5; % EKF滤波更新步长,单位s params.H0 = 500e3; % 初始轨道高度 m params.meas_std = [1e-3; 1e-3; 1e-3]; % 位置观测噪声标准差 m % 生成标称轨道初始状态 r0 = [params.Re + params.H0; 0; 0]; v0 = [0; sqrt(params.mu / norm(r0)); 0]; q0 = [1; 0; 0; 0]; w0 = [0; 0; 0.0012]; x_true_init = [r0; v0; q0; w0]; % 声明变量 tspan = 0:params.dt_sim:params.tf; N = length(tspan); X_true = zeros(13, N); X_ekf = zeros(13, N); X_ekf(:,1) = x_true_init + [2e3; -1.5e3; 1e3; 0.3; -0.2; 0.1; 0.01; -0.02; 0.03; 0; 1e-5; -5e-6; 2e-6]; P = diag([1e6*ones(1,3), 1.2e7*ones(1,3), 0.35*ones(1,4), 1e-8*ones(1,3)]); % 主循环(简化示意,完整循环见4.4) for k = 1:N-1 % 输入真实动力学积分 % 输入EKF预测与更新 end代码注释里需要强调:P矩阵对角线给出了初始协方差的大致量级。位置和速度的不确定性较大,角速度的不确定性很小,而四元数对应的协方差设置在0.35量级意味着初始姿态误差大约是37°左右。如果你的初始捕获姿态误差本身很小,P矩阵对应部分自然可以收紧。
4.2 状态方程函数(含耦合的完整右端函数)
状态方程函数是整篇文章的核心,它同时被真实仿真和EKF预测调用。这样设计保证了“滤波器里用的模型与环境模型一致”,排除由于模型不一致造成的滤波发散。
function dx = coupled_dynamics(x, params) % 输入: x = [r(3); v(3); q(4); w(3)] % 输出: dx = [v; a_j2; dq; w_dot] % 注意四元数约定: 标量在前 q = [qw; qx; qy; qz] r = x(1:3); v = x(4:6); q = x(7:10); w = x(11:13); % 四元数归一化 q = q / norm(q); % 计算姿态矩阵 C (惯性到本体) qw = q(1); qv = q(2:4); C = 2 * (qw^2 - 0.5) * eye(3) + 2 * (qv * qv') - 2 * qw * skew(qv); % 对偶四元数右手系乘法中的 C^T (本体到惯性) % 在后续观测模型的推导中留意C与C^T的区别 % 引力加速度(二体 + J2) rn = norm(r); a_grav = -params.mu / rn^3 * r; zr5 = (params.Re^2) / (rn^5); j2_factor = -1.5 * params.J2 * params.mu * (params.Re^2) / (rn^5) * ... ... % 分量式见下式 a_j2 = j2_factor * [ ... r(1) * (1 - 5 * r(3)^2 / rn^2); ... r(2) * (1 - 5 * r(3)^2 / rn^2); ... r(3) * (3 - 5 * r(3)^2 / rn^2)]; % 重力梯度力矩(本地轨道系下地心方向投影到本体系) % 这里使用状态中的r直接计算地心方向在本体系下的投影 r_body = C * r; % 本体系下的位置矢量(视线反方向为地心方向) ur = -r_body / norm(r_body); % 从星体指向地心的单位矢量在本体系下的投影 T_gg = 3 * params.mu / rn^3 * cross(ur, params.I * ur); % 干扰力矩(可开关) T_dist = zeros(3,1); % 默认关,用于验证 if isfield(params, 'enable_dist') && params.enable_dist T_dist = [1e-7; -2e-7; 1e-7]; % 常值干扰 end % 姿态动力学 w_dot w_dot = params.I \ (-cross(w, params.I * w) + T_gg + T_dist); % 四元数运动学 dq = 0.5 * quatmultiply_s ... % 将dq、w_dot拼装 dx = [v; a_grav + a_j2; dq; w_dot]; end这段代码中的 j2_factor 写法占位,实际应采用分量公式,在源码中实现为:
factor = -3/2 * params.J2 * params.mu * (params.Re^2) / (rn^5); a_j2 = factor * [... r(1) * (1 - 5 * r(3)^2 / rn^2); ... r(2) * (1 - 5 * r(3)^2 / rn^2); ... r(3) * (3 - 5 * r(3)^2 / rn^2)];四元数运动学部分在Matlab中建议用四元数工具箱的函数,或者手写:
dq = 0.5 * omega_operator(w) * q;其中 omega_operator 构造 4×4 反对称矩阵。
逻辑说明:重力梯度力矩的输入是状态中的 r,其通过姿态矩阵C投影到本体系。这是耦合的核心通道。把 T_dist 设计为params字段,是为了后续做滤波器鲁棒性测试时能快速打开干扰源而不改动动力学函数本体。
4.3 观测模型函数:星敏感器与GPS/BD
为了让EKF更贴近工程,这里采用“位置+双矢量姿态”的观测配置:
function [z, H] = measurement_model(x, params) % 输入: 当前状态 x % 输出: 观测向量 z 与观测矩阵 H(线性化近似) r = x(1:3); q = x(7:10); q = q / norm(q); C = qua2dcm(q'); % 注意Matlab航空航天工具箱四元数约定不同,这里用自定义函数 % 双矢量:太阳方向与某参考星方向(惯性系下已知) s_inertial = params.sun_dir; % 单位矢量 z1_inertial = params.star_dir; % 单位矢量 % 观测: 本体系下的方向 s_body = C * s_inertial; z1_body = C * z1_inertial; % 组合观测向量 z = [r; s_body; z1_body]; H = zeros(9, 13); % 位置部分 H(1:3,1:3)=I H(1:3,1:3) = eye(3); % 姿态部分近似: 对姿态的偏导在小姿态误差下线性化 % 详细推导见下文 H(4:6,7:10) = skew(C * s_inertial) * dcm_to_dQ(...); H(7:9,7:10) = skew(C * z1_inertial) * dcm_to_dQ(...); end这个函数里的H矩阵构造看起来复杂,但实际工程常用的做法是不显式推导对四元数的偏导,而是把观测模型写成姿态矩阵与方向矢量的乘积形式后,利用方向矢量对误差角的偏导关系简化计算。
简化后的观测矩阵写法:
% 姿态误差角delta_theta 到方向矢量误差的映射 % 若观测方向在本体系坐标为 r_body,则 % 误差角到方向误差的线性化矩阵为 -skew(r_body) % 对四元数状态而言,需要把误差角空间映射到四元数误差空间 % 在小角度近似下 dtheta ≈ 2*dqv H(4:6, 7:9) = -skew(s_body) * 0.5; % 对应四元数矢量部分的系数 H(7:9, 7:9) = -skew(z1_body) * 0.5;注意这里取 qv = q(2:4)作为姿态误差状态,而不是完整的四元数分量。在EKF状态空间里,第7维(qw)一般不参与协方差估计,而是通过归一化约束在量测更新后恢复。这是工程实现上的一个技巧:协方差只维护3维姿态误差,状态预测时补回标量部分。
这种“误差状态EKF”和“全状态EKF”的区别,会直接决定第5章中协方差是否半正定。推荐采用误差状态实现。
4.4 EKF主循环实现
主循环把前面几节串起来,分成预测和更新两步。滤波步长为 dt_filt,而动力学积分步长是 dt_sim,两者不同步时需要用多次积分或状态转移矩阵。
% EKF主循环(节选) for k = 1:N-1 t_now = tspan(k); % 真实状态的积分(使用ode4或rk4) k1 = coupled_dynamics(X_true(:,k), params); k2 = coupled_dynamics(X_true(:,k) + 0.5*params.dt_sim*k1, params); k3 = coupled_dynamics(X_true(:,k) + 0.5*params.dt_sim*k2, params); k4 = coupled_dynamics(X_true(:,k) + params.dt_sim*k3, params); X_true(:,k+1) = X_true(:,k) + (params.dt_sim/6)*(k1+2*k2+2*k3+k4); % 真实四元数归一化防漂移 X_true(7:10,k+1) = X_true(7:10,k+1)/norm(X_true(7:10,k+1)); if mod(k*params.dt_sim, params.dt_filt) < 0.01 % 滤波器预测 [dx, Phi] = linearized_dynamics(X_ekf(:,k), params, params.dt_filt); X_pred = rk4_step(X_ekf(:,k), params, params.dt_filt); X_pred(7:10) = X_pred(7:10)/norm(X_pred(7:10)); P_pred = Phi * P * Phi' + Q; % 量测更新(略) end end由于代码篇幅限制,这里展示结果保存与绘图部分也一并给出:
% 结果绘图 figure(1); plot(tspan, X_true(1:3,:) - X_ekf(1:3,:)); grid on; title('位置误差随时间变化'); figure(2); plot(tspan, rad2deg(...角速度误差...)); grid on;代码之后的说明:为什么不用ode45做EKF内部的预测传播?因为EKF要求状态转移矩阵在积分区间内保持线性时不变,使用定步长RK4更容易与转移矩阵的离散化保持一致性。工程上如果使用ode45,建议与解析F矩阵的离散化结果做对比,保证一致性。
参数说明:滤波步长与传感器测量步长需要匹配。虚拟星敏一般10Hz输出,GPS/BD是1Hz,在这个示例中统一为2Hz,后面可以在matlab里加一个时间段开关来模拟不同测量频率。
5. Matlab实现中常见的五个坑:发散、偏差与单位混乱
5.1 协方差发散或出现NaN
现象:EKF运行几十秒后P矩阵对角元素急剧增大,滤波器输出包含NaN,仿真中止。
原因:最常见的是两个。第一,四元数在状态更新时用普通加法,导致四元数模长偏移,姿态矩阵计算出现奇异;第二,在误差状态模型下对四元数协方差初始化过大,超出小角度线性化假设范围。
解决:严格采用误差四元数乘性更新;状态预测后强制归一化四元数;P矩阵中四元数部分初始化为小值(比如0.3²),不要把初值不确定性设置到几百度量级。若真需要应付大初始误差,先跑几步快速收敛的确定性算法。
5.2 估计误差不收敛但滤波器自身不发散
现象:滤波不报错,P矩阵保持稳定,但位置误差一直徘徊在一两百米量级降不下来,姿态误差在0.1°量级波动。
原因:过程噪声协方差Q被设置得太小,滤波器过于相信动力学模型;同时观测噪声R设置偏大,量测修正力度不足。另一种可能是动力学模型和真实模型有系统差,比如没有考虑大气阻力,但过程噪声完全没吸收这部分误差。
解决:把Q适当调大,尤其是轨道部分的加速度过程噪声。同时用残差观测检查滤波是否合理:残差均值明显非零,说明系统偏差。也可以做一个保守实验——把Q调整到残差的白噪声检验通过后,再看估计精度。
5.3 解析雅可比与数值雅可比不一致
现象:同一个初始条件下,EKF用解析F矩阵和用数值F矩阵得到完全不同的结果,一个收敛另一个发散。
原因:绝大多数是因为四元数对时间的导数求偏导时,没有考虑四元数归一化约束对雅可比的影响。所谓“归一化后的雅可比”与“未归一化的雅可比”在形式上不一致,但真实系统受约束,需要把约束投影到雅可比上。
解决:在解析雅可比推导完成后,写一个数值雅可比校验函数,每次修改模型后都自动运行一次对比。一般做法是随便取一组状态,给状态各维加10⁻⁷量级的微小扰动,对比解析与数值结果,相对误差应小于10⁻⁵。不匹配就逐项排查。
5.4 滤波更新的时间基准不一致
现象:整个系统偶尔表现正常,偶尔残差突然跳变,特别是在轨道半圈后开始。
原因:姿态运动学中的角速度是相对惯性系的,但观测模型中的参考矢量(比如太阳方向)在惯性系中是缓慢变化的,如果你把它当常数处理,残差就会缓慢增长。另一个常见原因是动力学积分在dt_sim和dt_filt不同时,量测更新用到的X_pred时间基准与观测数据时刻对不上。
解决:构建一个统一的时间轴管理逻辑,观测数据的时刻存到变量obs_time;在EKF预测步完成之前,先判断观测时刻与上一滤波时刻的时间差,按实际时间差离散化F矩阵,而不是固定用dt_filt。
5.5 单位一致性导致轨道半长轴漂移
现象:位置误差曲线看起来收敛,但轨道半长轴(轨道能量)单调变化,最终偏出目标轨道。
原因:单位不统一。最常见的错误是把J2系数、Re、mu分别从不同参考书抄来,J2的量纲不是无量纲的,或者把km和m混用。这类错误在数值上不会导致NaN,但轨道平运动频率会出现微小偏差,叠加在滤波器里无法收敛。
解决:建立参数表后,写一个fixture测试:把J2置零,把T_gg置零,初始轨道设置为圆形轨道,验证轨道半径变化在一个周期内小于1e-6量级。这是最基础的正向测试,代码一跑就能发现单位错误。
6. 让仿真结果可信:验证方法、调参经验与一组残差检验技巧
前五章已经把模型、滤波算法和代码流程走了一遍,但很多人在这里会问一句:我怎么知道我的EKF是真的好,还是恰好在这组随机种子下表现好?这就要把重心放在验证上。
我的习惯是第一层验证叫“无噪声收敛测试”。把过程和量测噪声同时置零,让滤波器从一个有明显初始偏差(比如位置误差2km,角速度误差1e-3 rad/s)的状态开始估计,观察残差是否指数收敛到机器精度。如果这个测试不通过,说明动力学方程和雅可比里一定有错,不要继续调Q和R。
第二层是用蒙特卡洛:随机生成20组以上初始偏差和噪声种子,统计估计误差的均值和均方差。EKF本身是次优滤波器,均值应当无偏,均方差应当和P矩阵对角线基本吻合。如果估计误差的实际标准差比P矩阵预测的值大三倍以上,通常说明模型误差被低估,要么加Q,要么在状态里面加待估参数(比如常值干扰力矩偏置)。
第三层是残差检验。这一步比看误差曲线更能暴露问题。记录新息(innovation)序列:
其中 z_k 为实际观测,h(x_pred) 为预测观测。对于设计良好的EKF,新息应该近似为零均值白噪声,协方差为 S = H P_pred H^T + R。我经常在仿真里直接打印新息的自相关函数,如果残差在零滞后处有峰、其余滞后处都落在置信区间内,说明滤波器的信息提取是干净的。如果残差有明显的周期振荡,往往意味着动力学模型漏了周期项(比如J4、太阳光压)。
下面这个代码片段可以直接放到你的仿真里做新息白噪声检验:
% 新息自相关检验 innov = zeros(3, N_filter); S_expected = zeros(3,3,N_filter); % 滤波循环中存储innov与S % 计算滞后k的自相关 for lag = 0:20 idx = 1:N_filter-lag; rho = mean(innov(:,idx) .* innov(:,idx+lag), 2); rho = rho ./ mean(innov(:,idx).^2, 2); bar(lag, rho(1)); hold on; end % 95%置信限约正负1.96/sqrt(N_filter)我自己的一个教训是:在一轮仿真里残差白噪声检验表现很好,但位置误差标准差略小于P矩阵预期值。后来发现是因为虚拟星敏和位置观测使用了同一组随机噪声种子,人为去掉了噪声的独立性。这是个很小的细节,但会让你的验证结果对种子敏感。所以注意,所有观测量噪声生成时要用不同的随机流,Matlab里就给每个传感器分配独立的randstream对象。另外调Q和R时记住一组原则:R的大小要贴近传感器手册的真实量测噪声,不要去“试”出一个好看的R;Q才是你可以用来吸收建模误差的旋钮。如果Q过小,残差相关;Q过大,位置误差增大但残差看不出问题。用上述白噪声检验把Q调到临界值,这个“临界”在工程上就是残差不再随时间强相关的点。
希望我的这些经验和代码框架能帮到你,哪怕只是让你少走一次仿真结果莫名发散的弯路。
本文还有配套的精品资源,点击获取