PID横向控制和仿真实现

文章目录

    • 1. PID介绍
    • 2. PID横向控制原理
    • 3. 算法和仿真实现

1. PID介绍

PID是一种常见的控制算法,全称为Proportional-Integral-Derivative,即比例-积分-微分控制器。PID控制器是一种线性控制器,它将设定值与实际值进行比较,根据误差的大小,控制器会相应地调整系统的比例、积分和微分系数,以减小误差。

PID控制器的基本公式为:

u ( t ) = K p ∗ e ( t ) + K i ∗ ∫ 0 t e ( τ ) d τ + K d ∗ d d t e ( t ) (1) u(t) = K_p * e(t) + K_i * \int^{t}_{0}{e(\tau)} d\tau + K_d *\frac{d}{dt} e(t) \tag{1} u(t)=Kpe(t)+Ki0te(τ)dτ+Kddtde(t)(1)

其中,$u(t) 是控制器的输出, 是控制器的输出, 是控制器的输出,e(t)$ 是误差信号(设定值与实际值之差), K p K_p Kp K i K_i Ki K d K_d Kd是控制器的比例、积分和微分系数。

PID控制器在工程、科学和工业等领域中有着广泛的应用。例如,在汽车定速巡航系统、空调系统、工业自动化生产线等系统中都可以看到PID控制器的身影。此外,PID控制器还广泛应用于机器人控制、化工生产、航天器控制等领域。

将公式(1)转换为离散形式,则有

u ( n ) = K p ∗ e ( n ) + K i ∗ T ∗ ∑ i = 0 n e ( i ) + K d ∗ e ( n ) − e ( n − 1 ) T (2) u(n) = K_p * e(n) + K_i * T*\sum^{n}_{i=0}{e(i)} + K_d *\frac{e(n)-e(n-1)}{T} \tag{2} u(n)=Kpe(n)+KiTi=0ne(i)+KdTe(n)e(n1)(2)

其中 T T T为采样周期,由此公式可以得到PID代码的实现如下

#PID.py

class PIDController:
    def __init__(self, Kp, Ki, Kd):
        self.Kp = Kp
        self.Ki = Ki
        self.Kd = Kd
        self.previous_error = 0
        self.integral = 0


    def cal_output(self, error, dt):
        derivative = error - self.previous_error
        u = self.Kp * error + self.Ki * self.integral * dt + self.Kd * derivative / dt
        self.integral += error
        self.previous_error = error
        return u

2. PID横向控制原理

在自动驾驶横向控制中,主要通过控制方向盘的角度来控制车辆的横向距离误差,因此我们可以通过横向距离误差 e y e_y ey来作为PID的输入,输出可以作为方向盘转角 δ f \delta_f δf,结合之前我们的车辆运动学模型(这里我们假设方向盘转角与前轮转角比是1),横向误差计算的几何结构如下图所示:

在这里插入图片描述

图中

  • P P P:当前车辆的目标点车
  • l d l_d ld:车辆后轴中心点 A A A F F F的距离
  • θ \theta θ l d l_d ld与车轴的夹角
  • φ \varphi φ:车辆的航向角
  • e y e_y ey:横向偏差

A A A P P P的坐标可以计算得

β = a r c t a n y 1 − y 0 x 1 − x 0 (3) \beta = arctan\frac{y_1-y_0}{x_1-x_0} \tag{3} β=arctanx1x0y1y0(3)

由图中的几何关系,我们可以得到

e y = l d s i n θ = l d s i n ( β − φ ) (4) e_y = l_d sin\theta =l_d sin(\beta - \varphi)\tag{4} ey=ldsinθ=ldsin(βφ)(4)

其中 φ \varphi φ为车辆的航向角yaw,其实现方法详见bicycle_model.py

3. 算法和仿真实现

bicycle_model.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import math
import matplotlib.pyplot as plt
import numpy as np
from celluloid import Camera


class Vehicle:
    def __init__(self,
                 x=0.0,
                 y=0.0,
                 yaw=0.0,
                 v=0.0,
                 dt=0.1,
                 l=3.0):
        self.steer = 0
        self.x = x
        self.y = y
        self.yaw = yaw
        self.v = v
        self.dt = dt
        self.L = l  # 轴距
        self.x_front = x + l * math.cos(yaw)
        self.y_front = y + l * math.sin(yaw)

    def update(self, a, delta, max_steer=np.pi):
        delta = np.clip(delta, -max_steer, max_steer)
        self.steer = delta

        self.x = self.x + self.v * math.cos(self.yaw) * self.dt
        self.y = self.y + self.v * math.sin(self.yaw) * self.dt
        self.yaw = self.yaw + self.v / self.L * math.tan(delta) * self.dt

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


