☰
船舶航向回步自适应控制器设计与Matlab仿真
2026/10/5 3:55:15 网站建设 项目流程

把船舶航向控制当成线性问题来处理,仿真里看似一切正常,一换航速、一压载、一遇海流,控制器马上露馅——模型参数在漂,固定增益根本不抗造。这次分享的是一套在Matlab里完整实现的船舶航向回步自适应控制器设计,核心方法是李亚普诺夫非线性分析加反步法(Backstepping,标题里的“回步”就是它的另一种常见译法)。整套仿真覆盖Norrbin非线性船舶模型、两层反步推导、自适应参数更新,以及最终的控制效果解读,适合正在做船舶运动控制、自动舵设计或非线性控制课题的朋友参考,也适合想弄明白“自适应控制器的稳定性到底怎么来的”的初学者。

1. 船舶航向控制遇到的问题:模型参数在“漂”

1.1 Nomoto线性模型为什么只能当“理想工况”参考

船舶航向控制最常用的线性模型是Nomoto一阶模型:

T·ψ̈ + ψ̇ = K·δ

这里的T是应舵时间常数,描述船艏响应舵角的快慢;K是回转能力指数,描述稳态转艏速率与舵角的比例关系;ψ是航向角,δ是舵角。用传递函数表示就是ψ(s)/δ(s)=K/(s(1+Ts))。这个模型参数少、物理含义清晰,很多PID型自动舵的工程整定都基于它,课堂上做课程设计也会从它起步。

我一开始做仿真也图省事直接用这个模型,后来在仿真里加入海流、风速或大角度转向工况时,发现一个特别实际的问题:K和T根本不是常数。航速下降10%,K可能缩水20%以上;装载状态变化,T会明显偏移;浅水航行时这两个参数的变化更离谱。这还没算Norrbin模型里强调的那一项——船舶在大转艏速率下,艏向阻尼会呈立方级增长,线性模型对阻尼的估计明显偏乐观。说白了,线性模型只是在“名义工况”附近的一段线性化近似,超出了这个范围,误差会越来越大。

可以这样类比:线性模型相当于“这台车在干燥平路上转向手感”的拟合结果,但同一台车上了倾斜路面、换了轮胎、后备箱装满重物,手感全变了。要么每次重新整定控制器参数,要么让控制器学会自己适应。自适应控制走的就是后一条路。

1.2 Norrbin非线性模型与控制问题的正式提法

在Nomoto模型基础上加入非线性阻尼项,就是Norrbin模型:

T·ψ̈ + ψ̇ + α·ψ̇³ = K·δ

写成状态方程,令x₁=ψ、x₂=r=ψ̇,可以得到:

ẋ₁ = x₂
ẋ₂ = -(1/T)x₂ - (α/T)x₂³ + (K/T)δ + d(t)

这里我额外加了一项d(t),用来表示外界等效干扰——海流偏置、波浪漂移力、未建模动态都可以折算到这个位置。实际工程里d(t)可能不是一个纯常数,它缓慢变化、有偏置、还叠一点波动,这就比教科书上“d是未知常数”的假设更贴近现实。

现在控制系统要解决的核心问题是:在参数(1/T、α/T、K/T)不完全已知、又存在干扰d(t)的条件下,设计舵角指令δ,使得航向角ψ收敛到设定航向ψd,并且整个过程的稳定性有理论上的保证。注意“参数不完全已知”这一点是实质性的:船舶的运动参数随航速、水深、装载甚至船体污底程度漂移,不可能每次上船都重新做辨识。控制器必须带上在线估计的能力,这就是“自适应”落地的位置;反步法是搭建控制器结构的工具,李亚普诺夫方法是证明“不管初始误差多大、参数偏多少,误差最终都会消掉”的理论基石。二者缺一不可。

2. 反步法(Backstepping)的设计思路与实现

2.1 把二阶系统拆成“两层串联”

反步法的中文译名确实比较乱,Backstepping被译成反步法、反推法、反步、回步的都有,实际意思完全一样:从控制目标出发,一层一层往回推,先给内层设计一个中间参考值,最后推出真正的控制量。

我们的模型碰巧就是一个严格的二阶反馈形式:

第一层 ẋ₁ = x₂:航向角的变化率就是转艏速率,控制量不直接进这一层。
第二层 ẋ₂ = f(x₂) + b·δ + d:转艏速率的变化由非线性阻尼、舵力、干扰共同决定,真实控制的入口在这里。

