ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

全驱动船舶轨迹跟踪:动态面滑模与扰动观测器MATLAB仿真

全驱动船舶轨迹跟踪:动态面滑模与扰动观测器MATLAB仿真

简介:面向无人船、水下机器人运动控制方向的学习者与研究者,这份 MATLAB 仿真资料聚焦全驱动船舶的轨迹跟踪难题,提供带扰动观测器的自适应动态面滑模控制实现,适合具备非线性控制基础、希望复现并吃透 DSC 与滑模结合思路的读者。压缩包共二十个文件、约五十六 KB,以十五份 m 脚本为主体,辅以两份 txt 说明、两个 mdl 模型和一个 r2012a 环境文件,覆盖算法实现、模型搭建与运行环境等环节。已有 1313 人学习下载,说明该主题在船舶运动控制与轨迹跟踪研究中具有一定关注度。内容涉及全驱动船舶数学模型、控制器设计、扰动观测器构建及仿真验证,并包含圆轨迹与直线轨迹两类仿真程序,便于读者对照理解扰动估计与补偿如何作用于跟踪性能,也可作为课程设计、论文复现或算法对比的基础材料。

1. 全驱动船舶轨迹跟踪:动态面滑模加扰动观测器到底解决什么问题

全驱动船舶的轨迹跟踪,难点从来不在推导公式,而在把动态面滑模控制律和扰动观测器塞进同一个 MATLAB 仿真回路里还能跑稳。三自由度全驱动意味着纵荡、横荡、艏摇三个方向都有执行力,听起来比欠驱动友好,实际调起来更容易翻车:位置跟得很漂亮,控制力却在 ±2000 N 上下高频抖,扰动幅值一改大就发散。纯反步法要对虚拟控制做解析微分,噪声一放大就没法用;滑模项能抑制建模误差,但符号函数会制造抖振;扰动观测器能把风浪流这类未知外力在线估出来前馈掉,可它又依赖你喂进去的输入是不是“真正施加的那个”。这套仿真工程就是把这三件事串成一条闭环链路,适合正在做船舶运动控制课程设计、论文复现或工程预研的开发者。

2. 工程骨架:一个 ode45 闭环里塞进船体、控制器与观测器

2.1 目录与文件职责:九个 .m 文件的分工

压缩包解开后是一个平铺的 MATLAB 工程,没有依赖任何工具箱,纯基础语法加 ode45,这一点对复现很友好。常见的组织方式是把“参数、被控对象、控制器、观测器、参考轨迹、绘图”拆成独立函数,主脚本只负责装配和调用。这样二次开发时换船型只动一个文件,换轨迹只动另一个,不至于在几百行的大脚本里翻找。

文件职责二次开发改动频率
sim_main.m主脚本,装配参数、调用 ode45、出图低
ship_params.m惯性矩阵 M、阻尼 D、科氏矩阵 C 的标称值高(换船型必改)
ctrl_params.m控制器与观测器增益集中管理高(整定时反复改)
ref_trajectory.m参考轨迹 ηd 及其一阶、二阶导中
disturbance.m环境扰动模型中
ship_dyn.m闭环状态导数,ode45 的被积函数低
ndo.m非线性扰动观测器低
ssa.m角度归一化到 [-π, π]几乎不改
plot_result.m结果绘图与误差指标低

这里有个容易被忽略的工程设计点:控制器和观测器本身也是动态系统(滤波器状态、观测器内部状态 z、自适应参数 θ̂),它们必须和船体状态一起被积分。把它们写成persistent变量或者放到全局变量里,ode45 在自适应步长下反复试探步会直接让这些状态失去意义,跑出来的曲线看着像对的,实际根本没法复现。

2.2 三自由度模型:M、C、D 三个矩阵的写法与量级

被控对象是三自由度水平面模型,状态取 η = [x, y, ψ]ᵀ 和 ν = [u, v, r]ᵀ,运动学用旋转矩阵 R(ψ) 连接:

η̇ = R(ψ)·ν,M·ν̇ + C(ν)·ν + D(ν)·ν = τ + d(t)

