第2章:车辆运动学模型:自行车模型推导、状态空间方程建立、离散化方法

各位同学,欢迎来到第二章。上一章我们聊了MPC的整体框架,今天咱们要啃一块硬骨头——车辆运动学模型。

说实话,很多做MPC的同学,最后调参调不好,根子就出在模型上。模型不准,你控制器设计得再花哨,也是白搭。我当年刚入行时,就吃过这个亏,辛辛苦苦调了一周的参数,实车一跑,直接偏到隔壁车道去了。后来才发现,是模型简化得太狠了。

2.1 为什么是自行车模型?

你想想看,一辆真正的汽车,四个轮子,每个轮子都有转向、悬架、轮胎侧偏……如果全建模进去,那状态变量得有几十个。MPC算起来,别说实时了,一帧都算不完。

所以,我们得简化。自行车模型(Bicycle Model)就是最经典的简化方式。它把左右两个前轮合并成一个虚拟前轮,左右后轮合并成一个虚拟后轮。说白了,就是想象你的车变成了一辆自行车。

核心假设:

  • 忽略车辆的侧倾和俯仰(只考虑平面运动)
  • 左右车轮的转向角相同(阿克曼转向几何的近似)
  • 轮胎的侧偏特性简化为线性(小角度假设)
  • 车辆纵向速度恒定(或者变化缓慢)

嗯,这里要注意。这些假设在低速(比如泊车、拥堵跟车)时非常准。但如果你开到80km/h以上做紧急避障,轮胎侧偏就会变得很严重,这时候自行车模型就不够用了。我在项目中遇到过,高速场景下用自行车模型做MPC,控制效果明显变差,后来换成了动力学模型才解决。

2.2 自行车模型的数学推导

好,咱们开始推公式。别怕,我会一步步来。

首先,定义车辆的状态。通常我们用三个量来描述车辆的位置和朝向:

  • x:车辆后轴中心在大地坐标系下的X坐标
  • y:车辆后轴中心在大地坐标系下的Y坐标
  • ψ:车辆的航向角(yaw angle),也就是车头指向

控制输入有两个:

  • v:纵向速度(通常假设为常数,或者由上层规划给出)
  • δ:前轮转向角

为什么选后轴中心作为参考点?我个人习惯用后轴中心,因为后轴不转向,运动学关系更简洁。当然也有人用质心,但那是动力学模型的事了。

现在,我们来看车辆的运动关系。想象一下,后轮的速度方向永远沿着车身的纵轴方向(因为后轮不能转向)。所以:

ẋ = v * cos(ψ)
ẏ = v * sin(ψ)

那航向角的变化率呢?这就要看前轮了。前轮的转向角δ,决定了车辆转弯的曲率。根据几何关系,车辆的转弯半径R和转向角δ的关系是:

R = L / tan(δ)

其中L是轴距(前轮到后轮的距离)。而角速度ψ_dot = v / R,所以:

ψ_dot = v * tan(δ) / L

小技巧:当δ很小时(比如高速公路上微调方向),tan(δ) ≈ δ。这时候模型就变成了线性模型,很多控制理论可以直接套用。我刚开始做车道保持时,就用这个线性近似,效果还不错。

2.3 状态空间方程建立

有了上面的微分方程,我们就可以写出连续时间下的状态空间方程了。

状态向量:ξ = [x, y, ψ]^T

控制输入:u = [v, δ]^T

那么状态方程就是:

ξ_dot = f(ξ, u) = 
[ v * cos(ψ) ]
[ v * sin(ψ) ]
[ v * tan(δ) / L ]

你看,这是一个非线性系统。因为状态方程里含有cos(ψ)、sin(ψ)和tan(δ)。MPC处理非线性系统,要么用非线性MPC(NMPC),要么在某个工作点附近线性化。

我个人更推荐线性化。为什么呢?因为NMPC的计算量太大了,在ADAS的嵌入式平台上,算力有限,线性MPC更实用。

