1. 欠驱动船舶轨迹跟踪:这个问题的难点到底在哪
做船舶运动控制的人,第一次看到“欠驱动”三个字多半是带着疑惑的。水面船明明有螺旋桨和舵,怎么还叫欠驱动?等你真正开始做仿真,天天对着发散的轨迹曲线挠头,才算理解这个“欠”字的分量——你的执行器数量少于自由度数量,横荡方向没有独立的推进器,想直接把船往侧面推一把,根本做不到。
这篇博文想完整梳理我在实际项目里搭建的一套“神经网络观测器+自适应滑模”欠驱动船舶轨迹跟踪控制方案的来龙去脉:从为什么选这个方案,到数学模型怎么建立,再到Matlab代码怎么落地、仿真曲线怎么一步步调出来,每个环节的坑也会一并交代。适合正在做船舶运动控制、欠驱动系统控制相关课题的研究生,以及想把手里的控制器理论变成能跑仿真代码的工程师。
1.1 欠驱动系统在船舶控制里意味着什么
水面船通常考虑三个自由度:纵荡、横荡、艏摇。常规配置下只有两个执行器——主机产生纵向推力,舵产生偏航力矩。横荡方向上没有独立推进器,所以它是个典型的欠驱动系统。
这意味着什么?意味着横荡速度不能直接控制,只能通过艏向运动和纵向运动之间的耦合“间接操作”。你想让船向右平移,只能先把船头摆到合适角度,再加大纵向推力,让船斜着走过去。这套操作逻辑在数学上很有挑战,因为系统的每个自由度都被迫通过非线性耦合联系在一起,而不是简单地“一个输入对应一个输出”。
水下航行器、鱼雷、部分水面无人艇也都有类似性质,所以这条技术路线的研究方法可以横向迁移,理解透彻之后收益面很广。
1.2 轨迹跟踪比路径跟踪难在哪里
很多人一开始没意识到,“路径跟踪”和“轨迹跟踪”是两个难度差别很大的问题。路径跟踪只要求船最终沿着某条空间路径走,对“什么时间到哪个点”没有硬约束;轨迹跟踪则要求船在指定时刻到达指定位置,参考轨迹是时间的函数。
轨迹跟踪的难点在于,参考位置随时间变化,位置误差动态里会带出参考速度、参考加速度项。如果不能把纵向速度也跟踪好,时间约束就会转化为持续的位置误差。再加上船体模型本身强非线性,控制器设计时若是忽略了时变参考的驱动项,跟踪就会出现明显的相位滞后,船总在轨迹“后面追”。
1.3 为什么常规滑模控制在这里不够用
滑模控制的一大优点是:对匹配扰动和模型不确定性具有天然鲁棒性。但经典滑模设计时需要知道不确定性的上界。实际问题里这个上界往往不知道,或者只能给一个特别保守的大值。
保守取值带来的麻烦很现实:控制增益过大,控制器输出剧烈抖振,执行器频繁饱和,仿真步长被压到极小,运行效率惨不忍睹。早期我用标准滑模硬怼不确定扰动,结果控制力矩曲线像锯齿一样,别说上真船,连仿真结果都不好意思放到论文里。所以必须引入自适应机制,在线估计扰动上界,同时用神经网络把系统未知非线性逼近掉,而不是靠一个夸大的常数增益去“硬扛”。
2. 数学模型与设计目标:从三自由度方程说起
2.1 坐标系与运动学方程
建立控制器前,先把模型写清楚。用两个坐标系:惯性坐标系(地球固定)和船体坐标系。惯性坐标下船的位置是(x, y),艏向角是ψ;船体坐标下纵向速度是u,横荡速度是v,艏摇角速度是r。两套坐标系之间通过旋转矩阵关联,运动学方程写为:
[ \begin{aligned} \dot{x} &= u \cos\psi - v \sin\psi, \ \dot{y} &= u \sin\psi + v \cos\psi, \ \dot{\psi} &= r. \end{aligned} ]
这三个式子看起来简单,但它是整个控制策略的核心枢纽。后面要设计的期望艏向、视线角引导,全都是从这组运动学关系反推出来的。
2.2 动力学方程
三自由度动力学方程在标量形式下可以写成:
[ \begin{aligned} m_{11}\dot{u} &= m_{22} v r - d_{11} u + \tau_u + d_u(t), \ m_{22}\dot{v} &= -m_{11} u r - d_{22} v + d_v(t), \ m_{33}\dot{r} &= (m_{22} - m_{11}) u v - d_{33} r + \tau_r + d_r(t). \end{aligned} ]
其中第一项是科氏力和向心力耦合项,第二项是线性阻尼项。τu是纵向推力,τr是艏摇力矩,du、dv、dr分别代表三个方向上的外部环境扰动。注意横荡方程里没有控制输入,这就是欠驱动约束的直接体现。
2.3 名义模型与聚合不确定性
实际工程中,m和d这些参数不可能精确知道。我把它们拆成“名义值+摄动”:
[ m_{11} = m_{110} + \Delta m_{11}, \quad d_{11} = d_{110} + \Delta d_{11}, \dots ]
把这些未知摄动项和外扰全部聚合起来,就可以把动力学改写成适合控制器设计的形式:
[ \dot{u} = f_u(u,v,r) + g_u \tau_u + \Delta_u(t) ]
这里的Δu是一个聚合项,里面既包含参数摄动产生的动态变化,又包含外部扰动,还包含建模时忽略的高阶非线性。神经网络观测器要做的事,就是把这个Δ当做一个“可在线学习的未知函数”,实时估计出来,再送到控制器里去补偿。
2.4 控制目标与控制方案分工
给定一条时变参考轨迹(xd(t), yd(t)),目标是设计τu、τr,让船的实际位置(x,y)在有限时间内收敛到参考轨迹附近,并保持纵向速度u跟踪期望速度ud,艏向角ψ跟踪由位置误差解算出来的期望艏向ψd。
整个控制器按内外环来组织:外环根据位置误差生成期望艏向,内环用滑模控制器驱动船跟踪这个期望艏向和期望纵向速度。神经网络观测器承担“认识模型误差”的任务,自适应滑模承担“兜底处置残余不确定性和外扰”的任务。这样分工清楚,后面每一块代码的职责也很明确。
3. 控制器整体架构:神经网络观测器和自适应滑模分别干什么
3.1 外环视线角引导与内环跟踪策略
欠驱动船的位置误差不能直接作为控制量,必须通过“期望艏向”这个中间变量传递。我用的经典做法是视线角策略:根据位置误差(e_x, e_y)直接反解出一个期望艏向角:
[ \psi_d = \operatorname{atan2}(-e_x, -e_y) ]
它的含义是让船头朝向“能够同时缩小纵向和横向位置误差”的方向。由于船体横荡方向没有推进器,这个期望艏向角会持续变化,船就会以一种“边转向边前进”的方式逼近轨迹。内环控制器再跟踪这个ψ_d角,形成完整的闭环。
3.2 为什么选“观测器补偿”而不是“网络直接做控制器”
神经网络在控制里的常见用法有两种:一种直接让网络输出控制量的一部分,叫做直接自适应控制;另一种是让网络在线逼近系统的不确定函数,再把逼近结果作为前馈补偿送到控制器里,也就是我用的观测器思路。
我倾向第二种,原因是分工更清晰、稳定性分析更好做。控制器主体仍然是滑模,它负责提供鲁棒性;网络只负责把“未知的非线性部分”越来越准地估计出来。万一网络某个时间点估计不准,滑模项依然能压制误差,不会让系统立刻发散。工程上调试时,也可以单独判断某一条曲线是网络的问题还是滑模参数的问题。
3.3 自适应滑模的兜底作用
滑模控制可以看作一个“硬性约束”:一旦系统状态到达滑模面,就会被强制保持在这个曲面上,从而对匹配不确定性鲁棒。但在实践里,“硬性约束”不能真的用无限大的切换增益去实现,否则抖振会毁掉控制品质。
自适应滑模的处理办法是:不再人为指定扰动上界,而是设置一个自适应参数λ_hat,让它在李雅普诺夫意义下自行调整。扰动大的时候λ_hat自动变大,补偿更强;扰动小的时候自适应项自动收敛到较小的水平,不会白白浪费控制能量。这和神经网络观测器配合起来,正好一个管“未知模型的稳态逼近”,一个管“残余扰动和突发干扰的瞬态抑制”。
3.4 闭环稳定性的整体思路
最终的闭环分析会构造一个李雅普诺夫候选函数,包含位置误差项、滑模变量项、网络权重误差项和自适应参数误差项:
[ V = V_{pos} + \frac{1}{2}s^T s + \frac{1}{2}\tilde{W}^T\Gamma^{-1}\tilde{W} + \frac{1}{2\gamma}\tilde{\lambda}^2 ]
设计的控制律和自适应律会让V的导数满足:
[ \dot{V} \le -\alpha V + \beta ]
这个条件表明系统信号最终一致有界(UUB)。注意,不是“误差渐进趋近于零”,而是“误差收敛到原点附近一个有界邻域内”。神经网络逼近能力再强,也不可能万无一失;外部扰动也不会完全消失。接受这一点很重要,不然你会把大量时间浪费在追求仿真里完美的零误差上。
4. 神经网络观测器的设计与Matlab实现
4.1 RBF网络结构与输入输出选择
我用的神经网络观测器是标准径向基函数网络(RBF),输出是对聚合不确定项的估计:
[ \hat f_i(\xi) = \hat W_i^T \phi(\xi) ]
其中φ是高斯基函数向量:
[ \phi_j(\xi) = \exp\left(-\frac{|\xi - c_j|^2}{2b_j^2}\right) ]
输入ξ的选择直接影响网络的学习效果。我习惯把速度状态u、v、r,位置误差e_x、e_y,以及期望信号的一部分组成输入向量。节点数取20到30个就够了。节点太少逼近能力不足,节点太多不仅计算量大,还容易在持续激励不足时出现权重漂移。
需要说明的是,神经网络观测器不会直接观测“外部扰动”本身,它是观测“聚合不确定性函数”,也就是模型误差动态。这个思路和一般的状态观测器不太一样,实际仿真里要把网络输出和真实不确定性做对比,才能判断补偿是否到位。
4.2 中心点、宽度的选取方法与踩坑经验
RBF网络的高斯基函数只在中心点附近有明显响应。如果中心点范围没有覆盖系统的正常运行区间,网络输出可能会一直处于“激励不足”状态,补偿效果非常差,甚至出现发散。
我的做法分三步:先用一组保守控制器跑一次短时开环或者粗调仿真,记录u、v、r的变化范围;然后在变化区间内均匀布置中心点;宽度b_j取覆盖区间的0.5到1倍步长量级。这样每个基函数之间既不会重叠太多,也不会留下覆盖盲区。
代码上可以用linspace直接在范围内均匀取点。有经验的同行也可以用模糊C均值聚类去自动确定中心点,但我个人感觉对于船舶控制这种低维问题,均匀取点就够用了,不需要引入额外的无监督算法负担。
4.3 权重更新律的设计
权重更新律一般由李雅普诺夫分析推导,常见形式是自适应项加σ修正项:
[ \dot{\hat W}_i = \Gamma_i \phi(\xi) s_i - \kappa_i \Gamma_i \hat W_i ]
第一项让网络权重根据滑模变量和基函数响应不断调整,第二项防止权重在外界持续激励下产生漂移。σ修正系数κ不能太大,太大等于把网络的逼近能力“绑住”了,太小则长期运行后权重可能漂移。一般Γ先取0.1到10区间,κ取0.01到0.1,再微调。
这里有一个很重要的经验:网络权重的初始值不要给随机大值,直接设为零向量。网络是从“什么都不懂”开始学习的,前几秒真实控制由滑模项顶着,随着权重调整到位,补偿逐渐接管。如果你一上来给随机大初始权重,控制器可能连初始状态都稳不住。
4.4 Matlab代码结构:在ode45里把权重当状态积分
Matlab仿真的核心问题是如何处理网络的连续更新。我直接用ode45求解闭环系统,但把网络权重和自适应参数都放进状态向量里,一起积分。
function dstate = ship_dynamics(t, state, param) % 解包状态 x = state(1); y = state(2); psi = state(3); u = state(4); v = state(5); r = state(6); W = reshape(state(7 : 6 + param.nNode * param.nOut), ... param.nNode, param.nOut); % RBF权重矩阵 lambda = state(7 + param.nNode * param.nOut); % 自适应参数 % 参考轨迹与时变信号 [xd, xd_dot] = param.ref_x(t); [yd, yd_dot] = param.ref_y(t); % 外环视线角 ex = x - xd; ey = y - yd; psi_d = atan2(-ex, -ey); % 计算滑模面 s_u = u - param.ud; s_psi = r - param.r_d + param.c_psi * (psi - psi_d); % 构造RBF输入与基函数 xi = [u; v; r; ex; ey; psi_d]; phi = exp(-sum((xi - param.C).^2 ./ (2 * param.B.^2), 1))'; f_hat = W' * phi; % 聚合不确定性估计 % 控制律:名义前馈 + 神经网络补偿 + 自适应滑模鲁棒项 tau_u = param.m11_0 * param.ud_dot - param.m22_0 * v * r + param.d11_0 * u ... - f_hat(1) - param.lambda_u * tanh(s_u / param.eps_u); tau_r = param.m33_0 * param.r_d_dot - (param.m22_0-param.m11_0) * u * v ... + param.d33_0 * r - f_hat(2) ... - param.lambda_r * tanh(s_psi / param.eps_r); % 动力学方程 du = (param.m22_0 * v * r - param.d11_0 * u + tau_u + param.d_u(t)) / param.m11_0; dv = (-param.m11_0 * u * r - param.d22_0 * v + param.d_v(t)) / param.m22_0; dr = ((param.m22_0-param.m11_0) * u * v - param.d33_0 * r + tau_r + param.d_r(t)) / param.m33_0; % 自适应与权重更新律 dW = param.Gamma * (phi * s_u' * param.gain(1) + phi * s_psi' * param.gain(2)) ... - param.kappa * param.Gamma * W; dlambda = param.gamma_lambda * (s_u * tanh(s_u/param.eps_u) ... + s_psi * tanh(s_psi/param.eps_psi)) - param.sigma_l * lambda; % 组装输出状态导数 dstate = [u*cos(psi)-v*sin(psi); u*sin(psi)+v*cos(psi); r; du; dv; dr; dW(:); dlambda]; end主脚本只需要调用ode45,之后从状态矩阵里抽列画出曲线。
4.5 观测器与控制器之间的数据流
把数据流理清楚,调试时才能少走弯路。我的数据流顺序是:位置误差ex、ey进入外环,生成期望艏向ψd;内环产生滑模变量;滑模变量、速度状态、位置误差共同构成神经网络输入;网络输出作为前馈补偿;滑模鲁棒项最后补刀。
5. 自适应滑模控制器设计与代码落地的核心细节
5.1 滑模面设计
内环需要两个控制输入,对应两个误差通道。纵向通道关注速度跟踪,艏向通道关注艏向跟踪。我采用了带积分项的滑模面,以消除稳态误差:
[ s_u = (u - u_d) + \lambda_u \int (u - u_d) dt ]
[ s_\psi = (r - r_d) + \lambda_\psi (\psi - \psi_d) ]
这里要注意ψd是时变的,滑模面里隐含了它的导数项。如果在控制律里不对ψd的导数做前馈补偿,跟踪相位就会一直滞后。很多新手代码跑出来“轨迹总是差一点”,根本原因就在这里。
5.2 趋近律选择与抖振抑制
滑模趋近律我习惯用指数趋近形式,加上连续化处理:
[ \dot{s} = -k \tanh(s / \varepsilon) ]
用双曲正切函数替代符号函数,边界层厚度ε是决定控制品质的关键参数。ε太小,等效于接近符号函数,会产生高频抖振并使ode45步长大幅缩短;ε太大,鲁棒性被削弱。我一般从0.05起步,观察控制输入曲线,如果出现高频毛刺就适度加大,如果跟踪误差变大就适度减小。
5.3 自适应参数在线估计
自适应参数λ_hat用来估计扰动和网络逼近误差的残余上界。它的更新律写成:
[ \dot{\hat\lambda} = \gamma_\lambda s \tanh(s / \varepsilon) - \sigma_\lambda \hat\lambda ]
第一项根据实际控制需求在线调整补偿强度,第二项σ项防止自适应参数无限增大,也保持参数有界。这样设计的直接好处是:风浪流扰动强的时候,系统自动“提起精神”加强补偿;扰动消失的时候,补偿自动回落,不会一直在那儿大力输出浪费能量。
5.4 最终控制律的表达
整个控制律可以写成三个部分的叠加:
[ \tau = \tau_{model} + \tau_{NN} + \tau_{rob} ]
- τ_model是名义模型前馈项,用于补偿可计算出来的动力学;
- τ_NN是神经网络输出项,用于逼近和补偿参数摄动、类未建模动态;
- τ_rob是自适应滑模鲁棒项,用于处理残余误差和外部扰动。
三者各司其职,有一个直观的理解方式:名义前馈相当于“按说明书操作”,神经网络相当于“老司机对车况的实时判断”,滑模鲁棒项则是“最后的安全垫”。任何一部分缺失,系统都能勉强工作,但长期稳定性和动态品质会大打折扣。
5.5 参数整定的顺序建议
为了不让调参变成玄学,我建议按递进顺序来:
- 关闭神经网络补偿,只保留名义模型前馈和自适应滑模,在理想模型下先把跟踪精度调到满意;
- 加入外部扰动,调自适应学习率γ和边界层ε,让系统在扰动下仍然稳定;
- 打开神经网络补偿,调RBF节点数、中心宽度和权重学习率;
- 整个系统稳定后,回头尝试减小滑模增益,观察神经网络是否真正承担了大部分补偿工作。
每步只动一组参数,做完一步再进入下一步,这样一旦出问题能立刻锁定是哪块引入的不稳定因素。
6. 仿真验证:如何从跑通到结果可信
6.1 仿真场景设计
参考轨迹我选择匀速圆周轨迹,圆心在(0,10),半径10米:
[ x_d(t) = 10 \sin(0.05t), \quad y_d(t) = 10 - 10 \cos(0.05t) ]
圆形轨迹的优势在于,它的期望艏向和期望速度都是时变的,能够持续激励系统动态,检验控制器的跟踪能力。船从约(0,0)出发,初始艏向5度,初始速度为零,给控制器一个明确的收敛过程可以观察。仿真时长180秒,足以覆盖多个运动周期。
外部扰动设置为多个频率叠加的正弦信号,再加一个分段平稳的“阵风型”信号:
| 通道 | 扰动表达式 |
|---|---|
| du | 2 sin(0.1t) + 阵风信号 |
| dv | 1.5 cos(0.15t) |
| dr | 0.3 sin(0.2t) |
6.2 参考参数与模型参数
仿真模型参数我采用一组典型的实验船模型数值,控制器参数如下:
| 参数 | 取值 | 说明 |
|---|---|---|
| m11, m22, m33 | 200, 250, 80 | 惯性及附加质量 |
| d11, d22, d33 | 70, 100, 50 | 线性阻尼系数 |
| λu, λψ | 1.0, 2.0 | 滑模面系数 |
| εu, εψ | 0.05, 0.05 | 边界层厚度 |
| γλ | 5.0 | 自适应学习率 |
| σλ | 0.05 | 自适应修正系数 |
| RBF节点数 | 25 | 隐藏节点数 |
| 宽度b | 0.8 | 高斯基函数宽度 |
| 权重学习率Γ | 0.5 | 网络更新速率 |
这些参数是一组可复现的起点,不是唯一解。不同船模参数和控制目标下,往往需要小幅调整。
6.3 应该看到的仿真曲线形态
一套正常的仿真结果应该能观察到几个特征:
第一,在x-y平面内,船的实际轨迹开始会有一个明显的暂态偏离,随后逐渐收敛到圆形参考轨迹附近,最终形成一条与参考圆几乎重合但存在微小误差的轨迹曲线。
第二,位置误差ex、ey在前5秒内通常会有一个尖峰,之后快速衰减,最终稳定在一个有界邻域内。以半径10米的圆形轨迹为例,稳态位置误差如果压缩到0.1米量级,这个结果已经相当不错。如果误差在0.5米以上持续徘徊,优先检查外环视线角增益和艏向跟踪内环的收敛性。
第三,控制输入曲线τu、τr不应出现持续高频振荡,只应在初始收敛段出现一次明显的幅值变化,之后保持平滑。若出现锯齿状曲线,说明ε太小或者自适应学习率太高。
第四,打开数据记录后,把神经网络估计值和真实聚合不确定性画在同一张图上,可以看到估计曲线基本贴合真实曲线,相位差很小。如果偏差很大,说明RBF中心点覆盖不足或者学习率太低。
6.4 鲁棒性验证不能省
我强烈建议完成标称仿真后,把模型参数摄动15%到20%,外部扰动幅度翻倍,再做一次完全相同的仿真。如果一个控制方案能在参数摄动和扰动增强的情况下仍然保持位置误差有界、控制输入不饱和,那才算真正可信。
这一步骤看起来简单,实际上能淘汰大量“仿真好看但只在标称模型下成立”的设计。我见过不少所谓先进控制器,参数一改就发散,问题往往出在李雅普诺夫分析里隐含了太多理想的精确抵消假设。多做几组参数摄动实验,风险会小很多。
7. 调试踩坑记录与拓展方向
7.1 atan2角度跳变导致控制量尖峰
这是整个项目里最典型的一个坑。外环用atan2反解期望艏向时,位置误差符号发生变化,会在±π附近产生角度跳变。滑模面里(ψ-ψd)瞬间跳变几度,控制器立刻给出一次巨大的控制动作,引起轨迹尖峰和仿真变慢。
解决思路有两种:一是对角度误差做周期性归约,确保跟踪误差始终处于(-π, π]区间;二是不要直接让ψd跳变,而是给期望艏向设计一个平滑的动态参考。两种办法都可行,我实现时更推荐第二种,因为它同时还能限制艏向变化率,避免执行器速率饱和。
7.2 ode45与符号函数导致的步长灾难
用ode45求解包含sign函数的滑模控制,仿真速度可能差几十倍。原因很简单,符号函数在零点附近产生不连续切换,变步长求解器被迫把步长压到极小。换用tanh或者饱和函数后,整个系统连续可微,步长恢复正常,仿真时间从半小时缩短到几秒。
这也是我在前文反复强调用连续化的实务原因,不是为了数学上的简便,而是为了实际的仿真效率。
7.3 RBF中心点覆盖不足导致的发散
如果在某一段运行区间中心点没有覆盖到,网络基函数输出可能接近零,补偿值被“静默”掉。遇到这种情况,系统的表现是:一部分工况下跟踪很好,换一个参考轨迹或者初始位置后,误差突然变大甚至发散。
我的预防办法是在调试阶段对网络输出做限幅保护,即使权重暂时异常,控制输出也不会飞出合理范围。这个保护并不影响最终稳态精度,但它能在参数出错时给你留出观察和定位的时间。
7.4 调参的黄金顺序
最后再把调参顺序压成一条经验:先让系统在名义模型下“能稳”,再开扰动“测稳”,最后开网络补偿“求准”。很多人一上来就同时调五个参数,结果是所有曲线全乱,完全无法判断问题出在哪一环。
7.5 这个框架还能往哪里扩展
这套控制器具备一定的泛化能力,后续拓展方向也很明确:考虑执行器饱和约束,在控制律里加入抗饱和补偿;结合视线角制导与高阶滑模,进一步抑制抖振;把单船控制推广到领航者-跟随者编队结构;甚至与强化学习结合,让外层视线角策略也能在线优化。每个方向都值得单独开项目深入。
我在实际测试中最深的体会是:理论推导提供的是一份“确定性”的保证,工程上真正的功夫在于如何把连续时间设计落到ode45的每一步积分、每一次权重更新和每一处曲线观察里。把公式变成能稳定复现的仿真结果,这中间的细节,往往比公式本身更值得反复打磨。