标称参数按一条两米级缩比船模给的,惯性矩阵取对角形式,阻尼用线性项。真实船舶的 M 会有 m₂₃ 这类耦合项,但入门复现阶段先对角化,能把问题聚焦在控制律本身。

function par = ship_params() % 1:70 缩比船模的标称水动力系数,换船型时整块替换 par.m11 = 25.8; par.m22 = 33.8; par.m33 = 2.76; % 附加质量+刚体质量 par.d11 = 12.0; par.d22 = 17.0; par.d33 = 0.5; % 线性阻尼 par.M = diag([par.m11, par.m22, par.m33]); par.D = diag([par.d11, par.d22, par.d33]); end function C = coriolis(nu, par) % 三自由度科氏-向心矩阵,必须反对称,符号写反会直接发散 u = nu(1); v = nu(2); C = [0, 0, -par.m22*v; 0, 0, par.m11*u; par.m22*v, -par.m11*u, 0]; end

M 和 D 都取正定对角阵,是后面做李雅普诺夫分析时能把交叉项配平的前提;C 的反对称性保证 νᵀCν = 0,这一条如果被写错(比如把 m₂₂v 的符号统一成负),能量会凭空注入,仿真跑十几秒后速度就冲到几百,现象很像“控制器发散”,其实是被控对象建模错了。参数的量级也要留意:m₃₃ 只有 2.76,比 m₁₁ 小一个数量级,意味着同样的力矩会产生大得多的角加速度,艏摇通道的增益必须单独整定,不能和纵荡共用一套。

旋转矩阵的写法同样要对齐约定。本文这套代码采用“船体坐标到大地坐标”的常规形式:

function R = rotm(psi) % 船体坐标 -> 大地坐标 R = [cos(psi), -sin(psi), 0; sin(psi), cos(psi), 0; 0, 0, 1]; end

写成这个形式后,虚拟控制量的表达式里用 Rᵀ,控制器里做坐标反变换用 R。R 是正交矩阵,R⁻¹ = Rᵀ,这一点在推导里省了很多事,但也最容易抄错——把 R 和 Rᵀ 弄反的典型现象是船一开始就往参考轨迹的反方向跑,然后被增益硬拽回来,画出来的曲线像“绕了一圈才追上”。

2.3 闭环积分:为什么把控制器状态一起塞进 ode45

主脚本的逻辑很朴素:定义初值、定义时间向量、调用 ode45、出图。关键在于状态向量的拼装。把船体六维状态和控制器内部状态拼成一个 13 维向量:

xa = [x, y, ψ, u, v, r, α₂(1:3), z(1:3), θ̂]

其中 α₂ 是动态面滤波器输出的“滤波后虚拟速度”,z 是观测器内部状态,θ̂ 是自适应参数。这样 ode45 每一步都能拿到前后一致的控制器状态,不会出现“控制器用的 α₂ 和积分器认为的 α₂ 不是同一个”的错位。

%% sim_main.m 主仿真脚本 clear; clc; close all; par = ship_params(); % 船体参数 ctrl = ctrl_params(); % 增益 ref = @ref_trajectory; % 参考轨迹函数句柄 % 初始状态:位置、艏向、速度 eta0 = [0; 0; 0]; nu0 = [0; 0; 0]; % 控制器内部状态初值,必须和 t=0 时刻的控制律自洽 e1_0 = eta0 - ref(0); alpha1_0 = rotm(eta0(3))' * (ref_dot(0) - ctrl.K1*e1_0); alpha2_0 = alpha1_0; % 滤波器初值取 alpha1(0),否则第一步就有跳变 z0 = zeros(3,1) - ctrl.Ko*(par.M*nu0); % 保证 d_hat(0)=0 theta0 = 0; xa0 = [eta0; nu0; alpha2_0; z0; theta0]; opts = odeset('RelTol',1e-6,'AbsTol',1e-8,'MaxStep',0.01); [t, xa] = ode45(@(t,x) ship_dyn(t,x,par,ctrl,ref), [0 120], xa0, opts); plot_result(t, xa, par, ctrl, ref);