线性化的方法很简单——泰勒展开,取一阶项。假设我们在参考点(ξ_ref, u_ref)附近工作,那么:

ξ_dot ≈ f(ξ_ref, u_ref) + A * (ξ - ξ_ref) + B * (u - u_ref)

其中A和B是雅可比矩阵:

A = ∂f/∂ξ, B = ∂f/∂u

具体算一下:

A = [0, 0, -v_ref * sin(ψ_ref)]
    [0, 0,  v_ref * cos(ψ_ref)]
    [0, 0,  0]

B = [cos(ψ_ref), 0]
    [sin(ψ_ref), 0]
    [tan(δ_ref)/L, v_ref / (L * cos²(δ_ref))]

避坑指南:我曾经在写代码时,忘了把cos²(δ_ref)写成(cos(δ_ref))^2,结果分母算出来是cos(2*δ_ref),整个B矩阵都错了。这种细节错误,调试起来特别痛苦。建议大家写代码时,每一步都手动验算一下。

2.4 离散化方法

好,现在我们有连续模型了。但MPC是在数字计算机上运行的,必须把连续模型变成离散模型。离散化的方法有很多,我挑三种最常用的讲。

2.4.1 前向欧拉法(一阶近似)

这是最简单的方法,也是我最早学的方法。公式就是:

ξ(k+1) = ξ(k) + Ts * ξ_dot(k)

其中Ts是采样时间。代入我们的模型:

x(k+1) = x(k) + Ts * v(k) * cos(ψ(k))
y(k+1) = y(k) + Ts * v(k) * sin(ψ(k))
ψ(k+1) = ψ(k) + Ts * v(k) * tan(δ(k)) / L

优点:简单,计算量小。缺点:精度一般,当Ts较大时误差明显。

2.4.2 零阶保持法(ZOH)

对于线性化后的系统,我们可以用零阶保持法。假设控制输入在两个采样点之间保持不变,那么:

ξ(k+1) = A_d * ξ(k) + B_d * u(k)

其中:

A_d = e^(A * Ts)
B_d = ∫₀ᵀˢ e^(A * τ) dτ * B

这个矩阵指数计算起来有点麻烦,但在MATLAB里直接用c2d函数就行。在C++里,可以用Eigen库的矩阵指数函数。

我的习惯:在仿真阶段,我一般用ZOH法,精度高。但在实车代码里,为了实时性,我经常用前向欧拉法。只要Ts选得足够小(比如20ms以内),欧拉法的精度完全够用。

2.4.3 龙格-库塔法(RK4)

如果你对精度要求很高,又不想用ZOH那么复杂,RK4是个折中方案。它用四个中间步长来逼近积分结果,精度比欧拉法高一个数量级。

公式我就不展开了,大家记住一点:RK4的计算量是欧拉法的4倍,但精度提升很明显。我在做泊车辅助时,因为车速极低(< 5km/h),用欧拉法就够了。但在做高速车道保持时,我换成了RK4。

2.5 离散化后的状态空间方程

最终,我们得到离散化的线性时变模型:

ξ(k+1) = A_d(k) * ξ(k) + B_d(k) * u(k) + d(k)

其中d(k)是线性化带来的偏差项:

d(k) = ξ_ref(k+1) - A_d(k) * ξ_ref(k) - B_d(k) * u_ref(k)

这个模型,就是后续MPC优化的基础。每一时刻,我们都要根据当前的工作点重新计算A_d和B_d,然后求解一个二次规划问题。

总结一下本章要点:

  1. 自行车模型把四轮车简化为两轮,适用于低速场景
  2. 状态空间方程是非线性的,需要线性化后才能用于线性MPC
  3. 离散化方法选欧拉法还是ZOH,取决于你的采样时间和精度要求
  4. 线性化后的模型是时变的,每个控制周期都要重新计算

下一章,我们会基于这个离散模型,搭建完整的MPC控制器。到时候,你会看到这些矩阵是怎么变成控制量的。咱们下章见。