这种结构的妙处在于,中间量x₂被“夹”在两层之间:它既要跟随上层航向跟踪需求,又要接受下层舵角指令的驱动。所以设计时可以分两步走:先假设我们能任意指定转艏速率r,让航向角先跟上ψd——这一步设计出一个“虚拟控制律”α₁;再看实际转艏速率和α₁差多少,用舵角去消除这个差值。整个过程像是把“领导给的任务”逐层翻译成“底层执行机构的动作”。

2.2 第一层虚拟控制:让转艏速率去逼近期望值

先定义航向跟踪误差:

z₁ = ψ - ψd

对时间求导,利用状态方程第一层:

ż₁ = x₂ - ψ̇d

如果x₂是一个可以直接指定的量,取x₂ = α₁ = -c₁·z₁ + ψ̇d,其中c₁是正增益,那么ż₁ = -c₁·z₁,航向误差会指数收敛到零。这个α₁并不需要是真的舵角指令,它只是告诉我们:当前时刻理想的转艏速率应该长什么样。

实际船不可能瞬间跳到理想的转艏速率,所以我们再定义第二步误差:

z₂ = x₂ - α₁

后面所有的工作,就是设计真实舵角δ,让z₂尽快变小。这一步很关键:z₂是反步法的“桥梁”,它把航向层的目标(虚拟控制α₁)与转艏层的控制能力(舵角δ)联系在一起。

2.3 第二层真实控制:舵角的表达式

写出z₂的导数:

ż₂ = ẋ₂ - α̇₁ = f(x₂) + b·δ + d(t) - α̇₁

先看理想情况:如果所有参数已知且没有干扰,控制律可以取成:

b·δ = -z₁ - c₂·z₂ - f(x₂) + α̇₁

代回ż₂的方程,得ż₂ = -z₁ - c₂·z₂。结合前面ż₁ = -c₁·z₁ + z₂,误差动力学是一个以c₁、c₂配置的渐近稳定系统。到这里,反步法结构已经清晰了:虚拟控制表达“理想的内层状态”,真实控制负责“追赶理想状态”。

但工程中f(x₂)里的参数未知、b未知、外面还挂着d(t),所以不能直接把这个理想控制律搬上船。需要卸下“已知”的假设,引入参数估计,让控制器在运行中自己补上认知缺口——这就是下一部分要展开的内容,也是“自适应反步法”相对于普通反步法的差异所在。

3. 李亚普诺夫候选函数与自适应律的相互成就

3.1 为什么要构造带参数估计误差的李亚普诺夫函数

一般做非线性控制课设,很容易陷入“按步骤抄公式,跑通就算完”的状态。但这里我要多说一句:公式里最关键的部分,其实是候选李亚普诺夫函数V怎么选。

李亚普诺夫第二方法的核心思路是:不直接求解微分方程,而是构造一个正定标量函数V,再看它沿系统轨迹的导数。如果V̇≤0,V就不会增长,系统状态被约束在有界区域内;如果V̇严格负定,再配合Barbalat引理,就能推出误差收敛到零。这等价于在问:系统的“总能量”会不会越折腾越小。

对于自适应系统,光看状态误差本身还不够,因为参数估计误差也在动态地影响系统。一个取巧但非常有效的做法是,把参数估计误差也写进V里,让V变成包含状态误差加参数误差的“增广能量函数”。这样设计的好处是:当参数估计偏了,能量函数会感应到,从而驱动自适应律去调整参数,而不是让偏差悄悄侵蚀跟踪精度。

3.2 V的导数推导与控制律、自适应律的配对

先把不确定性参数化。令:

φ(x₂) = [-x₂, -x₂³]ᵀ
θ = [1/T, α/T]ᵀ

那么f(x₂) = -θ₁x₂ - θ₂x₂³ = φ(x₂)ᵀ·θ,φ就是已知的回归向量,θ是待估计参数向量。舵效系数b = K/T也未知,外界干扰d(t)用估计值d̂来补偿。

构造增广候选函数:

V = ½z₁² + ½z₂² + ½θ̃ᵀΓ⁻¹θ̃ + ½b̃²/γ_b + ½d̃²/γ_d

其中θ̃、b̃、d̃是参数估计误差,Γ是对角正定矩阵,γ_b、γ_d是正标量。这个V的正定性一目了然,四个部分分别代表航向误差、转艏速率误差、参数估计误差和干扰估计误差的“能量”。

沿系统轨迹求导,经过交叉项重新组合,控制律取:

δ = (1/b̂)·(-z₁ - c₂·z₂ - φᵀθ̂ - d̂ + c₁·x₂)

自适应更新律取:

θ̂̇ = Γ·φ·z₂
b̂̇ = γ_b·δ·z₂
d̂̇ = γ_d·z₂