MaxStep设成 0.01 是个经验值。滑模控制律里带 tanh(s/ε),边界层 ε 取 0.02 时,s 在零点附近的变化尺度就是 0.02 量级,如果积分步长跨过这个尺度,ode45 会不停地把步长砍到极小,仿真时间成倍增长,甚至报“无法满足积分容差”。给一个上限,等于告诉求解器“这块区域我自己负责精度”。

初值自洽这条比看起来重要。α₂(0) 如果直接填 0 而 α₁(0) 不是 0,滤波器一上电就有一段强制收敛过程,这段过程会被滑模面当成“模型误差”去补偿,控制力在头 0.2 秒出现一个尖峰,很多人误以为是抖振,其实是初值没对齐。观测器的 z₀ 同理,取 z₀ = -Ko·M·ν₀ 才能保证 d̂(0) = z₀ + Ko·M·ν₀ = 0,从零开始估。

3. 控制律拆解:动态面外环与自适应滑模内环怎么接上

3.1 外环虚拟速度与一阶低通滤波

外环处理的是运动学。定义位置误差 e₁ = η - ηd,对其求导得 ė₁ = R·ν - η̇d。如果把 ν 当成这一层的“虚拟控制”,理想解是令 R·ν = η̇d - K₁e₁,即

α₁ = Rᵀ(η̇d - K₁e₁)

但 ν 不是真正的控制输入,它由动力学方程决定,所以下一层要跟踪 α₁。经典反步法在这里对 α₁ 求解析导数,α₁ 里含 Rᵀ 和 η̇d,展开后项数爆炸,再叠上测量噪声,微分结果根本不能用。动态面的做法是把 α₁ 送进一个一阶低通滤波器,用滤波器的输出 α₂ 代替 α₁,同时用 (α₁ - α₂)/τ_f 代替 α̇₂,把微分彻底绕开。

% 外环:虚拟速度 + 一阶低通滤波(动态面核心) e1 = eta - eta_d; e1(3) = ssa(e1(3)); % 艏向误差必须归一化,否则 2π 跳变 R = rotm(eta(3)); alpha1 = R' * (eta_d_dot - ctrl.K1*e1); % 期望速度 % 滤波器用一阶惯性环节:tau_f * alpha2_dot + alpha2 = alpha1 alpha2_dot = (alpha1 - alpha2) / ctrl.tau_f;

tau_f是这一层唯一新增的参数,它决定了“离反步法有多远”。取 0.01 s 时滤波器几乎透明,α₂ 紧跟 α₁,边界层的补偿能力下降,控制力开始抖;取 0.3 s 时滤波很柔和,但 α₂ 明显滞后于 α₁,跟踪误差里会出现一段稳定的稳态偏差。工程上一般从 0.05 s 起调,配合 K₁ 一起看。

ssa是必须调用的:

function a = ssa(a) % smallest signed angle,把角度折回 [-pi, pi] a = atan2(sin(a), cos(a)); end

艏向误差不做归一化,是最常见的“跑着跑着突然发散”。参考艏向从 6.2 rad 附近跨过 2π 回绕到 -0.1 rad 时,误差瞬间变成 6.3,控制器以为差了一整圈,输出一个巨大的力矩,船开始原地打转。加上 ssa 之后这类跳变消失,曲线立刻平滑。

3.2 内环滑模面、到达条件与自适应律

内环跟踪 α₂。定义速度误差 e₂ = ν - α₂,滑模面先取最简单的线性形式 s = e₂。对 s 求导:

ṡ = M⁻¹(τ + d - Cν - Dν) - α̇₂

要让 s 收敛,控制律里必须包含 M·α̇₂ 做前馈、Cν + Dν 做模型补偿、-M·K₂·s 做指数收敛项,再加上外环耦合项 -M·Rᵀe₁ 和扰动补偿项。扰动 d 未知,用观测器输出 d̂ 顶替;残余的估计误差用一个自适应参数 θ̂ 覆盖,配合 tanh 边界层代替 sign 函数抑制抖振。

