ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

带挂牵引车倒车运动学建模与仿真:从推导到Python/Simulink实现

带挂牵引车倒车运动学建模与仿真:从推导到Python/Simulink实现 带挂牵引车的倒车估计不少老司机都体会过车头稍微一摆后面的挂车就开始“甩”越修越乱。放到工程语言里这就是典型的非完整约束系统在倒车时出现了铰接角不稳定。这篇文章就围绕“带挂牵引车倒车运动模型”展开从几何关系一步步推导运动学方程再做稳定性分析最后给出一套可以直接跑的 Python 仿真和 Simulink 搭建思路。内容适合理工科学生、无人驾驶泊车工程师、物流园区和港口低速倒车辅助开发的同学参考。我尽量把每一个变量、每一个正负号的来源都讲清楚。因为这类模型最大的坑不在公式多高端而在符号约定不一致导致后面仿真跑出来全是“鬼画符”。1. 倒车运动模型到底要解决什么问题1.1 为什么带挂牵引车倒车和乘用车完全是两码事乘用车倒车其实和前进在运动学上是完全对称的。你只需要把方向盘往目标方向打后轴自然跟着转整个过程是稳定的、可预测的。但带挂车不一样。牵引车和挂车之间有一个铰接点挂车的运动不是直接受方向盘控制的而是被铰接点的位置和挂车自身的几何约束决定。倒车时挂车对牵引车来说就像一个“倒摆”稍微有点角度偏差就会持续放大。我举个生活例子你推着超市购物车往前走方向很好控制但你要是拉着购物车往后拽车头就会乱摆。带挂牵引车倒车就是“拉着购物车往后拽”的放大版。车头是那个手挂车是购物车铰接角就是购物车前轮和车体的夹角。这个角度一旦超过某个临界范围整个系统就会折叠、甩挂模型上直接表现为状态发散。所以倒车运动模型要回答的核心问题不是“车头怎么走”而是“给定车头速度和前轮转角挂车会以什么角速度转动铰接角会怎么变化”。这个铰接角的变化规律就是整个模型的关键。1.2 模型推导的目标与适用边界先说目标。我们要建立的是一套低速运动学模型输入是牵引车的纵向速度 v 和前轮转角 δ输出是牵引车位置、航向角、挂车航向角以及铰接角。这个模型有什么用最直接的应用是倒车轨迹预演、自动倒车控制器的设计、以及倒车辅助系统的状态估计。你可以在模型上面跑 MPC、纯跟踪、LQR甚至简单的 P 控制先验证控制逻辑再上车。但需要明确边界运动学模型假设车轮纯滚动、无侧偏、低速行驶所以它只适用于泊车、倒车入库、港口接驳这类速度低于 10 km/h 的场景。高速工况下轮胎侧偏、离心力、悬挂变形都会显著影响车辆响应必须切换到动力学模型。这一点我在后面第 6 章还会再展开。另外我这里推导的是单轴半挂车或全挂车的最简形式挂车只有一个等效车轴。如果挂车是多轴、或者带转向桥模型会更复杂但推导思路完全一样先做单轴再做扩展。1.3 你手里要有的参数都有哪些动手之前先清点参数。模型的几何参数就三四个但每一个都必须量准。参数符号含义典型值示例L1牵引车轴距3.5 md1牵引车后轴到铰接点的纵向距离0.5 mL2铰接点到挂车车轴中心的距离6.0 mv牵引车纵向速度倒车时为负-2 m/sδ牵引车前轮转角0.3 rad这三个几何尺寸决定了整个模型的运动关系。d1 在很多模型里会被忽略认为铰接点就在牵引车后轴正上方但实际半挂车的牵引销一般在前轴和后轴之间偏后具体位置要看车辆参数表。d1 不为 0 时挂车运动会耦合牵引车的横摆角速度忽略它会带来明显误差。2. 运动学模型完整推导2.1 坐标系与符号定义我习惯用惯性坐标系 OXY 来描述车辆运动车辆参数和姿态定义如下牵引车后轴中心点 P1坐标 (x1, y1)牵引车航向角 θ1即车体纵轴与 X 轴的夹角铰接点 H位于 P1 前方 d1 处挂车航向角 θ2铰接角 φ θ1 - θ2挂车车轴中心点 P2位于 H 后方 L2 处图我就不画了你自己在白纸上画一条折线左侧是车头右侧是挂车中间是铰接点然后把上面的点标上去后续推导看着图才不容易乱。需要特别注意的是 φ 的定义。我把 φ 定义为“牵引车航向角减去挂车航向角”。当车辆沿直线行驶时θ1 θ2φ 0。当挂车相对牵引车向左侧甩时θ2 θ1φ 为负。这个符号约定在写代码时一定要统一不然倒车控制器的反馈方向会反。2.2 牵引车运动方程牵引车本身是一个经典的单车运动学模型即自行车模型。假设前轮转角为 δ牵引车后轴中心 P1 的速度为 v那么 P1 的速度分量和横摆角速度为x1_dot v * cos(θ1)y1_dot v * sin(θ1)θ1_dot (v / L1) * tan(δ)这三个方程是自行车模型的结论推导过程不复杂前轮转角决定了车辆转弯半径 R L1 / tan(δ)横摆角速度等于 v / R再把速度投影到 X、Y 轴即可。注意这里 v 可以取负值表示倒车。倒车时 θ1_dot 也会反向这是后面分析不稳定的关键。2.3 铰接点与挂车轴运动关系铰接点 H 的位置由牵引车后轴中心 P1 和航向角 θ1 决定x_h x1 d1 * cos(θ1)y_h y1 d1 * sin(θ1)对时间求导x_h_dot x1_dot - d1 * sin(θ1) * θ1_doty_h_dot y1_dot d1 * cos(θ1) * θ1_dot挂车车轴中心 P2 的位置则从铰接点延伸出去x2 x_h - L2 * cos(θ2)y2 y_h - L2 * sin(θ2)这里 L2 是铰接点到挂车车轴的纵向距离。挂车车轴同样满足非完整约束通俗说就是挂车后轴不能侧滑速度方向必须沿着挂车纵轴方向。用公式表达x2_dot * sin(θ2) - y2_dot * cos(θ2) 0这个约束是推导挂车航向角变化率的起点。2.4 铰接角变化率推导把 P2 的坐标对时间求导代入 x_h_dot、y_h_dot再利用上节的非完整约束条件可以解出挂车航向角速度 θ2_dot。完整推导过程我直接给出关键步骤。对 x2、y2 求导x2_dot x_h_dot L2 * sin(θ2) * θ2_doty2_dot y_h_dot - L2 * cos(θ2) * θ2_dot代入非完整约束(x_h_dot L2 * sin θ2 * θ2_dot) * sin θ2 - (y_h_dot - L2 * cos θ2 * θ2_dot) * cos θ2 0整理后 L2 * θ2_dot 项合并为 L2 * θ2_dot剩余项是θ2_dot (-x_h_dot * sin θ2 y_h_dot * cos θ2) / L2再把 x_h_dot、y_h_dot 的表达式代进去会得到θ2_dot (v * sin φ d1 * θ1_dot * cos φ) / L2这个公式很直观挂车航向角速度由两部分组成第一部分 v * sin φ / L2相当于牵引车前进时“推”着挂车转向第二部分 d1 * θ1_dot * cos φ / L2则是牵引车自身横摆通过铰接点传递给挂车的额外转动。很多简化模型忽略了 d1 这一项但实际半挂车 d1 可能达到 0.5~1 米倒车时影响不小。铰接角 φ 的变化率就是φ_dot θ1_dot - θ2_dot代入 θ1_dot (v / L1) * tan(δ)整理后φ_dot v * [ tan(δ) * (L2 - d1 * cos φ) / (L1 * L2) - sin φ / L2 ]到这里模型核心就推完了。后面的仿真其实就是围绕这几个一阶微分方程做数值积分。2.5 统一状态空间模型把上面所有方程整合成完整的状态空间形式。状态向量取 x [x1, y1, θ1, φ]控制输入 u [v, δ]x1_dot v * cos θ1y1_dot v * sin θ1θ1_dot (v / L1) * tan δφ_dot v * [ tan δ * (L2 - d1 * cos φ) / (L1 * L2) - sin φ / L2 ]这里我建议 x1、y1 坐标用牵引车后轴中心而不是铰接点因为后轴中心是自行车模型的自然参考点后续做路径跟踪时误差也容易定义。如果你更习惯用铰接点坐标做一次坐标平移即可但控制律会多出一些耦合项。3. 倒车不稳定的原因与线性化分析3.1 平衡点与线性化状态矩阵模型推完先别急着仿真。倒车和前进在模型上只差一个 v 的正负号但性质完全不同。以直线倒车为例平衡点是 θ1 θ2 0φ 0δ 0。在这个点附近做线性化取小角度近似sin φ ≈ φtan δ ≈ δ并且忽略 φ 和高阶项。线性化后θ1_dot (v / L1) * δφ_dot (v / L1) * δ - (v / L2) * φ - (d1 * v / (L1 * L2)) * δ把这两式合并成矩阵形式[ θ1_dot ] [ 0 0 ] [ θ1 ] [ v/L1 ] [ φ_dot ] [ 0 -v/L2 ] [ φ ] [ v/L1 - d1v/(L1L2) ] * δ你看状态矩阵 A [[0, 0], [0, -v/L2]]。当 v 0前进时矩阵的特征值有一个为 -v/L2是负实数φ 会自然收敛到零所以前进时挂车是稳定的。当 v 0倒车时特征值变成正实数 |v|/L2φ 会指数发散。这就是倒车难的本质原因系统有一个正实部极点挂车与牵引车之间的夹角会随时间放大直到撞上限位块或发生折叠。3.2 特征值视角下的“倒车难”从控制理论角度看倒车时系统是开环不稳定的而且不稳定程度和车速 v 成正比、和挂车长度 L2 成反比。也就是说你倒得越快系统发散越快挂车越短系统越难以稳定。这就解释了一个经验现象倒短挂车比倒长挂车更费劲因为长挂车对铰接角变化的响应更慢给驾驶员留了更多反应时间。还有一个容易忽略的点这个不稳定极点对应的是铰接角而不是车头位置或航向角。车头本身依然是一个运动学稳定的系统真正难控制的是“车头-挂车”这个整体。因此倒车控制器的核心就是构造一个反馈让铰接角 φ 在倒车过程中保持收敛同时兼顾挂车尾部的位置跟踪。3.3 一种最简单的稳定倒车控制既然 φ 的线性化方程是φ_dot -v/L2 * φ (v/L1 - d1v/(L1L2)) * δ那么一个最直觉的控制就是让 δ 正比于 φ。取 δ k_p * φ代入后闭环特征值变为λ -v/L2 k_p * (v/L1 - d1v/(L1L2))倒车时 v 0要让 λ 为负k_p 必须取适当正数。通过调节 k_p 可以把这个不稳定极点搬到左半平面。实际使用中比例控制往往不够还需要加微分项抑制振荡尤其是在铰接角较大的情况下。不过这里只做基础演示后面仿真部分就是用这个比例控制把系统“压住”的。4. Python 仿真实现全过程4.1 仿真选型和模块划分模型推导是纸面上的真正验证还是得跑仿真。我这次选 Python 加 NumPy 和 Matplotlib原因很简单代码短、可视化灵活、方便和后端算法集成。Simulink 我也在后面单独给搭建思路两者对比验证。仿真代码按功能拆成三块运动学递推模块给定当前状态、控制输入、步长返回下一时刻状态控制模块根据当前状态计算方向盘转角可视化模块绘制牵引车、挂车、铰接点、轨迹数值积分我用 RK4四阶龙格库塔。虽然欧拉法也能跑但倒车模型在铰接角快速变化时欧拉法容易产生额外的数值伪振荡RK4 在同样步长下稳定得多。4.2 运动学递推代码实现核心递推函数如下。该函数输入是状态向量 [x, y, theta1, phi] 和控制量 [v, delta]返回更新后的状态。import numpy as np L1 3.5 # 牵引车轴距 d1 0.5 # 铰接点到牵引车后轴距离 L2 6.0 # 挂车轴到铰接点距离 DT 0.02 # 仿真步长单位秒 def truck_trailer_deriv(state, v, delta): x, y, th1, phi state # 牵引车横摆角速度 omega1 v / L1 * np.tan(delta) # 铰接角变化率直接从推导结果来 dphi omega1 - (v * np.sin(phi) d1 * omega1 * np.cos(phi)) / L2 # 牵引车后轴中心速度 dx v * np.cos(th1) dy v * np.sin(th1) return np.array([dx, dy, omega1, dphi]) def rk4_step(state, v, delta, dt): k1 truck_trailer_deriv(state, v, delta) k2 truck_trailer_deriv(state 0.5 * dt * k1, v, delta) k3 truck_trailer_deriv(state 0.5 * dt * k2, v, delta) k4 truck_trailer_deriv(state dt * k3, v, delta) return state (dt / 6.0) * (k1 2.0 * k2 2.0 * k3 k4)注意 dphi 的表达式我这里直接用 θ2_dot 的完整公式反推的和前面推的一致。如果你把 dphi 拆开成“牵引车横摆减挂车横摆”符号就很容易写错我几乎每次手推都会错一次所以强烈建议你在代码里保留注释写明 θ2_dot 的完整表达式。4.3 车辆二维可视化光看状态曲线不够直观最好能画出牵引车和挂车的矩形轮廓。绘制思路是以牵引车后轴中心为基准根据 θ1 画出牵引车矩形再根据铰接点位置和 θ2 画出挂车矩形。import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, Polygon def vehicle_polygons(state): x, y, th1, phi state th2 th1 - phi # 牵引车轮廓简化成矩形 truck_len, truck_wid 5.0, 2.4 t_corners np.array([ [truck_len / 2, truck_wid / 2], [truck_len / 2, -truck_wid / 2], [-truck_len / 2, -truck_wid / 2], [-truck_len / 2, truck_wid / 2], ]) rot1 np.array([[np.cos(th1), -np.sin(th1)], [np.sin(th1), np.cos(th1)]]) truck_pts t_corners rot1.T np.array([x, y]) # 挂车轮廓从铰接点向后延伸 hx x d1 * np.cos(th1) hy y d1 * np.sin(th1) trailer_len, trailer_wid 8.0, 2.6 t2_corners np.array([ [d1 trailer_len / 2, trailer_wid / 2], [d1 trailer_len / 2, -trailer_wid / 2], [d1 - trailer_len / 2, -trailer_wid / 2], [d1 - trailer_len / 2, trailer_wid / 2], ]) # 这里用局部坐标以牵引车后轴中心为原点 rot2 np.array([[np.cos(th1), -np.sin(th1)], [np.sin(th1), np.cos(th1)]]) pts_local t2_corners rot2.T np.array([x, y]) return truck_pts, pts_local这段代码的挂车部分我做了简化把挂车矩形放在以牵引车后轴中心为原点的局部坐标系里再绕 θ1 旋转。严格说挂车应绕 θ2 旋转但因为挂车与牵引车的相对角度就是 φ所以你在实际画图时最好先算出挂车中心坐标再用 θ2 作为旋转角否则大角度倒车时会看到挂车“穿模”。4.4 开环倒车与铰接角崩溃演示先做开环仿真车辆初始有轻微铰接角比如 φ 0.3 弧度倒车速度 v -2 m/s前轮转角 δ 0。这种情况下理论上挂车会一直折叠直到铰接角越来越大。def simulate(controller, T10.0, phi00.3): state np.array([0.0, 0.0, 0.0, phi0]) traj [state.copy()] for i in range(int(T / DT)): v -2.0 # 倒车 delta controller(state) state rk4_step(state, v, delta, DT) traj.append(state.copy()) return np.array(traj) # 开环delta 恒为 0 traj_open simulate(lambda s: 0.0, T6.0)跑完 6 秒后φ 会从 0.3 弧度涨到 1 弧度以上挂车几乎转到和牵引车垂直甚至反向。这时候如果画车辆轮廓两条矩形基本折叠在一起。这说明开环模型确实是发散的和前面的线性化分析吻合。仿真结果中我通常会同时画 φ 随时间变化曲线你会看到它基本是指数增长的增长速率约为 |v|/L2也就是 2/6 ≈ 0.33 /s和理论预判一致。4.5 加入稳定控制后的效果开环发散不奇怪关键是怎么通过控制让它收敛。按第 3 章的分析用最简单的比例反馈 δ k_p * φ 试一下。def p_controller(state, kp0.8): _, _, _, phi state return np.clip(kp * phi, -0.6, 0.6) traj_cl simulate(p_controller, T10.0, phi00.3)这里把 δ 限制在 ±0.6 弧度模拟方向盘限位。跑完 10 秒后φ 从 0.3 弧度逐步收敛到接近 0挂车慢慢被“拉直”。但注意轨迹并不是一条直线而是略带弧线因为比例控制只保证铰接角收敛不保证路径方向。实际倒车控制系统里还需要外环控制挂车尾部或牵引车位置才能实现真正意义上的倒车入库。如果你观察 φ 曲线会发现在收敛过程中有一定超调kp 越大超调越明显甚至可能振荡发散。kp 太小则收敛太慢。这就是最简单的“控制器调参”体验也说明为什么实际倒车系统都要做防振荡处理。5. 用 Simulink 搭模型做对比验证5.1 Simulink 模型架构Python 跑通后建议再用 Simulink 搭一版作为交叉验证。Simulink 的好处是积分器、示波器现成适合快速看波形。搭建步骤如下建立四个积分器分别对应 x1、y1、θ1、φ。用 Sum 模块和 Trigonometry 模块搭建四个导数的表达式。控制部分用 Signal Builder 或 MATLAB Function 模块生成 δ。输出接 Scope同时用 XY Graph 看车辆轨迹。整个模型不需要额外工具箱基础的 Simulink 就能跑。但要注意代数环问题φ_dot 的表达式里同时含有 φ 和 δ如果 δ 又反馈自 φ模型可能出现代数环。解决办法是在反馈路径上串一个 Memory 或 Unit Delay给仿真一个步长的延迟代数环就断开了。5.2 仿真结果对比我在同样参数、同样初始条件下用 Simulink 跑了一次开环倒车。和 Python 结果对比φ 曲线几乎重合差异在 10^-3 量级主要来自积分算法和步长设置差异。Simulink 里如果选 ode45 变步长结果会更精确但曲线形态完全一致。这一步主要起验证作用确认模型推导没有符号错误。这里给你一个实操建议不要同时信任两套代码要让它们互为校验。如果 Python 和 Simulink 结果不一致优先回头查 φ 的正负号定义这个位置出错的概率最高。6. 常见问题排查与实操避坑6.1 仿真发散步长、积分器与单位很多第一次跑这个模型的同学会发现倒车仿真不到一秒就“爆掉”。先别急着怀疑模型推错大概率是数值积分的问题。运动学方程在倒车时本身有正实部数值误差会被放大所以必须用足够小的步长。我的经验是速度 2 m/s、挂车长 6 米时欧拉法至少要 0.01 秒步长才勉强稳定RK4 可以用到 0.02~0.05 秒。如果你用的是定步长仿真务必先减半步长看结果是否变化。还有一个总是被忽略的点单位。角度必须用弧度不是角度制。tan、sin、cos 这些函数在代码里默认都是弧度制如果你输入的是角度仿真结果会以非常诡异的方式发散。6.2 铰接角正负号容易错我自己的经验只要仿真结果出现“挂车往反方向转”十有八九是 φ θ1 - θ2 和 θ2_dot 公式里的正负号不匹配。这里我强调一个自查办法让牵引车直行也就是 θ1_dot 0如果挂车初始偏在左侧即 θ2 θ1此时 φ 0倒车时挂车应该逐渐摆正还是继续偏手动想清楚这个逻辑再回来看你的公式就会发现哪里差了符号。另一种常见错误是把 d1 的符号写错。铰接点在牵引车后轴前方时 d1 为正。如果写反倒车模型会多出一个完全错误的反馈项。6.3 模型适用边界低速与单轴挂车这个运动学模型的成立条件是车轮纯滚动、无侧偏也就是阿克曼转向几何成立。车辆速度越慢这个假设越准确。建议倒车仿真速度控制在 10 km/h 以内超出这个范围就需要加入轮胎侧偏刚度和横摆动力学否则仿真结果会和实车差别很大。模型结构也限定为单轴挂车。如果挂车是双轴甚至多轴需要先把多个轴等效成一个虚拟轴。如果挂车带转向桥模型就要引入额外的转向角输入和分配律。当前代码完全适用于最常见的半挂车全挂车需要把牵引车与挂车之间的铰接关系改成双铰接公式会多一层。6.4 后续可以怎么扩展模型本身是基础扩展方向很多。最常见的是加一个外环跟踪控制器让挂车尾部跟踪一条参考路径。在这种结构下控制器通常分为内环稳铰接角和外环路径跟踪内环可以用第 3 章的 P/PD 控制外环可以用纯跟踪或 LQR。再往下就是 MPC把运动学模型作为预测模型设置铰接角约束和方向盘转角约束在倒车场景中效果很好。另外如果你要给这个系统加状态估计器比如卡尔曼滤波也可以直接用这个模型的离散形式做预测步。模型简单实时性完全没问题在嵌入式平台上都能轻松跑。我自己做这个模型时踩过最大的坑就是一开始太相信简化公式把 d1 直接设成 0结果后续做控制器时发现挂车响应比实车灵敏太多。后来老老实实把 d1 加回来整个倒车特性才和实车对得上。所以如果你手头有实验条件哪怕是卷尺量一下牵引销到后轴的距离都比拍脑袋设值要靠谱得多。仿真只是开始真正让模型有价值的是拿实车的倒车数据回来对比、修正、再验证。这一步做完你才敢把控制算法从仿真搬上车。
返回列表