代入V̇后,所有包含参数估计误差的交叉项恰好抵消,最终得到:

V̇ = -c₁·z₁² - c₂·z₂² ≤ 0

这一行就是我每次跑仿真最安心的地方:不管参数初值偏了多少、干扰有多大,V只降不升,系统误差能量被两个正增益项持续消耗。尤其要留意,自适应律的三个式子并不是拍脑袋写出来的——恰恰是为了让V̇中的交叉项干净地消失,它们是与李亚普诺夫函数“配对”设计的。

3.3 稳定性结论背后的工程含义

V̇≤0只能说明误差不会增长,为什么最终能收敛到零?这要补一步数学收尾:因为V有下界,V̇≤0,且系统状态有界、信号光滑,V̇是一致连续的,由Barbalat引理可知t→∞时V̇→0,从而推出z₁和z₂收敛到零。

这个结论落到工程上,含义非常直接:c₁、c₂不只是“两个调参用的正数”,它们直接出现在V̇的表达式里,决定误差能量被消耗的速率。调大它们,跟踪收敛更快;但代价是控制能量需求变大,更容易顶到舵机饱和。后面调参时反复权衡的就是这个矛盾。

另一个容易被忽视的点是:参数估计不一定要收敛到真值,跟踪误差也能收敛到零。因为系统只需要“有效组合”的补偿足够准确即可,不需要单独每个参数都精确。这个现象在恒定航向指令下特别明显——没有持续激励,参数识别不出来,但跟踪照样没问题。许多第一次做仿真的朋友看到参数曲线没走到期望值,就以为控制器失效,其实不是这样。

4. Matlab仿真源码实现:从状态方程到闭环曲线

4.1 系统参数与自适应初值的设定

仿真模型我就用Norrbin形式直接写状态方程:

ẋ₁ = x₂
ẋ₂ = -θ₁·x₂ - θ₂·x₂³ + b·δ + d(t)

数值取法说明一下:真实θ₁=0.1、θ₂=0.3、b=0.3,换算到Norrbin模型就相当于T=10s、α=3、K=3,这是一组比较适合在10~200秒时间尺度上观察响应的参数。真实干扰设为d(t)=0.02+0.005·sin(0.05t),也就是有一个0.02的常值偏置(模拟海流等效偏置力),再叠一点缓慢波动。

自适应初值设成真实值的50%:θ̂(0)=[0.05; 0.15]、b̂(0)=0.15、d̂(0)=0。这模拟的是“模型辨识结果明显偏小”的工程情形,很常见,因为辨识数据覆盖的工况不足,或者船况已经变化了。初始状态设为ψ=0、r=0,设定航向ψd=30°(约0.5236 rad),整个转向过程就是控制器第一次经受考验的时刻。

4.2 核心代码结构与关键函数

代码我按三个文件组织:主脚本main.m、闭环微分方程函数closed_loop.m、控制器输出函数controller.m。把被控对象、控制器、自适应律都放进同一个闭环函数里,是因为它们在每个积分步同时更新,用ode45直接积分最方便。

先看控制器函数,这是整个算法的输出核心:

function u = controller_output(psi, r, theta_hat, b_hat, d_hat, psi_d, c1, c2) % 回归向量与误差 phi = [-r; -r^3]; z1 = psi - psi_d; alpha1 = -c1 * z1; % 虚拟控制 alpha1_dot = -c1 * r; % 虚拟控制的导数 z2 = r - alpha1; % b_hat 下限保护,防止除零或变号 b_safe = max(b_hat, 0.05); % 反步自适应控制律 u = (-z1 - c2*z2 - theta_hat'*phi - d_hat + alpha1_dot) / b_safe; % 舵角限幅(工程限制) umax = deg2rad(30); u = max(-umax, min(umax, u)); end

注意一个细节:控制律里alpha1_dot=-c1*r,所以最后一项写成了“+alpha1_dot”。如果你在纸上推导时习惯用-α̇₁,容易在代码里搞错符号,我第一次就是在这一步把正负号弄反了,仿真里系统直接发散。

再看闭环微分方程函数,里面同时包含自适应律和真实被控对象:

function dX = closed_loop(t, X, c1, c2, Gamma, gamma_b, gamma_d) psi = X(1); r = X(2); theta_hat = X(3:4); b_hat = X(5); d_hat = X(6); psi_d = deg2rad(30); u = controller_output(psi, r, theta_hat, b_hat, d_hat, psi_d, c1, c2); phi = [-r; -r^3]; z1 = psi - psi_d; alpha1 = -c1 * z1; z2 = r - alpha1; % 自适应更新律 theta_hat_dot = Gamma * phi * z2; % b_hat 投影保护:低于下界时不允许继续下降 if b_hat <= 0.08 && gamma_b * u * z2 < 0 b_hat_dot = 0; else b_hat_dot = gamma_b * u * z2; end d_hat_dot = gamma_d * z2; % 真实被控对象(真实参数在仿真里固定,控制器并不知道) theta_real = [0.1; 0.3]; b_real = 0.3; d_real = 0.02 + 0.005 * sin(0.05 * t); f = [-r, -r^3] * theta_real; dpsi = r; dr = f + b_real * u + d_real; dX = [dpsi; dr; theta_hat_dot; b_hat_dot; d_hat_dot]; end

主程序只需要做初始化、调ode45、然后画图:

clc; clear; close all; % 控制器与自适应增益 c1 = 0.4; c2 = 1.0; Gamma = diag([0.2, 0.05]); gamma_b = 0.08; gamma_d = 0.05; % 自适应初值(真值的50%) X0 = [0; 0; 0.05; 0.15; 0.15; 0]; % 仿真时长与数值设置 Tf = 200; [t, X] = ode45(@(t,X) closed_loop(t,X,c1,c2,Gamma,gamma_b,gamma_d), ... [0 Tf], X0, odeset('RelTol',1e-6,'MaxStep',0.5)); % 提取状态 psi = X(:,1); r = X(:,2); theta1 = X(:,3); theta2 = X(:,4); b_hat = X(:,5); d_hat = X(:,6); % 计算控制量用于绘图 u = zeros(size(t)); for k = 1:length(t) u(k) = controller_output(psi(k), r(k), X(k,3:4)', X(k,5), X(k,6), deg2rad(30), c1, c2); end figure; subplot(2,2,1); plot(t, rad2deg(psi)); hold on; plot(t, 30*ones(size(t)), '--'); xlabel('时间/s'); ylabel('航向角/°'); legend('实际航向','设定航向'); subplot(2,2,2); plot(t, rad2deg(u)); xlabel('时间/s'); ylabel('舵角/°'); subplot(2,2,3); plot(t, theta1, t, theta2, t, b_hat); xlabel('时间/s'); legend('θ_1估计','θ_2估计','b估计'); subplot(2,2,4); plot(t, d_hat); xlabel('时间/s'); ylabel('干扰估计');

4.3 仿真实验设计:转向、扰动与参数失配

上面这套设置包含了三个“考验点”:第一个是大角度初始转向(0°到30°),这是控制器动态响应最强的时刻;第二个是参数失配50%,让自适应机构不得不干活;第三个是常值偏置加缓慢正弦的干扰,看d̂能否把偏置补回来。三个因素叠加,比单纯的“理想模型加控制器”有意义得多。

5. 仿真结果解读:跟踪精度、舵机负担与参数收敛

5.1 航向和转艏速率的动态响应

在我用上面参数跑出来的结果里,航向角从0°平滑上升到30°,大约在60~80秒附近进入稳定,整个过程没有明显的持续振荡,超调量也比较小。如果只看这条航向曲线,会觉得“这控制器不也就是个PD的效果吗”——但注意,这里的前提是模型参数偏了50%、还有偏置干扰,PD做不到这个精度。

转艏速率r的曲线呈典型的“先上升后回落”形态:转向初期r被拉起来,接近目标航向时控制器主动压r,防止超调。这个形态和反步法里的虚拟控制设计是吻合的——α₁先要求快速转艏,等z₁变小后又要求r回落,形成自然的减速过程。

5.2 参数估计曲线如何看

参数估计曲线是最值得花时间看的部分。我的仿真结果里,θ̂₁、θ̂₂、b̂都从初始的50%真实值向真实值方向爬升,d̂也从0开始往0.02附近靠拢。这是自适应机构在起作用:系统发现只用失配参数不足以消除跟踪误差,于是自动更新参数把误差压下去。

不过要提醒一点:这些估计值不会严格等于真实值,尤其是恒定航向指令时,参数识别存在不可观的方向,估计值可能停在某个“够用的值”附近,不再动弹。很多人第一次做自适应仿真会纠结“为什么参数没收敛到真值”,其实这不是故障,而是系统缺少持续激励。真要让参数也收敛干净,需要把航向指令改成分段变化,比如0°→15°→30°,给系统足够的信息去区分每个参数的贡献。

5.3 与固定增益控制器的对比效果

为了体现自适应的必要性,我做了一组对比实验:关掉自适应律(把θ̂̇、b̂̇、d̂̇全部置零),让控制器始终使用偏差50%的参数跑同样的工况。结果是航向角虽然能大致转向,但稳态附近始终存在一个可见的偏差,因为控制器内部的模型补偿和真实对象对不上,又没有参数更新去修正,这个偏差只能留在那里。

自适应版本则完全不同:z₁最终压到接近零的量级,舵角指令在稳定后也只保留小幅活动来对抗扰动。这个对比是最直观的证据——自适应控制不是“锦上添花”,而是参数失配条件下维持精度的必要机制。

6. 调参经验与踩坑记录

6.1 控制器增益和自适应增益的匹配

c₁、c₂的初值我习惯按二阶误差动力学来定。忽略自适应细节时,误差系统近似为z̈₁ + c₂·ż₁ + c₁·z₁ ≈ 0,所以c₁和c₂可以按二阶系统极点选,比如自然频率0.3~0.5 rad/s、阻尼比0.9~1.0,换算下来c₁取0.09~0.25、c₂取0.6~1.0附近,再根据仿真微调。c₁、c₂太大,舵角需求会顶到限幅,导致实际控制量不足,反而出现振荡和稳态误差。

自适应增益Γ、γ的处理原则是“从小往大加”。Γ太大,参数估计会抖,体现在舵角曲线上就是高频毛刺,严重时能激发出系统高频动态,直接发散;Γ太小,参数调整慢,收敛时间拖长。我的经验是:固定c₁、c₂之后,先让θ̂的Γ取对角元素0.05左右的量级,跑通后再逐步调大。

下面这张表是我调试过程中总结的常见现象对照,遇到类似问题可以按表里的方向排查:

现象主要原因处理方向
航向超调明显增大c₁/c₂搭配不当,虚拟控制减速太晚增大c₂,或适当减小c₁
舵角出现高频抖振Γ、γ_b过大,参数估计在跳动降低自适应增益
稳态误差迟迟消不掉θ̂初值偏差过大,或d̂增益太小增大γ_d,检查θ̂初值方向
参数估计曲线发散无投影保护,b̂穿越零点强制b̂下界,检查初始符号

6.2 b̂接近零的退化保护

b̂是控制器里的分母,它是舵效系数的估计值。工程上要注意两点:一是符号不能错,舵往哪个方向转、船往哪个方向偏,这个符号必须预知;二是幅值不能接近零,否则控制增量爆炸。代码里我在控制输出前用max(b_hat,0.05)做了下限保护,在自适应律里又用投影限制了b̂低于0.08时不允许继续下降。这在物理上相当于“我不完全确定舵效,但我肯定舵没坏到推不动的程度”,工程上是合理的先验信息。

6.3 噪声、舵机饱和与数值积分细节

仿真里我用的干扰是光滑的,但真实船舶的量测噪声会直接进入z₂,进而污染自适应律。观测噪声会让参数估计产生随机游走,时间长了可能漂到不合理的位置。工程上可以在航向/艏向速率的量测通道加低通滤波,或者给自适应律加死区,误差小于某个阈值时冻结参数更新。

舵机饱和也必须提前处理。30°限幅放进控制器输出函数后,如果参数初值偏得厉害,转向初期u会一直顶在限幅上,此时自适应律基于“实际舵角”更新,一旦退出饱和,估计值可能已经被带偏。简单做法是降低自适应增益,高级一点可以加抗饱和修正。课程设计阶段用低增益加限幅基本够用。

ode45的数值设置我提一下:RelTol至少给到1e-6,MaxStep给到0.5秒,否则自适应参数曲线会出现肉眼可见的数值毛刺。这个细节被很多教程忽略,卡住时可以先从这里排查。

6.4 持续激励问题:多大激励才够

前面多次提到参数收敛依赖持续激励。恒定航向时,系统只覆盖了有限的工作点,参数识别是“欠定”的;只有航向指令不断变化,让r和r³呈现不同的组合比例,θ̂₁和θ̂₂才能真正分开收敛。我在扩展实验里把ψd设计成15°保持100秒、再切到30°保持100秒,参数估计曲线明显比恒定航向时更贴近真实值。这套做法对后续做“自适应+系统辨识”结合很有用,建议拿到源码后一定试一下。

整套跑下来,我最深的感受是:反步法给出了控制律的“形状”,李亚普诺夫方法给出了参数更新方向的“约束”,两者合起来,自适应才不是玄学,而是有稳定边界的技术。你在自己的模型上复现时,建议先跑通恒定航向的基本工况,再把扰动加大、把航向改成分段指令,观察参数估计曲线的响应。如果仿真里出现发散,先关掉自适应跑固定参数,确认模型和正负号没问题,再逐步打开自适应——这条路能帮你快速定位是自己推导错了、代码符号错了,还是自适应增益调得过大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询