% 内环:滑模面 + 自适应滑模控制律 e2 = nu - alpha2; s = e2; % 线性滑模面,可加积分项 d_hat = z + ctrl.Ko*(M*nu); % 观测器输出 tau = M*(alpha2_dot - ctrl.K2*s - R'*e1) ... % 前馈 + 指数收敛 + 反步耦合 + C*nu + D*nu ... % 模型补偿 - d_hat ... % 扰动前馈 - M*(theta_hat * tanh(s/ctrl.eps)); % 自适应鲁棒项 % 自适应律:带泄漏项,防止 theta_hat 单调漂移 theta_dot = ctrl.gamma * sum(abs(s)) - ctrl.gamma*ctrl.sigma*theta_hat;

逐项说明一下。M*(alpha2_dot - ...)里的alpha2_dot来自滤波器,不是解析微分,这是动态面能成立的关键。-ctrl.K2*s是比例到达项,K₂ 越大收敛越快,但和tau_f一起调,K₂ 过大时 s 在零点附近来回穿越,tanh 的线性区被反复触发,控制力出现 5~10 Hz 的持续振荡。-R'*e1是反步法传下来的耦合项,把外环误差反馈进内环,量级不大但不能省,省掉之后位置误差收敛到 0.3 m 左右就下不去了。

自适应项theta_hat * tanh(s/eps)里,θ̂ 的物理含义是“残余扰动的上界估计”。tanh 在 |s| >> ε 时饱和到 ±1,等价于 sign;在 |s| << ε 时线性化为 s/ε,控制量连续,抖振被压掉。ε 取太小等于退回 sign,取太大会在零点附近留下死区,稳态误差变大。0.02 是个能兼顾两者的起点。

自适应律里的泄漏项-gamma*sigma*theta_hat是为了防止 θ̂ 一直往上爬。没有它,只要 s 长期非零(比如参考轨迹有常值加速度),θ̂ 就单调增长,最后控制力被这一项主导,船反而被推歪。

3.3 增益整定顺序与一组能跑的参数

这套控制律有六七个参数,一起调必然乱。可行的顺序是:先调观测器(K_o),再调外环(K₁、τ_f),最后调内环(K₂、ε、γ、σ)。理由很直接:观测器没收敛时,内环补偿的是错的扰动量,你怎么调 K₂ 都是在跟一个乱跳的 d̂ 较劲。

参数含义起始值调整方向
K₁运动学误差增益diag(0.8, 0.8, 1.2)增大收敛快,过大位置超调
τ_f动态面滤波时间常数0.05 s过小抖振,过大跟踪滞后
K₂速度误差增益diag(0.6, 0.6, 0.8)增大收敛快,过大高频振荡
K_o观测器带宽5需快于扰动变化率,过大放大噪声
γ自适应增益0.8增大估计快,过大控制力尖峰
σ泄漏系数0.01抑制 θ̂ 漂移,过大削弱鲁棒项
εtanh 边界层厚度0.02过小抖振,过大稳态误差
function ctrl = ctrl_params() ctrl.K1 = diag([0.8, 0.8, 1.2]); ctrl.K2 = diag([0.6, 0.6, 0.8]); ctrl.tau_f = 0.05; ctrl.Ko = 5; ctrl.gamma = 0.8; ctrl.sigma = 0.01; ctrl.eps = 0.02; end

艏摇通道给得比另外两个通道大(K₁ 的第三项 1.2),原因在前面的量级分析里:m₃₃ 小,角加速度响应快,同样的误差需要相对温和的增益才不会过冲。第一次跑通后建议做一次单参数扫描:固定其他参数,把 K₂ 从 0.2 扫到 1.5,记录位置 RMSE 和控制力方差,正常情况下会看到一条“RMSE 先降后平、控制力方差单调升”的曲线,拐点附近就是可用区间。

4. 扰动观测器:把未知环境力从状态里反解出来

4.1 非线性扰动观测器的构造与收敛条件

扰动 d 出现在动力学方程里,但没有传感器直接测它。观测器的思路是:既然 M·ν̇ = τ + d - Cν - Dν,那么只要知道 ν̇ 就能反解 d;可惜 ν̇ 要微分,噪声不可用。于是引入辅助状态 z,让 d̂ = z + K_o·M·ν,并设计 z 的动态使得 d̂ 的误差满足 d̃̇ = ḋ - K_o·d̃。

推导过程值得手推一遍:对 d̂ 求导得 d̂̇ = ż + K_o·M·ν̇,把 ν̇ 用动力学方程代入,再令 ż 等于 -K_o·z - K_o·(-Cν - Dν + τ + K_o·M·ν),所有含 ν̇ 的项刚好消掉,剩下的就是 d̂̇ = K_o(d - d̂)。整个构造里没有出现 ν 的导数,这是它能落地的根本原因。

function [d_hat, z_dot] = ndo(nu, tau, par, ctrl, z) % 非线性扰动观测器 % 输入:nu 速度、tau 实际施加的控制力、z 内部状态 M = par.M; C = coriolis(nu, par); D = par.D; d_hat = z + ctrl.Ko*(M*nu); % 注意括号:f = -C*nu - D*nu 是已知的模型项 z_dot = -ctrl.Ko*z - ctrl.Ko*( -C*nu - D*nu + tau + ctrl.Ko*(M*nu) ); end

K_o是观测器带宽,直接决定估计误差的收敛速度:d̃ 按 e^(-K_o·t) 衰减。理论上 K_o 越大越好,实际上它同时放大模型误差和测量噪声——K_o 从 5 提到 20,观测器能在 0.5 秒内跟上阶跃扰动,但 d̂ 上会叠一层明显的毛刺,前馈到控制力里就变成了抖振源。工程上取“扰动变化速率的 3~5 倍”作为起点比较稳妥。

4.2 观测器与控制器的耦合:τ 必须是真正施加的那个

这是整个工程里最容易出问题的一处。观测器方程里的 τ,必须是船体实际收到的那个力和力矩,而不是控制律算出来的原始值。只要中间加了任何东西——饱和、速率限制、一阶执行器模型、推力分配——观测器就必须看到处理后的值。

道理不复杂:观测器是用“已知输入 + 已知模型”去反推“未知扰动”,如果输入对不上,那部分差值会被它一股脑算进 d̂ 里。加了 ±50 N 的推力饱和以后,控制律要 80 N、实际给 50 N,观测器却按 80 N 反解,d̂ 里就凭空多出 30 N 的“扰动”,前馈回去让控制律要得更多,形成正反馈。

% 正确做法:先饱和,再把饱和后的值送给观测器和船体 tau_raw = M*(alpha2_dot - ctrl.K2*s - R'*e1) + C*nu + D*nu ... - d_hat - M*(theta_hat*tanh(s/ctrl.eps)); tau = max(min(tau_raw, ctrl.tau_max), -ctrl.tau_max); % 逐通道限幅 % 观测器吃的是 tau,不是 tau_raw [~, z_dot] = ndo(nu, tau, par, ctrl, z); % 船体积分也用 tau nu_dot = M \ (tau + d - C*nu - D*nu);

同样的逻辑适用于参考轨迹的切换、模式切换、甚至 ode45 内部的重试步——只要控制器在某一步用了不同的输入,观测器都得同步。我在第一次改这个工程加饱和时没注意,跑出来的曲线是“跟踪误差先收敛到 0.1 m,然后在 0.6 m 附近稳定振荡”,调了两天增益才发现问题在观测器输入。

4.3 用估计误差曲线验证观测器是否真的在工作

判断观测器有没有起作用,不能只看位置曲线跟得准不准——位置跟准了可能只是增益大,跟观测器没关系。要看的是 d̂ 和真实 d 的对比,以及估计误差的收敛时间。

% 从仿真结果里回算真实扰动与估计扰动 d_true = zeros(3, numel(t)); d_est = zeros(3, numel(t)); for k = 1:numel(t) xk = xa(k,:)'; nu = xk(4:6); d_true(:,k) = disturbance(t(k)); d_est(:,k) = xk(10:12) + ctrl.Ko*(par.M*nu); end err = d_true - d_est; fprintf('观测器 2s 后最大相对误差: %.2f%%\n', ... 100*max(vecnorm(err(:, t>2))) / max(vecnorm(d_true))); figure; subplot(2,1,1); plot(t, d_true(1,:), t, d_est(1,:)); % 纵荡扰动力 legend('真实','估计'); ylabel('d_1 (N)'); subplot(2,1,2); plot(t, vecnorm(err)); ylabel('||d - d_{hat}||');

正常情况下第一条曲线应该看到两条线在 1 秒内重合,第二条曲线单调衰减到接近 0。如果第二条曲线衰减到某个非零值就停住,说明存在稳态估计偏差,通常是模型参数(M、D)和真实值不一致导致的;如果第二条曲线在高频抖动,那是 K_o 太大把噪声放大了,往下调。这三个判断对应三种不同的改法,比盲目扫增益高效得多。

5. 排查清单:曲线发散、控制力抖振与观测器漂移的五个坑

5.1 轨迹跟踪类问题:飘、绕圈、艏向疯转

坑一:位置曲线慢慢飘走,二十秒后偏出十几米。

现象是误差前期收敛得很好,十秒之后开始单调增大,控制力看起来还在正常范围内。原因几乎都是艏向误差没做归一化:参考轨迹的 ψd 用omega*t生成时会一直增长到 2π 以上,而实际 ψ 被 ssa 折在 [-π, π],两者一减就出现 2π 的跳变,外环虚拟速度瞬间反向。解决是把误差项统一走ssa,并且让参考轨迹函数自己也输出归一化后的 ψd。

坑二:船绕着参考点转圈,怎么调增益都不收敛。

现象是轨迹图上出现一个半径稳定的圆,船沿着圆跑但进不到中心。原因是参考轨迹的 η̇d 和 η̈d 与 ηd 不匹配——手工写死的导数和解析函数对不上,或者用差分近似时步长太大。解决是把参考轨迹、一阶导、二阶导写在同一个函数里,用同一个 ω、同一组幅值,保证三者严格自洽。

function [eta_d, eta_d_dot, eta_d_ddot] = ref_trajectory(t) % 三者在同一个函数里生成,避免"轨迹和导数对不上"的经典坑 R0 = 8; w = 0.15; eta_d = [R0*cos(w*t); R0*sin(w*t); ssa(w*t + pi/2)]; eta_d_dot = [-R0*w*sin(w*t); R0*w*cos(w*t); w]; eta_d_ddot = [-R0*w^2*cos(w*t); -R0*w^2*sin(w*t); 0]; end

5.2 数值与抖振类问题:解算器步长和 sign 函数

坑三:控制力曲线像一条实心黑带,电机模型根本没法接。

现象是 τ 的波形全是高频毛刺,局部放大能看到每秒几十次的跳变。原因有两个叠加:控制律里用了sign(s)硬切换,以及MaxStep没有设上限,ode45 为了满足容差在切换面附近疯狂缩步。解决分两步,先把 sign 换成 tanh 并给 ε 一个不小于 0.01 的边界层,再把MaxStep固定到 0.01 或更小,让积分器在切换面附近的行为可预测。改完之后 τ 会变成一条有厚度的曲线,厚度对应边界层内的线性区。

坑四:仿真跑了十分钟还没结束,或者直接报积分容差失败。

现象是命令行停在那里不动,或者中途退出。原因是参数组合让系统进了刚性区:τ_f 很小(比如 1e-4)同时 K₂ 很大,滤波器和内环的时间常数差了三个数量级。解决是把 τ_f 提到 0.02~0.05,或者干脆换 ode15s。我一般会先用 ode45 加固定 MaxStep 跑一遍,如果耗时超过预期三倍,再考虑换求解器,因为换求解器会掩盖掉参数本身不合理的问题。

5.3 观测器类问题:估计值漂移与“假收敛”

坑五:d̂ 一直在缓慢上爬,最后控制力被这一项吃掉。

现象是 θ̂ 和 d̂ 都单调增长,位置误差反而在 5 秒后变大。原因有两个:一是自适应律没加泄漏项,只要滑模面长期非零,θ̂ 就无限增长;二是观测器的 z₀ 初值取成了 0,导致 d̂(0) = K_o·M·ν₀ ≠ 0,观测器一上电就带着一个虚假的初始扰动,这个偏置会被自适应项当成“需要补偿的量”继续放大。解决是补上-gamma*sigma*theta_hat,并且用 z₀ = -K_o·M·ν₀ 初始化。

还有一种更隐蔽的“假收敛”:d̂ 的曲线看起来很平滑,和真实扰动也大致同形,但幅值只有真实值的 60%。这通常是把tau_raw而不是tau喂给了观测器,或者 M、D 的标称值和仿真里用的不是同一套。检查方法很简单,把观测器方程里的模型项临时换成仿真里的真实值,如果估计立刻准了,问题就出在参数不一致上。

提示:改完任何一处参数,先把仿真时间缩到 5 秒单独看观测器收敛段,再去跑完整的 120 秒。收敛段没问题而全程有问题,说明问题在累积误差或饱和,不在观测器本身。

6. 二次开发:换船型、加饱和、把解析轨迹换成实测数据

换船型是最常见的改造需求,改动面比想象中小。把ship_params.m里的 M、D 换成你的辨识结果或 CFD 数据,注意保持 M 正定、D 正定、C 反对称这三条性质,然后按量级重新归一化增益:一个实用的经验是把 K₂ 写成K2 = k2 * inv(M)的形式,让增益随惯性自动缩放,换船后只需要微调标量 k2,而不是重新调三个对角元。

% 增益随惯性归一化,换船型时只改标量 ctrl.k2 = 15; ctrl.K2 = ctrl.k2 * inv(par.M); % 而不是硬编码 diag([0.6 0.6 0.8]) ctrl.K2 = diag(diag(ctrl.K2)); % 保持对角,便于分析

加输入饱和时,前面强调过的那条必须遵守:饱和后的 τ 同时送给船体和观测器。速率限制同理,tau = tau_prev + sat(tau_raw - tau_prev, rate_max*dt),这个tau才是观测器的输入。如果还要加推力分配(比如把三自由度力和力矩分到几个推进器上),分配后的实际输出才是最终输入,观测器要一路跟到底。

参考轨迹从解析函数换成实测数据时,别用gradient去数值微分两次。正确做法是把实测轨迹做平滑(低通或样条),一阶导用一次差分加低通,二阶导完全不参与计算——控制律里需要的 α̇₂ 本来就来自动态面滤波器,跟参考轨迹的导数无关,只有 α₁ 里用到 η̇d。这一条能省掉大量“为什么换成实测数据就发散”的麻烦。

验证方法我一般固定成三张图加两个指标:位置轨迹与参考轨迹的叠图、三通道误差范数、控制力与观测器估计误差;指标是稳态段的 RMSE 和观测器收敛到 5% 以内的时间。做完一次参数改动就跑一遍这三张图,比盯着一条曲线猜要快得多。

% 统一指标,每次改参数后跑一遍 idx = t > 20; % 跳过暂态 rmse_pos = sqrt(mean(sum((eta(idx,1:2) - eta_d_all(idx,1:2)).^2, 2))); t_conv = t(find(vecnorm(err) < 0.05*max(vecnorm(d_true)), 1)); fprintf('稳态位置 RMSE = %.3f m, 观测器收敛时间 = %.2f s\n', rmse_pos, t_conv);

从那以后我每次改这套代码,都强制先把仿真截到 5 秒只看观测器收敛段,确认 d̂ 和真实扰动的两条线在 1 秒内贴合,再去跑全程。这一步多花两分钟,能挡掉后面大半天的无效调参。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进