带挂牵引车倒车运动模型推导及仿真实现

1. 序言

半挂牵引车在倒车时,由于鞍座主销的连接限制,其运动方式与乘用车有显著差异。例如,乘用车在右侧倒车入库时,只需确定泊车起点并将方向盘向右打死。相反,牵引车在倒车时需要先向左转方向盘,当铰接角达到防折叠的临界值时,再将方向盘向右转回,以调整牵引车与挂车至直线状态,完成泊车入库。本研究旨在建立一个基于牵引车带挂车倒车运动学模型,该模型基于以下假设:

  • 道路在车辆行驶过程中保持平坦,无凹凸不平;
  • 车辆以低速行驶,且在运行过程中车身不发生侧倾或俯仰;
  • 行驶过程中,轮胎无变形,无侧向滑动,且不产生侧向力;
  • 牵引车和半挂车均遵循阿克曼转向几何原理;
  • 牵引车与半挂车在行驶过程中不会发生折叠。

2. 运动学模型及推导

带挂牵引车后向运动是开环不稳定的,由于挂车的长度远大于牵引车的长度,因此,在考虑倒车的时候应该优先考虑挂车的精确控制。其对应的自行车模型如下所示:

带挂牵引车后向运动模型

其中:

  • A A A:牵引车的前轮中心点,其坐标为 ( x f , y f ) (x_f,y_f) (xf,yf)
  • B B B:牵引车后轮中心点,其坐标为 ( x r , y r ) (x_r,y_r) (xr,yr)
  • C C C:牵引车与半挂车的铰接点,也是半挂车的等效前轮中心点,其坐标为 ( x 1 f , y 1 f ) (x_{1f},y_{1f}) (x1f,y1f)
  • D D D:半挂车后轮中心点,其坐标为 ( x 1 r , y 1 r ) (x_{1r},y_{1r}) (x1r,y1r)
  • L L L:牵引车的轴距;
  • L a L_a La:铰接点 C C C A A A的距离;
  • L b L_b Lb:铰接点 C C C B B B的距离;
  • L 1 L1 L1:半挂车的轴距;
  • δ f \delta_f δf:牵引车前轮转角;
  • δ 1 f \delta_{1f} δ1f:半挂车虚拟前轮转角;
  • φ \varphi φ:牵引车航向角;
  • φ 1 \varphi_1 φ1:半挂车的航向角;
  • θ \theta θ:铰接角,即牵引车与半挂车的航向的夹角;
  • v v v:牵引车的纵向速度;
  • v 1 v_1 v1:半挂车的纵向速度;
  • v 1 f v_{1f} v1f:挂车虚拟前轮速度;
  • α \alpha α:挂车虚拟前轮与牵引车航向的夹角。

