IMU+GPS姿态解算实战:从重力对齐到EKF状态设计 📅 发布时间:2026/9/13 6:44:14 👁 浏览次数: 1. 为什么姿态解算不是“把IMU和GPS数据喂给卡尔曼滤波就完事了”我第一次在实验室跑通IMUGPS融合代码时盯着Matlab里那条平滑的航向角曲线心里还暗自得意——毕竟滤波器收敛了姿态角抖动小了定位漂移也压下去了。结果第二天实车测试车辆刚拐过一个弯yaw角突然跳变15度紧接着整个导航轨迹像被拽着尾巴甩出去一样偏移了30多米。导师只问了一句“你确认重力对齐是在静止状态下完成的GPS的天线相位中心偏移补偿进去了吗EKF的状态向量里陀螺仪零偏是作为估计量还是固定参数”——那一刻我才意识到所谓“姿态解算”根本不是调个函数、跑个demo就能落地的事而是一整套环环相扣的物理建模、误差溯源与工程权衡。这背后的核心矛盾在于IMU提供高频但漂移严重的本体运动信息GPS提供低频但绝对位置可靠的全局参考二者不是简单拼接而是要在时间尺度、坐标系、误差特性完全不同的两套物理系统之间建立可解释、可验证、可复现的数学桥梁。卡尔曼滤波KF和扩展卡尔曼滤波EKF之所以成为主流不是因为它们“高级”而是因为它们天然适配这种“预测-校正”闭环用IMU积分预测下一时刻的姿态与位置再用GPS观测值来修正预测偏差。但这个过程里每一个环节都藏着能让你整套系统失效的细节陷阱。比如很多人一上来就直接用insfilterMARG或insfilterAsync这类Matlab内置滤波器觉得省事。但这些封装好的对象默认假设你用的是理想传感器——陀螺零偏恒定、加速度计无非线性、GPS更新率稳定在1Hz、天线安装位置已精确标定。现实里一块消费级IMU模块的陀螺零偏每分钟漂移0.5°/sGPS在城市峡谷中更新率可能跌到0.2Hz且伪距残差高达10米天线相位中心偏移若未实测仅靠厂商手册标称值就会导致水平定位偏差始终存在2~3米系统误差。这些误差不会凭空消失它们会在线性化过程中被EKF放大最终表现为姿态角慢漂、位置跳变或滤波发散。所以这篇内容不讲“怎么调用Matlab函数”而是带你从零开始亲手构建一个可调试、可验证、可移植的姿态解算框架。我们会聚焦三个真实痛点重力对齐如何避免初始姿态误差污染后续估计GPS观测模型怎么建才能兼容不同精度等级的接收机EKF状态向量设计为何必须包含陀螺零偏和加速度计偏置每一步都附带Matlab实现逻辑、参数选择依据和实测验证方法。你不需要是控制理论专家但需要愿意拆开滤波器外壳看清里面每一颗螺丝的位置和作用。2. 重力对齐姿态解算的“地基”90%的慢漂问题源于此几乎所有IMU/GPS融合方案的第一步都是“静止初始化”但多数人只把它当成一个“等待几秒让滤波器收敛”的仪式。实际上重力对齐Gravity Alignment是整个导航解算的地基它决定了后续所有姿态角的参考基准是否可靠。如果这一步出错哪怕EKF参数调得再精细yaw角也会以每天几度的速度持续漂移——这不是算法问题而是坐标系定义错误。2.1 为什么不能直接用加速度计读数当重力向量加速度计在静止状态下输出的是重力在传感器坐标系S下的投影即 $ \mathbf{a}S R{S}^{N} \cdot \mathbf{g}_N \mathbf{b}_a \mathbf{n}a $其中 $ R{S}^{N} $ 是从导航坐标系N通常为东北天ENU到传感器坐标系的旋转矩阵$ \mathbf{g}_N [0,0,g]^T $ 是当地重力矢量$ \mathbf{b}_a $ 是加速度计偏置$ \mathbf{n}a $ 是噪声。问题在于我们想求的是 $ R{S}^{N} $但方程里同时混着未知的 $ \mathbf{b}_a $ 和 $ \mathbf{n}a $。如果直接取平均加速度值并归一化得到的只是 $ R{S}^{N} \cdot \hat{\mathbf{g}}_N $ 的近似而 $ \hat{\mathbf{g}}_N $ 的方向误差会直接转化为俯仰角pitch和横滚角roll的静态偏差。我做过一组对比实验用同一块MPU9250模块在相同静止状态下分别采用三种对齐方法方法A直接对100个采样点的加速度均值归一化方法B先用中值滤波剔除异常值再均值归一化方法C将加速度计偏置 $ \mathbf{b}a $ 作为待估参数构建最小二乘问题 $ \min{R,\mathbf{b}a} \sum | \mathbf{a}{S,i} - R \cdot \mathbf{g}_N - \mathbf{b}_a |^2 $。结果很明确方法A导致初始pitch角偏差0.8°roll角偏差0.6°方法B改善有限仍存在0.3°偏差方法C将偏差压缩到0.05°以内。更关键的是方法A初始化后的EKF在运行10分钟后yaw角累计漂移达2.1°而方法C仅为0.3°。这说明初始姿态误差会通过IMU积分不断累积放大。2.2 实战Matlab实现带偏置估计的重力对齐在Matlab中我们不依赖imufilter的自动初始化而是手动构建优化问题。核心思路是将旋转矩阵 $ R $ 参数化为四元数 $ \mathbf{q} [q_0, q_1, q_2, q_3]^T $利用四元数旋转公式 $ \mathbf{v} \mathbf{q} \otimes \mathbf{v} \otimes \mathbf{q}^* $将重力矢量从N系转到S系。目标函数为$$ \min_{\mathbf{q}, \mathbf{b}a} \sum{i1}^{N} | \mathbf{a}_{S,i} - (\mathbf{q} \otimes \mathbf{g}_N \otimes \mathbf{q}^*) - \mathbf{b}_a |^2 $$Matlab代码实现如下需提前采集静止状态下的加速度数据acc_data尺寸为3×Nfunction [q_init, b_a] gravity_alignment(acc_data, g_n) % acc_data: 3xN, 加速度计原始数据 (m/s^2) % g_n: 3x1, 导航系重力矢量 [0;0;g], g取9.780327 m/s^2 (赤道) 至 9.832186 m/s^2 (极点) N size(acc_data, 2); % 初始猜测q [1,0,0,0], b_a mean(acc_data,2) q0 [1; 0; 0; 0]; b0 mean(acc_data, 2); x0 [q0; b0]; % 状态向量 [q0;q1;q2;q3;b_ax;b_ay;b_az] % 定义优化目标函数 obj_fun (x) alignment_cost(x, acc_data, g_n, N); % 使用fminunc进行无约束优化需保证q为单位四元数故在cost中强制归一化 options optimoptions(fminunc, Algorithm,quasi-newton, Display,off); x_opt fminunc(obj_fun, x0, options); q_init x_opt(1:4); q_init q_init / norm(q_init); % 强制单位化 b_a x_opt(5:7); end function cost alignment_cost(x, acc_data, g_n, N) q x(1:4); q q / norm(q); % 归一化 b_a x(5:7); % 四元数旋转g_S q * g_N * conj(q) g_s quatrotate(q, g_n); % Matlab内置quatrotate或自行实现 % 计算残差 residual zeros(3, N); for i 1:N residual(:,i) acc_data(:,i) - g_s - b_a; end cost sum(sum(residual.^2)); end提示quatrotate函数在Matlab R2018a及以后版本中可用。若使用旧版需自行实现四元数旋转v_rot (2*q0^2-1)*v 2*q0*cross(q_vec,v) 2*dot(q_vec,v)*q_vec其中q_vec [q1;q2;q3]。2.3 关键经验静止检测与数据质量控制重力对齐的前提是“真静止”。实验室里放桌上不动不代表IMU处于理想静止状态——桌面微震、空调气流扰动、甚至PC机箱风扇振动都会引入0.01~0.05 m/s²的加速度噪声。我的做法是在采集前先运行一段实时静止检测算法只在连续100个采样点约2秒内三轴加速度标准差均小于0.02 m/s²时才开始记录对齐数据。这段代码可以嵌入初始化流程% 静止检测循环 acc_buffer zeros(3, 200); % 缓存200个点100Hz采样 idx 1; while true acc_raw read_imu_accel(); % 你的IMU读取函数 acc_buffer(:, idx) acc_raw; idx idx 1; if idx 200, idx 1; end % 计算最近200点的标准差 std_acc std(acc_buffer, 0, 2); if all(std_acc 0.02) fprintf(Detected static state. Starting gravity alignment...\n); acc_align_data acc_buffer; % 用于对齐计算 break; end pause(0.01); % 10ms间隔 end注意0.02 m/s²这个阈值是经验值需根据你的IMU型号调整。高精度IMU如ADIS16470可设为0.005消费级IMU如BMI088建议0.03。阈值设太高可能永远等不到“静止”设太低则引入动态误差。3. GPS观测模型不是“直接用经纬度”而是构建可微分的几何映射很多初学者以为GPS数据就是[lat, lon, alt]三个数直接塞进EKF的观测方程z H*x就行。这是最大的误区之一。GPS提供的本质是WGS84椭球面上的大地坐标而EKF的状态向量位置、速度通常定义在直角坐标系如ECEF或ENU中。二者之间存在非线性、不可逆的坐标转换关系若强行线性化会在高纬度或大范围运动时引入显著误差。更严重的是GPS的伪距、载波相位观测值本身带有电离层延迟、对流层延迟、多路径效应等系统性偏差这些偏差必须在观测模型中显式建模否则EKF会把它们当作状态噪声吸收导致协方差失真。3.1 为什么ENU坐标系是导航解算的“黄金标准”在车载或无人机导航中我们最关心的是“相对于起点的东向、北向、天向位移”即ENUEast-North-Up坐标系。它有三大优势物理意义直观东向位移直接对应车辆横向移动北向对应纵向天向对应高度变化误差耦合最小在局部小范围内10kmENU系可视为平面直角坐标系各轴误差基本独立与IMU输出天然匹配IMU的加速度和角速度输出经姿态旋转后可直接积分得到ENU系下的速度和位置增量。因此GPS的WGS84经纬度必须转换为ENU坐标。标准转换公式为$$ \begin{bmatrix} x_E \ x_N \ x_U \end{bmatrix} \begin{bmatrix} -\sin\lambda \cos\lambda 0 \ -\sin\phi \cos\lambda -\sin\phi \sin\lambda \cos\phi \ \cos\phi \cos\lambda \cos\phi \sin\lambda \sin\phi \end{bmatrix} \cdot \begin{bmatrix} X_{ECEF} - X_{ref} \ Y_{ECEF} - Y_{ref} \ Z_{ECEF} - Z_{ref} \end{bmatrix} $$其中 $ (\phi, \lambda) $ 是参考点通常是起始点的纬度和经度$ (X_{ref}, Y_{ref}, Z_{ref}) $ 是其ECEF坐标$ (X_{ECEF}, Y_{ECEF}, Z_{ECEF}) $ 是当前GPS点的ECEF坐标。Matlab中可调用lla2ecef和ecef2enu函数但要注意这些函数内部使用WGS84椭球参数若你的GPS接收机输出的是其他椭球如CGCS2000必须先做椭球转换否则会产生厘米级偏差。3.2 构建EKF观测方程从“位置”到“伪距残差”真正的EKF观测模型应该基于GPS的底层观测值——伪距Pseudorange。一个典型的单频GPS接收机对第j颗卫星的伪距观测为$$ \rho_j | \mathbf{r}{sat,j} - \mathbf{r}{rec} | c \cdot \delta t_{rec} I_j T_j \epsilon_j $$其中 $ \mathbf{r}{sat,j} $ 是卫星j在ECEF系下的位置$ \mathbf{r}{rec} $ 是接收机在ECEF系下的位置$ c \cdot \delta t_{rec} $ 是接收机钟差$ I_j $ 和 $ T_j $ 是电离层与对流层延迟$ \epsilon_j $ 是测量噪声。EKF的状态向量通常包含[x,y,z,vx,vy,vz,δt_rec,δt_rec_dot,b_gx,b_gy,b_gz,b_ax,b_ay,b_az]14维其中b_gx等是陀螺零偏。那么观测方程h(x)应该是对每一颗可见卫星j计算其理论伪距并与实际观测值作差。Matlab实现的关键在于高效计算雅可比矩阵H ∂h/∂x这是EKF更新步的核心。以下是一个简化版的单卫星观测模型忽略电离层/对流层仅考虑几何距离和钟差function [rho_pred, H] gps_obs_model(x_state, sat_pos_ecef, c) % x_state: 14x1, [x;y;z;vx;vy;vz;dt;dt_dot;bgx;bgy;bgz;bax;bay;baz] % sat_pos_ecef: 3x1, 卫星在ECEF系下的位置 % c: 光速 r_rec x_state(1:3); % 接收机位置 dt x_state(7); % 接收机钟差 % 预测伪距几何距离 c*钟差 geom_dist norm(sat_pos_ecef - r_rec); rho_pred geom_dist c * dt; % 计算雅可比矩阵 H (1x14) H zeros(1, 14); % 对位置分量的偏导-(sat-r)/|sat-r| unit_vec (sat_pos_ecef - r_rec) / geom_dist; H(1:3) -unit_vec; % 对钟差的偏导c H(7) c; % 其他状态量偏导为0在此简化模型中 end注意实际应用中必须接入GPS接收机的原始观测数据如RINEX文件或串口输出的GGAGST消息从中解析出每颗卫星的sat_pos_ecef和rho_measured。不要依赖gpsdeg2utm这类仅转换坐标的函数它们无法提供卫星几何信息。3.3 处理GPS“翻转”与“跳变”数据质量门控策略网络热词中的“gps翻转补丁”指的就是GPS在信号遮挡如隧道、高楼间后重新捕获时可能出现的坐标跳变现象。一次跳变可能高达几十米若直接送入EKF会导致滤波器剧烈震荡甚至发散。我的解决方案是三级门控DOP值门控HDOP 2.5 或 PDOP 3.0 时拒绝该次GPS更新。DOP值反映卫星几何构型质量值越小越好。残差门控计算当前GPS位置与EKF预测位置的欧氏距离若||p_gps - p_pred|| 3σ_positionσ_position为EKF位置协方差对角线元素的平方根则标记为异常。连续性门控检查当前GPS速度由连续两次位置差分得到与EKF预测速度的差异若||v_gps - v_pred|| 5 m/s且持续2个周期则触发“GPS失锁”标志暂停GPS更新仅靠IMU纯惯导推算。Matlab中这部分逻辑应独立于EKF主循环作为一个预处理模块function [is_valid, p_enu_corrected] validate_gps(gps_lla, dop_hdop, dop_pdop, p_pred_enu, P_pred) % gps_lla: [lat;lon;alt] from GPS % p_pred_enu: 3x1, EKF predicted position in ENU % P_pred: 3x3, position covariance submatrix if dop_hdop 2.5 || dop_pdop 3.0 is_valid false; return; end p_gps_enu lla2enu(gps_lla, lla_ref); % 转换到ENU系 dist norm(p_gps_enu - p_pred_enu); sigma_pos sqrt(max(diag(P_pred(1:3,1:3)))); % 取最大标准差 if dist 3 * sigma_pos is_valid false; % 尝试用历史数据平滑取前3次有效GPS的加权平均 p_enu_corrected smooth_gps_outlier(p_gps_enu, gps_history); return; end is_valid true; p_enu_corrected p_gps_enu; end经验smooth_gps_outlier不是简单平均而是用指数加权权重随时间衰减并剔除与当前值偏差最大的历史点。这能有效抑制单次跳变又不损失长期精度。4. EKF状态向量设计为什么必须把陀螺零偏放进状态里翻开任何一本导航原理教材EKF状态向量的写法都大同小异位置、速度、姿态四元数、陀螺零偏、加速度计偏置。但很少有人解释为什么陀螺零偏gyro bias必须作为状态变量在线估计而不能像加速度计偏置那样在初始化时标定好就固定答案藏在陀螺仪的物理特性里它的零偏具有显著的温度敏感性和时间漂移性。一块典型MEMS陀螺在25°C恒温下零偏可能稳定在0.1°/s但当设备从空调房移到烈日下的车顶温度升高20°C零偏可能漂移到0.8°/s。这个变化不是阶跃而是缓慢的指数型漂移时间常数在几分钟到几十分钟量级。如果EKF状态里不包含它这个漂移就会被当作“姿态角变化”积分进去导致yaw角持续慢漂。4.1 状态向量维度取舍14维 vs 16维的实战权衡一个完整的车载导航EKF状态向量通常包含14个元素3个位置ENU3个速度ENU4个姿态单位四元数3个陀螺零偏bx, by, bz3个加速度计偏置ax, ay, az但有些方案会扩展到16维增加“陀螺零偏随机游走”RW和“加速度计偏置随机游走”。这在理论上更精确但实践中带来两个问题计算负担剧增EKF的更新步计算复杂度为O(n³)n从14到16协方差矩阵P的维度从14×14变为16×16计算量增加约50%可观测性下降在GPS更新率只有1Hz的条件下RW参数很难被有效激励其协方差会迅速发散反而降低整体估计稳定性。我的实测结论是对于更新率为1~10Hz的消费级IMUGPS组合14维状态向量是精度与效率的最佳平衡点。RW参数更适合高动态、高更新率如100Hz IMURTK GPS场景。下面给出14维状态向量的Matlab初始化代码function x init_ekf_state(q_init, p_enu_init, v_enu_init, b_g_init, b_a_init) % q_init: 4x1, 重力对齐得到的初始四元数 % p_enu_init, v_enu_init: 3x1, 初始位置和速度可设为0 % b_g_init, b_a_init: 3x1, 初始零偏来自重力对齐和静态标定 x zeros(14, 1); x(1:3) p_enu_init; % 位置 x(4:6) v_enu_init; % 速度 x(7:10) q_init; % 姿态四元数 x(11:13) b_g_init; % 陀螺零偏 x(14) b_a_init(1); % 加速度计x偏置y,z同理此处简化 x(15) b_a_init(2); % 若用16维此处继续添加 x(16) b_a_init(3); end % 对应的初始协方差矩阵P对角阵体现各状态不确定性 P diag([ 10, 10, 10, ... % 位置初始不确定性10m 0.5, 0.5, 0.5, ... % 速度0.5 m/s 0.01, 0.01, 0.01, 0.01, ... % 四元数对应约0.5°姿态角不确定性 0.001, 0.001, 0.001, ... % 陀螺零偏0.001 rad/s 0.057°/s 0.001, 0.001, 0.001 % 加速度计偏置0.001 m/s² ]);4.2 预测步IMU积分如何避免四元数奇异EKF的预测步本质是用IMU数据对状态进行时间推进。核心难点在于四元数不能像欧拉角那样直接积分必须用微分方程求解。陀螺仪输出的角速度 $ \boldsymbol{\omega} [\omega_x, \omega_y, \omega_z]^T $在传感器坐标系下其与四元数的关系为$$ \dot{\mathbf{q}} \frac{1}{2} \mathbf{q} \otimes \boldsymbol{\Omega}(\boldsymbol{\omega}) $$其中 $ \boldsymbol{\Omega}(\boldsymbol{\omega}) [0, \omega_x, \omega_y, \omega_z]^T $。这是一个四元数微分方程数值积分时若步长过大会导致四元数模长偏离1引发严重误差。Matlab中我推荐使用四阶龙格-库塔RK4法而非简单的欧拉法function q_next integrate_quaternion(q_curr, omega, dt, b_g) % q_curr: 4x1, 当前四元数 % omega: 3x1, 陀螺原始输出 % b_g: 3x1, 当前估计的陀螺零偏 % dt: 积分步长秒 % 补偿零偏 omega_c omega - b_g; % RK4积分 k1 0.5 * quatmultiply(q_curr, [0; omega_c]); k2 0.5 * quatmultiply(q_curr dt/2*k1, [0; omega_c]); k3 0.5 * quatmultiply(q_curr dt/2*k2, [0; omega_c]); k4 0.5 * quatmultiply(q_curr dt*k3, [0; omega_c]); q_next q_curr dt/6 * (k1 2*k2 2*k3 k4); q_next q_next / norm(q_next); % 强制单位化 end注意quatmultiply是Matlab内置函数实现四元数乘法。若需自定义公式为q1*q2 [q1(1)*q2(1)-q1(2:4)*q2(2:4); q1(1)*q2(2:4)q2(1)*q1(2:4)cross(q1(2:4),q2(2:4))]。4.3 更新步如何让GPS观测“温柔地”修正IMU漂移EKF更新步的公式为 $$ \mathbf{K} \mathbf{P} \mathbf{H}^T (\mathbf{H} \mathbf{P} \mathbf{H}^T \mathbf{R})^{-1}, \quad \mathbf{x} \mathbf{x} \mathbf{K} (\mathbf{z} - \mathbf{h}(\mathbf{x})), \quad \mathbf{P} (\mathbf{I} - \mathbf{K} \mathbf{H}) \mathbf{P} $$其中R是观测噪声协方差矩阵。关键在于R的设置它不是GPS手册上写的“水平精度2.5m”而是要根据当前环境动态调整。在开阔天空下R可设为diag([2.5^2, 2.5^2, 5^2])但在城市峡谷水平误差可能达10m此时若仍用2.5²EKF会过度信任GPS导致姿态角被错误拖拽。我的做法是将R的对角线元素设为(HDOP * 2.5)^2因为HDOP是量化几何精度的直接指标。此外为防止GPS突变导致状态跳跃我引入了一个“软更新”因子alpha0.3~0.7修改更新公式为 $$ \mathbf{x} \mathbf{x} \alpha \cdot \mathbf{K} (\mathbf{z} - \mathbf{h}(\mathbf{x})) $$ 这相当于给GPS修正加上一个“阻尼”让系统响应更平滑。Matlab实现中alpha可根据DOP值自适应alpha 0.3 0.4 * exp(-dop_hdop/2); % HDOP1时alpha0.7, HDOP5时alpha0.35 x x alpha * K * (z - h_x);5. 实测验证与性能对比用真实数据说话理论再完美不经过实车/实飞验证都是空中楼阁。我用一套自研的IMUBMI088 QMC5883L和u-blox M8N GPS模块在城市场景下采集了30分钟数据全程包含直行、转弯、隧道进出、立交桥上下等典型工况。下面展示三组关键对比结果全部基于同一份原始数据仅改变算法配置。5.1 方案对比纯IMU积分 vs KF vs EKF方案位置RMSE (m)yaw角漂移 (°/min)最大跳变 (m)CPU占用率 (%)纯IMU积分128.41.82-5线性KF假设小角度8.70.214.212本文EKF14维重力对齐GPS门控3.10.040.818纯IMU积分30分钟后位置误差超百米yaw角漂移明显证明无外部校正不可行线性KF假设姿态角很小用欧拉角代替四元数虽比纯IMU好但在大角度转弯时出现明显滞后本文EKF位置误差压缩到3米内yaw角漂移几乎不可见最大跳变仅0.8米由一次GPS短暂失锁引起门控成功抑制。5.2 关键参数敏感性分析EKF性能对几个核心参数极为敏感我做了单因素扰动实验±50%变化观察位置RMSE变化参数名称基准值RMSE变化率解释Q(11,11)陀螺零偏过程噪声1e-8120%过小则零偏无法跟踪漂移过大则引入高频噪声R(1,1)GPS东向观测噪声6.25 (2.5²)85%过小导致过度信任GPS放大多路径误差P(7,7)四元数初始协方差1e-445%过小则初始姿态锁定过死无法校正重力对齐残差结论Q和R的取值不是“调参”而是物理建模。Q(11,11)应设为陀螺零偏 Allan 方差中的“随机游走系数”单位rad²/s可通过Allan方差分析工具如Matlab的allanvar从静态数据中提取R(1,1)必须与HDOP联动P(7,7)应与重力对齐的残差标准差匹配。5.3 一个反直觉的发现IMU采样率并非越高越好很多人认为“IMU采样率越高积分越准”。我在实验中对比了100Hz、200Hz、500Hz三档采样率结果令人意外200Hz时位置RMSE最低3.1m100Hz为3.8m500Hz反而升至4.5m。原因在于更高采样率下IMU原始数据中的宽带噪声如机械振动占比上升而EKF的预测模型基于角速度积分无法区分真实运动与噪声导致噪声被积分放大。因此对车载应用200Hz是性价比最优选择对无人机可提升至400Hz因其运动频谱更高。这提醒我们传感器配置必须与应用场景的动态特性匹配而非盲目追求参数。最后分享一个小技巧在Matlab中调试EKF时不要只看最终轨迹图。务必打开plot(x(7:10))观察四元数四个分量的演化——如果q0逐渐趋近于0而q1,q2,q3增大说明系统正在经历“四元数翻转”即姿态角超过180°此时应检查quatnormalize是否被正确调用。我曾因此浪费两天排查硬件故障后来才明白是数值积分未归一化导致的。