做轨迹跟踪控制研究有一段时间了,说实话,最开始我用的一直是LQR和带线性预测模型的MPC。这两种方法在小曲率、中等车速工况下表现都不错,可一旦进入高速双移线或者紧急避障这类极限工况,控制效果就变得不太可靠。后来我把车辆动力学模型升级为带非线性轮胎特性的形式,再配合带约束的非线性模型预测控制(NMPC),在Matlab里搭了一套完整的仿真环境,才把这个问题真正解决。这篇文章我会从模型、控制器设计、代码实现再到参数调试,完整梳理一遍,适合正在做车辆横向控制、和想入坑NMPC的同学参考。
1. 从LQR到NMPC:是什么让我决定换控制策略
1.1 线性控制方案在极限工况下的失效表现
先说一个典型的失败场景:车速80km/h,前方出现障碍物,需要做一次紧急换道。用LQR设计一个横向跟踪控制器,在直线和小弯道时效果很理想,横向误差能控制在几个厘米以内。但当车道曲率突然变大,车辆需要产生较大的侧向加速度时,线性模型里“轮胎侧向力与侧偏角成正比”的假设就不成立了。轮胎进入非线性区后,实际侧向力会小于线性模型的预测值,控制器给出的前轮转角偏大,车辆实际横摆角速度跟不上期望值,表现为转向过度或者转向不足,严重的直接甩出参考轨迹。
线性MPC虽然比LQR多了一个“预测”环节,能在一定程度上提前规划控制序列,但它的预测模型仍然是线性化的。如果工作点变化剧烈,比如从直线工况切换到半径80米的弯道工况,线性化矩阵需要频繁更新,一旦更新不及时,预测出来的状态轨迹就和真实车辆行为偏离很大。我在仿真中见过最典型的现象是:MPC在预测时域内认为“当前转角可以让车辆回归参考路径”,但实际上车辆的真实响应比预测慢半拍,导致每个周期都在修正上一步的错误,控制量来回摆动。
1.2 约束处理才是工程落地的关键
还有一个容易被忽略的问题:约束。实际车辆的前轮转角有物理上限,通常乘用车在±35度到±40度左右,横摆角速度也受到轮胎附着极限的约束。LQR只能通过调节权重间接地“避免”控制量过大,无法硬性限制它;线性MPC理论上可以加约束,但因为预测模型是线性的,当系统状态远离线性化工作点时,约束边界对应的真实物理状态已经不一样了。比如线性模型里限制前轮转角不超过0.5rad,在低速小转角时是安全的,但在高速大转角时,同样的0.5rad可能已经超过轮胎附着极限。
NMPC把非线性模型和约束放在同一个优化框架里求解,每个预测步都用真实的非线性状态方程递推,并且可以在任意状态量上直接加约束——无论是控制量边界还是状态量边界,都能统一处理。这才是让我下决心换策略的根本原因。不是说LQR和线性MPC没用,而是它们在“极限工况+硬约束”这个组合下确实力不从心。
2. 车辆动力学建模:单车模型与轮胎特性的取舍
2.1 为什么选“自行车模型”而不是更复杂的模型
车辆动力学建模是个很开放的问题。四轮整车模型有十几个自由度,包括侧倾、俯仰、垂向载荷转移、悬架变形等等,精度高但计算量也高。做NMPC仿真时,每个控制周期要预测几十步,每一步都要算一次模型方程,如果模型本身太复杂,求解时间会爆炸。所以我最终选择了经典的自行车模型(Bicycle Model),也叫单轨模型。
这个名字有点误导,其实不是说车只有两个轮子,而是把前轴左右轮合并成一个等效车轮,后轴左右轮合并成一个等效车轮,忽略左右载荷差异和车辆侧倾。这个模型的核心假设是:车辆纵向速度变化较慢,质心侧偏角较小,轮胎侧向力与侧偏角的关系可以用一个函数来描述。对于横向控制研究来说,这个精度已经足够。
自行车模型的状态量包括:全局坐标下的位置X、Y,横摆角φ,质心侧偏角β,横摆角速度ω,以及纵向速度vx。控制量是前轮转角δ和纵向加速度a。完整的状态方程是这样的:
Ẋ = vx * cos(φ + β) Ẏ = vx * sin(φ + β) φ̇ = ω β̇ = (Fyf * cosδ + Fyr) / (m * vx) - ω ω̇ = (lf * Fyf * cosδ - lr * Fyr) / Iz v̇x = a其中m是整车质量,Iz是绕Z轴的转动惯量,lf和lr分别是质心到前轴和后轴的距离。Fyf和Fyr是前后轴的侧向力,它们的计算方式取决于轮胎模型的选择。
2.2 轮胎模型:从线性到非线性的关键一步
轮胎侧向力的建模是整个车辆模型里最微妙的地方。线性模型只用一个侧偏刚度Cα来描述:Fy = Cα * α。在小侧偏角(一般小于5度)时,这个关系基本准确。但侧偏角超过这个范围后,侧向力增长变缓,最终达到饱和,这个饱和值由路面附着系数决定。
我用了两种轮胎模型做对比。第一种是线性模型,方便调试控制逻辑;第二种是魔术公式(Magic Formula),它是目前工程上最广泛使用的半经验轮胎模型,形式是:
Fy = D * sin(C * atan(B * α - E * (B * α - atan(B * α))))参数B、C、D、E分别控制曲线的斜率、形状、峰值和曲率特性。D基本等于峰值侧向力,和路面附着条件直接相关;B*C的乘积决定了初始线性段的侧偏刚度;E控制曲线在峰值附近的下弯程度。下表是我在仿真中用的两组对照参数:
| 参数 | 线性模型 | 魔术公式模型 |
|---|---|---|
| 侧偏刚度 | Caf=80000 N/rad | 由B*C隐含表达 |
| 前轮C | 无 | 1.4 |
| 前轮D | 无 | 由附着系数决定 |
| 前轮B | 无 | 0.625 / (C*D) |
| 峰值侧向力 | 无上限 | 约等于 μ * 轴荷 |
之所以要做这个对比,是因为线性模型会让NMPC的预测“过于乐观”。在优化器看来,转角越大侧向力越大,永远不会饱和,所以它可能给出一个超过物理极限的控制序列。而在实际车辆或者高精度车辆模型(比如CarSim和Simulink联合仿真)里,这个控制序列早就让轮胎进入非线性区了。这也是很多数学上看起来很漂亮的MPC算法,一接到真实车辆模型上就发散的根本原因。
2.3 状态约束的物理来源
建模时还需要关注状态量本身的物理限制。质心侧偏角β是个很关键的状态量,在干沥青路面上,极限侧偏角大约在10到12度;在湿滑路面(附着系数0.3左右)上,这个值会急剧下降到3到4度。横摆角速度ω也受到附着极限的限制,理论上满足|ω| ≤ μg/vx,也就是侧向加速度不能超过轮胎附着力允许的最大值。
这些限制在控制器设计里必须体现出来,否则优化器很可能给出一个理论上算得出来但实际根本无法执行的轨迹。举个例子,如果路面附着系数是0.85,车速25m/s,那么侧向加速度上限约8.3m/s²,对应的横摆角速度上限约0.33rad/s。如果不加这个约束,NMPC可能会为了让横向误差更快收敛而生成一个0.5rad/s的横摆角速度指令——这在物理上根本不可能实现,在实际装车时就会导致车辆失控。
3. NMPC求解器设计:成本函数、约束条件与数值实现
3.1 预测时域内的数值积分方式
NMPC的核心思想是在每个采样时刻,用当前状态作为初值,在预测时域内向前滚动求解一个带约束的有限时域最优控制问题。预测模型是连续微分方程,要变成计算机可求解的形式必须离散化。这里涉及一个关键选择:用欧拉法还是龙格库塔法(RK4)。
欧拉法最简单,形式是x(k+1) = x(k) + dt * f(x(k), u(k)),每个预测步只调用一次模型方程。但它是一阶精度,当预测步长取0.1秒时,积累的截断误差已经不可忽略。我在调试中发现,用欧拉法做预测时,NMPC在80km/h双移线工况下的最大横向误差比RK4高出30%左右,而且控制量序列明显更抖。
RK4在每个预测步内做四次函数求值,精度高得多,代价是计算时间大约增加三到四倍。但对于采样周期50ms的控制系统来说,这个代价通常是可以接受的。我的建议是直接用RK4,不要在这个环节省时间。预测模型精度不够,后面控制器做再好也白搭。
3.2 成本函数设计与权重调节逻辑
NMPC每一时刻要最小化的成本函数一般写成这样的形式:
J = Σ_{k=0}^{N-1} (Δx_k' * Q * Δx_k + u_k' * R * u_k) + Δx_N' * P * Δx_N其中Δx_k是预测状态和参考状态之间的误差,Q是状态误差权重矩阵,R是控制量权重矩阵,P是终端代价权重矩阵。这个结构本身不复杂,难点在于Q和R怎么取。
我做了一个相对细致的权重设计。状态量有六个维度的偏差:X方向位置、Y方向位置、横摆角、质心侧偏角、横摆角速度、纵向速度。Q矩阵把这六个维度的惩罚系数区分开:
Q = diag([50; 80; 30; 5; 15; 10])横向位置误差Y的权重最高,这是跟踪控制的首要目标;纵向位置误差X的权重次之,主要防止车辆在轨迹上滞后;横摆角的权重用来保证车辆姿态正确;横摆角速度的权重抑制过快旋转;质心侧偏角的权重一般给小一些,因为正常工况下它本身就不大。
R矩阵惩罚控制量的大小,包括前轮转角平方和纵向加速度平方:
R = diag([10; 5])这里有个很容易犯的错误:把R控制量的惩罚设得太大,结果控制器变得非常“懒”,前轮转角变化很慢,横向误差收敛也慢。我的经验是,R的主要作用是防止控制量高频抖动,而不是限制控制量的大小。如果觉得控制量太大,优先检查约束条件是否合理,而不是盲目加大R。
3.3 约束条件的数学表达与可行性处理
NMPC能处理约束是和线性MPC最大的区别之一。我设置的约束分两类:
控制量约束:
δ_min ≤ δ_k ≤ δ_max a_min ≤ a_k ≤ a_max状态量约束:
|β_k| ≤ β_max |ω_k| ≤ ω_max控制量约束属于硬约束,因为执行机构的物理极限是硬性的,不能突破。状态量约束则需要小心,如果设置得过紧,优化器可能找不到可行解,尤其在紧急避障这类极端工况下。一个稳妥的做法是把状态约束写成软约束,引入松弛变量ε,在成本函数里加一个大的惩罚项M * ε²:
|β_k| ≤ β_max + ε |ω_k| ≤ ω_max + ε这样优化器在“必须满足硬约束”和“尽量满足软约束”之间自动做权衡。紧急情况下可以短暂突破状态约束,但会付出很大的成本代价。这个设计在实际调试中帮了大忙,很多“求不出解”的问题都是靠软约束解决的。
3.4 fmincon和CasADi的选择
在Matlab环境下求解带约束的非线性优化问题,最直接的工具是fmincon。它内置了几种算法,我推荐用'sqp'(序列二次规划)或'interior-point'(内点法)。SQP算法在NMPC问题中表现比较稳定,对初始猜测的敏感度相对较低,而且约束处理能力强。内点法也可以,但当约束条件较多时求解时间会明显增加。
我用fmincon完成了一套可以跑的NMPC控制器,优点是零额外依赖,Matlab自带优化工具箱就能运行;缺点是求解速度一般。在预测时域N=25、每个控制周期内需要求解25步非线性优化问题的情况下,fmincon单次求解时间大约在40到60毫秒。在很多仿真场景下这个速度够用,但如果后续要接实时硬件,或者预测时域拉长到50步以上,建议换成CasADi配合IPOPT求解器,利用稀疏性和底层C++加速,求解速度通常能提升一个数量级。
4. Matlab仿真代码的核心架构与关键函数实现
4.1 仿真主循环的骨架设计
整个Matlab仿真程序遵循“模型-控制器-仿真环境”三者分离的原则。模型文件只负责微分方程计算,控制器文件只负责优化求解,主脚本负责初始化、生成参考轨迹、调用控制器和模型、记录数据、画图。这样每次修改某个环节时,不需要动其他部分。
主循环的伪代码如下:
%% 仿真主循环 T_sim = 8; % 仿真总时长 dt = 0.05; % 控制周期 t = 0:dt:T_sim; N_sim = length(t); % 初始化状态 xi = x0; u_prev = zeros(N, 2); % 用于热启动的上一周期控制序列 for k = 1:N_sim-1 % 从参考轨迹中截取当前时刻的预测时域序列 ref_seq = build_ref_seq(ref_all, k, N); % 调用NMPC求解器,返回整个预测时域内的最优控制序列 [u_opt, exitflag] = nmpc_solve(xi, ref_seq, u_prev, param); % 只施加第一个控制量 u_applied = u_opt(1, :); % 用高阶精度的车辆模型(真实车辆)仿真一步 xi = rk4(@vehicle_dynamics_cont, xi, u_applied, dt, param); % 记录历史状态和控制量 x_history(:, k+1) = xi; u_history(k, :) = u_applied; % 热启动:把旧控制序列平移一个周期作为下一次的初值 u_prev = [u_opt(2:end, :); u_opt(end, :)]; end注意一个细节:我把“预测模型”和“被控车辆模型”分开处理。预测模型用于NMPC内部递推,可以被简化以提高求解速度;被控车辆模型用于仿真环境中的真实状态更新,可以更精细。在严格验证控制器性能时,应该让这两个模型不完全一致,比如预测模型用线性轮胎模型,仿真模型用魔术公式轮胎模型,这样可以检验控制器对模型失配的鲁棒性。
4.2 核心函数:车辆动力学模型
这是最关键的一段代码,车辆模型函数接收当前状态和控制量,返回状态导数,再交给RK4积分器做离散化:
function dxi = vehicle_dynamics_cont(xi, u, param) % 状态: xi = [X; Y; phi; beta; omega; vx] % 控制: u = [delta; a] X = xi(1); Y = xi(2); phi = xi(3); beta = xi(4); omega = xi(5); vx = xi(6); delta = u(1); a = u(2); % 防止低速时除零 if vx < 0.1 vx = 0.1; end % 前后轮侧偏角,采用近似形式 alpha_f = delta - beta - param.lf * omega / vx; alpha_r = -beta + param.lr * omega / vx; % 轮胎侧向力(这里用魔术公式,也可以在param里切换线性模型) if param.use_magic_formula Fyf = magic_formula_force(alpha_f, param.Cf); Fyr = magic_formula_force(alpha_r, param.Cr); else Fyf = param.Caf * alpha_f; Fyr = param.Car * alpha_r; end % 运动学和动力学方程 dX = vx * cos(phi + beta); dY = vx * sin(phi + beta); dphi = omega; dbeta = (Fyf * cos(delta) + Fyr) / (param.m * vx) - omega; domega = (param.lf * Fyf * cos(delta) - param.lr * Fyr) / param.Iz; dvx = a; dxi = [dX; dY; dphi; dbeta; domega; dvx]; end魔术公式的实现也不复杂:
function Fy = magic_formula_force(alpha, tyre) % tyre包含B, C, D, E四个参数 B = tyre.B; C = tyre.C; D = tyre.D; E = tyre.E; Fy = D * sin(C * atan(B * alpha - E * (B * alpha - atan(B * alpha)))); end这里需要注意侧偏角的符号约定。不同的教材和代码库可能使用不同的符号方向,需要保持一致。我在调试时遇到过因为侧偏角符号相反导致控制器输出方向完全反了的问题,查了很久才发现。
4.3 核心函数:NMPC求解器
NMPC求解器是整个程序的核心优化环节。我基于fmincon封装了一个函数,把控制序列作为决策变量,把状态约束写成非线性不等式约束,把成本函数写成目标函数。
function [u_opt, exitflag] = nmpc_solve(x0, ref_seq, u_init, param) N = param.N; n_u = 2; % 控制量维度 % 决策变量是全部预测时域内的控制序列,展开为一个列向量 dec0 = reshape(u_init, N*n_u, 1); % 控制量上下界 lb = [repmat(param.delta_min, N, 1); repmat(param.a_min, N, 1)]; ub = [repmat(param.delta_max, N, 1); repmat(param.a_max, N, 1)]; % 定义目标函数 costfun = @(dec) cost_function(dec, x0, ref_seq, param); % 定义非线性约束 nonlcon = @(dec) constraints_function(dec, x0, ref_seq, param); % fmincon选项 options = optimoptions('fmincon', ... 'Algorithm', 'sqp', ... 'Display', 'off', ... 'MaxIterations', 150, ... 'OptimalityTolerance', 1e-4, ... 'StepTolerance', 1e-6, ... 'ConstraintTolerance', 1e-4); [dec_opt, ~, exitflag] = fmincon(costfun, dec0, ... [], [], [], [], lb, ub, nonlcon, options); u_opt = reshape(dec_opt, N, n_u); end目标函数的实现逻辑是:从当前状态出发,用RK4递推每一个预测步的状态,计算与参考轨迹的偏差和控制量惩罚,累加得到总成本。需要特别注意的是,在成本函数内部调用RK4做递推时,每一步的模型函数不能和仿真环境中的模型函数产生冲突。我的做法是把预测模型的函数句柄也作为参数传递进去,方便以后替换模型。
4.4 成本函数与约束函数的实现细节
成本函数看起来简单,但有一个容易出错的点:角度差要归一化到[-π, π]。如果不做这个处理,当横摆角在π和-π之间跨界时,误差会突然从一个小值跳到一个接近2π的大值,导致控制器误以为巨大的角度误差需要剧烈修正,实际车辆行为就会奇怪地抖动。Matlab的wrapToPi函数就是干这个用的。
function J = cost_function(dec, x0, ref_seq, param) N = param.N; n_u = 2; U = reshape(dec, N, n_u); Q = param.Q; R = param.R; P = param.P; xi = x0; J = 0; for k = 1:N xi_next = rk4(@vehicle_dynamics_cont, xi, U(k, :), param.dt_pred, param); % 计算状态偏差 dx = xi_next(1) - ref_seq(1, k); dy = xi_next(2) - ref_seq(2, k); dphi = wrapToPi(xi_next(3) - ref_seq(3, k)); dbeta = xi_next(4) - ref_seq(4, k); domega = xi_next(5) - ref_seq(5, k); dvx = xi_next(6) - ref_seq(6, k); x_err = [dx; dy; dphi; dbeta; domega; dvx]; J = J + x_err' * Q * x_err + U(k, :) * R * U(k, :)'; xi = xi_next; end % 终端代价,使用上一次预测的误差 J = J + x_err' * P * x_err; end约束函数里,除了控制量的上下界在fmincon的lb/ub里直接设置之外,状态约束要写成非线性不等式约束c(x) ≤ 0的形式:
function [c, ceq] = constraints_function(dec, x0, ref_seq, param) N = param.N; n_u = 2; U = reshape(dec, N, n_u); xi = x0; c = []; ceq = []; for k = 1:N xi_next = rk4(@vehicle_dynamics_cont, xi, U(k, :), param.dt_pred, param); % 质心侧偏角约束(带松弛变量,作为软约束) beta_k = xi_next(4); eps_beta = max(0, abs(beta_k) - param.beta_max); c = [c; eps_beta]; % 这行可以再配合大惩罚项 % 实际上更合理的做法: % c1 = xi_next(4) - param.beta_max - param.eps_beta; % c2 = -param.beta_max - param.eps_beta - xi_next(4); % 松弛变量eps_beta也作为决策变量的一部分 xi = xi_next; end end严格来说,引入松弛变量后,松弛变量本身也是决策变量,需要在fmincon中一起优化。如果为了简化不想增加决策变量维度,也可以退一步,只对硬约束设置一个足够宽松的边界(比如β_max取5度),让优化器大概率能找到可行解。在仿真阶段这样做问题不大,但如果要做严谨的研究,还是应该把松弛变量完整地做进去。
5. 仿真实验与参数调节:哪些量最影响NMPC表现
5.1 权重矩阵的调节顺序
我做完第一版NMPC后,遇到最多的问题不是算法不收敛,而是调参。Q和R矩阵动一个数字,整个动态特性都会变。我总结了一套适合车辆轨迹跟踪的调参顺序,避免无头苍蝇一样乱试。
第一步:先把R矩阵设得非常小,比如diag([0.1, 0.1])。此时控制器几乎不受控制量惩罚,会尽可能快地消除跟踪误差。如果这时候出现控制量抖动或者发散,说明问题出在别的地方,先解决稳定性再调权重。
第二步:增大Q中的横向位置误差权重,保持其他权重默认,观察车辆在双移线工况下的横向跟踪误差。目标是把最大横向误差压缩到一个合理范围,比如0.2到0.3米。如果横向误差总是降不下来,大概率是预测时域太短,而不是权重不够。
第三步:加回R矩阵的惩罚,从diag([1, 1])开始逐步增大,观察前轮转角序列的平滑度。当转角不再出现明显的高频抖动时,说明R已经能起到阻尼作用。此时再看横向误差,如果增大得过多,就回调一点。
第四步:最后调状态量约束的松紧度。把β_max和ω_max从宽松值逐步收紧,直到优化器偶尔出现“近似可行但求解时间变长”的情况为止。这个边界就是工程上的安全边界,再紧就会频繁触发不可行。
这套流程走下来,通常能在一个小时左右收敛到一组可用的参数,比直接堆数值然后凭感觉微调要高效得多。
5.2 预测时域与控制时域的影响
预测时域N是NMPC里最敏感的超参数之一。N太小,控制器“目光短浅”,看不到远处的弯道,等发现偏差时已经来不及修正;N太大,决策变量维度增加,求解时间急剧上升。我在同一组参数下做了N=10、25、40三组对比,结果如下:
| 预测时域N | 最大横向误差(m) | 单步求解耗时(s) | 控制器表现 |
|---|---|---|---|
| 10 | 0.62 | 0.018 | 弯道明显跟踪延迟,转角抖动 |
| 25 | 0.24 | 0.045 | 跟踪误差可接受,转角较平顺 |
| 40 | 0.18 | 0.12 | 跟踪效果最好,但实时性紧张 |
折中下来我选了N=25。因为我的控制周期是50ms,45ms的求解时间已经占掉了90%的周期预算,如果还要处理传感器延迟或者额外逻辑,就非常紧张。这里也体现出一个工程观念:仿真里追求最好的控制效果当然没问题,但如果是往实车部署方向走,必须为系统鲁棒性留出时间余量。
控制时域Nc和预测时域N可以不同。我试过把控制时域设为5,预测时域保持25,结果优化自由度大幅下降,求解时间降到20ms左右,但跟踪误差略有增大。原因是前5步之后控制量被强制保持恒定,无法根据预测误差灵活调整。在车辆这类快动态系统中,建议控制时域不要小于预测时域的一半。
5.3 双移线工况下的实测表现
双移线测试是验证车辆横向控制器的经典工况,它模拟了高速公路上遇到障碍物后紧急换道再回到原车道的过程。我用Matlab生成了双移线参考轨迹,在80km/h车速下做了闭环仿真。
第一次跑线性轮胎模型的NMPC,最大横向误差在0.35米左右,轨迹基本能跟住,但前轮转角曲线有明显的毛刺。把预测模型换成魔术公式轮胎模型后,最大横向误差降到0.22米,转角曲线也平滑了很多。原因很简单:魔术公式模型更准确地反映了轮胎在大侧偏角下的力饱和特性,优化器知道“多打方向并不能无限增加侧向力”,所以控制策略更保守也更合理。
我还试过把控制量变化率也加入成本函数,用Δu = u_k - u_{k-1}的平方作为惩罚项。这个改动对转角平滑度的改善非常明显,转角抖动幅度降低了大约60%,而横向误差只增加了0.03米左右。如果做的是面向实车的控制器,强烈建议加这一项,它模拟了转向执行器的有限带宽特性。
5.4 低附着路面下的约束压力
把路面附着系数从0.85降到0.3,相当于模拟冰雪路面。这时候NMPC的表现变得很有意思,如果不设置横摆角速度约束,控制器在双移线工况下会输出一个很大的转角序列,车辆模型计算出很高的横摆角速度,仿真结果直接出现明显的侧滑,车辆实际轨迹和参考轨迹南辕北辙。原因是预测模型里轮胎力峰值变小,但优化器依然试图用大转角来快速消除横向误差。
加上横摆角速度约束|ω| ≤ μg/vx之后,控制器被迫放缓转向速率。最大横向误差明显增大,但车辆状态始终保持在可控范围内,没有失控。这是NMPC约束设计价值最直观的体现:它不只是让控制器“听话”,更是让控制器“知道物理极限在哪里”。在低附着路面上,这个能力比任何权重调节都管用。
6. 排查记录:从求解失败到仿真发散,实用避坑指南
6.1 fmincon报“无可行解”的完整排查链路
NMPC开发过程中最让人头疼的错误就是fmincon返回“No feasible solution found”。我第一次遇到时以为是模型写错了,折腾了很久才发现是约束冲突。后来我总结了一条固定的排查链路,每次都按这个顺序查,效率高很多。
第一步:检查lb和ub有没有“上下边界写反”的低级错误。比如前轮转角上限设为-0.5、下限设为0.5,这种错误的后果是所有控制序列都不可能满足边界条件。
第二步:检查预测时域内的参考轨迹有没有越界。参考轨迹序列的长度必须大于等于N,如果轨迹只剩5个点而预测时域是25,超出部分读到的是0,相当于要求车辆瞬间飞到原点,当然找不到解。
第三步:把状态约束暂时全关掉,只保留控制量约束。如果这时候能算出解,说明问题出在状态约束上。然后逐个加回状态约束,每加一个跑一次仿真,就能快速定位是哪个约束导致不可行。
第四步:如果确认某个状态约束确实会导致不可行,就把这个约束改成软约束,或者放宽到物理上更合理的范围。比如β_max设成0.2rad发现经常无解,先放宽到0.3rad看是否改善,如果改善明显,说明问题可能是预测模型在极端工况下给出的侧偏角本身就偏大。
这条链路我用了很多次,基本能在半小时内定位问题。关键思路是“一次只改变一个变量”,同时观察优化器的行为变化。
6.2 仿真发散的根本原因定位
仿真发散是另一个高频问题,表现形式通常是状态量在某一步突然跳到NaN或者无穷大。我遇到过三种情况,对应的解决办法完全不同。
第一种:低速除零。车辆模型里有除以vx的项,当车辆起步阶段vx接近0时,这个除法会导致无穷大。解决方法是加一个下限判断,vx小于某个阈值时强制设为一个小的正数,或者改用通过atan计算侧偏角的精确形式来避免除法。
第二种:数值积分不稳定。RK4的步长如果太大,在某些快速动态下可能数值发散。车辆动力学虽然不算刚性的系统,但当转向角变化很剧烈时,横摆角速度也会快速变化。检查方法是缩短积分步长看结果是否收敛,如果缩短后状态不再发散,说明是步长问题。
第三种:参考轨迹不连续。我在生成双移线轨迹时,如果直接用分段函数定义路径,连接处可能出现位置的跳变。控制器看到参考轨迹瞬间位移一大截,会给出极大的控制量,导致车辆模型发散。解决方法是对参考轨迹做平滑处理,比如用一个低通滤波器或者样条插值过度连接。这个坑特别隐蔽,因为从轨迹图上肉眼看不出明显断点,但NMPC的预测模型对参考输入的一阶导数很敏感。
6.3 实用小技巧汇总
最后分享几个在整个项目过程中积累的小技巧,这些细节不太会写进论文里,但对实际开发非常有帮助。
热启动是NMPC工程化最关键的优化手段。上一时刻求解得到的最优控制序列,去掉第一项、末尾补上一项,就直接作为当前时刻的初始猜测。这比用全零初始猜测的求解速度快2到3倍,而且更容易收敛到同一个局部最优解。我实测用热启动之后,fmincon的迭代次数从平均60次降到25次左右。
在绘制结果时,把每个预测步的中间状态也保存下来,而不仅仅是实际施加的那一步。这样当跟踪效果不好时,可以快速查看预测模型内部的预测轨迹,判断是模型预测不准还是控制器权重不合理。如果预测轨迹和实际轨迹贴合很好,说明模型没问题,问题出在控制参数上;如果两者偏差很大,说明模型需要修正。
终局代价矩阵P的作用经常被低估。没有终端代价时,预测时域末端的误差不会被惩罚,优化器可能为了前几步的跟踪效果而让末端状态往错误方向飘。P可以简单设为Q的一个倍数,比如P = 5 * Q,效果就已经很明显。更严谨的做法是从LQR黎卡提方程中解出终端代价矩阵,但在仿真阶段,倍数形式已经足够。
CarSim和Simulink联合仿真是另一个值得尝试的方向。我在Matlab里验证完控制算法后,曾经把车辆模型替换成CarSim的21自由度模型跑了一遍对比验证,发现NMPC在简单模型上调试出来的参数在高精度模型上依然有效,只是横向误差略有增大。这说明用自行车模型加魔术公式做NMPC预测,配合高精度车辆模型做仿真验证,是一个非常合理的开发路径——既保留了NMPC设计阶段的快速迭代优势,又在验证阶段用高保真模型兜底。
关于求解时间还有一个建议。如果fmincon单步求解时间不稳定,一会儿快一会儿慢,最常见的原因是初始猜测质量差,导致优化器在不同迭代次数之间反复横跳。除了热启动之外,还可以限制fmincon的MaxIterations,比如设为100到150次。如果迭代次数到了但还没收敛,那就接受当前的近似最优解,下一周期用热启动继续优化,而不是让当前周期超时。这种“截断优化”的思想在实时NMPC里非常重要。
这套项目做下来,我对车辆动力学和非线性模型预测控制都有了更具体、更落地的理解。以前看论文里NMPC的公式推演没什么感觉,直到自己亲手写过一遍模型方程、调过一组Q/R权重、排查过一次无可行解的问题,才真正明白这个领域的门槛到底在哪里。仿真发散的时候不要急着怀疑算法理论有问题,先从模型、数值积分、约束、参考轨迹这几个最基础的地方查起,90%的问题都会浮出水面。希望这篇文章能帮你少走一些我走过的弯路。