class VehicleInfo:
    # Vehicle parameter
    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  # 车辆长度

def draw_trailer(x, y, yaw, steer, ax, vehicle_info=VehicleInfo, color='black'):
    vehicle_outline = np.array(
        [[-vehicle_info.LB, vehicle_info.LF, vehicle_info.LF, -vehicle_info.LB, -vehicle_info.LB],
         [vehicle_info.W / 2, vehicle_info.W / 2, -vehicle_info.W / 2, -vehicle_info.W / 2, vehicle_info.W / 2]])

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

    rr_wheel = wheel.copy() #右后轮
    rl_wheel = wheel.copy() #左后轮
    fr_wheel = wheel.copy() #右前轮
    fl_wheel = wheel.copy() #左前轮
    rr_wheel[1,:] += vehicle_info.WD/2
    rl_wheel[1,:] -= vehicle_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)]])
    fr_wheel = np.dot(rot1, fr_wheel)
    fl_wheel = np.dot(rot1, fl_wheel)
    fr_wheel += np.array([[vehicle_info.L], [-vehicle_info.WD / 2]])
    fl_wheel += np.array([[vehicle_info.L], [vehicle_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

    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(vehicle_outline[0, :], vehicle_outline[1, :], color)
    # ax.axis('equal')



if __name__ == "__main__":

    vehicle = Vehicle(x=0.0,
                      y=0.0,
                      yaw=0,
                      v=0.0,
                      dt=0.1,
                      l=VehicleInfo.L)
    vehicle.v = 1
    trajectory_x = []
    trajectory_y = []
    fig = plt.figure()
    # 保存动图用
    # camera = Camera(fig)
    for i in range(600):
        plt.cla()
        plt.gca().set_aspect('equal', adjustable='box')
        vehicle.update(0, np.pi / 10)
        draw_trailer(vehicle.x, vehicle.y, vehicle.yaw, vehicle.steer, plt)
        trajectory_x.append(vehicle.x)
        trajectory_y.append(vehicle.y)
        plt.plot(trajectory_x, trajectory_y, 'blue')
        plt.xlim(-12, 12)
        plt.ylim(-2.5, 21)
        plt.pause(0.001)
    #     camera.snap()
    # animation = camera.animate(interval=5)
    # animation.save('trajectory.gif')

main.py

from scipy.spatial import KDTree
from bicycle_model import Vehicle, VehicleInfo, draw_trailer
from PID import PIDController
import numpy as np
import matplotlib.pyplot as plt
import math
import imageio

MAX_SIMULATION_TIME = 200.0  # 程序最大运行时间200*dt
PID = PIDController(2, 0.001, 3)


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


def main():
    # 设置跟踪轨迹
    ref_path = np.zeros((1000, 2))
    ref_path[:, 0] = np.linspace(0, 50, 1000)  # x坐标
    ref_path[:, 1] = 10 * np.sin(ref_path[:, 0] / 20.0)  # y坐标
    ref_tree = KDTree(ref_path)
    # 假设车辆初始位置为(0,0),航向角yaw=0.0,速度为2m/s,时间周期dt为0.1秒
    vehicle = Vehicle(x=0.0,
                      y=0.0,
                      yaw=np.pi/2,
                      v=2.0,
                      dt=0.1,
                      l=VehicleInfo.L)

    time = 0.0  # 初始时间

    # 记录车辆轨迹
    trajectory_x = []
    trajectory_y = []
    lat_err = []  # 记录横向误差

    i = 0
    image_list = []  # 存储图片
    plt.figure(1)

    last_idx = ref_path.shape[0] - 1  # 跟踪轨迹的最后一个点的索引
    old_idx = 0  # 记录上一次的索引点
    target_ind = 0  # 第一个目标点的索引
    while MAX_SIMULATION_TIME >= time and last_idx > target_ind:
        time += vehicle.dt  # 累加一次时间周期
        vehicle_positon = np.zeros(2)
        vehicle_positon[0] = vehicle.x
        vehicle_positon[1] = vehicle.y
        distance, target_ind = ref_tree.query(vehicle_positon)  # 在跟踪轨迹上查找离车辆最近的点
        if old_idx > target_ind:
            print("ERROR: Find the point behind the vehicle.")
            target_ind = old_idx + 1  # 查找到车辆后面的点,将目标点索引置为上一次的索引点idx+1
        old_idx = target_ind  # 记录本次索引点idx
        alpha = math.atan2(
            ref_path[target_ind, 1] - vehicle_positon[1], ref_path[target_ind, 0] - vehicle_positon[0])
        l_d = np.linalg.norm(ref_path[target_ind] - vehicle_positon)  # 目标点与车定位点距离l_d

        theta_e = NormalizeAngle(alpha - vehicle.yaw)
        e_y = l_d * math.sin(theta_e)  # 计算实际误差,0为目标距离
        lat_err.append(e_y)
        delta_f = PID.cal_output(e_y, vehicle.dt)

        vehicle.update(0.0, delta_f, np.pi / 10)  # 由于假设纵向匀速运动,所以加速度a=0.0
        trajectory_x.append(vehicle.x)
        trajectory_y.append(vehicle.y)

        # 显示动图
        plt.cla()
        plt.plot(ref_path[:, 0], ref_path[:, 1], '-.b', linewidth=1.0)
        draw_trailer(vehicle.x, vehicle.y, vehicle.yaw, vehicle.steer, plt)

        plt.plot(trajectory_x, trajectory_y, "-r", label="trajectory")
        plt.plot(ref_path[target_ind, 0], ref_path[target_ind, 1], "go", label="target")
        plt.axis("equal")
        plt.grid(True)
        plt.pause(0.001)
        plt.savefig("temp.png")
        i += 1
        if (i % 50) > 0:
            image_list.append(imageio.imread("temp.png"))

    imageio.mimsave("trailer_display.gif", image_list, duration=0.1)

    plt.figure(2)
    plt.subplot(2, 1, 1)
    plt.plot(ref_path[:, 0], ref_path[:, 1], '-.b', linewidth=1.0)
    plt.plot(trajectory_x, trajectory_y, 'r')
    plt.title("actual tracking effect")

    plt.subplot(2, 1, 2)
    plt.plot(lat_err)
    plt.title("lateral error")
    plt.show()


if __name__ == '__main__':
    main()

运行效果如下

在这里插入图片描述

跟踪效果和控制误差

在这里插入图片描述

文章首发公众号:iDoitnow如果喜欢话,可以关注一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/321885.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机的模拟量输入输出通道实验

实验一 模拟量输入输出通道实验&#xff08;C51&#xff09; 一、实验目的&#xff1a; 1、了解A/D、D/A转换的基本原理。 2、了解A/D转换芯片ADC0809、D/A转换芯片DAC0832的性能及编程方法。 3、掌握过程通道中A/D转换与D/A转换与计算机的接口方法。 4、了解计算机如何进…

VSCode 正则表达式 匹配多行

VS Code 正则表达式匹配多行 (.|\n)*? //test.js const test {str: VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code 正则表达式匹配多行VS Code …

数据库作业二

一&#xff0c;单表查询 1.创建表 1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 3、求出所有职工的人数。 4、列出最高工和最低工资。 5、列出职工的平均工资和总工资。 6、创建一个只有职工号、姓名和参加工作的…

【WPF.NET开发】WPF中的版式

本文内容 改进的文本质量和性能丰富的版式增强的国际文本支持增强的字体支持新的文本应用程序编程接口 (API) 本主题介绍 WPF 的主要版式功能。 这些功能包括改进的文本呈现质量和性能、OpenType 版式支持、增强的国际文本、增强的字体支持和新的文本应用程序编程接口 (API)。…

2024多系统萎缩最新全球特效药治疗进展

多系统萎缩是一种罕见的神经退行性疾病&#xff0c;由于缺乏有效的治疗方法&#xff0c;患者经常面临症状无法缓解和生活品质下降的困扰。然而&#xff0c;近期刘家峰大夫基于中医理论研究和临床实践&#xff0c;采用中药治疗多系统萎缩取得了显著疗效&#xff0c;给患者带来了…

VUE好看的个人简历模板

文章目录 1.设计来源1.1 首页界面1.2 关于我界面1.3 我的资历界面1.4 项目经验界面1.5 我的技能界面1.6 联系我界面 2.效果和源码2.1 动态效果2.2 源码目录结构 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/…

RMI简介

RMI 介绍 RMI (Remote Method Invocation) 模型是一种分布式对象应用&#xff0c;使用 RMI 技术可以使一个 JVM 中的对象&#xff0c;调用另一个 JVM 中的对象方法并获取调用结果。这里的另一个 JVM 可以在同一台计算机也可以是远程计算机。因此&#xff0c;RMI 意味着需要一个…

线程安全2

文章目录 锁的可重入性死锁内存可见性引起的线程安全 锁的可重入性 直观来看这个代码不能运行 为啥没有出现阻塞&#xff1f; 当前由于是同一个线程&#xff0c;此时的锁对象&#xff0c;就知道了第二次加锁的线程&#xff0c;就是持有锁的线程&#xff0c;第二次操作&#xff…

Linux下如何快速调试I2C设备

Linux下如何快速调试I2C设备 目录 1 什么场景下需要快速调试I2C设备 2 如何快速调试I2C设备 3 如何获取I2C Tools工具集 3.1 获取I2C Tools工具集源码 3.2 编译I2C Tools工具集源码 3.3 为设备添加I2C Tools工具集 4 如何使用I2C Tools工具集 5 小结 1 什么场景下需要快…

VScode设置自动添加自定义注释及修改字体

首先安装snippet mac可以键入commanp&#xff0c;输出> 选择自己所需的需要自动添加的文件类型配置文件 安装自己的需要修改 "Print to console": {"prefix": "xx", // 自己键入内容"body": [ // 注释信息"// xxx …

SpringMVC RESTful案例

文章目录 1、准备工作2、功能清单3、具体功能&#xff1a;访问首页a>配置view-controllerb>创建页面 4、具体功能&#xff1a;查询所有员工数据a>控制器方法b>创建employee_list.html 5、具体功能&#xff1a;删除a>创建处理delete请求方式的表单b>删除超链接…

docker部署私人云盘nextcloud

首先查看效果 1.拉取镜像 docker pull nextcloud 2.创建目录 mkdir -p /data/nextcloud/{config,data,apps} 3.创建实例 docker run -itd --name yznextcloud -v /data/nextcloud/config:/var/www/html/config -v /data/nextcloud/data:/var/www/html/data -v /data/nextc…

Minikube安装

文章目录 简介安装仪表盘 简介 Minikube是一个轻量级的工具&#xff0c;用于在本地机器上运行K8s集群。它允许开发人员在没有云环境的情况下进行K8s应用程序的开发和测试。 和k8s需要一个主机两个从机不同&#xff0c;Minikube用kubectl来控制节点&#xff0c;相当于在虚拟机…

如何制作网址链接活码?网址二维码生成器的使用方法

将网址转二维码图片来使用&#xff0c;是现在很常用的一种二维码类型&#xff0c;一般网址可以根据自己的用途来制作静态码或者活码两种形式。其中静态码只是单纯将网址链接转换成二维码&#xff0c;无法统计与修改&#xff0c;而生成网址活码可以在二维码图片不变情况下替换其…

基于RNN的模型

文本数据是一种典型的具有序列结构的数据&#xff0c;因为文本通常是由一系列的词语或字符组成的序列。每个词语或字符在文本中都有特定的位置和顺序&#xff0c;这种有序的结构对于理解和处理文本的含义至关重要。因此&#xff0c;多数情况下需要使用时间序列建模来完成相应的…

按键精灵调用奥迦插件实现图色字识别模拟键鼠操作源码

奥迦插件于2019年9月开始开发,在Windows 10操作系统上使用Visual Studio 2019编写,适用于所有较新的Windows平台,是一款集网络验证,深度学习,内核,视觉,文字,图色,后台,键鼠,窗口,内存,汇编,进程,文件,网络,系统,算法及其它功能于一身的综合插件 插件使用C语言和COM技术编写,是…

C#编程-属性和反射

属性和反射 属性是将元数据信息和行为添加到应用程序代码中的简单技术。属性是允许您将声明信息添加到程序的元素。此声明信息在运行时用途广泛,可使用应用程序开发工具在设计时使用。 介绍属性 对象是由其属性值描述的。例如,汽车可以使用它的构造、型号或颜色来描述。类似…

解决方案类常用网址

1.操作系统类&#xff08;原版操作系统下载网址&#xff09; https://next.itellyou.cn/ 之前的版本 https://msdn.itellyou.cn/ 2.ppt免费网站&#xff08;不用注册&#xff09; https://www.1ppt.com/

将 RGB 转换为十六进制、生成随机十六进制

RGB与十六进制 RGB&#xff08;Red, Green, Blue&#xff09;和十六进制是两种常用的颜色表示方式。 RGB是一种加法混色模式&#xff0c;它通过调节红、绿、蓝三个颜色通道的亮度来混合出各种颜色。对于每个颜色通道&#xff0c;取值范围是0到255&#xff0c;0表示该通道对应…