结合我们前面【带挂牵引车前向运动模型推导及仿真实现】的推导,可得挂车的运动学模型如下
{ x 1 r ˙ = v 1 cos ⁡ φ 1 y 1 r ˙ = v 1 sin ⁡ φ 1 φ ˙ = v tan ⁡ δ f L φ 1 ˙ = v 1 tan ⁡ δ 1 f L 1 θ = φ − φ 1 (1) \begin{cases} \dot{x_{1r}}=v_1\cos{\varphi_1}\\ \dot{y_{1r}}=v_1\sin{\varphi_1}\\ \dot{\varphi}=\frac{v\tan\delta_f}{L}\\ \dot{\varphi_1}=\frac{v_1\tan\delta_{1f}}{L_1}\\ \theta=\varphi-\varphi_1 \end{cases}\tag{1} x1r˙=v1cosφ1y1r˙=v1sinφ1φ˙=Lvtanδfφ1˙=L1v1tanδ1fθ=φφ1(1)
由图中车速和几何关系可得
v 1 = v 1 f cos ⁡ δ 1 f = v cos ⁡ α cos ⁡ δ 1 f = v cos ⁡ α cos ⁡ ( θ − α ) = v cos ⁡ θ cos ⁡ α + sin ⁡ θ sin ⁡ α cos ⁡ α = v ( cos ⁡ θ + tan ⁡ α sin ⁡ θ ) = v ( cos ⁡ θ + L b R sin ⁡ θ ) (2) \begin{equation} \begin{split} v_1&=v_{1f}\cos\delta_{1f}\\ &=\frac{v}{\cos\alpha}\cos\delta_{1f}\\ &=\frac{v}{\cos\alpha}\cos(\theta-\alpha)\\ &=v\frac{\cos\theta\cos\alpha+\sin\theta\sin\alpha}{\cos\alpha}\\ &=v(\cos\theta+\tan\alpha\sin\theta)\\ &=v(\cos\theta+\frac{L_b}{R}\sin\theta)\\ \end{split} \end{equation} \tag{2} v1=v1fcosδ1f=cosαvcosδ1f=cosαvcos(θα)=vcosαcosθcosα+sinθsinα=v(cosθ+tanαsinθ)=v(cosθ+RLbsinθ)(2)
结合阿克曼转向原理可得
R = L tan ⁡ δ f (3) R=\frac{L}{\tan\delta_f} \tag{3} R=tanδfL(3)
将3代入2可得
v 1 = v ( cos ⁡ θ + L b tan ⁡ δ f L sin ⁡ θ ) (4) v_1=v(\cos\theta+\frac{L_b\tan\delta_f}{L}\sin\theta) \tag{4} v1=v(cosθ+LLbtanδfsinθ)(4)
由图中的几何关系和3可得
tan ⁡ α = L b R = L b tan ⁡ δ f L (5) \tan\alpha=\frac{L_b}{R}=\frac{L_b\tan\delta_f}{L}\tag{5} tanα=RLb=LLbtanδf(5)

由5和图中的几何关系可得
tan ⁡ δ 1 f = tan ⁡ ( θ − α ) = tan ⁡ θ − tan ⁡ α 1 + tan ⁡ θ tan ⁡ α = tan ⁡ θ − L b tan ⁡ δ f L 1 + L b tan ⁡ δ f L tan ⁡ θ = L tan ⁡ θ − L b tan ⁡ δ f L + L b tan ⁡ δ f tan ⁡ θ (6) \begin{equation} \begin{split} \tan\delta_{1f}&=\tan(\theta-\alpha)\\ &=\frac{\tan\theta-\tan\alpha}{1+\tan\theta\tan\alpha}\\ &=\frac{\tan\theta-\frac{L_b\tan\delta_f}{L}}{1+\frac{L_b\tan\delta_f}{L}\tan\theta}\\ &=\frac{L\tan\theta-L_b\tan\delta_f}{L+L_b\tan\delta_f\tan\theta} \end{split} \end{equation} \tag{6} tanδ1f=tan(θα)=1+tanθtanαtanθtanα=1+LLbtanδftanθtanθLLbtanδf=L+LbtanδftanθLtanθLbtanδf(6)
将4和6代入1的 φ 1 ˙ \dot{\varphi_1} φ1˙可得
φ 1 ˙ = v 1 tan ⁡ δ 1 f L 1 = v ( cos ⁡ θ + L b tan ⁡ δ f L sin ⁡ θ ) L 1 L tan ⁡ θ − L b tan ⁡ δ f L + L b tan ⁡ δ f tan ⁡ θ = v L 1 L sin ⁡ θ − L b tan ⁡ δ f cos ⁡ θ + L b tan ⁡ δ f tan ⁡ θ sin ⁡ θ − L b 2 L tan ⁡ 2 δ f sin ⁡ θ L + L b tan ⁡ δ f tan ⁡ θ = v L 1 ( sin ⁡ θ − L b L cos ⁡ θ tan ⁡ δ f ) (7) \begin{equation} \begin{split} \dot{\varphi_1}&=\frac{v_1\tan\delta_{1f}}{L_1}\\ &=\frac{v(\cos\theta+\frac{L_b\tan\delta_f}{L}\sin\theta)}{L_1}\frac{L\tan\theta-L_b\tan\delta_f}{L+L_b\tan\delta_f\tan\theta}\\ &=\frac{v}{L_1}\frac{L\sin\theta-L_b\tan\delta_f\cos\theta+L_b\tan\delta_f\tan\theta\sin\theta-\frac{L_b^2}{L}\tan^2\delta_f\sin\theta}{L+L_b\tan\delta_f\tan\theta}\\ &=\frac{v}{L_1}(\sin\theta-\frac{L_b}{L}\cos\theta\tan\delta_f) \end{split} \end{equation} \tag{7} φ1˙=L1v1tanδ1f=L1v(cosθ+LLbtanδfsinθ)L+LbtanδftanθLtanθLbtanδf=L1vL+LbtanδftanθLsinθLbtanδfcosθ+LbtanδftanθsinθLLb2tan2δfsinθ=L1v(sinθLLbcosθtanδf)(7)
综上可得,带挂牵引车的倒车运动学模型如下
{ x 1 r ˙ = v ( cos ⁡ θ + L b tan ⁡ δ f L sin ⁡ θ ) cos ⁡ φ 1 y 1 r ˙ = v ( cos ⁡ θ + L b tan ⁡ δ f L sin ⁡ θ ) sin ⁡ φ 1 φ ˙ = v tan ⁡ δ f L φ 1 ˙ = v L 1 ( sin ⁡ θ − L b L cos ⁡ θ tan ⁡ δ f ) θ = φ − φ 1 (8) \begin{cases} \dot{x_{1r}}=v(\cos\theta+\frac{L_b\tan\delta_f}{L}\sin\theta)\cos{\varphi_1}\\ \dot{y_{1r}}=v(\cos\theta+\frac{L_b\tan\delta_f}{L}\sin\theta)\sin{\varphi_1}\\ \dot{\varphi}=\frac{v\tan\delta_f}{L}\\ \dot{\varphi_1}=\frac{v}{L_1}(\sin\theta-\frac{L_b}{L}\cos\theta\tan\delta_f)\\ \theta=\varphi-\varphi_1 \end{cases}\tag{8} x1r˙=v(cosθ+LLbtanδfsinθ)cosφ1y1r˙=v(cosθ+LLbtanδfsinθ)sinφ1φ˙=Lvtanδfφ1˙=L1v(sinθLLbcosθtanδf)θ=φφ1(8)
由图中的几何关系可得牵引车的后轴中心点坐标为
{ x r = x 1 r + L 1 cos ⁡ φ 1 − L b cos ⁡ φ y r = y 1 r + L 1 sin ⁡ φ 1 − L b sin ⁡ φ (9) \begin{cases} x_r=x_{1r}+L_1\cos\varphi_1-L_b\cos\varphi\\ y_r=y_{1r}+L_1\sin\varphi_1-Lb\sin\varphi \tag{9} \end{cases} {xr=x1r+L1cosφ1Lbcosφyr=y1r+L1sinφ1Lbsinφ(9)

3. 代码实现

semi_truck_back_model.py

import math
import matplotlib.pyplot as plt
import numpy as np
import imageio.v2 as imageio

class VehicleParamInfo:
    # 牵引车(车头)
    L = 3.0  # 轴距
    W = 2.0  # 宽度
    LF = 3.8  # 后轴中心到车头距离
    LB = 0.8  # 后轴中心到车尾距离
    MAX_STEER = 0.6  # 最大前轮转角
    TR = 0.5  # 轮子半径
    TW = 0.5  # 轮子宽度
    WD = W  # 轮距
    LENGTH = LB + LF  # 车辆长度
    La = 2.7  # 铰接点到牵引车前轴中心的距离
    Lb = 0.3  # 铰接点到牵引车后轴中心的距离
    # 挂车
    L1 = 7  # 挂车轴距
    LF1 = 8.0  # 挂车后轴中心到车头距离
    LB1 = 1.0  # 挂车后轴中心到车尾距离

def normalize_angle(angle):
    a = math.fmod(angle + np.pi, 2 * np.pi)
    if a < 0.0:
        a += (2.0 * np.pi)
    return a - np.pi

class Vehicle:
    def __init__(self, x1=0.0, y1=0.0, yaw=0.0, v=0.0, dt=0.1, vehicle_param_info=VehicleParamInfo):
        self.steer = 0
        self.x1 = x1
        self.y1 = y1
        self.yaw = yaw
        self.v = v
        self.dt = dt
        self.vehicle_param_info = vehicle_param_info

        # 挂车
        self.yaw1 = yaw  # 初始化时候默认牵引车与挂车在同一条直线上
        self.x_front1 = self.x1 + self.vehicle_param_info.L1 * math.cos(self.yaw1)
        self.y_front1 = self.y1 + self.vehicle_param_info.L1 * math.sin(self.yaw1)

        # 牵引车
        self.x = self.x_front1 - self.vehicle_param_info.Lb * math.cos(self.yaw)
        self.y = self.y_front1 - self.vehicle_param_info.Lb * math.sin(self.yaw)
        self.x_front = self.x + self.vehicle_param_info.L * math.cos(self.yaw)
        self.y_front = self.y + self.vehicle_param_info.L * math.sin(self.yaw)

    def update(self, a, delta, max_steer=None):
        if max_steer is None:
            max_steer = self.vehicle_param_info.MAX_STEER
        delta = np.clip(delta, -max_steer, max_steer)
        self.steer = delta
        hitchAngle = normalize_angle(self.yaw-self.yaw1)

        self.x1 += self.v * (math.cos(hitchAngle)+self.vehicle_param_info.Lb*math.tan(delta)/self.vehicle_param_info.L*math.sin(hitchAngle))*math.cos(self.yaw1) * self.dt
        self.y1 += self.v * (math.cos(hitchAngle)+self.vehicle_param_info.Lb*math.tan(delta)/self.vehicle_param_info.L*math.sin(hitchAngle))*math.sin(self.yaw1) * self.dt
        self.x_front1 = self.x1 + self.vehicle_param_info.L1 * math.cos(self.yaw1)
        self.y_front1 = self.y1 + self.vehicle_param_info.L1 * math.sin(self.yaw1)

        self.x = self.x_front1 - self.vehicle_param_info.Lb * math.cos(self.yaw)
        self.y = self.y_front1 - self.vehicle_param_info.Lb * math.sin(self.yaw)

        self.x_front = self.x + self.vehicle_param_info.L * math.cos(self.yaw)
        self.y_front = self.y + self.vehicle_param_info.L * math.sin(self.yaw)

        self.yaw += self.v / self.vehicle_param_info.L * math.tan(delta) * self.dt
        self.yaw = normalize_angle(self.yaw)

        self.v += a * self.dt
        self.yaw1 += self.v / self.vehicle_param_info.L1 * (
                    math.sin(hitchAngle) - self.vehicle_param_info.Lb * math.tan(self.steer) * math.cos(
                hitchAngle) / self.vehicle_param_info.L) * self.dt
        self.yaw1 = normalize_angle(self.yaw1)


def draw_vehicle_trailer(x, y, yaw,x1, y1, yaw1, steer, ax, vehicle_param_info=VehicleParamInfo, color='black'):

    vehicle_outline = np.array([
        [-vehicle_param_info.LB, vehicle_param_info.LF, vehicle_param_info.LF, -vehicle_param_info.LB, -vehicle_param_info.LB],
        [vehicle_param_info.W / 2, vehicle_param_info.W / 2, -vehicle_param_info.W / 2, -vehicle_param_info.W / 2, vehicle_param_info.W / 2]
    ])
    vehicle_outline1 = np.array([
        [-vehicle_param_info.LB1, vehicle_param_info.LF1, vehicle_param_info.LF1, -vehicle_param_info.LB1,
         -vehicle_param_info.LB1],
        [vehicle_param_info.W / 2, vehicle_param_info.W / 2, -vehicle_param_info.W / 2, -vehicle_param_info.W / 2,
         vehicle_param_info.W / 2]
    ])

    wheel = np.array([
        [-vehicle_param_info.TR, vehicle_param_info.TR, vehicle_param_info.TR, -vehicle_param_info.TR, -vehicle_param_info.TR],
        [vehicle_param_info.TW / 2, vehicle_param_info.TW / 2, -vehicle_param_info.TW / 2, -vehicle_param_info.TW / 2, vehicle_param_info.TW / 2]
    ])

    rr_wheel = wheel.copy()  # 右后轮
    rl_wheel = wheel.copy()  # 左后轮
    fr_wheel = wheel.copy()  # 右前轮
    fl_wheel = wheel.copy()  # 左前轮
    rr_wheel1 = wheel.copy()  # 挂车右后轮
    rl_wheel1 = wheel.copy()  # 挂车左后轮
    rr_wheel[1, :] += vehicle_param_info.WD / 2
    rl_wheel[1, :] -= vehicle_param_info.WD / 2
    rr_wheel1[1, :] += vehicle_param_info.WD / 2
    rl_wheel1[1, :] -= vehicle_param_info.WD / 2

    # 前轮旋转
    rot1 = np.array([
        [np.cos(steer), -np.sin(steer)],
        [np.sin(steer), np.cos(steer)]
    ])
    # yaw旋转矩阵
    rot2 = np.array([
        [np.cos(yaw), -np.sin(yaw)],
        [np.sin(yaw), np.cos(yaw)]
    ])

    # yaw1旋转矩阵
    rot3 = np.array([
        [np.cos(yaw1), -np.sin(yaw1)],
        [np.sin(yaw1), np.cos(yaw1)]
    ])

    fr_wheel = np.dot(rot1, fr_wheel)
    fl_wheel = np.dot(rot1, fl_wheel)
    fr_wheel += np.array([[vehicle_param_info.L], [-vehicle_param_info.WD / 2]])
    fl_wheel += np.array([[vehicle_param_info.L], [vehicle_param_info.WD / 2]])

    fr_wheel = np.dot(rot2, fr_wheel)
    fr_wheel[0, :] += x
    fr_wheel[1, :] += y
    fl_wheel = np.dot(rot2, fl_wheel)
    fl_wheel[0, :] += x
    fl_wheel[1, :] += y
    rr_wheel = np.dot(rot2, rr_wheel)
    rr_wheel[0, :] += x
    rr_wheel[1, :] += y
    rl_wheel = np.dot(rot2, rl_wheel)
    rl_wheel[0, :] += x
    rl_wheel[1, :] += y
    vehicle_outline = np.dot(rot2, vehicle_outline)
    vehicle_outline[0, :] += x
    vehicle_outline[1, :] += y

    rr_wheel1 = np.dot(rot3, rr_wheel1)
    rr_wheel1[0, :] += x1
    rr_wheel1[1, :] += y1
    rl_wheel1 = np.dot(rot3, rl_wheel1)
    rl_wheel1[0, :] += x1
    rl_wheel1[1, :] += y1
    vehicle_outline1 = np.dot(rot3, vehicle_outline1)
    vehicle_outline1[0, :] += x1
    vehicle_outline1[1, :] += y1

    ax.plot(fr_wheel[0, :], fr_wheel[1, :], color)
    ax.plot(rr_wheel[0, :], rr_wheel[1, :], color)
    ax.plot(fl_wheel[0, :], fl_wheel[1, :], color)
    ax.plot(rl_wheel[0, :], rl_wheel[1, :], color)
    ax.plot(rr_wheel1[0, :], rr_wheel1[1, :], color)
    ax.plot(rl_wheel1[0, :], rl_wheel1[1, :], color)
    ax.plot(vehicle_outline[0, :], vehicle_outline[1, :], color)
    ax.plot(vehicle_outline1[0, :], vehicle_outline1[1, :], color)

if __name__ == "__main__":
    vehicle = Vehicle(x1=0.0, y1=0.0, yaw=0.0, v=0.0, dt=0.1)
    vehicle.v = -1
    trajectory_x = []
    trajectory_y = []
    trailer_trajectory_x = []
    trailer_trajectory_y = []
    fig, ax = plt.subplots()
    image_list = []  # 存储图片
    for i in range(500):
        ax.cla()
        ax.set_aspect('equal', adjustable='box')
        delta = np.pi / 12 if (vehicle.yaw - vehicle.yaw1) > 0.2 else -np.pi / 12
        vehicle.update(0, delta)
        draw_vehicle_trailer(vehicle.x, vehicle.y, vehicle.yaw, vehicle.x1, vehicle.y1, vehicle.yaw1, vehicle.steer, ax)
        trajectory_x.append(vehicle.x)
        trajectory_y.append(vehicle.y)
        trailer_trajectory_x.append(vehicle.x1)
        trailer_trajectory_y.append(vehicle.y1)
        ax.plot(trajectory_x, trajectory_y, 'blue')
        ax.plot(trailer_trajectory_x, trailer_trajectory_y, 'green')
        ax.set_xlim(-45, 15)
        ax.set_ylim(-5, 35)
        plt.pause(0.001)
    #     plt.savefig("temp.png")
    #     i += 1
    #     if (i % 30) == 0:
    #         image_list.append(imageio.imread("temp.png"))
    #
    # imageio.mimsave("display.gif", image_list, duration=0.1)

运行效果如下:

参考文献

[1]贾生超.半挂牵引车自动泊车路径规划与运动控制方法研究[D].燕山大学,2023.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艰默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值